Continuous Batching:推理吞吐的翻倍密码

是什么:Continuous Batching 的定义

Step 1:没有 Batching —— 一次只处理一个

时间轴 →
请求A: [P▁▁▁▁D▁D▁D▁D▁D▁D▁D▁D]
请求B:                         [P▁▁▁▁D▁D▁D▁D▁D]
请求C:                                              [P▁▁▁▁D▁D▁D]

GPU 有大把时间闲着。因为推理 token-by-token 是串行的,处理完 A 的 10 个 token 才能开始 B。

浪费程度:GPU 利用率可能只有 20-30%。

Step 2:Static Batching —— 凑一拨一起处理

时间轴 →
Batch[ABC]: [P▁▁▁▁▁▁▁▁D▁D▁D▁D▁D▁D▁D▁D▁D▁D▁D▁D▁D] ← A 早完了,等 BC

                                           B 和 C 还在 decode,A 占着坑位等

Static Batching 把 A、B、C 凑成一个 batch,一起做 Prefill,然后一起做 Decode。

致命问题:所有请求必须等最慢的那个完成。A 只生成 3 个 token 就结束了,但 B 要生成 20 个——A 的 slot 被白白占着,新的 D 请求进不来。

这就像你的广告检索中——按固定 K 截断,K 设成 1000,但有的 query 前 200 个就够了,剩下 800 个位置被白白遍历

Step 3:Continuous Batching —— 随进随出

时间轴 →
Step 1: Batch[A,B,C] → [P▁▁▁▁D▁D▁D▁D▁D▁D▁...]
Step 2: A 完成了 → 立刻踢出,D 立刻加入
Step 3: Batch[B,C,D] → [...▁D▁D▁D▁D▁D▁...]
Step 4: B 完成了 → 踢出,E 加入
Step 5: Batch[C,D,E] → [...▁D▁D▁D▁...]

每个 decode step 之后,检查哪些请求已完成,立刻踢出,空位立刻给新请求。


为什么需要 Continuous Batching?

三个核心指标:

1. 吞吐量 (Throughput):tokens/秒

Static Batching:
  A(3 tokens) + B(20 tokens) + C(15 tokens)
  Batch 等待最慢的 B → 总共处理 20 步 decode
  → GPU 在 A 完成后的 17 步里,有效计算只有 BC 两个请求

Continuous Batching:
  A 完成(3步) → 踢出 → D 加入(17步)
  → GPU 始终保持 3 个请求在运行
  → 吞吐量翻倍

2. 延迟 (Latency):每个 token 的生成时间

Continuous Batching 不会增加单 token 延迟(因为 batch 大小基本恒定),但会显著降低首 token 延迟——新请求不需要等上一个 batch 全部完成就能加入。

3. 利用率 (Utilization):GPU 计算单元不空转

Static Batching 的 GPU 利用率 时间线:
  [████████░░░░░░░░░░░░░░░░]  ← 高 → 低 → 等 batch 结束

Continuous Batching 的 GPU 利用率 时间线:
  [████████████████████████]  ← 持续高,因为永远有新请求进来

广告系统类比

Static Batching    = 按固定 K 截断(K=1000,不管需要多少)
Continuous Batching = 动态截断(实时判断"够了就停,不够继续")

你的截断策略从 5% → 0.19% 的背后思想,就是 Continuous Batching 的精髓:
不让低效的等待占据宝贵的计算资源。

去掉 Continuous Batching 的代价

假设一个典型服务场景:

  • 平均输入长度:500 tokens
  • 平均输出长度:200 tokens(但方差极大:最短 10,最长 2000)
  • 并发请求数:50

Static Batching 的问题

Batch 1: [请求1(10 tokens), 请求2(50 tokens), ..., 请求8(2000 tokens)]

          等 2000 tokens 的请求完成 → 整个 batch 被拖累
          请求1 在 10 tokens 后完成 → 继续占坑 1990 步
          
总吞吐:受限于最慢请求的长度方差

Continuous Batching 的改善

请求1 完成(10步) → 立刻踢出 → 请求9 立即加入
请求2 完成(50步) → 立刻踢出 → 请求10 立即加入
...

每条请求的槽位利用率:接近 100%
总吞吐:翻倍甚至更高

数值上:从实际线上数据来看,从 Static 切到 Continuous Batching,吞吐量通常提升 1.5-3x,首 token 延迟降低 50%+。


覆盖多少场景?

场景 输出长度方差 Continuous Batching 收益
对话机器人 中(50-300 tokens) 大,方差适中
代码生成 高(10-2000 tokens) 最大,方差极大
翻译 低(输出≈输入长度) 小,但仍有改善
客服问答
长文档摘要 中,但配合 PD 分离效果更好

判断标准:如果输出长度方差 > 3x(最短和最长差 3 倍以上),Continuous Batching 收益超过 50% 吞吐提升。


不变的本质

Continuous Batching 背后三条元原理:

1. 动态调度 > 固定批处理

固定大小的 batch 永远无法达到最优——总有空闲容量被浪费。动态调度让资源利用率逼近上限。

和你的广告截断策略一样:不是所有请求都需要遍历 1000 条倒排链——动态判断“够不够”,够了就停,不够就继续。

2. 减少排队延迟 = 减少“等”的时间

排队理论的核心公式:

平均排队时间 = λ × σ² / 2

λ = 到达率
σ² = 服务时间的方差

服务时间方差越大,排队时间越久。Continuous Batching 通过让短请求不被长请求阻塞,等价于降低了服务时间的感知方差

3. 计算资源 = 流式处理管道

GPU 是一条流水线。理想的流水线永远不应该有空泡(bubble)。Continuous Batching 就是流水线的气泡消除器。


元结构映射

广告检索系统                        推理服务
────────                          ──────
请求排队                           →      请求队列
固定截断(K=1000)                  →      Static Batching
动态截断(质量分下降就停)           →      Continuous Batching
新 query 到达即处理                 →      新请求在下一步加入 batch
召回完成的 query 直接出队            →      完成的请求立刻从 batch 踢出
截断率 5% → 0.19% 的优化            →      吞吐 1.5-3x 提升
延迟 P97 下降 21ms                 →      首 token 延迟降 50%+
动态资源扩缩                        →      Batch size 自适应

关联我的经验

  1. 动态截断策略 = Continuous Batching 的前身

我在广告系统做的动态截断——根据已召回质量分动态决定是否继续遍历倒排链——和 Continuous Batching 的“完成即踢出、空位即填入”在本质上是同一个算法:

if (当前质量 < 阈值) { 截断 }     →    if (请求完成) { 踢出 }
while (已召回 < 目标) { 继续遍历 }  →    while (batch 有空位) { 加入新请求 }
  1. ADC(Auto Dynamic Configuration)= 自适应 Batch Size

你的广告系统里,根据负载自动调整召回候选数。Continuous Batching 里 batch size 也是动态的——负载高时多攒几个请求一起跑,负载低时来一个处理一个。

  1. 排队优化经验 = 服务方差管理

在做检索漏斗时你做过一件事:把复杂 query(多词、长 query)和简单 query(单词、高频词)分队列处理,不让慢 query 堵快 query。Continuous Batching 就是在 decode 层面的队列优化

  1. 客户沟通语言

“Continuous Batching 让你的 GPU 利用率从 40% 提升到 80%,同等硬件条件下,服务吞吐翻倍,延迟降一半。这相当于用 5 张卡干了原来 10 张卡的活。”


一句话总结本质

Continuous Batching = 动态截断在推理领域的应用:不做固定大小的 batch,让完成的请求立刻退出、新请求立刻加入,GPU 永远不空转。