Prhub

#48990 [Model] Use standard ModelOpt config for Inkling NVFP4

原始 PR 作者 mgoin 合并时间 2026-07-18 09:13 文件变更 7 提交数 1 评论 1 代码增减 +56 / -110

执行摘要

用标准 ModelOpt 配置统一 Inkling NVFP4 量化

PR 描述指出目标是为了重用 vLLM 标准 ModelOpt 量化基础设施,消除 Inkling 重复的 NVFP4 配置解析逻辑。评论中作者 mgoin 提到 "We maybe want to leave some enforcement here, but not a big deal for the reference checkpoint",说明希望尽可能统一路径。

值得精读,展示如何通过统一配置接口消除重复逻辑。特别关注 _normalize_quantization_config 中 legacy 检测的设计——这是支持旧版 checkpoint 的关键。同时 InklingMoE 从 per-layer 定制走向全局 quant_config 的模式也值得参考。

讨论亮点

唯一的 review 评论来自作者 mgoin,在 vllm/models/inkling/nvidia/moe.py 的 diff 行(对应原 per-layer 检查逻辑)留言:"We maybe want to leave some enforcement here, but not a big deal for the reference checkpoint"。这表明在移除 nvfp4_config 后曾考虑是否需要保留部分断言(如 shared_experts 不量化),但最终因参考 checkpoint 无此问题而放弃。WoosukKwon 直接批准并评论 "thanks for doing this!"。

实现拆解

  1. 增强配置转换器:在 vllm/transformers_utils/model_arch_config_convertor.py_normalize_quantization_config 方法中,除了检测 producer.name == "modelopt",还通过检查 "modelopt_quant_config" 字段识别旧版 ModelOpt 配置(如 Inkling 使用的)。当匹配时,根据 quant_algo 设置 quant_method"modelopt""modelopt_fp4"
  2. 移除自定义 NVFP4 配置类:删除 vllm/models/inkling/nvfp4.py 整个文件(包括 InklingNvfp4Config 类及其方法 from_hf_configexperts_quantized 等),因为标准 ModelOptNvFp4Config 已能处理 exclude_modules 和层级判断。
  3. 改造 InklingMoE 构造:将 InklingMoE.__init__ 的参数从 nvfp4_config: InklingNvfp4Config | None 改为 quant_config: QuantizationConfig | None;移除原根据 nvfp4_config.experts_quantized(layer_id) 动态构造 ModelOptNvFp4Config 的代码,直接透传 quant_config。同时将输入缩放常量改为 _NVFP4_INPUT_SCALE_DENOMINATOR 以复用标准符号。
  4. 简化模型和 DecoderLayer:在 InklingModelInklingDecoderLayer 中移除 nvfp4_config 参数的传递,由顶层 config_utils 通过全局 quant_config 统一管理。
  5. 添加回归测试:在 tests/config/test_model_arch_config.py 增加 test_legacy_modelopt_config_without_producer_is_normalized 验证无 producer.name 的旧配置仍能正确设置 quant_method;在 tests/models/inkling/test_moe_weight_layout.py 增加 test_inkling_mapper_maps_modelopt_exclusions 验证 apply_vllm_mapper 正确映射 exclude_modules。
文件 模块 状态 重要度
vllm/models/inkling/nvfp4.py 模型层 removed 8.83
vllm/models/inkling/nvidia/moe.py 专家层 modified 6.97
vllm/transformers_utils/model_arch_config_convertor.py 配置转换 modified 6.38
vllm/models/inkling/nvidia/model.py 模型层 modified 6.29
tests/config/test_model_arch_config.py 配置测试 modified 5.45
tests/models/inkling/test_moe_weight_layout.py 权重布局测试 modified 5.58

关键符号

InklingMoE.__init__ _normalize_quantization_config test_legacy_modelopt_config_without_producer_is_normalized test_inkling_mapper_maps_modelopt_exclusions

关键源码片段

vllm/models/inkling/nvidia/moe.py data-contract

修改 InklingMoE 构造,从 nvfp4_config 改为 quant_config,移除 per-layer 定制量化配置。

