A cache you keep because attention looks backwards
Generation is a loop. The model has produced tokens and must produce the -th. Self-attention at that position needs a query from the current token and a key and value from every position up to it:
with the causal mask. The naive loop recomputes and for the whole prefix at every step. That is pure waste, and the reason is worth stating precisely: the key and value at position are functions of the hidden state at position alone,
and causality guarantees never changes once produced. Nothing to the right can influence it. So and are constants for the rest of the sequence, and recomputing them is recomputing a value you already have.
Cache them and the per-step cost collapses. Generating tokens goes from
in attention FLOPs, and the projections , go from to . This is not a marginal optimisation — without it, long-context generation is not merely slow, it is infeasible. Every production inference stack does it, which is why the interesting question is never whether to cache but what the cache then costs you.
The trade you just made
You converted a compute problem into a memory problem, and memory is the resource that scales worst. The cache grows linearly in sequence length and linearly in batch size, it is written once and read on every subsequent step, and it cannot be recomputed cheaply if you drop it. Everything in the remaining eleven sections is a consequence of those three facts.
Two tensors per layer, and the arithmetic that follows
For one sequence the cache is a pair of tensors per layer, each of shape — number of key/value heads, positions so far, head dimension. Queries are not cached: the query for step is used once and discarded. Values are, because the weighted sum at every future step needs all of them.
The size formula
Counting elements: two tensors, layers, heads, per head, bytes per element, sequences of length :
Everything except and is fixed at architecture time. The per-token cost
is therefore a property of the model you can read straight off its config, and it is the number worth memorising for any model you deploy. For Llama-3.1-70B — 80 layers, 8 KV heads, , bf16:
Note what is absent. The number of attention heads does not appear — only . That gap is the entire subject of §05. Nor does the model's parameter count: a wide, shallow model and a narrow, deep one with the same have identical cache costs and very different weights.
Where the memory actually goes
Weights are a fixed cost you pay once per GPU. The cache is a variable cost you pay per concurrent user per token, and it is the one that decides how many of them you can serve. Below, real configurations read from their published config.json.
| Model | Attn | Layers | KV heads | d_h | Bytes/token | KiB/token | Cache @128k | Weights |
|---|---|---|---|---|---|---|---|---|
| Multi-head attention — one KV head per query head | ||||||||
| GPT-2 XL | MHA | 48 | 25 | 64 | 307,200 | 300.0 | 37.5 GiB | 3.0 GB |
| Llama-2-7B | MHA | 32 | 32 | 128 | 524,288 | 512.0 | 64.0 GiB | 13.4 GB |
| Grouped-query attention — query heads share a KV head | ||||||||
| Llama-3.1-8B | GQA-4 | 32 | 8 | 128 | 131,072 | 128.0 | 16.0 GiB | 16.0 GB |
| Mistral-7B-v0.3 | GQA-4 | 32 | 8 | 128 | 131,072 | 128.0 | 16.0 GiB | 14.4 GB |
| Llama-3.1-70B | GQA-8 | 80 | 8 | 128 | 327,680 | 320.0 | 40.0 GiB | 141.2 GB |
| Qwen2.5-72B | GQA-8 | 80 | 8 | 128 | 327,680 | 320.0 | 40.0 GiB | 145.4 GB |
| Multi-head latent attention — one compressed latent per layer | ||||||||
| DeepSeek-V3 | MLA | 61 | — | 576* | 70,272 | 68.6 | 8.6 GiB | 1342 GB |
| ↳ same geometry as MHA | MHA | 61 | 128 | 320 | 4,997,120 | 4880.0 | 610.0 GiB | — |
* MLA stores a single latent vector of 512 dims plus a 64-dim shared positional key per layer, not a per-head K and V — see §06. Weights are parameter count × 2 bytes. Cache @128k is one sequence at the model's full context window. Configurations read from each model's published config.json; GPT-2 XL's 128k figure is hypothetical, since its window is 1,024.
The crossover nobody budgets for
Llama-3.1-70B in bf16 is 141 GB of weights — already two H100s. Its cache costs 320 KiB per token, so:
until KV = weights
KV cache
at full context
for that batch alone
A batch of 32 users each holding a 128k context needs more HBM for their conversation history than sixteen H100s contain, before a single weight is loaded. This is why context length is sold in tiers and why long contexts cost more per token than short ones — you are not paying for compute, you are renting HBM.
Interactive
Work out your own budget
Every figure recomputes from the formula in §02. Presets are the real configurations from the table above.
Configuration
Memory
| Quantity | Value | Share of HBM |
|---|
Weights are estimated from the preset's published parameter count at the chosen precision and are held at bf16 regardless of the cache setting, since KV quantisation is independent of weight quantisation. Values use binary units: 1 GiB = 2³⁰ bytes.
Why decoding wastes almost all of your FLOPs
The cache is not only a capacity problem. It is a bandwidth problem, and this is the part that surprises people who size clusters by TFLOPs.
Take one decode step, one layer, batch , context . The attention over the cache reads
and performs a and an , each FLOPs:
The ratio is the arithmetic intensity — how much work you extract per byte moved. Note that , and all cancel:
This is a remarkable little result. The arithmetic intensity of decode attention is independent of context length and of batch size. You cannot batch your way out of it. It depends only on the query-to-KV head ratio and the bytes per element — which is to say, on two architectural decisions and a quantisation choice.
| Configuration | bf16 | fp8 | vs ridge |
|---|---|---|---|
| MHA (n_h/n_kv = 1) | 1.0 | 2.0 | 295× under |
| GQA-4 | 4.0 | 8.0 | 74× under |
| GQA-8 | 8.0 | 16.0 | 37× under |
| MQA (n_kv = 1) | 32.0 | 64.0 | 9× under |
| H100 ridge point | 295 | — | — |
FLOP per byte of KV read. Even MQA in fp8 runs below the ridge, so decode attention is memory-bound on every configuration in current use. The practical reading: a faster GPU with the same bandwidth buys you nothing here.
The batch-1 floor
Weights make the point even more starkly. Every decode step must stream the whole model from HBM at least once. For 70B in bf16 that is 141 GB, so
— about 24 tokens/second, on hardware capable of 989 TFLOP/s, doing roughly 140 GFLOPs of useful work. Utilisation is around 0.3%. Batching is what recovers it, because the weight read amortises across the batch while each sequence contributes its own FLOPs — but the KV cache read does not amortise, which is why throughput stops improving once the cache dominates the byte traffic.
Prefill and decode are different machines
| Prefill | Decode | |
|---|---|---|
| Shape of work | GEMM — tokens at once | GEMV — one token |
| Bound by | Compute | Memory bandwidth |
| Cache traffic | Write columns | Read all , write 1 |
| Cost model | in attention | per token |
| Scales with batch | Poorly — already saturated | Well, until KV dominates |
| User metric | Time to first token | Inter-token latency |
Two workloads with opposite bottlenecks sharing one GPU is a scheduling problem, and a badly-behaved one: a long prefill blocks every decode behind it, producing the latency spikes users describe as stuttering. Chunked prefill interleaves them; disaggregation (§12) puts them on separate hardware entirely.
Fewer KV heads, held at the same quality
appears linearly in both the size formula and the intensity formula, which makes it the highest-leverage number in the architecture. The obvious move is to shrink it — and the obvious risk is that you lose the representational diversity that made multi-head attention work.
GQA partitions query heads into groups sharing one KV head each, so and the cache shrinks by . The compression is exact and structural — there is no approximation in the attention itself, only fewer distinct keys and values to attend to. Crucially, it also raises arithmetic intensity by the same factor, so GQA buys capacity and speed:
Models are not usually trained from scratch as GQA. The standard recipe is uptraining: take an MHA checkpoint, mean-pool the key and value projections within each group, then continue training on a small fraction — around 5% — of the original budget. The mean-pool matters; picking one head per group and discarding the rest is markedly worse.
Caching a compressed latent instead of the keys
GQA removes heads. Multi-head Latent Attention removes the representation: it caches a single low-rank vector per token per layer and reconstructs every head's key and value from it on the fly. This is what lets DeepSeek-V3 — a 671B model — hold a smaller cache than an 8B Llama.
Project the hidden state down to a latent of dimension , and cache only that:
The obvious objection is that you have traded memory for compute — you now decompress keys and values on every step. The trick is that you never have to. Inside the score, the up-projection can be folded into the query projection ahead of time:
so attention scores are computed directly against the cached latents. is a fixed weight matrix; it never needs to materialise a single key. The same absorption works on the value side into the output projection. This is the whole idea, and it is why MLA is not simply "compression with a decompression cost".
The numbers for DeepSeek-V3
61 layers, , plus a 64-dimensional shared positional key (§07). Per token:
Against the same head geometry as plain MHA — 128 heads, , :
a reduction of 71×, or 98.6%. The cache stops scaling with head count altogether — it scales with a rank you choose.
equivalent MHA
671B parameters
Llama-3.1-70B
context
The comparison worth sitting with: DeepSeek-V3 has 9.5× the parameters of Llama-3.1-70B and a fifth of the cache. Cache cost and model capacity have been decoupled — which is exactly the move Engram makes for knowledge storage, one report over.
What it costs
- Absorption inflates the query path. is computed per step per head; you have moved work out of memory and into compute, which is the correct direction on current hardware but is not free.
- It is not a drop-in. GQA can be uptrained from an MHA checkpoint in ~5% of the original compute. MLA changes the attention algebra, and converting existing checkpoints to it is an active research problem rather than a recipe.
- Kernels are scarcer. GQA is supported everywhere. MLA needs implementations that understand absorption, and a naive implementation that materialises keys throws away the entire benefit.
- RoPE does not survive the compression — which needs its own section.
Rotary embeddings bake position into the key
RoPE applies a position-dependent rotation to queries and keys before the dot product. With the rotation for position ,
which is the property that makes it work: the score depends only on the relative offset . But it also means the tensor you cache is , not — position is already inside the stored value. Two consequences follow, and both are practical.
Cached prefixes are position-locked
A cached block of keys is only valid at the offset it was computed at. Reuse a system prompt at position 0 across requests and it is fine (§09); try to splice a cached document into the middle of a different prompt and the rotations are wrong. Doing it properly means either re-rotating — cheap, but you must have kept the unrotated keys — or accepting the mismatch, which degrades quality in ways that are hard to detect from a benchmark.
It breaks MLA's absorption
The absorption in §06 needs to commute out of the dot product. Insert a position-dependent rotation between and and it no longer can: sits in the middle, and differs for every pair of positions, so nothing can be precomputed.
Decoupled RoPE
DeepSeek's fix is to split the key in two. Most of it is compressed and carries no position; a small separate piece carries the rotation and is not compressed. Concatenate them and attend to both:
The compressed half keeps the absorption trick; the rotary half is small enough to cache directly, and — the detail that makes the accounting work — it is shared across all heads. So the per-token cost is with , rather than . Sixty-four dimensions buy back positional information for 128 heads at once.
This is the kind of design that only appears when the memory system is treated as a first-class constraint rather than an implementation detail. It costs 11% of the per-token cache and removes the one obstacle to the other 89%.
The allocator, not the algorithm
Everything above concerns how much cache a token needs. This section is about how much a naive serving system wastes, which through 2023 was most of it.
The obvious implementation reserves a contiguous buffer per sequence at its maximum possible length, because the cache grows and reallocation is expensive. That single decision produces three separate wastes:
- Internal fragmentation. A request that could emit 2,048 tokens but stops at 200 holds a 2,048-token buffer for its lifetime. Most of the reservation is never touched.
- Reservation waste. Slots allocated for tokens not yet generated are unusable by anyone else, even though the memory is provably idle right now.
- External fragmentation. Variable-length contiguous buffers leave gaps too small for the next request, exactly like a filesystem.
Measured across real serving traces, systems built this way used roughly 20–40% of the KV memory they held. PagedAttention's contribution was to notice that this is a problem operating systems solved in the 1960s, and to apply the same answer: stop requiring contiguity.
The waste bound is worth writing down. With block size , each sequence wastes at most slots in its final block, so for sequences of mean length :
At and that is under 3%. The block size is a real trade-off rather than a free parameter: smaller blocks waste less but lengthen the block table and add indirection per attention step; larger blocks amortise the lookup but waste more and make prefix sharing coarser.
The second gain is the one that compounds. Because blocks are indirected, two sequences with a common prefix can point their block tables at the same physical blocks, with copy-on-write when they diverge. Parallel sampling, beam search and shared system prompts all stop duplicating memory. That is the bridge to §09.
The cheapest cache is one you already filled
Prefill is and dominates time-to-first-token on long prompts. But real traffic repeats itself heavily: a fixed system prompt on every request, the same few-shot examples, the same document re-queried, an agent loop replaying its entire history each turn. If a prefix has been computed before, its keys and values are still valid — position is baked in (§07), but a prefix starts at position 0 by definition, so the rotations are correct.
Radix trees over the cache
Organise cached blocks as a radix tree keyed on token sequence. A new request walks the tree, matches the longest prefix already present, and prefills only the remainder. Nodes are reference-counted and evicted LRU. Matching is on exact token IDs — a single differing token forks the tree at that point, which is why prompt templates should put variable content last.
With a shared prefix of tokens and new ones, prefill attention drops from to — and the quadratic term vanishes for the part you skipped.
When it pays
| Workload | Shared prefix | TTFT gain |
|---|---|---|
| Fixed system prompt | High | Large |
| Few-shot classification | Very high | Large |
| Multi-turn chat | Grows per turn | Large |
| Agent / tool loops | Nearly total | Very large |
| Document Q&A, one doc | High | Large |
| Independent user prompts | None | None |
Note the fourth row. An agent that replays its whole transcript each turn looks pathological until you realise every turn shares all but the last few hundred tokens with the previous one — reuse turns the worst-shaped workload into the best-cached one.
The security caveat. A cache shared across users is a side channel. If a hit is measurably faster than a miss, an attacker can test whether a particular prefix is already cached and learn what other users have sent. Production systems isolate the cache per tenant, or accept the leak knowingly.
Fewer bits per number
sits in the size formula as a plain multiplier, so halving it halves the cache and doubles arithmetic intensity. fp16 → fp8 is close to free on modern models and is the default in most serving stacks. Below that it stops being free, and the reason is structural rather than numerical.
Keys and values are not alike
Key vectors have persistent outlier channels — specific coordinates that are large across essentially every token, an artefact of how attention learns to route. Quantise a key per-token, and those coordinates set the scale for the whole vector, crushing everything else to a few levels.
Value vectors do not show that structure; their outliers are scattered per-token instead. So the two need opposite treatments — quantise keys per-channel, along the axis the outliers live on, and values per-token. This asymmetry is the core observation behind KIVI and most low-bit KV work since.
Rotate the problem away
A newer line of work attacks the outliers directly: apply a random orthogonal rotation to each key and value before quantising. Rotation preserves the dot products attention depends on while spreading variance evenly across coordinates, so no single channel dominates the scale.
PolarQuant (AISTATS 2026) reports ~6× memory reduction and up to 8× faster attention at 3 bits on H100, with no training or calibration — the rotation is data-independent. Work pushing toward 1-bit exists; it is not yet something to deploy without measuring your own task.
| Precision | Bytes/elt | Cache vs bf16 | Intensity, GQA-8 | Practical status |
|---|---|---|---|---|
| bf16 / fp16 | 2 | 1.00× | 8.0 | Baseline |
| fp8 (E4M3) | 1 | 0.50× | 16.0 | Widely deployed, near-lossless |
| int4, per-channel K | 0.5 | 0.25× | 32.0 | Viable with the right grouping |
| 3-bit, rotated | 0.375 | 0.19× | 42.7 | Recent, promising |
| 1–2 bit | ≤0.25 | ≤0.13× | ≥64 | Research |
Intensity assumes the geometry of Llama-3.1-70B; the H100 ridge point is 295, so even 3-bit KV remains memory-bound. Quantisation buys capacity first and speed second. Errors accumulate: a quantised key written at position 10 is re-read for every subsequent token, so per-token error is not independent — this is why long-generation quality degrades more than a short-prompt benchmark suggests.
Keeping fewer tokens
Every technique so far stores all tokens more cheaply. The alternative is to stop storing some of them — turning a linear cost into a bounded one, at the price of information you cannot recover.
Sliding window
Keep the last w
Cache is capped at tokens regardless of context. Simple, and trained into the model rather than bolted on. Information beyond the window survives only through the residual stream, layer by layer.
Attention sinks
Keep the first few too
A pure sliding window collapses the moment the very first tokens are evicted. StreamingLLM's finding: models dump excess attention mass on the first few positions regardless of content. Retain 4 of them alongside the window and stability returns.
Heavy hitters
Keep what got attended to
Accumulated attention scores are heavily skewed — a small fraction of tokens receive most of the mass. H2O and SnapKV score and evict the rest, either continuously or once at the end of prefill.
The honest framing is that these are lossy and the loss is workload-dependent. Eviction policies are scored on benchmarks where the dropped tokens turned out not to matter; needle-in-a-haystack retrieval is precisely the task where a heavy-hitter policy discards the needle, because it was never attended to until the question arrived. A policy chosen on summarisation can fail badly on retrieval, and the failure is silent.
Interleaving is the current compromise: a few full-attention layers among many local-attention ones keeps a path for long-range information while capping most of the cache. It is an architectural decision made at training time, not a serving switch.
Moving it off the GPU, and what is still unsolved
Offload has a bandwidth budget
HBM is the only memory fast enough to be read every step, but cold sequences need not occupy it. Offloading to host DRAM over PCIe Gen5 ×16 gives roughly 64 GB/s against HBM's 3,350 — 52× slower. The arithmetic is unforgiving:
A 32k context of Llama-3.1-70B is 10 GiB, so a round trip over PCIe costs about 160 ms — acceptable to resume a paused conversation, ruinous inside a decode loop. Offload is a swap tier, not a capacity extension.
Disaggregated prefill and decode
Since §04's two phases have opposite bottlenecks, run them on different machines: compute-dense hardware for prefill, bandwidth-dense for decode, with the cache shipped between them. Each pool then scales on its own metric.
The cost is that the cache is now a network payload. The same 10 GiB moved over 400 Gb/s Ethernet is ~200 ms — so the transfer must overlap with computation, layer by layer, or you have moved the bottleneck rather than removed it.
What is genuinely unresolved
- No agreed evaluation for lossy caching. Eviction and low-bit quantisation are reported on benchmarks chosen by their authors. There is no standard suite that stresses the failure modes — long-range retrieval, multi-hop reasoning over the discarded region, degradation over very long generations.
- Compounding is under-studied. GQA and fp8 and paging and prefix reuse compose cleanly. Quantisation plus eviction plus a low-rank latent may not, and almost nothing reports the interaction.
- MLA has no conversion recipe. GQA's uptraining path is what made it universal. Until an MHA or GQA checkpoint can be converted to MLA cheaply and reliably, its 71× stays available only to those training from scratch.
- Position remains the obstacle to real reuse. RoPE means only true prefixes are shareable. Caching arbitrary fragments — a document usable at any offset in any prompt — needs either re-rotation with unrotated keys kept, or a positional scheme that separates content from place.
- The bandwidth gap is widening. Compute per generation has grown faster than memory bandwidth for a decade. Every year the ridge point moves right and decoding falls further below it, which means architectural cache reduction keeps getting more valuable relative to faster arithmetic.
The short version
The KV cache is where a transformer's inference cost actually lives. It is linear in context and batch, it is re-read in full on every token, and it makes decoding memory-bound by construction — not by implementation quality. The fixes stack in a definite order: architecture first (GQA or MLA, decided at training time and worth 4–71×), then the allocator (paging and prefix reuse, worth ~3× in effective capacity for no quality cost), then precision (fp8 for free, lower with measurement), and eviction last, because it is the only one that throws information away.