Prhub

#42546 [ModelOpt] Support Qwen3.5/3.6 VLM quantized prefix mapping

原始 PR 作者 meenchen 合并时间 2026-05-23 14:23 文件变更 1 提交数 16 评论 5 代码增减 +33 / -10

执行摘要

为 Qwen3.5/3.6 VLM 添加 ModelOpt 量化前缀映射

Qwen VLM checkpoints can export language model tensors and quantization config entries with wrapper prefixes such as model.language_model.* or language_model.model.*, while vLLM constructs the text model modules under normalized internal prefixes such as model.* / lm_head.*. This PR adds the required mapping so that Qwen3.5/3.6 VLM checkpoint weights load into the expected vLLM module names and ModelOpt quantized_layers entries resolve correctly when wrapper prefix ordering differs.

值得阅读 _quantized_layer_prefix_candidates 的设计模式,该模式通过静态方法生成候选列表,优雅地解决了跨模型前缀命名差异问题,可复用于其他类似的前缀兼容性场景。

讨论亮点

Review 中 gemini-code-assist[bot] 指出 _quantized_layer_prefix_candidates 缺少从 vLLM 归一化前缀到检查点包装前缀的必要映射,可能导致 Qwen VLM 的 quantized_layers 解析失败。作者 @meenchen 回复解释,ModelOptMixedPrecisionConfig.apply_vllm_mapper() 已在加载时通过 hf_to_vllm_mapperquantized_layers 归一化到 vLLM 侧前缀,因此反方向的映射在多数场景下已覆盖;但为增强鲁棒性,仍添加了候选枚举以应对未归一化的边缘情况。该讨论最终被标记为已解决,PR 获得批准。

实现拆解

  1. ModelOptNvFp4Config 类中新增 _quantized_layer_prefix_candidates 静态方法,根据输入前缀生成候选前缀列表(原始前缀、.lm_head 的裸前缀、language_model.model.*model.language_model.* 的互换前缀)。
  2. 修改 _resolve_quant_algo 方法的三个查找路径(直接查找、打包层查找、前缀查找),将单前缀查找替换为遍历候选前缀列表,首次匹配即返回。
  3. 在打包层查找中,对基础前缀同样应用候选列表,确保子模块也能从包装前缀解析。
  4. 保持方法签名和外部调用不变,仅增强内部解析能力。
  5. 测试方面依赖本地 Qwen3.6 VLM 混合精度检查点的冒烟生成;无新增单元测试。
文件 模块 状态 重要度
vllm/model_executor/layers/quantization/modelopt.py 量化层 modified 7.28

关键符号

_quantized_layer_prefix_candidates _resolve_quant_algo

关键源码片段

vllm/model_executor/layers/quantization/modelopt.py data-contract

核心变更文件,添加前缀映射逻辑,修改量化算法解析函数以支持候选前缀列表。

def _resolve_quant_algo(self, prefix: str) -> str | None:
    """Look up the quant_algo for a vLLM-side layer prefix.
    Tries three strategies, each using prefix candidates.
    """
    # 1. Direct lookup: iterate over candidates
    for candidate in self._quantized_layer_prefix_candidates(prefix):
        if candidate in self.quantized_layers:
            return self.quantized_layers[candidate]["quant_algo"].upper()
​
    # 2. Packed / fused layer lookup
    proj_name = prefix.rsplit(".", 1)[-1]
    if self.packed_modules_mapping and proj_name in self.packed_modules_mapping:
        algos: set[str] = set()
        base = prefix.rsplit(".", 1)[0]
        for base_candidate in self._quantized_layer_prefix_candidates(base):
            for shard_name in self.packed_modules_mapping[proj_name]:
                shard_prefix = f"{base_candidate}.{shard_name}"
                if shard_prefix in self.quantized_layers:
                    algos.add(
                        self.quantized_layers[shard_prefix]["quant_algo"].upper()
                    )
        if len(algos) == 1:
            return algos.pop()
        if len(algos) > 1:
            raise ValueError(
                f"Mixed quant_algo within fused layer {prefix}: "
                f"{algos}. All shards must use the same quantization."
            )
​
    # 3. Prefix-based lookup (for RoutedExperts / parent modules)
    for candidate in self._quantized_layer_prefix_candidates(prefix):
        prefix_dot = candidate + "."
        for key, info in self.quantized_layers.items():
            if key.startswith(prefix_dot):
                return info["quant_algo"].upper()
​
    return None@staticmethod
def _quantized_layer_prefix_candidates(prefix: str) -> tuple[str, ...]:
    # 生成候选前缀列表,用于处理 Qwen VLM 的包装前缀
    candidates = [prefix] # 原始前缀始终候选中
​
    # 例如:lm_head 可能被包装为 language_model.lm_head
    if prefix.endswith(".lm_head"):
        candidates.append("lm_head")
​
    # 处理两种包装前缀的互换
    # language_model.model.X <-> model.language_model.X
    if prefix.startswith("language_model.model."):
        candidates.append(
            "model.language_model." + prefix[len("language_model.model."):]
        )
    elif prefix.startswith("model.language_model."):
        candidates.append(
            "language_model.model." + prefix[len("model.language_model."):]
        )
​
    # 去重并保持插入顺序
    return tuple(dict.fromkeys(candidates))

评论区精华

前缀映射覆盖范围讨论 设计

gemini-code-assist[bot] 指出 `_quantized_layer_prefix_candidates` 缺少从 vLLM 归一化前缀到检查点包装前缀的必要映射,可能导致 Qwen VLM 量化解析失败。作者 @meenchen 解释 `apply_vllm_mapper()` 已在加载时映射,但为鲁棒性仍添加了候选枚举。

结论:作者解释映射已通过 apply_vllm_mapper 处理,但添加额外枚举以确保鲁棒性;问题已解决。 · 已解决

风险与影响

变更局限在单文件单方法内,风险较低。主要风险在于候选前缀的顺序影响首次匹配结果,若错误前缀出现在正确前缀之前可能导致误匹配。此外,未新增单元测试,完全依赖手动冒烟测试,可能遗漏边界情况。但由于查找失败会回退到 None(即不量化),影响可控。

正面影响:支持 Qwen3.5/3.6 VLM 用户正确加载 ModelOpt 混合精度检查点。对其他模型无影响,因为候选列表在不匹配时回退到单前缀查找,行为无变化。

缺少测试覆盖

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论