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 基线建立流程

  1. 空负载测试: 服务端无请求时,确认 CPU/GPU 空闲利用率 < 5%,内存无泄漏。
  2. 单并发摸底: 使用 1 个并发发送 1000 次请求,记录 P50/P99 延迟作为理论下限。
  3. 阶梯加压: 以 10、50、100、200、500 并发逐步加压,每个阶梯持续 60 秒,记录延迟与 QPS。
  4. 拐点识别: 当 P99 延迟出现非线性增长(如从 20ms 跳变到 200ms)时,标记为系统饱和点。
  5. 基线固化: 将饱和点对应的并发数、QPS、P50/P99/P999 延迟写入文档,作为后续优化效果的对比基准。
常见陷阱: 不要在压测过程中同时运行监控脚本(如 top、nvidia-smi -l)的日志输出到同一磁盘,避免 IO 干扰。建议将监控数据写入独立内存文件系统或远端收集。

3.4 压测执行与指标采集架构

下图展示了一次典型基准测试的组件关系与数据流。

基准测试执行架构与指标采集流 压测客户端 wrk / ghz / Locust 推理服务 TorchServe / Triton 监控采集 Prometheus + cAdvisor HTTP/gRPC 请求 metrics 暴露 原始结果 延迟分布 / QPS 记录 基线文档 P50/P99/QPS 饱和点 汇总分析

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%,需回滚或深入分析根因。