彻底理解系列之:FP32、FP16、TF32、BF16、混合精度
模型训练和推理的速度,随着大模型的到来变得越来越关键。减小计算过程中数据的长度,从而降低存储和带宽消耗,是提速的重要途径。正因如此,深入理解各种浮点精度的细节,避免一知半解,就显得尤为重要。

从FP32说起
计算机处理数字类型包括整数和浮点两大类,而IEEE 754标准规定了浮点数的存储结构。一个浮点数由三部分组成,以最常见的FP32(Float Point 32)为例:
![图1. FP32位数分配,来源[2]](图1.png)
- :最高位用1位表示,1表示负数,0表示正数,记为S
Sign(符号位)
- :中间8位表示指数,记为E
Exponent(指数位)
- :低23位表示小数部分,记为M
Mantissa(小数位)
拿十进制数9.625来举个例子,看看十进制与FP32二进制之间如何互转。
十进制 → 二进制
先拆成整数部分9和小数部分0.625。9转二进制是1001,0.625通过“乘2取整、余下小数继续乘2”的方法得到0.101,合起来就是1001.101,写成二进制的指数形式也就是1.001101 × 23。
按照IEEE 754规范,FP32的指数部分要加上127的偏移量,于是3+127=130,对应二进制10000010;小数部分001101后面补足23位;符号位为0。拼在一起就是:
0 10000010 00110100000000000000000。
用转换工具验证一下,果然正确。工具的上面把三个部分都标示出来了,复选框打勾表示对应位置的二进制数为1,否则为0。
![图2. FP32转换工具,来源[3]](图2.png)
二进制 → 十进制
反过来就是同样的步骤。将二进制按符号、指数、小数分成S、E、M三部分,FP32转十进制的公式为:
(-1)S × (1.M)2 × 2E-127
具体到这个例子,S=0,E=10000010(十进制130),M=00110100000000000000000,1.M去掉末尾的0,实为1.001101(二进制),转成十进制:1×20 + 0×2-1 + 0×2-2 + 1×2-3 + 1×2-4 + 0×2-5 + 1×2-6 = 1 + 0.125 + 0.0625 + 0.015625 = 1.203125,再乘以23 = 9.625,完美还原。
FP32搞清楚了,FP16、FP64也是类似的套路,只是指数位和小数位的长度不同:
| 类型 | 符号位长度 | 指数位长度 | 小数位长度 | 偏移量 |
|---|---|---|---|---|
半精度 FP16 |
1 | 5 | 10 | 15 |
单精度 FP32 |
1 | 8 | 23 | 127 |
双精度 FP64 |
1 | 11 | 52 | 1023 |
模型训练中不同精度的问题
先对比FP32和FP64。一个FP32只占32位,相比FP64有两大优势:
- :显存是关键瓶颈,FP32只用一半的空间,同样的GPU能训练更大的模型,或者同模型下可以塞进更大的batch size。
减少存储使用
- :同样一次计算,FP32位数少、计算量小,时间自然更短。
提高训练速度
同样的道理放在FP16和FP32比较上也成立。但这是否意味着直接全部用FP16就好?当然不是。位数少有两个硬伤:
- :位数少,表示的数就不够精确,可能导致准确率下降。
精度不足
- :位数少,能表示的最大值和最小值范围更窄,数据可能溢出,“装不下了”。
范围受限
先看精度问题。用FP64、FP32、FP16分别表示1/3,看看差别有多大:

精度越高,数据表示和计算越准确,模型训练拟合出来的参数也就越精准。具体得看任务对精度的要求。
再看表示范围。通过PyTorch的接口查一下FP32和FP16能表示的数范围:

试试看106这个大数能不能表示:

很明显,106已经超过了FP16的上限65504,无法处理。如果模型训练中某些参数超过这个数,FP16就直接“罢工”了。
混合精度
既然FP32和FP16各有长短,那就混合着用吧。在模型训练的不同步骤选用不同的精度:
![图3. 混合精度使用流程,来源[1]](图6.png)
- 把神经网络权重参数从初始化的FP32转为FP16;
- 用FP16进行前向和反向计算,并算出梯度;
- 把FP16的梯度转回FP32;
- 用FP32的梯度乘以学习率;
- 用FP32更新网络权重,得到更新后的FP32权重。
以上步骤不断循环。简单说就是:梯度更新权重这一步必须用FP32,因为梯度乘上学习率后数值往往很小,FP16的精度不够,容易丢成0。
混合精度里还有一项关键技术叫
“损失缩放”
资料[1]用DistilBERT模型做了一个电影情感分类任务的微调,对比了纯FP32、纯FP16和混合精度的性能与准确率:
![图4. FP32、FP16和混合精度训练对比,来源[1]](图7.png)
从图4可以看出,混合精度的训练时间接近FP16,只有FP32的三分之一;存储用量介于两者之间;但预测准确率却和FP32差不多,甚至略高。原文作者提到,可能因为用了正则化。而纯FP16的准确率低很多,多半是发生了数据溢出,模型已经不准了。
BF16、TF32
FP16的指数位只有5位,小数位10位,能表示的整数范围太小。于是谷歌为了自家的TPU定义了一种新格式:Brain Floating Point 16,简称BF16。和FP16一样总长16位,但指数位变成了8位(和FP32相同,因此整数范围一样),小数位缩短到7位。
英伟达则为自家GPU定义了TF32:指数位8位(和FP32、BF16一样),小数位10位(和FP16一样,比BF16多3位)。

资料[1]把所有类型(除TF32外)以及混合精度又做了统一对比,还是同一个任务:

可以看出,BF16速度最快(和FP16相当,因为都是16位)、存储最少(小数位短)、准确率也最高(其余几个92%在同一水平,微小偏差可能来自其他因素)。
到此为止,我们把各种浮点类型的定义、转换方法、在模型训练中的使用方式以及性能对比都理了一遍。希望这篇梳理能帮你彻底搞懂这些精度——从FP32到FP16、BF16、TF32,再到混合精度的实践。
-
- 关于宇宙的好的网名有哪些
- 角色扮演 | 1
- 网名