Chapter 12 · Section 12.3 · 70 min

FlashAttention derived

We now have both halves of an idea. §12.1 showed that standard attention is slow because it writes the T×TT\times T matrices S\mathbf{S} and P\mathbf{P} to HBM and reads them back, and that a kernel which read Q\mathbf{Q}, K\mathbf{K}, V\mathbf{V} once and wrote O\mathbf{O} once would be compute-bound. §12.2 showed that the softmax, the step that seemed to need a whole row at once, can be computed a block at a time with exact corrections.

Put them together and you get FlashAttention (Dao et al. 2022). This section derives it in full. We build the forward pass tile by tile, with the causal mask and the block skipping it allows. We work out what the forward pass must leave behind for the backward pass (one number per query) and derive the backward pass that recomputes everything else. Then we prove the IO complexity theorem, Θ(T2dk2/M)\Theta(T^2d_k^2/M) HBM accesses for SRAM of size MM, and its matching lower bound. Along the way we write the whole algorithm in NumPy with a counter on every byte that crosses between HBM and SRAM, and check it against naive attention to machine precision.

The picture, a grid of tiles

Fix one head. The queries Q∈RTq×dk\mathbf{Q} \in \R^{T_q\times d_k} are cut into row blocks Q1,…,Qnr\mathbf{Q}_1, \dots, \mathbf{Q}_{n_r} of BrB_r rows each, and the keys and values into column blocks K1,…,Knc\mathbf{K}_1, \dots, \mathbf{K}_{n_c} and V1,…,Vnc\mathbf{V}_1, \dots, \mathbf{V}_{n_c} of BcB_c rows each, so nr=⌈Tq/Br⌉n_r = \lceil T_q/B_r\rceil and nc=⌈Tk/Bc⌉n_c = \lceil T_k/B_c\rceil. (Here a subscript on a bold capital is a block of rows, not a single row.) The score matrix becomes a grid of nr×ncn_r\times n_c tiles,

Sij=QiKj⊤dk∈RBr×Bc.\mathbf{S}_{ij} = \frac{\mathbf{Q}_i\mathbf{K}_j^\top}{\sqrt{d_k}} \in \R^{B_r\times B_c} .

FlashAttention visits the tiles one at a time. For each tile it loads the blocks it needs into SRAM, computes Sij\mathbf{S}_{ij} there, uses it to update a running result, and throws it away. The tile never travels to HBM. Choose the block sizes so that a query block, a key block, a value block, the tile, and the running output all fit in one SM's SRAM, which on an A100 means blocks of 64 or 128 rows for head widths of 64 or 128.

Two orders of the loops are possible, and both are used. The original FlashAttention put the key and value blocks in the outer loop. FlashAttention-2 swapped them, putting the query blocks outside, which is simpler and parallelizes better (§12.4). We use the FlashAttention-2 order for the forward pass and come back to the original when we prove the theorem.

How many numbers cross between HBM and SRAM, tile by tile, in standard attention and in FlashAttention?
The square is the T by T score matrix of one head, cut into tiles. Step through either algorithm and watch the HBM counter. FlashAttention computes each tile on chip and throws it away, standard attention stores the whole matrix twice.
Loading interactive figure…

The forward pass

