跳转至

vLLM 大并发推理优化 · 运维手册

适用场景:用 vLLM 扛生产级 LLM 推理,并发用户多、请求长短不一,需要在「GPU 吃满」和「延迟不炸」之间找可压测、可回归的最优解。


1. 大并发下 vLLM 到底卡在哪

很多人第一反应是「换更大显卡」。但真到了大并发,瓶颈往往不在算力,而在两处:KV Cache 容量和调度争用。

KV Cache 是每生成一个 token 都要反复读取的历史键值矩阵。并发序列越多,它占的显存越大。一旦占满,vLLM 会触发抢占(默认 Recompute)——把某些序列踢出去重算。抢占一来,GPU 在等重算、吞吐直接塌,延迟长尾拉爆。所以大并发优化的本质,是让有限的 KV Cache 撑住最多的并发序列,同时别让抢占雪崩。

2. 连续批处理与 PagedAttention:并发容量的地基

vLLM 的底座是连续批处理(continuous batching)+ PagedAttention。前者让序列结束立刻腾出槽位接新请求,后者把 KV Cache 像操作系统分页一样按需分配,把碎片从 40%+ 压到 4% 以内。

这套机制决定了你的并发上限基线。我一般先确认它跑通、版本对齐(vLLM / CUDA / 驱动三层对齐),再谈上层调参——地基没稳,调参数都是空中楼阁。

3. 抢占与 OOM:--max-num-seqs 与 --gpu-memory-utilization 怎么定

两个参数直接决定「会不会炸」:

  • --max-num-seqs:同一时刻活跃的最大序列数。太小 GPU 吃不满,太大 KV Cache 撑不住→频繁抢占。我习惯先压测找到吞吐开始平台化的点,把它当起点。
  • --gpu-memory-utilization:启动时为 KV Cache 预占的显存比(默认常 0.90)。遇到频繁抢占可提到 0.95 换更大并发容量,但太高会 OOM 把 pod 干崩。

纪律:每次只动一个参数,改完重测。一次同时改五个参数,出问题你根本不知道是哪个。

python -m vllm.entrypoints.openai.api_server \
  --model meta-llama/Meta-Llama-3.1-8B-Instruct \
  --gpu-memory-utilization 0.95 \
  --max-num-seqs 256 \
  --max-model-len 4096

4. 长尾延迟:--enable-chunked-prefill 削平 ITL

预填充(Prefill)是计算密集、解码(Decode)是带宽密集。把一长段预填充和一堆轻量解码混在同一个调度步,解码任务全得等预填充算完——这就是队头阻塞,ITL 剧烈抖动。

--enable-chunked-prefill 把预填充切片,优先塞满解码保证流式平滑,剩余预算再排预填充块。代价是 TTFT 略升,换回的是 P95/P99 的 ITL 大幅改善,整体 TPS 通常还能涨。生产环境我基本都开。

5. 前缀缓存:--enable-prefix-caching 省掉的重复算力

如果很多请求共享同一段系统提示或多轮对话前缀,--enable-prefix-caching 把这段 KV 算一次、处处复用。Chatbot、少样本样例这类场景,能直接砍掉近一半预填充算力。

关键点:缓存必须跨请求保留。vLLM 自带前缀缓存,SGLang 的 RadixAttention 做得更激进(全局基数树复用,多轮命中率 75%–90%)。如果你的负载前缀重叠率超过 60%,这一项收益最大。

6. KV Cache 量化:fp8 显存减半、并发翻倍

长上下文场景下,KV Cache 量化往往比权重量化带来更大收益。--kv-cache-dtype fp8 把每个 token 的缓存足迹减半,单请求解码延迟下降,更重要的是让有限显存撑起两倍的并发。

# fp8 KV Cache:长上下文生产的性价比杠杆
python -m vllm.entrypoints.openai.api_server \
  --model ... --kv-cache-dtype fp8 \
  --enable-prefix-caching --enable-chunked-prefill

铁律:KV Cache 量化必须配自动化质量评估(见第 10 节)。fp8 一般安全(质量掉点 < 0.4 分),更低比特(int4)在复杂推理/长尾任务有断崖风险,没校准别上。

7. 分离式 PD(Prefill/Decode 解耦):万卡集群的下一步

单机调参到头后,真正的大头是 Prefill 和 Decode 在算力/带宽需求上的根本异构。把集群切成两个资源池:Prefill 池专吃长上下文、保持大 batch;Decode 池专注内存带宽和海量并发缓存。

代表性方案(Splitwise / DistServe / Mooncake 思路)能做到 2 倍吞吐、降本 30%–40%。但痛点是节点间网络 I/O——Prefill 算完必须把庞大 KV 张量毫秒级迁到 Decode 池,网络设计不当会抹平全部收益。这一步我建议先在小集群验证再上规模。

