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,不靠手感:
- 基线三指标:固定
--num-prompts和--request-rate,记录 TPS / TTFT / ITL。 - 逐项调参 + 回归:每改一个参数重跑基线,五指标全绿才进下一步。
- 质量回归门禁:KV Cache 量化、任何精度变更后,跑固定问答集的 faithfulness / 正确性评估,质量掉点即回滚。
- 抢占率监控:线上把 KV 抢占率接进监控,>10% 即 P1 告警。
- 容量水位线: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× 输入长度附近,让预填充和解码更好交错。