# Hub ローカルキャッシュ

このドキュメントは、HF Hub のローカルキャッシュのディスク上でのレイアウトを説明しています。どの言語でもキャッシュシステムを再実装するためのリファレンスとして意図されています。

このキャッシュレイアウトを使用しているライブラリやアプリケーションの例を以下に示します。ご自身のプロジェクトを追加する場合は、プルリクエスト（PR）を作成してください。

### ライブラリ

| ライブラリ | 言語 | 注記 |
|---------|----------|-------|
| [`huggingface_hub`](https://github.com/huggingface/huggingface_hub) | Python | これに依存するライブラリ（例: `transformers`、`diffusers`、`datasets`、`mlx`、`vllm` など） |
| [`hf-hub`](https://github.com/huggingface/hf-hub) | Rust | |
| [`swift-huggingface`](https://github.com/huggingface/swift-huggingface) | Swift | |
| [`@huggingface/hub`](https://github.com/huggingface/huggingface.js) | JavaScript | Node.js のみ |
| [`SMILE`](https://github.com/haifengl/smile) | Java | [`HuggingFaceHub`](https://haifengl.github.io/api/java/smile/util/HuggingFaceHub.html)のドキュメントを参照してください |

### アプリケーション

| アプリケーション | 言語 | 注記 |
|-------------|----------|-------|
| [`llama.cpp`](https://github.com/ggml-org/llama.cpp) | C++ | [#20775](https://github.com/ggml-org/llama.cpp/pull/20775)以降 |
| [`Llama-macOS`](https://github.com/ggml-org/Llama-macOS) | Swift | `llama.cpp`をベースに構築されたmacOSアプリ |
| [`HuggingFaceModelDownloader`](https://github.com/bodaay/HuggingFaceModelDownloader) | Go | |
| [`oMLX`](https://github.com/jundot/omlx) | Python | macOS MLX推論サーバー、[v0.4.0](https://github.com/jundot/omlx/commit/341aebbeaba1ca3452f83dedf03a0d102cef9fd4)以降 |

## キャッシュの保存場所

デフォルトのキャッシュディレクトリは次のとおりです:

```
~/.cache/huggingface/hub
```

これは環境変数で上書きできます:
- `HF_HUB_CACHE` - キャッシュディレクトリへの直接パス（優先されます）
- `HF_HOME` - Hugging Face ホームディレクトリへのパス。設定されている場合、キャッシュは `$HF_HOME/hub` に保存されます

## 概要

```
<CACHE_DIR>/
├── .locks/                                  # Lock files for concurrent download safety
├── blobs/                                   # Shared blob store: Xet files deduplicated across repos
├── models--<org>--<repo>/                   # Cached model repositories
├── datasets--<org>--<repo>/                 # Cached dataset repositories
└── spaces--<org>--<repo>/                   # Cached space repositories
```

ダウンロードされた各リポジトリには、単一のフラットなフォルダが割り当てられます。各リポジトリのフォルダ内では、ファイルは`blobs/`ディレクトリに1回のみ保存され、`snapshots/`のシンボリックリンクを介してアクセスされます。名前付きリファレンス（ブランチ、タグ）は`refs/`で管理されます。`huggingface_hub` 1.32以降、`hf_xet`でダウンロードされたファイルは、キャッシュのルートにある[共有ブロブストア](#shared-blob-store)に、キャッシュ全体で1回のみ保存されます。リポジトリの`blobs/`エントリは、そのファイルへのシンボリックリンクになります。

## スキーマ

```
                     ┌──────────────────────────────────────────┐
                     │           Repository folder              │
                     │  models--julien-c--EsperBERTo-small      │
                     └──────────────┬───────────────────────────┘
                                    │
              ┌─────────────┬───────┴───────┬──────────────┐
              │             │               │              │
              v             v               v              v
          ┌───────┐    ┌────────┐    ┌────────────┐   ┌──────────┐
          │ refs/ │    │ blobs/ │    │ snapshots/ │   │.no_exist/│
          └───┬───┘    └────┬───┘    └──────┬─────┘   └────┬─────┘
              │             │               │              │
              │             │               │              │
   "main" contains     Files stored     One folder     Empty marker
   commit hash         by content       per commit     files for
   e.g. "aaaaaa"       hash (SHA-1      hash, e.g.     files known
                       or SHA-256)      aaaaaa/        not to exist
   Resolves a                           bbbbbb/
   branch/tag to                            │
   a snapshot ──────────────────────►  Contains symlinks
                                       to ../../blobs/{hash}
```

## リポジトリのフォルダ命名

リポジトリは、キャッシュのルートディレクトリにフラットなディレクトリとして保存されます。フォルダ名には、リポジトリの種類とリポジトリ ID がエンコードされています。

```
{type}s--{repo_id_with_slashes_replaced_by_--}
```

ルール:
- リポジトリの種類は**複数形**です: `models`、`datasets`、`spaces`
- リポジトリID内のスラッシュ(`/`)は`--`に置き換えられます
- すべての部分の区切り文字は`--`です
- 大文字と小文字は保持されます

例:

| HubリポジトリID                          | リポジトリの種類 | キャッシュフォルダ名                            |
|--------------------------------------|-----------|----------------------------------------------|
| `julien-c/EsperBERTo-small`         | model     | `models--julien-c--EsperBERTo-small`         |
| `huggingface/DataMeasurementsFiles`  | dataset   | `datasets--huggingface--DataMeasurementsFiles` |
| `dalle-mini/dalle-mini`             | space     | `spaces--dalle-mini--dalle-mini`             |

> [!NOTE]
> Buckets は git 基盤ではないため、このキャッシュでは処理されません。代わりに専用の `hf buckets sync` コマンドを使用してください。

## リポジトリフォルダ内

キャッシュされたリポジトリはすべて同じ内部構造を持っています:

```
<repo_folder>/
├── blobs/
├── refs/
├── snapshots/
└── .no_exist/    # may not always be present
```

### `blobs/`: コンテンツアドレス方式のファイルストレージ

`blobs/` ディレクトリには、実際のファイル内容が保存されます。各ファイルは、Hub 上のファイル etag に基づいて命名されます:

- **Gitで管理されているファイル**: **SHA-1**ハッシュ（16進数40文字）で識別されます
- **Git LFS ファイル**: **SHA-256** ハッシュ（16 進数 64 文字）で識別されます

これはフラットなディレクトリであり、サブディレクトリはありません。異なるリビジョン間で同一のファイルは1回のみ保存されます。エントリは通常のファイルか、[共有ブロブストア](#shared-blob-store)への相対シンボリックリンクのいずれかであり、リーダーはそれを追跡する必要があります。

```
blobs/
├── 403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd   # SHA-256 (LFS)
├── 7cb18dc9bafbfcf74629a4b760af1b160957a83e                           # SHA-1 (git)
└── d7edf6bd2a681fb0175f7735299831ee1b22b812                           # SHA-1 (git)
```

### `refs/`: ブランチとタグのリファレンス

`refs/` ディレクトリは、人間が読みやすい参照（ブランチ名、タグ、PR 番号）をコミットハッシュにマッピングします。

各リファレンスは、完全なコミットハッシュ（16進数40文字）を1行だけ含むプレーンテキストファイルです。

```
refs/
├── main            # contains e.g. "bbc77c8132af1cc5cf678da3f1ddf2de43606d48"
├── 2.4.0           # a tag
└── refs/
    └── pr/
        └── 1       # pull request reference
```

ブランチ名またはタグ名を使用してファイルをダウンロードすると、対応する ref ファイルが最新のコミットハッシュで作成または更新されます。

### `snapshots/`: リビジョンビュー

`snapshots/` ディレクトリには、キャッシュされた各リビジョン（コミットハッシュ）ごとに1つのサブディレクトリが含まれます。各リビジョンディレクトリは、Hub上のリポジトリのファイル構造を反映していますが、ファイルは `../../blobs/{hash}` 内の **シンボリックリンク** になっています。

```
snapshots/
├── 2439f60ef33a0d46d85da5001d52aeda5b00ce9f/
│   ├── README.md -> ../../blobs/d7edf6bd2a681fb0175f7735299831ee1b22b812
│   └── pytorch_model.bin -> ../../blobs/403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd
└── bbc77c8132af1cc5cf678da3f1ddf2de43606d48/
    ├── README.md -> ../../blobs/7cb18dc9bafbfcf74629a4b760af1b160957a83e
    └── pytorch_model.bin -> ../../blobs/403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd
```

主なプロパティ:
- Symlinksは**相対パス**を使用します: `../../blobs/{hash}`
- 2つのリビジョン間でファイルに変更がない場合、両方のシンボリックリンクは**同じblob**を指し、データの重複は発生しません。
- Hub上のサブディレクトリ内のファイルは、スナップショット内でサブディレクトリとして表されます（完全な相対パスが保持されます）。

スナップショットの切り替えは、ローカルの git リポジトリで`git checkout`を使用することと似ています。

### `.no_exist/`: 非存在キャッシュ

`.no_exist/` ディレクトリは、リクエストされたが Hub に存在しないファイルをトラッキングします。これにより、オプションのファイルに対する HTTP リクエストの繰り返しを回避できます。

構造は`snapshots/`を模倣しています。各コミットハッシュに対応するサブディレクトリが1つ存在し、欠落ファイルの名前を付けた**空ファイル**（シンボリックリンクではありません）が含まれています。

```
.no_exist/
└── 2439f60ef33a0d46d85da5001d52aeda5b00ce9f/
    └── config_that_does_not_exist.json    # empty file
```

これらは空のマーカーファイルのみであるため、ディスク使用量はごくわずかです。

## 共有ブロブストア

Xet 経由でダウンロードされたファイルは、リポジトリ間で重複排除されます。そのようなファイルはキャッシュのルートにある `blobs/` 配下に一度だけ保存され、それが必要な各リポジトリにはコピーではなく相対シンボリックリンクが作成されます。

```
<CACHE_DIR>/
├── blobs/
│   ├── .huggingface-shared-blobs                 # marker file, contains the layout version ("1")
│   └── 73/                                       # first two hex characters of the Xet hash
│       ├── 73b079dc7bd546aed26d20a82289bd752fc52a2fda13e7f2b551f3af73845b59                       # the file, named by its Xet hash (64 hex characters)
│       ├── 73b079dc7bd546aed26d20a82289bd752fc52a2fda13e7f2b551f3af73845b59.refs                  # manifest: one line per repo blob that references it (path relative to the cache root)
│       └── 73b079dc7bd546aed26d20a82289bd752fc52a2fda13e7f2b551f3af73845b59.lock                  # lock file
│
└── models--HuggingFaceTB--SmolLM2-135M-Instruct/
    └── blobs/
        └── 5af571cbf074e6d21a03528d2330792e532ca608f24ac70a143f6b369968ab8c -> ../../blobs/73/73b079dc7bd546aed26d20a82289bd752fc52a2fda13e7f2b551f3af73845b59
```

主なプロパティ:
- リポジトリのエントリは etag 名を保持したまま、**相対シンボリックリンク**になります: `../../blobs/{xet_hash[:2]}/{xet_hash}`
- `snapshots/` のレイアウトは変更されないため、[ファイルの解決](#file-resolution-logic)には影響しません
- マーカーファイルはストアを識別します。キャッシュルートに存在する空でない`blobs/`ディレクトリは、このマーカーファイルがない限り、使用も変更もされません。
- ストアにはシンボリックリンクと`hf_xet`が必要です。共有に失敗した場合、ファイルは従来どおりリポジトリフォルダに保存されます。`HF_HUB_DISABLE_SHARED_BLOBS=1`でストアを無効にできます。
- 新しいダウンロードのみがストアを介して行われます。旧バージョンや他のクライアントによって書き込まれたファイルは、リポジトリフォルダ内に通常のファイルとして保持されるため、キャッシュには両方が含まれる可能性があります。

キャッシュの読み取りのみを行う実装は、変更を必要としません。シンボリックリンクの追跡は従来どおり動作します。

キャッシュから削除する実装では、リポジトリのブロブがシンボリックリンクである場合、それをストアへの参照として扱う必要があります。
- リポジトリの`blobs/`にあるシンボリックリンクを削除してください。共有ファイル自体を削除しないでください。
- マニフェストを確認して削除する前に`<xet_hash>.lock`をロックします
- マニフェストにリストされているシンボリックリンクがまだそのファイルを指していない場合のみ、共有ファイルとその`.refs`を削除します
- マニフェストが存在しないか、読み取れない場合は、共有ファイルを保持します。

削除の動作と旧バージョンのクライアントでの制限については、[`huggingface_hub` のキャッシュガイド](https://huggingface.co/docs/huggingface_hub/guides/manage-cache#shared-blobs-across-repos)を参照してください。

## ロックファイル

ロックファイルは、並行するプロセスが同じブロブを同時にダウンロードすることを防止します。これらは、リポジトリフォルダ内ではなく、キャッシュのルートにある`.locks/`ディレクトリに保存されます。

```
<CACHE_DIR>/.locks/<repo_folder_name>/<blob_hash>.lock
```

例:
```
<CACHE_DIR>/.locks/models--julien-c--EsperBERTo-small/403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd.lock
```

## 完全な例

```
~/.cache/huggingface/hub/
├── .locks/
│   └── models--julien-c--EsperBERTo-small/
│       └── 403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd.lock
│
└── models--julien-c--EsperBERTo-small/
    ├── blobs/
    │   ├── [321M]  403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd
    │   ├── [ 398]  7cb18dc9bafbfcf74629a4b760af1b160957a83e
    │   └── [1.4K]  d7edf6bd2a681fb0175f7735299831ee1b22b812
    │
    ├── refs/
    │   └── main    # contains "bbc77c8132af1cc5cf678da3f1ddf2de43606d48"
    │
    ├── snapshots/
    │   ├── 2439f60ef33a0d46d85da5001d52aeda5b00ce9f/
    │   │   ├── README.md         -> ../../blobs/d7edf6bd2a681fb0175f7735299831ee1b22b812
    │   │   └── pytorch_model.bin -> ../../blobs/403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd
    │   │
    │   └── bbc77c8132af1cc5cf678da3f1ddf2de43606d48/
    │       ├── README.md         -> ../../blobs/7cb18dc9bafbfcf74629a4b760af1b160957a83e
    │       └── pytorch_model.bin -> ../../blobs/403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd
    │
    └── .no_exist/
        └── 2439f60ef33a0d46d85da5001d52aeda5b00ce9f/
            └── optional_config.json    # empty file
```

`pytorch_model.bin` が両方のリビジョンで**同じブロブ**を指している点に注意してください。321 MB のファイルはディスク上に1回のみ保存されます。

## ファイル解決ロジック

ディスク上のキャッシュされたファイルの場所を特定するには:

1. **リビジョンをコミットハッシュに解決する**
   - リビジョンがすでに40文字の16進数文字列である場合は、そのまま直接使用します。
   - それ以外の場合は、`refs/{revision}` のファイルを読み取り、コミットハッシュを取得します。

2. **スナップショットを確認する**
   - `snapshots/{commit_hash}/{relative_path}` を検索してください
   - 存在する場合（シンボリックリンクまたはファイルとして）、ファイルはキャッシュされます。シンボリックリンクをたどってコンテンツを取得します

3. **存在しないことを確認する**
   - `.no_exist/{commit_hash}/{relative_path}` を検索してください
   - 存在する場合、そのファイルはこのリビジョンのHub上に存在しないことが知られています

4. **キャッシュミス**
   - どちらのパスも存在しない場合、ファイルはまだキャッシュされていません。

## Windowsでの動作

キャッシュは**シンボリックリンク**に依存しています。シンボリックリンクが利用できない Windows システムでは、キャッシュは**劣化モード**で動作します。このモードでは、シンボリックリンクの代わりに、実際のファイルのコピーが`snapshots/`に直接配置されます。`blobs/`ディレクトリと[共有ブロブストア](#shared-blob-store)はこのモードでは使用されません。

これは、同じファイルの内容がリビジョン間で重複する可能性があることを意味し、ディスク使用量が増加します。Windows でシンボリックリンクのサポートを有効にするには、[開発者モード](https://docs.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development)を有効にするか、管理者として実行してください。

