Module 23 · Part VI · Deployment

Serving

The KV-Cache Memory Wall, Prefill and Decode, Continuous Batching, Paged Attention, and Speculative Decoding

By the end you can size a serving deployment and choose the techniques that raise throughput.

Builds onModule 12·Module 15·
Check yourself ▸

Before you start, can you already…

  • ☐implement a decoding loop with a KV cache and choose a decoding strategy for a task. (Module 12)
  • ☐turn the 2017 block into a 2024 block and justify each change. (Module 15)

If any is shaky, the linked module teaches it.

Open in Colab
4 Hours·Core·Advanced·0/5

Every generated token costs a pass through the model, and a popular model generates trillions. Serving is the engineering of doing that cheaply for many users at once. This module starts from the KV cache's memory, explains why generation is limited by memory traffic rather than arithmetic, and covers the three techniques every modern serving system uses: continuous batching, paged attention and speculative decoding.

23.1 The Memory WallRun this section

The KV cache of section 12.2 is large at production scale. Its size in bytes is the product of every stored dimension:

MemoryKV=2×nlayers×nkv_heads×dhead×L×B×nbytes\text{Memory}_{\text{KV}} = 2 \times n_{\text{layers}} \times n_{\text{kv\_heads}} \times d_{\text{head}} \times L \times B \times n_{\text{bytes}}
  • The 22 counts keys and values.
  • Each of the nlayersn_{\text{layers}} layers has its own cache, with nkv_headsn_{\text{kv\_heads}} key/value heads of width dheadd_{\text{head}}.
  • LL is the context length in tokens (not a layer count) and BB the batch size.
  • nbytesn_{\text{bytes}} is bytes per number, 2 for fp16/bf16.

Worked example: LLaMA-2 7B.

  • nlayers=32n_{\text{layers}} = 32, nkv_heads=32n_{\text{kv\_heads}} = 32, dhead=128d_{\text{head}} = 128, L=4096L = 4096, B=1B = 1, nbytes=2n_{\text{bytes}} = 2 (fp16):
MemoryKV=2×32×32×128×4096×2=2,147,483,648 bytes=2.0 GiB per single sequence\text{Memory}_{\text{KV}} = 2 \times 32 \times 32 \times 128 \times 4096 \times 2 = 2{,}147{,}483{,}648 \text{ bytes} = 2.0 \text{ GiB per single sequence}

A GiB is 2302^{30} bytes, so 2 GiB is about 2.15 GB.

Measured (lab 3a, experiments/kv_cache.py). The lab recomputes the total from the parameters and checks every factor. Two factors are easy to leave out, and leaving out either one halves the answer: the leading 22 for K and V, and the precision term nbytes=2n_{\text{bytes}} = 2 at the end.

With 32 sequences, the cache alone needs 64.0 GiB64.0 \text{ GiB}, about 68.7 GB: almost three times an NVIDIA RTX 4090's 24 GB before a single weight is loaded, and far more than the model's own 13.5 GB of fp16 weights.

Worked example. LLaMA-3 8B: 32 layers, 8 KV heads (grouped-query attention, section 15.5), dhead=128d_{\text{head}} = 128, L=8192L = 8192, fp16, B=1B = 1 gives 2×32×8×128×8192×2=1,073,741,8242 \times 32 \times 8 \times 128 \times 8192 \times 2 = 1{,}073{,}741{,}824 bytes =1.0= 1.0 GiB: twice the context of LLaMA-2 7B in half the cache, entirely from having 4 times fewer key/value heads.

KV cache and GPU memory calculator

While a model generates text it stores a key and a value vector for every past token, in every layer: the KV cache. See how much GPU memory that cache and the model weights need, and how sharing key/value heads (GQA, or MQA with a single shared head) shrinks the cache. Pick a model or type your own numbers. The estimate ignores tricks such as Gemma 2's sliding-window layers, which cap part of the cache.

5128K32K128K
1163264
The formula: 2 (keys and values) × layers × key/value heads × numbers per head × tokens × batch × bytes per number
BytesKV=2×L×nkv×dhead×S×B×belem\text{Bytes}_{\text{KV}} = 2 \times L \times n_{\text{kv}} \times d_{\text{head}} \times S \times B \times b_{\text{elem}}
KV cache memory
4.00 GiB

For 8,192 tokens at batch size 4

Model weights memory
14.90 GiB

8B params @ FP16

GPU memory needed, at leastweights + cache
18.90 GiB

Fits on (before activations and overhead): 1x RTX 3090/4090 (24GB)

