Prefill and decode
Code version: DeepInfer @ 9bd9cf2 · 2026-10-21
In one sentence
Generation has two phases. Prefill reads the whole prompt in one step and fills the KV cache; decode then adds one token per step.
The idea#
Two phases of one request#
The loop of What an LLM does runs the model once per new token. But the first run is special:
- Prefill. The prompt is known in full. So the model reads all prompt tokens in one step. It computes the keys and values of every prompt token and stores them in the KV cache. The last position gives the first new token.
- Decode. Each later step has only one new token: the token from the step before. The model reads the keys and values of all earlier tokens from the cache, computes them for the new token, and samples the next token.
Take a request with a prompt of 1,000 tokens and an answer of 200 tokens. It has one prefill step with 1,000 tokens. Then it has 199 decode steps with one token each.
Why prefill can be parallel and decode cannot#
In prefill, all inputs are known before the step starts. The causal mask makes sure that position 5 sees only positions 0 to 5. So the model can compute all positions at the same time, as one big matrix multiply. The result is the same as one position at a time.
In decode, the next input does not exist until the step before has finished. Step 2 needs the token of step 1. So the steps of one request must run one after the other.
Why the two phases feel different to the hardware#
Each step must read all weights of the model from device memory. What changes is how many tokens use each weight in that step:
- Prefill: each weight serves all prompt tokens, often thousands. The device does a lot of math per byte that it reads. The speed limit is the math: prefill is compute-bound.
- Decode: each weight serves only one token per sequence. Even with many sequences in a batch, the math per byte is small. The speed limit is how fast memory can deliver the weights and the KV cache: decode is memory-bound.
Users feel the two phases as two latencies. The time to the first token (TTFT) is mostly prefill. The time between later tokens (TBT) is the time of one decode step.
An everyday comparison#
Think of a translator who gets a letter and must answer it. First, the translator reads the whole letter quickly, all at once. Then the translator writes the answer word by word, and each word depends on the words before it.
The comparison stops here. A human remembers a summary of the letter. The model keeps the keys and values of every token in the KV cache, and each decode step reads all of them again.
The picture#
Panel 1 has many tokens in one step. Panel 2 has one token per step, and the cache grows by one entry each time. Panel 3 sums up the difference, and how DeepInfer keeps the two kinds of steps apart.
In DeepInfer#
This is a basics page, so this part is short. The page The scheduler goes further.
Step 1: each step is a prefill step or a decode step#
The C++ scheduler decides the kind of each step. A NORMAL engine (the default) mostly runs decode steps. It tries a prefill step only at fixed points:
bool is_any_point = (decode_step != 0) && (decode_step % prefill_any_attempt_interval == 0);
bool is_all_point = (decode_step % prefill_attempt_interval == 0);
if (!is_any_point && !is_all_point) {
return _schedule_decode(acquire);
}
bool want = (get_gpu_cache_usage() <= GPU_CACHE_PREFILL_WATERMARK) && _has_prefill_waiting();
bool do_prefill;
if (is_any_point) {
do_prefill = (_dp_allreduce(want ? 0 : 1, c10d::ReduceOp::MIN) == 0); // ANY:任一 rank 想做即触发
} else {
do_prefill = (_dp_allreduce(want ? 1 : 0, c10d::ReduceOp::MIN) == 1); // ALL:全员想做才触发
}
if (!do_prefill) {
return _schedule_decode(acquire);
}prefill_attempt_interval decode steps (default 70) and every prefill_any_attempt_interval decode steps (default 100). A prefill needs waiting work and cache usage of 90 % or less.The defaults come from DEEPINFER_PREFILL_ATTEMPT_INTERVAL and DEEPINFER_PREFILL_ANY_ATTEMPT_INTERVAL (deepinfer/environ.py:174-178). After a prefill step, the decode counter goes back to 0 (csrc/engine/scheduler.hpp:676-680).
Step 2: a batch never mixes the two#
The sampler input check makes this a hard rule:
TORCH_CHECK(seq_metadata != nullptr);
TORCH_CHECK_EQ(seq_metadata->is_prompt, is_prompt) << "Mixed prefill and decode batches are unsupported.";In a prefill step, the sampler takes only the last row of each sequence, at the position seqlen_k (csrc/engine/prepare_sample.cpp:197-198).
Step 3: decode steps send one token plus drafts#
DeepInfer uses speculative decoding by default. A decode step sends the newest token plus the draft tokens of each sequence (csrc/engine/mtp_varlen.hpp:153-157). The default number of drafts is 5 (deepinfer/environ.py:129), so a sequence sends up to 6 tokens per decode step. Decode steps can also replay a CUDA graph; prefill steps never do (deepinfer/worker/model_runner.py:182).
Deep dive#
Deep diveArithmetic intensity with numbers
Arithmetic intensity is the math per byte: FLOPs divided by bytes moved. Use the rule of thumb from What an LLM does: about 2 FLOPs per weight per token. All numbers below are illustrative, for a dense model with 10 billion weights in BF16 (2 bytes each).
| Step | Tokens | FLOPs (2 × 1010 × tokens) | Weight bytes | FLOPs per byte |
|---|---|---|---|---|
| Prefill, 1 prompt of 2,000 tokens | 2,000 | 4 × 1013 | 2 × 1010 | 2,000 |
| Decode, batch of 64 sequences | 64 | 1.28 × 1012 | 2 × 1010 | 64 |
| Decode, 1 sequence | 1 | 2 × 1010 | 2 × 1010 | 1 |
Take an illustrative device with 2,000 TFLOP/s and 8 TB/s. It can do 2,000 / 8 = 250 FLOPs in the time it reads one byte (its ridge point).
- Prefill: 2,000 FLOPs per byte is above 250, so the math sets the time: 4 × 1013 / 2 × 1015 = 20 ms.
- Decode with 64 sequences: 64 is below 250, so the memory sets the time. 2 × 1010 B / 8 × 1012 B/s = 2.5 ms per step, for 64 tokens.
- Decode with 1 sequence: also 2.5 ms, but for 1 token. The device does almost no math.
Deep diveHow DeepInfer chooses the kind of step, line by line
switch (engine_type) {
case EngineType::PROMPT:
// PROMPT 引擎只做 prefill,即使没调度到请求也返回(空的)prefill 结果
return _schedule_prefill(acquire);
case EngineType::COMPLETION:
// COMPLETION 引擎只做 decode
return _schedule_decode(acquire);
case EngineType::NORMAL: {
// NORMAL 引擎:决定这一步全组做 prompt 还是 decode。dp 内必须同型,靠投票统一。
//
// 两类尝试点:
// ALL 点(每 prefill_attempt_interval 个 decode step):全员都有 prefill 活才做,
// 防止一个 rank 干活、其余 rank 陪跑空 prompt step。
// ANY 点(每 prefill_any_attempt_interval 个 decode step):一个 rank 有活就一起做,
// 给孤立 rank 兜底——它的 prefill 最多等这么多个 decode step。
// 两类点重合时按 ANY 算。
//
// prompt step 会把 decode_step 清零(见 schedule()),所以刚做完 prefill 的下一步
// decode_step==0,又落在 ALL 点上:想连着 prefill 同样要全员有活。
// decode_step 各 rank 一致,投票的次数和语义全组对得上,不会卡在 allreduce。
bool is_any_point = (decode_step != 0) && (decode_step % prefill_any_attempt_interval == 0);
bool is_all_point = (decode_step % prefill_attempt_interval == 0);
if (!is_any_point && !is_all_point) {
return _schedule_decode(acquire);
}
bool want = (get_gpu_cache_usage() <= GPU_CACHE_PREFILL_WATERMARK) && _has_prefill_waiting();
bool do_prefill;
if (is_any_point) {
do_prefill = (_dp_allreduce(want ? 0 : 1, c10d::ReduceOp::MIN) == 0); // ANY:任一 rank 想做即触发
} else {
do_prefill = (_dp_allreduce(want ? 1 : 0, c10d::ReduceOp::MIN) == 1); // ALL:全员想做才触发
}
if (!do_prefill) {
return _schedule_decode(acquire);
}
std::unique_ptr<SchedulerOutputs> outputs;
if (want) {
outputs = _schedule_prefill(acquire);
}
// 第二轮投票:确认这轮真有 rank 排出了 prefill。want 只看了 waiting 非空,
// 但 _schedule_prefill 仍可能空手(slot 满、KV 块不够)。
// 全组都空手时必须转 decode:硬做空 prompt step 会把 decode_step 清零,
// 下一步接着投票、接着空手——running 满 + waiting 非空就死循环了。
bool any_scheduled = (_dp_allreduce(outputs ? 0 : 1, c10d::ReduceOp::MIN) == 0);
if (!any_scheduled) {
return _schedule_decode(acquire);
}
if (outputs) {
return outputs;
}
// 组内确有 rank 在做 prompt:本 rank 陪跑空 prompt step 保持步型一致
return _make_outputs({}, true, 0, {}, {}, {}, 0);
}Translated comments (Chinese → English)
- 290 The PROMPT engine only does prefill. It returns a (possibly empty) prefill result even if no request was scheduled.
- 293 The COMPLETION engine only does decode.
- 296 NORMAL engine: decide if the whole group does a prompt step or a decode step. Inside DP the kind must be the same, so a vote decides it.
- 299-302 ALL point (every prefill_attempt_interval decode steps): prefill only if every rank has prefill work. ANY point (every prefill_any_attempt_interval decode steps): one rank with work is enough; an isolated rank's prefill waits at most this many decode steps.
- 305-306 A prompt step sets decode_step to 0, so the next step is again an ALL point.
- 328-331 Second vote: check that some rank really scheduled a prefill. If all ranks came back empty-handed, do a decode step, else the loop could hang.
- 339 Some rank does a prompt step: this rank joins with an empty prompt step, so all ranks run the same kind of step.
- Lines 288-294: an engine of type PROMPT does only prefill; COMPLETION does only decode. These two types are for separate prefill and decode machines.
- Lines 308-312: a NORMAL engine counts decode steps in
decode_step. Between attempt points, it always does decode. - Line 313: this rank wants a prefill if cache usage is at most 90 % and the first waiting request is schedulable.
- Lines 315-319: the data-parallel ranks vote with an all-reduce (MIN). At an ANY point one "yes" is enough; at an ALL point every rank must say yes.
- Lines 324-338: if the vote says yes, the rank schedules a prefill. A second vote makes sure that at least one rank found work.
Worked example (one rank, defaults 70 and 100). A long decode is running, and the counter decode_step is 5 when a new request arrives.
- Counter values 5 to 69: no attempt point, so each step is a decode step. The request waits.
- Counter value 70: an ALL point. The rank wants a prefill, and the vote of one rank says yes. This step is a prefill step for the new request.
- The counter goes back to 0. The next step is again an ALL point. If more requests wait, it is also a prefill step.
So a new request can wait up to 69 decode steps for its prefill. The page Data parallelism and step votes explains the votes with several ranks.
Deep diveChunked prefill and the token budget
One step may hold at most max_num_batched_tokens tokens. The default is the largest of max_model_len, 4,096 and max_num_seqs × (1 + drafts) (deepinfer/config.py:219-228). With the default max_num_seqs = 256 (deepinfer/engine/arg_utils.py:36) and 5 drafts, the last term is 256 × 6 = 1,536.
A long prompt can be larger than the budget that is left in the step. With chunked prefill, the scheduler cuts the prompt and runs the rest in later prefill steps (csrc/engine/scheduler.hpp:470-475). Chunked prefill is off by default (deepinfer/engine/arg_utils.py:42). Without it, a prompt that does not fit in the rest of the budget waits for a later step (csrc/engine/scheduler.hpp:504-507). Also, the prompt limit is the smaller of max_model_len and max_num_batched_tokens (csrc/engine/scheduler.hpp:112), and the config refuses a budget below max_model_len (deepinfer/config.py:208-212).
Worked example (chunked prefill on, budget 8,192, no context parallelism). A prompt of 20,000 tokens runs in three prefill steps, as chunks of 8,192, 8,192 and 3,616 tokens. Every chunk writes its keys and values to the cache. Only the last chunk produces the first output token.
The deployment notes describe the production prefill budget: 32,768 tokens per step, with the chunk size solved from a time budget (doc/inference_deployment_qa.md:115). That load balancer is not in this repository.
Deep diveDecode steps with draft tokens
With speculative decoding, a decode step does not send one token per sequence. It sends the last accepted token plus the draft tokens that the draft module proposed in the step before. The main model checks all of them in one step. This turns part of decode into a small prefill: several tokens per sequence share one read of the weights.
- Number of tokens. Without the confidence scheduler, a sequence sends
1 + len(draft_tokens)tokens (csrc/engine/mtp_varlen.hpp:153-157). That scheduler,DEEPINFER_ENABLE_MTP_VARLEN, is off by default (deepinfer/environ.py:131). The sampler checks that this is at mostnextn + 1(csrc/engine/prepare_sample.cpp:212). - After prefill. The draft list starts with placeholder ids,
vocab_size − 1, one per draft (deepinfer/model_executor/layers/mtp_info_pool.py:9-11). - Sampling. In a decode step, the sampler gets every row of the sequence, not only the last one (
csrc/engine/prepare_sample.cpp:202-206). It accepts or rejects each draft.
The page Speculative decoding explains the accept rule. The production numbers: the draft module proposes 5 tokens, and dynamic verification checks 3.2 to 4.4 of them on average (doc/inference_deployment_qa.md:243, doc/inference_deployment_qa.md:247).
Deep divePrefill and decode on separate machines (production)
Because the two phases stress the hardware differently, large services often run them on different machines. The DeepSeek deployment notes describe:
- ordinary 8-GPU machines for prefill, and 64- to 128-GPU supernodes for decode (
doc/inference_deployment_qa.md:11); - prefill with expert parallelism 2 to 8, decode with expert parallelism 64 to 128 (
doc/inference_deployment_qa.md:47-49).
The engine types PROMPT and COMPLETION exist for this split (csrc/engine/scheduler.hpp:288-294). But this release does not include the parts that connect them: the README lists prefill/decode disaggregation as "not included in this release" (README.md:38-41). An offline LLM runs a NORMAL engine that does both.
What can go wrong#
- A long wait for the first token. A NORMAL engine tries a prefill only at attempt points, by default every 70 decode steps. A new request can wait up to 69 decode steps (deep dive "How DeepInfer chooses"). Lower
DEEPINFER_PREFILL_ATTEMPT_INTERVALfor lower TTFT; decode then pauses more often. - No prefill when the cache is full. Above 90 % cache usage, the rank does not want a prefill (
csrc/engine/scheduler.hpp:80,csrc/engine/scheduler.hpp:313). New requests wait until running requests finish. - A prompt that is too long. A prompt above the prompt limit ends with
"prompt_length_exceeded"and never runs (csrc/engine/scheduler.hpp:377-384). - Mixed batches. Code that tries to put prefill and decode sequences in one batch fails the check in
prepare_sample.cpp(csrc/engine/prepare_sample.cpp:61).
Why it matters. Almost every design choice of an inference engine treats the two phases differently: batch sizes, CUDA graphs, chunking, parallelism, even separate machines. When you read about a feature, ask which phase it makes faster.
Check yourself#
- 1A request has a prompt of 300 tokens and gets 50 output tokens, with no draft tokens. How many prefill steps and decode steps does it need, without chunked prefill?easy
Show answer
One prefill step with 300 tokens, which also makes output token 1. Then 49 decode steps make tokens 2 to 50.
- 2Why can the model process all prompt tokens at the same time, but not all output tokens?easy
Show answer
All prompt tokens are known before the step, and the causal mask keeps each position from seeing later ones. An output token is unknown until the step before it has finished.
- 3In the illustrative table, why does a decode step for 1 sequence take about as long as a decode step for 64 sequences?medium
Show answer
Both steps must read all 20 GB of weights, and at 8 TB/s that takes about 2.5 ms. The math for 64 tokens is still much less than the device can do in that time, so the memory read sets the time.
- 4A DeepInfer NORMAL engine with one rank and default settings is at decode step 71 when a request arrives. At which decode step can its prefill run, and why?hard
Show answer
At decode step 100, the next ANY point:
decode_step % 100 == 0. The next ALL point would be 140. With one rank, both votes give the wish of that rank (csrc/engine/scheduler.hpp:308-319). The defaults are 70 and 100 (deepinfer/environ.py:174-178). The cache usage must also be at most 90 % (csrc/engine/scheduler.hpp:313).