Prhub

#34844 [Spec] Support MegaMoE for DSpark under dp attention

原始 PR 作者 weireweire 合并时间 2026-08-15 08:20 文件变更 2 提交数 1 评论 1 代码增减 +46 / -3

执行摘要

DSpark 在 DP attention 下支持 MegaMoE 后端

PR body 明确指出:_handle_dspark--enable-dp-attention 开启时拒绝了所有非 none--moe-a2a-backend,导致 DSpark 无法与 EP all-to-all 分发(如 MegaMoE)同时使用。作者论证该限制已过时:DSV4-MoE draft 路径已在 _fill_dp_moe_sync_metadata 中 all-gather global_num_tokens,且无本地生成请求的 rank 仍通过 run_idle_participation 进入 draft forward,因此每个 EP rank 每步调用 MoE 的次数一致,MegaMoE 的 fused kernel 已能容纳零 token rank,lockstep 前提已满足。

值得精读,但重点不在代码量,而在于它展示了一个典型的「先论证 lockstep 前提、再放开参数限制」的工程范式:用 _fill_dp_moe_sync_metadata 的 all-gather 与 run_idle_participation 的参与机制证明 EP 下每 rank MoE 调用次数一致,再以 SGLANG_RAGGED_VERIFY_MODE=static 兜底图 tier 一致性。PR body 附带的正确性/性能双验证也值得作为后续同类 gate 变更的参考模板。

讨论亮点

PR 无 review 评论,唯一 Discussion 是作者触发的 /tag-and-rerun-ci(恰好对应 Extra CI 失败后的重跑操作),reviewer hnyls2002 直接 APPROVED。PR body 中作者以数据自证了关键设计判断:gsm8k 400 题两臂均为 0.9775,8x GB300 下 640/640 请求成功、无 CUDA/NCCL/barrier 错误,并指出 target-verify 与 draft-verify CUDA graph 在 8 个 EP rank 上完全一致,以排除 phase-flip barrier 反同步风险。

实现拆解

实现共两步:

  1. 放宽 _handle_dspark 的 a2a 后端校验python/sglang/srt/arg_groups/speculative_hook.py):
    • 将原来的 moe_a2a_backend != "none" 拒绝条件改为 not in ("none", "megamoe"),即除内置 TP MoE 外,只放行 megamoedeepeppplx 等其他后端仍按原名报错。
    • 新增第二道校验:当 moe_a2a_backend != "none" 时,读取 SGLANG_RAGGED_VERIFY_MODE 环境变量,非 static 模式直接 raise,原因是 per-rank 图 tier 只有在 _dp_tier_gather_enabled(依赖 require_mlp_tp_gather,随 a2a 后端配置变化)成立时才能达成一致,token 维度 tier 不一致会导致各 rank 重放不同图形状。import 采用函数内延迟导入,避免模块级循环依赖。
  2. 新增门禁契约测试test/registered/spec/dspark/test_dspark_draft_path_default.py):
    • 新增 TestDsparkDpAttentionMoeA2aGate 测试类,通过 _dp_server_args 构造 --enable-dp-attention --enable-dp-lm-head dp_size=2 tp_size=2 的参数组合。
    • test_only_megamoe_is_admitted 在 static 模式下验证 megamoe 通过、deepep/pplx 按名抛出 ValueError
    • test_a2a_backend_with_compact_verify_mode_raises 验证 compact 模式下即使 megamoe 也会被拒绝。
    • 新增 from sglang.srt.environ import envs 导入,用于测试中覆盖环境变量。
  3. CI 与测试配套:无新增配置文件,测试注册为 CPU CI(register_cpu_ci),无需 GPU 即可跑通参数校验逻辑;PR 附带了 8x GB300 双节点 TP8/DP8/EP8 的速度与 profile 数据。
文件 模块 状态 重要度
python/sglang/srt/arg_groups/speculative_hook.py 参数校验 modified 6.43
test/registered/spec/dspark/test_dspark_draft_path_default.py 测试 modified 6.75

关键符号

_handle_dspark

关键源码片段

python/sglang/srt/arg_groups/speculative_hook.py dependency-wiring

核心变更文件:`_handle_dspark` 的 a2a 后端白名单从仅 `none` 扩展为 `none`/`megamoe`,并新增 `SGLANG_RAGGED_VERIFY_MODE=static` 的强制校验,是 DSpark + DP attention + EP 组合能否启动的 gate 所在。

# python/sglang/srt/arg_groups/speculative_hook.py
# DSpark + dp attention 下允许的 MoE a2a 后端白名单
def _handle_dspark(server_args: ServerArgs) -> None:
    _is_npu = server_args.device.startswith("npu")
    if not server_args.device.startswith("cuda") and not _is_npu:
        raise ValueError("DSpark speculative decoding only supports CUDA and NPU devices.")
​
    # dp_size == 1 且开 dp_attention 是 DSV4 CP 下的退化组合,跳过 DP 专属检查
    if server_args.enable_dp_attention and server_args.dp_size > 1:
        if not server_args.enable_dp_lm_head:
            raise ValueError("DSpark with dp attention requires --enable-dp-lm-head.")
​
        # 白名单:内置 TP MoE(none)或 MegaMoE;其余后端保持拒绝并点名
        if not _is_npu and server_args.moe_a2a_backend not in ("none", "megamoe"):
            raise ValueError(
                "DSpark with dp attention supports moe_a2a_backend 'none' "
                "(built-in TP MoE) or 'megamoe', got "
                f"{server_args.moe_a2a_backend!r}."
            )
