读 ncnn 源码(XXXV):`fuse_binaryop_eltwise`——识别加权求和并替换为 Eltwise
读 ncnn 源码(XXXV):fuse_binaryop_eltwise——识别加权求和并替换为 Eltwise
在
ncnnoptimize的图优化篇章中,我们已经见证了多种“算子融合”(如Conv+BN)和“算子消除”(如eliminate_dropout)。本篇,我们将探讨一种更高级的优化:基于模式识别的算子替换 (Pattern-based Operator Replacement)。fuse_binaryop_eltwise就是其典型代表。它的核心任务是识别出一种由多个
BinaryOp层构成的、在语义上等价于**“加权求和”的计算模式,并将其替换**为一个功能更强、性能更高的Eltwise专用层。
TL;DR
- 目标: 识别
Y = (A * C0) + (B * C1)这样的加权求和模式。A和B是特征图(Tensor),C0和C1是标量(Scalar)。 - 模式匹配:
ncnnoptimize查找一个BinaryOp(Add, Tensor, Tensor)作为中心,然后反向查找其两个输入A和B的生产者:- Case 1 (Full):
(A * C0) + (B * C1)。A的生产者是一个BinaryOp(Mul, A, C0),B的生产者也是一个BinaryOp(Mul, B, C1)。 - Case 2 (Partial):
(A * C0) + B或A + (B * C1)。只有一个输入来自BinaryOp(Mul)。
- Case 1 (Full):
- 替换为
Eltwise:ncnn::Eltwise层(op_type = SUM)天生支持Y = A*coeff[0] + B*coeff[1]这种加权求和操作。它在一个 Kernel 内完成所有计算,效率远高于三个BinaryOp(两次乘法,一次加法)的组合。 - 融合机制:
- 创建一个新的
Eltwise层,并将其op_type设为Eltwise::Operation_SUM。 - 将标量
C0和C1“烘焙”到eltwise->coeffs(一个Mat) 中。 - 将
Eltwise层的bottoms(输入)直接连接到A和B。 - 替换原
BinaryOp(Add)层,并将上游的BinaryOp(Mul)层标记为"ncnnfused"。
- 创建一个新的
- 性能优势: 此次替换将三个层、两次中间
Mat读写(A*C0和B*C1的结果)优化为了一个层、零中间Mat读写,极大地节省了内存带宽和调度开G销。

1. 融合动机:低效的加权求和
在许多网络结构中(如 FPN 特征融合、ResNet 的 shortcut 连接),加权求和 Y = A * C0 + B * C1 是一种常见操作。如果使用通用的 BinaryOp 来实现它,计算图会是这样的:
BinaryOp_Mul_0(with_scalar=1, b=C0) :X0 = A * C0BinaryOp_Mul_1(with_scalar=1, b=C1) :X1 = B * C1BinaryOp_Add_0(with_scalar=0) :Y = X0 + X1
这个过程存在巨大的效率问题:
- 三次 Kernel Launch: 需要调度三个独立的层。
- 两次中间
Mat读写:X0和X1作为中间结果,必须被完整地写入主存,然后再被BinaryOp_Add_0读回。这是巨大的内存带宽浪费。
2. Eltwise 层:更优的解决方案
ncnn 提供的 Eltwise 层是一个更强大的多输入元素操作层。当其 op_type 被设为 Eltwise::Operation_SUM 时,它可以接收多个(N 个)输入 blob,并执行:
coeffs 是一个存储在 Mat 中的系数向量。
显然,Eltwise(A, B, coeffs=[C0, C1]) 在数学上与上述三个 BinaryOp 组成的子图完全等价。Eltwise 层的 forward 实现会在一个高度优化的循环(或 SIMD Kernel)中,同时读取 A 和 B,分别乘以各自的系数,然后相加,最后写入 Y。这消除了所有的中间 Mat 读写。
fuse_binaryop_eltwise 的任务就是识别出这个低效的 BinaryOp 组合,并将其替换为高效的 Eltwise 实现。
3. 代码实现:模式匹配与替换
fuse_binaryop_eltwise 的实现逻辑非常清晰:
1 | int NetOptimize::fuse_binaryop_eltwise() |
4. 结语
fuse_binaryop_eltwise 是一个精妙的图优化 Pass。它超越了简单的层间融合,展现了 ncnn “理解” 计算图语义模式(Semantic Pattern)的能力。通过识别 (Tensor * Scalar) + (Tensor * Scalar) 这一常见模式,并将其替换为单一、高效的 Eltwise(SUM) 层,ncnnoptimize 能够将一个由三层 BinaryOp 构成的低效子图,优化为一个单一的高性能算子。
这不仅减少了 kernel launch 的开销,更重要的是消除了两次代价高昂的中间特征图内存读写,是优化内存带宽密集型网络(如 FPN)的关键步骤。





