文章

Continuous Batching 生产实战:用动态批处理提升 LLM Serving 吞吐

本文系统讲解 Continuous Batching 如何通过迭代级调度提升 LLM 推理吞吐,覆盖静态批处理痛点、调度机制、KV Cache 约束、关键参数配置、监控指标与上线误区,适合推理服务优化与容量规划参考。

背景问题:LLM Serving 的瓶颈不只是模型慢

大模型在线推理看似只是一个简单接口——用户发起请求,服务端返回 token。但在生产环境里,真正消耗成本的是 GPU 计算周期、KV Cache 显存、请求排队时间和长尾输出长度 之间的组合问题。

传统推理服务习惯沿用常规深度学习推理的批处理方式:先收集一批请求组成 batch,然后一起执行。这个方法在图像分类、Embedding 或固定长度模型里很有效,因为每个样本的计算形状相对稳定。但文本生成是 自回归解码——每个请求的输入长度不同,输出长度也不同,有的请求几步就结束,有的会持续生成几百个 token。

如果服务端仍按”整批请求一起开始、一起等待”的方式工作,GPU 会被两类浪费拖垮:

  • 短请求提前完成后,batch 槽位不能立刻被新请求使用;
  • 为对齐不同序列长度,系统可能处理大量 padding 或等待最慢请求。

Continuous Batching 的价值就在这里:它把调度粒度从”请求级”下沉到”解码迭代级”,让服务端在每一轮生成时重新整理正在运行的请求。

核心原理:从 Request-Level Scheduling 到 Iteration-Level Scheduling

Continuous Batching 也常被称为 iteration-level schedulingin-flight batching。ORCA 论文把这个问题讲得很清楚:生成式 Transformer 的每一步解码都是一次迭代,调度器不必把一个请求从开始到结束都固定在同一个 batch 里,而是可以在每个迭代边界重新决定当前 batch 由哪些请求组成。

简化后的执行流程如下:

  1. 请求进入队列,先执行 prefill,生成首个 KV Cache;
  2. 调度器选择一批可运行请求进入 decode batch;
  3. 模型执行一次 decode forward pass,通常生成每个序列的下一个 token;
  4. 已完成请求退出,释放对应 KV Cache 和 batch 槽位;
  5. 新请求或等待中的请求补位进入下一轮 decode;
  6. 重复步骤 3–5,直到请求结束或达到限制。

这个机制的核心不是”把 batch_size 调大”,而是 让 batch 在运行过程中持续变化。系统不再等待一整批请求全部结束,而是在每个 token 生成周期上做调度。这样 GPU 更少处于空转状态,请求也不必等待一个固定 batch 生命周期结束后才能进入执行阶段。

Hugging Face Text Generation Inference 明确把 continuous batching 列为提升总吞吐的核心能力;TensorRT-LLM 则使用 in-flight batching 这个名称,强调动态管理 context 与 generation 阶段,以提高 GPU 利用率并降低排队等待。

工程落地:不能只打开一个开关

在 vLLM、TGI、TensorRT-LLM 这类推理服务里,Continuous Batching 通常已被内置,但生产落地仍然要围绕几个控制面做配置。

1. 区分 Prefill 和 Decode

Prefill 阶段处理完整 prompt,计算量通常和输入长度相关;decode 阶段每轮生成少量 token,但会反复执行。Continuous Batching 最直接优化的是 decode 阶段的槽位利用率,但 prefill 太重时仍然会压垮 TTFT。

线上不能只看 tokens/s,还要拆开看:

指标构成
TTFTqueue waiting + prefill scheduling + first token generation
TPOTdecode scheduling + model forward + sampling + streaming overhead

如果长 prompt 很多,单纯提升 decode batch 效率并不能解决首 token 慢的问题,可能还需要:

  • Prefix cache 加速 prefill;
  • Prefill/Decode 分离部署;
  • 请求限流或 prompt 长度分级。

2. 控制最大并发序列数,而不是盲目调大 Batch

Continuous Batching 会让更多请求同时驻留在 GPU 上,但每个请求都需要 KV Cache。并发序列数越高,显存压力越大。生产参数通常要围绕以下指标做压测:

serving_tuning:
  max_running_requests: 64
  max_total_tokens: 32768
  max_input_tokens: 8192
  max_output_tokens: 1024
  admission_control: true
  overload_policy: "queue_or_reject"

这段配置不是某个框架的固定格式,而是容量规划思路:限制正在运行的请求数、限制总 token 预算、限制单请求最大输入输出长度,并定义过载策略

3. 和流式输出配合

Continuous Batching 常和 streaming 一起使用。流式输出可以降低用户感知等待时间,但也会引入网络 flush、连接管理和客户端消费速度差异。服务端需要防止慢客户端拖住资源,常见做法包括:

  • 输出缓冲上限;
  • 连接超时;
  • 取消请求回收;
  • 客户端断开后的 KV Cache 释放。

适用场景:高并发、长输出、GPU 利用率不足

Continuous Batching 适合以下场景:

  • 多用户共享同一模型服务,QPS 波动明显;
  • 输出长度差异大,静态 batch 容易被长请求拖住;
  • GPU 利用率不稳定,但请求排队时间较高;
  • 在线 Chat、代码补全、Agent 后端、批量内容生成等持续生成 token 的业务;
  • 已经使用 vLLM、TGI、TensorRT-LLM 等支持动态调度的推理框架。

