【Bug已解决】Loading repository after rename does not work (with old name) 解决方案

【Bug已解决】Loading repository after rename does not work (with old name) 解决方案

【Bug已解决】Loading repository after rename does not work (with old name) 解决方案

一、现象长什么样

你把 HF Hub 上的模型仓库从old-org/old-name改名成new-org/new-name,然后代码里还用旧名加载,结果失败:

# 现象 A:旧名直接 401/404 Repository Not Found: 401 Client Error. (Request ID: ...) old-org/old-name # 改名后旧名不再解析,from_pretrained 用旧名去 Hub 查 -> 404/401 # 现象 B:本地缓存按旧名组织,改名后读不到 OSError: Can't find 'old-org/old-name' in the cached model list. # 缓存目录是 .../models--old-org--old-name/,改名后新名对应新目录, # 旧缓存没被复用 # 现象 C:重定向没生效(部分版本) # Hub 改名应返回 redirect 到新名,但某些 huggingface_hub 版本/网络下 # 重定向没被正确跟随,旧名加载静默失败 # 典型触发 from transformers import AutoModel m = AutoModel.from_pretrained("old-org/old-name") # 改名后失败

最典型的指纹:仓库改名后,用旧名from_pretrained不再工作,要么 404、要么缓存读不到、要么重定向没生效

二、背景

HF Hub 的仓库改名(rename)机制:改名后,旧名应当通过 Hub 的**重定向(redirect)**解析到新名(类似网页永久重定向)。huggingface_hubmodel_info/hf_hub_download在拿到旧名时,应当:

  1. 请求旧名 → Hub 返回重定向(302/新名);
  2. 跟随重定向 → 用新名下载/查信息;
  3. 本地缓存按"规范名(新名)"组织,旧名作为别名。

但实际中,改名加载失败的常见原因:

  • 缓存键是旧名:本地~/.cache/huggingface下是models--old-org--old-name,改名后新会话用新名产生models--new-org--new-name,旧缓存不被复用(现象 B)。
  • 重定向在某些路径没被跟随from_pretrained内部先用model_info(old_name)拿 config,若model_info这步没跟随重定向(或被缓存的 404 拦截),后续下载全失败(现象 A/C)。
  • 验证/鉴权绑定旧名:某些 gated repo 的访问令牌与旧名关联,改名后旧名 401(现象 A)。

三、根因

根因有三类:

  1. 改名重定向未被from_pretrained全路径跟随from_pretrainedmodel_info(repo_id)再下载。若model_info旧名返回 404(重定向在某些 hub 版本默认不跟随),整条链失败,而不是自动换新名。

  2. 本地缓存按旧名隔离,旧缓存不复用。 Hub 缓存用models--<repo_id 规范名>--目录。改名后规范名变了,旧缓存目录models--old--old与新名models--new--new不共享,即使权重完全相同也要重新下载(浪费带宽)或旧名加载直接找不到。

  3. 旧名被缓存为"不存在"。 若之前用旧名查询过并得到 404,结果可能被短时缓存,改名后重定向已生效,但客户端仍读缓存的 404 → 现象 A 持续。

四、最小可运行复现

下面用纯 Python 模拟"旧名查询:重定向未跟随则 404,跟随则解析到新名":

from typing import Dict, Optional # 模拟 Hub:旧名 -> 重定向到新名 REDIRECTS = {"old-org/old-name": "new-org/new-name"} CACHE = {} # repo_id -> 是否已下载 def model_info(repo_id: str, follow_redirect: bool = False): if repo_id in REDIRECTS: if follow_redirect: return {"id": REDIRECTS[repo_id], "redirected_from": repo_id} else: return {"error": "404 Not Found", "id": repo_id} return {"id": repo_id} def from_pretrained_buggy(repo_id: str): """有 bug:不跟随重定向。""" info = model_info(repo_id, follow_redirect=False) if "error" in info: raise FileNotFoundError(f"Can't load {repo_id}: {info['error']}") return f"loaded {info['id']}" def from_pretrained_fixed(repo_id: str): """修正:跟随重定向到新名。""" info = model_info(repo_id, follow_redirect=True) if "error" in info: raise FileNotFoundError(f"Can't load {repo_id}") return f"loaded {info['id']}" # 复现:不跟随重定向 -> 404 try: from_pretrained_buggy("old-org/old-name") print("复现失败") except FileNotFoundError as e: print("复现成功(根因):", e) # 修正:跟随重定向 -> 新名加载成功 print(from_pretrained_fixed("old-org/old-name")) # loaded new-org/new-name

运行后,buggy 版因不跟随重定向而 404,fixed 版跟随重定向解析到新名,复现并修复了根因 1。

五、解决方案(第一层:最小直接修复)

最快的止血:在调用from_pretrained前,先解析仓库的新名(跟随重定向),用新名加载;或显式用新名

