main
py 752 lines 27.3 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 # Copyright 2025 The vLLM team.
5 # Copyright 2025 The Qwen Team.
6 # Copyright 2025 The HuggingFace Inc. team.
7 # All rights reserved.
8 #
9 # This code is based on EleutherAI's GPT-NeoX library and the GPT-NeoX
10 # and OPT implementations in this library. It has been modified from its
11 # original forms to accommodate minor architectural differences compared
12 # to GPT-NeoX and OPT used by the Meta AI team that trained the model.
13 #
14 # Licensed under the Apache License, Version 2.0 (the "License");
15 # you may not use this file except in compliance with the License.
16 # You may obtain a copy of the License at
17 #
18 # http://www.apache.org/licenses/LICENSE-2.0
19 #
20 # Unless required by applicable law or agreed to in writing, software
21 # distributed under the License is distributed on an "AS IS" BASIS,
22 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23 # See the License for the specific language governing permissions and
24 # limitations under the License.
25 """Inference-only Qwen3.5 Series compatible with HuggingFace weights."""
26
27 from collections.abc import Iterable
28 from typing import ClassVar
29
30 import torch
31 from torch import nn
32
33 from vllm.compilation.decorators import support_torch_compile
34 from vllm.config import VllmConfig
35 from vllm.distributed import (
36 get_pp_group,
37 )
38 from vllm.logger import init_logger
39 from vllm.model_executor.layers.fused_moe.utils import (
40 is_model_fused_shared_expert_compatible,
41 )
42 from vllm.model_executor.layers.fusion.mm_input_norm import build_mm_input_norm
43 from vllm.model_executor.layers.layernorm import GemmaRMSNorm as Qwen3_5RMSNorm
44 from vllm.model_executor.layers.logits_processor import LogitsProcessor
45 from vllm.model_executor.layers.mamba.gdn.qwen_gdn_linear_attn import (
46 QwenGatedDeltaNetAttention,
47 )
48 from vllm.model_executor.layers.mamba.mamba_utils import (
49 MambaStateCopyFunc,
50 MambaStateCopyFuncCalculator,
51 MambaStateDtypeCalculator,
52 MambaStateShapeCalculator,
53 )
54 from vllm.model_executor.layers.vocab_parallel_embedding import (
55 ParallelLMHead,
56 VocabParallelEmbedding,
57 )
58 from vllm.multimodal import MULTIMODAL_REGISTRY
59 from vllm.multimodal.inputs import MultiModalFeatureSpec
60 from vllm.sequence import IntermediateTensors
61 from vllm.tokenizers.registry import cached_tokenizer_from_config
62 from vllm.transformers_utils.configs.qwen3_5 import Qwen3_5Config, Qwen3_5TextConfig
63 from vllm.transformers_utils.configs.qwen3_5_moe import (
64 Qwen3_5MoeConfig,
65 Qwen3_5MoeTextConfig,
66 )
67
68 from .interfaces import (
69 HasInnerState,
70 IsHybrid,
71 MixtureOfExperts,
72 MultiModalEmbeddings,
73 SupportsEagle3,
74 SupportsLoRA,
75 SupportsMRoPE,
76 SupportsPP,
77 _require_is_multimodal,
78 )
79 from .qwen2_moe import Qwen2MoeMLP as Qwen3NextMLP
80 from .qwen3_next import (
81 Qwen3NextAttention,
82 Qwen3NextDecoderLayer,
83 Qwen3NextModel,
84 Qwen3NextSparseMoeBlock,
85 QwenNextMixtureOfExperts,
86 )
87 from .qwen3_vl import (
88 Qwen3_VisionTransformer,
89 Qwen3VLDummyInputsBuilder,
90 Qwen3VLForConditionalGeneration,
91 Qwen3VLMultiModalProcessor,
92 Qwen3VLProcessingInfo,
93 )
94 from .utils import (
95 AutoWeightsLoader,
96 PPMissingLayer,
97 WeightsMapper,
98 _merge_multimodal_embeddings,
99 extract_layer_index,
100 make_empty_intermediate_tensors_factory,
101 make_layers,
102 maybe_fuse_shared_experts,
103 maybe_prefix,
104 )
105
106 logger = init_logger(__name__)
107
108
109 class Qwen3_5ProcessingInfo(Qwen3VLProcessingInfo):
110 def get_hf_config(self):
111 return self.ctx.get_hf_config(Qwen3_5Config)
112
113
114 class Qwen3_5MoeProcessingInfo(Qwen3VLProcessingInfo):
115 def get_hf_config(self):
116 # transformers 5.x renames the top-level Qwen3.5-MoE config class to
117 # Qwen3_5MoeTextConfig for text-only models, while transformers ≤4.x
118 # returns Qwen3_5MoeConfig (the multimodal wrapper). Accept both so
119 # that vLLM works regardless of which transformers version is installed.
120 return self.ctx.get_hf_config((Qwen3_5MoeConfig, Qwen3_5MoeTextConfig))
121
122
123 class Qwen3_5DecoderLayer(Qwen3NextDecoderLayer):
124 def __init__(
125 self,
126 vllm_config: VllmConfig,
127 layer_type: str,
128 prefix: str = "",
129 ) -> None:
130 super(Qwen3NextDecoderLayer, self).__init__()
131
132 config = vllm_config.model_config.hf_text_config
133 model_config = vllm_config.model_config
134 cache_config = vllm_config.cache_config
135 parallel_config = vllm_config.parallel_config
136 quant_config = vllm_config.quant_config
137
138 self.layer_type = layer_type
139 self.layer_idx = extract_layer_index(prefix)
140 is_moe_layer = config.model_type == "qwen3_5_moe_text"
141 self.use_attn_reduce_scatter_for_moe = (
142 parallel_config.use_sequence_parallel_moe
143 and parallel_config.pipeline_parallel_size == 1
144 and is_moe_layer
145 )
146
147 if self.layer_type == "linear_attention":
148 self.linear_attn = QwenGatedDeltaNetAttention(
149 config=config,
150 vllm_config=vllm_config,
151 prefix=f"{prefix}.linear_attn",
152 gqa_interleaved_layout=False,
153 reduce_results=not self.use_attn_reduce_scatter_for_moe,
154 )
155 elif self.layer_type == "full_attention":
156 self.self_attn = Qwen3NextAttention(
157 config,
158 model_config=model_config,
159 cache_config=cache_config,
160 quant_config=quant_config,
161 prefix=f"{prefix}.self_attn",
162 reduce_results=not self.use_attn_reduce_scatter_for_moe,
163 )
164 else:
165 raise ValueError(f"Invalid layer_type {self.layer_type}")
166
167 # NOTE: Determine the MLP type based on the model type
168 # Qwen3.5 use all layers for MLP / Qwen3.5-MoE use sparse MoE blocks
169 if config.model_type == "qwen3_5_moe_text":
170 self.mlp = Qwen3NextSparseMoeBlock(
171 vllm_config=vllm_config,
172 prefix=f"{prefix}.mlp",
173 )
174 elif config.model_type == "qwen3_5_text":
175 self.mlp = Qwen3NextMLP(
176 hidden_size=config.hidden_size,
177 intermediate_size=config.intermediate_size,
178 hidden_act=config.hidden_act,
179 quant_config=quant_config,
180 prefix=f"{prefix}.mlp",
181 )
182 else:
183 raise ValueError(f"Invalid model_type {config.model_type}")
184
185 self.input_layernorm = Qwen3_5RMSNorm(
186 config.hidden_size, eps=config.rms_norm_eps
187 )
188 self.post_attention_layernorm = Qwen3_5RMSNorm(
189 config.hidden_size, eps=config.rms_norm_eps
190 )
191
192 self.layer_scale = getattr(config, "layer_scale", False)
193 if self.layer_scale:
194 self.attn_layer_scale = torch.nn.Parameter(
195 torch.zeros(
196 1,
197 1,
198 config.hidden_size,
199 ),
200 )
201 self.ffn_layer_scale = torch.nn.Parameter(
202 torch.zeros(
203 1,
204 1,
205 config.hidden_size,
206 ),
207 )
208
209
210 @support_torch_compile(
211 dynamic_arg_dims={
212 "input_ids": 0,
213 # positions is of shape (3, seq_len) if mrope is enabled for qwen2-vl,
214 # otherwise (seq_len, ).
215 "positions": -1,
216 "intermediate_tensors": 0,
217 "inputs_embeds": 0,
218 }
219 )
220 class Qwen3_5Model(Qwen3NextModel):
221 # Qwen3.5 ships the GDN in_proj checkpoints separately (qwen3-next
222 # pre-fuses them); fuse them on top of the qwen3-next QKV/gate_up mapping.
223 hf_to_vllm_mapper = Qwen3NextModel.hf_to_vllm_mapper | WeightsMapper(
224 orig_to_new_stacked={
225 ".in_proj_qkv": (".in_proj_qkvz", (0, 1, 2)),
226 ".in_proj_z": (".in_proj_qkvz", 3),
227 ".in_proj_b": (".in_proj_ba", 0),
228 ".in_proj_a": (".in_proj_ba", 1),
229 }
230 )
231
232 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
233 super(Qwen3NextModel, self).__init__()
234
235 config: Qwen3_5TextConfig | Qwen3_5MoeTextConfig = (
236 vllm_config.model_config.hf_text_config
237 )
238 parallel_config = vllm_config.parallel_config
239
240 eplb_config = parallel_config.eplb_config
241 self.num_redundant_experts = eplb_config.num_redundant_experts
242
243 self.config = config
244 self.quant_config = vllm_config.quant_config
245
246 self.vocab_size = config.vocab_size
247
248 self.embed_tokens = VocabParallelEmbedding(
249 self.vocab_size,
250 config.hidden_size,
251 )
252
253 def get_layer(prefix: str):
254 return Qwen3_5DecoderLayer(
255 vllm_config,
256 layer_type=config.layer_types[extract_layer_index(prefix)],
257 prefix=prefix,
258 )
259
260 self.start_layer, self.end_layer, self.layers = make_layers(
261 config.num_hidden_layers, get_layer, prefix=f"{prefix}.layers"
262 )
263 self.is_fused_shared_expert_enabled = is_model_fused_shared_expert_compatible(
264 self.layers,
265 Qwen3NextSparseMoeBlock,
266 "mlp",
267 )
268 self.make_empty_intermediate_tensors = make_empty_intermediate_tensors_factory(
269 ["hidden_states", "residual"], config.hidden_size
270 )
271
272 if get_pp_group().is_last_rank:
273 self.norm = Qwen3_5RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
274 else:
275 self.norm = PPMissingLayer()
276
277 self.aux_hidden_state_layers: tuple[int, ...] = ()
278
279 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
280 # FSE must match construction (Qwen3NextSparseMoeBlock): reroute the
281 # shared expert into the extra fused slot only when AITER FSE is both
282 # requested and compatible with the quant spec.
283 if "moe" in self.config.model_type:
284 weights = maybe_fuse_shared_experts(
285 weights,
286 enabled=self.is_fused_shared_expert_enabled,
287 n_routed_experts=self.config.num_experts,
288 n_shared_experts=1,
289 ckpt_prefix="mlp.shared_expert",
290 )
291 loader = AutoWeightsLoader(self)
292 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
293
294
295 class Qwen3_5ForCausalLMBase(
296 nn.Module,
297 HasInnerState,
298 IsHybrid,
299 SupportsEagle3,
300 SupportsLoRA,
301 SupportsMRoPE,
302 SupportsPP,
303 ):
304 packed_modules_mapping = {
305 "qkv_proj": [
306 "q_proj",
307 "k_proj",
308 "v_proj",
309 ],
310 "gate_up_proj": ["gate_proj", "up_proj"],
311 # GDN fused projections.
312 "in_proj_qkvz": ["in_proj_qkv", "in_proj_z"],
313 "in_proj_ba": ["in_proj_b", "in_proj_a"],
314 }
315 # Maps PEFT embed/lm_head LoRA targets onto vLLM embedding wrappers.
316 embedding_modules = {
317 "embed_tokens": "input_embeddings",
318 "lm_head": "output_embeddings",
319 }
320
321 # Some community text-only checkpoints keep the extraneous
322 # `model.language_model.` prefix inherited from the VL training stack.
323 # Strip it so both prefixed and clean checkpoints load correctly.
324 hf_to_vllm_mapper = WeightsMapper(
325 orig_to_new_prefix={"model.language_model.": "model.", "mtp.": None},
326 )
327
328 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
329 config = vllm_config.model_config.hf_text_config
330 self.vllm_config = vllm_config
331 self.model_config = vllm_config.model_config
332
333 scheduler_config = vllm_config.scheduler_config
334 self.quant_config = vllm_config.quant_config
335
336 super().__init__()
337 self.config = config
338 self.scheduler_config = scheduler_config
339 self.model = Qwen3_5Model(
340 vllm_config=vllm_config, prefix=maybe_prefix(prefix, "model")
341 )
342
343 if get_pp_group().is_last_rank:
344 self.lm_head = ParallelLMHead(
345 config.vocab_size,
346 config.hidden_size,
347 quant_config=self.quant_config,
348 prefix=maybe_prefix(prefix, "lm_head"),
349 )
350 if config.tie_word_embeddings:
351 self.lm_head = self.lm_head.tie_weights(self.model.embed_tokens)
352 else:
353 self.lm_head = PPMissingLayer()
354
355 self.logits_processor = LogitsProcessor(config.vocab_size)
356 self.make_empty_intermediate_tensors = (
357 self.model.make_empty_intermediate_tensors
358 )
359
360 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
361 return self.model.embed_input_ids(input_ids)
362
363 def set_aux_hidden_state_layers(self, layers: tuple[int, ...]) -> None:
364 self.model.aux_hidden_state_layers = layers
365
366 def get_eagle3_aux_hidden_state_layers(self) -> tuple[int, ...]:
367 num_layers = len(self.model.layers)
368 return (2, num_layers // 2, num_layers - 3)
369
370 def forward(
371 self,
372 input_ids: torch.Tensor,
373 positions: torch.Tensor,
374 intermediate_tensors: IntermediateTensors | None = None,
375 inputs_embeds: torch.Tensor | None = None,
376 **kwargs: object,
377 ):
378 hidden_states = self.model(
379 input_ids, positions, intermediate_tensors, inputs_embeds
380 )
381
382 return hidden_states
383
384 @classmethod
385 def get_mamba_state_dtype_from_config(
386 cls,
387 vllm_config: "VllmConfig",
388 ) -> tuple[torch.dtype, torch.dtype]:
389 return MambaStateDtypeCalculator.gated_delta_net_state_dtype(
390 vllm_config.model_config.dtype,
391 vllm_config.cache_config.mamba_cache_dtype,
392 vllm_config.cache_config.mamba_ssm_cache_dtype,
393 )
394
395 @classmethod
396 def get_mamba_state_shape_from_config(
397 cls, vllm_config: "VllmConfig"
398 ) -> tuple[tuple[int, int], tuple[int, int]]:
399 parallel_config = vllm_config.parallel_config
400 hf_config = vllm_config.model_config.hf_text_config
401 tp_size = parallel_config.tensor_parallel_size
402 num_spec = (
403 vllm_config.speculative_config.num_speculative_tokens
404 if vllm_config.speculative_config
405 else 0
406 )
407 return MambaStateShapeCalculator.gated_delta_net_state_shape(
408 tp_size,
409 hf_config.linear_num_key_heads,
410 hf_config.linear_num_value_heads,
411 hf_config.linear_key_head_dim,
412 hf_config.linear_value_head_dim,
413 hf_config.linear_conv_kernel_dim,
414 num_spec,
415 )
416
417 @classmethod
418 def get_mamba_state_copy_func(
419 cls,
420 ) -> tuple[MambaStateCopyFunc, MambaStateCopyFunc]:
421 return MambaStateCopyFuncCalculator.gated_delta_net_state_copy_func()
422
423 def compute_logits(
424 self,
425 hidden_states: torch.Tensor,
426 ) -> torch.Tensor | None:
427 return self.logits_processor(self.lm_head, hidden_states)
428
429 def compute_logits_local(
430 self,
431 hidden_states: torch.Tensor,
432 ) -> torch.Tensor:
433 return self.logits_processor(self.lm_head, hidden_states, skip_gather=True)
434
435 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
436 loader = AutoWeightsLoader(self)
437 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
438
439 def get_mrope_input_positions(
440 self,
441 input_tokens: list[int],
442 mm_features: list[MultiModalFeatureSpec],
443 ) -> tuple[torch.Tensor, int]:
444 positions = torch.arange(len(input_tokens), dtype=torch.long)
445 return positions.unsqueeze(0).expand(3, -1), 0
446
447
448 class Qwen3_5ForCausalLM(Qwen3_5ForCausalLMBase):
449 pass
450
451
452 class Qwen3_5MoeForCausalLM(Qwen3_5ForCausalLMBase, QwenNextMixtureOfExperts):
453 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
454 super().__init__(vllm_config=vllm_config, prefix=prefix)
455
456 # set MoE hyperparameters
457 self.set_moe_parameters()
458
459
460 ########################################################
461 # Qwen3_5-Dense
462 ########################################################
463
464
465 @MULTIMODAL_REGISTRY.register_processor(
466 Qwen3VLMultiModalProcessor,
467 info=Qwen3_5ProcessingInfo,
468 dummy_inputs=Qwen3VLDummyInputsBuilder,
469 )
470 class Qwen3_5ForConditionalGeneration(Qwen3VLForConditionalGeneration, IsHybrid):
471 supports_multimodal_pruning = True
472
473 hf_to_vllm_mapper = (
474 Qwen3VLForConditionalGeneration.hf_to_vllm_mapper
475 | WeightsMapper(orig_to_new_prefix={"mtp.": None})
476 )
477
478 packed_modules_mapping = Qwen3VLForConditionalGeneration.packed_modules_mapping | {
479 "in_proj_qkvz": ["in_proj_qkv", "in_proj_z"],
480 "in_proj_ba": ["in_proj_b", "in_proj_a"],
481 }
482
483 def __init__(self, *, vllm_config: VllmConfig, prefix: str = "model"):
484 # protocols have not __init__ method, so we need to use nn.Module.__init__
485 nn.Module.__init__(self)
486 config: Qwen3_5Config = vllm_config.model_config.hf_config
487 quant_config = vllm_config.quant_config
488 multimodal_config = vllm_config.model_config.get_multimodal_config()
489
490 self.config = config
491 self.model_config = vllm_config.model_config
492 self.multimodal_config = multimodal_config
493 self.use_data_parallel = multimodal_config.mm_encoder_tp_mode == "data"
494 self.is_multimodal_pruning_enabled = (
495 multimodal_config.is_multimodal_pruning_enabled()
496 )
497 self.video_pruning_rate = self.multimodal_config.video_pruning_rate
498 self._tokenizer = cached_tokenizer_from_config(vllm_config.model_config)
499
500 # attributes needed by EVS-related functions inherited from Qwen3-VL
501 self.use_deepstack = hasattr(config.vision_config, "deepstack_visual_indexes")
502 self.deepstack_num_level = (
503 len(config.vision_config.deepstack_visual_indexes)
504 if self.use_deepstack
505 else 0
506 )
507 self.visual_dim = config.vision_config.out_hidden_size
508 self.multiscale_dim = self.visual_dim * self.deepstack_num_level
509
510 with self._mark_tower_model(vllm_config, {"image", "video"}):
511 self.visual = Qwen3_VisionTransformer(
512 config.vision_config,
513 norm_eps=getattr(config, "rms_norm_eps", 1e-6),
514 quant_config=quant_config,
515 input_norm=build_mm_input_norm(self.model_config),
516 prefix=maybe_prefix(prefix, "visual"),
517 )
518
519 with self._mark_language_model(vllm_config):
520 self.language_model = Qwen3_5ForCausalLM(
521 vllm_config=vllm_config, prefix=maybe_prefix(prefix, "language_model")
522 )
523
524 self.make_empty_intermediate_tensors = (
525 self.language_model.make_empty_intermediate_tensors
526 )
527
528 def embed_input_ids(
529 self,
530 input_ids: torch.Tensor,
531 multimodal_embeddings: MultiModalEmbeddings | None = None,
532 *,
533 is_multimodal: torch.Tensor | None = None,
534 ) -> torch.Tensor:
535 inputs_embeds = self._embed_text_input_ids(
536 input_ids,
537 self.language_model.embed_input_ids,
538 is_multimodal=is_multimodal,
539 )
540
541 if multimodal_embeddings is None or len(multimodal_embeddings) == 0:
542 return inputs_embeds
543
544 is_multimodal = _require_is_multimodal(is_multimodal)
545
546 inputs_embeds = _merge_multimodal_embeddings(
547 inputs_embeds=inputs_embeds,
548 multimodal_embeddings=multimodal_embeddings,
549 is_multimodal=is_multimodal,
550 )
551
552 return inputs_embeds
553
554 def forward(
555 self,
556 input_ids: torch.Tensor,
557 positions: torch.Tensor,
558 intermediate_tensors: IntermediateTensors | None = None,
559 inputs_embeds: torch.Tensor | None = None,
560 **kwargs: object,
561 ) -> torch.Tensor | IntermediateTensors:
562 """Run forward pass for Qwen3.5.
563
564 Args:
565 input_ids: Flattened (concatenated) input_ids corresponding to a
566 batch.
567 positions: Flattened (concatenated) position ids corresponding to a
568 batch.
569 **NOTE**: If mrope is enabled (default setting for Qwen3VL
570 opensource models), the shape will be `(3, seq_len)`,
571 otherwise it will be `(seq_len,).
572 intermediate_tensors: Intermediate tensors from previous pipeline
573 stages.
574 inputs_embeds: Pre-computed input embeddings.
575 **kwargs: Additional keyword arguments including:
576 - pixel_values: Pixel values to be fed to a model.
577 `None` if no images are passed.
578 - image_grid_thw: Tensor `(n_images, 3)` of image 3D grid in
579 LLM. `None` if no images are passed.
580 - pixel_values_videos: Pixel values of videos to be fed to a
581 model. `None` if no videos are passed.
582 - video_grid_thw: Tensor `(n_videos, 3)` of video 3D grid in
583 LLM. `None` if no videos are passed.
584
585 """
586 if intermediate_tensors is not None:
587 inputs_embeds = None
588
589 hidden_states = self.language_model.model(
590 input_ids=input_ids,
591 positions=positions,
592 intermediate_tensors=intermediate_tensors,
593 inputs_embeds=inputs_embeds,
594 )
595
596 return hidden_states
597
598 def compute_logits_local(self, hidden_states: torch.Tensor) -> torch.Tensor:
599 return self.language_model.compute_logits_local(hidden_states)
600
601 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
602 loader = AutoWeightsLoader(self)
603 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
604
605 @classmethod
606 def get_mamba_state_dtype_from_config(
607 cls,
608 vllm_config: "VllmConfig",
609 ) -> tuple[torch.dtype, torch.dtype]:
610 return MambaStateDtypeCalculator.gated_delta_net_state_dtype(
611 vllm_config.model_config.dtype,
612 vllm_config.cache_config.mamba_cache_dtype,
613 vllm_config.cache_config.mamba_ssm_cache_dtype,
614 )
615
616 @classmethod
617 def get_mamba_state_shape_from_config(
618 cls, vllm_config: "VllmConfig"
619 ) -> tuple[tuple[int, int], tuple[int, int]]:
620 parallel_config = vllm_config.parallel_config
621 hf_config = vllm_config.model_config.hf_text_config
622 tp_size = parallel_config.tensor_parallel_size
623 num_spec = (
624 vllm_config.speculative_config.num_speculative_tokens
625 if vllm_config.speculative_config
626 else 0
627 )
628 return MambaStateShapeCalculator.gated_delta_net_state_shape(
629 tp_size,
630 hf_config.linear_num_key_heads,
631 hf_config.linear_num_value_heads,
632 hf_config.linear_key_head_dim,
633 hf_config.linear_value_head_dim,
634 hf_config.linear_conv_kernel_dim,
635 num_spec,
636 )
637
638 @classmethod
639 def get_mamba_state_copy_func(cls) -> tuple[MambaStateCopyFunc, MambaStateCopyFunc]:
640 return MambaStateCopyFuncCalculator.gated_delta_net_state_copy_func()
641
642
643 ########################################################
644 # Qwen3_5-MoE
645 ########################################################
646
647
648 class Qwen3_5_MoeMixtureOfExperts(MixtureOfExperts):
649 language_model: Qwen3_5ForCausalLM
650 num_local_physical_experts: int
651
652 def update_physical_experts_metadata(
653 self,
654 num_physical_experts: int,
655 num_local_physical_experts: int,
656 ) -> None:
657 assert self.num_local_physical_experts == num_local_physical_experts
658 self.num_physical_experts = num_physical_experts
659 self.num_local_physical_experts = num_local_physical_experts
660 self.num_redundant_experts = num_physical_experts - self.num_logical_experts
661 for layer in self.language_model.model.layers:
662 if isinstance(layer.mlp, Qwen3NextSparseMoeBlock):
663 moe = layer.mlp
664 moe.n_local_physical_experts = num_local_physical_experts
665 moe.n_physical_experts = num_physical_experts
666 moe.n_redundant_experts = self.num_redundant_experts
667 moe.experts.update_expert_map()
668
669 def set_moe_parameters(self):
670 self.moe_layers = []
671 example_moe = None
672 for layer in self.language_model.model.layers:
673 if isinstance(layer, Qwen3_5DecoderLayer) and isinstance(
674 layer.mlp, Qwen3NextSparseMoeBlock
675 ):
676 example_moe = layer.mlp
677 self.moe_layers.append(layer.mlp.experts)
678
679 if example_moe is None:
680 raise RuntimeError(
681 "No Qwen3_5 layer found in the language_model.model.layers."
682 )
683
684 # Set MoE hyperparameters
685 self.num_moe_layers = len(self.moe_layers)
686 self.num_expert_groups = 1
687 self.num_shared_experts = 0
688 self.num_logical_experts = example_moe.n_logical_experts
689 self.num_physical_experts = example_moe.n_physical_experts
690 self.num_local_physical_experts = example_moe.n_local_physical_experts
691 self.num_routed_experts = example_moe.n_routed_experts
692 self.num_redundant_experts = example_moe.n_redundant_experts
693
694
695 @MULTIMODAL_REGISTRY.register_processor(
696 Qwen3VLMultiModalProcessor,
697 info=Qwen3_5MoeProcessingInfo,
698 dummy_inputs=Qwen3VLDummyInputsBuilder,
699 )
700 class Qwen3_5MoeForConditionalGeneration(
701 Qwen3_5ForConditionalGeneration, Qwen3_5_MoeMixtureOfExperts
702 ):
703 # For MoE LoRA weights loading
704 is_3d_moe_weight: ClassVar[bool] = True
705
706 def __init__(self, *, vllm_config: VllmConfig, prefix: str = "model"):
707 # protocols have not __init__ method, so we need to use nn.Module.__init__
708 nn.Module.__init__(self)
709 config: Qwen3_5MoeConfig = vllm_config.model_config.hf_config
710 quant_config = vllm_config.quant_config
711 multimodal_config = vllm_config.model_config.get_multimodal_config()
712
713 self.config = config
714 self.model_config = vllm_config.model_config
715 self.multimodal_config = multimodal_config
716 self.use_data_parallel = multimodal_config.mm_encoder_tp_mode == "data"
717 self.is_multimodal_pruning_enabled = (
718 multimodal_config.is_multimodal_pruning_enabled()
719 )
720 self.video_pruning_rate = self.multimodal_config.video_pruning_rate
721 self._tokenizer = cached_tokenizer_from_config(vllm_config.model_config)
722
723 # attributes needed by EVS-related functions inherited from Qwen3-VL
724 self.use_deepstack = hasattr(config.vision_config, "deepstack_visual_indexes")
725 self.deepstack_num_level = (
726 len(config.vision_config.deepstack_visual_indexes)
727 if self.use_deepstack
728 else 0
729 )
730 self.visual_dim = config.vision_config.out_hidden_size
731 self.multiscale_dim = self.visual_dim * self.deepstack_num_level
732
733 with self._mark_tower_model(vllm_config, {"image", "video"}):
734 self.visual = Qwen3_VisionTransformer(
735 config.vision_config,
736 norm_eps=getattr(config, "rms_norm_eps", 1e-6),
737 quant_config=quant_config,
738 input_norm=build_mm_input_norm(self.model_config),
739 prefix=maybe_prefix(prefix, "visual"),
740 )
741
742 with self._mark_language_model(vllm_config):
743 self.language_model = Qwen3_5MoeForCausalLM(
744 vllm_config=vllm_config, prefix=maybe_prefix(prefix, "language_model")
745 )
746
747 self.make_empty_intermediate_tensors = (
748 self.language_model.make_empty_intermediate_tensors
749 )
750
751 # set MoE hyperparameters
752 self.set_moe_parameters()