[Bugfix] Skip VllmConfig re-validation for with_hf_config submodel views (#58212)
Signed-off-by: Nick Hill <nickhill123@gmail.com> Co-authored-by: Kimi Code <noreply@moonshot.ai> Co-authored-by: Roger Wang <rogerw@inferact.ai>
Nick Hill committed
Sep 22, 2026 at 17:50 UTC
826e300cde4c512670bf2905cfd03c87451da36c
3 files changed
+55
-1
tests/test_config.py
+40
@@ -289,6 +289,46 @@ def test_kv_offloading_does_not_skip_dcp_interleave_validation():
289
VllmConfig.validate_block_size(config)
290
291
292
+def test_nixl_dcp_check_skipped_for_submodel_config():
293
+ # with_hf_config() builds a submodule view of the config (e.g. a
294
+ # multimodal model's text stack) whose architecture list is empty.
295
+ # Re-running VllmConfig validation on it is unsafe: the NIXL DCP check
296
+ # reads use_mla, which resolves the architecture registry.
297
+ model_config = ModelConfig("Qwen/Qwen2-VL-2B-Instruct", max_model_len=2048)
298
+ vllm_config = VllmConfig(
299
+ model_config=model_config,
300
+ device_config=DeviceConfig(device="cpu"),
301
+ kv_transfer_config=KVTransferConfig(
302
+ kv_connector="NixlConnector",
303
+ kv_role="kv_both",
304
+ ),
305
+ )
306
+ submodel_config = vllm_config.with_hf_config(model_config.hf_text_config)
307
+ assert submodel_config.model_config.is_submodel_config
308
+ assert submodel_config.model_config.architectures == []
309
+ assert submodel_config.model_config.use_mla is False
310
+
311
+
312
+def test_nixl_dcp_check_rejects_non_mla_model_with_dcp(monkeypatch):
313
+ # Pretend the model has a single KV head so the DCP feasibility checks
314
+ # pass and the MLA-only assert is what actually fires.
315
+ monkeypatch.setattr(ModelConfig, "get_total_num_kv_heads", lambda self: 1)
316
+ with pytest.raises(ValidationError, match="only supported for MLA models"):
317
+ VllmConfig(
318
+ model_config=ModelConfig("Qwen/Qwen3-0.6B", max_model_len=2048),
319
+ device_config=DeviceConfig(device="cpu"),
320
+ parallel_config=ParallelConfig(
321
+ tensor_parallel_size=2,
322
+ decode_context_parallel_size=2,
323
+ distributed_executor_backend="mp",
324
+ ),
325
+ kv_transfer_config=KVTransferConfig(
326
+ kv_connector="NixlConnector",
327
+ kv_role="kv_both",
328
+ ),
329
+ )
330
+
331
+
332
def test_compile_config_repr_succeeds():
333
# setup: VllmBackend mutates the config object
334
config = VllmConfig()
vllm/config/model.py
+8
-1
@@ -187,6 +187,10 @@ class ModelConfig:
187
"""The Hugging Face config of the model."""
188
hf_text_config: PretrainedConfig = field(init=False)
189
"""The Hugging Face config of the text model (same as hf_config for text models)."""
190
+ is_submodel_config: bool = field(default=False, init=False)
191
+ """Whether this is a submodule view derived by `VllmConfig.with_hf_config`
192
+ (e.g. a multimodal model's text stack). Its architecture list is empty, so
193
+ deployment-level validation must not run against it."""
194
word_embeddings_untied_by_checkpoint: bool = field(default=False, init=False)
195
"""Whether `tie_word_embeddings` was overridden to `False` because the checkpoint
196
contains an `lm_head` of its own. The two may still turn out to be identical, in
@@ -1082,7 +1086,10 @@ class ModelConfig:
1086
# Check if the architecture we're wrapping has defaults
1087
runner = None
1088
task = None
1085
- if defaults := try_match_architecture_defaults(self.architectures[0]):
1089
+ # architectures is empty for with_hf_config() submodel views.
1090
+ if self.architectures and (
1091
+ defaults := try_match_architecture_defaults(self.architectures[0])
1092
+ ):
1093
_, (runner, task) = defaults
1094
# User specified value take precedence
1095
if self.runner != "auto":
vllm/config/vllm.py
+7
@@ -943,6 +943,7 @@ class VllmConfig:
943
944
model_config.hf_config = hf_config
945
model_config.model_arch_config = model_config.get_model_arch_config()
946
+ model_config.is_submodel_config = True
947
948
return replace(self, model_config=model_config)
949
@@ -1331,6 +1332,12 @@ class VllmConfig:
1332
# To give each torch profile run a unique instance name.
1333
self.instance_id = f"{time.time_ns()}"
1334
1335
+ if self.model_config is not None and self.model_config.is_submodel_config:
1336
+ # with_hf_config() view: the parent config was already validated,
1337
+ # and this view's empty architecture list makes the model-dependent checks
1338
+ # below unsafe (e.g. use_mla resolves the architecture registry).
1339
+ return
1340
+
1341
self._resolve_mm_encoder_only()
1342
1343
if self.performance_mode != "balanced":