Runbooks/ LLM Inference Runbook Track B · The machine and the two phases Deep readQuick learn
  1. 00 Start
  2. /
  3. 01 Tokens
  4. 02 Anatomy
  5. 03 Journey
  6. /
  7. 04 Hardware
  8. 05 Phases
  9. 06 KV cache
  10. /
  11. 07 Attention
  12. 08 Position
  13. 09 Precision
  14. 10 Cache ops
  15. /
  16. 11 Many GPUs
  17. 12 Engines
  18. 13 Decode loop
  19. /
  20. 14 Planning
  21. 15 Production
Document 06 · Quick learn One formula · Eight steps · Four levers

The KV cachein one formula

Every past token leaves behind a key and a value. Store them and you pay memory; throw them away and you pay compute — quadratically. This one formula decides how many people fit on a GPU, and everything in tracks C and D exists to make its answer smaller.

“The model has no memory. It re‑reads its own notes — every single token.”
128 KiB
per token · Llama 3.1 8B
1.00 GiB
per user at 8,192 tokens
53.73 GiB
KV budget on one H100
53 → 191
users: worst case → paged
Why there is a cachethe alternative is quadratic

Attention makes every new token look at every previous token. Those keys and values never change once computed — so you either store them once, or rebuild all of them on every single step.

Without a cache

Token 1

Compute K,V for 1 token

Token 2

Recompute all 2

Token 3

Recompute all 3…

Everything, again, every step
Cost grows with the square of the output

A 2,000‑token prompt with 300 output tokens costs 280× more compute. At 8,000 in and 1,000 out it is 945×.

Generation would be quadratic in length — unusable at any real context size

With a cache

Token 1

Compute K,V · store

Token 2

Compute 1 · read 1

Token 3

Compute 1 · read 2

One new token’s work per step
Constant work per step — but memory that only grows

Each token is written exactly once and then re‑read forever. Nothing is summarised, nothing decays, nothing is compressed.

You have traded a compute problem for a memory problem — that trade is the rest of this page

The correction almost everyone needs

Queries are never cached

A past token’s question is never asked again — only the current token asks. Its key and value, though, are read by every future token and never change. Cache what is re‑read; discard what is not.

That is why it is a KV cache, not a QKV cache — and why the formula starts with 2, not 3
The analogy — a court stenographer

Without a transcript, every witness would have to repeat their whole testimony before each new question. With one, each statement is written down once and simply re‑read. It is not a summary and it does not shrink — every line stays, forever, at full length.

And each case has its own transcript — which is why the cost is per user, not per machine
The formulasix factors, term by term
bytes = 2 × layers × kv_heads × head_dim × seq_len × bytes_per_element
Two of these you change with a flag · one you inherit when you pick the model · three are fixed
Six factors, and who controls each
2

One key, one value. Never three.

fixed · causal attention
layers

Every layer keeps its own cache. Nothing is shared.

32 · fixed by model
kv_heads

num_key_value_heads — not query heads.

8 · architecture
head_dim

Hidden size ÷ attention heads. 128 across Llama 2 and 3.

128 · fixed by model
seq_len

Tokens actually held, capped by the context limit.

≤ 8,192 · runtime flag
bytes

2 for bf16, 1 for an fp8 cache.

2 · runtime flag
Worked2 × 32 × 8 × 128 × 2 = 131,072 B = 128 KiB / token · × 8,192 = 1.00 GiB / userLlama 3.1 8B, bf16

The three mistakes that ruin it

Query heads instead of KV heads

The single most common error. On Llama 3.1 8B it is 8, not 32.

Overstates the cache by 4×
Dividing instead of multiplying

8 KV heads on a 4,096‑wide model is 8 × 128 = 1024, not 4096 ÷ 8 = 512.

Head dimension is fixed; the head count scales it
Forgetting layers is a multiplier

Every layer keeps its own cache; nothing is shared up the stack.

An 80‑layer model costs 2.5× a 32‑layer one per token
The ladderfrom one card to a user count, in eight steps

“How many concurrent users fit on one GPU?” is not trivia. It is a request to watch you reason with the formula. Run these eight steps out loud, in this order, every time — stopping early is always optimistic.

1
Reported capacitywhat the card actually exposes
79.65 GiB
2
× 0.90 utilisationvLLM default — the pool it may claim
71.69 GiB
3
− weights8,030,261,248 params × 2 bytes
− 14.96
4
− activationsCUDA graphs, logits buffer, workspace
− 3.00
5
= KV budgeteverything left is cache
53.73 GiB
6
÷ per tokenthe formula, from above
128 KiB
7
× context limitworst case: everyone fills the window
1.00 GiB
8
= concurrent userswhat you were actually asked for
53 users

Two numbers, and you must say which one you mean

53 users · worst case

