Warmup Guide — Inference Serving
Zero-to-expert primer for Phase 09 — the track's namesake phase: why LLM inference is shaped the way it is (prefill/decode, the KV cache you implement in the lab), and the serving stack built on top (batching, paging, quantization, speculation, SLOs).
Table of Contents
- Chapter 1: The Shape of the Problem
- Chapter 2: The KV Cache — Derivation and Cost
- Chapter 3: Prefill vs Decode — Two Workloads in One Request
- Chapter 4: Metrics and SLOs — TTFT, TPOT, Goodput
- Chapter 5: Batching — Where Throughput Comes From
- Chapter 6: Memory Management — PagedAttention and Prefix Caching
- Chapter 7: The Optimization Menu
- Chapter 8: Multi-GPU and the Serving Topology
- Lab Walkthrough Guidance
- Success Criteria
- Interview Q&A
- References
Chapter 1: The Shape of the Problem
Autoregressive generation (Phase 03 Ch. 1) produces one token per forward pass, and each pass at batch 1 must read every model weight. That makes decode memory-bandwidth-bound (the roofline argument, in full in the model-accuracy track's Phase 07 warmup):
$$\text{tokens/sec} \approx \frac{\text{memory bandwidth}}{\text{bytes read per token}}$$
A100 (~2 TB/s) serving a 7B FP16 model (14 GB): ceiling ≈ 140 tok/s at batch 1 — while the GPU's compute sits ~99% idle. The entire serving discipline is the consequence: every technique in this phase either reduces bytes-per-token (quantization, GQA, paging efficiency) or amortizes the bytes across more useful work (batching, speculation, prefix caching). Hold that sentence; the rest is elaboration.
Chapter 2: The KV Cache — Derivation and Cost
The lab's subject, derived from first principles:
Why it exists: attention at position $t$ needs $K_{1..t}, V_{1..t}$. Causality (Phase 04 Ch. 3) means past tokens' K/V never change — recomputing them per step makes generation $O(T^2)$ forward work for what should be $O(T)$. So: cache K and V per layer per head; each step computes only the new token's $q, k, v$, appends, and attends — a matvec against the cache instead of a recompute of history.
What it costs (the formula to know cold):
$$\text{bytes} = 2 \times n_{layers} \times n_{kv_heads} \times d_{head} \times \text{seq_len} \times \text{bytes/elem}$$
LLaMA-2-7B FP16 (32 layers, 32 KV heads, d=128): 512 KB per token — 2 GB at 4K context, per sequence. A 40 GB GPU holding 14 GB of weights fits ~13 such sequences — the KV cache, not compute, caps concurrency. This single calculation explains GQA (8× fewer KV heads → 8× more concurrent users — Phase 02 of the model-accuracy track), KV quantization, paging (Ch. 6), and most of vLLM's existence.
Correctness property (the lab's key test): cached and uncached generation must be bit-identical — the cache is pure memoization. Any divergence is an indexing/masking bug, typically at the position-embedding or mask boundary.
Chapter 3: Prefill vs Decode — Two Workloads in One Request
One request, two regimes (the most consequential fact in serving):
| Prefill (process prompt) | Decode (generate) | |
|---|---|---|
| Shape | all prompt tokens at once — big matmuls | one token — matvecs |
| Bound | compute | memory bandwidth |
| Cost driver | prompt length (quadratic attention term) | weights + KV bytes per step |
| User-visible as | time-to-first-token | inter-token latency |
They want opposite optimizations (more FLOPs vs fewer bytes) and they interfere: a long prefill entering a batch stalls every decoding request for those iterations. Production answers: chunked prefill (slice prompts across iterations, blending smoothly with decodes) and prefill/decode disaggregation (separate pools, KV shipped between them — the frontier-scale architecture). Diagnosing which regime a complaint lives in ("slow" = TTFT or TPOT?) is always step one.
Chapter 4: Metrics and SLOs — TTFT, TPOT, Goodput
The vocabulary of serving quality:
- TTFT (time to first token): queue wait + prefill. Dominated by prompt length and batch interference. The "feels responsive" metric.
- TPOT / ITL (time per output token): decode speed. The "streams smoothly" metric; human reading speed (~10–15 tok/s) is the UX threshold worth knowing.
- E2E latency = TTFT + TPOT × output_len; throughput = total tokens/sec across the fleet — and throughput trades against latency via batching (Ch. 5).
- Goodput: throughput that meets SLO — the honest fleet metric, because raw tokens/sec is gameable by letting tails rot. Report p50/p99 of TTFT and TPOT, under a realistic arrival process (Poisson, not back-to-back), with the workload's prompt/output length distribution stated. A benchmark without these caveats is marketing.
Chapter 5: Batching — Where Throughput Comes From
Decode reads all weights per step regardless of batch size — so batching B requests amortizes the weight read B ways: throughput scales near-linearly until you leave the bandwidth-bound regime or exhaust KV memory. This is THE serving lever.
- Static batching fails for LLMs: requests finish at different lengths (head-of-line blocking, padding waste) and arrive continuously (batch-formation delay).
- Continuous batching (Orca's insight; every modern engine): scheduling at iteration granularity — after each decode step, retire finished sequences, admit new ones. Utilization stays high under length variance; admission is immediate.
- The induced problem: admitting a request commits to its KV growth → memory pressure, preemption policies, and the fragmentation problem Ch. 6 solves.
- The latency-throughput dial: deeper batches = better throughput, worse TPOT per user (more work per iteration). Goodput (Ch. 4) is how you tune the dial honestly.
Chapter 6: Memory Management — PagedAttention and Prefix Caching
(Shared ground with the model-accuracy track's Phase 08 warmup, Ch. 7–8 — recap + serving-side emphasis.)
- The fragmentation problem: contiguous per-request KV allocation at max-length wastes 60–80% of KV memory (vLLM's measurement) on reservation nobody uses.
- PagedAttention: fixed-size KV blocks (~16 tokens) + per-sequence block tables — the OS virtual-memory design transplanted. Near-zero waste → 2–4× more concurrent sequences → directly multiplies batch depth and therefore throughput (Ch. 5).
- Prefix caching, the feature serving engineers actually tune around: shared prefixes (system prompts, few-shot headers, conversation history re-sent each turn) hash to the same physical blocks — prefill for the shared part becomes a cache hit (TTFT drops dramatically for chat workloads). Design consequence for application engineers: put the static part of your prompt first; a timestamp at position 0 destroys prefix reuse for the whole fleet.
- Preemption under pressure: evict by swap-to-CPU or drop-and-recompute (recompute usually wins — it's a prefill, and prefill is fast); either way it lands in the p99.
Chapter 7: The Optimization Menu
The serving engineer's toolbox, each tagged with what it spends and saves (deep dives live in the model-accuracy track — Phases 03, 08, 10; here is the serving view):
| Technique | Saves | Spends / Risks |
|---|---|---|
| Weight quantization (INT8/INT4/FP8) | bytes/token → decode speed, memory | accuracy (measure! — regression evals, Phase 08) |
| KV-cache quantization (FP8/INT8 KV) | cache bytes → concurrency, long context | accuracy on long-range attention |
| FlashAttention | prefill attention traffic | none — exact; it's the default |
| CUDA graphs | per-step launch overhead in decode | static-shape constraints |
| Speculative decoding | amortizes weight reads over γ tokens | draft compute; helps only bandwidth-bound batch-1-ish regimes |
| Prefix caching | repeated prefill | memory for the cache; prompt-design discipline |
| Chunked prefill | TTFT tails under mixed load | slightly lower prefill efficiency |
Sampling-layer care (fused softmax/top-p, no .item() syncs) | per-token CPU/GPU stalls | engineering attention (Phase 07 of model-accuracy: the trace finds it) |
The discipline: identify the binding constraint first (bandwidth? KV memory? launch overhead? interference?), then pick from the menu — the techniques are not additive freebies, and several (speculation + deep batching) actively conflict.
Chapter 8: Multi-GPU and the Serving Topology
When one GPU isn't enough — for memory or for latency:
- Tensor parallelism (TP): shard every matmul across GPUs (column/row splits with all-reduces per layer). Cuts both memory and latency per token; needs NVLink-class interconnect; the standard within-node answer (TP=2/4/8).
- Pipeline parallelism (PP): shard by layers; good for fitting giant models across nodes; adds bubble latency — for serving, used when TP runs out, with micro-batching to fill bubbles.
- Replication + routing: beyond one model instance, the problem becomes load balancing (session affinity for prefix-cache hits!), autoscaling on goodput, and the classic fleet questions — at which point you're running a distributed system and the PMC track's operational instincts apply.
- Rule of thumb for the lab's scale: a 7B fits one modern GPU (quantized: comfortably); TP enters at 70B-class or when TPOT SLOs demand splitting the bandwidth bill.
Lab Walkthrough Guidance
Lab 01 — KV Cache (implement it inside your Phase 04 mini-transformer):
- Write the equivalence test first: full-recompute generation vs cached generation must match token-for-token (greedy) over many steps. This test is the lab.
- Implement per-layer K/V append; mind the two classic bugs: position embeddings
(the new token's position is
cache_len, not 0) and mask shape (new query attends to all cached keys — no causal mask needed within a single new token's row beyond length). - Measure: tokens/sec with vs without cache as context grows (the O(T²)→O(T) curve,
personally produced); peak memory vs context length (verify Chapter 2's formula
against
torch.cuda.max_memory_allocatedto within bookkeeping). - Extensions in value order: batch the cache (per-sequence lengths — ragged batching is where continuous batching's bookkeeping becomes real); sliding-window eviction; INT8-quantize the cache and measure both memory and output drift.
Success Criteria
You are ready for Phase 10 when you can, from memory:
- Derive the bandwidth ceiling formula and compute batch-1 tok/s for any (model, GPU) pair.
- Write the KV-size formula and the 7B/4K worked example; state the equivalence property and the two classic implementation bugs.
- Explain prefill vs decode bounds, their interference, and chunked prefill / disaggregation.
- Define TTFT/TPOT/goodput and critique a "tokens/sec" benchmark's missing caveats.
- Explain why batching is near-free for decode, what continuous batching changes, and what PagedAttention + prefix caching each fix.
- Pick from the optimization menu for: batch-1 chatbot on one GPU; high-QPS summarization fleet; 32K-context analysis service — different answers, with reasons.
Interview Q&A
Q: Users say the bot "thinks forever then answers fast." What do you tune? That's high TTFT, fine TPOT: queueing + prefill. Check queue wait (admission/batching policy), prompt length (32K system prompts happen), prefill interference from other requests (→ chunked prefill), and prefix caching (a static system prompt should be a cache hit — if the app puts dynamic content first, fix the app). The instinct being tested: decompose "slow" into TTFT vs TPOT before touching anything.
Q: Doubling batch size doubled throughput at first, then stopped. Why? Three ceilings in order: (1) you left the bandwidth-bound regime — at large batch, decode matmuls become compute-bound and weight-read amortization stops paying; (2) KV memory — batch depth caps at cache capacity (paging mitigates waste but not physics); (3) per-iteration overhead (sampling, scheduling) growing with batch. Also check TPOT: throughput may have "scaled" while per-user latency blew the SLO — goodput is the honest scoreboard.
Q: When is speculative decoding the wrong answer? Large-batch high-QPS fleets (the regime is already compute-bound — verification no longer rides free bandwidth; deep batching is the better spend), tight memory (draft model + its KV eat capacity that batching wants), and high-temperature/creative workloads (acceptance rate collapses as draft and target diverge). It shines at low-batch, latency-sensitive, greedy-ish decoding — say where it wins and where it loses (full math in the model-accuracy Phase 08 warmup).
References
- Kwon et al., Efficient Memory Management for LLM Serving with PagedAttention (vLLM) (SOSP 2023) — arXiv:2309.06180
- Yu et al., Orca: Iteration-Level Scheduling (OSDI 2022) — continuous batching's origin
- Pope et al., Efficiently Scaling Transformer Inference (2022) — arXiv:2211.05102 — the TP/latency analysis
- Agrawal et al., Sarathi: Chunked Prefill (2023) — arXiv:2308.16369
- Zhong et al., DistServe: Prefill/Decode Disaggregation (OSDI 2024) — arXiv:2401.09670
- Chen, Transformer Inference Arithmetic — kipp.ly/transformer-inference-arithmetic — the worked numbers
- vLLM docs — scheduler, prefix caching, and metrics pages
- Model-accuracy track companions: Phase 07 WARMUP (roofline) and Phase 08 WARMUP (FlashAttention/speculation/paging internals)