vLLM auto_tune:在延迟预算下自动搜索最优服务参数的调优实战指南
vLLM 仓库内置了一套自动调参脚本(位于 benchmarks/auto_tune/),用于在给定输入/输出长度、P99 端到端延迟上限和(可选的)前缀缓存命中率约束下,自动搜索 max-num-seqs 与 max-num-batched-tokens 的最优组合,使服务吞吐量最大化。本文基于官方文档与脚本源码,完整讲解这套自动调优工具的配置变量、运行方式、输出解读,以及其背后的"OOM 安全内存搜索 + 延迟感知吞吐搜索 + 最优配置 Profiling"三段式算法流程,读完即可在自己的 GPU/TPU 集群上直接复现。
一、auto_tune 要解决什么问题
vLLM 服务端的两个核心参数——max-num-seqs(单步最多并发序列数)和 max-num-batched-tokens(单步最多批处理 token 数)——直接决定吞吐与延迟的平衡点:
- 两者设得过大:吞吐上限提高,但 P99 端到端延迟(E2E Latency)恶化,可能无法满足 SLA;
- 两者设得过小:延迟轻松达标,但硬件利用率不足,吞吐偏低。
auto_tune.sh 把"人肉改参数 → 起服务 → 跑压测 → 看结果"的循环自动化:它在给定参数空间内枚举每一个组合,对每个组合先用无穷请求率(--request-rate inf)压出极限吞吐与 P99 E2E 延迟;若不满足延迟约束,则从极限吞吐点开始逐步降低请求速率,找到该配置下"满足延迟约束的最高可持续吞吐"。此外它还支持 MIN_CACHE_HIT_PCT(前缀缓存命中率下限)约束,用于模拟多轮对话、Agent 等大量前缀复用的真实场景。
仓库内共涉及三个文件:
| 文件 | 作用 |
|---|---|
| benchmarks/auto_tune/README.md | 使用文档(本文主要依据) |
| benchmarks/auto_tune/auto_tune.sh | 单轮自动调优主脚本 |
| benchmarks/auto_tune/batch_auto_tune.sh | 批量调优:按 JSON 配置文件顺序执行多组实验并汇总 |
二、前置条件(Prerequisites)
按 README 的要求,运行前需要完成:
-
获取 vLLM 源码并切到目标分支:将 vLLM 仓库克隆到本地(当前仓库即是),进入
vllm目录并切到要调优的分支:git clone <vLLM仓库地址> cd vllm # git checkout <your-branch> -
安装运行环境:安装或更新对应的推理环境;如果使用 TPU,需要激活
conda环境并安装匹配的torch与torch_xla版本。 -
模型配置就位:如果使用自定义模型,确保其配置文件放置正确且可访问。
-
隐式依赖(从 auto_tune.sh 源码可见):脚本执行
pip install -q datasets自动安装数据集依赖;压测解析依赖bc(浮点比较)与curl(健康检查);批量脚本依赖jq。脚本内部还通过git rev-parse HEAD记录当前提交 hash,因此需要在 git 仓库内运行。
三、配置变量(Configuration)
所有变量既可以在 auto_tune.sh 顶部直接修改,也可以在运行时用环境变量覆盖(脚本对每个变量都写了 VAR=${VAR:-默认值} 形式的默认值)。官方给出的典型调用方式是:
MODEL=meta-llama/Llama-3.3-70B-Instruct SYSTEM=TPU TP=8 DOWNLOAD_DIR='' \
INPUT_LEN=128 OUTPUT_LEN=2048 MAX_MODEL_LEN=2300 MIN_CACHE_HIT_PCT=0 \
MAX_LATENCY_ALLOWED_MS=100000000000 NUM_SEQS_LIST="128 256" \
NUM_BATCHED_TOKENS_LIST="1024 2048 4096" VLLM_LOGGING_LEVEL=DEBUG bash auto_tune.sh
完整变量说明(默认值取自脚本源码,与 README 配置表对应):
| 变量 | 说明 | 示例值 | 脚本默认值 |
|---|---|---|---|
BASE |
必填。 vLLM 仓库目录的父目录绝对路径(脚本会 cd "$BASE/vllm" 并在 $BASE/auto-benchmark/ 下落日志) |
"$HOME" |
脚本相对路径 ../../.. |
MODEL |
必填。 vLLM 要服务的 Hugging Face 模型标识符 | "meta-llama/Llama-3.1-8B-Instruct" |
同左 |
SYSTEM |
必填。 硬件平台,TPU 或 GPU(其他硬件可能不支持保存 profile) |
"TPU" |
TPU |
TP |
必填。 张量并行(tensor-parallel)规模 | 1 |
1 |
DOWNLOAD_DIR |
必填。 模型权重的下载/加载目录 | ""(默认下载路径) |
"" |
INPUT_LEN |
必填。 请求输入长度(token) | 4000 |
4000 |
OUTPUT_LEN |
必填。 请求输出长度(token) | 16 |
16 |
MAX_MODEL_LEN |
必填。 模型最大上下文长度 | 4096 |
4096 |
MIN_CACHE_HIT_PCT |
前缀缓存命中率约束(0–100,百分比),设为 0 表示不启用 |
60 |
0 |
MAX_LATENCY_ALLOWED_MS |
允许的最大 P99 端到端延迟(毫秒);设为极大值(如 100000000000)等效于忽略延迟约束 |
500 |
100000000000 |
NUM_SEQS_LIST |
空格分隔的 max-num-seqs 候选值列表 |
"128 256" |
"128 256" |
NUM_BATCHED_TOKENS_LIST |
空格分隔的 max-num-batched-tokens 候选值列表 |
"1024 2048 4096" |
"512 1024 2048 4096" |
两个重要提示(来自 README 与脚本预检逻辑):
- 长度预检:脚本启动时会校验
INPUT_LEN + OUTPUT_LEN <= MAX_MODEL_LEN,不满足直接以红色报错退出(见 auto_tune.sh),不需要等到起服务才发现问题。 - 候选列表按场景调整:默认的
NUM_SEQS_LIST/NUM_BATCHED_TOKENS_LIST面向中等长度的输入输出场景。对于极短上下文(例如 20 输入 / 20 输出 token),可能需要把max-num-seqs的候选值调得更大。
四、如何运行(How to Run)
-
配置:编辑脚本或按上节用环境变量设置参数。
-
执行:由于全程耗时较长,官方强烈建议在
tmux或screen中运行,防止断连导致实验中断:cd <FOLDER_OF_THIS_SCRIPT> bash auto_tune.sh这里
<FOLDER_OF_THIS_SCRIPT>即 benchmarks/auto_tune/ 目录。
关键陷阱:README 特别警告,运行命令(包括所在路径)中不能包含关键字 vllm(完整或部分均可),因为脚本内部通过 pkill -if "vllm serve" 清理上一轮的服务进程,若本脚本自身路径匹配到 vllm 关键字,pkill 会把正在执行的调优脚本一并杀掉。从 auto_tune.sh 可以看到清理动作出现在 start_server() 每次启动服务前、run_benchmark() 开始前和结束后,因此这一约束贯穿整个流程。
五、典型使用案例(Example Use Cases)
README 给出了三种不同目标的配置示例,全部基于 1800 输入 / 20 输出的短请求场景(MAX_MODEL_LEN=2048):
案例 1:最大化吞吐(无延迟约束)
目标:找出让 1800 输入 / 20 输出吞吐最高的参数组合。
INPUT_LEN=1800
OUTPUT_LEN=20
MAX_MODEL_LEN=2048
MIN_CACHE_HIT_PCT=0
MAX_LATENCY_ALLOWED_MS=100000000000 # 极大数,等效关闭延迟约束
案例 2:带延迟要求的最大化吞吐
目标:在 P99 E2E 延迟必须低于 500ms 的前提下最大化吞吐。
INPUT_LEN=1800
OUTPUT_LEN=20
MAX_MODEL_LEN=2048
MIN_CACHE_HIT_PCT=0
MAX_LATENCY_ALLOWED_MS=500
案例 3:前缀缓存命中率 + 延迟双约束
目标:假设业务有 60% 前缀缓存命中率,且 P99 E2E 延迟要求 500ms,找最优参数。
INPUT_LEN=1800
OUTPUT_LEN=20
MAX_MODEL_LEN=2048
MIN_CACHE_HIT_PCT=60
MAX_LATENCY_ALLOWED_MS=500
六、算法原理详解(How It Works,结合源码逐段解析)
README 概括了五个阶段:确定最大安全 gpu-memory-utilization → 枚举压测 → 延迟感知吞吐搜索 → 跟踪最优结果 → 最优配置 Profiling。下面结合 auto_tune.sh 的实现逐段展开。
6.1 第一步:OOM 安全的 gpu-memory-utilization 搜索
从 auto_tune.sh 可以看到:脚本从 gpu_memory_utilization=0.98 起步,以 0.01 为步长向下递减(直到 0.9 为止),每次用候选列表中最大的 max-num-seqs 和 max-num-batched-tokens 尝试启动服务——这是整个参数空间里显存压力最大的组合。一旦某个利用率能成功起服务,就固定该值用于后续所有压测;如果 0.9 仍起不来,则报错退出。这样保证后续所有 benchmark 都在"不 OOM 的最高显存利用率"下运行,结果最具可比性。
6.2 服务启动与健康检查
start_server()(auto_tune.sh)的核心逻辑:
-
先
pkill -if "vllm serve"清理残留服务,然后以统一参数数组后台启动:VLLM_SERVER_DEV_MODE=1 \ vllm serve "$MODEL" \ --port 8004 --host "$HOSTNAME" \ --gpu-memory-utilization <util> \ --max-num-seqs <seqs> --max-num-batched-tokens <tokens> \ --tensor-parallel-size "$TP" \ --enable-prefix-caching \ --load-format dummy \ --download-dir "$DOWNLOAD_DIR" \ --max-model-len "$MAX_MODEL_LEN"注意两个实现细节:
--load-format dummy表示使用随机权重(调参阶段只关心调度/吞吐行为,跳过真实权重加载可显著加快每轮启动);--enable-prefix-caching常驻开启,为案例 3 的命中率约束提供基础。 -
启动后进入健康检查循环:每 10 秒请求一次
http://<host>:8004/health,同时用kill -0判断进程是否已崩溃,最多等待 60 次(10 分钟),超时即视为启动失败并返回错误码 1,由上层记录到对应vllm_log_*.txt。 -
VLLM_SERVER_DEV_MODE=1并非摆设:它用于开启开发用端点。从 routers.py 可以看到,只有当该环境变量为真时才会注册 dev 路由(register_vllm_dev_api_routers),其中就包含下一节压测循环依赖的POST /reset_prefix_cache(定义于 cache/api_router.py)。该端点仅在 dev mode 下暴露,官方文档 security.md 明确提示不应在生产环境开启。
6.3 第二步/第三步:延迟感知的吞吐搜索
run_benchmark()(auto_tune.sh)对每个参数组合执行两轮压测,底层命令是 vllm bench serve(对应实现为 vllm/benchmarks/serve.py):
前缀命中率模拟:MIN_CACHE_HIT_PCT 不是直接"要求缓存命中率达标",而是通过构造带公共前缀的随机数据集来模拟命中率。脚本计算 prefix_len = INPUT_LEN * MIN_CACHE_HIT_PCT / 100,令 adjusted_input_len = INPUT_LEN - prefix_len,压测时传 --random-prefix-len $prefix_len(每条请求共享该长度的公共前缀)和缩短后的 --random-input-len。命中率越高,前缀 prefill 复用越多,等效计算量越小。
第一轮:无穷请求率探极限(1000 条 prompt):
vllm bench serve \
--backend vllm --model "$MODEL" --dataset-name random \
--random-input-len $adjusted_input_len --random-output-len "$OUTPUT_LEN" \
--ignore-eos --disable-tqdm \
--request-rate inf \
--percentile-metrics ttft,tpot,itl,e2el \
--goodput e2el:"$MAX_LATENCY_ALLOWED_MS" \
--num-prompts 1000 \
--random-prefix-len $prefix_len \
--host "$HOSTNAME" --port 8004
脚本从日志里 grep 出三项指标:Request throughput (req/s)、P99 E2EL (ms)、Request goodput (req/s)。goodput 由 vllm bench serve 的 --goodput 参数计算(解析逻辑见 serve.py 的 parse_goodput,按 DistServe 论文定义统计满足 SLO 的请求速率)。
- 若
P99 E2EL <= MAX_LATENCY_ALLOWED_MS:该配置下极限吞吐即为有效吞吐,request_rate=inf,直接进入最优结果更新; - 若延迟超标:进入降速搜索循环——从
int(throughput) + 1开始,每轮把请求速率减 1,重新压测(此时改用 100 条 prompt 降低成本),直到延迟达标或速率降到 0。每个速率档之间都会先curl -X POST http://<host>:8004/reset_prefix_cache清空前缀缓存并sleep 5,保证各轮起点一致(这正是 6.2 节 dev mode 端点的用途)。
结果记录:每个组合的结果(无论达标与否)都追加写入 result.txt。达标组合中吞吐更高者会刷新 best_throughput / best_max_num_seqs / best_num_batched_tokens / best_goodput / best_request_rate 五个全局最优变量(auto_tune.sh)。
双层循环:主流程把 NUM_SEQS_LIST 与 NUM_BATCHED_TOKENS_LIST 解析为两个 bash 数组后做嵌套遍历(auto_tune.sh),即候选值数量的乘积决定总轮数——例如默认配置下 2×4=8 轮完整压测,每轮还包含可能的多档降速,因此务必使用 tmux。
6.4 第四步:最优配置的 Profiling 采集
压测全部结束后,只要找到了有效最优配置(best_throughput > 0),脚本会用最优的 max-num-seqs / max-num-batched-tokens 带 Profiling 重新起一次服务(auto_tune.sh):
- 服务端通过
--profiler-config '{"profiler": "torch", "torch_profiler_dir": "<profile_dir>"}'启用 torch profiler(对应 vllm/config/profiler.py 的ProfilerConfig,服务端侧参数解析见 arg_utils.py 中的--profiler-config); - 压测端在同一最优请求速率下追加
--profile标志触发采集; - trace 落盘到
$BASE/auto-benchmark/<时间戳>/profile/(GPU 上为.jsontrace,TPU 上为.xplane.pb),可用于 TensorBoard 等工具做逐算子级深挖。
如果没有任何配置满足延迟约束,脚本跳过 Profiling 并如实记录"未找到可行配置"。
七、输出物解读(Output)
脚本结束后,所有产物位于 $BASE/auto-benchmark/YYYY_MM_DD_HH_MM/(时间戳目录,同名目录会先被删除重建):
| 产物 | 说明 |
|---|---|
vllm_log_<seqs>_<tokens>.txt(及 vllm_log_gpu_memory_utilization_*.log、vllm_log_BEST_PROFILE.txt) |
每个参数组合的服务端日志 |
bm_log_<seqs>_<tokens>_requestrate_inf.txt / ..._requestrate_<rate>.txt |
每轮 vllm bench serve 的压测日志(含全部 TTFT/TPOT/ITL/E2EL 百分位与 goodput) |
result.txt |
结果汇总,首行为 git hash,随后逐组合记录,末行为全局最优 |
profile/ |
最优 run 的 profiler trace 目录 |
result.txt 的样例(来自 README):
# Example result.txt content
hash:a1b2c3d4...
max_num_seqs: 128, max_num_batched_tokens: 2048, request_rate: 10.0, e2el: 450.5, throughput: 9.8, goodput: 9.8
max_num_seqs: 128, max_num_batched_tokens: 4096 does not meet latency requirement 500
...
best_max_num_seqs: 256, best_num_batched_tokens: 2048, best_throughput: 12.5, profile saved in: /home/user/vllm/auto-benchmark/2024_08_01_10_30/profile
若末行是 best_max_num_seqs: 0, best_num_batched_tokens: 0, best_throughput: 0,说明没有找到可行参数——通常是服务根本没起来(检查对应 vllm_log_*.txt),或延迟约束过严(所有组合在速率降到 0 之前都未达标)。
八、批量调优:batch_auto_tune.sh
batch_auto_tune.sh 支持用单个 JSON 配置文件顺序跑多组 auto_tune.sh 实验,并把结果写回同一文件,适合做跨模型/跨场景的系统性调参矩阵。
前置依赖:必须安装 jq(解析 JSON);如需上传 GCS,还需安装并完成认证的 gcloud CLI(脚本会对这两者做启动检查,见 batch_auto_tune.sh)。
运行方式:
bash batch_auto_tune.sh <path_to_json_file> [gcs_upload_path]
<path_to_json_file>:必填,JSON 配置文件路径;[gcs_upload_path]:可选,形如gs://my-bucket/benchmark-results的 GCS 路径,每个 run 的日志与 profile 会通过gcloud storage rsync上传到<gcs_upload_path>/<run_id>;留空则结果只留在本地文件系统(具体位置见该轮输出的RESULT_FILE=...行)。
配置文件格式:一个 JSON 对象数组,每个对象的 key 对应 auto_tune.sh 的配置变量(小写,运行时被转换为大写环境变量注入子进程),示例来自 README:
[
{
"base": "/home/user",
"model": "meta-llama/Llama-3.1-8B-Instruct",
"system": "TPU",
"tp": 8,
"input_len": 128,
"output_len": 2048,
"max_model_len": 2300,
"num_seqs_list": "128 256",
"num_batched_tokens_list": "8192 16384"
},
{
"base": "/home/user",
"model": "meta-llama/Llama-3.1-70B-Instruct",
"system": "TPU",
"tp": 8,
"input_len": 4000,
"output_len": 16,
"max_model_len": 4096,
"num_seqs_list": "64 128",
"num_batched_tokens_list": "4096 8192",
"max_latency_allowed_ms": 500
}
]
结果回写:脚本以原地修改(in-place)方式把每轮结果写回输入 JSON 的对应对象,新增字段包括:
run_id:由时间戳目录名派生的唯一标识;status:SUCCESS、FAILURE或WARNING_NO_RESULT_FILE(进程成功但找不到 result.txt);results:该轮result.txt的完整内容;gcs_results:产物在 GCS 上的 URL(若提供了 GCS 路径)。
实现上,脚本用 jq 遍历数组(batch_auto_tune.sh),每轮结束即把中间进度落盘(.tmp 原子替换),因此中途中断也能保留已完成轮次的结果;全部结束后打印成功/失败计数汇总,并列出失败轮次的序号与参数。
九、实战注意事项
结合文档与源码,以下细节直接影响调优结果的有效性:
- dummy 权重:服务以
--load-format dummy启动,即随机权重。这对"调度参数对吞吐/延迟的影响"没有影响,但如果你的模型依赖特定量化/硬件路径,结果需要结合真实权重再复核一次。 - 端口固定为 8004:确保该端口空闲;脚本通过
--host $HOSTNAME绑定本机主机名,不要同时手动跑其他vllm serve占用它。 - 路径避开
vllm关键字:如 6.2 节所述,pkill -if "vllm serve"的匹配范围是整条命令行,脚本自身工作目录/调用命令含vllm字样有被误杀风险。 - 10 分钟启动超时:大模型冷启动(下载权重、TP 初始化)超过 10 分钟会被判为启动失败,建议先用
DOWNLOAD_DIR预拉权重。 - 延迟搜索的成本:延迟约束越严,每个组合的降速循环轮数越多(每档 100 条 prompt 的完整压测)。设置
MAX_LATENCY_ALLOWED_MS时先粗估 SLO,避免候选值过多导致总时长失控;批量实验优先用batch_auto_tune.sh并配合tmux。 - 短请求要放大
NUM_SEQS_LIST:默认候选列表面向中等长度场景(如 4000/16),若跑 20/20 这类极短请求,应把max-num-seqs候选放大(README 已明确提示)。 - 结果可追溯:
result.txt首行记录 git hash,跨版本对比调参结论时务必核对这一行,确保参数结论对应同一份代码。
十、小结
benchmarks/auto_tune/ 提供了一条从"参数候选空间"到"最优服务端配置 + 可深挖 profile"的完整自动化路径:auto_tune.sh 负责单模型单场景的 OOM 安全内存探测、延迟感知吞吐搜索与最优配置 Profiling;batch_auto_tune.sh 则以 JSON 驱动批量实验并回写结果,支持 GCS 归档。两者的配置变量、命令行示例与输出格式在 README 中均有完整说明,配合本文的源码级解析,即可在自有集群上快速复现并扩展这套调优流程。
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