3、批处理基础原理:连续批处理(Continuous Batching)的工作机制、与传统静态批处理的对比

在深度学习推理服务中,GPU 的算力利用率与吞吐量往往受限于请求的到达模式与调度方式。传统静态批处理(Static Batching)将请求收集到固定大小的批次中,待批次填满或超时后才统一执行,这会导致 GPU 在等待期间空转,且批次内所有请求必须等待最慢的一个完成才能释放资源。连续批处理(Continuous Batching,又称 In-flight Batching 或 Iteration-level Scheduling)打破了“请求级”批次的边界,将调度粒度细化到迭代(Iteration)级别,允许 GPU 在每个解码步骤完成后立即释放已完成序列的显存与计算资源,并动态插入新请求。本章将深入剖析连续批处理的工作机制、与传统静态批处理的本质差异,并通过量化对比与案例拆解帮助读者建立可落地的工程认知。

连续批处理 vs 传统静态批处理:调度粒度对比 传统静态批处理(请求级) Batch 1: Req A, Req B, Req C(等待填满) Batch 2: Req D, Req E(等待超时) Batch 3: 执行中,所有请求必须等最慢的完成 时间 GPU 空闲等待批次填满 批次内等待最慢请求 连续批处理(迭代级) Iter 1: A, B, C, D(4个序列并行) Iter 2: A, B, D, E(C完成,E插入) Iter 3: B, D, E, F(A完成,F插入) Iter 4: D, E, F(B完成,无新请求) 核心差异 • 静态批处理:以“请求”为调度单位,批次内所有请求同生共死 • 连续批处理:以“迭代”为调度单位,每个序列独立推进,完成即释放 • 静态批处理:GPU 利用率 = 有效计算时间 / (等待时间 + 计算时间) • 连续批处理: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 判断方法:如何识别系统是否支持连续批处理

在实际工程选型中,判断一个推理框架是否真正实现了连续批处理,可以从以下几个维度进行验证:

  1. 观察调度日志:如果日志中显示“Batch size 动态变化”或“序列完成时立即释放”,则说明支持连续批处理;如果日志显示“等待批次填满”或“批次固定为 N”,则为静态批处理。
  2. 检查 KV Cache 管理方式:连续批处理框架通常使用分页或块式 KV Cache(如 vLLM、TensorRT-LLM),而静态批处理框架往往为每个批次预分配固定大小的连续显存。
  3. 压测并发请求:向服务发送不同到达间隔的请求,观察 GPU 利用率曲线。连续批处理下 GPU 利用率应保持平稳高位,而静态批处理会出现明显的锯齿状波动(等待时利用率下降)。
  4. 查看 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 的算力在等待中被浪费。

实际操作步骤:

  1. 将推理框架迁移至 vLLM,启用连续批处理,设置 --max-num-seqs=16(允许更多并发序列)。
  2. 启用 PagedAttention,设置 --gpu-memory-utilization=0.9,充分利用显存。
  3. 调整调度策略为 --scheduling-policy=fcfs(先到先服务),保证公平性。
  4. 将最大输入长度限制为 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 常见误区与总结复盘

误区一:连续批处理 = 动态批处理(Dynamic Batching)
动态批处理是指将多个请求在时间上聚合后一次性推理,但批次内所有请求仍然同生共死。连续批处理则允许批次内的序列独立完成和退出。两者有本质区别,动态批处理仍然存在长尾阻塞问题。
误区二:连续批处理一定比静态批处理快
连续批处理的主要优势在于消除等待时间,但如果请求到达速率极低(如每 10 秒一个请求),连续批处理与静态批处理都只能以 batch size=1 运行,性能差异不大。此外,连续批处理引入的调度开销和 KV Cache 管理开销在极端小批次下可能超过收益。
经验总结:
  • 连续批处理的核心收益来自“消除等待”和“动态资源回收”,适用于请求到达不均匀、序列长度差异大的场景。
  • KV Cache 的分块大小是重要调优参数,建议根据平均序列长度设置,通常为 16-64 个 token/块。
  • 监控指标应关注“GPU 利用率”和“每迭代平均活跃序列数”,而非简单的 batch size。
  • 在混合负载(短查询 + 长生成)下,连续批处理能天然实现优先级隔离,短查询不会被长查询阻塞。