← All tutorials

Transformer Cost and Efficiency

On this page

1 Attention's cost grows with the square of the sequence length 2 FlashAttention does the same math with less memory traffic 3 The KV cache keeps past keys and values 3.1 The cache trades compute for memory 4 Query heads can share keys and values 5 Streaming models look back through a window 6 When a Transformer is the wrong tool 6.1 Four training and serving failures and their fixes 7 In practice, a fused kernel reads a growing cache 8 References

A Transformer's compute and memory grow with the number of tokens n; computing in small tiles, caching past keys and values, sharing them across heads and letting each token read only a recent window reduce that cost. The cost is paid in every attention layer, over all n tokens at once in training and on the prompt, then once per new token during generation.

Attention, the operation whose cost this page measures.
Attention(Q, K, V)
= softmax(QKT√dk)V
Each token's output is a weighted average of all the values, weighted by how well its query matches each key, and the cost lives in how many matches there are. Q, K and V hold one row per token, dk numbers each, so QKT is an n by n score table: one entry for every pair of tokens. Double the tokens and the table has four times the entries, which is where the cost on this page comes from.

Each token is d numbers wide (the model width); after attention, a feed-forward network (FFN) widens each token to dff and back. A decoder-only model like GPT generates one token per step and feeds it back in.

one attention headQn × dk ×KTdk × n=scoresn × nn² cells,one pertoken pairthe FFN, for each tokenddff (4 × d)d
Figure 1. The cost comes from the n by n score table in every head and the FFN widening every token.

1 Attention's cost grows with the square of the sequence length

Each layer has two costs, attention and the FFN; attention's grows with the square of the sequence length, the FFN's only linearly. Costs are in FLOPs, one per multiply or add; projections, softmax and norms are left out.

The two costs per layer, attention against the FFN, in FLOPs.
attention FLOPs = 4n2d
Attention's cost grows with the square of the sequence length. Building the n by n score table takes n²d multiply-adds, one length-d dot product per pair, and the weighted sum of the values takes as many again; at 2 FLOPs per multiply-add that makes 4n²d. Doubling n quadruples it.
FFN FLOPs = 4nddff = 16nd2
The FFN's cost grows only linearly with n, because it works on each token alone. Each token needs two matrix multiplications, d up to dff and back, whatever the sequence length; with dff = 4d, as in the original Transformer (Vaswani et al. 2017), that is 16nd². Its square is in the width d, not the length.
4n2d = 16nd2 at n = 4d
The two costs are equal when the sequence is four times as long as the model is wide; shorter sequences are FFN-bound and longer ones are attention-bound. Setting the attention line and the FFN line of this card equal and dividing both sides by 4nd leaves n = 4d. GPT-3 (Brown et al. 2020) has d = 12,288 and a 2,048-token context, so the n² work (scores plus weighted sum) passes the FFN only at 4d ≈ 49,152 tokens. Counting the Q, K, V and output projections on the attention side moves the crossing to 2d ≈ 24,576; both sit far past 2,048.

Memory follows the same square: at 100,000 tokens, one head's score table holds 10 billion entries.

attention, 4n2d FFN, 16nd2 your length n, at d = 512

Figure 2. Below n = 4d the FFN dominates; above it the square term takes over.

So long-context models restrict attention: windowed (each token reads only a recent window), sparse (each token reads a chosen subset) or linear (cost growing in a straight line with length).


2 FlashAttention does the same math with less memory traffic

Standard attention writes the n by n score table to the GPU's large, slow memory (HBM) and reads it back; Dao et al. (2022) found this traffic, more than the arithmetic, sets the time.

FlashAttention computes exactly the same output in tiles in the GPU's small fast on-chip memory (SRAM) of size M; each tile holds Bc rows of K and V, and the full table never lands in HBM. Softmax subtracts each row's maximum before exp, so exp never overflows; a tile sees only part of a row, so it keeps a running maximum and sum, and when a later tile has a bigger maximum, the earlier sum is rescaled by exp(old max − new max). The backward pass keeps only those per-row statistics and recomputes the tiles, so it does more arithmetic, not less.

K1K2K3K4Q1Q2Q3Q4tilescores, n × n1. load Q2, K3, V3into fast SRAM2. score one tile,update each row'srunning max and sum3. the full tablenever goes toslow HBM
Figure 3. Only one tile of scores exists at a time, inside the fast on-chip memory.

The math behind the running sum, on one row of 8 scores split into two tiles.

import numpy as np

