[Bugfix] Resolve the Hub revision once per repo (#56092)

Signed-off-by: Wauplin <lucainp@gmail.com> Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Harry Mellor <19981378+hmellor@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>

Lucain committed Sep 24, 2026 at 17:48 UTC 344fcc252ab15541fc50250ebdfcf691e92dd6e6
5 files changed +32 -22
tests/test_config.py
+2 -9
@@ -3867,19 +3867,12 @@ def test_load_config_rejects_non_string_load_format(bad_load_format):
3867 LoadConfig(load_format=bad_load_format)
3868
3869
3870 -# A real Qwen3-0.6B model revision that is used in the tests below.
3870 +# A real Qwen3-0.6B model revision that is used in the test below.
3871 REVISION = "c1899de289a04d12100db370d81485cdf75e47ca"
3872
3873
3874 @patch("vllm.config.model.resolve_revision", return_value=ResolvedRevision(REVISION))
3875 -def test_revision_not_resolved_when_weights_differ_from_model(mock_resolve):
3876 - model_weights = "unsloth/Qwen3-0.6B-GGUF:Q8_0"
3877 - config = ModelConfig("Qwen/Qwen3-0.6B", model_weights=model_weights)
3878 - assert config.revision is None
3879 -
3880 -
3881 -@patch("vllm.config.model.resolve_revision", return_value=ResolvedRevision(REVISION))
3882 -def test_revision_resolved_when_weights_match_model(mock_resolve):
3875 +def test_revision_resolved_for_model(mock_resolve):
3876 model = "Qwen/Qwen3-0.6B"
3877 config = ModelConfig(model)
3878 assert isinstance(config.revision, ResolvedRevision)
tests/transformers_utils/test_config.py
+1
@@ -208,6 +208,7 @@ def test_model_config_generation_fallback_forwards_code_revision():
208 model="org/model",
209 trust_remote_code=True,
210 revision="model-pin",
211 + _hf_config_revision=None,
212 code_revision="code-pin",
213 config_format="auto",
214 hf_token=None,
vllm/config/model.py
+18 -13
@@ -589,22 +589,27 @@ class ModelConfig:
589 self.maybe_pull_model_tokenizer_for_runai(self.model, self.tokenizer)
590
591 # If loading model/tokenizer from HF Hub, resolve the revision once
592 - # to prevent resolving it multiple times downstream.
593 - # If the weights come from a different repo, we cannot eagerly resolve revision
594 - weights_from_model = not self.model_weights or self.model_weights == self.model
595 - # If the config comes from a different repo, we cannot eagerly resolve revision
596 - config_from_model = not self.hf_config_path or self.hf_config_path == self.model
597 - can_resolve_model_revision = config_from_model and weights_from_model
598 - if can_resolve_model_revision:
599 - self.revision = resolve_revision(
600 - self.model,
592 + # to prevent resolving it multiple times downstream. A resolved revision
593 + # only pins the repo it was resolved for, so each repo needs its own call.
594 + self.revision = resolve_revision(
595 + self.model,
596 + self.revision,
597 + self.hf_token,
598 + )
599 +
600 + # The config can live in another repo, which `self.revision` does not pin.
601 + # It stays `None` if the config comes from `self.model`, so that call sites
602 + # fall back to `self.revision` the same way they fall back to `self.model`.
603 + self._hf_config_revision = None
604 + if self.hf_config_path and self.hf_config_path != self.model:
605 + self._hf_config_revision = resolve_revision(
606 + self.hf_config_path,
607 self.revision,
608 self.hf_token,
609 )
610
611 if (
606 - can_resolve_model_revision
607 - and self.tokenizer == self.model
612 + self.tokenizer == self.model
613 and self.tokenizer_revision == requested_revision
614 ):
615 self.tokenizer_revision = self.revision
@@ -632,7 +637,7 @@ class ModelConfig:
637 hf_config = get_config(
638 self.hf_config_path or self.model,
639 self.trust_remote_code,
635 - self.revision,
640 + self._hf_config_revision or self.revision,
641 self.code_revision,
642 self.config_format,
643 hf_overrides_kw=hf_overrides_kw,
@@ -1779,7 +1784,7 @@ class ModelConfig:
1784 config = try_get_generation_config(
1785 self.hf_config_path or self.model,
1786 trust_remote_code=self.trust_remote_code,
1782 - revision=self.revision,
1787 + revision=self._hf_config_revision or self.revision,
1788 code_revision=self.code_revision,
1789 config_format=self.config_format,
1790 hf_token=self.hf_token,
vllm/model_executor/model_loader/runai_streamer_loader.py
+6
@@ -15,6 +15,7 @@ from vllm.model_executor.model_loader.weight_utils import (
15 download_weights_from_hf,
16 runai_safetensors_weights_iterator,
17 )
18 +from vllm.transformers_utils.repo_utils import resolve_revision
19 from vllm.transformers_utils.runai_utils import is_runai_obj_uri, list_safetensors
20
21
@@ -87,6 +88,11 @@ class RunaiModelStreamerLoader(BaseModelLoader):
88 safetensors_pattern = "*.safetensors"
89 index_file = SAFE_WEIGHTS_INDEX_NAME
90
91 + if not is_local and not is_object_storage_path:
92 + # `model_weights` can point to another repo than the one `revision` was
93 + # resolved for, which does not pin this one.
94 + revision = resolve_revision(model_name_or_path, revision)
95 +
96 hf_folder = (
97 model_name_or_path
98 if (is_local or is_object_storage_path)
vllm/transformers_utils/repo_utils.py
+5
@@ -63,6 +63,11 @@ def resolve_revision(
63 requested revision that also carries the commit hash, so downstream error
64 messages stay readable and offline loads reuse the cached `refs/` entry.
65
66 + A commit hash only means something for the repo it was resolved against, so
67 + a repo that is not `repo_id` needs its own call. Passing a revision resolved
68 + for another repo back in is safe: the revision initially requested is
69 + resolved again, against `repo_id` this time.
70 +
71 Returns:
72 The resolved revision, or `revision` unchanged if it cannot be resolved
73 (local path, ModelScope, or any Hub error).