Prhub

#34883 [Kimi-K3] Use explicit SiTU activation for MegaMoE

原始 PR 作者 Fridge003 合并时间 2026-08-15 16:42 文件变更 4 提交数 4 评论 6 代码增减 +68 / -24

执行摘要

Kimi-K3 MegaMoE 显式 SiTU 激活并新增 B300 端到端测试

PR body 要求移除 sentinel clamp、直接调用 situ、以及在 runner 为 flashinfer_mxfp4 时仍为 MegaMoE 准备 MXFP4 权重;原 mock 测试无法覆盖真实权重加载、warmup 与 GSM8K 路径。旧实现的 0.03125 哨兵值依赖 swiglu clamp 不会使用该值的隐式约定,既脆弱又难维护,DeepGEMM PR #78 提供显式 situ 激活后可直接表达意图。

值得精读:

1) kimi_k3.py 中从哨兵 hack 到显式 API 的演进;
2) mxfp4.py 中多后端权重布局的条件分支设计;
3) e2e 测试如何配置 DSPARK + MegaMoE 并设置 SGLANG_OPT_DEEPGEMM_MEGA_MOE_NUM_MAX_TOKENS_PER_RANK。适合想了解 DeepGEMM MegaMoE 集成与 MXFP4 权重准备的工程师。

讨论亮点

本 PR 无正式 review 评论;评论区仅 Fridge003 的三次 /rerun-test 指令与 CI 回执,第一次失败后在 8x B300 上通过(965.5s,MegaMoE 子测试 GSM8K 0.985)。设计权衡沉淀在代码注释中:显式 situ 替代哨兵、mega 权重 repoint 回收 1.9GB/层/rank、MegaMoE 绕过 runner 分发需独立 DeepGEMM 权重准备。

实现拆解

  1. 依赖升级:在 python/pyproject.toml 将 sgl-deep-gemm 从 0.1.5.post2 升至 0.1.5.post3,该版本包含 DeepGEMM PR #78 的显式 situ 激活支持。
  2. 模型调用改造:删除 python/sglang/srt/models/kimi_k3.py 中的 _K3_MEGA_SITU_SENTINEL_CLAMP 常量,在 _forward_mega_experts 中将 fp8_fp4_mega_moe 调用从 activation="swiglu" + activation_clamp 改为 activation="situ",并同步更新注释与断言文案,明确 beta=4.0/linear_beta=25.0 由内核烘焙。
  3. 权重准备修正:在 python/sglang/srt/layers/quantization/mxfp4.py 新增 self.use_mega_moe 标志,将 create_weights 和 process_weights_after_loading 中原本仅判断 use_marlin/use_deep_gemm 的条件改为与 use_mega_moe 组合判断:MegaMoE 时跳过 marlin 的 padding 与 deinterleave,直接走 DeepGEMM 的 scale 布局转换,并调用 transform_weights_for_mega_moe 构建 mega_l1_weights/mega_l2_weights,将参数 repoint 到 mega 布局以回收约 1.9GB/层/rank 显存。
  4. 端到端测试:在 test/registered/models_e2e/test_kimi_k3_b300.py 新增 TestKimiK3B300MegaMoE 类,以 TP8/EP8/DCP8 启动 megamoe 服务,配置 DSPARK 投机与 SGLANG_OPT_DEEPGEMM_MEGA_MOE_NUM_MAX_TOKENS_PER_RANK=8320 环境变量,GSM8K 阈值 0.95,并将 register_cuda_ci 的 est_time 从 900 上调至 1200 秒。
文件 模块 状态 重要度
test/registered/models_e2e/test_kimi_k3_b300.py 端到端测试 modified 6.99
python/sglang/srt/models/kimi_k3.py 模型实现 modified 6.18
python/sglang/srt/layers/quantization/mxfp4.py 量化层 modified 6.02
python/pyproject.toml 依赖配置 modified 3.07

关键符号

_forward_mega_experts Mxfp4MoEMethod.create_weights Mxfp4MoEMethod.process_weights_after_loading TestKimiK3B300MegaMoE.setUpClass

