LLM + RoboticsModule 09 · Problems 9, 10, 17

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?

Problems covered9 (cut inter-token latency), 10 (speculative decoding), 17 (p99 is 10× p50)
Interview levelMid to senior. Problem 10 is a favourite senior follow-up.
Key ideasDecode is bandwidth-bound · batch size trades per-user speed for total throughput · prefill interference · acceptance rate α · queueing and length variance in the tail
Time to prep3–4 h: 1.5 h reading, 1.5 h working the numbers, 1 h with the demo and the exercise

Source: Vishal Singh's post listing 25 inference-engineering interview problems · saved in sources/09-vishal-inference-25.md

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

  1. Each new token needs one full pass through the model. That pass is mostly spent reading the model's weights from memory.
  2. 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.
  3. A huge new prompt processed in one go can freeze everyone else's next token. Cutting it into slices keeps the stream smooth.
  4. 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.
  5. 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

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

LeverITLThroughputCost or risk
Chunked prefill with a token budget per stepSmooths spikes, caps worst stepRoughly neutralLong prompts take a little longer to first token
Cap batch size or KV pages per replicaBetterWorseNeeds more replicas for the same load
FP8 / FP4 weights, FP8 KV cacheBetter (fewer bytes)Better (bigger batches fit)Quality must be re-measured (Page 06)
Tensor parallelism across more GPUsBetter (more aggregate bandwidth)Per GPU, often worseAll-reduce every layer; needs NVLink
CUDA graphs, fused kernelsBetter at small batchSlightly betterEngine-specific; static shapes
Speculative decodingMuch better at low loadCan drop at high loadExtra memory; gain depends on α (Problem 10)
Prefill/decode disaggregationRemoves prefill interferenceDepends on the splitKV 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

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

HelpsHurts 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 modelA 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 cacheMemory-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.

Tokens per verify
–
Cost per round
–
Speedup
–
Best k at this α
–
Break-even α
–

Try this: the defaults (α 0.8, k 4, c 0.05) give 3.36 tokens per verify and 2.80×. Raise v to 0.25 to mimic a busy GPU: 1.53×. Drop α to 0.3 with c = 0.1: you're at break-even. The model assumes each token is accepted independently with the same α, as in the Leviathan paper; real acceptance varies along the draft.

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

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:

Worked answer: a diagnosis plan

Assume a chat service, p50 E2E 2 s, p99 20 s. Work in this order:

  1. 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.
  2. 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.
  3. 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).
  4. 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.
  5. 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.
  6. 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

FixImprovesCosts
Run at lower utilisation (more headroom)Queue tailGPU cost per token
Chunked prefill, separate long-prompt poolTTFT tail for short requestsSlightly slower long prompts, more pools to manage
Max-tokens limits, length-aware SLOsE2E tailProduct constraints
Hedged requests (send a backup after the p95 time)Tail from slow hostsA few % more load; only for idempotent calls
Admission control and priority queuesTail for high-priority trafficRejected 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

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.

In this course

Why it matters for robotics

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