计算机存储小数的方式叫浮点数(floating point)。同一个小数可以用不同的精度来存——用更多的比特(bit)可以更精确,但也更占空间、算得更慢。
浮点数是怎么存的
一个浮点数由三部分组成:符号位(正还是负)、指数(数字的量级)、尾数(有效数字)。
可以类比科学计数法:
−3.14 × 10⁸
符号:负号(−)
有效数字:3.14
指数:8
计算机用二进制版本的科学计数法来存浮点数。指数位越多,能表示的范围越大(从极小到极大的数字)。尾数位越多,有效数字越精确。
FP32:32 比特浮点数
FP32 用 32 个比特来存一个数字——1 个符号位、8 个指数位、23 个尾数位。
[1 位符号][8 位指数][23 位尾数]
它能精确到约 7 位有效数字,能表示从约 10⁻³⁸ 到 10³⁸ 的范围。
7 位精度意味着:1.0000001 和 1.0000002 是可以区分的两个不同的数字。这对绝大多数计算来说绰绰有余。
FP32 的缺点是:一个数字占 4 字节。Sophia 的十亿个参数用 FP32 存储需要 4 GB。如果前向和反向传播的所有中间结果也用 FP32,显存开销会翻倍。
FP16:16 比特浮点数
FP16 用 16 个比特——1 个符号位、5 个指数位、10 个尾数位。
[1 位符号][5 位指数][10 位尾数]
精度只有约 3-4 位有效数字。一个数字只占 2 字节——存储空间和计算量都是 FP32 的一半。GPU 上 FP16 矩阵乘法的速度是 FP32 的两倍以上。
但 FP16 有一个致命的限制:指数只有 5 位,最大值只有 65,504。训练过程中,梯度或中间结果很容易超过这个范围——数值变成 inf(无穷大)或 NaN(非数字),训练直接崩溃。
FP32 最大值:约 3.4 × 10³⁸ ← 绰绰有余
FP16 最大值:65,504 ← 训练中常常不够
梯度在某些层可能达到几万甚至几十万的量级,轻松超过 65,504。这就是为什么纯 FP16 训练大模型是不可行的。
BF16:Brain Float 16
BF16 是 Google Brain 团队设计的一种折中方案:16 个比特——1 个符号位、8 个指数位、7 个尾数位。
FP32: [1 位符号][8 位指数][23 位尾数] ← 精确,但占 4 字节
FP16: [1 位符号][5 位指数][10 位尾数] ← 小,但容易溢出
BF16: [1 位符号][8 位指数][ 7 位尾数] ← 小,且不溢出
注意 BF16 跟 FP16 的区别:指数位跟 FP32 一样多(8 位),牺牲的是尾数位。 这意味着 BF16 的数值范围跟 FP32 完全一样(约 10⁻³⁸ 到 10³⁸),但精度只有约 2-3 位有效数字。
| 格式 | 大小 | 指数位 | 尾数位 | 范围 | 精度 |
|---|---|---|---|---|---|
| FP32 | 4 字节 | 8 | 23 | ±10³⁸ | ~7 位 |
| FP16 | 2 字节 | 5 | 10 | ±65504 | ~3-4 位 |
| BF16 | 2 字节 | 8 | 7 | ±10³⁸ | ~2-3 位 |
BF16 的精度比 FP16 低一点,但不会溢出——因为它的范围跟 FP32 一样大。对深度学习来说,不溢出比多一位精度重要得多——梯度可以很大很小都没问题,只是精度稍差,训练结果几乎不受影响。
BF16 还有一个实际的好处:FP32 和 BF16 的转换极其简单——直接截断 FP32 的尾数部分后 16 位就得到 BF16。不需要复杂的重新编码。
BF16 已经成为大模型训练的事实标准精度。Sophia 的训练全程使用 BF16。
下一章节来看混合精度训练——为什么有些计算用 BF16、有些必须用 FP32。