1、融合基础:什么是算子融合?为什么融合会导致精度问题?

大家好,我是你们的老朋友。今天咱们来聊聊算子融合这个事儿。

说实话,算子融合这个概念,在AI编译器里算是个“老演员”了。但很多人只知其然,不知其所以然。更别提融合之后还会出精度问题——嗯,这个坑我踩过不止一次。

1.1 什么是算子融合?

简单来说,算子融合就是把多个连续的计算操作合并成一个。

举个例子。你写了一个卷积,后面跟着一个ReLU激活。在原始的计算图里,这是两个独立的算子。融合之后,它们变成了一个“Conv+ReLU”的复合算子。

为什么要这么做?说白了就两个原因:

  • 减少显存读写:原来卷积的输出要写回显存,ReLU再读进来。融合后,中间结果直接留在寄存器里,省了两次访存。
  • 减少kernel启动开销:两个kernel变成一个,GPU的调度开销直接减半。

我刚开始做推理引擎优化时,觉得融合就是“无脑合并”,后来才发现——融合是个技术活。

核心观点:算子融合的本质,是用计算换访存。融合后的计算量可能不变,但数据搬移量大幅减少,从而提升整体性能。

1.2 常见的融合模式

在实际项目中,我见过最多的融合模式有这么几种:

融合类型 典型组合 收益来源
Conv+激活 Conv + ReLU / SiLU / GELU 减少中间显存
Conv+BN Conv + BatchNorm BN参数折叠到Conv权重
Element-wise链 Add + ReLU / Mul + Add 减少kernel启动
LayerNorm+激活 LayerNorm + ReLU 减少中间显存

你想想看,这些组合在Transformer和CNN里几乎无处不在。所以融合的好坏,直接决定了推理引擎的性能天花板。

1.3 融合为什么会导致精度问题?

好,重点来了。融合本身不会改变数学结果,但实现方式会。

为什么会这样?我总结了三个主要原因:

原因一:中间结果的精度丢失

原始计算图里,每个算子的输出都会写回显存。写回时,数据会被量化或截断。比如FP32转FP16,或者INT8量化。

融合之后,中间结果不再写回显存。这意味着:原本的量化/截断操作被跳过了。

举个例子:

// 原始计算:Conv -> ReLU
// Conv输出写回显存时,从FP32截断为FP16
// ReLU读入FP16数据,计算后输出

// 融合后:Conv+ReLU
// Conv的中间结果保留在FP32精度
// ReLU直接使用FP32数据计算

看起来融合后的精度更高了?不一定。因为模型训练时,中间结果是被量化过的。推理时精度变高,反而可能导致输出不一致。

注意:融合后精度变高,不等于结果正确。模型训练时已经适应了量化噪声,去掉这个噪声反而会引入偏差。

原因二:计算顺序的改变

这个坑我印象特别深。有一次排查一个BERT模型的精度问题,查了两天才发现——融合改变了浮点运算的结合顺序。

你看:

// 原始:Add -> LayerNorm
// Add计算:A + B
// LayerNorm计算:LayerNorm(A + B)

// 融合后:Add+LayerNorm
// 内部实现可能变成:LayerNorm(A) + LayerNorm(B)  // 错误!
// 或者:LayerNorm(A + B) 但内部累加顺序不同

浮点运算不满足结合律。a + (b + c) 和 (a + b) + c 的结果,在浮点数世界里可能不一样。融合后的实现如果改变了累加顺序,精度偏差就来了。

原因三:融合算子的数值稳定性

有些融合实现为了性能,会做一些“偷懒”的操作。比如:

  • 用近似公式代替精确计算(比如GELU的近似实现)
  • 减少中间变量的精度(比如用FP16累加)
  • 改变归一化参数的计算方式

我曾经遇到过一个问题:Conv+BN融合后,某个特定输入下的输出偏差达到了1e-2。排查后发现,BN的均值和方差在融合时被折叠到了卷积权重里,但折叠过程中的数值精度不够,导致大数值输入时误差被放大。

我的经验:融合后的精度问题,往往在极端输入下才会暴露。建议用正态分布、均匀分布、全零输入等多种数据做测试,别只跑一个batch就完事。

1.4 融合精度问题的排查思路

如果你遇到了融合后的精度问题,别慌。我一般按这个顺序排查:

  1. 确认融合是否真的触发了:有些编译器有融合开关,先确认融合确实生效了
  2. 对比融合前后的中间结果:在融合边界处插入dump,看哪个节点的输出开始偏离
  3. 检查数值范围:融合后的中间结果是否超出了预期范围?比如FP16能表示的最大值是65504
  4. 测试不同输入:用小数值、大数值、正数、负数分别测试
  5. 关闭部分融合:如果某个融合模式有问题,先关掉它,看精度是否恢复

嗯,这里要注意:不是所有精度问题都需要修复。如果偏差在1e-5以内,且不影响最终精度指标,我建议先放一放。毕竟,性能收益是实实在在的。

1.5 本章小结

算子融合是AI编译器的核心优化手段。它通过合并连续算子,减少访存和kernel启动开销,从而提升推理性能。

但融合不是免费的午餐。中间结果精度丢失、计算顺序改变、数值稳定性问题,都可能导致精度偏差。理解这些问题的根源,是后续排查和修复的基础。

下一章,我会用一个真实案例,带大家一步步排查一个Conv+BN融合导致的精度问题。到时候你会看到,理论和实践之间,差了多少个“我以为”。

一句话记住:融合优化的是性能,但精度问题往往藏在“看起来没问题”的细节里。

算子融合知识体系 算子融合 为什么融合? 减少显存读写 减少kernel启动 提升计算密度 精度问题原因 中间精度丢失 计算顺序改变 数值稳定性 常见融合模式 Conv + 激活 Conv + BN Element-wise链 LayerNorm+激活 排查思路:确认融合 → 对比中间结果 → 检查数值范围 → 测试不同输入 → 关闭部分融合
可可精品资料库