跳到主要内容

并发与批处理

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

传统 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 长期空闲,也可能成本过高。并发与批处理的核心,就是在用户体验、硬件利用率和稳定性之间找到可长期运行的平衡点。