TILE = 4
s = np.random.randn(2 * TILE)  # one row of 8 scores
tile1, tile2 = s[:TILE], s[TILE:]
m1, m2 = tile1.max(), tile2.max()
sum1 = np.exp(tile1 - m1).sum()
sum2 = np.exp(tile2 - m2).sum()

m = max(m1, m2)
total = sum1 * np.exp(m1 - m) + sum2 * np.exp(m2 - m)
full = np.exp(s - m).sum()
print(np.allclose(total, full))  # True
  1. 1Each tile's own maximum and sum, step 2 of Figure 3.
  2. 2Rescales both sums by exp(old max − new max) to the shared maximum, the running update of step 2 in Figure 3.
  3. 3The total equals the full row's sum, so the full table of step 3 in Figure 3 is never needed.

The FlashAttention paper timed attention in GPT-2 medium (1024 tokens, 16 heads of size 64, batch 64, forward and backward passes, one A100).

arithmetic, GFLOPsstandard66.6Flash75.2HBM traffic, GBstandard40.3Flash4.4time, msstandard41.7Flash7.3
Figure 4. More arithmetic, about a ninth of the memory traffic, under a fifth of the time.

3 The KV cache keeps past keys and values

A naive generation loop reruns the whole sequence at every step. Under the causal mask (a token never sees later tokens) past keys and values never change, so real systems store them in a KV cache. Each step computes only the new token's key and value, appends them, and scores the new query against everything stored; prefill runs the prompt through at once to fill the cache first.

The attention cost of step t, per layer, with and without the cache.
cost with cache = 4dt
With the cache, each step computes only its one new token. At step t, with t tokens so far and d = 512 here, the one new query is scored against the t stored keys and averages the t stored values, so the cost grows only linearly with t.
cost without cache = 4dt2
Without the cache, each step redoes the whole sequence. All t queries are recomputed against all t keys, the full t by t table, though only the last row is new; so step t costs t times the cached step, and the waste grows as the text gets longer.

In one decode step with the cache, one new token goes in and one output row comes out.

import torch

B, h, T, d_k = 1, 8, 10, 64
K = torch.randn(B, h, T, d_k)  # cache so far
V = torch.randn(B, h, T, d_k)
q, k, v = torch.randn(3, B, h, 1, d_k)  # the new token

K = torch.cat([K, k], dim=2)
V = torch.cat([V, v], dim=2)
S = q @ K.transpose(-2, -1) / d_k ** 0.5  # (B, h, 1, T + 1)
out = S.softmax(dim=-1) @ V               # (B, h, 1, d_k)
  1. 1The cache so far, one key and one value per earlier token, and the new token's query, key and value.
  2. 2The cache grows by one key and one value, so earlier keys and values are never recomputed.
  3. 3One query row attends over all cached keys, line 1 of the Section 3 card.

Cost per generated token, with and without the KV cache.

KV cache after token 128 of 128, one block per stored token

Cost of token 128, linear scale

No cache (recompute)
With KV cache

Figure 5. Each block is one stored key and value; the bars compare the newest token's cost with and without the cache.

3.1 The cache trades compute for memory

The cache holds a key and a value for every token, in every head of every layer:

The size of the KV cache for one sequence.
cache bytes = 2 · L · hkv · dk · T · b
The cache stores one key and one value (the 2) for every token, in every stored head of every layer, so its size is a plain product. L is the number of layers, hkv the stored heads, dk the numbers per head, T the tokens and b the bytes per number. Every factor counts the same: doubling the context or halving the heads moves the cache by the same factor of 2.

The cache size for a 12-layer model, read off the card.

L, h_kv, d_k = 12, 12, 64  # layers, KV heads, head size
T, b = 4096, 2             # tokens, bytes per number
BATCH = 32

per_seq = 2 * L * h_kv * d_k * T * b  # 2 = key + value
per_batch = BATCH * per_seq
print(per_seq, per_batch)  # 150994944 4831838208, about 151 MB and 4.8 GB
  1. 1The factors of the Section 3.1 card, line 1, for a 12-layer model.
  2. 2Their product for one sequence and for a batch of 32, the cache-bytes card, line 1.

Shazeer (2019) notes that each decode step reloads the large key and value tensors, so memory bandwidth, not arithmetic, sets the speed.


4 Query heads can share keys and values

Multi-query attention (MQA) keeps all h query heads but shares one key head and one value head. Grouped-query attention (GQA, Ainslie et al. 2023) splits the query heads into g groups, each sharing one key head and one value head.

