读 ncnn 源码(XXVI):fuse_deconvolutiondepthwise_batchnorm——深度反卷积的 BN 融合

在 ncnn 的图优化系列 Pass 中,我们已经见证了针对标准卷积 (Convolution)、深度卷积 (ConvolutionDepthWise) 和标准反卷积 (Deconvolution) 后续 BatchNorm 层的融合优化。为了构建一个完备的优化体系,ncnn 自然也覆盖了相对不那么常见、但同样可能存在的 DeconvolutionDepthWise -> BatchNorm 模式。

本篇,我们将剖析 fuse_deconvolutiondepthwise_batchnorm 的源码,理解其如何将 BN 融合的通用原理应用于深度反卷积 (DeconvolutionDepthWise) 这一特定层类型。

TL;DR

  1. 目标: 将 DeconvolutionDepthWise 层后紧随的 BatchNorm 层进行融合,消除 BatchNorm 在推理时的计算开销。
  2. 模式匹配: 遍历网络 layers,查找 DeconvolutionDepthWise -> BatchNorm 的直接连接模式。
  3. 数学原理: 与所有其他卷积/反卷积 + BN 融合完全一致DeconvolutionDepthWise 也是线性操作(逐通道独立),后接 BatchNorm 仍构成连续线性链。融合公式不变:
    • 令 BN 参数导出系数 b = slope / sqrt(var + eps)a = bias - slope * mean / sqrt(var + eps)
    • 新权重 Wdw_deconv=Wdw_deconvbW'_{dw\_deconv} = W_{dw\_deconv} \cdot b (逐通道相乘)。
    • 新偏置 bdw_deconv=bdw_deconvb+ab'_{dw\_deconv} = b_{dw\_deconv} \cdot b + a (逐通道计算)。
  4. 代码实现: 几乎与 fuse_convolutiondepthwise_batchnormfuse_deconvolution_batchnorm 完全相同
    • 计算中间系数 ab
    • 遍历 DeconvolutionDepthWise 的每个通道 i
    • 将该通道对应的权重 (weight_per_outch = kernel_w * kernel_h 个元素) 乘以 b[i]
    • 更新该通道的偏置 bias[i] = bias[i] * b[i] + a[i] (如果原层无偏置,则先创建)。
  5. 图结构修改: 将 DeconvolutionDepthWisetop 指向原 BatchNormtop,更新 blobproducer,并将 BatchNorm 标记为 "ncnnfused"
  6. 效果: 消除了 BatchNorm 层的计算和内存访问,确保了即使在使用了深度反卷积的网络结构中,BN 融合优化也能生效。

1. 融合动机:统一处理线性合并

DeconvolutionDepthWiseConvolutionDepthWise 类似,也是对每个通道独立应用一个反卷积核。其计算 yo=Wdw_deconv,oxo+bdw_deconv,oy_o = W_{dw\_deconv, o} * x_o + b_{dw\_deconv, o} 依然是线性的。因此,其后接的 BatchNormzo=γoyoμoσo2+ϵ+βoz_o = \gamma_o \frac{y_o - \mu_o}{\sqrt{\sigma^2_o + \epsilon}} + \beta_o 同样可以被代数合并。

提供 fuse_deconvolutiondepthwise_batchnorm Pass 的主要目的是确保优化策略的完备性,使得无论开发者如何组合卷积、反卷积及其 Depthwise 变体与 BatchNorm,ncnnoptimize 工具都能有效地进行融合。


2. 代码实现:复用 BN 融合核心逻辑

