Prhub

#35244 [Fix] Transformers-fallback (GPT-NeoX) + KV pool config (DeepSeek-VL2)

原始 PR 作者 pavansivaram 合并时间 2026-08-31 11:05 文件变更 4 提交数 6 评论 3 代码增减 +97 / -2

执行摘要

修复 GPT-NeoX 回退加载与 DeepSeek-VL2 V-cache 三个问题

PR body 开宗明义:Three small fixes surfaced by consecutive downstream accuracy runs。三个问题分属两条链路:一是 Transformers 回退层不理解 GPT-NeoX/Pythia 的 checkpoint 键约定(gpt_neox. 包装前缀、顶层 embed_out.,以及老 checkpoint 持久化的 .attention.bias causal-mask 缓冲),导致 load_weights 直接抛 ValueError;二是 ModelConfig 把 DeepSeek-VL2 的 v_head_dim=0(MLA 禁用哨兵)当作真实维度传给非 MLA 的 KV 池,使 V-cache 以零宽分配并在首次 extend prefill 崩溃。两者都是模型“加载不起来 / 一跑就崩”级别的可用性缺陷。

值得精读。规模虽小,但三个修复都展示了“从 checkpoint 序列化来源与加载器构造对象的不匹配反推根因”的分析方法,尤其 v_head_dim=0 哨兵的识别对理解 SGLang 非 MLA 与 MLA 双路径的 KV 池 sizing 很有帮助。值得关注的设计决策:用显式 is None or == 0 而非 not 的写法;测试先证伪再证真(每个用例都验证了修复前失败)。对维护 Transformers 回退加载或 KV 池尺寸推导的工程师有直接参考价值。

讨论亮点

唯一一条 inline review 评论来自 yanbing-j,针对 model_config.py 的 v_head_dim 判断:作者初版写的是 if not self.v_head_dim:,reviewer 建议改为显式 if self.v_head_dim is None or self.v_head_dim == 0:,以准确表达“0 是 MLA 禁用哨兵”的语义而非笼统的 falsy 判断。作者通过 GitHub UI 采纳(commit 4e4da462)。此外,yanbing-j 在 CHANGES_REQUESTED 中要求为三个修复补齐单元测试,作者补完后 reviewer 以 LGTM 通过。

实现拆解

变更入口:head 分支 fix/gpt-neox-fallback-weight-mapper,共 6 个 commit:三个独立修复 commit、一个采纳 review 建议的 model_config.py 更新、一个测试 commit、一次 merge main,整体改动 97 行。

  1. 修复 GPT-NeoX 权重前缀映射(python/sglang/srt/models/transformers.py):在 TransformersBase.hf_to_sglang_mapper 的 orig_to_new_prefix 字典中新增两条规则:"gpt_neox.": "model." 与 "embed_out.": "lm_head."。根因是回退路径用 AutoModel.from_config 拿到的是裸 GPTNeoXModel 骨干(挂在 self.model),而 Pythia/NeoX checkpoint 来自 GPTNeoXForCausalLM.state_dict(),键带 gpt_neox. 包装前缀、LM head 是顶层 embed_out.;改造前兜底规则 "" → "model." 会生成 model.gpt_neox.* 这种加载器找不到的键。
  2. 跳过 GPT-NeoX 风格 causal-mask 缓冲(同文件 TransformersBase.init):skip_substrs.extend(...) 中加入 ".attention.bias"。老 checkpoint 把 causal-mask 存为持久 buffer,新版 transformers 注册为非持久导致模型树中缺失该键;已有 .attn.bias 只覆盖 GPT-2 的 attn 命名,需为 NeoX 的 attention 命名单独补一条。
  3. 把 v_head_dim == 0 视为未设置(python/sglang/srt/configs/model_config.py 的 ModelConfig._derive_model_shapes):判断条件从 if self.v_head_dim is None: 改为 if self.v_head_dim is None or self.v_head_dim == 0:。这是 DeepSeek-VL2 配置里 use_mla=False + v_head_dim=0 的哨兵语义;非 MLA 分支的 pool_configurator.py 直接按 v_head_dim 切分 per-slot V buffer,导致末维为 0,首次 extend prefill 在 memory_pool.py::_set_kv_buffer_impl 处 v_cache[indices] = v 触发 shape mismatch。MLA 分支不受影响,因为后续架构专属 elif 分支会用真实值覆盖该字段。
  4. 测试配套:新增 test/registered/unit/model_loader/test_transformers_fallback.py(注册 CPU CI 套件 base-a-test-cpu,含 test_gpt_neox_prefix_rewrites、test_init_registers_attention_bias_skip),并在 test/registered/unit/configs/test_model_config_shapes.py 增加 test_v_head_dim_zero_falls_back_to_head_dim。三个用例均验证了“修复前失败、修复后通过”;PR body 还记录了 EleutherAI/pythia-12b(TP=2)、EleutherAI/gpt-neox-20b(TP=2)、deepseek-ai/deepseek-vl2-tiny(TP=1)的真实模型冒烟结果。
