# Cache Lokal Hub

Dokumen ini menjelaskan tata letak di-disk dari cache lokal HF Hub. Dokumen ini ditujukan sebagai referensi untuk mengimplementasikan ulang sistem cache dalam bahasa apa pun.

Berikut adalah daftar sebagian pustaka dan aplikasi yang menggunakan tata letak cache ini. Silakan buka PR untuk menambahkan milik Anda.

### Perpustakaan

| Library | Bahasa | Catatan |
|---------|----------|-------|
| [`huggingface_hub`](https://github.com/huggingface/huggingface_hub) | Python | Dan pustaka apa pun yang bergantung padanya (misalnya `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 | Hanya Node.js |
| [`SMILE`](https://github.com/haifengl/smile) | Java | Lihat dokumentasi [`HuggingFaceHub`](https://haifengl.github.io/api/java/smile/util/HuggingFaceHub.html) |

### Aplikasi

| Aplikasi | Bahasa | Catatan |
|-------------|----------|-------|
| [`llama.cpp`](https://github.com/ggml-org/llama.cpp) | C++ | Sejak [#20775](https://github.com/ggml-org/llama.cpp/pull/20775) |
| [`Llama-macOS`](https://github.com/ggml-org/Llama-macOS) | Swift | Aplikasi macOS, dibangun di atas `llama.cpp` |
| [`HuggingFaceModelDownloader`](https://github.com/bodaay/HuggingFaceModelDownloader) | Mulai | |
| [`oMLX`](https://github.com/jundot/omlx) | Python | Server inferensi MLX macOS, Sejak [v0.4.0](https://github.com/jundot/omlx/commit/341aebbeaba1ca3452f83dedf03a0d102cef9fd4) |

## Lokasi cache

Direktori cache default adalah:

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

Hal ini dapat ditimpa dengan variabel lingkungan:
- `HF_HUB_CACHE` - jalur langsung ke direktori cache (memiliki prioritas)
- `HF_HOME` - path ke direktori home Hugging Face; jika diset, cache berada di `$HF_HOME/hub`

## Ikhtisar

```
<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
```

Setiap repositori yang diunduh memiliki satu folder datar. Di dalam folder repositori, file disimpan sekali di direktori `blobs/` yang berbasis alamat konten dan diakses melalui symlink `snapshots/`. Referensi bernama (cabang, tag) dilacak di `refs/`. Sejak `huggingface_hub` 1.32, file yang diunduh dengan `hf_xet` disimpan sekali untuk seluruh cache, di [shared blob store](#shared-blob-store) pada akar cache. Entri `blobs/` repositori kemudian menjadi symlink ke file tersebut.

## Skema

```
                     ┌──────────────────────────────────────────┐
                     │           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}
```

## Penamaan folder repository

Repository disimpan sebagai direktori datar di akar cache. Nama folder mengkodekan tipe repository dan ID repository:

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

Aturan:
- Tipe repo di-**pluralisasi**: `models`, `datasets`, `spaces`
- Garis miring depan (`/`) dalam ID repositori diganti dengan `--`
- Pemisah antara semua bagian adalah `--`
- Huruf besar dan kecil dipertahankan

Contoh:

| ID repo Hub                          | Jenis repo | Nama folder cache                            |
|--------------------------------------|-----------|----------------------------------------------|
| `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 tidak ditangani oleh cache ini karena tidak didukung oleh git. Gunakan perintah khusus `hf buckets sync` sebagai gantinya.

## Di dalam folder repositori

Setiap repositori yang di-cache memiliki struktur internal yang sama:

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

### `blobs/`: penyimpanan file berbasis alamat konten

Direktori `blobs/` menyimpan konten file yang sebenarnya. Setiap file diberi nama berdasarkan etag file-nya di Hub:

- **Berkas yang dilacak Git**: dinamai berdasarkan hash **SHA-1** mereka (40 karakter heksadesimal)
- **Berkas Git LFS**: dinamai berdasarkan hash **SHA-256** mereka (64 karakter heksadesimal)

Ini adalah direktori datar -- tidak ada subdirektori. File yang identik di berbagai revisi disimpan hanya sekali. Sebuah entri adalah file reguler atau symlink relatif ke [shared blob store](#shared-blob-store); pembaca harus mengikutinya.

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

### `refs/`: referensi branch dan tag

Direktori `refs/` memetakan referensi yang dapat dibaca manusia (nama cabang, tag, nomor PR) ke hash commit.

Setiap referensi adalah file teks biasa yang berisi satu baris: hash commit lengkap (40 karakter heksadesimal).

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

Ketika sebuah file diunduh menggunakan nama branch atau tag, file ref yang sesuai dibuat atau diperbarui dengan hash commit terbaru.

### `snapshots/`: tampilan revisi

Direktori `snapshots/` berisi satu subdirektori untuk setiap revisi yang di-cache (commit hash). Setiap direktori revisi meniru struktur file dari repositori di Hub, tetapi file-file tersebut adalah **symlink** yang menunjuk ke `../../blobs/{hash}`.

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

Properti utama:
- Symlink menggunakan **path relatif**: `../../blobs/{hash}`
- Jika sebuah file tidak berubah antara dua revisi, kedua symlink menunjuk ke **blob yang sama** tanpa duplikasi data
- File di subdirektori pada Hub direpresentasikan sebagai subdirektori dalam snapshot (jalur relatif penuh dipertahankan)

Beralih antar snapshot mirip dengan menggunakan `git checkout` di repositori git lokal.

### `.no_exist/`: cache non-eksistensi

Direktori `.no_exist/` melacak file yang diminta tetapi tidak ada di Hub. Hal ini menghindari permintaan HTTP berulang untuk file opsional.

Struktur mencerminkan `snapshots/`: satu subdirektori per hash commit, berisi **file kosong** (bukan symlink) yang diberi nama sesuai file yang hilang.

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

Penggunaan disk dapat diabaikan karena ini hanyalah file penanda kosong.

## Penyimpanan blob bersama

File yang diunduh melalui Xet dideduplikasi di antara repositori. File seperti itu disimpan sekali di akar cache, di bawah `blobs/`, dan setiap repositori yang membutuhkannya menerima symlink relatif alih-alih salinan:

```
<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
```

Properti utama:
- Entri repo mempertahankan nama etag-nya dan menjadi **simlink relatif**: `../../blobs/{xet_hash[:2]}/{xet_hash}`
- Layout `snapshots/` tidak berubah, sehingga [resolusi file](#file-resolution-logic) tidak terpengaruh
- File penanda mengidentifikasi store: direktori `blobs/` yang tidak kosong di akar cache tanpa file ini tidak pernah digunakan atau dimodifikasi
- Store memerlukan symlink dan `hf_xet`. Jika berbagi gagal, file disimpan di folder repo seperti sebelumnya. `HF_HUB_DISABLE_SHARED_BLOBS=1` menonaktifkan store
- Hanya unduhan baru yang melewati store. File yang ditulis oleh versi lama atau oleh klien lain tetap menjadi file biasa di folder repo, sehingga cache dapat berisi keduanya

Implementasi yang hanya membaca cache tidak memerlukan perubahan: mengikuti symlink berfungsi seperti sebelumnya.

Implementasi yang menghapus dari cache harus memperlakukan blob repo yang berupa symlink sebagai referensi ke store:
- Hapus symlink di `blobs/` repo, bukan file bersama yang ditunjuknya
- Kunci `<xet_hash>.lock` sebelum memeriksa manifest dan menghapus
- Hapus file bersama dan `.refs` hanya jika tidak ada symlink yang terdaftar dalam manifest yang masih menunjuk ke file tersebut
- Jika manifest tidak ada atau tidak dapat dibaca, pertahankan file bersama

Lihat [panduan cache `huggingface_hub`](https://huggingface.co/docs/huggingface_hub/guides/manage-cache#shared-blobs-across-repos) untuk perilaku penghapusan dan keterbatasan pada klien versi lama.

## File kunci

File kunci mencegah proses konkuren dari mengunduh blob yang sama secara bersamaan. File ini disimpan di direktori `.locks/` di akar cache (bukan di dalam folder repo):

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

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

## Contoh lengkap

```
~/.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
```

Perhatikan bagaimana `pytorch_model.bin` menunjuk ke **blob yang sama** di kedua revisi. File berukuran 321 MB hanya disimpan sekali di disk.

## Logika resolusi file

Untuk menemukan file yang di-cache di disk:

1. **Selesaikan revisi menjadi hash commit**
   - Jika revisi sudah berupa string heksadesimal 40 karakter, gunakan langsung
   - Jika tidak, baca file di `refs/{revision}` untuk mendapatkan hash commit

2. **Periksa snapshot**
   - Cari `snapshots/{commit_hash}/{relative_path}`
   - Jika file tersebut ada (sebagai symlink atau file), file akan di-cache. Ikuti symlink untuk mendapatkan kontennya

3. **Periksa ketiadaan**
   - Cari `.no_exist/{commit_hash}/{relative_path}`
   - Jika ada, file diketahui tidak ada di Hub untuk revisi ini

4. **Cache miss**
   - Jika kedua jalur tersebut tidak ada, file tersebut belum di-cache

## Perilaku Windows

Cache bergantung pada **symbolic links**. Pada sistem Windows di mana symlink tidak tersedia, cache beroperasi dalam **mode terdegradasi**: salinan file aktual ditempatkan langsung di `snapshots/` alih-alih symlink. Direktori `blobs/` dan [shared blob store](#shared-blob-store) tidak digunakan dalam mode ini.

Ini berarti konten file yang sama dapat diduplikasi di antara revisi, sehingga meningkatkan penggunaan disk. Untuk mengaktifkan dukungan symlink di Windows, aktifkan [Developer Mode](https://docs.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development) atau jalankan sebagai administrator.

