DEV Community

Jasur Yuldoshev
Jasur Yuldoshev

Posted on

Your Hugging Face download isn't stuck — you're being rate-limited

I killed a perfectly healthy 2 GB model download three times before I understood what was happening. Each time the same picture: the first 100–200 MB fly by in about a minute, then the progress bar freezes at 0 B/s. Not slow — zero. A minute passes. Five. Ten. Any reasonable person concludes the download is dead, kills the process, and tries again. And again the first 200 MB arrive instantly, and again everything stops.

The download was fine. I was being rate-limited, and everything about how that presents itself is designed — unintentionally, I assume — to convince you it's a hang.

The part where I blamed the VPN

My setup at the time went through a VPN, so naturally the VPN got blamed first. It's the obvious suspect: flaky route, dropped connection, MTU weirdness, pick your favorite.

But the evidence didn't fit, and it took me embarrassingly long to notice. The first burst always came through at full speed. A broken network path doesn't hand you 200 MB in a minute and then die at exactly the same point every retry. Broken networks are random. This was punctual. Whatever was stopping the download lived on the other end and had a policy about it.

That's the diagnostic worth remembering, because it applies to a lot more than Hugging Face: a fast start followed by a consistent stall is a limiter, not a failure. Random is hardware; punctual is policy.

The warning everyone scrolls past

Once I stopped blaming infrastructure and read my own logs from the top, the answer was sitting in plain sight, printed once at the very beginning:

You are sending unauthenticated requests to the HF Hub.
Please set a HF_TOKEN to enable higher rate limits and faster downloads.
Enter fullscreen mode Exit fullscreen mode

It scrolls by in the first second, right before hundreds of progress-bar updates bury it. It reads like boilerplate — every tool prints some variation of "log in for a better experience", and we've all learned to ignore that sentence shape. But this one is literal.

Since then Hugging Face has actually published official numbers, and they explain the shape of what I saw. All quotas run in fixed 5-minute windows — which is precisely why the pattern is burst, wall, burst. File downloads ("resolver" requests) get 3,000 requests per window for anonymous users, and a big model is nowhere near one request: multiple files, ranged chunks, retries and redirects all count. Blow through the window and the Hub answers 429 with a RateLimit header saying exactly how long until reset.

And here's the detail that turns a rate limit into a "hang": recent huggingface_hub (1.2+) reads that header and silently sleeps until the window resets, then retries. Older versions sit in exponential backoff. Either way, what you see is a progress bar frozen at 0 B/s while the client obediently waits out its penalty. Nothing is printed. It's the correct behavior, and it looks exactly like a dead download. For the model I was pulling — about 2.2 GB, stored as both safetensors and pytorch bins, so effectively downloaded twice — that turned an expected 3–5 minutes into 15–25.

One more trap hiding in there: the anonymous quota is shared per IP address. Behind an office NAT, a university network or a busy CI runner, you're splitting those 3,000 requests with everyone else on the same address. That's how you get "works from home, stalls at work" — and, to be fair, the reverse mystery too: some anonymous users pull terabytes and never see a stall. Whether you hit the wall depends on who you share an IP with. It can even get sillier than a slow download: there's a llama.cpp issue where hitting the limit made the client unable to use a model it had already downloaded, because it checked freshness against the Hub before loading the cache.

How to check whether it's alive

Before killing anything, look at the cache. Hugging Face downloads land in content-addressed blobs:

ls -lh ~/.cache/huggingface/hub/models--<org>--<name>/blobs/
Enter fullscreen mode Exit fullscreen mode

There'll be a file ending in .incomplete. Note its size, wait a few minutes, look again. Flat, then a jump of a few hundred megabytes, then flat again — that's the window cycle, and the process (sitting quietly in sleep/IO-wait, not spinning CPU) will eventually get there. And because blob names are content hashes, partial files survive a restart — resume actually resumes.

The fixes

The token. A free account moves you from the shared anonymous pool to a per-user quota (and bumps resolvers to 5,000 per window). That per-user part is the real win — nobody else's CI can eat your budget anymore:

hf auth login          # huggingface_hub 1.0+
huggingface-cli login  # older installs
# or just: export HF_TOKEN=hf_...
Enter fullscreen mode Exit fullscreen mode

If this had been the first line of that warning message in bold red, this article wouldn't exist.

The mirror. For public open-weight models there's a fix that needs no account at all:

HF_ENDPOINT=https://hf-mirror.com python your_script.py
Enter fullscreen mode Exit fullscreen mode

hf-mirror.com is a long-running public mirror (still alive and growing as of 2026). It speaks the same protocol, so resume picks up your existing partial blobs. Since you're downloading tensors, not executable code, and integrity is checked against hashes, the trust story is manageable — a judgment call I'd only make for public weights, though, never for anything sensitive.

The speed flag — but check your version. This one changed under everyone's feet. The classic advice — pip install hf_transfer + HF_HUB_ENABLE_HF_TRANSFER=1 — is dead in huggingface_hub 1.0+: the Xet backend replaced it, and the old env var is now silently ignored, which is its own little gotcha-inside-a-gotcha. On current installs the equivalent knob is HF_XET_HIGH_PERFORMANCE=1 (meant for fat pipes and machines with RAM to spare). On pre-1.0 installs, the old hf_transfer advice still applies. Neither raises your rate limit — they just make the bytes flow faster between penalties.

The product-grade conclusion

I hit all of this building a desktop app whose backend pulls models on first run — which means my users would hit it too, on their machines, on their network routes, sharing IPs with strangers, with no idea what an HF_TOKEN is.

So the durable lesson for anything you ship to end users: don't make your users talk to Hugging Face at all. Host the models you depend on yourself — object storage behind a CDN, a manifest with SHA-256 checksums, resumable downloads. First-run should depend on your infrastructure, not on the rate-limit policy of a third party toward an anonymous user you'll never get to debug.

For your own machine, though, the whole fix is one line in your shell profile. Set the token, or set the mirror — and the next time a download sits at 0 B/s, check the .incomplete file before you reach for Ctrl-C. It's probably not dead. It's waiting for a five-minute window to roll over, and unlike you, it knows exactly how long that takes.

Top comments (0)