main
py 345 lines 13.5 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 from collections.abc import Iterable
4
5 import torch
6 import torch.nn as nn
7 from transformers import PreTrainedConfig
8
9 from vllm.config import VllmConfig
10 from vllm.logger import init_logger
11 from vllm.model_executor.layers.layernorm import GemmaRMSNorm
12 from vllm.model_executor.layers.logits_processor import LogitsProcessor
13 from vllm.model_executor.layers.quantization import QuantizationConfig
14 from vllm.model_executor.layers.vocab_parallel_embedding import (
15 ParallelLMHead,
16 VocabParallelEmbedding,
17 )
18 from vllm.model_executor.model_loader.mtp_validation import (
19 is_mtp_completeness_check_enabled,
20 )
21 from vllm.model_executor.model_loader.weight_utils import default_weight_loader
22 from vllm.sequence import IntermediateTensors
23
24 from .step3p5 import Step3p5DecoderLayer
25 from .utils import (
26 get_draft_quant_config,
27 get_spec_layer_idx_from_weight_name,
28 maybe_prefix,
29 )
30
31 logger = init_logger(__name__)
32
33
34 class SharedHead(nn.Module):
35 def __init__(
36 self,
37 config: PreTrainedConfig,
38 quant_config: QuantizationConfig | None = None,
39 prefix: str = "",
40 ) -> None:
41 super().__init__()
42 self.norm = GemmaRMSNorm(config.hidden_size, config.rms_norm_eps)
43 # Give the head its prefix so the quant config's exclude_modules matcher
44 # can skip it; without one it defaults to "" and never matches, so a
45 # checkpoint-excluded (BF16) MTP head gets quantized -> load crash.
46 self.head = ParallelLMHead(
47 config.vocab_size,
48 config.hidden_size,
49 quant_config=quant_config,
50 prefix=f"{prefix}.head",
51 )
52
53 def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
54 return self.norm(hidden_states)
55
56
57 class Step3p5AMultiTokenPredictorLayer(nn.Module):
58 def __init__(
59 self,
60 vllm_config: VllmConfig,
61 prefix: str,
62 ) -> None:
63 super().__init__()
64 config = vllm_config.model_config.hf_config
65 quant_config = vllm_config.quant_config
66 self.enorm = GemmaRMSNorm(config.hidden_size, config.rms_norm_eps)
67 self.hnorm = GemmaRMSNorm(config.hidden_size, config.rms_norm_eps)
68 self.eh_proj = nn.Linear(config.hidden_size * 2, config.hidden_size, bias=False)
69 self.shared_head = SharedHead(
70 config=config, quant_config=quant_config, prefix=f"{prefix}.shared_head"
71 )
72 self.mtp_block = Step3p5DecoderLayer(
73 vllm_config,
74 prefix=f"{prefix}.mtp_block",
75 )
76
77 def forward(
78 self,
79 input_ids: torch.Tensor,
80 positions: torch.Tensor,
81 previous_hidden_states: torch.Tensor,
82 inputs_embeds: torch.Tensor | None = None,
83 spec_step_index: int = 0,
84 ) -> torch.Tensor:
85 assert inputs_embeds is not None
86 inputs_embeds = self.enorm(inputs_embeds)
87 previous_hidden_states = self.hnorm(previous_hidden_states)
88
89 hidden_states = self.eh_proj(
90 torch.cat([inputs_embeds, previous_hidden_states], dim=-1)
91 )
92
93 hidden_states = self.mtp_block(positions=positions, hidden_states=hidden_states)
94 return hidden_states
95
96
97 class Step3p5AMultiTokenPredictor(nn.Module):
98 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
99 super().__init__()
100 config = vllm_config.model_config.hf_config
101 self.embed_tokens = VocabParallelEmbedding(
102 config.vocab_size,
103 config.hidden_size,
104 )
105 self.mtp_start_layer_idx = config.num_hidden_layers
106 self.num_mtp_layers = config.num_nextn_predict_layers
107 # to map the exact layer index from weights
108 self.layers = torch.nn.ModuleDict(
109 {
110 str(idx): Step3p5AMultiTokenPredictorLayer(
111 vllm_config,
112 f"{prefix}.layers.{idx}",
113 )
114 for idx in range(
115 self.mtp_start_layer_idx,
116 self.mtp_start_layer_idx + self.num_mtp_layers,
117 )
118 }
119 )
120
121 self.logits_processor = LogitsProcessor(config.vocab_size)
122
123 def forward(
124 self,
125 input_ids: torch.Tensor,
126 positions: torch.Tensor,
127 previous_hidden_states: torch.Tensor,
128 inputs_embeds: torch.Tensor | None = None,
129 spec_step_idx: int = 0,
130 ) -> torch.Tensor:
131 if inputs_embeds is None:
132 inputs_embeds = self.embed_tokens(input_ids)
133 current_step_idx = spec_step_idx % self.num_mtp_layers
134 return self.layers[str(self.mtp_start_layer_idx + current_step_idx)](
135 input_ids,
136 positions,
137 previous_hidden_states,
138 inputs_embeds,
139 current_step_idx,
140 )
141
142 def compute_logits(
143 self,
144 hidden_states: torch.Tensor,
145 spec_step_idx: int = 0,
146 ) -> torch.Tensor:
147 current_step_idx = spec_step_idx % self.num_mtp_layers
148 mtp_layer = self.layers[str(self.mtp_start_layer_idx + current_step_idx)]
149 logits = self.logits_processor(
150 mtp_layer.shared_head.head, mtp_layer.shared_head(hidden_states)
151 )
152 return logits
153
154 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
155 return self.embed_tokens(input_ids)
156
157
158 class Step3p5MTP(nn.Module):
159 # Each MTP layer's shared_head carries its own LM head weights.
160 has_own_lm_head = True
161
162 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
163 super().__init__()
164 # The target config may be a multimodal wrapper (Step3.7); build the
165 # draft from its own text and quant configs.
166 assert vllm_config.speculative_config is not None
167 draft_quant_config = get_draft_quant_config(vllm_config)
168 vllm_config = vllm_config.with_hf_config(
169 vllm_config.speculative_config.draft_model_config.hf_config
170 )
171 vllm_config.quant_config = draft_quant_config
172 self.config = vllm_config.model_config.hf_config
173 self.vllm_config = vllm_config
174 self.model = Step3p5AMultiTokenPredictor(
175 vllm_config=vllm_config, prefix=maybe_prefix(prefix, "model")
176 )
177
178 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
179 return self.model.embed_input_ids(input_ids)
180
181 def forward(
182 self,
183 input_ids: torch.Tensor,
184 positions: torch.Tensor,
185 hidden_states: torch.Tensor,
186 intermediate_tensors: IntermediateTensors | None = None,
187 inputs_embeds: torch.Tensor | None = None,
188 spec_step_idx: int = 0,
189 ) -> torch.Tensor:
190 hidden_states = self.model(
191 input_ids, positions, hidden_states, inputs_embeds, spec_step_idx
192 )
193 return hidden_states
194
195 def compute_logits(
196 self,
197 hidden_states: torch.Tensor,
198 spec_step_idx: int = 0,
199 ) -> torch.Tensor | None:
200 return self.model.compute_logits(hidden_states, spec_step_idx)
201
202 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
203 stacked_params_mapping = [
204 # (param_name, shard_name, shard_id)
205 ("qkv_proj", "q_proj", "q"),
206 ("qkv_proj", "k_proj", "k"),
207 ("qkv_proj", "v_proj", "v"),
208 ("gate_up_proj", "gate_proj", 0),
209 ("gate_up_proj", "up_proj", 1),
210 ]
211 params_dict = dict(self.named_parameters())
212 base_layer = (
213 "base_layer." if any(".base_layer." in name for name in params_dict) else ""
214 )
215
216 expert_params_mapping = [
217 (f".moe.experts.{base_layer}w13_weight", ".moe.gate_proj.weight", "w1"),
218 (f".moe.experts.{base_layer}w13_weight", ".moe.up_proj.weight", "w3"),
219 (f".moe.experts.{base_layer}w2_weight", ".moe.down_proj.weight", "w2"),
220 ]
221
222 loaded_params: set[str] = set()
223 for name, loaded_weight in weights:
224 if "rotary_emb.inv_freq" in name:
225 continue
226 spec_layer = get_spec_layer_idx_from_weight_name(self.config, name)
227 if "embed_tokens" not in name and spec_layer is None:
228 continue
229 name = self._rewrite_spec_layer_name(spec_layer, name)
230 for param_name, weight_name, shard_id in stacked_params_mapping:
231 # Skip non-stacked layers and experts (experts handled below).
232 if weight_name not in name:
233 continue
234 # We have mlp.experts[0].gate_proj in the checkpoint.
235 # Since we handle the experts below in expert_params_mapping,
236 # we need to skip here BEFORE we update the name, otherwise
237 # name will be updated to mlp.experts[0].gate_up_proj, which
238 # will then be updated below in expert_params_mapping
239 # for mlp.experts[0].gate_gate_up_proj, which breaks load.
240 if ("mlp.experts." in name) and name not in params_dict:
241 continue
242 if "experts" in name or "moe" in name:
243 continue
244 name = name.replace(weight_name, param_name)
245 # Skip loading extra bias for GPTQ models.
246 if name.endswith(".bias") and name not in params_dict:
247 continue
248
249 param = params_dict[name]
250 weight_loader = param.weight_loader
251 weight_loader(param, loaded_weight, shard_id)
252 break
253 else:
254 for mapping in expert_params_mapping:
255 param_name, weight_name, shard_id = mapping
256 if weight_name not in name:
257 continue
258 name = name.replace(weight_name, param_name)
259 # Skip loading extra bias for GPTQ models.
260 if (
261 name.endswith(".bias") or name.endswith("_bias")
262 ) and name not in params_dict:
263 continue
264 param = params_dict[name]
265 weight_loader = param.weight_loader
266 for expert_id in range(loaded_weight.shape[0]):
267 loaded_weight_expert = loaded_weight[expert_id]
268 weight_loader(
269 param,
270 loaded_weight_expert,
271 name,
272 shard_id=shard_id,
273 expert_id=expert_id,
274 )
275 loaded_params.add(name)
276 break
277 else:
278 # Skip loading extra bias for GPTQ models.
279 if (
280 name.endswith(".bias")
281 and name not in params_dict
282 or "tok_embeddings" in name
283 ):
284 continue
285
286 if spec_layer is not None and ".transformer." in name:
287 name = name.replace(".transformer.", ".")
288 if "shared_head" in name:
289 name = name.replace("shared_head.output", "shared_head.head")
290 if "embed_tokens" in name:
291 assert (
292 hasattr(self.config, "num_nextn_predict_layers")
293 and self.config.num_nextn_predict_layers > 0
294 )
295 name = "model.embed_tokens.weight"
296 param = params_dict[name]
297 weight_loader = getattr(
298 param, "weight_loader", default_weight_loader
299 )
300 weight_loader(param, loaded_weight)
301 loaded_params.add(name)
302 params_need_to_load = set(params_dict.keys())
303 # Some KV cache scales are optional: checkpoints may omit them and vLLM
304 # will fall back to default scales during initialization.
305 optional_params = {
306 name
307 for name, param in params_dict.items()
308 if name.endswith((".k_scale", ".v_scale", ".q_scale", ".prob_scale"))
309 and getattr(param, "numel", lambda: 0)() == 1
310 and getattr(param, "requires_grad", False) is False
311 }
312 params_need_to_load -= optional_params
313 if params_need_to_load != loaded_params and is_mtp_completeness_check_enabled():
314 missing_params = list(params_need_to_load - loaded_params)
315 param_name_example = missing_params[0]
316 raise RuntimeError(
317 "Some parameters like "
318 f"{param_name_example} are not in the checkpoint and will falsely "
319 "use random initialization"
320 )
321 return loaded_params
322
323 def _rewrite_spec_layer_name(self, spec_layer: int | None, name: str) -> str:
324 """Rewrite the weight name to match the format of the original model.
325 Add .mtp_block for modules in transformer layer block for spec layer
326 """
327 spec_layer_weight_names = [
328 "embed_tokens",
329 "enorm",
330 "hnorm",
331 "eh_proj",
332 "shared_head",
333 ]
334 spec_layer_weight = False
335 for weight_name in spec_layer_weight_names:
336 if weight_name in name:
337 spec_layer_weight = True
338 break
339 if not spec_layer_weight:
340 # treat rest weights as weights for transformer layer block
341 assert spec_layer is not None
342 name = name.replace(
343 f"model.layers.{spec_layer}.", f"model.layers.{spec_layer}.mtp_block."
344 )
345 return name