关键源码片段

test/registered/models_e2e/test_kimi_k3_b300.py test-coverage

新增 TP8/EP8/DCP8 MegaMoE 端到端测试类,真实拉起 8 卡 B300 服务,覆盖权重加载、warmup 与 GSM8K 精度路径,替代原 mock 单测,并将 CI 预估时间上调至 1200 秒。

# Kimi-K3 B300 端到端测试:新增 MegaMoE recipe
# 相比原来的 mock 单测,这里真实拉起 8 卡 B300 服务,覆盖权重加载、
# warmup 与 GSM8K 精度路径,DSPARK 草稿模型参与投机解码。
class TestKimiK3B300MegaMoE(GSM8KMixin, CustomTestCase):
    gsm8k_score_threshold = 0.95
    gsm8k_num_examples = 200
​
    @classmethod
    def setUpClass(cls):
        cls.model = MODEL_PATH
        cls.base_url = MEGAMOE_URL # 独立端口 30000,避免与其它 recipe 冲突
        cls.process = popen_launch_server(
            cls.model,
            cls.base_url,
            timeout=SERVER_LAUNCH_TIMEOUT,
            other_args=[
                "--trust-remote-code",
                "--tp-size", "8",
                "--moe-a2a-backend", "megamoe",
                "--ep", "8",
                "--dcp-size", "8",
                "--mem-fraction-static", "0.85",
                "--reasoning-parser", "kimi_k3",
                "--tool-call-parser", "kimi_k3",
                "--mamba-full-memory-ratio", "5.13",
                "--speculative-algorithm", "DSPARK",
                "--speculative-draft-model-path", DSPARK_DRAFT_MODEL,
                "--speculative-dspark-block-size", "7",
                "--enable-linear-replayssm-spec",
            ],
            env=MEGAMOE_ENV, # SGLANG_OPT_DEEPGEMM_MEGA_MOE_NUM_MAX_TOKENS_PER_RANK=8320
        )
​
    @classmethod
    def tearDownClass(cls):
        _stop_server(getattr(cls, "process", None))
python/sglang/srt/models/kimi_k3.py data-contract

核心调用变更:删除 activation_clamp 哨兵常量,将 fp8_fp4_mega_moe 的激活从 swiglu+sentinel 改为显式 situ,并更新 MegaMoE 相关注释与断言文案。

# Kimi-K3 MegaMoE 前向:显式 SiTU 激活
# 旧版本用 activation="swiglu" + activation_clamp=0.03125 哨兵值触发
# DeepGEMM 内核中的 K3 SiTU 分支(0.03125 恰好是 2^-5,且合法 swiglu clamp
# 不会用到它)。升级到 sgl-deep-gemm==0.1.5.post3 后,内核新增显式
# "situ" 激活参数,这里直接传入,彻底移除哨兵 hack,避免与真实 clamp 冲突。
def _forward_mega_experts(self, routed_input, ...):
    # ... 前序 topk / pre_dispatch 逻辑 ...
    mega_moe_pre_dispatch(...)
    # 至少保留一行,保证 tvm-ffi 绑定拿到非空 data_ptr
    y = torch.empty(
        (max(num_tokens, 1), self.moe_hidden_size),
        dtype=torch.bfloat16,
        device=routed_input.device,
    )
    deep_gemm.fp8_fp4_mega_moe(
        y,
        self.experts.mega_l1_weights,
        self.experts.mega_l2_weights,
        buf,
        recipe=(1, 1, 32),
        activation="situ", # 显式选择 K3 SiTU 激活;beta=4.0 / linear_beta=25.0 已烘焙进内核
        fast_math=True,
    )
    y = y[:num_tokens]
    # ... 后续 scaling 与返回 ...
    return y
python/sglang/srt/layers/quantization/mxfp4.py core-logic

权重准备逻辑修正:新增 use_mega_moe 标志,使 MegaMoE 在 flashinfer_mxfp4 runner 下也走 DeepGEMM 布局准备,并复用 transform_weights_for_mega_moe 构建 mega 权重。

