4、动态Batch队列:固定Batch vs 动态Batch、动态Batch的实现原理、动态Batch的适用场景
说到Batch队列,很多同学第一反应就是「设个32或者64,跑就完了」。嗯,我刚开始做推理优化时也是这么想的。直到有一次线上服务,输入请求的文本长度从几十到几千不等,固定Batch直接让显存炸了。那次事故之后,我才认真研究起动态Batch来。
说白了,固定Batch就像你开一辆固定座位的公交车——不管乘客多少,座位数不变。人少了浪费,人多了装不下。而动态Batch呢,更像一辆能根据乘客数量自动伸缩的魔法巴士。
固定Batch vs 动态Batch
先看看固定Batch的问题在哪。我习惯用一个简单的对比来说明:
| 对比维度 | 固定Batch | 动态Batch |
|---|---|---|
| 显存占用 | 预先分配固定大小,容易浪费或溢出 | 按需分配,利用率高 |
| 吞吐量 | 短文本时浪费算力,长文本时可能OOM | 长短文本混合时吞吐更稳定 |
| 实现复杂度 | 简单,直接设置batch_size即可 | 需要队列管理和动态调度逻辑 |
| 延迟抖动 | 相对稳定,但长文本请求会拖慢整个batch | 短文本请求响应更快,长文本不会阻塞 |
我在项目中遇到过这样一个场景:一个文本分类服务,输入长度从10个token到2000个token不等。固定Batch设为32时,短文本的GPU利用率只有不到30%,而长文本一来就直接OOM。后来改成动态Batch,同样的硬件,吞吐量提升了将近3倍。
核心差异:固定Batch是「请求等Batch」,动态Batch是「Batch等请求」。前者以固定大小凑满一批再处理,后者以时间窗口或请求数量为触发条件,灵活组合。
动态Batch的实现原理
动态Batch的实现,说白了就是一个「请求排队 + 智能打包」的过程。我画了一张流程图,帮你理清核心逻辑:
具体实现上,我一般用两个核心参数来控制:
- 最大等待时间(max_wait_ms):比如50ms,超过这个时间不管凑没凑满都开始推理
- 最大token数(max_tokens):比如4096,防止单个batch显存溢出
代码实现其实不复杂,我贴一个简化版的调度逻辑:
class DynamicBatchQueue:
def __init__(self, max_wait_ms=50, max_tokens=4096):
self.queue = []
self.max_wait_ms = max_wait_ms
self.max_tokens = max_tokens
self.last_process_time = time.time()
def add_request(self, request):
self.queue.append(request)
# 检查是否满足触发条件
if self._should_process():
self._process_batch()
def _should_process(self):
# 条件1:队列中的总token数超过阈值
total_tokens = sum(r.token_len for r in self.queue)
if total_tokens >= self.max_tokens:
return True
# 条件2:等待时间超过最大等待时间
if time.time() - self.last_process_time >= self.max_wait_ms / 1000:
return True
return False
def _process_batch(self):
# 按token长度排序,短请求优先处理
self.queue.sort(key=lambda r: r.token_len)
# 打包成batch,注意padding对齐
batch = self._pack_batch(self.queue)
# 执行推理
results = model.infer(batch)
# 分发结果
self._dispatch_results(results)
self.queue = []
self.last_process_time = time.time()
我的经验:排序这一步很关键。把短请求放在前面处理,能显著降低平均响应时间。我曾经在一个翻译服务中测试过,排序后P99延迟降低了40%。
动态Batch的适用场景
不是所有场景都适合动态Batch。我总结了几类典型场景:
- 文本长度差异大的场景:比如客服对话、文档摘要、代码补全。输入长度从几个字到几千字不等,动态Batch能最大化GPU利用率。
- 延迟敏感型服务:比如在线翻译、实时语音识别。短请求不需要等长请求处理完,响应更快。
- 混合负载场景:同时有批处理和流式请求。动态Batch能自动适配不同的负载模式。
但有些场景就不太适合:
- 所有请求长度都差不多:比如固定尺寸的图像分类,固定Batch反而更高效。
- 对延迟极度敏感:比如自动驾驶中的实时推理,动态Batch的排队逻辑可能引入额外延迟。
- 硬件资源极度受限:比如嵌入式设备,动态调度的开销可能得不偿失。
避坑指南:我曾经在一个OCR服务中强行上动态Batch,结果因为图像预处理时间远大于推理时间,动态调度的开销反而拖慢了整体性能。后来改成固定Batch + 异步预处理,效果才好起来。所以,先分析瓶颈在哪,再决定要不要用动态Batch。
嗯,动态Batch的核心思想其实就一句话:让Batch大小适应数据,而不是让数据适应Batch。你想想看,这跟我们在生活中处理任务是一样的——灵活应对,才能事半功倍。
想要更多资料,联系可可