2. 算子融合原理

大家好,我是你们的老朋友。今天咱们聊聊算子融合——这个在NPU优化里绕不开的话题。

说实话,我刚接触NPU的时候,对算子融合的理解也很浅。觉得不就是把几个算子拼一起嘛,有啥难的?后来在项目里踩了坑才明白,这里面的门道深着呢。

2.1 算子融合的定义与动机

什么是算子融合?

简单说,就是把多个连续的计算操作合并成一个。比如Conv后面接BN再接ReLU,这三个原本是独立的算子,融合后就变成一个大的计算核。

你可能会问:为什么要费这个劲?

原因有三点,我一个个说。

  1. 减少访存开销:每个算子执行完,中间结果要写回DDR。下一个算子再读出来。这一写一读,时间就浪费了。融合后,中间结果直接留在片上SRAM里,省掉大量访存。
  2. 减少kernel launch开销:每个算子启动都要花时间。融合后,一次启动搞定多个操作。
  3. 提升计算密度:融合后的算子,计算量更大,能更好地利用NPU的并行计算能力。

核心观点:算子融合的本质,是用计算换访存。多算一点,少搬一次数据,整体就快了。

我在项目中遇到过这样一个案例:一个Conv+BN+ReLU的模型,没融合时跑在NPU上,带宽利用率只有30%。融合后直接飙到85%。你说这优化值不值?

2.2 融合的数学基础

融合不是随便拼的。它背后有严格的数学支撑。

线性变换的可结合性

Conv本质是线性变换。BN在推理阶段也是线性变换(训练时不一样,但推理时是固定的)。两个线性变换叠加,结果还是线性变换。

数学上长这样:

Conv: Y = W * X + b
BN (推理): Z = γ * (Y - μ) / σ + β

融合后: Z = (γ * W / σ) * X + (γ * (b - μ) / σ + β)

你看,融合后的权重和偏置都可以提前算好。这就是为什么融合后的算子,计算量没变,但访存少了。

非线性激活函数的处理

ReLU这种非线性函数,不能和线性变换直接合并。但我们可以把它作为融合算子的最后一步。计算完线性部分,紧接着就做ReLU,中间结果不落DDR。

嗯,这里要注意:有些激活函数(比如Sigmoid)计算量比较大,融合后可能会影响计算单元的负载均衡。我建议根据实际情况取舍。

2.3 常见的融合模式:Conv+BN+ReLU

这是最经典的融合模式,没有之一。几乎所有主流NPU都支持。

融合流程

  1. 离线阶段:把BN的参数融合到Conv的权重和偏置中
  2. 在线阶段:执行融合后的Conv,然后直接做ReLU

代码实现(伪代码):

// 离线融合
void fuse_bn_into_conv(conv_t* conv, bn_t* bn) {
    for each channel c {
        float scale = bn->gamma[c] / sqrt(bn->var[c] + eps);
        conv->weight[c] *= scale;
        conv->bias[c] = (conv->bias[c] - bn->mean[c]) * scale + bn->beta[c];
    }
}

// 在线执行
void fused_conv_bn_relu(input_t* in, output_t* out, conv_t* conv) {
    // 执行融合后的卷积
    conv_forward(in, out, conv);
    // 原地ReLU
    for each element e in out {
        out[e] = max(0, out[e]);
    }
}

避坑指南:我曾经在融合BN时忘了处理epsilon。结果模型精度掉了0.5%。查了两天才发现是除零保护没做好。记住,BN的方差一定要加epsilon再开方。

其他常见融合模式

融合模式 适用场景 收益
Conv+BN+ReLU CNN主干网络 高
Conv+Add+ReLU ResNet残差连接 中
MatMul+Add 全连接层 中
LayerNorm+ReLU Transformer 低(计算量小)

你想想看,为什么Conv+BN+ReLU收益最高?因为Conv的计算量大,访存也大。融合后省掉的访存非常可观。而LayerNorm本身计算量就小,融合收益自然就低了。

2.4 融合的硬件视角

从NPU的角度看,算子融合需要硬件支持。不是所有NPU都能做。

硬件要求:

  • 片上SRAM要够大,能放下中间结果
  • 计算单元要支持多操作流水线
  • 要有灵活的指令调度能力

我记得有一次,在某个老款NPU上做融合优化。硬件不支持流水线,只能串行执行。结果融合后反而慢了。因为计算单元空闲等待的时间变长了。

警告:不要盲目融合。如果硬件不支持流水线执行,融合可能适得其反。先看硬件手册,再决定融合策略。

2.5 知识体系图

下面这张图,是我自己总结的算子融合知识体系。你看一眼就能明白整体脉络。

算子融合知识体系 定义与动机 减少访存 · 减少启动开销 数学基础 线性变换可结合性 常见融合模式 Conv+BN+ReLU 等 核心收益 • 访存减少30%-70% • 启动开销降低 • 计算密度提升 关键公式 • W' = γW/σ • b' = γ(b-μ)/σ+β • 离线预计算 模式对比 • Conv+BN+ReLU: 高收益 • Conv+Add: 中收益 • LayerNorm: 低收益 硬件视角:SRAM容量 · 流水线支持 · 指令调度 硬件不支持时,融合可能适得其反 核心思想:用计算换访存,多算少搬

这张图把算子融合的核心脉络理清楚了。从定义动机到数学基础,再到常见模式和硬件约束,一条线串下来。你保存下来,以后做优化时对照着看。

2.6 小结

算子融合,说白了就是「多算一点,少搬一次」。这个思想贯穿整个NPU优化过程。

我个人习惯是:拿到一个模型,先看哪些算子可以融合。能融合的尽量融合,不能融合的再想其他办法。但记住,融合不是万能的。硬件不支持时,强行融合反而坏事。