Phase 09 — Inference Serving Internals (vLLM/TensorRT-class)
The phase where the cost math from Phase 00 becomes a running system. Phase 00 told you decode is memory-bandwidth-bound and the KV-cache is the wall. This phase builds the three mechanisms a real serving engine uses to fight exactly those facts: PagedAttention (so the KV-cache doesn't fragment), continuous batching (so the batch stays full and amortizes the bandwidth-bound weight read), and speculative decoding (so you do fewer sequential decode steps). Get these and you can read the vLLM scheduler, size a fleet, and answer the serving-design round of any senior interview.
Why this phase exists
A model that passes evals is worth nothing until it serves traffic at a price and a latency the
business can live with. The gap between "I called model.generate()" and "I run a 100-QPS endpoint
at p99 < 200 ms TTFT for $X/1M tokens" is the serving stack, and the serving stack is three
ideas:
- The KV-cache is paged. Naive serving reserves a contiguous
max_lenslab of KV per request and wastes 60–80% of it to fragmentation and over-allocation. PagedAttention stores KV in fixed blocks addressed through a per-sequence block table — exactly like OS virtual memory — so memory is near-fully utilized and sequences can share blocks (prefix caching). - Batching is continuous. Static batching runs a fixed batch to completion, so one long request holds the whole batch hostage (head-of-line blocking). Continuous (in-flight) batching admits and retires requests every decode step, keeping the batch full — the 2–4× throughput win.
- Decoding can be speculative. Because decode is bandwidth-bound, you have spare compute. A cheap
draft proposes several tokens; the target verifies them in one parallel pass and accepts
with
min(1, p_target/p_draft), resampling the residual on rejection — provably preserving the target distribution while doing fewer sequential target steps.
Everything else in serving — chunked prefill, KV quantization, the SLOs — hangs off these three.
Concept map
┌──────────────────────────────────────────────┐
│ Phase 00 facts: decode is BW-bound; │
│ the KV-cache is the memory wall. │
└──────────────────────────────────────────────┘
│ │ │
┌──────────────┘ ┌─────────┘ ┌─────────┘
▼ ▼ ▼
FRAGMENTATION HEAD-OF-LINE SEQUENTIAL DECODE
(naive max_len KV) (static batch) (one token / step)
│ │ │
▼ ▼ ▼
PAGEDATTENTION CONTINUOUS SPECULATIVE DECODING
blocks + block table BATCHING draft → verify → accept
~0 fragmentation admit/retire/step min(1, p/q) + residual
prefix sharing / CoW 2–4× throughput distribution-preserving
│ │ │
└───────────┬───────────┴──────────┬───────┘
▼ ▼
SLOs: TTFT (prefill) throughput vs latency
TPOT/ITL (decode) ($/1M, p99, QPS)
│
▼
the stacks: vLLM · TensorRT-LLM · SGLang · DeepSpeed-Inference · ORT
The lab
| Lab | You build | Difficulty | Time |
|---|---|---|---|
| lab-01 — PagedAttention, Continuous Batching & Speculative Decoding | a paged KV-block allocator (BlockManager) with per-sequence block tables and the new-block-on-full off-by-one, an iteration-level ContinuousBatchingScheduler that beats static batching on wasted slot-steps, and the speculative_accept rule with a statistical proof it preserves the target distribution | ⭐⭐⭐⭐☆ | 4–6 h |
The lab is a runnable, test-verified miniature — see the lab standard.
Run it red (pytest test_lab.py), make it green, then read solution.py.
Integrated scenario ideas
- Size a vLLM deployment: given a 13B model, 80 GB GPU, 8k context — compute the number of KV
blocks (
gpu_memory_utilization × free / block_bytes), the max concurrent sequences, and the QPS. Show the KV blocks, not the weights, set the ceiling (ties to Phase 00). - Defend continuous batching: take a skewed workload (one 2k-token answer + many 50-token ones) and show static batching's wasted slot-steps vs continuous — the throughput slide for a design review.
- Decide whether to add speculative decoding: estimate the speedup from an acceptance rate
αand a draft cost, find the breakeven, and explain why it helps decode (bandwidth-bound, spare compute) but not prefill. - Pick a stack: vLLM vs TensorRT-LLM vs SGLang for a given workload (open weights vs NVIDIA-only, throughput vs lowest latency, complex prefix-sharing vs simple) — and name the deciding constraint.
Deliverables checklist
-
lab.pypassespytest test_lab.py -v(andLAB_MODULE=solutiondoes too). - You can explain PagedAttention from first principles: blocks, block table, why fragmentation goes to near zero, and how prefix sharing / copy-on-write works.
-
You can state the
append-allocates-a-new-block-only-when-full off-by-one and why it matters. - You can explain head-of-line blocking and why continuous batching is 2–4× throughput.
-
You can prove speculative decoding preserves the target distribution (
min(1, p/q)+ residual). - You can define TTFT and TPOT/ITL and say which mechanism moves which.
Key takeaways
- PagedAttention is OS paging for the KV-cache. Fixed blocks + a per-sequence block table turn a fragmented, over-allocated slab into near-fully-utilized memory, and make prefix sharing free. This is the single change that let one GPU serve far more concurrent sequences.
- Continuous batching removes head-of-line blocking. Schedule at the iteration level — admit and retire requests every decode step — and the batch stays full, which (because decode is bandwidth-bound) is where the 2–4× throughput comes from.
- Speculative decoding buys latency with spare compute, for free quality. The acceptance rule is exactly engineered so the output distribution equals the target's — you trade idle FLOPs for fewer sequential steps without changing what the model would have said.
- Serving is a throughput-vs-latency contract. TTFT is prefill (compute-bound), TPOT/ITL is decode (bandwidth-bound); batching raises throughput but can hurt TTFT — the SLOs decide the knobs, and the stack (vLLM/TensorRT-LLM/SGLang) is chosen to fit them.