Prhub

#46845 [Bugfix] Fix MiniMax-M3 compressed-tensors FP8 MoE SwiGLU params

原始 PR 作者 tanpinsiang 合并时间 2026-08-12 18:52 文件变更 2 提交数 5 评论 9 代码增减 +34 / -0

执行摘要

修复 MiniMax-M3 FP8 MoE 的 SwiGLU 参数转发

PR body 明确指出:MiniMax-M3 使用 hidden_act="swigluoai" 和非默认 SwiGLU 参数(swiglu_alpha=1.702swiglu_beta=1.0swiglu_limit=7.0),而 compressed-tensors FP8 MoE 路径此前只转发了 swiglu_limit,导致 fused MoE 使用了错误的激活参数,输出严重降级。tjtanaa 在评论中要求提供验证模型链接以帮助社区复现;prakhar-prakash-juspay 指出 ModelOptFp8MoEMethod 有完全相同遗漏并给出配套修复 #49473。

建议精读。这是一个小而精准的 bugfix,展示了非默认激活参数在量化 MoE 路径中容易遗漏转发的问题,并附带针对性单元测试与端到端精度数据作为验收标准。同时建议跟进 #49473,排查其他量化路径是否有同类遗漏。

讨论亮点

tjtanaa:@tanpinsiang please provide the link to the model that you used to validate this. It will be useful for the community...
tanpinsiang:https://huggingface.co/EmbeddedLLM/MiniMax-M3-FP8-dynamic This is the target model for FP8 hardware.
prakhar-prakash-juspay:ModelOptFp8MoEMethod has the identical omission — companion fix in #49473. Independently confirmed on a ModelOpt per-tensor FP8 MiniMax-M3 checkpoint: per-expert-layer cosine vs a pure-torch reference went 0.966 → 0.99+ once gemm1_alpha/gemm1_beta are forwarded... Thanks for the CT-side fix — it confirmed the pattern.
tjtanaa:/ci run ... /ci retry(CI 重试后通过)

实现拆解

  1. 修改 vllm/model_executor/layers/quantization/compressed_tensors/compressed_tensors_moe/compressed_tensors_moe_w8a8_fp8.pyget_fused_moe_quant_config,在调用 make_fp8_moe_quant_config 时新增 gemm1_alpha=getattr(layer, "swiglu_alpha", None)gemm1_beta=getattr(layer, "swiglu_beta", None)
  2. tests/quantization/test_compressed_tensors.py 中新增单元测试 test_compressed_tensors_w8a8_fp8_moe_forwards_swiglu_params,通过 object.__new__ 构造 CompressedTensorsW8A8Fp8MoEMethod 实例,用 Mock 模拟 layer 包含 swiglu_alpha=1.702swiglu_beta=Noneswiglu_limit=7.0,断言 gemm1_alpha == 1.702gemm1_beta is Nonegemm1_clamp_limit == 7.0,并补充 Fp8MoeBackendCompressedTensorsW8A8Fp8MoEMethod 的导入。
  3. 端到端验证使用 EmbeddedLLM/MiniMax-M3-FP8-dynamic 检查点,修复后 GSM8K Platinum 95.92(BF16 95.81)、GPQA Diamond 77.95(BF16 77.78),恢复至接近 BF16 精度;同时引入兼容性设计,None 默认值保留默认激活路径。

无配置或部署配套改动。

文件 模块 状态 重要度
vllm/model_executor/layers/quantization/compressed_tensors/compressed_tensors_moe/compressed_tensors_moe_w8a8_fp8.py 量化实现 modified 5.65
tests/quantization/test_compressed_tensors.py 量化测试 modified 5.65

关键符号

CompressedTensorsW8A8Fp8MoEMethod.get_fused_moe_quant_config test_compressed_tensors_w8a8_fp8_moe_forwards_swiglu_params

关键源码片段

vllm/model_executor/layers/quantization/compressed_tensors/compressed_tensors_moe/compressed_tensors_moe_w8a8_fp8.py data-contract

修复核心路径:`get_fused_moe_quant_config` 新增 `gemm1_alpha` 与 `gemm1_beta` 的转发,使 MiniMax-M3 的非默认 SwiGLU 参数能正确传入 `make_fp8_moe_quant_config`。

