LLM Inference | For You
Lecture 2 of 9

Systems Centered on the KV Cache

The main question of this lecture: the KV cache lives in GPU memory like a tenant who never cleans. How do we house it, share it, and evict it without wasting half the building?

Imagine you run a hotel. A guest checks in and says "I might stay one night, I might stay sixty. Reserve me the whole floor, just in case." You comply, because checkout time is unknowable in advance. Now do this for every guest, forever, and never let two guests share a floor. That is how pre-2023 serving stacks treated the KV cache. This lecture is the story of how the industry fixed it, one concrete flaw at a time.

Recall the protagonist from Lecture 1. Per token, per request, the cache stores K and V at every layer: \[ kv = 2 \times n_{layers} \times n_{kv} \times d_h \times b_{dtype} \quad \text{bytes/token,} \] and for Llama-3.3-70B (80 layers, GQA with \(n_{kv} = 8\) KV heads, \(d_h = 128\), BF16): \[ kv = 2 \times 80 \times 8 \times 128 \times 2\,\text{B} = 327{,}680\ \text{B} = \textbf{320 KB/token}. \] Two facts from Lecture 1 motivate everything today:

  1. The KV cache is per-request state that grows linearly with context. At 320 KB/token, a 70B-class model holding one 128K-token conversation caches \(320\ \text{KB} \times 131{,}072 \approx 40\) GiB. That is half of an H100, for one guest.
  2. Under agent workloads, KV becomes a long-lived asset shared across requests. Tool-using agents, multi-turn chat, and few-shot prompts resend the same prefixes over and over, so yesterday's cached KV is tomorrow's saved prefill.