# vllm/models/inkling/nvidia/moe.py (head 版本片段 )
class InklingMoE(nn.Module):
    def __init__(
        self,
        config: InklingModelConfig,
        *,
        prefix: str = "",
        quant_config: QuantizationConfig | None = None, # 直接用标准量化配置
    ) -> None:
        super().__init__()
        # ... 原有断言 ...
        self.gate = InklingGate(
            d_model=config.hidden_size,
            n_routed_experts=n_routed,
            n_shared_experts=n_shared,
            experts_per_token=config.num_experts_per_tok,
            route_scale=config.route_scale,
            use_gate_bias=config.use_gate_bias,
        )
        # 不再通过 nvfp4_config 逐层判断是否量化,
        # 直接透传全局 quant_config,由标准后端处理 exclude_modules
        self.experts = FusedMoE(
            num_experts=padded_experts,
            top_k=config.num_experts_per_tok,
            hidden_size=config.hidden_size,
            intermediate_size=config.intermediate_size,
            renormalize=False,
            quant_config=quant_config, # 标准配置
            prefix=f"{prefix}.experts",
            custom_routing_function=self._select_routed,
            # ...
        )
        # shared_experts 始终 bf16,不需额外处理
vllm/transformers_utils/model_arch_config_convertor.py data-contract

配置转换器扩充了对旧版 ModelOpt 配置的检测,是支持 Inkling 标准化的关键。

# vllm/transformers_utils/model_arch_config_convertor.py (head 版本片段 )
def _normalize_quantization_config(self, config: PretrainedConfig):
    quant_cfg = getattr(config, "quantization_config", None)
    # ... ckpt 处理 ...
    else:
        # 以下逻辑同时支持新版 (producer.name) 和旧版 ( 无 producer.name 但有 modelopt_quant_config)
        producer_name = quant_cfg.get("producer", {}).get("name")
        modelopt_quant_cfg = quant_cfg.get("quantization", {})
        is_legacy_modelopt = (
            isinstance(modelopt_quant_cfg, dict)
            and "modelopt_quant_config" in modelopt_quant_cfg
        )
        if producer_name == "modelopt" or is_legacy_modelopt:
            quant_algo = modelopt_quant_cfg.get("quant_algo")
            if quant_algo is not None:
                quant_algo_upper = str(quant_algo).upper()
                if quant_algo_upper in {"FP8", "FP8_PER_CHANNEL_PER_TOKEN", "FP8_PB_WO"}:
                    quant_cfg["quant_method"] = "modelopt"
                elif quant_algo_upper == "NVFP4":
                    quant_cfg["quant_method"] = "modelopt_fp4"
                else:
                    raise ValueError(f"Unknown ModelOpt quant algo: {quant_algo}")
    # ... 后续处理 ...

评论区精华

保留强制执行检查 设计

作者 mgoin 在 moe.py diff 上评论:‘We maybe want to leave some enforcement here, but not a big deal for the reference checkpoint’

结论:无后续讨论,决定不添加额外断言,因为参考 checkpoint 无误。 · 已解决

风险与影响

主要风险是向前兼容性:若 Inkling 用户有旧版 checkpoint 且配置文件结构不符合新的标准检测逻辑(例如 quantization 键内结构差异),可能导致 quant_method 未正确设置,量化降级或报错。但新增的 is_legacy_modelopt 检测已覆盖 Inkling 所用格式,且 PR 验证了参考 checkpoint 精度(gsm8k 0.892 vs 0.895)。次要风险是导出/权重映射:原自定义配置可能包含对 exclude_modules 的特殊处理,转移到标准 ModelOptNvFp4Config 后,apply_vllm_mapper 是否能正确转换路径(如 model.llm.layers -> model.layers)—— 新测试 test_inkling_mapper_maps_modelopt_exclusions 已覆盖。

直接用户:仅影响使用 Inkling NVFP4 模型的用户(目前主要为 thinkingmachines/Inkling-NVFP4)。变更后需要将启动参数中的 --load-format instanttensor 等保持不变,但不再依赖 Inkling 特有的配置类。对于系统:减少了自定义代码,统一了 ModelOpt 路径,降低后续维护成本。对其他模型无影响。

旧版配置兼容性 移除自定义解析可能引入回归 依赖标准量化路径的稳定性

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论