Worked examples — KV-cache optimization
Before we start, one tiny reminder of the symbols we will lean on, so nobody is left behind:
The scenario matrix
Every KV-cache question is really one of these cells. The examples that follow each declare which cell(s) they cover.
| # | Case class | Concrete trigger | Covered by |
|---|---|---|---|
| A | Empty / first token () | prefill of the very first token, cache starts empty | Ex 1 |
| B | Growing cache, one step | append token , attend over positions | Ex 2 |
| C | Prefill vs decode asymmetry | a whole prompt goes in at once, then decode one-by-one | Ex 3 |
| D | Zero / degenerate input | , or single-head (), or tiny | Ex 4 |
| E | Limiting behaviour () | when does cache memory overtake model weights? | Ex 5 |
| F | Head-layout variants | standard vs MQA vs GQA cache size | Ex 6 |
| G | Real-world word problem | "will a 32 GB GPU serve this?" — deployment sizing | Ex 7 |
| H | Exam twist | sliding window: cache stops growing, find the break-even token | Ex 8 |
We attack them in order. Cells A–C are the mechanics, D–E the edge and limit, F–H the engineering reality.
Example 1 — Cell A: the empty cache, first token
Forecast: Guess now — before we compute anything, how many rows does the cache have? And after one token?
Steps:
-
Before generation, the cache is empty. Its shape is — literally zero rows. Why this step? Caching is incremental; there is nothing to remember before the first token exists. This is the base case every loop starts from.
-
Compute the first token's projections: and , each of shape . Why this step? is the embedding of token 1 (a length- vector). Multiplying by the fixed matrix produces this token's key across all 4 heads. Same for value. See the projection step.
-
Append. Cache goes from to . Why this step? Appending is the whole trick: we grow by exactly one row per token.
-
Count stored numbers. Two matrices (K and V), each numbers: Why this step? This is the per-token memory formula from the parent note, evaluated at .
Verify: The attention for token 1 must attend only to position 1 — a score (one score per head, one position). With a cache of exactly 1 row that is exactly what we get. Units check: . ✓
Example 2 — Cell B: one growth step, the 3rd token
Forecast: How wide (how many columns) is the score row for token 3?
Steps:
-
Cache before: (tokens 1,2). Why this step? We only re-read; we never recompute . That is the point of the cache.
-
Compute the new token's key and value: and , each of shape , where is token 3's embedding. Why this step? Only token 3 is genuinely new, so only its K and V need computing — the reusable "what previous tokens offer" pieces that we will append to the cache.
-
Compute the new query separately: , also — and we do not cache it. Why this step? encodes "what token 3 is looking for". Every future token brings its own fresh query, and old queries are never re-attended, so caching would waste memory for nothing (this is Mistake 1 in the parent). Keys and values are remembered; queries are always thrown away after one use.
-
Append K and V: cache becomes . ( is not appended.)
-
Score shape. For each head, (a vector) dots against 3 cached keys (): Why this step? One row per head (4), one column per attended position (3). The scaling keeps the dot products from blowing up (from scaled dot-product attention).

