20. 性能测试与分析
前面每一篇都在说“某项优化能让某个指标变好”。这一篇回答怎样证明:怎样做一次有解释力的压测,怎样找到吞吐与延迟的拐点,怎样用分析工具定位瓶颈,以及怎样防止升级后性能悄悄退化。读完你应该能设计一组对照实验,留下可复现的记录,并把一次性能变化归因到具体的机制上,而不是只得到“快了 20%”这样一个数字。
先固定条件,再改变一个因素
性能测试最常见的问题不是工具不对,而是条件不一致:两次测试用的模型版本、对话模板、输出长度、并发方式不同,得出的“提升”无法解释。
一次可复现的测试,至少要记录这些信息:
yaml
model: 模型标识与版本
template: 对话模板与分词器版本
engine: 引擎名称与精确版本、镜像摘要
device: GPU 型号、数量、显存、互联方式
software: 驱动、CUDA 版本、注意力后端
precision: 权重、激活、KV 的数据类型
limits: 最大上下文、max_num_seqs、token 预算
generation: 温度、top_p、max_tokens、停止条件、是否开启思考模式
cache: 前缀缓存冷热状态
load: 输入输出长度分布、并发数或请求到达率
quality: 固定的质量样本与通过情况然后每次只改变一个因素。比如比较两个引擎,就保持模型、精度、负载完全相同;评估 FP8 KV,就只改 --kv-cache-dtype 这一项。
先确认回答对,再测快不快
压测之前,先发几个请求,确认输出正常:
bash
curl --fail http://127.0.0.1:8000/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model": "Qwen/Qwen3-8B",
"messages": [{"role": "user", "content": "用三句话解释 KV cache。"}],
"max_tokens": 256, "temperature": 0, "stream": true}'检查回答是否完整、结束原因是正常停止还是被长度截断、思考内容和正文是否分开。Qwen3 这类模型默认会先输出一段思考过程,开不开思考模式,输出长度可能差好几倍。如果两组对比的输出长度不同,速度比较就没有意义。
用 vLLM 自带的压测工具
vLLM 提供了 vllm bench serve 命令,可以对任何兼容 OpenAI 接口的服务(包括 SGLang)发起压测:
bash
vllm bench serve \
--model Qwen/Qwen3-8B \
--dataset-name random --random-input-len 2000 --random-output-len 200 \
--num-prompts 200 --max-concurrency 16 \
--percentile-metrics ttft,tpot,itl,e2el --metric-percentiles 50,95,99它报告的几个指标和第 5 篇的定义一一对应:
- TTFT:首 token 延迟。
- TPOT:每个请求的
(端到端延迟 − TTFT) ÷ (输出 token 数 − 1),排除了第一个 token。 - ITL:所有请求所有相邻两次流式返回之间的间隔,汇总成分布,能看出偶发的长停顿。
- E2EL:端到端延迟。
还可以用 --goodput ttft:1000 tpot:50 这样的参数直接统计满足 SLO 的有效吞吐。
需要注意,random 数据集用的是随机 token,不会触发前缀缓存,也不代表真实的输出长度分布。最终评估要用接近业务的数据:真实的系统提示、真实的输入长度分布、真实的输出长度。
如果只想测单个请求的首字和流式间隔,可以用这个只依赖 Python 标准库的小脚本。它按正文的第一个片段计算 TTFT,不会把思考内容误当成正文,并单独标出错误和截断:
bash
python measure_stream.py \
--url http://127.0.0.1:8000/v1/chat/completions \
--model Qwen/Qwen3-8B --prompt '用三句话解释 KV cache。' \
--max-tokens 256 --requests 8 --concurrency 2五组有解释力的实验
| 实验 | 固定什么 | 改变什么 | 观察什么 |
|---|---|---|---|
| 单请求基线 | 模型、精度、长度、采样 | 引擎或版本 | TTFT、TPOT、冷启动时间 |
| 冷热前缀 | 长文档 + 不同的问题 | 首次请求与重复前缀 | 命中 token 数、TTFT 的变化 |
| 并发扫描 | 输入输出长度分布 | 并发 1、2、4、8、16…… | 吞吐的拐点、P95/P99 延迟、抢占次数 |
| 长短混合 | 同一组请求 | 到达顺序、token 预算 | 正在生成的请求的 ITL 尖刺 |
| 单项优化 | 质量集与负载 | 量化、投机解码等一个开关 | 质量、显存、TTFT、TPOT、Goodput |
做缓存实验时,要分别测真正的冷请求(重启服务或清空缓存后)、完全重复的请求、共享文档换问题的请求。两个引擎比较时,一个是热缓存、另一个是冷缓存,结论就是错的。
找到吞吐与延迟的拐点
并发扫描是容量规划最重要的实验。把并发从 1 开始逐步加倍,每一档记录系统吞吐和延迟分位数,画成一张图:
典型的形状是:低并发时,吞吐随并发近似线性增长,延迟几乎不变(第 6 篇:Decode 访存受限,批变大几乎不增加步长);接近某个点后,吞吐增长变慢,延迟开始明显上升(KV 读取占比变大、开始抢占、排队);再往后,吞吐不再增长,延迟急剧恶化。
图中标出的拐点,就是在满足延迟目标的前提下这套部署的容量。错误率或尾延迟失控时就该停止加压,这个转折本身就是要找的容量信息。
两种发压方式
固定并发(闭环):始终保持 N 个请求在途,完成一个再发一个。服务变慢时,发压也自然变慢。它适合观察“某个并发下的表现”,但会掩盖真实的排队问题。
固定到达率(开环):按每秒 R 个请求的节奏发送,不管之前的请求有没有完成,可以用泊松分布模拟真实用户的随机到达。它能暴露系统扛不住时队列越来越长、延迟失控的情况,更接近线上。vllm bench serve 的 --request-rate 参数就是这种方式。
稳定状态下,可以用利特尔定律做一个交叉检查:系统内平均请求数 = 平均到达率 × 平均停留时间。比如每秒到达 10 个请求、每个平均停留 3 秒,系统里平均就有 30 个请求在途。如果你的并发上限是 16,这个负载必然会排队。
定位瓶颈:从现象到机制
压测告诉你“慢了”,分析告诉你“为什么慢”。先根据现象缩小范围:
| 现象 | 先拆解什么 | 下一步验证 |
|---|---|---|
| 第一次很慢,之后正常 | 模型加载、编译、图录制、冷缓存 | 分别记录冷启动和热请求 |
| TTFT 高,出字正常 | 排队、分词、Prefill、对话模板 | 对比短输入和长输入,冷热前缀 |
| 生成中途偶尔停顿 | 长 Prefill 插入、抢占、CPU 空隙 | 长短混合实验,查看抢占计数 |
| 并发高时吞吐下降 | KV 容量、抢占重算、CPU 调度 | 降低并发,看缓存使用率和等待队列 |
| 量化后反而变慢 | 内核选择、反量化开销、批大小 | 查看启动日志里实际使用的内核 |
| 多卡反而变慢 | 卡间互联、通信占比 | 能放进单卡时做单卡对照 |
| 结构化输出出错 | Schema 支持、截断、与投机解码的组合 | 先关闭其他功能,用简单 Schema 逐项恢复 |
然后用工具看细节:
- 引擎指标。 vLLM 和 SGLang 都暴露 Prometheus 格式的指标:运行中和等待中的请求数、KV 缓存使用率、前缀命中率、抢占次数、TTFT 和 TPOT 的直方图。这是线上最先看的东西。
- PyTorch Profiler。 可以看到每一步里 CPU 和 GPU 的时间线,哪些内核耗时最长,GPU 之间有没有空隙。vLLM 支持通过环境变量开启一段时间的采样。
- Nsight Systems。 NVIDIA 的系统级分析工具,能看到 CUDA 内核、内存拷贝、NCCL 通信和 CPU 线程在时间轴上的关系,适合定位“GPU 在等什么”。
- Nsight Compute。 针对单个内核的深入分析,能看到它的算术强度、带宽利用率,落在 Roofline 的哪个位置。
分析时要把客户端观察到的时间和服务端日志对应起来,用请求 ID 串起来。只看一个“GPU 利用率”数字,分不清是在排队、在计算还是在传输。nvidia-smi 显示的 GPU 利用率只表示“这段时间里有内核在运行”,不表示算力被用满,Decode 时它可能显示 100%,实际算力利用率只有个位数。
防止性能悄悄退化
推理引擎、驱动、模型每次升级都可能带来性能变化。把压测变成一道回归检查:
- 固定一套代表业务的基准负载和质量集,作为每次变更的必测项。
- 每次升级前后各跑一次,对比关键指标的分位数,而不只是平均值。
- 设定可以接受的退化范围,超出就阻止上线,再用分析工具定位原因。
- 保留每次的完整记录和回退版本。
最后,不能用一次测试的数字外推。“在 H100 上用 8B 模型、输入 2000、输出 200 测得 TPOT 12 毫秒”只在这组条件下成立。换了模型、硬件、长度分布或并发,都需要重新测。
可信的性能测试先固定条件、确认输出正确,每次只改一个因素;用 vllm bench serve 等工具测 TTFT、TPOT、ITL 的分位数和 Goodput,用并发扫描找到吞吐与延迟的拐点;开环发压更接近线上。定位瓶颈时,从现象缩小范围,再用引擎指标、PyTorch Profiler 和 Nsight 工具看细节。
测试证明了部署方案可行,下一步是让它长期稳定地运行。下一篇讲生产部署与运维。
