Module 09 — KV Cache & Long Context¶
Collection: Hardware-Aware LLM Quantization | Previous: ← Module 08 | Next: Module 10 →
Every ledger so far has treated the KV cache as a footnote. At 4 K context it is one. At 262 K it is co-dominant with the entire model's weights, and the correct optimization strategy inverts.
This module computes the inversion point exactly, and shows that a 262 K context target on a 32 GB card is not a quantization problem you can solve by compressing weights harder — it is an architecture-and-KV-format problem with a hard feasibility boundary.
Learning objectives¶
By the end of this module you should be able to:
- Compute KV cache bytes per token from a model's GQA geometry.
- Compute the crossover context length where KV traffic equals weight traffic.
- Predict tok/s as a function of context length.
- Determine whether a given (model, context, VRAM) triple is feasible at all, before running anything.
- Explain why KV quantization is a different problem from weight quantization — online, per-head, and latency-critical.
1. KV cache arithmetic¶
The cache stores one K and one V vector per KV head (not per query head — that is what GQA buys), per layer, per token:
KV_bytes_per_token = 2 × n_layers × n_kv_heads × head_dim × bytes_per_element
│ │
K and V GQA groups, NOT query heads
For the case-study geometry (48 layers, head_dim 128 — with n_kv_heads shown both ways since the published inventory does not pin it):
n_kv |
Elements/token | BF16 | FP8 | NVFP4 |
|---|---|---|---|---|
| 8 | 98,304 | 192 KiB | 96 KiB | 54 KiB |
| 4 | 49,152 | 96 KiB | 48 KiB | 27 KiB |
Two things follow immediately, and both are structural:
- KV cost is linear in
n_kv_heads. Halving KV heads halves both capacity and traffic — a bigger lever than any KV quantization format. This is why GQA (and MLA) exist, and why architecture choices dominate format choices here. - KV cost is independent of
d_modelandd_ff. A wider MLP costs weights, not cache. Weight optimization and KV optimization are decoupled problems.
2. The crossover: when KV traffic overtakes the weights¶
At every decode step, attention reads the entire cache for all previous tokens. So KV traffic grows linearly with context while weight traffic stays flat:
Setting the two terms equal gives the crossover:
n_kv |
KV format | KV/token | Crossover |
|---|---|---|---|
| 8 | BF16 | 192 KiB | 80,770 tokens |
| 8 | FP8 | 96 KiB | 161,540 |
| 8 | NVFP4 | 54 KiB | 287,182 |
| 4 | BF16 | 96 KiB | 161,540 |
| 4 | FP8 | 48 KiB | 323,079 |
| 4 | NVFP4 | 27 KiB | 574,363 |
traffic
▲
│ ╱ KV (grows with L)
│ ╱
│ ╱
W ─┼─────────────────────────────────╳────────────── weights (flat)
│ ╱ │
│ ╱ └── crossover
│ ╱
└──────────────────────────────────────────────▶ context length L
L < crossover : a WEIGHT-dominated problem → Modules 04–07 apply
L > crossover : a KV-dominated problem → weight quantization stops helping
With BF16 KV and 8 KV heads, the crossover is ~81 K tokens. Past that point, further weight quantization is attacking the smaller of the two terms. If you serve at 262 K, you have been on the wrong side of this line for most of your context window.
3. Throughput versus context¶
Predicted decode throughput at the measured BW_eff = 1296 GB/s, with 4 KV heads and FP8 KV:
| Context | KV traffic | B_token |
tok/s | vs short context |
|---|---|---|---|---|
| 0 | 0.00 GB | 15.88 GB | 81.6 | — |
| 4 K | 0.20 GB | 16.08 GB | 80.6 | −1 % |
| 32 K | 1.61 GB | 17.49 GB | 74.1 | −9 % |
| 128 K | 6.44 GB | 22.32 GB | 58.1 | −29 % |
| 262 K | 12.88 GB | 28.76 GB | 45.1 | −45 % |
Decode throughput nearly halves across the context window — even with FP8 KV and only 4 KV heads, which is already an aggressive configuration.
This has a direct consequence for benchmarking honesty: a tok/s number without a context length is meaningless. A benchmark run at 4 K and deployed at 262 K overstates real throughput by ~80 %. Module 12 treats context as a mandatory reported variable for exactly this reason.
4. Feasibility: does 262 K fit at all?¶
Capacity, not traffic, is the harder constraint. The budget:
32 GB card = 29.80 GiB
− weights − 18.80 GiB
− workspace / activations − 1.00 GiB (approx.)
────────────────────────────────────────────
available for KV ≈ 10.00 GiB
Now the 262 144-token cache under each configuration:
n_kv |
Format | 262 K cache | Fits in 10 GiB? |
|---|---|---|---|
| 8 | BF16 | 48.00 GiB | ✗ (4.8× over) |
| 8 | FP8 | 24.00 GiB | ✗ (2.4× over) |
| 8 | NVFP4 | 13.50 GiB | ✗ (1.35× over) |
| 4 | BF16 | 24.00 GiB | ✗ |
| 4 | FP8 | 12.00 GiB | ✗ (marginal) |
| 4 | NVFP4 | 6.75 GiB | ✓ |
262 K context on a 32 GB card with 18.8 GiB of weights requires
BOTH 4 KV heads AND 4-bit KV. Every other combination overflows.
And notice what does not help: quantizing the vision tower (−0.86 GiB) or the embeddings (−1.18 GiB) buys VRAM but not nearly enough to rescue an 8-KV-head BF16 configuration, which is 38 GiB over budget.
This is where the "capacity optimization ≠ bandwidth optimization" distinction from Module 01 finally pays off in the other direction. Evicting the vision tower and quantizing embeddings gained you zero tok/s — but at 262 K they are 2.04 GiB of headroom, and headroom is exactly what is scarce. The same change is worthless for one objective and valuable for the other. That is the whole reason the framework scores four factors instead of one.
Practical order of operations at long context:
1. reduce n_kv_heads ← architecture; biggest lever, but requires the model to have it
2. quantize KV to FP8/FP4 ← format; 2–3.6× capacity AND traffic
3. evict Class B/C tensors ← vision tower, unused adapters: pure capacity
4. quantize embeddings ← pure capacity
5. quantize weights harder ← LAST; you are attacking the smaller term
Note that the list is exactly inverted from the short-context priority order in Module 01 §8.
5. KV quantization is a different engineering problem¶
Weight quantization is offline, one-shot, and you can spend an hour per tensor. KV quantization is none of those things:
WEIGHTS KV CACHE
────────────────────────── ───────────────────────────────────
quantized once, offline quantized ONLINE, every token
full calibration available no future data — must be causal
error is static error accumulates over the context
cost amortized to zero cost is IN the decode critical path
Four consequences that shape any workable design:
1. Scaling must be per-head and per-token. These are the legal axes from Module 06 §4 — a per-token scale on the K vector factors out of the attention logit, so it is free. A per-channel scale across head_dim does not.
2. The quantizer must be cheap. It runs on the decode critical path. An MSE grid search (Module 05) is out of the question; absmax or a running percentile is what you can afford.
3. Errors persist. A weight error is the same on every token. A KV error is written once and then re-read for every subsequent token in the sequence — an early-token quantization error influences all 262 K downstream steps. This argues for keeping the first few tokens' KV in higher precision.
4. Which connects directly to attention sinks. Module 06 §3 showed the first token carries massive activations and absorbs surplus attention mass. It is also the entry that gets re-read most often. Both arguments point the same way:
keep the first N tokens' KV in BF16/FP8 (N ≈ 4–128, cheap: 128 tokens × 48 KiB = 6 MB)
quantize the rest to FP4
→ protects the sink mechanism
→ protects the most-re-read entries
→ costs ~0.06 % of the cache
This "sink-preserving KV quantization" pattern is common in current long-context systems, and both halves of its justification are things you derived in earlier modules.
6. The long-context ledger¶
Extend Module 04's ledger with the context term:
def ledger_at_context(B_token_weights_GB, n_layers, n_kv, head_dim,
kv_bytes_per_elem, context_len,
vram_GB=32.0, weights_GiB=18.80, workspace_GiB=1.0,
bw_eff_GBs=1296):
GiB = 1024**3
kv_per_token = 2 * n_layers * n_kv * head_dim * kv_bytes_per_elem # bytes
kv_traffic = context_len * kv_per_token / 1e9 # GB per decode step
kv_capacity = context_len * kv_per_token / GiB # GiB resident
b_token = B_token_weights_GB + kv_traffic
available = vram_GB * 1e9 / GiB - weights_GiB - workspace_GiB
return {
"kv_per_token_KiB": kv_per_token / 1024,
"crossover_tokens": B_token_weights_GB * 1e9 / kv_per_token,
"B_token_GB": b_token,
"predicted_tps": bw_eff_GBs / b_token,
"kv_capacity_GiB": kv_capacity,
"fits": kv_capacity <= available,
"headroom_GiB": available - kv_capacity,
}
Run it across your full context range before choosing a KV format. It answers the feasibility question in microseconds, and infeasible configurations are extremely common at 100 K+ on consumer cards.
Checkpoint¶
You should now be able to:
- Compute KV bytes/token from GQA geometry and explain why
d_modeldoes not appear. - Compute the crossover context length and interpret which side of it you are on.
- Predict the tok/s decay curve across a context range.
- Determine feasibility of a (model, context, VRAM) triple before running anything.
- Give both reasons for keeping sink-token KV in higher precision.
- Explain why the long-context optimization order is the inverse of the short-context one.
Ship it¶
Produce a context sweep for your deployment: KV bytes/token for your geometry in each format; the crossover length for each; a tok/s-vs-context table from 0 to your maximum; a feasibility table with headroom; and a written statement of which side of the crossover your p50 and p99 serving context lengths fall on.
If p99 is past the crossover, your next optimization is KV, not weights — regardless of what the rest of this course made you want to do.
Current as of¶
- Timeless: KV arithmetic, the crossover derivation, the capacity/traffic distinction, per-token scaling legality, the sink-preservation argument.
- Case-study pins:
W = 15.88 GB,BW_eff = 1296 GB/s, 48 layers /head_dim128, 32 GB VRAM.n_kv_headsis not pinned by the published inventory — both 8 and 4 are tabulated; substitute your model's actual value. - Refresh surface: runtime support for FP4 KV cache is less mature than for FP8. Verify what your serving stack actually implements before planning around 4-bit KV; an unsupported format is an infeasible plan, not an aggressive one.