跳到主要内容

并发与批处理

并发和批处理决定大模型推理服务能承载多少请求,也决定用户感受到的延迟。

传统 Web 服务里,并发常常可以理解成“同时有多少 HTTP 请求”。但 LLM 服务不是简单的请求处理系统。它同时受输入 token、输出 token、prefill、decode、KV Cache、调度队列和 GPU 显存影响。

一句话概括:

LLM 并发能力 = 请求数量 + token 数量 + batch 调度 + KV Cache 余量

如果只看 QPS 或 HTTP 并发,很容易误判容量。两个请求可能都是 1 个并发,但一个只有 100 个输入 token,另一个可能有 32000 个输入 token,它们对 GPU 和 KV Cache 的压力完全不同。

1. 先区分几种并发​

LLM 服务里至少要区分三种并发。

类型含义主要影响
请求并发同时进入服务的请求数量队列长度、连接数、用户等待
序列并发推理引擎中同时运行的 sequence 数量batch 大小、KV Cache 分配
token 并发同一时刻正在处理的 token 数量GPU 计算、显存带宽、调度效率

请求并发高,不一定代表 GPU 已经吃满。也可能大量请求在排队,还没有进入推理引擎。

序列并发高,也不一定代表吞吐高。如果这些 sequence 很短,或者 decode 阶段每步只有少量 token,GPU 可能仍然利用不足。

token 并发更接近 LLM 的真实工作量。压测和容量规划时,应该同时记录:

  • 每秒请求数。
  • 输入 token 数。
  • 输出 token 数。
  • 当前运行中的 sequence 数。
  • waiting queue 长度。
  • KV Cache 使用率。

2. Prefill 和 Decode 的调度差异​

LLM 推理通常分成两个阶段。

阶段做什么主要瓶颈影响指标
Prefill一次性处理输入 prompt,建立 KV Cache算力、attention 计算、长输入TTFT
Decode每一步生成新 token,持续复用 KV Cache显存带宽、KV Cache 读写、调度ITL、输出 tokens/s

2.1 Prefill​

Prefill 是“读题”阶段。输入越长,模型越晚开始输出第一个 token。

长 prompt、RAG 拼接内容、长历史对话和大工具定义都会增加 prefill 成本。对于交互式产品,prefill 太重会直接拉高 TTFT。

同一个服务里,短问答和长文档总结混跑时,长 prefill 请求可能拖慢短请求。生产中常见做法是:

  • 限制最大输入长度。
  • 把超长上下文请求放入单独队列。
  • 使用 prefix cache 复用固定前缀。
  • 对长 prompt 设置更保守的并发上限。

2.2 Decode​

Decode 是“逐字生成”阶段。每个活跃请求通常每轮生成一个 token,调度器把多个请求合成一个 batch,让 GPU 同时处理。

Decode 阶段常常更受显存带宽影响,因为每一步都要读取模型权重和 KV Cache。并发太低时,GPU 吃不满;并发太高时,KV Cache 和排队延迟会变差。

所以 LLM 服务优化不是简单地“把并发调高”。更准确的目标是:

在 TTFT、ITL、p99 和显存安全边界内,提高 token throughput

3. Static Batching​

Static batching 是传统批处理思路:收集一批请求,组成固定 batch,一起送进模型。

它适合离线任务,例如:

  • 批量摘要。
  • 批量打标签。
  • 批量生成评测答案。
  • 同长度或近似长度的数据集推理。

优点是实现简单,吞吐稳定。缺点是对在线服务不够友好:

  • 短请求可能要等长请求。
  • batch 内请求结束时间不同,容易浪费算力。
  • 流式输出不自然。
  • 很难动态插入新请求。

对于离线批处理,可以接受更高的 E2E latency 来换吞吐;对于聊天类在线服务,用户更关心 TTFT 和流式输出是否顺滑,static batching 通常不是最佳选择。

4. Continuous Batching​

Continuous batching 是现代 LLM 推理框架常用的在线调度方式。它不是等一整批请求全部结束再处理下一批,而是在每个 decode step 动态维护 batch。

可以粗略理解为:

每一轮 decode:
1. 选择一批正在运行的请求
2. 为每个请求生成下一个 token
3. 移除已经结束的请求
4. 从等待队列补充新请求
5. 继续下一轮

