Prhub

#46756 Add MiniMax-M3 modelopt nvfp4 support

原始 PR 作者 jasonlizhengjian 合并时间 2026-06-30 00:29 文件变更 4 提交数 2 评论 11 代码增减 +93 / -9

执行摘要

添加 MiniMax-M3 NVFP4 量化支持

当前 main 分支缺少对 MiniMax-M3 NVFP4 模型的必要支持。原始 PR #46380 已合入 minimax-m3-perf 分支,但 main 仍未包含 ModelOpt 混合 MXFP8 dispatch、fused projection 前缀 fallback 以及 NVFP4 MoE 的 alpha/beta/clamp 参数传递等关键改动,导致直接使用 MiniMax-M3-NVFP4 模型时输出乱码。

值得精读,特别是 Per-expert 参数 fallback 和注册的设计模式,以及混合精度配置中的 parent-prefix fallback 策略,可作为为特定模型添加量化支持的良好范例。

讨论亮点

核心讨论围绕 FlashInfer 激活映射的适用范围展开:

  • mgoin 指出新增的 SWIGLUOAI_UNINTERLEAVE 映射可能影响其他使用 trtllm 后端的模型(如 trtllm_bf16_moe.py、trtllm_fp8_moe.py),建议只添加必备类型以减少未测试用例。
  • xinli-sw 和 jasonlizhengjian 同意此观点,最终移除了额外变种,仅保留 SWIGLUOAI_UNINTERLEAVE。
  • 另外,mgoin 建议将新增的 logger 降级为 debug_once 以避免生产环境日志过载,作者已采纳。

实现拆解

  1. ModelOptMixedPrecisionConfig 改造(modelopt.py):

    • init 中新增 mxfp8_config 参数,存储 MXFP8 子配置。
    • 在 _from_config 中构建 ModelOptMxFp8Config 实例并传入。
    • 在 _resolve_quant_algo 中新增第 4 种 fallback 策略:对 fused projection 前缀(如 qkv_proj)通过 parent-prefix 推导量化算法。
    • 在 get_quant_method 中为 MXFP8 分派对应的 LinearMethod 和 FusedMoE。
  2. TrtLlmNvfp4MoEMethod 增强(trtllm_nvfp4_moe.py):

    • 引入辅助函数 _per_expert,将标量值广播为 per-expert tensor。
    • 实现 fallback 链:clamp/alpha/beta 优先从 quant_config 取值,若为 None 则回退到 moe_config.swiglu_*。
    • 在 process_weights_after_loading 中注册 gemm1_beta(经 g1_alphas 折叠)和 gemm1_alpha(不折叠)为 layer parameter,供 kernel 使用。
    • 在 _supports_activation 中添加 MoEActivation.SWIGLUOAI_UNINTERLEAVE
  3. FlashInfer 激活映射(flashinfer_utils.py):

    • activation_to_flashinfer_type 的映射表中添加 MoEActivation.SWIGLUOAI_UNINTERLEAVE → ActivationType.Swiglu
  4. 测试覆盖(test_modelopt.py):

    • 导入 ModelOptMxFp8Config,在 _mixed_precision_config 中构建 mxfp8_config 并传入混合精度配置,验证新配置可正确创建。
文件 模块 状态 重要度
vllm/model_executor/layers/fused_moe/experts/trtllm_nvfp4_moe.py MoE 内核 modified 7.71
vllm/model_executor/layers/quantization/modelopt.py 量化配置 modified 6.66
vllm/model_executor/layers/quantization/utils/flashinfer_utils.py 工具函数 modified 4.93
tests/quantization/test_modelopt.py 测试 modified 4.54

关键符号

_per_expert TrtLlmNvfp4MoEMethod.__init__ TrtLlmNvfp4MoEMethod.process_weights_after_loading ModelOptMixedPrecisionConfig.__init__ ModelOptMixedPrecisionConfig._from_config ModelOptMixedPrecisionConfig._resolve_quant_algo ModelOptMixedPrecisionConfig.get_quant_method activation_to_flashinfer_type

关键源码片段

vllm/model_executor/layers/fused_moe/experts/trtllm_nvfp4_moe.py data-contract

核心 MoE 方法类,实现 per-expert 参数 fallback、注册与 folding,是 NVFP4 正确推理的关键

# vllm/model_executor/layers/fused_moe/experts/trtllm_nvfp4_moe.pydef __init__(self, moe_config: FusedMoEConfig, quant_config: FusedMoEQuantConfig):
    # ... 已有属性赋值 ...
​
    # Fall back to moe_config.swiglu_* 当 quant_config 不携带时
    # (ModelOpt NVFP4 checkpoints 将这些存在 moe_config 上,而不是 quant_config)
    device = torch.accelerator.current_device_index()
​
    def _per_expert(val: float | None) -> torch.Tensor | None:
        """将标量值扩展为 per-expert tensor,或返回 None"""
        if val is None:
            return None
        return torch.full(
            (self.local_num_experts,),
            float(val),
            dtype=torch.float32,
            device=device,
        )
​
    clamp = quant_config.gemm1_clamp_limit
    if clamp is None:
        clamp = getattr(moe_config, "swiglu_limit", None)
    alpha = quant_config.gemm1_alpha
    if alpha is None:
        alpha = getattr(moe_config, "swiglu_alpha", None)
    beta = quant_config.gemm1_beta
    if beta is None:
        beta = getattr(moe_config, "swiglu_beta", None)
