Preserve revision pins in secondary artifact loaders (#51446)

Signed-off-by: Clinton Thomas <1033162+KernelClint@users.noreply.github.com> Co-authored-by: Lucas Bourtoule <35483370+dhalf@users.noreply.github.com>

Clinton Thomas committed Aug 11, 2026 at 00:33 UTC 90fd4a333fd62b09310924dcb04d8e827bcbcd9b
11 files changed +226 -13
tests/config/test_model_arch_config.py
+27
@@ -7,6 +7,7 @@ from copy import copy
7 from pathlib import Path
8 from types import SimpleNamespace
9 from typing import cast
10 +from unittest.mock import Mock
11
12 import pytest
13 from transformers import PretrainedConfig
@@ -138,6 +139,32 @@ def test_head_size_falls_back_when_head_dim_is_zero():
139 assert convertor.get_head_size() == 128
140
141
142 +def test_qk_rope_head_dim_recovery_uses_model_revision(monkeypatch: pytest.MonkeyPatch):
143 + hf_config = PretrainedConfig(
144 + model_type="deepseek_v2",
145 + qk_rope_head_dim=128,
146 + qk_nope_head_dim=128,
147 + )
148 + hf_config.name_or_path = "org/model"
149 + get_hf_file_to_dict = Mock(return_value={"qk_rope_head_dim": 64})
150 +
151 + monkeypatch.setattr(
152 + "vllm.transformers_utils.repo_utils.get_hf_file_to_dict",
153 + get_hf_file_to_dict,
154 + )
155 +
156 + convertor = ModelArchConfigConvertorBase(
157 + hf_config,
158 + hf_config,
159 + revision="pinned-revision",
160 + )
161 +
162 + assert convertor._get_qk_rope_head_dim() == 64
163 + get_hf_file_to_dict.assert_called_once_with(
164 + "config.json", "org/model", "pinned-revision"
165 + )
166 +
167 +
168 @pytest.mark.parametrize(
169 "attribute",
170 [
tests/test_quantization_revision_pin.py new
+40
@@ -0,0 +1,40 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +from types import SimpleNamespace
4 +from unittest.mock import Mock
5 +
6 +import pytest
7 +import torch
8 +
9 +from vllm.config.vllm import VllmConfig
10 +
11 +pytestmark = [pytest.mark.cpu_test, pytest.mark.skip_global_cleanup]
12 +
13 +
14 +def test_quantization_config_forwards_model_revision(monkeypatch: pytest.MonkeyPatch):
15 + quant_config = Mock()
16 + quant_config.get_min_capability.return_value = 0
17 + quant_config.get_supported_act_dtypes.return_value = [torch.float32]
18 + model_config = SimpleNamespace(
19 + quantization="auto_gptq",
20 + model="repo/model",
21 + hf_config=object(),
22 + dtype=torch.float32,
23 + revision="pinned-revision",
24 + )
25 +
26 + monkeypatch.setattr(
27 + "vllm.model_executor.model_loader.weight_utils.get_quant_config",
28 + lambda model_config, load_config: quant_config,
29 + )
30 + monkeypatch.setattr(
31 + "vllm.platforms.current_platform.get_device_capability", lambda: None
32 + )
33 +
34 + VllmConfig._get_quantization_config(model_config, SimpleNamespace())
35 +
36 + quant_config.maybe_update_config.assert_called_once_with(
37 + "repo/model",
38 + hf_config=model_config.hf_config,
39 + revision="pinned-revision",
40 + )
tests/test_tensorizer_revision_pin.py new
+51
@@ -0,0 +1,51 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +import contextlib
4 +import io
5 +from types import SimpleNamespace
6 +from unittest.mock import Mock
7 +
8 +import pytest
9 +import torch
10 +
11 +from vllm.model_executor.model_loader import tensorizer
12 +
13 +pytestmark = [pytest.mark.cpu_test, pytest.mark.skip_global_cleanup]
14 +
15 +
16 +def test_serialize_vllm_model_forwards_model_revision(
17 + monkeypatch: pytest.MonkeyPatch,
18 +):
19 + @contextlib.contextmanager
20 + def fake_open_stream(*args, **kwargs):
21 + yield io.BytesIO()
22 +
23 + snapshot_download = Mock()
24 + monkeypatch.setattr(tensorizer, "open_stream", fake_open_stream)
25 + monkeypatch.setattr(tensorizer, "TensorSerializer", Mock())
26 + monkeypatch.setattr(
27 + tensorizer,
28 + "hf_api",
29 + lambda: SimpleNamespace(snapshot_download=snapshot_download),
30 + )
31 +
32 + model = torch.nn.Module()
33 + tensorizer_config = SimpleNamespace(
34 + _construct_tensorizer_args=lambda: SimpleNamespace(
35 + tensorizer_uri="/tmp/model.tensors",
36 + tensorizer_dir="/tmp/out",
37 + stream_kwargs={},
38 + ),
39 + encryption_keyfile=None,
40 + _is_sharded=False,
41 + serialization_kwargs=None,
42 + )
43 + model_config = SimpleNamespace(
44 + served_model_name="repo/model",
45 + revision="pinned-revision",
46 + )
47 +
48 + tensorizer.serialize_vllm_model(model, tensorizer_config, model_config)
49 +
50 + assert snapshot_download.call_args.args == ("repo/model",)
51 + assert snapshot_download.call_args.kwargs["revision"] == "pinned-revision"
tests/transformers_utils/test_config.py
+47
@@ -6,7 +6,15 @@ only get the `eos_token_id` from the tokenizer as defined by
6 `BaseRenderer.get_eos_token_id`.
7 """
8
9 +from types import SimpleNamespace
10 +from typing import cast
11 +from unittest.mock import patch
12 +
13 +from transformers import PretrainedConfig
14 +
15 +from vllm.config.model import ModelConfig
16 from vllm.tokenizers import get_tokenizer
17 +from vllm.transformers_utils import config as config_module
18 from vllm.transformers_utils.config import try_get_generation_config
19
20
@@ -30,3 +38,42 @@ def test_get_blip2_eos_token():
38 generation_config = try_get_generation_config(model_name, trust_remote_code=False)
39 assert generation_config is not None
40 assert generation_config.eos_token_id == 50118
41 +
42 +
43 +def test_model_config_generation_fallback_forwards_code_revision():
44 + model_config = cast(
45 + ModelConfig,
46 + SimpleNamespace(
47 + generation_config="auto",
48 + hf_config_path=None,
49 + model="org/model",
50 + trust_remote_code=True,
51 + revision="model-pin",
52 + code_revision="code-pin",
53 + config_format="auto",
54 + hf_token=None,
55 + ),
56 + )
57 +
58 + with (
59 + patch.object(
60 + config_module.GenerationConfig,
61 + "from_pretrained",
62 + side_effect=OSError,
63 + ),
64 + patch.object(
65 + config_module,
66 + "get_config",
67 + return_value=PretrainedConfig(),
68 + ) as get_config,
69 + ):
70 + ModelConfig.try_get_generation_config(model_config)
71 +
72 + get_config.assert_called_once_with(
73 + "org/model",
74 + trust_remote_code=True,
75 + revision="model-pin",
76 + code_revision="code-pin",
77 + config_format="auto",
78 + token=None,
79 + )
vllm/config/model.py
+7 -1
@@ -890,7 +890,11 @@ class ModelConfig:
890 convertor_cls = MODEL_ARCH_CONFIG_CONVERTORS.get(
891 self.hf_config.model_type, ModelArchConfigConvertorBase
892 )
893 - convertor = convertor_cls(self.hf_config, self.hf_text_config)
893 + convertor = convertor_cls(
894 + self.hf_config,
895 + self.hf_text_config,
896 + revision=getattr(self, "revision", None),
897 + )
898 return convertor.convert(
899 supports_multimodal=self._supports_multimodal_for_mm_prefix()
900 )
@@ -1651,6 +1655,7 @@ class ModelConfig:
1655 self.hf_config_path or self.model,
1656 trust_remote_code=self.trust_remote_code,
1657 revision=self.revision,
1658 + code_revision=self.code_revision,
1659 config_format=self.config_format,
1660 hf_token=self.hf_token,
1661 )
@@ -1658,6 +1663,7 @@ class ModelConfig:
1663 config = try_get_generation_config(
1664 self.generation_config,
1665 trust_remote_code=self.trust_remote_code,
1666 + code_revision=self.code_revision,
1667 config_format=self.config_format,
1668 hf_token=self.hf_token,
1669 )
vllm/config/vllm.py
+1
@@ -779,6 +779,7 @@ class VllmConfig:
779 quant_config.maybe_update_config(
780 model_config.model,
781 hf_config=model_config.hf_config,
782 + revision=model_config.revision,
783 )
784 return quant_config
785 return None
vllm/model_executor/model_loader/tensorizer.py
+9 -2
@@ -621,7 +621,9 @@ def is_vllm_tensorized(tensorizer_config: "TensorizerConfig") -> bool:
621
622
623 def serialize_extra_artifacts(
624 - tensorizer_args: TensorizerArgs, served_model_name: str | list[str] | None
624 + tensorizer_args: TensorizerArgs,
625 + served_model_name: str | list[str] | None,
626 + revision: str | None = None,
627 ) -> None:
628 if not isinstance(served_model_name, str):
629 raise ValueError(
@@ -632,6 +634,7 @@ def serialize_extra_artifacts(
634 with tempfile.TemporaryDirectory() as tmpdir:
635 hf_api().snapshot_download(
636 served_model_name,
637 + revision=revision,
638 local_dir=tmpdir,
639 ignore_patterns=[
640 "*.pt",
@@ -693,7 +696,11 @@ def serialize_vllm_model(
696 serializer.write_module(model)
697 serializer.close()
698
696 - serialize_extra_artifacts(tensorizer_args, model_config.served_model_name)
699 + serialize_extra_artifacts(
700 + tensorizer_args,
701 + model_config.served_model_name,
702 + revision=model_config.revision,
703 + )
704
705 logger.info("Successfully serialized model to %s", str(output_file))
706 return model
vllm/model_executor/models/funaudiochat.py
+7 -2
@@ -519,12 +519,17 @@ class FunAudioChatProcessingInfo(BaseProcessingInfo):
519
520 @cached_property
521 def feature_extractor(self) -> WhisperFeatureExtractor:
522 - return WhisperFeatureExtractor.from_pretrained(self.model_id)
522 + return WhisperFeatureExtractor.from_pretrained(
523 + self.model_id,
524 + revision=self.ctx.model_config.revision,
525 + )
526
527 @cached_property
528 def speech_tokenizer(self) -> TokenizersBackend:
529 return TokenizersBackend.from_pretrained(
527 - self.model_id, subfolder="speech_tokenizer"
530 + self.model_id,
531 + subfolder="speech_tokenizer",
532 + revision=self.ctx.model_config.tokenizer_revision,
533 )
534
535 def get_feature_extractor(self) -> WhisperFeatureExtractor:
vllm/renderers/hf.py
+13 -2
@@ -210,7 +210,7 @@ def _build_mixed_prompt_embeds(
210 return full_embeds, is_token_ids.tolist()
211
212
213 -_PROCESSOR_CHAT_TEMPLATES = dict[tuple[str, bool], str | None]()
213 +_PROCESSOR_CHAT_TEMPLATES = dict[tuple[str, str | None, str | None, bool], str | None]()
214 """
215 Used in `_try_get_processor_chat_template` to avoid calling
216 `cached_get_processor` again if the processor fails to be loaded.
@@ -222,9 +222,16 @@ This is needed because `lru_cache` does not cache when an exception happens.
222 def _try_get_processor_chat_template(
223 tokenizer: HfTokenizer,
224 *,
225 + revision: str | None,
226 + code_revision: str | None,
227 trust_remote_code: bool,
228 ) -> str | None:
227 - cache_key = (tokenizer.name_or_path, trust_remote_code)
229 + cache_key = (
230 + tokenizer.name_or_path,
231 + revision,
232 + code_revision,
233 + trust_remote_code,
234 + )
235 if cache_key in _PROCESSOR_CHAT_TEMPLATES:
236 return _PROCESSOR_CHAT_TEMPLATES[cache_key]
237
@@ -234,6 +241,8 @@ def _try_get_processor_chat_template(
241 processor = cached_get_processor(
242 tokenizer.name_or_path,
243 processor_cls=(PythonBackend, TokenizersBackend, ProcessorMixin),
244 + revision=revision,
245 + code_revision=code_revision,
246 trust_remote_code=trust_remote_code,
247 )
248 if (
@@ -271,6 +280,8 @@ def resolve_chat_template(
280 if tools is None:
281 chat_template = _try_get_processor_chat_template(
282 tokenizer,
283 + revision=model_config.revision,
284 + code_revision=model_config.code_revision,
285 trust_remote_code=model_config.trust_remote_code,
286 )
287 if chat_template is not None:
vllm/transformers_utils/config.py
+2
@@ -1071,6 +1071,7 @@ def try_get_generation_config(
1071 model: str,
1072 trust_remote_code: bool,
1073 revision: str | None = None,
1074 + code_revision: str | None = None,
1075 config_format: str | ConfigFormat = "auto",
1076 hf_token: bool | str | None = None,
1077 ) -> GenerationConfig | None:
@@ -1086,6 +1087,7 @@ def try_get_generation_config(
1087 model,
1088 trust_remote_code=trust_remote_code,
1089 revision=revision,
1090 + code_revision=code_revision,
1091 config_format=config_format,
1092 token=hf_token,
1093 )
vllm/transformers_utils/model_arch_config_convertor.py
+22 -6
@@ -24,9 +24,15 @@ logger = init_logger(__name__)
24
25
26 class ModelArchConfigConvertorBase:
27 - def __init__(self, hf_config: PretrainedConfig, hf_text_config: PretrainedConfig):
27 + def __init__(
28 + self,
29 + hf_config: PretrainedConfig,
30 + hf_text_config: PretrainedConfig,
31 + revision: str | None = None,
32 + ):
33 self.hf_config = hf_config
34 self.hf_text_config = hf_text_config
35 + self.revision = revision
36
37 def get_per_layer_hf_configs(
38 self,
@@ -122,7 +128,7 @@ class ModelArchConfigConvertorBase:
128 model_path = self.hf_config.name_or_path
129 if not model_path:
130 return qk_rope_head_dim
125 - raw = get_hf_file_to_dict("config.json", model_path)
131 + raw = get_hf_file_to_dict("config.json", model_path, self.revision)
132 if raw and "qk_rope_head_dim" in raw:
133 correct = raw["qk_rope_head_dim"]
134 if correct != qk_rope_head_dim:
@@ -595,16 +601,26 @@ def _strip_mimo_v2_attention_chunk_size(
601
602
603 class MimoV2ModelArchConfigConvertor(ModelArchConfigConvertorBase):
598 - def __init__(self, hf_config: PretrainedConfig, hf_text_config: PretrainedConfig):
604 + def __init__(
605 + self,
606 + hf_config: PretrainedConfig,
607 + hf_text_config: PretrainedConfig,
608 + revision: str | None = None,
609 + ):
610 if getattr(hf_config, "vision_config", None):
611 hf_config.architectures = ["MiMoV2OmniForCausalLM"]
601 - super().__init__(hf_config, hf_text_config)
612 + super().__init__(hf_config, hf_text_config, revision)
613 _strip_mimo_v2_attention_chunk_size(hf_config, hf_text_config)
614
615
616 class MimoV2MTPModelArchConfigConvertor(ModelArchConfigConvertorBase):
606 - def __init__(self, hf_config: PretrainedConfig, hf_text_config: PretrainedConfig):
607 - super().__init__(hf_config, hf_text_config)
617 + def __init__(
618 + self,
619 + hf_config: PretrainedConfig,
620 + hf_text_config: PretrainedConfig,
621 + revision: str | None = None,
622 + ):
623 + super().__init__(hf_config, hf_text_config, revision)
624 _strip_mimo_v2_attention_chunk_size(hf_config, hf_text_config)
625
626 def get_num_hidden_layers(self) -> int: