Chunked Prefill 原理:LLM 调度、延迟与吞吐权衡
AI 推理优化中的 Chunked Prefill
读者定位:面向已经了解 Transformer 推理、KV cache 和批处理概念的工程读者。
本文把 chunked prefill 当作一个调度与系统优化问题来讲,而不是只把它描述成“把 prompt 切小”的技巧。
阅读地图:本文解决什么问题

Chunked prefill 主要解决 LLM 在线推理中的一个不舒服的矛盾:长 prompt 的 prefill 阶段计算密集、耗时长,但 decode 阶段每次只生成一个 token,更依赖 KV cache 读写和批量并发。传统连续批处理可以把多个 decode 请求拼在一起,但一旦来了一个很长的 prefill,它仍然可能在某个调度轮次中占据大量 token budget,让已经在生成的请求等待。
本文会依次回答五个问题:
| 问题 | 读完后应能做出的判断 |
|---|---|
| chunked prefill 到底切的是什么 | 能区分“切执行粒度”和“截断模型上下文” |
| 它为什么改善吞吐和延迟 | 能解释 compute-bound prefill 与 memory-bound decode 的互补 |
| 它和 continuous batching、KV cache、paged attention 有什么关系 | 能把它放进完整推理服务栈 |
| chunk size / token budget 怎么调 | 能按 TTFT、ITL、吞吐和显存目标做初始配置 |
| 上线后看哪些指标 | 能发现饥饿、抢占、OOM 和尾延迟退化 |
知识点 1:Prefill 与 Decode 的负载不对称

一次自回归 LLM 请求通常分成两个阶段。Prefill 读取整段输入 prompt,计算每一层的中间状态和 KV cache,并产出第一个输出 token 所需的上下文状态;decode 则在之后的每一步读取已有 KV cache,为每个活跃请求生成一个新 token。
这两个阶段的系统画像差别很大。Prefill 一次处理很多输入 token,矩阵乘规模大,较容易把 GPU 计算单元喂饱;decode 每轮每个请求只新增一个 token,常常受 KV cache 读取、写入和内存带宽影响。
用户侧看到的指标也不同:prefill 强烈影响 TTFT,即 time to first token;decode 强烈影响 ITL,即 inter-token latency。
实践含义是:如果服务中只有短请求,连续批处理通常已经能提供不错的 decode 批量度;如果服务中混入长文档总结、RAG 大上下文、多轮会话重放等请求,长 prefill 会变成一个粗粒度的大任务。Chunked prefill 的入口正是在这里:它让调度器不必把一个长 prefill 当作不可拆的整块工作。
知识点 2:未切分 Prefill 为什么会阻塞在线生成

连续批处理的核心思想是:每个调度轮次把可执行的请求组成一个 batch,以提高 GPU 利用率。但 batch 通常受 token budget、最大序列数、KV cache 可用空间和内核形态限制。一个长 prompt 如果以完整 prefill 形式进入某个轮次,会带来两个问题。
第一,它占用大量 token budget,让 decode 请求无法在同一轮次中被充分调度。第二,它让这个轮次的耗时显著变长,即使 decode 请求被混入了同一个 batch,也可能因为同轮 prefill 太重而增加 ITL。于是短交互请求会被长上下文请求牵连,表现为尾部延迟恶化。
这也是为什么“只做 continuous batching”不等于解决了混合负载调度。Continuous batching 提升了请求进入和离开 batch 的灵活性;chunked prefill 进一步降低了单个长 prefill 的调度粒度。
知识点 3:Chunked Prefill 的准确定义

Chunked prefill 是把一个长输入 prompt 的 prefill 计算拆成多个较小 chunk,并在多个调度轮次中逐步执行。每个 chunk 处理 prompt 的一段 token,处理完成后把对应 KV 写入缓存;后续 chunk 继续基于已经处理过的前缀推进,直到整个 prompt 的 prefill 完成并进入 decode。
这里有两个边界必须说清楚。
| 容易混淆的说法 | 更准确的说法 |
|---|---|
| “把上下文切断了” | 没有切断语义上下文;只是分轮执行 prefill |
| “减少了整个请求的 KV cache” | 不会消除完整前缀所需的 KV;主要降低单轮处理峰值和调度阻塞 |
| “一定让首 token 更快” | 对长请求自身,过小 chunk 可能拉长 prefill 完成时间;它更多是在保护混合负载中的整体延迟和吞吐 |
| “只是实现细节” | 它会改变调度策略、批次组成、TTFT/ITL 权衡和显存压力 |
| vLLM 文档把它描述为将大 prefill 切成小块,并与 decode 请求放在一起批处理;TensorRT-LLM 文档中也称其为 chunked context,即把输入 token 分成更小 chunk 后与 decode 请求混合执行。SARATHI 系列论文则把 chunked-prefills 与 decode-maximal batching 绑定起来,用于缓解吞吐与延迟之间的冲突。 |
知识点 4:Decode-Maximal Batching 的核心思想

