1.引言
在地平线算法工具链进行量化部署时,许多开发者会遇到两种融合模式:OnlyBN 和 BNAddReLu。

这引出了一个关键问题:为什么要进行融合?具体如何实施?以及,这种操作在数学上是否严格等价?
实际上,在神经网络推理阶段,算子融合(Operator Fusion)是一种经典且高效的优化策略,其目标明确:降低计算量、减少内存访问开销、加速推理执行。常见的融合模式主要有两种:
- Conv + BatchNorm
- Conv + BatchNorm + Add(残差) + ReLU
2.Conv + BN 融合原理
在推理(Eval)模式下,BatchNorm2d 的计算本质上是线性变换:
y = (x - mean) / sqrt(var + eps) * gamma + beta
其中,mean 与 var 是统计得到的均值和方差,gamma 和 beta 是可学习的参数。
接下来是关键步骤。我们推导 BN 如何与卷积“合二为一”。常规卷积的输出为:
z = Conv(x) = W * x + b
BN 层作用于 z 的结果:
y = (z - mean) / sqrt(var + eps) * gamma + beta
将 z 代入展开后,可以发现该组合可直接等效为一个新的卷积层:
W_fused = W * (gamma / sqrt(var + eps))b_fused = (b - mean) / sqrt(var + eps) * gamma + beta
注意,权重和偏置均发生了变化。通过此操作,Conv+BN 组合被无缝替换为一个全新的 Conv,而 BN 则从计算图中“消失”。
这种融合带来的好处立竿见影:
- 减少一次 BN 的访存,降低内存带宽压力。
- 减少一次算子调用,简化推理调度。
- 便于后续量化,避免 BN 层对数值范围产生潜在影响。
3.Conv+BN+Add+ReLU 融合原理
如上一节所述,Conv 和 BN 已经融合成新的 Conv'。
那么,后面的 Add 和 ReLU 如何处置?在量化部署时,Add 操作(通常来自残差连接)要求两个输入具有一致的量化尺度 scale。因此,框架在 FX 层面会识别出类似 add(conv_out, skip_tensor) 的模式。
接着,框架可能会调整量化 scale,使整个链路的计算处于同一量化域。这样,硬件便可在同一个 kernel 内高效执行 Add 和 ReLU。
从数学角度看,ReLU 直接作用于 Add 的输出,数值关系完全一致,因此可将 Add+ReLU 合并为一个 fused activation:
fused_out = relu(add(x, y))
在量化推理语境下,这意味着 Add 的输出可直接进入 ReLU 的裁剪范围,从而减少中间不必要的量化/反量化开销。
举一个具体示例:
import torchimport torch.nn as nnfrom torch.fx import symbolic_traceclass ResidualModel(nn.Module):def __init__(self):super().__init__()self.conv = nn.Conv2d(3, 3, kernel_size=3, padding=1)self.bn = nn.BatchNorm2d(3)self.relu = nn.ReLU()
该模型的 FX 图输出为:
placeholder x xcall_module conv convcall_module bn bncall_function add
部署框架会识别出这个子图,其中 Conv+BN 被融合,而 Add 和 ReLU 形成连续节点,可进一步融合优化。
看到这里,很多人可能会疑惑:Conv+BN 可以通过等效公式融合为一个算子,但后续的 Add 和 ReLU 显然无法类似地合并。那它们又是如何处理的?目的何在?
4.拓展解读
“Conv+BN+Add+ReLU 融合”这个说法,实际上并非将所有算子物理合并成一个。更准确的理解是分步完成:
- Conv+BN → 融合为 Conv'(数学上严格等价)
- Conv' + Add → 融合为更高效的“卷积 + 残差加法”算子(计算图优化)
- Add + ReLU → 融合为“带激活的残差”算子(计算图优化)
真正“从数学上消失”的其实只有 BN。Add 和 ReLU 更多是一种算子合并(Operator Folding)。
4.1 conv+add“融合”
Conv+Add 的“融合”并非代数消去,而是操作符流水线融合(Operator Fusion)。
典型的残差块为:out = Conv'(x),然后 out = out + skip。
Add 操作本身是逐元素加法,不涉及权重或参数。其数学形式(Add(a,b) = a+b)不会改变 Conv' 的结构,也无法被代数合并成新卷积。但在推理框架(如 TensorRT、ONNXRuntime、OpenExplorer)中,实现方式完全不同。Conv' 的输出内存可直接作为 Add 的输入,在同一个 kernel 中完成累加,无需创建中间张量。硬件可执行:
for each output pixel:tmp = ConvKernel(x)tmp = tmp + skip(x)output = tmp
这样做能带来实实在在的性能提升:
- 节省一次 Conv 输出的内存写回。
- 节省一次 Add 输入 a-tmp 的内存读回。
- 节省一次 Add 输出的内存写回。
因此,尽管数学类型上无法合并,但在计算图优化层面,它们可以被“内联”到同一个 kernel 中执行。此融合可在量化结束后、编译时进行。
4.2 add+ReLu“融合”
Add 和 ReLU 的融合原理类似。其数学形式为 y = relu(a + b),ReLU 仅对加法结果做逐元素裁剪(relu(z) = max(z, 0)),同样不改变 Add 的结构。
但在硬件执行中,在执行加法后的同一寄存器内直接进行 ReLU 操作,无需将 Add 的输出再写回内存。融合后的过程简化为:
tmp = a + bout = max(tmp, 0)
这也节省了:
- Add 输出的写回。
- Add 输出再次读回以执行 ReLU。
总的来说,BN 可以在数学上融合到 Conv 中;而 Add 和 ReLU 虽然不能进行数学融合,但可在硬件执行层面融合到同一个 kernel 中,避免中间张量的反复读写,从而实现显著的性能提升。
