14
DCU技术达人
DCU 性能分析方法:从端到端时间、PyTorch Profiler 到整卡显存
2026-09-30 23:25:50
21
> 本文只讨论通用性能分析方法,不披露任何未结束项目(依旧是博主的先导杯还没结束)的模型(先导杯赛题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. 为什么同步决定了时间是否可信 设备计算通常异步提交。下面的写法只测到了提交开销: ```python start = time.perf_counter() y = model(x) elapsed = time.perf_counter() - start ``` 更可靠的主机计时: ```python 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模块首次导入; - 自定义扩展编译和加载; - 动态库初始化; - 后端算法选择; - 内存池扩张; - 文件缓存和数据页加载。 建议同时保存原始序列和稳定段统计: ```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 找方向 一个通用的推理采样模板: ```python 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. 如何读算子表 算子表至少关注五列: ```text 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显存的四个数字 常用接口: ```python 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`:本轮内存池峰值。 测试单轮峰值前可以重置统计: ```python 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不同 设备管理工具看到的整卡显存通常还包含: ```text HIP context 已加载代码模块 数学库workspace 通信库buffer PyTorch allocator 其他进程 ``` 因此应同时记录两套数据: ```text 框架内:allocated / reserved 框架外:设备管理工具的进程或整卡VRAM ``` 如果两者变化方向相反,先检查采样时机、扩展加载、首次库初始化和同卡其他进程,不要马上得出“代码泄漏”的结论。 ## 9. `empty_cache()`不是通用优化 `torch.cuda.empty_cache()` 只能归还没有活动引用的缓存块。它不能释放仍被tensor使用的显存,而且可能导致下一轮重新申请。 只有在明确需要把空闲缓存归还给其他进程,或正在诊断allocator行为时,才应考虑调用。性能循环中每轮调用通常会增加同步和分配开销。 降低峰值的优先顺序应该是: 1. 找出峰值时同时存活的tensor; 2. 缩短临时tensor生命周期; 3. 复用固定buffer; 4. 调整算法工作区; 5. 最后再讨论allocator缓存策略。 ## 10. 一个可复现的实验矩阵 每个候选只改一个变量: | 字段 | 示例内容 | |---|---| | variant | baseline / candidate_a | | device | 设备型号与架构 | | software | DTK、PyTorch、Python版本 | | input | 固定shape和dtype | | repetitions | 原始样本数或重复次数 | | timing | 完整时间数组、稳定段均值 | | memory | max allocated、max reserved、整卡峰值 | | correctness | 最大误差、平均误差或任务指标 | 不要只保存“最快的一次”。建议至少重复多轮,报告中位数或稳定均值,并保留原始数据。 ## 11. 性能优化的三道门 一个候选进入最终版本前应同时通过: ```text 正确性门:输出误差在允许范围内 性能门:端到端收益稳定,不只是微基准更快 资源门:显存、编译时间和包体积没有不可接受回退 ``` 只通过微基准的实现仍然是实验原型。真正可提交或可上线的优化必须在完整工作负载中成立。 ## 参考与依赖说明 本文示例基于 PyTorch Profiler、PyTorch accelerator memory API 和通用HIP/DCU设备管理方法。实际字段和命令应以当前DTK、PyTorch及设备官方文档为准。文章不包含任何项目专有性能数据或优化实现。
全部评论(0)
只看楼主
暂无数据
--
楼主
Level.2
0 帖子
楼主热帖
暂无数据