Charges everyone the full 8,192‑token window from the moment they connect.

Quote this when asked what you can guarantee
191 users · paged

A typical 2,300‑token conversation holds 0.28 GiB, not 1.00. Paging recovers about 3.6×.

Quote this when asked what you are observing

Three caveats a good interviewer will probe — so volunteer them

Concurrency is not batch size

The scheduler interleaves prefill and decode, so not every admitted request is decoding in the same step. Cache bounds how many are resident; the ridge point bounds how many are usefully in one forward pass.

Context is a maximum, not an allocation

With paging, a user who has sent 500 tokens holds about 32 blocks — not the full window. A 128k window does not mean 128k of memory is reserved for you.

Throughput has a second ceiling

Past the ridge point you are compute‑bound, and adding users raises per‑token latency instead of throughput. Memory tells you what fits; benchmarks tell you what is fast.

The leversindependent, so they multiply

Five ways to shrink the per‑user cache. They act on different factors of the same formula, which means they compound — and saying that out loud is worth more in an interview than any one of them.

Fewer KV heads

Grouped‑query attention. Query heads ÷ KV heads.

4× on the 8B · 8× on the 70B
Decided before training · doc 07
Fewer bytes each

An fp8 cache. One byte per element instead of two.

2× exactly
Runtime flag · --kv-cache-dtype fp8
Fewer tokens

A lower context limit. The cheapest lever, and the most visible.

proportional
Runtime flag · --max-model-len
No waste

Paged allocation. Blocks, not reservations.

~3.6× observed
Automatic, not optional · doc 10
Compress, not shrink

Multi‑head latent attention. The model learns a shared latent.

>90% on DeepSeek‑V2
Before training, like GQA · doc 07

What they cost you

A fraction of a percent of quality, and nothing at serving time. But no flag changes it — it is baked into the shape of W_K and W_V.

Accuracy error that accumulates over a generation — a key quantised at token 10 is still read at token 10,000. Test on long outputs.

Requests over the limit are rejected. Free in memory terms, but a product decision rather than a tuning knob.

Nothing — but it is an observed saving, not a guaranteed one. It depends on your length distribution.

Extra arithmetic per step, newer, less universally supported. A model‑selection decision made months earlier.

Stacked, on the same card

13
53
106
382
Multi‑head, bf16, no paging+ GQA ×4+ fp8 cache ×2+ paging ×3.6 — 382 users
When it breakstwo symptoms, two different fixes

Inter‑token latency climbing

Plot ITL against sequence position. If it rises, it is the cache read.

  • The cache is not only stored — it is re‑read every step
  • At 2,000 tokens it is 6% of the read; at 131,072 it is 52%
  • At batch 64 and 8k it is 68.7 GB against 16.06 GB of weights — 81%
  • Fixes: fp8 cache · fewer KV heads · lower limit
Different symptoms.
Different fixes.

Utilisation at 98%, queue growing

You have admitted more work than you can hold. The scheduler is preempting.

  • First action is admission control, not tuning
  • Preemption evicts mid‑generation, then recomputes or swaps — both cost real work
  • Cap running sequences below where preemption starts
  • Then: fp8 cache · lower limit · quantised weights · hardware
Make preemption count a first‑class alarm

Cache utilisation pinned at 100% with a growing queue is the signature of thrashing — the system spends its time undoing and redoing rather than progressing. It is easy to miss until users notice.

Better to queue a request and serve it well than admit it and thrash
Answer it out loudthe ninety-second version, then seven questions
“The KV cache stores the keys and values of every past token so they are never recomputed — without it, generation would be quadratic in output length, about 280 times more compute on a typical chat request. The formula is two, for key and value, times layers, times KV heads, times head dimension, times sequence length, times bytes per element. For Llama 3.1 8B that is 128 KiB a token and exactly a gigabyte per user at 8k. Two properties make it expensive: it is linear in conversation length, and it is per user rather than shared. That is what caps batch size — and since decode throughput is a function of batch size, the cache is what actually decides how many people fit on a card. On an H100 the ladder gives 53 users worst case and about 191 with paging, and the levers are independent, so they multiply.”

Seven questions, and the spine of each answer

Architect

Write the formula and explain each term.

  • 2 = key and value. Q is never cached — attention is causal
  • Layers multiplies; nothing is shared up the stack
  • It is num_key_value_heads, not query heads
  • head_dim = hidden ÷ heads = 128 on every Llama 2 and 3
Land on: linear in length, and per request — that is what caps concurrency.
Architect

How many concurrent users fit on one H100?

  • “Let me run the ladder rather than guess” — then all eight steps
  • 53 worst case, ~191 paged — say which you are quoting and why
  • Levers in cost order: fp8 cache, lower limit, fp8 weights (+14%)
If it still will not fit: a second GPU running an independent replica — not tensor parallelism, because the model fits on one card.
Eng manager

