3. 性能基准测试与评估:CPU基线性能测试、GPU加速比计算、Profiling工具使用、瓶颈分析
说实话,性能基准测试这件事,我见过太多团队栽跟头了。
有人拿个单batch跑一遍,就宣称“GPU比CPU快100倍”。有人用PyTorch默认配置跑ONNX,然后说“ONNX Runtime太慢”。这些我都遇到过。说白了,没有规范的基线测试,后面的优化全是空中楼阁。
这一章,我们就来聊聊怎么科学地做性能评估。我会把我在几个量产项目中踩过的坑、总结的经验,都摊开来讲。
3.1 CPU基线性能测试
做迁移的第一步,不是急着上GPU。而是先把CPU上的性能摸清楚。我个人习惯,至少跑三组数据:单线程、多线程、以及实际部署场景下的吞吐量。
3.4.1 PyTorch CPU基准测试
PyTorch在CPU上跑推理,有几个关键点要注意。嗯,这里我直接给代码模板,你们拿去就能用。
import torch
import time
import numpy as np
# 设置CPU线程数,我建议先测单线程
torch.set_num_threads(1)
model = torch.load('model.pth', map_location='cpu')
model.eval()
# 构造输入,batch_size=1 和 batch_size=32 都要测
dummy_input = torch.randn(1, 3, 224, 224)
# 预热,这个步骤很多人会忘
for _ in range(10):
_ = model(dummy_input)
# 正式测试
num_iter = 100
start = time.time()
for _ in range(num_iter):
_ = model(dummy_input)
end = time.time()
avg_latency = (end - start) / num_iter * 1000 # 转成毫秒
print(f'CPU单线程延迟: {avg_latency:.2f} ms')
3.4.2 ONNX Runtime CPU基准测试
ONNX Runtime在CPU上的优化,其实比PyTorch原生要好一些。特别是对于静态图,ORT的图优化能省不少事。
import onnxruntime as ort
import time
import numpy as np
# 创建session,我习惯用CPUExecutionProvider
session = ort.InferenceSession('model.onnx',
providers=['CPUExecutionProvider'])
input_name = session.get_inputs()[0].name
dummy_input = np.random.randn(1, 3, 224, 224).astype(np.float32)
# 预热
for _ in range(10):
_ = session.run(None, {input_name: dummy_input})
# 正式测试
num_iter = 100
start = time.time()
for _ in range(num_iter):
_ = session.run(None, {input_name: dummy_input})
end = time.time()
avg_latency = (end - start) / num_iter * 1000
print(f'ONNX Runtime CPU延迟: {avg_latency:.2f} ms')
3.2 GPU加速比计算
加速比,说白了就是CPU耗时除以GPU耗时。但这里有个坑:你拿什么CPU和什么GPU比?
我建议这样:
- 单卡 vs 单核CPU:看纯计算加速比,适合评估算法优化效果
- 单卡 vs 多核CPU:看实际部署加速比,适合评估硬件选型
加速比计算公式很简单:
加速比 = CPU平均延迟 / GPU平均延迟
# 举个例子
cpu_latency = 45.2 # ms
gpu_latency = 2.3 # ms
speedup = cpu_latency / gpu_latency
print(f'加速比: {speedup:.1f}x')
3.3 Profiling工具使用
光看加速比不够,你得知道时间花在哪了。Profiling工具就是干这个的。
3.3.1 Nsight Systems
Nsight Systems是我最常用的工具。它能看CPU和GPU的时间线,一眼就能看出哪里在等待、哪里在计算。
基本用法:
# 命令行启动
nsys profile -o output_profile python inference.py
# 或者用GUI界面,更直观
# 我习惯先跑一次命令行,拿到profile文件,再用GUI打开分析
看什么?我一般关注这几个指标:
- GPU利用率:如果低于80%,说明数据加载或预处理在拖后腿
- CPU-GPU数据传输:看memcpy的时间,如果占比超过10%,就要考虑异步传输了
- Kernel启动开销:小模型经常被这个拖慢,可以考虑kernel fusion
3.3.2 nvprof(老牌工具)
nvprof虽然被Nsight Systems取代了,但有些场景还是好用。比如你想快速看每个kernel的耗时,nvprof更直接。
# 基本用法
nvprof --print-gpu-trace python inference.py
# 只看最耗时的几个kernel
nvprof --print-gpu-trace --metrics gpu__time_duration.sum python inference.py
输出示例:
| Kernel名称 | 耗时(us) | 占比 |
|---|---|---|
| conv2d_forward | 450.2 | 35% |
| matmul_kernel | 320.1 | 25% |
| relu_forward | 45.3 | 3.5% |
3.4 瓶颈分析
有了Profiling数据,下一步就是找瓶颈。我总结了一个四步分析法:
- 看整体:CPU和GPU的时间线,有没有明显的空闲期
- 看传输:Host到Device的数据传输是不是瓶颈
- 看计算:哪个kernel最耗时,能不能优化或融合
- 看调度:有没有不必要的同步点
举个例子,我最近优化一个模型:
- Nsight Systems显示,GPU利用率只有60%
- 进一步看,发现每次推理前都有一次memcpy,耗时2ms
- 而模型推理本身才5ms,传输占了将近30%
- 解决方案:用pinned memory + 异步传输,把传输时间降到0.5ms
优化后,GPU利用率到了85%,整体延迟从7ms降到了5.5ms。
好了,这一章的内容就这些。记住:没有基准测试的优化都是耍流氓。下一章我们会聊量化技术的基础,但在此之前,我建议你先把手头的模型跑一遍完整的Profiling,把数据留好。后面优化完了,对比起来才有说服力。