def get_fused_moe_quant_config(self, layer: torch.nn.Module) -> FusedMoEQuantConfig:
    is_per_token = self.input_quant.strategy == QuantizationStrategy.TOKEN
    # 关键修复:转发非默认 SwiGLU-OAI 参数(如 MiniMax-M3 的 swiglu_alpha=1.702)
    # 缺省为 None 以兼容默认 SwiGLU 激活路径,不影响现有模型
    return make_fp8_moe_quant_config(
        fp8_backend=self.fp8_backend,
        w1_scale=layer.w13_weight_scale,
        w2_scale=layer.w2_weight_scale,
        a1_scale=getattr(layer, "w13_input_scale", None),
        a2_scale=getattr(layer, "w2_input_scale", None),
        per_act_token_quant=is_per_token,
        per_out_ch_quant=is_per_token,
        block_shape=self.weight_block_size,
        gemm1_alpha=getattr(layer, "swiglu_alpha", None),
        gemm1_beta=getattr(layer, "swiglu_beta", None),
        swiglu_limit=getattr(layer, "swiglu_limit", None),
        layer=layer,
    )
tests/quantization/test_compressed_tensors.py test-coverage

新增单元测试覆盖转发行为,直接断言 `gemm1_alpha`、`gemm1_beta`、`gemm1_clamp_limit` 被正确传递,防止回归。

def test_compressed_tensors_w8a8_fp8_moe_forwards_swiglu_params():
    # 通过 object.__new__ 绕过 __init__ 构造实例,减少依赖
    quant_method = object.__new__(CompressedTensorsW8A8Fp8MoEMethod)
    quant_method.input_quant = QuantizationArgs(
        num_bits=8,
        type=QuantizationType.FLOAT,
        strategy=QuantizationStrategy.TOKEN,
        dynamic=True,
        symmetric=True,
    )
    quant_method.weight_block_size = None
    quant_method.fp8_backend = Fp8MoeBackend.TRITON
​
    # 用 Mock 模拟 layer,只暴露 get_fused_moe_quant_config 需要的字段
    layer = Mock()
    layer.w13_weight_scale = torch.ones(2, 4, 1)
    layer.w2_weight_scale = torch.ones(2, 8, 1)
    layer.w13_input_scale = None
    layer.w2_input_scale = None
    layer.swiglu_alpha = 1.702
    layer.swiglu_beta = None
    layer.swiglu_limit = 7.0
​
    quant_config = quant_method.get_fused_moe_quant_config(layer)
​
    # 验证转发:alpha 为 1.702,beta 为 None(未设置),clamp_limit 为 7.0
    assert quant_config.gemm1_alpha == 1.702
    assert quant_config.gemm1_beta is None
    assert quant_config.gemm1_clamp_limit == 7.0

评论区精华

验证模型链接询问 question

tjtanaa 请求提供验证模型链接,作者提供 https://huggingface.co/EmbeddedLLM/MiniMax-M3-FP8-dynamic。

结论:社区可通过该链接复现 FP8 硬件上的效果。 · 已解决

ModelOptFp8MoEMethod 同类遗漏 设计

prakhar-prakash-juspay 指出 `ModelOptFp8MoEMethod` 存在相同遗漏,并提供 #49473 配套修复;独立验证转发 `gemm1_alpha`/`gemm1_beta` 后 per-expert-layer cosine 从 0.966 提升至 0.99+。

结论:确认了参数转发的通用模式,催生 #49473 配套修复。 · 已解决

CI 与重试 other

tjtanaa 触发 Buildkite CI,随后对 2 个失败 job 执行 /ci retry。

结论:CI 通过后 PR 获得批准并合入。 · 已解决

风险与影响

风险点:

  • make_fp8_moe_quant_configgemm1_alpha/gemm1_betaNone 时的默认行为未在本次 PR 中显式验证,但 getattr 默认 None 与之前行为一致,默认 SwiGLU 模型不受影响;
  • 同类转发遗漏可能存在于其他量化路径(如 ModelOptFp8MoEMethod,已在 #49473 中修复),需排查 gemm1_alpha/gemm1_beta 在所有 MoE 量化入口的转发情况;
  • 变更仅影响 compressed-tensors W8A8 FP8 MoE 路径,改动面小,回归风险低。

影响范围:

  • 用户侧:MiniMax-M3 使用 compressed-tensors FP8 动态检查点的用户将获得正确输出质量(GSM8K 从 0.00 恢复至 95.92);使用默认 SwiGLU 的模型行为不变。
  • 系统侧:修改局限于单个量化方法与对应测试,无性能、安全影响。
  • 团队侧:验证了 swiglu_alpha/swiglu_beta 转发的通用模式,推动 #49473 配套修复落地,有助于后续统一 MoE 激活参数转发契约。
同类遗漏未排查完 需确认 None 兼容性 局部变更,影响面小

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论