Learn: The Quadratic Bottleneck & IO-Aware Attention

What you're learning

The one idea that made long context tractable: naive attention is memory-bound, not compute-bound — it wastes its time moving the score matrix through HBM, not computing it. The fix isn’t fewer FLOPs, it’s fewer bytes. By the end you’ll be able to derive the online (streaming) softmax recurrence — the mathematical heart of FlashAttention — and explain why it drops HBM traffic from to while computing exactly the same output.

First lesson of Tier 2. Assumes Tier 1: 01-scaled-dot-product-attention (SDPA, the wall, FLOP counting), 02-multi-head-attention-and-the-block, and 03-positional-information. Vocabulary follows the attention-glossary.

This is the algorithm angle, not the kernel angle

This lesson is about why attention is memory-bound and the math that fixes it. The hardware/kernel treatment — warp specialization, TMA/wgmma overlap, FA-2/3/4 generations, roofline analysis — lives in 06-memory-wall-and-flashattention. We deliberately don’t repeat it here; cross-links point you there for the systems details.


1. Learning map

graph TD
    A["Recap: attention core is<br/>O(s²·d) FLOPs (lesson 01)"] --> B["But wall-clock ≠ FLOPs:<br/>it materializes s×s S,P in HBM"]
    B --> C["Arithmetic intensity ~ d,<br/>far below the ridge point<br/>⇒ MEMORY-BOUND"]
    C --> D["Realization: fix bytes moved,<br/>not FLOPs done (IO-awareness)"]
    D --> E["Obstacle: softmax needs a<br/>GLOBAL row normalizer"]
    E --> F["Online softmax:<br/>running m, ℓ, o + rescale α"]
    F --> G["Compute row output<br/>block-by-block, never form s×s"]
    G --> H["HBM traffic: O(s²) → O(s)<br/>same FLOPs, long context unlocked"]
    H --> I["Kernel/hardware details:<br/>see GPU lesson 06"]
    style B fill:#a44,color:#fff
    style C fill:#a44,color:#fff
    style F fill:#4a4,color:#fff
    style H fill:#44a,color:#fff

2. Intuition: attention is memory-bound, not compute-bound

Lesson 01 left you with the headline: the attention core costs FLOPs — quadratic in sequence length . Double the context, quadruple the compute. That’s the wall, and it’s real. But it’s only half the story, and not even the binding half on modern hardware.

Here’s the realization. Count what the naive implementation actually does to memory when it runs as three library calls (S = Q@K.T/√d, P = softmax(S), O = P@V):

  1. — a matmul that reads () and writes the full score matrix to HBM.
  2. — an elementwise+reduction kernel that reads back (), does a handful of FLOPs per element, and writes back ().
  3. — a matmul that reads () and , writes ().

The matrix is round-tripped through HBM roughly four times (write , read , write , read ) — bytes moved — while carrying only FLOPs split across separate kernels. The softmax in the middle is the smoking gun: it’s pure elementwise work with arithmetic intensity FLOP/byte, so it runs at HBM bandwidth and does nothing but shuttle the score matrix. Its entire runtime is “time to read + time to write .”

So the attention block, as launched, is bottlenecked by moving the score matrix, not by the tensor cores computing it. Its effective arithmetic intensity collapses toward (tens to ~128) — far below the hardware’s ridge point (~296 FLOP/byte on an H100).1 Attention has plenty of FLOPs; it just can’t feed them because it’s starved on bandwidth. That’s what “memory-bound” means.2

The one-sentence version

Naive attention is slow not because it does too much math, but because it writes an matrix to slow memory only to immediately read it back. The win is to never materialize that matrix in HBM — same FLOPs, a fraction of the bytes.

There’s a second, equally fatal consequence of materializing : capacity. Holding an matrix costs HBM storage. At that’s ~128 MiB per head in BF16; at it’s ~32 GiB2 — larger than an entire H100’s 80 GB if you keep a few around. That storage wall is, quite literally, what capped context length before 2022.

The fix is IO-awareness: design the algorithm around minimizing HBM reads/writes, the way tiled GEMM is designed around SRAM reuse. Compute attention block-by-block, keeping each block’s scores on-chip, producing, consuming, and discarding them before they ever reach HBM. The obstacle is softmax: its normalizer is a sum over the entire row, which seems to demand seeing the whole row at once. Section 3 shows why it doesn’t.


3. The math: online (streaming) softmax

