BF16、FP16、FP32 与模型量化
浮点格式的范围与精度、整数量化的误差,以及模型权重和计算格式的区别。
FP32、FP16、BF16 是浮点格式;INT8、INT4 是整数格式,常用于权重量化。位数越少,每个值占用的空间越小,可表示的值也越少。
数值精度描述数值表示得有多细,不等于模型的任务准确率。 低精度带来的误差是否影响效果,需要实际评估。
1. 权重与计算精度
- 权重是训练得到的参数,推理时通常不变。
- 激活是各层产生的中间结果,随输入变化。
它们通常保存为张量(多维数组),dtype 表示元素的数据类型。线性层的一个输出为:
是输入, 是权重, 是偏置。输入与权重相乘,乘积求和并加上偏置,得到输出 。
- 权重精度:参数以什么格式保存,加载模型时可以转换。
- 激活精度:层间输入、输出以什么格式保存,不必与权重相同。
- 乘法精度:运算实际使用输入中的多少有效位。
- 累加精度:多个乘积相加时,部分和以什么精度保留。
存储格式、乘法精度和累加精度不必相同。
常见组合:权重和输入激活存为 BF16,乘法使用 BF16 输入,乘积以 FP32 累加,输出存回 BF16。
FP32 累加减少求和时的舍入损失,不能恢复输入已经丢失的信息;输出存回 BF16 时仍会舍入。
混合精度是在同一次训练或推理中,组合使用不同精度。 矩阵乘常采用低精度,归一化、损失计算等数值敏感部分可能保留 FP32。
2. 浮点数的表示
正常的二进制浮点数类似科学计数法:
- 是符号位:0 为正,1 为负。
- 是实际指数,控制数量级;指数位越多,通常范围越大。
- 是小数字段,常称尾数位;位数越多,同一数量级下相邻数的间隔越小。
正常数隐含首位 1,因此有效二进制精度 = 小数字段位数 + 1。指数按“实际指数 + 固定偏置”编码,部分编码留给零、无穷大等特殊情况。
FP32 占 4 字节,位布局为 1 位符号 + 8 位指数 + 23 位小数,有效精度为 24 位。最小正常正数约为 ,最大有限值约为 。
范围决定数值是否装得下,精度决定能保留多少细节。 超出范围可能溢出为无穷大,太小可能舍入为零;落在两个可表示数之间的值也需要舍入。
最小正常正数不是最小正数。次正规数能表示更靠近零的值,但有效精度较低,部分硬件会将其按零处理。
3. BF16 与 FP16
两者都占 16 位:BF16 范围更大,FP16 在共同的正常数范围内更精细。 BF16 是 Brain Floating Point 的简称。
| 比较项 | FP16 | BF16 |
|---|---|---|
| 位宽与存储 | 16 位,2 字节 | 16 位,2 字节 |
| 符号 / 指数 / 小数字段 | 1 / 5 / 10 位 | 1 / 8 / 7 位 |
| 正常数有效二进制精度 | 11 位 | 8 位 |
| 最小正常正数 | 约 | 约 |
| 最大有限值 | 65504 | 约 ,接近 FP32 |
| 内相邻数间隔 | ||
| 数值取舍 | 小数字段多,舍入更细;范围较窄 | 指数位多,较少因范围不足而溢出或下溢;舍入较粗 |
| 训练中的损失缩放 | 常需要,减少小梯度下溢 | 通常不需要,较大的指数范围减少了小梯度下溢 |
| 选择依据 | 硬件、算子对 FP16 支持更好,或更看重有效精度时考虑 | 硬件、算子支持且训练数值范围较大时,常优先考虑 |
按最近值舍入:1.001 在 FP16 中变成 1.0009765625,在 BF16 中变成 1,体现了有效精度差异。
70000 超过 FP16 的最大有限值,会溢出为无穷大;BF16 仍能用有限值近似表示,体现了范围差异。
正常数的指数每增加 1,相邻数间隔也扩大为原来的 2 倍。表中的间隔只针对 ;FP32 在这个区间的间隔为 ,比两种 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 位 |
| 乘积的累加精度 | FP32 | FP32 |
TF32 在数据进入乘法单元时,将输入舍入到 10 位小数字段。原始张量不被改写,但本次乘法用不到被舍入掉的细节。 它保留接近 FP32 的范围,乘法输入的有效精度与 FP16 相当。
矩阵乘的一项为 ,其中 、,均能被 FP32 精确保存。按最近值舍入:
- 完整 FP32:保留输入,乘积为 1.000244140625。
- TF32: 内的间隔为 , 被舍入为 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 个编码,范围为 ;INT4 有 16 个编码,范围为 。
整数量化把浮点值映射为整数编码,并保存换算关系。 常见的线性量化与反量化为:
- 是原始值, 是整数编码, 是恢复的近似值。
- 是缩放系数:相邻编码在原始数值中的间隔。
- 是零点:原始值 0 对应的编码,对称量化通常取 0。
round 舍入为整数,clip 将越界值截到编码上下限 、;两者都可能产生误差。
一组权重位于 ,零点取 0。选取两组能覆盖此范围的示例系数:
- INT8,:0.43 编码为 43,恢复为 。
- INT4,: 舍入为 2,恢复为 ,误差为 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 405B | Meta 分别提供 BF16 和 FP8 混合精度版本 |
GPTQ 是训练后对权重量化、补偿部分误差的方法,不是浮点格式。配置中的 torch_dtype 也不足以描述所有量化张量。
BF16 权重转存 FP32 会增加占用,却不能恢复已舍入掉的信息。 未公开权重或部署细节的服务模型,不能从 API 表现或价格反推内部精度。
8. 存储与运行开销
裸权重字节数 = 参数数量 × 每个参数的位数 ÷ 8。 INT4 按两个参数打包进一个字节计算。
假设模型恰好有 70 亿个参数,全部采用同一种格式,不计任何额外数据。按 字节计算:
权重格式 裸权重大小 FP32 28 GB BF16 / FP16 14 GB FP8 / INT8 7 GB INT4 3.5 GB
裸权重之外,还要计算这些开销:
| 场景 | 额外数据 |
|---|---|
| 模型文件 | 缩放系数、零点、文件元数据,以及未量化部分增加的占用 |
| 推理 | 激活、临时计算空间、KV Cache |
| 训练 | 反向传播所需的激活、临时空间、梯度、优化器状态,以及可能保留的 FP32 主权重 |
- KV Cache 保存历史 token 的 Key、Value,避免重复计算。格式可独立于权重选择,长上下文、高并发会增加其占用。
- 优化器状态 保存更新所需的历史信息,如 Adam 中梯度的一阶、二阶矩估计,其精度不必与权重相同。
- FP32 主权重 用于累积微小更新,低精度计算副本由它转换得到,减少更新被舍入掉的情况;是否保留取决于训练实现。
权重位数减半,不等于总显存或推理耗时减半。
权重读取是瓶颈时,INT8、INT4 可能加速推理;缺少适配内核时,反量化与格式转换可能抵消收益。任务效果、峰值显存和吞吐需要在目标硬件上分别测量。
评论
使用 GitHub 登录后参与讨论,评论将公开保存在 GitHub。
站长管理删除会同步到 GitHub,无法撤销。
阅读到这里时加载评论。