3、常见算子类型与计算模式:卷积、池化、全连接、激活函数、归一化、矩阵乘
做算子精度比对这些年,我最大的感受就是:不懂算子的计算模式,根本没法做精度分析。你想想看,一个卷积算子的精度问题,可能出在im2col的实现上,也可能出在Winograd的系数上。不了解这些,你连问题定位的方向都没有。
这一章,我就带你过一遍最常见的算子类型。每个算子我都会讲它的计算模式、精度敏感点,以及我踩过的坑。
3.1 卷积(Convolution)
卷积是深度学习最核心的算子,没有之一。它的计算模式说白了就是:滑动窗口 + 点积。
具体来说,输入是一个三维张量(H×W×C_in),卷积核是四维张量(K_h×K_w×C_in×C_out)。输出特征图的每个点,都是输入的一个局部区域与卷积核做点积的结果。
- 累加顺序:浮点加法不满足结合律,不同的累加顺序会导致不同的舍入误差
- Winograd变换:变换矩阵的系数精度直接影响计算结果
- im2col + GEMM:矩阵乘的精度问题会传导到卷积结果中
我记得有一次,一个客户反馈我们的卷积结果和PyTorch对不上。排查了两天,最后发现是Winograd变换矩阵的系数精度不够。PyTorch用的是float64做变换,我们用的是float32。嗯,这个坑希望大家别踩。
3.2 池化(Pooling)
池化算子相对简单,主要分两种:最大池化和平均池化。
最大池化的计算模式就是取窗口内的最大值。精度问题很少,但要注意边界处理——补零还是补负无穷?这个细节会影响结果。
平均池化是取窗口内的平均值。这里有个坑:除法的精度。窗口大小是2×2还是3×3?除数不同,浮点误差也不同。
3.3 全连接(Fully Connected)
全连接层,本质上就是矩阵乘加偏置。计算模式:y = Wx + b。
精度问题主要集中在矩阵乘部分。我习惯把全连接层的精度比对拆成两步:
- 先比对矩阵乘的结果(不含偏置)
- 再比对加偏置后的结果
这样做的好处是,一旦精度对不上,能快速定位是矩阵乘的问题还是偏置的问题。
3.4 激活函数(Activation Function)
激活函数种类很多,常见的有ReLU、Sigmoid、Tanh、GELU等。它们的计算模式各不相同:
| 激活函数 | 计算模式 | 精度敏感点 |
|---|---|---|
| ReLU | max(0, x) | 几乎无精度问题 |
| Sigmoid | 1 / (1 + exp(-x)) | exp函数的实现精度 |
| Tanh | (exp(x) - exp(-x)) / (exp(x) + exp(-x)) | exp函数的实现精度,除法精度 |
| GELU | x * Φ(x) | erf函数的近似精度 |
我个人觉得,激活函数的精度比对相对简单,因为它们是逐元素操作。但要注意:exp函数在输入很大或很小时,精度损失会非常严重。我曾经遇到过Sigmoid在输入为-100时,不同硬件平台的结果差了10个数量级——因为有的平台直接返回0,有的返回一个极小的非零值。
3.5 归一化(Normalization)
归一化算子包括BatchNorm、LayerNorm、InstanceNorm等。它们的计算模式都是:先算均值和方差,再做标准化。
精度问题主要集中在:
- 均值计算:大数吃小数的问题
- 方差计算:平方的精度损失
- 除法:除以标准差时的精度
- 缩放和平移:γ和β参数的精度
3.6 矩阵乘(Matrix Multiply)
矩阵乘是所有算子的基础。卷积、全连接、甚至注意力机制,底层都离不开矩阵乘。
矩阵乘的计算模式:C = A × B,其中A是M×K,B是K×N,C是M×N。
精度问题主要来自:
- 累加误差:K个乘积相加,浮点误差会累积
- 大数吃小数:当A和B的数值范围差异很大时
- 矩阵规模:K越大,累加次数越多,误差越大
3.7 知识体系总览
下面这张图,是我整理的本章节知识体系。你可以看到,所有算子最终都指向精度分析这个核心目标。
3.8 精度比对实战要点
最后,我总结几个实战中的要点:
- 先理解计算模式,再做精度比对——不懂计算模式,你连误差来源都猜不到
- 关注数值范围——不同算子的数值范围差异很大,会影响浮点精度
- 准备多组测试数据——覆盖正常值、边界值、极端值
- 使用双精度参考——单精度对单精度,有时候看不出问题
好了,这一章的内容就到这里。算子类型和计算模式是精度比对的基础,下一章我们会深入具体的精度比对方法和工具链。记住:理解算子的计算模式,是做好精度比对的第一步。
想要更多资料,联系可可