TorchNPU API 到 ACLNN 算子映射原理与分析方法
作者:昇腾实战派
知识地图:https://blog.csdn.net/Lumos_Lovegood/article/details/161601003
1. 背景
1.1 调用关系
TorchNPU API 在 Ascend NPU 上执行时,通常经过以下层次:
TorchNPU Python API
↓
ATen 算子或框架逻辑
↓
torch_npu / op-plugin
↓
ACLNN 算子接口
↓
NPU
1.2 映射差异来源
TorchNPU API 与 ACLNN 算子并非固定的一对一关系,主要原因包括:
- 同一 API 存在 Tensor、Scalar、out、inplace 等多种重载;
- 不同数据类型、数据格式或参数会触发不同的 dispatch 分支;
- 部分 API 通过 TorchNPU Composite 机制分解为多个基础 API;
- optimizer、callback、policy、autocast 和 export 包含框架逻辑或用户代码;
- view 类 API 仅修改张量元数据,不执行 ACLNN 算子;
- 分布式 API 可能调用通信库,而非 ACLNN 算子;
- 不支持的 API 可能回退到 CPU 执行。
1.3 分析目标
映射分析需要确定目标 API 在各个可触发分支中实际调用的 ACLNN 算子,并区分以下状态:源码确认、运行时验证、无法触发、环境不支持。映射结论与 TorchNPU、torch_npu、CANN 及设备版本相关。
2. 分析原理
2.1 以 NPU 适配层为准
ACLNN 算子名称由 torch_npu 和 op-plugin 的 NPU 后端实现决定。TorchNPU 源码用于确认 ATen dispatch、Composite 分解和 API 转调关系,但不能直接替代 NPU 后端源码。
源码检查顺序:
torch_npu和 op-plugin;- opapi、手写适配和 aclops 实现;
- TorchNPU ATen dispatch、原生实现和算子分解;
- NPU Profiler 运行结果。
2.2 递归到底层 ACLNN 算子
以下内容属于调用链中间层,不作为最终映射:
CompositeImplicitAutograd;CompositeExplicitAutograd;- 转调到另一个 TorchNPU API;
- 调用 callback 或 policy;
- 返回可执行图或其他产物。
分析需要继续展开,直到得到实际执行的 ACLNN 算子,或确认该路径没有 ACLNN 算子调用。
2.3 按具体分支分析
源码中出现多个 ACLNN 算子名称并不等同于组合算子。需要确认这些算子是在同一次调用中共同执行,还是由不同条件互斥选择。
例如:
aclnnAdd|aclnnAdds
表示两个互斥的单算子分支。
aclnnMm&aclnnRelu
表示同一分支中依次执行两个 ACLNN 算子。
2.4 区分源码证据和运行时证据
源码用于确定可触发的候选分支,Profiler 用于确认当前输入实际进入的分支。两类证据应分别记录。源码存在某个分支时,还需要确认目标 API 是否能够触发该分支。被转调 API 支持某种数据格式,不代表目标 API 也允许该数据格式。
3. 分析流程
3.1 规范化 API 名称
确认目标 API 的命名空间和重载。
| API 类型 | 检查内容 |
|---|---|
torch.xxx | 对应的 ATen function |
Tensor.xxx | Tensor、Scalar 和 out 重载 |
Tensor.xxx_ | inplace 重载 |
| optimizer/module 方法 | Python 分支控制逻辑 |
torch_npu API | NPU 自定义注册和实现 |
| callback、policy、export | 外层逻辑、用户函数和返回产物 |
| 分布式 API | 通信语义,不与同名 Tensor API 合并 |
源码搜索使用规范化后的精确名称,模糊子串结果仅用于定位。
3.2 检查 NPU 后端实现
先检查 op_plugin_functions.yaml,再定位对应的 opapi 或手写 kernel。
常用源码入口:
op_plugin_functions.yaml
ops/opapi/
ops/aclops/
torch_npu/csrc/aten/ops/op_api/
需要搜索的调用形式:
EXEC_NPU_CMD
DO_COMPATIBILITY
GetOpApiFuncAddr
RunOpApiV2
检查内容:
- 所有 ACLNN 算子调用;
- Tensor、Scalar、out 和 inplace 分支;
- 数据类型、形状、内存布局和数据格式条件;
- CANN 或设备版本条件;
- API 转调路径;
- 动态加载的 ACLNN 算子调用。
同一文件中的其他重载不能直接并入目标 API。存在当前 opapi 实现时,不将 aclops 旧路径名称合并到同一映射中。YAML 中的 structured_inherit 需要递归解析。YAML 信息不完整时,以实际 kernel 源码为准。
3.3 检查 TorchNPU 分解路径
没有 NPU 后端实现时,检查:
aten/src/ATen/native/native_functions.yaml
torch/_decomp/
torch/_refs/
aten/src/ATen/native/
对分解或转调得到的每个子 API,重新执行 NPU 后端实现检查,直到得到底层 ACLNN 算子。如果路径最终仅包含 view、元数据或框架控制逻辑,则记录为无算子,不继续搜索同名 ACLNN 算子。
3.4 确认分支触发条件
对每个候选分支检查:
- 目标 API 是否提供对应参数;
- 数据类型和数据格式是否允许;
- 上层逻辑是否提前执行检查或转换;
- 当前运行库和设备是否支持;
- 能否构造满足条件的输入。
处理规则:
| 状态 | 处理方式 |
|---|---|
| 目标 API 可触发,运行时已触发 | 写入源码、运行和最终映射 |
| 目标 API 可触发,当前未触发 | 写入源码验证,备注未验证原因 |
| 目标 API 无法触发 | 不写入目标 API 最终映射 |
| 目标 API 可触发,当前环境不支持 | 保留源码验证,记录运行限制 |
3.5 设计运行时测试
测试条件由源码中的 dispatch 条件确定。常见维度包括:
- Tensor 与 Scalar;
- function、method、inplace 和 out;
- 数据类型、形状和数据格式;
- dim、keepdim、alpha、beta 等参数;
- foreach、fused、capturable 等模式;
- 首次调用、后续调用、export 和产物执行阶段。
仅对会改变 dispatch 的条件生成独立用例。
Profiler 用例要求:
- 输入在 Profiler 外创建;
- Profiler 内仅调用目标 API;
- 调用后执行设备同步;
- 从
key_averages()的event.key收集全部 ACLNN 算子; - 不使用
exec(); - 高风险用例使用独立进程。
activities = [torch.profiler.ProfilerActivity.CPU]
if hasattr(torch.profiler.ProfilerActivity, "NPU"):
activities.append(torch.profiler.ProfilerActivity.NPU)
x = torch.randn(8, 8, device="npu")
with torch.profiler.profile(activities=activities) as prof:
result = torch.abs(x)
torch.npu.synchronize()
ops = []
for event in prof.key_averages():
name = str(event.key)
if "aclnn" in name.lower() and name not in ops:
ops.append(name)
3.6 对比源码和运行结果
分别保留:
- 源码中目标 API 可触发的 ACLNN 算子;
- 当前环境实际捕获的 ACLNN 算子;
- 综合两者得到的最终映射。
运行时出现静态扫描未发现的 ACLNN 算子时,继续检查动态加载、包装逻辑和首次调用初始化。源码存在但运行时未触发时,继续检查输入条件和环境能力。
4. 结果说明
4.1 分类
| 具体分支中的底层 ACLNN 算子 | 分类 |
|---|---|
| 0 个 | 无算子,并按视图、框架、分布式或 CPU 回退细分 |
| 1 个 | 单算子;API 转调后仍只有一个底层算子也按单算子处理 |
| 2 个及以上不同算子 | 组合算子 |
| 多个算子互斥选择,一次只执行一个 | 单算子多变体 |
| NPU 专属 API | NPU 自定义算子 |
4.2 ACLNN 算子表达式
算子名称字段仅使用 ACLNN 算子名称、&、| 和括号。
| 符号 | 含义 | 示例 |
|---|---|---|
& | 同一分支共同执行 | aclnnMm&aclnnRelu |
| | 互斥分支或变体 | aclnnAdd|aclnnAdds |
() | 标记组合分支边界 | (aclnnA&aclnnB)|(aclnnA&aclnnC) |
分支条件和限制写入备注,不写入算子字段。
4.3 七列表格
| 列 | 内容 |
|---|---|
| API 名称 | 完整 API 名称 |
| 是否单算子调用 | 单算子、组合算子、NPU 自定义算子或无算子细分类别 |
| aclnn 算子名称 | 最终 ACLNN 算子表达式 |
| 分类 | 计算、框架、分布式或自定义 API |
| 源码验证 | 目标 API 可触发的源码表达式;没有则 notfound |
| 运行日志验证 | Profiler 实际捕获的表达式;没有则 NONE |
| 备注 | 分解关系、无法触发的分支、环境限制和差异 |
相关列需要同步更新:
- 运行时发现辅助算子:更新最终映射、运行验证和备注;
- 源码确认新分支:更新分类、最终映射、源码验证和备注;
- 确认目标 API 无法触发分支:从最终映射和源码验证中移除,并记录原因。
4.4 示例:torch._addmm_activation
该 API 包含 addmm,随后根据 use_gelu 选择 ReLU 或 GELU。
运行结果:
use_gelu=False: aclnnAddmm&aclnnInplaceRelu
use_gelu=True: aclnnAddmm&aclnnGelu
最终映射:
(aclnnAddmm&aclnnInplaceRelu)|(aclnnAddmm&aclnnGelu)
普通 addmm 支持 aclnnAddmmWeightNz,但 _addmm_activation 会拒绝对应内部格式,因此该分支不能由目标 API 触发。aclnnGeluV2 所需模式也不能由该 API 的 NPU 路径选择。
5. 补充说明
5.1 框架入口类 API
optimizer、callback、policy、autocast 和 export 分别分析:
- API 固定外层逻辑;
- 用户传入函数或模型;
- 返回产物的后续执行。
用户函数中的算子不作为外层 API 的固定映射。导出图执行产生的算子不作为 export 阶段的固定映射。optimizer 需要分别分析 foreach、fused 和 single-tensor 分支。
5.2 动态 ACLNN 算子调用
EXEC_NPU_CMD 搜索可能漏掉通过 GetOpApiFuncAddr 或 RunOpApiV2 执行的 ACLNN 算子。Profiler 捕获到静态扫描未发现的算子时,需要继续检查动态调用路径。
5.3 CPU 回退
Profiler 中没有 ACLNN 算子不代表 API 一定无算子。需要同时检查 stderr 中的 CPU fallback 警告和实际输出设备。
5.4 结果状态
报告中区分以下状态:
- 源码确认且运行时验证;
- 源码确认但尚未触发;
- 目标 API 无法触发;
- 当前环境不支持;
- 运行时捕获但静态扫描未定位。
5.5 版本范围
没有 NPU 运行条件时,仅输出源码分析结果,不标记为运行时已验证。不同版本之间需要重新核对源码分支和 Profiler 结果。
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐
所有评论(0)