用 Profiler 追踪 ops-transformer 算子:GE 融合与 Runtime 调度的实战调试

用 Profiler 追踪 ops-transformer 算子:GE 融合与 Runtime 调度的实战调试

大模型训练跑不动,大多数人第一反应是"算力不够"。但我见过的实际情况里,80% 以上的性能问题出在算子调度和数据搬运上,不是算力本身。解决这个问题最有用的工具,就是 CANN 内置的 Profiler。

这篇文章不聊架构理论,手把手带你在真实环境里用 Profiler 追踪 ops-transformer 的算子,理解 GE 的融合决策和 Runtime 的调度行为。

环境确认:先检查工具是否就绪

bash 复制代码
# 确认 CANN 已安装
source /usr/local/Ascend/ascend-toolkit/set_env.sh
which ascend snorkel-validate

# 确认昇腾 NPU 可用
npu-smi info

npu-smi info 输出 NPU 型号和状态后,继续下一步。如果这一步就报错,先把 CANN 环境装好再往下走。

第一步:写一个带 ops-transformer 算子的测试脚本

找个能复现问题的地方,先写一个简单的测试脚本:

python 复制代码
# test_attention_profile.py
import torch
import torch_npu
from flash_attention_ops import flash_attention_npu

# 构造一个 Attention 计算
batch, heads, seq_len, dim = 4, 32, 2048, 64
q = torch.randn(batch, heads, seq_len, dim, dtype=torch.float16).npu()
k = torch.randn(batch, heads, seq_len, dim, dtype=torch.float16).npu()
v = torch.randn(batch, heads, seq_len, dim, dtype=torch.float16).npu()

# 跑 10 次取平均时间
import time
for _ in range(3):
    _ = flash_attention_npu(q, k, v, causal=True)  # 先预热,有 JIT 编译

torch.npu.synchronize()
start = time.perf_counter()
for _ in range(10):
    out = flash_attention_npu(q, k, v, causal=True)
torch.npu.synchronize()
elapsed = (time.perf_counter() - start) / 10
print(f"单次耗时: {elapsed*1000:.2f}ms")

跑通之后记录下单次耗时。后面开 Profiler 再跑一次,对比有没有性能下降。

第二步:开启 Profiler 数据采集

CANN 的 Profiler 会抓 GPU Trace 和 AI Trace 两类数据。GPU Trace 记录每个算子在 NPU 上的执行时间,AI Trace 记录 Python 层的调用链路。

python 复制代码
# test_attention_profile.py 中加入 Profiler
from torch_npu.profiler import profile

with profile(
    activities=[
        torch_npu.profiler.ProfilerActivity.NPU,  # 记录 NPU 层
        torch_npu.profiler.ProfilerActivity.CPU,  # 记录 CPU 层调用
    ],
    record_shapes=True,        # 记录输入 shape,用于分析算子融合
    profile_memory=True,      # 记录显存使用
    with_stack=True,          # 记录 Python 调用栈
    export_name="attention_trace.json"  # 输出文件名
):
    for _ in range(10):
        out = flash_attention_npu(q, k, v, causal=True)

执行完成后,当前目录下会生成 attention_trace.json

第三步:用 CANN 的 Profiler UI 分析数据

attention_trace.json 拉到有 GUI 的机器上分析,或者在服务器上用内置的查看工具:

bash 复制代码
# 如果服务器有图形界面,直接用 HUD 打开
# CANN Profiler GUI 的启动命令
ascend-profile -i attention_trace.json

# 如果是纯命令行机器,导出关键数据
ascend-profile -i attention_trace.json -o summary.csv --format csv

在 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 输出详细的融合信息:

bash 复制代码
# 设置 GE 融合日志级别
export ASCEND_GLOBAL_LOG_LEVEL=3
export ENABLE_OOL_OP痕过融合LOG=1

# 重新跑你的训练脚本,日志会输出 GE 的融合决策过程
python train_llama.py 2>&1 | tee ge_fusion_log.txt

