跳至内容

BF16、FP16、FP32 与模型量化

浮点格式的范围与精度、整数量化的误差,以及模型权重和计算格式的区别。

  • BF16
  • FP16
  • FP32
  • 混合精度
  • 模型量化

FP32、FP16、BF16 是浮点格式;INT8、INT4 是整数格式,常用于权重量化。位数越少,每个值占用的空间越小,可表示的值也越少。

数值精度描述数值表示得有多细,不等于模型的任务准确率。 低精度带来的误差是否影响效果,需要实际评估。

1. 权重与计算精度

  • 权重是训练得到的参数,推理时通常不变。
  • 激活是各层产生的中间结果,随输入变化。

它们通常保存为张量(多维数组),dtype 表示元素的数据类型。线性层的一个输出为:

y=∑i=1nwixi+by=\sum_{i=1}^{n}w_i x_i+b

xix_i 是输入,wiw_i 是权重,bb 是偏置。输入与权重相乘,乘积求和并加上偏置,得到输出 yy。

  • 权重精度:参数以什么格式保存,加载模型时可以转换。
  • 激活精度:层间输入、输出以什么格式保存,不必与权重相同。
  • 乘法精度:运算实际使用输入中的多少有效位。
  • 累加精度:多个乘积相加时,部分和以什么精度保留。

存储格式、乘法精度和累加精度不必相同。

常见组合:权重和输入激活存为 BF16,乘法使用 BF16 输入,乘积以 FP32 累加,输出存回 BF16。

FP32 累加减少求和时的舍入损失,不能恢复输入已经丢失的信息;输出存回 BF16 时仍会舍入。

混合精度是在同一次训练或推理中,组合使用不同精度。 矩阵乘常采用低精度,归一化、损失计算等数值敏感部分可能保留 FP32。

2. 浮点数的表示

正常的二进制浮点数类似科学计数法:

x=(−1)s×(1.f)2×2Ex=(-1)^s\times(1.f)_2\times2^E
  • ss 是符号位:0 为正,1 为负。
  • EE 是实际指数,控制数量级;指数位越多,通常范围越大。
  • ff 是小数字段,常称尾数位;位数越多,同一数量级下相邻数的间隔越小。

正常数隐含首位 1,因此有效二进制精度 = 小数字段位数 + 1。指数按“实际指数 + 固定偏置”编码,部分编码留给零、无穷大等特殊情况。

FP32 占 4 字节,位布局为 1 位符号 + 8 位指数 + 23 位小数,有效精度为 24 位。最小正常正数约为 1.18×10−381.18\times10^{-38},最大有限值约为 3.40×10383.40\times10^{38}。

范围决定数值是否装得下,精度决定能保留多少细节。 超出范围可能溢出为无穷大,太小可能舍入为零;落在两个可表示数之间的值也需要舍入。

最小正常正数不是最小正数。次正规数能表示更靠近零的值,但有效精度较低,部分硬件会将其按零处理。

3. BF16 与 FP16

两者都占 16 位:BF16 范围更大,FP16 在共同的正常数范围内更精细。 BF16 是 Brain Floating Point 的简称。

比较项FP16BF16
位宽与存储16 位,2 字节16 位,2 字节
符号 / 指数 / 小数字段1 / 5 / 10 位1 / 8 / 7 位
正常数有效二进制精度11 位8 位
最小正常正数约 6.10×10−56.10\times10^{-5}约 1.18×10−381.18\times10^{-38}
最大有限值65504约 3.39×10383.39\times10^{38},接近 FP32
[1,2)[1,2) 内相邻数间隔2−10=0.00097656252^{-10}=0.00097656252−7=0.00781252^{-7}=0.0078125
数值取舍小数字段多,舍入更细;范围较窄指数位多,较少因范围不足而溢出或下溢;舍入较粗
训练中的损失缩放常需要,减少小梯度下溢通常不需要,较大的指数范围减少了小梯度下溢
选择依据硬件、算子对 FP16 支持更好,或更看重有效精度时考虑硬件、算子支持且训练数值范围较大时,常优先考虑

按最近值舍入:1.001 在 FP16 中变成 1.0009765625,在 BF16 中变成 1,体现了有效精度差异。

70000 超过 FP16 的最大有限值,会溢出为无穷大;BF16 仍能用有限值近似表示,体现了范围差异。