8. 异步调度与流控:--stream-interval / --api-server-count

高并发下,前端 HTTP/gRPC 的响应分发本身也会吃掉 CPU。两个实用开关:

  • --stream-interval:把 token 攒成块再发(首 token 立即发保住 TTFT,后续批发送),降 CPU 开销,高并发 benchmark 能涨 30%+。
  • --api-server-count:数据并行部署时,强制设 1 能把被默认关掉的 stats 日志打开——每 10 秒打印吞吐和 KV 利用率,排查瓶颈全靠它。
# 高并发压测时的诊断组合
VLLM_LOG_STATS_INTERVAL=10 python -m vllm.entrypoints.openai.api_server \
  --model ... --stream-interval 20 --api-server-count 1

9. 一次真实故障复盘:max-num-seqs 调太高,KV 抢占雪崩

某次大促前压测,我把 --max-num-seqs 从 256 一把提到 1024,单机 TPS 数字好看。上线后高峰一来,KV Cache 被占满,抢占率飙升到 40%,大量请求被 Recompute,P99 从 800ms 飙到 6s,告警洪水。

根因:只看了平均吞吐,没看抢占率和 P99。回滚到 320 并提 --gpu-memory-utilization 到 0.95,抢占率降到 5% 以内,P99 回到 1.1s。教训:大并发调参必须同时盯「吞吐 / 抢占率 / TTFT / ITL / P99」五条线,任何一条恶化都算失败。

10. 上线前的压测 SOP 与巡检纪律

我把大并发上线固化成一套 SOP,不靠手感:

  1. 基线三指标:固定 --num-prompts 和 --request-rate,记录 TPS / TTFT / ITL。
  2. 逐项调参 + 回归:每改一个参数重跑基线,五指标全绿才进下一步。
  3. 质量回归门禁:KV Cache 量化、任何精度变更后,跑固定问答集的 faithfulness / 正确性评估,质量掉点即回滚。
  4. 抢占率监控:线上把 KV 抢占率接进监控,>10% 即 P1 告警。
  5. 容量水位线:GPU 利用率长期 <80% 说明 batch/调度有瓶颈;KV 利用率长期 >90% 是扩容信号。
# 压测基线(每次调参前都跑一遍)
python -m vllm.benchmarks.benchmark_serving \
  --backend openai --model meta-llama/Meta-Llama-3.1-8B-Instruct \
  --num-prompts 200 --request-rate 30

11. 常见问题排查

问题 1:GPU 利用率低但吞吐上不去

通常是 batch 太小或 CPU 预处理/网络 I/O 卡住请求队列。先 watch -n 1 nvidia-smi 看利用率,低于 80% 就查前后处理链路和 --stream-interval。

问题 2:并发一高就 OOM 重启

--gpu-memory-utilization 设太高。逐步下调到稳定值,或降低 --max-num-seqs、--max-model-len 给 KV Cache 留余量。

问题 3:TTFT 正常但 ITL 抖动厉害

典型队头阻塞,开 --enable-chunked-prefill,并把 --max-num-batched-tokens 调到 2× 输入长度附近,让预填充和解码更好交错。


FAQ

vLLM 大并发下到底卡在哪里? 不是 GPU 算力,而是 KV Cache 容量和调度争用。并发一高,KV Cache 被占满就触发抢占(Recompute),吞吐骤降、延迟长尾。调参的核心就是让 KV Cache 撑住更多并发序列,同时避免抢占雪崩。

--max-num-seqs 设多大合适? 没有定值。先压测找到「吞吐开始平台化、P99 延迟陡增」的并发点,把它作为起点,再按业务偏吞吐还是偏延迟微调。设太高会抢占频发,设太低 GPU 吃不满。每次改完要重测 TTFT/ITL/TPS。

--enable-chunked-prefill 要不要开? 生产环境基本都该开。它把大预填充切片,和解码任务交错执行,牺牲一点 TTFT 换来 P95/P99 的 ITL 大幅改善,整体 TPS 通常还能涨。长上下文、输出长短不一的负载收益最明显。

prefix caching 真能省算力吗? 能,而且省得狠。多轮对话、共享系统提示、少样本样例这类「前缀高度重叠」的场景,命中缓存就跳过整段预填充重算。前提是缓存要跨请求保留(RadixAttention 这类机制),否则白配。

怎么验证调优没把质量搞退化? 每次改参数都跑一遍固定数据集的自动化评估(faithfulness / answer 正确性),把「吞吐」和「质量」两个指标一起盯。质量回退就回滚参数;KV Cache 量化尤其要配评估,fp8 一般安全,更低比特可能掉点。


相关文章