Learn: The Memory Wall & FlashAttention
What you're learning
Why the single most important kernel in modern LLMs — attention — is limited by moving bytes, not doing math, and how FlashAttention fixes it without changing the math at all. By the end you should be able to derive the online softmax recurrence from scratch, compute the HBM traffic of naive vs. tiled attention, and explain why every FlashAttention generation is a story of algorithm–hardware co-design rather than “more FLOPs.”
This lesson assumes Tier 1 and lessons 04-first-cuda-kernels (writing kernels, launch overhead) and 05-optimizing-gemm (SMEM tiling, register blocking). It is the payoff of 02-gpu-memory-hierarchy (the memory wall) and 03-performance-modeling-roofline (arithmetic intensity, the ridge point). If “ridge point” or “memory-bound” don’t feel automatic, reread those first.
1. Learning map
graph TD A["Memory wall + roofline<br/>(lessons 02–03)"] --> B["Kernel fusion<br/>fewer HBM round-trips"] B --> B1["Vertical fusion<br/>(op chains)"] B --> B2["Horizontal fusion<br/>(sibling ops)"] B --> B3["torch.compile / inductor<br/>(autofuser)"] A --> C["Attention is memory-bound<br/>materializing NxN S,P in HBM"] C --> D["FlashAttention<br/>(IO-aware, arXiv:2205.14135)"] D --> D1["Tile Q,K,V into SRAM<br/>never write NxN"] D --> D2["Online softmax<br/>running m, l + rescale"] D --> D3["Backward: recompute S<br/>trade FLOPs for memory"] D --> E["FlashAttention-2<br/>work partitioning (2307.08691)"] E --> F["FlashAttention-3<br/>Hopper async, FP8 (2407.08608)"] F --> G["FlashAttention-4<br/>Blackwell co-design (2603.05451)"] D2 --> H["Big idea:<br/>algorithm–hardware co-design"] D1 --> H G --> H style C fill:#a44,color:#fff style D fill:#44a,color:#fff style D2 fill:#4a4,color:#fff style H fill:#a83,color:#fff
2. Why this matters
Attention is where LLMs spend a large and growing fraction of their time as context lengths climb — and, done naively, it is one of the most bandwidth-wasteful operations in the whole model. The naive implementation materializes an score matrix in HBM, so its memory traffic (and memory footprint) grows quadratically in sequence length. At a single head’s score matrix is already 128 MiB in BF16; at it is 32 GiB — larger than an H100’s entire 80 GB of HBM1 if you keep a few of them around. Long context was, quite literally, blocked by this.
FlashAttention (Dao et al., 2022) removed that wall without approximation. It computes exactly the same attention, but by respecting the memory hierarchy — tiling the computation so the matrix never touches HBM — it turned a memory-bound kernel into a near-compute-bound one, cut wall-clock 2–4×, and made million-token context tractable.2 It is the canonical example of the thesis running through this whole curriculum: on modern accelerators you win by moving fewer bytes, not by doing fewer FLOPs.
The one-sentence version
FlashAttention does more arithmetic than naive attention (it recomputes things), yet runs far faster — because it pays FLOPs, which are cheap, to avoid HBM traffic, which is expensive. That trade is the entire game.
3. Kernel fusion: why many small kernels waste HBM
Before attention, understand the general disease it’s a cure for. Recall from lesson 03 the arithmetic intensity of an elementwise op: read the tensor, do a fixed few FLOPs per element, write it back. Intensity FLOP/byte — far left of the H100 ridge point (~296 FLOP/byte for BF16).1 Every such op runs at HBM bandwidth, and its runtime is essentially “time to read the tensor + time to write it.”
Now stack a typical activation chain: x → bias-add → GELU → dropout → residual-add. If each is its own kernel, each one:
- reads the full activation tensor from HBM,
- does a trivial amount of math,
- writes the full tensor back to HBM.
For a tensor of BF16 elements, four separate kernels move bytes through HBM, to perform maybe a dozen FLOPs per element. The tensor is round-tripped four times. Each intermediate is born in HBM and immediately re-read by the next kernel — pure waste.
Kernel fusion folds the chain into one kernel: read once into registers/SMEM, apply bias → GELU → dropout → add in place while it’s on-chip, write once. Traffic drops from to bytes — a ~4× speedup on a purely bandwidth-bound op, for free. Nothing about the math changed; you just stopped spilling intermediates to HBM.
Fusion raises arithmetic intensity
Fusion doesn’t add FLOPs and doesn’t remove them — it removes bytes. Same numerator, smaller denominator in , so the fused kernel sits further right on the roofline. You are literally buying arithmetic intensity by keeping intermediates on-chip. There is also a second, smaller win: one kernel launch instead of four, so less launch/overhead latency (lesson 04) — this matters most for tiny tensors where launch cost rivals compute.
Vertical vs. horizontal fusion
- Vertical (producer→consumer) fusion fuses ops along a dependency chain: the output of one is the input of the next (the bias→GELU→dropout example). This is the high-value case because it eliminates the intermediate tensors entirely. FlashAttention is an extreme vertical fusion — it fuses the entire
matmul → softmax → matmulchain of attention into one kernel. - Horizontal fusion fuses independent ops that share an input or can share a launch — e.g. computing the Q, K, and V projections as one batched GEMM instead of three, or applying the same elementwise op to several tensors in one grid. The win here is amortized launch overhead and better occupancy, not eliminated intermediates.
graph LR subgraph "Vertical fusion (chain)" direction TB v1["read x"] --> v2["bias"] --> v3["GELU"] --> v4["dropout"] --> v5["write y"] end subgraph "Horizontal fusion (siblings)" direction TB h0["read x once"] --> hq["→ Q proj"] h0 --> hk["→ K proj"] h0 --> hv["→ V proj"] end
torch.compile / Inductor as an autofuser
You rarely hand-write fusions for elementwise chains anymore. torch.compile traces your model into an FX graph and its backend TorchInductor performs automatic fusion: it groups compatible elementwise/reduction ops into fused Triton kernels (pointwise fusion, and reductions like softmax/layernorm folded with their neighbors), so the intermediates stay in registers/SMEM. This is why torch.compile gives large speedups on the “glue” between GEMMs with zero code changes. It generally will not discover a fundamentally new algorithm like FlashAttention on its own — that requires the algorithmic restructuring below — which is why attention is dispatched to a hand-written fused kernel (or F.scaled_dot_product_attention, which calls a FlashAttention backend). We go deep on Triton and Inductor in 07-triton-and-modern-kernels.
Quiz: Why does splitting a bias→GELU→dropout chain into three separate kernels waste HBM bandwidth, and roughly how much do you save by fusing? Would fusing help a large matmul the same way?
Answer
Why it wastes bandwidth. Each op is elementwise with arithmetic intensity , so each is memory-bound: its runtime is dominated by reading the tensor from HBM and writing it back. Three separate kernels therefore round-trip the entire activation tensor three times — the intermediate after bias is written to HBM only to be immediately re-read by GELU, etc. For BF16 elements: unfused bytes; fused bytes (read once, write once). That’s a ~3× traffic reduction → ~3× speedup, because the op is purely bandwidth-bound so time bytes.
Would it help a matmul? No, not in the same way. A large GEMM is compute-bound ( ridge point) — its runtime is set by tensor-core throughput, not HBM traffic, so removing a round-trip of its (small relative to FLOPs) I/O barely moves the needle. Fusion is a lever for the memory-bound ops, which is precisely why we fuse the elementwise/softmax “glue” around GEMMs rather than the GEMMs themselves. (You can fuse an elementwise epilogue into a GEMM — e.g. bias+GELU in the GEMM’s epilogue — and that helps, but the GEMM itself stays compute-bound.)
4. The attention memory problem
Standard scaled dot-product attention, for one head with :
The naive implementation (and what you get from three separate library calls) executes this as a sequence of kernels, each round-tripping through HBM:
- GEMM : read (), write ().
- Softmax (a memory-bound elementwise+reduction kernel): read (), write ().
- GEMM : read (), read (), write ().
Count the HBM traffic in elements. The matrix is written once, read once (softmax), written once (probs), and read once again (second GEMM): roughly element-accesses of -sized traffic, versus only for the actual inputs and output. For the traffic is — quadratic in sequence length — while the useful FLOPs are . The arithmetic intensity is therefore only (tens to ~128), far below the ridge point.
Attention is memory-bound at typical shapes — the softmax is the smoking gun
The two matmuls ( and ) are individually fine, but the softmax kernel in the middle is pure elementwise/reduction: it reads the entire score matrix and writes it back, doing a handful of FLOPs per element (). It sits on the memory roof. And it forces the matrix to be materialized in HBM so it can be read back — the matmuls can’t hand it off on-chip because a separate kernel needs it. So the whole attention block, as launched, is dominated by HBM traffic and runs far below tensor-core peak. It is bottlenecked by moving the score matrix, not by the matmuls.
There’s a second, equally fatal problem: memory capacity. Materializing (and ) costs HBM storage, which is what historically capped context length. This is the wall FlashAttention tears down.
Figure 1 — Naive attention’s HBM traffic is dominated by four round-trips of the score/probability matrix (quadratic in ). FlashAttention never materializes those matrices in HBM, leaving only the linear-in- traffic of reading and writing . This collapse in traffic is the whole win.
Quiz: Standard attention does FLOPs — genuinely a lot of compute. Why is it nonetheless memory-bound in practice, and which sub-step is the culprit?
Answer
Because the FLOPs and the bytes don’t live in the same kernel. The FLOPs are in the two matmuls, but the algorithm as launched forces the score matrix through HBM four times (write , read , write , read ), which is traffic with a tiny amount of math attached — the arithmetic intensity of the whole block collapses toward , far below the ~296 FLOP/byte ridge point. The softmax is the culprit: it’s an elementwise/reduction kernel () that sits on the memory roof and forces the matrix to be materialized in HBM so it can be read back. So even though attention has plenty of FLOPs, wall-clock is set by shuttling the score matrix, not by the tensor cores. (This is also why lower head dim makes it worse: intensity shrinks.) FlashAttention fixes exactly this by never letting reach HBM.
5. FlashAttention: the IO-aware algorithm
FlashAttention (Dao, Fu, Ermon, Rudra, Ré — arXiv:2205.14135) is “IO-aware”: it is designed around minimizing HBM reads/writes, the same way tiled GEMM (lesson 05) is designed around reusing SMEM tiles.2 Two ideas do all the work:
- Tile into blocks small enough to fit in SRAM/SMEM. Compute attention block-by-block. The block-level scores live only in SRAM — they are produced, consumed, and discarded on-chip, and never written to HBM.
- Online (streaming) softmax.3 Softmax needs a global normalizer over the whole row (), which seems to require seeing all of at once. FlashAttention computes it incrementally with a running max and running denominator, applying a small correction each time a new block shifts the max. This is the mathematical heart of the method.
5.1 Deriving the online softmax
Start from safe softmax for one query row with scores (here ). To avoid overflow of we subtract the row max :
The obstacle: both and the denominator depend on the entire row, but we want to stream over blocks of the key/value sequence, seeing only one block at a time. Can we maintain the running state and correct it when a later block reveals a bigger max?
Maintain three running quantities after processing blocks :
- — running max of all scores seen so far,
- — running denominator, normalized to the current max ,
- — running unnormalized output accumulator, also referred to .
Initialize , , . For block , let be the block-local max. The recurrence is:
After the last block, normalize once: .
Why the correction factor ? The old accumulators were computed with exponentials referred to the old max . When the max jumps to , every previously accumulated term must be re-referenced to the new max:
Multiplying the whole running sum by does exactly this to every stored term at once — that’s why a single scalar multiply corrects the entire accumulator. If the new block doesn’t raise the max (), then and no rescale happens. Note always (we subtract a value), so it never overflows.
The invariant that makes it exact
The recurrence maintains, at every step , the exact partial softmax over the prefix seen so far:
By induction (base case trivial; inductive step is the algebra above), after the final block equals the full softmax attention output bit-for-bit up to floating-point associativity — FlashAttention is exact, not an approximation. This is the crucial selling point: you can drop it into any model with no accuracy change.
Because and are just running scalars/vectors, the block scores can be thrown away immediately after they update the state — they never need to be stored. That is why the matrix never reaches HBM.
Figure 2 — For each query block , FlashAttention streams over the K/V blocks. Each score tile is formed, consumed to update the running state, and discarded — all in SRAM. Only (in) and (out) cross HBM; the -sized running state stays in registers. The whole matmul→softmax→matmul chain is fused into one kernel.
5.2 The HBM traffic reduction:
Naive attention’s HBM traffic is — dominated by the score/probability round-trips. FlashAttention never materializes those matrices, so the only mandatory HBM traffic is reading and writing : that’s — linear in . The score matrix’s contribution to HBM traffic goes to zero.
There is a re-read subtlety: because you tile, whichever operand is in the outer loop gets re-read once per inner-loop pass. The paper’s exact bound is that FlashAttention performs HBM accesses (with = SRAM size in elements), versus for standard attention — a reduction by a factor . Since (tens of thousands of elements) is much larger than (e.g. ), this is a large multiplicative win; empirically the original paper measured ~9× fewer HBM accesses for GPT-2 and 2–4× wall-clock speedup.2 The headline mental model, though, is the clean one: you replaced quadratic score-matrix traffic with linear activation traffic.
5.3 The backward pass: recomputation trades FLOPs for memory
The backward pass needs the attention probabilities to compute gradients (e.g. , and the softmax Jacobian for ). Naive backprop would store the matrix from the forward pass to reuse it — reintroducing exactly the HBM footprint we just eliminated.
FlashAttention’s fix is recomputation (a form of activation checkpointing specialized to attention): during backward, it re-derives the block scores and probabilities on the fly in SRAM from , using the stored per-row softmax statistics. Cleverly, it only needs to save the logsumexp per row (an vector, not ) from the forward pass — with in hand, a block’s probabilities are , recomputable exactly without re-running the softmax reduction.2 This trades extra FLOPs (recomputing ) for a massive memory saving (no matrix stored), and because attention is memory-bound, the recomputed backward is still faster than the store-and-reload version. It is the same FLOPs-for-bytes bargain as the forward pass.
Recomputation is not free FLOPs — it's cheaper than the memory it saves
Recomputing in the backward pass genuinely does more arithmetic than a store-based backward. That sounds backwards until you internalize the memory wall: the recomputed FLOPs run on idle tensor cores that were waiting on HBM anyway, whereas storing/reloading the matrix would saturate the scarce bandwidth. On a memory-bound op, spending FLOPs to save bytes is a win. This is the same logic as gradient checkpointing (lesson 03’s HFU/MFU gap) — but here it also speeds you up, not just saves memory.
Quiz: In the online-softmax recurrence, justify the correction factor from first principles. What goes wrong if you omit it, and why is never a source of overflow?
Answer
Justification. The running accumulators after block store exponentials referred to the then-current max : and likewise . When block reveals a larger max, the running max updates to , and every stored term must be re-referenced to . For a single term: . Multiplying the whole accumulator by the scalar applies this correction to all stored terms simultaneously, restoring the invariant . Then the new block’s terms (already referred to ) are added.
If you omit it: the old terms stay scaled by while new terms use — you’d be summing exponentials with inconsistent reference points, so the denominator and numerator no longer correspond and the result is wrong (unless the max never changed). The final normalization would not equal the true softmax.
No overflow: , so the exponent , hence . It’s a shrink factor, never a blow-up. Likewise every because . Numerical stability is preserved throughout — which is the whole point of the safe-softmax max subtraction, now maintained incrementally.
6. FlashAttention-2: better work partitioning
FlashAttention-1 already cut HBM traffic, but the forward pass still only reached ~30–50% of A100 peak FLOP/s (the backward worse, ~25–35%) — not because of HBM traffic anymore, but because of suboptimal work partitioning across thread blocks and warps, leaving GPU resources idle. FlashAttention-2 (Dao — arXiv:2307.08691, ICLR 2024) is a scheduling rewrite that keeps the same math and IO story but feeds the hardware better.4 Three changes:
- Fewer non-matmul FLOPs. Non-matmul ops (the softmax rescales, exponentials) run on the general-purpose units, whose throughput can be ~16× lower than the tensor cores. FA-2 restructures the algorithm to minimize them — notably, it defers the per-block rescaling of the output accumulator, keeping unnormalized through the loop and dividing by once at the very end instead of rescaling on every block. Same result, far fewer non-matmul operations, so more of the time is spent in tensor-core matmuls.
- Parallelize over sequence length. FA-1 parallelized over batch × heads; when sequences are long (and batch is therefore small), that’s too few thread blocks to fill the GPU. FA-2 also parallelizes across the sequence-length (query-block) dimension, raising occupancy for the long-context regime that matters most.
- Better warp scheduling within a block. FA-1 split the K/V dimension across warps (“split-K”), forcing warps to communicate partial results through SMEM. FA-2 instead splits the Q dimension across warps so each warp owns a slice of the output independently — eliminating inter-warp SMEM communication and syncs.
Result: ~2× over FlashAttention-1, reaching 50–73% of A100 peak FLOP/s and ~72% MFU in end-to-end GPT training — i.e. attention finally approaches GEMM-level efficiency.4 Note that none of this changed the IO complexity; FA-2 is entirely about mapping the same tiled algorithm onto warps/blocks so the hardware isn’t idle. (This is the lesson-05 GEMM lesson applied to attention: the algorithm was right, the occupancy and warp layout were the bottleneck.)
7. FlashAttention-3: Hopper asynchrony and FP8
FlashAttention-2 was designed for a synchronous execution model and left ~65% of an H100 on the table (only ~35% utilization on Hopper). Hopper (H100) added hardware that FA-2’s model doesn’t exploit: the Tensor Memory Accelerator (TMA) for asynchronous bulk HBM↔SMEM copies, asynchronous warpgroup matmul (wgmma) instructions, and FP8 tensor cores. FlashAttention-3 (Shah, Bikshandi, Zhang, Thakkar, Ramani, Dao — arXiv:2407.08608, NeurIPS 2024) is a Hopper-native redesign around asynchrony and low precision.5 Three techniques:
- Warp-specialization / producer–consumer async. Split warps into producers (issue TMA loads of the next K/V tile into SMEM) and consumers (run wgmma + softmax on the current tile), coordinated by async barriers. This overlaps data movement with computation — the TMA fetches the next block while tensor cores chew on the current one, so neither unit stalls.
- Interleaved (ping-pong) matmul and softmax. The matmul (tensor cores) and the softmax (exp on the multifunction/special-function units) use different hardware units, so FA-3 schedules them to overlap: while one warpgroup does softmax on block , another runs the matmul for block . This hides the non-matmul softmax latency behind matmul throughput — attacking exactly the “non-matmul is slow” problem FA-2 identified, but by overlap rather than reduction.
- FP8 with block quantization + incoherent processing. To use Hopper’s 2× FP8 throughput, FA-3 quantizes in blocks and applies an incoherent-processing (random orthogonal/Hadamard rotation) trick to spread outliers and cut quantization error, addressing FP8’s tiny mantissa.
Reported performance: 1.5–2.0× over FA-2 on H100, with FP16 reaching up to ~740 TFLOP/s (~75% utilization) and FP8 approaching ~1.2 PFLOP/s (the camera-ready version reports up to ~840 TFLOP/s BF16, ~85% utilization). FA-3 also improves accuracy vs a naive FP8 attention baseline thanks to the incoherent processing.5
Each generation targets a different bottleneck — read them as a sequence
FA-1 killed HBM traffic (IO). FA-2 killed idle warps/blocks (occupancy & non-matmul FLOPs). FA-3 killed synchronous stalls (async overlap) and unlocked FP8. The math — tiling + online softmax — is unchanged since 2022. What changes is which hardware resource is the binding constraint, generation to generation. That’s the co-design loop in action.
8. FlashAttention-4: Blackwell co-design (current SOTA, Sept 2026)
As of September 2026 the newest generation is FlashAttention-4 (Zadouri et al. — arXiv:2603.05451, published March 2026; first shown at Hot Chips, August 2025), purpose-built for NVIDIA Blackwell (B200/GB200).6 Its motivating observation is asymmetric hardware scaling: from Hopper to Blackwell, BF16 tensor-core throughput jumps ~2.25× (to ~2.25 PFLOP/s), but the special-function units (SFUs) that compute exp and the shared-memory bandwidth did not scale.6 So on Blackwell the tensor cores are no longer the bottleneck — the new limits are (a) the SFU exp throughput in the forward pass and (b) SMEM bandwidth in the backward pass. FA-4’s techniques follow directly:
- Software-emulated exponentials. Instead of routing
expthrough the scarce SFUs (MUFU.EX2), FA-4 computes it via a polynomial approximation on the abundant FMA units — moving the softmax bottleneck onto general-purpose compute Blackwell has in surplus. - Conditional online-softmax rescaling. The rescale-on-every-max-change from §5.1 is skipped unless the max shift is large enough to threaten numerical stability, cutting rescaling operations ~10×.6 (A direct optimization of the recurrence you derived above.)
- Tensor Memory (TMEM) for the backward pass. Blackwell’s per-SM 256 KB TMEM (wired into the tensor cores) holds backward intermediates, relieving SMEM-bandwidth pressure; combined with the new 2-CTA MMA mode to further cut SMEM traffic and halve atomic reductions.
- CuTe-DSL in Python. The kernel is written in a Python-embedded CuTe DSL, giving ~20–30× faster compile times than C++ templates while keeping full control.
Reported: up to ~1.6 PFLOP/s BF16 (~71% utilization) on B200, 1.3× over cuDNN 9.13 and 2.7× over the Triton backend for the forward pass.6 FA-4 also backs PyTorch’s FlexAttention as a JIT target on Hopper/Blackwell, so custom variants (ALiBi, sliding-window, doc-masking, soft-capping) compile into FA-4 kernels. Caveat: FA-4 requires Hopper or Blackwell; older GPUs stay on FA-2/FA-3.
SOTA moves fast — verify before quoting
The FlashAttention line has shipped a new generation roughly yearly (2022 → 2023 → 2024 → 2026), each retargeting the then-current GPU’s binding constraint. The exact TFLOP/s numbers above are the papers’ reported figures and depend on head dim, causal masking, and precision; treat them as ballparks and re-check the paper/repo for your shape. The ideas (IO-awareness, online softmax, async overlap, precision) are the durable takeaways.
9. The broader lesson: algorithm–hardware co-design
Step back. Across four generations, FlashAttention never reduced the FLOP count of attention — the forward pass still does multiply-adds, and the backward adds FLOPs via recomputation. Every speedup came from respecting the memory hierarchy:
- FA-1: don’t materialize in HBM (IO).
- FA-2: map the work onto warps/blocks so the tensor cores stay busy (occupancy).
- FA-3: overlap async copy/compute and exploit FP8 (Hopper).
- FA-4: move
expoff the SFUs and intermediates into TMEM (Blackwell).
This is algorithm–hardware co-design: the algorithm is (re)shaped to the specific costs and capabilities of the memory/compute hierarchy it runs on. It is the same principle as tiled GEMM (lesson 05) and elementwise fusion (§3) — do more work per byte pulled from HBM, and match the computation’s structure to the units that execute it. The deepest lesson of this entire GPU tier: on the far side of the memory wall, performance engineering is data-movement engineering. FLOPs are cheap; bytes and the units that produce them are what you optimize around.
The transferable heuristic
When a kernel is slow, don’t ask “how do I do fewer FLOPs?” Ask “what’s the binding hardware resource — HBM bandwidth, SMEM bandwidth, SFU throughput, occupancy, or launch overhead? — and how do I restructure the algorithm to stop starving it?” FlashAttention is the textbook worked example of answering that question four times in a row.
10. Practice problems
Practice problem 1: HBM traffic — naive vs. FlashAttention
Problem: One attention head, sequence length , head dim , BF16 ( bytes). Ignore causal masking. (a) Estimate the HBM traffic of naive attention, counting the score/probability round-trips. (b) Estimate FlashAttention’s ideal HBM traffic (Q,K,V read once, O written once). (c) Give the ratio, and state how it scales with .
Worked solution:
(a) Naive. The matrix is written after , read by softmax, written as , and read by the matmul — 4 round-trips of an matrix:
The input/output traffic () is bytes — negligible (~1.6%) beside the term. So naive bytes, dominated by the score matrix.
(b) FlashAttention (ideal). No traffic. Read once and write once:
(In reality there are bounded K/V re-reads from tiling, giving the paper’s ; still far below the naive . The ideal number captures the asymptotics.)
(c) Ratio. less HBM traffic here. More importantly the scaling differs: naive is , flash is — linear in . So the advantage grows with sequence length: double and naive’s traffic 4בs while flash’s only 2בs. This is why FlashAttention is what made long context feasible — and note the naive version’s 512 MiB is also 512 MiB of HBM you must hold, the capacity wall that capped context length before FA.
Practice problem 2: Work an online-softmax update by hand
Problem: A single query attends to 4 keys, processed in two blocks of two. The raw scores (post-) are and . The value vectors are scalars for simplicity: . Run the online-softmax recurrence block by block, tracking , and verify the final output equals the direct softmax-weighted sum.
Worked solution:
Init: .Block 1 (): block max ; ; .
.
.Block 2 (): block max ; ; .
Rescale old state, then add new terms (for ) and (for ):
.
.Normalize: .
Direct check (global softmax, max ): weights , sum ✓ (matches ). Weighted sum of values ✓ (matches ). Output ✓. The streaming computation reproduces the exact softmax — note how block 2’s larger max () triggered the rescale that corrected block 1’s contribution.
11. Practice & resources
Hands-on
- Implement online softmax in NumPy. Write a function that computes softmax-weighted attention for one row by streaming over key/value blocks, maintaining and applying the rescale. Assert it matches
scipy.special.softmax @ Vto floating-point tolerance for random inputs and for adversarial cases (one huge score, all-equal scores). This cements the recurrence far better than reading it. - Work the Triton FlashAttention tutorial. The official Triton tutorial implements a real fused FlashAttention forward (and backward) kernel. Read it after you can derive the online softmax by hand — the tutorial’s
m_i,l_i,accare exactly your . Modify the block sizes and profile. (Deep-dived in 07-triton-and-modern-kernels.) - Profile attention memory, naive vs. Flash. In PyTorch, run attention two ways at : (a) explicit
S = Q@K.T; P = S.softmax(-1); O = P@V, and (b)F.scaled_dot_product_attention(FlashAttention backend). Compare peak memory (torch.cuda.max_memory_allocated) and wall-clock. You should see naive’s memory grow and OOM first, while SDPA stays roughly linear. Thennsys/ncuthe two and confirm naive is dominated by the softmax/elementwise kernels round-tripping HBM. - Measure the HBM traffic gap. In Nsight Compute, read
dram__bytes.sumfor the naive attention’s softmax kernel and for the fused SDPA kernel at fixed . Confirm the ratio tracks your Problem-1 estimate.
Real resources
- FlashAttention (primary): Dao, Fu, Ermon, Rudra, Ré, “FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness” — arXiv:2205.14135 (NeurIPS 2022). The IO-complexity theorem and the tiling+online-softmax algorithm. Read §3 (algorithm) and the IO-complexity analysis. arxiv.org/abs/2205.14135
- FlashAttention-2: Dao, “FlashAttention-2: Faster Attention with Better Parallelism and Work Partitioning” — arXiv:2307.08691 (ICLR 2024). Work partitioning, seq-length parallelism, warp scheduling. arxiv.org/abs/2307.08691
- FlashAttention-3: Shah, Bikshandi, Zhang, Thakkar, Ramani, Dao, “FlashAttention-3: Fast and Accurate Attention with Asynchrony and Low-precision” — arXiv:2407.08608 (NeurIPS 2024). Hopper warp-specialization, TMA/wgmma overlap, FP8. arxiv.org/abs/2407.08608
- FlashAttention-4: Zadouri et al. (Tri Dao co-author), “FlashAttention-4: Algorithm and Kernel Pipelining Co-Design for Asymmetric Hardware Scaling” — arXiv:2603.05451 (2026). Blackwell: software
exp, conditional rescaling, TMEM, 2-CTA MMA. Companion blog: tridao.me/blog/2026/flash4; reverse-engineering write-up: modal.com/blog/reverse-engineer-flash-attention-4. - Code / repo: github.com/Dao-AILab/flash-attention — reference CUDA/CuTe implementations for all generations.
- Triton FlashAttention tutorial: triton-lang.org → Tutorials → “Fused Attention” — a readable, hackable fused kernel; the fastest way to see the recurrence in real code.
- GPU MODE lecture on FlashAttention: the GPU MODE (formerly CUDA MODE) lecture series has a dedicated FlashAttention session walking the algorithm and the Triton kernel. github.com/gpu-mode/lectures
- The mental model (must-read): Horace He, “Making Deep Learning Go Brrrr From First Principles” — compute vs. bandwidth vs. overhead, and why fusion wins. The prose companion to this whole lesson. horace.io/brrr_intro.html
- Online softmax (background): Milakov & Gimelshein, “Online normalizer calculation for softmax” — arXiv:1805.02867. The streaming-softmax trick that FlashAttention builds on, predating it by four years.
12. What’s next
Two threads, in order:
- 07-triton-and-modern-kernels (next) — write the fused kernels yourself. Triton is the language FlashAttention’s tutorial and much of the modern kernel ecosystem is written in; you’ll implement a fused attention forward and see
torch.compile/Inductor generate fusions automatically. The natural continuation of §3 and the Triton hands-on above. - 05-optimizing-gemm (back) — reread the SMEM tiling and warp-partitioning material now that you’ve seen FA-2 apply exactly that thinking to attention. FlashAttention is “tiled GEMM with an online-softmax epilogue fused in the middle”; the two lessons are the same discipline.
Further threads to pull on: paged attention / KV-cache management (the inference-time memory problem, a cousin of this one), linear-attention & SSMs (changing the algorithm to escape entirely rather than just its IO), and FP8/FP4 attention numerics (the precision frontier FA-3/FA-4 opened).
Reference topic: gpu-systems-for-llms | Concepts: memory-wall, arithmetic-intensity, gpu-memory-hierarchy | Filed: 2026-09-02
References
Footnotes
-
NVIDIA H100 Tensor Core GPU — 80 GB HBM3, 989 TFLOP/s FP16/BF16 tensor-core (dense), ~3.9 TFLOP/s special-function (exp) throughput; BF16 arithmetic-intensity ridge ≈ 296 FLOP/byte (≈ 989 TFLOP/s ÷ ~3.35 TB/s HBM3). NVIDIA, “NVIDIA H100 Tensor Core GPU Architecture” whitepaper; SFU vs tensor-core throughput figures also stated in the FA-3 paper (arXiv:2407.08608). ↩ ↩2
-
[established] Tri Dao, Daniel Y. Fu, Stefano Ermon, Atri Rudra, Christopher Ré, “FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness” — arXiv:2205.14135 (NeurIPS 2022). arxiv.org/abs/2205.14135. Source of the IO-complexity bound, the ~9× fewer HBM accesses (GPT-2) and 2–4× wall-clock speedup, and the recomputation/logsumexp backward pass. ↩ ↩2 ↩3 ↩4
-
[established] Maxim Milakov, Natalia Gimelshein, “Online normalizer calculation for softmax” — arXiv:1805.02867 (2018). arxiv.org/abs/1805.02867. The single-pass streaming-softmax recurrence (running max + running denominator with rescale) that FlashAttention adapts. ↩
-
[established] Tri Dao, “FlashAttention-2: Faster Attention with Better Parallelism and Work Partitioning” — arXiv:2307.08691 (ICLR 2024). arxiv.org/abs/2307.08691. Source of the ~2× over FA-1, 50–73% of A100 peak, ~72% MFU figures, and the FA-1 ~30–50% (fwd) / 25–35% (bwd) A100 baselines. ↩ ↩2
-
[recent] Jay Shah, Ganesh Bikshandi, Ying Zhang, Vijay Thakkar, Pradeep Ramani, Tri Dao, “FlashAttention-3: Fast and Accurate Attention with Asynchrony and Low-precision” — arXiv:2407.08608 (NeurIPS 2024). arxiv.org/abs/2407.08608. Source of the 1.5–2.0× over FA-2, ~740 TFLOP/s FP16 (~75% util) and ~1.2 PFLOP/s FP8 (arXiv v1/v2); the camera-ready ~840 TFLOP/s BF16 (~85%) figures appear in the revised abstract (OpenReview / AI-at-Meta). FA-2’s ~35% H100 utilization is stated here. ↩ ↩2
-
[recent] Ted Zadouri, Markus Hoehnerbach, Jay Shah, Timmy Liu, Vijay Thakkar, Tri Dao, “FlashAttention-4: Algorithm and Kernel Pipelining Co-Design for Asymmetric Hardware Scaling” — arXiv:2603.05451 (March 2026). arxiv.org/abs/2603.05451. Companion blog: tridao.me/blog/2026/flash4; reverse-engineering write-up: modal.com/blog/reverse-engineer-flash-attention-4. Source of the asymmetric-scaling motivation (B200 ~2.25 PFLOP/s BF16 tensor cores; SFU/SMEM did not scale), software-emulated
exp, conditional rescaling (~10× fewer rescales), TMEM / 2-CTA MMA, CuTe-DSL compile-time claim, and the ~1.6 PFLOP/s BF16 (~71% util) / 1.3× cuDNN 9.13 / 2.7× Triton results. Note: treat the exact TFLOP/s figures as shape/precision-dependent — verify against the primary source when quoting. ↩ ↩2 ↩3 ↩4