main
py 419 lines 16.6 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
5 import typing
6 from collections.abc import Callable, Iterable
7
8 import torch
9 import torch.nn as nn
10
11 from vllm.config import VllmConfig
12 from vllm.distributed import tensor_model_parallel_all_reduce
13 from vllm.model_executor.layers.fused_moe import (
14 fused_moe_make_expert_params_mapping,
15 )
16 from vllm.model_executor.layers.fused_moe.utils import (
17 is_model_fused_shared_expert_compatible,
18 )
19 from vllm.model_executor.layers.layernorm import RMSNorm
20 from vllm.model_executor.layers.logits_processor import LogitsProcessor
21 from vllm.model_executor.layers.vocab_parallel_embedding import (
22 VocabParallelEmbedding,
23 )
24 from vllm.model_executor.model_loader.weight_utils import (
25 default_weight_loader,
26 maybe_remap_kv_scale_name,
27 )
28 from vllm.model_executor.models.deepseek_mtp import SharedHead
29 from vllm.model_executor.models.deepseek_v2 import (
30 DeepseekV2MixtureOfExperts,
31 DeepseekV2MoE,
32 _try_load_fp8_indexer_wk,
33 get_spec_layer_idx_from_weight_name,
34 )
35 from vllm.model_executor.models.interfaces import SupportsPP
36 from vllm.model_executor.models.utils import (
37 get_pp_missing_layer_names,
38 make_empty_intermediate_tensors_factory,
39 maybe_prefix,
40 )
41 from vllm.models.deepseek_v32.common.kernels import fused_eh_norm
42 from vllm.platforms import current_platform
43 from vllm.sequence import IntermediateTensors
44
45 from .model import DeepseekV32DecoderLayer
46
47
48 class DeepseekV32MultiTokenPredictorLayer(nn.Module):
49 def __init__(self, vllm_config: VllmConfig, prefix: str) -> None:
50 super().__init__()
51 assert vllm_config.speculative_config is not None
52 config = vllm_config.speculative_config.draft_model_config.hf_config
53 self.config = config
54 quant_config = vllm_config.quant_config
55
56 self.enorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
57 self.hnorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
58 self.eh_proj = nn.Linear(config.hidden_size * 2, config.hidden_size, bias=False)
59
60 topk_indices_buffer = torch.empty(
61 vllm_config.scheduler_config.max_num_batched_tokens,
62 config.index_topk,
63 dtype=torch.int32,
64 device=current_platform.device_type,
65 )
66 self.shared_head = SharedHead(
67 config=config, prefix=prefix, quant_config=quant_config
68 )
69 self.mtp_block = DeepseekV32DecoderLayer(
70 vllm_config,
71 prefix,
72 config=config,
73 topk_indices_buffer=topk_indices_buffer,
74 )
75
76 def forward(
77 self,
78 input_ids: torch.Tensor,
79 positions: torch.Tensor,
80 previous_hidden_states: torch.Tensor,
81 inputs_embeds: torch.Tensor | None = None,
82 spec_step_index: int = 0,
83 ) -> torch.Tensor:
84 assert inputs_embeds is not None
85 eh_input = fused_eh_norm(
86 positions,
87 inputs_embeds,
88 previous_hidden_states,
89 self.enorm.weight,
90 self.hnorm.weight,
91 self.enorm.variance_epsilon,
92 )
93 hidden_states = self.eh_proj(eh_input)
94 hidden_states, residual = self.mtp_block(
95 positions=positions, hidden_states=hidden_states, residual=None
96 )
97 hidden_states = tensor_model_parallel_all_reduce(hidden_states)
98 hidden_states = residual + hidden_states
99 hidden_states = self.shared_head.norm(hidden_states)
100 return hidden_states, hidden_states
101
102
103 class DeepseekV32MultiTokenPredictor(nn.Module):
104 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
105 super().__init__()
106 config = vllm_config.model_config.hf_config
107 self.mtp_start_layer_idx = config.num_hidden_layers
108 self.num_mtp_layers = config.num_nextn_predict_layers
109 self.layers = torch.nn.ModuleDict(
110 {
111 str(idx): DeepseekV32MultiTokenPredictorLayer(
112 vllm_config, 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 self.embed_tokens = VocabParallelEmbedding(
121 config.vocab_size,
122 config.hidden_size,
123 prefix=maybe_prefix(prefix, "embed_tokens"),
124 )
125 self.logits_processor = LogitsProcessor(config.vocab_size)
126
127 def set_skip_topk(self, skip: bool):
128 for layer in self.layers.values():
129 self_attn = getattr(layer.mtp_block, "self_attn", None)
130 if self_attn is not None and hasattr(self_attn, "skip_topk"):
131 self_attn.skip_topk = skip
132
133 def compact_topk_indices(self, slot_ids: torch.Tensor):
134 """Gather the top-k index rows at ``slot_ids`` to the front of the buffer."""
135 num_slots = slot_ids.numel()
136 for layer in self.layers.values():
137 self_attn = getattr(layer.mtp_block, "self_attn", None)
138 if self_attn is not None and hasattr(self_attn, "topk_indices_buffer"):
139 topk_indices_buffer = self_attn.topk_indices_buffer
140 topk_indices_buffer[:num_slots] = topk_indices_buffer[slot_ids]
141
142 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
143 return self.embed_tokens(input_ids)
144
145 def forward(
146 self,
147 input_ids: torch.Tensor,
148 positions: torch.Tensor,
149 previous_hidden_states: torch.Tensor,
150 inputs_embeds: torch.Tensor | None = None,
151 spec_step_idx: int = 0,
152 ) -> torch.Tensor:
153 if inputs_embeds is None:
154 inputs_embeds = self.embed_tokens(input_ids)
155 current_step_idx = spec_step_idx % self.num_mtp_layers
156 return self.layers[str(self.mtp_start_layer_idx + current_step_idx)](
157 input_ids,
158 positions,
159 previous_hidden_states,
160 inputs_embeds,
161 current_step_idx,
162 )
163
164 def compute_logits(
165 self,
166 hidden_states: torch.Tensor,
167 spec_step_idx: int = 0,
168 ) -> torch.Tensor:
169 current_step_idx = spec_step_idx % self.num_mtp_layers
170 mtp_layer = self.layers[str(self.mtp_start_layer_idx + current_step_idx)]
171 return self.logits_processor(mtp_layer.shared_head.head, hidden_states)
172
173
174 class DeepseekV32MTP(nn.Module, DeepseekV2MixtureOfExperts, SupportsPP):
175 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
176 super().__init__()
177 self.config = vllm_config.model_config.hf_config
178 self.quant_config = vllm_config.quant_config
179 self.model = DeepseekV32MultiTokenPredictor(
180 vllm_config=vllm_config, prefix=maybe_prefix(prefix, "model")
181 )
182 self.set_moe_parameters()
183 self.make_empty_intermediate_tensors = make_empty_intermediate_tensors_factory(
184 ["hidden_states", "residual"], self.config.hidden_size
185 )
186 self.is_fused_shared_expert_enabled = is_model_fused_shared_expert_compatible(
187 self.model.layers.values(),
188 DeepseekV2MoE,
189 "mtp_block.mlp",
190 )
191
192 def set_moe_parameters(self):
193 self.num_moe_layers = self.config.num_nextn_predict_layers
194 self.num_expert_groups = self.config.n_group
195 self.moe_layers = []
196 self.moe_mlp_layers = []
197 example_moe = None
198 for layer in self.model.layers.values():
199 mlp = layer.mtp_block.mlp
200 if isinstance(mlp, DeepseekV2MoE):
201 example_moe = mlp
202 self.moe_mlp_layers.append(mlp)
203 self.moe_layers.append(mlp.experts)
204 self.extract_moe_parameters(example_moe)
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( # type: ignore[override]
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 spec_step_idx: int = 0,
217 ) -> torch.Tensor:
218 return self.model(
219 input_ids, positions, hidden_states, inputs_embeds, spec_step_idx
220 )
221
222 def compute_logits(
223 self,
224 hidden_states: torch.Tensor,
225 spec_step_idx: int = 0,
226 ) -> torch.Tensor | None:
227 return self.model.compute_logits(hidden_states, spec_step_idx)
228
229 def _rewrite_spec_layer_name(self, spec_layer: int, name: str) -> str:
230 spec_layer_weight_names = [
231 "embed_tokens",
232 "enorm",
233 "hnorm",
234 "eh_proj",
235 "shared_head",
236 ]
237 shared_weight_names = ["embed_tokens"]
238 spec_layer_weight = False
239 shared_weight = False
240 for weight_name in spec_layer_weight_names:
241 if weight_name in name:
242 spec_layer_weight = True
243 if weight_name in shared_weight_names:
244 shared_weight = True
245 break
246 if not spec_layer_weight:
247 name = name.replace(
248 f"model.layers.{spec_layer}.", f"model.layers.{spec_layer}.mtp_block."
249 )
250 elif shared_weight:
251 name = name.replace(f"model.layers.{spec_layer}.", "model.")
252 return name
253
254 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
255 stacked_params_mapping = [
256 ("gate_up_proj", "gate_proj", 0),
257 ("gate_up_proj", "up_proj", 1),
258 ("fused_qkv_a_proj", "q_a_proj", 0),
259 ("fused_qkv_a_proj", "kv_a_proj_with_mqa", 1),
260 ("wk_weights_proj", "wk", 0),
261 ("wk_weights_proj", "weights_proj", 1),
262 ]
263 expert_params_mapping = fused_moe_make_expert_params_mapping(
264 self,
265 ckpt_gate_proj_name="gate_proj",
266 ckpt_down_proj_name="down_proj",
267 ckpt_up_proj_name="up_proj",
268 num_experts=self.config.n_routed_experts
269 + (
270 self.config.n_shared_experts
271 if self.is_fused_shared_expert_enabled
272 else 0
273 ),
274 )
275
276 pp_missing_layer_names = get_pp_missing_layer_names(self)
277 params_dict = dict(self.named_parameters())
278 loaded_params: set[str] = set()
279 _pending_wk_fp8: dict = {}
280 for name, loaded_weight in weights:
281 if "rotary_emb.inv_freq" in name:
282 continue
283 spec_layer = get_spec_layer_idx_from_weight_name(self.config, name)
284 if spec_layer is None:
285 # A tied top-level embed_tokens has no spec layer to rewrite
286 # from; the draft needs its own copy under PP.
287 param = params_dict.get(name) if "embed_tokens" in name else None
288 if param is not None:
289 weight_loader = getattr(
290 param, "weight_loader", default_weight_loader
291 )
292 weight_loader(param, loaded_weight)
293 loaded_params.add(name)
294 continue
295 is_fusion_moe_shared_experts_layer = (
296 self.is_fused_shared_expert_enabled and ("mlp.shared_experts" in name)
297 )
298 name = self._rewrite_spec_layer_name(spec_layer, name)
299
300 if _try_load_fp8_indexer_wk(
301 name,
302 loaded_weight,
303 _pending_wk_fp8,
304 params_dict,
305 loaded_params,
306 pp_missing_layer_names,
307 ):
308 continue
309
310 for param_name, weight_name, shard_id in stacked_params_mapping:
311 if weight_name not in name:
312 continue
313 if ("mlp.experts." in name) and name not in params_dict:
314 continue
315 if is_fusion_moe_shared_experts_layer:
316 continue
317 name_mapped = name.replace(weight_name, param_name)
318 if (
319 param_name == "fused_qkv_a_proj"
320 ) and name_mapped not in params_dict:
321 continue
322 else:
323 name = name_mapped
324 if name.endswith(".bias") and name not in params_dict:
325 continue
326 param = params_dict[name]
327 weight_loader = param.weight_loader
328 weight_loader(param, loaded_weight, shard_id)
329 break
330 else:
331 num_chunks = 1
332 if is_fusion_moe_shared_experts_layer:
333 num_chunks = getattr(self.config, "n_shared_experts", 1) or 1
334 split_dim = (
335 1
336 if ("down_proj.weight" in name and loaded_weight.ndim > 1)
337 else 0
338 )
339 total = loaded_weight.shape[split_dim]
340 assert total % num_chunks == 0
341 chunk_size = total // num_chunks
342
343 for j in range(num_chunks):
344 chunk_name = name
345 weight_to_load = loaded_weight
346 if is_fusion_moe_shared_experts_layer:
347 chunk_slice = slice(j * chunk_size, (j + 1) * chunk_size)
348 if loaded_weight.ndim == 1:
349 weight_to_load = loaded_weight[chunk_slice]
350 elif split_dim == 0:
351 weight_to_load = loaded_weight[chunk_slice, :]
352 else:
353 weight_to_load = loaded_weight[:, chunk_slice]
354 chunk_name = name.replace(
355 "mlp.shared_experts",
356 f"mlp.experts.{self.config.n_routed_experts + j}",
357 )
358
359 is_expert_weight = False
360 for mapping in expert_params_mapping:
361 param_name, weight_name, expert_id, shard_id = mapping # type: ignore[assignment]
362 if weight_name not in chunk_name:
363 continue
364 is_expert_weight = True
365 name_mapped = chunk_name.replace(weight_name, param_name)
366 param = params_dict[name_mapped]
367 weight_loader = typing.cast(
368 Callable[..., bool], param.weight_loader
369 )
370 success = weight_loader(
371 param,
372 weight_to_load,
373 name_mapped,
374 shard_id=shard_id,
375 expert_id=expert_id,
376 return_success=True,
377 )
378 if success:
379 if not is_fusion_moe_shared_experts_layer:
380 name = name_mapped
381 else:
382 loaded_params.add(name_mapped)
383 break
384 else:
385 if is_expert_weight:
386 continue
387 if name.endswith(".bias") and name not in params_dict:
388 continue
389 name = maybe_remap_kv_scale_name(name, params_dict) # type: ignore[assignment]
390 if name is None:
391 continue
392 if (
393 spec_layer != self.model.mtp_start_layer_idx
394 and ".layers" not in name
395 ):
396 continue
397 param = params_dict[name]
398 weight_loader = getattr(
399 param, "weight_loader", default_weight_loader
400 )
401 weight_loader(param, loaded_weight)
402 if not is_fusion_moe_shared_experts_layer:
403 loaded_params.add(name)
404
405 loaded_layers: set[int] = set()
406 for param_name in loaded_params:
407 spec_layer = get_spec_layer_idx_from_weight_name(self.config, param_name)
408 if spec_layer is not None:
409 loaded_layers.add(spec_layer)
410 for layer_idx in range(
411 self.model.mtp_start_layer_idx,
412 self.model.mtp_start_layer_idx + self.model.num_mtp_layers,
413 ):
414 if layer_idx not in loaded_layers:
415 raise ValueError(
416 f"MTP speculative decoding layer {layer_idx} weights "
417 f"missing from checkpoint."
418 )
419 return loaded_params