首页 > 教程攻略 > ai教程 >Conv+BN+Add+ReLU 融合机制简介

Conv+BN+Add+ReLU 融合机制简介

来源:互联网 时间:2026-06-02 08:42:10

1.引言

在使用地平线算法工具链做量化部署时,很多人会遇到两种FuseMode:OnlyBN、BNAddReLu。

Conv+BN+Add+ReLU 融合机制简介

这就引出了一个核心问题:为什么需要融合?它具体是怎么做到的?以及,这种操作在数学上是严格等价的吗?

实际上,在神经网络推理阶段,运算符融合(Operator Fusion)是一种非常经典且高效的优化策略。它的目的很明确:降低计算量、减少访存开销、加速推理执行。常见的融合模式主要有两种:

  • Conv + BatchNorm
  • Conv + BatchNorm + Add(残差) + ReLU

2.Conv + BN 融合原理

在推理阶段(Eval Mode),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 call_module relu reluoutput output output

部署框架会识别出这个子图,其中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里,避免中间张量的反复读写,从而实现明显的性能提升。