Prhub

#49990 Resolve revision to commit_hash once per model load, via huggingface_hub's `resolve_revision`

原始 PR 作者 Wauplin 合并时间 2026-08-05 21:49 文件变更 9 提交数 15 评论 8 代码增减 +84 / -6

执行摘要

模型加载时一次性解析并复用 Hub revision,减少 HTTP 请求与竞态

PR body 明确指出 primary goal 是 reduce rate limits in the CI:此前每次模型加载都会对同一个可变 revision 发起多次 HTTP 请求来解析 commit,且若仓库在加载期间更新会导致行为不一致。作者 Wauplin 是 huggingface_hub 维护者,选择 Hub 侧新 API resolve_revision 以同时获得离线缓存复用与可读错误信息。

值得精读。主要看点在:1)ResolvedRevisionstr 子类设计如何在不改动下游签名的情况下同时满足可读性与确定性;2)Hub revision 解析的集中化如何降低 CI 速率限制;3)local_files_only 与离线缓存的结合方式。建议读者关注 vllm/config/model.py 中解析时机的选取与 fallback 策略。

讨论亮点

Review 中 hmellor 提出:与其在 requirements/common.txt 添加版本下限,不如仅提升测试 lockfile 版本并在 _resolve_hf_revision 中通过 getattr 检测 hf_api 是否支持该 API,以保持向后兼容。Wauplin 回应尊重 vllm 团队的决策,并指出小版本提升无破坏性变更风险。最终采纳了显式依赖下限的方案。此外 hmellor 曾询问是否要为 code_revision 也做解析,相关提交 35ab8813 最终回退了 code_revision 的处理,仅保留主 revision 的解析。

实现拆解

  1. vllm/transformers_utils/repo_utils.py 新增 resolve_revision 辅助函数:对本地路径与 ModelScope 直接返回原 revision,否则调用 hf_api().resolve_revision,并 local_files_only=HF_HUB_OFFLINE 以支持离线从 refs/ 缓存读取;任何异常回退到原 revision 并记录 debug 日志。
  2. vllm/config/model.pyModelConfig.__post_init__ 中,先保存 requested_revision,再在 hf_config_path 为空或等于 self.model 时解析 self.revision;若 tokenizer 与模型同仓库且 revision 相同,直接复用已解析值,否则独立解析 tokenizer 的 revision。
  3. ultravox.py 中 audio/text tower 的 DefaultModelLoader.Sourcerevision 改为 None,因为 tower 位于各自独立 Hub 仓库,主仓库的 revision 不适用。
  4. gpu_model_runner.pyreload_weights 中,切换到新 weights_path 时同时将 revision 重置为 None,避免把旧模型的 revision 错误地带到新模型。
  5. 依赖与 lockfile 更新:在 requirements/common.txt 显式声明 huggingface_hub >= 1.26.0,并将 requirements/test/{cuda,rocm,xpu,cpu}.txt 中的 lock 版本从 1.22.0 提升到 1.26.0。
  6. PR 未附带新增测试文件,主要依赖现有模型加载与 CI 回归测试覆盖。
文件 模块 状态 重要度
vllm/transformers_utils/repo_utils.py 工具层 modified 7.1
vllm/config/model.py 配置层 modified 6.9
vllm/model_executor/models/ultravox.py 模型层 modified 5.66
vllm/v1/worker/gpu_model_runner.py 执行器 modified 5.33
requirements/common.txt 依赖清单 modified 1.3
requirements/test/cuda.txt 测试依赖 modified 2.11
requirements/test/rocm.txt 测试依赖 modified 2.11
requirements/test/xpu.txt 测试依赖 modified 2.11
requirements/test/cpu.txt 测试依赖 modified 1.98

关键符号

resolve_revision ModelConfig.__post_init__ reload_weights

关键源码片段

vllm/transformers_utils/repo_utils.py core-logic

新增核心辅助函数 `resolve_revision`,封装 Hub revision 解析、离线缓存与 fallback 逻辑,是整个 PR 的入口。

# vllm/transformers_utils/repo_utils.py
def resolve_revision(
    repo_id: str,
    revision: str | None = None,
    token: str | bool | None = None,
) -> str | None:
    """Best-effort 将 Hub revision 解析为 commit hash。    在加载流程入口解析一次,避免下游重复 HTTP 调用,并规避加载期间分支移动的竞态。
    `HfApi.resolve_revision` 返回 `ResolvedRevision`:其字符串值仍等于请求的 revision(便于错误消息可读),
    同时携带 `.resolved` commit hash;下载相关 API 可直接识别并跳到该 commit。
    """
    # 本地路径与 ModelScope 不走 Hub 解析
    if Path(repo_id).exists() or envs.VLLM_USE_MODELSCOPE:
        return revision
