准确测试cuda代码执行性能

准确测试cuda代码执行性能
gogongxtNOTE
参考自文章:https://blog.speechmatics.com/cuda-timings
强烈推荐大家去阅读原文
GPU 算子性能测试优化点总结
| 优化点 | 核心原理 | 解决的问题 |
|---|---|---|
| 主机-设备同步 (Host-Device Sync) | CUDA 核函数是异步执行的。CPU 只是把任务丢进队列就继续往下跑了。 | 避免只测到了“分发任务”的时间,而不是“执行任务”的时间。 |
| CUDA Events | 在 GPU 指令流中插入“时间戳”标记,由 GPU 硬件直接记录。 | 减少 CPU 侧 perf_counter
带来的系统调用开销和内核启动(Launch)干扰。 |
| 预热 (Warmup) | 排除 JIT 编译、cudnn
算子自动选择(autotune)、显存重新分配和延迟加载等一次性开销。 |
避免初次运行的巨大延迟拉高平均值,使数据更符合稳定运行状态。 |
| 固定时钟频率 (Fixed Clocks) | 现代 GPU 会根据功耗和温度动态调频(Boost/Throttling)。 | 消除因硬件状态波动导致的测试结果不一致,确保实验可重复。 |
| 清除缓存 (Cache Flush) | 大部分 GPU 算子会受益于 L2 缓存。如果连续测试同一算子,后续运行会因为缓存命中而显得极快。 | 模拟真实的、非连续命中的运行环境,反映算子的真实带宽/计算需求。 |
| 休眠/CUDA 图 (Sleep/Graphs) | 对于极短的算子,内核启动时间可能超过执行时间,导致 GPU 等待 CPU 发指令。 | 通过 _sleep
让指令队列堆积或使用 CUDAGraphs
将多个小算子打包,消除启动间隔。 |
测试脚本
这个脚本集成上述所有技巧,旨在为你提供一个准确、可重复的算子性能测试框架。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import torch
import torch.utils.benchmark as benchmark
import numpy as np
from typing import Callable
def flush_cache():
"""清除 L2 缓存。A100 的 L2 约为 40MB,这里分配 40MB 零向量并操作它。"""
# 这里的容量根据你的 GPU 型号调整,通常 40MB-80MB 足够
cache_size = 40 * 1024 * 1024
x = torch.empty(cache_size, dtype=torch.int8, device='cuda')
x.zero_()
def benchmark_operator(op_func: Callable, warmup_steps: int = 5, test_steps: int = 10):
"""
专业的算子性能测试函数
"""
# 1. 预热 (Warmup)
# 排除 JIT 编译和 cuDNN autotune 的干扰
for _ in range(warmup_steps):
op_func()
torch.cuda.synchronize()
# 2. 使用 CUDA Events 进行精确计时
start_events = [torch.cuda.Event(enable_timing=True) for _ in range(test_steps)]
end_events = [torch.cuda.Event(enable_timing=True) for _ in range(test_steps)]
for i in range(test_steps):
# 3. 清除缓存 (可选,视测试需求而定)
flush_cache()
# 4. 插入少量时钟周期休眠以确保 GPU 队列饱和(针对极快算子)
torch.cuda._sleep(10_000_000)
start_events[i].record()
op_func()
end_events[i].record()
# 等待所有操作完成
torch.cuda.synchronize()
# 计算平均时间 (单位:毫秒)
times = [s.elapsed_time(e) for s, e in zip(start_events, end_events)]
times_array = np.array(times)
avg_time = np.mean(times_array)
median_time = np.median(times_array)
p90_time = np.percentile(times_array, 90)
p99_time = np.percentile(times_array, 99)
print(f"Average Execution Time: {avg_time:.4f} ms")
print(f"Median Time: {median_time:.4f} ms")
print(f"P90 Time: {p90_time:.4f} ms")
print(f"P99 Time: {p99_time:.4f} ms")
print(f"Min Time: {min(times):.4f} ms | Max Time: {max(times):.4f} ms")
return avg_time
# --- 使用示例 ---
if __name__ == "__main__":
# 定义你想要测试的算子,例如一个矩阵乘法
A = torch.randn(2048, 2048, device='cuda')
B = torch.randn(2048, 2048, device='cuda')
def my_op():
return torch.matmul(A, B)
print("Benchmarking MatMul...")
benchmark_operator(my_op)补充建议:
- 关于固定时钟:脚本中未包含
nvidia-smi命令,因为这通常需要sudo权限。建议在运行 Python 脚本前,在终端手动执行:- 启用持久模式:
sudo nvidia-smi -pm 1 - 锁定频率(以 A100
为例):
sudo nvidia-smi -lgc 1215
- 启用持久模式:
- PyTorch 官方工具:如果你追求更简便的封装,可以使用
torch.utils.benchmark.Timer,它内部已经处理了同步和预热的逻辑,但手动编写上述脚本能让你在处理“缓存刷新”和“底层休眠”时有更高的自由度。
评论
匿名评论隐私政策




