跳到正文

vLLM 性能调优:把吞吐从 1200 提到 3400 tok/s

一次完整的推理服务优化记录。连续批处理、PagedAttention、前缀缓存、投机解码,每一项带来多少提升都有实测数字。

1367 字约 4 分钟直达下载 ↓
本文目录(9)

接手一个内部推理服务时,它的吞吐是 1200 tok/s,P99 延迟 4.2 秒,机器是单张 A100 80G 跑 13B 模型。听起来还行,但实际并发一上来就开始排队。

调了一周之后:吞吐 3400 tok/s,P99 降到 1.6 秒。 这篇把每一步和对应的数字都记下来。

先建立基线,别急着调

调优最大的陷阱是「改一堆参数然后感觉变快了」。我第一件事是搭一套固定的压测:

# 固定输入输出长度,只变并发数
python benchmark_serving.py \
  --backend vllm \
  --base-url http://localhost:8000 \
  --model Qwen/Qwen2.5-13B-Instruct \
  --dataset-name sharegpt \
  --num-prompts 800 \
  --request-rate 8 \
  --max-concurrency 32

关键指标只看三个:输出吞吐(tok/s)、首 token 延迟(TTFT)、P99 端到端延迟。

只看吞吐会得出错误结论 —— 有些配置能把吞吐堆上去,代价是长尾延迟爆炸。

优化前后关键指标对比
优化前后关键指标对比

第一项:把 gpu-memory-utilization 调对(+35%)

原始配置是 0.90,但机器上还跑着一个小的 embedding 服务,实际可用显存比预期少。vLLM 启动时按 0.90 预留 KV Cache 池,结果就是池子偏小、频繁触发抢占。

# 优化前:池子不够,KV Cache 频繁换出
--gpu-memory-utilization 0.90

# 优化后:留出合理余量,池子反而更大更稳
--gpu-memory-utilization 0.88

听起来反直觉 —— 比例调低了,吞吐反而涨了 35%。原因是避免了抢占(preemption)。当 KV Cache 块耗尽时,vLLM 会把某些序列换出重算,这个代价极高。

# 调优时要盯这个日志,GPU KV cache usage 长期贴近 100% 就是池子不够
INFO: GPU KV cache usage: 99.2%
INFO: Preemption count: 1847

看到 Preemption count 持续增长,就该调显存比例或者降 max-model-len 了。

第二项:限制 max-model-len(+28%)

这是最容易被忽略的一项。模型原生支持 128K 上下文,配置里就真的写了 128K。但实际上这个服务的请求平均只有 1.5K。

KV Cache 的池子是固定的,块大小也是固定的。 允许 128K 上下文意味着单个序列最多可能占用 128K 那么多块,调度器分配时会非常保守。

# 128K → 8K,显存池能容纳的并发序列数直接涨了 6 倍
--max-model-len 8192

经验做法:统计线上请求的 P99 长度,然后乘以 1.5 作为上限。 别按模型能力上限配。

第三项:开前缀缓存(+41%,取决于业务)

我们的场景是客服问答,系统提示词特别长(约 2000 token)而且所有请求完全一样。这种情况前缀缓存(prefix caching)几乎是白捡的。

--enable-prefix-caching

开启后,系统提示词那段 KV 只算一次,后续请求直接复用:

指标未开开启
TTFT(P50)380 ms96 ms
吞吐2100 tok/s2960 tok/s

第四项:批处理参数(+18%)

三个参数需要一起看:

--max-num-seqs 64          # 调度器同时处理的最大序列数
--max-num-batched-tokens 8192   # 单次前向的总 token 预算
--max-padding-tokens 512

调参逻辑:

max-num-seqs 不是越大越好。 从 32 提到 64 时吞吐涨了 18%,提到 128 反而下降 —— 因为同时活跃的序列太多,KV Cache 池不够,又开始抢占。这个值必须和显存池大小匹配。

max-num-batched-tokens 是真正控制批大小的旋钮。 它比 max-num-seqs 更直接影响单次前向的计算量。

我的调参顺序是:先固定 max-num-seqs,扫一遍 max-num-batched-tokens;找到最优点后再回过头调 max-num-seqs。

第五项:投机解码(延迟 -30%,吞吐基本不变)

投机解码(speculative decoding)用一个小模型预测多个 token,大模型批量验证。它降低的是延迟,不是提高吞吐。

--speculative-model Qwen/Qwen2.5-0.5B-Instruct \
--num-speculative-tokens 5 \
--speculative-draft-tensor-parallel-size 1

实测:

指标未开开启
单请求生成速度42 tok/s61 tok/s
TTFT96 ms118 ms
总吞吐3400 tok/s3320 tok/s

单请求快了 45%,但总吞吐略降 —— 因为小模型也占显存和计算。如果你的服务是低并发、重体验(比如桌面端助手),开它;如果是高并发服务,别开。

还有个前提:草稿模型和目标模型必须同词表,否则接受率会低到没有收益。接受率低于 60% 基本就是负优化。

优化清单汇总

步骤改动吞吐变化
基线—1200 tok/s
调 gpu-memory-utilization0.90 → 0.88+35%
限制 max-model-len128K → 8K+28%
开前缀缓存新增+41%
调批处理参数—+18%
投机解码新增延迟优化(吞吐 -2%)

累计吞吐 1200 → 3400 tok/s。注意这些收益不是简单相加,它们有重叠,表里的百分比是每一步相对上一步的增量。

几个反直觉的结论

显存利用率调低反而更快。 因为避免了抢占。

限制上下文长度是最省事的优化。 一行配置,28% 提升。

不要同时改多个参数。 我吃过这个亏 —— 一口气改了四个参数,吞吐涨了但延迟变差,完全不知道是哪一项导致的。一次只改一个,每次都跑压测。

复现

压测脚本、调优前后的配置文件和完整报告都在下载区。要复现的话建议用同一套 sharegpt 数据集,否则数字不可比。

最后说一句:先把基线建起来,再谈优化。 没有基线的调优都是玄学。

资源下载

2 个入口

链接若失效,欢迎发邮件告诉我,我会尽快重新上传。