​
    if moe_config.is_act_and_mul:
        self.gemm1_clamp_limit = _per_expert(clamp)
        self.gemm1_alpha = _per_expert(alpha)
        self.gemm1_beta = _per_expert(beta)
    else:
        self.gemm1_clamp_limit = None
        self.gemm1_alpha = None
        self.gemm1_beta = None
​
    logger.debug_once(
        "activation=%s, gemm1_alpha=%s, gemm1_beta=%s, gemm1_clamp_limit=%s",
        moe_config.activation, alpha, beta, clamp,
    )
    # 在 process_weights_after_loading 中,beta 会被除以 g1_alphas 后注册为 layer parameter,
    # alpha 则直接注册,供 kernel 推理时使用。
vllm/model_executor/layers/quantization/modelopt.py data-contract

混合精度配置类,新增 mxfp8_config、parent-prefix fallback 和 MXFP8 dispatch,是模型加载的入口

# vllm/model_executor/layers/quantization/modelopt.pyclass ModelOptMixedPrecisionConfig(ModelOptQuantConfigBase):
    def __init__(self, ..., mxfp8_config: ModelOptMxFp8Config):
        # ... 已有属性 ...
        self.mxfp8_config = mxfp8_config # 新增 MXFP8 子配置
​
    @classmethod
    def _from_config(cls, ...):
        # ... 已有配置构建 ...
​
        # 新增:为混合精度 checkpoint 创建 MXFP8 子配置
        mxfp8_config = ModelOptMxFp8Config(
            is_checkpoint_mxfp8_serialized=True,
            kv_cache_quant_algo=kv_cache_quant_method,
            exclude_modules=[],
        )
        return cls(
            ...,
            mxfp8_config=mxfp8_config,
        )
​
    def _resolve_quant_algo(self, prefix: str) -> str | None:
        """新增 parent-prefix fallback 以支持 fused projection"""
        # 前三种策略同上 ...
        # 4. Parent-prefix fallback for fused projections (qkv_proj, gate_up_proj)
        for candidate in self._quantized_layer_prefix_candidates(prefix):
            parent_dot = candidate.rsplit(".", 1)[0] + "."
            algos = {
                info["quant_algo"].upper()
                for key, info in self.quantized_layers.items()
                if key.startswith(parent_dot) and "." not in key[len(parent_dot):]
            }
            if len(algos) == 1:
                return algos.pop()
        return None
​
    def get_quant_method(self, layer, prefix):
        quant_algo = self._resolve_quant_algo(prefix)
        if quant_algo == "MXFP8":
            # 新增:为 MXFP8 层分派对应的 LinearMethod/FusedMoE
            if isinstance(layer, Linear):
                return ModelOptMxFp8LinearMethod(self.mxfp8_config)
            if isinstance(layer, FusedMoE):
                return ModelOptMxFp8FusedMoE(
                    quant_config=self.mxfp8_config,
                    moe_config=layer.moe_config,
                )
        # ... 其他分支 ...
vllm/model_executor/layers/quantization/utils/flashinfer_utils.py data-contract

FlashInfer 激活映射添加 SWIGLUOAI_UNINTERLEAVE,是 kernel 正确调用的前提

# vllm/model_executor/layers/quantization/utils/flashinfer_utils.pydef activation_to_flashinfer_type(activation: MoEActivation) -> "ActivationType":
    ACTIVATION_TO_FI_ACTIVATION = {
        # ... existing mappings ...
        MoEActivation.SWIGLUOAI_UNINTERLEAVE: ActivationType.Swiglu,
        # ^ 新增:将 SwiGLU-OAI 变种映射到 FlashInfer Swiglu,
        # clamped/biased 行为由 per-expert gemm1_alpha/beta 驱动
    }
    return ACTIVATION_TO_FI_ACTIVATION[activation]

评论区精华

SWIGLUOAI_UNINTERLEAVE 映射范围 正确性

mgoin 询问该映射是否对所有 trtllm 后端有效,建议只添加必需的类型以避免未测试情况。xinli-sw 和 jasonlizhengjian 同意并移除了额外的 SWIGLUOAI 条目。

结论:仅保留 MoEActivation.SWIGLUOAI_UNINTERLEAVE 到 Swiglu 的映射,其他变种不添加。 · 已解决

日志级别降级 style

mgoin 建议将新增的 logger 语句从默认级别降级为 debug_once,避免生产环境日志过多。

结论:改为 logger.debug_once。 · 已解决

风险与影响

  1. 配置 fallback 风险:新增的 parent-prefix fallback 逻辑可能改变其他模型在混合精度配置下的量化算法解析,但影响范围局限于 ModelOptMixedPrecisionConfig。
  2. per-expert 参数注册:在 TrtLlmNvfp4MoEMethod 中为每个 expert 注册 gemm1_alpha/beta 参数,会增加少量显存消耗,但 params 为 float32,开销可忽略。
  3. 测试覆盖不足:单元测试仅验证配置构造,缺少端到端推理测试(如使用 MiniMax-M3-NVFP4 模型的完整推理),未来若重构相关路径可能未能及时发现回归。
  4. 依赖外部库:MXFP8 dispatch 依赖 ModelOptMxFp8Config 类存在,若该类后续移除或签名变化将导致失败。

对用户:MiniMax-M3-NVFP4 模型用户现在可在 main 上正常使用该量化格式,获得正确输出。对系统:增加约 93 行代码,不改变其他模型行为。对团队:完成从实验分支(minimax-m3-perf)到主线的合并,降低维护成本,为后续类似模型支持提供模式参考。

配置 fallback 可能影响其他模型 per-expert 参数注册增加显存 单元测试缺少端到端验证

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论