Multi-head, 8 KV heads8 query headsfull KV cacheGrouped-query, 2 KV heads8 query heads1/4 of the cacheMulti-query, 1 KV head8 query heads1/8 of the cache
Figure 6. Every query head still asks its own question (purple); only the stored keys and values (orange) shrink.
How much of the multi-head cache grouped-query attention keeps.
GQA cache = gh · multi-head cache
GQA keeps the fraction g/h of the full multi-head cache. The cache is proportional to the stored heads hkv in the cache-bytes card of Section 3.1, and GQA stores g key-value heads instead of all h, each shared by h/g query heads. g = h is ordinary multi-head attention and g = 1 is MQA; values in between trade cache size against quality.

Grouped-query attention in PyTorch, with 8 query heads sharing 2 key-value heads.

import torch
import torch.nn.functional as F

sdpa = F.scaled_dot_product_attention
B, T, d_k = 1, 16, 64
h, g = 8, 2  # query heads, KV heads
Q = torch.randn(B, h, T, d_k)
K = torch.randn(B, g, T, d_k)  # the cache stores g heads
V = torch.randn(B, g, T, d_k)

per_group = h // g
K_rep = K.repeat_interleave(per_group, dim=1)  # temporary
V_rep = V.repeat_interleave(per_group, dim=1)
out = sdpa(Q, K_rep, V_rep, is_causal=True)
print(out.shape)  # torch.Size([1, 8, 16, 64])
  1. 1h = 8 query heads and g = 2 key-value heads, as in Figure 6.
  2. 2Copies each stored head to its 4 query heads, the shared orange heads of Figure 6.
  3. 3Plain multi-head attention on the copies; only g heads were stored, the GQA card in Section 4, line 1.

Sharing trades a little quality for much faster decoding. The MQA paper timed an 8-head English to German translator with 211M weights on one TPUv2, a Google chip built for neural networks. The GQA paper took T5 XXL, Google's pretrained 11-billion-weight text model, averaged each group's key and value heads, trained 5% more steps and scored summarization, translation and question answering.

MQA paper, decode time per token, μsmulti-head46BLEU 26.7MQA3.8BLEU 26.51GQA paper, T5 XXL average score46.547.0MQA 46.6GQA-8 47.1multi-head 47.22
Figure 7. One shared key-value head decodes about 12 times faster for 0.2 BLEU (1); 8 groups land within 0.1 of multi-head (2).

5 Streaming models look back through a window

In online speech recognition (often trained with CTC loss, which needs no frame-by-frame labels) or live translation the model can't see the whole sequence: the future hasn't been spoken yet. Windowed attention lets each query look back only a fixed number of frames (time slices of the input, such as 10 ms of audio) and masks everything else to negative infinity. The cost then grows in a straight line with length.

The windowed mask for n frames and a window of w.

import torch

n, w = 10, 3  # frames, window
NEG_INF = float("-inf")
mask = torch.full((n, n), NEG_INF)  # added to the scores
for i in range(n):
    start = max(0, i - w + 1)  # oldest frame query i reads
    mask[i, start:i + 1] = 0
print(mask)
  1. 1Starts with every score blocked at negative infinity, so the softmax gives those frames weight 0.
  2. 2Opens the last w = 3 frames up to and including each query.

Size the window from the smallest unit you must capture (a phoneme, one distinct sound of a language; a gesture; a word), converted to frames.

Each layer's tokens already carry their window's context, so reach grows with depth, like a receptive field.

layer 2layer 1frames1 layer, 3 frames2 layers, 5 frames
Figure 8. The reach grows with depth: each extra layer adds another window of frames.

Chunked streaming emits output in fixed-size chunks, and every token may see its whole chunk, so with chunk size 4, counting from 0, token 9 can see tokens 10 and 11.

Which keys a query may read when the stream is cut into chunks.
j ≤ (⌊iC⌋ + 1) · C − 1
Query i may read every key up to the end of its own chunk, so it sees its whole chunk and all the past, never a later chunk. C is the chunk size; dividing i by C and rounding down (⌊ ⌋) gives i's chunk number, adding 1 and multiplying by C gives where the next chunk starts, and the −1 steps back to the last position of i's chunk. With C = 1 this is the plain causal mask; a larger C lets tokens see later tokens within the chunk.

Chunked streaming, token by token.

chunk size
Tokens arrived 
Tokens emitted 
may readmaskednot reached yetorange row numberquery still waiting for its chunk