​
    try:
        return hf_api().resolve_revision(
            repo_id,
            revision=revision,
            # 离线模式下直接读 refs/ 缓存,不发 HTTP 请求
            local_files_only=huggingface_hub.constants.HF_HUB_OFFLINE,
            token=token,
        )
    except Exception:
        # 任何失败都回退到原始 revision,保证加载不中断
        logger.debug(
            "Failed to resolve revision for %s; falling back to %s.",
            repo_id,
            revision,
            exc_info=True,
        )
        return revision
vllm/config/model.py data-contract

`ModelConfig.__post_init__` 中引入 resolution 时机,实现模型与 tokenizer revision 的一次解析与复用,是行为变更的核心调度点。

# vllm/config/model.py ModelConfig.__post_init__ 中的关键片段
        requested_revision = self.revision
        self.model = maybe_model_redirect(self.model)
        if self.tokenizer is None:
            self.tokenizer = self.model
        if self.tokenizer_revision is None:
            self.tokenizer_revision = self.revision
        self.tokenizer = maybe_model_redirect(self.tokenizer)
​
        # 在 hf_config_path 与 model 指向同一仓库(或没指定 config 路径)时,
        # 才可把 model 的 revision 解析结果用于 tokenizer。
        can_resolve_model_revision = (
            self.hf_config_path is None or self.hf_config_path == self.model
        )
        if can_resolve_model_revision:
            self.revision = resolve_revision(self.model, self.revision, self.hf_token)
​
        # tokenizer 与 model 同仓库且请求 revision 一致时,直接复用已解析值,
        # 避免对同一 repo 发起第二次解析请求;否则独立解析。
        if (
            can_resolve_model_revision
            and self.tokenizer == self.model
            and self.tokenizer_revision == requested_revision
        ):
            self.tokenizer_revision = self.revision
        else:
            self.tokenizer_revision = resolve_revision(
                self.tokenizer, self.tokenizer_revision, self.hf_token
            )

评论区精华

依赖版本下限 vs 运行时特性检测 设计

hmellor 建议不在 common.txt 加版本下限,而是提升测试 lockfile 版本并在 `_resolve_hf_revision` 中使用 `getattr` 检测 `hf_api` 是否支持 `resolve_revision`,以保留对旧版 huggingface_hub 的兼容。

结论:Wauplin 表示尊重 vllm 团队决策,小版本提升无 breaking change,最终采纳在 common.txt 显式声明下限的方案。 · 已解决

是否同时解析 code_revision 设计

hmellor 在 review 中询问是否需要为 `code_revision`(trust_remote_code 场景)也添加解析。

结论:提交 `35ab8813` 回退了 code_revision 的处理,仅保留主 revision 的解析,降低变更范围。 · 已解决

风险与影响

  1. 依赖下限变更:huggingface_hub >= 1.26.0 从传递依赖变为显式依赖,若用户环境锁定旧版本会安装失败;但 1.22.0 到 1.26.0 为 minor bump,风险可控。
  2. 离线缓存依赖:离线模式依赖 refs/ 缓存存在,若缓存未填充则 fallback 回原始 revision,行为与之前一致,但弱化了可追溯性。
  3. 行为变化面:revision 变为在 __post_init__ 早期解析,若解析结果被 Pickle 到 worker 进程,需确认每个 worker 拿到的是同一个已解析值;gpu_model_runner.pyrevision=None 的重置会影响热重载场景,需验证重载路径的回归。
  4. 缺少直接针对 resolve_revision 逻辑的单元测试,主要依赖现有 CI 模型加载测试,覆盖率存在缺口。

影响面主要是模型加载路径(config、tokenizer、remote code 与局部权重仓库),对所有从 Hugging Face Hub 加载的模型生效,可显著减少 CI 中的 Hub API 请求量。对本地路径与 ModelScope 场景无行为变化;Offline 模式行为从「使用可变 revision」变为「使用缓存的 commit hash」(若缓存存在)。对用户可观察的影响主要是错误消息仍显示原始 revision 名称,保持可读。团队维护上新增一个与 Hub 交互的集中入口,便于后续统一管控。

依赖版本变更 缺失单元测试 热重载路径行为调整 离线缓存依赖

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论