从位宽到梯度:深度学习底层精度的“平滑”艺术
规格化 & 非规格化
- 规格化:正常的浮点数表示范围。只要在规格化范围内,有效数字的位数是满的,算起来最准、最快。
- 非规格化:在规格化的精度位到达最低的时候,为了表示更小的数不被截断所做的扩展
规格化
- 规定:指数位 EEE 不全为 0 且 不全为 1(例如 FP16 中,1≤E≤301 \le E \le 301≤E≤30)
- 公式:V=(−1)S×(1+M)×2E−BiasV = (-1)^S \times (\mathbf{1} + M) \times 2^{E - Bias}V=(−1)S×(1+M)×2E−Bias
- 隐藏位:公式中 MMM 前边的 111 是硬件自动补上的,不占内存。
- 精度表现:尾数 MMM 的每一位都代表有效精度,相对精度恒定。
- 适用场景:绝大多数正常的科学计算和模型权重
非规格化
- 规定:指数位 EEE 全为 0(例如 FP16 中,E=0E = 0E=0,且尾数 M≠0M \neq 0M=0)
- 公式:V=(−1)S×(0+M)×21−BiasV = (-1)^S \times (\mathbf{0} + M) \times 2^{1 - Bias}V=(−1)S×(0+M)×21−Bias
- 隐藏位变身:开头的隐藏位从 111 变成了 000。这就是为了能表示比“最小规格化数”还要小的数。
- 指数固定:注意指数部分不再是 0−Bias0-Bias0−Bias,而是固定为 1−Bias1-Bias1−Bias。这样做是为了和规格化数的最小值平滑衔接,没有断层。
- 适用场景:数值极度接近 000 时的“保命”阶段(渐进式下溢)。
上述定义的目的:平滑过渡
- 根据规格化的定义:最小的规格化数(E=1E=1E=1):1.0×21−15=2−141.0 \times 2^{1-15} = \mathbf{2^{-14}}1.0×21−15=2−14。
- 根据非规格化的定义:M全为1的时候,0+M0 + M0+M 接近1,最大值 ≈2−14\approx 2^{-14}≈2−14
- 最大值:M=11...11M = 11...11M=11...11(接近 111)→0.999...×2−14≈2−14\rightarrow 0.999... \times 2^{-14} \approx 2^{-14}→0.999...×2−14≈2−14。
- 最小值:M=00...01→2−10×2−14=2−24M = 00...01 \rightarrow 2^{-10} \times 2^{-14} = 2^{-24}M=00...01→2−10×2−14=2−24。
- 当非规格化 MMM 全为1的时候,0+M0 + M0+M 接近1,最大值 ≈2−14\approx 2^{-14}≈2−14,完美衔接规格化最小值,实现数值平滑过渡
例子 (以FP16为例)
在 FP16 中,指数位 EEE 只有 5 位。
- EEE 能够表示的二进制数是从
00000到11111(即 0 到 31)。 - Bias(偏置)固定为 15。
规格化数的 EEE 取值范围是 111 到 303030
- 最大的规格化数(E=30E=30E=30):230−15=215=327682^{30-15} = 2^{15} = 32768230−15=215=32768。
- 最小的规格化数(E=1E=1E=1):1.0×21−15=2−141.0 \times 2^{1-15} = \mathbf{2^{-14}}1.0×21−15=2−14。
当 E=1E = 1E=1 最小时,指数部分已经是 2−142^{-14}2−14 了。如果想表示比 2−142^{-14}2−14 更小的数,EEE 必须变成 00000
如果没有“非规格化”处理:
- 当 E=0E=0E=0 时,公式依然是 (1+M)×20−15(1 + M) \times 2^{0-15}(1+M)×20−15。
- 那么能表示的最小值(M=0M=0M=0)是 1.0×2−151.0 \times 2^{-15}1.0×2−15。即所能表示的最小正数是 2−152^{-15}2−15。比 2−152^{-15}2−15 更小的数(比如 2−202^{-20}2−20)一个也表示不出来! 它们会全部直接跳到 0。这个 000 到 2−152^{-15}2−15 之间的真空,就是“空档"
非规格化补档:
当 E=0E=0E=0 时,隐藏位从 1. 变成 0.。
公式变为:0.MMMM×2−140.MMMM \times 2^{-14}0.MMMM×2−14(注意:为了平滑衔接,指数固定为 1−Bias1-Bias1−Bias)。
可以利用尾数 MMM 继续往小了表示:
- 0.1×2−14=2−150.1 \times 2^{-14} = 2^{-15}0.1×2−14=2−15
- 0.01×2−14=2−160.01 \times 2^{-14} = 2^{-16}0.01×2−14=2−16
- …
- 0.0000000001×2−14=2−240.0000000001 \times 2^{-14} = \mathbf{2^{-24}}0.0000000001×2−14=2−24(这是 FP16 的极限精度点)
平滑过渡
- 根据规格化的定义:最小的规格化数(E=1E=1E=1):1.0×21−15=2−141.0 \times 2^{1-15} = \mathbf{2^{-14}}1.0×21−15=2−14。
- 根据非规格化的定义:M全为1的时候,0+M0 + M0+M 接近1,最大值 ≈2−14\approx 2^{-14}≈2−14
- 最大值:M=11...11M = 11...11M=11...11(接近 111)→0.999...×2−14≈2−14\rightarrow 0.999... \times 2^{-14} \approx 2^{-14}→0.999...×2−14≈2−14。
- 最小值:M=00...01→2−10×2−14=2−24M = 00...01 \rightarrow 2^{-10} \times 2^{-14} = 2^{-24}M=00...01→2−10×2−14=2−24。
- 当非规格化 MMM 全为1的时候,0+M0 + M0+M 接近1,最大值 ≈2−14\approx 2^{-14}≈2−14,完美衔接规格化最小值,既实现数值平滑过渡,又保证了可以继续表示更小的数
结论
- 如果不划分非规格化,FP16 只能表示到 2−152^{-15}2−15 就截断了; 有了非规格化,它能靠尾数前面的 000 一直撑到 2−242^{-24}2−24
浮点数数据精度格式

