用 Profiler 追踪 ops-transformer 算子GE 融合与 Runtime 调度的实战调试大模型训练跑不动大多数人第一反应是算力不够。但我见过的实际情况里80% 以上的性能问题出在算子调度和数据搬运上不是算力本身。解决这个问题最有用的工具就是 CANN 内置的 Profiler。这篇文章不聊架构理论手把手带你在真实环境里用 Profiler 追踪 ops-transformer 的算子理解 GE 的融合决策和 Runtime 的调度行为。环境确认先检查工具是否就绪# 确认 CANN 已安装source/usr/local/Ascend/ascend-toolkit/set_env.shwhichascend snorkel-validate# 确认昇腾 NPU 可用npu-smi infonpu-smi info输出 NPU 型号和状态后继续下一步。如果这一步就报错先把 CANN 环境装好再往下走。第一步写一个带 ops-transformer 算子的测试脚本找个能复现问题的地方先写一个简单的测试脚本# test_attention_profile.pyimporttorchimporttorch_npufromflash_attention_opsimportflash_attention_npu# 构造一个 Attention 计算batch,heads,seq_len,dim4,32,2048,64qtorch.randn(batch,heads,seq_len,dim,dtypetorch.float16).npu()ktorch.randn(batch,heads,seq_len,dim,dtypetorch.float16).npu()vtorch.randn(batch,heads,seq_len,dim,dtypetorch.float16).npu()# 跑 10 次取平均时间importtimefor_inrange(3):_flash_attention_npu(q,k,v,causalTrue)# 先预热有 JIT 编译torch.npu.synchronize()starttime.perf_counter()for_inrange(10):outflash_attention_npu(q,k,v,causalTrue)torch.npu.synchronize()elapsed(time.perf_counter()-start)/10print(f单次耗时:{elapsed*1000:.2f}ms)跑通之后记录下单次耗时。后面开 Profiler 再跑一次对比有没有性能下降。第二步开启 Profiler 数据采集CANN 的 Profiler 会抓 GPU Trace 和 AI Trace 两类数据。GPU Trace 记录每个算子在 NPU 上的执行时间AI Trace 记录 Python 层的调用链路。# test_attention_profile.py 中加入 Profilerfromtorch_npu.profilerimportprofilewithprofile(activities[torch_npu.profiler.ProfilerActivity.NPU,# 记录 NPU 层torch_npu.profiler.ProfilerActivity.CPU,# 记录 CPU 层调用],record_shapesTrue,# 记录输入 shape用于分析算子融合profile_memoryTrue,# 记录显存使用with_stackTrue,# 记录 Python 调用栈export_nameattention_trace.json# 输出文件名):for_inrange(10):outflash_attention_npu(q,k,v,causalTrue)执行完成后当前目录下会生成attention_trace.json。第三步用 CANN 的 Profiler UI 分析数据把attention_trace.json拉到有 GUI 的机器上分析或者在服务器上用内置的查看工具# 如果服务器有图形界面直接用 HUD 打开# CANN Profiler GUI 的启动命令ascend-profile-iattention_trace.json# 如果是纯命令行机器导出关键数据ascend-profile-iattention_trace.json-osummary.csv--formatcsv在 Profiler GUI 里重点关注这几个视图Timeline 视图GPU Trace找到 ops-transformer 的 FlashAttention 算子看它在整个 Timeline 里占的宽度。宽说明执行时间长窄说明快。同时注意它旁边有没有其他算子紧挨着——如果 MatMul → Softmax → MatMul 三个算子紧紧挨着说明没有被融合如果中间有大段空白说明有数据搬运的等待时间。Operator 视图AI Trace这个视图列出每个算子的总耗时和调用次数。重点找 ops-transformer 相关的算子看单次平均耗时是多少有没有异常值。如果某个算子的耗时波动很大最大值是最小值的 5 倍以上大概率是数据排布不对触发了额外的格式转换。Memory 视图看 HBM 的读写量。ops-transformer 的融合算子应该显著减少中间结果的写入。如果发现大量小块的 HBM 读写说明算子没走融合路径GE 的优化没有生效。第四步分析 GE 的融合决策GE 的融合决策记录在编译日志里。要看这个日志先在训练脚本里加上环境变量让 GE 输出详细的融合信息# 设置 GE 融合日志级别exportASCEND_GLOBAL_LOG_LEVEL3exportENABLE_OOL_OP痕过融合LOG1# 重新跑你的训练脚本日志会输出 GE 的融合决策过程python train_llama.py21|teege_fusion_log.txt打开ge_fusion_log.txt搜索ops-transformer或flash_attention会看到类似这样的输出[GE Fusion] 子图 #15 检测到算子序列: MatMul[qkt] Softmax MatMul[pv] 匹配融合规则: flash_attention_fusion_pass 融合为单一算子: FlashAttentionKernel 输出 shape: (batch, heads, seq_len, dim) [GE Fusion] 子图 #23 检测到算子序列: GeLU Add 匹配融合规则: activation_fusion_pass 融合为: FastGeLU如果你的 FlashAttention 没有出现在融合日志里说明 GE 没有识别到这个算子序列。可能的原因输入的 dtype 是 float32 而不是 float16某些融合规则需要 dtype 匹配、Tensor 的 shape 没有对齐到融合规则要求的倍数、算子没有被正确注册到 Framework Adaptor。第五步看 Runtime 的调度行为Runtime 的调度信息也在 Profiler 里但要看得更细可以用npu-smi实时监控# 另开一个终端持续采样 NPU 利用率和 HBM 带宽watch-n0.5npu-smi dmon-c1-spuc-d10这个命令每 0.5 秒输出一次 NPU 利用率和显存带宽。如果 NPU 利用率长期低于 60%但模型在正常跑问题大概率出在数据搬运而不是计算——可能是输入数据没有提前搬到 NPU、算子之间有依赖等待、或者 batch size 太小导致计算密度不够。如果 HBM 带宽利用率很高80%但 NPU 利用率不高说明带宽成了瓶颈——这是经典的分块策略问题ops-transformer 的 FlashAttention 在这种场景下尤其容易出问题因为它本来就是为了解决带宽瓶颈而设计的如果带宽还是不够可能是 tile 大小没有针对当前 shape 做优化。第六步调优之后的验证根据 Profiler 的分析结果做调优改完之后再跑一次带 Profiler 的测试对比两张 Timeline 图# 对比脚本importjsondefload_profile(path):withopen(path)asf:returnjson.load(f)beforeload_profile(attention_trace_before.json)afterload_profile(attention_trace_after.json)# 提取 ops-transformer 相关算子的耗时defget_op_time(profile,op_name_prefix):returnsum(evt[duration]forevtinprofile[traceEvents]ifevt[name].startswith(op_name_prefix))op_nameFlashAttention# 根据实际日志里的名字调整t_beforeget_op_time(before,op_name)t_afterget_op_time(after,op_name)speedupt_before/t_afterprint(f加速比:{speedup:.2f}x)如果加速比 1.5x说明调优方向对了。如果没变化说明改动没有触及真正的瓶颈。系统学习 GE 和 Runtime 的调试方法Profiler 只是一个工具真正用好它需要理解 GE 和 Runtime 的设计逻辑。cann-learning-hub 里有关于 Profiler 使用和结果解读的专项教程比官方文档更贴近实战建议按这个顺序学cann-learning-hub → CANN Profiler 使用指南 → 学会解读 Timeline 和 Operator 视图cann-learning-hub → GE 图引擎调优 → 理解融合规则怎么写、怎么调试cann-learning-hub → Runtime 性能调优 → 理解调度策略和显存管理回到你的训练脚本用 Profiler 验证学到的调优方法相关仓库https://atomgit.com/cann/ops-transformerhttps://atomgit.com/cann/cann-learning-hubhttps://atomgit.com/cann/ge
用 Profiler 追踪 ops-transformer 算子:GE 融合与 Runtime 调度的实战调试
发布时间:2026/5/22 3:35:49
用 Profiler 追踪 ops-transformer 算子GE 融合与 Runtime 调度的实战调试大模型训练跑不动大多数人第一反应是算力不够。但我见过的实际情况里80% 以上的性能问题出在算子调度和数据搬运上不是算力本身。解决这个问题最有用的工具就是 CANN 内置的 Profiler。这篇文章不聊架构理论手把手带你在真实环境里用 Profiler 追踪 ops-transformer 的算子理解 GE 的融合决策和 Runtime 的调度行为。环境确认先检查工具是否就绪# 确认 CANN 已安装source/usr/local/Ascend/ascend-toolkit/set_env.shwhichascend snorkel-validate# 确认昇腾 NPU 可用npu-smi infonpu-smi info输出 NPU 型号和状态后继续下一步。如果这一步就报错先把 CANN 环境装好再往下走。第一步写一个带 ops-transformer 算子的测试脚本找个能复现问题的地方先写一个简单的测试脚本# test_attention_profile.pyimporttorchimporttorch_npufromflash_attention_opsimportflash_attention_npu# 构造一个 Attention 计算batch,heads,seq_len,dim4,32,2048,64qtorch.randn(batch,heads,seq_len,dim,dtypetorch.float16).npu()ktorch.randn(batch,heads,seq_len,dim,dtypetorch.float16).npu()vtorch.randn(batch,heads,seq_len,dim,dtypetorch.float16).npu()# 跑 10 次取平均时间importtimefor_inrange(3):_flash_attention_npu(q,k,v,causalTrue)# 先预热有 JIT 编译torch.npu.synchronize()starttime.perf_counter()for_inrange(10):outflash_attention_npu(q,k,v,causalTrue)torch.npu.synchronize()elapsed(time.perf_counter()-start)/10print(f单次耗时:{elapsed*1000:.2f}ms)跑通之后记录下单次耗时。后面开 Profiler 再跑一次对比有没有性能下降。第二步开启 Profiler 数据采集CANN 的 Profiler 会抓 GPU Trace 和 AI Trace 两类数据。GPU Trace 记录每个算子在 NPU 上的执行时间AI Trace 记录 Python 层的调用链路。# test_attention_profile.py 中加入 Profilerfromtorch_npu.profilerimportprofilewithprofile(activities[torch_npu.profiler.ProfilerActivity.NPU,# 记录 NPU 层torch_npu.profiler.ProfilerActivity.CPU,# 记录 CPU 层调用],record_shapesTrue,# 记录输入 shape用于分析算子融合profile_memoryTrue,# 记录显存使用with_stackTrue,# 记录 Python 调用栈export_nameattention_trace.json# 输出文件名):for_inrange(10):outflash_attention_npu(q,k,v,causalTrue)执行完成后当前目录下会生成attention_trace.json。第三步用 CANN 的 Profiler UI 分析数据把attention_trace.json拉到有 GUI 的机器上分析或者在服务器上用内置的查看工具# 如果服务器有图形界面直接用 HUD 打开# CANN Profiler GUI 的启动命令ascend-profile-iattention_trace.json# 如果是纯命令行机器导出关键数据ascend-profile-iattention_trace.json-osummary.csv--formatcsv在 Profiler GUI 里重点关注这几个视图Timeline 视图GPU Trace找到 ops-transformer 的 FlashAttention 算子看它在整个 Timeline 里占的宽度。宽说明执行时间长窄说明快。同时注意它旁边有没有其他算子紧挨着——如果 MatMul → Softmax → MatMul 三个算子紧紧挨着说明没有被融合如果中间有大段空白说明有数据搬运的等待时间。Operator 视图AI Trace这个视图列出每个算子的总耗时和调用次数。重点找 ops-transformer 相关的算子看单次平均耗时是多少有没有异常值。如果某个算子的耗时波动很大最大值是最小值的 5 倍以上大概率是数据排布不对触发了额外的格式转换。Memory 视图看 HBM 的读写量。ops-transformer 的融合算子应该显著减少中间结果的写入。如果发现大量小块的 HBM 读写说明算子没走融合路径GE 的优化没有生效。第四步分析 GE 的融合决策GE 的融合决策记录在编译日志里。要看这个日志先在训练脚本里加上环境变量让 GE 输出详细的融合信息# 设置 GE 融合日志级别exportASCEND_GLOBAL_LOG_LEVEL3exportENABLE_OOL_OP痕过融合LOG1# 重新跑你的训练脚本日志会输出 GE 的融合决策过程python train_llama.py21|teege_fusion_log.txt打开ge_fusion_log.txt搜索ops-transformer或flash_attention会看到类似这样的输出[GE Fusion] 子图 #15 检测到算子序列: MatMul[qkt] Softmax MatMul[pv] 匹配融合规则: flash_attention_fusion_pass 融合为单一算子: FlashAttentionKernel 输出 shape: (batch, heads, seq_len, dim) [GE Fusion] 子图 #23 检测到算子序列: GeLU Add 匹配融合规则: activation_fusion_pass 融合为: FastGeLU如果你的 FlashAttention 没有出现在融合日志里说明 GE 没有识别到这个算子序列。可能的原因输入的 dtype 是 float32 而不是 float16某些融合规则需要 dtype 匹配、Tensor 的 shape 没有对齐到融合规则要求的倍数、算子没有被正确注册到 Framework Adaptor。第五步看 Runtime 的调度行为Runtime 的调度信息也在 Profiler 里但要看得更细可以用npu-smi实时监控# 另开一个终端持续采样 NPU 利用率和 HBM 带宽watch-n0.5npu-smi dmon-c1-spuc-d10这个命令每 0.5 秒输出一次 NPU 利用率和显存带宽。如果 NPU 利用率长期低于 60%但模型在正常跑问题大概率出在数据搬运而不是计算——可能是输入数据没有提前搬到 NPU、算子之间有依赖等待、或者 batch size 太小导致计算密度不够。如果 HBM 带宽利用率很高80%但 NPU 利用率不高说明带宽成了瓶颈——这是经典的分块策略问题ops-transformer 的 FlashAttention 在这种场景下尤其容易出问题因为它本来就是为了解决带宽瓶颈而设计的如果带宽还是不够可能是 tile 大小没有针对当前 shape 做优化。第六步调优之后的验证根据 Profiler 的分析结果做调优改完之后再跑一次带 Profiler 的测试对比两张 Timeline 图# 对比脚本importjsondefload_profile(path):withopen(path)asf:returnjson.load(f)beforeload_profile(attention_trace_before.json)afterload_profile(attention_trace_after.json)# 提取 ops-transformer 相关算子的耗时defget_op_time(profile,op_name_prefix):returnsum(evt[duration]forevtinprofile[traceEvents]ifevt[name].startswith(op_name_prefix))op_nameFlashAttention# 根据实际日志里的名字调整t_beforeget_op_time(before,op_name)t_afterget_op_time(after,op_name)speedupt_before/t_afterprint(f加速比:{speedup:.2f}x)如果加速比 1.5x说明调优方向对了。如果没变化说明改动没有触及真正的瓶颈。系统学习 GE 和 Runtime 的调试方法Profiler 只是一个工具真正用好它需要理解 GE 和 Runtime 的设计逻辑。cann-learning-hub 里有关于 Profiler 使用和结果解读的专项教程比官方文档更贴近实战建议按这个顺序学cann-learning-hub → CANN Profiler 使用指南 → 学会解读 Timeline 和 Operator 视图cann-learning-hub → GE 图引擎调优 → 理解融合规则怎么写、怎么调试cann-learning-hub → Runtime 性能调优 → 理解调度策略和显存管理回到你的训练脚本用 Profiler 验证学到的调优方法相关仓库https://atomgit.com/cann/ops-transformerhttps://atomgit.com/cann/cann-learning-hubhttps://atomgit.com/cann/ge