main
py 470 lines 16.7 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 from collections.abc import Iterable
5 from typing import TYPE_CHECKING
6
7 import torch
8 import torch.nn as nn
9 from transformers import LlamaConfig
10
11 from vllm.compilation.decorators import support_torch_compile
12 from vllm.config import VllmConfig, get_current_vllm_config
13 from vllm.logger import init_logger
14 from vllm.model_executor.layers.layernorm import RMSNorm
15 from vllm.model_executor.layers.linear import QKVParallelLinear, ReplicatedLinear
16 from vllm.model_executor.layers.logits_processor import LogitsProcessor
17 from vllm.model_executor.layers.quantization.base_config import QuantizationConfig
18 from vllm.model_executor.layers.vocab_parallel_embedding import (
19 ParallelLMHead,
20 VocabParallelEmbedding,
21 )
22 from vllm.model_executor.models.llama import (
23 LlamaAttention,
24 LlamaForCausalLM,
25 LlamaMLP,
26 )
27 from vllm.model_executor.models.llama import (
28 LlamaDecoderLayer as BaseLlamaDecoderLayer,
29 )
30 from vllm.multimodal.inputs import NestedTensors
31
32 from .utils import (
33 AutoWeightsLoader,
34 WeightsMapper,
35 get_draft_quant_config,
36 maybe_prefix,
37 process_eagle_weight,
38 )
39
40 logger = init_logger(__name__)
41
42
43 if TYPE_CHECKING:
44
45 class _Eagle3LlamaDecoderLayerBase(nn.Module):
46 hidden_size: int
47 self_attn: LlamaAttention
48 mlp: LlamaMLP
49 input_layernorm: RMSNorm
50 post_attention_layernorm: RMSNorm
51
52 def __init__(
53 self,
54 vllm_config: VllmConfig,
55 prefix: str = "",
56 config: LlamaConfig | None = None,
57 ) -> None: ...
58
59 class _Eagle3LlamaForCausalLMBase(nn.Module):
60 pass
61
62 else:
63 _Eagle3LlamaDecoderLayerBase = BaseLlamaDecoderLayer
64 _Eagle3LlamaForCausalLMBase = LlamaForCausalLM
65
66
67 class LlamaDecoderLayer(_Eagle3LlamaDecoderLayerBase):
68 def __init__(
69 self,
70 vllm_config: VllmConfig,
71 prefix: str = "",
72 config: LlamaConfig | None = None,
73 layer_idx: int = 0,
74 ) -> None:
75 super().__init__(vllm_config, prefix=prefix, config=config)
76
77 config = config or vllm_config.model_config.hf_config
78 quant_config = self.get_quant_config(vllm_config)
79
80 # First layer uses 2*hidden_size (embeds + hidden_states concatenated)
81 # Subsequent layers use hidden_size (only hidden_states, no embeds)
82 qkv_input_size = 2 * self.hidden_size if layer_idx == 0 else self.hidden_size
83
84 # Parallel drafting checkpoints may have attention bias enabled
85 qkv_bias = getattr(config, "attention_bias", False)
86
87 # Override qkv_proj with correct input size and bias setting
88 self.self_attn.qkv_proj = QKVParallelLinear(
89 qkv_input_size,
90 self.self_attn.head_dim,
91 self.self_attn.total_num_heads,
92 self.self_attn.total_num_kv_heads,
93 bias=qkv_bias,
94 quant_config=quant_config,
95 prefix=maybe_prefix(prefix, "self_attn.qkv_proj"),
96 )
97
98 self.hidden_norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
99 self.layer_idx = layer_idx
100
101 if getattr(config, "norm_before_residual", False):
102 self._residual_norm = self._norm_before_residual
103 else:
104 self._residual_norm = self._norm_after_residual
105
106 def get_quant_config(self, vllm_config: VllmConfig) -> QuantizationConfig | None:
107 """Use drafter's quantization config instead of verifier's."""
108 return get_draft_quant_config(vllm_config)
109
110 def _norm_before_residual(
111 self, hidden_states: torch.Tensor
112 ) -> tuple[torch.Tensor, torch.Tensor]:
113 hidden_states = self.hidden_norm(hidden_states)
114 residual = hidden_states
115 return hidden_states, residual
116
117 def _norm_after_residual(
118 self, hidden_states: torch.Tensor
119 ) -> tuple[torch.Tensor, torch.Tensor]:
120 residual = hidden_states
121 hidden_states = self.hidden_norm(hidden_states)
122 return hidden_states, residual
123
124 def forward(
125 self,
126 positions: torch.Tensor,
127 embeds: torch.Tensor,
128 hidden_states: torch.Tensor,
129 residual: torch.Tensor | None,
130 ) -> tuple[torch.Tensor, torch.Tensor]:
131 if self.layer_idx == 0:
132 # First layer: concatenate embeds with hidden_states
133 embeds = self.input_layernorm(embeds)
134 hidden_states, residual = self._residual_norm(hidden_states=hidden_states)
135 hidden_states = torch.cat([embeds, hidden_states], dim=-1)
136 else:
137 # Subsequent layers: process hidden_states and residuals only
138 hidden_states, residual = self.input_layernorm(hidden_states, residual)
139
140 # Self Attention
141 hidden_states = self.self_attn(
142 positions=positions,
143 hidden_states=hidden_states,
144 )
145
146 hidden_states, residual = self.post_attention_layernorm(hidden_states, residual)
147
148 # Fully Connected
149 hidden_states = self.mlp(hidden_states)
150
151 return hidden_states, residual
152
153
154 @support_torch_compile(
155 dynamic_arg_dims={
156 "input_ids": 0,
157 "positions": -1,
158 "hidden_states": 0,
159 "input_embeds": 0,
160 }
161 )
162 class LlamaModel(nn.Module):
163 def __init__(
164 self,
165 *,
166 vllm_config: VllmConfig,
167 start_layer_id: int = 0,
168 prefix: str = "",
169 ) -> None:
170 super().__init__()
171 speculative_config = vllm_config.speculative_config
172 assert speculative_config is not None
173 self.config = speculative_config.draft_model_config.hf_config
174 self.vocab_size = self.config.vocab_size
175
176 # Get drafter's quantization config
177 self.quant_config = get_draft_quant_config(vllm_config)
178
179 eagle_config = getattr(self.config, "eagle_config", None) or {}
180 if "use_aux_hidden_state" in eagle_config:
181 self.use_aux_hidden_state = eagle_config["use_aux_hidden_state"]
182 else:
183 self.use_aux_hidden_state = True
184 self.norm_before_fc = bool(
185 eagle_config.get(
186 "norm_before_fc", getattr(self.config, "norm_before_fc", False)
187 )
188 )
189
190 current_vllm_config = get_current_vllm_config()
191
192 self.embed_tokens = VocabParallelEmbedding(
193 self.config.vocab_size,
194 self.config.hidden_size,
195 prefix=maybe_prefix(prefix, "embed_tokens"),
196 )
197
198 self.layers = nn.ModuleList(
199 [
200 LlamaDecoderLayer(
201 current_vllm_config,
202 prefix=maybe_prefix(prefix, f"layers.{layer_idx + start_layer_id}"),
203 config=self.config,
204 layer_idx=layer_idx,
205 )
206 for layer_idx in range(self.config.num_hidden_layers)
207 ]
208 )
209 if self.use_aux_hidden_state:
210 self.num_aux_hidden_states = getattr(
211 self.config, "num_aux_hidden_states", None
212 )
213 if self.num_aux_hidden_states is None:
214 eagle_config = getattr(self.config, "eagle_config", None) or {}
215 layer_ids = eagle_config.get("eagle_aux_hidden_state_layer_ids")
216 self.num_aux_hidden_states = len(layer_ids) if layer_ids else 3
217
218 target_hidden_size = getattr(
219 self.config, "target_hidden_size", self.config.hidden_size
220 )
221 self.fc_input_size = target_hidden_size * self.num_aux_hidden_states
222
223 if self.norm_before_fc:
224 self.input_norm: RMSNorm | None = RMSNorm(
225 self.fc_input_size,
226 eps=self.config.rms_norm_eps,
227 )
228 else:
229 self.input_norm = None
230
231 use_fc_norm = getattr(self.config, "fc_norm", False)
232 if use_fc_norm:
233 self.fc_norm = nn.ModuleList(
234 [
235 RMSNorm(target_hidden_size, eps=self.config.rms_norm_eps)
236 for _ in range(self.num_aux_hidden_states)
237 ]
238 )
239 else:
240 self.fc_norm = None
241
242 self.fc = ReplicatedLinear(
243 input_size=self.fc_input_size,
244 output_size=self.config.hidden_size,
245 bias=False,
246 params_dtype=vllm_config.model_config.dtype,
247 quant_config=self.quant_config,
248 prefix=maybe_prefix(prefix, "fc"),
249 return_bias=False,
250 )
251
252 self.norm_output = getattr(self.config, "norm_output", False)
253 self.norm = RMSNorm(
254 self.config.hidden_size,
255 eps=self.config.rms_norm_eps,
256 )
257
258 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
259 return self.embed_tokens(input_ids)
260
261 def forward(
262 self,
263 input_ids: torch.Tensor,
264 positions: torch.Tensor,
265 hidden_states: torch.Tensor,
266 input_embeds: torch.Tensor | None = None,
267 ) -> tuple[torch.Tensor, torch.Tensor]:
268 if input_embeds is None:
269 input_embeds = self.embed_input_ids(input_ids)
270 torch._assert(
271 hidden_states.shape[-1] == input_embeds.shape[-1],
272 "hidden_states and input_embeds must have the same last dimension",
273 )
274
275 residual = None
276 for layer in self.layers:
277 hidden_states, residual = layer(
278 positions=positions,
279 embeds=input_embeds,
280 hidden_states=hidden_states,
281 residual=residual,
282 )
283 hidden_states, hidden_prenorm = self.norm(hidden_states, residual)
284
285 # norm_output variant uses the post-norm hidden states.
286 aux_output = hidden_states if self.norm_output else hidden_prenorm
287
288 return hidden_states, aux_output
289
290 hf_to_vllm_mapper = WeightsMapper(
291 orig_to_new_substr={"midlayer.": "layers.0."},
292 orig_to_new_stacked={
293 ".q_proj": (".qkv_proj", "q"),
294 ".k_proj": (".qkv_proj", "k"),
295 ".v_proj": (".qkv_proj", "v"),
296 ".gate_proj": (".gate_up_proj", 0),
297 ".up_proj": (".gate_up_proj", 1),
298 },
299 )
300
301 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
302 loader = AutoWeightsLoader(self)
303 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
304
305
306 class Eagle3LlamaForCausalLM(_Eagle3LlamaForCausalLMBase):
307 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
308 nn.Module.__init__(self)
309 speculative_config = vllm_config.speculative_config
310 assert speculative_config is not None
311 self.config = speculative_config.draft_model_config.hf_config
312 # Ensure draft_vocab_size is set
313 # default to the base vocab size when absent
314 if getattr(self.config, "draft_vocab_size", None) is None:
315 base_vocab_size = getattr(self.config, "vocab_size", None)
316 self.config.draft_vocab_size = base_vocab_size
317 target_layer_num = vllm_config.model_config.get_total_num_hidden_layers()
318
319 # Store target layer count in draft config for
320 # proper layer_types indexing in draft models
321 self.config.target_layer_count = target_layer_num
322 self.model = LlamaModel(
323 vllm_config=vllm_config,
324 prefix=maybe_prefix(prefix, "model"),
325 start_layer_id=target_layer_num,
326 )
327
328 logit_scale = getattr(self.config, "logit_scale", 1.0)
329 self.lm_head = ParallelLMHead(
330 self.config.draft_vocab_size,
331 self.config.hidden_size,
332 quant_config=get_draft_quant_config(vllm_config),
333 prefix=maybe_prefix(prefix, "lm_head"),
334 )
335 self.logits_processor = LogitsProcessor(
336 self.config.draft_vocab_size, scale=logit_scale
337 )
338 target_vocab_size = vllm_config.model_config.get_vocab_size()
339 if self.config.draft_vocab_size != target_vocab_size:
340 self.draft_id_to_target_id = nn.Parameter(
341 torch.zeros(self.config.draft_vocab_size, dtype=torch.long),
342 requires_grad=False,
343 )
344 else:
345 self.draft_id_to_target_id = None
346
347 self.use_parallel_drafting = speculative_config.parallel_drafting
348
349 if self.use_parallel_drafting:
350 self.register_buffer(
351 "mask_hidden",
352 torch.zeros(1, self.model.fc_input_size),
353 persistent=False,
354 )
355
356 def embed_input_ids(
357 self,
358 input_ids: torch.Tensor,
359 multimodal_embeddings: NestedTensors | None = None,
360 is_multimodal: torch.Tensor | None = None,
361 ) -> torch.Tensor:
362 return self.model.embed_input_ids(input_ids)
363
364 def forward(
365 self,
366 input_ids: torch.Tensor,
367 positions: torch.Tensor,
368 hidden_states: torch.Tensor,
369 inputs_embeds: torch.Tensor | None = None,
370 ) -> tuple[torch.Tensor, torch.Tensor]:
371 return self.model(input_ids, positions, hidden_states, inputs_embeds)
372
373 def compute_logits(
374 self,
375 hidden_states: torch.Tensor,
376 ) -> torch.Tensor | None:
377 logits = self.logits_processor(self.lm_head, hidden_states)
378 if self.draft_id_to_target_id is None:
379 assert logits.shape[1] == self.config.vocab_size, (
380 "Expected logits to have shape "
381 f"(*, {self.config.vocab_size}), but got {logits.shape}"
382 )
383 return logits
384
385 base = torch.arange(self.config.draft_vocab_size, device=logits.device)
386 targets = base + self.draft_id_to_target_id
387 logits_new = logits.new_full(
388 (
389 logits.shape[0],
390 self.config.vocab_size,
391 ),
392 float("-inf"),
393 )
394 logits_new[:, targets] = logits
395 return logits_new
396
397 def combine_hidden_states(
398 self,
399 hidden_states: torch.Tensor,
400 ) -> torch.Tensor:
401 if not self.model.use_aux_hidden_state:
402 return hidden_states
403 # combine multiple auxiliary hidden states returned by eagle3
404
405 if self.model.norm_before_fc:
406 assert self.model.input_norm is not None
407 hidden_states = self.model.input_norm(hidden_states)
408
409 # `norm_before_fc` adds a single RMSNorm before the FC layer, whereas `fc_norm`
410 # applies separate RMSNorms to each chunk of the hidden states.
411 if self.model.fc_norm is not None:
412 chunks = hidden_states.chunk(self.model.num_aux_hidden_states, dim=-1)
413 hidden_states = torch.cat(
414 [norm(chunk) for norm, chunk in zip(self.model.fc_norm, chunks)],
415 dim=-1,
416 )
417
418 return self.model.fc(hidden_states)
419
420 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]):
421 model_weights = {}
422 includes_draft_id_mapping = False
423 includes_embed_tokens = False
424 includes_mask_hidden = False
425 for name, loaded_weight in weights:
426 if "t2d" in name:
427 continue
428 if "d2t" in name:
429 name = name.replace("d2t", "draft_id_to_target_id")
430 includes_draft_id_mapping = True
431 elif "mask_hidden" in name:
432 # Load mask_hidden directly into buffer
433 if not self.use_parallel_drafting:
434 logger.warning(
435 "mask_hidden found in weights but "
436 "model is not configured for parallel drafting. "
437 "Skipping loading mask_hidden."
438 )
439 continue
440 self.mask_hidden.copy_(loaded_weight.view(1, -1))
441 includes_mask_hidden = True
442 continue
443 elif "lm_head" not in name:
444 name = "model." + name
445 if "embed_tokens" in name:
446 includes_embed_tokens = True
447 model_weights[name] = loaded_weight
448 process_eagle_weight(self, name)
449
450 if not includes_mask_hidden and self.use_parallel_drafting:
451 raise ValueError(
452 "mask_hidden not found in weights but "
453 "model is configured for parallel drafting. "
454 "Please provide mask_hidden in the weights."
455 )
456
457 orig_to_new_substr = {"mask_hidden": None}
458 if not includes_draft_id_mapping:
459 orig_to_new_substr["draft_id_to_target_id"] = None
460 if not includes_embed_tokens:
461 orig_to_new_substr["embed_tokens"] = None
462 if not self.model.use_aux_hidden_state:
463 orig_to_new_substr["fc."] = None
464 if not self.model.norm_before_fc:
465 orig_to_new_substr["input_norm."] = None
466 loader = AutoWeightsLoader(self)
467 loader.load_weights(
468 model_weights.items(),
469 mapper=WeightsMapper(orig_to_new_substr=orig_to_new_substr),
470 )