3. 动态批处理概念

3.1 动态批处理定义

动态批处理(Dynamic Batching)是 TensorRT-LLM 推理服务中用于提升 GPU 吞吐量的核心调度机制。其基本思想是:不等待单个请求完整执行完毕,而是在每个解码步骤(decode step)结束时,将当前处于"可执行状态"的多个请求动态组合成一个批次,送入 GPU 执行一次前向传播。每个请求在批次中的位置、批次的大小、甚至批次内请求的组成,都会随着解码进度实时变化。

与传统的"请求级批处理"不同,动态批处理在token 级(迭代级)进行调度。一个请求完成生成后,其占用的显存槽位立即被新到达的请求接管,GPU 无需等待整个批次的所有序列都结束。下图展示了动态批处理的核心调度循环:

请求队列 Req A (prompt=128) Req B (prompt=256) Req C (prompt=64) Req D (prompt=512) Req E (prompt=32) 新请求 动态调度器 每步检查完成状态 回收槽位 填充新请求 形成新批次 组装批次 当前批次(迭代 t) 槽位 0 Req A · 已生成 45/128 tokens 槽位 1 Req C · 已生成 12/64 tokens 槽位 2 Req E · 已生成 3/32 tokens 空闲槽位(等待新请求) 请求完成 → 释放槽位 → 加入新请求 动态批处理:每个解码迭代重新组合批次,GPU 永不因单个请求的结束而空闲

上图中,请求 A、C、E 正在 GPU 上并行解码,请求 B、D 在队列中等待。当请求 E 在下一迭代完成生成后,其槽位会被立即分配给请求 B,整个过程对用户完全透明。

3.2 与静态批处理的区别

静态批处理(Static Batching)是动态批处理出现之前的主流方案,也是许多初学者容易混淆的概念。两者的本质区别在于批次的粒度和槽位释放的时机。下表从多个维度进行了对比:

对比维度 静态批处理 动态批处理
批次组成时机 请求到达时一次性组批,批次固定 每个解码迭代动态组批
最短执行单元 整个请求(完整生成序列) 单个解码步骤(一次前向传播)
槽位释放 批次内所有请求完成后统一释放 单个请求完成后立即释放并复用
GPU 空闲率 高(批次尾部效应严重) 低(持续保持高利用率)
对长尾请求的容忍度 差,一个超长请求拖慢整个批次 好,长请求独立推进不影响他人
调度开销 低(仅组批一次) 较高(每步需检查状态)
典型适用场景 离线批量推理、长度均匀的任务 在线流式服务、长度高度不均

用一个具体案例来说明:假设静态批次中有 4 个请求,生成长度分别为 10、50、100、200 tokens。当最短的请求(10 tokens)完成时,它必须继续占用 GPU 资源等待其他 3 个请求完成,因为整个批次共享一次前向传播。这导致 GPU 计算量中约 30%~50% 是无效的 padding 计算。而动态批处理中,10-token 的请求完成后立即退出,其槽位马上被新请求填充,GPU 始终在处理"有效"的 token。

3.3 核心优势

动态批处理带来的收益在真实负载下非常显著,主要体现在以下四个方面:

  • 吞吐量提升 2~5 倍:GPU 的算力被持续利用,消除了批次尾部的等待空洞。在 TensorRT-LLM 的官方 benchmark 中,动态批处理相比静态批处理在混合长度负载下吞吐提升可达 3.2 倍。
  • 降低平均延迟:新请求无需等待当前批次完全结束,只要有一个空闲槽位即可开始推理。在负载 50% 时,平均排队延迟可降低 60% 以上。
  • 天然适配流式输出:动态批处理与 token 流式生成(streaming)完美契合——每个请求的 token 是逐步产生的,调度器可以在每个 token 之间插入其他请求的计算。
  • 资源利用率与服务质量解耦:通过控制最大批次大小(max_batch_size),可以在吞吐和延迟之间灵活权衡,而不影响已运行请求的稳定性。

实际收益参考(TensorRT-LLM 在 A100 上的测试数据):

负载类型: 在线流式, 输入长度 50~800 tokens, 输出长度 20~200 tokens
静态批处理: 吞吐 850 tokens/s, 平均延迟 420 ms, GPU 利用率 61%
动态批处理: 吞吐 2,340 tokens/s, 平均延迟 180 ms, GPU 利用率 89%
提升幅度:   吞吐 ×2.75, 延迟 ↓57%, 利用率 ↑28 个百分点

3.4 挑战与代价

动态批处理并非银弹,它在工程实现上引入了新的复杂性,需要在实际部署中仔细权衡:

  • KV Cache 内存碎片化:每个请求占用的 KV Cache 大小随生成长度动态增长。频繁的槽位释放与分配会导致显存碎片,TensorRT-LLM 通过 paged KV cache(类似虚拟内存的分页机制)来缓解,但需要额外配置 page 大小和最大 token 数。
  • 调度器开销:每个解码迭代都需要执行一次调度决策——检查完成状态、回收槽位、从队列取新请求、更新注意力掩码。当 batch size 很大(如 256)时,调度本身可能消耗 5%~10% 的 GPU 时间。
  • 长请求饥饿风险:如果持续有新请求到达,调度器可能倾向于填充新请求而让长请求"插队",导致个别请求迟迟无法完成。需要设置合理的最大等待时间或优先级策略。
  • 不确定性增加:由于批次组成动态变化,单次请求的完成时间波动比静态批处理更大,对延迟敏感型业务需要配合超时和重试机制。

3.5 常见误区与复盘

在实际使用 TensorRT-LLM 动态批处理时,开发者常陷入以下认知误区,这里逐一澄清:

误区 1:动态批处理 = 增大 batch size

动态批处理的核心是"动态重组",而非单纯增大批次。即使 batch size 保持 8 不变,动态调度也能通过及时替换已完成请求来提升效率。盲目增大 max_batch_size 会导致显存溢出或延迟恶化。

误区 2:动态批处理会自动解决所有并发问题

动态批处理只负责 GPU 端的调度,不涉及请求分发、负载均衡或超时管理。如果上游服务没有合理的并发控制,大量请求堆积在队列中,动态批处理反而会加剧排队延迟。

误区 3:所有模型都适合动态批处理

动态批处理对解码阶段收益最大。对于纯预填充(prefill)任务(如分类、embedding),请求长度固定且计算密集,静态批处理反而更高效。TensorRT-LLM 允许对 prefill 和 decode 阶段分别配置不同的批处理策略。

复盘建议:在部署动态批处理时,建议先通过 trtllm-bench 工具分别测量静态和动态模式下的吞吐-延迟曲线,找到适合自身负载的切换点。同时监控 KV Cache 利用率(而非仅看显存总量),当利用率低于 60% 时,优先检查是否存在槽位碎片或调度器配置过小的问题。