Skip to content

Performance debugging chains: what the symptom should have cost

8 chains

A debugging round rewards the candidate who computes what the system should have done before guessing why it did not. Each chain below produces that expected number; the gap between it and the observation is the thing to explain. For routing a symptom to its first measurement, see debugging by symptom.

Lost prefix reuse

inputs   3,000-token prompts; before a template deploy, 95% of a 2,560-token
         shared prefix was cached: 243,200 cached tokens per 100 requests
before   cached per request = 2,432; computed = 3,000 - 2,432 = 568
after    the new template changes an early token: computed = 3,000
ratio    3,000 / 568 = 5.28x more prefill per request
sanity   the prompt length did not change; the reusable part did

A 5x rise in prefill work can raise queueing a great deal, but it does not predict the p99 ratio by itself. Measure the token IDs where old and new prompts diverge first.

Drilled in: TTFT jumped after a template deploy

The inter-token floor and a mixed step

inputs   Llama 3.1 8B BF16 on one H100 SXM, batch 64, 4,096 tokens of context
floor    (16.06 GB weights + 34.36 GB KV) / 3.35 TB/s = 15.05 ms
mixed    a 2,048-token prefill chunk in the step: 2 x 8.03e9 x 2,112 tokens
         = 33.9 TFLOP, 68.6 ms at 50% of peak, plus 10.3 ms of KV = ~79 ms
512 chunk  2 x 8.03e9 x 576 / 494.5e12 + 10.3 ms = ~29 ms
sanity   a measured 24 ms p50 is 63% of the floor's bandwidth, ordinary for
         a real server; a bimodal histogram is a different problem

Inter-token latency is the time of one engine step. Decompose the step and the cause of a bimodal histogram usually names itself.

Drilled in: Bad ITL with fine TTFT

A startup OOM that the totals hide

inputs   Llama 3.3 70B BF16 at TP=2 on H100 SXM 80GB, vLLM v0.32.0, 0.92
weights  70,553,706,496 x 2 B / 2 = 70.55 GB per GPU
budget   0.92 x 85.02 GB = 78.22 GB per GPU: 7.66 GB left for everything else
one seq  131,072 tokens x 163,840 B of KV per GPU = 21.47 GB
sanity   141 GB of weights on 160 GB of GPUs, and the engine cannot hold a
         single full-length sequence

"Fits" means weights, activation peak, runtime overhead and one max_model_len sequence inside the budget of each GPU. FP8 weights or TP=4 each leave about 41 GB per GPU for KV.

Drilled in: vLLM startup OOM walkthrough

A preemption page ledger

inputs   a synthetic 80e9 B device x 0.92 = 73.6e9 B; weights 16.06e9 B;
         an assumed 2.0e9 B for everything else; Llama 3.1 8B, 16-token pages
pool     (73.6 - 16.06 - 2.0)e9 / (16 x 131,072 B) = 26,483 pages
start    200 prompts of 2,048 tokens = 25,600 pages: they fit
finish   with 1,024 output tokens each = 200 x 192 = 38,400 pages
sanity   only 26,483 / 192 = 137 requests can reach full length together

The prompts fitting proves nothing about the outputs. Add each request's future output pages to the ledger before reading the preemption counter.

Drilled in: Constant preemption at peak

What FP8 should have bought

inputs   Llama 3.3 70B at TP=4 on H100 SXM, batch 64, 4,096 context, BF16 KV
BF16     35.28 GB weights + 21.47 GB KV per GPU = 16.94 ms per step
FP8      17.64 GB weights + 21.47 GB KV = 11.68 ms per step
expected 1.45x more throughput, not 2x: the KV did not shrink
observed a 15% loss puts the FP8 step near 16.94 / 0.85 = 19.93 ms,
         1.71x what it should be
sanity   scale overhead is about 1 ms; a 1.7x gap is a kernel or a config

Splitting the gap needs one fixed-batch profiler trace per configuration. The expected step time is what makes the trace readable.

Drilled in: FP8 throughput regression

inputs   Llama 3.3 70B at TP=4, BF16 activations, batch 64, 4,096 context
bytes    2 x 80 layers x 2 x (4 - 1) / 4 x 8,192 x 2 B = 3.93 MB per token per GPU
step     64 x 3.93 MB = 251.7 MB of all-reduce per GPU per step
NVLink   at about 360 GB/s achieved: 0.70 ms; step 16.94 + 0.70 = 17.64 ms
PCIe     at about 50 GB/s achieved: 5.03 ms; step 21.97 ms
sanity   1.25x slower, and in a TP group the slowest GPU sets the pace for
         all four

Before suspecting hardware, prove the replica is slower at equal load: compare inter-token latency per replica at matched running-request counts.

Drilled in: One slow replica in eight

Benchmark 2x, production 10%

inputs   a change that halves weight bytes on Llama 3.3 70B at TP=4
batch 1  640-token context: 10.55 ms -> 5.28 ms per step, 2.00x
batch 64 4,000-token context: 16.79 ms -> 11.53 ms, 1.46x
Amdahl   prefill 40%, decode 60% of production GPU time, measured step gain 1.2x:
         1 / (0.40 + 0.60 / 1.2) = 1.11x end to end
sanity   both numbers can be right; they measure different workloads

Write production speedup as Amdahl over the phases the change touches, using the phase shares from production histograms, then replay production traffic to confirm.

Drilled in: Benchmark 2x, production 10%

A periodic p99 with a flat median

inputs   Llama 3.3 70B BF16 at TP=4, batch 32, 2,000 tokens of mean context
plain    (35.28 + 5.24) GB / 3.35 TB/s = 12.1 ms per step
mixed    a 2,048-token chunk: 2 x 70.55e9 x 2,048 / 4 / 989e12 = 73 ms at peak,
         so the step is at least 85 ms, 7.0x
period   one 16,384-token prompt every 5 s = 8 chunks; (5 - 8 x 0.085) / 12.1 ms
         = 357 normal steps
sanity   8 / 365 = 2.2% of steps are slow: the p99 lands on them, the p50
         never does

A regular period is evidence. List what in the system runs on that period, then correlate slow steps with it.

Drilled in: Periodic p99 TPOT spikes

Next card: Implementation Coding Chains