From these two facts, four engineering questions follow, and they are the spine of this lecture: how do we place growing KV in HBM without wasting it (paging), how do we reuse identical prefixes across requests (prefix caching), what do we do when the working set exceeds one machine (tiered storage), and can the model itself be designed so that \(kv\) is smaller (MLA)? Plus a 2026 postscript: what if most of the state is not per-token at all (Kimi K3's dual cache)?

Main idea: KV bytes tie memory capacity, batch size, and throughput into one knot: free HBM caps the batch \(B\), \(B\) caps the decode intensity \(I \approx \frac{2PB}{2P + B \cdot kv}\), and \(I\) caps throughput. Every system in this lecture is an intervention somewhere in that chain. Keep the knot in your pocket.

The Naive Landlord: Contiguous Pre-allocation

The easiest thing you can do (and what HuggingFace Transformers and FasterTransformer actually did) is this: when a request arrives, hand it one contiguous KV region sized at max_seq_len. Contiguous, because the attention kernels of that era indexed KV with a base pointer plus dense strides. Sized at the maximum, because the output length is unknowable at admission.

You probably can guess why this is not the best way to run a hotel. Two failure modes, and they have exactly the names an OS textbook would give them:

? Suppose realized lengths \(D\) are uniform on \(\{1, \dots, R\}\), the kindest possible workload to a contiguous allocator. What fraction of reserved memory is wasted on average? Try it before opening; it is one line of expectation.

Derivation

\(\mathrm{E}[D] = \frac{1}{R}\sum_{d=1}^{R} d = \frac{R+1}{2}\), so the utilization \[ u \equiv \frac{\mathrm{E}[D]}{R} = \frac{R+1}{2R} \xrightarrow[R \to \infty]{} \tfrac{1}{2}. \] Half the reservation is empty, on average, in the best case. At \(R = 4096\): \(\mathrm{E}[D] = 2048.5\), \(\mathrm{E}[\text{waste}] = 2047.5\) tokens, \(u = 0.5001\). Real workloads are worse. Output lengths are heavy-tailed well below the limit, so \(\mathrm{E}[D] = 2\)K against \(R = 8\)K gives \(u = 0.25\), squarely inside the 20–40% utilization band the vLLM paper measured on production-like workloads. At any moment, 60–80% of the memory reserved for KV holds no live data.

Why should you care about an accounting statistic? Because utilization is a throughput multiplier, in three steps.

Step 1 (waste). \(\mathrm{E}[\text{waste}] = R - \mathrm{E}[D]\); define \(u = \mathrm{E}[D]/R\).

Step 2 (batch cap). With contiguous reservations of \(R\) tokens and \(M_{free}\) bytes of HBM available for KV, the number of concurrent requests obeys \[ B \le \frac{M_{free}}{kv \cdot R} = \frac{M_{free}}{kv \cdot \mathrm{E}[D]} \cdot u. \] The utilization enters multiplicatively: waste translates one-for-one into lost batch slots.

Step 3 (throughput). Decode is memory-bound; in the weight-dominated regime (\(B \cdot L \cdot kv \ll 2P\)) the intensity reduces to \(I \approx B\) and per-step time is \(2P/\beta\), so \[ \text{decode throughput} \approx \frac{B\,\beta}{2P} \propto B \propto u. \] A measured utilization of 20–40% means the system runs \(u^{-1} \in [2.5, 5]\times\) below the batch (and hence the throughput) it could sustain with perfect packing.

Worked example (fragmentation → batch cap → throughput): Serve Llama-3.3-70B (BF16, \(kv = 320\) KB/token, weights 140 GB) on a node of 8×H100 (80 GB each). Tensor parallelism shards weights to \(140/8 = 17.5\) GB per GPU; ≈ 2.5 GB of activations/workspace leaves \(M_{free} = 60\) GB for KV per GPU.

Contiguous policy: reserve \(R = 8\)K tokens per request, realized \(\mathrm{E}[D] = 2\)K, so \(u = 0.25\) and \[ B \le \frac{60 \times 10^9}{327{,}680 \times 8192} = 22.3 \;\Rightarrow\; B = 22. \] Ideal packing (reserve only what is used): \(B \le 60\times 10^9 / (327{,}680 \times 2048) \approx 89\).

Throughput per GPU (Lecture 1's formula with live KV reads at \(D = 2\)K, per-GPU weight bytes \(17.5 \times 10^9\), per-GPU \(kv = 40{,}960\) B/token under TP=8): contiguous gives \(I \approx 19.9\) and \(\approx 3{,}810\) tok/s; packed gives \(I \approx 62.4\) and \(\approx 11{,}940\) tok/s. That is a ≈ 3.1× gap created purely by allocation policy. Both intensities sit far below the H100 ridge \(\pi/\beta \approx 295\), so decode stays memory-bound and throughput scales nearly linearly with \(B\).
Note: the quick estimate "\(\text{throughput} \propto B\), so the gap is \(89/22 = 4.0\times\)" overstates the packed case by ~40%: at \(B = 89\) the KV reads are already 43% of weight bytes, so the \(I \approx B\) idealization breaks. The full intensity formula keeps you honest; use the short one only when weights dominate.

PagedAttention: Virtual Memory for KV

So contiguous, max-sized reservations waste 60–80% of HBM, and the waste multiplies straight through to throughput. How did operating systems solve exactly this problem for process memory? Think for a second before reading on. You already know the answer: fixed-size pages, a page table per process, on-demand allocation, and a shared free pool. PagedAttention (vLLM) imports the whole design into KV management:

the old way: one contiguous reservation per request (R = 8K) A: live 2K reserved, empty (75%) B: live 2K reserved, empty ← external fragmentation: a hole that fits nobody u = 0.25 → B capped at 22 instead of 89 the paged way: block tables + a shared pool (b = 16 tokens/block) A logical: 01 23 B logical: 01 2 block table = ⌈D/b⌉ entries (4 B each) global physical pool free B1 A1 free B0 A3 free A0 B2 A2 0 7 physical blocks need not be contiguous; freed blocks return to the pool only the last block of a request can be partially filled
How to: on top, the old policy reserves a fixed 8K-token region per request and mostly holds air. On the bottom, pick a logical block of A (green) and follow its arrow into the pool: placement is scattered, and only the tail block can be partially filled. Dashed blue arrows are B's mapping.

What does the last block cost us? A request of length \(D\) holds \(\lceil D/b \rceil\) blocks, of which only the last can be partially filled. If the final position is uniform within the last block (reasonable when \(D \gg b\)), the waste is \(b\lceil D/b \rceil - D \in \{0, \dots, b-1\}\), so \[ \mathrm{E}[\text{waste}] \le \frac{b-1}{2} \quad \text{tokens per request.} \] For \(b = 16\): \(\le 7.5\) tokens. Against an 8K-token reservation that is \(7.5/8192 \approx 0.092\%\) wasted, versus 75% under the contiguous policy of the worked example. Utilization ≈ 100%, and the batch cap becomes the ideal \(B \le M_{free}/(kv \cdot \mathrm{E}[D])\).

The price is metadata and indirection. The block table costs \(\lceil L/b \rceil\) entries per request: at \(L = 8\)K, 512 entries (2 KB with 4-byte ids) at \(b = 16\), versus 128 entries (512 B) at \(b = 64\). That is negligible next to the KV itself, but its read sits on the kernel's critical path. A contiguous kernel computes addresses as \(\text{base} + \text{stride} \cdot t\); a paged kernel must, per block, read the table entry and then gather. Attention kernels must explicitly support this: FlashAttention, FlashInfer, and FlashMLA all expose paged-KV interfaces. Sanity check of the trade: as \(b \to 1\), waste → 0 but the table grows to \(L\) entries with a per-token gather, and coalescing is destroyed. So \(b\) is negotiated, not minimized:

smaller pageslarger pages
less internal wastefewer block-table reads → less indirection
finer prefix-reuse granularity (next section)better coalescing / TMA efficiency
more table entries per tokencoarser reuse, more last-block waste
Engineering Takeaway: page size is not a free parameter. It is an interface contract between the kernel and the memory manager. FlashMLA's tiling is built around 64-token pages, so a manager handing it 16-token pages simply cannot call it. DeepSeek V3.2 needs two page sizes at once: 64 for its indexer-cache kernel and 1 for its token-level sparse-read operator; SGLang supports both simultaneously in one attention backend. Choose the kernel set first, then size the block pool; never treat page_size as a tunable you can sweep at deployment time.

Block-table indirection has a second payoff: two logical blocks can point to the same physical block. With a reference count per physical block, \(n\) parallel samples (or a beam fork) of one prompt share all prompt blocks read-only; the first request to write a shared block triggers a copy-on-write, so only the divergent suffix ever consumes new memory. This is exactly OS copy-on-write for forked address spaces, and it is the mechanical foundation of what comes next: a "cache hit" is nothing more than inserting shared physical block ids into a new request's block table and bumping refcounts.

Prefix Caching: Yesterday's KV Is Tomorrow's Saved Prefill

We stopped wasting HBM. Next flaw: we are still recomputing enormous prefixes that somebody (often the same agent, one turn ago) has computed already. What are we allowed to reuse, and how do we find it fast?

The key observation: KV content is a pure function of the prefix token sequence. Layer by layer, the K and V at position \(t\) are computed from the hidden state at \(t\), which is computed from positions \(1..t\) and nothing else. Sampling parameters decide which tokens get sampled next, but once the tokens are fixed, the KV is fixed. Therefore: \[ \text{prefix tokens identical} \;\Longrightarrow\; \text{corresponding KV blocks bitwise reusable.} \]

A hit is defined at block granularity and is prefix-only: a block of \(b\) tokens is reusable iff all \(b\) tokens match and all preceding blocks matched (KV at position \(t\) depends on the whole prefix, so matching can never skip a mismatch). Worked example: with \(b = 16\), a request whose first 8,000 tokens match a cached prefix but whose token 8,001 differs reuses exactly \(\lfloor 8000/16 \rfloor = 500\) blocks, and not one token more.

Engineering Takeaway: hits are all-or-nothing per block and prefix-only, which makes the ordering of prompt content a performance decision. Any dynamic bytes (a timestamp, a random session id, a per-request UUID) placed at token position \(j\) poison every block from \(\lfloor j/b \rfloor\) onward. The rule is strict: invariant content (system prompt, tool schemas, few-shot examples) first, variable content last.

How do we find the longest cached prefix? Two index structures dominate:

Eviction has two constraints that naive LRU violates: blocks pinned by in-flight requests (refcount > 0) must never be evicted from under an active request; and in the radix tree a node may be evicted only if it has no descendants (a child's KV is meaningless without its ancestors), so eviction proceeds leaf-first.

Routing becomes cache-aware. In a multi-instance deployment a hit exists only in one instance's memory, so routing stops being pure load balancing. Simplest policy: session stickiness. General policy (SGLang router, Dynamo KV-aware routing): keep a per-instance prefix index, and pick \[ \arg\max_i\; w_1 \cdot overlap_i - w_2 \cdot load_i, \] a weighted combination of cache overlap and current load. The payoff is exactly Lecture 1's discount: effective prefill FLOPs become \((1-h)\cdot 2P \cdot L\). But now \(h\) is an engineered quantity, not just a property of the workload.

Worked example (TTFT decomposition: a hit is fetched, not free): prefix length \(L\), hit rate \(h\), hit KV transferred at tier bandwidth \(\beta_{tier}\) (\(= \beta\) if HBM-resident, where transfer ≈ free), misses computed at effective throughput \(\pi \cdot \mathrm{MFU}\): \[ \mathrm{TTFT} \approx \underbrace{\frac{(1-h)\cdot 2P \cdot L}{\pi \cdot \mathrm{MFU}}}_{\text{compute the miss}} + \underbrace{\frac{h \cdot L \cdot kv}{\beta_{tier}}}_{\text{transfer the hit}}. \] Sanity: \(h = 0\) recovers pure prefill; \(h = 1\) with an HBM hit recovers ≈ 0 ✓. Numbers (70B, \(L = 8\)K, MFU = 0.4, PCIe tier at 64 GB/s): \(h = 0 \Rightarrow 2.90\) s; \(h = 0.8 \Rightarrow 0.58\) s compute \(+\, 33.6\) ms transfer \(= 0.61\) s. That is a 4.7× TTFT reduction, with the transfer term almost negligible at PCIe bandwidth. We will see exactly when it stops being negligible.

TA says: how much does this pay in production? Kimi K3's serving stack reports >90% prefix-cache hit rates on coding workloads, whose typical agent prefix is ≈ 400K tokens growing by ≈ 4K per turn (Moonshot, 2026). And the API prices it directly: $0.30/MTok cache-hit input vs $3.00/MTok cache-miss, a hit priced at exactly 1/10 of a miss, just like Lecture 1's rule of thumb. One caveat that foreshadows the end of this lecture: for hybrid linear-attention models the recurrent-state part of the cache can only be reused at sparse state-checkpoint boundaries (every 6,144 tokens in vLLM's K3 support, against 512-token hash blocks), so the effective \(h\) on the state side is rounded down to the last checkpoint. Keep that in the back of your mind.

When the Building Is Full: Tiered Storage and Offload

Next flaw: even perfectly packed, HBM is finite, and there are two pressures pushing idle KV out of it. First, hit rate is monotone in retained KV: every evicted byte is a future hit converted into a full recompute. Second, idle KV competes with the running batch: HBM held by a paused agent session cannot host concurrent requests, and by the batch-cap formula it subtracts directly from throughput. And agents are bursty: inter-turn gaps of minutes (human reading time, tool execution) keep megabytes-to-gigabytes of state in the scarcest memory in the data center doing nothing.

The resolution is the classical one: sink idle KV down a memory hierarchy and buy it back when needed. Here is the ladder:

tiercapacitybandwidthvs. HBM
HBM3 (H100)80 GB3.35 TB/s
CPU DRAM (PCIe 5.0 x16)TB-scale64 GB/s≈ 2%
remote pool (RDMA)cluster-scale25–50 GB/s per link≈ 1%
local NVMeseveral TB7–14 GB/s≈ 0.3%

Bandwidth drops ~50× from HBM to host DRAM, and another ~2–9× into the NVMe/remote tiers. So offloading is rational only if fetching back beats recomputing. That is a quantitative question. Let's just count both sides.

Derivation (the break-even bandwidth): A cached prefix of \(L\) tokens must be restored to HBM. Option A: transfer it. Option B: recompute it as a prefill (compute-bound) at \(\pi \cdot \mathrm{MFU}\): \[ T_{xfer} = \frac{L \cdot kv}{\beta_{tier}}, \qquad T_{re} \approx \frac{2P \cdot L}{\pi \cdot \mathrm{MFU}}. \] Transfer is worthwhile iff \(T_{xfer} \le T_{re}\): \[ \frac{L \cdot kv}{\beta_{tier}} \le \frac{2P \cdot L}{\pi \cdot \mathrm{MFU}} \;\Longleftrightarrow\; \beta_{tier} \ge \beta^* \equiv \frac{kv \cdot \pi \cdot \mathrm{MFU}}{2P}. \] The prefix length \(L\) cancels. Both options are linear in \(L\), so the decision is a property of (model, hardware tier) alone, through the ratio \(kv/(2P)\): bytes of KV per token relative to FLOPs per token. Note that \(\beta^*\) is exactly the bandwidth at which the KV data stream could be regenerated as fast as it can be moved: a roofline ridge point for cache restoration, the tiered-storage echo of Lecture 1's \(\pi/\beta\).

Numbers (70B, \(\pi = 990\) TFLOPS, MFU = 0.4): effective compute \(3.96 \times 10^{14}\) FLOP/s; recompute per token \(2P/(\pi\,\mathrm{MFU}) = 1.4\times 10^{11}/3.96\times 10^{14} \approx 354\) µs/token. \[ \beta^*_{GQA} = \tfrac{327{,}680 \times 3.96\times 10^{14}}{1.4\times 10^{11}} \approx 0.93\ \text{GB/s}, \qquad \beta^*_{MLA} = \tfrac{35{,}136 \times 3.96\times 10^{14}}{1.4\times 10^{11}} \approx 0.099\ \text{GB/s}. \] Per-token transfer times: PCIe 5.0 x16 → 5.12 µs (GQA) / 0.55 µs (MLA); NVMe 7–14 GB/s → 46.8–23.4 µs (GQA); RDMA 25–50 GB/s → 13.1–6.6 µs (GQA). Every realistic tier clears the break-even by 1–2 orders of magnitude: at \(L = 8\)K, PCIe swap-in of a GQA prefix costs 42 ms vs. 2.90 s of recompute: a 69× saving.
time to restore an L = 8K GQA-70B prefix to HBM (log scale) 0.01 ms 0.1 ms 1 ms 10 ms 100 ms 1 s 10 s recompute (MFU 0.4) 2,900 ms NVMe, 7–14 GB/s (GQA) 192–384 ms remote RDMA, 25–50 GB/s 54–107 ms CPU DRAM over PCIe, 64 GB/s 42 ms PCIe, MLA cache (34 KB/tok) 4.5 ms
How to: each bar runs from "no time" to the restore time of one 8K-token prefix, on a logarithmic axis. Recompute is the red bar; every storage tier beats it by one to two orders of magnitude, and shrinking \(kv\) (bottom bar, MLA) shortens transfer proportionally. The unseen threshold: recompute would tie only at \(\beta_{tier} = \beta^* \approx 0.93\) GB/s, far below even NVMe.

Two caveats keep the decision honest. (i) Recompute consumes GPU compute that could serve other requests, an opportunity cost not in \(T_{re}\), which only strengthens the case for transfer when the GPU is busy. (ii) Latency-bound small prefixes on slow tiers can make the transfer term visible in TTFT: NVMe at 7 GB/s needs 384 ms for 8K GQA tokens. And two corollaries do real work:

  1. Architectural compression improves offload economics. Since \(T_{xfer} \propto kv\) while \(T_{re}\) is architecture-independent, shrinking \(kv\) speeds transfer linearly: MLA's 34 KB/token over PCIe moves 9.3× faster than GQA's 320 KB/token (0.55 vs. 5.12 µs/token; at \(L = 8\)K, 4.5 ms vs. 42 ms). Compression and offload are complementary, not competing.
  2. Layer-wise pipelining hides the transfer. KV is consumed layer by layer, so transfer and compute can overlap: compute attention for layer \(i-1\) while layer \(i\)'s KV streams in. If per-layer compute ≥ per-layer transfer, only the last layer's transfer is exposed: \[ T_{exposed} \approx \frac{T_{xfer}}{n_{layers}} \quad \text{when} \quad \frac{2P/n_{layers}}{\pi\,\mathrm{MFU}} \ge \frac{kv/n_{layers}}{\beta_{tier}}. \] For the 70B/PCIe example at \(L = 8\)K: per-layer compute is 36.2 ms vs. per-layer transfer 0.52 ms. The condition holds with 69× headroom, and the exposed stall is ≈ 0.52 ms instead of 42 ms. This pipelining is standard practice in production offload stacks.

The industrial substrate already exists. Mooncake (Moonshot AI, open-sourced) organizes DRAM and SSDs across the cluster as a distributed KV cache pool, with prefill and decode disaggregated and KV flowing through an RDMA transfer engine; its public production trace is a commonly used workload dataset for exactly these policies, and in 2026 deployments Mooncake Store serves as the L3 tier behind both major engines (vLLM via its MooncakeStoreConnector, SGLang via HiCache L3). NIXL (NVIDIA) unifies asynchronous movement among VRAM, DRAM, NVMe, and remote memory, auto-selecting NVLink or RDMA/UCX backends.

Engineering Takeaway: the offload decision is model- and tier-specific, not workload-length-specific (\(L\) cancels). Compute \(\beta^*\) for your model once; if your cheapest tier clears it (it almost always does), offload is bandwidth-justified, and the real design variables become when to evict down (session-gap prediction), which tier per prefix (hot → DRAM, warm → NVMe, cold → remote), and how to overlap restoration with layer-by-layer compute. Reuse an existing transfer library (NIXL) rather than hand-rolling DMA.

MLA: Teach the Tenant to Pack Smaller

Main idea (restated): free HBM caps \(B\), \(B\) caps intensity, intensity caps throughput. Every byte of \(kv\) we eliminate slackens the whole chain at once. So far we managed \(kv\) as given. Now the model itself changes the quantity.

Sections so far housed, shared, and evicted the tenant. But what if the tenant simply carried less luggage? Multi-head Latent Attention (MLA, introduced in DeepSeek-V2, carried into V3/R1/V3.2) redesigns the attention layer so that what must be cached is small: instead of caching K and V of all heads, project the hidden state down to a low-rank latent and cache that: \[ c_t = W^{DKV} h_t \in \mathbb{R}^{512}, \qquad k_t = W^{UK} c_t, \quad v_t = W^{UV} c_t, \] with K and V recovered on demand through up-projection weights, resident like any other parameters. One latent serves both K and V of all heads: a joint compression.

There is one catch, and it is worth a minute of your thought: what about RoPE?

? If we rotate the up-projected key by the position-dependent rotation \(R_j\), the score becomes \(q_i^{\top} R_j W^{UK} c_j\). Why does this break the plan to fold \(W^{UK}\) into the query projection once, at load time? Think about what "fold" requires before opening.

Possible answer

Folding needs a single merged matrix \(W_Q^{\top} R_j W^{UK}\). But \(R_j\) changes with position \(j\), so no such fixed matrix exists: the rotation sits exactly between the two matrices we wish to merge. RoPE would block the absorption trick that makes MLA cheap at decode. MLA's fix is decoupled RoPE: positional information takes a separate, narrow path, a 64-dimensional key \(k^R_t\) that carries RoPE, is shared by all heads (MQA-style), and is concatenated with the cached latent:

\[ \text{cached per token per layer} = \big[\, c_t\ (512),\; k^R_t\ (64) \,\big] \Rightarrow 576\ \text{elements.} \] At FP8 that is 576 B per token per layer; over DeepSeek-V3's 61 layers: \[ kv_{MLA} = 576 \times 61 \times 1\ \text{B} = 35{,}136\ \text{B} \approx \textbf{34 KB/token}, \] i.e. ≈ 4.4 GB at 128K context, versus ≈ 40 GiB for GQA-70B.

h_t hidden state W_DKV down-project the cache (per token, per layer) c_t: 512 dim k^R: 64 dim 576 elements = 576 B at FP8 (RoPE lives only in the 64-dim sliver) W_UK on demand k_t all heads W_UV on demand v_t all heads absorbed mode: score û_t,h against the latent directly; K never materialized kv bytes / token (whole model) GQA-70B: 320 KB MLA: 34 KB ≈ 9.3×
How to: follow \(h_t\) into the dashed green cache box. Per token, only the 512-dim latent plus the amber 64-dim RoPE sliver is stored. In materialized mode (solid arrows) K and V are rebuilt through up-projections; in absorbed mode (red dashed) the query is folded to score against the latent directly. Right: what this does to bytes per token.

Two equivalent computation modes. (a) MHA mode: recover full per-head K and V from the latent and run ordinary attention. This costs an extra GEMM and transient full-size K/V; fine for prefill, which is compute-bound anyway and well served by mature dense kernels. (b) MQA mode (absorption). This is where the elegance lives. Now watch what happens to the score between query at \(t\) and key at \(j\), head \(h\) (ignoring the RoPE path for one line): \[ s_{tj} = q_{t,h}^{\top} k_{j,h} = \big(W_{Q,h} h_t\big)^{\top} W^{UK}_h c_j = \underbrace{\big(W^{UK}_h W_{Q,h} h_t\big)^{\top}}_{\text{absorbed query } \hat{u}_{t,h} \in \mathbb{R}^{512}} c_j. \] The product \(W^{UK}_h W_{Q,h}\) is a single fused matrix computed once at load time; each head's absorbed query is 512-dimensional and scores directly against the cached latent. K is never materialized. The RoPE path re-enters additively: the full score is \(\hat{u}_{t,h}^{\top} c_j + q^R_{t,h}{}^{\top} k^R_j\), which is exactly why \(k^R\) had to be decoupled: that second term cannot fold into the first. On the value side the same magic works in reverse: \[ out_{t,h} = \sum_j \alpha_{tj}\, W^{UV}_h c_j = W^{UV}_h \underbrace{\sum_j \alpha_{tj}\, c_j}_{\in\, \mathbb{R}^{512}}, \] so the attention-weighted sum accumulates in the 512-dim latent space and is up-projected once. And \(W^{UV}\) merges with the output projection just as \(W^{UK}\) merged with \(W_Q\).

? After absorption, DeepSeek-V3's \(n_h = 128\) query heads all score against the same cached \([c_j; k^R_j]\). What does that make MLA look like, in Lecture-1 architecture vocabulary? And what are the bytes read per decode step per cached token, versus GQA?

Possible answer

It is multi-query attention with head dim 576: all 128 heads attend to one shared 576-dimensional "KV" per token, so a decode step reads one latent cache, not one KV copy per head. Bytes per cached token per step: exactly \(kv_{MLA} = 576\ \text{B} \times 61 \approx 34\) KB, versus 320 KB for GQA-70B: \[ \frac{kv_{GQA}}{kv_{MLA}} = \frac{327{,}680}{35{,}136} \approx 9.3. \] In the intensity formula the \(kv\) term divides by 9.3: bandwidth traffic per step and HBM capacity per cached token drop by the same factor. That means larger \(B\) at fixed memory and higher throughput at fixed bandwidth.

Worked example (one decode step, GQA vs. MLA absorbed mode): context \(L = 32\)K, BF16 weights, H100. GQA-70B: KV read \(= 32{,}768 \times 327{,}680 \approx 10.7\) GB → time floor \(10.7\times 10^9 / 3.35\times 10^{12} \approx 3.2\) ms per token just for KV, on top of the \(2P/\beta \approx 42\) ms weight read. MLA (absorbed MQA mode): KV read \(= 32{,}768 \times 35{,}136 \approx 1.15\) GB → 0.34 ms. At 128K context the gap widens to 42.9 GB (12.8 ms) vs. 4.6 GB (1.37 ms). The 9.3× \(kv\) ratio appears verbatim in per-step latency. And it compounds with capacity: the same HBM holds 9.3× more MLA-cached tokens.

The costs, because there is no free lunch. (i) Effective head dim grows to 576: attention FLOPs per token per layer per cached token scale as \(4\, n_h\, d_{eff}\), so absorbed MLA costs \(4 \times 128 \times 576 = 294{,}912\) versus \(4 \times 64 \times 128 = 32{,}768\) for GQA-70B: 9× the attention FLOPs. The trade is deliberate: exchange surplus compute for scarce bandwidth. That is the right direction, because decode is memory-bound. (For contrast: had MLA materialized full K/V instead, the ratio would be only 2×.) (ii) Non-standard kernel shape: "high-compute-intensity MQA" (128 query heads, one shared 576-dim KV) matches no standard kernel's tiling, so MLA needs a dedicated implementation: FlashMLA (Lecture 7), which supports FP8 KV with the latent quantized on write, and (as you now expect) dictates page size 64.

Engineering Takeaway: MLA moves the bottleneck rather than removing it: after absorption, decode attention is compute-shaped (9× the FLOPs of GQA at 1/9.3 the bytes). Expect MLA decode to be kernel-quality-bound, not bandwidth-bound. Budget for FlashMLA-class kernels and accept the page-size-64 contract. Size memory with 34 KB/token (FP8), but do not reuse GQA intuition when sizing compute.

The 2026 Plot Twist: Two Kinds of State (Kimi K3)

Everything so far assumed all model state is per-token KV. The 2026 generation of flagship hybrids breaks that assumption. Kimi K3 (Moonshot AI; 2.8T total / 104B activated, 93 layers, 1M context) interleaves two attention mechanisms in a 3:1 ratio: 69 KDA layers (Kimi Delta Attention, a gated delta-rule linear attention) plus 24 Gated-MLA layers at positions 4, 8, …, 92, with NoPE throughout. Each mechanism carries a different kind of serving state:

Step 1: the MLA side is per-token and paged. Each Gated-MLA layer caches exactly the MLA object from the previous section: a 512-dim latent plus a 64-dim positional key (under NoPE the key is never rotated, but it is still cached), 576 elements per token per layer: \[ kv^{K3}_{MLA} = 576 \times 2\,\text{B} \times 24\ \text{layers} = 27{,}648\ \text{B} = 27.0\ \text{KiB/token} \quad (\text{FP8 halves it to} \ 13.5), \] growing linearly in \(L\), paged and prefix-cacheable exactly as above.

Step 2: the KDA side is a fixed-size recurrent state. A KDA layer keeps, per head, a state matrix \(S_t \in \mathbb{R}^{128 \times 128}\), updated by a gated delta-rule recurrence \[ S_t = \big(I - \eta_t k_t k_t^{\top}\big)\, \mathrm{Diag}(\alpha_t)\, S_{t-1} + \eta_t\, k_t v_t^{\top}, \] with per-channel decay gate \(\alpha_t\) and delta-rule step size \(\eta_t\) (not to be confused with bandwidth \(\beta\)!): \[ \text{KDA state per layer} = 128 \times 128 \times 96\ \text{heads} \times 2\,\text{B} = 3.0\ \text{MiB}, \] fixed regardless of context length. Over 69 layers: \(\approx 0.20\) GiB per request. A constant, not a function of \(L\).

Step 3: the whole-request footprint. At \(L = 2^{20}\) (1M tokens): MLA cache \(= 27.0\ \text{KiB} \times 2^{20} = 27.0\) GiB; KDA state = 0.20 GiB; total ≈ 27.2 GiB, versus 320 GiB for a GQA-70B-style model. ≈ 12×. Sanity checks worth doing yourself: the crossover where KDA state exceeds the MLA cache sits at \(0.20\ \text{GiB} / 27.0\ \text{KiB} \approx 7{,}900\) tokens. Past ≈ 8K context the per-token cache dominates again, but with an 11.8× gentler slope than GQA. And one KDA layer's whole state equals \(3.0\ \text{MiB} / 1152\ \text{B} \approx 2{,}731\) tokens of one MLA layer's per-token cache: the constant price paid so that most of the model's state never grows.

Why does the KDA side break prefix caching? The hit condition rests on attention KV being a pure per-token function of the prefix: a block of \(b\) tokens is reusable iff its tokens match. A KDA state has no such granularity: it is a single running matrix, overwritten in place at every token, a function of all tokens \(1..t\). There is no "state of token \(t\)" to share independently, and the state cannot be reconstructed from any suffix. Classic block-granular caching is impossible; the fix, in both production engines, is to checkpoint the recurrent state at sparse boundaries and treat checkpoints as cache entries: vLLM decouples state blocks from hash blocks (6,144-token state blocks against 512-token hash blocks, copy-on-write restore, off by default); SGLang runs one unified pool (54 MB FP32 TP8 KDA state block + 27 KB MLA KV block) with copy-on-write / snapshot / donate primitives, so forking, rollback (speculative decoding, Lecture 8), and cache handoff operate uniformly across both cache types. In Mooncake, NIXL exposes each physical page through dual page views (the same bytes addressable as MLA KV pages and as KDA state checkpoints), so the pool is typed per state but the transport is unified.

TA says: a hybrid model is two KV-cache systems in one process. Size capacity from the sum (27 KiB/token growing + 0.20 GiB fixed per request for K3), but design reuse from the intersection: the effective prefix-cache hit is limited by the coarser of the two granularities (512-token MLA blocks vs. 6,144-token KDA checkpoints). When prompting agents, place mutable content so turn boundaries land near state checkpoints, or accept replaying up to one state block per turn (cheap: linear attention costs \(O(1)\) state-size work per replayed token). And remember this when Lecture 5 disaggregates prefill from decode: both state types must cross the network together.

Common Pitfalls (Read Before You Operate)

Important: five ways this all goes wrong in practice:
  1. Dynamic-content poisoning. A timestamp or session nonce at token position \(j\) makes every block from \(\lfloor j/b \rfloor\) onward unhittable. One dynamic field early in a system prompt silently zeroes the cache's value. Fix: invariant content strictly before variable content; audit prompts for hidden entropy sources.
  2. Treating page size as a tuning knob. It is a kernel–memory-manager contract (FlashMLA: 64; V3.2: 64 and 1 simultaneously). Setting page_size=16 and then enabling FlashMLA fails at integration time, not at design time.
  3. Evicting referenced blocks. Prefix-cache eviction must respect refcounts (in-flight blocks are pinned) and, in radix trees, leaf-first order. An LRU that ignores either corrupts live requests or breaks prefix semantics.
  4. Assuming a cache hit is free. A hit in DRAM/NVMe/remote tiers costs \(L \cdot kv / \beta_{tier}\). Cheap at PCIe bandwidth for small \(L\); visible at NVMe bandwidth and long \(L\) (384 ms for 8K GQA tokens at 7 GB/s) unless layer-wise pipelining hides it.
  5. Believing MLA removes the memory problem. MLA divides \(kv\) by ≈ 9.3, a constant factor. Total KV still grows linearly in \(L\), and every decode step still reads all of history. Constant-factor relief shifts the crossover; it does not change the scaling. Attacking the scaling law itself is Lecture 3's subject.

Research Thinking

How to: read the starting point; read the question and think, for a minute, a day, a week... and only then open the answers. You are not supposed to reinvent several months of engineering; it's the habit of thinking about these things that counts: you have ideas, you try; if they don't work, you think again.

? Card 1, "invent" paged KV memory. Starting point: contiguous reservations of \(R = 8\)K tokens against realized \(\mathrm{E}[D] = 2\)K waste 75% of HBM, and by the batch cap that waste is a 4× throughput multiplier. Question: before vLLM existed (2022), sitting with an OS textbook, what design would you propose? And which two properties of the workload make it safe? Think first; then look.

What happened

Your OS textbook says: fixed-size pages, per-process page tables, allocate on demand, free into a shared pool. The two workload properties: (i) the reservation-blindness matters precisely because output lengths are unknowable at admission (so demand-paging beats pre-sizing); (ii) all requests' KV have the same per-token byte cost, so one block size fits every request and external fragmentation dies by construction. This is exactly PagedAttention (vLLM, 2023): expected waste drops to \((b-1)/2 = 7.5\) tokens at \(b = 16\), and the block abstraction became the substrate for everything in this module: sharing, prefix caching, offload. vLLM, SGLang, and TensorRT-LLM all now use it. The one cost the textbook also predicts: indirection on the critical path, and a page-size war settled not by operators but by kernels (FlashMLA → 64; V3.2 → 64 + 1).

? Card 2, which instance should take the request? Starting point: cache-aware routing maximizes \(w_1 \cdot overlap - w_2 \cdot load\). Your router can send an \(L = 8\)K request to instance A (prefix overlap \(h = 0.8\), cached in a PCIe-resident pool, current queueing adds 100 ms) or instance B (cold, \(h = 0\), no queue). Using the TTFT decomposition with the 70B / MFU 0.4 constants: which wins? And at what hit rate would you be indifferent (ignoring queueing)? Compute before opening.

Possible answer (Module 2, Exercise 2.5)

A: \(0.2 \times 2.896\ \text{s} + 0.8 \times 8192 \times 327{,}680 / 64\times 10^{9}\ \text{s} + 0.1\ \text{s} = 0.579 + 0.034 + 0.1 = 0.71\) s. B: the full 2.90 s of prefill. Choose A: the 100 ms queue is repaid many times over. For indifference versus halved TTFT, solve \((1-h)\,T_{re} + h\,T_{xfer} = T_{re}/2\) with \(T_{xfer} = 8192 \times 327{,}680 / 64\times10^9 = 41.9\) ms: \[ h = \frac{T_{re} - T_{re}/2}{T_{re} - T_{xfer}} = \frac{2.896 - 1.448}{2.896 - 0.042} \approx 0.51. \] Any overlap above ≈ 51% halves TTFT even when the hit arrives over PCIe. This is why cache-aware routing pays, and why session stickiness is such a strong baseline.

? Card 3, can a recurrent state be prefix-cached at all? Starting point: the hit legality of the prefix-caching section needs cache content to be a pure per-token function of the prefix; a KDA state \(S_t\) is overwritten in place and entangles all tokens \(1..t\). Question: you're building vLLM support for Kimi K3. What is the minimal change to the caching abstraction that makes the 0.20 GiB of KDA state reusable? And what do you give up in granularity? Sketch your design before opening the production answers.

Existing attempts (2026)

What you give up: hit granularity degrades to the coarser of the two granularities, so effective \(h\) on the state side is rounded down to the last checkpoint. The general lesson: once the model carries two state types, the pool must be typed per state but the transport and the CoW machinery can be unified.

Have Fun! Will My Hit Survive the Trip?

You are the router. A request with a cached prefix arrives, but the cache lives on some storage tier. Is fetching the hit worth it, or should you just recompute (or route cold)? Set the hit rate, the context, and the tier, and watch the TTFT decomposition. Can you find a corner where a hit hurts? (Hint frames: slowest tier, long context, GQA model... then explain it still rarely loses to recompute (the break-even derivation tells you why).)




Playground physics: TTFT = (1−h)·2P·L/(π·MFU) + h·L·kv/βtier, with the lecture's constants: 2P = 1.4×10¹¹, β = 3.35 TB/s, π·MFU = 990 TFLOPS × 0.4. Check it against the worked example: MLA, h = 0.8, L = 8K, PCIe should land near 0.58 s; GQA near 0.61 s.

Seminar & Homework

Take Lab 2: KV Cache and Prefix Caching from the course's hands-on pack (vLLM, one GPU):

Pitfalls to dodge in your own benchmark: duplicated prompts silently inflate \(h\); comparing engines with caching toggled inconsistently; forgetting that hit counters reset per engine restart.

Summary

← Lecture 1: Principles, Metrics, and the Roofline Lecture 3: Long Context and Sparse Attention →