Every Hub repo is a git repo, which means every unpinned from_pretrained is a dependency on a moving target.
ML Tooling
NLP
HuggingFace
Author
Ravi Kalia
Published
April 6, 2025
Hugging Face Hub Python SDK
pipeline("sentiment-analysis", model="distilbert/distilbert-base-uncased-finetuned-sst-2-english") looks like a pinned dependency. It is not. It names a repository, not a version, and it resolves to whatever the main branch of that repository points at the moment you run it. If the owner pushes new weights tomorrow, your code silently gets them.
This is not a flaw so much as an unadvertised consequence of the Hub’s actual design: every repo — model, dataset, or Space — is a git repository with large files in LFS. Once you see that, the SDK stops looking like a download API and starts looking like a git client, and the reproducibility question becomes the same one you already know how to answer.
The repo is literally a git repo
The SDK will tell you this directly. Every repository has branches, commits, and a resolvable head:
Code
from huggingface_hub import HfApiREPO ="distilbert/distilbert-base-uncased-finetuned-sst-2-english"api = HfApi()info = api.model_info(REPO)print(f"current head : {info.sha}")print(f"last modified: {info.lastModified}")refs = api.list_repo_refs(REPO)print(f"branches : {[b.name for b in refs.branches]}")print("\nrecent commits:")for commit inlist(api.list_repo_commits(REPO))[:3]:print(f" {commit.commit_id[:8]}{str(commit.created_at)[:10]}{commit.title[:48]}")
current head : 714eb0fa89d2f80546fda750413ed43d93601a13
last modified: 2023-12-19 16:29:37+00:00
branches : ['main']
recent commits:
714eb0fa 2023-12-19 Change loaded mode to correct on that this model
4643665f 2023-10-26 Adding ONNX file of this model (#23)
3d65bad4 2023-03-21 Addition of paper (#21)
A commit history, exactly as you would get from git log. The sha is the thing your unpinned call is implicitly resolving to — and the thing you can pin to instead.
Pinning is one argument
Everything in the ecosystem that loads from the Hub takes a revision, and it accepts a branch, a tag, or a commit sha. Passing a sha turns a moving reference into a fixed one:
Code
from transformers import pipelinePINNED = info.sha # resolved above; hard-code this in real workclassifier = pipeline("sentiment-analysis", model=REPO, revision=PINNED)print(classifier("I love the Hugging Face Hub!"))
Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.
The prediction is the same one the unpinned call would give today. The difference is that this one will still give it next year. Note the deliberate inconsistency in the cell above: pinning to info.sha is only pinning to “whatever was current when this ran”, which is exactly the thing being argued against — in real code you paste the literal sha.
Caveat: pinning does not make a model immutable
A sha pins content, not availability. In August 2024 the runwayml/stable-diffusion-v1-5 repository — one of the most depended-upon repos on the Hub — was removed by its owner, and the weights moved to stable-diffusion-v1-5/stable-diffusion-v1-5. Pinned and unpinned code broke alike, because there was nothing left to resolve against. A redirect now covers the old id, but redirects are a courtesy, not a guarantee.
The lesson is the one every package ecosystem has learned: pin for reproducibility, but if you actually cannot tolerate the dependency disappearing, hold your own copy.
Downloading is a git checkout
snapshot_download is the SDK’s git clone, and it takes the same revision. It fetches the whole repo to the local cache and hands back a path:
Code
from huggingface_hub import snapshot_downloadimport ospath = snapshot_download(repo_id="google/flan-t5-small", revision="main")files =sorted(os.listdir(path))print(f"{len(files)} files at {os.path.basename(path)}")print(files[:8])
The cache is content-addressed by commit, so downloading two revisions of the same repo stores both and shares any unchanged files between them. This is also why the cache directory grows the way it does — it is keeping history, not just the latest.
Datasets resolve the same way
The Dataset Hub is the same git substrate with a different loader on top, which means the same pinning argument applies. It also means dataset ids follow the same namespace/name rule — and as of datasets 5.x, the bare aliases that used to work ("ag_news", "imdb", "glue") now raise HfUriError:
Code
from datasets import load_datasetnews = load_dataset("fancyzhx/ag_news", split="train")print(news)print(f"\nfirst row: {news[0]['text'][:90]}...")
Dataset({
features: ['text', 'label'],
num_rows: 120000
})
first row: Wall St. Bears Claw Back Into the Black (Reuters) Reuters - Short-sellers, Wall Street's d...
About this corpus. AG News is a subset of Antonio Gulli’s news crawl, packaged for classification by Zhang, Zhao and LeCun (2015) to benchmark character-level convolutional networks. It is 120,000 training and 7,600 test headlines-plus-descriptions, balanced across four topic classes — World, Sports, Business, Sci/Tech — chosen as the four largest categories in the crawl.
Two things follow from how it was built. The balance is an artefact of the packaging, not of the news: real newswire is not a quarter sports. And the articles date from around 2004, so a topic classifier trained here has never seen a smartphone, a social network, or a pandemic — “Sci/Tech” means something measurably different now. If a decision downstream routed real stories using this model, the errors would concentrate in exactly the categories that have drifted most, and test accuracy on this frozen corpus would report none of it. It is used here because it is small, public and pinned — properties that matter for demonstrating versioning, and that have nothing to do with whether it is fit for classification today.
Publishing closes the loop
Writing works the same way, and the git framing keeps holding: create_repo is git init, upload_file is a commit. The following is not executed here — it would push to a real account — but it is the whole API:
Each upload_file is one commit on main, and each commit is a sha someone else can pin to. Authentication is hf auth login once, which writes a token to the same cache directory the downloads use.
What the git framing is worth
The claim at the top was that an unpinned from_pretrained is a dependency on a moving target. The Hub being git is what makes that true, and also what makes it fixable: the same call takes a revision, the history is inspectable before you choose one, and the local cache keeps revisions side by side rather than overwriting.
Where the analogy stops is deletion and trust. Git gives you content-addressing within a repo that still exists; it gives you nothing if the remote disappears, and a sha certifies that bytes are unchanged, not that they were ever safe to run. from_pretrained on an untrusted repo executes that repo’s config against your process — pinning a sha to a repo you have not read is reproducibly running someone else’s code.