main
py 279 lines 10.9 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 # Copyright 2026 The ZhipuAI Team.
5 # Copyright 2026 The vLLM team.
6 # Copyright 2022 EleutherAI and the HuggingFace Inc. team. All rights reserved.
7 #
8 # This code is based on EleutherAI's GPT-NeoX library and the GPT-NeoX
9 # and OPT implementations in this library. It has been modified from its
10 # original forms to accommodate minor architectural differences compared
11 # to GPT-NeoX and OPT used by the Meta AI team that trained the model.
12 #
13 # Licensed under the Apache License, Version 2.0 (the "License");
14 # you may not use this file except in compliance with the License.
15 # You may obtain a copy of the License at
16 #
17 # http://www.apache.org/licenses/LICENSE-2.0
18 #
19 # Unless required by applicable law or agreed to in writing, software
20 # distributed under the License is distributed on an "AS IS" BASIS,
21 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 # See the License for the specific language governing permissions and
23 # limitations under the License.
24 """Inference-only GLM-OCR MTP model compatible with HuggingFace weights."""
25
26 from collections.abc import Iterable
27
28 import torch
29 import torch.nn as nn
30
31 from vllm.config import VllmConfig
32 from vllm.model_executor.layers.layernorm import RMSNorm
33 from vllm.model_executor.layers.logits_processor import LogitsProcessor
34 from vllm.model_executor.layers.vocab_parallel_embedding import (
35 VocabParallelEmbedding,
36 )
37 from vllm.model_executor.model_loader.weight_utils import (
38 default_weight_loader,
39 maybe_remap_kv_scale_name,
40 )
41 from vllm.platforms import current_platform
42 from vllm.sequence import IntermediateTensors
43
44 from .glm4 import Glm4DecoderLayer
45 from .glm4_moe_lite_mtp import (
46 Glm4MoeLiteMultiTokenPredictor,
47 SharedHead,
48 )
49 from .interfaces import SupportsPP
50 from .utils import (
51 get_spec_layer_idx_from_weight_name,
52 is_pp_missing_parameter,
53 maybe_prefix,
54 )
55
56
57 class GlmOcrMultiTokenPredictorLayer(nn.Module):
58 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
59 nn.Module.__init__(self)
60
61 speculative_config = vllm_config.speculative_config
62 assert speculative_config is not None
63 config = speculative_config.draft_model_config.hf_config.text_config
64 self.config = config
65 quant_config = vllm_config.quant_config
66
67 self.enorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
68 self.hnorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
69 self.eh_proj = nn.Linear(config.hidden_size * 2, config.hidden_size, bias=False)
70
71 self.device = current_platform.device_type
72 self.shared_head = SharedHead(
73 config=config, prefix=prefix, quant_config=quant_config
74 )
75 self.mtp_block = Glm4DecoderLayer(
76 vllm_config=vllm_config, prefix=prefix, config=self.config
77 )
78
79 def forward(
80 self,
81 input_ids: torch.Tensor,
82 positions: torch.Tensor,
83 previous_hidden_states: torch.Tensor,
84 inputs_embeds: torch.Tensor | None = None,
85 spec_step_index: int = 0,
86 ) -> torch.Tensor:
87 assert inputs_embeds is not None
88 # masking inputs at position 0, as not needed by MTP
89 token_positions = positions[0] if positions.ndim == 2 else positions
90 inputs_embeds.masked_fill_((token_positions == 0).unsqueeze(-1), 0)
91
92 inputs_embeds = self.enorm(inputs_embeds)
93 previous_hidden_states = self.hnorm(previous_hidden_states)
94
95 hidden_states = self.eh_proj(
96 torch.cat([inputs_embeds, previous_hidden_states], dim=-1)
97 )
98
99 hidden_states, residual = self.mtp_block(
100 positions=positions, hidden_states=hidden_states, residual=None
101 )
102 hidden_states = residual + hidden_states
103 return hidden_states
104
105
106 class GlmOcrMultiTokenPredictor(Glm4MoeLiteMultiTokenPredictor):
107 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
108 nn.Module.__init__(self)
109 config = vllm_config.model_config.hf_config.text_config
110 self.mtp_start_layer_idx = config.num_hidden_layers
111 self.num_mtp_layers = config.num_nextn_predict_layers
112 self.layers = torch.nn.ModuleDict(
113 {
114 str(idx): GlmOcrMultiTokenPredictorLayer(
115 vllm_config=vllm_config,
116 prefix=f"{prefix}.layers.{idx}",
117 )
118 for idx in range(
119 self.mtp_start_layer_idx,
120 self.mtp_start_layer_idx + self.num_mtp_layers,
121 )
122 }
123 )
124 self.embed_tokens = VocabParallelEmbedding(
125 config.vocab_size,
126 config.hidden_size,
127 )
128 self.logits_processor = LogitsProcessor(config.vocab_size)
129
130
131 class GlmOcrMTP(nn.Module, SupportsPP):
132 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
133 super().__init__()
134 self.config = vllm_config.model_config.hf_config.text_config
135 quant_config = vllm_config.quant_config
136 self.quant_config = quant_config
137 self.model = GlmOcrMultiTokenPredictor(
138 vllm_config=vllm_config, prefix=maybe_prefix(prefix, "model")
139 )
140
141 self.num_layers = self.config.num_nextn_predict_layers
142 for layer in self.model.layers.values():
143 assert isinstance(layer, GlmOcrMultiTokenPredictorLayer)
144 layer = layer.mtp_block
145 assert isinstance(layer, Glm4DecoderLayer)
146
147 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
148 return self.model.embed_input_ids(input_ids)
149
150 def forward( # type: ignore[override]
151 self,
152 input_ids: torch.Tensor,
153 positions: torch.Tensor,
154 hidden_states: torch.Tensor,
155 intermediate_tensors: IntermediateTensors | None = None,
156 inputs_embeds: torch.Tensor | None = None,
157 spec_step_idx: int = 0,
158 ) -> torch.Tensor:
159 hidden_states = self.model(
160 input_ids, positions, hidden_states, inputs_embeds, spec_step_idx
161 )
162 return hidden_states
163
164 def compute_logits(
165 self,
166 hidden_states: torch.Tensor,
167 spec_step_idx: int = 0,
168 ) -> torch.Tensor | None:
169 return self.model.compute_logits(hidden_states, spec_step_idx)
170
171 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
172 if self.quant_config is not None and (
173 cache_scale_mapper := self.quant_config.get_cache_scale_mapper()
174 ):
175 weights = cache_scale_mapper.apply(weights)
176 stacked_params_mapping = [
177 # (param_name, shard_name, shard_id)
178 (".qkv_proj", ".q_proj", "q"),
179 (".qkv_proj", ".k_proj", "k"),
180 (".qkv_proj", ".v_proj", "v"),
181 (".gate_up_proj", ".gate_proj", 0),
182 (".gate_up_proj", ".up_proj", 1),
183 ]
184 params_dict = dict(self.named_parameters())
185 loaded_params: set[str] = set()
186 for name, loaded_weight in weights:
187 if name == "lm_head.weight":
188 spec_layer = self.model.mtp_start_layer_idx
189 name = f"model.layers.{spec_layer}.shared_head.head.weight"
190 elif name == "model.embed_tokens.weight":
191 spec_layer = self.model.mtp_start_layer_idx
192 else:
193 spec_layer = get_spec_layer_idx_from_weight_name(self.config, name)
194 if spec_layer is None:
195 continue
196
197 name = self._rewrite_spec_layer_name(spec_layer, name)
198
199 if "scale" in name or "zero_point" in name:
200 # Remapping the name of FP8 kv-scale or zero point.
201 remapped_name = maybe_remap_kv_scale_name(name, params_dict)
202 if remapped_name is None:
203 continue
204 name = remapped_name
205
206 for param_name, weight_name, shard_id in stacked_params_mapping:
207 if weight_name not in name:
208 continue
209 name = name.replace(weight_name, param_name)
210 # Skip loading extra bias for GPTQ models.
211 if name.endswith(".bias") and name not in params_dict:
212 continue
213
214 if is_pp_missing_parameter(name, self):
215 continue
216
217 param = params_dict[name]
218 weight_loader = param.weight_loader
219 weight_loader(param, loaded_weight, shard_id)
220 break
221 else:
222 # Skip loading extra bias for GPTQ models.
223 if name.endswith(".bias") and name not in params_dict:
224 continue
225 # Some checkpoints include weight scale tensors for the
226 # LM head even when the quantized head isn't built. Skip
227 # them if the model does not expose a matching parameter
228 # to avoid KeyError during load.
229 if name.endswith(".weight_scale") and name not in params_dict:
230 continue
231
232 # According to DeepSeek-V3 Technical Report, MTP modules
233 # shares embedding layer. We only load the first weights.
234 if (
235 spec_layer != self.model.mtp_start_layer_idx
236 and ".layers" not in name
237 ):
238 continue
239
240 if is_pp_missing_parameter(name, self):
241 continue
242 param = params_dict[name]
243 weight_loader = getattr(param, "weight_loader", default_weight_loader)
244 weight_loader(param, loaded_weight)
245 loaded_params.add(name)
246 return loaded_params
247
248 def _rewrite_spec_layer_name(self, spec_layer: int, name: str) -> str:
249 """Rewrite the weight name to match the format of the original model.
250 Add .mtp_block for modules in transformer layer block for spec layer
251 and rename shared layer weights to be top level.
252 """
253 name = name.replace("model.language_model.layers", "model.layers")
254
255 spec_layer_weight_names = [
256 "embed_tokens",
257 "enorm",
258 "hnorm",
259 "eh_proj",
260 "shared_head",
261 ]
262 shared_weight_names = ["embed_tokens"]
263 spec_layer_weight = False
264 shared_weight = False
265 for weight_name in spec_layer_weight_names:
266 if weight_name in name:
267 spec_layer_weight = True
268 if weight_name in shared_weight_names:
269 shared_weight = True
270 break
271 if not spec_layer_weight:
272 # treat rest weights as weights for transformer layer block
273 name = name.replace(
274 f"model.layers.{spec_layer}.", f"model.layers.{spec_layer}.mtp_block."
275 )
276 elif shared_weight:
277 # treat shared weights as top level weights
278 name = name.replace(f"model.layers.{spec_layer}.", "model.")
279 return name