main
py 312 lines 11.6 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-VL-MoE model compatible with HuggingFace weights."""
26
27 from itertools import islice
28 from typing import ClassVar
29
30 import torch
31 from transformers.models.qwen3_vl_moe.configuration_qwen3_vl_moe import (
32 Qwen3VLMoeConfig,
33 )
34
35 from vllm.compilation.decorators import support_torch_compile
36 from vllm.config import VllmConfig
37 from vllm.distributed import get_pp_group
38 from vllm.logger import init_logger
39 from vllm.model_executor.layers.fusion.mm_input_norm import build_mm_input_norm
40 from vllm.model_executor.layers.logits_processor import LogitsProcessor
41 from vllm.model_executor.layers.vocab_parallel_embedding import ParallelLMHead
42 from vllm.multimodal import MULTIMODAL_REGISTRY
43 from vllm.sequence import IntermediateTensors
44 from vllm.tokenizers.registry import cached_tokenizer_from_config
45
46 from .interfaces import MixtureOfExperts
47 from .qwen3_moe import (
48 Qwen3MoeDecoderLayer,
49 Qwen3MoeForCausalLM,
50 Qwen3MoeModel,
51 Qwen3MoeSparseMoeBlock,
52 )
53 from .qwen3_vl import (
54 Qwen3_VisionTransformer,
55 Qwen3VLDummyInputsBuilder,
56 Qwen3VLForConditionalGeneration,
57 Qwen3VLMultiModalProcessor,
58 Qwen3VLProcessingInfo,
59 )
60 from .utils import StageMissingLayer, maybe_prefix
61
62 logger = init_logger(__name__)
63
64
65 class Qwen3VLMoeProcessingInfo(Qwen3VLProcessingInfo):
66 def get_hf_config(self):
67 return self.ctx.get_hf_config(Qwen3VLMoeConfig)
68
69
70 class Qwen3VLMoeDecoderLayer(Qwen3MoeDecoderLayer):
71 def __init__(self, vllm_config: VllmConfig, prefix: str = "") -> None:
72 super().__init__(
73 vllm_config=vllm_config,
74 prefix=prefix,
75 is_fused_checkpoint_transposed=True,
76 )
77
78
79 @support_torch_compile(
80 dynamic_arg_dims={
81 "input_ids": 0,
82 # positions is of shape (3, seq_len) if mrope is enabled for qwen2-vl,
83 # otherwise (seq_len, ).
84 "positions": -1,
85 "intermediate_tensors": 0,
86 "inputs_embeds": 0,
87 # the same shape as input_embeds
88 "deepstack_input_embeds": 0,
89 }
90 )
91 class Qwen3MoeLLMModel(Qwen3MoeModel):
92 def __init__(
93 self,
94 *,
95 vllm_config: VllmConfig,
96 prefix: str = "",
97 decoder_layer_type: type[torch.nn.Module] = Qwen3VLMoeDecoderLayer,
98 ):
99 super().__init__(
100 vllm_config=vllm_config,
101 prefix=prefix,
102 decoder_layer_type=decoder_layer_type,
103 )
104
105 def forward(
106 self,
107 input_ids: torch.Tensor | None,
108 positions: torch.Tensor,
109 intermediate_tensors: IntermediateTensors | None = None,
110 inputs_embeds: torch.Tensor | None = None,
111 deepstack_input_embeds: IntermediateTensors | None = None,
112 ) -> torch.Tensor | IntermediateTensors:
113 if get_pp_group().is_first_rank:
114 if inputs_embeds is not None:
115 hidden_states = inputs_embeds
116 else:
117 hidden_states = self.embed_input_ids(input_ids)
118 residual = None
119 else:
120 assert intermediate_tensors is not None
121 hidden_states = intermediate_tensors["hidden_states"]
122 residual = intermediate_tensors["residual"]
123
124 aux_hidden_states = self._maybe_add_hidden_state(
125 [], self.start_layer, hidden_states, residual
126 )
127 for layer_idx, layer in islice(
128 enumerate(self.layers), self.start_layer, self.end_layer
129 ):
130 hidden_states, residual = layer(
131 positions,
132 hidden_states,
133 residual,
134 )
135 if deepstack_input_embeds is not None and layer_idx in range(
136 0, len(deepstack_input_embeds)
137 ):
138 hidden_states = (
139 hidden_states
140 + deepstack_input_embeds[f"deepstack_input_embeds_{layer_idx}"]
141 )
142
143 self._maybe_add_hidden_state(
144 aux_hidden_states, layer_idx + 1, hidden_states, residual
145 )
146
147 if not get_pp_group().is_last_rank:
148 return IntermediateTensors(
149 {"hidden_states": hidden_states, "residual": residual}
150 )
151 hidden_states, _ = self.norm(hidden_states, residual)
152
153 if len(aux_hidden_states) > 0:
154 return hidden_states, aux_hidden_states
155 return hidden_states
156
157
158 class Qwen3MoeLLMForCausalLM(Qwen3MoeForCausalLM):
159 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
160 super(Qwen3MoeForCausalLM, self).__init__()
161 self.config = vllm_config.model_config.hf_config
162 self.quant_config = vllm_config.quant_config
163 self.model = Qwen3MoeLLMModel(
164 vllm_config=vllm_config, prefix=maybe_prefix(prefix, "model")
165 )
166 self.lm_head = ParallelLMHead(
167 self.config.vocab_size,
168 self.config.hidden_size,
169 quant_config=self.quant_config,
170 prefix=maybe_prefix(prefix, "lm_head"),
171 )
172 if self.config.tie_word_embeddings:
173 self.lm_head = self.lm_head.tie_weights(self.model.embed_tokens)
174 self.logits_processor = LogitsProcessor(self.config.vocab_size)
175 self.make_empty_intermediate_tensors = (
176 self.model.make_empty_intermediate_tensors
177 )
178
179
180 class Qwen3VLMoeMixtureOfExperts(MixtureOfExperts):
181 language_model: Qwen3MoeLLMForCausalLM
182 num_local_physical_experts: int
183
184 def update_physical_experts_metadata(
185 self,
186 num_physical_experts: int,
187 num_local_physical_experts: int,
188 ) -> None:
189 assert self.num_local_physical_experts == num_local_physical_experts
190 self.num_physical_experts = num_physical_experts
191 self.num_local_physical_experts = num_local_physical_experts
192 self.num_redundant_experts = num_physical_experts - self.num_logical_experts
193 for layer in self.language_model.model.layers:
194 if isinstance(layer.mlp, Qwen3MoeSparseMoeBlock):
195 moe = layer.mlp
196 moe.n_local_physical_experts = num_local_physical_experts
197 moe.n_physical_experts = num_physical_experts
198 moe.n_redundant_experts = self.num_redundant_experts
199 moe.experts.update_expert_map()
200
201 def set_moe_parameters(self):
202 self.moe_layers = []
203 example_moe = None
204 for layer in self.language_model.model.layers:
205 if hasattr(layer, "mlp") and isinstance(layer.mlp, Qwen3MoeSparseMoeBlock):
206 example_moe = layer.mlp
207 self.moe_layers.append(layer.mlp.experts)
208
209 if example_moe is None:
210 raise RuntimeError("No Qwen3Moe layer found in the language_model.")
211
212 # Set MoE hyperparameters
213 self.num_moe_layers = len(self.moe_layers)
214 self.num_expert_groups = 1
215 self.num_shared_experts = 0
216 self.num_logical_experts = example_moe.n_logical_experts
217 self.num_physical_experts = example_moe.n_physical_experts
218 self.num_local_physical_experts = example_moe.n_local_physical_experts
219 self.num_routed_experts = example_moe.n_routed_experts
220 self.num_redundant_experts = example_moe.n_redundant_experts
221
222
223 @MULTIMODAL_REGISTRY.register_processor(
224 Qwen3VLMultiModalProcessor,
225 info=Qwen3VLMoeProcessingInfo,
226 dummy_inputs=Qwen3VLDummyInputsBuilder,
227 )
228 class Qwen3VLMoeForConditionalGeneration(
229 Qwen3VLForConditionalGeneration, Qwen3VLMoeMixtureOfExperts
230 ):
231 is_3d_moe_weight: ClassVar[bool] = True
232 packed_modules_mapping = {
233 "qkv_proj": [
234 "q_proj",
235 "k_proj",
236 "v_proj",
237 ],
238 }
239
240 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
241 super(Qwen3VLForConditionalGeneration, self).__init__()
242 config: Qwen3VLMoeConfig = vllm_config.model_config.hf_config
243 quant_config = vllm_config.quant_config
244 multimodal_config = vllm_config.model_config.get_multimodal_config()
245
246 self.config = config
247 self.model_config = vllm_config.model_config
248 self._tokenizer = cached_tokenizer_from_config(vllm_config.model_config)
249 self.multimodal_config = multimodal_config
250 self.use_data_parallel = multimodal_config.mm_encoder_tp_mode == "data"
251 self._init_video_pruning(multimodal_config)
252
253 with self._mark_tower_model(vllm_config, {"image", "video"}):
254 self.visual = Qwen3_VisionTransformer(
255 config.vision_config,
256 norm_eps=getattr(config, "rms_norm_eps", 1e-6),
257 quant_config=quant_config,
258 input_norm=build_mm_input_norm(self.model_config),
259 prefix=maybe_prefix(prefix, "visual"),
260 )
261
262 self.use_deepstack = hasattr(
263 config.vision_config, "deepstack_visual_indexes"
264 ) and not isinstance(self.visual, StageMissingLayer)
265 self.deepstack_num_level = (
266 len(config.vision_config.deepstack_visual_indexes)
267 if self.use_deepstack
268 else 0
269 )
270 self.visual_dim = config.vision_config.out_hidden_size
271 self.multiscale_dim = self.visual_dim * self.deepstack_num_level
272
273 if self.use_deepstack:
274 self.deepstack_input_embeds = [
275 torch.zeros(
276 vllm_config.scheduler_config.max_num_batched_tokens,
277 config.text_config.hidden_size,
278 )
279 for _ in range(self.deepstack_num_level)
280 ]
281
282 with self._mark_language_model(vllm_config):
283 self.language_model = Qwen3MoeLLMForCausalLM(
284 vllm_config=vllm_config.with_hf_config(
285 config.text_config,
286 architectures=["Qwen3MoeForCausalLM"],
287 ),
288 prefix=maybe_prefix(prefix, "language_model"),
289 )
290
291 if not get_pp_group().is_first_rank and hasattr(
292 config.vision_config, "deepstack_visual_indexes"
293 ):
294 assert self.language_model.model.start_layer >= len(
295 config.vision_config.deepstack_visual_indexes
296 ), (
297 "start_layer should be greater than or equal to "
298 "len(deepstack_visual_indexes)"
299 )
300
301 # Whether to include the gate_up_proj mapping is determined by
302 # the language model. Keep this override local to the instance.
303 self.packed_modules_mapping = ( # type: ignore[misc]
304 self.packed_modules_mapping | self.language_model.packed_modules_mapping
305 )
306
307 self.make_empty_intermediate_tensors = (
308 self.language_model.make_empty_intermediate_tensors
309 )
310
311 # Set MoE hyperparameters
312 self.set_moe_parameters()