Skip to content

Resolve the Hub revision once per load instead of passing a private _commit_hash around - #47611

Draft
Wauplin wants to merge 1 commit into
mainfrom
agent/resolve-revision-once
Draft

Resolve the Hub revision once per load instead of passing a private _commit_hash around#47611
Wauplin wants to merge 1 commit into
mainfrom
agent/resolve-revision-once

Conversation

@Wauplin

@Wauplin Wauplin commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

CPU CI GPU run-slow

Follow-up on huggingface_hub v1.26.0, which shipped resolve_revision / ResolvedRevision. Same change as vllm-project/vllm#49990 (in production since) and huggingface/diffusers#14340. Alternative to #47583, same goal, opposite approach.

Important

Blocked on huggingface/huggingface_hub#4767, which makes a ResolvedRevision only pin the repo it was resolved for. The huggingface_hub minimum here is set to 1.30.0 and needs adjusting to whatever release ships it.

What

Loading a model, a tokenizer or a pipeline fetches many files from the same repo one by one (config.json, the weight index, each shard, the tokenizer files, remote code, adapters, ...). Each of those lookups resolves revision="main" on its own, and nothing guarantees that two of them land on the same commit.

transformers already had a mechanism against this — the private _commit_hash kwarg, threaded from call to call — but it has to be passed explicitly everywhere and has been dropped in several loading paths over time (#47583 is the fourth or fifth round of putting it back).

This PR resolves the revision once, at the top of every public loading entry point, and passes the resulting ResolvedRevision down as the regular revision argument. revision is already plumbed everywhere, so nothing else needs threading around and _commit_hash is removed entirely. As ResolvedRevision is a str subclass whose string value stays the revision the user asked for, URLs and error messages are unchanged.

  • utils.hub.resolve_revision wraps HfApi.resolve_revision: no-op for local folders and for callers passing proxies (the shared HfApi client cannot honor per-call proxies), and fail-open — on any Hub error the revision the caller asked for is used as is, so the regular loading path reports the problem with its usual message.
  • Called from PreTrainedConfig, GenerationConfig, PreTrainedModel, PreTrainedTokenizerBase, MistralCommonBackend, ProcessorMixin, ImageProcessingMixin, BaseVideoProcessor, FeatureExtractionMixin, every Auto* class, pipeline, load_adapter and the Bark/Ernie special paths.
  • A load spanning two repositories (an adapter and its base model, a pipeline component, a config given as a separate repo id) resolves each repository on its own. Nothing special is needed for that: a ResolvedRevision is only trusted by the repo it was resolved for, so a component loaded from elsewhere resolves the revision that was requested.
  • cached_files trusts the cache whenever the revision pins a commit, for present and known-missing files, so no HEAD is sent for something the cache already knows about.
  • RequestCounter counted urllib3 log lines, which observes nothing since the Hub client moved to httpx; it now counts httpx requests. The two test_cached_*_has_minimum_calls_to_head tests that were skipped as "failing on main" are enabled again.
  • Minimum huggingface_hub bumped: #4692 is needed as well (the config kwargs of a load are deep-copied, which would otherwise lose the resolved commit).

Measurements

HTTP calls on a warm cache, counted by wrapping httpx.Client.send:

main this PR
AutoModel.from_pretrained 4 1
AutoModel.from_pretrained (sharded) 4 1
AutoTokenizer.from_pretrained 6 3
pipeline("text-classification") 9 3
any of the above, revision=<commit hash> 0 0

The 3 remaining calls are the revision resolution plus the two repository listings the tokenizer uses to discover its chat templates and vocabulary files. A cold cache is a wash: one repo_info more, a few HEADs less (9 → 8 for a model, 28 → 27 for the pipeline).

Behavior changes worth a look during review

  • config._commit_hash and generation_config._commit_hash no longer exist. They were private, but they were readable.
  • BarkProcessor used to pop the download kwargs, so its tokenizer was loaded without the requested revision and only the first of the three voice preset files honored them. Both now use the resolved revision.

Testing

tests/utils, tests/models/auto, tests/tokenization, tests/peft_integration and tests/pipelines/test_pipelines_common.py pass, except failures that reproduce on main in the same environment. Error messages and loaded objects were also compared against main on ~30 scenarios (bad revision, missing repo, gated repo, no weights, offline mode, local folder, cross-repo config/tokenizer, adapters loaded through an Auto* class, a concrete class and load_adapter): identical, only the request counts change.

AI assistance

This draft was prepared with AI assistance. The submitting human should review and understand every changed line before marking it ready for review.

@HuggingFaceDocBuilderDev

Copy link
Copy Markdown

The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

@Wauplin

Wauplin commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

let's wait for huggingface/huggingface_hub#4604 to be shipped first

@github-actions

Copy link
Copy Markdown
Contributor

[For maintainers] Suggested jobs to run (before merge)

run-slow: auto, bark, ernie4_5_vl_moe

Replace the private `_commit_hash` plumbing by resolving the requested
revision with `HfApi.resolve_revision` at each public loading entry
point, then passing the returned `ResolvedRevision` around as the
regular `revision` argument.

Signed-off-by: Wauplin <lucainp@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Wauplin
Wauplin force-pushed the agent/resolve-revision-once branch from 5c02c9e to 9adb84e Compare August 27, 2026 17:21
@github-actions

Copy link
Copy Markdown
Contributor

Thank you for your contribution 🤗!

CI Security Gate — automatic approval blocked

This PR was not automatically approved for CI because the security gate failed.

Possible reasons:

  • The PR touches 50 or more files — only PRs with fewer than 50 changed files are automatically approved
  • A changed file is outside the allowed directories (src/, tests/, docs/, utils/), has a disallowed extension (only .py, .txt, .md permitted outside tests/ and docs/), or is not .md/.yml inside docs/ — this covers files the PR deletes or renames, not only the ones it edits
  • A new high-severity security issue was detected in the changed Python files (Bandit check)
  • The PR touches a path this repository protects from untrusted PRs, such as the file that decides who reviews it — a maintainer must make that change in a separate PR

See the workflow run for the exact violations.

A maintainer can review and manually approve CI if a finding is a false positive.

@github-actions

Copy link
Copy Markdown
Contributor

CI recap

Dashboard: View test results in Grafana
Latest run: 33095493953:1
Result: failure | Jobs: 16 | Tests: 184,013 | Failures: 0 | Duration: 13h 51m

Code quality check failed: test jobs were skipped. Fix the code quality issues and push again to run tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants