Skip to content

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.

Terminal window
NORA_HF_ENABLED=true nora serve

If 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 = true
proxy = "https://huggingface.co"
proxy_timeout = 30 # seconds
cdn_hosts = ["hf.co", "huggingface.co"] # where a file redirect may go

Environment variables:

VariableDescriptionDefault
NORA_HF_ENABLEDEnable the Hugging Face proxyfalse
NORA_HF_PROXYUpstream Hub URL; empty disables the upstreamhttps://huggingface.co
NORA_HF_PROXY_TIMEOUTUpstream timeout in seconds30
NORA_HF_CDN_HOSTSHosts a file redirect may go to, comma-separated: each entry and the names under ithf.co,huggingface.co

Point huggingface_hub at NORA with HF_ENDPOINT:

Terminal window
export HF_ENDPOINT=http://nora.example.com:4000/hf
hf download openai-community/gpt2 # a whole snapshot
hf download openai-community/gpt2 config.json # one file
hf download openai-community/gpt2 config.json --revision refs/pr/198

The 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.

  • 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 Location reaches 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 of cdn_hosts — the Hub’s CDN, us.aws.cdn.hf.co, is under hf.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, so localhost or an internal name does not belong in a production list. A redirect anywhere else is a 502, and that host is never asked. A mirror whose files live on another host needs that host in cdn_hosts.
  • Always fresh. The Hub’s resolve answer is asked on every download: it names the commit and the file’s digest. main therefore 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. xetHash is removed from metadata at any depth, Xet headers and links never reach the client, and the Xet token endpoints answer 404.
EndpointMethodStatusNotes
/hf/api/models/{repo}GETFullModel info, without xetHash
/hf/api/models/{repo}/revision/{rev}GETFullRevision as the client encoded it (refs%2Fpr%2F1 stays one segment)
/hf/api/models/{repo}/tree/{rev}[/{path}]GETFullPaginated listing; Link rel="next" points under /hf
/hf/{repo}/resolve/{rev}/{file}HEADFullAnswered from the Hub’s resolve answer (commit, etag, size)
/hf/{repo}/resolve/{rev}/{file}GETFullCached by digest; Range supported
/hf/api/models/{repo}/xet-read-token/…, xet-write-token/…GETRefused (404)NORA never serves Xet
/hf/api/datasets/…, /hf/datasets/…*— (501)Models only
Uploads (POST, PUT, DELETE, PATCH)*— (405)Proxy-only

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.

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.

  • 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-Code and 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 429 is handed on with Retry-After, RateLimit and RateLimit-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.
  • HfFileSystem is not measured; its paths-info call is a POST and answers 405.
  • An alias and its canonical id are cached separately (gpt2 and openai-community/gpt2).
  • Web UI: the dashboard shows the format, but cached files are not listed yet.
  • nora backup includes the cache — every stored weight file. There is no option to leave hf/ out yet, so on a large cache keep it within proxy_cache_max_bytes or a retention rule before a backup.
  • S3: set an AbortIncompleteMultipartUpload lifecycle 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).