With the query block Qi\mathbf{Q}_i held in SRAM, the inner loop streams key and value blocks past it and runs the block form of the online softmax from §12.2. Keep, for the BrB_r rows of the block, a running maximum m∈RBr\mathbf{m}\in\R^{B_r}, a running sum ℓ∈RBr\boldsymbol{\ell}\in\R^{B_r}, and an unnormalized output O~∈RBr×dv\tilde{\mathbf{O}}\in\R^{B_r\times d_v}, initialized to −∞-\infty, 00, and 0\mathbf{0}.

  1. Load Qi\mathbf{Q}_i from HBM into SRAM.
  2. For each key block, j=1,…,ncj = 1, \dots, n_c, do the following.
    1. Load Kj\mathbf{K}_j and Vj\mathbf{V}_j into SRAM.
    2. Compute the tile Sij=QiKj⊤/dk\mathbf{S}_{ij} = \mathbf{Q}_i\mathbf{K}_j^\top/\sqrt{d_k} (a matrix product on tensor cores).
    3. Update the maximum, mnew=max⁡(m,rowmax⁡(Sij))\mathbf{m}^{\text{new}} = \max(\mathbf{m}, \operatorname{rowmax}(\mathbf{S}_{ij})).
    4. Exponentiate, P~ij=exp⁡(Sij−mnew)\tilde{\mathbf{P}}_{ij} = \exp(\mathbf{S}_{ij} - \mathbf{m}^{\text{new}}), with each row's maximum subtracted from that row.
    5. Rescale and accumulate, ℓ←em−mnew⊙ℓ+rowsum⁡(P~ij)\boldsymbol{\ell} \leftarrow e^{\mathbf{m}-\mathbf{m}^{\text{new}}}\odot\boldsymbol{\ell} + \operatorname{rowsum}(\tilde{\mathbf{P}}_{ij}) and O~←diag⁡(em−mnew) O~+P~ijVj\tilde{\mathbf{O}} \leftarrow \operatorname{diag}(e^{\mathbf{m}-\mathbf{m}^{\text{new}}})\,\tilde{\mathbf{O}} + \tilde{\mathbf{P}}_{ij}\mathbf{V}_j, then set m←mnew\mathbf{m} \leftarrow \mathbf{m}^{\text{new}}.
  3. Write Oi=diag⁡(ℓ)−1O~\mathbf{O}_i = \operatorname{diag}(\boldsymbol{\ell})^{-1}\tilde{\mathbf{O}} and lse⁡i=m+log⁡ℓ\operatorname{lse}_i = \mathbf{m} + \log\boldsymbol{\ell} to HBM.

In plain words, each query block makes one sweep over all the keys and values, keeping a few numbers per row that are corrected each time a larger score shows up, and writes its finished output exactly once.

Why is the result exactly attention? Step 2 is the merge of §12.2 applied, for each row, to the summary of keys 1,…,(j−1)Bc1, \dots, (j-1)B_c and the summary of key block jj. By the merge claim proved there, after the last block each row holds the summary of all its keys, ℓr=∑lesrl−mr\ell_r = \sum_l e^{s_{rl} - m_r} and o~r=∑lesrl−mrvl\tilde{\mathbf{o}}_r = \sum_l e^{s_{rl} - m_r}\mathbf{v}_l with mrm_r the row maximum. Dividing gives ∑lsoftmax⁡(sr)l vl\sum_l \operatorname{softmax}(\mathbf{s}_r)_l\,\mathbf{v}_l, which is row rr of PV\mathbf{P}\mathbf{V}. Nothing is approximated. FlashAttention is exact attention, computed in a different order, and its output differs from the naive one only by floating-point rounding.

Here it is in NumPy, with the HBM counter from §12.1 on every load and store. The outer loop is over query blocks, the inner loop over key blocks, and the comments match the steps above.

Python · cell 1
Python · cell 2

The outputs match naive attention to about 10−1510^{-15}, and so do the log-sum-exps. The traffic tells the story. Each of the nr=8n_r = 8 query blocks reads all of K\mathbf{K} and V\mathbf{V} once, so the count is

Tqdk⏟Q+nr⋅2Tkdk⏟K, V per query block+Tqdv⏟O+Tq⏟lse⁡,\underbrace{T_qd_k}_{\mathbf{Q}} + \underbrace{n_r\cdot 2T_kd_k}_{\mathbf{K},\,\mathbf{V}\text{ per query block}} + \underbrace{T_qd_v}_{\mathbf{O}} + \underbrace{T_q}_{\operatorname{lse}} ,