- S:Sign bit,符号位。决定数字的正负(0 为正,1 为负)
- E:Exponent,指数位。决定数字的范围(Range),即这个数能有多大或多小
- M:Mantissa / Fraction,尾数位/精度位。决定数字的精度(Precision),即小数点后能精确到多少位
规格化:V=(−1)S×(1+M)×2E−Bias非规格化:V=(−1)S×(0+M)×21−Bias \begin{align} & 规格化: & V = (-1)^S \times (1 + M) \times 2^{E - Bias} \\ & 非规格化: & V = (-1)^S \times (\mathbf{0} + M) \times 2^{1 - Bias} \end{align} 规格化:非规格化:V=(−1)S×(1+M)×2E−BiasV=(−1)S×(0+M)×21−Bias
-
(−1)S(-1)^S(−1)S (符号部分):
- 如果 S=0S=0S=0,结果为正;
- 如果 S=1S=1S=1,结果为负。
-
(1+M)(1 + M)(1+M) (尾数部分):
- 为了节省空间,计算机默认二进制开头永远是
1.(比如 1.011...1.011...1.011...),所以那个1不占位,只存后面的小数部分 MMM。这就是为什么同样的位数,浮点数比定点数能表示更精细的小数。
- 为了节省空间,计算机默认二进制开头永远是
-
2E−Bias2^{E - Bias}2E−Bias (指数部分):
-
EEE 是你在内存里看到的那个无符号整数。在 IEEE 754 标准中,E=0E = 0E=0 被预留给了“非规格化数”,所以规格化数的最小 E=1E = 1E=1
-
BiasBiasBias (偏置值):为了让指数能表示负数(即非常小的数,如 2−1262^{-126}2−126),需要减去一个中间数
- 对于 FP32,Bias=127Bias = 127Bias=127
- 对于 FP16,Bias=15Bias = 15Bias=15
-
常见格式
| 格式 | 总位数 (bit) | S (符号) | E (指数) | M (尾数/精度) | Bias (偏置) | 数值范围 (Range) | 核心用途 |
|---|---|---|---|---|---|---|---|
| FP32 | 32 | 1 | 8 | 23 | 127 | 非常广 | 训练基准 / 科学计算 |
| TF32 | 19 (内部) | 1 | 8 | 10 | 127 | 与 FP32 相同 | A100/H100 训练加速 |
| BF16 | 16 | 1 | 8 | 7 | 127 | 与 FP32 相同 | 大模型训练 (最稳) |
| FP16 | 16 | 1 | 5 | 10 | 15 | 窄 (最高 65504) | 混合精度训练 / 推理 |
| Int32 | 32 | 1 | - | 31 | - | ±2.1×109\pm 2.1 \times 10^9±2.1×109 | 索引 / 计数 / 高压整数运算 |
| Int16 | 16 | 1 | - | 15 | - | ±32768\pm 32768±32768 | 信号处理 / 特定硬件压缩 |
| Int8 | 8 | 1 | - | 7 | - | -128 ~ 127 | 模型推理量化 (省显存) |
-
FP32 (单精度):传统的“标杆”。8 位指数,23 位尾数。精度高,但太占空间。
-
FP16 (半精度):为了快。只有 5 位指数,10 位尾数。缺点是容易“溢出”(数字太大或太小就没法表示了)。
-
BF16 (Brain Float 16):Google 发明的。8位指数位,7 位尾数
- 重点: 牺牲了精度来换取和 FP32 一样的数值范围,这样训练时就不容易溢出,比 FP16 更稳。
-
TF32 (Tensor Float 32):NVIDIA A100 专用。它是 FP32 的“瘦身版”,保留了 FP32 的范围,但在计算内部截断了精度。
-
Int32 (整型):标准的 32 位整数。1 位符号位,31 位数值位。
- 特点:范围极大(约 ±21\pm 21±21 亿),但在深度学习模型参数中很少直接使用,因为它太占空间且不支持小数点。
Int16 (短整型):16 位整数。1 位符号位,15 位数值位。
- 特点:范围在 ±32768\pm 32768±32768 之间。在某些特定的底层硬件加速或早期的音频处理中会用到,但在主流深度学习训练/推理中不如 Int8 普及。
-
Int8:8 位整数。主要用于推理(部署),因为模型训练好后,很多参数不需要那么精确也能跑出好结果
梯度归零问题
- 梯度归零:指在采用低精度浮点数(如 FP16)存储梯度时,由于数值过小,超出了该数据格式能表示的最小正值范围,导致梯度在表示或计算过程中被强制截断为 0。这会导致模型参数停止更新,训练陷入停滞。
原因
- 硬件维度的指数限制:FP16 的指数位仅有 5 位,配合偏置值(Bias=15),其能表示的最小规范化正数为 2−142^{-14}2−14。
- 算法维度的精细化:FP16 的尾数位仅有 10 位。当数值接近 2−142^{-14}2−14 时,其最小可感知的变化步长(精度坍塌点)仅为 2−242^{-24}2−24。
- 正则化压制:在 Word2Vec 或大型 Transformer 中,L2 正则化会强制将权重和梯度压制在极小范围内。当梯度值 g<2−24g < 2^{-24}g<2−24 时,在 FP16 体系下该数值无法被表示,直接映射为 0。
推导
- 公式:V=(−1)S×(1+M)×2E−BiasV = (-1)^S \times (1 + M) \times 2^{E - Bias}V=(−1)S×(1+M)×2E−Bias
- 最小规格化正数:为获得最小值,令指数位 E=1E = 1E=1,尾数位 M=0M = 0M=0:Vmin_norm=1.0×21−15=2−14V_{min\_norm} = 1.0 \times 2^{1 - 15} = 2^{-14}Vmin_norm=1.0×21−15=2−14
- 最小精度步长:在最小指数范围内,数值的精度由尾数 MMM 的最后一位决定。FP16 有 10 位尾数,最后一位的权重为 2−102^{-10}2−10。ΔV=2−10×2−14=2−24\Delta V = 2^{-10} \times 2^{-14} = 2^{-24}ΔV=2−10×2−14=2−24
- 归零判定:若梯度计算结果 ggg 满足 ∣g∣<ΔV (即 2−24)|g| < \Delta V \text{ (即 } 2^{-24}\text{)}∣g∣<ΔV (即 2−24),则在 FP16 的位宽限制下,计算机无法区分 ggg 与 000 之间的差异,执行硬件截断
解决方案
- 损失放大:在反向传播前,将 Loss 乘以一个巨大的比例因子 SSS(例如 2162^{16}216):Lossscaled=Loss×SLoss_{scaled} = Loss \times SLossscaled=Loss×S
- 链式传导:根据链式法则,所有梯度都会随之放大 SSS 倍,从而将原本位于 2−242^{-24}2−24 以下的梯度推回至 FP16 的有效表示范围(2−142^{-14}2−14 以上)。Gscaled=∂(Loss×S)∂w=Goriginal×SG_{scaled} = \frac{\partial (Loss \times S)}{\partial w} = G_{original} \times SGscaled=∂w∂(Loss×S)=Goriginal×S
- 权重更新前还原: 在更新权重 www 之前,先将梯度除以 SSS 还原,并在 FP32 格式下进行更新,确保优化步长不受影响
低精度的意义
- 高精度问题:高精度 = 费钱、耗电、慢
- 低精度优点
- 更少内存:显存占用减半,你可以跑更大的 Batch Size。
- 更低能耗:执行一次 FP32 乘法 的能量约为 3.7pJ,而 FP16 仅需 1.1pJ。由于乘法器电路复杂度与位数平方成正比,低精度不仅能节省 2x 的内存,更能带来近 4x 的能效比提升,是大规模集群训练的工业标准
- 更快的计算:单位时间内 GPU 能处理更多的运算(TFLOPS 更高)
精度选择
混合精度训练 (AMP)
- 全称:Automatic Mixed Precision
- 核心思想:能省则省,该准则准
操作
-
准备阶段:在内存中同时存在一套 FP32 权重(主权重)和一套 FP16 权重(计算用)。
-
前向传播 (Forward):使用 FP16 权重进行计算,得到 Loss。
-
损失缩放 (Loss Scaling) —— 关键步骤!:
-
在调用
loss.backward()之前,先将 Loss 乘以一个因子 SSS(比如 65536)。 -
公式:Lossscaled=Loss×SLoss_{scaled} = Loss \times SLossscaled=Loss×S。
-
-
反向传播 (Backward):
-
此时计算出的梯度全都被放大了 SSS 倍。
-
原本会变成 000 的微小梯度(比如 2−202^{-20}2−20),现在变成了 2−20×216=2−42^{-20} \times 2^{16} = 2^{-4}2−20×216=2−4,成功留在 FP16 的规格化安全区。
-
-
梯度还原 (Unscaling):
- 在更新权重前,把这些 FP16 梯度除以 SSS,还原回真实大小。
-
权重更新:
-
将还原后的梯度应用到那套 FP32 主权重上。
-
为什么用 FP32 更新? 因为梯度 ×\times× 学习率后的变化量非常小,只有 FP32 能精准捕捉这种细微的挪动。
-
A100/H100/V100
- 训练阶段
- 旧卡(如 V100),首选 FP16 混合精度。
- A100/H100 等新卡,强烈建议用 BF16,它更稳定,基本不需要调 Loss Scaling
- 推理阶段
- CV 任务:普遍能接受 Int8 量化。
- NLP/大模型:通常用 FP16 或 Int8/FP16 混合。
更多推荐
所有评论(0)