【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_hub的model_info/hf_hub_download在拿到旧名时,应当:
- 请求旧名 → Hub 返回重定向(302/新名);
- 跟随重定向 → 用新名下载/查信息;
- 本地缓存按"规范名(新名)"组织,旧名作为别名。
但实际中,改名加载失败的常见原因:
- 缓存键是旧名:本地
~/.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)。
三、根因
根因有三类:
改名重定向未被
from_pretrained全路径跟随。from_pretrained先model_info(repo_id)再下载。若model_info旧名返回 404(重定向在某些 hub 版本默认不跟随),整条链失败,而不是自动换新名。本地缓存按旧名隔离,旧缓存不复用。 Hub 缓存用
models--<repo_id 规范名>--目录。改名后规范名变了,旧缓存目录models--old--old与新名models--new--new不共享,即使权重完全相同也要重新下载(浪费带宽)或旧名加载直接找不到。旧名被缓存为"不存在"。 若之前用旧名查询过并得到 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,以后只要有人又用旧名硬编码加载(不解析重定向),测试可提示改为解析新名。
八、排查清单
当改名后旧名加载失败,按顺序查:
401/404 Not Found→ 旧名重定向没被跟随,用resolve解析到新名再加载。- 本地缓存读不到 → 缓存按旧名隔离,用新名加载会复用(或重新下载),旧缓存可手动软链到新名目录。
- 之前查过旧名得到 404 被短时缓存 → 清掉
huggingface_hub的查询缓存或换新名。 - gated repo 改名后旧令牌 401 → 去 Hub 重新授权(令牌绑定新名)。
- 长期方案:用
RepoRenameResolver统一"旧名→新名解析 + 缓存复用",代码里用新名。
九、小结
"Loading repository after rename does not work (with old name)" 的根因是:仓库改名后,旧名依赖 Hub 的重定向解析到新名,但from_pretrained的某些路径不跟随重定向(直接 404)、本地缓存按旧名隔离不复用、旧名的 404 查询被缓存,于是旧名加载失败。
- 第一层:加载前用
model_info跟随重定向解析出新名,或直接改用新名,立刻能加载。 - 第二层:用
RepoRenameResolver把"旧名→新名解析 + 缓存按新名复用"收口,避免重复下载与 404。 - 第三层:pytest 断言"旧名解析到新名、重定向缓存、缓存路径用新名",防止回归。
记住:仓库改名后,规范名(新名)才是真相;代码里应当用新名,或用重定向解析器把旧名翻译成新名——不要硬编码已失效的旧名。
