Chapter 6 · Section 6.5 · 45 min

The KV cache and fast generation

Here is the generation loop from §6.4, stripped to its core. Run the model on everything so far, take the last position's logits, pick a token, append it, repeat. It's correct, and it is wildly wasteful. To produce token 1,000 it runs the model on 999 tokens, and to produce token 1,001 it runs it on 1,000, recomputing 999 rows it computed one step earlier.

Here's the puzzle. Of everything the model computes for the 1,000-token prefix, all but one row is identical to what it computed for the 999-token prefix. Which row is new, and what is the least we need to remember from earlier steps to compute it? The answer is the KV cache, the single most important optimization in LLM inference. Every serving system you'll ever use is organized around it, and its size is what limits how many users one GPU can serve at once.

Why naive generation is quadratic

Let NN be the number of parameters that every token is multiplied through (the blocks' matrices plus the output head, but not the embedding lookup, which is free), LL the number of layers, and dd the width. A forward pass over tt tokens costs about 2Nt2Nt floating-point operations for the matrix multiplies (one multiply and one add per parameter per token, as in §5.6), plus attention, where each of the tt queries is compared with up to tt keys and mixes up to tt values. With a causal mask that's about 2Ldt22Ldt^2 more.

The naive loop runs that forward pass at every step. Generating nn tokens from a short prompt therefore costs

Cnaive≈∑t=1n(2Nt+2Ldt2)≈Nn2+23Ld n3.C_{\text{naive}} \approx \sum_{t=1}^{n}\big(2Nt + 2Ldt^2\big) \approx N n^2 + \tfrac{2}{3} L d\, n^3 .

In plain words, doubling the length of the output quadruples the matmul work and multiplies the attention work by eight. Generating a 1,000-token answer redoes, in total, about five hundred times the work of reading a 1,000-token prompt once.

What changes when you append a token

Here's the observation that fixes it. In a causal model, the hidden state at position jj depends only on tokens 0,…,j0, \dots, j. That's true at the input, where xj0=Ewj,:+Pj,:\mathbf{x}^0_j = \mathbf{E}_{w_j,:} + \mathbf{P}_{j,:}. And if it's true at layer ℓ\ell, it stays true at layer ℓ+1\ell + 1. The MLP and LayerNorm act on each position separately, and the attention at position jj reads only keys and values from positions ≤j\le j, because the mask forbids anything later. By induction over the layers, appending token tt changes nothing at positions 0,…,t−10, \dots, t-1, at any layer.

So at step tt the only new row is position tt's, and to compute it, each layer needs exactly three things.

  • the new token's own vector, which comes from the layer below,
  • its query qt\mathbf{q}_t, computed from that vector,
  • the keys and values of all positions 0,…,t0, \dots, t at that layer.

Old queries are never used again, since past positions are finished. Old MLP outputs are never used again either. Only the keys and values of past positions are read by future tokens. Store them, one K\mathbf{K} and one V\mathbf{V} per layer, and each step needs only the new token's computation,

K0:tℓ=[K0:t−1ℓktℓ],V0:tℓ=[V0:t−1ℓvtℓ],head⁡i=softmax⁡ ⁣(qt,i (K0:t,iℓ)⊤dk)V0:t,iℓ,\mathbf{K}^{\ell}_{0:t} = \begin{bmatrix}\mathbf{K}^{\ell}_{0:t-1} \\ \mathbf{k}^{\ell}_t\end{bmatrix}, \qquad \mathbf{V}^{\ell}_{0:t} = \begin{bmatrix}\mathbf{V}^{\ell}_{0:t-1} \\ \mathbf{v}^{\ell}_t\end{bmatrix}, \qquad \operatorname{head}_i = \operatorname{softmax}\!\left(\frac{\mathbf{q}_{t,i}\,(\mathbf{K}^{\ell}_{0:t,i})^\top}{\sqrt{d_k}}\right)\mathbf{V}^{\ell}_{0:t,i},

where the subscript ii selects the dk=d/hd_k = d/h columns that belong to head ii, one of the hh heads. No mask is needed in decoding, since every cached position is in the past. In plain words, each layer appends one row to its key table and one to its value table, and the new query reads both tables in full.

The cost per generated token drops to about 2N+4Ldt2N + 4Ldt (every weight used once, plus one query against tt keys and tt values in every layer), so

Ccached≈∑t=1n(2N+4Ldt)≈2Nn+2Ld n2.C_{\text{cached}} \approx \sum_{t=1}^{n}\big(2N + 4Ldt\big) \approx 2Nn + 2Ld\,n^2 .

The matmul work is now linear in the length. Attention is still quadratic in total, because the tt-th token really does have to look at tt earlier tokens, but its per-token cost is linear and small. Let's put numbers on it for GPT-2 small.

Python · cell 1

The ratio grows roughly like n/2n/2, about 500 times less work at the full 1,024-token context.

Building it

We need the §6.2 model again, written for one sequence at a time (no batch axis) so that the shapes stay readable. gpt_forward is the reference with no cache. forward_cached runs any number of new tokens. It looks up their positions starting where the cache ends, appends their keys and values, and lets each new token attend to the whole cache plus the new tokens up to itself.

Python · cell 2
Python · cell 3

The mask line deserves a second look. With t0t_0 tokens cached and nn new ones, the score matrix is n×(t0+n)n \times (t_0 + n), not square. New token ii sits at absolute position t0+it_0 + i, so it may see columns 00 to t0+it_0 + i. That's a lower triangle shifted right by t0t_0, which is np.tril(..., k=t0). With n=1n = 1 the mask is all True, and with t0=0t_0 = 0 it's the ordinary causal mask.

Generation now has two phases. Prefill runs the whole prompt through forward_cached in one pass, filling the cache and producing the first new token's logits. Decode then feeds one token at a time. Let's check that the cached generator produces exactly the same tokens as the naive one, and time both.

Python · cell 4

The tokens are identical, and the logits agree to rounding error, about 10−1410^{-14}. (They aren't always bit-identical, because a matrix-vector product and a matrix-matrix product may add the same numbers in a different order.) Even for this tiny model the cache is many times faster, and the gap grows with length, as the widget below shows by timing the real tiny GPT in your browser.

How much faster is generation with a KV cache?
Loading interactive figure…
Interview

A generate loop with a KV cache that matches the uncached output exactly

The setup provides P (a small GPT in the §6.2 format), n_head, the reference gpt_forward(P, idx, h) returning (T, V) logits for a 1-D idx, and the helpers layer_norm, gelu, and softmax. Implement two functions.

decode_step(P, token, cache, h) processes exactly one token. cache is a dict {"k": [...], "v": [...]} with one entry per layer, each None at first and afterwards an array of shape (t, d) holding that layer's keys (or values) for all heads side by side. The function uses position t (the cache length), appends the token's key and value rows to every layer, and returns the (V,) logits for the next token.

generate_cached(P, prompt, n_new, h) feeds the prompt through decode_step one token at a time, then generates n_new tokens greedily (np.argmax), and returns the full list of ids. It must equal the naive result exactly.

Hidden tests, hints, and AI help, from $1.99 a month.
Research

Prefill a chunk in one pass

Feeding a prompt one token at a time wastes the GPU, which is fast precisely at processing many tokens together. Write prefill_chunk(P, ids, cache, h) that processes a whole chunk of new tokens in one batched pass, continuing an existing cache (which may be empty). It must use positions t0, ..., t0 + n - 1 where t0 is the current cache length, append all n key and value rows to every layer, and return the (n, V) logits. This is how servers do chunked prefill, splitting a long prompt into pieces so decoding requests aren't starved. The setup is the same as in 6.5a.

Hidden tests, hints, and AI help, from $1.99 a month.

How big is the cache?

The cache holds one key vector and one value vector per token, per layer, per key-value head. With nkvn_{kv} key-value heads of size dkd_k (for GPT-2, nkv=hn_{kv} = h and nkvdk=dn_{kv} d_k = d), a context of TT tokens, a batch of BB sequences, and ss bytes per number,

MKV=2⋅L⋅nkv⋅dk⋅T⋅B⋅s.M_{\text{KV}} = 2 \cdot L \cdot n_{kv} \cdot d_k \cdot T \cdot B \cdot s .

In plain words, two tables (keys and values), times layers, times the width of a key, times tokens, times sequences, times bytes per number. The leading 2 and the use of nkvn_{kv} rather than hh are the two places people slip.

Work three examples in 16-bit precision (s=2s = 2).

  • GPT-2 small has L=12L = 12 and hdk=768h d_k = 768, so each token costs 2⋅12⋅768⋅2=36,8642 \cdot 12 \cdot 768 \cdot 2 = 36{,}864 bytes, 36 KiB, and a full 1,024-token sequence 36 MiB.
  • LLaMA-2 7B has L=32L = 32, nkv=32n_{kv} = 32, dk=128d_k = 128, so each token costs 2⋅32⋅32⋅128⋅2=524,2882 \cdot 32 \cdot 32 \cdot 128 \cdot 2 = 524{,}288 bytes, exactly 0.5 MiB. Its 4,096-token context is 2 GiB per sequence. A batch of 16 such sequences needs 32 GiB of cache, more than twice the 13.5 GB of weights.
  • LLaMA-2 70B uses grouped-query attention with only nkv=8n_{kv} = 8 key-value heads shared by its 64 query heads (Ainslie et al., 2023). With L=80L = 80, a token costs 2⋅80⋅8⋅128⋅2=327,6802 \cdot 80 \cdot 8 \cdot 128 \cdot 2 = 327{,}680 bytes, 320 KiB, less than the 7B model despite ten times the parameters. Without GQA it would be 2.5 MiB.

When does the cache outgrow the weights? Take multi-head attention (nkvdk=dn_{kv} d_k = d), with weights and cache stored at the same precision. The weights are about 12Ld212Ld^2 numbers (§5.6), and the cache for BB sequences of TT tokens is 2LdTB2LdTB, so

MKVMweights≈2LdTB12Ld2=TB6d.\frac{M_{\text{KV}}}{M_{\text{weights}}} \approx \frac{2LdTB}{12Ld^2} = \frac{TB}{6d} .

In plain words, the cache matches the weights once the batch holds about 6d6d tokens in total, whatever the depth. For LLaMA-2 7B that is 6×4096=24,5766 \times 4096 = 24{,}576 tokens, six sequences at its 4,096-token context, which agrees with the 2 GiB per sequence against 13.5 GB of weights above. Grouped-query attention multiplies the ratio by nkv/hn_{kv}/h, so a model with 8 key-value heads for 64 query heads holds eight times as many tokens before its cache catches up with its weights.

So at long context the cache, not the weights, is what limits how many users a GPU can serve. That single fact drives a whole family of techniques. Sharing keys and values across heads (multi-query and grouped-query attention) and compressing them into a latent vector (multi-head latent attention) are derived in §12.5. Storing the cache in 8 or 4 bits and splitting it into fixed-size pages that are allocated on demand, so that sequences of different lengths don't waste memory, are the domain of serving systems built around paged attention and quantized KV caches.

Python · cell 5

LLaMA-2 70B's weights alone exceed one 80 GB GPU, so it shows a batch of zero. It has to be split across GPUs (§10.4), and the cache is split with it. Explore other configurations below.

How much memory does the KV cache need?
Bytes = 2 × layers × KV heads × head size × tokens × batch × bytes per number. Compare it with the weights and with one GPU.
Loading interactive figure…
Core

KV cache memory, and the batch that fits

Write kv_cache_bytes(n_layers, n_kv_heads, head_dim, seq_len, batch, bytes_per_elem=2) returning the cache size in bytes, and max_batch(gpu_bytes, weight_bytes, n_layers, n_kv_heads, head_dim, seq_len, bytes_per_elem=2) returning the largest batch of full-length sequences whose cache fits in the memory left after the weights (0 if none). Use Python integers, so that large models don't overflow.

Hidden tests, hints, and AI help, from $1.99 a month.

Prefill versus decode

The two phases of generation stress the hardware in opposite ways, and understanding why is the starting point for everything in inference engineering.

Prefill processes all pp prompt tokens at once. Every weight matrix is multiplied by a p×dp \times d block of activations, so each weight read from memory is used pp times. The work is 2Np2Np FLOPs for about 2N2N bytes of weights in 16-bit, an arithmetic intensity of roughly pp FLOPs per byte. For a prompt of hundreds of tokens that is far above what the GPU needs to stay busy (§11.2), so prefill is compute-bound. Its duration sets the time to first token.

Decode processes one token per sequence. Every weight is read from memory to be used once per sequence in the batch, so at batch size 1 the intensity is about 1 FLOP per byte, and the step also reads the entire KV cache. Decode is memory-bound. The time per token is about the bytes moved divided by the memory bandwidth, whatever the GPU's FLOPS.

Put numbers on a 7B model in bfloat16, about 13.5 GB of weights, on a GPU with roughly 2 TB/s of memory bandwidth and 312 TFLOPS of bfloat16 compute (an A100 80GB). One decode step at batch 1 must read the weights at least once, 13.5 GB/2 TB/s≈713.5\,\text{GB} / 2\,\text{TB/s} \approx 7 ms, so at most about 150 tokens per second, while the arithmetic would take 2×6.7×109/3.12×1014≈0.042 \times 6.7 \times 10^9 / 3.12 \times 10^{14} \approx 0.04 ms. Prefilling a 1,000-token prompt needs 1.3×10131.3 \times 10^{13} FLOPs, about 43 ms at full utilization, the price of six or seven decode steps for a thousand tokens of work.

The consequence is that decode is almost free to batch. Serving 32 sequences at once reads the same weights once per step and does 32 times the arithmetic, so throughput rises nearly 32-fold until the cache reads or the compute catch up. With the cache counted, one decode step takes about (Mweights+MKV)(M_{\text{weights}} + M_{\text{KV}}) divided by the memory bandwidth. For the 7B model at batch 16 with full 4,096-token contexts that is 13.5 GB of weights plus 34 GB of cache, about 24 ms per step, so at large batch and long context the cache, not the weights, sets the speed, a second reason to shrink it. Inference servers therefore batch decode steps from many users together, and treat the prefill of a long prompt as a separate, compute-heavy job, which they often split into chunks (exactly Exercise 6.5b) so it doesn't stall everyone else's next token. Serving systems track latency as two numbers, time to first token (set by prefill) and time per output token (set by decode), and schedule the two phases separately to keep both low, which is the core of fast inference engineering.

In PyTorch

Production caches are preallocated, one tensor of shape (L,B,nkv,Tmax⁡,dk)(L, B, n_{kv}, T_{\max}, d_k) for keys and one for values, written in place. Concatenating as our NumPy code does copies the whole cache at every step, which turns linear memory traffic quadratic and fragments GPU memory. A static shape also lets the decode step be captured once as a CUDA graph (§11.7).

🔥 In PyTorchfor a machine with PyTorch

One line in that block hides a famous bug. For the cached path we build the mask by hand instead of passing is_causal=True. When the query length differs from the key length, PyTorch's is_causal aligns the triangle to the top-left corner, so a single decode query would be allowed to see only the first key. The correct mask for a cache is aligned to the bottom-right, which is what tril(diagonal=t0) builds.

Batching adds one more wrinkle. Prompts in a batch have different lengths, but a preallocated cache wants every row to write at the same index. The simple fix is left padding. Pad the short prompts at the start, mask the pad positions out of every attention row, and give each real token its position counted from its own first real token, so that all sequences then decode in lockstep. Serving systems avoid the waste altogether with paged caches, where each sequence owns its own list of fixed-size blocks and the attention kernel gathers them (Kwon et al., 2023).

naive loopKV cache
work to generate nn tokens≈Nn2+23Ldn3\approx Nn^2 + \tfrac{2}{3}Ldn^3≈2Nn+2Ldn2\approx 2Nn + 2Ldn^2
extra memorynone2LnkvdkTBs2Ln_{kv}d_kTBs bytes
per-step costgrows with the prefix, all layers recomputedone token's matmuls plus a read of the cache
sliding past TT (learned positions)crop and recomputecrop and recompute the cache too
used in practicenever, except as a test referencealways

Read more

Watch next

🎬 The KV Cache: Memory Usage in Transformers · Efficient NLPA short, precise walkthrough of what the cache stores and how big it gets.
🎬 LLaMA explained: KV-Cache, Rotary Positional Embedding, RMS Norm, Grouped Query Attention, SwiGLU · Umar JamilThe KV cache and grouped-query attention drawn step by step, with the LLaMA details we use in §6.7.