Prhub

#31727 [AMD] Fix DeepSeek-V4 fused-RMS FP8 scale metadata on gfx950

原始 PR 作者 hdt98 合并时间 2026-08-02 15:06 文件变更 4 提交数 2 评论 11 代码增减 +116 / -0

执行摘要

修复 DSV4 fused-RMS FP8 scale 错位,gfx950 精度回归消除

关联 issue #31490 报告 DeepSeek-V4-Flash-FP8 在 dp8ep8 拓扑下 GSM8K 从约 0.925 波段跌至约 0.912,紧贴 0.91 的 CI 门槛,并通过源码 overlay 二分定位到 #29275 的 commit 8d2b66fd。该 commit 为 gfx95 bpreshuffle GEMM 引入 transpose_scale=False 加显式 materialize 的 scale 布局流程,但在 DeepSeek-V4 独立的 fused-RMS FP8 producer 上未兑现布局契约。PR body 说明在 TP4/DP4/EP4 上的排查已排除 Triton/collective 路径,240 个相关 GEMM 全部走 CK,指向 fused-RMS producer 的 scale 元数据错位。

值得精读。核心看点:

1) 布局契约(logical vs physical stride)在跨算子调用链中如何被破坏与修复;
2) producer 端修复优于 consumer 端修复的设计判断,有 #31617 失败案例佐证;
3) torch.as_strided 零拷贝修复技巧与配套的存储别名断言测试。对 AMD 量化和多拓扑部署团队有直接参考价值。

讨论亮点

讨论中的核心交锋与结论:

  • michaelzhang-ai 解释了此前消费端修复失败的根因:误关联的 scale 元数据起源于上游 _fused_rmsnorm_fp8_quant,而非 aiter_w8a8_block_fp8_linear,因此他此前的 #31617(消费端 x_scale.contiguous())无法生效;本 PR 改在 producer 端修复才正确。
  • PR body 标注精确的 TP8/DP8/EP8 MORI 两节点验证待补,michaelzhang-ai 用 MI355X 2N 1P1D disagg 工作流补测,FP8 0.923、FP4 0.928 双 PASS。
  • Lzy17 独立复验覆盖 EP16 2P1D 宽拓扑(4 节点),四个 Flash leg 从约 0.90-0.92 恢复到 0.924-0.933,dsv4pro 无回退。
  • XinyuJiangCMU 从 RL 训练场景给出跨负载证据:train_rollout_logprob_abs_diff 从 0.2143 降至 0.0438,与已知良好基线 0.0445 一致。
  • amd-bot 汇总 CI:本 PR 相关用例(CPU 单测、MI300 真实算子、gfx950 runner)全部通过,其余失败均为基础设施/环境问题。

实现拆解

实现拆解如下:

  1. 定位生产端契约违约。AITER 的 fused_rms_fp8_group_quant 在 transpose_scale=True 时把 scale 写成 CK 就绪的转置物理布局,却返回行主序外观的 [M, G] 元数据。对逻辑 scale [[a,b,c],[d,e,f]],物理存储是 [a,d,b,e,c,f],需要 stride (1, M) 才能正确阅读。
  2. 新增零拷贝修复原语。fp8_utils.py 新增 view_aiter_fused_rms_transposed_fp8_scale,对二维 scale 用 torch.as_strided 把 stride 重解释为 (1, M),不改物理字节;非二维输入原样返回。它与既有 materialize_bpreshuffle_fp8_scale 形成配对:view 负责生产端元数据纠正,materialize 负责消费端物理布局落地。
  3. 在 DeepSeek-V4 生产端注入。deepseek_v4.py 的 _fused_rmsnorm_fp8_quant 在 _use_aiter_bpreshuffle_gfx95 分支对 x_quant[1] 套用修复,保持 (q_input, x_scale) tuple 契约、BF16 旁路输出不变。此 tuple 只注入 DeepSeek-V4 attention 侧 dense GEMM(MLA q/kv/o 投影),Triton 路径、共享 DeepSeekV2/GLM producer、CK materializer 与 communicator 均未改动。
  4. 测试配套。CPU 单测 test/registered/unit/layers/test_fp8_bpreshuffle_scale.py 新增两个用例,用 patch 模拟 AITER 输出,验证值、stride、data_ptr 别名与幂等性;真实算子测试 test/registered/quant/test_fused_rms_fp8_group_quant.py 新增 test_transposed_scale_matches_bpreshuffle_layout_contract,在 GPU 上对比 transpose_scale=True/False 的 AITER 输出,断言修复后与行主序 scale 完全一致且共享同一存储指针,覆盖 M=1 与 M=64、K=1024/4096 的批量化形状。
