[Config][EC] Normalize producer-only encoder config (#53656)

Signed-off-by: Tianyu Guo <guoty@inferact.ai>

Tianyu Guo committed Aug 25, 2026 at 15:53 UTC 12c84b98ddeb71186da8b77382b0fad71ad03c8c
3 files changed +48 -10
tests/config/test_multimodal_config.py
+28
@@ -329,6 +329,34 @@ def test_auto_mm_processor_device_leaves_an_explicit_request_alone():
329 assert _resolve_mm_processor_device(ec_role="ec_producer", device="cpu") == "cpu"
330
331
332 +@pytest.mark.parametrize(
333 + ("ec_role", "expected"),
334 + [
335 + (None, False),
336 + ("ec_consumer", False),
337 + ("ec_both", False),
338 + ("ec_producer", True),
339 + ],
340 +)
341 +def test_ec_producer_only_enables_mm_encoder_only(
342 + ec_role: ECRole | None, expected: bool
343 +):
344 + mm_config = MultiModalConfig()
345 + model_config = MagicMock(spec=ModelConfig)
346 + model_config.multimodal_config = mm_config
347 + vllm_config = MagicMock(spec=VllmConfig)
348 + vllm_config.model_config = model_config
349 + vllm_config.ec_transfer_config = (
350 + None
351 + if ec_role is None
352 + else ECTransferConfig(ec_connector="ECExampleConnector", ec_role=ec_role)
353 + )
354 +
355 + VllmConfig._resolve_mm_encoder_only(vllm_config)
356 +
357 + assert mm_config.mm_encoder_only is expected
358 +
359 +
360 def test_vllm_config_runs_the_mm_processor_device_check():
361 """Startup must reach the check; the rule itself is covered above.
362
tests/v1/core/utils.py
+3
@@ -10,6 +10,7 @@ from vllm.config import (
10 ECTransferConfig,
11 KVTransferConfig,
12 ModelConfig,
13 + MultiModalConfig,
14 ObservabilityConfig,
15 ParallelConfig,
16 SchedulerConfig,
@@ -94,6 +95,8 @@ def create_scheduler(
95 seed=42,
96 skip_tokenizer_init=skip_tokenizer_init,
97 )
98 + if use_ec_connector and ec_role == "ec_producer":
99 + model_config.multimodal_config = MultiModalConfig()
100 if max_model_len is None:
101 max_model_len = max_num_batched_tokens
102 scheduler_config = SchedulerConfig(
vllm/config/vllm.py
+17 -10
@@ -562,15 +562,6 @@ class VllmConfig:
562 ]
563 return hash_str
564
565 - @property
566 - def is_ec_producer_only(self) -> bool:
567 - ec_config = self.ec_transfer_config
568 - return (
569 - ec_config is not None
570 - and ec_config.is_ec_producer
571 - and not ec_config.is_ec_consumer
572 - )
573 -
565 @property
566 def is_mm_encoder_only(self) -> bool:
567 mm_config = (
@@ -578,7 +569,7 @@ class VllmConfig:
569 if self.model_config is not None
570 else None
571 )
581 - return self.is_ec_producer_only or bool(mm_config and mm_config.mm_encoder_only)
572 + return bool(mm_config and mm_config.mm_encoder_only)
573
574 @property
575 def max_concurrent_batches(self) -> int:
@@ -1121,6 +1112,8 @@ class VllmConfig:
1112 # To give each torch profile run a unique instance name.
1113 self.instance_id = f"{time.time_ns()}"
1114
1115 + self._resolve_mm_encoder_only()
1116 +
1117 if self.performance_mode != "balanced":
1118 logger.info_once("Performance mode set to '%s'.", self.performance_mode)
1119
@@ -2380,6 +2373,20 @@ class VllmConfig:
2373 "omit the embedding tensor."
2374 )
2375
2376 + def _resolve_mm_encoder_only(self) -> None:
2377 + """Enable encoder-only mode for a dedicated EC producer."""
2378 + ec_config = self.ec_transfer_config
2379 + if ec_config is None or not ec_config.is_encode_only:
2380 + return
2381 +
2382 + model_config = self.model_config
2383 + mm_config = model_config.multimodal_config if model_config is not None else None
2384 + if mm_config is None:
2385 + raise ValueError(
2386 + "An EC producer-only instance requires a multimodal model."
2387 + )
2388 + mm_config.mm_encoder_only = True
2389 +
2390 def _resolve_mm_processor_device(self) -> None:
2391 """Settle `--mm-processor-device=auto` now that the EC role is known.
2392