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

1.引言

在使用地平线算法工具链进行量化部署时,遇到两种 FuseMode:OnlyBN、BNAddReLu。

引起思考,融合的目的是什么?融合怎么做到的?是等效的吗?

神经网络在推理端(inference)经常通过运算符融合(operator fusion)来降低计算量、减少访存开销、加速执行。典型融合模式包括:

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

2.Conv + BN 融合原理

在推理阶段(evaluation mode),BatchNorm2d 的计算本质为:

Plain 复制代码
y = (x - mean) / sqrt(var + eps) * gamma + beta

其中:

  • mean、var 为均值和方差
  • gamma、beta 为可学习参数

下面来看 BN 与卷积合并的数学推导,常规卷积输出:

Plain 复制代码
z = Conv(x) = W * x + b

BN 作用于 z:

Plain 复制代码
y = (z - mean) / sqrt(var + eps) * gamma + beta

展开后可等效为新的卷积:

Plain 复制代码
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(通常来自残差)需要保证两个输入具有一致量化 scale,因此框架在 FX 层面会识别以下模式:

Plain 复制代码
add(conv_out, skip_tensor)

并可能重调 scale 以便后续融合(具体依赖量化模式 QATMode),例如在 INT8 量化时:

  • 若 Conv' 输出 scale = S1
  • Add 需要输入两个 scale 对齐(S1 与 S2)

框架会插入或调整 scale,使整个链路在同一量化域中执行。这样才能在同一个硬件 kernel 内完成 Add 和 ReLU(见后文)。

ReLU 可以直接作用在 Add 的输出上而不改变数值关系,因此 Add+ReLU 也可以合并为一个 fused activation:

Plain 复制代码
fused_out = relu(add(x, y))

在量化推理中,这意味着 Add 的输出可直接进入 ReLU 的裁剪范围,减少中间量化/反量化开销。

举个例子:

Plain 复制代码
import torch
import torch.nn as nn
from torch.fx import symbolic_trace

class 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()

输出为:

Plain 复制代码
placeholder x x
call_module conv conv
call_module bn bn
call_function add <built-in function add>
call_module relu relu
output 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)

典型残差块:

Plain 复制代码
out = Conv'(x)    # Conv 和 BN 可以合并为新的 Conv'
out = out + skip

Add 这一步本身是逐元素加法,不涉及权重或参数,它的数学形式:Add(a, b) = a + b,并不会改变 Conv' 的结构,也不能被代数合并成一个新的卷积。但是在推理框架(TensorRT、ONNXRuntime,OpenExplorer)中:Conv' 的输出内存可以作为 Add 的输入,直接在同一 Kernel 中累加,不需要创建新的中间张量。硬件中可以这样执行:

Plain 复制代码
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),ReLu 不改变 Add 算式的结构,也不能让 Add 消失。但在硬件执行中:在执行加法后的同一个寄存器中直接进行 ReLU,而不需要将 Add 的输出再写回内存。融合后的过程:

Plain 复制代码
tmp = a + b
out = max(tmp, 0)

节省了:

  • Add 的输出写回
  • Add 输出再次读回执行 ReLU

总而言之,BN 可以数学融合到 Conv;Add 和 ReLU 不能数学融合,但可以在硬件执行层面融合到同一个 kernel,避免中间 tensor 读写,从而提升性能。

相关推荐
yuanyuan2o21 小时前
模型预训练:Hugging Face Transformers 基础
算法·ai·语言模型·自然语言处理·nlp·深度优先
杨充1 小时前
1.3 浮点型数据设计灵魂
开发语言·python·算法
妄想出头的工业炼药师2 小时前
GS slam mono
算法·开源
_日拱一卒3 小时前
LeetCode:207课程表
java·数据结构·算法·leetcode·职场和发展
用户987409238875 小时前
llamafactory 0.6.3 没有 llamafactory-cli
算法
计算机安禾5 小时前
【算法分析与设计】第26篇:参数化算法与固定参数可解性理论
大数据·人工智能·算法·机器学习·剪枝
AI科技星5 小时前
基于**v=c(空间光速螺旋运动)唯一第一性原理**重新完整求导证明
人工智能·线性代数·算法·机器学习·架构·概率论·学习方法
风筝在晴天搁浅6 小时前
美团 LeetCode 692.前K个高频单词
算法·leetcode·职场和发展