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 融合精度问题的排查思路
如果你遇到了融合后的精度问题,别慌。我一般按这个顺序排查:
- 确认融合是否真的触发了:有些编译器有融合开关,先确认融合确实生效了
- 对比融合前后的中间结果:在融合边界处插入dump,看哪个节点的输出开始偏离
- 检查数值范围:融合后的中间结果是否超出了预期范围?比如FP16能表示的最大值是65504
- 测试不同输入:用小数值、大数值、正数、负数分别测试
- 关闭部分融合:如果某个融合模式有问题,先关掉它,看精度是否恢复
嗯,这里要注意:不是所有精度问题都需要修复。如果偏差在1e-5以内,且不影响最终精度指标,我建议先放一放。毕竟,性能收益是实实在在的。
1.5 本章小结
算子融合是AI编译器的核心优化手段。它通过合并连续算子,减少访存和kernel启动开销,从而提升推理性能。
但融合不是免费的午餐。中间结果精度丢失、计算顺序改变、数值稳定性问题,都可能导致精度偏差。理解这些问题的根源,是后续排查和修复的基础。
下一章,我会用一个真实案例,带大家一步步排查一个Conv+BN融合导致的精度问题。到时候你会看到,理论和实践之间,差了多少个“我以为”。
一句话记住:融合优化的是性能,但精度问题往往藏在“看起来没问题”的细节里。