# Hub 로컬 캐시

이 문서는 HF Hub 로컬 캐시의 디스크 레이아웃을 설명합니다. 이는 캐시 시스템을 어떤 언어로든 재구현할 때 참고용으로 제공됩니다.

이 캐시 레이아웃을 사용하는 라이브러리 및 애플리케이션의 일부 목록입니다. 직접 추가하려면 PR을 열어 주세요.

### 라이브러리

| 라이브러리 | 언어 | The input is a single word "Notes".

참고 |
|---------|----------|-------|
| [`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) 문서를 참조하세요 |

### 응용 프로그램

| Application | 언어 | The input is a single word "Notes".

참고 |
|-------------|----------|-------|
| [`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) | 시작 | |
| [`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/` 디렉터리에 한 번만 저장되고, `snapshots/` 심볼릭 링크를 통해 접근됩니다. 이름이 지정된 참조(브랜치, 태그)는 `refs/`에 추적됩니다. `huggingface_hub` 1.32부터 `hf_xet`로 다운로드된 파일은 캐시 루트의 [공유 블롭 스토어](#shared-blob-store)에 캐시 전체를 위해 한 번만 저장됩니다. 이 경우 저장소의 `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}
```

## 리포지토리 폴더 이름

Repositories는 캐시 루트에서 평탄한 디렉터리로 저장됩니다. 폴더 이름에는 리포지토리 유형과 리포지토리 ID가 인코딩되어 있습니다:

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

규칙:
- 리포지토리 유형은 **복수형**으로 표시됩니다: `models`, `datasets`, `spaces`
- 리포지토리 ID의 슬래시(`/`)는 `--`로 대체됩니다
- 모든 부분 사이의 구분자는 `--`입니다.
- 대소문자가 유지됩니다

예:

| Hub 저장소 ID                          | 레포지토리 유형 | 캐시 폴더 이름                            |
|--------------------------------------|-----------|----------------------------------------------|
| `julien-c/EsperBERTo-small`         | 모델     | `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자)로 이름이 지정됩니다

이것은 평탄한 디렉터리이며 하위 디렉터리가 없습니다. 다른 리비전 간에 동일한 파일은 한 번만 저장됩니다. 항목은 일반 파일이거나 [shared blob store](#shared-blob-store)를 가리키는 상대 심볼릭 링크입니다. 리더는 이를 따라야 합니다.

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

### `refs/`: 브랜치 및 태그 참조

`refs/` 디렉터리는 사람이 읽을 수 있는 참조(브랜치 이름, 태그, PR 번호)를 커밋 해시로 매핑합니다.

각 참조는 단일 줄로 구성된 일반 텍스트 파일이며, 이 줄에는 전체 커밋 해시(16진수 40자)가 포함되어 있습니다.

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

branch 또는 tag 이름으로 파일이 다운로드되면, 해당 ref 파일이 최신 커밋 해시로 생성되거나 업데이트됩니다.

### `snapshots/`: 리비전 뷰

`snapshots/` 디렉터리에는 캐시된 각 리비전(커밋 해시)마다 하나의 하위 디렉터리가 포함됩니다. 각 리비전 디렉터리는 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}`
- 두 리비전 사이에 파일이 변경되지 않은 경우, 두 심볼릭 링크 모두 **동일한 blob**을 가리키며 데이터 중복이 발생하지 않습니다
- Hub의 하위 디렉토리에 있는 파일은 스냅샷에서 하위 디렉토리로 표시됩니다(전체 상대 경로가 유지됩니다).

스냅샷을 전환하는 것은 로컬 git 저장소에서 `git checkout`를 사용하는 것과 유사합니다.

### `.no_exist/`: 존재하지 않는 항목 캐시

`.no_exist/` 디렉터리는 Hub에 존재하지 않지만 요청된 파일들을 추적합니다. 이를 통해 선택적 파일에 대한 반복적인 HTTP 요청을 방지합니다.

구조는 `snapshots/`와 동일합니다. 각 커밋 해시마다 하위 디렉터리가 하나씩 있으며, 누락된 파일 이름으로 명명된 **빈 파일**(심볼릭 링크가 아님)을 포함합니다.

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

이것들은 빈 마커 파일일 뿐이므로 디스크 사용량은 무시할 수 있는 수준입니다.

## 공유된 blob 저장소

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`와 공유 파일은 매니페스트에 나열된 심볼릭 링크가 더 이상 해당 파일로 가리키지 않는 경우에만 삭제합니다
- manifest가 없거나 읽을 수 없는 경우, 공유 파일을 유지합니다

삭제 동작 및 이전 버전 클라이언트의 제한 사항에 대해서는 [`huggingface_hub` 캐시 가이드](https://huggingface.co/docs/huggingface_hub/guides/manage-cache#shared-blobs-across-repos)를 참조하세요.

## 락 파일

Lock 파일은 동시 실행 중인 프로세스가 동일한 blob을 동시에 다운로드하는 것을 방지합니다. 캐시 루트(리포지토리 폴더 내부가 아닌)의 `.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`가 두 리비전 모두에서 **동일한 blob**을 가리키도록 하는 방식을 주목해 보세요. 321 MB 파일은 디스크에 한 번만 저장됩니다.

## 파일 해석 로직

디스크에서 캐시된 파일을 찾으려면:

1. **리비전을 커밋 해시로 해석**
   - 수정판이 이미 40자 16진 문자열인 경우, 이를 그대로 사용합니다.
   - 그렇지 않은 경우, `refs/{revision}`에 있는 파일을 읽어서 커밋 해시를 가져옵니다

2. **스냅샷 확인**
   - `snapshots/{commit_hash}/{relative_path}`를 찾아보세요
   - (심볼릭 링크 또는 파일로) 존재하는 경우, 해당 파일은 캐시에 저장됩니다. 심볼릭 링크를 따라가면 콘텐츠를 가져올 수 있습니다

3. **존재하지 않는지 확인**
   - `.no_exist/{commit_hash}/{relative_path}`을(를) 찾아보세요
   - 존재하는 경우, 해당 파일은 이 리비전에서는 Hub에 존재하지 않는 것으로 알려져 있습니다

4. **캐시 미스**
   - 두 경로 모두 존재하지 않으면, 해당 파일은 아직 캐시되지 않은 것입니다.

## Windows 동작

캐시는 **심볼릭 링크**에 의존합니다. 심볼릭 링크를 사용할 수 없는 Windows 시스템에서는 캐시가 **제한된 모드**로 작동합니다. 이 모드에서는 심볼릭 링크 대신 실제 파일 사본이 `snapshots/`에 직접 배치됩니다. `blobs/` 디렉터리와 [공유 blob 저장소](#shared-blob-store)는 이 모드에서 사용되지 않습니다.

이는 동일한 파일 내용이 리비전 간에 중복되어 디스크 사용량이 증가할 수 있음을 의미합니다. Windows에서 심볼릭 링크(symlink) 지원을 활성화하려면 [개발자 모드](https://docs.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development)를 활성화하거나 관리자로 실행하세요.

