3. 性能基准测试方法:延迟与吞吐量指标定义、压测工具选型与测试环境搭建
性能基准测试是延迟优化的起点。没有可复现的基线,任何优化都只是猜测。本节将定义核心指标、对比主流压测工具,并给出可落地的环境搭建步骤。
3.1 核心指标定义
在线推理链路关注两个核心维度:延迟(Latency) 和 吞吐量(Throughput)。两者相互制约,需在特定并发条件下联合评估。
| 指标 | 定义 | 常用单位 | 关键分位值 |
|---|---|---|---|
| P50 延迟 | 50% 请求的响应时间在此值以下 | ms | 反映典型用户体验 |
| P99 延迟 | 99% 请求的响应时间在此值以下 | ms | 反映长尾延迟,SLA 核心关注点 |
| P999 延迟 | 99.9% 请求的响应时间在此值以下 | ms | 极端异常场景监控 |
| 吞吐量(QPS) | 每秒成功完成的请求数 | req/s | 系统容量上限标尺 |
| 并发数 | 同时发送请求的虚拟用户数 | 个 | 压力负载的输入参数 |
指标选取建议: 在线推理场景优先关注 P99 延迟 与 吞吐量 的联合曲线。单独看 P50 容易掩盖尾部抖动,单独看 QPS 无法感知响应质量。
3.2 压测工具选型对比
不同工具适用于不同协议和场景。以下对比聚焦于在线推理链路最常见的 HTTP/gRPC 协议。
| 工具 | 协议支持 | 语言 | 核心优势 | 适用场景 |
|---|---|---|---|---|
| wrk | HTTP/1.1, HTTP/2 | C + Lua | 极低资源开销,高并发能力,毫秒级延迟统计 | 纯 HTTP 推理接口的吞吐量摸底 |
| ghz | gRPC (HTTP/2) | Go | 原生 gRPC 支持,内置 protobuf 序列化,支持元数据注入 | gRPC 推理服务的延迟与吞吐测试 |
| Locust | HTTP, gRPC (插件) | Python | 分布式压测,脚本灵活,Web UI 实时监控 | 复杂业务场景编排、长时间稳定性测试 |
选型原则: 对于纯 HTTP 推理链路,优先使用 wrk 做快速摸底;对于 gRPC 链路,ghz 是首选;当需要模拟用户登录、Token 刷新、多步骤推理链时,Locust 的脚本灵活性不可替代。
3.3 测试环境搭建与基线建立
环境一致性是基准测试有效性的前提。以下步骤确保结果可复现。
3.3.1 环境隔离与配置清单
- 硬件隔离: 压测客户端与推理服务端部署在不同物理机或容器组,避免资源争抢。
- 网络确认: 使用
ping和mtr确认客户端到服务端的 RTT 基线(通常应 < 1ms 同机房)。 - 固定版本: 记录推理引擎版本、模型版本、CUDA/cuDNN 版本、内核参数(net.core.somaxconn 等)。
3.3.2 基线建立流程
- 空负载测试: 服务端无请求时,确认 CPU/GPU 空闲利用率 < 5%,内存无泄漏。
- 单并发摸底: 使用 1 个并发发送 1000 次请求,记录 P50/P99 延迟作为理论下限。
- 阶梯加压: 以 10、50、100、200、500 并发逐步加压,每个阶梯持续 60 秒,记录延迟与 QPS。
- 拐点识别: 当 P99 延迟出现非线性增长(如从 20ms 跳变到 200ms)时,标记为系统饱和点。
- 基线固化: 将饱和点对应的并发数、QPS、P50/P99/P999 延迟写入文档,作为后续优化效果的对比基准。
常见陷阱: 不要在压测过程中同时运行监控脚本(如
top、nvidia-smi -l)的日志输出到同一磁盘,避免 IO 干扰。建议将监控数据写入独立内存文件系统或远端收集。
3.4 压测执行与指标采集架构
下图展示了一次典型基准测试的组件关系与数据流。
3.5 典型压测命令示例
以下命令覆盖 HTTP 和 gRPC 两种协议,可直接用于基线采集。
3.5.1 wrk(HTTP 推理接口)
# 200 并发,持续 60 秒,打印延迟分布
wrk -t8 -c200 -d60s --latency http://inference-host:8080/v1/models/resnet50:predict
3.5.2 ghz(gRPC 推理接口)
# 200 并发,持续 60 秒,输出 P99 延迟
ghz --insecure \
--proto inference.proto \
--call inference.ModelInfer.Predict \
-d '{"model_name":"resnet50","inputs":[...]}' \
-c 200 -z 60s \
--output-format pretty \
inference-host:8500
3.5.3 Locust(复杂场景编排)
# locustfile.py 示例片段
class InferenceUser(HttpUser):
@task
def predict(self):
payload = {"instances": [prepare_input()]}
with self.client.post("/v1/models/resnet50:predict",
json=payload,
catch_response=True) as resp:
if resp.elapsed.total_seconds() > 0.5:
resp.failure("P99 超限")
# 启动命令:locust -f locustfile.py --host=http://inference-host:8080 --web-port=8089
3.6 基线文档模板
每次基准测试后,按以下模板固化结果,确保后续优化有据可查。
| 测试日期 | 模型版本 | 并发数 | QPS (req/s) | P50 (ms) | P99 (ms) | P999 (ms) | 是否饱和 |
|---|---|---|---|---|---|---|---|
| 2025-03-01 | resnet50:v1 | 10 | 450 | 18.2 | 32.5 | 45.1 | 否 |
| 2025-03-01 | resnet50:v1 | 100 | 2100 | 42.7 | 88.3 | 120.5 | 否 |
| 2025-03-01 | resnet50:v1 | 300 | 3200 | 89.4 | 245.0 | 410.2 | 是 |
基线使用守则: 每次代码变更、模型更新、配置调整后,重复上述阶梯加压流程,将新结果与基线对比。若 P99 延迟恶化超过 10%,需回滚或深入分析根因。