它的收益是:

  • 新请求可以更快进入运行队列。
  • 短请求结束后,slot 可以及时释放。
  • GPU 更容易保持较高利用率。
  • 更适合流式输出。
  • 能和 PagedAttention、KV Cache block 管理配合。

但 continuous batching 也会引入调度取舍:

  • 补充新请求太激进,可能拉高已有请求的 ITL。
  • prefill 插入过多,可能让 decode 流式输出卡顿。
  • 长输出请求占住 slot 太久,可能影响短请求。
  • KV Cache 接近上限时,调度器可能需要等待、抢占或拒绝请求。

5. Batch Size 不是越大越好​

batch 变大通常能提高 GPU 利用率,但不一定改善用户体验。

batch 变大带来的收益batch 变大带来的代价
更容易摊薄 kernel 启动和调度开销排队时间可能增加
GPU 利用率更高TTFT 可能上升
token throughput 更高ITL 可能变差或抖动
单位 token 成本可能下降KV Cache 和显存压力增加

在线服务最常见的矛盾是:吞吐和延迟不能同时无限优化。

如果目标是离线生成,可以把 batch 做大,追求总 tokens/s。
如果目标是实时聊天,要给 TTFT、ITL 和 p99 留预算,不能只追求峰值吞吐。

6. vLLM 中的两个关键参数​

不同推理框架参数名不同,但思路类似。以 vLLM 为例,常见需要理解两个参数。

6.1 max_num_seqs​

max_num_seqs 控制单次调度中允许同时处理的最大序列数。

它主要限制 sequence 并发。

调大可能带来:

  • 更多请求同时运行。
  • decode batch 更大。
  • 吞吐提升。

但也可能带来:

  • KV Cache 占用上升。
  • TTFT 和 p99 变差。
  • 长输出请求挤占运行 slot。
  • 显存不足时更容易 OOM 或触发抢占。

如果服务经常排队,但 GPU 利用率和显存还有余量,可以尝试调大。
如果 KV Cache 使用率高、p99 变差或频繁 OOM,应调小或限制请求长度。

6.2 max_num_batched_tokens​

max_num_batched_tokens 控制单次调度最多处理多少 token。

它限制的是 token 维度的 batch 上限,通常会影响 prefill 和 decode 的混合调度。

调大可能带来:

  • 长 prompt prefill 更容易合批。
  • 总 token throughput 提升。
  • GPU 利用率提高。

但也可能带来:

  • 单轮调度耗时变长。
  • 短请求等待长 prefill。
  • TTFT 上升。
  • 显存和 workspace 压力增加。

调这个参数时,不能只看 QPS。更应该观察:

  • prefill tokens/s。
  • decode tokens/s。
  • TTFT。
  • ITL。
  • p95 / p99 latency。
  • KV Cache usage。
  • waiting requests。

7. 队列和排队延迟​

高并发下,延迟不一定来自模型计算本身,也可能来自排队。

一个请求的端到端耗时可以拆成:

E2E latency
= 网关 / 鉴权 / 路由
+ prompt 构造 / tokenizer
+ queue wait
+ prefill
+ decode
+ 网络传输 / 前端渲染

如果 TTFT 突然升高,要先判断是 prefill 慢,还是队列等待变长。

常见排队原因包括:

  • 入口请求速率超过服务处理速率。
  • max_num_seqs 或 KV Cache 已到上限。
  • 长 prompt 请求占用大量 token budget。
  • 长输出请求占住运行 slot。
  • tokenizer、RAG、工具调用等前处理慢。
  • GPU worker 数量不足或路由不均衡。

路由层不要只按请求数分配流量。更好的路由信号包括:

  • 每个 worker 的 waiting queue。
  • running sequence 数。
  • KV Cache 使用率。
  • 当前 batch token 数。
  • 最近 TTFT / ITL。
  • GPU 显存余量。

8. 吞吐、TTFT、ITL 和 p99 的取舍​

不同业务目标下,优化方向不一样。

目标优先关注常见策略
在线聊天TTFT、ITL、p99控制 batch 上限、限制长上下文、流式输出、prefix cache
高吞吐 APItokens/s、成功率、GPU 利用率提高 batch、增加副本、优化调度
离线批处理总完成时间、成本大 batch、非流式、按长度分桶
长文档总结TTFT、KV Cache、OOM限制并发、单独队列、chunked prefill、上下文裁剪
多租户服务p99、限流、公平性按用户限流、配额、队列隔离、动态降级

