先记住一句话
精度优化的正确问题不是“能不能用 FP16”,而是每一段计算需要多大的动态范围、多少有效位、什么累加精度,以及误差是否通过端到端任务验证。
1. 浮点数在存什么
x = (-1)s × significand × 2exponent指数位决定可表示的数量级,尾数位决定相邻数的间距。FP16 尾数较细但范围较窄;BF16 继承 FP32 的指数范围、尾数更粗;TF32 是 NVIDIA Tensor Core 的 FP32 输入计算路径;FP8 再用范围换吞吐和显存。格式名字不能单独推出准确率。
| 格式 | 典型优势 | 主要风险 |
|---|---|---|
| FP64 | 科学计算的精度与范围 | AI GPU 上吞吐/带宽成本高 |
| FP32 / TF32 | 稳定 baseline;TF32 可加速矩阵乘 | 显存大;TF32 乘法有效位较少 |
| FP16 | 成熟 Tensor Core、高吞吐 | 容易 overflow/underflow |
| BF16 | 范围大,训练更省心 | 尾数短,小差值会丢失 |
| FP8 / INT8 | 更低带宽和更高矩阵吞吐 | 强依赖 scale、校准与算子支持 |
2. Mixed precision 的四个位置
- storage dtype:权重、激活、KV cache 如何放在显存;
- compute input:乘法器实际接收什么格式;
- accumulator:大量乘加通常在 FP32 或更高精度中累积;
- output cast:写回前是否 scale、round、saturate。
因此“FP16 GEMM”常是 FP16 inputs + FP32 accumulation + FP16 output。优化时必须把四者写清楚。
3. 训练中的 loss scaling
FP16 小梯度会 underflow 成 0。loss scaling 先放大 loss/gradient,反向后再除回;dynamic scaling 遇到 inf/NaN 会降低 scale,稳定后提高。它解决表示范围,不修复不稳定 loss、错误学习率或爆炸梯度。
4. 稳定公式往往比更多位更重要
log Σ exp(xᵢ) = m + log Σ exp(xᵢ - m), m = max(x)softmax/log-sum-exp 先减最大值可避免指数溢出。variance 避免两个大数相减;norm 加合适 epsilon;长归约用 pairwise/tree reduction,极敏感求和可用 compensated summation。并行归约改变加法顺序,所以浮点结果通常不 bitwise deterministic。
5. 量化的核心是 scale
x_real ≈ scale × (q - zero_point)per-tensor 最便宜但一个 outlier 会浪费全张量动态范围;per-channel/per-block 更贴合局部分布但增加 scale metadata 与 kernel 复杂度。对称量化简化计算;非对称量化能表达偏移。weight-only 主要省权重带宽,activation quantization 才能进一步改变更多计算路径。
6. PTQ、QAT 与显式 Q/DQ
- PTQ:训练后用代表性校准数据定 scale,成本低;
- QAT:训练时模拟量化误差,通常更能保住质量;
- Q/DQ graph:Quantize/Dequantize 节点显式表达哪些张量被量化、scale 在哪里,部署器更少猜测。
校准集必须覆盖真实长尾、模态和序列长度;只看一小批平均样本很容易掩盖 outlier failure。
7. 验证不是只跑 allclose
逐层比较 max/mean error、cosine similarity、饱和比例和 NaN/inf;端到端比较 loss/perplexity、成功率或任务指标;再测 latency、吞吐、峰值显存与能耗。容差要按 dtype、tensor 规模和归约长度设定,不能为了让测试通过而任意放宽。
8. 四个数值稳定性手算
例 1:stable softmax
Logits [1000,999] 直接 exp 会 overflow;减 max 后 [0,−1],softmax=[1,e⁻¹]/(1+e⁻¹)≈[0.731,0.269],数学结果不变。
例 2:loss scaling
Gradient=3×10−8,scale=32768 后 backward 值≈9.83×10⁻⁴,避免低精度 underflow;optimizer 前除 scale 恢复 3×10⁻⁸。
例 3:symmetric INT8 scale
Tensor max absolute=2.54,INT8 qmax=127,scale=2.54/127=0.02。Value 1.13 quantize 为 round(56.5)=57,dequant=1.14,error=0.01。
例 4:累加误差
1000 个值各 0.001,真和=1。若低精度在大 partial sum 附近的间隔超过 0.001,后续加法可能不再改变结果;FP32 accumulation 保留更多有效增量。
数值不同不一定是 bug,数值接近也不保证行为正确。分类边界、closed-loop policy 和 autoregressive sampling 都可能放大小误差;最终判断必须回到任务分布。
自测
1. BF16 为什么常比 FP16 更适合训练?
它保留与 FP32 相近的指数范围,更不容易 overflow/underflow;代价是尾数更短。
2. weight-only INT8 为什么未必让所有模型快 2 倍?
还存在解量化、activation、非矩阵算子与 launch 成本;若 workload compute-bound 且 kernel 不匹配,收益会更小。
3. 为什么 scale 粒度会影响准确率?
粒度越细,局部数值越能占满量化范围;但 metadata、访存和 kernel 实现成本更高。
官方资料
NVIDIA TensorRT 的 Quantized Types and Schemes定义量化表示、Q/DQ 与支持粒度;Capabilities列出精度和平台支持边界。