FP8缩放分母钳位 | AI生成和翻译
在 nanochat 的 FP8 量化代码中,scale = fp8_max / amax.double().clamp(min=EPS) 这行代码里的 .clamp(min=EPS) 是除零保护,而非饱和截断。这个代码片段中出现了两种不同的 clamp,它们的作用相反:
-
amax.double().clamp(min=EPS)—— 对分母取下限。如果一个张量或行全为零(死激活、被掩码的行、零梯度),则amax = 0,fp8_max / 0会得到inf/nan。将分母钳制到极小值EPS(通常如1e-12)可以强制scale为一个很大的有限数而非无穷大。后续x_scaled = 0 * huge_scale = 0,零张量正确量化为零,不会通过矩阵乘法传播 NaN。 -
x_scaled.clamp(-fp8_max, fp8_max)—— 这是对分子进行实际的饱和截断,原因相反:PyTorch 的.to(fp8_dtype)在浮点数超出目标 dtype 的可表示范围时会回绕/溢出而非饱和(注释中明确说明——此 clamp 可防止转换时溢出,因为 PyTorch 默认是回绕而非饱和)。没有第二个 clamp,离群值可能会以错误符号回绕成一个巨大的值,静默地破坏矩阵乘法。
总结:第一个 .clamp(min=EPS) 保护scale 的计算免受全零输入的退化情况;第二个 .clamp(-fp8_max, fp8_max) 保护类型转换免受离群值静默回绕的影响。这两步都是量化代码库(torchao、transformer_engine、bitsandbytes 等均包含对 amax 分母的等效 epsilon 下限)中典型的“在边界处保护数值”操作。
一个值得注意的实现细节:amax.double() —— 在 fp64 中执行倒数 scale 的数学运算,再将 scale 转回 fp32,避免了当 amax 非常小时精度丢失的问题,因为 fp8_max / amax 在 epsilon 下限附近对数值非常敏感。
参考资料:
