Latency: inter-token latency, speculative decoding and tail latency
Three interview problems about time. How do you make each token arrive sooner without serving fewer users? When does guessing ahead with a draft model pay off, and when does it cost you? And when the slowest 1% of requests take ten times longer than the typical one, where do you look first?
Start here (no maths needed)
A language model writes one word-piece at a time. How long you wait between pieces, and how long the unluckiest customers wait, are the numbers people feel. This page is about making both smaller.
Think of it like this
Picture a busy restaurant kitchen with one head chef. Every plate must pass the head chef, one at a time. The chef can carry a whole tray of plates per trip, so serving more tables at once barely slows each trip. But a giant banquet order that needs an hour of prep can block the pass, and every small table waits. Now add an apprentice who prepares the next four plates in advance. The head chef checks all four in one glance: if they're right, four plates go out for the price of one check. If the apprentice guesses badly, the chef throws them away and the trip was wasted. And a few tables always wait far longer than the rest, usually because they ordered something huge or arrived at the busiest moment.
How it works
- Each new token needs one full pass through the model. That pass is mostly spent reading the model's weights from memory.
- Serving many users in the same pass (a batch) shares that reading cost, so total output goes up while each user's pace drops a little.
- A huge new prompt processed in one go can freeze everyone else's next token. Cutting it into slices keeps the stream smooth.
- A small draft model can guess several tokens. The big model checks all of them in one pass and keeps the ones it agrees with.
- The slowest requests are slow for a reason: very long answers, long queues, memory running out, or a sick machine. You find the reason by splitting each request's time into parts.
Why robots care
A robot's onboard model has a fixed time slot. If a planner must produce its next instruction every 100 ms, a token that takes 150 ms means the robot acts on stale information. The worst case matters more than the average: a robot that is usually quick but sometimes freezes for a second is not safe to stand next to.
Try it
- With a friend, read a paragraph aloud. One of you tries to say the next three words before the reader does. Count how often all three guesses are right. That fraction is close to what engineers call the acceptance rate.
- Time how long ten classmates take to get lunch from a queue. Is the slowest one ten times slower than the middle one? Ask why.
- In the demo below, move Acceptance rate α down from 0.8 and watch the speedup fall below 1×. Then raise Draft length k, the Draft cost c and the Verify cost per extra token to see what makes guessing ahead stop paying.
Words to know
- Inter-token latency (ITL)
- The gap between two streamed tokens. Its average over a request is often called TPOT, time per output token.
- Throughput
- Total tokens produced per second across all users.
- Speculative decoding
- A cheap draft guesses several tokens; the big model checks them all at once.
- Acceptance rate (α)
- The chance the big model agrees with a drafted token.
- p50 / p99
- The latency that half / 99% of requests beat. p99 is the "unlucky 1%".
Going deeper: everything below is the full version, with some maths. On a first read, skim the headings and the "Why it matters for robotics" box, and come back for the rest later.
Problem 9 · Cut inter-token latency without tanking throughput
What the interviewer is testing: whether you know why decode is slow (memory bandwidth, not arithmetic), that latency and throughput are coupled through batch size, and which levers move one without wrecking the other.
The concepts
During decode, each step produces one token for every sequence in the batch. To do it, the GPU streams all the weights once (shared by the batch) plus each sequence's own KV cache. At normal batch sizes the arithmetic is tiny compared with the bytes moved, so the step time is close to a bandwidth floor:
t_step ≈ (W + B × KV_seq) / BW + t_overhead
W = weight bytes (params × bytes per weight)
KV_seq = KV-cache bytes per sequence (see Module 08, Stage 08)
B = sequences in the batch
BW = HBM bandwidth (bytes/s)
ITL (per user) = t_step
throughput (tokens/s) = B / t_step
Two things follow. First, raising B grows throughput almost linearly while the weights dominate, but each step also gets longer because every sequence adds its own cache. Second, anything else that runs in the same step adds to everyone's ITL. The biggest offender is a prefill: processing a new prompt is compute-bound, and a long one can take longer than a hundred decode steps.
Worked answer with real numbers
Assumptions: a Llama-3-8B-shaped model (32 layers, 8 KV heads, head dim 128, about 8.0B parameters), BF16 weights (≈16 GB) and BF16 KV cache (128 KiB = 131,072 B per token), an average of 2,048 tokens of context per sequence (≈268 MB of cache each). GPU: H100 SXM, 80 GB, 3.35 TB/s and 989 TFLOPS dense BF16 (NVIDIA's table lists 1,979 TFLOPS with sparsity; dense is half). The bandwidth floor ignores kernel overhead and assumes 100% of peak bandwidth, so real numbers will be slower.
B = 1: (16 GB + 1 × 0.268 GB) / 3.35 TB/s = 4.86 ms → 206 tok/s per user, 206 tok/s total
B = 32: (16 GB + 32 × 0.268 GB) / 3.35 TB/s = 7.34 ms → 136 tok/s per user, 4,360 tok/s total
B = 128: (16 GB + 128 × 0.268 GB) / 3.35 TB/s = 15.03 ms → 67 tok/s per user, 8,515 tok/s total
B = 256: needs 16 + 68.7 = 84.7 GB of weights + cache → does not fit in 80 GB
Compute check at B = 128: 2 × 8.0e9 × 128 = 2.0 TFLOP per step
÷ 989 TFLOPS ≈ 2.1 ms < 15 ms, so still bandwidth-bound
Going from B = 32 to B = 128 doubles throughput and doubles ITL. That is the core trade-off. Now the prefill problem. Prefill costs about 2 × params FLOPs per prompt token. Assume 50% of dense peak is achieved (an assumption; measure yours):
4,096-token prompt in one step: 2 × 8.0e9 × 4,096 / (0.5 × 989e12) ≈ 133 ms
512-token chunk per step: 2 × 8.0e9 × 512 / (0.5 × 989e12) ≈ 17 ms
With B = 32 decoding: a normal step is 7.3 ms.
unchunked prefill step → ~140 ms (a visible stall for 32 users)
chunked (512) steps → ~24 ms each, for 8 steps
So the answer has a clear shape: first remove interference (chunked prefill, Sarathi-Serve style, or move prefill to separate GPUs), then shrink bytes per step (FP8 weights and FP8 KV), then cut fixed overhead (CUDA graphs, fused kernels), and only then reach for speculative decoding or more GPUs per replica. With FP8 weights (8 GB) and FP8 KV (134 MB per sequence), the floor at B = 128 becomes (8 + 17.2) GB / 3.35 TB/s = 7.5 ms. That is the same ITL as BF16 at B = 32, with four times the users.
Trade-offs
| Lever | ITL | Throughput | Cost or risk |
|---|---|---|---|
| Chunked prefill with a token budget per step | Smooths spikes, caps worst step | Roughly neutral | Long prompts take a little longer to first token |
| Cap batch size or KV pages per replica | Better | Worse | Needs more replicas for the same load |
| FP8 / FP4 weights, FP8 KV cache | Better (fewer bytes) | Better (bigger batches fit) | Quality must be re-measured (Page 06) |
| Tensor parallelism across more GPUs | Better (more aggregate bandwidth) | Per GPU, often worse | All-reduce every layer; needs NVLink |
| CUDA graphs, fused kernels | Better at small batch | Slightly better | Engine-specific; static shapes |
| Speculative decoding | Much better at low load | Can drop at high load | Extra memory; gain depends on α (Problem 10) |
| Prefill/decode disaggregation | Removes prefill interference | Depends on the split | KV transfer and more moving parts (Page 04) |
How to say it in the interview
Decode is memory-bandwidth bound: each step reads all weights plus every sequence's cache, so ITL is roughly bytes read divided by bandwidth. Batch size is the dial between per-user speed and total throughput, so I'd first set an ITL target, say 50 ms at p99, and size the batch to it. Then I'd remove interference: chunked prefill with a per-step token budget, so a 100K prompt can't freeze the stream. Next I'd cut the bytes with FP8 weights and KV, which helps both latency and throughput at once, and cut overhead with CUDA graphs. Speculative decoding and wider tensor parallelism come last, because they help latency but can cost throughput at high load.
Common mistakes
- Treating latency and throughput as independent. They share one dial, the batch, so always quote both.
- Reporting average ITL. Users feel the spikes, so track ITL at p99 and the maximum gap per stream.
- Assuming a bigger GPU fixes ITL. Only bandwidth per token and interference move it; FLOPs barely matter for decode.
- Forgetting that memory caps the batch: in the example, B = 256 simply doesn't fit.
Problem 10 · Speculative decoding: when it helps and when it hurts
What the interviewer is testing: whether you can derive the expected speedup from the acceptance rate, and whether you know it is a latency tool that can lose throughput when the GPU is already busy.
The concepts
A cheap draft proposes k tokens. The target model scores all k in one forward pass, accepts a prefix of them using a modified rejection-sampling rule, and adds one token of its own (either the correction at the first rejection, or a bonus token if all were accepted). The rule keeps the output distribution exactly the same as the target's. Leviathan, Kalman and Matias (2023) and Chen et al. (2023) derived this independently.
If each drafted token is accepted with probability α, independently, the expected tokens produced per verify round are:
E[tokens per round] = 1 + α + α² + … + α^k = (1 − α^(k+1)) / (1 − α)
cost per round (in units of one target forward pass):
k × c drafting (c = draft cost per token ÷ target cost)
+ 1 the verify pass
+ k × v extra verify cost per drafted token (≈ 0 when memory-bound,
grows when the GPU is compute-bound at large batch)
speedup = E[tokens per round] / (k·c + 1 + k·v)
With v = 0 this is the improvement factor in the Leviathan paper. The key assumption is v ≈ 0: verifying k + 1 tokens costs about the same as producing one, because the pass is bound by reading weights, not by arithmetic. That holds at small batch. At large batch the GPU is closer to compute-bound, every rejected token is wasted arithmetic, and v grows.
Worked answer with real numbers
α = 0.8, k = 4, c = 0.05, v = 0:
E = (1 − 0.8⁵) / 0.2 = (1 − 0.328) / 0.2 = 3.36 tokens per round
cost = 4 × 0.05 + 1 = 1.20
speedup = 3.36 / 1.20 = 2.80×
Same, but α = 0.6: E = 2.31, speedup = 2.31 / 1.20 = 1.92×
α = 0.5, k = 8, c = 0.1: E = 2.00, cost = 1.8, speedup = 1.11×
α = 0.3, k = 4, c = 0.1: E = 1.43, cost = 1.4, speedup = 1.02× (break-even)
α = 0.8, k = 4, c = 0.05, but at high load v = 0.25:
cost = 0.2 + 1 + 1.0 = 2.2 → speedup = 1.53×
Best k at α = 0.8, c = 0.05, v = 0: k = 8, speedup ≈ 3.09×
The published numbers fit this picture. Leviathan et al. report 2–3× on T5-XXL; Chen et al. report 2–2.5× on Chinchilla 70B. EAGLE-3 (Li et al., 2025), which drafts from the target's own hidden features, reports up to 6.5× in its best setting but a 1.38× throughput gain at batch 64 in SGLang. The gain shrinks as the batch grows, exactly as the v term predicts. Module 05 covers training an EAGLE-3 draft directly on acceptance rate.
When it helps, when it hurts
| Helps | Hurts or does nothing |
|---|---|
| Low concurrency, latency-sensitive (chat, agents, a robot at batch 1) | High concurrency where the GPU is already compute-saturated |
| Predictable text: code, JSON, edits that copy the prompt (n-gram/prompt lookup works well) | High temperature or creative sampling, which lowers α |
| A draft that is very cheap (c ≤ 0.05) and well aligned: EAGLE/Medusa heads, a small sibling model | A draft that is a big fraction of the target's cost, or a mismatched tokenizer |
| Plenty of spare HBM for draft weights and draft KV cache | Memory-tight setups, where the draft's memory shrinks the batch |
Interactive: speculative-decoding speedup
Speedup against acceptance rate α. Solid: your draft length k. Dashed: the best k at each α. The red line is 1× (break-even). The marker is your α; green above break-even, red below. Costs are in units of one target forward pass.
How to say it in the interview
Speculative decoding trades spare compute for fewer sequential steps. A draft proposes k tokens, the target verifies them in one pass, and rejection sampling keeps the output distribution unchanged. Expected tokens per pass are (1 − α^(k+1)) / (1 − α), so at α = 0.8 and k = 4 you get about 3.4 tokens for 1.2 passes of cost, roughly 2.8×. It helps most at low batch, where verification is nearly free because decode is bandwidth-bound. It hurts at high load, at high temperature, or when the draft is expensive or eats memory the batch needed. So I'd enable it per traffic class and watch acceptance rate and throughput, not just ITL.
Common mistakes
- Saying it changes model quality. With the proper acceptance rule the output distribution is identical; only speed changes. (Lossy "accept if top-1 matches" shortcuts are a different, approximate thing.)
- Quoting a speedup without a batch size. The same setup can give 2.8× at batch 1 and nothing at batch 128.
- Forgetting the draft's own KV cache and weights in the memory budget.
- Picking k by gut. The best k depends on α and c; too long a draft wastes work on tokens that will be rejected.
Problem 17 · Your p99 latency is 10× your p50. Diagnose it
What the interviewer is testing: a calm, data-first method. Can you decompose latency, separate "slow because the request is big" from "slow because the system is unhealthy", and know the usual suspects in LLM serving?
The concepts
End-to-end latency for one request breaks into parts, and the first job is to log them per request:
E2E = t_network + t_queue + t_tokenise + TTFT_compute + (N_out − 1) × TPOT + t_stalls
TTFT_compute grows with prompt length (prefill)
(N_out − 1) × TPOT grows with output length
t_queue grows sharply as utilisation ρ → 1
t_stalls = preemption / recompute when the KV cache runs out, GC, retries
Three effects create a heavy tail even in a healthy system:
- Length variance. E2E is proportional to output tokens. If most answers are 100 tokens and 1% are 2,000, then at 20 ms TPOT the p50 is about 2 s and the p99 about 40 s. That is a 20× ratio with nothing broken. Always normalise: look at TTFT and TPOT percentiles, not only E2E.
- Queueing. In the textbook M/M/1 queue the time in system is exponential with rate μ − λ. Its p99/p50 ratio is ln(100)/ln(2) ≈ 6.6 at any load, and its mean scales as 1/(1 − ρ). Going from 50% to 90% utilisation makes the mean wait five times longer. Real LLM servers are not M/M/1, but the lesson holds: near saturation, small bursts create huge waits.
- Fan-out. Dean and Barroso's "The Tail at Scale": if a request touches 100 servers and each is slow 1% of the time, 1 − 0.99¹⁰⁰ = 63% of requests hit at least one slow server. Agents that call the model many times per task inherit this.
Worked answer: a diagnosis plan
Assume a chat service, p50 E2E 2 s, p99 20 s. Work in this order:
- Split by phase. Plot p50 and p99 for queue time, TTFT and TPOT separately. If TPOT p99 is flat and only E2E is bad, it is mostly output length. Report per-token latency and set a max-tokens policy.
- Slice by request shape. Bucket by prompt length and output length. If TTFT p99 tracks long prompts, and short requests that arrived alongside them also have high TTFT, it is head-of-line blocking from prefill. Fix: chunked prefill, a per-step token budget, a separate queue or pool for long prompts.
- Check queueing. Plot queue time against concurrency and utilisation. If queue time explodes at traffic peaks, you're over ρ ≈ 0.8. Fix: admission control, more replicas, autoscaling with warm capacity (Page 09).
- Check KV-cache pressure. Look for preemptions and recomputes in the engine metrics (vLLM logs and exposes these). If they spike with p99, the cache is full. Fix: lower max concurrency, FP8 KV, prefix caching, more memory per replica.
- Slice by replica and node. One slow GPU, a thermally throttled node, a noisy neighbour or a bad NIC shows up as a per-host outlier. Fix: drain it; add hedged or retried requests for idempotent calls.
- Check the CPU side. Tokenisation, detokenisation, JSON grammar constraints, Python GC pauses and the HTTP layer all live outside the GPU and show up as gaps in a timeline trace.
A numeric sanity check for step 2: with the Problem 9 numbers, a 4,096-token prompt prefilled in one step costs about 133 ms. If 30 such prompts arrive in a burst on one replica, a short chat queued behind them waits about 4 s before its own prefill even starts.
Trade-offs
| Fix | Improves | Costs |
|---|---|---|
| Run at lower utilisation (more headroom) | Queue tail | GPU cost per token |
| Chunked prefill, separate long-prompt pool | TTFT tail for short requests | Slightly slower long prompts, more pools to manage |
| Max-tokens limits, length-aware SLOs | E2E tail | Product constraints |
| Hedged requests (send a backup after the p95 time) | Tail from slow hosts | A few % more load; only for idempotent calls |
| Admission control and priority queues | Tail for high-priority traffic | Rejected or delayed low-priority requests (Page 03) |
How to say it in the interview
First I'd check whether it's a real problem or just length variance: end-to-end latency scales with output tokens, so I'd look at TTFT and TPOT percentiles separately. Then I'd decompose each slow request into queue, prefill, decode and stalls, and slice by prompt length, output length, replica and time. The usual LLM culprits are head-of-line blocking from long prefills, queueing near saturation, KV-cache preemption, and one bad host. Each has a signature and a fix: chunked prefill, headroom or admission control, more cache, or draining the host. I'd finish by setting SLOs per token, not per request, and alerting on the decomposed metrics.
Common mistakes
- Jumping to fixes before splitting the latency into parts.
- Using averages. The mean hides the tail; p99 needs enough samples (at least a few thousand requests) to be stable.
- Treating p99/p50 = 10 as broken by itself. With mixed output lengths it can be normal; per-token metrics tell you.
- Forgetting client-side and network time, which the server's metrics never see.
Practise it
(a) Speculative decoding simulator (laptop, 20 minutes). Confirm the formula by Monte Carlo, then add a batch-dependent verify cost:
import random
def sim(a, k, c, v=0.0, rounds=200_000, seed=0):
rng = random.Random(seed)
toks = 0
for _ in range(rounds):
n = 0
while n < k and rng.random() < a:
n += 1
toks += n + 1 # accepted drafts + 1 target token
return toks / rounds, toks / rounds / (k * c + 1 + k * v)
for a in (0.3, 0.6, 0.8):
print(a, sim(a, 4, 0.05)) # expect E ≈ (1 - a**5) / (1 - a)
(b) Measure the latency/throughput dial with vLLM (one 16–24 GB GPU or Colab). Serve a small model such as Qwen2.5-1.5B-Instruct. Run vllm bench serve with random prompts (512 in, 256 out) at several request rates, and record TTFT, TPOT and ITL at p50 and p99, plus output tokens/s. Plot ITL against throughput: that curve is Problem 9.
- Repeat with a few 8K-token prompts mixed in, with and without chunked prefill, and compare short-request TTFT at p99.
- Enable n-gram speculative decoding (
--speculative-config; check your vLLM version's docs for the exact keys) and compare ITL at request rates of 1 and at saturation. Note where the speedup disappears. - Log per-request prompt and output lengths, then reproduce Problem 17's diagnosis: compute p99/p50 for E2E and for TPOT and explain the difference.
In this course
- Module 05 · Serving: speculative decoding with an EAGLE-3 draft trained on acceptance rate, and fleet scheduling.
- Module 08 · Inference-aware design: the KV-cache maths behind KV_seq, and design choices that make speculation cheap.
- Module 04 · Latency is a safety number: measuring wait-after-speech on a device.
- Module 01 · Decision-model evals: setting thresholds and choosing metrics, which applies to latency SLOs too.
- Page 03 · Batching and scheduling and Page 04 · Prefix caching and disaggregation: the scheduling fixes referenced above.
Why it matters for robotics
- The bandwidth floor is harsher on the robot. NVIDIA lists 204.8 GB/s for the Jetson AGX Orin 64GB module, against 3.35 TB/s for an H100 SXM. By the same formula, an 8B model at BF16 (16 GB) has a floor of about 78 ms per token on that module. At 4-bit weights it is closer to 20–25 ms. That is why robots run small, quantised models.
- Batch 1 is where speculative decoding shines. A robot runs one episode at a time, so verification is nearly free and a well-aligned draft can cut action-token latency. Structured outputs (action tokens, JSON plans) tend to be predictable, which helps α.
- Control loops care about the worst case. A 100 ms planner budget with a p99 of 1 s means the arm sometimes acts on a stale plan. Robot stacks bound this with timeouts, a fast fallback policy and fixed-length outputs, which also removes the length-variance source of tail latency.
- TTFT is reaction time. A new camera frame or spoken command is a prefill. Keeping the prompt short and caching the fixed prefix (Page 04) is the robot version of chunked prefill.
- Some of this is cloud-only. Queueing near saturation, fan-out and hedged requests matter for a fleet backend serving many robots, not for one onboard model.
Self-check
An 8B BF16 model with 268 MB of KV per sequence runs at batch 32 on a 3.35 TB/s GPU. What is the ITL floor and total throughput?
(16 + 32 × 0.268) GB / 3.35 TB/s = 24.6 GB / 3.35 TB/s ≈ 7.3 ms. Throughput ≈ 32 / 7.3 ms ≈ 4,360 tokens/s. Real numbers are lower because peak bandwidth is never fully reached.
Why does a single 100K-token prompt hurt the ITL of everyone else on the replica?
Its prefill is compute-heavy and, if run in one step, occupies the GPU for far longer than a decode step. Every sequence in the batch waits for that step. Chunked prefill caps the prefill tokens per step so decode steps stay short.
α = 0.7, k = 3, c = 0.1, v = 0. What is the expected speedup?
E = (1 − 0.7⁴) / 0.3 = (1 − 0.2401) / 0.3 = 2.53 tokens. Cost = 3 × 0.1 + 1 = 1.3. Speedup ≈ 1.95×.
Why can speculative decoding reduce throughput at high batch?
At high batch the GPU is closer to compute-bound, so verifying k extra tokens per sequence costs real arithmetic, and the rejected ones are wasted. The draft also takes memory that could have held more sequences.
Your E2E p99/p50 is 12, but TPOT p99/p50 is 1.4. What does that tell you?
Per-token speed is healthy; the tail comes from request size, most likely long outputs (or long prompts if TTFT is also skewed). Report per-token SLOs and consider max-token limits rather than hunting for a system fault.
A request fans out to 50 calls, each with a 1% chance of being slow. What fraction of requests see a slow call?
1 − 0.99⁵⁰ ≈ 39.5%.
Resources
- Fast Inference from Transformers via Speculative Decoding (Leviathan, Kalman, Matias, ICML 2023)
- Accelerating Large Language Model Decoding with Speculative Sampling (Chen et al., 2023)
- EAGLE-3: Scaling up Inference Acceleration of LLMs via Training-Time Test (Li et al., 2025)
- Medusa: Simple LLM Inference Acceleration Framework with Multiple Decoding Heads (Cai et al., 2024)
- Sarathi-Serve: Taming Throughput-Latency Tradeoff in LLM Inference (Agrawal et al., 2024): chunked prefill and stall-free batching
- Orca: A Distributed Serving System for Transformer-Based Generative Models (Yu et al., OSDI 2022): iteration-level scheduling
- DistServe (Zhong et al., 2024) and Splitwise (Patel et al., 2024): prefill/decode disaggregation
- Efficient Memory Management for LLM Serving with PagedAttention (Kwon et al., 2023)
- The Tail at Scale (Dean and Barroso, CACM 2013)
- Docs: vLLM speculative decoding · vLLM bench serve · SGLang · TensorRT-LLM · Nsight Systems user guide
- Specs: NVIDIA H100 · NVIDIA Jetson Orin