图级优化的手术刀:自定义 Scope 融合模式与 Pass 开发
2025年昇腾CANN训练营第二季推出系列课程,助力开发者提升算子开发技能,参与可获认证证书及丰厚奖品。本文重点介绍图优化(GraphOptimization)中的自定义Pass开发技术,以ScopeFusionPass为例,详细讲解如何实现"Add+Relu"算子融合。内容包括:核心概念图解、ScopeFusionPass工作机制、实战编写自定义Pass(定义模式、执行融合、
训练营简介 2025年昇腾CANN训练营第二季,基于CANN开源开放全场景,推出0基础入门系列、码力全开特辑、开发者案例等专题课程,助力不同阶段开发者快速提升算子开发技能。获得Ascend C算子中级认证,即可领取精美证书,完成社区任务更有机会赢取华为手机,平板、开发板等大奖。
报名链接:https://www.hiascend.com/developer/activities/cann20252#cann-camp-2502-intro

前言
在算子开发(Kernel)和图构建(Graph Build)之间,还存在一个神秘的中间层——图优化(Graph Optimization)。
当你把模型交给 GE(Graph Engine)时,GE 不会傻乎乎地照单全收。它会启动一系列的 Pass(优化趟),像安检扫描一样扫描你的计算图:
-
“哎,这里有三个算子可以合并!”
-
“这块内存可以复用!”
-
“这个数据格式转来转去太多余了,删掉!”
虽然 GE 内置了很多通用 Pass,但在面对特定的自定义算子或新兴模型架构(如 MoE, Mamba)时,内置规则可能失效。这时,你需要自己写一个 Custom Pass,告诉编译器:“如果你看到 A->B->C 这样的结构,请把它们换成 D。”
本期我们将深入 GE 的 Scope Fusion Pass 机制,手写一个自定义的图融合插件。
一、 核心图解:图上的“连连看”
图融合的本质就是 Pattern Matching(模式匹配) 和 Replacement(替换)。

二、 融合利器:Scope Fusion Pass
昇腾提供了多种 Pass 接口(如 Graph Fusion, UB Fusion),其中 Scope Fusion Pass 是最灵活的一种。它基于 TensorFlow/ONNX 的 Scope(命名空间)机制来识别子图。
2.1 融合流程
-
Define Pattern:定义你要找什么样的“猎物”(子图结构)。
-
Match:GE 自动在全图中寻找匹配的子图。
-
Fuse:触发回调函数,你在这里修改图结构(断开旧连线,插入新节点)。
三、 实战:编写一个 "Add + Relu" 融合 Pass
假设我们想把所有的 Add 后面紧接 Relu 的结构,融合为一个 AddRelu 算子(我们在第十一期写过这个算子)。
3.1 头文件与基类
我们需要继承 ScopeFusionPass 类。
#include "register/scope/scope_fusion_pass_register.h"
namespace ge {
class AddReluFusionPass : public ScopeFusionPass {
protected:
// 1. 定义模式:长什么样?
std::vector<ScopeFusionPatterns> DefinePatterns() override;
// 2. 定义名称
std::string PassName() override { return "AddReluFusionPass"; }
// 3. 执行融合:怎么改?
Status LastMatchScopesAndFusion(
std::vector<std::vector<Scope *>> &scopes,
std::vector<Scope *> &fused_scopes) override;
};
}
3.2 定义匹配模式 (Pattern)
我们需要告诉 GE:先找一个 Add,再找一个 Relu,且它们之间相连。
std::vector<ScopeFusionPatterns> AddReluFusionPass::DefinePatterns() {
std::vector<ScopeFusionPatterns> patterns;
ScopeFusionPatterns pattern;
// 定义节点类型
pattern.AddNodeSpec("add_node", "Add") // 找 Type 为 Add 的节点,别名叫 add_node
.AddNodeSpec("relu_node", "Relu"); // 找 Type 为 Relu 的节点
// 定义连接关系:add_node -> relu_node
// 注意:这里只是简单的线性连接,Scope Pass 其实更擅长基于 Scope Name 的层级匹配
// 如果是基于拓扑结构的匹配,通常使用 "GraphFusionPass"
// 但 Scope Pass 对于 TF 这种 Scope 层级清晰的模型非常有效
patterns.push_back(pattern);
return patterns;
}
注:实际的 Scope Pass 通常基于 Scope(如 Bert/Layer1/Attention/...)进行匹配。如果只是纯拓扑匹配,CANN 还提供了 GraphFusionPass。为了简化理解,这里展示概念逻辑。
3.3 执行融合逻辑 (Fuse)
这是“手术”最关键的一步。
Status AddReluFusionPass::LastMatchScopesAndFusion(
std::vector<std::vector<Scope *>> &scopes,
std::vector<Scope *> &fused_scopes) {
// scopes 包含了所有匹配到的子图列表
for (auto &subgraph : scopes) {
// 1. 创建新节点 (AddRelu)
// 使用 GenOperator 构造一个 IR
auto fused_op = ge::op::AddRelu("FusedAddRelu_" + std::to_string(index));
// 2. 迁移属性
// 把旧 Add 节点的输入连到新节点上
// 把旧 Relu 节点的输出连到新节点上
// 3. 替换图中的节点
// GE 提供了 InsertNode, RemoveNode 等接口
// 这是一个复杂的图操作过程,需要处理 Edge 的重连
// 4. 注册新 Scope
// ...
}
return SUCCESS;
}
3.4 注册 Pass
最后,使用宏将 Pass 注册到 GE 系统中。
// 注册到内置的 "System Scope Fusion" 阶段
REGISTER_SCOPE_FUSION_PASS("AddReluFusionPass", AddReluFusionPass);
四、 编译与生效
自定义 Pass 通常被编译为 .so 动态库,放在 ATC 工具的插件目录下。
-
编译:使用 CMake 编译成
libadd_relu_fusion.so。 -
部署:放入
${ASCEND_HOME}/opp/framework/custom/tensorflow/(路径视具体框架而定)。 -
运行:当你再次运行 ATC 转换模型时,日志里会出现
[INFO] AddReluFusionPass run success,说明你的手术刀生效了!
五、 总结
自定义 Pass 是 AI 编译器开发的皇冠明珠。
-
价值:它能解决通用优化解决不了的 Corner Case,带来 20%~50% 的额外性能收益。
-
难度:需要对图论(Graph Theory)和 GE 的 IR 接口非常熟悉。
-
场景:主要用于 大算子融合(Op Fusion) 和 布局调整(Layout Transform)。
掌握了这一招,你就不仅仅是在“写”算子,而是在“改”编译器。你拥有了上帝视角,可以重塑整个计算图。
更多推荐





所有评论(0)