[Model] Enable EVS for Qwen3.5 (#48912)

Signed-off-by: Rui "Garry" Gao <garrygaogg@gmail.com>

Rui "Garry" Gao committed Jul 27, 2026 at 21:42 UTC dbccc5ae328d7b9168bc33b278a9125cfd89cc69
1 file changed +32 -12
vllm/model_executor/models/qwen3_5.py
+32 -12
@@ -53,6 +53,7 @@ from vllm.model_executor.layers.vocab_parallel_embedding import (
53 )
54 from vllm.multimodal import MULTIMODAL_REGISTRY
55 from vllm.sequence import IntermediateTensors
56 +from vllm.tokenizers.registry import cached_tokenizer_from_config
57 from vllm.transformers_utils.configs.qwen3_5 import Qwen3_5Config, Qwen3_5TextConfig
58 from vllm.transformers_utils.configs.qwen3_5_moe import (
59 Qwen3_5MoeConfig,
@@ -397,8 +398,7 @@ class Qwen3_5MoeForCausalLM(Qwen3_5ForCausalLMBase, QwenNextMixtureOfExperts):
398 dummy_inputs=Qwen3VLDummyInputsBuilder,
399 )
400 class Qwen3_5ForConditionalGeneration(Qwen3VLForConditionalGeneration, IsHybrid):
400 - # Qwen3.5 does not support multimodal pruning (EVS).
401 - supports_multimodal_pruning = False
401 + supports_multimodal_pruning = True
402
403 packed_modules_mapping = Qwen3VLForConditionalGeneration.packed_modules_mapping | {
404 "in_proj_qkvz": ["in_proj_qkv", "in_proj_z"],
@@ -416,8 +416,21 @@ class Qwen3_5ForConditionalGeneration(Qwen3VLForConditionalGeneration, IsHybrid)
416 self.model_config = vllm_config.model_config
417 self.multimodal_config = multimodal_config
418 self.use_data_parallel = multimodal_config.mm_encoder_tp_mode == "data"
419 - # Qwen3.5 does not support multimodal pruning (EVS).
420 - self.is_multimodal_pruning_enabled = False
419 + self.is_multimodal_pruning_enabled = (
420 + multimodal_config.is_multimodal_pruning_enabled()
421 + )
422 + self.video_pruning_rate = self.multimodal_config.video_pruning_rate
423 + self._tokenizer = cached_tokenizer_from_config(vllm_config.model_config)
424 +
425 + # attributes needed by EVS-related functions inherited from Qwen3-VL
426 + self.use_deepstack = hasattr(config.vision_config, "deepstack_visual_indexes")
427 + self.deepstack_num_level = (
428 + len(config.vision_config.deepstack_visual_indexes)
429 + if self.use_deepstack
430 + else 0
431 + )
432 + self.visual_dim = config.vision_config.out_hidden_size
433 + self.multiscale_dim = self.visual_dim * self.deepstack_num_level
434
435 with self._mark_tower_model(vllm_config, {"image", "video"}):
436 self.visual = Qwen3_VisionTransformer(
@@ -462,12 +475,6 @@ class Qwen3_5ForConditionalGeneration(Qwen3VLForConditionalGeneration, IsHybrid)
475
476 return inputs_embeds
477
465 - def recompute_mrope_positions(self, *args, **kwargs):
466 - raise NotImplementedError(
467 - "Qwen3.5 does not support multimodal pruning (EVS). "
468 - "recompute_mrope_positions should never be called."
469 - )
470 -
478 def forward(
479 self,
480 input_ids: torch.Tensor,
@@ -628,8 +635,21 @@ class Qwen3_5MoeForConditionalGeneration(
635 self.model_config = vllm_config.model_config
636 self.multimodal_config = multimodal_config
637 self.use_data_parallel = multimodal_config.mm_encoder_tp_mode == "data"
631 - # Qwen3.5 does not support multimodal pruning (EVS).
632 - self.is_multimodal_pruning_enabled = False
638 + self.is_multimodal_pruning_enabled = (
639 + multimodal_config.is_multimodal_pruning_enabled()
640 + )
641 + self.video_pruning_rate = self.multimodal_config.video_pruning_rate
642 + self._tokenizer = cached_tokenizer_from_config(vllm_config.model_config)
643 +
644 + # attributes needed by EVS-related functions inherited from Qwen3-VL
645 + self.use_deepstack = hasattr(config.vision_config, "deepstack_visual_indexes")
646 + self.deepstack_num_level = (
647 + len(config.vision_config.deepstack_visual_indexes)
648 + if self.use_deepstack
649 + else 0
650 + )
651 + self.visual_dim = config.vision_config.out_hidden_size
652 + self.multiscale_dim = self.visual_dim * self.deepstack_num_level
653
654 with self._mark_tower_model(vllm_config, {"image", "video"}):
655 self.visual = Qwen3_VisionTransformer(