先记住一句话

精度优化的正确问题不是“能不能用 FP16”,而是每一段计算需要多大的动态范围、多少有效位、什么累加精度,以及误差是否通过端到端任务验证。

定位敏感 ops选择 storage/compute/accum dtype加 scaling/stable formula校准量化 scalelayerwise 对比end-task 验证
StorageFP16/BF16/INT8
load/dequant
Computemultiply dtype
Accumulateoften FP32
scale/cast
Outputvalidated error
权重存储、乘法、累加和通信可以是不同精度;“模型 dtype”不是单一事实。

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 的四个位置

  1. storage dtype:权重、激活、KV cache 如何放在显存;
  2. compute input:乘法器实际接收什么格式;
  3. accumulator:大量乘加通常在 FP32 或更高精度中累积;
  4. 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列出精度和平台支持边界。