1.18 million values here against 4.46 million for standard attention. And nothing of size T×TT\times T was ever stored.

The causal mask, and the tiles you never visit

A causal mask (§4.4) lets query rr see keys l≤rl \le r. In the tile grid that splits the tiles into three kinds.

  • Below the diagonal, every key in the tile is at or before every query, so the tile is computed with no mask at all.
  • On the diagonal, the tile crosses r=lr = l, so it is computed and then masked with −∞-\infty above the diagonal.
  • Above the diagonal, every key is after every query, so every weight would be zero. The tile is skipped entirely, with no load, no product, and no exponential.

That last line is the block skipping that makes causal FlashAttention cost about half of non-causal. In the run above, 72 of the 128 tiles were visited. With nn square blocks per side the visited fraction is n(n+1)/2n(n+1)/2 out of n2n^2, approaching one half. A naive implementation, by contrast, computes the full T×TT\times T matrix and throws half of it away (§4.7).

There is one subtlety. The skip test compares the first key of the block with the last query of the block, j0 > rows[-1], because a tile can be skipped only if every one of its entries is masked. Comparing first with first, an off-by-one that is easy to make when Br≠BcB_r \ne B_c, silently drops keys that some rows should see.

Interview

Tiled FlashAttention forward with causal block skipping

Implement flash_attention_forward(Q, K, V, Br, Bc, causal=False) returning (O, lse, tiles) for single-head scaled dot-product attention, where Q is (T_q, d_k), K is (T_k, d_k), V is (T_k, d_v), O is (T_q, d_v), lse is the length-TqT_q vector of log⁡∑lesrl\log\sum_l e^{s_{rl}} over the allowed keys, and tiles is the number of (i,j)(i, j) tiles whose scores you computed. Use query blocks of Br rows in the outer loop and key blocks of Bc rows in the inner loop, with the online softmax, and never form a matrix larger than one tile. With causal=True (only used when Tq=TkT_q = T_k), query rr may attend to keys l≤rl \le r, and you must skip every tile that lies entirely above the diagonal. Blocks need not divide the lengths.

Hidden tests, hints, and AI help, from $1.99 a month.

What the forward pass leaves behind

Standard attention keeps P\mathbf{P} for the backward pass, TqTkT_qT_k numbers per head. FlashAttention keeps the output O\mathbf{O}, which the layer returns anyway, and one extra number per query row, the log-sum-exp

lse⁡r=log⁡∑lesrl=mr+log⁡ℓr.\operatorname{lse}_r = \log\sum_{l} e^{s_{rl}} = m_r + \log\ell_r .

That is enough, because every attention weight can be rebuilt from it and its score,

Prl=esrl∑l′esrl′=esrl−lse⁡r.P_{rl} = \frac{e^{s_{rl}}}{\sum_{l'} e^{s_{rl'}}} = e^{s_{rl} - \operatorname{lse}_r} .

In plain words, the log-sum-exp is the softmax's normalizer in log space. Subtract it from a score and exponentiate, and you have the weight, with no maximum and no sum needed. And since srl≤max⁡lsrl≤lse⁡rs_{rl} \le \max_l s_{rl} \le \operatorname{lse}_r, the exponent is never positive, so the reconstruction cannot overflow.

The original FlashAttention stored m\mathbf{m} and ℓ\boldsymbol{\ell} separately. FlashAttention-2 stores only their combination lse⁡\operatorname{lse} (Dao 2023), half the statistics and one fewer operation per weight in the backward pass. Either way the extra memory is O(T)O(T) per head instead of O(T2)O(T^2), which is Theorem 1 of Dao et al. (2022), O(T2dk)O(T^2d_k) FLOPs and O(T)O(T) memory beyond the inputs and output.

For one layer of Llama 3 8B at 8,192 tokens, the difference is between storing P\mathbf{P}, 32×8192232\times8192^2 numbers or 4 GiB in bf16, and storing the log-sum-exps, 32×819232\times8192 numbers or 1 MiB in float32. That factor of T/2≈4,000T/2 \approx 4{,}000 is why FlashAttention made long-context training possible on its own, even setting its speed aside.

