Prhub

#33166 [AMD] DeepSeek-V4 MI355X: eliminate bpreshuffle fp8-scale copies at producer sites (MoE down, MLA o_proj bmm)

原始 PR 作者 karverma-amd 合并时间 2026-08-21 12:46 文件变更 5 提交数 11 评论 10 代码增减 +292 / -10

执行摘要

MI355X 上 DSV4 fp8 scale 逐层拷贝改零拷贝视图

PR body 明确说明:若干 DeepSeek-V4 站点会预量化激活值并把 (fp8, scale) 元组交给下游 Linear;这些 scale 以行主序发射,随后用 materialize_bpreshuffle_fp8_scale 重新布局——在 MI355X(gfx950)上是每站点、每层一次的重排拷贝。本 PR 的目标是消除这些拷贝:让支持 transpose_scale 的 AITER 量化 kernel 直接发射 bpreshuffle 所需布局,再用 torch.as_strided 零拷贝视图替代。同时由于 fused_rms_fp8_group_quant 曾有不 honor transpose_scale 的前科(#31727 已修复),PR 用 producer 级 bit-exact 测试作为正确性保证,而不只依赖 GSM8K 数值。

值得精读。它展示了硬件专用性能优化如何在保持正确性的前提下安全落地:一是 zero-copy stride 重解释的布局契约设计(as_strided 交换 stride 恢复逻辑索引),二是 M>=2 发射 gate 对单 token 退化场景的处理,三是 review 中"测试必须命中真实 producer kernel 而非伪造布局"的验证方法论。对于后续在 gfx95 上扩展更多预量化站点或复用 fp8_utils helper 的工程师,本 PR 是很好的范本。

讨论亮点

Review 的核心交锋集中在"零拷贝布局优化必须证明真实内核等价"这一评审标准上:

  1. kkHuang-amd 首轮指出 CPU 测试用 _simulate_transpose_scale_emit 构造的是"伪造布局",无法发现真实 producer 忽略或错误实现 transpose_scale(并提醒 fused_rms_fp8_group_quant 曾有此缺陷),要求补真实 producer 测试;作者新增 test_fp8_bpreshuffle_producer_mi35x.py 直接驱动 fused_clamp_act_mul 与 fused_flatten_fp8_group_quant。
  2. 关于 helper 命名,作者原计划引入新的 producer 中立名称,后按 review 合并到 main 上 #31727 已有的 view_aiter_fused_rms_transposed_fp8_scale,并补充 _tuple 变体,删除重复的 bpreshuffle_fp8_scale_nocopy。
  3. kkHuang-amd 提出两个 blocker:ROCm MLA 已 dispatch 到 forward_mla_rocm.py,改 forward_mla.py 不再影响 MI355X 路径;以及 M == 1 时 contiguous [1, G] tensor 的 stride 断言不正确。作者分别以迁移优化到 rocm_absorb_v_bmm 与修正 M == 1 断言((G, 1) stride、materialize no-op 语义)回应。
  4. 合并前 reviewer 要求 rebase 到最新 main 并重跑 CPU 与 MI35X 测试;1am9trash 确认 base-a-test-cpu 与 stage-b-test-1-gpu-small-amd-mi35x-rocm720 通过,并注明 MI300 的 test_fp32_lm_head.py 与 Qwen disaggregation 失败与本 PR 无关。

实现拆解

  1. 收敛 producer 中立的零拷贝契约:在 python/sglang/srt/layers/quantization/fp8_utils.py 中,将 main 上 #31727 引入的 view_aiter_fused_rms_transposed_fp8_scale 的文档契约升级为 producer 中立(描述 transpose_scale=True 的物理列主字节序与 stride 交换语义),并新增 view_aiter_fused_rms_transposed_fp8_scale_tuple(仅重解释 (q_input, x_scale, ...) 元组的 scale 槽位,其余按 identity 透传)与 emit_transposed_bpreshuffle_scale(m, on_bpreshuffle_gfx95=...)(统一 M>=2 且 gfx95 的发射 gate)。这为所有 producer site 提供了单一决策与单一视图入口。
  2. MoE down-proj 输入 producer 改造:在 python/sglang/srt/models/deepseek_v2.py 的 MoE forward 中,fused_clamp_act_mul 调用点先通过 emit_transposed_bpreshuffle_scale 计算 _emit_bpre,以其作为 transpose_scale 参数;为真时用 view_aiter_fused_rms_transposed_fp8_scale(x_scale) 零拷贝替换原 materialize_bpreshuffle_fp8_scale(x_scale),为假时维持 materialize 回退,消除每层 MoE 输入 scale 的重排拷贝。
  3. MLA o_proj bmm producer 改造:在 forward_mla_rocm.py 的 rocm_absorb_v_bmm 中对两处 fused_flatten_fp8_group_quant 调用点做同等改造(transpose_scale=_emit_bpre + view_aiter_fused_rms_transposed_fp8_scale_tuple / materialize_bpreshuffle_fp8_scale_tuple 二选一)。此改动经历了路径迁移:上游将 ROCm MLA dispatch 拆分到 forward_mla_rocm.py 后,原在 forward_mla.py 的改动不再影响 MI355X 调用路径,作者将其搬到实际路径并确认 forward_mla.py 与 upstream 一致。
  4. 测试与验证配套:CPU 侧在 test_fp8_bpreshuffle_scale.py 新增 producer no-copy 单测(非 2D scale 直通、tuple 助手仅重解释 scale 槽位)与 TestEmitTransposedBpreshuffleScaleGate(M>=2 边界、非 gfx95 恒 false);GPU 侧新增 test_fp8_bpreshuffle_producer_mi35x.py,在真实 gfx95 内核上驱动两个真实 producer,断言两条路径量化输出 bit 一致、scale 值相等、(1, M) 列主 stride 与零拷贝存储共享,覆盖 M∈{1,2,8,16}。PR 还给出 GSM8K 双次运行与 TPOT 初步数据,但性能提升在噪声范围内,作者未将其作为确认收益。
文件 模块 状态 重要度
python/sglang/srt/layers/quantization/fp8_utils.py 量化工具 modified 7.12
python/sglang/srt/models/deepseek_common/attention_forward_methods/forward_mla_rocm.py MLA 前向 modified 6.5
python/sglang/srt/models/deepseek_v2.py MoE 层 modified 5.75
test/registered/unit/layers/test_fp8_bpreshuffle_producer_mi35x.py 测试 added 7.31
test/registered/unit/layers/test_fp8_bpreshuffle_scale.py 测试 modified 6.35

关键符号

view_aiter_fused_rms_transposed_fp8_scale view_aiter_fused_rms_transposed_fp8_scale_tuple emit_transposed_bpreshuffle_scale rocm_absorb_v_bmm test_fused_clamp_act_mul_producer_paths_equivalent test_fused_flatten_fp8_group_quant_producer_paths_equivalent

关键源码片段

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

核心变更文件:新增 producer 中立的零拷贝 scale 视图 helper(含 tuple 变体)与统一的 M>=2 转置发射 gate,是所有 producer site 收敛的公共入口。

def view_aiter_fused_rms_transposed_fp8_scale(scale: torch.Tensor) -> torch.Tensor:
    """Zero-copy view of a ``transpose_scale=True`` fp8 group scale.    与 ``materialize_bpreshuffle_fp8_scale`` 对应的零拷贝路径:当 AITER 量化
    kernel 以 ``transpose_scale=True`` 发射 scale 时,物理字节序已是
    ``[num_groups, tokens]`` 列主布局,只是被包装成了 ``[tokens, num_groups]``
    行主视图;交换 stride 即可恢复逻辑 ``[M, G]`` 索引,全程不复制。
    """
    if scale.dim() != 2:
        return scale # 非 2D scale(如 per-tensor)不做重解释,直接透传
    # as_strided 用 (1, M) stride 覆盖 [M, G] 形状:第 0 维步长 1、第 1 维步长
    # M,正好把物理列主字节映射回逻辑行主索引,即 bpreshuffle GEMM 的消费布局。
    return torch.as_strided(scale, scale.shape, (1, scale.shape[0]))
​
​
def view_aiter_fused_rms_transposed_fp8_scale_tuple(
    value: Tuple[torch.Tensor, ...],
) -> Tuple[torch.Tensor, ...]:
    """零拷贝重解释 FP8 ``(q_input, x_scale, ...)`` 元组中的 scale 槽位。"""
    # 仅处理第 1 个槽位(scale),其余元素按原对象透传,保持引用语义不变。
    return (value[0], view_aiter_fused_rms_transposed_fp8_scale(value[1]), *value[2:])
​
​
def emit_transposed_bpreshuffle_scale(m: int, *, on_bpreshuffle_gfx95: bool) -> bool:
    """统一决策 producer 是否直接发射转置(列主)布局的 fp8 scale。    返回 True 时 producer 以 ``transpose_scale=True`` 发射并配合上面的零拷贝
    视图;返回 False 时保持行主发射 + ``materialize`` 拷贝路径。仅在 gfx95
    bpreshuffle 且 M >= 2 时走零拷贝:M == 1 时 ``[1, G]`` 与 ``[G, 1]`` 字节序
    重合,转置发射没有收益,统一回退 materialize。
    """
    return on_bpreshuffle_gfx95 and m >= 2
python/sglang/srt/models/deepseek_common/attention_forward_methods/forward_mla_rocm.py data-contract

ROCm MLA 的实际 MI355X 调用路径:rocm_absorb_v_bmm 内两处 fused_flatten_fp8_group_quant o_proj producer 应用零拷贝 scale 路径,是 review 中发起路径迁移修正的关键文件。

        elif _is_block_scale_fp8(attn.o_proj):
            # 统一 gate:gfx95 bpreshuffle 且 M >= 2 时让 producer 直接发射转置
            # scale,否则保持行主 + materialize 拷贝(M == 1 或非 gfx95 回退)。
            _emit_bpre = emit_transposed_bpreshuffle_scale(
                _bmm_buf.shape[0], # token 数 M
                on_bpreshuffle_gfx95=_use_aiter_bpreshuffle_gfx95,
            )
            attn_bmm_output = fused_flatten_fp8_group_quant(
                _bmm_buf,
                group_size=128,
                dtype_quant=torch.float8_e4m3fn,
                transpose_scale=_emit_bpre,
            )
            if _emit_bpre:
                # 零拷贝路径:对元组的 scale 槽位做 stride 交换,不复制字节。
                attn_bmm_output = view_aiter_fused_rms_transposed_fp8_scale_tuple(
                    attn_bmm_output
                )
            elif _use_aiter_bpreshuffle_gfx95:
                # 行主发射的回退:仍需要 relayout 才能被 bpreshuffle GEMM 消费,
                # 但只在 gfx95 上才需要这一额外拷贝步骤。
                attn_bmm_output = materialize_bpreshuffle_fp8_scale_tuple(
                    attn_bmm_output
                )
test/registered/unit/layers/test_fp8_bpreshuffle_producer_mi35x.py test-coverage

新增的 MI35X GPU 测试:直接调用两个真实 AITER producer(fused_clamp_act_mul、fused_flatten_fp8_group_quant),断言 transpose_scale=True 零拷贝路径与 transpose_scale=False 物化路径 bit 等价,是 PR 正确性论证的核心证据。

    @classmethod
    def setUpClass(cls):
        # 设计要点:测试直接导入真实 AITER producer,而不是用模拟布局,
        # 这样才能发现内核忽略或误实现 transpose_scale 的回归。
        try:
            from aiter import dtypes # noqa: F401
            from aiter.ops.triton.fused_fp8_quant import ( # noqa: F401
                fused_flatten_fp8_group_quant,
            )
            from aiter.ops.triton.fusions.fused_clamp_act_mul import ( # noqa: F401
                fused_clamp_act_mul,
            )
        except Exception as err: # 环境缺 aiter 时跳过,避免在非 gfx95 上误报
            raise unittest.SkipTest(f"aiter producers unavailable: {err}")
        cls.device = "cuda" # torch 的 cuda 设备在 ROCm 上映射为 HIP 设备
​
    def setUp(self):
        torch.manual_seed(0) # 固定种子,保证两条路径输入完全一致
​
    def _run_fused_clamp_act_mul(self, m, transpose_scale):
        # MoE down-proj 输入 producer:gate_up 先做 SiLU 门控激活再 fp8 量化。
        inter = 4 * _GROUP_SIZE # 中间维度 = 4 组,G = 4
        gate_up = torch.randn(m, 2 * inter, device=self.device, dtype=torch.bfloat16)
        q, scale = fused_clamp_act_mul(
            gate_up,
            swiglu_limit=7.0,
            activation="silu",
            dtype_quant=dtypes.fp8,
            transpose_scale=transpose_scale,
        )
        return q, scale, gate_up
​
    def _run_fused_flatten_fp8_group_quant(self, m, transpose_scale):
        # MLA o_proj 的 producer:bmm 缓冲先量化为 fp8 再交给下游 GEMM。
        heads, dim = 8, _GROUP_SIZE # heads * dim = 1024 -> G = 8 组
        buf = torch.randn(m, heads, dim, device=self.device, dtype=torch.bfloat16)
        out = fused_flatten_fp8_group_quant(
            buf,
            group_size=_GROUP_SIZE,
            dtype_quant=torch.float8_e4m3fn,
            transpose_scale=transpose_scale,
        )
        return out[0], out[1], buf

评论区精华

测试未覆盖真实 producer kernel 测试

kkHuang-amd 指出 _simulate_transpose_scale_emit() 只构造了 helper 期望的存储布局,无法发现真实 producer 忽略或错误实现 transpose_scale(并提醒 fused_rms_fp8_group_quant 曾有此缺陷)。

结论:作者新增 test_fp8_bpreshuffle_producer_mi35x.py,在真实 gfx95 内核上验证 transpose_scale=True + 零拷贝 与 transpose_scale=False + materialize 两条路径 bit 等价。 · 已解决

ROCm MLA dispatch 路径迁移 正确性

kkHuang-amd 指出 rebase 后 ROCm MLA 已 dispatch 到 forward_mla_rocm.py,而 PR 修改的 forward_mla.py 不再影响 MI355X 路径,属于 blocker。

结论:作者将 o_proj producer 优化移到 forward_mla_rocm.py 的 rocm_absorb_v_bmm,并确认 forward_mla.py 与 upstream 一致。 · 已解决

M == 1 stride 断言正确性 正确性

kkHuang-amd 指出新的 M == 1 stride 断言不正确:contiguous [1, G] tensor 的行主与列主字节序一致,materialize 应保留自然 (G, 1) stride 而非 (1, M)。

结论:作者修正断言,明确 M == 1 走 materialize fallback 且保持 (G, 1) stride、共享存储。 · 已解决

helper 命名与合并 设计

作者原计划引入新的 producer 中立名称 bpreshuffle_fp8_scale_nocopy,review 后改为合并到 main 上 #31727 已有的 view_aiter_fused_rms_transposed_fp8_scale,并补充 _tuple 变体。

结论:统一为单一 helper,三个 producer 站点(fused-RMS、MoE down、MLA o_proj)共用同一 API。 · 已解决

合并前 rebase 与 CI 刷新 question

reviewer 要求在合并前 rebase 到最新 main 并重跑 CPU 与 MI35X 测试;因 120 分钟 trigger cooldown 部分 suite 被跳过。

结论:作者刷新分支,1am9trash 确认 base-a-test-cpu 与 stage-b-test-1-gpu-small-amd-mi35x-rocm720 通过后合并。 · 已解决

风险与影响

正确性风险:整个优化依赖 AITER producer 真正实现 transpose_scale=True 的"物理列主字节序"语义,任何实现偏差都会让零拷贝路径静默产出错误布局;测试仅覆盖当前 aiter 版本与 gfx95,跨版本内核行为变化无法由本 PR 保证。M==1 特判风险:emit_transposed_bpreshuffle_scale 保证生产路径在单 token 时走 materialize((G, 1) stride 的共享存储 no-op),但直接调用 helper 且 m==1 会得到 (1, M) stride 视图,契约文档已声明仅 M>=2 有效。性能声明强度:TPOT 数据为单次运行(Δ ≈ ±0.4–0.8%),在 decode 噪声范围内,不能作为确认的加速结论。回归面:仅影响 gfx95 + _use_aiter_bpreshuffle_gfx95 的 DeepSeek-V4 路径,NV 与其它 ROCm 卡不受影响;但 forward_mla_rocm.py 近期被上游独立改动且本分支有多次 merge 历史,后续冲突风险略高。

对用户:MI355X 上 DeepSeek-V4 每层少一次 scale relayout 拷贝,decode 延迟有轻微改善可能;GSM8K 显示 patched 低约 0.8–0.9 pt,作者归因于 fp4-MoE 噪声,与 producer 级 bit-exact 测试结论一致。对系统:fp8_utils.py 成为 bpreshuffle scale 布局的唯一仲裁点,后续新增 producer 只需遵循 emit_transposed_bpreshuffle_scale 与视图 helper。对团队:确立了"布局优化必须以真实内核等价测试验证"的评审标准,该标准在 #33165 与 #33166 之间互相印证,并推动 ROCm MLA 优化落在实际 dispatch 路径 forward_mla_rocm.py 上。

依赖 aiter 内核 transpose_scale 契约 M==1 特判走拷贝回退 性能收益仅单次采样未确认 仅 gfx95 AMD 路径生效 多次 rebase/merge 历史复杂

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论