main
py 844 lines 34.2 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 #
4 # Copyright 2025 the LLAMA4, Meta Inc., vLLM, and HuggingFace Inc. team.
5 # All rights reserved.
6 #
7 #
8 # Licensed under the Apache License, Version 2.0 (the "License");
9 # you may not use this file except in compliance with the License.
10 # You may obtain a copy of the License at
11 #
12 # http://www.apache.org/licenses/LICENSE-2.0
13 #
14 # Unless required by applicable law or agreed to in writing, software
15 # distributed under the License is distributed on an "AS IS" BASIS,
16 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 # See the License for the specific language governing permissions and
18 # limitations under the License.
19 """Inference-only LLaMA model compatible with HuggingFace weights."""
20
21 from collections.abc import Callable, Iterable
22
23 import torch
24 from torch import nn
25 from transformers import Llama4TextConfig
26
27 from vllm.compilation.decorators import support_torch_compile
28 from vllm.config import CacheConfig, VllmConfig
29 from vllm.distributed import (
30 get_ep_group,
31 get_tensor_model_parallel_world_size,
32 tensor_model_parallel_all_gather,
33 )
34 from vllm.logger import init_logger
35 from vllm.model_executor.layers.attention import (
36 Attention,
37 ChunkedLocalAttention,
38 )
39 from vllm.model_executor.layers.fused_moe import (
40 FusedMoEFactory,
41 GateLinear,
42 fused_moe_make_expert_params_mapping,
43 )
44 from vllm.model_executor.layers.layernorm import RMSNorm
45 from vllm.model_executor.layers.linear import (
46 QKVParallelLinear,
47 RowParallelLinear,
48 )
49 from vllm.model_executor.layers.quantization import QuantizationConfig
50 from vllm.model_executor.layers.rotary_embedding import get_rope
51 from vllm.model_executor.model_loader.weight_utils import (
52 default_weight_loader,
53 maybe_remap_kv_scale_name,
54 maybe_remap_moe_expert_param_name,
55 )
56 from vllm.model_executor.models.interfaces import MixtureOfExperts
57 from vllm.model_executor.models.utils import sequence_parallel_chunk
58 from vllm.platforms import current_platform
59 from vllm.utils.torch_utils import is_torch_equal_or_newer
60
61 from .llama import LlamaForCausalLM, LlamaMLP, LlamaModel
62 from .utils import (
63 AutoWeightsLoader,
64 PPMissingLayer,
65 extract_layer_index,
66 fast_topk,
67 is_pp_missing_parameter,
68 )
69
70 logger = init_logger(__name__)
71
72
73 class Llama4MoE(nn.Module):
74 @staticmethod
75 def custom_routing_function(
76 hidden_states: torch.Tensor,
77 gating_output: torch.Tensor,
78 topk: int,
79 renormalize: bool,
80 ) -> tuple[torch.Tensor, torch.Tensor]:
81 router_scores, router_indices = fast_topk(gating_output, topk, dim=-1)
82 # pseudo-standard is that the router scores are floats
83 router_scores = torch.sigmoid(router_scores.float())
84 return (router_scores, router_indices.to(torch.int32))
85
86 def __init__(self, vllm_config: VllmConfig, prefix: str = ""):
87 super().__init__()
88
89 config = vllm_config.model_config.hf_config
90 parallel_config = vllm_config.parallel_config
91 quant_config = vllm_config.quant_config
92
93 self.tp_size = get_tensor_model_parallel_world_size()
94 self.top_k = config.num_experts_per_tok
95 self.is_sequence_parallel = parallel_config.use_sequence_parallel_moe
96 self.ep_group = get_ep_group().device_group
97 self.ep_rank = get_ep_group().rank_in_group
98 self.ep_size = self.ep_group.size()
99
100 intermediate_size_moe = config.intermediate_size
101 self.router = GateLinear(
102 config.hidden_size,
103 config.num_local_experts,
104 prefix=f"{prefix}.router",
105 )
106
107 self.shared_expert = LlamaMLP(
108 hidden_size=config.hidden_size,
109 intermediate_size=intermediate_size_moe,
110 hidden_act="silu",
111 quant_config=quant_config,
112 bias=False,
113 prefix=f"{prefix}.shared_expert",
114 reduce_results=False,
115 disable_tp=self.is_sequence_parallel,
116 )
117
118 # Load balancing settings.
119 eplb_config = parallel_config.eplb_config if parallel_config else None
120 self.enable_eplb = parallel_config.enable_eplb if parallel_config else False
121 self.n_redundant_experts = (
122 eplb_config.num_redundant_experts if eplb_config else 0
123 )
124
125 self.n_routed_experts: int = config.num_local_experts
126 self.n_logical_experts = self.n_routed_experts
127 self.n_shared_experts: int = 1
128 self.n_local_experts: int = config.num_local_experts
129 self.n_physical_experts = self.n_local_experts + self.n_redundant_experts
130 self.n_local_physical_experts = self.n_physical_experts // self.ep_size
131
132 self.experts = FusedMoEFactory(
133 shared_experts=self.shared_expert,
134 num_experts=config.num_local_experts,
135 top_k=config.num_experts_per_tok,
136 hidden_size=config.hidden_size,
137 custom_routing_function=Llama4MoE.custom_routing_function,
138 intermediate_size=intermediate_size_moe,
139 apply_router_weight_on_input=True,
140 renormalize=False,
141 quant_config=quant_config,
142 prefix=f"{prefix}.experts",
143 is_sequence_parallel=self.is_sequence_parallel,
144 enable_eplb=self.enable_eplb,
145 num_redundant_experts=self.n_redundant_experts,
146 )
147
148 def forward(self, hidden_states):
149 num_tokens = hidden_states.shape[0]
150 if self.is_sequence_parallel:
151 hidden_states = sequence_parallel_chunk(hidden_states)
152
153 router_logits, _ = self.router(hidden_states)
154
155 experts_out = self.experts(
156 hidden_states=hidden_states,
157 router_logits=router_logits,
158 )
159
160 if self.is_sequence_parallel:
161 experts_out = tensor_model_parallel_all_gather(experts_out, 0)
162 experts_out = experts_out[:num_tokens]
163
164 return experts_out
165
166
167 class Llama4Attention(nn.Module):
168 def __init__(
169 self,
170 config: Llama4TextConfig,
171 hidden_size: int,
172 num_heads: int,
173 num_kv_heads: int,
174 max_position_embeddings: int = 8192,
175 quant_config: QuantizationConfig | None = None,
176 bias: bool = False,
177 bias_o_proj: bool = False,
178 cache_config: CacheConfig | None = None,
179 prefix: str = "",
180 ) -> None:
181 super().__init__()
182 self.layer_idx = extract_layer_index(prefix)
183 self.hidden_size = hidden_size
184 self.no_rope_layers = config.no_rope_layers
185 self.nope = self.no_rope_layers[self.layer_idx] == 0
186 self.use_qk_norm = config.use_qk_norm and not self.nope
187 tp_size = get_tensor_model_parallel_world_size()
188 self.total_num_heads = num_heads
189 assert self.total_num_heads % tp_size == 0
190 self.num_heads = self.total_num_heads // tp_size
191 self.total_num_kv_heads = num_kv_heads
192 if self.total_num_kv_heads >= tp_size:
193 # Number of KV heads is greater than TP size, so we partition
194 # the KV heads across multiple tensor parallel GPUs.
195 assert self.total_num_kv_heads % tp_size == 0
196 else:
197 # Number of KV heads is less than TP size, so we replicate
198 # the KV heads across multiple tensor parallel GPUs.
199 assert tp_size % self.total_num_kv_heads == 0
200 self.num_kv_heads = max(1, self.total_num_kv_heads // tp_size)
201 self.head_dim = config.head_dim
202 self.q_size = self.num_heads * self.head_dim
203 self.kv_size = self.num_kv_heads * self.head_dim
204 self.scaling = self.head_dim**-0.5
205 self.attn_temperature_tuning = self.nope and config.attn_temperature_tuning
206
207 self.floor_scale = getattr(config, "floor_scale", 8192.0)
208 self.attn_scale = getattr(config, "attn_scale", 0.1)
209 self.max_position_embeddings = max_position_embeddings
210 self.n_rep = self.num_heads // self.num_kv_heads
211 self.qk_norm = (
212 RMSNorm(
213 hidden_size=self.head_dim,
214 eps=config.rms_norm_eps,
215 has_weight=False,
216 dtype=torch.float32,
217 )
218 if self.use_qk_norm
219 else None
220 )
221 self.qkv_proj = QKVParallelLinear(
222 hidden_size=hidden_size,
223 head_size=self.head_dim,
224 total_num_heads=self.total_num_heads,
225 total_num_kv_heads=self.total_num_kv_heads,
226 bias=bias,
227 quant_config=quant_config,
228 prefix=f"{prefix}.qkv_proj",
229 )
230
231 self.o_proj = RowParallelLinear(
232 input_size=self.total_num_heads * self.head_dim,
233 output_size=hidden_size,
234 bias=bias_o_proj,
235 quant_config=quant_config,
236 prefix=f"{prefix}.o_proj",
237 )
238 is_neox_style = True
239
240 self.rotary_emb = (
241 get_rope(
242 self.head_dim,
243 max_position=max_position_embeddings,
244 rope_parameters=config.rope_parameters,
245 is_neox_style=is_neox_style,
246 )
247 if not self.nope
248 else None
249 )
250
251 use_chunked_local_attn = not self.nope and config.attention_chunk_size
252 attn_cls = ChunkedLocalAttention if use_chunked_local_attn else Attention
253 self.attn = attn_cls(
254 self.num_heads,
255 self.head_dim,
256 self.scaling,
257 num_kv_heads=self.num_kv_heads,
258 cache_config=cache_config,
259 quant_config=quant_config,
260 prefix=f"{prefix}.attn",
261 **(
262 {"attention_chunk_size": config.attention_chunk_size}
263 if use_chunked_local_attn
264 else {}
265 ),
266 )
267
268 def _get_attn_scale(self, positions: torch.Tensor) -> torch.Tensor:
269 floor = torch.floor((positions + 1.0) / self.floor_scale)
270 attn_scale = torch.log(floor + 1.0) * self.attn_scale + 1.0
271
272 return attn_scale.unsqueeze(-1)
273
274 def forward(
275 self,
276 positions: torch.Tensor,
277 hidden_states: torch.Tensor,
278 ) -> torch.Tensor:
279 qkv, _ = self.qkv_proj(hidden_states)
280 q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
281
282 if self.rotary_emb is not None:
283 q, k = self.rotary_emb(positions, q, k)
284
285 if self.qk_norm is not None:
286 # Normalization is applied on the head_dim dimension. The rest of
287 # the dimensions are collapsed into a single dimension to support
288 # custom rms_norm cuda kernel.
289 q = q.reshape(-1, self.head_dim)
290 q = self.qk_norm(q.float()).reshape(-1, self.q_size).to(q.dtype)
291 k = k.reshape(-1, self.head_dim)
292 k = self.qk_norm(k.float()).reshape(-1, self.kv_size).to(k.dtype)
293
294 # We are applying temperature tuning (https://arxiv.org/abs/2501.19399)
295 # to NoPE layers, where the inference-time temperature tuning function
296 # is customized to not affect short context
297 # while working at very long context
298 # https://arxiv.org/abs/2501.19399
299 #
300 # We should apply temperature tuning between (after) rotary / QK norm
301 # and (before) attention.
302 if self.attn_temperature_tuning and self.nope:
303 attn_scale = self._get_attn_scale(positions)
304 q = (q * attn_scale).to(q.dtype)
305 attn_output = self.attn(q, k, v)
306 output, _ = self.o_proj(attn_output)
307 return output
308
309
310 class Llama4DecoderLayer(nn.Module):
311 def __init__(
312 self,
313 vllm_config: VllmConfig,
314 prefix: str = "",
315 config: Llama4TextConfig | None = None,
316 ) -> None:
317 super().__init__()
318
319 config = config or vllm_config.model_config.hf_config
320 cache_config = vllm_config.cache_config
321 quant_config = vllm_config.quant_config
322
323 self.layer_idx = extract_layer_index(prefix)
324 self.global_layer = config.no_rope_layers[self.layer_idx] == 0
325 self.hidden_size = config.hidden_size
326 max_position_embeddings = config.max_position_embeddings
327
328 self.self_attn = Llama4Attention(
329 config=config,
330 hidden_size=self.hidden_size,
331 num_heads=config.num_attention_heads,
332 num_kv_heads=config.num_key_value_heads,
333 max_position_embeddings=max_position_embeddings,
334 quant_config=quant_config,
335 bias=False,
336 bias_o_proj=False,
337 cache_config=cache_config,
338 prefix=f"{prefix}.self_attn",
339 )
340 is_moe_layer = (
341 config.interleave_moe_layer_step > 0
342 and (self.layer_idx + 1) % config.interleave_moe_layer_step == 0
343 )
344 if is_moe_layer:
345 self.feed_forward = Llama4MoE(
346 vllm_config=vllm_config,
347 prefix=f"{prefix}.feed_forward",
348 )
349 else:
350 self.feed_forward = LlamaMLP(
351 hidden_size=self.hidden_size,
352 intermediate_size=config.intermediate_size_mlp,
353 hidden_act="silu",
354 quant_config=quant_config,
355 bias=False,
356 prefix=f"{prefix}.feed_forward",
357 )
358 self.input_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
359 self.post_attention_layernorm = RMSNorm(
360 config.hidden_size, eps=config.rms_norm_eps
361 )
362
363 def forward(
364 self,
365 positions: torch.Tensor,
366 hidden_states: torch.Tensor,
367 residual: torch.Tensor | None,
368 ) -> tuple[torch.Tensor, torch.Tensor]:
369 # Self Attention
370 if residual is None:
371 residual = hidden_states
372 hidden_states = self.input_layernorm(hidden_states)
373 else:
374 hidden_states, residual = self.input_layernorm(hidden_states, residual)
375 hidden_states = self.self_attn(positions=positions, hidden_states=hidden_states)
376
377 # Fully Connected
378 hidden_states, residual = self.post_attention_layernorm(hidden_states, residual)
379 hidden_states = self.feed_forward(hidden_states)
380 return hidden_states, residual
381
382
383 @support_torch_compile
384 class Llama4Model(LlamaModel):
385 def __init__(
386 self,
387 *,
388 vllm_config: VllmConfig,
389 prefix: str = "",
390 layer_type: type[Llama4DecoderLayer] = Llama4DecoderLayer,
391 ):
392 self.num_experts = vllm_config.model_config.hf_config.num_local_experts
393 self.n_redundant_experts = (
394 vllm_config.parallel_config.eplb_config.num_redundant_experts
395 )
396 super().__init__(vllm_config=vllm_config, prefix=prefix, layer_type=layer_type)
397
398 def load_moe_expert_weights(
399 self,
400 name: str,
401 loaded_weight: torch.Tensor,
402 params_dict: dict[str, nn.Parameter],
403 loaded_params: set[str],
404 expert_params_mapping: list[tuple[str, str, int, str]],
405 fused: bool = True,
406 ) -> bool:
407 """Load MoE expert weights.
408
409 Args:
410 name: The name of the weight to load.
411 loaded_weight: The weight to load.
412 params_dict: The dictionary of module parameters.
413 loaded_params: The set of already loaded parameters.
414 expert_params_mapping: The mapping of expert parameters. Must be
415 generated by fused_moe_make_expert_params_mapping().
416 fused: Whether the expert weights are fused into a single weight
417 tensor or are separate weight tensors for each expert.
418 When fused is True, loaded_weight should have shape of:
419 [num_experts, hidden_in, hidden_out] for gate/up/down proj and
420 [hidden_out, hidden_in] for the others like router.
421 When fused is False, loaded_weight should have shape of:
422 [hidden_out, hidden_in].
423
424 Returns:
425 True if loaded_weight is one of MoE weights and the MoE expert
426 weights are loaded successfully, False otherwise.
427
428 """
429 # Whether the MoE expert weights are loaded successfully.
430 expert_param_loaded = False
431
432 # If fused is True, the loaded weight is in the layout of:
433 # [num_experts, hidden_in, hidden_out], so we must transpose the last
434 # two dimensions to match the expected layout of the parameters.
435 if fused and loaded_weight.ndim == 3:
436 loaded_weight = loaded_weight.transpose(-1, -2)
437
438 # If the gate_proj and up_proj weights are fused into a single
439 # weight tensor, we need to split the weight tensor into a tuple
440 # of two weight tensors along the hidden_out dimension.
441 if "experts.gate_up_proj" in name:
442 loaded_weight = loaded_weight.chunk(2, dim=-2)
443
444 # Iterate over all the expert parameters and load the weights if we find
445 # a match in weight name.
446 for param_name, weight_name, expert_id, shard_id in expert_params_mapping:
447 # Get a view of the loaded_weight to avoid modifying the original
448 # one across iterations.
449 new_loaded_weight = loaded_weight
450
451 # If expert weights are fused into a single weight tensor, remove
452 # the expert index from the expected weight name.
453 if fused:
454 # The string between e_str and proj_str is the expert index.
455 e_str, _, proj_str, _ = weight_name.split(".")
456 weight_name = f"{e_str}.{proj_str}"
457 param_name = f"{param_name}weight"
458
459 # Skip if the current weight is not one of the MoE weights.
460 if weight_name not in name:
461 continue
462
463 # Replace the weight name with the parameter name.
464 full_param_name = name.replace(weight_name, param_name)
465
466 # Skip if the current weight corresponds to a parameter that
467 # does not exist on the current PP (pipeline parallel) rank.
468 if is_pp_missing_parameter(name, self):
469 continue
470
471 # Skip if the current weight is for the bias.
472 if (
473 name.endswith(".bias") or name.endswith("_bias")
474 ) and name not in params_dict:
475 continue
476
477 param = params_dict[full_param_name]
478 weight_loader = param.weight_loader
479
480 if fused:
481 # If the parameter is for w13 together, the corresponding weight
482 # will be a tuple, so we must select the correct weight
483 # depending on the shard id, which is either "w1" or "w3".
484 if "w13" in full_param_name:
485 assert shard_id in ["w1", "w3"]
486 shard_idx = 0 if shard_id == "w1" else 1
487 new_loaded_weight = new_loaded_weight[shard_idx]
488
489 # If EP (expert parallel) is enabled, update expert_id to the
490 # starting expert index for the current EP rank and extract the
491 # corresponding expert weights.
492 layer_idx = extract_layer_index(name)
493 expert_map = self.layers[layer_idx].feed_forward.experts.expert_map
494 if expert_map is not None:
495 local_expert_indices = (
496 (expert_map != -1)
497 .nonzero()
498 .flatten()
499 .to(new_loaded_weight.device)
500 )
501 # Workaround for FP8 CPU indexing on older PyTorch:
502 # https://github.com/vllm-project/vllm/issues/32862
503 is_fp8_dtype = new_loaded_weight.dtype == (
504 current_platform.fp8_dtype()
505 ) or (
506 new_loaded_weight.dtype.is_floating_point
507 and new_loaded_weight.element_size() == 1
508 )
509 if (
510 new_loaded_weight.device.type == "cpu"
511 and is_fp8_dtype
512 and not is_torch_equal_or_newer("2.11.0")
513 ):
514 # PyTorch < 2.11 doesn't support CPU float8 indexing.
515 new_loaded_weight = new_loaded_weight.to(torch.float16)[
516 local_expert_indices
517 ].to(new_loaded_weight.dtype)
518 else:
519 new_loaded_weight = new_loaded_weight[local_expert_indices]
520 expert_id = local_expert_indices[0].item()
521 else:
522 # TODO: add EP support for non fused weights
523 pass
524
525 # Load the weight into the module parameter with corresponding
526 # shard id and expert id.
527 weight_loader(
528 param,
529 new_loaded_weight,
530 full_param_name,
531 shard_id=shard_id,
532 expert_id=expert_id,
533 )
534 loaded_params.add(full_param_name)
535 expert_param_loaded = True
536
537 return expert_param_loaded
538
539 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
540 # Name mapping from the parameter name to the shard name and
541 # corresponding shard id.
542 stacked_params_mapping = [
543 # (param_name, shard_name, shard_id)
544 (".qkv_proj", ".q_proj", "q"),
545 (".qkv_proj", ".k_proj", "k"),
546 (".qkv_proj", ".v_proj", "v"),
547 (".gate_up_proj", ".gate_proj", 0),
548 (".gate_up_proj", ".up_proj", 1),
549 ]
550 # Indicate whether the expert weights are fused into a single weight
551 # tensor.
552 fused_experts_params = False
553 # Expert parameter mapping for the case where the expert weights are
554 # not fused into a single weight tensor.
555 expert_params_mapping = fused_moe_make_expert_params_mapping(
556 self,
557 ckpt_gate_proj_name="gate_proj",
558 ckpt_down_proj_name="down_proj",
559 ckpt_up_proj_name="up_proj",
560 num_experts=self.num_experts,
561 num_redundant_experts=self.n_redundant_experts,
562 )
563 # Expert parameter mapping for the case where the expert weights are
564 # fused into a single weight tensor.
565 expert_params_mapping_fused = fused_moe_make_expert_params_mapping(
566 self,
567 ckpt_gate_proj_name="gate_up_proj",
568 ckpt_down_proj_name="down_proj",
569 ckpt_up_proj_name="gate_up_proj",
570 num_experts=1,
571 )
572 # All the module parameters.
573 params_dict = dict(self.named_parameters())
574 # The module parameters that have been loaded.
575 loaded_params: set[str] = set()
576 weight_loader: Callable[..., object]
577
578 # Iterate over all the weights and load them into module parameters.
579 for name, loaded_weight in weights:
580 # If the name contains "experts.gate_up_proj" or "experts.down_proj"
581 # without the expert indices, it means the expert weights are fused
582 # into a single weight tensor across all experts.
583 if "experts.gate_up_proj" in name or "experts.down_proj" in name:
584 fused_experts_params = True
585 expert_params_mapping = expert_params_mapping_fused
586
587 # Iterate over stacked_params_mapping to check if the current weight
588 # is one of the stacked parameters. If so, load the weight with the
589 # corresponding shard id. Note that MoE weights are handled
590 # separately in the else block.
591 for param_name, weight_name, shard_id in stacked_params_mapping:
592 # Skip if the current weight is not one of the stacked
593 # parameters or if the current weight is a MoE weight.
594 if weight_name not in name or "experts" in name:
595 continue
596
597 # For ModelOpt checkpoints, we need to rename the self_attn
598 # weight/weight_scale names except for kv cache scales.
599 if not (
600 name.endswith((".k_scale", ".v_scale")) and "self_attn" in name
601 ):
602 name = name.replace(weight_name, param_name)
603
604 # Skip if the current weight corresponds to a parameter that
605 # does not exist on the current PP (pipeline parallel) rank.
606 if is_pp_missing_parameter(name, self):
607 continue
608
609 # Remap kv cache scale names for any checkpoint format the
610 # quant config's `get_cache_scale_mapper` does not cover
611 # (idempotent for names already renamed by the mapper).
612 if name.endswith("scale"):
613 remapped_name = maybe_remap_kv_scale_name(name, params_dict)
614 if remapped_name is None:
615 continue
616 name = remapped_name
617
618 # Load the weight into the module parameter with corresponding
619 # shard id and exit the for loop and the else block.
620 param = params_dict[name]
621 weight_loader = getattr(param, "weight_loader", default_weight_loader)
622
623 if weight_loader == default_weight_loader:
624 weight_loader(param, loaded_weight)
625 else:
626 weight_loader(param, loaded_weight, shard_id)
627
628 loaded_params.add(name)
629 break
630
631 # Handle normal (non-stacked) weights and MoE weights.
632 else:
633 # First, try to load MoE weights using load_moe_expert_weights.
634 # If successful, move on to next loaded weight.
635 if self.load_moe_expert_weights(
636 name,
637 loaded_weight,
638 params_dict,
639 loaded_params,
640 expert_params_mapping,
641 fused=fused_experts_params,
642 ):
643 continue
644
645 # Skip if the current weight corresponds to a parameter that
646 # does not exist on the current PP (pipeline parallel) rank.
647 if is_pp_missing_parameter(name, self):
648 continue
649
650 # Handle flat expert scale parameters that don't match
651 # per-expert patterns, i.e. one weight scale tensor for all
652 # experts.
653 scale_names = [
654 "w13_input_scale",
655 "w13_weight_scale",
656 "w2_input_scale",
657 "w2_weight_scale",
658 ]
659 if "experts." in name and any(
660 scale_name in name for scale_name in scale_names
661 ):
662 name = maybe_remap_moe_expert_param_name(name, params_dict)
663 param = params_dict[name]
664 weight_loader = getattr(
665 param, "weight_loader", default_weight_loader
666 )
667
668 # If weight loader supports special moe loading, use it to
669 # avoid expensive runtime reflection
670 if getattr(weight_loader, "supports_moe_loading", False):
671 # Map the weight name to the corresponding shard id.
672 shard_id = "w2" if "w2_" in name else "w1"
673
674 # Transpose if weight scales are FP8 block scales with
675 # three dimensions:
676 # [num_experts, hidden_in, hidden_out].
677 if (
678 name.endswith("weight_scale")
679 and loaded_weight.dtype == torch.float8_e4m3fn
680 and loaded_weight.ndim == 3
681 ):
682 loaded_weight = loaded_weight.transpose(-1, -2)
683
684 # Load the weight into the module parameter with
685 # corresponding shard id and expert id.
686 weight_loader(
687 param, loaded_weight, name, shard_id=shard_id, expert_id=0
688 )
689
690 else:
691 # Regular weight loader (handles both
692 # param.weight_loader and default_weight_loader)
693 weight_loader(param, loaded_weight)
694
695 loaded_params.add(name)
696 continue
697
698 # Handle normal (non-stacked, non-MoE) weights.
699 param = params_dict[name]
700 weight_loader = getattr(param, "weight_loader", default_weight_loader)
701 weight_loader(param, loaded_weight)
702 loaded_params.add(name)
703
704 # Finally, return the set of loaded parameters.
705 return loaded_params
706
707
708 class Llama4ForCausalLM(LlamaForCausalLM, MixtureOfExperts):
709 packed_modules_mapping = {
710 "qkv_proj": ["q_proj", "k_proj", "v_proj"],
711 "gate_up_proj": ["gate_proj", "up_proj"],
712 }
713
714 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
715 # update temperature tuning config from generation config
716 gen_config = vllm_config.model_config.try_get_generation_config()
717 gen_config.update(vllm_config.model_config.override_generation_config)
718 # enable temperature tuning by default when max_model_len > 32K
719 default_attn_temperature_tuning = vllm_config.model_config.max_model_len > 32768
720 vllm_config.model_config.hf_config.attn_temperature_tuning = gen_config.get(
721 "attn_temperature_tuning", default_attn_temperature_tuning
722 )
723
724 super().__init__(
725 vllm_config=vllm_config, prefix=prefix, layer_type=Llama4DecoderLayer
726 )
727 # Set MoE hyperparameters
728 self.set_moe_parameters()
729
730 def set_moe_parameters(self):
731 self.moe_layers = []
732 example_moe = None
733 for layer in self.model.layers:
734 if isinstance(layer, PPMissingLayer):
735 continue
736
737 assert isinstance(layer, Llama4DecoderLayer)
738 if isinstance(layer.feed_forward, Llama4MoE):
739 # Pick last one layer since the first ones may be dense layers.
740 example_moe = layer.feed_forward
741 self.moe_layers.append(layer.feed_forward.experts)
742
743 if example_moe is None:
744 self.num_moe_layers = 0
745 self.num_expert_groups = 0
746 self.num_logical_experts = 0
747 self.num_physical_experts = 0
748 self.num_local_physical_experts = 0
749 self.num_routed_experts = 0
750 self.num_shared_experts = 0
751 self.num_redundant_experts = 0
752 logger.warning("No Llama4MoE layer found in model.layers.")
753 else:
754 self.num_moe_layers = len(self.moe_layers)
755 self.num_expert_groups = 1
756 self.num_logical_experts = example_moe.n_logical_experts
757 self.num_physical_experts = example_moe.n_physical_experts
758 self.num_local_physical_experts = example_moe.n_local_physical_experts
759 self.num_routed_experts = example_moe.n_routed_experts
760 self.num_shared_experts = example_moe.n_shared_experts
761 self.num_redundant_experts = example_moe.n_redundant_experts
762
763 def update_physical_experts_metadata(
764 self,
765 num_physical_experts: int,
766 num_local_physical_experts: int,
767 ) -> None:
768 assert self.num_local_physical_experts == num_local_physical_experts
769 self.num_physical_experts = num_physical_experts
770 self.num_local_physical_experts = num_local_physical_experts
771 self.num_redundant_experts = num_physical_experts - self.num_logical_experts
772 for layer in self.model.layers:
773 if isinstance(layer, PPMissingLayer):
774 continue
775
776 if isinstance(layer.feed_forward, Llama4MoE):
777 moe = layer.feed_forward
778 moe.n_local_physical_experts = num_local_physical_experts
779 moe.n_physical_experts = num_physical_experts
780 moe.n_redundant_experts = self.num_redundant_experts
781 moe.experts.update_expert_map()
782
783 def _init_model(
784 self,
785 vllm_config: VllmConfig,
786 prefix: str = "",
787 layer_type: type[Llama4DecoderLayer] = Llama4DecoderLayer,
788 ):
789 return Llama4Model(
790 vllm_config=vllm_config, prefix=prefix, layer_type=layer_type
791 )
792
793 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
794 loader = AutoWeightsLoader(self)
795 # Use a generator (not a list comprehension) so the weights iterator is
796 # consumed lazily by AutoWeightsLoader. Materializing it here would hold
797 # the entire language-model checkpoint in host memory at once, which can
798 # OOM loaders that return private copies rather than mmap views.
799 weights = (
800 self.permute_qk_weight_for_rotary(name, loaded_weight)
801 for name, loaded_weight in weights
802 )
803 return loader.load_weights(weights)
804
805 def permute_qk_weight_for_rotary(
806 self,
807 name: str,
808 loaded_weight: torch.Tensor,
809 ) -> tuple[str, torch.Tensor]:
810 modules = name.split(".")
811 # Permute Q/K weights and corresponding scales for rotary embedding.
812 # This pathway is validated against modelopt and compressed-tensors ckpts,
813 # and for per-tensor, per-group (e.g. GPTQ), and per-channel quant schemes.
814 # Note: permutations are not feasible only for per-block (e.g. DeepSeek 128x128)
815 # For per-block quantization, consider not quantizing q/k_proj.
816 is_weight = modules[-1] in ("weight", "weight_packed")
817 is_weight_scale = (
818 modules[-1] == "weight_scale"
819 and loaded_weight.numel() > 1 # no need to permute per-tensor scales
820 )
821 is_k_proj = "wk" in modules or "k_proj" in modules
822 is_q_proj = "wq" in modules or "q_proj" in modules
823
824 if (is_weight or is_weight_scale) and (is_k_proj or is_q_proj):
825 original_ndim = loaded_weight.ndim
826 if original_ndim == 1:
827 loaded_weight = loaded_weight.unsqueeze(-1)
828
829 f_out, f_in = loaded_weight.shape
830 n_heads = (
831 self.config.num_key_value_heads
832 if is_k_proj
833 else self.config.num_attention_heads
834 )
835 loaded_weight = (
836 loaded_weight.view(n_heads, f_out // n_heads // 2, 2, f_in)
837 .transpose(1, 2)
838 .reshape(f_out, f_in)
839 )
840
841 if original_ndim == 1:
842 loaded_weight = loaded_weight.squeeze(-1)
843
844 return name, loaded_weight