Explainer
Two phases, two bottlenecks
A request is answered in two phases that share every weight and every line of code, and still run into different walls. Move the prompt, the output and the batch below, and watch which limit each phase is waiting on.
First the whole prompt goes through the model in a single pass. That is prefill: it builds the KV cache and produces the first token, and it is everything the client waits for before anything appears. Then the model generates one token at a time, each step reading the entire cache and every weight again. That is decode, and it repeats until the answer ends.
Prefill runs the tensor cores at 100% of peak. Decode, on the same hardware with the same weights, gets 0.5%. Nothing is broken; a lone token cannot do enough arithmetic per byte to keep up.
Prefill is bound by arithmetic. A thousand prompt tokens are a thousand rows of the same matrix multiply, and the weights are read from memory once for all of them. Arithmetic per byte is high, the tensor cores are the limit, and the first token costs about what a thousand decode steps would. Below roughly two hundred tokens the prompt is too short to earn back the read and even prefill falls onto the memory slope.
Decode is bound by bandwidth. One token per request per step, and the 140 GB of weights are read in full to produce it. At batch 1 that is one FLOP per byte, two hundred times short of the ridge, and the tensor cores sit at a fraction of a percent. Batching climbs the slope because the read is shared, exactly as the KV cache page argued. But every request brings its cache along. With the 1K prompt above, decode does not cross the ridge until 512 requests, and holding 512 of them takes 182 GB of cache, two more than four 80 GB cards have left once the weights are loaded. Give each request 4K instead and it never crosses at all. The ridge is reachable in principle and, on the budget from the previous page, almost never in practice.
This is why disaggregation exists. One engine serving both phases interleaves them, and a long prefill arriving mid-stream stalls every decode step in flight: the reader sees tokens pause. Split the phases onto separate pools and each can be tuned for its own wall, prefill for compute and decode for memory, with the cache shipped between them. Whether the shipping costs less than the interference is the question the whole disaggregation debate turns on.
The four numbers an SLO is written in
Each metric belongs to a phase, which is why tuning one rarely moves the other.
- time to first token
The prefill, plus any time spent queued. Owned by compute and by prompt length. Prefix caching attacks this one.
- inter-token latency
One decode step, the gap between consecutive tokens. Owned by bandwidth, by batch size, and by how much cache the batch drags. Quantisation and speculative decoding attack this one.
- request latency
First token plus every step after it. For a long answer the prefill barely registers; for a short one it dominates.
- output tokens per second
Across every request in flight. Rises with batch until the cache no longer fits, which is where the KV cache page ended. The lever that raises it is the same one that lengthens inter-token latency, and that trade is the subject of the next explainer.
What this model leaves out
Two FLOPs per parameter per token counts the linear layers and ignores attention itself, which grows with context. At long contexts attention takes a real share of prefill, and its own arithmetic intensity is low, so prefill is somewhat less compute-bound than drawn here. FlashAttention is the response to exactly that.
A roofline is two floors and a maximum. Real kernels lose time to launch overhead, to the interconnect between four cards, to padding and to scheduling, and none of it appears here. Measured numbers sit below the roof, never on it.
Only the A100 40GB is offered, for the same reason as the previous page: its dense BF16 peak and bandwidth are published and settled. Newer parts have a higher ridge, not a different shape, and the two phases land on the same sides of it.
Read next
- explainers/
- You are not renting the weights. You are renting the cache.
The budget that stops decode from ever reaching the ridge.
- The hit rate is the workload
What a ten-second prefill does to an agent that has to pay it every turn.
- When to disaggregate
Doubleword on the conditions under which splitting the phases actually pays.
- DistServe: disaggregating prefill and decoding
The paper that measured the interference and proposed the split.
- glossary/
- Time to first token
23.3 ms, the floor on four A100s for Llama 3 70B: the time to read 140 GB of weights once, which no cache hit removes
- Inter-token latency
23.3 ms, per token at batch 1 on four A100s for Llama 3 70B in BF16, about 43 tokens per second, set entirely by memory bandwidth
- Throughput
512 requests, the batch at which decode on four A100s for Llama 3 70B with 1K contexts crosses from memory-bound to compute-bound, needing 182 GB of cache
- Service level objective
p99 TTFT, the objective most often missed, because prefill of one long prompt delays the first token of everyone queued behind it
- Memory bandwidth
140 GB per step, the weight read for Llama 3 70B in BF16, so 6 TB/s across four A100s gives a 23.3 ms floor per token
- Prefill
208 tokens, the prompt length above which prefill on an A100 becomes compute-bound, the roofline's ridge point for BF16
- Decode
1 FLOP per byte, at batch 1 in BF16: two operations per parameter over two bytes per parameter, two hundred times below the A100's ridge
- Roofline model
312 TFLOPS and 1.5 TB/s, the A100's two ceilings for dense BF16, meeting at a ridge of 208 FLOP per byte
- Arithmetic intensity
2 FLOPs per parameter per token, the multiply and add each weight contributes, the numerator every intensity on this site is built from
- Ridge point
208 FLOP per byte, on the A100 for dense BF16; every explainer's prefill crosses it at about 208 prompt tokens
- Memory-bound
below 208, FLOP per byte on the A100; decode at batch 1 sits at about 1
- Compute-bound
above 208, FLOP per byte on the A100; a 4K prompt's prefill sits at about 4,000
- Tensor cores
312 versus 19.5 TFLOPS, the A100's dense BF16 rate on tensor cores against its FP32 rate on the general cores, a factor of 16
- GEMM
M = tokens in the step, the dimension batching grows; at M = 1 a GEMM is a matrix-vector product and bandwidth-bound
- Tensor parallelism
6 TB/s, aggregate weight-read bandwidth across four A100s under tensor parallelism, the number behind every latency floor on this site