main
py 360 lines 15.8 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/vllm-project/vllm/blob/v0.7.3/vllm/model_executor/models/deepseek_mtp.py
6 from collections.abc import Iterable
7
8 import torch
9 import torch.nn as nn
10 from transformers import PreTrainedConfig
11
12 from vllm.config import VllmConfig
13 from vllm.model_executor.layers.layernorm import RMSNorm
14 from vllm.model_executor.layers.linear import ReplicatedLinear
15 from vllm.model_executor.layers.logits_processor import LogitsProcessor
16 from vllm.model_executor.layers.quantization import QuantizationConfig
17 from vllm.model_executor.layers.quantization.utils.int8_utils import block_dequant
18 from vllm.model_executor.layers.vocab_parallel_embedding import (
19 ParallelLMHead,
20 VocabParallelEmbedding,
21 )
22 from vllm.model_executor.model_loader.weight_utils import default_weight_loader
23 from vllm.model_executor.models.longcat_flash import FlashConfig
24 from vllm.sequence import IntermediateTensors
25
26 from .deepseek_v2 import DeepseekV2DecoderLayer
27 from .utils import maybe_prefix
28
29
30 class LongCatMultiTokenPredictorLayer(nn.Module):
31 def __init__(
32 self,
33 config: PreTrainedConfig,
34 prefix: str,
35 vllm_config: VllmConfig,
36 quant_config: QuantizationConfig | None = None,
37 ) -> None:
38 super().__init__()
39 self.enorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
40 self.hnorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
41 self.eh_proj = ReplicatedLinear(
42 2 * config.hidden_size,
43 config.hidden_size,
44 bias=False,
45 quant_config=quant_config,
46 prefix="eh_proj",
47 )
48 self.mtp_block = DeepseekV2DecoderLayer(vllm_config, prefix)
49 self.final_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
50
51 def forward(
52 self,
53 input_ids: torch.Tensor,
54 positions: torch.Tensor,
55 previous_hidden_states: torch.Tensor,
56 inputs_embeds: torch.Tensor | None = None,
57 spec_step_index: int = 0,
58 ) -> torch.Tensor:
59 assert inputs_embeds is not None
60 inputs_embeds = self.enorm(inputs_embeds)
61 previous_hidden_states = self.hnorm(previous_hidden_states)
62
63 hidden_states, _ = self.eh_proj(
64 torch.cat([inputs_embeds, previous_hidden_states], dim=-1)
65 )
66
67 hidden_states, residual = self.mtp_block(
68 positions=positions, hidden_states=hidden_states, residual=None
69 )
70 hidden_states, _ = self.final_layernorm(hidden_states, residual)
71 return hidden_states
72
73
74 class LongCatMultiTokenPredictor(nn.Module):
75 def __init__(
76 self,
77 *,
78 vllm_config: VllmConfig,
79 quant_config: QuantizationConfig | None = None,
80 prefix: str = "",
81 ):
82 super().__init__()
83 config = FlashConfig(**vllm_config.model_config.hf_config.__dict__)
84 vllm_config.model_config.hf_config.intermediate_size = config.intermediate_size
85 self.mtp_start_layer_idx = config.num_hidden_layers * 2
86 self.num_mtp_layers = 1
87 self.layers = torch.nn.ModuleDict(
88 {
89 str(idx): LongCatMultiTokenPredictorLayer(
90 config,
91 prefix=f"{prefix}.layers.{idx}",
92 vllm_config=vllm_config,
93 quant_config=quant_config,
94 )
95 for idx in range(
96 self.mtp_start_layer_idx,
97 self.mtp_start_layer_idx + self.num_mtp_layers,
98 )
99 }
100 )
101 self.embed_tokens = VocabParallelEmbedding(
102 config.vocab_size,
103 config.hidden_size,
104 )
105
106 def forward(
107 self,
108 input_ids: torch.Tensor,
109 positions: torch.Tensor,
110 previous_hidden_states: torch.Tensor,
111 inputs_embeds: torch.Tensor | None = None,
112 spec_step_idx: int = 0,
113 ) -> torch.Tensor:
114 if inputs_embeds is None:
115 inputs_embeds = self.embed_tokens(input_ids)
116 current_step_idx = spec_step_idx % self.num_mtp_layers
117 return self.layers[str(self.mtp_start_layer_idx + current_step_idx)](
118 input_ids,
119 positions,
120 previous_hidden_states,
121 inputs_embeds,
122 current_step_idx,
123 )
124
125
126 class LongCatFlashMTP(nn.Module):
127 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
128 super().__init__()
129 # LongCat MTP has no MoE layers: clear n_routed_experts so the predictor
130 # builds a dense MLP. object.__setattr__ bypasses the ngram remote
131 # config's strict validation (it rejects setting the int field to None).
132 object.__setattr__(vllm_config.model_config.hf_config, "n_routed_experts", None)
133 self.config = FlashConfig(**vllm_config.model_config.hf_config.__dict__)
134 self.quant_config = (
135 None
136 if "mtp" in getattr(self.config, "disable_quant_module", [])
137 else vllm_config.quant_config
138 )
139
140 self.model = LongCatMultiTokenPredictor(
141 vllm_config=vllm_config,
142 quant_config=self.quant_config,
143 prefix=maybe_prefix(prefix, "model"),
144 )
145 self.lm_head = ParallelLMHead(
146 self.config.vocab_size,
147 self.config.hidden_size,
148 quant_config=self.quant_config,
149 prefix=maybe_prefix(prefix, "lm_head"),
150 )
151 self.logits_processor = LogitsProcessor(self.config.vocab_size)
152
153 def forward(
154 self,
155 input_ids: torch.Tensor | None,
156 positions: torch.Tensor,
157 hidden_states: torch.Tensor,
158 intermediate_tensors: IntermediateTensors | None = None,
159 inputs_embeds: torch.Tensor | None = None,
160 spec_step_idx: int = 0,
161 ) -> torch.Tensor:
162 hidden_states = self.model(
163 input_ids, positions, hidden_states, inputs_embeds, spec_step_idx
164 )
165 return hidden_states
166
167 def compute_logits(
168 self,
169 hidden_states: torch.Tensor,
170 spec_step_idx: int = 0,
171 ) -> torch.Tensor | None:
172 logits = self.logits_processor(self.lm_head, hidden_states)
173 return logits
174
175 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
176 stacked_params_mapping = [
177 ("gate_up_proj", "gate_proj", 0),
178 ("gate_up_proj", "up_proj", 1),
179 ("fused_qkv_a_proj", "q_a_proj", 0),
180 ("fused_qkv_a_proj", "kv_a_proj_with_mqa", 1),
181 ]
182
183 new_to_old_names_mapping = {
184 "model.mtp.embed_tokens.weight": "model.layers.0.embed_tokens.weight",
185 "model.mtp.layers.0.eh_proj.weight": "eh_proj.weight",
186 "model.mtp.layers.0.eh_proj.weight_scale_inv": "eh_proj.weight_scale_inv",
187 "model.mtp.layers.0.enorm.m.weight": "enorm.weight",
188 "model.mtp.layers.0.hnorm.m.weight": "hnorm.weight",
189 "model.mtp.layers.0.input_layernorm.weight": "model.layers.0.input_layernorm.weight", # noqa: E501
190 "model.mtp.layers.0.post_attention_layernorm.weight": "model.layers.0.post_attention_layernorm.weight", # noqa: E501
191 "model.mtp.layers.0.self_attn.kv_a_layernorm.weight": "model.layers.0.self_attn.kv_a_layernorm.weight", # noqa: E501
192 "model.mtp.layers.0.self_attn.kv_a_proj_with_mqa.weight": "model.layers.0.self_attn.kv_a_proj_with_mqa.weight", # noqa: E501
193 "model.mtp.layers.0.self_attn.kv_a_proj_with_mqa.weight_scale_inv": "model.layers.0.self_attn.kv_a_proj_with_mqa.weight_scale_inv", # noqa: E501
194 "model.mtp.layers.0.self_attn.kv_b_proj.weight": "model.layers.0.self_attn.kv_b_proj.weight", # noqa: E501
195 "model.mtp.layers.0.self_attn.kv_b_proj.weight_scale_inv": "model.layers.0.self_attn.kv_b_proj.weight_scale_inv", # noqa: E501
196 "model.mtp.layers.0.self_attn.o_proj.weight": "model.layers.0.self_attn.o_proj.weight", # noqa: E501
197 "model.mtp.layers.0.self_attn.o_proj.weight_scale_inv": "model.layers.0.self_attn.o_proj.weight_scale_inv", # noqa: E501
198 "model.mtp.layers.0.self_attn.q_a_layernorm.weight": "model.layers.0.self_attn.q_a_layernorm.weight", # noqa: E501
199 "model.mtp.layers.0.self_attn.q_a_proj.weight": "model.layers.0.self_attn.q_a_proj.weight", # noqa: E501
200 "model.mtp.layers.0.self_attn.q_a_proj.weight_scale_inv": "model.layers.0.self_attn.q_a_proj.weight_scale_inv", # noqa: E501
201 "model.mtp.layers.0.self_attn.q_b_proj.weight": "model.layers.0.self_attn.q_b_proj.weight", # noqa: E501
202 "model.mtp.layers.0.self_attn.q_b_proj.weight_scale_inv": "model.layers.0.self_attn.q_b_proj.weight_scale_inv", # noqa: E501
203 "model.mtp.layers.0.transformer_layer.mlp.down_proj.weight": "model.layers.0.mlp.down_proj.weight", # noqa: E501
204 "model.mtp.layers.0.transformer_layer.mlp.down_proj.weight_scale_inv": "model.layers.0.mlp.down_proj.weight_scale_inv", # noqa: E501
205 "model.mtp.layers.0.transformer_layer.mlp.gate_proj.weight": "model.layers.0.mlp.gate_proj.weight", # noqa: E501
206 "model.mtp.layers.0.transformer_layer.mlp.gate_proj.weight_scale_inv": "model.layers.0.mlp.gate_proj.weight_scale_inv", # noqa: E501
207 "model.mtp.layers.0.transformer_layer.mlp.up_proj.weight": "model.layers.0.mlp.up_proj.weight", # noqa: E501
208 "model.mtp.layers.0.transformer_layer.mlp.up_proj.weight_scale_inv": "model.layers.0.mlp.up_proj.weight_scale_inv", # noqa: E501
209 "model.mtp.norm.weight": "final_layernorm.weight",
210 }
211
212 params_dict = dict(self.named_parameters())
213 loaded_params: set[str] = set()
214 for name, loaded_weight in weights:
215 if "rotary_emb.inv_freq" in name:
216 continue
217 spec_layer = self.get_spec_layer_idx_from_weight_name(self.config, name)
218 if spec_layer is None:
219 continue
220 name = self._rewrite_spec_layer_name(
221 spec_layer, name, new_to_old_names_mapping
222 )
223 for param_name, weight_name, shard_id in stacked_params_mapping:
224 # Skip non-stacked layers and experts (experts handled below).
225 if weight_name not in name:
226 continue
227 # We have mlp.experts[0].gate_proj in the checkpoint.
228 # Since we handle the experts below in expert_params_mapping,
229 # we need to skip here BEFORE we update the name, otherwise
230 # name will be updated to mlp.experts[0].gate_up_proj, which
231 # will then be updated below in expert_params_mapping
232 # for mlp.experts[0].gate_gate_up_proj, which breaks load.
233 if ("mlp.experts." in name) and name not in params_dict:
234 continue
235 name = name.replace(weight_name, param_name)
236
237 # QKV fusion is optional, fall back to normal
238 # weight loading if it's not enabled
239 if (param_name == "fused_qkv_a_proj") and name not in params_dict:
240 continue
241
242 # Skip loading extra bias for GPTQ models.
243 if name.endswith(".bias") and name not in params_dict:
244 continue
245
246 param = params_dict[name]
247 weight_loader = param.weight_loader
248 weight_loader(param, loaded_weight, shard_id)
249 break
250 else:
251 # Skip loading extra bias for GPTQ models.
252 if name.endswith(".bias") and name not in params_dict:
253 continue
254
255 # According to DeepSeek-V3 Technical Report, MTP modules
256 # shares embedding layer. We only load the first weights.
257 if (
258 spec_layer != self.model.mtp_start_layer_idx
259 and ".layers" not in name
260 ):
261 continue
262
263 param = params_dict[name]
264 weight_loader = getattr(param, "weight_loader", default_weight_loader)
265 weight_loader(param, loaded_weight)
266 loaded_params.add(name)
267 spec_layer_id = self.config.num_hidden_layers * 2
268 self_attn = self.model.layers[str(spec_layer_id)].mtp_block.self_attn
269 if (
270 self.quant_config is not None
271 and hasattr(self.quant_config, "weight_block_size")
272 and self_attn.kv_b_proj.weight.dtype
273 in (
274 torch.float8_e4m3fn,
275 torch.float8_e4m3fnuz,
276 )
277 ):
278 weight_block_size = self.quant_config.weight_block_size
279 if weight_block_size is not None:
280 dtype = torch.get_default_dtype()
281 w = block_dequant(
282 self_attn.kv_b_proj.weight,
283 self_attn.kv_b_proj.weight_scale_inv,
284 weight_block_size,
285 ).to(dtype)
286 else:
287 w = self_attn.kv_b_proj.weight
288 else:
289 w = self_attn.kv_b_proj.weight
290 w_kc, w_vc = w.unflatten(
291 0, (-1, self_attn.qk_nope_head_dim + self_attn.v_head_dim)
292 ).split([self_attn.qk_nope_head_dim, self_attn.v_head_dim], dim=1)
293 self_attn.w_kc = w_kc.transpose(1, 2).contiguous().transpose(1, 2)
294 self_attn.w_vc = w_vc.contiguous().transpose(1, 2)
295 # Guard against compounding on incremental load_weights calls (the
296 # in-place *= would otherwise double-apply the LoRA scaling).
297 if self.config.mla_scale_q_lora and not getattr(
298 self_attn, "_mla_q_lora_scaled", False
299 ):
300 self_attn.q_a_layernorm.weight.data *= (
301 self.config.hidden_size / self.config.q_lora_rank
302 ) ** 0.5
303 self_attn._mla_q_lora_scaled = True
304 if self.config.mla_scale_kv_lora and not getattr(
305 self_attn, "_mla_kv_lora_scaled", False
306 ):
307 self_attn.kv_a_layernorm.weight.data *= (
308 self.config.hidden_size / self.config.kv_lora_rank
309 ) ** 0.5
310 self_attn._mla_kv_lora_scaled = True
311 return loaded_params
312
313 def _rewrite_spec_layer_name(
314 self, spec_layer: int, name: str, new_to_old_names_mapping: dict
315 ) -> str:
316 """Rewrite the weight name to match the format of the original model.
317 Add .mtp_block for modules in transformer layer block for spec layer
318 and rename shared layer weights to be top level.
319 """
320 if name in new_to_old_names_mapping:
321 name = new_to_old_names_mapping[name]
322 spec_layer_weight_names = [
323 "embed_tokens",
324 "enorm",
325 "hnorm",
326 "eh_proj",
327 "shared_head",
328 ]
329 if (
330 name.startswith("enorm")
331 or name.startswith("hnorm")
332 or name.startswith("eh_proj")
333 or name.startswith("final_layernorm")
334 ):
335 name = "model.layers." + str(spec_layer) + "." + name
336 shared_weight_names = ["embed_tokens"]
337 spec_layer_weight = False
338 shared_weight = False
339 for weight_name in spec_layer_weight_names:
340 if weight_name in name:
341 spec_layer_weight = True
342 if weight_name in shared_weight_names:
343 shared_weight = True
344 break
345 if not spec_layer_weight:
346 # treat rest weights as weights for transformer layer block
347 name = name.replace(
348 "model.layers.0.", f"model.layers.{spec_layer}.mtp_block."
349 )
350 elif shared_weight:
351 # treat shared weights as top level weights
352 name = name.replace("model.layers.0.", "model.")
353 return name
354
355 def get_spec_layer_idx_from_weight_name(
356 self, config: PreTrainedConfig, weight_name: str
357 ) -> int | None:
358 if "model.mtp" in weight_name:
359 return config.num_hidden_layers * 2
360 return None