4、动态批处理基础:静态批处理 vs 动态批处理、请求队列与调度、批处理窗口概念

好,咱们今天聊点实在的。动态批处理,说白了就是让GPU别闲着。

我记得刚入行那会儿,做推理服务总是被老板骂:「你这GPU利用率怎么才20%?」那时候我用的还是静态批处理,一个batch凑不够人,GPU就干等着。后来接触了vLLM的动态批处理,才明白什么叫「让每一滴算力都发光」。

4.1 静态批处理 vs 动态批处理

先说说静态批处理。这玩意儿就像绿皮火车——发车时间固定,人不够也得走,人多了也装不下。

静态批处理的工作方式:

  • 请求先攒着,攒够N个才一起处理
  • 所有请求必须等最慢的那个跑完
  • GPU显存一次性分配,用完再释放

我在项目中遇到过最坑的情况:静态批处理设了batch size=8,结果来了7个请求,最后一个等了500ms才到。这500ms里GPU在干嘛?在摸鱼。你说气不气人?

动态批处理就不一样了。它像地铁——随到随走,随时有人下车,随时有人上车。

  • 请求来了就处理,不用等凑够数
  • 每个请求独立推进,谁先跑完谁先走
  • GPU显存动态分配,用完的请求立刻释放空间给新请求

核心区别一句话:静态批处理是「人等车」,动态批处理是「车等人」。

你想想看,大模型推理最怕什么?怕长尾请求。一个请求生成了1024个token,另一个只生成10个。静态批处理里,那个10token的请求得眼巴巴等着1024token的跑完。动态批处理呢?10token的跑完直接走人,GPU立刻接新活。

4.2 请求队列与调度

动态批处理的核心,其实是两个东西:请求队列调度器

请求队列,说白了就是一个「待办清单」。每个请求进来,先排进队列。调度器呢,就是那个「项目经理」,决定谁先上GPU干活。

调度策略我见过三种:

策略 原理 适用场景
FIFO(先来先服务) 谁先到谁先跑 请求量均匀,无优先级需求
最短请求优先 预估生成token少的先跑 延迟敏感,长尾请求多
优先级队列 高优请求插队 付费用户、实时交互场景

我曾经踩过一个坑:用FIFO调度,结果一个超长请求堵住了后面所有短请求。那叫一个惨,用户反馈说「你们这服务是不是卡死了?」后来我改成了最短请求优先,配合动态批处理,延迟直接降了40%。

嗯,这里要注意:调度器不能太复杂。调度本身也要消耗CPU时间,如果调度开销比推理开销还大,那就得不偿失了。vLLM默认用的是FIFO + 饥饿避免机制,我个人觉得够用。

小技巧:如果你的业务有明确的SLA(比如99%的请求要在200ms内返回),建议用优先级队列。把高优请求标记出来,调度器优先处理它们。但别忘了给低优请求一个「保底机制」,别让它们永远排不上队。

4.3 批处理窗口概念

批处理窗口,这个词听着玄乎,其实很简单——就是「调度器多久看一次队列」。

我习惯把批处理窗口比作「公交车发车间隔」。窗口太小,调度器频繁检查队列,CPU开销大;窗口太大,请求在队列里等太久,延迟飙升。

窗口大小怎么定? 我总结了一个经验公式:

窗口大小 ≈ 平均推理延迟 × 0.1 ~ 0.3

举个例子:你的模型平均推理延迟是100ms,那窗口设在10ms~30ms比较合适。太小了(比如1ms),调度器忙死;太大了(比如100ms),请求排队时间等于推理时间,用户体验直接腰斩。

我在项目中遇到过这么个事:一开始窗口设了50ms,结果并发一上来,请求排队时间比推理时间还长。后来我把窗口调到20ms,配合动态批处理的「即时插入」机制,排队时间降到了5ms以下。

避坑指南:我曾经把窗口设成0ms,以为「实时调度」最好。结果CPU被打满,推理反而变慢了。记住:调度不是免费的,窗口太小会引发「调度风暴」。

另外,批处理窗口还有一个隐藏作用:控制batch size的波动。窗口内到达的请求数,决定了当前batch的大小。窗口越大,batch size越可能变大,GPU利用率高,但延迟也高。窗口越小,batch size稳定,延迟低,但GPU可能吃不饱。

说白了,这就是一个「利用率 vs 延迟」的权衡。没有标准答案,得根据你的业务场景来调。

4.4 核心逻辑流程图

下面这张图,是我自己画的核心流程。你看一遍就能明白动态批处理到底在干嘛。

动态批处理核心流程 用户请求 请求队列 FIFO / 优先级 / 最短优先 调度器 批处理窗口: 20ms 动态批处理引擎 即时插入 / 请求独立推进 GPU 推理执行 输出结果 请求完成,释放显存

这张图里,我特别想强调那个反馈回路。动态批处理之所以「动态」,就是因为GPU跑完一个请求后,会立刻通知调度器:「我空出来了,赶紧塞新活进来。」这个机制,是静态批处理做不到的。

好了,关于动态批处理的基础,咱们就聊到这儿。记住三个关键词:不等、不浪费、不堵车。下一节咱们会深入聊vLLM的具体实现,到时候我会拿源码出来讲。

一句话总结:动态批处理 = 请求队列 + 智能调度 + 即时插入。核心目标就一个——让GPU永远在干活,别闲着。

专注资料整理