文件 模块 状态 重要度
python/sglang/srt/models/transformers.py 回退加载 modified 5.8
python/sglang/srt/configs/model_config.py 模型配置 modified 5.1
test/registered/unit/model_loader/test_transformers_fallback.py 回退测试 added 7.26
test/registered/unit/configs/test_model_config_shapes.py 配置测试 modified 4.98

关键符号

_derive_model_shapes TransformersBase.__init__ test_gpt_neox_prefix_rewrites test_init_registers_attention_bias_skip test_v_head_dim_zero_falls_back_to_head_dim

关键源码片段

python/sglang/srt/models/transformers.py data-contract

Transformers 回退加载器的核心文件,两处修复(权重前缀映射 + skip_substrs)都在这里,直接影响 Pythia / GPT-NeoX 系模型能否加载。

class TransformersBase(nn.Module):
    # 权重键前缀重写表:把 HuggingFace checkpoint 的键改写到 SGLang
    # 回退模型期望的布局。Pythia / GPT-NeoX 的 checkpoint 来自
    # GPTNeoXForCausalLM.state_dict(),骨干带 `gpt_neox.` 包装前缀,
    # LM head 是顶层 `embed_out.`;而 AutoModel.from_config 只返回裸
    # backbone 作为 self.model,CausalMixin 再把 lm_head 挂到 self.lm_head。
    hf_to_sglang_mapper = WeightsMapper(
        orig_to_new_prefix={
            # 多模态语言模型子模块规则(既有)
            "language_model.model.": "model.language_model.",
            "model.transformer.": "model.",
            # GPT-2 系列规则(既有,attention 模块名为 attn)
            "transformer.": "model.",
            # 新增:剥离 GPT-NeoX 骨干包装前缀
            "gpt_neox.": "model.",
            # 新增:把 embed_out 路由到 SGLang 的 lm_head 槽位
            "embed_out.": "lm_head.",
            "model.": "model.",
            "lm_head.": "lm_head.",
            "": "model.", # 兜底规则,保障未命中前缀的键仍可映射
        }
    )
​
    def __init__(self, config, quant_config=None, prefix=""):
        ...
        # 回退加载时按子串跳过不应加载的键。老版 GPT-NeoX checkpoint 把
        # causal-mask 作为持久 buffer(键含 .attention.bias)存入权重表,
        # 新版 transformers 已改为 persistent=False,模型树中不再存在该键,
        # 因此需要主动跳过,否则 AutoWeightsLoader 会以 unexpected key 报错。
        self.skip_substrs: list[str] = []
        self.skip_substrs.extend(
            [
                ".attn.bias", # GPT-2 风格 causal-mask 缓冲(attn 命名)
                ".attn.masked_bias", # GPT-2 风格填充值
                ".attention.bias", # 新增:GPT-NeoX 风格 causal-mask 缓冲
                ".masked_bias", # 通用填充值
            ]
        )
python/sglang/srt/configs/model_config.py data-contract

ModelConfig._derive_model_shapes 决定 KV 池 sizing 的关键输入 v_head_dim,一行判断修复 DeepSeek-VL2 非 MLA 模式的零宽 V-cache 崩溃。

    def _derive_model_shapes(self):
        from sglang.srt.configs.dots3 import Dots3Config
​
        # 先统一 head_dim:缺省时由 hidden_size 与注意力头数推导,
        # 并回写 hf_text_config,保证下游模块读取的是归一化后的数值。
        self.head_dim = getattr(self.hf_text_config, "head_dim", None)
        if self.head_dim is None:
            self.head_dim = (
                self.hf_text_config.hidden_size
                // self.hf_text_config.num_attention_heads
            )
            setattr(self.hf_text_config, "head_dim", self.head_dim)