Chunked prefill 本身只是让 prefill 可拆;真正产生收益的是调度策略。一个常见策略是 decode 优先:先把所有等待执行下一 token 的 decode 请求放进 batch,再用剩余 token budget 安排 prefill。如果某个 prefill 放不下,就只放入它的一段 chunk。
这种方式的直觉是:decode 请求延迟敏感,每轮缺席都会直接增加用户看到的 token 间隔;prefill chunk 则提供计算密度,让 batch 不至于变成只有 decode 的低利用率内存访问工作。SARATHI 把这种组合称为让 decode “piggyback” 在 prefill chunk 上:prefill 提供足够计算量,decode 借同一批次执行,摊薄单独 decode batch 的低利用率问题。
这并不意味着每个系统都必须严格只放一个 prefill chunk。不同推理框架会根据 token budget、最大序列数、KV cache、模型后端和调度器实现做不同取舍。关键原则是相同的:prefill 被变成可以被调度器细粒度安排的工作单元,而不是阻塞整个轮次的大块。
知识点 5:调度循环中发生了什么

在一个简化的调度循环里,系统会维护至少三类状态:等待 prefill 的新请求、正在 decode 的活跃请求、以及可用的 KV cache 和 token budget。每轮调度大致经过以下判断。
| 步骤 | 调度器要判断什么 | 结果 |
|---|---|---|
| 收集 decode | 哪些活跃序列需要生成下一个 token | 优先放入 batch,保护 ITL |
| 计算剩余预算 | max_num_batched_tokens 或等价预算还剩多少 | 决定能放多少 prefill token |
| 安排 prefill | 下一个 prompt 能否完整放入当前预算 | 能放则完整 prefill;不能放则切 chunk |
| 执行模型 | batch 中同时包含 decode token 和 prefill token | GPU 利用率通常更平衡 |
| 更新状态 | 写入新 KV,更新请求进度 | prefill 未完成的请求回到等待队列 |
vLLM 的公开文档明确提到,启用 chunked prefill 后调度策略会优先处理 decode,再用 max_num_batched_tokens 的剩余额度调度 prefill;如果 pending prefill 放不下,就自动 chunk。这个描述足以抓住生产调参的核心旋钮:token budget 不是一个孤立的吞吐参数,它会改变 decode 和 prefill 的相遇频率。 |
知识点 6:KV Cache 与显存压力的真实关系

Chunked prefill 常被误解为“降低长上下文显存需求”。更准确的说法是:它降低单个调度轮次需要处理的 prompt token 数量,因而可能降低某些执行路径上的峰值工作区、attention 临时开销或每轮 token 上限压力;但一个请求最终要使用完整上下文生成答案,已经处理过的前缀 KV 仍然要被保存,除非另有滑动窗口、压缩、淘汰或近似 attention 机制。
TensorRT-LLM 文档强调,chunked context 可以把每次迭代的内存消耗从完整上下文长度解耦到较小 chunk size,并有助于在相同内存消耗下提高并发或处理更长上下文。这个说法应结合具体实现理解:服务端是否能接收更长输入,取决于每轮最大 token、KV block 管理、总 KV cache 池、并发准入策略和模型最大上下文长度。
生产判断时可以分两层看:
| 层次 | 受 chunked prefill 直接影响吗 | 主要观测 |
|---|---|---|
| 单轮执行峰值 | 是,chunk 越小单轮 prefill 工作越小 | batch token 数、workspace、kernel 时间 |
| 完整请求 KV 总量 | 间接受影响,但不会凭空消失 | KV cache 占用、block 数、抢占/重算 |
| 并发准入 | 受实现策略影响 | 同时活跃序列数、排队时间、拒绝率 |
知识点 7:TTFT、ITL 与吞吐的三角权衡

