FlashAttention
Exactly the same function as scaled dot-product attention, computed without ever writing the n × n score matrix to HBM.
Standard attention is memory-bound, not compute-bound: it reads and writes words of high-bandwidth memory to hold a matrix it immediately reduces away. FlashAttention tiles the computation so that the score block for a (query, key) tile lives only in SRAM.
The obstacle is the softmax denominator, which is a global reduction over each row. Online softmax fixes this with a running maximum and a running sum, rescaling the accumulated output whenever the maximum moves.
The backward pass does not store either. It recomputes each score tile from , and the saved per-row statistics , trading a second pass of FLOPs for the removal of the read.
The FLOP count is unchanged and the result is bit-comparable to a well-ordered reference. Everything gained is bandwidth. This is the cleanest example in the book of an algorithm whose complexity class says nothing about its speed.
import math
import torch
from torch import Tensor
def flash_reference(q: Tensor, k: Tensor, v: Tensor, tile: int = 128) -> Tensor:
"""Illustrates the online-softmax recurrence. Real speedups
require the tiles to live in SRAM, which needs CUDA."""
n, d = q.shape
scale = 1.0 / math.sqrt(d)
out = torch.zeros_like(q)
m = torch.full((n, 1), -float("inf"))
l = torch.zeros((n, 1))
for j in range(0, n, tile):
kj, vj = k[j:j + tile], v[j:j + tile]
s = (q @ kj.T) * scale # [n, tile]
m_new = torch.maximum(m, s.max(-1, keepdim=True).values)
correction = (m - m_new).exp()
p = (s - m_new).exp()
l = correction * l + p.sum(-1, keepdim=True)
out = correction * out + p @ vj
m = m_new
return out / lThe correction factor is applied to the accumulator and the normaliser on every tile — dropping it from either one gives a result that looks plausible and is wrong by a per-row constant. Initialising to makes the first correction , which is intended and not an edge case to special-case.