The backward pass, derived

Recall the backward pass of attention from §4.3. With c=1/dkc = 1/\sqrt{d_k}, S=c QK⊤\mathbf{S} = c\,\mathbf{Q}\mathbf{K}^\top, P=softmax⁡(S)\mathbf{P} = \operatorname{softmax}(\mathbf{S}), O=PV\mathbf{O} = \mathbf{P}\mathbf{V}, and upstream gradient dO\mathrm{d}\mathbf{O},

dV=P⊤dO,dP=dO V⊤,dS=P⊙(dP−D1⊤),dQ=c dS K,dK=c dS⊤Q,\mathrm{d}\mathbf{V} = \mathbf{P}^\top\mathrm{d}\mathbf{O}, \quad \mathrm{d}\mathbf{P} = \mathrm{d}\mathbf{O}\,\mathbf{V}^\top, \quad \mathrm{d}\mathbf{S} = \mathbf{P}\odot\big(\mathrm{d}\mathbf{P} - \mathbf{D}\mathbf{1}^\top\big), \quad \mathrm{d}\mathbf{Q} = c\,\mathrm{d}\mathbf{S}\,\mathbf{K}, \quad \mathrm{d}\mathbf{K} = c\,\mathrm{d}\mathbf{S}^\top\mathbf{Q},

with Dr=dOr⋅OrD_r = \mathrm{d}\mathbf{O}_r\cdot\mathbf{O}_r, the dot product of row rr of dO\mathrm{d}\mathbf{O} with row rr of O\mathbf{O}. Each of these is written in terms of T×TT\times T matrices. The job is to rewrite every one in terms of tiles, so that the backward pass, like the forward pass, never holds more than one tile at a time. Three observations do it.

Observation 1, the weights can be recomputed tile by tile. By the reconstruction above, the tile of weights is

Pij=exp⁡ ⁣(c QiKj⊤−lse⁡i),\mathbf{P}_{ij} = \exp\!\big(c\,\mathbf{Q}_i\mathbf{K}_j^\top - \operatorname{lse}_i\big),

with block ii of the stored log-sum-exps subtracted from each row. It needs only Qi\mathbf{Q}_i, Kj\mathbf{K}_j, and lse⁡i\operatorname{lse}_i, and it equals the forward pass's weights exactly (up to rounding), not approximately.

Observation 2, the row terms need no row of P\mathbf{P}. The softmax backward needs Dr=∑lPrl dPrlD_r = \sum_l P_{rl}\,\mathrm{d}P_{rl}, a sum over a whole row. §4.3 showed that this equals dOr⋅Or\mathrm{d}\mathbf{O}_r\cdot\mathbf{O}_r, because ∑lPrl dPrl=∑lPrl (dOr⋅vl)=dOr⋅∑lPrlvl=dOr⋅Or\sum_l P_{rl}\,\mathrm{d}P_{rl} = \sum_l P_{rl}\,(\mathrm{d}\mathbf{O}_r\cdot\mathbf{v}_l) = \mathrm{d}\mathbf{O}_r\cdot\sum_l P_{rl}\mathbf{v}_l = \mathrm{d}\mathbf{O}_r\cdot\mathbf{O}_r. So D\mathbf{D} is computed before the main loop, in one pass over dO\mathrm{d}\mathbf{O} and O\mathbf{O}, and each tile then needs only its block Di\mathbf{D}_i.

Observation 3, every product splits into a sum over tiles. With the tile of score gradients

dSij=Pij⊙(dOiVj⊤−Di1⊤),\mathrm{d}\mathbf{S}_{ij} = \mathbf{P}_{ij}\odot\big(\mathrm{d}\mathbf{O}_i\mathbf{V}_j^\top - \mathbf{D}_i\mathbf{1}^\top\big),

