[Bugfix][Structured Output] Disallow MRV1 + PP>1 + async sched + structured output (#56250)

Signed-off-by: Artem Perevedentsev <aperevedents@nvidia.com> Co-authored-by: CNE Pierre FICHEPOIL <pierre-1.fichepoil@gendarmerie.interieur.gouv.fr>

Artem Perevedentsev committed Sep 22, 2026 at 17:00 UTC 1ea7c63f4af7bb4fd6f025c8db44434ab274cb51
2 files changed +67 -2
tests/test_config.py
+44 -2
@@ -1082,10 +1082,11 @@ def test_models_default_to_v2_model_runner(model_config, expected, monkeypatch):
1082
1083 def test_v1_model_runner_rejects_v2_only_features():
1084 config = SimpleNamespace(
1085 - parallel_config=SimpleNamespace(
1085 + parallel_config=ParallelConfig(
1086 prefill_context_parallel_size=2,
1087 - enable_batch_sharded_sampling=False,
1087 + distributed_executor_backend="mp",
1088 ),
1089 + scheduler_config=SchedulerConfig.default_factory(async_scheduling=False),
1090 speculative_config=None,
1091 model_config=None,
1092 )
@@ -1245,6 +1246,47 @@ def test_async_scheduling_with_pipeline_parallelism_is_allowed():
1246 assert cfg.scheduler_config.async_scheduling is True
1247
1248
1249 +def test_v1_model_runner_drops_async_scheduling_with_pipeline_parallelism(monkeypatch):
1250 + """PP>1 must stay buildable whenever the V1 model runner is selected, such
1251 + as the external_launcher fallback; async scheduling is dropped instead."""
1252 + monkeypatch.setenv("VLLM_USE_V2_MODEL_RUNNER", "0")
1253 +
1254 + cfg = VllmConfig(
1255 + scheduler_config=SchedulerConfig(
1256 + max_model_len=8192,
1257 + is_encoder_decoder=False,
1258 + ),
1259 + parallel_config=ParallelConfig(
1260 + pipeline_parallel_size=2,
1261 + distributed_executor_backend="mp",
1262 + nnodes=2,
1263 + ),
1264 + )
1265 + assert cfg.scheduler_config.async_scheduling is False
1266 +
1267 +
1268 +def test_v1_model_runner_rejects_pipeline_parallelism_with_async_scheduling():
1269 + """Only the async combination desyncs the grammar FSM (#45014), so plain
1270 + PP>1 must stay usable on the V1 model runner."""
1271 + config = SimpleNamespace(
1272 + parallel_config=ParallelConfig(
1273 + pipeline_parallel_size=2,
1274 + distributed_executor_backend="mp",
1275 + ),
1276 + scheduler_config=SchedulerConfig.default_factory(async_scheduling=False),
1277 + speculative_config=None,
1278 + model_config=None,
1279 + )
1280 + config._dflash_needs_multi_kv_group = lambda: False
1281 + config._is_dflash2_draft = lambda: False
1282 +
1283 + assert VllmConfig._get_v1_model_runner_unsupported_features(config) == []
1284 +
1285 + config.scheduler_config.async_scheduling = True
1286 + unsupported = VllmConfig._get_v1_model_runner_unsupported_features(config)
1287 + assert "pipeline parallelism with async scheduling" in unsupported
1288 +
1289 +
1290 def test_data_parallel_rpc_port_has_fixed_default():
1291 assert ParallelConfig().data_parallel_rpc_port == 29550
1292
vllm/config/vllm.py
+23
@@ -1522,6 +1522,15 @@ class VllmConfig:
1522 "DP+EP generation accuracy."
1523 )
1524 self.scheduler_config.async_scheduling = False
1525 + elif (
1526 + self.parallel_config.pipeline_parallel_size > 1
1527 + and not self.use_v2_model_runner
1528 + ):
1529 + logger.warning_once(
1530 + "Async scheduling is disabled because the V1 model runner "
1531 + "does not support it with pipeline parallelism."
1532 + )
1533 + self.scheduler_config.async_scheduling = False
1534 else:
1535 self.scheduler_config.async_scheduling = True
1536
@@ -2962,6 +2971,20 @@ class VllmConfig:
2971 if self.parallel_config.prefill_context_parallel_size > 1:
2972 unsupported.append("prefill context parallel")
2973
2974 + # Note(arpera):
2975 + # MRV1 + PP>1 + async sched + structured output
2976 + # does not work in vLLM. For more info see:
2977 + # https://github.com/vllm-project/vllm/issues/45014
2978 + # Since recently MRV1 has been deprecated then
2979 + # there was decided not to fix the issue but instead
2980 + # to disallow such configuration.
2981 + # At the same time MRV2 works fine in this case.
2982 + if (
2983 + self.parallel_config.pipeline_parallel_size > 1
2984 + and self.scheduler_config.async_scheduling
2985 + ):
2986 + unsupported.append("pipeline parallelism with async scheduling")
2987 +
2988 # DSpark is implemented only by the V2 GPU model runner.
2989 if self.speculative_config:
2990 if self.speculative_config.method == "dspark":