Prhub

#35174 [Diffusion] Reuse shared checkpoint quant metadata resolver

原始 PR 作者 mickqian 合并时间 2026-08-19 13:36 文件变更 3 提交数 9 评论 2 代码增减 +38 / -14

执行摘要

diffusion 量化加载复用共享解析器,修复嵌套配置不可达

PR body 明确指出两个动机:一是修复 "text_config.quantization_config and compression_config being unreachable when the top-level key is absent"——旧代码在顶层没有 quantization_config 时直接返回 None,导致后续针对 vision 模型 text_config 和 compressed-tensors 风格 compression_config 的分支永远执行不到;二是"correct TransformerQuantLoadSpec to reference diffusion's actual QuantizationConfig base rather than the unrelated SRT type",即类型引用漂移。作者同时强调这是一次刻意保持小范围的 stacked PR,不新增量化格式,也不改动 ModelOpt/backend 映射。

值得精读,尤其对在 multimodal_gen 与 SRT 之间抽取共享逻辑有参考价值。建议重点关注:resolve_checkpoint_quant_spec 返回 None 的语义与 spec.config 非空保证、以及顶层键缺失时兜底报错的消息安全性。若要在此基础上继续扩展,可补充多来源并存时的优先级测试。

讨论亮点

该 PR 无 review 评论(review_comments_count = 0),主要讨论来自作者本人的 issue 评论。作者明确预告了后续两个 stacked follow-up:"#35182 adds exact ModelOpt checkpoint admission (no new backend), and #35184 safely routes standard Diffusers quantized VAE component repos while keeping native VAEs fail-closed",并强调本 PR 刻意控制在 3 文件、+36/-14 的小范围,format 相关的扩展一律留待后续。

实现拆解

  1. 接入共享解析器(quantization_utils.py):在 get_quant_config 中,原本的手动查找逻辑(先判顶层 quantization_config 是否存在,再在 None 分支下尝试 text_config 与 compression_config)整体替换为一次 resolve_checkpoint_quant_spec(model_config) 调用;返回 None 时提前退出,非 None 时取其 .config 交给 normalize_flat_modelopt_quant_config 规范化,再走原有 GGUF、ModelOpt FP8 ignore_remap、目录 glob 兜底等分支。modelslim 路径保持不变。
  2. 修正类型引用(transformer_load_utils.py):QuantizationConfig 的 import 从 sglang.srt.layers.quantization 改为 sglang.multimodal_gen.runtime.layers.quantization,确保 TransformerQuantLoadSpec 等处的类型注解引用 diffusion 实际的基类,避免与 SRT 侧类型混淆。
  3. 补回归测试(test_transformer_quant.py):新增 test_fp8_quant_config_resolves_from_text_config(验证顶层无 quantization_config 时从 text_config 嵌套位置解析 FP8)和 test_bitsandbytes_quant_config_resolves_from_compression_config(验证 compressed-tensors 风格的 compression_config 路径);原有顶层 hf_config 测试保留,三个用例正好覆盖共享解析器的三处来源。
  4. 提交链清理:9 个 commit 中包含多次与 #35172 分支的 merge、resolver 位置 relocate、以及 isort 风格的 import 排序,最终合并回 main,说明这是典型的 stacked PR 收尾流程。
文件 模块 状态 重要度
python/sglang/multimodal_gen/runtime/utils/quantization_utils.py 量化配置 modified 6.43
python/sglang/multimodal_gen/test/unit/test_transformer_quant.py 量化测试 modified 6.09
python/sglang/multimodal_gen/runtime/loader/transformer_load_utils.py 权重加载 modified 4.81

关键符号

get_quant_config resolve_checkpoint_quant_spec test_fp8_quant_config_resolves_from_text_config test_bitsandbytes_quant_config_resolves_from_compression_config

关键源码片段

python/sglang/multimodal_gen/runtime/utils/quantization_utils.py dependency-wiring

核心变更文件:get_quant_config 主路径从手写三源查找改为共享 resolve_checkpoint_quant_spec,修复嵌套配置不可达并删除约 13 行重复逻辑。

# python/sglang/multimodal_gen/runtime/utils/quantization_utils.pydef get_quant_config(
    model_config,
    component_model_path: str,
    packed_modules_mapping: Dict[str, List[str]] = {},
    reverse_param_names_mapping: Dict[str, List[str]] = {},
    remap_prefix: Dict[str, str] | None = None,
    quant_ignore_remap: Optional[Dict[str, str]] = None,
) -> QuantizationConfig:
    # modelslim 格式保留独立解析路径,行为不变
    quant_cfg = find_quant_modelslim_config(model_config, component_model_path)
    if quant_cfg is not None:
        quant_cls = _load_quant_cls(quant_cfg)
        return quant_cls.from_config(quant_cfg, reverse_param_names_mapping)
​
    # 核心变更:把三处手动查找(quantization_config /
    # text_config.quantization_config / compression_config)收敛到
    # SRT 侧共享解析器 resolve_checkpoint_quant_spec。它返回 None 时才
    # 提前退出,从而修复顶层键缺失导致嵌套配置不可达的旧 bug
    checkpoint_quant_spec = resolve_checkpoint_quant_spec(model_config)
    if checkpoint_quant_spec is None:
        return None
