第2章:精度对齐基础概念

各位同学好,我是老张。今天咱们聊聊精度对齐里最基础、也最绕不开的几个概念。说实话,我刚入行那会儿,觉得浮点数嘛,不就是小数点能飘来飘去嘛,有啥好学的?直到第一次做算子精度对齐,被一个BF16的误差坑了整整三天……嗯,从那以后我再也不敢小看这些基础概念了。

2.1 浮点数表示:FP32 / FP16 / BF16

浮点数这东西,说白了就是用有限的二进制位去近似表示一个实数。你想想看,32个比特,满打满算也就2^32种组合,怎么可能精确表示无穷多的实数?所以误差是必然的,关键是你得知道误差长什么样。

我习惯把浮点数拆成三部分来看:符号位、指数位、尾数位。不同精度的区别,其实就是这三部分怎么分配。

格式 总位数 符号位 指数位 尾数位 指数偏置
FP32 32 1 8 23 127
FP16 16 1 5 10 15
BF16 16 1 8 7 127

看到没?BF16和FP32的指数位一样多,都是8位。这意味着什么?意味着BF16能表示的数值范围和FP32几乎一样大。但代价呢?尾数位只有7位,精度差得远。我在做某个大模型算子对齐时,就遇到过这种情况:数值范围没溢出,但精度死活对不上,最后发现是BF16的尾数精度不够,累积误差炸了。

核心要点:FP32是基准精度,FP16适合梯度缩放训练,BF16适合推理场景。选哪个,取决于你的数值范围和精度容忍度。

2.2 精度度量指标:ULP、相对误差、绝对误差

误差怎么量化?总不能光靠感觉说「嗯,差不多」吧。我常用的三个指标:ULP、相对误差、绝对误差。每个都有它的适用场景。

2.2.1 绝对误差

最简单粗暴的指标。公式:abs_error = |computed - reference|。比如你算出来是3.14159,参考值是3.14158,那绝对误差就是0.00001。

但绝对误差有个坑:它不反映数值本身的量级。你想想看,误差0.00001对于1来说很小,但对于0.000001来说就很大了。所以单独用绝对误差,容易误判。

2.2.2 相对误差

相对误差解决了量级问题。公式:rel_error = |computed - reference| / |reference|。还是刚才的例子,相对误差就是0.00001 / 3.14158 ≈ 0.000003,也就是3e-6。

我个人习惯在精度对齐时,优先看相对误差。一般要求小于1e-5就算通过。但注意,当参考值接近0时,相对误差会变得非常大,这时候就得换ULP了。

2.2.3 ULP(Unit in the Last Place)

ULP是个好东西,它衡量的是两个浮点数之间隔了多少个「最小步长」。说白了,就是看你的计算结果和参考值之间,差了几个「格子」。

举个例子:在FP32中,1.0和下一个可表示的浮点数1.000000119之间的差,就是1个ULP。如果你的计算结果和参考值差了2个ULP,说明精度还不错。

我的经验:对于FP32算子,ULP容忍度一般设在2~4之间。对于FP16/BF16,可以放宽到8~16。我曾经遇到过一个案例,某个算子的ULP差到了50多,查了半天发现是中间计算用了低精度累加器。

2.3 误差来源分析

误差从哪来?我总结了四个主要来源。每个我都踩过坑,你们记一下。

2.3.1 舍入误差

这是最基础的误差。浮点数无法精确表示所有实数,比如0.1在二进制里是无限循环小数。每次舍入都会引入一点点误差,累积起来就不可忽视了。

我记得有一次做矩阵乘法精度对齐,单次乘加误差只有0.5个ULP,但做了几百万次后,累积误差直接到了几百个ULP。嗯,这就是舍入误差的可怕之处。

2.3.2 运算顺序误差

浮点数运算不满足结合律!(a + b) + c 和 a + (b + c) 的结果可能不一样。为什么?因为中间结果的舍入不同。

我建议在写算子时,尽量用Kahan求和或者Pairwise求和来减少这种误差。别小看这个,我在做某个Transformer的LayerNorm对齐时,就是靠改求和顺序把误差从10个ULP降到了2个ULP。

2.3.3 类型转换误差

FP32转FP16,或者FP32转BF16,都会引入截断误差。尤其是BF16,尾数只有7位,转换时直接丢掉低16位尾数,误差可能达到几十个ULP。

注意:类型转换时,如果数值超出目标格式的表示范围,会发生溢出(变成Inf)或下溢(变成0)。这两种情况在精度对齐中都是致命的,必须提前做数值范围分析。

2.3.4 非规范化数(Denormal)误差

这个坑比较隐蔽。当数值非常接近0时,浮点数会进入非规范化模式,精度会急剧下降。FP16的非规范化数精度只有几个比特,误差可能达到几百个ULP。

我曾经在某个语音模型的算子中,因为输入数据中有大量接近0的值,导致精度对齐一直不过。后来我强制把非规范化数刷成0(flush-to-zero),误差一下子就降下来了。

2.4 知识体系总览

下面这张图,是我自己整理的精度对齐基础概念框架。你们可以对照着看,心里有个谱。

精度对齐基础概念框架 浮点数表示 精度度量指标 误差来源分析 FP32:8位指数 + 23位尾数 FP16:5位指数 + 10位尾数 BF16:8位指数 + 7位尾数 绝对误差:|计算值 - 参考值| 相对误差:|差值| / |参考值| ULP:最后一位的单位步长 舍入误差:无限小数截断 运算顺序:不满足结合律 类型转换:截断与溢出 非规范化数:精度急剧下降 核心原则:理解数值范围 → 选择合适精度 → 控制误差累积 精度对齐不是追求零误差,而是把误差控制在可接受范围内

好了,这一章的内容就到这儿。浮点数表示、精度度量指标、误差来源,这三个东西是精度对齐的基石。你把这些搞明白了,后面讲具体对齐流程时,才能听得懂我在说什么。

想要更多资料,联系可可