| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | """CPU-only tests for Qwen3.5 MTP speculative decoding config overrides.""" |
| 4 | |
| 5 | from typing import Any |
| 6 | |
| 7 | import pytest |
| 8 | from transformers import AutoConfig, PreTrainedConfig |
| 9 | |
| 10 | from vllm.config.speculative import SpeculativeConfig |
| 11 | |
| 12 | _CHECKPOINTS = { |
| 13 | "qwen3_5": "Qwen/Qwen3.8-27B", |
| 14 | "qwen3_5_moe": "Qwen/Qwen3.6-35B-A3B", |
| 15 | } |
| 16 | |
| 17 | |
| 18 | def _mtp_config(model_type: str) -> PreTrainedConfig: |
| 19 | """Create a top-level MTP configuration with mtp_num_hidden_layers.""" |
| 20 | kwargs: dict[str, Any] = { |
| 21 | "model_type": model_type, |
| 22 | "architectures": ["SomeArch"], |
| 23 | "mtp_num_hidden_layers": 1, |
| 24 | } |
| 25 | return PreTrainedConfig(**kwargs) |
| 26 | |
| 27 | |
| 28 | def _multimodal_wrapper_mtp_config( |
| 29 | model_type: str, mtp_layers: int = 1 |
| 30 | ) -> PreTrainedConfig: |
| 31 | """Download a multimodal wrapper config via AutoConfig.from_pretrained |
| 32 | and configure mtp_num_hidden_layers in text_config. |
| 33 | |
| 34 | Uses real-world Hugging Face Hub checkpoints: |
| 35 | - Dense (qwen3_5): Qwen/Qwen3.8-27B |
| 36 | - MoE (qwen3_5_moe): Qwen/Qwen3.6-35B-A3B |
| 37 | """ |
| 38 | repo = _CHECKPOINTS[model_type] |
| 39 | config: PreTrainedConfig = AutoConfig.from_pretrained(repo) |
| 40 | text_config = config.get_text_config() |
| 41 | text_config.mtp_num_hidden_layers = mtp_layers |
| 42 | return config |
| 43 | |
| 44 | |
| 45 | @pytest.mark.parametrize( |
| 46 | "model_type,expected_arch", |
| 47 | [ |
| 48 | ("qwen3_5", "Qwen3_5MTP"), |
| 49 | ("qwen3_5_moe", "Qwen3_5MoeMTP"), |
| 50 | # Text-only config variants must map to the same MTP architectures. |
| 51 | ("qwen3_5_text", "Qwen3_5MTP"), |
| 52 | ("qwen3_5_moe_text", "Qwen3_5MoeMTP"), |
| 53 | ], |
| 54 | ) |
| 55 | def test_mtp_override_recognizes_text_only_types( |
| 56 | model_type: str, expected_arch: str |
| 57 | ) -> None: |
| 58 | """Verify that text-only config variants map to the expected MTP architectures.""" |
| 59 | cfg = SpeculativeConfig.hf_config_override(_mtp_config(model_type)) |
| 60 | assert cfg.model_type == "qwen3_5_mtp" |
| 61 | assert cfg.architectures == [expected_arch] |
| 62 | assert cfg.n_predict == 1 |
| 63 | |
| 64 | |
| 65 | @pytest.mark.parametrize( |
| 66 | "model_type,expected_arch", |
| 67 | [ |
| 68 | ("qwen3_5", "Qwen3_5MTP"), |
| 69 | ("qwen3_5_moe", "Qwen3_5MoeMTP"), |
| 70 | ], |
| 71 | ) |
| 72 | def test_mtp_override_extracts_n_predict_from_multimodal_wrapper( |
| 73 | model_type: str, expected_arch: str |
| 74 | ) -> None: |
| 75 | """Verify that multimodal wrapper checkpoints with mtp_num_hidden_layers |
| 76 | in text_config resolve n_predict and architecture correctly.""" |
| 77 | cfg = SpeculativeConfig.hf_config_override( |
| 78 | _multimodal_wrapper_mtp_config(model_type, mtp_layers=2) |
| 79 | ) |
| 80 | assert cfg.model_type == "qwen3_5_mtp" |
| 81 | assert cfg.architectures == [expected_arch] |
| 82 | assert cfg.n_predict == 2 |
| 83 | |
| 84 | |
| 85 | @pytest.mark.parametrize( |
| 86 | "model_type,expected_arch", |
| 87 | [ |
| 88 | ("qwen3_5", "Qwen3_5MTP"), |
| 89 | ("qwen3_5_moe", "Qwen3_5MoeMTP"), |
| 90 | ], |
| 91 | ) |
| 92 | def test_mtp_override_top_level_precedence_over_nested_text_config( |
| 93 | model_type: str, expected_arch: str |
| 94 | ) -> None: |
| 95 | """Verify that an explicit top-level mtp_num_hidden_layers takes precedence |
| 96 | over a nested text_config value.""" |
| 97 | cfg = _multimodal_wrapper_mtp_config(model_type, mtp_layers=2) |
| 98 | cfg.mtp_num_hidden_layers = 3 |
| 99 | overridden = SpeculativeConfig.hf_config_override(cfg) |
| 100 | assert overridden.model_type == "qwen3_5_mtp" |
| 101 | assert overridden.architectures == [expected_arch] |
| 102 | assert overridden.n_predict == 3 |
| 103 | |
| 104 | |
| 105 | @pytest.mark.parametrize( |
| 106 | "model_id,expected_arch", |
| 107 | [ |
| 108 | ("Qwen/Qwen3.8-27B", "Qwen3_5MTP"), |
| 109 | ("Qwen/Qwen3.6-35B-A3B", "Qwen3_5MoeMTP"), |
| 110 | ], |
| 111 | ) |
| 112 | def test_mtp_override_downloads_real_hf_hub_configs( |
| 113 | model_id: str, expected_arch: str |
| 114 | ) -> None: |
| 115 | """Verify that unmodified real-world checkpoints downloaded via |
| 116 | AutoConfig.from_pretrained resolve n_predict=1 from text_config.""" |
| 117 | hf_config = AutoConfig.from_pretrained(model_id) |
| 118 | cfg = SpeculativeConfig.hf_config_override(hf_config) |
| 119 | assert cfg.model_type == "qwen3_5_mtp" |
| 120 | assert cfg.architectures == [expected_arch] |
| 121 | assert cfg.n_predict == 1 |