文件 模块 状态 重要度
python/sglang/srt/layers/quantization/fp8_utils.py 量化层 modified 6.74
python/sglang/srt/models/deepseek_v4.py 模型层 modified 6.98
test/registered/unit/layers/test_fp8_bpreshuffle_scale.py 单元测试 modified 6.26
test/registered/quant/test_fused_rms_fp8_group_quant.py 算子测试 modified 6.23

关键符号

view_aiter_fused_rms_transposed_fp8_scale _fused_rmsnorm_fp8_quant test_repairs_aiter_scale_before_downstream_layout_handling test_deepseek_v4_repairs_fused_rms_scale_at_producer test_transposed_scale_matches_bpreshuffle_layout_contract

关键源码片段

python/sglang/srt/layers/quantization/fp8_utils.py core-logic

新增核心修复原语 view_aiter_fused_rms_transposed_fp8_scale,用 torch.as_strided 零拷贝恢复 AITER 转置 scale 的逻辑 [M, G] 索引。

# fp8_utils.py:零拷贝修复 AITER fused-RMS 转置 scale 的逻辑索引def view_aiter_fused_rms_transposed_fp8_scale(scale: torch.Tensor) -> torch.Tensor:
    # AITER transpose_scale=True 写入 CK 就绪的转置物理存储(列主序),
    # 但返回的元数据看起来是行主序 [M, G]。例如逻辑 scale 为 [[a,b,c],[d,e,f]]
    # 时,物理字节是 [a,d,b,e,c,f],需要 stride (1, M) 才能正确阅读。
    if scale.dim() != 2:
        return scale
    # torch.as_strided 只重解释元数据、不复制字节:
    # 存储指针不变,stride 变为列主序 (1, M),即恢复逻辑索引。
    return torch.as_strided(scale, scale.shape, (1, scale.shape[0]))
python/sglang/srt/models/deepseek_v4.py data-contract

修复真正落地处:_fused_rmsnorm_fp8_quant 在 gfx95 分支对 x_quant[1] 注入 stride 修复,保持 tuple 数据契约。

# deepseek_v4.py:修复必须在 producer 端完成,再喂给 attention 侧 dense GEMMdef _fused_rmsnorm_fp8_quant(hidden_states, weight, eps):
    x_quant, x_bf16, _, _ = fused_rms_fp8_group_quant(
        hidden_states,
        weight,
        eps,
        inp2=None,
        inp2_weight=None,
        inp2_epsilon=None,
        group_size=128,
        dtype_quant=torch.float8_e4m3fn,
        res1=None,
        output_unquantized_inp1=True,
        transpose_scale=_use_aiter_bpreshuffle_gfx95, # gfx95 下 AITER 写转置存储
    )
    if _use_aiter_bpreshuffle_gfx95:
        # 此 tuple 直接注入 MLA q/kv/o 等 dense GEMM,CK materializer 会信任
        # scale 的 stride 元数据;只在消费端修复(如 #31617 的 contiguous)
        # 无效,因为误关联的元数据在此处就已生成。
        x_quant = (
            x_quant[0],
            view_aiter_fused_rms_transposed_fp8_scale(x_quant[1]),
        )
    return x_quant, x_bf16
test/registered/quant/test_fused_rms_fp8_group_quant.py test-coverage

真实 AITER 算子测试:对比 transpose_scale=True/False 的输出,验证修复后与行主序 scale 完全一致且共享存储指针,是布局契约的最强证据。

# test_fused_rms_fp8_group_quant.py:真实 AITER 算子上的布局契约测试def test_transposed_scale_matches_bpreshuffle_layout_contract(self):
    from aiter.ops.triton.fused_fp8_quant import fused_rms_fp8_group_quant
​
    common_kwargs = dict(inp2=None, inp2_weight=None, inp2_epsilon=None,
                         group_size=128, dtype_quant=torch.float8_e4m3fn,
                         res1=None, output_unquantized_inp1=False)
​
    for m, k in ((1, 1024), (64, 1024), (1, 4096), (64, 4096)):
        with self.subTest(m=m, k=k):
            x = torch.randn(m, k, dtype=torch.bfloat16, device="cuda")
            weight = torch.ones(k, dtype=torch.float32, device="cuda")
​
            # 同一输入分别以两种模式量化,作为互相参照的契约基准
            (q_row_major, scale_row_major), *_ = fused_rms_fp8_group_quant(
                x, weight, 1e-6, transpose_scale=False, **common_kwargs)
            (q_transposed, scale_transposed), *_ = fused_rms_fp8_group_quant(
                x, weight, 1e-6, transpose_scale=True, **common_kwargs)