block matrix multiplication gives

dVj=∑iPij⊤ dOi,dKj=c∑idSij⊤ Qi,dQi=c∑jdSij Kj.\mathrm{d}\mathbf{V}_j = \sum_{i}\mathbf{P}_{ij}^\top\,\mathrm{d}\mathbf{O}_i, \qquad \mathrm{d}\mathbf{K}_j = c\sum_{i}\mathrm{d}\mathbf{S}_{ij}^\top\,\mathbf{Q}_i, \qquad \mathrm{d}\mathbf{Q}_i = c\sum_{j}\mathrm{d}\mathbf{S}_{ij}\,\mathbf{K}_j .

To check the tile formula for dS\mathrm{d}\mathbf{S}, look at one entry. dSrl=Prl(dPrl−Dr)\mathrm{d}S_{rl} = P_{rl}(\mathrm{d}P_{rl} - D_r) involves only entry (r,l)(r, l) of P\mathbf{P} and dP\mathrm{d}\mathbf{P} and the row term DrD_r, and dPrl=dOr⋅vl\mathrm{d}P_{rl} = \mathrm{d}\mathbf{O}_r\cdot\mathbf{v}_l, so restricting rr to block ii and ll to block jj gives exactly the tile formula. The three sums are the definition of a matrix product, written block by block. In plain words, the backward pass is the forward pass's tile loop run again, recomputing each tile of weights from the stored log-sum-exps, and adding each tile's contribution into the three gradients.

Which loop goes outside? dKj\mathrm{d}\mathbf{K}_j and dVj\mathrm{d}\mathbf{V}_j are sums over query blocks, and dQi\mathrm{d}\mathbf{Q}_i a sum over key blocks. FlashAttention-2 puts the key blocks outside, so dKj\mathrm{d}\mathbf{K}_j and dVj\mathrm{d}\mathbf{V}_j accumulate in SRAM and are written once, and lets the contributions to dQi\mathrm{d}\mathbf{Q}_i go to HBM as additions. On a GPU, different thread blocks handle different key blocks at the same time, so their additions to the same dQi\mathrm{d}\mathbf{Q}_i use atomic adds (Dao 2023). In our sequential NumPy version that is a read, an add, and a write. Atomic additions land in whatever order the thread blocks finish, and floating-point addition is not associative, so dQ\mathrm{d}\mathbf{Q} can differ in its last bits from one run to the next. The flash-attn package offers a deterministic backward pass (deterministic=True) that fixes the order at some cost in speed, which is worth turning on when you need bitwise reproducible training or are hunting a numerical bug.

Python · cell 3

The gradients agree with the naive formulas to about 10−1510^{-15}, with and without the causal mask, and the backward pass's traffic is a few dozen times TdkTd_k, with no T2T^2 term. Causal block skipping works in the backward pass exactly as in the forward pass.

The price of recomputation

The backward pass now does five tile products instead of four. Besides dOiVj⊤\mathrm{d}\mathbf{O}_i\mathbf{V}_j^\top, Pij⊤dOi\mathbf{P}_{ij}^\top\mathrm{d}\mathbf{O}_i, dSijKj\mathrm{d}\mathbf{S}_{ij}\mathbf{K}_j, and dSij⊤Qi\mathrm{d}\mathbf{S}_{ij}^\top\mathbf{Q}_i, it recomputes QiKj⊤\mathbf{Q}_i\mathbf{K}_j^\top. Counting 2T2dk2T^2d_k FLOPs per product, a training step costs 4T2dk4T^2d_k forward plus 10T2dk10T^2d_k backward instead of 4+84 + 8, about 17% more arithmetic. Dao et al. measured 13% more FLOPs (75.2 against 66.6 GFLOPs) for GPT-2 medium, and a 5.7 times shorter runtime, because the arithmetic was never what took the time.