它不适合被理解为”所有延迟问题的通用解”。如果系统瓶颈是网络传输、上游业务串行调用、模型加载慢、CPU tokenizer 饱和,或者请求量太低导致 GPU 本来就很空,Continuous Batching 的收益会变小。

常见误区

误区一:吞吐提升就等于用户体验提升

吞吐提升通常意味着单位 GPU 可以处理更多 token,但用户体验更依赖 TTFT、TPOT 和尾延迟。如果为追求吞吐把并发序列数推得过高,短请求可能排队更久,P95/P99 延迟反而变差。

误区二:Batch 越大越好

更大的 batch 可以提高 GPU 利用率,但也会增加显存占用、调度复杂度和单轮迭代时间。最优点通常来自压测,而不是经验值。需要按模型大小、prompt 长度、输出长度、GPU 型号和业务 SLO 做组合测试。

误区三:忽略 KV Cache 的显存增长

Continuous Batching 让更多请求同时运行,KV Cache 占用会随着输入长度和已生成 token 增长。没有 admission control 的系统很容易在流量高峰触发 OOM,导致服务整体抖动。

误区四:只看平均值

LLM Serving 的问题经常藏在长尾里。平均 tokens/s 好看,不代表用户请求稳定。上线前必须看 P95/P99 TTFT、TPOT、排队时间和取消率。

上线检查清单

容量与压测

  • 使用真实 prompt 长度分布,而不是只用短 prompt;
  • 覆盖短输出、长输出、混合输出三类请求;
  • 分别测试低 QPS、中等 QPS、接近饱和 QPS;
  • 记录最大可持续 tokens/s,而不是只看峰值;
  • 单独测试流式输出和非流式输出。

关键监控指标

指标含义
request_queue_time_seconds请求排队等待时间
llm_ttft_seconds首 token 延迟
llm_tpot_seconds每输出 token 平均延迟
running_requests正在运行的请求数
waiting_requests等待中的请求数
gpu_utilizationGPU 利用率
kv_cache_usage_ratioKV Cache 使用率
prefill_tokens_per_secondPrefill 阶段吞吐
decode_tokens_per_secondDecode 阶段吞吐
request_abort_total取消请求总数
request_oom_totalOOM 次数

其中 KV Cache 使用率waiting_requests 特别重要。前者决定服务是否接近显存风险,后者反映调度器是否已经进入拥塞状态。

过载保护

  • 限制最大输入 token;
  • 限制最大输出 token;
  • 为不同业务设置不同优先级或队列;
  • 对超长请求单独路由;
  • 超过阈值时拒绝请求,而不是让所有请求一起变慢;
  • 客户端断开后立即取消生成并释放资源。

选型建议

如果目标是快速上线通用 LLM 服务,vLLM 和 TGI 都是常见选择。TGI 强调 continuous batching、streaming、Prometheus metrics 和 OpenTelemetry tracing;vLLM 生态则围绕 PagedAttention、调度、KV Cache 管理和高吞吐服务持续迭代。TensorRT-LLM 更偏向 NVIDIA GPU 上的深度优化,in-flight batching、paged attention、量化和多 GPU 推理能力更适合对性能有强要求的部署。

实际选型时,不建议只问”哪个框架最快”。更合理的问题是:

  1. 我的业务更看重 TTFT 还是总吞吐?
  2. 请求长度分布是否稳定?
  3. 是否需要 OpenAI-compatible API?
  4. 是否需要多 GPU / 多节点?
  5. 团队能否维护 TensorRT-LLM 这类更底层的优化栈?
  6. 是否已经有监控体系接入 Prometheus / OpenTelemetry?

常见问题 (FAQ)

Continuous Batching 会改变模型输出吗?

通常不会。它改变的是服务端调度方式,不改变模型权重和采样逻辑。但如果上线时同时调整了采样参数、最大输出长度、停止条件或框架版本,输出仍可能出现差异,所以灰度验证仍然必要。

它和 PagedAttention 是什么关系?

两者解决的问题不同但经常一起出现。Continuous Batching 解决请求调度与 GPU 利用率问题;PagedAttention 解决 KV Cache 显存管理问题。没有高效 KV Cache 管理,持续增加运行中请求会更容易遇到显存瓶颈。

小流量服务是否需要 Continuous Batching?

低 QPS 场景收益有限,因为 GPU 本来就没有足够请求可批处理。但保留这个能力仍有价值:当流量突然上升或多个租户共享服务时,它可以减少 GPU 空转和排队抖动。

参考资料

常见问题

Continuous Batching 和普通动态批处理有什么区别?
普通动态批处理通常在请求进入时凑批,然后整批执行到结束;Continuous Batching 会在每个解码迭代重新组织 batch,让已完成请求退出,新请求及时进入。
Continuous Batching 一定能降低每个请求的延迟吗?
不一定。它主要提升 GPU 利用率和总体吞吐,单请求延迟还会受到排队、prefill 长度、输出长度、batch 上限和调度策略影响。
上线 Continuous Batching 最应该监控什么?
至少监控 TTFT、TPOT、请求排队时间、运行中序列数、GPU 利用率、KV Cache 使用率、OOM 次数和拒绝请求数。