3. 基准测试方法论:性能指标定义、工具选择与场景设计
聊到 vLLM 的性能测试,我见过不少团队一上来就开跑,结果测出来的数据自己都看不懂。说白了,基准测试不是跑个数字就完事,你得先搞清楚:你到底要测什么?用什么测?在什么场景下测?
这一节,我就把这三个问题掰开揉碎了讲清楚。嗯,都是我在实际项目中踩过的坑和总结的经验。
3.1 性能指标定义:别被数字骗了
性能指标是基准测试的“度量衡”。定义不清楚,后面的所有工作都是白费。我个人习惯把指标分成两大类:吞吐量类和延迟类。
3.1.1 吞吐量(Throughput)
吞吐量衡量的是系统在单位时间内能处理多少请求或生成多少 Token。对于 LLM 推理,我们通常关注两个维度:
- 请求吞吐量(Requests Per Second, RPS):每秒能处理多少个完整的请求。适合衡量系统的并发处理能力。
- Token 吞吐量(Tokens Per Second, TPS):每秒能生成多少个 Token。这是更细粒度的指标,直接反映模型的生成速度。
我在项目中遇到过一个问题:只看 RPS 很高,但每个请求生成的 Token 数很少,导致 TPS 其实很低。所以,我建议你同时关注这两个指标,才能全面评估系统性能。
3.1.2 延迟(Latency)
延迟是用户体验的“生命线”。对于 LLM 推理,延迟不是单一指标,它包含几个关键阶段:
- 首 Token 延迟(Time to First Token, TTFT):从发送请求到收到第一个 Token 的时间。这反映了系统的“响应速度”。
- 每个输出 Token 的时间(Time Per Output Token, TPOT):生成后续每个 Token 的平均时间。这反映了系统的“生成速度”。
- 端到端延迟(End-to-End Latency):从发送请求到收到完整响应的总时间。
核心观点:TTFT 和 TPOT 是 LLM 推理独有的指标。传统 Web 服务只看端到端延迟,但在 LLM 场景下,用户对“第一个字出来得快不快”和“后续字生成得顺不顺”的感受是完全不同的。
为什么会这样?你想想看,用户等第一个 Token 时是“空等”,焦虑感最强。一旦第一个 Token 出来了,后续的生成是流式的,用户感知到的延迟会大大降低。所以,优化 TTFT 往往比优化 TPOT 更能提升用户体验。
3.1.3 指标之间的关系
吞吐量和延迟通常是相互制约的。提高并发数,吞吐量会上升,但延迟也会随之增加。你需要找到一个平衡点。
| 指标 | 定义 | 关注点 | 优化方向 |
|---|---|---|---|
| TTFT | 首 Token 延迟 | 响应速度 | 减少 Prefill 阶段耗时 |
| TPOT | 每个输出 Token 的时间 | 生成速度 | 优化 Decode 阶段效率 |
| RPS | 每秒请求数 | 并发处理能力 | 提升 Batch Size 和调度效率 |
| TPS | 每秒 Token 数 | 生成吞吐量 | 提升模型推理速度和硬件利用率 |
3.2 测试工具选择:工欲善其事,必先利其器
选工具这件事,我踩过不少坑。一开始图省事用通用压测工具,结果发现根本测不准 LLM 场景。后来我总结了一套选择逻辑:
3.2.1 vllm-bench:官方标配,首选
vllm-bench 是 vLLM 官方提供的基准测试工具。它最大的优势是原生支持 LLM 推理场景,能准确测量 TTFT、TPOT 等指标。
# 基本用法
vllm-bench --model meta-llama/Llama-2-7b-hf \
--request-rate 10 \
--num-prompts 1000 \
--max-concurrency 8
我个人习惯用 vllm-bench 做离线测试,因为它能精确控制请求的到达模式,方便复现问题。
小技巧:使用 --request-rate 参数模拟泊松分布到达的请求流,这比固定间隔的请求更接近真实场景。
3.2.2 wrk:轻量级,适合快速验证
wrk 是一个通用的 HTTP 压测工具,轻量、高效。但它不感知 LLM 的流式输出,只能测量端到端延迟。
# 简单压测
wrk -t 4 -c 100 -d 30s http://localhost:8000/v1/completions
我曾经用 wrk 做过一次快速对比测试,发现它测出来的延迟比实际偏高。原因很简单:wrk 会等待完整响应才记录时间,而 LLM 的流式响应在第一个 Token 出来时就已经开始返回了。所以,wrk 更适合做粗粒度的吞吐量测试。
3.2.3 Locust:灵活,适合复杂场景
Locust 是一个基于 Python 的负载测试工具,灵活性极高。你可以编写自定义的测试逻辑,模拟用户行为。
from locust import HttpUser, task, between
class LLMUser(HttpUser):
wait_time = between(1, 5)
@task
def send_request(self):
payload = {
"prompt": "What is the capital of France?",
"max_tokens": 100,
"stream": True
}
with self.client.post("/v1/completions", json=payload, stream=True) as resp:
for chunk in resp.iter_lines():
# 模拟流式读取
pass
我建议用 Locust 做在线测试,因为它能模拟真实用户的思考时间和行为模式。不过,Locust 的学习曲线比前两个工具陡一些。
| 工具 | 适用场景 | 优势 | 劣势 |
|---|---|---|---|
| vllm-bench | 离线测试、指标精确测量 | 原生支持 LLM 指标 | 灵活性有限 |
| wrk | 快速验证、吞吐量测试 | 轻量、高效 | 不感知流式输出 |
| Locust | 在线测试、复杂场景模拟 | 灵活、可编程 | 学习成本较高 |
3.3 测试场景设计:别在理想环境下测
很多人在测试时喜欢用“理想环境”:单请求、无并发、短序列。这种测试结果除了自我安慰,没有任何实际意义。你需要设计贴近真实部署场景的测试。
3.3.1 在线场景 vs 离线场景
- 在线场景(Online):模拟实时交互,请求到达时间随机,对延迟敏感。适合用 Locust 或 vllm-bench 的泊松分布模式。
- 离线场景(Offline):模拟批量处理,请求集中到达,对吞吐量敏感。适合用 vllm-bench 的固定速率模式。
我记得有一次帮客户做性能评估,他们只测了离线场景,结果上线后用户反馈“太慢了”。后来发现,在线场景下的 TTFT 比离线场景高了 3 倍。所以,一定要根据你的业务场景选择测试模式。
3.3.2 不同并发数的测试
并发数是影响性能的关键因素。我建议你至少测试 3 个并发级别:
- 低并发(1-4):测试系统的基线性能,了解单请求的延迟上限。
- 中并发(8-32):模拟正常负载,观察吞吐量和延迟的变化趋势。
- 高并发(64-256):测试系统的极限能力,找到性能拐点。
避坑指南:我曾经在测试高并发时,发现延迟突然飙升。排查了半天,原来是 vLLM 的调度器在处理大量请求时出现了锁竞争。所以,高并发测试不仅能测性能,还能帮你发现系统瓶颈。
3.3.3 测试场景设计示例
下面是一个我常用的测试矩阵:
| 场景 | 并发数 | 请求数 | 输入长度 | 输出长度 | 工具 |
|---|---|---|---|---|---|
| 基线测试 | 1 | 100 | 128 | 128 | vllm-bench |
| 在线负载 | 16 | 500 | 256 | 256 | Locust |
| 高吞吐测试 | 64 | 1000 | 512 | 512 | vllm-bench |
| 极限压测 | 128 | 2000 | 1024 | 1024 | wrk |
3.4 知识体系总览
为了让你更直观地理解这一章的内容,我画了一张图:
这张图把三个核心模块串起来了。你从左边开始,先定义指标,再选工具,最后设计场景。每一步都环环相扣。
好了,这一章的内容就到这里。记住:基准测试不是跑个分,而是为了理解系统的行为边界。下一章,我们会深入具体的测试执行流程和数据分析方法。