一个常见误区是把 benchmark 的峰值 tokens/s 当成线上体验。峰值吞吐通常是在特定输入长度、输出长度和并发下测出来的,不一定能代表真实流量。

线上更应该看稳定区间:

在错误率可控、p99 可接受、显存不紧张的条件下,服务能长期维持的吞吐

9. 长短请求混跑问题​

LLM 请求长度差异很大。长短请求混跑时,容易出现“少量重请求拖慢整体服务”。

典型场景:

  • 用户上传长文档总结。
  • RAG 召回过多 chunk。
  • 工具定义很长。
  • 某些用户要求生成超长答案。
  • 多轮对话历史没有裁剪。

常见治理方式:

  • 按输入长度分队列。
  • 按最大输出长度分队列。
  • 对长上下文请求降低并发。
  • 对超长请求使用异步任务模式。
  • 为实时聊天和批处理任务拆分不同实例。
  • 对单用户设置 token budget 和并发限制。

如果业务同时有在线聊天和离线批处理,不建议完全混用同一组 worker。离线任务更适合使用独立实例,在低峰期运行,或者通过队列限速。

10. 限流和降级​

LLM 服务限流不能只按请求数。更合理的是按 token、显存压力和用户配额综合限制。

可用的限制维度包括:

限制项作用
最大输入 token控制 prefill 和 KV Cache
最大输出 token控制 decode 时长和 KV Cache 增长
单用户并发避免单个用户占满队列
单用户 token/min比 QPS 更公平
全局 running sequence控制推理引擎并发
全局 waiting queue避免排队无限增长
KV Cache 使用率阈值接近 OOM 前提前拒绝或降级

高负载时可以按层级降级:

  1. 限制新请求最大输出长度。
  2. 降低长上下文请求优先级。
  3. 对低优先级用户排队或返回限流。
  4. 关闭非必要工具或减少 RAG chunk 数。
  5. 将批处理任务转移到异步队列。
  6. 扩容副本或切换到更短上下文配置。

降级策略要提前设计。等到 GPU OOM 或服务超时后再处理,用户体验通常已经明显受损。

11. 离线批处理的实践​

离线批处理和在线服务的目标不同。离线任务通常更关注总吞吐、成本和失败重试。

实践上可以:

  • 按输入长度分桶,减少 padding 和调度浪费。
  • 使用更大的 batch 或 token budget。
  • 限制单条样本最大输出,避免少数样本拖慢整体任务。
  • 记录每条样本的输入 token、输出 token、耗时和失败原因。
  • 对失败样本单独重试,不要整批重跑。
  • 把离线任务和在线 worker 隔离。

离线批处理可以牺牲 TTFT,因为没有用户盯着第一个 token。但仍然要关注 OOM、超时和长尾样本,否则任务整体完成时间会被少数异常样本拖垮。

12. 监控指标​

生产环境建议至少监控这些指标。

指标说明
request rate入口请求速率
successful / failed requests成功率和错误率
TTFT首 token 延迟
ITLtoken 间延迟
E2E latency完整请求耗时
p50 / p90 / p99延迟分布
input tokens / request输入长度分布
output tokens / request输出长度分布
running requests正在运行的请求
waiting requests等待调度的请求
prefill tokens/sprefill 吞吐
decode tokens/sdecode 吞吐
KV Cache usageKV Cache 使用率
GPU memory used / reserved显存占用和预留
preemption / eviction抢占或缓存淘汰次数
timeout / rate limit超时和限流数量

排障时不要只看 GPU 利用率。GPU 利用率高可能是正常高吞吐,也可能是长 prompt 压力;GPU 利用率低也不代表系统没问题,可能是 tokenizer、队列、网络、存储或调度策略拖住了服务。

13. 常见问题​

13.1 并发一上来,TTFT 明显变差​

可能原因:

  • 请求在 waiting queue 中排队。
  • 长 prompt prefill 占用 token budget。
  • batch 上限过大,单轮调度时间变长。
  • tokenizer 或 RAG 前处理成为瓶颈。

处理方式:

  • 观察 queue wait 和 prefill 耗时。
  • 限制最大输入长度。
  • 将长 prompt 请求拆到单独队列。
  • 调整 max_num_batched_tokens。
  • 启用 prefix cache 或减少固定前缀长度。

13.2 GPU 利用率高,但用户感觉慢​

