Hugging Face Hub
NORA can be a pull-through cache for public models on the Hugging Face Hub. Model info, revisions and file listings are asked of the Hub on every request; model files are cached by the digest the Hub names them by and checked against it on the way in. This is a proxy-only registry, and it is opt-in: off by default, and left out of all. Available since NORA 1.4.0.
Enable
Section titled “Enable”NORA_HF_ENABLED=true nora serveIf you select registries with NORA_REGISTRIES_ENABLE (or [registries] enable), name it there: all leaves the Hugging Face proxy off, all,hf turns it on together with everything else.
config.toml:
[hf]enabled = trueproxy = "https://huggingface.co"proxy_timeout = 30 # secondscdn_hosts = ["hf.co", "huggingface.co"] # where a file redirect may goEnvironment variables:
| Variable | Description | Default |
|---|---|---|
NORA_HF_ENABLED | Enable the Hugging Face proxy | false |
NORA_HF_PROXY | Upstream Hub URL; empty disables the upstream | https://huggingface.co |
NORA_HF_PROXY_TIMEOUT | Upstream timeout in seconds | 30 |
NORA_HF_CDN_HOSTS | Hosts a file redirect may go to, comma-separated: each entry and the names under it | hf.co,huggingface.co |
Client Configuration
Section titled “Client Configuration”Point huggingface_hub at NORA with HF_ENDPOINT:
export HF_ENDPOINT=http://nora.example.com:4000/hf
hf download openai-community/gpt2 # a whole snapshothf download openai-community/gpt2 config.json # one filehf download openai-community/gpt2 config.json --revision refs/pr/198The Python API reads the same variable:
from huggingface_hub import hf_hub_download, snapshot_download
hf_hub_download("openai-community/gpt2", "config.json")snapshot_download("openai-community/gpt2")Measured with huggingface_hub 0.36.0, 1.0.0, 1.33.0, 2.0.0, 2.1.1 and 2.2.0, and with 2.1.1 against huggingface.co: a regular file, an LFS file of a Xet-backed repository, a legacy repository id (gpt2), revisions refs/pr/N and a commit — every request through NORA. With hf_xet installed the client still downloads over plain HTTP: NORA never advertises Xet.
How It Works
Section titled “How It Works”- Redirects stay inside NORA. The Hub answers a file request with a redirect — a relative 307 to its resolve cache, or a 302 to its CDN for an LFS file. NORA follows them itself, so no
Locationreaches the client and every byte comes through NORA. A redirect is followed only to the upstream’s own origin or, over https, to a host ofcdn_hosts— the Hub’s CDN,us.aws.cdn.hf.co, is underhf.co. An IP address matches as an address, never as a suffix, and http to a CDN host is followed only when the whole walk is http, from an http upstream on — never down from https. An entry admits every port of its host, solocalhostor an internal name does not belong in a production list. A redirect anywhere else is a502, and that host is never asked. A mirror whose files live on another host needs that host incdn_hosts. - Always fresh. The Hub’s resolve answer is asked on every download: it names the commit and the file’s digest.
maintherefore never goes stale, and the bytes come from the cache when NORA already holds that digest. - Cached by digest. Files are kept per repository under the digest the Hub names them by (the sha256 of an LFS file, the git blob sha1 of a regular one):
hf/models/{repo}/blobs/{digest}. A weight file that does not change across commits is downloaded once. - Checked on the way in. A file is checked against that digest with its last chunk held back until it matches: a corrupt file ends one chunk short and is not stored. A file read back from the cache is checked against its pin the same way, and a copy that fails is dropped, so the next download fetches it again. A range read from the cache is not checked (a part cannot be rehashed), as for every format.
- Passed through, not stored: a range request on a file not yet cached, a
HEAD, and a file whose etag names no digest. A client that leaves early leaves nothing behind. - No Xet.
xetHashis removed from metadata at any depth, Xet headers and links never reach the client, and the Xet token endpoints answer404.
Features
Section titled “Features”| Endpoint | Method | Status | Notes |
|---|---|---|---|
/hf/api/models/{repo} | GET | Full | Model info, without xetHash |
/hf/api/models/{repo}/revision/{rev} | GET | Full | Revision as the client encoded it (refs%2Fpr%2F1 stays one segment) |
/hf/api/models/{repo}/tree/{rev}[/{path}] | GET | Full | Paginated listing; Link rel="next" points under /hf |
/hf/{repo}/resolve/{rev}/{file} | HEAD | Full | Answered from the Hub’s resolve answer (commit, etag, size) |
/hf/{repo}/resolve/{rev}/{file} | GET | Full | Cached by digest; Range supported |
/hf/api/models/{repo}/xet-read-token/…, xet-write-token/… | GET | Refused (404) | NORA never serves Xet |
/hf/api/datasets/…, /hf/datasets/… | * | — (501) | Models only |
Uploads (POST, PUT, DELETE, PATCH) | * | — (405) | Proxy-only |
Cache Size and Retention
Section titled “Cache Size and Retention”Cached files count toward [gc] proxy_cache_max_bytes (NORA_GC_PROXY_CACHE_MAX_BYTES) together with proxy-cached RPM and Debian packages; above the cap GC evicts the oldest files first. Retention rules that name hf prune them (older_than_days, and keep_last counted in files); a * rule does not apply to this opt-in format.
Curation
Section titled “Curation”A download is named by its repository id and versioned by its commit. A rule on the repository refuses before the Hub is asked; a rule on a commit holds for main when main resolves to it, also for a file already cached. An alias — a legacy id such as gpt2, another case, a renamed repository — is checked under its canonical id too (openai-community/gpt2), which NORA learns from the Hub; if NORA cannot learn it, enforcing curation refuses the download (502), while audit mode logs it under the requested id and goes on. An allowlist of commits lets nothing through without a commit. curation.min_release_age with the Hugging Face proxy enabled is refused at start: a Hub commit has no publish date NORA can read. See Curation.
Known Limitations
Section titled “Known Limitations”- Models only: datasets and Spaces are not served (
501). - Public repositories only: a client’s token is never sent upstream, so a gated or private repository answers with the Hub’s own refusal (its status,
X-Error-Codeand message). - Shared rate limit: NORA asks the Hub without a token, so the Hub’s anonymous rate limits are shared by every client behind NORA, and a cached file still costs one resolve request. A
429is handed on withRetry-After,RateLimitandRateLimit-Policy, by which huggingface_hub waits for the window to reset. - The Hub must be reachable for a download: the resolve answer is asked every time, and there is no stale serving yet.
HfFileSystemis not measured; itspaths-infocall is aPOSTand answers405.- An alias and its canonical id are cached separately (
gpt2andopenai-community/gpt2). - Web UI: the dashboard shows the format, but cached files are not listed yet.
nora backupincludes the cache — every stored weight file. There is no option to leavehf/out yet, so on a large cache keep it withinproxy_cache_max_bytesor a retention rule before a backup.- S3: set an
AbortIncompleteMultipartUploadlifecycle rule on the bucket. A cache fill cut off during its upload (a restart) leaves multipart parts that the object store keeps until they are aborted. - Uploads through NORA are not supported (
405).