一、动态批处理概述

什么是动态批处理

动态批处理,说白了就是让推理引擎自己决定「什么时候攒够一批请求,什么时候开始干活」。

我刚开始接触这个概念时,觉得它挺反直觉的。你想啊,传统的批处理都是等数据凑够了再统一处理,但动态批处理不一样——它有个时间窗口,到了时间窗口还没凑够,也得开工。

举个例子。假设你的推理服务每秒能处理 100 个请求。如果采用动态批处理,系统会设置一个最大延迟时间,比如 50 毫秒。在这 50 毫秒内,来的请求会被收集起来,组成一个 batch。50 毫秒一到,不管来了 1 个请求还是 100 个请求,都得开始推理。

核心要点:动态批处理 = 时间窗口 + 最大 batch 大小,两者谁先触发,谁说了算。

为什么需要动态批处理

这个问题,我当年在做一个推荐系统时深有体会。那时候用的是静态批处理,请求量一波动,系统就崩。为什么?

原因有三:

  • 请求到达不均匀——你想想看,用户访问是有高峰低谷的。凌晨 3 点可能就几个请求,晚上 8 点可能几千个。静态批处理在这种场景下,要么等太久,要么浪费资源。
  • GPU 利用率太低——我记得有一次排查线上问题,发现 GPU 利用率只有 15%。为什么?因为静态批处理要求 batch 必须凑满 64 个请求才推理,但实际流量根本撑不起来。
  • 延迟和吞吐的平衡——说白了,静态批处理是「要么延迟高,要么吞吐低」。动态批处理给了你一个调节旋钮,可以按业务需求来权衡。

我的经验:我曾经在一个广告推荐项目里,把静态批处理改成动态批处理,GPU 利用率从 20% 直接飙到 75%,P99 延迟反而降了 30%。这就是动态批处理的魅力。

动态批处理 vs 静态批处理

咱们直接上对比,这样更直观:

对比维度 静态批处理 动态批处理
batch 大小 固定,比如 32、64 可变,1 到最大 batch 之间
触发时机 凑满固定数量 时间窗口或凑满最大 batch
延迟表现 低流量时延迟高 低流量时延迟可控
吞吐表现 高流量时吞吐稳定 高流量时吞吐接近静态
资源利用率 低流量时浪费严重 自适应,利用率更高
实现复杂度 简单 中等,需要调度器

嗯,这里要注意一点。静态批处理并不是一无是处。如果你的请求量非常稳定,比如每秒 1000 个请求,那静态批处理反而更简单、更高效。动态批处理适合的是「流量波动大」的场景。

避坑指南:我曾经犯过一个错误——把动态批处理的时间窗口设得太短。结果呢?每个 batch 就 1-2 个请求,GPU 利用率反而比静态批处理还低。后来我把窗口从 10ms 调到 50ms,效果才出来。

动态批处理的核心逻辑

我画了一张流程图,帮你理解动态批处理的决策过程:

请求到达 在窗口内? 加入等待队列 超时或满? 执行推理 入口 判断 队列 触发条件 推理

这张图其实就讲了三件事:

  1. 请求来了先判断——当前是否在时间窗口内?在的话就进队列等着。
  2. 触发条件有两个——要么时间窗口到了,要么 batch 满了。谁先到就听谁的。
  3. 执行推理——把队列里的请求打包成一个 batch,送进模型推理。

一个小技巧:实际项目中,我习惯把时间窗口设成 P99 延迟的 1/3 左右。比如业务要求 P99 延迟 150ms,那窗口就设 50ms。这样即使最坏情况,加上推理时间也不会超。

动态批处理的实现思路

最后,我简单说下实现思路。其实核心就两个组件:

  • 请求收集器——负责接收请求,维护一个等待队列。我一般用 Go 的 channel 或者 Java 的 BlockingQueue 来实现。
  • 调度器——负责决定什么时候触发推理。这里需要一个定时器,配合 batch 大小计数器。

伪代码大概长这样:

// 伪代码示例
class DynamicBatcher:
    max_batch_size = 64
    window_ms = 50
    
    queue = []
    timer = Timer(window_ms, flush)
    
    def add_request(req):
        queue.append(req)
        if len(queue) >= max_batch_size:
            flush()
    
    def flush():
        if queue is empty:
            return
        batch = queue.copy()
        queue.clear()
        model.infer(batch)

嗯,代码看着简单,但实际坑不少。比如并发安全、超时处理、backpressure 等等。这些后面章节会细讲。

总结一下:动态批处理就是「等一会儿,但不等太久」。它让推理服务在延迟和吞吐之间找到了一个动态平衡点。说白了,就是让机器适应人,而不是让人适应机器。


专注资料整理