Interview questions · OpenAI
Explain KV cache in Transformer inference
This question evaluates understanding of KV cache mechanisms in Transformer inference, including attention-state caching, memory and latency trade-offs, and engineering optimizations for autoregressive decoding.
Question
In Transformer-based large-language-model inference, what is a key-value (KV) cache?
Give a complete, systems-level explanation that covers:
- What gets cached — which tensors, their shapes at a high level, and at which parts of the model they live.
- Why KV caching speeds up autoregressive decoding (the asymptotics it changes).
- The distinction between the prefill phase (processing the prompt) and the decode phase (generating tokens one at a time), and the very different performance profile of each.
- The main tradeoffs and pitfalls: memory growth, batched / variable-length request management, the multi-head-attention variants (MHA vs. MQA vs. GQA), positional-encoding consistency, and long-context handling.
- At least two practical optimizations used in production serving systems (e.g. paged attention, quantized KV cache, sliding-window / streaming attention, GQA).
Hint: Where to start
Begin from what self-attention recomputes at every decode step. For a new token's query , which of the per-token projections of the earlier tokens are functions only of already-fixed hidden states, and therefore never change once computed?
Hint: The key invariant
Only and of past tokens are reused across steps; is used once for its own token and discarded. Reason about what becomes per step versus what stays once you stop recomputing the prefix's projections.
Hint: Two regimes
Separate the prompt pass from the per-token loop. One is a tall matrix–matrix multiply (many query rows at once); the other is a thin matrix–vector multiply (one query row). Think about which is limited by GPU FLOPs and which by HBM bandwidth — that dictates which one batching helps.
Hint: Memory and pitfalls
Write the cache size as a product of the obvious factors (layers, batch, sequence, heads, head-dim, bytes/elem, ×2 for K and V) and notice which factors grow at runtime. From there, the production fixes fall out: shrink (GQA/MQA), shrink bytes/elem (quantization), bound the sequence term (sliding window), or stop pre-reserving
max_len(paged/block-wise allocation).
Constraints & Assumptions
- Assume a standard decoder-only Transformer doing autoregressive generation (causal self-attention), served on GPU.
- = number of layers, = batch size, = prompt length, = current sequence length, = query heads, = KV heads, = per-head dimension.
- The discussion is about inference, not training — no backward pass, weights are frozen.
- "Production" means a multi-tenant serving system handling many concurrent requests of differing lengths, not a single-sequence toy script.
Clarifying Questions to Ask
A candidate would scope the question by asking:
- Is the target a decoder-only model (the common case), or are we including encoder–decoder cross-attention caching as well?
- Are we optimizing for time-to-first-token (TTFT), inter-token latency / throughput (TPOT, tokens/sec), or max concurrent requests — they pull the design in different directions?
- What context lengths and batch sizes matter? (This decides whether the cache or the weights dominate HBM.)
- What positional scheme is in use (RoPE, learned absolute, ALiBi)? It changes what must be stored and how eviction/sliding interacts with positions.
- Is the model architecture fixed, or can we assume / choose GQA/MQA (an architecture-time decision, not an inference-time knob)?
What a Strong Answer Covers
A strong answer is judged on these dimensions (not the answers themselves):
- Precision of the cached object: that per-layer and for every past position are stored, is not cached, and the cache lives only in attention layers (not embeddings/MLP/LM head).
- Correct asymptotics: identifies the redundant recomputation a cache removes (per-step projection cost , eliminating the projection work over tokens) while being honest that the attention scan over cached keys stays .
- Prefill vs. decode as distinct regimes: compute-bound prompt pass → TTFT vs. bandwidth-bound per-token loop → TPOT, and why batching amortizes the weight read during decode.
- The memory model: a correct size formula, the observation that it is linear in , and the consequence that throughput is usually KV-memory-limited.
- Attention variants and their cache effect: MHA vs. MQA vs. GQA and how scales the footprint.
- At least two concrete production optimizations named with their mechanism, not just buzzwords (e.g. paged attention's block table + prefix sharing; quantization's bytes/elem reduction; sliding window's bounded sequence term).
- Awareness of the sharp edges: ragged/variable-length batching, per-sequence causal masking when packing, positional consistency under eviction, and beam/multi-sample cache duplication.
Follow-up Questions
- Derive the KV-cache size in bytes for a concrete model (give it , , , FP16) at batch and context , and compare it to the weight memory — at what context length does the cache dominate?
- How does paged attention enable prefix sharing across requests with a common system prompt, and what has to happen on a copy-on-write divergence?
- Why are keys more sensitive than values to low-bit quantization, and what scaling granularity (per-tensor vs. per-channel vs. per-token) mitigates it?
- With a sliding-window cache, what breaks if you naively drop the oldest tokens, and how do "attention-sink" schemes keep streaming generation stable at fixed memory?
- In disaggregated serving, why might you place prefill and decode on separate hardware, and what must be transferred between them?
Solution
What a KV cache is
In a decoder-only Transformer, generation is autoregressive: token is produced by attending over all previous tokens . Self-attention at each layer computes three projections of the hidden state :
and then , where is the causal mask and is the per-head dimension.
The key observation: when you append a new token, the and vectors of all previous tokens do not change — they depend only on those tokens' hidden states, which are already fixed. Only the new token's needs to attend over them. So instead of recomputing and for the whole prefix at every step, you store them.
A KV cache holds the per-layer, per-head and tensors for every position seen so far. At each decode step you compute only for the single new token, append its to the cache, and run attention of that one query against the full cached .
Note: is not cached — each query is used once (for its own token) and then discarded. Only and are reused across future steps.
What gets cached and the shapes
For each of the transformer layers you keep two tensors:
K_cache: [batch, n_kv_heads, seq_len, head_dim]
V_cache: [batch, n_kv_heads, seq_len, head_dim]
seq_lengrows by 1 every decode step (and starts at the prompt length after prefill).n_kv_headsis the number of KV heads, which may be fewer than the number of query heads under grouped-query attention (see below).- The cache lives in every attention layer; the embedding layer, MLPs, and final LM head are not cached because they have no cross-token reuse.
Exact memory cost
Total KV-cache bytes:
where the leading is for and , = layers, = batch, = sequence length, = KV heads, = head dim. The cache is linear in sequence length and batch size — this is what makes it the dominant memory consumer at long context. For a single sequence it only rivals the weights at extreme context length, but across a large batch the aggregate cache routinely exceeds the weights, and that is the regime that caps how many requests fit in HBM.
Why it speeds up decoding
Without a cache, generating each new token re-runs attention over the entire prefix, recomputing and projections for all past tokens. Over generated tokens that is redundant projection work.
With a cache:
- Projection work per step is — you project only the new token.
- Attention for the new query still reads cached keys/values (the dot products can't be avoided), but you no longer recompute the projections of the past.
So the cache turns the per-step projection cost from to . The remaining attention scan is cheap relative to the projections and MLP, and is memory-bandwidth-bound rather than compute-bound. The net effect is a large, roughly constant-time-per-token decode loop instead of one that degrades with prefix length.
Prefill vs. decode
These are two operationally distinct phases with different performance profiles.
| Prefill | Decode | |
|---|---|---|
| Input per forward pass | Whole prompt, tokens | 1 token |
| Cache action | Populate all positions, all layers | Append 1 position |
| Matrix shape | matrix–matrix (tall ) | matrix–vector (single-row ) |
| Bottleneck | Compute-bound (GPU FLOPs) | Memory-bandwidth-bound |
| Parallelism | All prompt positions in parallel | Inherently sequential |
Prefill runs the full prompt through the model once. Because has rows, the attention and FFN are large dense matmuls that saturate the GPU's compute units — it's throughput-oriented and batches well. Its latency determines time-to-first-token (TTFT).
Decode processes one token at a time, so each step is a thin matrix–vector multiply. The arithmetic intensity is low: you stream the entire model weights (and the growing KV cache) from HBM to do very little compute, so you're limited by memory bandwidth, not FLOPs. Decode latency determines inter-token latency (TPOT, time per output token) — the per-token latency whose reciprocal is the tokens-per-second throughput. This is why serving systems batch many concurrent decodes together — it amortizes the one-time weight read from HBM across many requests.
This split is why the two phases are often scheduled, and even placed, on separate hardware ("disaggregated" prefill/decode) in large deployments — see the follow-up below.
Tradeoffs and pitfalls
1. Memory growth dominates. As shown above, KV memory is linear in . At long context the cache can dwarf the weights, capping how many concurrent requests (batch size) fit in HBM. Throughput in production is usually KV-memory-limited, not FLOP-limited.
2. Variable-length / ragged batching. Sequences in a batch have different prompt lengths and finish at different times. Naively reserving max_seq_len per slot wastes most of the cache. Finished sequences leave holes; you need bookkeeping to reclaim and reuse that space (this is the core motivation for paged attention, below).
3. Multi-head attention variants change the cache size. The cache scales directly with :
- MHA (multi-head): — largest cache.
- MQA (multi-query): a single shared KV head, — smallest cache, some quality loss.
- GQA (grouped-query): between and (query heads share KV heads in groups) — the common modern compromise. GQA shrinks the cache by the grouping factor with little quality cost, which is why it is now standard.
4. Positional-encoding consistency. With RoPE, the rotation is applied to as a function of absolute position before caching, so cached keys already carry their positional phase — straightforward as long as you rotate at the correct index. The pitfall appears when positions shift (sliding window or cache eviction): naive eviction misaligns the position math unless you re-anchor or use a relative scheme. With learned absolute positions you must add the embedding at the right index.
5. Masking with packed batches. The causal mask must keep each token attending only to earlier positions of its own sequence. When multiple sequences are packed into one tensor, you need per-sequence (block-diagonal) masks so tokens don't leak across sequence boundaries.
6. Beam search / multi-sample. Beam search or parallel sampling multiplies the cache by the beam width / number of samples unless the shared prompt prefix is stored once and only the branches are duplicated (prefix sharing).
Practical production optimizations
1. Paged attention (block-wise KV management). Store the cache in fixed-size blocks ("pages") and keep a per-sequence block table mapping logical positions to physical pages, analogous to OS virtual memory. Benefits: near-zero internal fragmentation, growth one page at a time instead of pre-reserving max_len, and prefix sharing — multiple requests with a common system prompt point at the same physical pages, with copy-on-write on divergence. This typically allows substantially larger effective batch sizes from the same HBM. (Popularized by vLLM.)
2. Quantized KV cache. Store and in lower precision (e.g. INT8 or FP8) while keeping matmul accumulation in BF16/FP16. Roughly halves or quarters both the cache footprint and the bandwidth needed to read it during decode, raising batch capacity and decode throughput. Requires per-channel/per-token scaling — keys are more sensitive than values because their outlier channels, once corrupted, distort every attention score through the dot product (see follow-up).
3. GQA / MQA at the architecture level. Reducing is the most direct lever — it shrinks the cache proportionally essentially for free at inference time (decided at training/architecture time). This is why nearly all recent large models ship with GQA.
4. Sliding-window / streaming attention. Cap the cache to the most recent tokens, giving bounded memory regardless of total length. Pure truncation loses long-range dependencies; "attention-sink" variants keep the first few tokens plus the recent window, which preserves stability for streaming/infinite generation at fixed memory (see follow-up).
5. KV offloading / hierarchical cache. Spill cold KV blocks to CPU RAM (or NVMe) and page them back when needed — trades latency for capacity, useful for very long contexts or for caching reusable prefixes across requests. Often combined with chunked/retrieval-style attention so you don't read the whole offloaded cache every step.
Worked answers to the follow-ups
KV-cache size vs. weights — when does the cache dominate?
Take a model with layers, KV heads (GQA), , FP16 (2 bytes). Per token per sequence:
- One sequence at tokens: GiB.
- Batch of at : GiB — comparable to or larger than the weights of a ~7–13B model in FP16, and this is the regime that fills an 80 GB GPU.
The crossover is governed by versus a fixed weight term, so the cache overtakes the weights once is large enough — pushed sooner by long context, large batch, and (had we used MHA instead of GQA) more KV heads.
How paged attention enables prefix sharing
Each request's logical token positions are mapped, through its block table, to physical KV pages. Two requests that begin with the same system prompt prefill that prefix once; both block tables point their leading entries at the same physical pages (refcounted). They diverge only at the first differing token. On a write that would mutate a shared page (the point of divergence, or any later token), the server does copy-on-write: allocate a fresh page, copy the shared block, redirect that request's block-table entry, and decrement the shared page's refcount. The shared prefix's compute and memory are paid once across all requests that share it.
Why keys are more quantization-sensitive than values
Keys enter the model through the dot product before the softmax, so a quantization error on a key perturbs the pre-softmax logits, which the softmax can amplify into a large shift in the attention distribution. Values enter after the softmax as a weighted average, so value errors are averaged and damped. Empirically, key activations also carry large per-channel outliers; per-channel (or grouped) scaling for keys and per-token scaling for values, rather than a single per-tensor scale, contains the error and lets INT8/FP8 KV match BF16 quality closely.
Sliding window — why naive truncation breaks, and attention sinks
Naively dropping the oldest tokens removes the model's ability to refer back beyond — but more acutely, models trained with full attention place a disproportionate amount of attention mass on the first few tokens (an "attention sink"). When sliding eviction removes those initial tokens, the softmax has no low-information landing spot to dump excess attention, the distribution destabilizes, and generation degrades or diverges. StreamingLLM-style schemes fix this by always retaining the first few "sink" tokens plus the recent -token window, giving stable, fixed-memory streaming over effectively unbounded length (at the cost of true long-range recall, which sliding inherently sacrifices).
Disaggregated prefill/decode
Prefill is compute-bound (saturates FLOPs) and decode is bandwidth-bound; co-locating them lets a long prefill stall the latency-sensitive decode loop ("decode interference"). Disaggregation runs prefill on one pool of GPUs and decode on another, each tuned for its bottleneck and scaled independently. The cost is that the prefilled KV cache must be transferred from the prefill node to the decode node (over NVLink/InfiniBand) before the first token is generated — a bandwidth and TTFT tradeoff that pays off at scale because it removes interference and improves overall goodput.
How to land this in an interview
- Define it precisely: cached per layer per position; is not cached; lives only in attention layers.
- State the cost formula and call out that it's linear in sequence length × batch — the central scaling constraint.
- Frame the core tradeoff as latency vs. memory, and connect prefill → TTFT (compute-bound) and decode → TPOT (bandwidth-bound).
- Name a concrete optimization with its mechanism (paged attention for fragmentation/sharing, quantization or GQA for footprint, sliding window for bounded context) rather than just listing buzzwords.
Community answers
Nothing here yet.
Join in on CSWork →