可能原因:

  • batch 过大,吞吐高但 p99 差。
  • decode ITL 变高,流式输出卡顿。
  • KV Cache 接近上限,调度器频繁等待或抢占。
  • 长输出请求占住运行 slot。

处理方式:

  • 同时看 TTFT、ITL 和 p99。
  • 限制最大输出 token。
  • 降低运行 sequence 上限。
  • 对长输出任务分队列。
  • 增加副本而不是继续堆单实例并发。

13.3 模型能启动,但高并发 OOM​

可能原因:

  • 只估算了权重显存,没有估算 KV Cache。
  • max_model_len 太大。
  • max_num_seqs 太高。
  • 输入和输出 token 上限过宽。
  • 显存预留不足。

处理方式:

  • 降低 max_model_len。
  • 降低 max_num_seqs。
  • 限制输入和输出长度。
  • 启用 KV Cache 量化。
  • 使用更多副本或更大显存 GPU。

13.4 QPS 不高,但服务还是很慢​

可能原因:

  • 每个请求 token 很长。
  • 输出 token 很多。
  • RAG 或工具调用耗时高。
  • 请求速率有突发峰值,平均 QPS 掩盖了排队。
  • 路由把重请求集中到少数 worker。

处理方式:

  • 按 token 而不是请求数做分析。
  • 记录输入和输出长度分布。
  • 观察 p95 / p99,而不是只看平均值。
  • 增加基于 KV Cache 和队列长度的路由策略。

14. 压测建议​

压测并发和 batch 时,建议分几组进行。

压测类型目的
固定并发看某个并发下的稳定性和延迟
固定请求速率更接近线上流量进入方式
ramp up找到服务开始恶化的临界点
长短请求混合验证调度公平性和 p99
长上下文压测验证 KV Cache 和 TTFT
长输出压测验证 decode、ITL 和 slot 占用
突发流量压测验证队列、限流和恢复能力

每次压测至少记录:

  • 模型名称、精度、量化方式。
  • GPU 型号、数量、显存。
  • 推理框架和启动参数。
  • 输入 token 分布。
  • 输出 token 分布。
  • 并发或请求速率。
  • TTFT、ITL、E2E latency。
  • p50、p90、p99。
  • tokens/s。
  • 错误率、超时率、限流率。
  • KV Cache 使用率和 OOM 情况。

只有这些条件一致,压测结果才有可比性。

15. 和其他概念的关系​

  • 和 KV Cache:并发越高、上下文越长,KV Cache 压力越大;batch 调度会直接影响缓存分配和释放。
  • 和 上下文管理:上下文裁剪、历史摘要和 RAG 拼接会影响 prefill 成本和 KV Cache。
  • 和 流式输出:流式输出主要发生在 decode 阶段,ITL 决定输出是否顺滑。
  • 和 推理参数:max_tokens、stop、采样参数会影响输出长度和请求耗时。
  • 和 容量与成本规划:容量规划要按 token、并发、延迟目标和显存余量估算。
  • 和 AIPerf:可以用压测工具验证固定并发、固定请求速率和长尾延迟。

16. 实践检查清单​

上线前可以按这份清单检查:

  1. 是否记录输入 token 和输出 token 分布。
  2. 是否区分 fixed concurrency 和 fixed request rate 压测。
  3. 是否分别观察 TTFT、ITL、E2E latency 和 p99。
  4. 是否估算了 KV Cache,而不是只看权重显存。
  5. 是否设置最大输入长度和最大输出长度。
  6. 是否为长上下文请求设计单独队列或限流。
  7. 是否监控 running requests 和 waiting requests。
  8. 是否监控 KV Cache usage 和 GPU memory reserved。
  9. 是否有超时、限流和降级策略。
  10. 是否把在线服务和离线批处理隔离。

17. 总结​

LLM 服务的并发能力不是一个单独数字。它取决于请求数、输入长度、输出长度、KV Cache、batch 调度、GPU 显存和延迟目标。

更实用的判断方式是:

先定义用户能接受的 TTFT、ITL 和 p99,
再在这个边界内调 batch、并发和 token budget,
最后用真实流量分布压测稳定吞吐。

吞吐高但 p99 很差,不是好的在线服务;延迟很好但 GPU 长期空闲,也可能成本过高。并发与批处理的核心,就是在用户体验、硬件利用率和稳定性之间找到可长期运行的平衡点。