Chunked prefill
Stop blocking decoders with one big prompt
Sources: Chunked Prefill — vLLM documentation; DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving — Zhong et al., 2024
We’ve made decode efficient (batching), KV memory efficient (paging), and prefix sharing efficient (caching). One serving pathology remains: a single huge prompt blocking everything else. Chunked prefillchunked prefillSplitting a long prompt into multiple smaller prefills so decoding requests aren’t blocked behind one giant compute step.See in glossary → is the fix.
The problem
Suppose 32 decode requests are running, each happily generating one token per step at, say, 50 ms per step. Now a new request arrives with a 64,000-token prompt. Its prefill needs to process all 64k tokens through the model. The attention-score computation scales quadratically with sequence length because every token attends to every earlier token, while the projections and MLP scale linearly. Depending on the model and hardware, such a prefill can take hundreds of milliseconds or more.
If you naively schedule the prefill as a single forward pass, every one of the 32 decoders is frozen for those 500 ms. Their inter-token latency spikes from 50 ms to 550 ms. Users start watching the text catch up in a sudden burst. SLOs blow up.
Worse, you can’t just lower the priority of the prefill: it has to happen before the new request can generate even its first token, and there is no way to “decode without prefilling first.”
The fix: cut the prefill into bites
Chunked prefill splits a long prompt across multiple forward passes. Instead of one 64k-token prefill, you do, say, 8 chunks of 8k tokens, interleaved with decode steps from the other 32 requests. Each step’s forward pass is:
[ 8k prefill tokens from request A ] + [ 1 decode token each from requests 1..32 ]
All 8,000 + 32 = 8,032 tokens flow through one packed batch. The attention kernel handles the variable-length structure. Per-step latency rises, but no one is frozen behind the entire prefill; everyone can make progress at each scheduled step.
After 8 such steps, request A’s prefill is done; it then joins the decoders, generating one token per step like everyone else.
Mixing prefill and decode kernels
The implementation challenge is that prefill tokens and decode tokens, in the same batch, have very different shapes for attention:
- A prefill token at position attends to positions 0.. within its own request (causal-masked).
- A decode token attends to all of its request’s cached positions.
Serving engines pass metadata such as sequence lengths, positions, and KV-block tables so that their attention kernels can handle these different structures in a packed batch. The exact kernel layout is implementation- and version-dependent.
Throughput vs latency, finally meeting
Chunked prefill is the moment in this essay where the textbook tradeoff between throughput and latency becomes adjustable in real time. Three knobs:
- Chunk size: smaller = better decoder latency, lower per-chunk compute efficiency.
- Max number of decode requests per step: bigger = more throughput, more per-step latency.
- Admission policy: do we accept a new long prompt now, or queue it until the decoder population is smaller?
A serving system can tune these settings against observed SLOs, but the choice involves policy as well as measurement. Operators typically benchmark representative workload mixes and configure vLLM’s scheduling limits accordingly.
The corollary: prefill-decode disaggregation
For very large deployments, the asymmetry between prefill (compute-bound, big matmuls) and decode (memory-bound, tiny matmuls) is so stark that some teams run them on different GPUs entirely:
- Prefill GPUs: provisioned for prompt-processing throughput and the prompt/KV footprint they must hold. They receive prompts, do prefill, and emit KV cache.
- Decode GPUs: provisioned for sustained decode throughput and KV-cache capacity. They receive KV cache and do decode.
The KV cache has to be transferred from the prefill GPU to the decode GPU (over NVLink or a network path) when prefill finishes. That transfer adds latency and consumes bandwidth, so whether disaggregation improves cost or throughput depends on the workload and deployment. vLLM supports disaggregated prefill/decode through KV-transfer interfaces.
We’ve now covered every batching, memory, and scheduling trick in vLLM’s main playbook. There’s one more category, orthogonal to all of the above: making the autoregressive decode loop generate more than one token per forward pass. That’s speculative decoding.