main
py 202 lines 7.1 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Inference-only ExaoneMoe 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.parallel_state import get_pp_group
13 from vllm.logger import init_logger
14 from vllm.model_executor.layers.layernorm import RMSNorm
15 from vllm.model_executor.layers.linear import ColumnParallelLinear
16 from vllm.model_executor.layers.logits_processor import LogitsProcessor
17 from vllm.model_executor.layers.vocab_parallel_embedding import (
18 ParallelLMHead,
19 VocabParallelEmbedding,
20 )
21 from vllm.model_executor.models.exaone_moe import ExaoneMoeDecoderLayer
22 from vllm.sequence import IntermediateTensors
23
24 from .utils import AutoWeightsLoader, WeightsMapper, maybe_prefix
25
26 logger = init_logger(__name__)
27
28 KVCache = tuple[torch.Tensor, torch.Tensor]
29
30
31 @support_torch_compile
32 class ExaoneMoeMultiTokenPredictor(nn.Module):
33 hf_to_vllm_mapper = WeightsMapper(
34 orig_to_new_stacked={
35 ".q_proj": (".qkv_proj", "q"),
36 ".k_proj": (".qkv_proj", "k"),
37 ".v_proj": (".qkv_proj", "v"),
38 # Scope to dense mlp; experts are handled separately.
39 ".mlp.gate_proj": (".mlp.gate_up_proj", 0),
40 ".mlp.up_proj": (".mlp.gate_up_proj", 1),
41 }
42 )
43
44 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
45 super().__init__()
46
47 model_config = vllm_config.model_config
48 quant_config = vllm_config.quant_config
49 config = model_config.hf_config
50
51 self.config = config
52 self.vocab_size = config.vocab_size
53 self.org_vocab_size = config.vocab_size
54
55 self.mtp_start_layer_idx = config.num_hidden_layers
56 self.num_mtp_layers = getattr(config, "num_nextn_predict_layers", 1)
57
58 self.embed_tokens = VocabParallelEmbedding(
59 self.vocab_size,
60 config.hidden_size,
61 )
62
63 self.fc = ColumnParallelLinear(
64 self.config.hidden_size * 2,
65 self.config.hidden_size,
66 gather_output=True,
67 bias=False,
68 return_bias=False,
69 quant_config=quant_config,
70 prefix=f"{prefix}.fc",
71 )
72 self.layers = nn.ModuleList(
73 ExaoneMoeDecoderLayer(
74 vllm_config.model_config.hf_config,
75 quant_config=quant_config,
76 prefix=f"{prefix}.layers.{idx}",
77 is_mtp=True,
78 )
79 for idx in range(self.num_mtp_layers)
80 )
81
82 self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
83 self.pre_fc_norm_hidden = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
84 self.pre_fc_norm_embedding = RMSNorm(
85 config.hidden_size, eps=config.rms_norm_eps
86 )
87
88 def get_input_embeddings(self, input_ids: torch.Tensor) -> torch.Tensor:
89 return self.embed_tokens(input_ids)
90
91 def forward(
92 self,
93 input_ids: torch.Tensor,
94 positions: torch.Tensor,
95 hidden_states: torch.Tensor,
96 intermediate_tensors: IntermediateTensors | None = None,
97 inputs_embeds: torch.Tensor | None = None,
98 spec_step_idx: int = 0,
99 ) -> torch.Tensor:
100 if get_pp_group().is_first_rank:
101 if inputs_embeds is None:
102 inputs_embeds = self.get_input_embeddings(input_ids)
103 assert hidden_states.shape[-1] == inputs_embeds.shape[-1]
104 inputs_embeds = self.pre_fc_norm_embedding(inputs_embeds)
105 hidden_states = self.pre_fc_norm_hidden(hidden_states)
106 hidden_states = torch.cat([inputs_embeds, hidden_states], dim=-1)
107 hidden_states = self.fc(hidden_states)
108 residual = None
109 else:
110 assert intermediate_tensors is not None
111 hidden_states = intermediate_tensors["hidden_states"]
112 residual = intermediate_tensors["residual"]
113
114 current_step_idx = spec_step_idx % self.num_mtp_layers
115 hidden_states, residual = self.layers[current_step_idx](
116 positions=positions,
117 hidden_states=hidden_states,
118 residual=residual,
119 )
120
121 if not get_pp_group().is_last_rank:
122 return IntermediateTensors(
123 {"hidden_states": hidden_states, "residual": residual}
124 )
125
126 hidden_states, _ = self.norm(hidden_states, residual)
127 return hidden_states
128
129 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
130 loader = AutoWeightsLoader(self)
131 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
132
133
134 @support_torch_compile
135 class ExaoneMoeMTP(nn.Module):
136 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
137 config = vllm_config.model_config.hf_config
138 self.vllm_config = vllm_config
139 self.quant_config = vllm_config.quant_config
140
141 super().__init__()
142 self.config = config
143 self.model = ExaoneMoeMultiTokenPredictor(
144 vllm_config=vllm_config, prefix=maybe_prefix(prefix, "mtp")
145 )
146 self.unpadded_vocab_size = config.vocab_size
147 self.lm_head = ParallelLMHead(
148 self.unpadded_vocab_size,
149 config.hidden_size,
150 org_num_embeddings=config.vocab_size,
151 # padding_size=DEFAULT_VOCAB_PADDING_SIZE,
152 prefix=maybe_prefix(prefix, "lm_head"),
153 )
154 if config.tie_word_embeddings:
155 self.lm_head = self.lm_head.tie_weights(self.model.embed_tokens)
156 self.logits_processor = LogitsProcessor(
157 self.unpadded_vocab_size, config.vocab_size
158 )
159
160 def get_input_embeddings(self, input_ids: torch.Tensor) -> torch.Tensor:
161 return self.model.get_input_embeddings(input_ids)
162
163 def forward(
164 self,
165 input_ids: torch.Tensor,
166 positions: torch.Tensor,
167 hidden_states: torch.Tensor,
168 intermediate_tensors: IntermediateTensors | None = None,
169 inputs_embeds: torch.Tensor | None = None,
170 spec_step_idx: int = 0,
171 **kwargs: object,
172 ):
173 hidden_states = self.model(
174 input_ids,
175 positions,
176 hidden_states,
177 intermediate_tensors,
178 inputs_embeds,
179 spec_step_idx,
180 )
181 return hidden_states
182
183 def compute_logits(
184 self,
185 hidden_states: torch.Tensor,
186 spec_step_idx: int = 0,
187 ) -> torch.Tensor | None:
188 return self.logits_processor(self.lm_head, hidden_states)
189
190 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
191 shared_weight_names = ["embed_tokens", "lm_head"]
192
193 def remap_weight_names(weights):
194 for name, weight in weights:
195 if name.startswith("mtp."):
196 name = name.replace("mtp.", "model.")
197 elif not any(key in name for key in shared_weight_names):
198 continue
199 yield name, weight
200
201 loader = AutoWeightsLoader(self)
202 return loader.load_weights(remap_weight_names(weights))