Chunked Prefill
Breaking long prompts into chunks to prevent head-of-line blocking — trading slightly higher TTFT for stable TPOT across all requests.
When a long prompt arrives, its prefill can monopolize the GPU for hundreds of milliseconds — causing all decode requests to stall and their TPOT to spike. Chunked prefill solves this by breaking long prefills into fixed-size chunks interleaved with decode steps.
1. The Head-of-Line Blocking Problem
Without chunked prefill, a 10K-token prompt requires one massive prefill step:
Step t: [Decode(A), Decode(B), Decode(C)] — normal, ~5ms per step
Step t+1: [Prefill(D, 10K tokens)] — 50ms! A, B, C get NO decode
Step t+2: [Decode(A), Decode(B), Decode(C), Decode(D)] — back to normal
During step t+1, requests A, B, C receive no tokens for 50ms. Their users see a stutter — TPOT spikes from 5ms to 50ms. This is head-of-line blocking: one heavy request stalls everyone.
Why 50ms? Prefill is compute-bound. For 70B on H100 with TP=4: plus overhead. Larger models or longer prompts make this worse.
2. The Solution: Chunk the Prefill
Break the 10K-token prefill into chunks of chunk_size tokens (e.g., 512):
Step t: [Decode(A,B,C) + PrefillChunk(D, tokens 0-511)] — ~6ms
Step t+1: [Decode(A,B,C) + PrefillChunk(D, tokens 512-1023)] — ~6ms
Step t+2: [Decode(A,B,C) + PrefillChunk(D, tokens 1024-1535)] — ~6ms
...
Step t+19: [Decode(A,B,C) + PrefillChunk(D, tokens 9728-9999)] — ~5ms
Step t+20: [Decode(A,B,C,D)] — D fully prefilled, joins decode
Every step includes decode for A, B, C — their TPOT stays at ~6ms consistently. D’s prefill takes 20 steps instead of 1, but the system stays smooth.
The Tradeoff
| Without chunking | With chunking | |
|---|---|---|
| D’s TTFT | ~50ms (fast, one shot) | ~120ms (20 × 6ms, slightly slower) |
| A/B/C’s TPOT during D’s prefill | 50ms spike | ~6ms (stable) |
| P99 TPOT | Very high (spikes) | Low (smoothed) |
The tradeoff is deliberate: slightly slower TTFT for one request in exchange for stable TPOT for all requests. For streaming applications, this is almost always the right trade.
3. Token Budget: The Unified Scheduler
Chunked prefill needs a scheduling framework to decide how much prefill and decode work fits in each step. The answer is a token budget — a unified “currency” for all work:
Scheduling Priority
- Decode first: All active decode requests get their 1-token slot (guaranteed)
- Prefill fills remaining budget: Whatever budget remains goes to prefill chunks
- Fairness: Multiple pending prefills are served round-robin
# Simplified token budget scheduler
def schedule_step(running_decode, pending_prefill, token_budget):
batch = []
budget_remaining = token_budget
# Priority 1: guarantee all decode requests
for req in running_decode:
batch.append(DecodeToken(req))
budget_remaining -= 1
# Priority 2: fill remaining budget with prefill chunks
for req in pending_prefill:
chunk_size = min(req.remaining_prefill, budget_remaining)
if chunk_size > 0:
batch.append(PrefillChunk(req, chunk_size))
budget_remaining -= chunk_size
if budget_remaining <= 0:
break
return batch
Choosing the Token Budget
| Budget | Effect |
|---|---|
| 512 | Very smooth TPOT, but slow prefill throughput |
| 2048 | Good balance (vLLM default range) |
| 8192 | Fast prefill, but decode may see latency bumps |
| max_model_len | Effectively disables chunking |
4. Interaction with Other Techniques
| Technique | Interaction with chunked prefill |
|---|---|
| Continuous batching | Chunked prefill is an extension — scheduling granularity moves from “request” to “token budget” |
| PagedAttention | KV blocks are allocated incrementally as chunks are processed |
| Prefix caching | Cached prefix blocks skip chunked prefill entirely (already computed) |
| CUDA graphs | Each unique batch shape may need its own captured graph |
For how the attention kernel handles mixed prefill+decode batches, see Attention Backends.
Self-Check
- Explain head-of-line blocking and how it causes TPOT spikes
- Describe the token budget scheduling algorithm
- Calculate the TTFT increase from chunking a 10K prompt into 512-token chunks
- Explain why decode requests get scheduling priority over prefill chunks
- Choose an appropriate token budget for a latency-sensitive vs throughput-sensitive workload