Verify: Look at the figure — the new row (coral) is height-1, and the score arrow fans out to exactly 3 cached columns. Column count = current token index = 3. ✓ And , a clean integer. ✓
Example 3 — Cell C: prefill vs decode
Forecast: Will the cached run be roughly times cheaper, or something in between because of the big prefill chunk?
Steps:
-
Fix the unit. Projecting one token means multiplying its -vector by each of (each ), which costs multiply-adds. We call that one block. Projecting tokens is independent such products, so it costs exactly blocks — whether done one-at-a-time or fused into a single batched matmul. Why this step? A batched matmul does not do less arithmetic than separate ones — it packs the same token-products into one call for hardware efficiency. So "10 tokens in one matmul" is still 10 blocks of work, not 1. This is why prefill of tokens costs blocks, not a constant.
-
Naive projection cost. At each token position the naive model recomputes K,V for all positions: blocks. Why this step? Without a cache, generating token treats the sequence as fresh — projections each time.
-
Cached projection cost. Prefill projects all prompt tokens once (10 blocks, by step 1), then each of the decode steps projects exactly 1 new token (5 blocks): blocks. Why this step? The cache means we never redo the prompt's K,V, and each decode step adds only its single new token.
-
Speed ratio. . Why this step? This is the concrete cash value of the theoretical "" speed-up — but it lands at 8, not 15, because the naive sum is , not , and the cached cost equals .
Verify: General check: naive , cached , ratio . For : . ✓ Matches step 4. The parent's "" is the big-O scaling; the exact ratio is .
Example 4 — Cell D: degenerate inputs
Forecast: Which of these gives zero bytes, and which gives the smallest non-zero cache?
Steps:
-
(a) . Total cache . Why this step? Empty sequence → nothing to store. Confirms the formula degrades gracefully; no divide-by-zero, no negative memory.
-
(b) . Per token bytes. Why this step? A single-head model collapses to just . This is also the MQA per-token figure (one shared K/V) — a preview of Ex 6.
-
(c) , . Per token bytes. Why this step? Shrinking the head dimension to 1 makes each key/value a single scalar. Still 8 heads, so 8 scalars for K + 8 for V = 16 numbers = 32 bytes.
Verify: (a) . ✓ (b) bytes, non-zero and smallest of the "real" cases per number of heads. (c) bytes is actually the smallest total here. Ordering: . ✓ Every formula stays finite and non-negative under degenerate inputs.
Example 5 — Cell E: the crossover limit ()
Forecast: Thousands of tokens? Millions? Guess the order of magnitude.
Steps:
-
Per-token full-model cache. bytes KB. Why this step? This is the parent's formula summed over layers, per token. Every new token adds this fixed slab.
-
Set cache = weights. Solve : Why this step? Cache grows linearly in while weights are constant — so there is always a crossover. Past it, memory is dominated by cache, not the model.
-
Interpret the limit. As the cache dwarfs everything: . This is why MQA/GQA and sliding windows exist (Ex 6, 8). Why this step? The whole "advanced optimizations" section of the parent is a response to this unbounded growth.
Verify: MB. ✓ Linear-in- growth means the ratio has no finite bound. ✓ (Here MB = bytes, decimal — see Ex 7 for a units clarification.)
Example 6 — Cell F: standard vs MQA vs GQA
Forecast: MQA should be some big divisor smaller. By exactly what factor, and where does GQA land?
Steps:
-
(a) Standard. bytes KB. Why this step? Every query head keeps its own K and V. This is the baseline the parent's formula gives.
-
(b) MQA. One shared K/V head: bytes. Why this step? MQA replaces K/V heads with a single one. Reduction .
-
(c) GQA, . bytes KB. Why this step? GQA keeps one K/V per group. With 8 groups the reduction is — the parent's stated Llama-2 figure.

Verify: Standard B; MQA B → ratio . ✓ GQA B → ratio . ✓ GQA sits between MQA and standard, exactly as the figure's staircase shows. ✓
Example 7 — Cell G: real-world deployment sizing
Forecast: With plenty of GPU headroom, is the cache even a problem here — yes or no?
Steps:
-
Per-user cache (standard). From Ex 5, per-token full-model cache bytes. For 2048 tokens: bytes. In binary MiB (): MiB exactly. Why this step? One user's cache = per-token slab context length. The bytes divide cleanly by , so 72 MiB is exact, not rounded.
-
8 users + weights. MiB GiB. Why this step? Concurrent requests each need their own cache; weights are shared once. Total must fit the 32 GiB budget.
-
Fit check. → yes, fits easily. After reserving weights, budget for caches MiB, so Why this step? The cache, not the weights, is the scaling bottleneck for concurrency — this is the core inference-optimization insight.
-
MQA switch. MQA shrinks K/V by the head factor , so per-user cache MiB. Max users . Why this step? Cutting cache multiplies serveable concurrency by roughly (448 → ~5378). This is why production LLMs use MQA/GQA — see deployment.
Verify: Standard: MiB, and MiB. ✓ Max standard users ; MQA ; ratio . ✓ Units: MiB / (MiB/user) = users, all binary throughout. ✓
Example 8 — Cell H: exam twist, sliding window break-even
Forecast: Does the window cache save memory from token 1, or only after some threshold?
Steps:
-
Growth laws. Full cache rows (grows forever). Window cache rows . Why this step? Sliding window drops the oldest token once you exceed , so its size plateaus at 512 — constant memory, from the parent's "Sliding Window" section.
-
First difference. For every both caches hold rows (nothing dropped yet), so they are identical. At the full cache has 513 rows but the window has already evicted token 1 and holds only . So they first differ at . Why this step? Below the window size there is no saving at all; the saving begins exactly one token past . That threshold is the "break-even" the question asks for.
-
Ratio at . Full rows; window rows. Why this step? At long context the window is smaller here — the tradeoff is losing dependencies older than 512 tokens (relevant to long-horizon decoding where distant context can matter).
-
Limit behaviour. Window memory is constant while full is linear, so as the ratio : the saving grows without bound. Why this step? This is the whole point of sliding-window attention for 100K+ token contexts — memory stops growing entirely.
Verify: and → first divergence at . ✓ . ✓ Ratio is unbounded as . ✓
Recall Self-check
With-cache projection cost vs naive, exact ratio for length ? ::: (not — that's the big-O). When does KV-cache first differ from a length- window cache? ::: At token . MQA cache reduction factor vs standard multi-head with heads? ::: (one shared K/V head). Why is never cached? ::: Each new token needs a fresh query ("what it's looking for"); old queries are never re-attended. Per-token full-model cache formula? ::: .
Related: 6.3.1-model-quantization shrinks the bytes-per-number factor in every formula above — orthogonal to head-count tricks, and stacks with them.