main
py 242 lines 8.39 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Inference-only Qwen3Next MTP model."""
4
5 from collections.abc import Iterable
6
7 import torch
8 from torch import nn
9
10 from vllm.compilation.decorators import support_torch_compile
11 from vllm.config import VllmConfig
12 from vllm.distributed import get_pp_group, tensor_model_parallel_all_gather
13 from vllm.logger import init_logger
14 from vllm.model_executor.layers.fused_moe.utils import (
15 is_model_fused_shared_expert_compatible,
16 )
17 from vllm.model_executor.layers.linear import ColumnParallelLinear
18 from vllm.model_executor.layers.logits_processor import LogitsProcessor
19 from vllm.model_executor.layers.vocab_parallel_embedding import (
20 ParallelLMHead,
21 VocabParallelEmbedding,
22 )
23 from vllm.model_executor.models.qwen3_next import (
24 Qwen3NextDecoderLayer,
25 Qwen3NextModel,
26 Qwen3NextRMSNorm,
27 Qwen3NextSparseMoeBlock,
28 QwenNextMixtureOfExperts,
29 )
30 from vllm.model_executor.models.utils import sequence_parallel_chunk
31 from vllm.sequence import IntermediateTensors
32 from vllm.transformers_utils.configs.qwen3_next import Qwen3NextConfig
33
34 from .utils import (
35 AutoWeightsLoader,
36 make_empty_intermediate_tensors_factory,
37 maybe_fuse_shared_experts,
38 maybe_prefix,
39 )
40
41 logger = init_logger(__name__)
42
43 KVCache = tuple[torch.Tensor, torch.Tensor]
44
45
46 @support_torch_compile
47 class Qwen3NextMultiTokenPredictor(nn.Module):
48 hf_to_vllm_mapper = Qwen3NextModel.hf_to_vllm_mapper
49
50 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
51 super().__init__()
52
53 model_config = vllm_config.model_config
54 quant_config = vllm_config.quant_config
55
56 config: Qwen3NextConfig = model_config.hf_config
57
58 self.config = config
59
60 self.vocab_size = config.vocab_size
61
62 self.mtp_start_layer_idx = config.num_hidden_layers
63 self.num_mtp_layers = getattr(config, "num_nextn_predict_layers", 1)
64
65 self.embed_tokens = VocabParallelEmbedding(
66 self.vocab_size,
67 config.hidden_size,
68 )
69
70 # Workaround: mtp.fc is stored as BF16 in NVFP4 checkpoints but is
71 # missing from the checkpoint quant exclude list (its `ignore` glob
72 # does not cover `mtp.fc`). Force unquantized to match the weights,
73 # mirroring the Qwen3.5 MTP handling (PR #38832).
74 fc_quant = (
75 None
76 if (quant_config and quant_config.get_name() == "modelopt_fp4")
77 else quant_config
78 )
79 self.fc = ColumnParallelLinear(
80 self.config.hidden_size * 2,
81 self.config.hidden_size,
82 gather_output=True,
83 bias=False,
84 return_bias=False,
85 quant_config=fc_quant,
86 prefix=f"{prefix}.fc",
87 )
88
89 self.layers = torch.nn.ModuleList(
90 Qwen3NextDecoderLayer(
91 vllm_config,
92 layer_type="full_attention",
93 prefix=f"{prefix}.layers.{idx}",
94 )
95 for idx in range(self.num_mtp_layers)
96 )
97
98 self.is_fused_shared_expert_enabled = is_model_fused_shared_expert_compatible(
99 self.layers,
100 Qwen3NextSparseMoeBlock,
101 "mlp",
102 )
103 self.make_empty_intermediate_tensors = make_empty_intermediate_tensors_factory(
104 ["hidden_states", "residual"], config.hidden_size
105 )
106
107 self.norm = Qwen3NextRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
108 self.pre_fc_norm_hidden = Qwen3NextRMSNorm(
109 config.hidden_size, eps=config.rms_norm_eps
110 )
111 self.pre_fc_norm_embedding = Qwen3NextRMSNorm(
112 config.hidden_size, eps=config.rms_norm_eps
113 )
114
115 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
116 return self.embed_tokens(input_ids)
117
118 def forward(
119 self,
120 input_ids: torch.Tensor,
121 positions: torch.Tensor,
122 hidden_states: torch.Tensor,
123 intermediate_tensors: IntermediateTensors | None = None,
124 inputs_embeds: torch.Tensor | None = None,
125 spec_step_idx: int = 0,
126 ) -> torch.Tensor:
127 if get_pp_group().is_first_rank:
128 if inputs_embeds is None:
129 inputs_embeds = self.embed_input_ids(input_ids)
130 assert hidden_states.shape[-1] == inputs_embeds.shape[-1]
131 inputs_embeds = self.pre_fc_norm_embedding(inputs_embeds)
132 hidden_states = self.pre_fc_norm_hidden(hidden_states)
133 hidden_states = torch.cat([inputs_embeds, hidden_states], dim=-1)
134 hidden_states = self.fc(hidden_states)
135 residual = None
136 else:
137 assert intermediate_tensors is not None
138 hidden_states = intermediate_tensors["hidden_states"]
139 residual = intermediate_tensors["residual"]
140
141 current_step_idx = spec_step_idx % self.num_mtp_layers
142 mtp_layer = self.layers[current_step_idx]
143 if mtp_layer.use_attn_reduce_scatter_for_moe:
144 assert hidden_states.shape[0] == positions.shape[-1]
145 hidden_states = sequence_parallel_chunk(hidden_states)
146 assert residual is None
147 hidden_states, residual = mtp_layer(
148 positions=positions,
149 hidden_states=hidden_states,
150 residual=residual,
151 )
152
153 if not get_pp_group().is_last_rank:
154 return IntermediateTensors(
155 {"hidden_states": hidden_states, "residual": residual}
156 )
157
158 hidden_states, _ = self.norm(hidden_states, residual)
159 if mtp_layer.use_attn_reduce_scatter_for_moe:
160 hidden_states = tensor_model_parallel_all_gather(hidden_states, 0)
161 hidden_states = hidden_states[: positions.shape[-1]]
162 return hidden_states
163
164 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
165 weights = maybe_fuse_shared_experts(
166 weights,
167 enabled=self.is_fused_shared_expert_enabled,
168 n_routed_experts=self.config.num_experts,
169 n_shared_experts=1,
170 ckpt_prefix="mlp.shared_expert",
171 )
172 loader = AutoWeightsLoader(self)
173 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
174
175
176 @support_torch_compile
177 class Qwen3NextMTP(nn.Module, QwenNextMixtureOfExperts):
178 packed_modules_mapping = {
179 "qkv_proj": [
180 "q_proj",
181 "k_proj",
182 "v_proj",
183 ],
184 "gate_up_proj": ["gate_proj", "up_proj"],
185 }
186
187 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
188 config = vllm_config.model_config.hf_config
189 self.vllm_config = vllm_config
190 self.quant_config = vllm_config.quant_config
191
192 super().__init__()
193 self.config = config
194 self.model = Qwen3NextMultiTokenPredictor(
195 vllm_config=vllm_config, prefix=maybe_prefix(prefix, "mtp")
196 )
197
198 self.lm_head = ParallelLMHead(
199 config.vocab_size,
200 config.hidden_size,
201 prefix=maybe_prefix(prefix, "lm_head"),
202 )
203 self.logits_processor = LogitsProcessor(config.vocab_size)
204 self.set_moe_parameters()
205
206 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
207 return self.model.embed_input_ids(input_ids)
208
209 def forward(
210 self,
211 input_ids: torch.Tensor | None,
212 positions: torch.Tensor,
213 hidden_states: torch.Tensor,
214 intermediate_tensors: IntermediateTensors | None = None,
215 inputs_embeds: torch.Tensor | None = None,
216 **kwargs: object,
217 ):
218 hidden_states = self.model(
219 input_ids, positions, hidden_states, intermediate_tensors, inputs_embeds
220 )
221 return hidden_states
222
223 def compute_logits(
224 self,
225 hidden_states: torch.Tensor,
226 spec_step_idx: int = 0,
227 ) -> torch.Tensor | None:
228 return self.logits_processor(self.lm_head, hidden_states)
229
230 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
231 shared_weight_names = ["embed_tokens", "lm_head"]
232
233 def remap_weight_names(weights):
234 for name, weight in weights:
235 if name.startswith("mtp."):
236 name = name.replace("mtp.", "model.")
237 elif not any(key in name for key in shared_weight_names):
238 continue
239 yield name, weight
240
241 loader = AutoWeightsLoader(self)
242 return loader.load_weights(remap_weight_names(weights))