​
            repaired = view_aiter_fused_rms_transposed_fp8_scale(scale_transposed)
            materialized = materialize_bpreshuffle_fp8_scale(repaired)
​
            # 断言:修复后值、stride、物理存储指针(零拷贝)全部符合契约
            torch.testing.assert_close(q_transposed, q_row_major, rtol=0, atol=0)
            torch.testing.assert_close(repaired, scale_row_major, rtol=0, atol=0)
            torch.testing.assert_close(materialized, scale_row_major, rtol=0, atol=0)
            self.assertEqual(repaired.stride(), (1, repaired.shape[0]))
            self.assertEqual(repaired.data_ptr(), scale_transposed.data_ptr())

评论区精华

生产端修复 vs 消费端修复的根因判断 设计

michaelzhang-ai 指出此前 #31617 在 aiter_w8a8_block_fp8_linear 消费端做 x_scale.contiguous() 未生效,因为误关联的 scale 元数据起源于上游 _fused_rmsnorm_fp8_quant;本 PR 改为在 producer 端修复。

结论:确认生产端修复是正确位置,view_aiter_fused_rms_transposed_fp8_scale 在 fused-RMS 输出处纠正 stride。 · 已解决

两节点 TP8/DP8/EP8 验收门 测试

PR body 标注精确的 TP8/DP8/EP8 MORI 两节点验证待补;michaelzhang-ai 在 MI355X 2N 1P1D disagg 工作流上补测。

结论:FP8 0.923、FP4 0.928 双 PASS,回到约 0.925 波段,与 TP4/DP4/EP4 结论一致。 · 已解决

EP16 2P1D 宽拓扑独立复验 测试

Lzy17 在 4 节点 EP16 2P1D 上仅 overlay 两个变更文件独立复验,覆盖了先前验证的盲区。

结论:四个 Flash leg 从约 0.90-0.92 恢复到 0.924-0.933,dsv4pro 无回退。 · 已解决

RL 训练场景的跨负载证据 正确性

XinyuJiangCMU 在四节点 MI355X 的 DeepSeek-V4-Flash FP8 RL 训练中测量 train_rollout_logprob_abs_diff,控制变量只改代码。

结论:step 0 的 abs_diff 从 0.2143 降至 0.0438,与已知良好基线 0.0445 一致。 · 已解决

CI 失败归属与合并判定 other

amd-bot 汇总 CI:本 PR 相关用例(CPU 单测、MI300 真实算子、gfx950 runner)均通过,其余失败为 NVIDIA uv 缓存锁、AMD JIT 编译、XPU 超时等基础设施问题。

结论:判定已合并(1685d29f),无归因于本 PR 的 CI 失败。 · 已解决

风险与影响

  1. 覆盖面:修复只作用于 _use_aiter_bpreshuffle_gfx95 分支的 DeepSeek-V4 fused-RMS producer。若 DeepSeekV2/GLM 等其他模型存在同样的 AITER fused-RMS + CK materializer 组合,仍可能有同类隐患;PR 作者声明共享 producer 路径不受影响,但 issue #31490 曾提示 #29275 波及 shared 路径,此处存在不确定。
  2. 零拷贝视图:torch.as_strided 不复制数据,修复后的 scale 与原 AITER 输出共享存储;若下游未来出现 in-place 写 scale 的逻辑,会反写 AITER buffer。当前所有下游只读,风险可控但需在后续改动中留意。
  3. M=1 等价性:M=1 时 stride (1, M) 与行主序相同,数值与行为完全不变,测试已覆盖。
  4. 精度敏感型回归:修复效果以端到端 GSM8K 实测为准,不同批大小/拓扑下有统计波动;CI 中精确的 TP8/DP8/EP8 两节点场景未自动化,依赖手工验收记录。

影响面集中在 AMD gfx950(MI350X/MI355X)上 DeepSeek-V4-Flash FP8/FP4 的推理与 RL rollout 路径,覆盖 DP-attention、EP16 等拓扑;对 NVIDIA/CPU 与其他模型零影响。性能实测无回退(TPOT 与输出吞吐在噪声范围内)。团队侧收益是让此前在 0.91 门槛上闪烁的两个 dp8ep8 leg 稳定回到约 0.925 波段,消除推理、RL、nightly CI 多团队共同面对的精度隐患。

精度敏感回归修复 gfx95/AITER 分支专属 零拷贝共享存储 仅覆盖 DeepSeek-V4 producer

关联 Issue

#31490 [Bug] GSM8K accuracy regression on DeepSeek-V4-Flash-FP8 (dp8ep8, gfx950) traced to #29275 "Fix gfx95 bpreshuffle FP8 activation scale layout"

完整报告

参与讨论