Hugging Face の公開モデルを手元で動かす技術検証で、重みは commit で版を固定して取り、ふだんはネットに取りに行かせずに使うことにしました。ところが hf download に --revision <commit> を渡して取った重みは、オフライン(HF_HUB_OFFLINE=1)にすると「キャッシュに無い」と言われて読めませんでした。原因と、取るたびに使った小さな関数をまとめます。

結論

  • huggingface_hub 1.27.0 の hf download <repo> --revision <commit> は、キャッシュに refs/main を書きません。repo 名だけで from_pretrained("<repo>") する呼び方は、HF_HUB_OFFLINE=1 のもとで「キャッシュに無い」と落ちます。
  • 取るたびに、$HF_HOME/hub/models--<org>--<name>/refs/main へ commit を自分で書きます。
  • 版を固定する役目はオフラインに持たせます。オンラインのまま repo 名で呼ぶと、refs/main があっても main を問い合わせて追随します。
  • /etc/profile.d/ の設定は login シェルでしか効きません。非対話の ssh や systemd では HF_HOME が空になり、~/.cache/huggingface に重みを落とし直します。

試した環境

  • Ubuntu、Radeon RX 7900 XT、ROCm 7.2.4
  • huggingface_hub 1.27.0(hf コマンド)
  • 重みは Hugging Face のキャッシュの形で、モデル用のディスクに置いた HF_HOME の下に置きました
  • 取るとき以外は /etc/profile.d/ に置いた設定で HF_HUB_OFFLINE=1 を立て、勝手に取りに行かせないようにしました

なぜ版を固定するか

Hugging Face の main は動きます。たとえば MOSS-Audio-8B では、手元の版より main が先に進んでいました(差は README だけで、重みは同じでした)。モデルを読むコードの多くは repo 名だけで from_pretrained("<repo>") するので、何もしなければ main に引っ張られます。

何が起きるか

--revision で取った重みは、キャッシュに入ってはいます。ただ refs/main が無いので、repo 名だけの呼び方からは見つけられません。次の図は、repo 名だけで読むときの分かれ道です。

repo 名だけで読むときの分かれ道

オンラインのままだと main に追随し、オフラインでも refs/main が無ければ落ちます。版を固定して読めるのは、オフラインで、かつ refs/main に commit が書いてあるときだけです。

薄暗い倉庫で、目印の柱が無いために探している木箱の前を素通りする人と、箱の横に新しい柱を立てる助け手の絵

取るたびに refs/main を書く

そこで、取るときに refs/main を自分で書く関数を作って使いました。検証ではオフラインにしていたので、取るときだけ HF_HUB_OFFLINE=0 にしました。

export HF_HOME=/path/to/hf   # 非対話の ssh・systemd では毎回明示する

pin() {  # 使い方: pin <repo> <commit> [ファイル名...]
  r=$1; s=$2; shift 2
  HF_HUB_OFFLINE=0 HF_HUB_DISABLE_TELEMETRY=1 hf download "$r" "$@" --revision "$s" || return 1
  d=$HF_HOME/hub/models--$(printf %s "$r" | sed 's#/#--#g')/refs
  mkdir -p "$d" && printf %s "$s" > "$d/main"
}

# 全部取る形と、ファイル名を並べる形
pin black-forest-labs/FLUX.2-klein-4B <commit>
pin ZhengPeng7/BiRefNet <commit> model.safetensors config.json README.md

コードから呼ぶなら、from_pretrained(..., revision="<sha>") と commit を渡す手もあります。ただ、すべての呼び方がこれだけでオフラインで通るかは確かめていません。

同じ取得で踏んだこと

一部のファイルだけを取るとき

  • 位置引数にグロブは使えません。文字どおりのファイル名として探しに行き、404 になります。
  • ファイル名と --include を一緒に渡すと、--include は無視されます。
  • ファイル名を並べる形は、並べ漏らすと取得が足りません。私たちも、モデルがあるかを確かめるための一部のファイル名だけを並べた取り方を書いていて、そのとおりに打つと 5 つのモデル(FLUX.2-klein、Qwen3-VL、AST、DINOv3、BiRefNet)で取得が足りませんでした。

trees の権限

trees/*.json は、umask に関係なく 0600 で書かれます(1.27.0 が tempfile.mkstemp で書いて置き換えるため)。取った人とは別の利用者で読むと、「Ignoring corrupted tree cache file … Permission denied」が毎回出ます。動作は止まりませんが、取り終えたらグループで読めるようにしておきます。

chmod -R g+rX "$HF_HOME/hub"

login シェルでしか効かない設定

/etc/profile.d/ の設定が効くのは login シェルだけです。非対話の ssh(ssh host 'cmd')や systemd では HF_HOME も HF_HUB_OFFLINE も空になり、~/.cache/huggingface に重みを落とし直します。数 GB の重みを黙って取り直すので、気づいたときにはルートのディスクが埋まっている、ということになりかねません。毎回明示するか、bash -lc で打ちます。

--local-dir で取る物

ACE-Step のチェックポイントのように --local-dir で取る物は、キャッシュの形ではないので、refs/main は関係ありません。

確かめていないこと

  • 最初に踏んだときのエラーの原文(例外の名前を含めて)は控えていません。「キャッシュに無い」は言い換えです。
  • huggingface_hub の版が変わったときに、--revision に commit を渡したときの refs の書き方が変わるか。1.27.0 を前提にしています。
  • from_pretrained(..., revision="<sha>") だけで、refs/main 無しに全部の呼び方がオフラインで通るか。
  • ファイル名を並べず、--include だけで取る形。

おわりに

commit で取った重みをオフラインで読むなら、取るたびに refs/main を書いてください。版を固定するのはオフラインの役目にして、取るときだけオンラインにすると、main の動きに引っ張られません。

※ Hugging Face と huggingface_hub の振る舞いは執筆時点の版(1.27.0)で見た物で、いまは変わっている可能性があります。