vLLM 性能基准测试体系:latency / throughput / serving 三类测试用例、执行脚本与结果发布机制详解
本文基于 vLLM 仓库的 Buildkite 性能基准描述文件 performance-benchmarks-descriptions.md 展开,完整讲解三类基准测试(延迟、吞吐、在线服务)的输入长度、批量策略与评估指标定义,以及这份"模板文档"如何被 convert-results-json-to-markdown.py 填充为真实结果表并发布到 CI。读完你能理解:vLLM 的 PR 级性能回归检测是如何定义测试工况、如何采集结果、如何转成 Markdown 表格和可复用的 JSON 数据,以及如何在本地复现同一套基准流程。
1. 概述:这套基准测试测什么
vLLM 在 .buildkite/performance-benchmarks/ 目录下维护了一套面向开发者的基准测试套件,用于判断某个 PR 是否提升或劣化了引擎性能。核心组成:
| 组件 | 相对路径 | 作用 |
|---|---|---|
| 基准描述模板 | .buildkite/performance-benchmarks/performance-benchmarks-descriptions.md | 定义 latency / throughput / serving 三类测试的工况与指标,内含待填充的 Markdown 表格占位符 |
| 总执行脚本 | .buildkite/performance-benchmarks/scripts/run-performance-benchmarks.sh | 检测硬件、下载数据集、依次执行各测试、后处理并上传 Buildkite |
| 测试用例 JSON | .buildkite/performance-benchmarks/tests/ 下的 latency-tests.json、throughput-tests.json、serving-tests.json 等 |
以 JSON 声明每个测试用例的参数 |
| 结果转换脚本 | .buildkite/performance-benchmarks/scripts/convert-results-json-to-markdown.py | 把 JSON 结果格式化进描述模板,生成 benchmark_results.md 与 benchmark_results.json |
| 结果对比脚本 | .buildkite/performance-benchmarks/scripts/compare-json-results.py | 对比本次与基线结果 |
覆盖的硬件平台(见 README.md)包括 B200、A100、H100、Intel Xeon 处理器、Intel Gaudi 3 加速器与 Arm Neoverse,完整一轮基准大约需要 1 小时,README 也建议开发者自行新增用例时把时长控制在这一量级。
2. 延迟测试(Latency tests):固定工况下的端到端延迟
描述文档中 latency 一节给出的标准工况:
- 输入长度:32 tokens;
- 输出长度:128 tokens;
- 批大小:固定 8;
- GPU/HPU 模型:llama-3.1 8B、llama-3 70B、mixtral 8x7B;
- CPU 模型:llama-3.1 8B;
- 评估指标:端到端延迟(mean、median、p99)。
文档中 {latency_tests_markdown_table} 占位符在结果生成后会被真实表格替换。表格各列并非凭空约定,而是由转换脚本中的列映射固定下来(见 convert-results-json-to-markdown.py):
Test name:测试名(取自 JSON 的test_name);GPU:硬件型号,多卡时会归一化为8xGPU型号形式(脚本在生成表格前会把nvidia-smi输出里按行重复的型号折叠成Nx型号,见 脚本第 370-374 行);Mean latency (ms)、Median latency (ms)、P99 latency (ms):原始结果以秒为单位,脚本把avg_latency以及 P10/P25/P50/P75/P90/P99 各分位数统一乘以 1000 转成毫秒(见 脚本第 280-285 行),表中只保留 Mean、Median(P50)、P99 三列。
对应测试用例定义在 latency-tests.json,每个用例形如:
{
"test_name": "latency_llama8B_tp1",
"parameters": {
"model": "meta-llama/Meta-Llama-3.1-8B-Instruct",
"tensor_parallel_size": 1,
"load_format": "dummy",
"num_iters_warmup": 5,
"num_iters": 15
}
}
parameters 中的每个键值对会被执行脚本 json2args 转成 vllm bench latency 的命令行参数,下划线自动替换为短横线(见 run-performance-benchmarks.sh 第 92-106 行),上例最终执行为 --model meta-llama/Meta-Llama-3.1-8B-Instruct --tensor-parallel-size 1 --load-format dummy --num-iters-warmup 5 --num-iters 15。默认文件中的三条用例分别覆盖 8B/TP1、70B/TP4、Mixtral-8x7B/TP2。README 特别提醒:延迟数字对参数取值高度敏感,且脚本会自行保存 JSON 结果,不要在用例 JSON 里再配置 --output-json,以免与脚本的 --output-json $RESULTS_FOLDER/${test_name}.json 冲突(见 run-performance-benchmarks.sh 第 536-538 行)。
3. 吞吐测试(Throughput tests):ShareGPT 真实分布下的极限吞吐
描述文档中 throughput 一节的工况:
- 输入长度:从 ShareGPT 数据集随机采样 200 条 prompt(固定随机种子);
- 输出长度:这 200 条 prompt 对应的输出长度;
- 批大小:由 vLLM 动态决定以达成最大吞吐;
- GPU/HPU 模型:llama-3.1 8B、llama-3 70B、mixtral 8x7B;
- CPU 模型:llama-3.1 8B;
- 评估指标:吞吐(throughput)。
用例定义在 throughput-tests.json,参数直接透传给 vllm bench throughput,例如:
{
"test_name": "throughput_llama8B_tp1",
"parameters": {
"model": "meta-llama/Meta-Llama-3.1-8B-Instruct",
"tensor_parallel_size": 1,
"load_format": "dummy",
"dataset": "./ShareGPT_V3_unfiltered_cleaned_split.json",
"num_prompts": 200,
"backend": "vllm"
}
}
ShareGPT 数据集由执行脚本在运行前自动下载(ensure_sharegpt_downloaded,从 Hugging Face 拉取 ShareGPT_V3_unfiltered_cleaned_split.json,见 脚本第 83-90 行)。与延迟测试的固定批量不同,吞吐测试让引擎自行攒批到极限,因此 README 指出该数字同样"稳定但敏感"——微小的工况变化都可能引起较大波动。
吞吐结果表的列映射(见 convert-results-json-to-markdown.py 第 33-41 行)包括:Test name、GPU、# of req.、Total # of tokens、Elapsed time (s)、Tput (req/s)、Tput (tok/s),正好对应"请求数、总 token 数、耗时、每秒请求数、每秒 token 数"这组吞吐口径。
4. 服务测试(Serving tests):QPS、到达过程与 TTFT/ITL 指标
描述文档中 serving 一节的定义:
- 输入/输出长度:同样从 ShareGPT 采样 200 条 prompt(固定随机种子)及其对应输出长度;
- 批大小:由 vLLM 与请求到达模式共同动态决定;
- 平均 QPS(query per second):取 1、4、16 与 inf。
QPS = inf表示所有请求一次性到达;其他 QPS 值下每条查询的到达时间由(固定随机种子的)泊松过程决定; - GPU/HPU 模型:llama-3.1 8B、llama-3 70B、mixtral 8x7B;另外在 GPU 上为 llama-3 70B 增加了 QPS 2 下的推测解码(speculative decoding)测试;
- CPU 模型:llama-3.1 8B,并额外增加随机数据集测试,以 100 条 prompt 固定输入/输出长度来压测;
- 评估指标:吞吐、TTFT(首 token 延迟,mean/median/p99)、ITL(inter-token latency,mean/median/p99)。
{serving_tests_markdown_table} 的列由 convert-results-json-to-markdown.py 第 45-74 行 的映射定义,覆盖面很广:Model、Dataset Name、Input Len、Output Len、TP Size、PP Size、dtype、GPU、# of req.、qps、# of max concurrency.、Tput (req/s)、Total Token Tput (tok/s)、Output Tput (tok/s),以及 Mean/Median/P99/STD 四组 TTFT 和 Mean/Median/P99 三组 TPOT/ITL 列。
4.1 serving-tests.json 的 defaults + tests 结构
当前默认的用例文件 serving-tests.json 采用"默认参数 + 用例覆盖"结构:顶层 defaults 对全部 serving 测试全局生效,单条 tests 内可覆盖。摘录其骨架:
{
"defaults": {
"qps_list": ["inf"],
"max_concurrency_list": [12, 16, 24, 32, 64, 128, 200],
"server_parameters": {
"model": "meta-llama/Llama-3.1-8B-Instruct",
"tensor_parallel_size": 1,
"dtype": "bfloat16"
},
"client_parameters": {
"model": "meta-llama/Llama-3.1-8B-Instruct",
"backend": "vllm",
"ignore-eos": "",
"temperature": 0,
"num_prompts": 200
}
},
"tests": [
{
"test_name": "serving_llama8B_tp1_sharegpt",
"server_parameters": { "tensor_parallel_size": 1 },
"client_parameters": {
"dataset_name": "sharegpt",
"dataset_path": "./ShareGPT_V3_unfiltered_cleaned_split.json"
}
},
{
"test_name": "serving_llama8B_tp1_random_2048_128",
"client_parameters": {
"dataset_name": "random",
"random-input-len": 2048,
"random-output-len": 128
}
}
]
}
tests 中的其余用例覆盖了 random 数据集的四种长度组合(128/128、128/2048、2048/128、2048/2048)、Llama-3.3-70B TP4(附带 async_scheduling、no_enable_prefix_caching、max_num_batched_tokens=8192 等服务器开关)等。注意 qps_list 与 max_concurrency_list 来自 defaults,脚本会先把二者合并进每个用例,再以 --request-rate 与 --max-concurrency 传给客户端(合并逻辑见 run-performance-benchmarks.sh 第 566-606 行 的 merge_serving_tests_stream;qps 遍历见 第 770-799 行)。
执行流程是:先用 server_parameters(model 作为位置参数)拉起 vllm serve,轮询 http://localhost:8000/v1/models 等待就绪(最多 1200 秒),再运行:
vllm bench serve \
--save-result \
--result-dir results/ \
--result-filename ${test_name}_qps_${qps}_concurrency_${max_concurrency}.json \
--request-rate $qps \
--max-concurrency $max_concurrency \
--metadata tensor_parallel_size=$tp \
<client_parameters 转换来的参数>
因此 README 的警告是:serving 用例 JSON 中不要配置 --save-results 之类的结果保存参数,保存动作由脚本统一完成。与 latency/throughput 相比,serving 数字受 ShareGPT 随机采样影响波动更大(README 原文:该测试数字不如延迟/延迟基准稳定,但 5% 量级的变化仍会引起明显差异)。脚本还支持通过 PROMPTS_PER_CONCURRENCY 环境变量按 max_concurrency 动态放大 --num-prompts,以及 REMOTE_HOST/REMOTE_PORT 对远端已有 vLLM 服务直接压测(见 脚本第 667-699 行 与 第 740-763 行)。
5. 结果发布:模板占位符如何被填充
描述文档本质是一个带 5 个占位符的模板,转换脚本在结尾读取它并逐一填充(见 convert-results-json-to-markdown.py 第 390-405 行):
| 占位符 | 填充内容 |
|---|---|
{latency_tests_markdown_table} |
延迟结果表(tabulate 的 pipe 格式) |
{throughput_tests_markdown_table} |
吞吐结果表 |
{serving_tests_markdown_table} |
服务结果表 |
{platform_markdown_table} |
平台信息表 |
{benchmarking_results_in_json_string} |
三张结果表的合并 JSON 字符串 |
脚本按文件名把 results/ 下的 JSON 分流为三类:
- serving:额外读取同名
.commands文件(记录本次实际执行的 server/client 命令),从中解析--tensor-parallel-size、--pipeline-parallel-size、--dtype与--dataset-name、--random-input-len、--random-output-len、--request-rate,补全 TP/PP/dtype/数据集/QPS 等表格列(解析函数parse_client_command见 脚本第 133-191 行); - latency:从
percentiles字段取 P10-P99 并换算为毫秒; - throughput:直接附加命令元数据。
之后用 tabulate 渲染成 Markdown 表格,按 Test name 排序,写入 results/benchmark_results.md 与 results/benchmark_results.json。在 Buildkite 环境中,执行脚本最后调用 upload_to_buildkite:buildkite-agent annotate 把 benchmark_results.md 贴到 job 页面,artifact upload 上传 results/ 全部内容(见 run-performance-benchmarks.sh 第 174-191 行);没有 agent 时静默跳过,本地运行时流程依然完整。
6. JSON 版本数据与 pandas 加载方式
描述文档专门保留了一节"json version of the benchmarking tables",说明表格数据同时以 JSON 形式内嵌在 Markdown 文件中,可直接载入 pandas:
import json
import pandas as pd
benchmarking_results_json = """The json string"""
benchmarking_results = json.loads(benchmarking_results_json)
latency_results = pd.DataFrame.from_dict(benchmarking_results["latency"])
throughput_results = pd.DataFrame.from_dict(benchmarking_results["throughput"])
serving_results = pd.DataFrame.from_dict(benchmarking_results["serving"])
这里的 JSON 字符串即模板中 {benchmarking_results_in_json_string} 占位符被替换后的内容,其顶层键为 latency、throughput、serving,与各表 DataFrame 的 to_dict() 结果一一对应(序列化逻辑见 results_to_json)。文档同时提示:原始实验数据(每个用例的 JSON 结果文件)可以在 Buildkite 页面的 Artifact 标签页中查看。这意味着做性能趋势分析、跨版本对比时无需解析 Markdown 文本,直接消费 JSON 即可。
7. 平台信息表(Platform Information)
描述文档的 {platform_markdown_table} 占位符由脚本采集的机器信息填充(见 convert-results-json-to-markdown.py 第 317-336 行):
- Physical cores / Total cores:
psutil.cpu_count()分别取物理核与逻辑核数; - Total Memory:
psutil.virtual_memory().total换算成 KB/MB/GB 单位; - Total NUMA nodes:在安装了
numa包时通过numa.info.get_num_configured_nodes()获取; - CPU Brand:在安装了
cpuinfo包时读取brand_raw。
这套信息解释了描述文档中"CPU Models"与 GPU/HPU 并存的原因:执行脚本通过 ON_CPU=1 走 check_cpus(依赖 NUMA 节点数而非 GPU 数),并按 uname -m 区分 cpu 与 arm64-cpu 后缀,自动选用 latency-tests-cpu.json、throughput-tests-cpu.json、serving-tests-cpu.json(或 arm64 对应版本);Gaudi 3 则通过 hl-smi 识别并附加 -hpu 后缀(见 run-performance-benchmarks.sh 第 52-68 行 与 第 836-886 行)。
8. 本地复现:触发方式与环境变量
整套基准需要在仓库根目录手动触发:
bash .buildkite/performance-benchmarks/scripts/run-performance-benchmarks.sh
运行前提:HF_TOKEN 必须设置且以 hf_ 开头(脚本会校验,check_hf_token),依赖 wget、curl、jq、lsof(缺失时会尝试 apt-get 安装),GPU 场景要求至少 1 块可见 GPU,CPU 场景要求至少 1 个 NUMA 节点。主要环境变量(README 列出的核心项):
| 变量 | 默认值 | 说明 |
|---|---|---|
ON_CPU |
0 | 在 Intel Xeon 与 Arm Neoverse 上设为 1 |
SERVING_JSON / LATENCY_JSON / THROUGHPUT_JSON |
空(用默认文件) | 指定对应的测试用例 JSON 文件名 |
REMOTE_HOST / REMOTE_PORT |
空 | 对远端已有 vLLM 服务压测 |
TEST_SELECTOR |
空 | 只运行 test_name 匹配该正则的用例 |
DRY_RUN |
0 | 为 1 时只打印命令不执行(可配合 MODEL_FILTER、DTYPE_FILTER 过滤) |
执行顺序为:先 serving(启动真实服务器),再 latency、startup、throughput;结束后 pip install tabulate pandas,运行转换脚本生成 benchmark_results.md/json,再运行 compare-json-results.py 与基线对比。需要留意:serving 用例中 GPU/HPU 的 70B 用例需要 4 卡(TP4),8 卡以下机器上 TP4 的 70B 用例会被脚本以"GPU 数量不足"自动跳过(见 run-performance-benchmarks.sh 第 720-725 行);load_format: "dummy" 表示用随机权重压测引擎性能而非具体模型精度,因此这套数字只应解读为引擎层面的性能回归信号,不代表该模型的端到端真实服务质量。
9. 小结
- 描述文档定义了"测什么":latency 固定 32 输入/128 输出/batch 8,报 mean/median/p99 端到端延迟;throughput 用 200 条 ShareGPT prompt 让引擎自攒批冲极限吞吐;serving 用 QPS 1/4/16/inf(泊松到达或一次性灌入)测吞吐、TTFT 与 ITL 的 mean/median/p99;
- 执行脚本定义了"怎么测":JSON 用例经
json2args转成vllm bench latency|throughput参数,serving 测试则由脚本拉起vllm serve并以vllm bench serve --request-rate --max-concurrency压测; - 转换脚本定义了"怎么读":结果按列映射渲染为 Markdown 表格回填占位符,同时输出可被 pandas 直接加载的 JSON,原始数据以 Artifact 形式留存;
- 想评估自己 PR 的性能影响,最稳妥的做法是用同一组测试 JSON、同一硬件与同一
load_format跑前后两次,再用 JSON 结果做对比,避免被随机采样带来的正常抖动误导。
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 StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00