We want to compute, for a single query row with scores (here ) and value vectors , the softmax-weighted output

where subtracting the row max is the standard safe-softmax trick to avoid overflow of (every exponent becomes ). The obstacle for streaming: both and the denominator depend on the whole row, but we want to sweep over blocks of the key/value sequence, seeing only one block at a time and keeping off HBM.

The trick (Milakov & Gimelshein, 2018)3 is to maintain a small running state and correct it whenever a later block reveals a larger max. Track three quantities after processing blocks :

  • — running max of all scores seen so far;
  • — running denominator, referred to the current max ;
  • — running unnormalized output accumulator, also referred to .

Initialize . For block let . The recurrence is:

After the last block, normalize once: .

Why the correction factor ?

The old accumulators were built with exponentials referred to the old max . When the max jumps to , every previously stored term must be re-referenced to the new max. For a single term:

Multiplying the whole running sum by the scalar applies this correction to every stored term at once — that’s why a single multiply fixes the entire accumulator. If the new block doesn’t raise the max (), then and nothing changes. And because , the exponent is , so — a shrink factor, never an overflow.

The invariant that makes it exact

At every step the recurrence maintains the exact partial softmax over the prefix seen so far:

By induction (base case trivial; the inductive step is exactly the rescale algebra above), after the final block equals full softmax attention bit-for-bit up to floating-point associativity. FlashAttention is exact, not an approximation — you can drop it into any model with zero accuracy change. That is the crucial selling point that separates it from the sparse/linear-attention approximations.

Because and are just a running scalar and a running -vector, each block’s scores can be thrown away the instant they update the state — they never need to be stored. That is the mechanical reason the matrix never reaches HBM. The state is per query row, independent of .


4. Illustration: tiled block-by-block attention

For each query block , stream over the key/value blocks , keeping only the running state on-chip. The scores are never assembled.

flowchart LR
    subgraph HBM["HBM (slow, large) — only linear-in-s traffic crosses here"]
        Qi["Q_i block"]
        KV["K_1..K_T, V_1..V_T"]
        Oi["O_i (written once)"]
    end
    subgraph SRAM["SRAM / on-chip — loop t = 1..T"]
        S1["1. score tile<br/>S_it = Q_i K_tᵀ/√d<br/>(stays on-chip, never to HBM)"]
        S2["2. update state<br/>m ← max(m, m̃_t)<br/>α = e^(m_old−m)<br/>ℓ ← αℓ + Σ e^(S−m)<br/>o ← αo + Σ e^(S−m) V_t"]
        S3["3. after last block<br/>O_i = o / ℓ"]
    end
    Qi --> S1
    KV --> S1
    S1 --> S2
    S2 -->|"next K/V block"| S1
    S2 --> S3
    S3 --> Oi

Figure 1 — 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 on-chip. Only (in) and (out) cross HBM. The matrix is never materialized.

Before vs. after, in HBM traffic:

HBM traffic per attention call (single head) 0 Naive ≈ 4s² (quadratic) write S / read S write P / read P the s×s round-trips IO-aware (tiled) ≈ O(s·d) (linear in s) Q,K,V read + O write only S, P never leave on-chip

Figure 2 — Naive attention’s HBM traffic is dominated by four round-trips of the score/probability matrix (quadratic in ). The tiled, online-softmax algorithm never materializes those matrices, leaving only the linear-in- traffic of reading and writing . This collapse is the entire win.

The tiling reorders the softmax sum — associativity, not identity

“Bit-for-bit exact” in the invariant means up to floating-point associativity. Streaming accumulates the exponential sums in a different order than a single-pass softmax, so results can differ in the last bit or two. This is the ordinary non-associativity of float addition (the same caveat as any tiled reduction), not an approximation of attention — accuracy is unaffected in practice.


5. FLOPs & memory: same math, a fraction of the bytes

The crucial accounting, side by side. Let = batch, = sequence length, = head dim.

Naive (3 kernels)IO-aware / tiled
Attention-core FLOPs (same — forward)
HBM traffic (four round-trips) (Q,K,V in, O out)
Peak HBM footprint of running state / row
Bound byHBM bandwidth (memory-bound)near tensor-core peak (compute-bound)