This is selective recomputation, the same trade as activation checkpointing (§10.1), applied to the one activation that is quadratic in length. Here it is a pure win, because recomputing a tile on chip is cheaper than reading it from HBM.

Interview

FlashAttention backward from the stored log-sum-exp

Implement flash_attention_backward(Q, K, V, O, dO, lse, Br, Bc, causal=False) returning (dQ, dK, dV), the gradients of single-head scaled dot-product attention, given the inputs, the forward output O, the upstream gradient dO, and the forward pass's per-row log-sum-exp lse. You are not given P\mathbf{P}. Recompute each tile of weights from lse, compute D=rowsum⁡(dO⊙O)\mathbf{D} = \operatorname{rowsum}(\mathrm{d}\mathbf{O}\odot\mathbf{O}) once, loop over key blocks outside and query blocks inside, skip fully masked causal tiles, and never form a matrix larger than one tile.

Hidden tests, hints, and AI help, from $1.99 a month.

The IO complexity theorem

Now we can say precisely how much FlashAttention saves. Use the external memory model of §12.1, with SRAM holding MM numbers, and write d=dk=dvd = d_k = d_v for the head width and T=Tq=TkT = T_q = T_k, to match the paper's statement.

Theorem (Dao et al. 2022, Theorem 2). For d≤M≤Tdd \le M \le Td, standard attention requires Θ(Td+T2)\Theta(Td + T^2) HBM accesses, and FlashAttention requires Θ(T2d2/M)\Theta(T^2d^2/M).

The proof is a count. We give it for the loop order of the paper's Algorithm 1, which puts the key and value blocks outside, and then note why the other order gives the same answer.

Block sizes. Algorithm 1 sets Bc=⌈M/4d⌉B_c = \lceil M/4d\rceil and Br=min⁡(⌈M/4d⌉,d)B_r = \min(\lceil M/4d\rceil, d). Then everything that must be on chip at once fits in a constant multiple of MM. The key and value blocks take 2Bcd≈M/22B_cd \approx M/2, the query block and its output take 2Brd≤M/22B_rd \le M/2, and the tile takes BrBc≤d⋅M/4d=M/4B_rB_c \le d\cdot M/4d = M/4. (The cap Br≤dB_r \le d exists only to keep the tile small.)

Counting. The outer loop runs over the nc=⌈T/Bc⌉n_c = \lceil T/B_c\rceil key blocks. Each key and value block is loaded once, 2Td2Td in total. Inside, for each key block, the algorithm sweeps every query block, loading Qi\mathbf{Q}_i, the partial output Oi\mathbf{O}_i, and its statistics ℓi\boldsymbol{\ell}_i and mi\mathbf{m}_i, and writing back Oi\mathbf{O}_i, ℓi\boldsymbol{\ell}_i, mi\mathbf{m}_i. One sweep therefore moves 3Td+4T3Td + 4T values, and there are ncn_c sweeps, so

HBM accesses=2Td+nc (3Td+4T),nc=⌈T⌈M/4d⌉⌉≈4TdM.\text{HBM accesses} = 2Td + n_c\,(3Td + 4T), \qquad n_c = \Big\lceil\frac{T}{\lceil M/4d\rceil}\Big\rceil \approx \frac{4Td}{M} .

Substituting, the second term is about 4Td/M⋅3Td=12 T2d2/M4Td/M\cdot 3Td = 12\,T^2d^2/M. The first term, 2Td2Td, is at most of the same order, because M≤TdM \le Td means T2d2/M≥TdT^2d^2/M \ge Td. So the total is Θ(T2d2/M)\Theta(T^2d^2/M). ■\blacksquare

In plain words, the number of HBM accesses is the number of passes over the inner operand times the size of that operand. The number of passes is the length divided by the outer block size, and the outer block can hold about M/dM/d rows. So the traffic is T/(M/d)T/(M/d) passes of TdTd values each, T2d2/MT^2d^2/M.