​
        self.v_head_dim = getattr(self.hf_text_config, "v_head_dim", None)
        # DeepSeek-VL2 的 language_config 在 use_mla=False 时仍带
        # v_head_dim=0,这是 DeepseekV2 风格配置里“MLA 已禁用”的哨兵值,
        # 并非真实维度。非 MLA 分支的 pool_configurator 直接按 v_head_dim
        # 切分 V buffer,若把 0 当真,V-cache 会变成零宽张量,首次 extend
        # prefill 即在 _set_kv_buffer_impl 中 shape mismatch,因此 0 必须
        # 与 None 一样回退到 head_dim。后续 MLA 架构专属分支会再覆盖该值。
        if self.v_head_dim is None or self.v_head_dim == 0:
            self.v_head_dim = self.head_dim
            setattr(self.hf_text_config, "v_head_dim", self.v_head_dim)
​
        # swa_* 系列同样做缺省推导(当前仍只认 None)
        self.swa_head_dim = getattr(self.hf_text_config, "swa_head_dim", None)
        if self.swa_head_dim is None:
            self.swa_head_dim = self.head_dim
            setattr(self.hf_text_config, "swa_head_dim", self.swa_head_dim)
​
        self.swa_v_head_dim = getattr(self.hf_text_config, "swa_v_head_dim", None)
        if self.swa_v_head_dim is None:
            self.swa_v_head_dim = self.swa_head_dim
            setattr(self.hf_text_config, "swa_v_head_dim", self.swa_v_head_dim)
​
        # FIXME: 依赖架构名白名单判断 MLA(如 DeepseekV2/V3、Glm4MoeLite 等),
        # 后续应改为更通用的特征探测,避免新架构漏进白名单时被错误回退。
        if (... "DeepseekV2ForCausalLM" in self.hf_config.architectures ...):
            ...

评论区精华

v_head_dim == 0 判断条件写法 设计

yanbing-j 在 model_config.py 的 diff 上看到初版 if not self.v_head_dim:,建议改为 if self.v_head_dim is None or self.v_head_dim == 0:,理由是显式条件能准确表达“0 是 MLA 禁用哨兵”这一意图,`not` 会把所有 falsy 值笼统纳入回退,语义不透明。

结论:作者通过 GitHub UI 采纳建议(commit 4e4da462),改用显式 is None or == 0 判断,并同步回写 hf_text_config.v_head_dim。 · 已解决

为三个修复补齐单元测试 测试

yanbing-j 在 CHANGES_REQUESTED 中要求把三个修复的 UTs 加到 test/registered/unit/model_loader/ 与 test/registered/unit/configs/test_model_config_shapes.py。作者新建 test_transformers_fallback.py 并补 test_v_head_dim_zero_falls_back_to_head_dim,每个用例都验证了修复前失败、修复后通过。

结论:测试补齐后 yanbing-j 转为 APPROVED(LGTM)。 · 已解决

风险与影响

  1. python/sglang/srt/models/transformers.py 的映射规则对所有走 Transformers 回退的模型全局生效:gpt_neox. 前缀目前仅 NeoX 家族命中,但 embed_out. 作为顶层前缀若未来某 checkpoint 真实包含该键,会被误路由到 lm_head.;skip_substrs 是子串匹配,.attention.bias 理论上可能误伤确需加载的同名参数(当前未见实例)。
  2. model_config.py 只处理了 v_head_dim,swa_head_dim / swa_v_head_dim 仍只认 None,若出现同类 0 哨兵会再次触发零宽分配(目前无已知模型)。
  3. MLA 判断依赖架构名白名单(代码中有 FIXME 注释),新增 DeepSeek 系架构若忘记进白名单且带 v_head_dim=0,会被错误回退到 head_dim。
  4. 真实模型验证是手工冒烟而非 CI 集成测试,pythia-12b / gpt-neox-20b 权重较大,无法常驻日常 CI,回归主要靠单测。

对用户:Pythia 全系列与 gpt-neox-20b 等 NeoX 系模型可经 Transformers 回退正常加载并完成首次生成(此前直接报 key mismatch);DeepSeek-VL2(尤其 tiny)非 MLA 模式下 KV 缓存从 0.00 GB 恢复为正确大小(实测 K、V 各 3.38 GB),首次 extend prefill 不再崩溃。对系统:修复的是 KV 池非 MLA sizing 的输入值,不影响 MLA 分配路径;映射与 skip 规则只作用于回退加载器,原生模型与其它回退模型不命中即无行为变化。对团队:新增两个 CPU CI 测试文件(base-a-test-cpu 套件),为回退加载路径提供回归护栏,并留下可复现的模型清单(pythia-12b、gpt-neox-20b、deepseek-vl2-tiny)。整体影响面窄、程度中等偏低,但修复的是可用性级别的问题。

回退映射全局生效 子串跳过规则过宽 swa 维度未同步处理 MLA 白名单依赖

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论