DCU 性能分析方法:从端到端时间、PyTorch Profiler 到整卡显存
本文只讨论通用性能分析方法,不披露任何未结束项目(依旧是博主的先导杯还没结束)的模型(先导杯赛题pangu-weather)结构、热点分布、优化方案或评测结果。
摘要
DCU 性能优化最常见的误区,是拿一个时间或一个显存数字直接下结论。端到端 wall time、设备事件、Profiler 算子时间、PyTorch allocated、PyTorch reserved 和整卡 VRAM 回答的是不同问题。只有把这些口径分开,优化结果才可解释、可复现。
本文给出一套适用于 PyTorch/DCU 推理和训练任务的性能分析流程:如何建立基线、如何放置同步、如何导出 trace、如何判断算子热点、如何测量显存,以及怎样避免首次编译和缓存状态污染实验。
关键词:DCU、PyTorch Profiler、性能分析、显存测量、Benchmark
1. 先定义要测什么
一次完整任务至少有四种时间:
| 时间口径 | 包含内容 | 适合回答的问题 |
|---|---|---|
| 进程wall time | 启动、导入、编译、加载、数据、计算、保存 | 用户实际等待多久 |
| 样本端到端时间 | 规定区域内的搬运、计算和同步 | 正式业务性能 |
| 设备事件时间 | 两个设备event之间的GPU/DCU工作 | 某段设备工作多久 |
| Profiler算子时间 | 按operator/kernel聚合 | 时间花在哪些算子 |
如果目标是优化正式推理,最重要的是样本端到端时间。微基准和 Profiler 用来解释它,而不是替代它。
2. 为什么同步决定了时间是否可信
设备计算通常异步提交。下面的写法只测到了提交开销:
start = time.perf_counter()
y = model(x)
elapsed = time.perf_counter() - start
更可靠的主机计时:
torch.cuda.synchronize()
start = time.perf_counter()
y = model(x)
torch.cuda.synchronize()
elapsed = time.perf_counter() - start
HIP版PyTorch通常继续使用 torch.cuda.synchronize() API。多 stream 程序还要确认被测工作是否全部在当前同步范围内;只同步默认 stream 可能遗漏 copy stream。
正式评测如果规定了时间边界,应以规定为准。不能为了得到更小数字,把必要搬运、同步或后处理移到计时区外。
3. 冷启动与稳定段要分开记录
首轮可能包含:
- Python模块首次导入;
- 自定义扩展编译和加载;
- 动态库初始化;
- 后端算法选择;
- 内存池扩张;
- 文件缓存和数据页加载。
建议同时保存原始序列和稳定段统计:
times = []
for sample in samples:
torch.cuda.synchronize()
begin = time.perf_counter()
run(sample)
torch.cuda.synchronize()
times.append(time.perf_counter() - begin)
print("all:", times)
print("steady_mean:", sum(times[2:]) / len(times[2:]))
这里跳过前两个样本只是分析示例,不应擅自改变正式规则。原始时间必须完整保留。
4. 用 PyTorch Profiler 找方向
一个通用的推理采样模板:
import torch
activities = [torch.profiler.ProfilerActivity.CPU]
if torch.cuda.is_available():
activities.append(torch.profiler.ProfilerActivity.CUDA)
with torch.profiler.profile(
activities=activities,
record_shapes=True,
profile_memory=True,
with_stack=False,
) as prof:
with torch.inference_mode():
output = model(inputs)
torch.cuda.synchronize()
print(prof.key_averages().table(
sort_by="self_cuda_time_total",
row_limit=30,
))
prof.export_chrome_trace("trace.json")
在 HIP 版 PyTorch 中,Profiler 的字段可能仍沿用 CUDA 命名。判断依据应是当前PyTorch是否为HIP构建,而不是字段名字。
5. 如何读算子表
算子表至少关注五列:
self device time
total device time
调用次数
平均单次时间
输入shape
不同模式代表不同问题:
- 总时间高、调用少:适合优化单个大算子;
- 总时间高、调用多、单次很短:可能是调度和中间张量问题;
- CPU时间高、设备时间低:可能是Python循环或同步;
- device memory增长明显:需要检查工作区和生命周期;
- shape种类很多:专用kernel可能难以覆盖。
不要只按总时间排序就开始写kernel。先确认该算子是否在端到端关键路径、是否能被后端自动融合、输出是否被复用,以及替换它会不会增加显存。
6. Chrome Trace 看调用关系
聚合表告诉你“谁最贵”,trace告诉你“为什么会贵”。打开 trace.json 后重点观察:
- kernel之间是否有大段空洞;
- 是否出现频繁H2D/D2H;
- CPU线程是否及时提交工作;
- 相邻逐元素算子是否反复读写同一大张量;
- stream之间是否真正重叠;
- 是否有意外同步把流水切断。
一次性能问题往往不是某个kernel慢,而是十几个短kernel之间存在同步或临时分配。
7. PyTorch显存的四个数字
常用接口:
torch.cuda.memory_allocated()
torch.cuda.memory_reserved()
torch.cuda.max_memory_allocated()
torch.cuda.max_memory_reserved()
含义:
allocated:仍被活动tensor占用;reserved:PyTorch allocator向运行时申请并保留;max_allocated:本轮活动tensor峰值;max_reserved:本轮内存池峰值。
测试单轮峰值前可以重置统计:
torch.cuda.reset_peak_memory_stats()
run_once()
torch.cuda.synchronize()
print(torch.cuda.max_memory_allocated())
print(torch.cuda.max_memory_reserved())
reserved 大于 allocated 并不意味着泄漏。它可能是缓存分配器为了复用而保留的block。真正的泄漏通常表现为多轮运行后活动引用和峰值持续增长。
8. 为什么整卡显存与PyTorch不同
设备管理工具看到的整卡显存通常还包含:
HIP context
已加载代码模块
数学库workspace
通信库buffer
PyTorch allocator
其他进程
因此应同时记录两套数据:
框架内:allocated / reserved
框架外:设备管理工具的进程或整卡VRAM
如果两者变化方向相反,先检查采样时机、扩展加载、首次库初始化和同卡其他进程,不要马上得出“代码泄漏”的结论。
9. empty_cache()不是通用优化
torch.cuda.empty_cache() 只能归还没有活动引用的缓存块。它不能释放仍被tensor使用的显存,而且可能导致下一轮重新申请。
只有在明确需要把空闲缓存归还给其他进程,或正在诊断allocator行为时,才应考虑调用。性能循环中每轮调用通常会增加同步和分配开销。
降低峰值的优先顺序应该是:
- 找出峰值时同时存活的tensor;
- 缩短临时tensor生命周期;
- 复用固定buffer;
- 调整算法工作区;
- 最后再讨论allocator缓存策略。
10. 一个可复现的实验矩阵
每个候选只改一个变量:
| 字段 | 示例内容 |
|---|---|
| variant | baseline / candidate_a |
| device | 设备型号与架构 |
| software | DTK、PyTorch、Python版本 |
| input | 固定shape和dtype |
| repetitions | 原始样本数或重复次数 |
| timing | 完整时间数组、稳定段均值 |
| memory | max allocated、max reserved、整卡峰值 |
| correctness | 最大误差、平均误差或任务指标 |
不要只保存“最快的一次”。建议至少重复多轮,报告中位数或稳定均值,并保留原始数据。
11. 性能优化的三道门
一个候选进入最终版本前应同时通过:
正确性门:输出误差在允许范围内
性能门:端到端收益稳定,不只是微基准更快
资源门:显存、编译时间和包体积没有不可接受回退
只通过微基准的实现仍然是实验原型。真正可提交或可上线的优化必须在完整工作负载中成立。
参考与依赖说明
本文示例基于 PyTorch Profiler、PyTorch accelerator memory API 和通用HIP/DCU设备管理方法。实际字段和命令应以当前DTK、PyTorch及设备官方文档为准。文章不包含任何项目专有性能数据或优化实现。
更多推荐




所有评论(0)