vLLM 参数扫描实战:用 vllm bench sweep 完成配置调优与延迟-吞吐权衡探索
vLLM 内置了 vllm bench sweep 参数扫描(Parameter Sweeps)工具集,它能在多组服务器/压测配置上自动运行基准测试,并把结果汇总为 CSV 与可视化曲线,帮助你在上线前完成吞吐、延迟与 SLA 的权衡决策。本文基于 vLLM 仓库中 docs/benchmarking/sweeps.md 的官方文档,结合 vllm/benchmarks/sweep/ 目录下的实际源码实现,完整覆盖 serve、serve_workload、startup、plot、plot_pareto 五个子命令的用法、参数文件格式、结果目录结构与底层执行流程,读完即可复制运行完整的调优实验。
一、Parameter Sweeps 概览:五个子命令与统一调度
vllm bench sweep 是运行多配置基准测试并对比结果的命令套件。它的五个子命令在 vllm/benchmarks/sweep/cli.py 中统一注册:
| 子命令 | 作用 | 实现文件 |
|---|---|---|
serve |
启动 vllm serve,对每组服务器配置迭代执行 vllm bench serve |
serve.py |
serve_workload |
在 serve 基础上自动探索不同负载水平,寻找延迟-吞吐权衡 |
serve_workload.py |
startup |
对多组参数组合运行 vllm bench startup,对比冷/热启动时间 |
startup.py |
plot |
从扫描结果绘制性能曲线 | plot.py |
plot_pareto |
绘制帕累托前沿,平衡单用户吞吐与单 GPU 吞吐 | plot_pareto.py |
CLI 入口由 vllm/entrypoints/cli/benchmark/sweep.py 中的 BenchmarkSweepSubcommand 挂到 vllm bench 主命令下,子命令通过 argparse 的 set_defaults(dispatch_function=entrypoint) 分发到各自的 main 函数。
需要说明:如果你只需要对单一服务器配置做压测,可以单独使用 vllm bench serve,或参考社区维护的 GuideLLM(官方文档中也给出了同样的提示),它在数据集加载、请求格式与负载模式上更加灵活。vllm bench sweep 的价值在于把“多配置 × 多负载 × 多次重复”这件事自动化,并沉淀为可绘图的结构化结果。
二、在线压测扫描:vllm bench sweep serve
2.1 基本用法四步走
运行 serve 扫描的步骤如下(与 docs/benchmarking/sweeps.md 一致):
- 构造
vllm serve基础命令,传给--serve-cmd; - 构造
vllm bench serve基础命令,传给--bench-cmd; - (可选)若要改变
vllm serve的设置,创建 JSON 参数组合文件,路径传给--serve-params; - (可选)若要改变
vllm bench serve的设置,创建 JSON 参数组合文件,路径传给--bench-params; - 设置
--output-dir,并可选设置--experiment-name控制结果保存位置。
示例 1:扫描 --max-num-seqs 与 --max-num-batched-tokens 的组合(写入 benchmarks/serve_hparams.json):
[
{
"max_num_seqs": 32,
"max_num_batched_tokens": 1024
},
{
"max_num_seqs": 64,
"max_num_batched_tokens": 1024
},
{
"max_num_seqs": 64,
"max_num_batched_tokens": 2048
},
{
"max_num_seqs": 128,
"max_num_batched_tokens": 2048
},
{
"max_num_seqs": 128,
"max_num_batched_tokens": 4096
},
{
"max_num_seqs": 256,
"max_num_batched_tokens": 4096
}
]
示例 2:扫描 random 数据集的不同输入/输出长度(写入 benchmarks/bench_hparams.json):
[
{
"_benchmark_name": "scenario_A",
"random_input_len": 128,
"random_output_len": 32
},
{
"_benchmark_name": "scenario_B",
"random_input_len": 256,
"random_output_len": 64
},
{
"_benchmark_name": "scenario_C",
"random_input_len": 512,
"random_output_len": 128
}
]
完整命令示例:
vllm bench sweep serve \
--serve-cmd 'vllm serve meta-llama/Llama-2-7b-chat-hf' \
--bench-cmd 'vllm bench serve --model meta-llama/Llama-2-7b-chat-hf --backend vllm --endpoint /v1/completions --dataset-name sharegpt --dataset-path benchmarks/ShareGPT_V3_unfiltered_cleaned_split.json' \
--serve-params benchmarks/serve_hparams.json \
--bench-params benchmarks/bench_hparams.json \
--output-dir benchmarks/results \
--experiment-name demo
默认情况下,每个参数组合会压测 3 次以提高结果可靠性(源码中 --num-runs 默认值确为 3,见 serve.py),可用 --num-runs 调整,且必须至少为 1,否则在 SweepServeArgs.from_cli_args 中直接抛出 ValueError。
2.2 参数文件格式:列表或字典两种写法
参数文件的解析实现在 vllm/benchmarks/sweep/param_sweep.py 的 ParameterSweep.read_json 中:
- 列表格式:每个元素是一个 dict,代表一组参数组合(上文两个示例都是这种格式);
- 字典格式:键为基准名、值为参数字典(见
read_from_dict),加载时自动转换为{"_benchmark_name": name, **params}的记录,适合给组合起语义化名字; _benchmark_name字段:不是 CLI 参数,仅用于命名;若提供了多个_benchmark_name,源码会校验其唯一性,重复直接报错。
对于变量较多的参数组合,官方文档建议设置 _benchmark_name 提供人类可读的名字;当组合参数很多、生成的文件路径可能超过文件系统最大路径长度时,该字段实际上是必需的。
2.3 参数如何被注入到命令中:apply_to_cmd
ParameterSweepItem 继承自 dict,其 apply_to_cmd 方法(param_sweep.py)描述了参数覆盖的完整规则,理解它能帮你正确编写参数文件:
- 优先替换已有参数:如果基础命令(
--serve-cmd/--bench-cmd)中已存在同名参数,则原地替换其值,避免重复追加; - 不存在则追加:否则作为新的
--key value追加到命令末尾; - 键名归一化:JSON 中优先用下划线(
max_num_seqs),CLI 中优先用连字符(--max-num-seqs),_iter_param_key_candidates/_iter_cmd_key_candidates会在两种写法之间互相探测; - 布尔值:非嵌套布尔参数
true变成--flag、false变成--no-flag;带.的嵌套布尔参数则使用--key=true/false形式; - dict 值:会被序列化为 JSON 字符串传入(适合一些复杂配置项);
- 带
.的嵌套键:如config.xxx这类内层配置参数按前缀逐段归一化,不被 CLI 转换影响。
serve 主流程 run_combs(serve.py)的循环结构是:外层遍历 serve_params,为每组服务器参数只启动一次服务器;内层遍历 bench_params,在同一台服务器上依次压测多个基准配置。每组基准跑完后调用 server.after_bench()——默认调用全部 /reset_*_cache 端点清空前缀缓存等状态,为下一次运行提供“干净起点”。如果你使用了自定义 --serve-cmd,可以通过 --after-bench-cmd 覆盖这个重置行为。
重要机制提示(官方文档以 important 标注):
- 同时传入
--serve-params和--bench-params时,脚本遍历二者的笛卡尔积; - 可用
--dry-run预览将要执行的全部命令而不实际运行; - 每组
--serve-params只启动一次服务器,并让它存活以支撑多个--bench-params的运行。
2.4 serve 子命令完整参数表
以下参数、默认值均取自 serve.py 的 add_cli_args:
| 参数 | 默认值 | 说明 |
|---|---|---|
--serve-cmd |
必填 | 启动服务器的命令:vllm serve ... |
--bench-cmd |
必填 | 压测命令:vllm bench serve ... |
--after-bench-cmd |
无 | 每次基准运行完成后调用该命令,替代默认的缓存重置(clear_cache()) |
--show-stdout |
关 | 打印子命令的标准输出,调试时有用但较吵 |
--server-ready-timeout |
300 | 等待服务器就绪的超时时间(秒) |
--serve-params |
无 | vllm serve 参数组合 JSON 文件(列表或字典) |
--bench-params |
无 | vllm bench serve 参数组合 JSON 文件(列表或字典) |
--link-vars |
空 | serve 与 bench 之间的联动变量,如 max_num_seqs=max_concurrency,max_model_len=random_input_len |
-o, --output-dir |
results |
结果根目录 |
-e, --experiment-name |
当前时间戳 | 实验名,结果存于 output_dir/experiment_name |
--num-runs |
3 | 每个参数组合的运行次数 |
--dry-run |
关 | 仅打印命令,不执行 |
--resume |
关 | 从上次中断处继续:只运行尚无输出文件的参数组合 |
其中 --link-vars 值得单独说明:它声明 serve 侧与 bench 侧必须相等的变量对,_comb_is_valid(serve.py)会检查每个(serve 组合, bench 组合)对,两侧任一变量缺失或值不相等的组合会被静默跳过。典型用途是把服务端的 max_num_seqs 与压测的 max_concurrency 绑定,或把 max_model_len 与 random_input_len 绑定,避免压测出非法配置。
结果文件的断点续跑能力来自对输出路径的存在性检查:run_benchmark 发现 run=N.json 已存在时直接跳过(打印 [SKIPPED BENCHMARK]),而 server_ctx 在某个 serve 组合下所有 bench 组合的 summary.json 都已存在时连服务器都不启动(_comb_needs_server 返回 False,使用空上下文)。这也是 --resume 在意外中断(例如连接 HF Hub 超时)后能“继续扫描”的原理;官方文档提示遇到此类错误时可以使用 --resume。另注意:不带 --resume 时,如果实验目录已存在会直接报错“Cannot overwrite existing experiment_dir”,防止误覆盖历史结果。
三、负载探索器:vllm bench sweep serve_workload
serve_workload 是 serve 的变体(源码上 SweepServeWorkloadArgs 直接继承 SweepServeArgs),它自动探索不同负载水平,用来定位延迟与吞吐的权衡点;结果同样可以用 第四节 的 plot 命令可视化,从而判断哪些配置能满足可行的 SLA。
负载可用请求速率或并发数表达,用 --workload-var 选择(取值 request_rate 或 max_concurrency,默认 request_rate)。
命令示例:
vllm bench sweep serve_workload \
--serve-cmd 'vllm serve meta-llama/Llama-2-7b-chat-hf' \
--bench-cmd 'vllm bench serve --model meta-llama/Llama-2-7b-chat-hf --backend vllm --endpoint /v1/completions --dataset-name sharegpt --dataset-path benchmarks/ShareGPT_V3_unfiltered_cleaned_split.json --num-prompts 100' \
--workload-var max_concurrency \
--serve-params benchmarks/serve_hparams.json \
--bench-params benchmarks/bench_hparams.json \
--num-runs 1 \
--output-dir benchmarks/results \
--experiment-name demo
3.1 负载探索算法
官方文档将算法概括为四步,源码 explore_comb_workloads(serve_workload.py)与之逐条对应:
- 串行推理(最低负载):以
max_concurrency=1逐个发请求运行一次基准,得到最低延迟与吞吐; - 批量推理(最高负载):以
max_concurrency=数据集大小一次性发出所有请求运行一次基准,得到最高延迟与吞吐; - 估计第 2 步对应的
workload_var数值; - 在中间均匀取点:用
np.linspace(serial_value, batch_value, workload_iters)[1:-1]生成中间负载值(去重取整后)依次压测。
第 3 步的估计逻辑在 _estimate_workload_value(serve_workload.py):
request_rate:直接取结果中的request_throughput(稳态下吞吐即请求到达率);max_concurrency:取request_throughput × mean_e2el_ms / 1000——这正是 Little 定律(并发数 ≈ 吞吐 × 平均时延),从批量运行的观测值反推出等效并发水平。
数据集大小(num_prompts)的确定:优先取 bench_params 组合中的 num_prompts 字段,否则从 --bench-cmd 命令行中解析 --num-prompts 值,两者都没有时使用 vllm/benchmarks/datasets 的 DEFAULT_NUM_PROMPTS。
3.2 两个专属参数
| 参数 | 默认值 | 说明 |
|---|---|---|
--workload-var |
request_rate |
每次迭代调整的变量,可选 request_rate / max_concurrency |
--workload-iters |
10 | 要探索的负载级别数(包含用于插值的前两次迭代),源码强制至少为 2 |
官方文档给出的经验提示值得照做:--workload-var max_concurrency 通常产生更可靠的结果,因为它直接控制施加到 vLLM 引擎上的负载;但为与 GuideLLM 的行为保持一致,默认仍是 --workload-var request_rate。这个子命令在功能上对应 GuideLLM 的 --profile sweep 模式。
另外源码中有一处硬校验:如果 --bench-params 的任何组合里已经手动设置了 workload_var(request_rate 或 max_concurrency),explore_combs_workloads 会直接抛错——该变量由探索器自动管理,不允许外部覆盖。
四、启动时间扫描:vllm bench sweep startup
startup 子命令对多组参数组合运行 vllm bench startup,用于比较不同引擎设置下的冷启动/热启动时间。运行步骤(与 docs/benchmarking/sweeps.md 一致):
- (可选)构造
vllm bench startup基础命令传给--startup-cmd(默认就是vllm bench startup); - (可选)复用
serve扫描的--serve-paramsJSON 来变化引擎设置,只有vllm bench startup支持的参数会被应用; - (可选)创建
--startup-paramsJSON 来变化启动专属选项(如迭代次数); - 指定
--output-dir保存结果。
示例 --serve-params(变化并行规模):
[
{
"_benchmark_name": "tp1",
"model": "Qwen/Qwen3-0.6B",
"tensor_parallel_size": 1,
"gpu_memory_utilization": 0.9
},
{
"_benchmark_name": "tp2",
"model": "Qwen/Qwen3-0.6B",
"tensor_parallel_size": 2,
"gpu_memory_utilization": 0.9
}
]
示例 --startup-params(变化冷/热迭代次数):
[
{
"_benchmark_name": "qwen3-0.6",
"num_iters_cold": 2,
"num_iters_warmup": 1,
"num_iters_warm": 2
}
]
完整命令示例:
vllm bench sweep startup \
--startup-cmd 'vllm bench startup --model Qwen/Qwen3-0.6B' \
--serve-params benchmarks/serve_hparams.json \
--startup-params benchmarks/startup_hparams.json \
--output-dir benchmarks/results \
--experiment-name demo
重要提示:--serve-params 或 --startup-params 中不支持的参数默认只打警告并忽略;用 --strict-params 可以在遇到未知键时快速失败。这个过滤逻辑由 _get_supported_startup_keys(startup.py)实现——它通过反射 vllm bench startup 的 argparse 参数表动态构建“支持键集合”,因此即使 vllm bench startup 的参数集发生变化,扫描器也能自动适配。
与 serve 的差异点(均已在源码确认):--num-runs 默认值为 1 而非 3(启动时间测量本身已含冷/热多次迭代);没有服务器进程管理,每个组合通过 subprocess.run 独立执行 --startup-cmd,并用 _apply_output_json 强制追加 --output-json <path> 把每次运行结果落到指定 JSON 文件。参数组合的笛卡尔积逻辑与 serve 相同,--dry-run / --resume / --show-stdout 行为一致。
五、结果目录结构:如何组织与复用扫描产物
从 serve.py 的路径函数 _get_comb_base_path / _get_comb_run_path(serve.py)可以看出统一的目录约定(startup 的 SERVE-/STARTUP- 前缀同理):
<output_dir>/<experiment_name>/
├── SERVE-<serve组合名>/
│ ├── BENCH-<bench组合名>/ # serve_workload 下另有 WL-<var>=<value>/ 一层
│ │ ├── run=0.json # 每次运行:bench 结果 + serve/bench 覆盖参数 + run_number
│ │ ├── run=1.json
│ │ ├── run=2.json
│ │ └── summary.json # 该组合全部 run 的数组
│ └── BENCH-<另一个组合>/...
└── summary.csv # 所有运行记录合并成的扁平表
几个关键细节:
- 每个
run=N.json是vllm bench serve --save-result的原始结果 JSON,_update_run_data会在其中合并写入该组合的 serve/bench 覆盖参数与run_number,因此单文件即可知道“这行数据是什么配置跑出来的”; - 压测命令被固定追加
--percentile-metrics ttft,tpot,itl,e2el(serve.py),保证结果里始终带有 TTFT/TPOT/ITL/E2EL 分位数指标,供后续绘图使用; summary.csv由 pandas 从所有 run 记录生成,是plot与plot_pareto的数据基础。
六、结果可视化:vllm bench sweep plot
plot 从扫描结果目录读取全部 JSON,绘制性能曲线。它接受一个位置参数 EXPERIMENT_DIR,并用以下分组变量组织图形(实现见 plot.py):
--var-x(x 轴,默认total_token_throughput)、--var-y(y 轴,默认median_ttft_ms);--fig-by:每个变量组合生成一张独立图;--row-by/--col-by:按变量拆行/拆列(子图网格);--curve-by:按变量组合拆分曲线;--filter-by:逗号分隔的过滤语句,支持==、!=、<=、>=、<、>六种运算符(注意源码按长运算符优先匹配,如max_concurrency<1000,max_num_batched_tokens<=4096),适合剔除离群点;--bin-by:分箱语句,仅支持%运算符(如request_throughput%1表示按 1 分箱),避免过密的点;--scale-x/--scale-y:坐标轴刻度,接受log、sqrt等字符串;--fig-name(默认FIGURE)、--fig-dir(相对实验目录)、--fig-height(默认 6.4 英寸)、--fig-dpi(默认 300)、--no-error-bars(默认显示误差棒,因为每个组合有多次运行)。
官方文档给出的三个 Workload Explorer 结果绘图示例(可直接复制):
EXPERIMENT_DIR=${1:-"benchmarks/results/demo"}
# Latency increases as the workload increases
vllm bench sweep plot $EXPERIMENT_DIR \
--var-x max_concurrency \
--var-y median_ttft_ms \
--col-by _benchmark_name \
--curve-by max_num_seqs,max_num_batched_tokens \
--fig-name latency_curve
# Throughput saturates as workload increases
vllm bench sweep plot $EXPERIMENT_DIR \
--var-x max_concurrency \
--var-y total_token_throughput \
--col-by _benchmark_name \
--curve-by max_num_seqs,max_num_batched_tokens \
--fig-name throughput_curve
# Tradeoff between latency and throughput
vllm bench sweep plot $EXPERIMENT_DIR \
--var-x total_token_throughput \
--var-y median_ttft_ms \
--col-by _benchmark_name \
--curve-by max_num_seqs,max_num_batched_tokens \
--fig-name latency_throughput
这三个图分别回答三类调优问题:延迟随负载上升的曲线形态、吞吐随负载趋于饱和的位置、以及在“吞吐-延迟”平面上各配置的权衡曲线。--dry-run 可以只打印将要绘制的图形信息而不真正出图,适合先核对分组是否正确。
七、帕累托前沿:vllm bench sweep plot_pareto
plot_pareto 帮助你在单用户吞吐(per-user)与单 GPU 吞吐(per-GPU)之间做平衡选择。其动机是:更高的并发/批大小能提升 GPU 利用率(per-GPU 吞吐),但会增加单用户延迟;更低并发则相反;帕累托前沿展示了所有运行中可达成的一对最好组合(实现见 plot_pareto.py 的 _pareto_frontier)。
坐标轴与输出的定义(均已在源码中核实):
- x 轴:tokens/s/user =
output_throughput÷ 并发数。并发数优先取--user-count-var(默认max_concurrency),缺失时回退request_rate,再回退观测峰值max_concurrent_requests(见_infer_user_count); - y 轴:tokens/s/GPU =
output_throughput÷ GPU 数。GPU 数优先取--gpu-count-var(如设置了);否则从结果中推断为tensor_parallel_size × pipeline_parallel_size × data_parallel_size(见_infer_gpu_count,缺省各因子为 1); - 输出:单张图,保存在
OUTPUT_DIR/pareto/PARETO.png; --label-by:在每个数据点上标注所用配置,默认max_concurrency,gpu_count。
命令示例(官方文档):
EXPERIMENT_DIR=${1:-"benchmarks/results/demo"}
vllm bench sweep plot_pareto $EXPERIMENT_DIR \
--label-by max_concurrency,tensor_parallel_size,pipeline_parallel_size
同样支持 --dry-run 预览。注意该图要求结果中存在 output_throughput 字段,否则 _get_throughput 会报错并列出可用的键名,方便排查。
八、小结与实操建议
vllm bench sweep 把“多配置压测”变成了可脚本化、可复现、可续跑的工程流程,几个基于源码确认的实操要点:
- 先
--dry-run后实跑:所有子命令都支持,先用它核对生成的完整命令序列(笛卡尔积、link-vars 过滤、参数替换结果); - 善用
--resume:serve与startup均按“输出文件是否存在”做断点续跑,长时间扫描中断后无需从头再来; - 给复杂组合命名:变量多时设置
_benchmark_name(且必须唯一),既是可读性也是路径长度的保险; - 负载探索优先用
max_concurrency:它直接约束引擎负载,结果更稳定;默认request_rate是为了对齐 GuideLLM 行为; - startup 扫描注意
--strict-params:默认静默丢弃不支持的参数(只告警),在自动化流水线中建议开启严格模式防止参数文件写错而无感知; - 绘图先看
summary.csv:plot的全部过滤/分箱/分组能力都作用于这同一份数据,先理解列名再调--filter-by/--curve-by效率最高。
相关延伸阅读:Benchmark CLI 文档 介绍 vllm bench 各基础命令,优化配置文档 说明调优的一般方法;实现代码集中在 vllm/benchmarks/sweep/(含服务器进程管理 server.py 与文件名清洗 utils.py)。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00