From 826e300cde4c512670bf2905cfd03c87451da36c Mon Sep 17 00:00:00 2001 From: Nick Hill Date: Tue, 22 Sep 2026 17:50:17 -0700 Subject: [PATCH] [Bugfix] Skip VllmConfig re-validation for with_hf_config submodel views (#58212) Signed-off-by: Nick Hill Co-authored-by: Kimi Code Co-authored-by: Roger Wang --- tests/test_config.py | 40 ++++++++++++++++++++++++++++++++++++++++ vllm/config/model.py | 9 ++++++++- vllm/config/vllm.py | 7 +++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/tests/test_config.py b/tests/test_config.py index 0720007135..02639db26e 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -289,6 +289,46 @@ def test_kv_offloading_does_not_skip_dcp_interleave_validation(): VllmConfig.validate_block_size(config) +def test_nixl_dcp_check_skipped_for_submodel_config(): + # with_hf_config() builds a submodule view of the config (e.g. a + # multimodal model's text stack) whose architecture list is empty. + # Re-running VllmConfig validation on it is unsafe: the NIXL DCP check + # reads use_mla, which resolves the architecture registry. + model_config = ModelConfig("Qwen/Qwen2-VL-2B-Instruct", max_model_len=2048) + vllm_config = VllmConfig( + model_config=model_config, + device_config=DeviceConfig(device="cpu"), + kv_transfer_config=KVTransferConfig( + kv_connector="NixlConnector", + kv_role="kv_both", + ), + ) + submodel_config = vllm_config.with_hf_config(model_config.hf_text_config) + assert submodel_config.model_config.is_submodel_config + assert submodel_config.model_config.architectures == [] + assert submodel_config.model_config.use_mla is False + + +def test_nixl_dcp_check_rejects_non_mla_model_with_dcp(monkeypatch): + # Pretend the model has a single KV head so the DCP feasibility checks + # pass and the MLA-only assert is what actually fires. + monkeypatch.setattr(ModelConfig, "get_total_num_kv_heads", lambda self: 1) + with pytest.raises(ValidationError, match="only supported for MLA models"): + VllmConfig( + model_config=ModelConfig("Qwen/Qwen3-0.6B", max_model_len=2048), + device_config=DeviceConfig(device="cpu"), + parallel_config=ParallelConfig( + tensor_parallel_size=2, + decode_context_parallel_size=2, + distributed_executor_backend="mp", + ), + kv_transfer_config=KVTransferConfig( + kv_connector="NixlConnector", + kv_role="kv_both", + ), + ) + + def test_compile_config_repr_succeeds(): # setup: VllmBackend mutates the config object config = VllmConfig() diff --git a/vllm/config/model.py b/vllm/config/model.py index 7cdba9f78f..7b938bc8e5 100644 --- a/vllm/config/model.py +++ b/vllm/config/model.py @@ -187,6 +187,10 @@ class ModelConfig: """The Hugging Face config of the model.""" hf_text_config: PretrainedConfig = field(init=False) """The Hugging Face config of the text model (same as hf_config for text models).""" + is_submodel_config: bool = field(default=False, init=False) + """Whether this is a submodule view derived by `VllmConfig.with_hf_config` + (e.g. a multimodal model's text stack). Its architecture list is empty, so + deployment-level validation must not run against it.""" word_embeddings_untied_by_checkpoint: bool = field(default=False, init=False) """Whether `tie_word_embeddings` was overridden to `False` because the checkpoint contains an `lm_head` of its own. The two may still turn out to be identical, in @@ -1082,7 +1086,10 @@ class ModelConfig: # Check if the architecture we're wrapping has defaults runner = None task = None - if defaults := try_match_architecture_defaults(self.architectures[0]): + # architectures is empty for with_hf_config() submodel views. + if self.architectures and ( + defaults := try_match_architecture_defaults(self.architectures[0]) + ): _, (runner, task) = defaults # User specified value take precedence if self.runner != "auto": diff --git a/vllm/config/vllm.py b/vllm/config/vllm.py index 7fcef9be62..8be154d3ed 100644 --- a/vllm/config/vllm.py +++ b/vllm/config/vllm.py @@ -943,6 +943,7 @@ class VllmConfig: model_config.hf_config = hf_config model_config.model_arch_config = model_config.get_model_arch_config() + model_config.is_submodel_config = True return replace(self, model_config=model_config) @@ -1331,6 +1332,12 @@ class VllmConfig: # To give each torch profile run a unique instance name. self.instance_id = f"{time.time_ns()}" + if self.model_config is not None and self.model_config.is_submodel_config: + # with_hf_config() view: the parent config was already validated, + # and this view's empty architecture list makes the model-dependent checks + # below unsafe (e.g. use_mla resolves the architecture registry). + return + self._resolve_mm_encoder_only() if self.performance_mode != "balanced":