main
py 510 lines 18.4 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 # Adapted from
5 # https://github.com/huggingface/transformers/blob/v4.28.0/src/transformers/models/qwen2_moe/modeling_qwen2_moe.py
6 # Copyright 2024 The Qwen team.
7 # Copyright 2023 The vLLM team.
8 # Copyright 2022 EleutherAI and the HuggingFace Inc. team. All rights reserved.
9 #
10 # This code is based on EleutherAI's GPT-NeoX library and the GPT-NeoX
11 # and OPT implementations in this library. It has been modified from its
12 # original forms to accommodate minor architectural differences compared
13 # to GPT-NeoX and OPT used by the Meta AI team that trained the model.
14 #
15 # Licensed under the Apache License, Version 2.0 (the "License");
16 # you may not use this file except in compliance with the License.
17 # You may obtain a copy of the License at
18 #
19 # http://www.apache.org/licenses/LICENSE-2.0
20 #
21 # Unless required by applicable law or agreed to in writing, software
22 # distributed under the License is distributed on an "AS IS" BASIS,
23 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24 # See the License for the specific language governing permissions and
25 # limitations under the License.
26 """Inference-only Qwen2MoE model compatible with HuggingFace weights."""
27
28 from collections.abc import Iterable
29 from itertools import islice
30 from typing import Any
31
32 import torch
33 import torch.nn.functional as F
34 from torch import nn
35 from transformers import Qwen2MoeConfig
36
37 from vllm.compilation.decorators import support_torch_compile
38 from vllm.config import CacheConfig, VllmConfig
39 from vllm.distributed import get_pp_group, get_tensor_model_parallel_world_size
40 from vllm.logger import init_logger
41 from vllm.model_executor.layers.activation import SiluAndMul
42 from vllm.model_executor.layers.attention import Attention
43 from vllm.model_executor.layers.fused_moe import (
44 FusedMoEFactory,
45 GateLinear,
46 )
47 from vllm.model_executor.layers.layernorm import RMSNorm
48 from vllm.model_executor.layers.linear import (
49 MergedColumnParallelLinear,
50 QKVParallelLinear,
51 ReplicatedLinear,
52 RowParallelLinear,
53 )
54 from vllm.model_executor.layers.logits_processor import LogitsProcessor
55 from vllm.model_executor.layers.quantization import QuantizationConfig
56 from vllm.model_executor.layers.rotary_embedding import get_rope
57 from vllm.model_executor.layers.vocab_parallel_embedding import (
58 ParallelLMHead,
59 VocabParallelEmbedding,
60 )
61 from vllm.sequence import IntermediateTensors
62
63 from .interfaces import SupportsLoRA, SupportsPP
64 from .utils import (
65 AutoWeightsLoader,
66 WeightsMapper,
67 extract_layer_index,
68 make_empty_intermediate_tensors_factory,
69 make_layers,
70 maybe_prefix,
71 )
72
73 logger = init_logger(__name__)
74
75
76 class Qwen2MoeMLP(nn.Module):
77 def __init__(
78 self,
79 hidden_size: int,
80 intermediate_size: int,
81 hidden_act: str,
82 quant_config: QuantizationConfig | None = None,
83 reduce_results: bool = True,
84 expert_gate: torch.nn.Linear | None = None,
85 is_sequence_parallel: bool = False,
86 disable_tp: bool = False,
87 prefix: str = "",
88 ) -> None:
89 super().__init__()
90 disable_tp = disable_tp or is_sequence_parallel
91 self.gate_up_proj = MergedColumnParallelLinear(
92 hidden_size,
93 [intermediate_size] * 2,
94 bias=False,
95 quant_config=quant_config,
96 disable_tp=disable_tp,
97 prefix=f"{prefix}.gate_up_proj",
98 )
99 self.down_proj = RowParallelLinear(
100 intermediate_size,
101 hidden_size,
102 bias=False,
103 quant_config=quant_config,
104 reduce_results=reduce_results,
105 disable_tp=disable_tp,
106 prefix=f"{prefix}.down_proj",
107 )
108 if hidden_act != "silu":
109 raise ValueError(
110 f"Unsupported activation: {hidden_act}. Only silu is supported for now."
111 )
112 self.act_fn = SiluAndMul()
113 self.expert_gate = expert_gate
114
115 def forward(self, x):
116 gate_up, _ = self.gate_up_proj(x)
117 out = self.act_fn(gate_up)
118 out, _ = self.down_proj(out)
119
120 if self.expert_gate is not None:
121 out = F.sigmoid(self.expert_gate(x)[0]) * out
122
123 return out
124
125
126 class Qwen2MoeSparseMoeBlock(nn.Module):
127 def __init__(
128 self,
129 config: Qwen2MoeConfig,
130 quant_config: QuantizationConfig | None = None,
131 prefix: str = "",
132 ):
133 super().__init__()
134 self.tp_size = get_tensor_model_parallel_world_size()
135
136 if self.tp_size > config.num_experts:
137 raise ValueError(
138 f"Tensor parallel size {self.tp_size} is greater than "
139 f"the number of experts {config.num_experts}."
140 )
141
142 self.gate = GateLinear(
143 config.hidden_size,
144 config.num_experts,
145 prefix=f"{prefix}.gate",
146 )
147
148 self.shared_expert_gate = ReplicatedLinear(
149 config.hidden_size,
150 1,
151 bias=False,
152 quant_config=None,
153 prefix=f"{prefix}.shared_expert_gate",
154 )
155
156 self.shared_expert: Qwen2MoeMLP | None
157 if config.shared_expert_intermediate_size > 0:
158 self.shared_expert = Qwen2MoeMLP(
159 hidden_size=config.hidden_size,
160 intermediate_size=config.shared_expert_intermediate_size,
161 hidden_act=config.hidden_act,
162 quant_config=quant_config,
163 reduce_results=False,
164 expert_gate=self.shared_expert_gate,
165 prefix=f"{prefix}.shared_expert",
166 )
167 else:
168 self.shared_expert = None
169
170 self.experts = FusedMoEFactory(
171 shared_experts=self.shared_expert,
172 num_experts=config.num_experts,
173 top_k=config.num_experts_per_tok,
174 hidden_size=config.hidden_size,
175 intermediate_size=config.moe_intermediate_size,
176 renormalize=config.norm_topk_prob,
177 quant_config=quant_config,
178 prefix=f"{prefix}.experts",
179 )
180
181 def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
182 # NOTE: hidden_states can have either 1D or 2D shape.
183 orig_shape = hidden_states.shape
184 hidden_dim = hidden_states.shape[-1]
185 hidden_states = hidden_states.view(-1, hidden_dim)
186
187 # router_logits: (num_tokens, n_experts)
188 router_logits, _ = self.gate(hidden_states)
189 final_hidden_states = self.experts(
190 hidden_states=hidden_states, router_logits=router_logits
191 )
192
193 return final_hidden_states.view(orig_shape)
194
195
196 class Qwen2MoeAttention(nn.Module):
197 def __init__(
198 self,
199 hidden_size: int,
200 num_heads: int,
201 num_kv_heads: int,
202 rope_parameters: dict[str, Any] | None = None,
203 max_position_embeddings: int = 8192,
204 cache_config: CacheConfig | None = None,
205 quant_config: QuantizationConfig | None = None,
206 prefix: str = "",
207 dual_chunk_attention_config: dict[str, Any] | None = None,
208 ) -> None:
209 super().__init__()
210 self.hidden_size = hidden_size
211 tp_size = get_tensor_model_parallel_world_size()
212 self.total_num_heads = num_heads
213 assert self.total_num_heads % tp_size == 0
214 self.num_heads = self.total_num_heads // tp_size
215 self.total_num_kv_heads = num_kv_heads
216 if self.total_num_kv_heads >= tp_size:
217 # Number of KV heads is greater than TP size, so we partition
218 # the KV heads across multiple tensor parallel GPUs.
219 assert self.total_num_kv_heads % tp_size == 0
220 else:
221 # Number of KV heads is less than TP size, so we replicate
222 # the KV heads across multiple tensor parallel GPUs.
223 assert tp_size % self.total_num_kv_heads == 0
224 self.num_kv_heads = max(1, self.total_num_kv_heads // tp_size)
225 self.head_dim = hidden_size // self.total_num_heads
226 self.q_size = self.num_heads * self.head_dim
227 self.kv_size = self.num_kv_heads * self.head_dim
228 self.scaling = self.head_dim**-0.5
229 self.max_position_embeddings = max_position_embeddings
230 self.dual_chunk_attention_config = dual_chunk_attention_config
231
232 self.qkv_proj = QKVParallelLinear(
233 hidden_size,
234 self.head_dim,
235 self.total_num_heads,
236 self.total_num_kv_heads,
237 bias=True,
238 quant_config=quant_config,
239 prefix=f"{prefix}.qkv_proj",
240 )
241
242 self.o_proj = RowParallelLinear(
243 self.total_num_heads * self.head_dim,
244 hidden_size,
245 bias=False,
246 quant_config=quant_config,
247 prefix=f"{prefix}.o_proj",
248 )
249
250 self.rotary_emb = get_rope(
251 self.head_dim,
252 max_position=max_position_embeddings,
253 rope_parameters=rope_parameters,
254 dual_chunk_attention_config=dual_chunk_attention_config,
255 )
256 attention_kwargs: dict[str, Any] = {}
257 if dual_chunk_attention_config:
258 attention_kwargs = {
259 "layer_idx": extract_layer_index(prefix),
260 "dual_chunk_attention_config": dual_chunk_attention_config,
261 }
262 self.attn = Attention(
263 self.num_heads,
264 self.head_dim,
265 self.scaling,
266 num_kv_heads=self.num_kv_heads,
267 cache_config=cache_config,
268 quant_config=quant_config,
269 prefix=f"{prefix}.attn",
270 **attention_kwargs,
271 )
272
273 def forward(
274 self,
275 positions: torch.Tensor,
276 hidden_states: torch.Tensor,
277 ) -> torch.Tensor:
278 qkv, _ = self.qkv_proj(hidden_states)
279 q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
280 q, k = self.rotary_emb(positions, q, k)
281 attn_output = self.attn(q, k, v)
282 output, _ = self.o_proj(attn_output)
283 return output
284
285
286 class Qwen2MoeDecoderLayer(nn.Module):
287 def __init__(
288 self,
289 config: Qwen2MoeConfig,
290 cache_config: CacheConfig | None = None,
291 quant_config: QuantizationConfig | None = None,
292 prefix: str = "",
293 ) -> None:
294 super().__init__()
295 self.hidden_size = config.hidden_size
296 dual_chunk_attention_config = getattr(
297 config, "dual_chunk_attention_config", None
298 )
299 max_position_embeddings = getattr(config, "max_position_embeddings", 8192)
300 self.self_attn = Qwen2MoeAttention(
301 hidden_size=self.hidden_size,
302 num_heads=config.num_attention_heads,
303 num_kv_heads=config.num_key_value_heads,
304 rope_parameters=config.rope_parameters,
305 max_position_embeddings=max_position_embeddings,
306 cache_config=cache_config,
307 quant_config=quant_config,
308 prefix=f"{prefix}.self_attn",
309 dual_chunk_attention_config=dual_chunk_attention_config,
310 )
311
312 # Note: Qwen/Qwen2-57B-A14B-Instruct does not have
313 # `mlp_only_layers` in the config.
314 layer_idx = extract_layer_index(prefix)
315 mlp_only_layers = (
316 [] if not hasattr(config, "mlp_only_layers") else config.mlp_only_layers
317 )
318 if (layer_idx not in mlp_only_layers) and (
319 config.num_experts > 0 and (layer_idx + 1) % config.decoder_sparse_step == 0
320 ):
321 self.mlp = Qwen2MoeSparseMoeBlock(
322 config=config, quant_config=quant_config, prefix=f"{prefix}.mlp"
323 )
324 else:
325 self.mlp = Qwen2MoeMLP(
326 hidden_size=config.hidden_size,
327 intermediate_size=config.intermediate_size,
328 hidden_act=config.hidden_act,
329 quant_config=quant_config,
330 prefix=f"{prefix}.mlp",
331 )
332 self.input_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
333 self.post_attention_layernorm = RMSNorm(
334 config.hidden_size, eps=config.rms_norm_eps
335 )
336
337 def forward(
338 self,
339 positions: torch.Tensor,
340 hidden_states: torch.Tensor,
341 residual: torch.Tensor | None,
342 ) -> torch.Tensor:
343 # Self Attention
344 if residual is None:
345 residual = hidden_states
346 hidden_states = self.input_layernorm(hidden_states)
347 else:
348 hidden_states, residual = self.input_layernorm(hidden_states, residual)
349 hidden_states = self.self_attn(
350 positions=positions,
351 hidden_states=hidden_states,
352 )
353
354 # Fully Connected
355 hidden_states, residual = self.post_attention_layernorm(hidden_states, residual)
356 hidden_states = self.mlp(hidden_states)
357 return hidden_states, residual
358
359
360 @support_torch_compile
361 class Qwen2MoeModel(nn.Module):
362 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
363 super().__init__()
364
365 config = vllm_config.model_config.hf_config
366 cache_config = vllm_config.cache_config
367 quant_config = vllm_config.quant_config
368
369 self.vocab_size = config.vocab_size
370 self.config = config
371
372 self.embed_tokens = VocabParallelEmbedding(
373 config.vocab_size,
374 config.hidden_size,
375 quant_config=quant_config,
376 prefix=f"{prefix}.embed_tokens",
377 )
378 self.start_layer, self.end_layer, self.layers = make_layers(
379 config.num_hidden_layers,
380 lambda prefix: Qwen2MoeDecoderLayer(
381 config=config,
382 cache_config=cache_config,
383 quant_config=quant_config,
384 prefix=prefix,
385 ),
386 prefix=f"{prefix}.layers",
387 )
388 self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
389 self.make_empty_intermediate_tensors = make_empty_intermediate_tensors_factory(
390 ["hidden_states", "residual"], config.hidden_size
391 )
392
393 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
394 return self.embed_tokens(input_ids)
395
396 def forward(
397 self,
398 input_ids: torch.Tensor | None,
399 positions: torch.Tensor,
400 intermediate_tensors: IntermediateTensors | None = None,
401 inputs_embeds: torch.Tensor | None = None,
402 ) -> torch.Tensor | IntermediateTensors:
403 if get_pp_group().is_first_rank:
404 if inputs_embeds is not None:
405 hidden_states = inputs_embeds
406 else:
407 hidden_states = self.embed_input_ids(input_ids)
408 residual = None
409 else:
410 assert intermediate_tensors is not None
411 hidden_states = intermediate_tensors["hidden_states"]
412 residual = intermediate_tensors["residual"]
413 for layer in islice(self.layers, self.start_layer, self.end_layer):
414 hidden_states, residual = layer(positions, hidden_states, residual)
415 if not get_pp_group().is_last_rank:
416 return IntermediateTensors(
417 {"hidden_states": hidden_states, "residual": residual}
418 )
419 hidden_states, _ = self.norm(hidden_states, residual)
420 return hidden_states
421
422
423 class Qwen2MoeForCausalLM(nn.Module, SupportsPP, SupportsLoRA):
424 fall_back_to_pt_during_load = False
425 hf_to_vllm_mapper = WeightsMapper(
426 orig_to_new_stacked={
427 # weight_name: (param_name, shard_id)
428 ".q_proj": (".qkv_proj", "q"),
429 ".k_proj": (".qkv_proj", "k"),
430 ".v_proj": (".qkv_proj", "v"),
431 # .experts.gate_up_proj must be handled by MoERunner.load_weights for EP
432 ".mlp.gate_proj": (".mlp.gate_up_proj", 0),
433 ".mlp.up_proj": (".mlp.gate_up_proj", 1),
434 ".shared_expert.gate_proj": (".shared_expert.gate_up_proj", 0),
435 ".shared_expert.up_proj": (".shared_expert.gate_up_proj", 1),
436 }
437 )
438 packed_modules_mapping = {
439 "qkv_proj": [
440 "q_proj",
441 "k_proj",
442 "v_proj",
443 ]
444 }
445
446 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
447 super().__init__()
448 config = vllm_config.model_config.hf_config
449 quant_config = vllm_config.quant_config
450 self.config = config
451 self.quant_config = quant_config
452 # Only perform the following mapping when Qwen2MoeMLP exists
453 if (
454 getattr(config, "mlp_only_layers", [])
455 or config.shared_expert_intermediate_size > 0
456 ):
457 self.packed_modules_mapping["gate_up_proj"] = ["gate_proj", "up_proj"]
458
459 self.model = Qwen2MoeModel(
460 vllm_config=vllm_config, prefix=maybe_prefix(prefix, "model")
461 )
462 self.lm_head = ParallelLMHead(
463 config.vocab_size,
464 config.hidden_size,
465 quant_config=quant_config,
466 prefix=maybe_prefix(prefix, "lm_head"),
467 )
468 if self.config.tie_word_embeddings:
469 self.lm_head = self.lm_head.tie_weights(self.model.embed_tokens)
470 self.logits_processor = LogitsProcessor(config.vocab_size)
471 self.make_empty_intermediate_tensors = (
472 self.model.make_empty_intermediate_tensors
473 )
474
475 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
476 return self.model.embed_input_ids(input_ids)
477
478 def forward(
479 self,
480 input_ids: torch.Tensor | None,
481 positions: torch.Tensor,
482 intermediate_tensors: IntermediateTensors | None = None,
483 inputs_embeds: torch.Tensor | None = None,
484 ) -> torch.Tensor | IntermediateTensors:
485 hidden_states = self.model(
486 input_ids, positions, intermediate_tensors, inputs_embeds
487 )
488 return hidden_states
489
490 def compute_logits(
491 self,
492 hidden_states: torch.Tensor,
493 ) -> torch.Tensor | None:
494 logits = self.logits_processor(self.lm_head, hidden_states)
495 return logits
496
497 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
498 def _maybe_reshape(
499 weights: Iterable[tuple[str, torch.Tensor]],
500 ) -> Iterable[tuple[str, torch.Tensor]]:
501 for name, loaded_weight in weights:
502 # GGUF: make sure that shared_expert_gate is a 2D tensor.
503 if "mlp.shared_expert_gate" in name and loaded_weight.dim() == 1:
504 loaded_weight = loaded_weight[None, :]
505 yield name, loaded_weight
506
507 loader = AutoWeightsLoader(self)
508 return loader.load_weights(
509 _maybe_reshape(weights), mapper=self.hf_to_vllm_mapper
510 )