一、批处理基础概念:从单挑到群殴的艺术

大家好,我是你们的老朋友。今天咱们聊聊批处理——这个看似简单,实则门道极多的概念。

说实话,我刚接触大模型推理时,也觉得批处理不就是把几个请求攒一起处理嘛,有啥好讲的?直到我在生产环境里踩了坑,才发现这里面的水有多深。

1.1 什么是批处理?

批处理,说白了就是「一次处理多个请求」。

想象一下你开了一家奶茶店:

  • 单条处理:来一个客人做一杯,做完再接下一单
  • 批处理:等攒够5个订单,一次性做5杯

在vLLM里,批处理就是把多个用户的推理请求打包,一起喂给GPU计算。这样做的好处很明显——GPU的并行计算能力被充分利用了。

核心原理:GPU擅长并行计算,单条请求往往喂不饱它。批处理相当于把多个「小任务」合并成「大任务」,让GPU满负荷运转。

1.2 动态批处理 vs 静态批处理

这里有个关键区别,我当年可是花了不少时间才搞明白。

静态批处理

静态批处理,就是提前定好批大小,雷打不动。

# 伪代码示例
batch_size = 8  # 固定值
while True:
    requests = collect_n_requests(batch_size)  # 必须凑够8个
    process_batch(requests)

我在早期项目里用过这种方式。优点是实现简单,缺点是——如果请求不够,GPU就在空转;如果请求太多,后面的请求就得排队等着。

我曾经踩过的坑:有一次线上流量波动大,静态批处理导致延迟忽高忽低。高峰期请求积压,低峰期GPU利用率不到30%。

动态批处理

动态批处理就灵活多了。它会根据当前负载,自动调整批大小。

# 伪代码示例
max_batch_size = 8
max_wait_time = 50  # 毫秒

while True:
    requests = collect_requests(max_wait_time)  # 等50ms,能收多少收多少
    if len(requests) > 0:
        process_batch(requests[:max_batch_size])

动态批处理的核心思路是:要么攒够数量,要么等够时间。哪个条件先满足,就立即处理。

我的建议:生产环境首选动态批处理。虽然实现复杂一点,但能同时兼顾吞吐量和延迟。

1.3 批处理大小对吞吐量的影响

吞吐量,就是单位时间内能处理多少请求。批大小和吞吐量的关系,不是简单的线性增长。

我画了一张图,帮你理解这个关系:

批处理大小 1 4 8 16 吞吐量 快速增长区 饱和区 拐点

从图上你能看到:

  • 快速增长区:批大小从1增加到4,吞吐量几乎翻倍。因为GPU终于吃饱了。
  • 饱和区:批大小超过8后,吞吐量增长变缓。GPU的计算资源接近极限。
  • 拐点:每个模型都有一个最佳批大小。超过这个值,收益微乎其微。

实战经验:我测试过一个7B模型,批大小从1提到4,吞吐量提升了3倍;但从8提到16,只提升了20%。所以别盲目追求大batch。

1.4 批处理延迟权衡

吞吐量上去了,延迟怎么办?这是个经典的trade-off。

咱们用表格对比一下:

批大小 单请求延迟 吞吐量 适用场景
1 低(50ms) 低(20 req/s) 实时对话、低延迟要求
4 中(80ms) 中(80 req/s) 一般在线服务
8 高(150ms) 高(120 req/s) 离线批处理、非实时任务
16 很高(280ms) 很高(140 req/s) 后台分析、批量生成

为什么会这样?原因有两点:

  1. 排队延迟:批处理需要等请求凑够,这个等待时间直接加到每个请求上。
  2. 计算延迟:批越大,单次计算耗时越长。虽然平均到每个请求的时间少了,但最慢的那个请求得等所有请求都算完。

注意:别只看平均延迟。在批处理场景下,P99延迟(最慢的1%请求)往往比平均延迟高好几倍。我见过一个案例,平均延迟100ms,但P99延迟飙到了500ms。

1.5 如何找到最佳批大小?

这个问题没有标准答案。我分享一个我常用的方法:

# 最佳批大小搜索脚本(伪代码)
for batch_size in [1, 2, 4, 8, 16, 32]:
    throughput = benchmark(batch_size)
    latency_p99 = benchmark_latency(batch_size)
    
    # 定义你的目标函数
    score = throughput / latency_p99
    
    print(f"批大小 {batch_size}: 吞吐量={throughput}, P99延迟={latency_p99}ms, 得分={score}")

我的经验:对于大多数LLM推理场景,批大小在4-8之间是个不错的起点。如果你的延迟要求高,就选小一点;如果追求吞吐量,就选大一点。但别超过16,除非你的GPU显存特别大。

嗯,批处理的基础概念就聊到这儿。记住一句话:批处理不是越大越好,找到那个平衡点才是关键


专注资料整理