背景问题:长上下文请求为什么会拖慢在线推理
大模型在线推理通常分成两个阶段:prefill 和 decode。prefill 阶段一次性处理输入 tokens,生成后续解码需要的 KV Cache;decode 阶段则按 token 逐步生成输出。两者使用 GPU 的方式并不相同——prefill 更偏 compute-bound,容易吃满矩阵计算单元;decode 更偏 memory-bound,每一步只处理新 token,但要读取不断增长的 KV Cache。
在短 prompt 场景里,这个差异不明显。一旦进入长上下文、Agent 工具链、RAG 拼接、多轮对话压缩不充分的场景,prefill 会突然变成在线服务的主要扰动源。一个几万 token 的请求进入队列后,如果调度器把它作为完整 prefill 一次性执行,正在 decode 的请求可能被迫等待——用户看到的不是平均吞吐下降,而是 inter-token latency(ITL) 抖动、流式输出卡顿、尾延迟升高。
传统静态批处理的问题更明显:一个 batch 中部分请求已经结束,仍可能被长请求拖住;新请求也要等当前 batch 完成后才能进入。Orca 在 OSDI 2022 中提出 iteration-level scheduling,把调度粒度从请求级降到迭代级,使完成的请求可以及时返回,新请求可以在下一轮迭代进入 batch。这是今天 continuous batching 的基础思想之一。
但 continuous batching 本身还不够。它解决的是「请求如何动态进出 batch」,没有完全解决「一个超长 prefill 是否会在某次迭代中占用过多预算」。这就是 Chunked Prefill 要处理的问题。
核心原理:把长 prefill 切小,让 decode 不被饿死
Continuous Batching 解决批次空洞
Continuous batching 的核心是:不要等整个 batch 全部结束后再换下一批,而是在每个生成迭代后重新检查队列。某个请求完成,就释放它的位置;队列中有新请求,就尽快加入。Hugging Face TGI 文档也把 continuous batching 与 streaming 作为生产推理引擎的核心能力,强调动态组合 in-flight requests 并通过 SSE 流式返回 tokens。
它主要解决两个问题:
- 短输出请求不再被长输出请求拖住。
- GPU batch 尽量保持填充状态,减少静态批处理中的空洞。
不过,continuous batching 面对长上下文输入时仍会遇到一个尖锐问题:如果某个请求的 prefill 很大,它在某一轮中占用过多 token budget,decode 请求虽然理论上可以插入,但实际仍可能被 prefill 拉慢。
Chunked Prefill 解决长输入阻塞
Chunked Prefill 的做法是把一个大 prefill 拆成多个小块执行。vLLM 文档明确说明,chunked prefill 会把 large prefills 切成 smaller chunks,并与 decode requests 一起 batch,从而在 compute-bound 的 prefill 和 memory-bound 的 decode 之间取得更好的平衡。
在 vLLM V1 中,chunked prefill 在可能时默认启用;调度策略会优先处理 pending decode requests,然后再把剩余的 max_num_batched_tokens 预算分配给 prefill。如果某个 prefill 放不进预算,系统会自动把它切块。
可以把它理解成一个简单调度策略:
while serving:
batch = []
# 1. 先保护正在流式输出的 decode 请求
batch.extend(pending_decode_requests)
# 2. 再用剩余 token budget 填充 prefill 工作
remaining = max_num_batched_tokens - tokens(batch)
while remaining > 0 and has_pending_prefill():
chunk = next_prefill_chunk(max_tokens=remaining)
batch.append(chunk)
remaining -= tokens(chunk)
run_one_iteration(batch)
这个策略的关键不是「让 prefill 更快」,而是让 prefill 不再以完整长请求的形式堵塞调度循环。decode 请求可以更频繁地拿到执行机会,流式输出也更稳定。
为什么 Chunk Size 是核心旋钮
Chunked prefill 的核心调参项通常是 token budget 或 chunk size。在 vLLM 中,一个关键参数是 max_num_batched_tokens。文档给出的方向很清楚:
| 策略 | 优势 | 代价 |
|---|---|---|
| 较小 chunk | 改善 ITL,prefill 对 decode 的干扰更少 | prefill 迭代次数增加,TTFT 可能变差 |
| 较大 chunk | 降低 TTFT,单次 batch 处理更多 prefill | 可能阻塞 decode,影响尾延迟 |
NVIDIA TensorRT-LLM 的 chunked prefill 技术博客也指出,较大的 chunk size 可以减少处理 prefill 序列所需迭代次数,从而降低 TTFT;但它也会增加正在进行的 decode 阶段完成时间,影响 query completion time 和输出 TPS。因此,chunk size 不是越大越好,也不是越小越好,而是一个由业务目标决定的折中。
工程落地:从指标、参数到部署策略
先拆分服务目标
上线前不要只看 tokens/s。至少要拆成四类指标:
- TTFT(Time to First Token):用户等待第一个 token 的时间,主要受 prefill、排队、调度策略影响。
- ITL(Inter-Token Latency):流式输出期间相邻 token 的间隔,直接影响「是否卡顿」。
- End-to-end Latency:完整响应完成时间,受输出长度、decode 速度和排队影响。
- Goodput:在满足 SLO 的前提下真正完成的请求吞吐,而不是无约束压测中的最大吞吐。
很多推理优化在低并发压测中表现很好,但进入混合 workload 后会暴露问题。比如 RAG 请求、普通聊天请求、Agent 长轨迹请求混在同一实例中时,平均 tokens/s 可能仍然不错,但 P95/P99 ITL 已经不可接受。
再确定调参顺序
推荐按以下顺序推进:
1. 固定模型、量化方式、并发压测脚本
2. 先开启 continuous batching / 默认调度能力
3. 再调 max_num_batched_tokens 或等价 token budget
4. 分别观察 TTFT、ITL、P95/P99 latency、OOM/preemption
5. 最后再叠加 prefix caching、KV offloading、P/D disaggregation
在 vLLM 中,可以通过类似方式指定 token budget:
from vllm import LLM
llm = LLM(
model="meta-llama/Llama-3.1-8B-Instruct",
max_num_batched_tokens=8192,
)
这个值不是通用答案。小模型、大显存、短输出场景可以尝试更大的 token budget;长上下文、多租户、强流式交互场景应更关注 ITL 与尾延迟,通常需要更保守的 chunk 配置。
观察 Preemption 和队列积压
Chunked prefill 不是显存无限扩容。长上下文请求仍会消耗 KV Cache,输出长度不确定仍可能导致峰值显存超出预估。vLLM 文档在优化章节中也提醒可以通过 Prometheus metrics 监控 preemption requests。上线时应同时观察:
- prefill queue 长度是否持续增长
- decode queue 是否被长 prefill 间歇性阻塞
- preemption 是否增加
- KV Cache 使用率是否长期接近上限
- P99 ITL 是否比 P50 ITL 高出过多
如果 preemption 频繁出现,说明问题可能已经不是单纯 chunk size 调参,而是并发上限、上下文长度、KV Cache 策略、实例隔离需要重新设计。
适用场景
适合使用 Chunked Prefill 的场景
- 长上下文但仍需要流式体验:例如 RAG、代码仓库问答、文档分析、Agent 任务回放。用户不只关心最终完成时间,也关心输出是否连续。
- 混合请求长度明显:短聊天和长 prompt 共用一组 GPU。如果没有 chunked prefill,长请求容易制造尾延迟。
- 单实例内还没有必要做 P/D 解耦:当业务规模还没有复杂到需要独立 prefill 集群和 decode 集群时,chunked prefill 是较轻量的治理手段。
不适合只靠 Chunked Prefill 的场景
- 极长上下文占比很高:如果大部分请求都是几十万 token 级别,单实例调度很难兼顾 TTFT 与 ITL,应评估上下文压缩、检索裁剪、KV Cache 分层、P/D 解耦。
- 严格低延迟对话服务:如果业务要求每个 token 都要非常稳定,且 P99 ITL 是硬指标,单纯调 chunk size 可能不够,需要请求分级、实例隔离或 decode 优先队列。
- 离线批处理:如果不需要流式输出,也不关心 TTFT,过度追求 chunked prefill 反而可能增加调度复杂度。离线任务更关注整体吞吐和资源占用。
常见误区
误区一:Continuous Batching 开了就万事大吉
Continuous batching 只解决动态进出 batch 的问题。长 prefill 如果不切块,仍可能在某次迭代中压住 decode。对长上下文服务而言,continuous batching 与 chunked prefill 应该一起看。
误区二:只看吞吐,不看 ITL
用户感知最强的不是 tokens/s,而是流式输出是否停顿。一个系统可能在 tokens/s 上表现很好,但输出过程中频繁停顿,用户仍会认为服务很慢。
误区三:把 Chunk Size 当成静态最优值
Chunk size 与模型大小、GPU、并发、输入长度分布、输出长度分布都相关。NVIDIA 的资料也提到 动态 chunk sizing 的价值:开发者手动寻找平衡点并不轻松,动态策略可以根据 GPU utilization 等信息给出更合适的配置方向。
误区四:忽略业务入口的上下文治理
推理层只能缓解问题,不能替代上游治理。RAG 拼接过多、工具轨迹无限增长、系统提示词重复注入,都会把压力转嫁给 prefill。Chunked prefill 不是纵容无限上下文的理由。
上线检查清单
压测数据准备
上线前至少构造三类 workload:
- 短输入短输出:模拟普通聊天。
- 长输入短输出:模拟 RAG、文档问答、代码检索。
- 长输入长输出:模拟报告生成、Agent 总结、复杂推理。
不要只压单一输入长度。真实线上最危险的是混合分布,尤其是少量长 prompt 对大量短请求的干扰。
指标看板
建议把这些指标放到同一个看板:
- QPS / active requests / waiting requests
- TTFT P50/P95/P99
- ITL P50/P95/P99
- output tokens/s 与 request/s
- GPU SM utilization、HBM bandwidth、KV Cache 使用率
- preemption 次数、OOM 次数、请求取消次数
- 不同输入长度 bucket 的延迟分布
回滚条件
建议提前定义回滚条件,而不是线上再讨论:
- P99 ITL 超过目标阈值并持续 10 分钟
- preemption 明显增加并伴随 TTFT 上升
- 长上下文请求导致短请求 P95 latency 翻倍
- GPU 利用率上升但 goodput 下降
- 错误率、取消率、超时率高于基线
与其他技术的关系
Chunked Prefill 与 PagedAttention:两者解决的问题不同。PagedAttention 更偏 KV Cache 的显存管理与块级调度,减少碎片和提升并发能力;Chunked Prefill 更偏调度层,把长 prefill 拆成更小执行单位,避免阻塞 decode。生产中通常会叠加使用。
何时考虑 P/D 解耦:当少量长上下文请求持续影响短请求体验时,应考虑按输入长度、用户等级或任务类型做路由隔离。Chunked prefill 可以缓解干扰,但不是所有混合 workload 都适合同池运行。
参考资料
- vLLM Documentation: Optimization and Tuning - Chunked Prefill
- NVIDIA Technical Blog: Streamlining AI Inference Performance and Deployment with NVIDIA TensorRT-LLM Chunked Prefill
- TensorRT-LLM Documentation
- USENIX OSDI 2022: Orca - A Distributed Serving System for Transformer-Based Generative Models
- Hugging Face Inference Endpoints: Text Generation Inference
- arXiv: Slice-Level Scheduling for High Throughput and Load Balanced LLM Serving
- arXiv: AlignedServe: Orchestrating Prefix-aware Batching
- arXiv: Past-Future Scheduler for LLM Serving under SLA Guarantees