FlashAttention from Scratch — The Paradox of Doing More Math to Go Faster
FlashAttention deliberately recomputes the same math in the backward pass. It does strictly more arithmetic and still runs faster — because on a GPU, moving numbers costs more than multiplying them. From counting HBM round trips, through the softmax wall that blocks tiling, to the online-softmax recurrence that breaks it, and the silent backend fallback that bites people in production.
FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness
Primary source — what this article is built on
undefined2026-08-27
FlashAttention: Fast and Memory-Efficient Exact Attention with IO-AwarenessarXiv:2205.14135Paper page·PDFFlashAttention-2: Faster Attention with Better Parallelism and Work PartitioningarXiv:2307.08691Paper page·PDF
FlashAttention-3: Fast and Accurate Attention with Asynchrony and Low-precisionarXiv:2407.08608Paper page·PDF
Online normalizer calculation for softmaxarXiv:1805.02867Paper page·PDF
An optimization that adds arithmetic
When you hear "make it faster," you usually picture cutting wasted work. FlashAttention does the opposite. In the backward pass it recomputes almost everything the forward pass already computed. The arithmetic count strictly goes up. And it is still faster than the naive implementation.
The trick fits in one line: on a modern GPU, fetching a number from memory costs more than multiplying it. That sets an exchange rate — if recomputing saves you a trip to memory, recomputing is the cheaper option. And FlashAttention is not an approximation. Nothing is dropped or sparsified; it produces the same answer as the naive version. What changed is the route to the answer, not the answer.
The metaphor: never write the score sheet down
Picture a library where the documents live in a basement warehouse and your desk is tiny. Trips to the warehouse are slow; work on the desk is instant.
The job: score 1,000 applicants on 1,000 criteria, then blend their opinions in proportion to the scores. The naive approach builds a million-cell score sheet, can't fit it on the desk, files it in the warehouse, fetches it back to compute row sums, converts to proportions, files it again, and fetches it a third time to blend the opinions.
FlashAttention never writes the sheet down. It splits the applicants into batches of 50, scores each batch on the desk, and keeps only a running partial answer sitting on the desk. When the last batch is done, the finished answer is already there.
The warehouse is the GPU's HBM (large, slow, off-chip); the desk is SRAM (small, fast, on-chip). How many orders of magnitude separate them is covered in The GPU Memory Hierarchy.
Recap: only the middle blows up to N×N
As shown in Attention from Scratch, self-attention is three lines. Let be the sequence length (number of tokens) and the head dimension.
are all matrices whose rows are each token's "question," "name tag," and "content." is the score table pairing everyone with everyone, is that table rescaled so each row sums to one, and is the output (). So input and output are both , but the middle swells to . That is the root of everything.
The slow part is the traffic, not the math
Numbers help. With and , holds about 520K elements while holds about 16.8M — 32× more. Quadruple the sequence length and grows 4× while grows 16×, widening the ratio to 128×.
The naive implementation shuttles that giant table through HBM repeatedly: write , read it back for the softmax, write , read it again for the product with . Bytes moved scale as while arithmetic scales as , so you get only about operations per byte moved. That is squarely bandwidth-bound territory.
And both and are intermediates that get thrown away. We are hauling something larger than the final output to the warehouse and back, only to discard it. That is the opening.
The softmax wall that blocks tiling
"Cut the big matrix into blocks and process whatever fits in fast memory" — tiling has been standard practice for matrix multiplication for decades. So why wasn't attention doing it?
Because softmax gets in the way.
Here is one cell of the score table and is that row's maximum. Subtracting the maximum before exponentiating is the standard guard against overflowing; it multiplies numerator and denominator by the same constant, so the answer is unchanged.
The trouble is and that sum in the denominator. Neither is final until you have seen the whole row. After one block, a larger score might still show up later. So implementations produced the entire table before normalizing, which meant holding — or so it seemed for a long time.
Comments
Sign in to comment