03. 什么是 Continuous Batching / 动态 Batch?它为什么重要?
整理 Continuous Batching 的原理、价值与调度方式。
简单回答
Continuous Batching(连续批处理)是一种动态调度策略:不像静态 Batching 那样等一批请求全部完成才开始下一批,而是每当有请求完成生成就立刻释放资源并插入新请求。传统静态 Batching 会被"最慢的请求"拖累——一批中最长的输出还没生成完,其他已完成的位置就空着浪费。Continuous Batching 大幅提升了 GPU 利用率和吞吐量,是现代推理框架(vLLM、TGI)的标配功能。
详细解释
静态 Batching 的问题
在最朴素的 batch 推理中,系统把 N 个请求凑成一个 batch,同时做 Prefill,然后同步做 Decode——所有请求每一步同时生成一个 token,直到所有请求都生成完毕(或达到最大长度),才开始下一个 batch。
问题出在 Decode 阶段不同请求的输出长度差异很大。一个 batch 里,有的请求可能只需要生成 20 个 token 就够了,有的可能要生成 500 个 token。在静态 Batching 下,那个只需要 20 个 token 的请求在第 20 步就可以结束了,但它必须等到整个 batch 中最慢的请求(500 个 token)完成后才能释放。这期间这个位置的 GPU 计算资源和 KV Cache 显存都被白白浪费了。
如果 batch 中最短的输出是 20 token、最长的是 500 token,那个短请求的 GPU 利用率只有 20/500 = 4%。整个 batch 的平均利用率取决于输出长度的分布,通常远低于 100%。
Continuous Batching 怎么做
Continuous Batching 的思路很直接:不要等整个 batch 结束,而是在每一步(或每几步)检查有没有已完成的请求。一旦某个请求完成了生成(遇到了 EOS token 或达到了最大长度),立刻把它从 batch 中移出、释放 KV Cache 显存,同时从等待队列中拉入一个新请求填补空位。
这样 GPU 的每一个"槽位"始终都在处理活跃的请求,不会有空闲。吞吐量可以提升 2~10 倍,具体取决于输出长度的方差——方差越大,Continuous Batching 的收益越大。
Iteration-level Scheduling
更精细的调度方式是 iteration-level scheduling——在每一个 Decode 步都做调度决策,而不是等几步再检查。每一步结束后,系统检查哪些请求完成了、哪些新请求可以加入、当前显存是否够。
这种调度需要处理一个技术细节:新加入的请求需要先做 Prefill(计算输入 Prompt 的 KV Cache),已有的请求在做 Decode(每步只生成一个 token)。两者的计算模式不同(Prefill 是计算密集型、Decode 是访存密集型),混在一起调度需要仔细处理。
vLLM 的做法是在每一步中,先对新请求做 Prefill(可能只做部分 Prefill——chunked prefill),然后和其他请求一起做 Decode。这种混合调度能进一步提升吞吐量。
和 Dynamic Batching 的区别
Dynamic Batching 有时候指的是"攒一小段时间的请求凑成一个 batch"(比如等 50ms,把这段时间内到达的请求凑成一个 batch 一起处理),本质上还是静态 Batching,只是 batch 的大小是动态的。
Continuous Batching 是完全不同的概念——它的核心是在生成过程中动态地增删 batch 中的请求。两者不要混淆。
对延迟和吞吐的影响
Continuous Batching 主要提升的是吞吐量(单位时间处理的请求数),对单个请求的延迟影响比较小。因为每个请求的 Decode 过程本身没有变(还是逐 token 生成),变的是 GPU 的利用率——空闲的资源被分配给了新请求。
在某些情况下,Continuous Batching 甚至可能略微增加单个请求的延迟——因为 batch 中请求更多了,每步的计算量稍大。但综合来看,吞吐量的提升远大于延迟的微小增加。
面试时可以这样答
Continuous Batching 是指在生成过程中动态地插入和移出请求。传统静态 Batching 等一整批全部生成完才开始下一批,导致先完成的请求白白占着 GPU 资源等最慢的那个——如果一批里输出长度差异大,利用率会很低。
Continuous Batching 在每一步检查有没有完成的请求,完成了就移出释放资源,同时从队列拉入新请求。GPU 的每个位置始终在处理活跃的请求,吞吐量能提升 2 到 10 倍。
工程上的核心挑战是调度——新加入的请求需要做 Prefill,已有的请求在做 Decode,两者计算模式不同,需要混合调度。vLLM 的 chunked prefill 就是处理这个问题的。
Continuous Batching 现在是所有主流推理框架的标配。如果还在用静态 Batching,吞吐量上基本没法和用了 Continuous Batching 的系统比。
常见追问
- Continuous Batching 下新请求的 Prefill 和已有请求的 Decode 怎么混合调度?
- Batch 大小有没有上限?受什么约束?
- 如果所有请求的输出长度差不多,Continuous Batching 还有收益吗?