Chunked Prefill 深度调优:平衡首字延迟与生成吞吐的黄金切片步长
在大促长文本多轮对话、智能客服知识库检索(RAG)以及代码辅助等复杂业务场景中,推理集群经常面临一种极端的“负载撕裂”:
一方面,大量在线交互请求正在进行逐 Token 的流式生成(Decode 阶段),对**序列间延迟(Inter-Token Latency, ITL)**有着严苛的平稳性要求(如波动不得超过 30ms);
另一方面,突发的长 Prompt 请求(如 4K ~ 16K Tokens)不定期涌入,如果系统采用传统的整包 Prefill 策略,GPU 会在长达 150~300ms 内全量投入该长文本的矩阵乘计算,导致所有处于 Decode 阶段的请求发生剧烈的“停顿等待(Hiccup)”,用户端打字机效果严重卡死。
**切片预填充(Chunked Prefill)**技术的出现,彻底重塑了大模型调度器的执行范式。它将一个庞大的 Prompt 按照固定步长切分为多个 Chunk,与正在运行的 Decode 请求无缝交错执行。本文深入微架构计算开销,探讨如何调优切片步长以达成吞吐与平稳性的极致平衡。
传统 Prefill 阻塞 vs Chunked Prefill 交错调度对比: 1. 传统无切片调度 (Decode 被动遭长 Prefill 阻断): Step 1 (40ms) : [ Decode 128 seqs ] Step 2 (220ms!) : [ Long Prefill 8192 Tokens 独占 GPU ! 造成严重 ITL 顿挫 ] Step 3 (40ms) : [ Decode 128 seqs (恢复生成) ] 2. Chunked Prefill 调度 (固定 Chunk 步长交错流水): Step 1 (45ms) : [ Decode 128 seqs ] + [ Chunk 1 (1024 Tokens) ] Step 2 (46ms) : [ Decode 128 seqs ] + [ Chunk 2 (1024 Tokens) ] Step 3 (45ms) : [ Decode 128 seqs ] + [ Chunk 3 (1024 Tokens) ] Step 4 (45ms) : [ Decode 128 seqs ] + [ Chunk 4 (1024 Tokens) -> 完成 Prefill ]切片预填充的微观算力与访存权衡
Chunked Prefill 的核心原理在于通过算力与访存的算子融合,填补 GPU 的空闲执行单元:
- Decode 序列天然是访存受限(Memory-Bound):计算小、读取大,GPU 的 Tensor Core 计算单元利用率通常低于 25%;
- Prefill 切片是计算密集型(Compute-Bound):将一个 1024 Tokens 的 Chunk 塞入同一个 Batch 中,能够将矩阵乘维度拉大,将 Tensor Core 利用率瞬间拉升至 80% 以上;
- 步长选择的博弈:
- 步长过小(如 256):矩阵乘计算粒度不够,GPU 计算核心未充分预热,算子启动(Kernel Launch)与小 GEMM 的固定开销占比过高,导致整体 Prefill 总耗时被拉长;
- 步长过大(如 4096):单步计算时间过长,Decode 序列的 ITL 延迟毛刺重新显现,切片失去平滑抖动的意义。
实测对账矩阵(8 卡 H100 SXM5 80GB,70B 模型在 512 并发下不同 Chunk Size 对比)
在 512 并发背景流量下,针对 8192 Tokens 长 Prompt 输入,对比不同 Chunk Size 的性能指标:
| Chunk 切片步长 (Tokens) | 单步 Step 平均耗时 | Decode P99 ITL 抖动 | 8K Prompt 首字耗时 (TTFT) | 单卡总吞吐 (Tokens/s) | GPU 算力利用率 (MFU) |
|---|---|---|---|---|---|
| 未开启切片 (Baseline) | 35ms (Decode) / 280ms (Prefill) | 285.0 ms (严重顿挫) | 280 ms | 1,820 | 54.2% |
| Chunk Size = 256 | 38.5 ms | 18.2 ms (极度平稳) | 1,230 ms (过慢) | 1,980 | 61.5% |
| Chunk Size = 512 | 41.0 ms | 21.5 ms | 650 ms | 2,340 | 72.8% |
| Chunk Size = 1024 (黄金平衡) | 44.5 ms | 25.0 ms (完全达标) | 360 ms | 2,680 (+47.2%) | 83.5% (算力吃满) |
| Chunk Size = 2048 | 58.0 ms | 48.5 ms | 290 ms | 2,710 | 84.2% |
实测数据显示,Chunk Size = 1024在保持 P99 ITL 低于 25ms 平稳红线的同时,将整机吞吐提升了 47.2%,首字延迟(360ms)亦处于完全可接受范围。
切片注意力(Chunked Attention)状态维护与 KV Cache 写入
在实现 Chunked Prefill 时,模型并非每次切片都从头计算,而是必须维护连续的 KV Cache 链条:
# Chunked Prefill 调度与分步前向核心逻辑示意 class ChunkedPrefillRunner: def __init__(self, model_runner, chunk_size=1024): self.runner = model_runner self.chunk_size = chunk_size def step_chunked_forward(self, active_decode_reqs, pending_prefill_req): # 1. 提取当前处于 Decode 阶段的 Tokens decode_tokens = [req.get_last_token() for req in active_decode_reqs] # 2. 截取新请求的一个 Chunk prefill_chunk_tokens = [] is_last_chunk = False if pending_prefill_req is not None: prefill_chunk_tokens = pending_prefill_req.fetch_next_chunk(self.chunk_size) is_last_chunk = pending_prefill_req.is_all_chunks_consumed() # 3. 拼接混合 Batch mixed_batch_tokens = decode_tokens + prefill_chunk_tokens # 4. 执行混合注意力 Kernel (Decode 读取历史 KV, Chunk 执行 Causal Attention 并追加 KV) output_logits = self.runner.execute_mixed_step( mixed_batch_tokens, decode_count=len(decode_tokens), prefill_chunk_size=len(prefill_chunk_tokens) ) # 5. 更新请求状态 if is_last_chunk: pending_prefill_req.transition_to_decode_phase() return output_logits大促生产级参数配置建议
在 vLLM 与 SGLang 生产部署中,启用并固化以下参数:
# vLLM 启用 Chunked Prefill 配置 python3 -m vllm.entrypoints.openai.api_server \ --model /models/Meta-Llama-3-70B-Instruct \ --tensor-parallel-size 8 \ --enable-chunked-prefill true \ --max-num-batched-tokens 8192 \ --max-num-seqs 512 \ --gpu-memory-utilization 0.95# SGLang 启用切片配置 python3 -m sglang.launch_server \ --model-path /models/Meta-Llama-3-70B-Instruct \ --tp 8 \ --chunked-prefill-size 1024 \ --max-running-requests 512 \ --port 30000通过将切片大小精准锁定在 1024 Tokens,推理引擎得以在大促高并发的复杂混合场景中,彻底消灭长文本请求引发的延迟顿挫,让在线生成流如丝般顺滑。