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.
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.
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:
- The counts keys and values.
- Each of the layers has its own cache, with key/value heads of width .
- is the context length in tokens (not a layer count) and the batch size.
- is bytes per number, 2 for
fp16/bf16.
Worked example: LLaMA-2 7B.
- , , , , , (
fp16):
A GiB is 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 for K and V, and the precision term at the end.
With 32 sequences, the cache alone needs , 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), , ,
fp16, gives bytes 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.
For 8,192 tokens at batch size 4
8B params @ FP16
Fits on (before activations and overhead): 1x RTX 3090/4090 (24GB)
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 tokens at once, a matrix multiplication's intensity is roughly : 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.
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.
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 is accepted with probability
where is the target model's probability for that token and the draft model's. At the first rejection, a replacement is drawn from the leftover distribution
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 . A good draft gets two or three tokens accepted per pass, a two- to three-fold speedup with identical output.
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.
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.
Speculative sampling reproduces the large model’s distribution
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
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?
The large model gives a drafted token probability and the draft model gave it . What is the chance it is accepted?
If each drafted token is accepted independently with probability and the draft proposes tokens, the expected number of tokens produced per large-model pass is . Evaluate it.
Why is decoding one sequence memory-bound while prefilling a long prompt is compute-bound, on the same hardware?
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
- the same, 32 sequences at once
- 64 GiB
- LLaMA-3 8B with GQA, 8,192 positions
- 1 GiB
- largest output difference, with vs without cache
- max |Δ| = 0.000000715256
- key/value computations while generating
- 468 → 31
- speedup in seconds
- 1.53x
per sequence; dropping the bytes factor gives 1.07 GB, off by 2x
twice the context in half the memory
checked at every position of the same input
15.1x fewer with the cache
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
cd tiny-transformers && .venv/bin/python experiments/kv_cache.pyThe code being tested