The headline: the FLOP count does not change. Online softmax is not a cheaper algorithm in arithmetic — it’s the same multiply-adds. What changes is the bytes moved: HBM traffic drops from quadratic to linear in , and the storage footprint vanishes. Because the op was memory-bound, cutting the bytes is what cuts the wall-clock — the original paper measured ~9× fewer HBM accesses and 2–4× wall-clock speedup on GPT-2, computing identical outputs.2

Why this unlocked long context — the scaling, not the constant

Naive traffic is ; tiled traffic is . So the advantage grows with context: double and naive’s traffic 4בs while the tiled version’s only 2בs. Equally important, the storage of the score matrix — the capacity wall that once OOM’d long sequences — is gone. Same FLOPs, linear bytes: that combination is what turned 128k+ token context from impossible to routine.

"Fewer FLOPs" is the wrong instinct here

Faced with the wall, the tempting move is to approximate attention to cut FLOPs (sparse, low-rank, linear attention). Those change the math and can cost accuracy. FlashAttention shows a different, often better lever: keep the exact math, cut the bytes. On a memory-bound op, bytes — not FLOPs — are what you’re actually paying for. (The backward pass even adds FLOPs via recomputation and still wins — see 06-memory-wall-and-flashattention §5.3.)

For the hardware mechanics that turn this IO complexity into realized TFLOP/s — SRAM tiling specifics, the exact IO bound, warp specialization, and the FA-2/FA-3/FA-4 generations — see the systems lesson 06-memory-wall-and-flashattention. FlashAttention-24 in particular re-derives the recurrence to defer per-block rescaling to a single final divide, cutting non-matmul work; that’s an optimization of the exact recurrence in §3.


6. Primary source & where to go deeper

  • FlashAttention (primary): Dao, Fu, Ermon, Rudra, Ré, “FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness” — arXiv:2205.14135 (NeurIPS 2022).2 Read §3 (the tiling + online-softmax algorithm) and the IO-complexity analysis. This is the paper that framed attention as an IO problem.
  • Online softmax (the recurrence itself): Milakov & Gimelshein, “Online normalizer calculation for softmax” — arXiv:1805.02867 (2018).3 The single-pass running-max + running-denominator trick, four years before FlashAttention adapted it.
  • Kernel/hardware angle (companion): 06-memory-wall-and-flashattention — roofline, SRAM tiling, recomputation backward, and FA-2/3/4.

Ask your teacher

Stuck on the induction that makes online softmax exact, the role of , or the FLOPs-vs-bytes accounting? Ask — I’m your teacher for this course and can go deeper on any piece, hand-run the recurrence on a new example, or build you a harder problem.


7. Practice


8. What’s next

  1. 05-mqa-gqa-mla (next) — the other attention memory problem. FlashAttention fixed training/prefill HBM traffic; at autoregressive inference the binding constraint is the KV cache (linear in but read every step). MQA/GQA/MLA shrink it by sharing or compressing keys/values. Different bottleneck, same “move fewer bytes” philosophy.
  2. 03-positional-information (back) — revisit how positions enter attention. Position schemes (especially RoPE) must be applied inside the fused kernel, block-by-block, since the scores never materialize — a concrete constraint the IO-aware algorithm imposes on everything upstream.

Further threads: linear attention & SSMs (change the algorithm to escape entirely, trading exactness for asymptotics — the opposite bet to FlashAttention), and the kernel-level continuation in 06-memory-wall-and-flashattention.


Reference: attention-glossary | Topic: attention-mechanisms | Filed: 2026-09-10


References

Footnotes

  1. NVIDIA H100 Tensor Core GPU — BF16 arithmetic-intensity ridge point ≈ 296 FLOP/byte (≈ 989 TFLOP/s tensor-core ÷ ~3.35 TB/s HBM3). See NVIDIA H100 architecture whitepaper; the ridge-point figure and its use in the attention roofline are detailed in 06-memory-wall-and-flashattention. ↩

  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-aware framing, tiling + online-softmax algorithm, the exactness result, the ~9× fewer HBM accesses and 2–4× wall-clock speedup on GPT-2, and the storage/capacity wall. ↩ ↩2 ↩3 ↩4 ↩5

  3. [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. ↩ ↩2

  4. [established] Tri Dao, “FlashAttention-2: Faster Attention with Better Parallelism and Work Partitioning” — arXiv:2307.08691 (ICLR 2024). arxiv.org/abs/2307.08691. Restructures the recurrence to defer per-block output rescaling to a single final normalization, cutting non-matmul work; work partitioning / warp scheduling covered in the GPU-systems lesson. ↩