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 四种策略的对比与选择
为了让你看得更清楚,我画了一张对比图:
最后说说我的选择原则:
- 小模型(MobileNet、ShuffleNet):优先做垂直融合,水平融合收益不大,因为并行分支少。
- 大模型(ResNet、EfficientNet):垂直融合打底,交叉融合优化残差连接,复合融合收尾。
- Transformer系列:水平融合做多头注意力,垂直融合做FFN层,复合融合做整个block。
记住一点:融合策略不是越复杂越好。我见过有人把整个模型融合成一个kernel,结果编译时间从10秒变成10分钟,性能反而下降了。适可而止,用profiling数据说话。
想要更多资料,联系可可