The FlashAttention-2 order swaps the roles. The query blocks are outside, with BrB_r about M/4dM/4d rows, and each makes one pass over K\mathbf{K} and V\mathbf{V}, so the traffic is Td+2Td⋅⌈T/Br⌉+Td+TTd + 2Td\cdot\lceil T/B_r\rceil + Td + T, again Θ(T2d2/M)\Theta(T^2d^2/M), with the bonus that O\mathbf{O} is written once rather than read and written in every sweep. That is the formula our NumPy code measured. The backward pass obeys the same bound (Theorem 5 of the paper), by the same count over its tile loop.

How big is the saving? The ratio of standard to FlashAttention traffic is about T2/(T2d2/M)=M/d2T^2\big/(T^2d^2/M) = M/d^2, up to the constants. The plot shows it for T=4,096T = 4{,}096 and d=64d = 64. With an SM's worth of SRAM, around M=105M = 10^5 numbers, FlashAttention moves an order of magnitude less. But notice the left of the plot. When MM is near d2d^2, the blocks are so thin that FlashAttention rereads K\mathbf{K} and V\mathbf{V} more times than standard attention reads its matrices, and it loses. FlashAttention needs SRAM much larger than d2d^2, so wide heads cost it more, and each GPU generation's larger shared memory helps.

Python · cell 4
Core

Count the HBM accesses of both loop orders

Implement two functions that return exact HBM access counts (numbers moved, as integers) for self-attention with TT tokens, head width dd (dk=dv=dd_k = d_v = d), and SRAM of MM numbers, ignoring the initialization of outputs.

  • fa1_accesses(T, d, M), the paper's Algorithm 1, with Bc=⌈M/4d⌉B_c = \lceil M/4d\rceil and key and value blocks outside. Each key and value block is read once. For each key block, every query block's Qi\mathbf{Q}_i, Oi\mathbf{O}_i, ℓi\boldsymbol{\ell}_i, mi\mathbf{m}_i is read and Oi\mathbf{O}_i, ℓi\boldsymbol{\ell}_i, mi\mathbf{m}_i written.
  • fa2_accesses(T, d, M), the FlashAttention-2 order, with Br=⌈M/4d⌉B_r = \lceil M/4d\rceil and query blocks outside. Q\mathbf{Q} is read once, K\mathbf{K} and V\mathbf{V} once per query block, and O\mathbf{O} and lse⁡\operatorname{lse} written once.
Hidden tests, hints, and AI help, from $1.99 a month.

No exact algorithm does better

Is T2d2/MT^2d^2/M the best possible? Proposition 3 of the paper says there is no algorithm that computes exact attention with o(T2d2/M)o(T^2d^2/M) HBM accesses for every MM in [d,Td][d, Td]. The proof takes the largest allowed SRAM, M=TdM = Td. There the bound is T2d2/(Td)=TdT^2d^2/(Td) = Td, and any algorithm must at least read its inputs, Ω(Td)\Omega(Td) numbers, so nothing can be asymptotically smaller at that end of the range. This is a weak form of optimality, since it pins down only one value of MM. A lower bound that holds for each MM separately is left by the paper as future work. In practice the question is moot, because at realistic sizes FlashAttention is already compute-bound.

The theorem also explains why tiling attention looks like tiling a matrix multiply. Both sweep one operand once per block of the other, so both move about n3/Mn^3/\sqrt{M} or T2d2/MT^2d^2/M values, a total that falls as fast memory grows (§11.5).

What it bought, measured

The FlashAttention paper reports these results on A100 GPUs, all for exact attention.

measurementresult
GPT-2 medium attention, forward and backwardHBM traffic 40.3 GB to 4.4 GB, runtime 41.7 ms to 7.3 ms
attention kernel alone, GPT-2up to 7.6 times faster than the PyTorch implementation
BERT-large training (sequence 512)15% faster end to end than the MLPerf 1.1 training speed record
GPT-2 training (sequence 1K)up to 3 times faster than Hugging Face, and 1.7 to 1.8 times faster than Megatron-LM
Long Range Arena (sequences 1K to 4K)2.4 times faster
memoryup to 20 times less than exact attention baselines, linear in sequence length
Path-X (sequence 16K) and Path-256 (64K)61.4% and 63.1% accuracy, the first Transformers to do better than chance

