Covers: Definitions; The pipeline; Results; Design rules; Interview questions, with the follow-ups; Common mistakes; What the example does not do.
Definitions
Prefill and decode
Prefill: processing all input tokens in parallel to build the cache and the first output. Decode: generating one token per step, reading the whole model and cache each time.
TTFT and TPOT
TTFT: time to first token. TPOT: time per output token after the first.
KV cache
KV cache: stored attention keys and values of earlier tokens, so each step computes only the new token's.
Memory-bound and compute-bound
Memory-bound: step time set by bytes moved from memory. Compute-bound: set by arithmetic. The ridge point is where the two times are equal.
Static, dynamic and continuous batching
Static: wait for a full batch, finish all together. Dynamic: also start after a time window. Continuous: swap finished requests out and new ones in every step.
Speculative decoding
Speculative decoding: a draft model proposes K tokens, the target verifies them in one pass, and a rejection rule keeps the target's distribution.
Quantisation
Quantisation: storing numbers in fewer bits (weights, cache or activations) to cut memory and bandwidth, at some cost in accuracy.
The pipeline
KV cache work, batch limits and decode-step time (assumed hardware).
Results
Tokens generated (5-token prompt)
Positions, no cache
Positions, cache
Max logit difference
20
290
25
4e-15
40
980
45
5e-15
80
3,560
85
7e-15
160
13,520
165
7e-15
Sequence length
16-bit cache
8-bit cache
4-bit weights
1/8 as many KV heads (hypothetical)
512
128
257
175
1,029
2,048
32
64
43
257
8,192
8
16
10
64
Batch (sequence 2,048)
Compute ms
Memory ms
Tokens per second
1
0.08
13.8
72
4
0.33
16.4
245
8
0.67
19.7
406
16
1.33
26.4
606
32
2.67
39.8
803
Batching policies, speculative decoding speed-up and quantisation (toy simulations).
Requests per step
Static p50 / p95
Dynamic p50 / p95
Continuous p50 / p95
Utilisation (s / d / c)
0.01
483 / 1,134
139 / 467
58 / 204
0.38 / 0.16 / 0.18
0.02
370 / 735
199 / 560
58 / 202
0.39 / 0.22 / 0.24
0.03
369 / 715
299 / 726
59 / 206
0.39 / 0.29 / 0.32
0.04
791 / 1,547
805 / 1,567
58 / 211
0.39 / 0.37 / 0.41
0.07
7,811 / 14,559
8,022 / 14,685
64 / 216
0.39 / 0.39 / 0.69
Draft cost c
a = 0.5
a = 0.7
a = 0.8
a = 0.9
0.05 (best K, speed-up)
3, 1.63x
6, 2.35x
8, 3.09x
13, 4.67x
0.128 (best K, speed-up)
2, 1.39x
4, 1.83x
5, 2.25x
9, 3.03x
Tokens per verification at K=4
1.94
2.77
3.36
4.10
Bits
Weights: KL, top-1 agreement
KV cache: KL, top-1 agreement
8
0.00007, 0.997
0.00001, 0.998
6
0.0014, 0.975
0.0001, 0.997
4
0.027, 0.931
0.0019, 0.982
3
0.113, 0.875
0.011, 0.959
2
0.847, 0.705
0.091, 0.884
The cache trades memory for compute, and at scale memory is the scarcer resource.
Decode is memory-bound, so reducing bytes (quantisation, fewer KV heads, shorter context) helps more than adding FLOPs.
Batching raises throughput sublinearly because the cache grows with the batch.
Continuous batching mostly removes waiting, not compute: latency stayed near 60 steps median while static batching collapsed.
Speculative decoding pays only when acceptance is high and the draft is cheap.
Quantisation sensitivity must be measured; it differs between weights and cache and between models.
Design rules
Measure TTFT and TPOT separately, and name which one your users feel.
Size the cache first: batch x sequence x layers x hidden x 2 x bytes decides how many requests fit.
Use continuous batching for variable-length outputs; static batching wastes slots on the longest reply.
Quantise one thing at a time and compare quality on your own evaluation set, not only perplexity.
Check acceptance rate on your real traffic before adopting speculative decoding; pick K from it.
Put stable content first in prompts so prefix caching can reuse it.
Report hardware, batch, sequence length and precision with every latency number.
Treat vendor speed-ups as upper bounds until you reproduce them on your workload.
Interview questions, with the follow-ups
Why is decoding memory-bound and prefill compute-bound?
"Prefill processes many tokens in one pass, so the weights read from memory are reused across all of them and arithmetic dominates. Decode produces one token per sequence per step, so every step reads all the weights and the cache for little arithmetic. In my model compute was under 10 percent of the step time at every feasible batch." Follow-up: "What would make decode compute-bound?" A large batch; without the cache the ridge in my model was near batch 156, but the cache grows with the batch and memory fills first.
How does the KV cache work and what does it cost?
"It stores keys and values of past tokens so each step computes only the new one: linear instead of quadratic work, with identical outputs (mine matched to 1e-14). The cost is 2 x batch x sequence x layers x hidden x bytes; the Llama 2 13B example is 54 GB at batch 32 and 2,048 tokens." Follow-up: "How do you reduce it?" Quantise it, use fewer key-value heads, use paged allocation, or shorten context.
Static versus continuous batching?
"Static waits for the slowest request in a batch; with variable output lengths most slots idle. Continuous batching refills a slot as soon as a request ends. In my simulation static saturated near 0.039 requests per step and continuous near 0.10, and at 0.07 static latency exploded while continuous stayed near 64 steps median." Follow-up: "What do you give up?" Scheduler complexity and per-step overhead, which my simulation ignores.
When does speculative decoding help?
"When the draft is much cheaper than the target and agrees often. Expected tokens per verification is (1 - a^(K+1)) / (1 - a) and the cost is K draft steps plus one target step. At draft cost 0.128 I needed acceptance near 0.8 for a 2x gain. The rejection rule keeps the target distribution, so quality is unchanged." Follow-up: "Does it help at large batch?" Less: a compute-bound server has no spare compute to spend on verifying extra tokens (my model does not cover this).
Common mistakes
Quoting one latency number without batch, sequence length and hardware.
Ignoring the cache when sizing a GPU.
Using static batching for variable-length generation.
Assuming quantisation is free or uniformly harmful.
Choosing K without measuring acceptance rate.
Putting changing text at the start of a prompt, defeating prefix caching.
Optimising TPOT when TTFT is the complaint.
Trusting a vendor's speed-up on a different workload.
What the example does not do
No GPU or kernels: time comes from a two-term formula (bytes over bandwidth, FLOPs over peak); real kernels, launch overhead and communication change it.
Assumed peak: 312 TFLOP/s is my assumption for an A100-class GPU; the 2 TB/s figure is the published A100-80GB bandwidth, rounded.
The batching simulation uses one step as the time unit, no prefill cost and no preemption.
The quantisation toy uses random weights, so its tolerance to bits is not evidence about trained models; the cache vs weights ordering may differ.
No paged attention, FlashAttention, tensor parallelism, or disaggregated prefill.
Speculative decoding is tested on a synthetic draft with controlled disagreement, not a real small model.
Optional real experiment (I did not run this): serve a small open model with vLLM or llama.cpp, measure TTFT, TPOT and throughput at batch 1, 8 and 32, then repeat with a quantised build and prefix caching on.
Check yourself
Why does the KV cache, not the weights, limit how many requests one GPU can serve? (Weights are a fixed cost, while the cache grows with batch size and sequence length. For Llama 2 13B at batch 32 and 2,048 tokens the cache is 53.69 GB, twice the 26 GB of weights.)
For the curious Batching policy changes latency more than speed
Static batching holds every reply until the longest in its batch finishes; continuous batching refills a slot the moment a reply ends. At 0.04 requests per step the p95 was 1,547 steps for static and 211 for continuous, and at 0.07 static was overloaded. Speculative decoding keeps the target model's output distribution exactly, and pays off only when the draft's acceptance rate is high (about 0.8 or more for a 2x gain at c = 0.128). The hardware figures are assumed round numbers and the model is tiny.
What you will use daily at work
Practice
What it looks like
Cache stable prompt prefixes
System prompt, tool list, long documents.
Size the batch from cache memory
Weights fixed, cache per sequence.
Quantise weights or cache only after measuring quality
Compare on your own eval.
Watch TTFT and TPOT separately
Prefill and decode have different fixes.
Draft: confirm the serving stack you will use (hosted API or your own) and its current batching, caching and quantisation options and prices.
What you must be able to say (one page)
Two phases, two bottlenecks.Prefill (processing the input tokens) is compute-bound and decode (generating output tokens one by one) is memory-bandwidth-bound. The two latency metrics follow: TTFT (time to first token, dominated by prefill) and TPOT (time per output token, dominated by decode).
The KV cache removes repeated work. Without it, each new token recomputes keys and values for the whole prefix. In my NumPy transformer, cached and uncached generation gave identical tokens and a maximum logit difference near 7e-15; positions processed to generate N tokens after a 5-token prompt were 25 vs 290 (N=20), 45 vs 980 (N=40), 85 vs 3,560 (N=80) and 165 vs 13,520 (N=160): linear against quadratic. Wall time at N=160 was 0.022 s vs 0.310 s on my machine (machine-specific).
The cache costs memory. Size = 2 x batch x sequence x layers x hidden x bytes. For Llama 2 13B (batch 32, sequence 2,048, 16-bit) the cache is 53.69 GB, more than the 26 GB of weights. Per sequence: 0.42, 1.68 and 6.71 GB at 512, 2,048 and 8,192 tokens.
So the cache sets the batch size. On an 80 GB GPU with 13B 16-bit weights, my arithmetic gives a largest batch of 128, 32 and 8 at 512, 2,048 and 8,192 tokens. An 8-bit cache doubles that (257, 64, 16); 4-bit weights help much less (175, 43, 10) because weights are a fixed cost and the cache scales with batch.
Decode is memory-bound, so batching is nearly free until it is not. My roofline-style model (312 TFLOP/s assumed for an A100-class GPU, about 2 TB/s for an A100-80GB) at sequence 2,048: batch 1 takes 13.8 ms (72 tokens/s), batch 32 takes 39.8 ms (803 tokens/s): 32 times the work for 2.9 times the time, but only 11 times the throughput, because the growing cache adds bytes to read. Compute is under 10 percent of the step time at every feasible batch size. Ignoring the cache, the weights alone would turn compute-bound near a batch of 156.
Batching policy changes latency more than speed. Slot limit 8, mean output 78 tokens, mean longest-of-8 203 tokens. Static batching holds every reply until the longest in its batch finishes and saturates near 0.039 requests per step; continuous batching (the idea behind Orca) refills a slot the moment a reply ends and saturates near 0.10. At 0.04 requests per step: static p50 791, p95 1,547; continuous p50 58, p95 211. At 0.07 static is overloaded (p50 7,811) while continuous holds 64.
Speculative decoding: a cheap draft model proposes K tokens; the target model checks them in one pass and keeps the longest agreeing prefix. With the accept/reject rule the output distribution equals the target's: in my test the total-variation distance to the target was 0.006 and 0.005 while the draft itself was 0.15 and 0.47 away. Expected tokens per verification = (1 - a^(K+1)) / (1 - a) for acceptance rate a; speed-up = that divided by (K c + 1) where c is draft cost relative to a target step.
How much speed-up? With c = 0.128 (a 70B Chinchilla target at 14.1 ms against a 4B draft at 1.8 ms, from the speculative sampling paper as I recall it): a=0.5 gives 1.39x (K=2); 0.7 gives 1.83x (K=4); 0.8 gives 2.25x (K=5); 0.9 gives 3.03x (K=9). In my model, cutting latency by more than half needs roughly an acceptance rate of at least 0.8.
Quantisation (tiny model, KL from the 16-bit model and top-1 agreement): weights at 8, 6, 4, 3, 2 bits gave KL 0.00007, 0.0014, 0.027, 0.113, 0.847 and agreement 0.997, 0.975, 0.931, 0.875, 0.705; the KV cache at the same bit widths gave KL 0.00001, 0.0001, 0.0019, 0.011, 0.091 and agreement 0.998, 0.997, 0.982, 0.959, 0.884. In this toy the cache tolerated fewer bits than the weights; this is a toy, so measure on your model.
Prompt caching: reusing a processed prefix. My arithmetic: a 1,000-token prompt at 1M calls a day is 1B tokens, $3,000 a day at an assumed $3 per million, or $300 with a 90 percent discount (Anthropic announced up to 90 percent lower cost for cached input when it launched prompt caching; check current terms before you plan on any discount).
What to say in an interview: "Prefill is compute-bound, decode is memory-bound. I use a KV cache, size it to set the batch, use continuous batching and quantise weights or cache after measuring quality, add speculative decoding when the draft's acceptance rate is high enough to pay for its cost, and cache stable prompt prefixes."
Honest limits: a tiny NumPy model, an arithmetic hardware model with assumed peak FLOPs, a toy request simulator; no GPU kernels, no paged attention, no FlashAttention, no real serving stack.