main
py 228 lines 8.42 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 # Copyright 2025 the LLAMA4, Meta Inc., vLLM, and HuggingFace Inc. team.
4 # All rights reserved.
5 #
6 #
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
10 #
11 # http://www.apache.org/licenses/LICENSE-2.0
12 #
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
18
19 from collections.abc import Iterable
20 from typing import TYPE_CHECKING
21
22 import torch
23 import torch.nn as nn
24
25 from vllm.compilation.decorators import support_torch_compile
26 from vllm.config import VllmConfig
27 from vllm.logger import init_logger
28 from vllm.model_executor.layers.layernorm import RMSNorm
29 from vllm.model_executor.layers.logits_processor import LogitsProcessor
30 from vllm.model_executor.layers.quantization import QuantizationConfig
31 from vllm.model_executor.layers.quantization.torchao import TorchAOConfig
32 from vllm.model_executor.layers.vocab_parallel_embedding import (
33 ParallelLMHead,
34 VocabParallelEmbedding,
35 )
36 from vllm.model_executor.models.llama4 import Llama4DecoderLayer, Llama4ForCausalLM
37 from vllm.model_executor.models.utils import extract_layer_index
38
39 from .interfaces import SupportsMultiModal, SupportsMultiModalEmbeddings
40 from .utils import (
41 AutoWeightsLoader,
42 WeightsMapper,
43 maybe_prefix,
44 process_eagle_weight,
45 )
46
47 logger = init_logger(__name__)
48
49
50 if TYPE_CHECKING:
51
52 class _EagleLlama4ForCausalLMBase(nn.Module):
53 def set_moe_parameters(self) -> None: ...
54
55 def permute_qk_weight_for_rotary(
56 self, name: str, loaded_weight: torch.Tensor
57 ) -> tuple[str, torch.Tensor]: ...
58
59 else:
60 _EagleLlama4ForCausalLMBase = Llama4ForCausalLM
61
62
63 @support_torch_compile
64 class LlamaModel(nn.Module):
65 hf_to_vllm_mapper = WeightsMapper(
66 orig_to_new_prefix={"model.": ""},
67 orig_to_new_stacked={
68 ".q_proj": (".qkv_proj", "q"),
69 ".k_proj": (".qkv_proj", "k"),
70 ".v_proj": (".qkv_proj", "v"),
71 ".gate_proj": (".gate_up_proj", 0),
72 ".up_proj": (".gate_up_proj", 1),
73 },
74 )
75
76 def __init__(
77 self,
78 *,
79 vllm_config: VllmConfig,
80 prefix: str = "",
81 start_layer_id: int = 0,
82 quant_config: QuantizationConfig | None = None,
83 ) -> None:
84 super().__init__()
85 speculative_config = vllm_config.speculative_config
86 assert speculative_config is not None
87 self.config = speculative_config.draft_model_config.hf_config
88 self.validate_and_update_config(start_layer_id, quant_config)
89 self.vocab_size = self.config.vocab_size
90 self.embed_tokens = VocabParallelEmbedding(
91 self.config.vocab_size,
92 self.config.hidden_size,
93 prefix=maybe_prefix(prefix, "embed_tokens"),
94 )
95
96 # Temporarily modify vllm_config.quant_config for draft model layers
97 original_quant_config = vllm_config.quant_config
98 vllm_config.quant_config = quant_config
99 try:
100 self.layers = nn.ModuleList(
101 [
102 Llama4DecoderLayer(
103 vllm_config=vllm_config,
104 prefix=maybe_prefix(prefix, f"layers.{i + start_layer_id}"),
105 config=self.config,
106 )
107 for i in range(self.config.num_hidden_layers)
108 ]
109 )
110 finally:
111 # Restore original quant_config
112 vllm_config.quant_config = original_quant_config
113 self.fc = torch.nn.Linear(
114 self.config.hidden_size * 2, self.config.hidden_size, bias=False
115 )
116 self.norm = RMSNorm(self.config.hidden_size, eps=self.config.rms_norm_eps)
117
118 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
119 return self.embed_tokens(input_ids)
120
121 def forward(
122 self,
123 input_ids: torch.Tensor | None,
124 positions: torch.Tensor,
125 hidden_states: torch.Tensor,
126 inputs_embeds: torch.Tensor | None = None,
127 ) -> tuple[torch.Tensor, torch.Tensor]:
128 if inputs_embeds is None:
129 inputs_embeds = self.embed_input_ids(input_ids)
130 hidden_states = self.fc(torch.cat((inputs_embeds, hidden_states), dim=-1))
131 residual = None
132 for layer in self.layers:
133 hidden_states, residual = layer(
134 positions,
135 hidden_states,
136 residual,
137 )
138 hidden_states, _ = self.norm(hidden_states, residual)
139 return hidden_states, hidden_states
140
141 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
142 loader = AutoWeightsLoader(self)
143 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
144
145 def validate_and_update_config(
146 self, start_layer_id: int, quant_config: QuantizationConfig | None = None
147 ) -> None:
148 # yoco and moe is not supported by draft model yet
149 assert self.config.yoco_global_kv_layer is None
150 assert self.config.yoco_local_kv_layer is None
151 assert len(self.config.moe_layers) == 0
152 # draft model layer index is increased by start_layer_id,
153 # so we need to pad relevant configs accordingly
154 self.config.no_rope_layers = [0] * start_layer_id + self.config.no_rope_layers
155 # currently only TorchAO quantization is supported
156 if isinstance(quant_config, TorchAOConfig):
157
158 def pad_layer_name(layer: str) -> str:
159 layer_index = extract_layer_index(layer)
160 return layer.replace(
161 str(layer_index), str(layer_index + start_layer_id)
162 )
163
164 torchao_config = quant_config.torchao_config
165 torchao_config.module_fqn_to_config = {
166 pad_layer_name(layer): quantization
167 for layer, quantization in torchao_config.module_fqn_to_config.items()
168 }
169
170
171 class EagleLlama4ForCausalLM(_EagleLlama4ForCausalLMBase, SupportsMultiModalEmbeddings):
172 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
173 nn.Module.__init__(self)
174 speculative_config = vllm_config.speculative_config
175 assert speculative_config is not None
176 self.config = speculative_config.draft_model_config.hf_config
177 target_layer_num = vllm_config.model_config.get_num_layers(
178 vllm_config.parallel_config
179 )
180 # draft model quantization config may differ from target model
181 quant_config = VllmConfig.get_quantization_config(
182 speculative_config.draft_model_config, vllm_config.load_config
183 )
184 self.model = LlamaModel(
185 vllm_config=vllm_config,
186 prefix=maybe_prefix(prefix, "model"),
187 start_layer_id=target_layer_num,
188 quant_config=quant_config,
189 )
190 logit_scale = getattr(self.config, "logit_scale", 1.0)
191 self.logits_processor = LogitsProcessor(
192 self.config.vocab_size, scale=logit_scale
193 )
194
195 self.lm_head = ParallelLMHead(
196 self.config.draft_vocab_size,
197 self.config.hidden_size,
198 prefix=maybe_prefix(prefix, "lm_head"),
199 )
200
201 # Set MoE hyperparameters
202 self.set_moe_parameters()
203
204 def get_language_model(self) -> torch.nn.Module:
205 return self.model
206
207 embed_input_ids = SupportsMultiModal.embed_input_ids # type: ignore
208
209 def forward(
210 self,
211 input_ids: torch.Tensor,
212 positions: torch.Tensor,
213 hidden_states: torch.Tensor,
214 inputs_embeds: torch.Tensor | None = None,
215 ) -> tuple[torch.Tensor, torch.Tensor]:
216 return self.model(input_ids, positions, hidden_states, inputs_embeds)
217
218 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> None:
219 def transform(inputs):
220 name, loaded_weight = inputs
221 name, weight = self.permute_qk_weight_for_rotary(name, loaded_weight)
222 if "lm_head" not in name:
223 name = "model." + name
224 process_eagle_weight(self, name)
225 return name, weight
226
227 loader = AutoWeightsLoader(self)
228 loader.load_weights(map(transform, weights))