Chunked prefill 的调参不能只问“快不快”,必须先定义快的是哪个指标。
TTFT 衡量用户等到第一个 token 的时间。对长 prompt 自身来说,prefill 被切成太多轮以后,完成整个 prefill 的轮次数增加,TTFT 可能变差;但在混合流量中,短请求不再被长 prefill 大块阻塞,它们的 TTFT 可能改善。
ITL 衡量生成过程中相邻 token 的间隔。decode 优先和较小 chunk 往往能保护 ITL,因为每轮 decode 更容易被及时调度,且不会被巨大的 prefill 同轮拖慢。
吞吐通常看 tokens/s、requests/s 或在 tail latency SLO 下的最大可承载请求率。较大的 token budget 可以让每轮装入更多 prefill token,减少调度轮次和额外开销,提高总吞吐或长请求 TTFT;但过大又可能重新制造“长 prefill 拖 decode”的问题。
因此常见方向是:交互式在线服务先保护 ITL 和短请求 TTFT;离线批处理或长文档批量总结可以更偏吞吐;多租户混合服务需要按租户和 prompt 长度分桶观察 p95/p99。
知识点 8:Chunk Size 与 Token Budget 怎么调

不同框架暴露的参数名不完全一致。vLLM 中常见旋钮是 max_num_batched_tokens;TensorRT-LLM 文档提到启用 enable_chunked_prefill,并要求相关 token 上限与 KV cache block size 对齐。无论名字如何,背后的调参问题都是:每个调度轮次最多允许多少 token 工作量,以及一个长 prefill 被切到多细。
一个实用起点是先按服务目标分组:
| 服务形态 | 初始方向 | 重点观察 |
|---|---|---|
| 聊天、代码助手、Agent 交互 | 较小到中等 budget,优先保护 decode | ITL p95/p99、短请求 TTFT |
| RAG 混合短问答和长上下文 | 中等 budget 起测,按 prompt 长度分桶 | 长请求 TTFT、短请求尾延迟 |
| 离线摘要、批量抽取 | 较大 budget,减少轮次开销 | tokens/s、GPU 利用率、总完成时间 |
| 多租户服务 | budget 与准入控制一起调 | 租户间尾延迟、排队时间、抢占 |
| 调参时不要只改一个参数后看平均值。建议固定模型、并发、prompt 长度分布和输出长度分布,做三到五组 token budget 梯度测试。例如从保守值、中间值、偏大值开始,比较 TTFT p95、ITL p95、吞吐和 KV cache 抢占次数。只有当 p95/p99 不退化或退化可接受时,吞吐收益才算有效。 |
知识点 9:它和其他推理优化的关系

Chunked prefill 不替代其他优化,它通常与它们叠加。
| 优化 | 主要解决什么 | 与 chunked prefill 的关系 |
|---|---|---|
| Continuous batching | 动态加入/移除请求,提高批量利用率 | chunked prefill 让长 prompt 也能以更细粒度加入 |
| PagedAttention / paged KV | 管理 KV cache block,减少碎片和搬移 | chunked prefill 产生的 KV 仍要进入 block 管理 |
| Prefix caching | 复用相同前缀的 KV | 可减少需要 prefill 的 token,但未命中部分仍可 chunk |
| Speculative decoding | 用草稿模型减少主模型 decode 步数 | 主要优化 decode 路径,可与 prefill 调度同时存在 |
| Pipeline parallelism | 把层切到多 GPU | chunked prefill 可让 micro-batch 更均匀,减少 bubble |
| Admission control | 控制进入系统的请求和 KV 预算 | 决定 chunked prefill 是否会导致饥饿或抢占 |
| 这张关系图也提醒一个工程事实:如果 KV cache 池太小、准入控制过松、输出长度极不稳定,单独打开 chunked prefill 不会把系统变稳。它只是提供更好的调度粒度,是否转化为收益取决于整个服务栈。 |
知识点 10:Pipeline Parallel 中为什么能减少 Bubble

在 pipeline parallelism 中,不同 GPU 负责不同层,micro-batch 像流水线一样依次通过各 stage。如果某些 micro-batch 是长 prefill,另一些是轻量 decode,各 stage 的处理时间差异会变大,后面的 stage 可能等待前面的慢 micro-batch,形成 bubble。
SARATHI 的一个重要贡献是把 prefill 切成近似等长 chunk,再用 decode-maximal batching 组织更均匀的批次。这样每个 micro-batch 的计算负载更接近,流水线不需要频繁等待最慢的那一批。论文摘要中报告了在 pipeline parallelism 场景下显著减少 bubble 并提升端到端吞吐的结果;OSDI 2024 的 Sarathi-Serve 进一步把这个方向扩展为低停顿调度,用于缓解 throughput-latency tradeoff。
对生产系统而言,这部分收益通常出现在大模型、多 GPU、长上下文和混合流量同时存在的时候。如果只是单卡小模型、短 prompt、低并发,pipeline bubble 本身不是主矛盾,chunked prefill 的收益会更有限。
知识点 11:上线前后应该看哪些指标

