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