[Bugfix][Model] Fix Qwen3.5 MTP for text-only checkpoints (#50734)
Signed-off-by: efschu <51944948+efschu@users.noreply.github.com>
efschu committed
Aug 10, 2026 at 13:04 UTC
900d09f91a1fe164c0df175c334060cad68ce245
3 files changed
+55
-7
tests/models/test_qwen3_5_mtp_config.py
new
+33
@@ -0,0 +1,33 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+"""CPU-only tests for Qwen3.5 text-only MTP speculative decoding."""
4
+
5
+import pytest
6
+from transformers import PretrainedConfig
7
+
8
+from vllm.config.speculative import SpeculativeConfig
9
+
10
+
11
+def _mtp_config(model_type: str) -> PretrainedConfig:
12
+ return PretrainedConfig(
13
+ model_type=model_type,
14
+ architectures=["SomeArch"],
15
+ mtp_num_hidden_layers=1,
16
+ )
17
+
18
+
19
+@pytest.mark.parametrize(
20
+ "model_type,expected_arch",
21
+ [
22
+ ("qwen3_5", "Qwen3_5MTP"),
23
+ ("qwen3_5_moe", "Qwen3_5MoeMTP"),
24
+ # Text-only config variants must map to the same MTP architectures.
25
+ ("qwen3_5_text", "Qwen3_5MTP"),
26
+ ("qwen3_5_moe_text", "Qwen3_5MoeMTP"),
27
+ ],
28
+)
29
+def test_mtp_override_recognizes_text_only_types(model_type, expected_arch):
30
+ cfg = SpeculativeConfig.hf_config_override(_mtp_config(model_type))
31
+ assert cfg.model_type == "qwen3_5_mtp"
32
+ assert cfg.architectures == [expected_arch]
33
+ assert cfg.n_predict == 1
vllm/config/speculative.py
+10
-2
@@ -528,8 +528,16 @@ class SpeculativeConfig:
528
hf_config.update(
529
{"n_predict": n_predict, "architectures": ["Exaone4_5_MTP"]}
530
)
531
- if hf_config.model_type in ("qwen3_5", "qwen3_5_moe"):
532
- is_moe = hf_config.model_type == "qwen3_5_moe"
531
+ if hf_config.model_type in (
532
+ "qwen3_5",
533
+ "qwen3_5_moe",
534
+ "qwen3_5_text",
535
+ "qwen3_5_moe_text",
536
+ ):
537
+ # Checkpoints that ship only the text config resolve to the
538
+ # `qwen3_5_text` / `qwen3_5_moe_text` model types and carry the
539
+ # same `mtp_num_hidden_layers` field as the multimodal ones.
540
+ is_moe = hf_config.model_type in ("qwen3_5_moe", "qwen3_5_moe_text")
541
hf_config.model_type = "qwen3_5_mtp"
542
n_predict = getattr(hf_config, "mtp_num_hidden_layers", None)
543
hf_config.update(
vllm/multimodal/registry.py
+12
-5
@@ -114,11 +114,18 @@ class MultiModalRegistry:
114
try:
115
info = self._create_processing_info(model_config, tokenizer=None)
116
except ValueError:
117
- logger.warning_once(
118
- "Model %s is treated as multimodal but has no registered "
119
- "multimodal processor; running in text-only mode.",
120
- model_config.model,
121
- )
117
+ # Speculative drafters for multimodal targets (e.g. Qwen3_5MTP,
118
+ # Exaone4_5_MTP, MiMoV2OmniMTP) declare `SupportsMultiModal` so
119
+ # that they can consume the embeddings merged by the target model,
120
+ # but they never run a multi-modal processor of their own. Running
121
+ # in text-only mode is the expected outcome for them, not a
122
+ # misconfiguration worth warning about.
123
+ if model_config.runner_type != "draft":
124
+ logger.warning_once(
125
+ "Model %s is treated as multimodal but has no registered "
126
+ "multimodal processor; running in text-only mode.",
127
+ model_config.model,
128
+ )
129
return False
130
131
# Check if all supported modalities have limit == 0