AI Knowledge Tree

FlashAttention

Tile attention into SRAM-fitting blocks, recompute softmax statistics, and avoid materializing the n x n attention matrix.

⏱ 24 minPrerequisites:Self-attention

Standard attention computes softmax⁡(QK⊤/d)V\operatorname{softmax}(QK^\top/\sqrt{d})V by materializing the full N×NN \times N attention matrix in slow GPU memory. FlashAttention computes the exact same result with O(N)O(N) memory by tiling the computation into chunks that fit in fast on-chip memory. The speedup is 2-4x; the memory savings unlock contexts that wouldn't otherwise fit. It is now the default in every production Transformer training pipeline.

The memory hierarchy

GPU memory is layered:

  • HBM (high-bandwidth memory): 40-80 GB on a modern GPU, ~3 TB/s bandwidth. Slow.
  • SRAM (on-chip cache): ~50 MB per streaming multiprocessor, ~30 TB/s bandwidth. Fast but tiny.

Standard attention's bottleneck is HBM bandwidth: reading and writing N×NN \times N attention scores at every step dominates the runtime, not the compute.

FLOP-wise, attention's matmul is fast; the data movement is the issue.

The FlashAttention algorithm

Dao et al. (2022)[1]. Two innovations:

1. Tiling: split the computation into blocks small enough to fit in SRAM. Compute each block fully before moving to the next.

2. Online softmax: compute softmax incrementally as new blocks arrive, without ever holding the full N×NN \times N matrix.

The naive softmax needs the full row to compute the normalizing constant. But softmax has a useful property:

softmax⁡([z;z′])=softmax⁡([z−m 1;z′−m 1])\operatorname{softmax}([z; z']) = \operatorname{softmax}([z - m\, \mathbf{1}; z' - m\, \mathbf{1}])

