3、融合策略分类:水平融合、垂直融合、混合融合策略详解

算子融合这事儿,说白了就是「把多个小操作打包成一个」。但怎么打包?往哪个方向打包?这里头门道不少。

我刚开始接触融合时,以为就是把相邻的算子硬塞到一起。后来踩了坑才发现——融合策略分三种,每种适用场景完全不同。你选错了策略,性能可能不升反降。

今天咱们就把这三种策略掰开揉碎讲清楚。

3.1 水平融合:同层操作的「横向合并」

水平融合,也叫横向融合。它针对的是同一计算层级、处理逻辑相似的算子。

核心思想:把多个独立的、相同类型的操作合并成一个。

举个例子,你有一批数据,需要分别做 ReLU、Sigmoid、Tanh 三种激活。传统做法是三个算子串行执行。水平融合后,变成一个算子一次性处理完。

典型场景:多个 element-wise 操作(加法、乘法、激活函数)的合并。

我在项目中遇到过这样一个案例:一个 Transformer 模型里,Q、K、V 三个矩阵分别做 LayerNorm。原本是三个独立的 Norm 算子,每个都要读一次内存、写一次内存。水平融合后,一次性读完三个矩阵,连续做完 Norm,再一次性写回。

你想想看,内存访问次数从 6 次降到了 2 次。这提速效果,立竿见影。

// 水平融合前:三个独立算子
y1 = relu(x1)
y2 = sigmoid(x2)
y3 = tanh(x3)

// 水平融合后:一个算子搞定
[y1, y2, y3] = fused_activations(x1, x2, x3)
我的经验:水平融合最适合「数据并行、操作相似」的场景。如果操作类型差异太大(比如一个加法一个卷积),强行融合反而会引入分支判断,得不偿失。

3.2 垂直融合:流水线上的「纵向合并」

垂直融合,也叫纵向融合。它针对的是计算流水线上前后依赖的算子。

核心思想:把「生产者-消费者」关系的多个算子合并,减少中间结果的存储和传输。

这就像工厂流水线——原本每个工位做完活,要把半成品搬到仓库,下一个工位再去取。垂直融合后,两个工位直接对接,半成品「手递手」传递。

最经典的例子就是 Conv-BN-ReLU 融合。卷积做完,紧接着做 BatchNorm,然后做 ReLU。这三个算子天然就是前后依赖关系。

我曾经在一个部署项目里,把整个模型的 Conv-BN-ReLU 全部做了垂直融合。推理速度直接快了 30%。为什么?因为中间的特征图不需要写回显存了,全部在寄存器里完成。

典型场景:Conv + BN + ReLU、MatMul + Add + Activation、Gemm + Bias + ReLU。
// 垂直融合前:三步走
h = conv2d(x, w)
h = batch_norm(h)
y = relu(h)

// 垂直融合后:一步到位
y = fused_conv_bn_relu(x, w)
注意:垂直融合不是万能的。如果两个算子之间需要保留中间结果用于反向传播(比如训练场景),强行融合会导致梯度计算困难。我建议推理场景大胆用,训练场景谨慎用。

3.3 混合融合:水平 + 垂直的「组合拳」

混合融合,就是把水平融合和垂直融合结合起来用。说白了,先做垂直融合把流水线缩短,再做水平融合把同层操作合并。

为什么需要混合融合?因为实际模型里,算子关系往往是「既有前后依赖,又有并行分支」。

举个例子,一个残差块里:主路径是 Conv-BN-ReLU,分支路径是 Conv-BN。最后两个路径相加。这时候,你可以先对主路径做垂直融合(Conv-BN-ReLU 合并),对分支路径也做垂直融合(Conv-BN 合并)。然后,把两个路径的加法操作做水平融合。

我个人的习惯是:先画计算图,标出所有「生产者-消费者」关系,做垂直融合。然后看哪些同层操作可以合并,做水平融合。最后检查有没有冲突。

核心原则:垂直融合优先,水平融合次之,混合融合兜底。

下面这张图,是我自己总结的融合策略选择流程,你可以参考一下:

融合策略选择流程图 输入计算图 有前后依赖? 是 执行垂直融合 有同层操作? 是 执行水平融合 否 有同层操作? 否 是 执行水平融合 输出:混合融合策略

3.4 三种策略的对比与选择

说了这么多,到底什么时候用哪种?我整理了一张表,你直接对照着看:

策略 适用场景 核心收益 典型算子组合 风险点
水平融合 同层、同类型、无依赖 减少 kernel launch 次数 多个 ReLU、多个 Add 操作类型差异大时无效
垂直融合 前后依赖、流水线 减少中间结果读写 Conv+BN+ReLU 训练场景需保留中间结果
混合融合 复杂子图、残差结构 综合收益最大化 残差块、Inception 模块 融合策略冲突需手动调优
避坑指南:我曾经在一个 MobileNet 上做融合,上来就搞混合融合,结果性能反而下降了。后来发现是水平融合把不同通道的 Depthwise Conv 合并了,导致计算局部性变差。所以我的建议是——先做垂直融合,性能不够再加水平融合。别一上来就搞大杂烩。

3.5 实战中的融合策略选择

最后,分享一个我自己的决策流程:

  1. 画计算图:把模型的计算图画出来,标出所有算子节点。
  2. 找依赖链:找出所有「生产者-消费者」关系的算子链,优先做垂直融合。
  3. 找同层节点:看有没有多个同类型、无依赖的算子,做水平融合。
  4. 检查冲突:确保融合后的算子不会破坏计算图的正确性。
  5. 性能验证:跑 benchmark,对比融合前后的延迟和吞吐。

嗯,这套流程我用了好几年,基本没出过大问题。你刚开始做融合时,也可以按这个步骤来。等经验多了,自然一眼就能看出哪些算子该合、哪些不该合。

说白了,融合策略没有银弹。水平融合、垂直融合、混合融合,都是工具。关键是你得知道什么时候用哪个工具。

可可精品资料库