Prhub

#51179 [CI Bug] Fix `pydantic_core._pydantic_core.ValidationError: Input should be a valid integer`

原始 PR 作者 yewentao256 合并时间 2026-08-06 04:57 文件变更 1 提交数 1 评论 2 代码增减 +4 / -1

执行摘要

修复 num_experts_per_token 为 list 时 pydantic 校验失败的 CI bug

PR body 明确指出该失败由 https://github.com/vllm-project/vllm/pull/50940 引入,并附上 Buildkite 构建 #82455 的完整报错栈:pydantic_core._pydantic_core.ValidationError: 1 validation error for ModelConfig / num_experts_per_token / Input should be a valid integer [type=int_type, input_value=[8, 8, 8, ...], input_type=list]。即远端模型配置里 num_experts_per_token 是 list,而 ModelConfig 字段要求 int,导致 tests/models/test_initialization.py 的 can_initialize 测试对 HunYuanMoEV1ForCausalLM 失败。

值得快速阅读,是一个小而典型的防御性配置解析修复。可对照学习两点:一是对远端模型配置类型不可信的规范化处理模式;二是 get_num_experts(取第一)与 get_num_experts_per_token(取 max)未统一策略的遗留问题,后续如需扩展多 block 异构 top-k 配置应再次审视该方法。

讨论亮点

PR 无实质性技术评论:claude[bot] 仅自动提示仓库配置了手动 review;审阅人 noooop 直接给出 APPROVED 且未附带意见。Issue 评论区只有 /ci run 命令与 Buildkite 触发回执(构建 #82517)。整体属于无争议的小型 CI 修复,快速合入。

实现拆解

  1. 定位根因:vllm/transformers_utils/model_arch_config_convertor.py 的 get_num_experts_per_token 方法直接返回 getattr_iter(self.hf_text_config, names, 0) 的结果,未对 list 类型做归一化,导致 ModelConfig.num_experts_per_token 字段收到 list 值。
  2. 实施修复:将取值先存入 num_experts_per_token,若 isinstance 为 list 则返回 max(num_experts_per_token, default=0),否则保留原有 or 0 语义。这样对非 list 配置行为完全不变,仅对 list 配置做防御性归一化。
  3. 与既有逻辑对照:同文件的 get_num_experts 早已处理 list(Ernie VL remote code),方式是取第一个元素 num_experts[0];本修复采用取 max 而非取第一个,对空列表更安全(default=0),且语义上兼容“各 block 的 top-k 相同”的模型假设。
  4. 配套情况:无新增测试、无配置或部署改动,依赖现有 tests/models/test_initialization.py 回归验证。
文件 模块 状态 重要度
vllm/transformers_utils/model_arch_config_convertor.py 配置转换 modified 5.51

关键符号

get_num_experts_per_token

关键源码片段

vllm/transformers_utils/model_arch_config_convertor.py data-contract

唯一的变更文件,负责将 HuggingFace 模型配置转换为 vLLM ModelConfig。get_num_experts_per_token 返回 list 是 pydantic ValidationError 的直接根因,本次在其中加入 list 归一化分支。

def get_num_experts(self) -> int:
    """返回模型的总专家数。"""
    num_expert_names = [
        "num_experts", # Jamba
        "moe_num_experts", # Dbrx
        "n_routed_experts", # DeepSeek
        "num_local_experts", # Mixtral
    ]
    num_experts = getattr_iter(self.hf_text_config, num_expert_names, 0)
    if isinstance(num_experts, list):
        # 部分远端模型(如 Ernie VL 的 remote code)把每个 block 的
        # 专家数写成 list,值总是一致,取第一个即可
        return num_experts[0]
    if not num_experts:
        num_experts = self.get_num_experts_from_block_configs()
    return num_experts
​
​
def get_num_experts_per_token(self) -> int:
    """返回每个 token 激活的专家数(MoE top-k),必须归一化为 int。"""
    names = [
        "num_experts_per_tok",
        "num_experts_per_token",
        "top_k_experts",
        "moe_topk",
        "moe_top_k",
    ]
    num_experts_per_token = getattr_iter(self.hf_text_config, names, 0)
    if isinstance(num_experts_per_token, list):
        # HunYuanMoE 等 remote code 会产出每个 block 的 top-k 列表,
        # ModelConfig.num_experts_per_token 是 pydantic int 字段,
        # 直接透传 list 会触发 ValidationError,取 max 归一化为单值
        return max(num_experts_per_token, default=0)
    return num_experts_per_token or 0

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

  1. 回归风险低但存在语义不一致:修复仅对 list 输入新增分支,非 list 路径与原逻辑完全等价。但与 get_num_experts 取第一个元素不同,此处取 max;若未来某个远端模型的 list 值并非全部相同,max 可能掩盖真实路由语义(应取第一个或按层分别配置)。
  2. 空列表边界:max([], default=0) 返回 0,与原有 or 0 对空/None 的行为一致,不会引入新的崩溃点。
  3. 缺少测试覆盖:未新增针对 list 类型配置的单测,后续若 #50940 方向的改动再次引入类似类型污染,回归保护仍依赖 CI 的模型初始化测试。

对用户:修复 HunYuanMoE 等使用 remote code 且将 top-k 配置为 list 的 MoE 模型在 vLLM 加载时的崩溃,模型注册与初始化恢复正常。对 CI:解除 Buildkite 上 tests/models/test_initialization.py 对主分支的阻塞,降低团队等待成本。对系统:改动位于所有模型配置解析的必经路径(model_arch_config_convertor),但行为变更面极窄,仅影响 list 类型配置的模型,无部署与迁移负担。

配置解析路径 缺少测试覆盖 数据契约变更

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论