Anatomy of LLM inference
Decode latency floor
Why one generated token costs roughly the weight bytes divided by memory bandwidth, and how that gives a tokens-per-second ceiling.
Decode is the phase where the model writes one new token at a time. At small batch sizes, each token needs to read almost all of the weights from GPU memory once. That read takes time no matter how fast the math is. So the time to read the weights sets a floor under the time per token.
Concept
Prefill processes the whole prompt in one go. Decode produces one token per step. Memory bandwidth is how many bytes per second the GPU can read from HBM, usually written as TB/s.
The big idea
Picture a librarian who must fetch the whole encyclopedia for every sentence they write. The fetch takes the same time whether the sentence is short or long. The weights play the encyclopedia.
The picture breaks in two places. Real GPUs cache some data, and the librarian can write several sentences per trip. That second point is the batch ceiling.
Walking through it
t_decode ≈ weight_bytes / memory_bandwidth
tok_per_s ≈ 1 / t_decodeExample 1: 8B model in BF16 on one H100
- Weights: 16 GB = 16e9 bytes.
- Bandwidth: 3.35 TB/s = 3.35e12 bytes per second.
- Time per token: 16e9 / 3.35e12 ≈ 0.0048 s = 4.8 ms.
- Tokens per second: 1 / 0.0048 ≈ 210.
This assumes perfect bandwidth and ignores KV reads. Real engines land somewhat lower.
Example 2: 70B model, tensor parallel across 2 H100s, FP8
- Full weights in FP8: about 70 GB. Each GPU holds a 35 GB shard.
- Each GPU reads its shard: 35e9 / 3.35e12 ≈ 10.4 ms per token.
- Tokens per second: about 96.
Activation all-reduces add a few percent. Treat about 90 tokens/s as the best single-user speed before batching.
Common mistakes
- Predicting chat speed from peak FLOPS. Twice the FLOPS with the same bandwidth barely changes single-stream decode.
- Ignoring KV reads. At long context, the cache adds bytes to every step.
- Treating the floor as a promise. Kernel inefficiency, sampling and launch overhead push real speed below it.
Check yourself
- Each GPU holds 35 GB of weights and has 3.35 TB/s of bandwidth. What is the floor? Answer: 35e9 / 3.35e12 ≈ 10.4 ms per token, about 96 tokens/s.
- You double the GPU's FLOPS but not its bandwidth. What changes for batch-1 decode? Answer: Almost nothing. Each step still waits on the same weight bytes.
Key takeaways
- At small batch, decode reads the weights once per token.
- The floor is weight bytes divided by bandwidth. Check it before trusting any FLOPS figure.
- Faster single-user decode needs fewer bytes (quantization, smaller models) or more bandwidth.
- Batching shares one weight read across many tokens. See the batch ceiling.
Further learning
Next, prefill time covers the phase that is limited by compute instead.
Related
Inference arithmetic
Napkin math for any model on any GPU: weight memory, decode and prefill time, batch limits and KV budget.
The roofline model and arithmetic intensity
Predicting performance from FLOPs and bytes: compute-bound vs memory-bound. Start here for the map, then work the examples.
GPU architecture
SMs, warps, tensor cores and the memory hierarchy from registers to HBM, with H100/B200 numbers.