上线 chunked prefill 前,先记录一个无 chunk 或当前默认策略的基线。上线后至少对比以下指标:
| 指标 | 为什么重要 | 典型风险信号 |
|---|---|---|
| TTFT p50/p95/p99 | 衡量 prefill 与排队体验 | 长 prompt p99 变差,短 prompt 被误伤 |
| ITL p50/p95/p99 | 衡量 decode 是否被保护 | p95 ITL 升高说明 chunk 或 batch 太重 |
| tokens/s | 衡量有效吞吐 | 吞吐没升但延迟变差,说明只增加了调度开销 |
| GPU 利用率与显存占用 | 判断 compute/memory 是否更平衡 | 利用率提高但抢占暴增,说明 KV 预算不足 |
| KV cache preemption / recompute | 判断缓存压力 | 抢占频繁会拉高端到端延迟 |
| prefill queue time | 判断 prefill 是否饥饿 | decode 过度优先导致新请求迟迟进不了首 token |
| batch 组成比例 | 解释性能变化 | decode-only 或 prefill-heavy 都可能不是目标状态 |
| 重点是分桶:按 prompt 长度、输出长度、租户、模型、并发水平和是否命中 prefix cache 分开看。平均值经常掩盖真正的问题,因为 chunked prefill 的收益和代价都集中在混合流量与尾部延迟。 |
知识点 12:常见失败模式

第一类失败是 chunk 或 token budget 过大。它看似减少了调度轮次,却把大 prefill 又变回阻塞项,表现为 decode ITL 上升、短请求尾延迟变差。
第二类失败是 chunk 过小。每个长 prompt 需要更多调度轮次才能完成 prefill,首 token 可能变慢,调度开销占比升高。如果服务主要是长文档批处理,这种配置通常不划算。
第三类失败是 decode 优先策略导致 prefill 饥饿。系统一直服务活跃 decode,新请求迟迟无法完成 prefill,TTFT p99 会恶化。这需要准入控制、队列优先级、最大等待时间或分级调度来兜底。
第四类失败是 KV cache 压力被低估。Chunked prefill 降低单轮峰值,不代表总 KV 压力消失。并发过高时仍可能发生抢占、重算、swap 或 OOM。
第五类失败是流量分布漂移。白天短问答多、夜间批量长文档多,最佳 budget 可能不同。固定配置如果没有流量分桶监控,很容易在某个时段失效。
第六类失败是只看平均延迟。Chunked prefill 的目标通常是让系统在高并发混合负载下更平滑,所以 p95/p99、排队时间和拒绝率比均值更能说明问题。
知识点 13:生产落地 Playbook

建议按四步推进,而不是直接在生产全量打开。
- 建立基线:记录当前配置下不同 prompt 长度分桶的 TTFT、ITL、吞吐、GPU 利用率、KV cache 占用、抢占次数和错误率。
- 小范围开启:选择一个中等 token budget 起测,不要一开始追求最大吞吐。确认短请求没有被长请求拖慢。
- 压测混合流量:构造短问答、中等 RAG、长文档、长输出四类请求,按真实比例和峰值比例分别压测。
- 灰度与回滚:先灰度低风险租户或内部流量,设置清晰回滚条件,例如 ITL p99 退化超过阈值、prefill queue time 异常、抢占次数上升或 OOM。
知识点 14:哪些场景最适合用

Chunked prefill 的高收益场景通常有三个特征:长 prompt 足够多、decode 请求足够多、系统存在尾延迟或 GPU 利用率问题。
| 场景 | 适用性 | 原因 |
|---|---|---|
| RAG 问答中混入长文档 | 高 | 长 prefill 与短交互共存,容易互相干扰 |
| 多租户聊天服务 | 高 | 需要保护活跃 decode,不让少量长请求拖慢其他人 |
| 长上下文 Agent | 中高 | 历史消息重放造成大 prefill,但输出也可能很长 |
| 离线批量摘要 | 中 | 可能更重视吞吐,较大 budget 反而更好 |
| 全部都是短 prompt | 低到中 | 长 prefill 阻塞不是主矛盾 |
| 极低并发服务 | 低 | 没有足够 decode 可以混合,收益有限 |
| 如果流量天然可以分池,例如短交互请求和长文档任务可路由到不同实例,分池加独立调参有时比一个池里强行用统一 chunk 策略更稳定。Chunked prefill 是工具,不是替代流量治理的手段。 |
知识点 15:常见误区校正

