
【Bug已解决】FineGrainedFP8 DeepGEMM path returns silently wrong results on SM100 (UE8M0 scale rounding without requantization) 解决方案一、现象长什么样在 B200SM100Blackwell上跑Fine-Grained FP8训练/推理走DeepGEMM深度学习的 FP8 矩阵乘微缩放路径时结果静默地错了——不报错、不崩溃、loss 也能跑但数值与基线fp16/bf16 或 H100 FP8对不上且差得很规律系统性偏移不是随机噪声。典型表现同样的模型在 H100SM90上 FP8 结果正常到 B200SM100上 FineGrainedFP8 路径输出明显偏差精度对比测试失败但没有任何异常抛出细看是 scaling 系数偏小/偏大导致反量化后的激活/权重偏离真实值。最迷惑的是它不报错。这比崩溃更危险——你以为 FP8 在 B200 上正常工作实际 DeepGEMM 的某条微缩放路径在计算 scale 时对 UE8M0 格式的 scale 做了舍入rounding却没做重新量化requantization累积的舍入误差让 scale 不再准确对应实际张量范围反量化后数值系统性错误但算子本身成功执行了。二、背景Fine-Grained FP8 是比标准 FP8 更精细的量化标准 FP8 对整个 tensor 用一个 scaleFine-Grained 把 tensor 切成小块如 per-1x16 或 per-block每块一个 scale大幅降低量化误差适合 B200 的 FP8 矩阵乘单元。B200 的 FP8 scale 格式是UE8M0无符号 8 位、纯指数、无尾数value 2^exponent。它只能表示 2 的整数次幂的 scale。这意味着任何非 2 次幂的 scale 都必须舍入到最近的 2 的幂UE8M0 的表示而这个舍入会引入误差。正确的 UE8M0 scale 流程是计算每个块的原始 scale浮点如 0.3舍入到最近的 2 的幂0.3 → 0.25 或 0.5取最接近→ 得到 UE8M0 编码requantization重新量化用这个舍入后的 UE8M0 scale 作为最终的量化/反量化系数并且确保前向/反向用的都是同一个舍入后的值关键是不能舍入出一个 UE8M0 值却仍用原始浮点 0.3 去做反量化——那样量化与反量化用的 scale 不一致结果系统性错。SM100的 DeepGEMM 路径的 bug 正是scale 被舍入成 UE8M0 后反量化侧仍用了未舍入的原始浮点 scale或反之缺少 requantization 把舍入后的 scale固化成两端统一的系数于是量化用 A scale、反量化用 B scale不对称 → 输出静默错。下面用可运行代码复现scale 舍入后未 requantize导致量化/反量化不对称输出偏差。三、根因根因一句话B200SM100的 FineGrainedFP8 DeepGEMM 路径在计算 UE8M0 scale 时做了舍入rounding 到 2 的幂但没有 requantization把舍入后的值固化为量化与反量化统一的系数导致量化侧与反量化侧用的 scale 不一致输出系统性偏差且静默无报错。三个具体失配UE8M0 舍入未固化scale 舍入成 2 的幂后没作为两端统一系数。量化/反量化 scale 不对称量化用原始浮点 scale反量化用舍入后 scale或反之。静默错误算子执行成功仅数值错无任何异常。四、最小可运行复现用纯 Python 模拟UE8M0 scale 舍入后未 requantize导致量化/反量化 scale 不一致输出偏差import math def round_to_ue8m0(x: float) - float: 把 scale 舍入到最近的 2 的幂UE8M0 表示。 if x 0: return 1.0 exp round(math.log2(x)) return 2.0 ** max(0, min(255, exp)) def quantize_dequantize(value, scale_quant, scale_dequant): 模拟量化除 scale反量化乘 scale。两侧 scale 必须一致。 q value / scale_quant return q * scale_dequant def main(): true_scale 0.3 rounded round_to_ue8m0(true_scale) # 0.3 - 0.25 # 错误量化用原始 0.3反量化用舍入后 0.25缺 requantization bad quantize_dequantize(1.0, true_scale, rounded) # 正确量化与反量化都用舍入后的 0.25requantization 固化 good quantize_dequantize(1.0, rounded, rounded) print(ftrue_scale{true_scale}, ue8m0{rounded}) print(f错误不对称 scale结果: {bad:.4f} (应为 ~1.0 但有偏差)) print(f正确requantize 后结果: {good:.4f}) if __name__ __main__: main()运行会显示错误路径结果偏离 1.0因为量化用 0.3、反量化用 0.25不对称正确路径用统一的 0.25 得到预期值——正是scale 舍入未 requantize 导致静默错误的本质。五、解决方案第一层最小直接修复最立竿见影的修复对 UE8M0 scale 做舍入后必须 requantization——把舍入后的值同时用于量化与反量化两侧确保两端 scale 完全一致。即先算原始 scale舍入成 UE8M0然后用这个 UE8M0 值作为唯一的量化/反量化系数。import math import torch def requantize_scale(raw_scale: torch.Tensor) - torch.Tensor: 修复舍入到 UE8M0 后固化成两端统一系数requantization。 # 舍入到最近的 2 的幂UE8M0 exp torch.round(torch.log2(raw_scale.clamp_min(1e-6))) exp exp.clamp(0, 255) ue8m0 2.0 ** exp # requantization返回这个舍入后的值量化与反量化都用它 return ue8m0 def main(): raw torch.tensor([0.3, 0.7, 0.15]) s requantize_scale(raw) print(UE8M0 scale固化:, s.tolist()) # 量化: x / s ; 反量化: y * s - 两侧都用 s对称 if __name__ __main__: main()第一层修复让量化/反量化用同一个舍入后的 UE8M0 scale消除不对称导致的静默错误。六、解决方案第二层结构性改进把FP8 scale 的舍入 requantization收口成一个Fp8ScaleQuantizer保证任何 scale 在舍入成 UE8M0 后都被固化并对称使用且对 SM100 与非 SM100 路径统一避免 DeepGEMM 路径漏掉 requantization。import torch from dataclasses import dataclass dataclass class Fp8ScaleQuantizer: def ue8m0(self, raw: torch.Tensor) - torch.Tensor: exp torch.round(torch.log2(raw.clamp_min(1e-6))).clamp(0, 255) return 2.0 ** exp def quantize(self, x: torch.Tensor, raw_scale: torch.Tensor): s self.ue8m0(raw_scale) # 舍入 固化requantization return (x / s).to(torch.float8_e4m3fn) # FP8 量化用 s def dequantize(self, x_fp8: torch.Tensor, raw_scale: torch.Tensor): s self.ue8m0(raw_scale) # 同一 s对称 return x_fp8.float() * s def main(): q Fp8ScaleQuantizer() x torch.tensor([1.0, 2.0, 0.5]) s_raw torch.tensor([0.3]) xq q.quantize(x, s_raw) xr q.dequantize(xq, s_raw) print(反量化结果:, xr.tolist(), (应与 x 接近)) if __name__ __main__: main()第二层的关键是Fp8ScaleQuantizer把舍入成 UE8M0与量化/反量化对称使用同一 scale绑死DeepGEMM 路径只要走它就不会再漏 requantization。七、解决方案第三层断言 / CI 守护加 pytest 守护(1) 量化与反量化必须用同一个 requantized scale对称(2) UE8M0 scale 必须是 2 的整数次幂(3) 往返误差在 FP8 正常范围内非系统性偏差。import torch import pytest def ue8m0(raw): exp torch.round(torch.log2(raw.clamp_min(1e-6))).clamp(0, 255) return 2.0 ** exp def roundtrip(x, raw_scale): s ue8m0(raw_scale) q (x / s).to(torch.float8_e4m3fn) return q.float() * s def test_ue8m0_is_power_of_two(): s ue8m0(torch.tensor([0.3])) assert torch.log2(s).round().item() torch.log2(s).item() def test_symmetric_scale(): # 量化与反量化用同一 s结果应接近原始FP8 误差内 x torch.tensor([1.0, 2.0]) raw torch.tensor([0.3]) s ue8m0(raw) q (x / s).to(torch.float8_e4m3fn) out q.float() * s assert torch.allclose(out, x, atol0.2) # 非系统性大幅偏移 def test_no_silent_drift(): # 若量化用 raw、反量化用 ue8m0不对称会偏 - 这里验证对称路径正确 x torch.tensor([1.0]) raw torch.tensor([0.3]) out roundtrip(x, raw) assert abs(out.item() - 1.0) 0.25 if __name__ __main__: pytest.main([__file__, -q])CI 里test_symmetric_scale通过就能保证量化/反量化 scale 对称杜绝 SM100 上 FineGrainedFP8 的静默错误回归。八、排查清单FineGrainedFP8 DeepGEMM 在 SM100 静默错误时按此顺序查确认是否静默错误无报错但数值对不上基线基本锁定 scale/量化问题。检查 UE8M0 scale 流程B200 用 UE8M0确认 scale 舍入后是否 requantize。量化与反量化 scale 是否对称这是核心——两侧必须用同一个舍入后 scale。对比 H100H100(SM90) 用 fp32/e4m3 scaleB200(SM100) 用 UE8M0差异在 scale 格式。用 Fp8ScaleQuantizer 兜底统一舍入对称 requantizationDeepGEMM 路径走它。加数值回归测试固定输入跑 DeepGEMM对比 fp16 基线偏差超阈值即失败。升级 transformers/torchao较新版本可能已修 SM100 的 requantization。九、小结FineGrainedFP8 的 DeepGEMM 路径在 B200SM100静默返回错误结果根因不在算子崩溃而在B200 的 FP8 scale 格式是 UE8M0只能表示 2 的幂scale 计算时舍入到 2 的幂后DeepGEMM 路径缺少 requantization——没把舍入后的值固化为量化与反量化两侧统一的系数导致两侧 scale 不对称反量化后数值系统性偏差且算子成功执行不报错。修复三层第一层对 UE8M0 scale 舍入后做 requantization量化与反量化都用同一个舍入后 scale第二层用Fp8ScaleQuantizer把舍入对称绑死DeepGEMM 路径统一走它第三层用 pytest 断言UE8M0 是 2 的幂、量化/反量化对称、无系统性漂移。记住B200 的 FP8 scale 是 UE8M0舍入后必须 requantize量化反量化用同一 scale否则静默错。