We want to go from 8k to 128k context. What do you tell me?

  • It is a capacity decision, not a config change: 1 GiB → 16 GiB per user
  • Worst-case concurrency 53 → three. On the 70B, half a card per person
  • Latency roughly doubles; prefill goes to tens of seconds
Then the question back: what fraction of requests actually needs it? If 2%, build a separate long-context deployment — do not size every short request against the worst case.
Architect

Can I change the KV head count with a flag?

  • No — it is the shape of W_K and W_V, baked into the weights
  • Changing it means a new checkpoint: mean‑pool within each group, then uptrain
  • That costs roughly 5% of the original pre‑training compute
What is a flag is the cache dtype. Halve it tomorrow — yes. Quarter it — that was a model-selection decision months ago.
Architect

Why not cache the queries too, or compress the cache?

  • Queries would be pure waste — 50% more cache for zero benefit
  • Quantisation shrinks each stored number; fp8 halves it, and it is a runtime flag
  • Latent attention learns a small shared latent — >90% on DeepSeek-V2, but a pre-training decision
  • Eviction is genuinely lossy: it changes the output
Reach for quantisation first because it is reversible and measurable; choose latent attention at model-selection time; treat eviction as research, not a default.
Architect

Why does the cache make long conversations slower, not just more expensive?

  • Because it is not only stored, it is read — every active sequence’s whole cache, every step
  • At 2,000 tokens it is ~6% of the read; at 131,072 it is 52%
  • At 8,192 tokens × 64 sequences it is 68.7 GB against 16.06 — 81%
The diagnostic is simple: plot ITL against sequence position. If it climbs, it is the cache read — and that is the regime where quantising the weights barely helps.
Eng manager

Cache utilisation is 98% and the queue is growing.

  • We admitted more work than we can hold. The scheduler is preempting
  • Preemption evicts mid-generation, then recomputes or swaps — both cost real work
  • Immediate action is admission control, not tuning
Then structural fixes in cost order: fp8 cache, lower context limit, quantised weights, hardware. And make preemption count a first-class alarm.
Fast answersthe ten that come up most

QWhy cache K and V but not Q?

Attention is causal, so a past token’s query is never consulted again. Its key and value are read by every future token and never change. Cache what is re‑read.

QDoes each layer keep its own cache?

Yes. Every layer produces different keys and values from a different input, so nothing is shared. That is why layers is a multiplier.

QIs head_dim always 128?

Not by law, but remarkably stable: the 8B, 70B and 405B all land on 128. Models scale by adding heads and layers, not by widening each head. Read it from the config.

QWhy is 8 KV heads so common?

Good point on the quality‑versus‑memory curve, and it maps onto eight‑way tensor parallelism — one head per GPU. If parallelism exceeds the KV head count, heads get replicated and the aggregate cache goes up.

QContext window vs cache?

The window is a limit; the cache is memory in use right now. A 128k window does not allocate 128k. That gap is why worst‑case sizing gives 53 and paged reality gives 191.

QIs quantising the cache riskier than the weights?

Different risk. Weight error is fixed and calibratable. Cache error accumulates across a generation. 8‑bit is generally safe; below that, test on long outputs.

QCan I share cache between users?

Only where token sequences are identical from the very start — a shared system prompt, a common few‑shot block. That is prefix caching: exact‑prefix‑only, and one of the largest levers on chat and agent workloads.

QWhy is it not compressed like a normal cache?

It is not a cache of bytes, it is a tensor that gets multiplied. Any compression must be readable directly by the attention kernel — which is why the real answers are numeric (quantisation) or architectural (latent attention).

QWhat happens when a request finishes?

Its blocks return to the free pool immediately — unless the engine keeps them for prefix reuse, in which case they stay until evicted by LRU and a later matching request skips that prefill.

QOne number to memorise?

2 × 32 × 8 × 128 × 2 = 131,072 bytes = 128 KiB per token, and 1.00 GiB per user at 8k. Write that, say what each factor is and where you read it, and you can derive the rest live.

At any real scale you are not serving a model — you are serving a cache. At batch 64 and 8,192 tokens it is 68.7 GB against 16.06 GB of weights: 81% of every byte you move. That is the regime where quantising the weights barely helps, because the weights are no longer the problem.

KV cache · 81%
Weights · 19%
Where this goes next

Questions to ask them

What is your KV cache utilisation at peak, and is preemption count monitored?

Is the cache bf16 or fp8? If bf16, has anyone tested fp8 on long generations?

What is max-model-len, and what fraction of requests come near it?

Does the capacity number in the design doc assume worst case or paged?

At your typical batch and length, what share of decode bandwidth is cache?

Was the KV head count part of the model-selection criteria?

← 05 · Phases Read the full chapter → 07 · Attention →