Figure 9. A chunk's rows light up only when its last token arrives, so output lags input by up to one chunk.


6 When a Transformer is the wrong tool

Conformer puts convolution back beside attention for speech; Quantization and Pruning make a trained Transformer cheaper to serve, and Adapters fine-tune one without retraining all of it.

6.1 Four training and serving failures and their fixes

causesymptomfix
Post-LN without warmuploss diverges earlywarm up, or use Pre‑LN
a fully masked rowNaN spreads through trainingmask padding keys only
inputs past the trained lengthquality falls offALiBi, or train longer
a KV cache too bigout of memoryGQA or MQA
Figure 10. Four failures this series teaches, each linked to its page.

7 In practice, a fused kernel reads a growing cache

Generation runs once over the prompt, then once per token against the cache.

import torch
import torch.nn.functional as F

sdpa = F.scaled_dot_product_attention
B, h, h_kv, T, d_k = 1, 8, 2, 16, 64
q = torch.randn(B, h, T, d_k)
K = torch.randn(B, h_kv, T, d_k)  # cache after the prompt
V = torch.randn(B, h_kv, T, d_k)
prompt_out = sdpa(q, K, V, is_causal=True, enable_gqa=True)

q_new = torch.randn(B, h, 1, d_k)
k_new, v_new = torch.randn(2, B, h_kv, 1, d_k)
K = torch.cat([K, k_new], dim=2)  # cache grows by one
V = torch.cat([V, v_new], dim=2)
step_out = sdpa(q_new, K, V, enable_gqa=True)
print(step_out.shape)  # torch.Size([1, 8, 1, 64])
  1. 1The prompt in one causal call fills the cache (prefill), the blocks of Figure 5.
  2. 2One decode step adds one key and value and attends from one query, the KV card in Section 3, line 1.
FlashAttentionEvery attention call, forward and backward; same output, and it keeps only each row's softmax maximum and sum (two numbers per row, so this memory grows only linearly with n) for the backward pass.
KV cacheLives through generation; 2·L·hkv·dk·T·b bytes.
MQA, GQAFixed in the architecture; the cache shrinks by h/g.
StreamingEach chunk of C frames as it arrives; earlier chunks stay readable.
KernelPyTorch picks FlashAttention or another kernel; FlashAttention needs fp16 or bf16, the two 16-bit number formats.
CacheHugging Face's default DynamicCache grows per token; cache_implementation="static" preallocates.
Cache length TGrows the bytes card linearly, one key and value per layer per token. Longer remembers more and costs more; capped by the trained context, 2048 for GPT-3.
Groups gKV heads; the cache keeps g/h of full size. More approach multi-head quality, fewer MQA speed; 1 is MQA, h is multi-head. GQA chose 8.
Block sizeK and V rows per tile, Bc = M/4dk for on-chip memory M. Larger tiles cut HBM traffic and run faster up to about 256 rows. Past the on-chip memory they no longer fit.
Chunk size CSets which keys a query may read, the chunk card in Section 5. Larger lets each token see more of its own chunk and adds up to one chunk of delay; smaller cuts the delay. At 1 it is the plain causal mask; at the full length it is ordinary attention with no streaming. No library default; set it from the delay the application allows.
Window wFrames each query looks back over, in the windowed mask of Section 5. Wider sees more context per layer at w scores per query; narrower is cheaper and needs more layers to reach far. At 1 a token sees only itself; at the full length it is plain causal attention. Mistral 7B (Jiang et al. 2023) uses 4096 tokens.
CodeF.scaled_dot_product_attention (PyTorch docs), flash-attention (Dao-AILab), Cache strategies (Hugging Face).

8 References

  1. Vaswani, A., et al. (2017). Attention Is All You Need. NeurIPS 2017. arXiv:1706.03762.
  2. Dosovitskiy, A., et al. (2021). An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale. ICLR 2021. arXiv:2010.11929.
  3. Brown, T. B., et al. (2020). Language Models are Few-Shot Learners. NeurIPS 2020. arXiv:2005.14165.
  4. Shazeer, N. (2019). Fast Transformer Decoding: One Write-Head is All You Need. arXiv:1911.02150.
  5. Dao, T., et al. (2022). FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness. arXiv:2205.14135.
  6. Jiang, A. Q., et al. (2023). Mistral 7B. arXiv:2310.06825.
  7. Ainslie, J., et al. (2023). GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints. arXiv:2305.13245.