from huggingface_hub import model_info, HfApi def resolve_repo_id(repo_id: str) -> str: """第一层修复:跟随改名重定向,返回当前规范名。""" try: info = model_info(repo_id, token=True, files_metadata=False) # model_info 在 hub 支持改名重定向时,返回对象的 id 已是新名 return getattr(info, "id", repo_id) except Exception: # 若 model_info 仍 404,尝试用 api 的 repo 查找 return repo_id # 使用:改名后用解析出的新名加载 resolved = resolve_repo_id("old-org/old-name") model = AutoModel.from_pretrained(resolved) # 或者最简单:直接改代码用新名(推荐,最稳) model = AutoModel.from_pretrained("new-org/new-name")

第一层让用户立刻能加载改名后的仓库(跟随重定向或用新名),不再 404。

六、解决方案(第二层:结构性改进)

RepoRenameResolver把"旧名→新名解析 + 缓存复用 + 重定向容错"收口:

from dataclasses import dataclass from typing import Dict, Optional @dataclass class RepoRenameResolver: """处理 Hub 仓库改名:解析新名、复用旧缓存、容错重定向。""" redirect_cache: Dict[str, str] = None def __post_init__(self): self.redirect_cache = {} def resolve(self, repo_id: str) -> str: if repo_id in self.redirect_cache: return self.redirect_cache[repo_id] # 跟随重定向解析(hub 返回的新名) try: from huggingface_hub import model_info info = model_info(repo_id, token=True) new_id = getattr(info, "id", repo_id) if new_id != repo_id: self.redirect_cache[repo_id] = new_id return new_id except Exception: return repo_id def cached_path_for(self, repo_id: str) -> str: # 缓存目录按规范名(解析后的新名)组织,旧名别名也指向它 new = self.resolve(repo_id) return f"models--{new.replace('/', '--')}--" # 使用 resolver = RepoRenameResolver() new = resolver.resolve("old-org/old-name") print("解析到:", new, "缓存目录:", resolver.cached_path_for("old-org/old-name")) model = AutoModel.from_pretrained(new)

RepoRenameResolver的语义是:仓库名应以"规范名(新名)"为准,旧名只是别名;解析一次后缓存,加载与缓存都走新名,避免重复下载与 404。

七、解决方案(第三层:断言 / CI 守护)

用 pytest 固化"旧名能被解析到新名、缓存复用、重定向容错":

import pytest def test_old_name_resolves_to_new(monkeypatch): from repo_rename import RepoRenameResolver # 模拟 model_info 返回新名(重定向生效) class FakeInfo: id = "new-org/new-name" monkeypatch.setattr("huggingface_hub.model_info", lambda *a, **k: FakeInfo()) r = RepoRenameResolver() assert r.resolve("old-org/old-name") == "new-org/new-name" def test_redirect_cached(): from repo_rename import RepoRenameResolver calls = {"n": 0} class FakeInfo: id = "new-org/new-name" import huggingface_hub orig = huggingface_hub.model_info def fake(*a, **k): calls["n"] += 1 return FakeInfo() huggingface_hub.model_info = fake try: r = RepoRenameResolver() r.resolve("old-org/old-name") r.resolve("old-org/old-name") # 第二次应命中缓存 finally: huggingface_hub.model_info = orig assert calls["n"] == 1, "重定向结果应被缓存,避免重复查询" def test_cache_path_uses_new_name(): from repo_rename import RepoRenameResolver r = RepoRenameResolver() r.redirect_cache["old-org/old-name"] = "new-org/new-name" assert r.cached_path_for("old-org/old-name") == "models--new-org--new-name--"

CI 跑pytest tests/test_repo_rename.py,以后只要有人又用旧名硬编码加载(不解析重定向),测试可提示改为解析新名。

八、排查清单

当改名后旧名加载失败,按顺序查:

  1. 401/404 Not Found→ 旧名重定向没被跟随,用resolve解析到新名再加载。
  2. 本地缓存读不到 → 缓存按旧名隔离,用新名加载会复用(或重新下载),旧缓存可手动软链到新名目录。
  3. 之前查过旧名得到 404 被短时缓存 → 清掉huggingface_hub的查询缓存或换新名。
  4. gated repo 改名后旧令牌 401 → 去 Hub 重新授权(令牌绑定新名)。
  5. 长期方案:用RepoRenameResolver统一"旧名→新名解析 + 缓存复用",代码里用新名。

九、小结

"Loading repository after rename does not work (with old name)" 的根因是:仓库改名后,旧名依赖 Hub 的重定向解析到新名,但from_pretrained的某些路径不跟随重定向(直接 404)、本地缓存按旧名隔离不复用、旧名的 404 查询被缓存,于是旧名加载失败。

  • 第一层:加载前用model_info跟随重定向解析出新名,或直接改用新名,立刻能加载。
  • 第二层:用RepoRenameResolver把"旧名→新名解析 + 缓存按新名复用"收口,避免重复下载与 404。
  • 第三层:pytest 断言"旧名解析到新名、重定向缓存、缓存路径用新名",防止回归。

记住:仓库改名后,规范名(新名)才是真相;代码里应当用新名,或用重定向解析器把旧名翻译成新名——不要硬编码已失效的旧名。