3、批处理基础原理:连续批处理(Continuous Batching)的工作机制、与传统静态批处理的对比
在深度学习推理服务中,GPU 的算力利用率与吞吐量往往受限于请求的到达模式与调度方式。传统静态批处理(Static Batching)将请求收集到固定大小的批次中,待批次填满或超时后才统一执行,这会导致 GPU 在等待期间空转,且批次内所有请求必须等待最慢的一个完成才能释放资源。连续批处理(Continuous Batching,又称 In-flight Batching 或 Iteration-level Scheduling)打破了“请求级”批次的边界,将调度粒度细化到迭代(Iteration)级别,允许 GPU 在每个解码步骤完成后立即释放已完成序列的显存与计算资源,并动态插入新请求。本章将深入剖析连续批处理的工作机制、与传统静态批处理的本质差异,并通过量化对比与案例拆解帮助读者建立可落地的工程认知。
3.1 核心原理:从请求级调度到迭代级调度
连续批处理的核心思想源于 Orca 论文(2022)提出的迭代级调度(Iteration-level Scheduling)。在自回归解码过程中,每个请求的生成过程被拆解为多个前向传播迭代(即一次 Transformer 层的完整计算)。传统静态批处理在请求粒度上分配 GPU 资源,而连续批处理在每次迭代结束后检查所有序列的状态:如果一个序列已经生成结束标记(EOS)或达到最大长度,立即将其从批次中移除,释放其 KV Cache 和计算槽位;同时,调度器可以从等待队列中选取新的请求加入当前批次,参与下一轮迭代。
这种机制的关键在于 KV Cache 的动态管理。每个序列在解码过程中都会维护独立的 Key-Value 缓存,用于避免重复计算历史 token 的注意力。连续批处理要求 KV Cache 能够按序列动态分配和回收,通常采用分块(Paged)管理方式(如 vLLM 的 PagedAttention),将 KV Cache 划分为固定大小的块,按需分配给序列,从而避免显存碎片化并支持任意数量的并发序列。
- 调度粒度:以一次前向传播迭代为最小调度单位,而非整个请求。
- 动态插入:每轮迭代结束后,调度器检查等待队列,将新请求加入下一轮迭代。
- 提前释放:序列生成 EOS 或达到 max_tokens 时,立即释放其 KV Cache 与计算槽位。
- 显存管理:采用 PagedAttention 或类似机制,按块分配 KV Cache,支持高并发下的显存复用。
3.2 判断方法:如何识别系统是否支持连续批处理
在实际工程选型中,判断一个推理框架是否真正实现了连续批处理,可以从以下几个维度进行验证:
- 观察调度日志:如果日志中显示“Batch size 动态变化”或“序列完成时立即释放”,则说明支持连续批处理;如果日志显示“等待批次填满”或“批次固定为 N”,则为静态批处理。
- 检查 KV Cache 管理方式:连续批处理框架通常使用分页或块式 KV Cache(如 vLLM、TensorRT-LLM),而静态批处理框架往往为每个批次预分配固定大小的连续显存。
- 压测并发请求:向服务发送不同到达间隔的请求,观察 GPU 利用率曲线。连续批处理下 GPU 利用率应保持平稳高位,而静态批处理会出现明显的锯齿状波动(等待时利用率下降)。
- 查看 API 参数:部分框架(如 vLLM)提供
--max-num-seqs参数控制最大并发序列数,而静态批处理框架通常只有--max-batch-size参数。
| 判断维度 | 静态批处理 | 连续批处理 |
|---|---|---|
| 调度单位 | 请求(整个序列) | 迭代(单次前向传播) |
| 批次大小 | 固定,需等待填满 | 动态变化,每轮迭代可增删 |
| KV Cache 管理 | 预分配连续显存 | 分页/块式动态分配 |
| GPU 利用率 | 锯齿波动,有等待空隙 | 平稳高位,等待被消除 |
| 尾部延迟 | 受最慢请求影响 | 独立推进,互不阻塞 |
3.3 案例拆解:在线聊天服务的吞吐量优化实战
某互联网公司部署了一款基于 Llama-2-7B 的在线客服机器人,使用传统静态批处理框架,配置 max_batch_size=8,平均请求延迟为 1.2 秒,GPU 利用率仅为 45%。用户请求到达时间服从泊松分布,平均间隔 300ms,但存在明显的突发流量(峰值时每秒 20 个请求)。
初始现象与目标:在突发流量下,大量请求排队等待批次填满,平均排队时间达到 800ms,用户体验下降。目标是将 GPU 利用率提升至 80% 以上,并将 p95 延迟控制在 2 秒以内。
分析判断过程:通过监控发现,GPU 利用率曲线呈明显锯齿状——批次填满时利用率飙升至 90%,但批次执行完毕后,GPU 空闲等待下一批请求,利用率骤降至 20%。这说明静态批处理无法应对稀疏到达模式,GPU 的算力在等待中被浪费。
实际操作步骤:
- 将推理框架迁移至 vLLM,启用连续批处理,设置
--max-num-seqs=16(允许更多并发序列)。 - 启用 PagedAttention,设置
--gpu-memory-utilization=0.9,充分利用显存。 - 调整调度策略为
--scheduling-policy=fcfs(先到先服务),保证公平性。 - 将最大输入长度限制为 1024 tokens,输出长度限制为 256 tokens,避免长序列占用过多 KV Cache。
结果对比:迁移后,GPU 利用率从 45% 提升至 82%,平均延迟从 1.2 秒降至 0.6 秒,p95 延迟从 2.8 秒降至 1.5 秒。在突发流量下,排队时间从 800ms 降至 50ms 以内。吞吐量从每秒 6.7 个请求提升至每秒 15.2 个请求,提升约 127%。
风险与代价:连续批处理对显存管理要求更高,PagedAttention 的块大小需要调优(默认 16 个 token/块)。块过小会导致频繁的显存分配开销,块过大会造成内部碎片。此外,动态批次大小可能导致某些迭代的计算形状不规则,需要框架层面对 GPU kernel 进行优化。
复盘经验:连续批处理并非万能,对于请求到达非常均匀且批次始终能填满的场景,静态批处理与连续批处理性能差异不大。但在真实互联网流量下,请求到达天然具有突发性和稀疏性,连续批处理能显著提升资源利用率。建议在部署前使用真实流量回放进行压测,找到最优的 max-num-seqs 与 KV Cache 块大小。
3.4 常见误区与总结复盘
动态批处理是指将多个请求在时间上聚合后一次性推理,但批次内所有请求仍然同生共死。连续批处理则允许批次内的序列独立完成和退出。两者有本质区别,动态批处理仍然存在长尾阻塞问题。
连续批处理的主要优势在于消除等待时间,但如果请求到达速率极低(如每 10 秒一个请求),连续批处理与静态批处理都只能以 batch size=1 运行,性能差异不大。此外,连续批处理引入的调度开销和 KV Cache 管理开销在极端小批次下可能超过收益。
- 连续批处理的核心收益来自“消除等待”和“动态资源回收”,适用于请求到达不均匀、序列长度差异大的场景。
- KV Cache 的分块大小是重要调优参数,建议根据平均序列长度设置,通常为 16-64 个 token/块。
- 监控指标应关注“GPU 利用率”和“每迭代平均活跃序列数”,而非简单的 batch size。
- 在混合负载(短查询 + 长生成)下,连续批处理能天然实现优先级隔离,短查询不会被长查询阻塞。