FP32、FP16、BF16 与混合精度
表示范围和精度是两个问题
| 格式 | 符号位 | 指数位 | 显式 fraction 位 | 最大有限正数(约) | 最小正规正数(约) |
|---|---|---|---|---|---|
| FP32 | 1 | 8 | 23 | 3.4028×10^38 | 1.1755×10^-38 |
| FP16 | 1 | 5 | 10 | 65504 | 6.1035×10^-5 |
| BF16 | 1 | 8 | 7 | 3.3895×10^38 | 1.1755×10^-38 |
表中列的是正规数边界,subnormal 另有更小数值,实际执行还可能受 flush-to-zero 等行为影响。BF16 与 FP32 指数位宽相同,范围相近,但最大有限值并非完全相等;BF16 的有效精度明显更低。
对于二进制正规数,表示可写为符号 × (1+fraction) × 2^(e-bias)。在区间 [1,2) 内,相邻可表示值的距离为 2^-p,其中 p 是显式 fraction 位数:
例:1.001 四舍五入到邻近表示值,FP16 可得到约 1.0009765625,BF16 则得到 1.0。反过来,70000 超出 FP16 的有限范围,却在 BF16 的范围内。两例分别展示 fraction 和指数位的作用。
为什么 BF16 常用于训练
梯度、激活与中间值可能跨越很大动态范围。BF16 的较宽指数范围降低了相对 FP16 的溢出/下溢压力,但不消除舍入误差,也不保证所有模型都能稳定训练。原文“训练对精度容忍高、对溢出容忍低”是有用的经验动机,不能推导成“任意训练均应使用 BF16”。
FP16 训练常结合 loss scaling:放大 loss 后梯度随之放大,在更新前再除去尺度。例:某梯度为 10^-8,乘以 1024 后为 1.024×10^-5,可能避开部分下溢问题;但放大过度又会引入溢出,因此需要检查非有限值和调整尺度。BF16 通常较少依赖这一手段,但仍需依据训练实现判断。
混合精度还需要区分四层:输入/权重存储格式、乘法使用的格式、累加格式、优化器状态或主权重格式。低精度矩阵输入常配合 FP32 累加,但这不是所有指令和配置的统一承诺。例:低精度里较小的增量逐次加到 1 上可能每步都被舍掉;先在更高精度累加再转换,可以减少这种反复舍入。
为什么不能只凭格式判断谁快
FP16 与 BF16 都占 2 字节,纯搬运同样数量元素时字节数相同;相对 FP32 都能降低存储流量。对应 Tensor Core 指令吞吐、支持的 shape、数据转换和 accumulation 配置要看架构及实现,不能笼统说 BF16 比 FP16 更快。
例:搬运 1024 个元素,FP32 为 4096 字节,FP16/BF16 为 2048 字节。但若 kernel 时间由 launch 或不适配的 shape 主导,流量减半并不等于端到端时间减半。性能结论需给出 GPU、dtype 路径、矩阵形状和测量。
参考:NVIDIA Floating Point Guide;CUDA Math API 的 BF16 类型与转换。本篇是格式基础,不是具体训练框架的精度配置说明。