2. 浮点数基础
做深度学习这几年,我越来越觉得浮点数是个被低估的话题。很多人上来就开混合精度训练,结果模型不收敛,或者精度掉得莫名其妙。说白了,你不了解FP16的脾气,它就会给你颜色看。
今天我们就从最底层的浮点数讲起。别担心,我不会搬出一堆枯燥的公式。我会结合我踩过的坑,帮你把这些概念串起来。
IEEE 754标准回顾
IEEE 754是浮点数的国际标准。它定义了浮点数在计算机里怎么存、怎么算。几乎所有现代CPU和GPU都遵循这个标准。
一个浮点数由三部分组成:
- 符号位:1位,0表示正数,1表示负数
- 指数位:决定数值的范围
- 尾数位:决定数值的精度
公式很简单:(-1)^符号 × 2^(指数-偏置) × 1.尾数
嗯,这里要注意。指数是有偏置的。FP32的偏置是127,FP16的偏置是15。为什么要偏置?为了能表示很小的数。这个细节我当年面试时被问过,答不上来挺尴尬的。
FP32与FP16的位宽与精度对比
先看一张对比表,一目了然:
| 类型 | 总位宽 | 符号位 | 指数位 | 尾数位 | 最大正数 | 最小正数 |
|---|---|---|---|---|---|---|
| FP32 | 32位 | 1 | 8 | 23 | ~3.4×10³⁸ | ~1.4×10⁻⁴⁵ |
| FP16 | 16位 | 1 | 5 | 10 | 65504 | ~6.1×10⁻⁵ |
你看,FP32的尾数有23位,FP16只有10位。这意味着什么?FP32能精确表示大约7位十进制小数,FP16只能表示大约3位。精度差了整整一个数量级。
我在项目中遇到过一个问题:用FP16训练一个BERT模型,loss死活降不下去。后来发现是梯度太小,直接被FP16截断成0了。这就是精度不够的典型表现。
FP16的表示范围与精度限制
FP16的最大值是65504。你想想看,一个激活值如果超过65504,直接变成inf。这在训练中很常见,尤其是大模型。
FP16的最小正数是6.1×10⁻⁵。听起来很小对吧?但梯度更新时,很多参数的变化量比这个还小。一旦小于这个值,就变成0了。这就是所谓的「下溢」问题。
我曾经在训练一个ResNet-50时,发现某些层的梯度全是0。排查了半天,原来是FP16的表示范围不够,梯度下溢了。后来加了loss scaling才解决。所以,FP16训练必须配合梯度缩放,这是铁律。
还有一个坑:FP16的精度在数值接近0时特别差。因为尾数只有10位,很多小数根本表示不了。比如0.1这个数,在FP16里其实是个近似值。多次累加后误差会越来越大。
BF16与TF32简介
为了解决FP16的问题,Google和NVIDIA分别推出了自己的方案。
BF16(Brain Floating Point)
BF16是Google为TPU设计的。它保留了FP32的8位指数,只把尾数砍到7位。这样表示范围和FP32一样大,但精度更低。
| 类型 | 总位宽 | 指数位 | 尾数位 | 最大正数 |
|---|---|---|---|---|
| BF16 | 16位 | 8 | 7 | ~3.4×10³⁸ |
我个人习惯在训练大模型时优先用BF16。为什么?因为它的范围和FP32一样,不会出现inf或下溢的问题。虽然精度低一点,但深度学习对精度的容忍度其实很高。
如果你的GPU支持BF16(比如A100、H100),建议优先使用。BF16训练比FP16更稳定,几乎不需要调loss scaling。
TF32(TensorFloat-32)
TF32是NVIDIA在Ampere架构上推出的。它用19位来存储:1位符号、8位指数、10位尾数。说白了,它是个「缩水版」的FP32。
TF32的好处是:输入输出都是FP32格式,但内部计算用TF32加速。你不需要改任何代码,NVIDIA的库会自动处理。我在A100上跑过实验,TF32比纯FP32快了大约8倍,精度几乎没损失。
- FP32:精度高,速度慢,适合需要高精度的场景
- FP16:速度快,但范围小、精度低,需要loss scaling
- BF16:范围大,精度低,适合大模型训练
- TF32:自动加速,无需改代码,适合Ampere及以上架构
下面这张图展示了不同浮点格式的位宽分布,帮你直观理解它们的区别:
从这张图可以清楚看到:FP16和BF16虽然都是16位,但指数位不同。BF16的指数位和FP32一样多,所以范围大。FP16的尾数位更多,所以精度稍高。这是个取舍问题。
我个人建议:如果你的模型参数少于1B,用FP16配合loss scaling就够了。如果模型更大,或者你不想折腾loss scaling,直接上BF16。至于TF32,它是个「无痛升级」方案,适合不想改代码又想提速的场景。
好了,浮点数基础就讲到这里。记住一句话:没有最好的格式,只有最合适的格式。下一节我们会深入混合精度训练的具体实现,到时候这些知识都会用上。
为什么FP16训练时,loss scaling的初始值通常设为2的幂次(如128、256、512)?这和浮点数的表示有什么关系?想明白这个问题,你对FP16的理解就更深一层了。