外观
推理基准测试实践
没有测准的数字,比没有数字更危险——它给优化决策披上"科学"的外衣,却掩盖了真实的瓶颈。
推理优化圈有一句老话:"我的模型在你的硬件上跑得比你快"——这是营销话术,不是工程结论。同一模型、同一 GPU,两家测出来的 tokens/s 能差 3 倍,原因在测试方法:batch size 不同、prompt 长度不同、量化精度不同、KV cache 配置不同、cold/warm start 不分。没有方法论的 benchmark 比没有 benchmark 更害人——它让你在错误的方向上持续投入。
本文给出一套可复用、可对比、可防作弊的 LLM 推理基准测试方法论。读完本文,你应该能:写出任何两个引擎对比都能复现的测试报告,识别出 5 种最常见的"benchmark 作弊",并把测试结果转成决策依据。这是 从零部署一个推理服务 与 渐进式教程 性能数字的来源方法。
本文的边界
本文聚焦单实例 LLM 推理基准——单机单卡或多卡上的引擎对比。分布式系统级基准(集群 QPS、跨实例延迟、网络开销)是另一套方法论,本文不展开。详见 Triton 推理服务 与 模型服务化与编排。
一、测什么:LLM 推理的核心指标
LLM 推理与经典 ML 推理最大的差别:指标不是一个数字,而是一组。只看 tokens/s 等于只看平均分——它会掩盖长尾延迟、掩盖首字延迟、掩盖并发退化。下面五个指标缺一不可。
1. 五个核心指标
| 指标 | 定义 | 业务含义 | 测什么 |
|---|---|---|---|
| TTFT (Time To First Token) | 从请求到收到第一个 token 的时间 | 聊天场景的"响应感"、用户耐心边界 | prefill 阶段 + queue 等待 |
| TPOT (Time Per Output Token) | 生成阶段平均每 token 时间 | 流式生成的"打字速度" | decode 阶段 + KV cache 访问 |
| 端到端延迟 (E2E) | 从请求到收到完整输出 | 批处理、文档摘要等"等结果"场景 | TTFT + N × TPOT |
| 吞吐 (tokens/s) | 单位时间生成的总 token 数 | 集群吞吐能力、成本核算 | (并发数 × output_len) / 总时间 |
| 显存峰值 (peak GPU memory) | 推理过程中显存最高占用 | 部署可行性、并发上限 | 权重 + KV pool + 临时 |
2. 延迟分布:p50 / p95 / p99
平均延迟会骗人——30 个并发里 28 个 1s、2 个 10s,平均 1.6s 看着还行,但那 2 个 10s 的请求在聊天场景下用户已经走了。生产环境必须看分位数:
延迟分布(32 并发,TPOT 排序后):
p50 = 22 ms ← 中位数,"正常情况下的体验"
p90 = 35 ms ← 90% 的请求快于此值
p95 = 50 ms ← SRE 关心的"长尾"
p99 = 180 ms ← 真正的"倒霉用户"
p999 = 1200 ms ← 系统在退化时的极端案例- p50:业务体感的代表,但掩盖了长尾
- p95:SRE 主看这个——它代表"大多数用户的下限"
- p99:成本敏感场景看这个——为了把 p99 从 200ms 降到 180ms,可能要多花一倍机器
- p999:通常不报,因为 0.1% 的极端案例往往来自 GC、网络抖动等非模型因素
平均值与分位数的纪律
- 对外汇报:报 p50 + p99(一对,反映中心与长尾)
- 内部 SRE:报 p50/p90/p95/p99/p999(一组,反映分布形状)
- 永远不要只报平均值——平均值会让 1 个慢请求被 99 个快请求平均掉,问题看不见
详见 延迟、吞吐与并发 的"延迟分布与排队论"。
3. 显存峰值的三层
显存不是一个数字,是三层:
| 层 | 内容 | 谁负责 |
|---|---|---|
| 权重层 | 模型参数本身 | 量化、剪枝、蒸馏优化(详见 模型量化基础) |
| KV cache 层 | 历史 token 的 K/V 张量 | PagedAttention、block_size、max_num_seqs 优化 |
| 临时层 | attention 中间矩阵、激活 | kernel fusion、FlashAttention 优化 |
nvidia-smi 看到的"显存占用"是三者之和——不看每一层,调参时就抓瞎:"为啥 14 GB 模型占了 36 GB?" 答案是 KV pool 占了 22 GB。详见 显存层次与带宽墙。
二、用什么测:工具盘点
LLM 推理基准测试的工具有四大类,每类测的指标不同、用途不同:
1. 工具矩阵
| 工具 | 主要用途 | 测什么 | 谁维护 |
|---|---|---|---|
vLLM benchmarks/ | vLLM 自身性能基准与对比 | 吞吐、延迟、显存 | vLLM 项目组 |
| MLPerf-LLM | 跨硬件/跨引擎的标准化基准 | 吞吐、延迟、成本 | MLCommons |
SGLang bench/ | SGLang 与 vLLM 对比 | 吞吐、延迟、复杂调度 | SGLang 项目组 |
| lm-eval-harness | 模型质量评测(不是性能) | MMLU/HellaSwag 等精度 | EleutherAI |
| trtllm-bench | TensorRT-LLM 性能基准 | 吞吐、延迟 | NVIDIA |
| openllm-bench | 跨引擎、跨量化对比 | 全维度 | 社区 |
2. vLLM offline 推理基准(最常用)
vLLM 自带的 benchmarks/benchmark_throughput.py 与 benchmark_latency.py 是最常用的"入门级"基准测试:
bash
# 离线吞吐基准(不通过 HTTP,直接 Python API)
python benchmarks/benchmark_throughput.py \
--model meta-llama/Llama-2-7b-chat-hf \
--backend vllm \
--input-len 1024 \
--output-len 256 \
--num-prompts 512 \
--dtype bfloat16 \
--max-model-len 4096
# 单请求延迟基准
python benchmarks/benchmark_latency.py \
--model meta-llama/Llama-2-7b-chat-hf \
--backend vllm \
--input-len 1024 \
--output-len 256 \
--batch-size 1 \
--num-iters 100它的优点是与 vLLM 内部 API 直连,没有 HTTP 层开销,测的是引擎本身。缺点是只能测 vLLM(要测其他引擎得用其他脚本)。详见 vLLM 与 PagedAttention。
3. MLPerf-LLM(标准化对比)
MLPerf-LLM 是跨硬件、跨引擎的标准化基准——所有参测方按相同规则测,结果可公开对比:
| 维度 | 测什么 |
|---|---|
| Single-stream | 单请求延迟(TTFT + TPOT) |
| Multi-stream | 多请求并发下的延迟分布 |
| Offline | 纯吞吐(无并发约束) |
| Server | 在线 QPS 与 SLO 达成率 |
MLPerf 的纪律很严:所有参测方必须报完整配置(硬件型号、driver 版本、引擎版本、模型权重 hash),结果必须可复现。这是工业级对比的金标准——你的 vLLM 在 A100 上 1100 tok/s 的数字,与 MLPerf 公开榜单的数字能直接对得上,否则就是你的方法有问题。
4. SGLang bench(对比测试)
SGLang 项目的 bench/ 提供了 vLLM 与 SGLang 的对比脚本——它对复杂场景调度(多轮对话、长上下文、structured output)的覆盖比 vLLM 自带脚本更细:
bash
# SGLang vs vLLM 对比
python -m sglang.bench_serving \
--backend vllm \
--base-url http://localhost:8000 \
--model meta-llama/Llama-2-7b-chat-hf \
--dataset-name random \
--random-input-len 1024 \
--random-output-len 256 \
--num-prompts 512详见 vLLM 与 PagedAttention 与 推理引擎选型对比 中"复杂调度场景"。
5. lm-eval-harness(模型质量)
性能不是全部——量化、剪枝、蒸馏后模型是不是变笨了?这是 lm-evaluation-harness 的事:
bash
# 跑 MMLU 5-shot 评测
lm_eval --model vllm \
--model_args pretrained=meta-llama/Llama-2-7b-chat-hf \
--tasks mmlu \
--num_fewshot 5 \
--batch_size 8
# 跑 AWQ 量化模型
lm_eval --model vllm \
--model_args pretrained=./Llama-2-7b-chat-hf-awq,quantization=awq \
--tasks mmlu,hellaswag,arc_challenge \
--num_fewshot 5性能数字必须配质量数字一起报——只报 tokens/s 不报 MMLU,等于只报成本不报效果。详见 模型量化基础 的"量化是 trade-off"。
三、关键变量:什么会显著影响数字
下面这些变量每一个都能让数字差一倍以上。报告必须显式声明每个变量的取值,否则不可复现。
1. 变量矩阵
| 变量 | 默认值 | 影响方向 | 典型影响 |
|---|---|---|---|
batch_size / max_num_seqs | 1 / 256 | 大→吞吐↑但 p99 延迟↑ | 5–10× |
input_len (prompt 长度) | 不固定 | 长→TTFT↑、KV cache↑ | 线性 |
output_len (生成长度) | 不固定 | 长→单请求延迟↑、TPOT 不变 | 线性 |
| 并发数 | 1 | 高→吞吐↑但 p99 延迟↑ | 3–30× |
| 量化精度 | bf16 | INT4→显存↓但 TPOT 也↓ | 1.3–2× |
| KV cache size (gpu_mem_util × block_size) | 0.9 × 16 | 大→能接更多并发 | 2–5× |
| 模型 | 不固定 | 大→所有指标都退化 | 5–10× |
| GPU 型号 | A100 40GB | 新卡更快(A100→H100:2×) | 1.5–3× |
| CUDA / driver 版本 | 最新 | 不匹配可能掉 30% | 1.0–1.5× |
2. 公平对比的纪律
两个引擎对比时,必须控制:
固定模型(同权重 hash)
固定 GPU(同型号、同 driver)
固定 batch_size 或并发数
固定 input_len 与 output_len
固定量化精度
固定 max_model_len 与 KV pool
唯一变量:引擎本身少一个变量,对比就不公平。最经典的反例是"vLLM 默认开 PagedAttention,TGI 默认不开"——直接跑默认参数对比,vLLM 必赢,但赢的不是引擎本身而是 KV pool 配置。公平的对比必须把两边都调到各自最优配置,而不是默认配置。
"默认配置对比"是耍流氓
很多"vLLM 比 TGI 快 5×"的对比文章都是默认配置对比——一边开 PagedAttention 一边不开。真正的对比是"vLLM 最优 vs TGI 最优",这才是用户买来用会调成的状态。详见 推理引擎选型对比。
四、防作弊:5 种最常见的小动作
benchmark 圈的 5 种"作弊"——多数不是故意,而是测错了不知道:
1. Cold start vs Warm start 不分
症状:第一次跑很慢,后续跑很快,报告只报后续。
根因:CUDA kernel 编译、CUDA Graph capture、KV cache 预热都发生在前几次推理里。cold start 慢 3–10 倍是常态。
修复:
python
# 标准做法:warmup N 次(N ≥ 3),然后再 bench
for _ in range(3):
_ = model.generate(**inputs, max_new_tokens=8) # warmup
# 正式测试
t0 = time.perf_counter()
out = model.generate(...)
t1 = time.perf_counter()报告里写明"warmup 3 次后测试 100 次,取 p50/p99"。
2. CPU-GPU overlap 没算
症状:用 time.time() 测端到端,结果包含了 Python 解释、HTTP 序列化等 CPU 时间。
根因:CUDA 是异步的——model.generate 返回时 GPU 还在跑,没 torch.cuda.synchronize() 就测时间等于测了"Python 启动 GPU 的时间"。
修复:
python
torch.cuda.synchronize()
t0 = time.perf_counter()
out = model.generate(...)
torch.cuda.synchronize() # ← 等 GPU 真跑完
t1 = time.perf_counter()HTTP 服务测试则要用客户端收完最后一个 chunk 的时间,不是 request.send 的时间。
3. Padding 把延迟拉高
症状:batch 里所有 prompt 长度不同时,被 pad 到最长,每个请求都按最长 prompt 算 prefill。
根因:经典 batching 没 PagedAttention 时,必须 pad 等长。vLLM 的 PagedAttention 解决了这个问题——但如果你测的是非 vLLM 引擎,padding 是常见拉高延迟的原因。
修复:报告里写明 padding 策略,最好用相同长度的 prompt 测(--random-input-len 1024)。
4. 单 batch 测延迟当生产延迟
症状:报"延迟 30ms"——结果只测了 batch=1 的理想情况,生产环境 32 并发时延迟 200ms。
根因:单 batch 延迟是引擎最优情况,不是生产情况。生产延迟必须带并发测。
修复:永远同时报单请求延迟 + N 并发延迟,最好画"并发数 vs 延迟"曲线:
并发 p50 p99
1 22 25
4 24 30
16 28 50
32 35 180
64 50 1200 ← 这里开始退化这是 常见陷阱与反模式 的"单 batch 测延迟就当成品级延迟"。
5. 量化精度不标
症状:报"vLLM 跑 Llama-2-7B 吞吐 1100 tok/s"——结果用的是 AWQ INT4 模型,而对比的 TGI 用 bf16。
根因:量化版本和原始版本的吞吐本就差 1.5–2×,不是引擎差异而是模型差异。
修复:模型版本、量化精度、权重组大小(q_group_size)必须显式声明,对比时两边量化精度必须一致。
自检清单
报出 benchmark 数字前,过一遍这五条:
- [ ] warmup 至少 3 次,再测至少 100 次
- [ ] 用
torch.cuda.synchronize()或 HTTP 完整往返时间 - [ ] 显式声明 prompt 长度与 padding 策略
- [ ] 同时报单请求延迟 + 至少 3 个并发档位延迟
- [ ] 模型权重 hash、量化精度、CUDA 版本都写在报告里
五、报告模板
一份合格的推理基准报告应该包括五部分:
markdown
# 推理基准报告:<模型名> 在 <硬件> 上的对比
## 1. 环境
- 硬件:NVIDIA A100 80GB × 1
- Driver:535.104.05, CUDA 12.4, cuDNN 9.0
- 引擎:vLLM 0.6.3, TensorRT-LLM 0.13.0
- 模型:Llama-2-7b-chat-hf(HuggingFace, sha256: ...)
- 量化:bf16(无量化)
## 2. 配置
- max_model_len: 4096
- max_num_seqs: 32
- gpu_memory_utilization: 0.9
- input_len: 1024, output_len: 256
- 并发档位: 1 / 4 / 16 / 32 / 64
- warmup: 3 次,测试: 100 次/档
## 3. 结果
| 并发 | 引擎 | p50 (ms) | p99 (ms) | 吞吐 (tok/s) | 显存峰值 (GB) |
|---|---|---|---|---|---|
| 1 | vLLM | 200 | 250 | 50 | 18 |
| 1 | TRT-LLM | 180 | 220 | 55 | 17 |
| 32 | vLLM | 35 | 180 | 1100 | 36 |
| 32 | TRT-LLM | 30 | 150 | 1300 | 32 |
## 4. 结论
- TRT-LLM 在所有档位都比 vLLM 快 10–20%
- 但 TRT-LLM 部署复杂度更高(需 build engine)
- 在线场景推荐 vLLM(开发效率优先),离线场景推荐 TRT-LLM(吞吐优先)
## 5. 复现
- 代码:`scripts/bench_vllm_vs_trtllm.sh`
- 模型权重:HuggingFace `meta-llama/Llama-2-7b-chat-hf`
- 命令:`bash scripts/bench_vllm_vs_trtllm.sh`模板的纪律
报告五部分缺一不可:
- 缺环境:换机器就复现不了
- 缺配置:别人不知道你 vLLM 怎么调的
- 缺结果矩阵:没法做对比决策
- 缺结论:报告变成"数据堆"
- 缺复现命令:报告等于"我的话"
详见 基准数据与工具档案 的"如何读 benchmark 榜单"。
六、把数字转成决策
benchmark 不是终点,是决策依据。下面是把数字转成决策的常见模式:
1. 引擎选型
测出 vLLM 与 TRT-LLM 的对比数字后,决策路径:
延迟差 < 10% → 选 vLLM(部署简单、社区大)
延迟差 10–30% → 看团队:有 NVIDIA 背景选 TRT-LLM,没有选 vLLM
延迟差 > 30% → 业务对延迟极敏感 → 选 TRT-LLM
吞吐差 > 50% → 离线批处理 → 选 TRT-LLM详见 推理引擎选型对比。
2. 量化决策
测出 AWQ INT4 与 bf16 的对比后:
吞吐提升 > 50%, MMLU 下降 < 1 → 全量上线 AWQ
吞吐提升 20–50%, MMLU 下降 1–3 → 灰度 20%,看业务指标
吞吐提升 < 20%, MMLU 下降 > 3 → 不上 AWQ,保留 bf163. 并发调参
测出"并发数 vs p99 延迟"曲线后,找拐点:
1 → 22ms
4 → 28ms
16 → 35ms ← 拐点之前
32 → 180ms ← 拐点!p99 突然飞涨
64 → 1200ms ← 严重退化设 max_num_seqs = 16(拐点之前),超过的请求走队列或 autoscale。详见 调参与性能调优 的"max_num_seqs 调参"。
4. 硬件升级决策
测出 A100 与 H100 的对比后:
吞吐提升 < 1.5× → 不值得升级(H100 贵 2×)
吞吐提升 1.5–2× → 看业务 SLA 是否吃紧
吞吐提升 > 2× → 升级划算详见 硬件基础速查 与 GPU 体系结构与优化。
七、延伸阅读
- 延迟、吞吐与并发 —— 本文指标的理论版
- 推理引擎选型对比 —— benchmark 决策的应用
- 从零部署一个推理服务 —— 本文数字的来源
- 渐进式教程:三版跑起来 —— 每版只加一项优化的对比
- 调参与性能调优 —— benchmark 转调参决策
- 部署设计原则 —— 端到端基准的原则
- 常见陷阱与反模式 —— benchmark 反模式的完整版
- 基准数据与工具档案 —— 公开 benchmark 榜单怎么读
- 硬件基础速查 —— 跨硬件对比参考
- vLLM 与 PagedAttention —— vLLM bench 工具的来源
参考资料
- vLLM: benchmarks/ —— vLLM 自带基准脚本
- MLCommons Inference: Language —— MLPerf-LLM 标准化基准
- SGLang: bench —— SGLang 对比基准
- lm-evaluation-harness —— 模型质量评测框架
- TensorRT-LLM: Benchmarks —— TRT-LLM 性能基准
- OpenLLM Bench —— 综合对比榜单
- NVIDIA Nsight Systems —— GPU 级 profiling 工具
- Prometheus + Grafana for LLM serving —— 生产监控(与 benchmark 互补)