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 自适应
关联我的经验
- 动态截断策略 = Continuous Batching 的前身
我在广告系统做的动态截断——根据已召回质量分动态决定是否继续遍历倒排链——和 Continuous Batching 的“完成即踢出、空位即填入”在本质上是同一个算法:
if (当前质量 < 阈值) { 截断 } → if (请求完成) { 踢出 }
while (已召回 < 目标) { 继续遍历 } → while (batch 有空位) { 加入新请求 }
- ADC(Auto Dynamic Configuration)= 自适应 Batch Size
你的广告系统里,根据负载自动调整召回候选数。Continuous Batching 里 batch size 也是动态的——负载高时多攒几个请求一起跑,负载低时来一个处理一个。
- 排队优化经验 = 服务方差管理
在做检索漏斗时你做过一件事:把复杂 query(多词、长 query)和简单 query(单词、高频词)分队列处理,不让慢 query 堵快 query。Continuous Batching 就是在 decode 层面的队列优化。
- 客户沟通语言
“Continuous Batching 让你的 GPU 利用率从 40% 提升到 80%,同等硬件条件下,服务吞吐翻倍,延迟降一半。这相当于用 5 张卡干了原来 10 张卡的活。”
一句话总结本质
Continuous Batching = 动态截断在推理领域的应用:不做固定大小的 batch,让完成的请求立刻退出、新请求立刻加入,GPU 永远不空转。