with m=max⁡([z;z′])m = \max([z; z']). So you can stream through blocks, keep running max and running sum, and combine them correctly across blocks.

The full algorithm:

  1. Split QQ, KK, VV into row blocks QiQ_i and column blocks KjK_j, VjV_j.
  2. For each row block QiQ_i:
    • Load into SRAM.
    • For each column block jj (left to right):
      • Load KjK_j, VjV_j into SRAM.
      • Compute Sij=QiKj⊤S_{ij} = Q_i K_j^\top in SRAM.
      • Update running max and running sum.
      • Update partial output OiO_i in SRAM.
    • Write final OiO_i back to HBM.

The full N×NN \times N matrix is never materialized; only a few blocks at a time exist in SRAM. HBM traffic drops from O(N2)O(N^2) to O(N2/B)O(N^2/B) where BB is block size, but more importantly memory drops from O(N2)O(N^2) to O(N)O(N).

Why it's faster

For typical sequence lengths and block sizes, FlashAttention does fewer HBM reads/writes than standard attention. The compute is the same (or slightly more due to recomputation in the backward pass), but compute was never the bottleneck.

Concrete: 2-4x faster than the standard implementation, with the ratio growing as sequence length grows.

Backward pass

The backward pass needs the same statistics. Either store them from the forward pass (cheap: O(N)O(N) instead of O(N2)O(N^2)) or recompute them. FlashAttention's backward also tiles over blocks, computing gradients block-by-block.

A small overhead: the backward pass redoes ~10% extra FLOPs compared to a naive implementation that stored everything. Saved memory is worth it.

FlashAttention-2 and -3

Refinements that further increase throughput:

  • FA-2 (Dao, 2023): better parallelism across thread blocks, swap of inner and outer loops to use more SMs effectively. ~2x speedup over FA-1.
  • FA-3 (Shah et al., 2024): NVIDIA Hopper-specific instructions (asynchronous TMA, FP8 support), another 1.5-2x on H100.

Each generation is a re-implementation for the dominant hardware generation, illustrating the IO-aware-kernel discipline.

Variants

  • PagedAttention (vLLM): paginate the KV cache to handle variable- length sequences efficiently in serving. Different problem (inference), related discipline.
  • Sparse FlashAttention: extend to sliding-window and other sparse attention patterns.
  • Causal masking efficiency: cleaner handling of the lower- triangular mask in autoregressive decoding.

How to use it

Modern frameworks integrate FlashAttention transparently:

# PyTorch 2.x: scaled_dot_product_attention auto-dispatches to FlashAttention
y = torch.nn.functional.scaled_dot_product_attention(q, k, v, is_causal=True)
[2]

The framework picks the right backend (FlashAttention, memory-efficient attention, naive) based on shapes, hardware, and dtype.

For custom kernels, the flash-attn Python package provides direct access to the implementations.

Why it matters beyond speed

FlashAttention shifted attention from being memory-bound to being compute-bound in practice. This:

  • Made long contexts (32K+ tokens) practical for training.
  • Made larger batches feasible.
  • Reduced the relative cost of attention in the Transformer block, shifting more focus to FFN optimization.

It is one of the most impactful systems contributions to modern deep learning, on par with mixed precision and FSDP for changing what is possible to train.

Standard attention materializes the full n×n score matrix (quadratic memory); FlashAttention streams it in tiles, keeping memory linear: gigabytes vs megabytes at long context.
Standard attention materializes the full n×n score matrix (quadratic memory); FlashAttention streams it in tiles, keeping memory linear: gigabytes vs megabytes at long context.

The bigger lesson

FlashAttention is the same algorithm, just implemented to be kind to the memory hierarchy. The general principle: memory bandwidth, not FLOPs, is often the bottleneck on modern hardware. Any algorithm that operates on large intermediate tensors should consider tiling, fusion, and recomputation to reduce data movement.

This thinking permeates modern kernel writing: KV cache management, mixture-of-experts routing, attention variants.

Exercises

Exercise 1. Standard attention materializes the N×NN \times N score matrix in HBM. For a single head with sequence length N=8192N = 8192 and fp16 scores (2 bytes each), compute the memory of that matrix. Then state FlashAttention's memory order and why it does not store the matrix.

Solution

Step 1. The score matrix has N2=81922=6.71×107N^2 = 8192^2 = 6.71 \times 10^7 entries.

Step 2. At 2 bytes each, 6.71×107×2≈1.34×1086.71 \times 10^7 \times 2 \approx 1.34 \times 10^8 bytes ≈134\approx 134 MB per head, per layer, for one sequence. Across many heads and layers this dominates memory and blocks long contexts.

Step 3. FlashAttention keeps only a few row/column blocks in SRAM at a time and an O(N)O(N) running max and running sum per query row, never materializing the full matrix. Its memory is O(N)O(N): 81928192 entries of statistics rather than 819228192^2 of scores, so memory grows linearly in sequence length instead of quadratically.

Exercise 2. FlashAttention's correctness rests on the shift-invariance of softmax: softmax⁡(z)=softmax⁡(z−c1)\operatorname{softmax}(z) = \operatorname{softmax}(z - c\mathbf{1}) for any scalar cc. Prove it, then explain how it lets two blocks with different running maxima be combined into one normalized output.

Solution

Step 1. Write the ii-th softmax component and subtract cc from every logit:

softmax⁡(z−c1)i=ezi−c∑jezj−c=e−cezie−c∑jezj=ezi∑jezj=softmax⁡(z)i.\operatorname{softmax}(z - c\mathbf{1})_i = \frac{e^{z_i - c}}{\sum_j e^{z_j - c}} = \frac{e^{-c} e^{z_i}}{e^{-c}\sum_j e^{z_j}} = \frac{e^{z_i}}{\sum_j e^{z_j}} = \operatorname{softmax}(z)_i.

The common factor e−ce^{-c} cancels, so the result is unchanged.

Step 2. When streaming blocks, each block is exponentiated against its own running max mm to stay numerically safe. When a later block raises the max to m′m', the already-accumulated sum and output are rescaled by em−m′e^{m - m'}, which is exactly subtracting the new shift c=m′−mc = m' - m from the earlier terms.

Step 3. Because shift-invariance guarantees the final normalized softmax is independent of which constant was subtracted, rescaling partial results by em−m′e^{m - m'} and continuing yields the same output as computing softmax over the whole row at once. The block-by-block stream is exact, not approximate.

Exercise 3. A team weighs storing the forward softmax statistics (O(N)O(N) per row) versus recomputing them in the backward pass. The recompute path adds about 10% FLOPs but saves the O(N2)O(N^2) storage a naive backward would need. For N=32768N = 32768, compare the storage of the two approaches (fp16, one head) and state which the lesson recommends and why.

Solution

Step 1. Naive backward that stored the full score matrix needs O(N2)O(N^2): 327682×2≈2.1×10932768^2 \times 2 \approx 2.1 \times 10^9 bytes ≈2.1\approx 2.1 GB per head per layer, infeasible across a deep model.

Step 2. FlashAttention stores only the O(N)O(N) per-row statistics (max and sum): about 32768×232768 \times 2 values ×\times a few bytes ≈\approx tens of kilobytes, then recomputes the block scores on the fly during backward.

Step 3. The recompute path costs about 10% extra FLOPs but turns 2.1 GB of storage into kilobytes. Since attention was memory-bound, not compute-bound, the lesson recommends recomputation: the extra FLOPs are nearly free while the memory saving is what makes long-context backward feasible.

The next lesson covers the broader infrastructure that wraps all of these: training clusters and orchestration.


References

  1. [1]Dao, T., Fu, D. Y., Ermon, S., Rudra, A., & Ré, C. (2022). "FlashAttention: fast and memory-efficient exact attention with IO-awareness". NeurIPS. arXiv:2205.14135. The tiling plus online-softmax algorithm derived here.↩
  2. [2]Paszke, A., et al. (2019). "PyTorch: an imperative style, high-performance deep learning library". NeurIPS. arXiv:1912.01703. The framework whose scaled_dot_product_attention auto-dispatches to a FlashAttention backend.↩