Problem
A decoder-only model generates by running itself over and over, appending the chosen token each time (see decoding). Naively, step t runs the whole network on all t tokens, so writing n tokens costs about n²/2 token-positions of work. On the page's four-token example that is 10 key/value computations where only 4 were really new.
The waste is avoidable because of causality: the keys and values of token 3 depend only on tokens 1 to 3, so they never change when token 4 arrives. Keep them and the cost of a step no longer grows with the work already done. The price is memory, and for long conversations that price is large.
Intuition
Imagine taking notes in a meeting. To answer a question about what was said, you do not replay the recording from the start; you look at your notes. Each attention layer keeps two notes per past token: a key (what this token is about, used for matching) and a value (what it contributes if matched). A new token writes its own two notes, then reads all the notes to decide what to attend to. Nothing already written is edited.
Generation therefore has two phases. Prefill reads the whole prompt at once and fills the cache, with many tokens processed in parallel, which keeps the hardware busy. Decode then adds one token at a time, and each step must read the entire cache but does little arithmetic with it. That makes decode limited by memory bandwidth, not by compute.
Prefill is compute-heavy and parallel over the prompt; decode is a sequential loop over the cache.
Mechanics
What is cached, and why only K and V
In each layer, token i produces a query qi, key ki and value vi
(see attention). Its output is
softmax(qi·K≤iT/√d) V≤i. For a new token only its own
query is needed: past queries were used once, for the past tokens' outputs, and are never read again. Past
keys and values are read by every later token, so they are the things to store. The causal
mask is also free: the cache only contains earlier positions, so nothing needs masking at decode time. The
cache has one slot per layer, per token, per K/V head: shape
[layers, 2, B, Hkv, S, D] with S the length so far.
How big the cache is
Per token the cache holds 2 (K and V) × layers × Hkv × head width × bytes per value. For an 8B-class configuration with 32 layers, 32 query heads, 8 key/value heads, head width 128 and 2-byte values (fp16 or bf16) that is 131,072 bytes, 128 KiB, per token. An 8,192-token context costs 1 GiB for one sequence, and a batch of 32 such sequences costs 32 GiB, comparable to the weights of the model itself.
Multi-query and grouped-query attention
Cache size is proportional to the number of key/value heads, so share them. Multi-query attention
(MQA) keeps all the query heads but a single K/V head. Grouped-query attention (GQA) is the
middle: query heads are split into groups, each group sharing one K/V head. With
32 query heads and 8 K/V heads, every K/V head serves
4 queries. In PyTorch,
F.scaled_dot_product_attention(..., enable_gqa=True) does this by repeating each key and value head
to match the query heads
(lines 6442-6444 of the reference code in its docstring),
so the saving is in what you store, not in the attention arithmetic.
Sharing the cache across layers, and storing it in 4 bits
The per-token size is a product, 2 × layers × Hkv × head width × bits, and GQA attacks only one factor. The others can shrink too. Cross-layer attention (CLA, Brandon et al. 2024) lets a group of adjacent layers share one set of keys and values: only the first layer in the group projects and caches K and V, and the others attend to them with their own queries. Sharing across pairs halves the cache again, on top of GQA. You Only Cache Once (YOCO, Sun et al. 2024) takes it to the limit: the bottom half of the network is a self-decoder with cheap local attention, and the middle layer's output is projected once into a single global K/V that every upper layer cross-attends to. The upper layers no longer need the prompt run through them to build their own caches, so prefill does roughly half the work as well.
DeepSeek-V4.1-Flash (2026) combines all three axes. Its report frames them as entry size (GQA, latent attention), sequence (keeping one compressed entry per m tokens) and layer (cache reuse). It splits its 40 layers into a causal encoder-decoder after YOCO: the 20 decoder layers project their global K/V from the hidden state at layer 20, so a prompt only passes through the encoder half to fill them. Inside each half, most layers reuse a neighbour's work: a Full layer computes its own compressed K/V, sparse-attention indexer keys and top-k selection, a Reindex layer keeps the K/V but picks its own top-k, and a Reuse layer takes both. Finally the main cache is stored in FP4: E2M1 values (sign, 2 exponent bits, 1 mantissa bit, so magnitudes 0 to 6) with one E4M3 scale shared by every 16 channels, 4.5 bits per value. The values are quantized after rotary embeddings and the model is trained for it with quantization-aware training; the small sliding-window cache stays in FP8. The report gives about 890 bytes of global K/V per token, a quarter of its predecessor's.
Paged attention
A serving system holds many sequences of different, unknown final lengths. Reserving one contiguous region of the maximum length for each wastes most of it, and the leftovers fragment memory. Paged attention (the idea behind vLLM) stores each sequence's cache in fixed-size blocks of tokens that need not be adjacent, with a per-sequence block table mapping positions to blocks, as an operating system pages virtual memory. A sequence wastes at most the unused tail of its last block, and blocks can be shared between sequences with a common prefix.
| Component | Shape/Params | Meaning | Source |
|---|---|---|---|
| Key cache (per layer) | [B, Hkv, S, 128] |
Keys of all tokens so far; grows by one row per decode step. | sdpa reference code (key argument) |
| Value cache (per layer) | [B, Hkv, S, 128] |
Values of all tokens so far. | sdpa reference code (value argument) |
| Query at decode | [B, Hq, 1, 128] |
L = 1 against S cached keys; is_causal=True would align the mask to the upper-left, so omit it and pass no mask. |
functional.py |
enable_gqa=True |
Hq divisible by Hkv | Repeats K and V heads to match the query heads. | lines 6442-6444 |
nn.MultiheadAttention |
kdim, vdim, no cache argument |
Recomputes keys and values from its inputs every call; the module has no cache of its own, so a cache must be kept outside it. | activation.py |
| Block table (paged) | [sequences, max_blocks] ints |
Maps a sequence's block number to a physical block. Not part of PyTorch; defined by the serving engine. | Kwon et al., 2023 (see references) |
Worked example
Cached decoding gives the same answer
Take the four-token example from the attention page. Decode it step by step: at step t, append key and value t to the cache, and attend query t to the cache. The outputs are [1.0, 0.0], [0.3302, 0.6698], [0.2321, 0.9546], [0.3719, 0.3719], and they differ from full causal attention over all four tokens by at most 0e+00, i.e. zero. The work differs: the cache computes 4 key/value pairs in total, the recompute version 10. At length n these are n and n(n+1)/2.
Cache memory for 32 layers, 8,192 tokens
| Variant | K/V heads | Bytes per token | 8,192 tokens (GiB) | Batch of 32 (GiB) |
|---|---|---|---|---|
| MHA | 32 | 524,288 | 4 | 128 |
| GQA | 8 | 131,072 | 1 | 32 |
| MQA | 1 | 16,384 | 0.12 | 4 |
Why decode is memory-bound: each step reads the cache once, 2·S·Hkv·D·b bytes per layer, and does about 4·S·Hq·D floating-point operations on it (a multiply-add for each of the scores and the weighted sum). The ratio, in FLOPs per byte, is 2·Hq/(Hkv·b), independent of S: 1 for MHA with 2-byte values, 4 for this GQA and 32 for MQA. A modern GPU can do on the order of hundreds of FLOPs per byte it reads, so all three are far below that and the memory system is the limit; sharing K/V heads both shrinks the cache and raises the ratio.
Multiplying the savings
Start from the same 32-layer configuration and apply one factor at a time:
| Step | K/V heads | Layers per cache | Bits per value | Bytes per token | × smaller than MHA |
|---|---|---|---|---|---|
| Multi-head attention, 16-bit | 32 | 1 | 16 | 524,288 | 1.0 |
| Grouped-query attention | 8 | 1 | 16 | 131,072 | 4.0 |
| Share each K/V across 2 layers | 8 | 2 | 16 | 65,536 | 8.0 |
| Store values in FP8 | 8 | 2 | 8 | 32,768 | 16.0 |
| FP4 values, one 8-bit scale per 16 | 8 | 2 | 4.5 | 18,432 | 28.4 |
The scales are why FP4 costs 4.5 bits, not 4: every 16 values carry one 8-bit scale. Quantizing one block of 16 values whose largest magnitude is 1.95: the scale is 1.95/6 rounded to E4M3, 0.3125, and each value divided by it snaps to the nearest of ±0, 0.5, 1, 1.5, 2, 3, 4 or 6. The codes are 0.5, -3.0, 1.5, 6.0, 0.0, 2.0, -4.0, 1.0, 3.0, -1.5, 0.0, 4.0, -0.5, 2.0, -3.0, 1.0. Multiplied back, the largest error is 0.15 and the mean 0.054; the block takes 72 bits instead of 256 in bf16. The scale's own range caps what a block can hold at 448 × 6 = 2,688, and the coarse grid is why the model is trained with quantization in the loop rather than quantized afterwards.
Paged versus contiguous
Four sequences have 37, 120, 5, 64 tokens so far, 226 in all. Reserving 128 slots each takes 512 slots and wastes 286 (56%). With blocks of 16 tokens the sequences take 3, 8, 1, 4 blocks, 256 slots in all, wasting 30 (12%).
Implementation
import torch
import torch.nn.functional as F
class CachedAttention(torch.nn.Module):
def __init__(self, d, q_heads, kv_heads):
super().__init__()
self.hq, self.hkv, self.dh = q_heads, kv_heads, d // q_heads
self.q = torch.nn.Linear(d, q_heads * self.dh, bias=False)
self.k = torch.nn.Linear(d, kv_heads * self.dh, bias=False)
self.v = torch.nn.Linear(d, kv_heads * self.dh, bias=False)
self.o = torch.nn.Linear(d, d, bias=False)
def forward(self, x, cache=None): # x: [B, T, d]; T = prompt length, then 1
B, T, _ = x.shape
q = self.q(x).view(B, T, self.hq, self.dh).transpose(1, 2)
k = self.k(x).view(B, T, self.hkv, self.dh).transpose(1, 2)
v = self.v(x).view(B, T, self.hkv, self.dh).transpose(1, 2)
if cache is not None: # append, never rewrite
k = torch.cat([cache[0], k], dim=2)
v = torch.cat([cache[1], v], dim=2)
out = F.scaled_dot_product_attention(
q, k, v, is_causal=(cache is None), # prefill: causal; decode: T == 1
enable_gqa=self.hq != self.hkv,
)
out = out.transpose(1, 2).reshape(B, T, -1)
return self.o(out), (k, v)
Prefill calls it with the whole prompt and no cache, then each decode step passes the previous
(k, v) back with one new token. torch.cat copies the whole cache every step; real systems
preallocate a buffer and write into it (or use blocks, as above). Rotary positions, if the model uses them, are applied
to q and k before k is cached, so the cache holds already-rotated keys.
A decode step with T = 1 needs no mask, because the cache holds only earlier positions.
Tradeoffs
- Speed versus memory. The cache turns quadratic recompute into linear work but its size is linear in context and batch, and often decides how many requests fit on a GPU.
- MQA and GQA shrink the cache by Hq/Hkv at some loss in quality that grows as the K/V heads shrink; GQA is the usual compromise. A model must be trained with the choice (or converted and fine-tuned), since the projections differ.
- Precision. Storing the cache in 8-bit or lower cuts memory again, trading accuracy; see quantization. At 4 bits the per-block scales are essential and the model generally has to be trained for it.
- Sharing across layers (CLA, YOCO-style encoder-decoders) divides the cache by the sharing factor and, for YOCO, roughly halves prefill, but the layers that read a shared cache lose their own view of the prefix, which costs quality, and the architecture must be chosen before pre-training.
- Paging improves utilisation and allows prefix sharing, at the cost of a more complex attention kernel that follows the block table.
- Pitfalls. Passing
is_causal=Truewith a one-token query and a longer key sequence aligns the mask to the wrong corner (the attention page covers the two alignments). Off-by-one position ids when resuming from a cache silently corrupt outputs. And a cache is tied to the exact prefix: change an earlier token and every later entry is stale.
Check yourself
Why cache keys and values but not queries?
A token's query is used once, to compute its own output. Later tokens attend to earlier keys and values, not earlier queries.
The per-token cache is 2 × layers × Hkv × D × bytes. Which factor does GQA change?
Hkv, from 32 to 8 here, a 4× saving.
Why is decode memory-bound but prefill is not?
Prefill reuses each loaded weight and cache entry across many tokens at once; decode reads the whole cache to process a single token.
Connections
Read first
- Attention core
- Decoding and sampling core
Read next
References
- Shazeer. Fast Transformer Decoding: One Write-Head is All You Need (multi-query attention). 2019 (arXiv 1911.02150).
- Ainslie et al. GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints. EMNLP, 2023 (arXiv 2305.13245).
- Kwon et al. Efficient Memory Management for Large Language Model Serving with PagedAttention. SOSP, 2023 (arXiv 2309.06180).
- Brandon, Mishra, Nrusimha, Panda and Ragan-Kelley. Reducing Transformer Key-Value Cache Size with Cross-Layer Attention. NeurIPS, 2024 (arXiv 2405.12981).
- Sun, Dong, Zhu, Huang, Wang, Ma, Zhang, Wang and Wei. You Only Cache Once: Decoder-Decoder Architectures for Language Models. NeurIPS, 2024 (arXiv 2405.05254).
- DeepSeek-AI. DeepSeek-V4.1-Flash: Pushing the Limits of KV Cache Compression. 2026 (technical report).
- Vaswani et al. Attention Is All You Need. NeurIPS, 2017 (arXiv 1706.03762).
- PyTorch v2.15.0:
scaled_dot_product_attention,MultiheadAttention.