main
py 222 lines 8.11 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 # Copyright 2025 Xiaomi Corporation.
7 # Copyright 2023 The vLLM team.
8 # Copyright 2024 DeepSeek-AI team.
9
10 # Licensed under the Apache License, Version 2.0 (the "License");
11 # you may not use this file except in compliance with the License.
12 # You may obtain a copy of the License at
13 #
14 # http://www.apache.org/licenses/LICENSE-2.0
15 #
16 # Unless required by applicable law or agreed to in writing, software
17 # distributed under the License is distributed on an "AS IS" BASIS,
18 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 # See the License for the specific language governing permissions and
20 # limitations under the License.
21 """Inference-only MiMo-MTP model."""
22
23 from collections.abc import Iterable
24
25 import torch
26 import torch.nn as nn
27 from transformers import PreTrainedConfig
28
29 from vllm.config import CacheConfig, ModelConfig, VllmConfig
30 from vllm.model_executor.layers.layernorm import RMSNorm
31 from vllm.model_executor.layers.logits_processor import LogitsProcessor
32 from vllm.model_executor.layers.quantization import QuantizationConfig
33 from vllm.model_executor.layers.vocab_parallel_embedding import (
34 ParallelLMHead,
35 VocabParallelEmbedding,
36 )
37 from vllm.model_executor.models.qwen2 import Qwen2DecoderLayer
38 from vllm.sequence import IntermediateTensors
39
40 from .utils import AutoWeightsLoader, WeightsMapper, maybe_prefix
41
42
43 class MiMoMultiTokenPredictorLayer(nn.Module):
44 def __init__(
45 self,
46 config: PreTrainedConfig,
47 prefix: str,
48 model_config: ModelConfig,
49 cache_config: CacheConfig | None = None,
50 quant_config: QuantizationConfig | None = None,
51 ) -> None:
52 super().__init__()
53
54 self.token_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
55 self.hidden_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
56 self.input_proj = nn.Linear(
57 config.hidden_size * 2, config.hidden_size, bias=False
58 )
59 self.mtp_block = Qwen2DecoderLayer(
60 config=config,
61 cache_config=cache_config,
62 quant_config=quant_config,
63 prefix=prefix,
64 )
65 self.final_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
66
67 def forward(
68 self,
69 inputs_embeds: torch.Tensor,
70 positions: torch.Tensor,
71 previous_hidden_states: torch.Tensor,
72 spec_step_index: int = 0,
73 ) -> torch.Tensor:
74 assert inputs_embeds is not None
75 # masking inputs at position 0, as not needed by MTP
76 inputs_embeds[positions == 0] = 0
77 inputs_embeds = self.token_layernorm(inputs_embeds)
78 previous_hidden_states = self.hidden_layernorm(previous_hidden_states)
79
80 hidden_states = self.input_proj(
81 torch.cat([previous_hidden_states, inputs_embeds], dim=-1)
82 )
83
84 hidden_states, residual = self.mtp_block(
85 positions=positions, hidden_states=hidden_states, residual=None
86 )
87 hidden_states = residual + hidden_states
88 return self.final_layernorm(hidden_states)
89
90
91 class MiMoMultiTokenPredictor(nn.Module):
92 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
93 super().__init__()
94
95 config = vllm_config.model_config.hf_config
96 self.mtp_start_layer_idx = config.num_hidden_layers
97 self.num_mtp_layers = config.num_nextn_predict_layers
98
99 self.embed_tokens = VocabParallelEmbedding(
100 config.vocab_size,
101 config.hidden_size,
102 )
103
104 self.mtp_layers = torch.nn.ModuleDict(
105 {
106 str(idx): MiMoMultiTokenPredictorLayer(
107 config,
108 f"{prefix}.layers.{idx}",
109 model_config=vllm_config.model_config,
110 cache_config=vllm_config.cache_config,
111 quant_config=vllm_config.quant_config,
112 )
113 for idx in range(
114 self.mtp_start_layer_idx,
115 self.mtp_start_layer_idx + self.num_mtp_layers,
116 )
117 }
118 )
119
120 self.logits_processor = LogitsProcessor(config.vocab_size)
121
122 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
123 return self.embed_tokens(input_ids)
124
125 def forward(
126 self,
127 input_ids: torch.Tensor,
128 positions: torch.Tensor,
129 previous_hidden_states: torch.Tensor,
130 inputs_embeds: torch.Tensor | None = None,
131 spec_step_idx: int = 0,
132 ) -> torch.Tensor:
133 if inputs_embeds is None:
134 inputs_embeds = self.embed_tokens(input_ids)
135 return self.mtp_layers[str(self.mtp_start_layer_idx + spec_step_idx)](
136 inputs_embeds,
137 positions,
138 previous_hidden_states,
139 spec_step_idx,
140 )
141
142 def compute_logits(
143 self,
144 hidden_states: torch.Tensor,
145 lm_head: ParallelLMHead,
146 spec_step_idx: int = 0,
147 ) -> torch.Tensor:
148 self.mtp_layers[str(self.mtp_start_layer_idx + spec_step_idx)]
149 logits = self.logits_processor(lm_head, hidden_states)
150 return logits
151
152
153 class MiMoMTP(nn.Module):
154 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
155 super().__init__()
156 self.config = vllm_config.model_config.hf_config
157 self.model = MiMoMultiTokenPredictor(
158 vllm_config=vllm_config, prefix=maybe_prefix(prefix, "model")
159 )
160 self.lm_head = ParallelLMHead(
161 self.config.vocab_size,
162 self.config.hidden_size,
163 prefix=maybe_prefix(prefix, "lm_head"),
164 )
165 # Checkpoint stores MTP layers 0-indexed and without the `mtp_block`
166 # wrapper around the transformer block; remap onto the offset index.
167 start = self.config.num_hidden_layers
168 self.hf_to_vllm_mapper = WeightsMapper(
169 orig_to_new_substr={
170 ".self_attn.": ".mtp_block.self_attn.",
171 ".mlp.": ".mtp_block.mlp.",
172 ".input_layernorm.": ".mtp_block.input_layernorm.",
173 ".post_attention_layernorm.": ".mtp_block.post_attention_layernorm.",
174 },
175 orig_to_new_stacked={
176 ".q_proj": (".qkv_proj", "q"),
177 ".k_proj": (".qkv_proj", "k"),
178 ".v_proj": (".qkv_proj", "v"),
179 ".gate_proj": (".gate_up_proj", 0),
180 ".up_proj": (".gate_up_proj", 1),
181 },
182 orig_to_new_prefix={
183 f"model.mtp_layers.{i}.": f"model.mtp_layers.{i + start}."
184 for i in range(self.config.num_nextn_predict_layers)
185 },
186 )
187
188 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
189 return self.model.embed_input_ids(input_ids)
190
191 def forward(
192 self,
193 input_ids: torch.Tensor | None,
194 positions: torch.Tensor,
195 hidden_states: torch.Tensor,
196 intermediate_tensors: IntermediateTensors | None = None,
197 inputs_embeds: torch.Tensor | None = None,
198 spec_step_idx: int = 0,
199 ) -> torch.Tensor:
200 assert spec_step_idx == 0, "mimo_mtp only support predict one token now"
201 hidden_states = self.model(
202 input_ids, positions, hidden_states, inputs_embeds, spec_step_idx
203 )
204 return hidden_states
205
206 def compute_logits(
207 self,
208 hidden_states: torch.Tensor,
209 spec_step_idx: int = 0,
210 ) -> torch.Tensor | None:
211 return self.model.compute_logits(hidden_states, self.lm_head, spec_step_idx)
212
213 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
214 # The checkpoint carries the full model; keep only the MTP layers and
215 # the shared embedding/head.
216 def mtp_weights():
217 for name, weight in weights:
218 if "mtp_layers" in name or "embed_tokens" in name or "lm_head" in name:
219 yield name, weight
220
221 loader = AutoWeightsLoader(self)
222 return loader.load_weights(mtp_weights(), mapper=self.hf_to_vllm_mapper)