​
    # resolve 已把非 dict 配置规范化,这里只需补齐 ModelOpt 的 flat 字段
    hf_quant_config = normalize_flat_modelopt_quant_config(checkpoint_quant_spec.config)
    quant_cls = _load_quant_cls(hf_quant_config)
​
    # GGUF 没有配置文件,直接返回空配置
    if hf_quant_config["quant_method"] == "gguf":
        return quant_cls.from_config({})
​
    if hf_quant_config is not None:
        hf_quant_config["packed_modules_mapping"] = packed_modules_mapping
        is_modelopt_fp8 = (
            hf_quant_config.get("quant_method") == "modelopt"
            and "FP8" in str(hf_quant_config.get("quant_algo", "")).upper()
        )
        extra_kwargs = (
            {"ignore_remap": quant_ignore_remap}
            if quant_ignore_remap and is_modelopt_fp8
            else {}
        )
        return quant_cls.from_config(hf_quant_config, **extra_kwargs)
​
    # 兜底:从模型目录的 *.json 中按 quant_cls 约定文件名反查量化配置
    hf_folder = model_config["model_path"]
    possible_config_filenames = quant_cls.get_config_filenames()
    if not possible_config_filenames:
        return quant_cls()
​
    config_files = glob.glob(os.path.join(hf_folder, "*.json"))
    quant_config_files = [
        f for f in config_files if any(f.endswith(x) for x in possible_config_filenames)
    ]
    if len(quant_config_files) == 0:
        raise ValueError(
            f"Cannot find the config file for {model_config['quantization_config']['quant_method']}"
        )
python/sglang/multimodal_gen/test/unit/test_transformer_quant.py test-coverage

新增两个回归测试,精确对应旧代码中顶层键缺失导致 text_config 与 compression_config 分支不可达的缺陷,是本次修复的守卫。

# python/sglang/multimodal_gen/test/unit/test_transformer_quant.py
# 新增回归:顶层 quantization_config 缺失时,从 text_config 嵌套位置解析 FP8 配置def test_fp8_quant_config_resolves_from_text_config(self):
    config = get_quant_config(
        {
            "text_config": {
                "quantization_config": {
                    "quant_method": "fp8",
                    "activation_scheme": "dynamic",
                }
            }
        },
        "/unused/component/path",
    )
​
    self.assertIsInstance(config, Fp8Config)
    self.assertTrue(config.is_checkpoint_fp8_serialized)
​
​
# 新增回归:compressed-tensors 风格仓库,量化元数据在 compression_configdef test_bitsandbytes_quant_config_resolves_from_compression_config(self):
    config = get_quant_config(
        {
            "compression_config": {
                "quant_method": "bitsandbytes",
                "load_in_4bit": True,
                "bnb_4bit_quant_storage": "uint8",
            }
        },
        "/unused/component/path",
    )
​
    self.assertEqual(config.get_name(), "bitsandbytes")
    self.assertTrue(config.load_in_4bit)

评论区精华

后续 stacked PR 计划与范围控制 other

作者在 issue 评论中预告本 PR 之后 #35182 增加精确的 ModelOpt checkpoint 准入(不新增 backend),#35184 安全路由标准 Diffusers 量化 VAE 组件仓库并保持原生 VAE fail-closed;PR body 也声明本期刻意不加量化格式、不改 allowlist。

结论:本期仅完成共享解析器接线与嵌套配置修复,format 相关扩展全部留到后续 PR。 · 已解决

风险与影响

  1. 共享解析器行为依赖:resolve_checkpoint_quant_spec 来自 #35172,diffusion 侧现在完全依赖其查找顺序与规范化行为;若多来源(顶层、text_config、compression_config)同时存在时其优先级与旧逻辑不一致,可能导致部分模型解析出不同的量化配置,而现有测试未覆盖多来源并存场景。
  2. config 为 None 的边界:head 代码在 resolve 返回非 None 但 spec.config 为 None 时,会先执行 hf_quant_config["quant_method"] 触发 TypeError;旧代码同样存在此类脆弱性,但新代码把判空保护(if hf_quant_config is not None)放在访问 quant_method 之后,防御顺序不严谨,依赖高层调用方保证 config 非空。
  3. 兜底报错潜在 KeyError:目录 glob 兜底分支的 ValueError 消息仍引用 model_config['quantization_config']['quant_method'];若量化配置实际来自 text_config 或 compression_config(顶层无此键),走到该分支会在报错前抛出 KeyError,影响排障体验。

影响范围集中在 diffusion(multimodal_gen)量化 checkpoint 加载路径。对用户而言,修复了 vision 模型将量化配置放在 text_config、以及 compressed-tensors 风格仓库使用 compression_config 时量化配置解析失败的问题。对系统而言,diffusion 与 SRT 共享同一解析逻辑,后续新增量化格式只需改 resolver 一处,降低双轨维护成本。对团队而言,本 PR 为 #35182、#35184 提供了统一的解析入口,是 diffusion 量化支持向 checkpoint 元数据驱动演进的中间步骤。

嵌套配置解析路径变更 依赖共享解析器行为 兜底报错潜在 KeyError config 为 None 边界未防护

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论