读 ncnn 源码(XXIV):fuse_deconvolution_mul——反卷积层的乘法融合

在前文中,我们已经分析了 ncnnoptimize 如何融合标准卷积 (Convolution) 和深度可分离卷积 (ConvolutionDepthWise) 后续的 BatchNormMulAdd 操作。为了进一步完善对线性计算链的优化,ncnn 也提供了针对反卷积层 (Deconvolution) 后接逐通道乘法的融合 Pass:fuse_deconvolution_mul

本篇,我们将剖析该函数的源码,理解其如何将 fuse_convolution_mul 的逻辑应用于 Deconvolution 这一上采样场景中的常见层。

TL;DR

  1. 目标: 将 Deconvolution 层后接一个扮演逐通道乘法 (Per-Channel Scaling) 角色的 BinaryOp 层(Mul 操作,第二输入来自 MemoryData)进行融合。
  2. 模式匹配: 查找 Deconvolution -> BinaryOp 结构,附加条件与 fuse_convolution_mul 完全一致:BinaryOp 必须是 Mul (op_type == 2),非标量 (!with_scalar),且第二输入来自形状匹配通道数的 MemoryData 向量。
  3. 数学原理: fuse_convolution_mul 完全一致Deconvolution 作为线性变换,后接逐通道乘法构成连续线性链。融合公式为:
    • 新权重 Wdeconv,o=Wdeconv,oSoW'_{deconv, o} = W_{deconv, o} \cdot S_o (第 o 个输出通道的反卷积核乘以缩放系数 SoS_o)。
    • 新偏置 bdeconv,o=bdeconv,oSob'_{deconv, o} = b_{deconv, o} \cdot S_o (第 o 个输出通道的偏置乘以 SoS_o)。
  4. 代码实现: 几乎与 fuse_convolution_mul 完全相同
    • 遍历 Deconvolution 层的每个输出通道 i
    • 将该通道对应的所有权重 (weight_per_outch 个元素) 乘以 memorydata->data[i]
    • 如果存在偏置,则将 bias[i] 也乘以 memorydata->data[i]
  5. 图结构修改: 将 Deconvolution 层的 top 指向原 BinaryOptop,更新 blobproducer,并将 BinaryOp 标记为 "ncnnfused"
  6. 效果: 消除了 BinaryOp 层引入的逐元素乘法计算和额外的内存读写,优化了包含反卷积和逐通道缩放的网络结构。

1. 融合动机:线性链合并原理的延伸

Deconvolution 作为一种线性变换 y=Wdeconvx+bdeconvy = W_{deconv} * x + b_{deconv},如果其后紧跟一个逐通道乘法 zo=yoSoz_o = y_o \cdot S_o,那么这两个操作可以合并。将缩放因子 SoS_o 提前吸收到 WdeconvW_{deconv}bdeconvb_{deconv} 中,可以得到一个等效的 Deconvolution 操作 z=Wdeconvx+bdeconvz = W'_{deconv} * x + b'_{deconv},从而在推理时消除 BinaryOp 的开销。


2. 代码实现:复用乘法融合逻辑

fuse_deconvolution_mul 的代码实现与 fuse_convolution_mul 高度一致,仅在层类型匹配上有所不同,再次体现了融合逻辑的通用性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
int NetOptimize::fuse_deconvolution_mul()
{
const size_t layer_count = layers.size();
for (size_t i = 0; i < layer_count; i++) // 遍历查找 Deconvolution
{
if (layers[i]->type != "Deconvolution") continue;
int top_blob_index = layers[i]->tops[0];

// 查找后续的 BinaryOp (Mul, 非标量, 第二输入来自 MemoryData)
// ... (模式匹配代码与 fuse_convolution_mul 完全一致) ...
size_t j = i + 1;
// ... (find BinaryOp j) ...
if (j == layer_count) continue;

ncnn::Deconvolution* deconvolution = (ncnn::Deconvolution*)layers[i];
ncnn::BinaryOp* binaryop = (ncnn::BinaryOp*)layers[j];

if (binaryop->op_type != 2 || binaryop->with_scalar) continue; // 必须是 Mul, 非标量

size_t k = 0;
// ... (find MemoryData k as the second input of j) ...
if (k == j) continue;

ncnn::MemoryData* memorydata = (ncnn::MemoryData*)layers[k];

int channels = deconvolution->num_output; // Deconvolution 的输出通道数

// 校验 MemoryData 形状是否为 [channels] 的向量
if (memorydata->w != channels || memorydata->h != 0 || memorydata->c != 0)
{
continue; // 形状不匹配,无法融合
}

fprintf(stderr, "fuse_deconvolution_mul %s %s\n", deconvolution->name.c_str(), binaryop->name.c_str());

// --- 参数变换核心 ---
{
// 计算每个输出通道的权重数量
const int weight_per_outch = deconvolution->weight_data_size / channels;

float* weight = deconvolution->weight_data; // 指向 Deconvolution 权重
float* bias = deconvolution->bias_data; // 指向 Deconvolution 偏置 (可能为 NULL)

// 遍历每个输出通道,更新权重和偏置
for (int ch = 0; ch < channels; ch++) // ch 即为输出通道索引 o
{
// 获取指向当前通道 ch 的权重的指针
float* conv_weight_outch = weight + weight_per_outch * ch;
float scale_factor = memorydata->data[ch]; // 获取缩放系数 S_o

// 将该通道的所有权重乘以 S_o
for (int wi = 0; wi < weight_per_outch; wi++)
{
conv_weight_outch[wi] *= scale_factor; // W'_deconv = W_deconv * S_o
}

// 如果存在偏置,也将偏置乘以 S_o
if (bias)
{
bias[ch] = bias[ch] * scale_factor; // b'_deconv = b_deconv * S_o
}
}
} // --- 参数变换结束 ---

// --- 图结构修改 (标准融合操作) ---
int top_blob_index_final = binaryop->tops[0];
deconvolution->tops[0] = top_blob_index_final;
blobs[top_blob_index_final].producer = i;
binaryop->type = "ncnnfused";
// --- 图结构修改结束 ---
}
return 0;
}

关键点:

  • 模式匹配: 查找 Deconvolution -> BinaryOp(Mul) 结构,且第二个输入来自符合广播条件的 MemoryData
  • 参数更新: 将 MemoryData 中的缩放系数 memorydata->data[ch] Deconvolution 层对应输出通道 ch 的所有权重和偏置上。
  • 图修改: 标准的重定向连接 + 标记融合操作。

3. 意义:完善对反卷积的优化覆盖

Deconvolution 层常用于生成对抗网络(GANs)、图像分割等需要上采样的任务中。虽然 Deconvolution -> Mul(per-channel) 模式不如 Deconvolution -> BN 常见,但 fuse_deconvolution_mul Pass 的存在,保证了 ncnn 优化工具箱对这类潜在的线性计算链也具备优化能力。


4. 结语

fuse_deconvolution_mulncnnoptimize 中针对反卷积线性链优化的又一补充。它复用了与标准卷积融合乘法相同的逻辑,将逐通道缩放操作合并到 Deconvolution 层中。这进一步体现了 ncnn 图优化策略的系统性和一致性——尽可能地识别并消除计算图中可合并的连续线性变换,以达到简化结构、减少计算和访存开销的最终目的。

该封面图片由Mykyta TretiakovPixabay上发布