# Mxfp4MoEMethod:当启用 MegaMoE(use_mega_moe=True)时,即使常规
# MoE runner 是 flashinfer_mxfp4,也需要按 DeepGEMM 的布局准备权重,
# 因为 MegaMoE 直接调用 deep_gemm 而不经过 runner 分发。
def process_weights_after_loading(self, layer):
    # 常规 runner 走 marlin 时正常 marlin 准备;MegaMoE 不走 marlin
    if self.use_marlin and not self.use_mega_moe:
        # ... marlin 专属 deinterleave / prepare 逻辑 ...
        layer._mxfp4_backend = "marlin"
        return
​
    # DeepGEMM(无论常规 deep_gemm runner 还是 MegaMoE)需要:
    # 1) 权重转 int8 视图(packed e2m1)
    # 2) scale 转 TMA 对齐的 MN-major 布局
    # 3) MegaMoE 额外调用 transform_weights_for_mega_moe 得到
    # interleaved/UTCCP 布局,并回收连续布局的原始显存
    if self.use_deep_gemm or self.use_mega_moe:
        from deep_gemm import transform_sf_into_required_layout
        layer.w13_weight.data = layer.w13_weight.data.view(torch.int8)
        layer.w2_weight.data = layer.w2_weight.data.view(torch.int8)
        for scale_name, weight in (("w13_weight_scale", layer.w13_weight),
                                   ("w2_weight_scale", layer.w2_weight)):
            scale = getattr(layer, scale_name)
            num_experts, n, _ = scale.data.shape
            k = weight.shape[2] * 2
            scale_f32 = scale.data.view(torch.float8_e8m0fnu).to(torch.float32)
            scale.data = transform_sf_into_required_layout(
                scale_f32, mn=n, k=k, recipe=(1, 32),
                num_groups=num_experts, disable_ue8m0_cast=False,
            )
        if self.use_mega_moe:
            from deep_gemm import transform_weights_for_mega_moe
            l1_pair, l2_pair = transform_weights_for_mega_moe(
                (layer.w13_weight.data, layer.w13_weight_scale.data),
                (layer.w2_weight.data, layer.w2_weight_scale.data),
            )
            layer.mega_l1_weights = l1_pair
            layer.mega_l2_weights = l2_pair
            # 将参数重新指向 mega 布局,连续布局原值变死代码
            # 每层每 rank 可回收约 1.9GB(92 层双份专家权重会 OOM)
            layer.w13_weight.data = l1_pair[0]
            layer.w13_weight_scale.data = l1_pair[1]
            layer.w2_weight.data = l2_pair[0]
            layer.w2_weight_scale.data = l2_pair[1]
            layer._mega_moe_weights_built = True
        layer._mxfp4_backend = "deep_gemm"
        return
    # ... flashinfer cutlass 各 SM 分支 ...

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

mxfp4.py 的条件分支改动影响所有使用 Mxfp4MoEMethod 的 MoE 场景,但 use_mega_moe 仅在 --moe-a2a-backend megamoe 时置真,默认路径行为不变,仍需留意与 use_marlin/use_deep_gemm 的组合是否有遗漏分支。sgl-deep-gemm 为 patch 版本升级,仍有兼容性回归风险,尤其是已在生产使用 0.1.5.post2 的 MegaMoE 用户。register_cuda_ci(est_time=1200) 使 base-c 阶段耗时增加约 5 分钟,可能影响 B300 CI 队列容量。行为变更局限在 Kimi-K3 模型路径,对其它模型无直接风险。

改善 Kimi-K3 在 Blackwell B300 上的 MegaMoE 使用体验与可维护性,扩大端到端测试覆盖;对其它模型路径无直接影响,但 mxfp4.py 条件分支变化影响所有 Mxfp4MoEMethod 使用者,CI 时间增加约 5 分钟,团队需同步升级 sgl-deep-gemm 版本。

依赖版本升级 MoE 核心路径变更 CI 测试时长增加 量化层多后端分支调整

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论