02. FP32、FP16 与 BF16
上一篇把 1.25 装进了 FP16。现在改用 FP32 或 BF16,1.25 仍能准确保存;但遇到更大、更小,或者小数位更长的数,三种格式就开始分道扬镳。比较它们,需要同时看空间、范围和刻度。
位数花在了哪里
FP 是 Floating Point,也就是浮点。FP32 占 32 位;FP16 和 BF16 都占 16 位。BF16 的全称是 bfloat16,名称来自 Brain Floating Point。常见格式写作 BF16。
| 格式 | 符号位 | 指数位 | 小数位 | 每个数 |
|---|---|---|---|---|
| FP32 | 1 | 8 | 23 | 4 字节 |
| FP16 | 1 | 5 | 10 | 2 字节 |
| BF16 | 1 | 8 | 7 | 2 字节 |
小数位也常称为尾数位;规格化数还隐含开头的 1,所以有效位分别是 24、11、8 位。后文说“小数位”时,指表中实际保存的那部分。
下面都保存同一个数 1.25 = 1.01₂ × 2^0。图上从左向右是高位到低位,字段条按位数比例绘制;下面标出各段的位置、实际编码和读取方法。
FP32:32 位怎样分工
位 31 保存正负号;位 30 到 23 保存指数编码;位 22 到 0 保存小数部分。指数编码 01111111 是 127,减去偏移量 127,实际指数就是 0。
小数部分从左到右的位权依次是 1/2、1/4、1/8……。这里第一位为 0、第二位为 1,后面全是 0,所以小数部分是 1/4;加上隐含的开头 1,就得到了 1.25。最右边那一位的位权是 2^-23。
FP16:指数和小数都缩短
指数编码改为 01111,也就是 15,减去偏移量 15 后仍为 0。小数部分保存 10 位,最右边的位权是 2^-10。1.25 的二进制小数只需要两位,因此依然能准确装下。
BF16:把更多空间留给指数
BF16 用 8 位指数、7 位小数部分。它与 FP32 使用相同的指数偏移量 127;小数部分的最后一位只有 2^-7 那么细。16 位总预算中,它比 FP16 多拿出三位给指数,小数部分相应少三位。
三个编码都能读回 1.25。差别要到更大、更小或细节更多的数上才显现,接下来分别看范围和刻度。
范围:一把尺能量多远
指数决定数量级。FP16 的 5 位指数有 2^5 = 32 种编码,能写 0 到 31。两端的 0、31 留给特殊用途,普通数使用 1 到 30。减去上一篇的偏移量 15,实际指数就是 −14 到 15。
FP32 和 BF16 都有 8 位指数,共 256 种编码,普通数使用 1 到 254。它们的偏移量是 127,实际指数范围都是 −126 到 127。BF16 能容纳更大的数量级,就是从这里来的。
再取 FP16 最大的普通指数 15,以及小数位全为 1 的 1.1111111111₂。后者等于 2 − 2^-10,相乘得到 (2 − 2^-10) × 2^15 = 65504。FP32 和 BF16 同理,各自最大有限值都约为 3.4 × 10^38。
因此,70000 可以存进 FP32/BF16,却超出了 FP16 的有限范围;按通常的就近舍入转换,会得到正无穷大。
范围也包含靠近零的一端。FP16 最小正规正数是 2^-14 ≈ 0.000061。借助特殊编码,还能继续表示更小的次正规数,最小正数到 2^-24 ≈ 0.0000000596。10^-8 更靠近 0,转成 FP16 时会舍入为 0。BF16 的指数范围宽得多,可以容纳这个数。
次正规数用指数全 0 的编码,读取时把通常隐含的开头 1 改为 0,写成 0.小数部分 × 2^-14。10 位小数部分的最小非零值是 2^-10,因此最小正数为 2^-10 × 2^-14 = 2^-24。小数部分也全 0 时,就到了零。
这些是格式的表示能力。实际计算设备有时会把次正规数直接处理成零,遇到极小数还要检查具体算子的行为。
刻度:两个相邻的数隔多远
固定在 1 到 2 之间,包含 1、暂不包含 2,数学上写作 [1, 2)。这个区间的数具有相同指数,相邻可表示值的距离只取决于小数位数:
| 格式 | 1 附近的间距 | 保存 1.0351 后的结果 |
|---|---|---|
| FP32 | 2^-23 ≈ 0.0000001192 | 1.035099983215332 |
| FP16 | 2^-10 = 0.0009765625 | 1.03515625 |
| BF16 | 2^-7 = 0.0078125 | 1.03125 |
间距的算法很直接:[1, 2) 宽度是 1,10 个小数位有 2^10 = 1024 种组合,于是把这个区间分成 1024 份,每份 1/1024。BF16 只有 128 份,所以刻度更疏。
FP16 能区分 1 和 1.0009765625,BF16 在这个位置的下一个数则是 1.0078125。这解释了为什么 BF16 范围更宽,而在两者都处于规格化范围时,FP16 在同一数量级内的细节更多。靠近零、进入 FP16 的次正规区域后,要根据实际间距比较;例如 10^-7 附近,FP16 间距约 5.96 × 10^-8,BF16 间距约 4.66 × 10^-10,此处 BF16 的刻度更细。
数字变大,刻度也跟着变大
浮点数的刻度随数量级变化。在 [2, 4),间距是 [1, 2) 的两倍;在 [1024, 2048),FP16 间距变成 1,BF16 间距变成 8。它们能精细表示 1 附近的小数,却很难在一千附近保留四分之一这样的增量。
可以把“范围”理解成能够覆盖的远近,把“细节”理解成局部刻度的疏密。FP32 两方面都较充足,代价是每个数占 4 字节;FP16 和 BF16 各占 2 字节,分配方案不同。选哪种,要看实际计算里的数会走到哪里。
下一篇舍入、溢出与计算误差会让同一个数在几把尺之间切换,再观察一次很小的存储误差怎样进入计算。FP64、FP8 等其他格式的逐位结构可以在存储图解中查阅。