The last row is the one people forget. Memory linear in TT meant models could be trained on much longer sequences, and longer context improved quality. GPT-2 small trained with 4K context reached a perplexity of 17.5 on OpenWebText against 18.2 at 1K, and still trained 30% faster than Megatron-LM's 1K model.

Dropout, block-sparsity, and other real-kernel details

Real kernels handle a few more things, each of which fits the tile structure.

  • Dropout on the weights. Storing a T×TT\times T dropout mask would defeat the purpose. The forward pass instead saves the state of the pseudo-random number generator, and the backward pass regenerates exactly the same mask tile by tile (Appendix B of the paper).
  • Block-sparse attention. If a mask is known at block granularity, tiles marked empty are skipped, exactly as causal tiles are. Proposition 4 of the paper gives Θ(Td+T2d2s/M)\Theta(Td + T^2d^2s/M) HBM accesses, with ss the fraction of nonzero blocks. This is the hardware view of the sparse patterns in §12.6.
  • Other score modifications. Biases such as ALiBi (§5.5), logit soft-capping, sliding windows, and document masks are all functions of (r,l)(r, l) and the score, computed inside the tile. PyTorch's FlexAttention lets you write such a function in Python and compiles it into a FlashAttention-style kernel (§12.4).
Research

Block-sparse FlashAttention

Implement block_sparse_attention(Q, K, V, B, block_mask) for self-attention with TT divisible by the block size B, where block_mask is a boolean array of shape (T // B, T // B) and entry (i,j)(i, j) says whether query block ii may attend to key block jj (all keys in the block, no finer masking). Return (O, lse, tiles). Visit only the allowed tiles. A query block whose row of block_mask is all False attends to nothing; give its rows O = 0 and lse = -inf without any NaN.

Hidden tests, hints, and AI help, from $1.99 a month.

The kernel, in Triton

On a GPU the NumPy loops become a kernel. Each program (a thread block) owns one query block of one head, which is exactly our outer loop, and the inner loop runs inside it. Triton, the subject of §11.6, lets us write it at the level of tiles, with tl.dot for the tile products on tensor cores. The forward kernel below is complete for contiguous inputs of shape (batch * heads, T, D). It cannot run in your browser, and production kernels add the details that make it fast (software pipelining, exp2 in place of exp, autotuned block sizes, and a separate path for the diagonal tiles), but every line maps to a step of the algorithm.

Notice the launch grid, one program per query block and per head. Parallelizing over query blocks, not only over batch and heads, is one of FlashAttention-2's central changes, and the reason it keeps the GPU busy when the batch is small (§12.4).

In PyTorch

To see the whole algorithm as a differentiable operation, wrap the tiled forward and backward passes in a torch.autograd.Function. The forward saves only Q\mathbf{Q}, K\mathbf{K}, V\mathbf{V}, O\mathbf{O}, and the log-sum-exps, and the backward recomputes the tiles. gradcheck compares it with numerical derivatives, and the comparison with scaled_dot_product_attention checks both passes against PyTorch's own.

🔥 In PyTorchfor a machine with PyTorch

Read more

Watch next

🎬 Flash Attention derived and coded from first principles with Triton (Python) · Umar JamilA long, patient derivation of the forward and backward passes, then a full Triton implementation.
🎬 Lecture 12: Flash Attention · GPU MODEA GPU MODE lecture on the FlashAttention algorithm and what it takes to implement it as a kernel.
🎬 MedAI #54: FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness | Tri Dao · Stanford MedAITri Dao presents the FlashAttention paper, its IO-aware design, and its results.