正常数的指数每增加 1,相邻数间隔也扩大为原来的 2 倍。表中的间隔只针对 [1,2)[1,2);FP32 在这个区间的间隔为 2−232^{-23},比两种 16 位格式都细。

梯度是损失对参数的导数,影响参数更新;过小的梯度被舍入为零时,更新可能消失。损失缩放(loss scaling)给损失乘上系数,放大反向传播中的梯度;更新参数前,把梯度除以同一系数,以减少下溢。

BF16 范围更大,并不保证任务准确率更高。两者的实际速度取决于硬件和执行内核,不能仅凭位宽判断。

4. FP32、TF32 与 BF32

TF32 保留 FP32 存储,但降低了乘法输入的有效精度。 TensorFloat-32 是 NVIDIA GPU 用于矩阵乘和卷积的计算格式,同一份 FP32 张量可走不同计算路径:

比较项完整 FP32 计算典型 TF32 计算
输入、输出张量的存储FP32,每个值 4 字节FP32,每个值 4 字节
乘法输入的小数字段23 位,正常数有效精度 24 位10 位,正常数有效精度 11 位
乘法输入的指数位8 位8 位
乘积的累加精度FP32FP32

TF32 在数据进入乘法单元时,将输入舍入到 10 位小数字段。原始张量不被改写,但本次乘法用不到被舍入掉的细节。 它保留接近 FP32 的范围,乘法输入的有效精度与 FP16 相当。

矩阵乘的一项为 a×ba\times b,其中 a=1.000244140625=1+2−12a=1.000244140625=1+2^{-12}、b=1b=1,均能被 FP32 精确保存。按最近值舍入:

  • 完整 FP32:保留输入,乘积为 1.000244140625。
  • TF32:[1,2)[1,2) 内的间隔为 2−102^{-10},aa 被舍入为 1,乘积为 1。

后续的 FP32 累加和存储,无法补回乘法输入丢掉的 0.000244140625。

TF32 借助 Tensor Core(GPU 的矩阵乘加单元)提高吞吐,实际收益取决于矩阵规模、硬件和访存开销。它不压缩 FP32 权重,也不会让权重显存减半。

需要完整 FP32 计算时,除了检查 dtype,还要检查框架是否允许 TF32。

BF32 是厂商特定的计算模式,不是通用权重类型。Intel Extension for PyTorch 的 FP32MathMode.BF32 保持 FP32 接口与存储,允许部分计算采用 BF16 精度。

5. FP8

FP8 每个值占 1 字节,仍有符号、指数和小数字段。NVIDIA Transformer Engine 常见的两种定义是:

  • E4M3:1 位符号、4 位指数、3 位小数,最大有限值为 448。
  • E5M2:1 位符号、5 位指数、2 位小数,最大有限值为 57344。

E5M2 范围更大,E4M3 的小数字段更多。FP8 通常配合缩放系数,将数据调整到可表示范围,计算时恢复数量级;缩放能减少溢出,不能消除舍入误差。

FP8 需要硬件、计算库和缩放策略支持,累加及数值敏感运算通常仍采用更高精度。

6. INT8 与 INT4 量化

INT8、INT4 本身是整数类型:有符号 INT8 有 256 个编码,范围为 [−128,127][-128,127];INT4 有 16 个编码,范围为 [−8,7][-8,7]。

整数量化把浮点值映射为整数编码,并保存换算关系。 常见的线性量化与反量化为:

q=clip⁡(round⁡(x/s)+z, qmin⁡, qmax⁡)x^=s(q−z)\begin{aligned} q&=\operatorname{clip}\left(\operatorname{round}(x/s)+z,\ q_{\min},\ q_{\max}\right) \\ \hat{x}&=s(q-z) \end{aligned}
  • xx 是原始值,qq 是整数编码,x^\hat{x} 是恢复的近似值。
  • s>0s>0 是缩放系数:相邻编码在原始数值中的间隔。
  • zz 是零点:原始值 0 对应的编码,对称量化通常取 0。

round 舍入为整数,clip 将越界值截到编码上下限 qmin⁡q_{\min}、qmax⁡q_{\max};两者都可能产生误差。

