graph-autofusion:CANN 的自动算子融合引擎
GE 的图优化 pass 里,算子融合是对推理性能影响最大的一个。但 GE 的融合规则是硬编码的——Conv+BN+ReLU 写一条规则,BMM+Softmax+BMM 写一条规则。规则多了维护成本直线上升,总有覆盖不到的融合场景。
graph-autofusion 解决了这个问题。它是一个独立的自动融合框架,不靠人工写规则,而是通过分析计算图的 Tensor 流和硬件特性,自动识别可融合的算子组合。在秦昇腾NPU 上的推理链路中,graph-autofusion 是 GE 和 Runtime 之间的一道关键工序——把 GE 输出的优化图进一步压榨到极致。
为什么算子融合重要
一个不融合的算子执行路径:
算子 A:从 DDR 读输入 Tensor → 计算 → 写中间 Tensor 到 DDR
算子 B:从 DDR 读中间 Tensor → 计算 → 写中间 Tensor 到 DDR
算子 C:从 DDR 读中间 Tensor → 计算 → 写输出到 DDR
每个算子的输入输出都要经过 DDR。DDR 的带宽是 NPU 片上 L1 Buffer 带宽的十分之一左右。中间 Tensor 每在 DDR 中走一趟,就浪费一次搬运开销。
融合后:
融合算子 A+B+C:从 DDR 读输入 → L1 上顺序执行 A→B→C → 写输出到 DDR
中间 Tensor 只在 L1 上流转。搬运量从 A+B+C 的输入输出全部读写,减少到只读一次输入、只写一次输出。对于卷积或 Attention 这类中间 Tensor 比输入输出大得多的算子,搬运量的减少在 2-4 倍。
graph-autofusion 如何工作
graph-autofusion 的核心是一个基于图模式的自动识别引擎。它不靠手写规则,而是通过几个步骤自动发现融合机会:
第一步:图扫描。 从 GE 传过来的优化计算图中,遍历每个算子节点,记录每个节点的输入 Tensor 和输出 Tensor 的引用计数——哪些节点读这个 Tensor、哪些节点写这个 Tensor。
第二步:依赖分析。 找到"写后读"的 Tensor 链——A 的输出是 B 的输入、B 的输出是 C 的输入,并且 ABC 之间没有分支。这条链上的算子可以融合成一个。
A → B → C(直线链,可融合)
A → B ↘
→ D(B 有分支,不能简单融合 A 和 B)
C ↗
第三步:硬件约束检查。 即使算子在依赖图上可以融合,还需要检查硬件约束:
- L1 容量约束。 融合后的算子需要的片上 L1 Buffer 总容量不能超过硬件上限。Attention Score 的
[n,n]矩阵在 n 很大时可能超出 L1,需要分块执行。 - 算子类型约束。 某些算子有特殊的硬件执行单元——比如 Cube Unit 专门做矩阵乘,Vector Unit 做逐元素运算。融合时不能把两种硬件单元的不同指令混成一个 Kernel。
- 数据类型约束。 FP16 算子和 INT8 算子的融合有限制——混精度融合需要中间 Tensor 做类型转换。
第四步:融合收益预估。 对候选融合方案做量化收益评估——融合后省了多少搬运量、有没有引入额外的计算开销。收益超过阈值才执行融合。
// graph-autofusion 的收益评估逻辑(简化)
for (auto& candidate : candidates) {
float saved_bytes = candidate.input_size + candidate.output_size;
float extra_compute = candidate.recompute_overhead;
if (saved_bytes / total_bytes > THRESHOLD && extra_compute < 0.1 * saved_bytes) {
apply_fusion(candidate);
}
}
Transformer 中的融合场景
graph-autofusion 在 Transformer 推理中最典型的几个融合:
MHA(Multi-Head Attention)融合。 QKV 投影 → Split → Q@K → Softmax → Score@V → Concat → 输出投影 这条长链,如果不做融合需要 8 次搬运 + 7 个中间 Tensor。graph-autofusion 把整条链识别为一个融合模式,产出 3 到 4 个融合算子——按硬件执行单元切分,但不额外增加 DDR 访问。
FFN 融合。 GEMM1 → BiasAdd → ReLU → GEMM2 → BiasAdd。5 个操作可以融合成 2 个 Kernel:GEMM1+BiasAdd+ReLU 在第一个 Kernel 里完成,GEMM2+BiasAdd 在第二个里完成。两个 Kernel 之间通过 Stream 串联,中间 Tensor (ReLU 输出)在 L1 上流转,不经过 DDR。
LayerNorm 融合。 LayerNorm 的 mean → var → normalize 过程通常被 GE 表示为多个独立算子。graph-autofusion 会识别出这组操作为 LayerNorm 模式,替换成 CANN 内置的优化 LayerNorm 算子。
融合执行的性能收益
在 Ascend 910 上,以 BERT-Base 推理为基准:
| 配置 | 延迟 (ms) | DDR 访问量 (MB) | 算子 Launch 次数 |
|---|---|---|---|
| 不融合(逐算子执行) | 12.5 | 85 | 472 |
| GE 基础融合 | 7.8 | 52 | 186 |
| GE + graph-autofusion | 5.2 | 31 | 94 |
graph-autofusion 在 GE 基础融合之上又减少了 40% 的 DDR 访问和 50% 的算子 Launch 次数。延迟从 7.8ms 降到 5.2ms。
加速的来源不是计算变快了——算子的计算量没有变——而是搬运量和调度开销减少了。融合后的 Kernel 数变少,Runtime 的 Launch 开销从 472 次降到 94 次,每节省一次 Launch 就省掉了约 10μs 的调度延迟。
从 graph-autofusion 到 GE
graph-autofusion 不是 GE 的替代品,它是 GE 图优化链路上的一个扩展插件。GE 做第一轮优化(常量折叠、无用节点消除、Layout 转换),graph-autofusion 做第二轮的自动融合。
两者的分工是:GE 负责"哪些优化在所有条件下都安全",graph-autofusion 负责"哪些融合在当前硬件和模型配置下收益最大"。理解了这两层的关系,就理解了 CANN 图编译管线的完整链路。
GE 与 graph-autofusion 的分工
graph-autofusion 不是 GE 的替代,它是 GE 优化链路上的一个扩展插件。GE 先执行第一轮基础优化——常量折叠、无用节点消除、Layout 转换。这些优化在所有场景下都安全。然后 GE 调用 graph-autofusion 做第二轮自动融合——根据当前模型的图结构和硬件约束,找到收益最大的融合方案。
graph-autofusion 的融合结果反馈给 GE,GE 把融合后的 Fusion 算子写进 OM 模型的执行计划。后续推理时 Runtime 直接加载这个执行计划,不需要重新做融合决策。
融合的边界
不是所有算子都能无脑融合。graph-autofusion 在决策时会检查几个硬约束:
L1 容量边界。 融合后的算子需要更大的临时 Buffer。如果融合后的 L1 需求超过硬件上限,graph-autofusion 会回退到分块融合——把 A→B→C→D 拆成 A→B(融合)和 C→D(融合)。
执行单元冲突。 Cube Unit 和 Vector Unit 各有专用的指令集。融合时如果混用 Cube 和 Vector 指令,需要拆成两个 Kernel。graph-autofusion 检测到这种情况时会把 Cube 部分和 Vector 部分分开融合。
动态 Shape 约束。 输入 Shape 变化大的算子融合后需要处理多种形状的分块参数。graph-autofusion 对动态 Shape 的算子做保守融合——只融合形状变化不敏感的操作(如常量折叠),Shape 敏感的操作留到运行时再决定。
手动干预融合
graph-autofusion 的自动融合并不是 100% 确定的。高级用户可以通过配置影响融合决策:
fusion:
enabled: true
aggressive_level: 2 # 融合激进程度:0-关闭,1-保守,2-激进(默认)
exclude_ops: ["TopK"] # 跳过某些算子的融合
fusion_blacklist: [] # 禁止融合的算子对
激进模式(aggressive_level=2)会尝试更大的融合范围——哪怕 L1 空间只剩 10% 的余量也尝试融合。保守模式多留余量,保证任何 Shape 都能正确执行。激进模式在固定 Shape 推理场景中性能更高,保守模式在动态 Shape 场景中更可靠。
graph-autofusion 的 Transformer 特殊优化
对于 Transformer 类模型,graph-autofusion 有几个专门的融合策略:
Attention 投影融合。 Q、K、V 三个投影可以融合成一个算子——同一个输入 X 乘以三个不同的权重矩阵。融合后 X 只需要从 DDR 读一次,三个投影的结果写到三块不同的显存区域。DDR 读取量从 3 次降到 1 次。
FFN 中的多个连续 GEMM。 SwiGLU 等结构有 GEMM → 门控 → 逐元素乘 → GEMM 的模式。第一层 GEMM 的输出被门控函数消费后直接喂给第二层 GEMM,graph-autofusion 把这三步合并成一个融合算子——L1 上完成 GEMM → 门控 → 逐元素乘 → 第二层 GEMM,最后写一次 DDR。
总结
graph-autofusion 的自动融合是 CANN 推理性能优化的关键一环。它不需要人工编写融合规则,通过图分析和硬件约束检查自动识别融合机会。在 Transformer 类模型的推理场景中,graph-autofusion 的融合效果最显著——Attention 和 FFN 的长算子链可以被高效压缩为少数几个融合算子,DDR 访问量和 Launch 次数同步降低。
参考仓库
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐


所有评论(0)