What sharing key/value heads saves
Cache if every query head had its own keys and values (MHA):16.00 GiB
Cache with the key/value heads above:4.00 GiB
Cache is smaller by:4.0x
Predict, then try

Set up LLaMA-3 8B at 8,192 tokens, then double the context. Before you look, predict what happens to the cache, and then what happens when you double the batch size instead.

In words. A calculator from layers, key-value heads, head width, context, batch and precision to the KV cache in gigabytes, beside the memory of common GPUs.

23.2 Prefill and DecodeRun this section

The two phases of section 12.1 stress the hardware in opposite ways:

  • Prefill reads each weight once and uses it for every prompt token, so each byte feeds hundreds or thousands of multiply-adds: compute-bound.
  • Decode reads every weight and the whole KV cache for one token per sequence, so each byte feeds one or two multiply-adds and the arithmetic units wait: memory-bound.

Arithmetic intensity is operations per byte read. A modern GPU does a few hundred operations in the time it reads one byte (section 16.1), so lower intensity is memory-limited. With 2-byte weights and BB tokens at once, a matrix multiplication's intensity is roughly BB: about 1 for single-sequence decode.

Prefill mostly sets time to first token and decode the time per output token; systems often schedule the phases differently, some on separate machines. Since decode is limited by reading weights and cache, serving is about getting more tokens from each read. Sections 12.2 and 15.5 shrank the reads; the rest of this module shares them.

23.3 Continuous Batching

Applying the weights to many sequences per read costs little more than to one, so servers batch requests. In a fixed batch, everything waits for the longest sequence while finished slots sit idle. Continuous batching (Yu et al., 2022) re-forms the batch every step, so a waiting request takes a finished one's place at once, multiplying a busy server's throughput several times over.

23.4 Paged Attention

Many sequences mean many KV caches of unknown final length, and reserving each one's maximum wasted 60–80% of cache memory in earlier systems (Kwon et al., 2023, the vLLM paper). PagedAttention borrows virtual memory from operating systems: the cache lives in small fixed-size blocks, say 16 tokens, allocated as a sequence grows, with a per-sequence table mapping positions to blocks. Waste falls below 4%, so the same memory holds several times as many sequences, and sequences sharing a prefix, such as a long system prompt, can share its blocks.

Figure 23.4
Reserving memory versus paging it

The same eight requests arrive over time and grow a token at a time. Reserving each one’s maximum length up front leaves most of its space empty, so later requests wait. Paging hands out 16-token blocks only as they fill.

A pool of 96 blocks of 16 tokens. Reservation assumes a maximum of 384 tokens per request, since the final length is unknown when a request arrives.
Predict, then try

Eight requests arrive with unknown final lengths. Before switching from reserving each request's maximum to paging, predict how many of them can run at once in each mode.

In words. A pool of 96 blocks serving eight requests that arrive over time and grow token by token. Reserving each maximum fills the pool with empty blocks, so only four run. Paging hands out 16-token blocks as needed, so every request runs on arrival.

23.5 Speculative DecodingRun this section

Every token costs a full pass of the large model, however easy. Speculative decoding (Leviathan et al., 2023; Chen et al., 2023) lets a small draft model guess the next few tokens, and the large target model checks them all in one pass, which costs about the same as one token since decode is limited by reading weights. Each drafted token xx is accepted with probability

min⁡(1, p(x)q(x))\min\left(1, \, \frac{p(x)}{q(x)}\right)

where pp is the target model's probability for that token and qq the draft model's. At the first rejection, a replacement is drawn from the leftover distribution

p′(x)=max⁡(0, p(x)−q(x))∑x′max⁡(0, p(x′)−q(x′))p'(x) = \frac{\max\left(0, \, p(x) - q(x)\right)}{\sum_{x'} \max\left(0, \, p(x') - q(x')\right)}

and the remaining drafts are discarded. The output distribution is then exactly the target's: tokens the draft over-predicts are thinned by the acceptance test, and under-predicted ones are made up by p′p'. A good draft gets two or three tokens accepted per pass, a two- to three-fold speedup with identical output.

Figure 23.5
Draft, verify, keep what passes

The draft model proposed the four tokens in the top row. Each step verifies one: accepted with probability min(1, p/q), otherwise replaced from the leftover distribution and the rest thrown away.

If all four drafts pass, the large model’s same pass also yields a fifth token for free. Each column shows p (large model, red) and q (draft, gold) over five candidates.
Predict, then try

The draft model proposes four tokens. Before stepping, predict what happens to the remaining drafts once one is rejected, and whether the final text can ever differ in distribution from sampling the large model directly.

