[Model] Support Qwen3.5 text-only dense and MoE models (#50210)

Signed-off-by: Perkz Zheng <PerkzZheng@users.noreply.github.com> Co-authored-by: Perkz Zheng <PerkzZheng@users.noreply.github.com>

Perkz Zheng committed Jul 29, 2026 at 16:21 UTC ad5d29db702ce5bce78d9d3b2da1a76eab72e9c2
5 files changed +62
tests/models/registry.py
+2
@@ -497,6 +497,8 @@ _TEXT_GENERATION_EXAMPLE_MODELS = {
497 "Qwen2MoeForCausalLM": _HfExamplesInfo("Qwen/Qwen1.5-MoE-A2.7B-Chat"),
498 "Qwen3ForCausalLM": _HfExamplesInfo("Qwen/Qwen3-8B"),
499 "Qwen3MoeForCausalLM": _HfExamplesInfo("Qwen/Qwen3-30B-A3B"),
500 + "Qwen3_5ForCausalLM": _HfExamplesInfo("codecho/Qwen3.5-0.8B-text-only"),
501 + "Qwen3_5MoeForCausalLM": _HfExamplesInfo("codecho/Qwen3.5-35B-A3B-text-only"),
502 "MellumForCausalLM": _HfExamplesInfo("JetBrains/Mellum2-12B-A2.5B-Base"),
503 "Qwen3NextForCausalLM": _HfExamplesInfo(
504 "Qwen/Qwen3-Next-80B-A3B-Instruct",
vllm/model_executor/models/config.py
+16
@@ -768,6 +768,20 @@ class Qwen3_5ForConditionalGenerationConfig(VerifyAndUpdateConfig):
768 )
769
770
771 +class Qwen3_5ForCausalLMConfig(Qwen3_5ForConditionalGenerationConfig):
772 + @staticmethod
773 + def verify_and_update_config(vllm_config: "VllmConfig") -> None:
774 + Qwen3_5ForConditionalGenerationConfig.verify_and_update_config(vllm_config)
775 +
776 + # Text-only Qwen3.5 models use one-dimensional positions. Remove the
777 + # M-RoPE fields inherited from the multimodal configuration.
778 + hf_text_config = vllm_config.model_config.hf_text_config
779 + rope_parameters = getattr(hf_text_config, "rope_parameters", None)
780 + if rope_parameters is not None:
781 + rope_parameters.pop("mrope_section", None)
782 + rope_parameters.pop("mrope_interleaved", None)
783 +
784 +
785 class ColQwen3_5Config(Qwen3_5ForConditionalGenerationConfig):
786 """Apply the attention contract declared by a ColQwen3.5 checkpoint."""
787
@@ -884,7 +898,9 @@ MODELS_CONFIG_MAP: dict[str, type[VerifyAndUpdateConfig]] = {
898 "Qwen2ForRewardModel": Qwen2ForRewardModelConfig,
899 "Qwen3ForSequenceClassification": Qwen3ForSequenceClassificationConfig,
900 "Qwen3VLForSequenceClassification": Qwen3VLForSequenceClassificationConfig,
901 + "Qwen3_5ForCausalLM": Qwen3_5ForCausalLMConfig,
902 "Qwen3_5ForConditionalGeneration": Qwen3_5ForConditionalGenerationConfig,
903 + "Qwen3_5MoeForCausalLM": Qwen3_5ForCausalLMConfig,
904 "Qwen3_5MoeForConditionalGeneration": Qwen3_5ForConditionalGenerationConfig,
905 "UnlimitedOCRForCausalLM": UnlimitedOCRForCausalLMConfig,
906 "VoyageQwen3BidirectionalEmbedModel": VoyageQwen3BidirectionalEmbedModelConfig,
vllm/model_executor/models/qwen3_5.py
+40
@@ -282,6 +282,7 @@ class Qwen3_5Model(Qwen3NextModel):
282 class Qwen3_5ForCausalLMBase(
283 nn.Module,
284 HasInnerState,
285 + IsHybrid,
286 SupportsEagle3,
287 SupportsLoRA,
288 SupportsPP,
@@ -361,6 +362,45 @@ class Qwen3_5ForCausalLMBase(
362
363 return hidden_states
364
365 + @classmethod
366 + def get_mamba_state_dtype_from_config(
367 + cls,
368 + vllm_config: "VllmConfig",
369 + ) -> tuple[torch.dtype, torch.dtype]:
370 + return MambaStateDtypeCalculator.gated_delta_net_state_dtype(
371 + vllm_config.model_config.dtype,
372 + vllm_config.cache_config.mamba_cache_dtype,
373 + vllm_config.cache_config.mamba_ssm_cache_dtype,
374 + )
375 +
376 + @classmethod
377 + def get_mamba_state_shape_from_config(
378 + cls, vllm_config: "VllmConfig"
379 + ) -> tuple[tuple[int, int], tuple[int, int]]:
380 + parallel_config = vllm_config.parallel_config
381 + hf_config = vllm_config.model_config.hf_text_config
382 + tp_size = parallel_config.tensor_parallel_size
383 + num_spec = (
384 + vllm_config.speculative_config.num_speculative_tokens
385 + if vllm_config.speculative_config
386 + else 0
387 + )
388 + return MambaStateShapeCalculator.gated_delta_net_state_shape(
389 + tp_size,
390 + hf_config.linear_num_key_heads,
391 + hf_config.linear_num_value_heads,
392 + hf_config.linear_key_head_dim,
393 + hf_config.linear_value_head_dim,
394 + hf_config.linear_conv_kernel_dim,
395 + num_spec,
396 + )
397 +
398 + @classmethod
399 + def get_mamba_state_copy_func(
400 + cls,
401 + ) -> tuple[MambaStateCopyFunc, MambaStateCopyFunc]:
402 + return MambaStateCopyFuncCalculator.gated_delta_net_state_copy_func()
403 +
404 def compute_logits(
405 self,
406 hidden_states: torch.Tensor,
vllm/model_executor/models/registry.py
+2
@@ -195,6 +195,8 @@ _TEXT_GENERATION_MODELS = {
195 "Qwen2MoeForCausalLM": ("qwen2_moe", "Qwen2MoeForCausalLM"),
196 "Qwen3ForCausalLM": ("qwen3", "Qwen3ForCausalLM"),
197 "Qwen3MoeForCausalLM": ("qwen3_moe", "Qwen3MoeForCausalLM"),
198 + "Qwen3_5ForCausalLM": ("qwen3_5", "Qwen3_5ForCausalLM"),
199 + "Qwen3_5MoeForCausalLM": ("qwen3_5", "Qwen3_5MoeForCausalLM"),
200 "RWForCausalLM": ("falcon", "FalconForCausalLM"),
201 "SarvamMoEForCausalLM": ("sarvam", "SarvamMoEForCausalLM"),
202 "SarvamMLAForCausalLM": ("sarvam", "SarvamMLAForCausalLM"),
vllm/transformers_utils/config.py
+2
@@ -124,7 +124,9 @@ _CONFIG_REGISTRY: dict[str, type[PretrainedConfig]] = LazyConfigDict(
124 qwen3_asr="Qwen3ASRConfig",
125 qwen3_next="Qwen3NextConfig",
126 qwen3_5="Qwen3_5Config",
127 + qwen3_5_text="Qwen3_5TextConfig",
128 qwen3_5_moe="Qwen3_5MoeConfig",
129 + qwen3_5_moe_text="Qwen3_5MoeTextConfig",
130 laguna="LagunaConfig",
131 lfm2_moe="Lfm2MoeConfig",
132 **{"unlimited-ocr": "UnlimitedOCRConfig"},