打开 ge_fusion_log.txt,搜索 ops-transformerflash_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 实时监控:

bash 复制代码
# 另开一个终端,持续采样 NPU 利用率和 HBM 带宽
watch -n 0.5 npu-smi dmon -c 1 -s puc -d 10

这个命令每 0.5 秒输出一次 NPU 利用率和显存带宽。

如果 NPU 利用率长期低于 60%,但模型在正常跑,问题大概率出在数据搬运而不是计算------可能是输入数据没有提前搬到 NPU、算子之间有依赖等待、或者 batch size 太小导致计算密度不够。

如果 HBM 带宽利用率很高(>80%),但 NPU 利用率不高,说明带宽成了瓶颈------这是经典的分块策略问题,ops-transformer 的 FlashAttention 在这种场景下尤其容易出问题,因为它本来就是为了解决带宽瓶颈而设计的,如果带宽还是不够,可能是 tile 大小没有针对当前 shape 做优化。

第六步:调优之后的验证

根据 Profiler 的分析结果做调优,改完之后再跑一次带 Profiler 的测试,对比两张 Timeline 图:

python 复制代码
# 对比脚本
import json

def load_profile(path):
    with open(path) as f:
        return json.load(f)

before = load_profile("attention_trace_before.json")
after = load_profile("attention_trace_after.json")

# 提取 ops-transformer 相关算子的耗时
def get_op_time(profile, op_name_prefix):
    return sum(
        evt["duration"] for evt in profile["traceEvents"]
        if evt["name"].startswith(op_name_prefix)
    )

op_name = "FlashAttention"  # 根据实际日志里的名字调整
t_before = get_op_time(before, op_name)
t_after = get_op_time(after, op_name)
speedup = t_before / t_after
print(f"加速比: {speedup:.2f}x")

如果加速比 > 1.5x,说明调优方向对了。如果没变化,说明改动没有触及真正的瓶颈。

系统学习 GE 和 Runtime 的调试方法

Profiler 只是一个工具,真正用好它需要理解 GE 和 Runtime 的设计逻辑。cann-learning-hub 里有关于 Profiler 使用和结果解读的专项教程,比官方文档更贴近实战:

建议按这个顺序学:

  1. cann-learning-hub → CANN Profiler 使用指南 → 学会解读 Timeline 和 Operator 视图
  2. cann-learning-hub → GE 图引擎调优 → 理解融合规则怎么写、怎么调试
  3. cann-learning-hub → Runtime 性能调优 → 理解调度策略和显存管理
  4. 回到你的训练脚本,用 Profiler 验证学到的调优方法

相关仓库:

https://atomgit.com/cann/ops-transformer

https://atomgit.com/cann/cann-learning-hub

https://atomgit.com/cann/ge

相关推荐
皮皮学姐分享-ppx2 小时前
政府绿色采购数据库(2015-2024.3)
大数据·网络·数据库·人工智能·制造
GIS数据转换器2 小时前
基于3D GIS的监控视频精准标定平台
人工智能·物联网·3d·音视频·无人机·知识图谱
专注VB编程开发20年3 小时前
AI 生成C# WinForm 窗体 = 目前就是垃圾
开发语言·人工智能·c#
深小乐3 小时前
Claude Fable5 尝鲜,效果挺不错
人工智能
Nayxxu3 小时前
Gemini + RAG 企业知识库教程:从文档切片到答案生成
运维·人工智能
冬奇Lab3 小时前
真正的 AI-Native Workflow 是什么?——四个判断测试
人工智能·agent
冬奇Lab3 小时前
每日一个开源项目(第128篇):Agent Skills - 给 AI 编程 Agent 装上工程纪律
人工智能·开源·资讯
Deepoch4 小时前
Deepoc VLA开发板:采摘机器人的环境鲁棒作业与不确定性应对
人工智能·机器人·采摘机器人·deepoc
云栖梦泽在4 小时前
AI安全专项:AI人脸识别的安全风险与防护
人工智能·安全