连续批处理策略(Continuous Batching)

连续批处理策略(Continuous Batching)
Chase Woo连续批处理策略(Continuous Batching)
在调用本地昇腾部署的大模型时,观察到一个很有意思的现象:在执行高并发问答时,所有的回答都趋于同一个短暂的时间窗口完成,即使有不同的首字延迟时间。
关键词: 批处理、大语言模型、本地部署
1 | 2025-10-10 11:04:08 [INFO] LLM图像分析455成功响应,用时 86.08秒 | 首字延迟: 63.554秒 | 输入tokens: 872 | 输出tokens: 8 | 总tokens: 880 | tokens/秒: 0.09 |
一、批处理策略
LLM推理服务器(尤其是使用vLLM、TensorRT-LLM等框架)通常会采用 连续批处理(Continuous Batching) 策略:
1. 时间轴
1 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ |
虽然首字返回时间不同,但服务器会:
- 将同时到达的请求组成一个批次
- 在GPU上并行处理这个批次
- 批次内的请求会相互等待,直到整批完成
总时间 = 首字延迟 + 生成时间,这也就意味着部分请求可能已经很早就完成了,但是需要等待其他请求。
💡部分请求的首字token仅需不到1秒,但是后续需要时间过长,且总时间与其他请求相差不多
1 | 请求2: 首字 0.870秒 + 后续 10.48秒 ≈ 11.35秒总时间 |
2. 服务器调度策略
可能使用了公平调度或批次同步机制:
- 保证同一批次的请求不会因为完成早就立即返回
- 而是等待批次内所有请求都处理完
- 这样可以优化GPU利用率和简化调度逻辑
3. 为什么不是”快的先完成”?
虽然是异步流式响应,但服务器端的批处理机制决定了:
- 即使客户端异步接收
- 服务器端仍然是批量同步推理
- 这导致同一批次的请求几乎同时完成
二、测试
1 | # 在循环开始时记录每个chunk到达的时间 |
1. Chunk 完全交错到达(最重要的证据)
看第一批请求(115-128)的chunk顺序:
1 | 时间戳 1760063687.956-958: |
这说明服务器并非逐个处理请求,而是:
- 在GPU批次中并行生成所有请求的token
- 每生成一轮,就轮流返回每个请求的chunk
- 类似”时间片轮转”的调度方式
2. 首字延迟差异巨大,但总时间趋同
| 请求ID | 首字延迟 | 总时间 | 首字后耗时 |
|---|---|---|---|
| 129 | 0.913秒 | 5.70秒 | 4.79秒 |
| 131 | 3.676秒 | 5.68秒 | 2.00秒 |
| 140 | 5.396秒 | 5.60秒 | 0.20秒 |
| 142 | 5.374秒 | 5.58秒 | 0.21秒 |
公式:首字延迟 + 首字后耗时 ≈ 恒定值(~5.6秒)
3. 批处理的三个阶段
阶段1:等待队列调度(首字延迟)
1 | 1760063688.79秒 ← 请求129, 135先进入 |
阶段2:并行推理(chunk交错返回)
1 | 1760063693.46-52秒: 所有16个请求的chunk交错返回 |
每轮间隔约50毫秒,说明GPU批次推理速度很快
阶段3:同步完成
1 | 所有请求在 10:34:53 的同一秒内完成 |
三、结论
1. 服务器采用了 Continuous Batching + 同步返回策略
1 | # 伪代码:服务器端推理逻辑 |
2. 这样设计的优势
- GPU利用率最大化:所有请求并行推理,充分利用GPU并行能力
- 吞吐量最优:批处理比逐个处理快得多
- 调度简化:批次内同步,避免频繁的上下文切换
3. 代价
快的请求需要等待慢的请求,所以即使首字延迟只有0.9秒,也要等到5.7秒才能完成。
总结
观察到的现象:首字快的应该先完成,但实际没有。
原因:服务器端批处理策略强制同步了所有请求,为了最大化吞吐量,牺牲了单个请求的延迟优化。这是典型的 吞吐量优先(Throughput-oriented) 而非 延迟优先(Latency-oriented) 的设计。
如果想要”快的先完成”,需要服务器配置改为更小的batch size或禁用批处理,但这会大幅降低整体吞吐量。