​
        # MegaMoE 等 a2a 后端依赖 per-rank 图 tier 一致:
        # _dp_tier_gather_enabled 依赖 require_mlp_tp_gather(随 a2a 配置变化),
        # 若 rank 间 token 维度 tier 不一致会重放不同图形状,因此强制 static 模式。
        # 函数内延迟 import,避免模块级循环依赖。
        if not _is_npu and server_args.moe_a2a_backend != "none":
            from sglang.srt.speculative.ragged_verify import (
                RaggedVerifyMode,
                read_ragged_verify_mode,
            )
​
            if read_ragged_verify_mode() is not RaggedVerifyMode.STATIC:
                raise ValueError(
                    "DSpark with dp attention + "
                    f"moe_a2a_backend={server_args.moe_a2a_backend!r} requires "
                    "SGLANG_RAGGED_VERIFY_MODE=static."
                )
​
        if server_args.attn_cp_size > 1:
            raise ValueError(
                "DSpark with dp attention does not support context parallel "
                f"(attn_cp_size={server_args.attn_cp_size})."
            )
​
        # spec 后端必须与目标后端一致,否则 EP 集合通信会错位
        if (
            not _is_npu
            and server_args.speculative_moe_a2a_backend is not None
            and server_args.speculative_moe_a2a_backend != server_args.moe_a2a_backend
        ):
            raise ValueError(
                "DSpark ignores --speculative-moe-a2a-backend; with dp attention it "
                f"must match the target moe_a2a_backend={server_args.moe_a2a_backend!r} "
                f"(got {server_args.speculative_moe_a2a_backend!r})."
            )
test/registered/spec/dspark/test_dspark_draft_path_default.py test-coverage

新增 `TestDsparkDpAttentionMoeA2aGate` 测试类,覆盖白名单两侧(megamoe 通过 / deepep、pplx 拒绝)与 compact 模式拒绝,是本次 gate 变更的回归保障。

# test/registered/spec/dspark/test_dspark_draft_path_default.py
# DSpark + dp attention + MoE a2a 后端的门禁契约测试
class TestDsparkDpAttentionMoeA2aGate(CustomTestCase):
    """Gate contract for DSpark + dp attention + MoE a2a backends."""
​
    def _dp_server_args(self, *, moe_a2a_backend: str) -> ServerArgs:
        # 构造 DSpark + DP attention 的典型参数组合
        server_args = _make_dspark_server_args(
            model_path=_BUNDLED_MODEL_PATH, hf_config=_bundled_hf_config()
        )
        server_args.enable_dp_attention = True
        server_args.enable_dp_lm_head = True
        server_args.dp_size = 2
        server_args.tp_size = 2
        server_args.moe_a2a_backend = moe_a2a_backend
        return server_args
​
    def test_only_megamoe_is_admitted(self):
        """Both sides of the allowlist: megamoe passes, others raise by name."""
        # static 模式下 megamoe 应被放行,deepep/pplx 按名抛错
        with envs.SGLANG_RAGGED_VERIFY_MODE.override("static"):
            _handle_dspark(self._dp_server_args(moe_a2a_backend="megamoe"))
            for backend in ("deepep", "pplx"):
                with self.assertRaisesRegex(ValueError, backend):
                    _handle_dspark(self._dp_server_args(moe_a2a_backend=backend))
​
    def test_a2a_backend_with_compact_verify_mode_raises(self):
        # compact 模式无法保证 per-rank 图 tier 一致,megamoe 也必须拒绝
        server_args = self._dp_server_args(moe_a2a_backend="megamoe")
        with envs.SGLANG_RAGGED_VERIFY_MODE.override("compact"):
            with self.assertRaisesRegex(ValueError, "static"):
                _handle_dspark(server_args)

评论区精华

CI extra 运行失败与重跑 other

作者在 PR 中触发 `/tag-and-rerun-ci`,对应 Latest PR Test (Extra) 状态为失败,Latest PR Test (Base) 为通过。

结论:通过重跑命令重试,reviewer `hnyls2002` 最终 APPROVED。 · 已解决

风险与影响

  1. 图形状一致性风险SGLANG_RAGGED_VERIFY_MODE=static 是硬性前置条件,若用户遗漏该环境变量,_handle_dspark 直接报错,属预期防护;但若运行环境与参数校验时的环境不一致(如 worker 进程环境被改动),理论上仍可能出现 rank 间图 tier 分歧。
  2. 依赖面扩大megamoe 路径涉及的 fused kernel 与 DeepEP 类后端不同,当前仅在 moe_a2a_backend != "none" 分支内做校验,后续若新增其他兼容后端,需同步扩展 _dp_tier_gather_enabled 判定。
  3. 覆盖缺口:测试只覆盖参数门禁,未覆盖真实 CUDA 图验证;PR body 的 8 卡数据是一次性验证,仓库 CI 不保证该组合持续回归。
  4. 兼容性:NPU 设备沿用 _is_npu 分支跳过 a2a 校验,行为不变,但 megamoe 在 NPU 上的表现未验证。

影响范围集中在 DSpark + DP attention 的部署组合:此前必须退回内置 TP MoE,现在可选用 MegaMoE(EP)获得更优的 MoE 扩展性。对现有使用 none 后端的用户零影响;对其他 a2a 后端用户,报错信息更明确(按名提示)。团队侧新增了一个参数组合的门禁契约测试,后续改动 _handle_dspark 时会被该测试约束。整体影响面小且正向。

参数门禁变更 环境变量强依赖 缺少端到端回归 仅限 CUDA 验证

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论