# Caché local de Hub

Este documento describe la estructura en disco de la caché local de HF Hub. Está destinado a servir como referencia para reimplementar el sistema de caché en cualquier lenguaje.

A continuación se muestra una lista parcial de bibliotecas y aplicaciones que utilizan esta estructura de caché. Abra un PR para agregar la suya.

### Bibliotecas

| Biblioteca | Idioma | Notas |
|---------|----------|-------|
| [`huggingface_hub`](https://github.com/huggingface/huggingface_hub) | Python | Y cualquier biblioteca que dependa de ella (p. ej. `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 | Solo Node.js |
| [`SMILE`](https://github.com/haifengl/smile) | Java | Consulte la documentación de [`HuggingFaceHub`](https://haifengl.github.io/api/java/smile/util/HuggingFaceHub.html) |

### Aplicaciones

| Aplicación | Idioma | Notas |
|-------------|----------|-------|
| [`llama.cpp`](https://github.com/ggml-org/llama.cpp) | C++ | Desde [#20775](https://github.com/ggml-org/llama.cpp/pull/20775) |
| [`Llama-macOS`](https://github.com/ggml-org/Llama-macOS) | Swift | Aplicación para macOS, construida sobre `llama.cpp` |
| [`HuggingFaceModelDownloader`](https://github.com/bodaay/HuggingFaceModelDownloader) | Ir | |
| [`oMLX`](https://github.com/jundot/omlx) | Python | Servidor de inferencia MLX para macOS, desde [v0.4.0](https://github.com/jundot/omlx/commit/341aebbeaba1ca3452f83dedf03a0d102cef9fd4) |

## Ubicación de la caché

El directorio de caché predeterminado es:

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

Esto se puede anular con variables de entorno:
- `HF_HUB_CACHE` - ruta directa al directorio de caché (tiene prioridad)
- `HF_HOME` - ruta al directorio principal de Hugging Face; si se establece, la caché se encuentra en `$HF_HOME/hub`

## Resumen

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

Cada repositorio descargado obtiene una única carpeta plana. Dentro de cada carpeta de repositorio, los archivos se almacenan una vez en el directorio `blobs/` con dirección de contenido y se accede a ellos a través de enlaces simbólicos en `snapshots/`. Las referencias con nombre (ramas, etiquetas) se rastrean en `refs/`. Desde `huggingface_hub` 1.32, los archivos descargados con `hf_xet` se almacenan una vez para todo el caché, en un [almacén de blobs compartido](#shared-blob-store) en la raíz del caché. La entrada `blobs/` del repositorio es entonces un enlace simbólico a ese archivo.

## Esquema

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

## Nomenclatura de carpetas del repositorio

Los repositorios se almacenan como directorios planos en la raíz de la caché. El nombre de la carpeta codifica el tipo de repositorio y el ID del repositorio:

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

Reglas:
- El tipo de repositorio se **pluraliza**: `models`, `datasets`, `spaces`
- Las barras diagonales (`/`) en el ID del repositorio se reemplazan por `--`
- El separador entre todas las partes es `--`
- Se preserva la capitalización

Ejemplos:

| ID del repositorio de Hub                          | Tipo de repositorio | Nombre de la carpeta de caché                            |
|--------------------------------------|-----------|----------------------------------------------|
| `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]
> Los buckets no se gestionan con esta caché, ya que no están respaldados por git. Utilice en su lugar el comando dedicado `hf buckets sync`.

## Dentro de una carpeta de repositorio

Cada repositorio en caché tiene la misma estructura interna:

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

### `blobs/`: almacenamiento de archivos con dirección de contenido

El directorio `blobs/` almacena el contenido real de los archivos. Cada archivo se nombra según su etag de archivo en el Hub:

- **Archivos controlados por Git**: identificados por su hash **SHA-1** (40 caracteres hexadecimales)
- **Archivos de Git LFS**: nombrados por su hash **SHA-256** (64 caracteres hexadecimales)

Este es un directorio plano, sin subdirectorios. Los archivos idénticos entre distintas revisiones se almacenan solo una vez. Una entrada es un archivo regular o un enlace simbólico relativo al [almacén de blobs compartidos](#shared-blob-store); los lectores deben seguirlo.

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

### `refs/`: referencias de ramas y etiquetas

El directorio `refs/` asocia las referencias legibles por humanos (nombres de ramas, etiquetas, números de PR) con los hashes de commit.

Cada referencia es un archivo de texto plano que contiene una única línea: el hash de commit completo (40 caracteres hexadecimales).

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

Cuando se descarga un archivo usando el nombre de una rama o etiqueta, el archivo de referencia correspondiente se crea o actualiza con el hash del último commit.

### `snapshots/`: vistas de revision

El directorio `snapshots/` contiene un subdirectorio por cada revisión en caché (hash de commit). Cada directorio de revisión replica la estructura de archivos del repositorio en el Hub, pero los archivos son **enlaces simbólicos** que apuntan a `../../blobs/{hash}`.

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

Propiedades clave:
- Los enlaces simbólicos usan **rutas relativas**: `../../blobs/{hash}`
- Si un archivo no cambia entre dos revisiones, ambos enlaces simbólicos apuntan al **mismo blob** sin duplicación de datos
- Los archivos en subdirectorios en el Hub se representan como subdirectorios en la instantánea (se preserva la ruta relativa completa)

Cambiar entre instantáneas es similar a usar `git checkout` en un repositorio git local.

### `.no_exist/`: caché de inexistencia

El directorio `.no_exist/` registra los archivos que se solicitaron pero que no existen en el Hub. Esto evita solicitudes HTTP repetidas para archivos opcionales.

La estructura refleja la de `snapshots/`: un subdirectorio por hash de commit, que contiene **archivos vacíos** (no enlaces simbólicos) con el nombre del archivo que falta.

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

El uso del disco es despreciable, ya que se trata únicamente de archivos marcadores vacíos.

## Almacenamiento de blobs compartido

Los archivos descargados a través de Xet se deduplican entre repositorios. Dicho archivo se almacena una sola vez en la raíz de la caché, bajo `blobs/`, y cada repositorio que lo necesita recibe un enlace simbólico relativo en lugar de una copia:

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

Propiedades clave:
- La entrada del repositorio conserva su nombre etag y se convierte en un **enlace simbólico relativo**: `../../blobs/{xet_hash[:2]}/{xet_hash}`
- El diseño de `snapshots/` no cambia, por lo que la [resolución de archivos](#file-resolution-logic) no se ve afectada
- El archivo marcador identifica el almacén: un directorio `blobs/` no vacío en la raíz de la caché sin él nunca se usa ni se modifica
- El almacén requiere enlaces simbólicos y `hf_xet`. Si el proceso de compartir falla, el archivo se guarda en la carpeta del repositorio como antes. `HF_HUB_DISABLE_SHARED_BLOBS=1` desactiva el almacén
- Solo las descargas nuevas pasan por la tienda. Los archivos escritos por versiones anteriores u otros clientes permanecen como archivos regulares en la carpeta del repositorio, por lo que una caché puede contener ambos

Las implementaciones que solo leen la caché no requieren cambios: seguir los enlaces simbólicos funciona como antes.

Las implementaciones que eliminan elementos de la caché deben tratar un blob de repositorio que es un enlace simbólico como una referencia al almacén:
- Elimina el enlace simbólico en `blobs/` del repositorio, no el archivo compartido al que apunta
- Bloquear `<xet_hash>.lock` antes de comprobar el manifiesto y eliminar
- Elimina el archivo compartido y su `.refs` solo si ningún enlace simbólico listado en el manifiesto sigue apuntando a él
- Si el manifiesto no existe o no se puede leer, mantenga el archivo compartido

Consulte la [guía de la caché de `huggingface_hub`](https://huggingface.co/docs/huggingface_hub/guides/manage-cache#shared-blobs-across-repos) para conocer el comportamiento de eliminación y las limitaciones con clientes más antiguos.

## Archivos de bloqueo

Los archivos de bloqueo impiden que procesos concurrentes descarguen el mismo blob simultáneamente. Se almacenan en un directorio `.locks/` en la raíz de la caché (no dentro de la carpeta del repositorio):

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

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

## Ejemplo completo

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

Observe cómo `pytorch_model.bin` apunta al **mismo blob** en ambas revisiones. El archivo de 321 MB se almacena solo una vez en el disco.

## Lógica de resolución de archivos

Para localizar un archivo en caché en el disco:

1. **Resolver la revisión a un hash de commit**
   - Si la revisión ya es una cadena hexadecimal de 40 caracteres, úsela directamente
   - En caso contrario, lee el archivo en `refs/{revision}` para obtener el hash del commit

2. **Comprobar la instantánea**
   - Busque `snapshots/{commit_hash}/{relative_path}`
   - Si existe (como un enlace simbólico o un archivo), el archivo se almacena en caché. Siga el enlace simbólico para obtener el contenido

3. **Comprobar la inexistencia**
   - Busque `.no_exist/{commit_hash}/{relative_path}`
   - Si existe, se sabe que el archivo no está presente en el Hub para esta revisión

4. **Fallo de caché**
   - Si ninguna de las rutas existe, el archivo aún no se ha almacenado en caché

## Comportamiento en Windows

La caché se basa en **enlaces simbólicos**. En sistemas Windows donde los enlaces simbólicos no están disponibles, la caché opera en un **modo degradado**: se colocan copias reales de los archivos directamente en `snapshots/` en lugar de enlaces simbólicos. El directorio `blobs/` y el [almacén de blobs compartido](#shared-blob-store) no se utilizan en este modo.

Esto significa que el mismo contenido de archivo puede duplicarse entre revisiones, lo que aumenta el uso del disco. Para habilitar el soporte de enlaces simbólicos en Windows, active [Developer Mode](https://docs.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development) o ejecute como administrador.