fuse_deconvolutiondepthwise_batchnorm 的代码实现与其他卷积变体的 BN 融合 Pass 高度一致,仅层类型名称不同。

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
74
int NetOptimize::fuse_deconvolutiondepthwise_batchnorm()
{
const size_t layer_count = layers.size();
for (size_t i = 0; i < layer_count; i++) // 遍历查找 DeconvolutionDepthWise
{
if (layers[i]->type != "DeconvolutionDepthWise") continue;
int top_blob_index = layers[i]->tops[0];

// 查找后续的 BatchNorm
// ... (模式匹配代码与其他 BN 融合 Pass 完全一致) ...
size_t j = i + 1;
// ... (find BatchNorm j) ...
if (j == layer_count) continue;

ncnn::DeconvolutionDepthWise* deconvolutiondepthwise = (ncnn::DeconvolutionDepthWise*)layers[i];
ncnn::BatchNorm* batchnorm = (ncnn::BatchNorm*)layers[j];

fprintf(stderr, "fuse_deconvolutiondepthwise_batchnorm %s %s\n", deconvolutiondepthwise->name.c_str(), batchnorm->name.c_str());

// --- 参数变换核心 (与其他 BN 融合 Pass 完全一致) ---
{
int channels = batchnorm->channels; // = deconvolutiondepthwise->num_output
float eps = batchnorm->eps;

// 1. 计算中间系数 a 和 b
std::vector<float> a(channels);
std::vector<float> b(channels);
for (int ch = 0; ch < channels; ch++)
{
float sqrt_var = static_cast<float>(sqrt(batchnorm->var_data[ch] + eps));
a[ch] = batchnorm->bias_data[ch] - batchnorm->slope_data[ch] * batchnorm->mean_data[ch] / sqrt_var;
b[ch] = batchnorm->slope_data[ch] / sqrt_var;
}

// 2. 如果 DeconvolutionDepthWise 没有偏置,则创建并初始化为 0
if (deconvolutiondepthwise->bias_term == 0)
{
deconvolutiondepthwise->bias_term = 1;
deconvolutiondepthwise->bias_data = ncnn::Mat(channels);
deconvolutiondepthwise->bias_data.fill(0.f);
}

// 3. 计算每个通道 (group) 的权重数量
// 对于 DeconvolutionDepthWise, weight_data_size = channels * kernel_w * kernel_h
// weight_per_outch = kernel_w * kernel_h
const int weight_per_outch = deconvolutiondepthwise->weight_data_size / channels;

float* weight = deconvolutiondepthwise->weight_data; // 指向 DW Deconv 权重
float* bias = deconvolutiondepthwise->bias_data; // 指向 DW Deconv 偏置

// 4. 遍历每个通道 (group),更新权重和偏置
for (int ch = 0; ch < channels; ch++)
{
// 获取指向当前通道 ch 的权重的指针
float* conv_weight_outch = weight + weight_per_outch * ch;
// 将该通道的所有权重乘以 b[ch]
for (int k = 0; k < weight_per_outch; k++)
{
conv_weight_outch[k] *= b[ch]; // W'_dw_deconv = W_dw_deconv * b
}
// 更新偏置 b'_dw_deconv = b_dw_deconv * b + a
bias[ch] = bias[ch] * b[ch] + a[ch];
}
} // --- 参数变换结束 ---

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

关键点:

  • 模式匹配: 查找 DeconvolutionDepthWise -> BatchNorm 的直接连接。
  • 参数计算: 计算 a, b 系数的逻辑不变。
  • 权重更新: 将 DeconvolutionDepthWise 层每个通道 ch 对应的所有权重(kernel_w * kernel_h 个)乘以该通道的 b[ch]
  • 偏置更新: 将偏置 bias[ch] 更新为 bias[ch] * b[ch] + a[ch]
  • 图修改: 标准的重定向连接 + 标记融合操作。

3. 意义:优化策略的闭环

DeconvolutionDepthWise 本身可能不如标准卷积或深度卷积常用,但它仍然是构成某些特定网络结构(例如轻量级的上采样模块)的可能组件。提供 fuse_deconvolutiondepthwise_batchnorm Pass 体现了 ncnn 优化工具的设计哲学:力求覆盖所有理论上可行的、能够带来性能收益的线性算子融合模式。这确保了无论模型结构如何演变,只要存在 Conv/Deconv (及其 DW 变体) -> BN 的模式,都能被 ncnnoptimize 有效优化。


4. 结语

fuse_deconvolutiondepthwise_batchnorm 是 ncnn 针对卷积类算子与 BatchNorm 融合优化的最后一块拼图。它将成熟的融合逻辑应用于深度反卷积层,确保了优化策略的完整覆盖。通过这一系列(fuse_convolution_batchnorm, fuse_convolutiondepthwise_batchnorm, fuse_deconvolution_batchnorm, fuse_deconvolutiondepthwise_batchnorm)高度一致的 Pass,ncnn 系统性地消除了推理阶段各种形式卷积层后的冗余 BatchNorm 计算,为端侧模型的极致性能优化提供了有力支撑。

该封面图片由Andreas NeefPixabay上发布