In words. Target and draft probabilities over candidate tokens at each position. Stepping through four drafted tokens, each is accepted with probability the smaller of one and p over q; at a rejection a replacement is drawn from the leftover distribution and the rest are discarded. The readout counts tokens per large-model pass.

Browser lab

Speculative sampling reproduces the large model’s distribution

200,000 single-token rounds: the draft model samples from q, the token is verified with min(1, p/q), and rejections are resampled from the leftover distribution. Section 23.5 says the emitted tokens follow p exactly, however different q is, and that a draft is kept with probability Σ min(p, q).

Exactness is a property of the sampling rule and holds for any q. What the draft model's quality changes is the acceptance rate, and with it the speed-up.

Exercises

Apply

What is the KV cache for 32 sequences of 4,096 tokens on LLaMA-2 7B (32 layers, 32 KV heads, head width 128, fp16), in GiB?

Apply

The large model gives a drafted token probability p=0.3p = 0.3 and the draft model gave it q=0.5q = 0.5. What is the chance it is accepted?

Apply

If each drafted token is accepted independently with probability α=0.8\alpha = 0.8 and the draft proposes γ=4\gamma = 4 tokens, the expected number of tokens produced per large-model pass is 1−αγ+11−α\frac{1 - \alpha^{\gamma+1}}{1 - \alpha}. Evaluate it.

Check

Why is decoding one sequence memory-bound while prefilling a long prompt is compute-bound, on the same hardware?

Extend

A system reserves each request's maximum length of KV cache up front. Explain two separate ways this wastes memory, and how paging fixes both.

Core Academic References

  • Pope et al. (2023): Efficiently Scaling Transformer Inference on TPU v4. MLSys 2023. arXiv:2211.05102
  • Yu et al. (2022): Orca: A Distributed Serving System for Transformer-Based Generative Models (continuous batching). OSDI 2022.
  • Kwon et al. (2023): Efficient Memory Management for Large Language Model Serving with PagedAttention (vLLM). SOSP 2023. arXiv:2309.06180
  • Leviathan, Kalman & Matias (2023): Fast Inference from Transformers via Speculative Decoding. ICML 2023. arXiv:2211.17192
  • Chen et al. (2023): Accelerating Large Language Model Decoding with Speculative Sampling. arXiv:2302.01318
  • Zhong et al. (2024): DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving. OSDI 2024. arXiv:2401.09670

Measured labs

These labs test what this module says by running real code. Each card states the idea, what the experiment found, and what it does not prove. The numbers come from experiments/results.json, the output of running experiments/run_all.sh on the tiny-transformers reference code at version 137f7ef. The project checks fail if a measured number in the module text stops matching that file.

Lab 3a — how much memory the KV cache needs, and what it saves

What the module says

Section 12.2 computes the KV cache for LLaMA-2 7B: the saved keys and values for every layer, head and position. The formula is 2 (keys and values) × 32 layers × 32 heads × 128 numbers per head × 4,096 positions × 2 bytes per fp16 number. It argues that at batch 32 this cache alone is bigger than a 24 GB graphics card.

What the experiment found

The product is 2,147,483,648 bytes: 2.0 GiB per sequence and 64 GiB at batch 32, so the module's point holds. A common slip is to drop the final ×2 bytes, which gives 1,073,741,824 (about 1.07 GB), half the true size. That smaller number happens to be the correct cache for LLaMA-3 8B at 8,192 positions, which shares keys and values across heads (GQA). The experiment also checks that the cache changes nothing: outputs with and without it agree to within 7.2e-07.

LLaMA-2 7B, 4,096 positions, fp16
2 GiB

per sequence; dropping the bytes factor gives 1.07 GB, off by 2x

the same, 32 sequences at once
64 GiB
LLaMA-3 8B with GQA, 8,192 positions
1 GiB

twice the context in half the memory

largest output difference, with vs without cache
max |Δ| = 0.000000715256

checked at every position of the same input

key/value computations while generating
468 → 31

15.1x fewer with the cache

speedup in seconds
1.53x

small, because on a ~125k-parameter model fixed per-call overhead dominates

What this does not prove

The correctness test compares the model's output scores, not the generated text. Tiny rounding differences between the two paths can flip a near-tie between two tokens, and the texts then diverge even though the cache is correct. The cache also needs position lookups that know how far into the sequence a new token is. The repository's learned position table does not provide that, which is one real cost of choosing it (see section 7.1).

Run it yourself

bash
cd tiny-transformers && .venv/bin/python experiments/kv_cache.py

The code being tested