常见融合模式:横向融合、纵向融合、交叉融合
算子融合这事儿,说白了就是把多个小算子合并成一个大算子。为什么要这么干?因为每个算子执行时都有启动开销、内存读写开销。你想想看,十个算子各跑一遍,和两个算子跑一遍,哪个快?
我个人习惯把融合模式分成三类:横向、纵向、交叉。这三类各有各的适用场景,也各有各的坑。今天咱们一个一个聊。
横向融合:把兄弟算子合并
横向融合,也叫同层融合。它处理的是同一计算图中,没有依赖关系的算子。
举个例子,一个输入数据同时要做 ReLU、Sigmoid、Tanh 三种激活。这三个激活函数互不依赖,完全可以并行计算。但 GPU 上每个 kernel 启动都有开销,分开跑就是三次启动、三次内存访问。
我做过一个项目,模型里有十几个并行的 BatchNorm 分支。一开始每个分支单独写一个 kernel,跑一次推理要 2.3 毫秒。后来我把它们横向融合成一个 kernel,一次启动搞定,时间直接降到 0.8 毫秒。
横向融合的核心思路:把多个独立的、同类型的算子合并成一个 kernel,减少启动次数和内存访问。
横向融合的典型场景:
- 多个激活函数并行(ReLU + Sigmoid + Tanh)
- 多个 BatchNorm 分支
- 多个独立的卷积层
- 多个 Element-wise 操作
小技巧:横向融合时,要注意每个算子的输出形状是否一致。如果不一致,需要做 padding 或 reshape。我一般会先检查 shape,再决定是否融合。
纵向融合:把父子算子合并
纵向融合,也叫流水线融合。它处理的是有依赖关系的算子,比如 Conv + BN + ReLU 这种经典组合。
纵向融合的思路很简单:把前一个算子的输出直接喂给后一个算子,中间不写回显存。这样能省掉一次内存读写,而且能利用 GPU 的流水线特性。
我记得有一次优化一个 ResNet 模型,里面全是 Conv + BN + ReLU 的组合。每个组合都拆成三个 kernel 跑,显存带宽被浪费得厉害。我把它们纵向融合成一个 kernel,推理速度提升了 40%。
纵向融合的典型模式:
- Conv + BN + ReLU
- Conv + Add + ReLU
- MatMul + Add + Softmax
- LayerNorm + Add + Dropout
注意:纵向融合不是万能的。如果两个算子的计算量差距太大,融合后反而可能降低并行度。比如一个轻量级的 ReLU 和一个重量级的 Conv 融合,ReLU 部分可能拖慢 Conv 的执行。我曾经踩过这个坑,后来加了阈值判断才解决。
交叉融合:跨层跨分支的合并
交叉融合是最复杂的一种。它同时涉及横向和纵向的依赖关系,需要跨层、跨分支进行合并。
举个例子,Transformer 里的 Multi-Head Attention。它里面有多个并行的 Q、K、V 计算,每个计算又包含 MatMul + Scale + Softmax 等操作。这些操作既有横向的并行关系,又有纵向的依赖关系。
交叉融合的难点在于:
- 依赖关系复杂,容易出错
- 内存复用策略需要精心设计
- 调试难度大,出 bug 很难定位
我做过一个交叉融合的案例:把 BERT 模型里的 QKV 投影、Attention 计算、输出投影全部融合成一个 kernel。代码写了三天,调试花了一周。但效果确实好,推理速度提升了 2.1 倍。
交叉融合的适用场景:
- Transformer 的 Attention 模块
- ResNet 的残差块
- Inception 的多分支结构
- DenseNet 的密集连接
三种融合模式的对比
| 融合模式 | 依赖关系 | 复杂度 | 收益 | 典型场景 |
|---|---|---|---|---|
| 横向融合 | 无依赖 | 低 | 减少启动次数 | 并行激活、多分支 BN |
| 纵向融合 | 有依赖 | 中 | 减少内存访问 | Conv+BN+ReLU |
| 交叉融合 | 混合依赖 | 高 | 综合收益最大 | Attention、残差块 |
三种融合模式的知识体系
下面这张图展示了三种融合模式的关系和适用场景。我画这张图时参考了多个框架的实现,包括 TensorRT、TVM 和 XLA。
实战中的选择策略
实际项目中,这三种融合模式往往不是单独使用的。我一般会按以下步骤来:
- 先用 profiler 跑一遍,看看哪些算子耗时最多
- 分析算子之间的依赖关系,画出计算图
- 优先做纵向融合,因为收益最直接
- 再看横向融合,把并行的兄弟算子合并
- 最后考虑交叉融合,处理复杂结构
我的经验:不要一开始就想着做交叉融合。先做简单的纵向和横向融合,往往能解决 80% 的性能问题。交叉融合收益大,但风险也大,适合作为最后的优化手段。
嗯,三种融合模式就聊到这儿。每种模式都有它的用武之地,关键是要根据实际场景灵活选择。记住一点:融合不是目的,加速才是。
想要更多资料,联系可可