一组权重位于 [−1.2,1.2][-1.2,1.2],零点取 0。选取两组能覆盖此范围的示例系数:

  • INT8,s=0.01s=0.01:0.43 编码为 43,恢复为 43×0.01=0.4343\times0.01=0.43。
  • INT4,s=0.2s=0.2:0.43/0.2=2.150.43/0.2=2.15 舍入为 2,恢复为 2×0.2=0.42\times0.2=0.4,误差为 0.03。

系数仅用于演示。INT4 编码更少,覆盖相近范围时需要更大的间隔,更容易丢失细节。

量化粒度决定哪些数值共享系数:

  • 按张量:整个张量共用。
  • 按输出通道:线性层中,每个输出对应的一组权重共用。
  • 按组:将权重分成更小的组,各组分别保存。

分组更细通常能贴近局部数值分布,但增加额外存储。量化算法、分组方式、异常大值的处理和保留高精度的层,都会影响效果。

W、A 分别标记权重和激活的位宽:

  • W8A8:权重、激活均为 8 位,可能采用 INT8 或 FP8。
  • W8A16:权重为 8 位,激活通常为 FP16 或 BF16。
  • W4A16:权重为 4 位,激活通常为 FP16 或 BF16。

W4A16 常采用仅权重量化(weight-only):指定权重以 4 位保存,计算时反量化,与浮点激活运算;其他层可能保留高精度。存储位宽不等于计算位宽。

W、A 不说明累加精度。 整数矩阵乘可能采用 INT32 累加,浮点计算常采用 FP32 累加。

4 位不一定是 INT4。NF4 针对近似正态分布权重设计,16 个编码对应不均匀的代表值。QLoRA 是一种微调方法,冻结这类 4 位基础权重,只训练额外的适配参数。

7. 常见模型的权重格式

checkpoint 是保存的模型状态。下表列出具体发布版本的权重及相关张量,不代表整个模型家族,也不等于运行时精度。

具体版本发布权重及相关张量
Google BERT base-uncased官方发布权重为 FP32
Qwen2.5-7B-Instruct官方非量化版权重为 BF16
Qwen2.5-7B-Instruct GPTQ-Int4主要权重为 4 位 GPTQ 量化;配置浮点类型为 FP16,并非全部张量都是 INT4
DeepSeek-V3以 FP8 发布,包含 BF16、FP8 E4M3、FP32 张量;FP32 可保存缩放参数
Llama 3.1 405BMeta 分别提供 BF16 和 FP8 混合精度版本

GPTQ 是训练后对权重量化、补偿部分误差的方法,不是浮点格式。配置中的 torch_dtype 也不足以描述所有量化张量。

BF16 权重转存 FP32 会增加占用,却不能恢复已舍入掉的信息。 未公开权重或部署细节的服务模型,不能从 API 表现或价格反推内部精度。

8. 存储与运行开销

裸权重字节数 = 参数数量 × 每个参数的位数 ÷ 8。 INT4 按两个参数打包进一个字节计算。

假设模型恰好有 70 亿个参数,全部采用同一种格式,不计任何额外数据。按 1 GB=1091\ \mathrm{GB}=10^9 字节计算:

权重格式裸权重大小
FP3228 GB
BF16 / FP1614 GB
FP8 / INT87 GB
INT43.5 GB

裸权重之外,还要计算这些开销:

场景额外数据
模型文件缩放系数、零点、文件元数据,以及未量化部分增加的占用
推理激活、临时计算空间、KV Cache
训练反向传播所需的激活、临时空间、梯度、优化器状态,以及可能保留的 FP32 主权重
  • KV Cache 保存历史 token 的 Key、Value,避免重复计算。格式可独立于权重选择,长上下文、高并发会增加其占用。
  • 优化器状态 保存更新所需的历史信息,如 Adam 中梯度的一阶、二阶矩估计,其精度不必与权重相同。
  • FP32 主权重 用于累积微小更新,低精度计算副本由它转换得到,减少更新被舍入掉的情况;是否保留取决于训练实现。

权重位数减半,不等于总显存或推理耗时减半。

权重读取是瓶颈时,INT8、INT4 可能加速推理;缺少适配内核时,反量化与格式转换可能抵消收益。任务效果、峰值显存和吞吐需要在目标硬件上分别测量。

评论

使用 GitHub 登录后参与讨论,评论将公开保存在 GitHub。

阅读到这里时加载评论。