误区一:开启后所有请求都会变快。
实际情况是,短请求可能因为不再等待长 prefill 而更快,长请求自身 TTFT 可能因为被切成多轮而持平或变慢。目标应是优化业务 SLO 下的整体服务能力。
误区二:它能让任意长上下文都不占显存。
完整前缀的 KV 仍要保存,除非模型或系统另有滑动窗口、KV 压缩、前缀复用或淘汰策略。Chunked prefill 主要改变每轮执行粒度和调度公平性。
误区三:chunk 越小越好。
过小 chunk 会增加调度轮次、拉长长 prompt TTFT,并可能增加 CPU 调度和 kernel launch 相关开销。更小只是在保护 decode 时有价值。
误区四:只要 GPU 利用率升高就是成功。
GPU 利用率升高但 p99 ITL、prefill queue time、抢占或错误率恶化,说明系统只是更忙,不一定更好。
实践检查清单
| 检查项 | 通过标准 |
|---|---|
| 是否有长 prompt 与活跃 decode 混合 | 有真实混合负载,或压测能模拟真实比例 |
| 是否分桶观测 | 至少按 prompt 长度、输出长度、租户或接口类型分桶 |
| 是否设置回滚条件 | 有明确 p95/p99、OOM、抢占、错误率阈值 |
| 是否理解当前框架默认行为 | 确认当前版本是否默认启用、参数是否生效 |
| 是否同时调准入控制 | 不让 chunked prefill 掩盖过量并发导致的 KV 压力 |
| 是否验证业务 SLO | 不只看 tokens/s,也看用户可感知延迟 |
FAQ
Chunked prefill 和 streaming 输出有什么关系?
Streaming 是把 decode 生成的 token 逐步返回给用户;chunked prefill 是服务端内部如何执行输入 prompt 的 prefill。它可能影响用户等到第一个 streamed token 的时间,也可能影响后续 token 间隔,但它不是 streaming 协议本身。
它和 prefix caching 谁更重要?
两者解决的问题不同。Prefix caching 在前缀重复时直接跳过一部分 prefill;chunked prefill 在仍然需要 prefill 时改善调度粒度。RAG 模板、系统提示词、多轮历史高度重复时,prefix caching 可能非常有价值;长且不重复的文档请求仍需要 chunked prefill 这类调度优化。
为什么有时打开后 TTFT 变差?
因为长 prompt 的 prefill 被拆到多个调度轮次中,如果系统还优先服务 decode,长请求要等多个轮次才能完成首 token 所需的全部上下文计算。对交互服务来说,这可能是可接受的,因为它换来了更稳定的 ITL 和更低的短请求尾延迟。
是否应该为所有模型统一一个 chunk size?
不建议。模型大小、attention 后端、GPU 类型、KV cache block size、并发水平、prompt 分布和输出长度都会改变最佳点。同一模型在不同业务接口上也可能需要不同 token budget。
术语表
| 术语 | 含义 |
|---|---|
| Prefill | 处理输入 prompt,建立前缀 KV,并准备生成首 token 的阶段 |
| Decode | 自回归生成阶段,每轮通常为每个活跃请求生成一个 token |
| TTFT | Time To First Token,用户等到第一个输出 token 的时间 |
| ITL | Inter-Token Latency,相邻输出 token 之间的时间 |
| Token budget | 单个调度轮次可处理的最大 token 工作量或近似约束 |
| KV cache | Transformer attention 中保存历史 key/value 的缓存 |
| Preemption | KV 资源不足时抢占部分请求,可能通过重算或换出恢复 |
| Pipeline bubble | 流水线并行中某些 stage 因负载不均而空等的时间 |
总结
Chunked prefill 的本质是把长 prompt 的 prefill 从“不可拆的大块工作”变成“可被调度器分轮安排的小块工作”。它的价值不只是处理长上下文,更在于让 compute-bound prefill 和 memory-bound decode 有机会在同一批次中互补,从而改善混合负载下的 GPU 利用率、ITL 和 tail latency。
但它不是免费午餐。chunk 太大,decode 仍会被拖慢;chunk 太小,长请求 TTFT 和调度开销会变差;KV cache 总压力仍然存在;decode 优先也可能导致 prefill 饥饿。正确使用它的方式是:先确认工作负载确实有长 prefill 与活跃 decode 的混合,再围绕 TTFT、ITL、吞吐、KV cache 和抢占做分桶压测,最后通过灰度和回滚条件把收益锁在真实业务 SLO 内。
参考资料
- https://arxiv.org/abs/2308.16369
- https://www.usenix.org/conference/osdi24/presentation/agrawal
- https://docs.vllm.ai/en/stable/configuration/optimization
- https://github.com/NVIDIA/TensorRT-LLM/blob/main/docs/source/features/long-sequence.md
阅读导航




