3. 融合策略分类:水平融合、垂直融合、交叉融合、复合融合

聊完了算子融合的基本概念,咱们得深入看看具体的融合策略了。说实话,我刚接触这个领域时,看到各种论文里冒出来的「水平」「垂直」这些词,也是一头雾水。后来在项目里亲手调过几个模型,才慢慢摸清了门道。

其实说白了,融合策略就四种:水平融合、垂直融合、交叉融合、复合融合。它们分别解决不同场景下的性能瓶颈。我习惯把它们想象成「怎么把多个小任务合并成一个大任务」——合并的方式不同,收益和代价也完全不同。

核心观点:选择哪种融合策略,取决于你的计算图结构、硬件特性以及内存带宽瓶颈。没有银弹,只有权衡。

3.1 水平融合(Horizontal Fusion)

水平融合,也叫同层融合。它处理的是「同一计算层级上、操作相同但数据不同」的算子。

举个例子,你有一个批量归一化(BatchNorm)层,后面跟着一个激活函数(比如ReLU)。如果模型里有多个独立的BatchNorm+ReLU分支,它们之间没有数据依赖关系。这时候,水平融合就把这些相同结构的子图合并成一个更大的计算核。

我遇到过的一个典型场景:在优化一个多任务学习模型时,模型有4个并行的全连接层,每个后面都跟着一个LayerNorm。如果不融合,GPU要启动4次小kernel,每次启动都有开销。水平融合后,4个全连接+LayerNorm合并成一个kernel,启动开销直接降到1/4。

水平融合的收益主要来自:

  • 减少kernel启动次数——这是GPU上最容易被忽视的开销
  • 提高数据局部性——连续的内存访问比随机访问快得多
  • 减少中间结果的读写——不用把每个分支的结果写回显存再读出来

我的经验:水平融合特别适合Transformer中的多头注意力机制。每个头的计算结构完全一样,只是输入数据不同。我曾经把8个头的QKV投影合并成一个大的矩阵乘法,显存带宽利用率从35%提升到了78%。

3.2 垂直融合(Vertical Fusion)

垂直融合,也叫流水线融合或深度融合。它处理的是「同一个计算路径上、前后相连」的算子。

这是最直观的融合方式。比如 Conv2D + BatchNorm + ReLU 这条链,三个算子一个接一个。垂直融合就是把它们合并成一个算子,中间不产生显存读写。

你想想看,如果不融合,Conv2D的输出要写回显存,BatchNorm再读进来,写出去,ReLU再读进来。这一来一回,显存带宽就被浪费了。垂直融合后,Conv2D的计算结果直接留在寄存器或共享内存里,BatchNorm和ReLU接着算,最后才写出去。

我曾经踩过的一个坑:在优化一个ResNet-50时,我把所有Conv+BN+ReLU都做了垂直融合。结果发现某些层的融合反而变慢了。后来一查,是因为融合后的kernel寄存器压力太大,导致编译器不得不做寄存器溢出(spill),性能反而下降。嗯,这里要注意:垂直融合不是越深越好,得看硬件资源。

融合深度 减少的显存读写 寄存器压力 推荐场景
2个算子 约50% 低 几乎所有场景
3-4个算子 约60-70% 中等 计算密集型层
5个以上 约75%+ 高 需谨慎,建议profiling验证

3.3 交叉融合(Cross Fusion / Inter-Layer Fusion)

交叉融合,也叫跨层融合。它跳过了严格的「前后相连」限制,把计算图中不相邻但有数据依赖关系的算子合并。

这听起来有点反直觉——不相邻怎么合并?其实在复杂的计算图里,经常出现「先分后合」的结构。比如一个特征图经过两个不同的分支处理,然后在某个点相加。交叉融合就是把这两个分支的计算和后面的加法合并到一起。

我举个例子你就明白了:

// 原始计算图
input → Conv1 → ReLU1 → \
                          → Add → ReLU3 → output
input → Conv2 → ReLU2 → /

// 交叉融合后
input → FusedKernel(Conv1+ReLU1+Conv2+ReLU2+Add+ReLU3) → output

交叉融合的难点在于:两个分支的计算量可能不一样,导致线程束(warp)之间出现负载不均衡。我在优化一个语义分割模型时就遇到过这个问题——一个分支是3x3卷积,另一个是1x1卷积,计算量差了好几倍。融合后,快的分支要等慢的分支,反而浪费了计算资源。

避坑指南:交叉融合前,一定要分析两个分支的计算量比例。如果比例超过1:3,建议不要强行融合,或者考虑在快的分支里插入一些padding计算来平衡负载。

3.4 复合融合(Compound Fusion)

复合融合,说白了就是把前面三种策略组合起来用。实际项目中,很少有模型只用一种融合策略就能达到最优性能。

我习惯把复合融合分成两类:

  • 串行复合:先做垂直融合,再做水平融合。比如先把每个分支内的Conv+BN+ReLU垂直融合,再把多个分支水平融合。
  • 并行复合:同时应用多种融合策略。比如在Transformer中,既做多头注意力的水平融合,又做QKV投影+注意力计算的垂直融合。

复合融合的收益往往是1+1>2的。但代价也很明显——调试难度指数级上升。我曾经花了两周时间调一个复合融合的kernel,最后发现是共享内存的bank conflict导致性能上不去。

我的建议:做复合融合时,先单独验证每种融合策略的收益,再组合。不要一上来就搞大杂烩。我一般会先用profiling工具跑一遍,看看哪些算子是热点,哪些是冷点。热点算子优先做垂直融合,冷点算子考虑水平融合。

3.5 四种策略的对比与选择

为了让你看得更清楚,我画了一张对比图:

四种融合策略对比 水平融合 • 同层、同结构、不同数据 • 减少kernel启动次数 • 适合并行分支、多头注意力 • 收益:中等,稳定 垂直融合 • 同路径、前后相连 • 减少中间显存读写 • 适合Conv+BN+ReLU链 • 收益:高,但有寄存器限制 交叉融合 • 跨层、分支合并 • 减少分支间数据搬运 • 适合残差连接、特征融合 • 收益:高,但负载均衡难 复合融合 • 多种策略组合 • 收益最大化 • 适合复杂模型(Transformer) • 收益:最高,调试难度大

最后说说我的选择原则:

  • 小模型(MobileNet、ShuffleNet):优先做垂直融合,水平融合收益不大,因为并行分支少。
  • 大模型(ResNet、EfficientNet):垂直融合打底,交叉融合优化残差连接,复合融合收尾。
  • Transformer系列:水平融合做多头注意力,垂直融合做FFN层,复合融合做整个block。

记住一点:融合策略不是越复杂越好。我见过有人把整个模型融合成一个kernel,结果编译时间从10秒变成10分钟,性能反而下降了。适可而止,用profiling数据说话。

想要更多资料,联系可可