首页
/ vLLM 参数扫描实战:用 vllm bench sweep 完成配置调优与延迟-吞吐权衡探索

vLLM 参数扫描实战:用 vllm bench sweep 完成配置调优与延迟-吞吐权衡探索

2026-09-05 18:41:47作者:范靓好Udolf

vLLM 内置了 vllm bench sweep 参数扫描(Parameter Sweeps)工具集,它能在多组服务器/压测配置上自动运行基准测试,并把结果汇总为 CSV 与可视化曲线,帮助你在上线前完成吞吐、延迟与 SLA 的权衡决策。本文基于 vLLM 仓库中 docs/benchmarking/sweeps.md 的官方文档,结合 vllm/benchmarks/sweep/ 目录下的实际源码实现,完整覆盖 serveserve_workloadstartupplotplot_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 一致):

  1. 构造 vllm serve 基础命令,传给 --serve-cmd
  2. 构造 vllm bench serve 基础命令,传给 --bench-cmd
  3. (可选)若要改变 vllm serve 的设置,创建 JSON 参数组合文件,路径传给 --serve-params
  4. (可选)若要改变 vllm bench serve 的设置,创建 JSON 参数组合文件,路径传给 --bench-params
  5. 设置 --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.pyParameterSweep.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 变成 --flagfalse 变成 --no-flag;带 . 的嵌套布尔参数则使用 --key=true/false 形式;
  • dict 值:会被序列化为 JSON 字符串传入(适合一些复杂配置项);
  • . 的嵌套键:如 config.xxx 这类内层配置参数按前缀逐段归一化,不被 CLI 转换影响。

serve 主流程 run_combsserve.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.pyadd_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_validserve.py)会检查每个(serve 组合, bench 组合)对,两侧任一变量缺失或值不相等的组合会被静默跳过。典型用途是把服务端的 max_num_seqs 与压测的 max_concurrency 绑定,或把 max_model_lenrandom_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_workloadserve 的变体(源码上 SweepServeWorkloadArgs 直接继承 SweepServeArgs),它自动探索不同负载水平,用来定位延迟与吞吐的权衡点;结果同样可以用 第四节plot 命令可视化,从而判断哪些配置能满足可行的 SLA。

负载可用请求速率并发数表达,用 --workload-var 选择(取值 request_ratemax_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_workloadsserve_workload.py)与之逐条对应:

  1. 串行推理(最低负载):以 max_concurrency=1 逐个发请求运行一次基准,得到最低延迟与吞吐;
  2. 批量推理(最高负载):以 max_concurrency=数据集大小 一次性发出所有请求运行一次基准,得到最高延迟与吞吐;
  3. 估计第 2 步对应的 workload_var 数值
  4. 在中间均匀取点:用 np.linspace(serial_value, batch_value, workload_iters)[1:-1] 生成中间负载值(去重取整后)依次压测。

第 3 步的估计逻辑在 _estimate_workload_valueserve_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/datasetsDEFAULT_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_varrequest_ratemax_concurrency),explore_combs_workloads 会直接抛错——该变量由探索器自动管理,不允许外部覆盖。

四、启动时间扫描:vllm bench sweep startup

startup 子命令对多组参数组合运行 vllm bench startup,用于比较不同引擎设置下的冷启动/热启动时间。运行步骤(与 docs/benchmarking/sweeps.md 一致):

  1. (可选)构造 vllm bench startup 基础命令传给 --startup-cmd(默认就是 vllm bench startup);
  2. (可选)复用 serve 扫描的 --serve-params JSON 来变化引擎设置,只有 vllm bench startup 支持的参数会被应用
  3. (可选)创建 --startup-params JSON 来变化启动专属选项(如迭代次数);
  4. 指定 --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_keysstartup.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_pathserve.py)可以看出统一的目录约定(startupSERVE-/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.jsonvllm bench serve --save-result 的原始结果 JSON,_update_run_data 会在其中合并写入该组合的 serve/bench 覆盖参数与 run_number,因此单文件即可知道“这行数据是什么配置跑出来的”;
  • 压测命令被固定追加 --percentile-metrics ttft,tpot,itl,e2elserve.py),保证结果里始终带有 TTFT/TPOT/ITL/E2EL 分位数指标,供后续绘图使用;
  • summary.csv 由 pandas 从所有 run 记录生成,是 plotplot_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:坐标轴刻度,接受 logsqrt 等字符串;
  • --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 把“多配置压测”变成了可脚本化、可复现、可续跑的工程流程,几个基于源码确认的实操要点:

  1. --dry-run 后实跑:所有子命令都支持,先用它核对生成的完整命令序列(笛卡尔积、link-vars 过滤、参数替换结果);
  2. 善用 --resumeservestartup 均按“输出文件是否存在”做断点续跑,长时间扫描中断后无需从头再来;
  3. 给复杂组合命名:变量多时设置 _benchmark_name(且必须唯一),既是可读性也是路径长度的保险;
  4. 负载探索优先用 max_concurrency:它直接约束引擎负载,结果更稳定;默认 request_rate 是为了对齐 GuideLLM 行为;
  5. startup 扫描注意 --strict-params:默认静默丢弃不支持的参数(只告警),在自动化流水线中建议开启严格模式防止参数文件写错而无感知;
  6. 绘图先看 summary.csvplot 的全部过滤/分箱/分组能力都作用于这同一份数据,先理解列名再调 --filter-by / --curve-by 效率最高。

相关延伸阅读:Benchmark CLI 文档 介绍 vllm bench 各基础命令,优化配置文档 说明调优的一般方法;实现代码集中在 vllm/benchmarks/sweep/(含服务器进程管理 server.py 与文件名清洗 utils.py)。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384