main
py 903 lines 32.7 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import torch
5
6 from vllm.logger import init_logger
7 from vllm.model_executor.layers.attention import Attention
8 from vllm.model_executor.layers.fused_moe import (
9 FusedMoEConfig,
10 FusedMoEMethodBase,
11 FusedMoEParallelConfig,
12 FusedMoEQuantConfig,
13 RoutedExperts,
14 SharedExperts,
15 )
16 from vllm.model_executor.layers.fused_moe import modular_kernel as mk
17 from vllm.model_executor.layers.fused_moe.config import (
18 mxfp4_w4a16_moe_quant_config,
19 )
20 from vllm.model_executor.layers.fused_moe.moe_output import UnfinalizedMoEOutput
21 from vllm.model_executor.layers.fused_moe.oracle.mxfp4 import (
22 TRITON_BACKENDS,
23 Mxfp4MoeBackend,
24 convert_gpt_oss_weight_to_mxfp4_moe_kernel_format,
25 convert_weight_to_mxfp4_moe_kernel_format,
26 make_mxfp4_moe_kernel,
27 make_mxfp4_moe_quant_config,
28 mxfp4_round_up_hidden_size_and_intermediate_size,
29 select_deepseek_v4_mxfp4_moe_backend,
30 select_mxfp4_moe_backend,
31 )
32 from vllm.model_executor.layers.linear import LinearBase, UnquantizedLinearMethod
33 from vllm.model_executor.layers.quantization import QuantizationMethods
34 from vllm.model_executor.layers.quantization.base_config import (
35 QuantizationConfig,
36 QuantizeMethodBase,
37 )
38 from vllm.model_executor.layers.quantization.utils.quant_utils import is_layer_skipped
39 from vllm.model_executor.utils import (
40 is_weights_pre_processed,
41 replace_parameter,
42 set_weight_attrs,
43 )
44 from vllm.platforms import current_platform
45
46 logger = init_logger(__name__)
47
48
49 class Mxfp4Config(QuantizationConfig):
50 """Canonical base config for MXFP4 quantization.
51
52 Subclasses override get_name() and override_quantization_method() to
53 register themselves as the handler for a specific checkpoint format.
54 """
55
56 def __init__(self, ignored_layers: list[str] | None = None):
57 super().__init__()
58 self.ignored_layers = ignored_layers
59
60 @classmethod
61 def from_config(cls, config):
62 return cls()
63
64 @classmethod
65 def get_min_capability(cls) -> int:
66 return 80
67
68 @classmethod
69 def get_name(cls) -> QuantizationMethods:
70 return "mxfp4"
71
72 @classmethod
73 def get_supported_act_dtypes(cls) -> list[torch.dtype]:
74 return [torch.bfloat16]
75
76 @classmethod
77 def get_config_filenames(cls) -> list[str]:
78 return []
79
80 def _make_moe_method(self, moe: FusedMoEConfig) -> FusedMoEMethodBase:
81 """MoE method for RoutedExperts. Subclasses override to pick a
82 checkpoint-specific kernel family."""
83 return Mxfp4MoEMethod(moe)
84
85 def get_quant_method(
86 self, layer: torch.nn.Module, prefix: str
87 ) -> "QuantizeMethodBase | None":
88 if isinstance(layer, LinearBase):
89 if self.ignored_layers and is_layer_skipped(
90 prefix=prefix,
91 ignored_layers=self.ignored_layers,
92 fused_mapping=self.packed_modules_mapping,
93 ):
94 return UnquantizedLinearMethod()
95 logger.debug_once(
96 "MXFP4 linear layer is not implemented - falling back to "
97 "UnquantizedLinearMethod.",
98 )
99 return UnquantizedLinearMethod()
100 elif isinstance(layer, RoutedExperts):
101 return self._make_moe_method(layer.moe_config)
102 elif isinstance(layer, Attention):
103 logger.debug_once(
104 "MXFP4 attention layer is not implemented. "
105 "Skipping quantization for this layer.",
106 )
107 return None
108
109
110 class GptOssMxfp4Config(Mxfp4Config):
111 """MXFP4 config for GPT-OSS checkpoints.
112
113 Checkpoints carry ``"quant_method": "mxfp4"`` in their JSON config.
114 override_quantization_method() maps that to the canonical internal name
115 so that the rest of the loading path uses "gpt_oss_mxfp4" consistently.
116 """
117
118 @classmethod
119 def get_name(cls) -> QuantizationMethods:
120 return "gpt_oss_mxfp4"
121
122 @classmethod
123 def override_quantization_method(
124 cls, hf_quant_cfg, user_quant, hf_config=None
125 ) -> QuantizationMethods | None:
126 # Match both "mxfp4" (original checkpoint value) and "gpt_oss_mxfp4"
127 # (already normalized by verify_and_update_model_config) so that
128 # explicit --quantization mxfp4 from the user doesn't cause a mismatch.
129 if not (
130 isinstance(hf_quant_cfg, dict)
131 and hf_quant_cfg.get("quant_method") in ("mxfp4", "gpt_oss_mxfp4")
132 ):
133 return None
134 # Require explicit confirmation that this is a GPT-OSS model.
135 # Do NOT fall back to returning the override when hf_config is None,
136 # as that would silently claim all mxfp4 checkpoints.
137 model_type = getattr(hf_config, "model_type", None)
138 if model_type != "gpt_oss":
139 return None
140 return "gpt_oss_mxfp4"
141
142 def _make_moe_method(self, moe: FusedMoEConfig) -> FusedMoEMethodBase:
143 return GptOssMxfp4MoEMethod(moe)
144
145
146 class GptOssMxfp4MoEMethod(FusedMoEMethodBase):
147 """MXFP4 MoE quantization method."""
148
149 def __init__(self, moe: FusedMoEConfig):
150 super().__init__(moe)
151 self.weight_dtype = "gpt_oss_mxfp4"
152 self.mxfp4_backend, self.experts_cls = select_mxfp4_moe_backend(moe)
153
154 self.max_capture_size = moe.max_capture_size
155
156 self._cache_permute_indices: dict[torch.Size, torch.Tensor] = {}
157 self.moe_kernel: mk.FusedMoEKernel | None = None
158
159 # Used for triton kernel precision configs
160 self.w13_precision_config = None
161 self.w2_precision_config = None
162
163 @property
164 def skip_forward_padding(self) -> bool:
165 # SM100_FI_MXFP4_MXFP8_TRTLLM supports padding with mxfp8 quant
166 # so can skip the padding in the forward before applying the moe method
167 return self.mxfp4_backend == Mxfp4MoeBackend.FLASHINFER_TRTLLM_MXFP4_MXFP8
168
169 # TODO(bnell): move to MK/expert_class?
170 @property
171 def has_unpadded_output(self) -> bool:
172 return self.mxfp4_backend in [
173 Mxfp4MoeBackend.FLASHINFER_TRTLLM_MXFP4_MXFP8,
174 Mxfp4MoeBackend.FLASHINFER_TRTLLM_MXFP4_BF16,
175 ]
176
177 def maybe_roundup_sizes(
178 self,
179 hidden_size: int,
180 intermediate_size_per_partition: int,
181 act_dtype: torch.dtype,
182 moe_parallel_config: FusedMoEParallelConfig,
183 ) -> tuple[int, int]:
184 hidden_size, intermediate_size_per_partition = super().maybe_roundup_sizes(
185 hidden_size=hidden_size,
186 intermediate_size_per_partition=intermediate_size_per_partition,
187 act_dtype=act_dtype,
188 moe_parallel_config=moe_parallel_config,
189 )
190 return mxfp4_round_up_hidden_size_and_intermediate_size(
191 self.mxfp4_backend, hidden_size, intermediate_size_per_partition
192 )
193
194 def create_weights(
195 self,
196 layer: RoutedExperts,
197 num_experts: int,
198 hidden_size: int,
199 intermediate_size_per_partition: int,
200 params_dtype: torch.dtype,
201 **extra_weight_attrs,
202 ):
203 self.num_experts = num_experts
204 weight_dtype = torch.uint8
205 scale_dtype = torch.uint8
206 mxfp4_block = 32
207
208 layer.params_dtype = params_dtype
209 layer.num_experts = num_experts
210 self.intermediate_size = intermediate_size_per_partition
211 self.hidden_size = hidden_size
212
213 # Fused gate_up_proj (column parallel)
214 w13_weight = torch.nn.Parameter(
215 torch.zeros(
216 num_experts,
217 self.moe.w13_num_shards * intermediate_size_per_partition,
218 hidden_size // 2,
219 dtype=weight_dtype,
220 ),
221 requires_grad=False,
222 )
223 layer.register_parameter("w13_weight", w13_weight)
224 set_weight_attrs(w13_weight, extra_weight_attrs)
225
226 w13_weight_scale = torch.nn.Parameter(
227 torch.zeros(
228 num_experts,
229 self.moe.w13_num_shards * intermediate_size_per_partition,
230 hidden_size // mxfp4_block,
231 dtype=scale_dtype,
232 ),
233 requires_grad=False,
234 )
235 layer.register_parameter("w13_weight_scale", w13_weight_scale)
236 set_weight_attrs(w13_weight_scale, extra_weight_attrs)
237 w13_weight_scale.quant_method = "block"
238
239 # down_proj (row parallel)
240 w2_weight = torch.nn.Parameter(
241 torch.zeros(
242 num_experts,
243 hidden_size,
244 intermediate_size_per_partition // 2,
245 dtype=weight_dtype,
246 ),
247 requires_grad=False,
248 )
249 layer.register_parameter("w2_weight", w2_weight)
250 set_weight_attrs(w2_weight, extra_weight_attrs)
251
252 w2_weight_scale = torch.nn.Parameter(
253 torch.zeros(
254 num_experts,
255 hidden_size,
256 intermediate_size_per_partition // mxfp4_block,
257 dtype=scale_dtype,
258 ),
259 requires_grad=False,
260 )
261 layer.register_parameter("w2_weight_scale", w2_weight_scale)
262 set_weight_attrs(w2_weight_scale, extra_weight_attrs)
263 w2_weight_scale.quant_method = "block"
264
265 if self.moe.has_bias:
266 w13_bias = torch.nn.Parameter(
267 torch.zeros(
268 num_experts,
269 self.moe.w13_num_shards * intermediate_size_per_partition,
270 dtype=torch.bfloat16,
271 ),
272 requires_grad=False,
273 )
274 layer.register_parameter("w13_bias", w13_bias)
275 set_weight_attrs(w13_bias, extra_weight_attrs)
276
277 w2_bias = torch.nn.Parameter(
278 torch.zeros(
279 num_experts,
280 hidden_size,
281 dtype=torch.bfloat16,
282 ),
283 requires_grad=False,
284 )
285 layer.register_parameter("w2_bias", w2_bias)
286 set_weight_attrs(w2_bias, extra_weight_attrs)
287
288 def _setup_kernel(
289 self,
290 layer: RoutedExperts,
291 w13: torch.Tensor,
292 w2: torch.Tensor,
293 w13_scale: torch.Tensor,
294 w2_scale: torch.Tensor,
295 w13_bias: torch.Tensor | None = None,
296 w2_bias: torch.Tensor | None = None,
297 ) -> None:
298 num_experts = self.num_experts
299 intermediate_size = self.intermediate_size
300 hidden_size = self.hidden_size
301 sf_block_size = 32
302
303 # Shape assertions
304 assert (
305 w13.dim() == 3
306 and w13.shape[0] == num_experts
307 and w13.shape[1] == intermediate_size * self.moe.w13_num_shards
308 and w13.shape[2] == hidden_size // 2
309 )
310 assert (
311 w13_scale.dim() == 3
312 and w13_scale.shape[0] == num_experts
313 and w13_scale.shape[1] == intermediate_size * self.moe.w13_num_shards
314 and w13_scale.shape[2] == hidden_size // sf_block_size
315 )
316 assert (
317 w2.dim() == 3
318 and w2.shape[0] == num_experts
319 and w2.shape[1] == hidden_size
320 and w2.shape[2] == intermediate_size // 2
321 )
322 assert (
323 w2_scale.dim() == 3
324 and w2_scale.shape[1] == hidden_size
325 and w2_scale.shape[2] == intermediate_size // sf_block_size
326 )
327 if w13_bias is not None:
328 assert (
329 w13_bias.dim() == 2
330 and w13_bias.shape[0] == num_experts
331 and w13_bias.shape[1] == intermediate_size * self.moe.w13_num_shards
332 )
333 if w2_bias is not None:
334 assert (
335 w2_bias.dim() == 2
336 and w2_bias.shape[0] == num_experts
337 and w2_bias.shape[1] == hidden_size
338 )
339
340 # Convert weights to kernel format
341 w13, w2, w13_scale, w2_scale, w13_bias, w2_bias = (
342 convert_gpt_oss_weight_to_mxfp4_moe_kernel_format(
343 mxfp4_backend=self.mxfp4_backend,
344 layer=layer,
345 w13_weight=w13,
346 w2_weight=w2,
347 w13_weight_scale=w13_scale,
348 w2_weight_scale=w2_scale,
349 w13_bias=w13_bias,
350 w2_bias=w2_bias,
351 _cache_permute_indices=self._cache_permute_indices,
352 )
353 )
354
355 # For TRITON backends, weights are wrapped tensors from triton_kernels
356 # that don't support .detach(). Manually assign parameters.
357 if self.mxfp4_backend not in TRITON_BACKENDS:
358 replace_parameter(layer, "w13_weight", w13)
359 replace_parameter(layer, "w2_weight", w2)
360 replace_parameter(layer, "w13_weight_scale", w13_scale)
361 replace_parameter(layer, "w2_weight_scale", w2_scale)
362 else:
363 layer.w13_weight = w13
364 layer.w2_weight = w2
365 self.w13_precision_config = w13_scale
366 self.w2_precision_config = w2_scale
367
368 if w13_bias is not None and w2_bias is not None:
369 replace_parameter(layer, "w13_bias", w13_bias)
370 replace_parameter(layer, "w2_bias", w2_bias)
371
372 # Build quant config
373 self.moe_quant_config = self.get_fused_moe_quant_config(layer)
374
375 # Build kernel (modular or monolithic)
376 if self.moe_quant_config is not None and self.experts_cls is not None:
377 self.moe_kernel = make_mxfp4_moe_kernel(
378 moe_quant_config=self.moe_quant_config,
379 moe_config=self.moe,
380 mxfp4_backend=self.mxfp4_backend,
381 experts_cls=self.experts_cls,
382 routing_tables=layer._expert_routing_tables(),
383 )
384 self.moe_kernel.fused_experts.process_weights_after_loading(layer)
385
386 def process_weights_after_loading(self, layer: RoutedExperts) -> None:
387 w13 = layer.w13_weight
388 w2 = layer.w2_weight
389 w13_scale = layer.w13_weight_scale
390 w2_scale = layer.w2_weight_scale
391 w13_bias = getattr(layer, "w13_bias", None)
392 w2_bias = getattr(layer, "w2_bias", None)
393
394 if self.mxfp4_backend == Mxfp4MoeBackend.NONE:
395 return
396
397 self._setup_kernel(layer, w13, w2, w13_scale, w2_scale, w13_bias, w2_bias)
398
399 def get_fused_moe_quant_config(
400 self, layer: RoutedExperts
401 ) -> FusedMoEQuantConfig | None:
402 w1_bias = getattr(layer, "w13_bias", None)
403 w2_bias = getattr(layer, "w2_bias", None)
404
405 if self.mxfp4_backend in TRITON_BACKENDS:
406 # TRITON backends free w13/w2_weight_scale after swizzling; the
407 # swizzled scales live inside the precision configs instead.
408 assert self.w13_precision_config is not None
409 assert self.w2_precision_config is not None
410 w1_scale = self.w13_precision_config
411 w2_scale = self.w2_precision_config
412 else:
413 w1_scale = layer.w13_weight_scale
414 w2_scale = layer.w2_weight_scale
415
416 return make_mxfp4_moe_quant_config(
417 mxfp4_backend=self.mxfp4_backend,
418 w1_scale=w1_scale,
419 w2_scale=w2_scale,
420 w1_bias=w1_bias,
421 w2_bias=w2_bias,
422 gemm1_alpha=1.702,
423 gemm1_beta=1.0,
424 swiglu_limit=7.0,
425 layer=layer,
426 )
427
428 def apply(
429 self,
430 layer: RoutedExperts,
431 x: torch.Tensor,
432 topk_weights: torch.Tensor,
433 topk_ids: torch.Tensor,
434 shared_experts: SharedExperts | None,
435 shared_experts_input: torch.Tensor | None,
436 ) -> torch.Tensor | UnfinalizedMoEOutput:
437 assert not self.is_monolithic
438 assert self.moe_kernel is not None
439 return self.moe_kernel.apply(
440 hidden_states=x,
441 w1=layer.w13_weight,
442 w2=layer.w2_weight,
443 topk_weights=topk_weights,
444 topk_ids=topk_ids,
445 activation=layer.activation,
446 global_num_experts=layer.global_num_experts,
447 apply_router_weight_on_input=layer.apply_router_weight_on_input,
448 expert_map=layer.expert_map,
449 shared_experts=shared_experts,
450 shared_experts_input=shared_experts_input,
451 )
452
453 def apply_monolithic(
454 self,
455 layer: RoutedExperts,
456 x: torch.Tensor,
457 router_logits: torch.Tensor,
458 input_ids: torch.Tensor | None = None,
459 ) -> torch.Tensor | UnfinalizedMoEOutput:
460 assert self.is_monolithic
461 assert self.moe_kernel is not None
462 return self.moe_kernel.apply_monolithic(
463 hidden_states=x,
464 w1=layer.w13_weight,
465 w2=layer.w2_weight,
466 router_logits=router_logits,
467 activation=layer.activation,
468 global_num_experts=layer.global_num_experts,
469 expert_map=layer.expert_map,
470 apply_router_weight_on_input=layer.apply_router_weight_on_input,
471 num_expert_group=layer.num_expert_group,
472 topk_group=layer.topk_group,
473 e_score_correction_bias=layer.e_score_correction_bias,
474 routed_scaling_factor=layer.routed_scaling_factor,
475 )
476
477
478 class Mxfp4MoEMethod(FusedMoEMethodBase):
479 """MXFP4 MoE quantization method."""
480
481 supports_pre_processed_weights = True
482
483 def __init__(self, moe: FusedMoEConfig):
484 super().__init__(moe)
485
486 self.weight_dtype = "mxfp4"
487 self.mxfp4_backend, self.experts_cls = select_deepseek_v4_mxfp4_moe_backend(moe)
488
489 self.max_capture_size = moe.max_capture_size
490
491 self._cache_permute_indices: dict[torch.Size, torch.Tensor] = {}
492 self.moe_kernel: mk.FusedMoEKernel | None = None
493
494 # Used for triton kernel precision configs
495 self.w13_precision_config = None
496 self.w2_precision_config = None
497
498 @property
499 def supports_eplb(self) -> bool:
500 return True
501
502 @property
503 def skip_forward_padding(self) -> bool:
504 # SM100_FI_MXFP4_MXFP8_TRTLLM supports padding with mxfp8 quant
505 # so can skip the padding in the forward before applying the moe method
506 return self.mxfp4_backend == Mxfp4MoeBackend.FLASHINFER_TRTLLM_MXFP4_MXFP8
507
508 # TODO(bnell): move to MK/expert_class?
509 @property
510 def has_unpadded_output(self) -> bool:
511 return self.mxfp4_backend in [
512 Mxfp4MoeBackend.FLASHINFER_TRTLLM_MXFP4_MXFP8,
513 Mxfp4MoeBackend.FLASHINFER_TRTLLM_MXFP4_BF16,
514 ]
515
516 def maybe_roundup_sizes(
517 self,
518 hidden_size: int,
519 intermediate_size_per_partition: int,
520 act_dtype: torch.dtype,
521 moe_parallel_config: FusedMoEParallelConfig,
522 ) -> tuple[int, int]:
523 hidden_size, intermediate_size_per_partition = super().maybe_roundup_sizes(
524 hidden_size=hidden_size,
525 intermediate_size_per_partition=intermediate_size_per_partition,
526 act_dtype=act_dtype,
527 moe_parallel_config=moe_parallel_config,
528 )
529 return mxfp4_round_up_hidden_size_and_intermediate_size(
530 self.mxfp4_backend,
531 hidden_size,
532 intermediate_size_per_partition,
533 activation=self.moe.activation,
534 )
535
536 @staticmethod
537 def _encode_mxfp4_weight_scale(loaded_weight: torch.Tensor) -> torch.Tensor:
538 if loaded_weight.dtype == torch.uint8:
539 return loaded_weight
540 if loaded_weight.dtype == torch.float8_e8m0fnu:
541 return loaded_weight.view(torch.uint8)
542 if loaded_weight.is_floating_point():
543 return loaded_weight.to(torch.float8_e8m0fnu).view(torch.uint8)
544 return loaded_weight
545
546 @staticmethod
547 def get_scale_weight_loader(weight_loader):
548 def mxfp4_weight_loader(
549 param: torch.nn.Parameter,
550 loaded_weight: torch.Tensor,
551 weight_name: str,
552 shard_id: str,
553 expert_id: int,
554 return_success: bool = False,
555 ) -> bool | None:
556 loaded_weight = Mxfp4MoEMethod._encode_mxfp4_weight_scale(loaded_weight)
557 return weight_loader(
558 param,
559 loaded_weight,
560 weight_name,
561 shard_id,
562 expert_id,
563 return_success=return_success,
564 )
565
566 return mxfp4_weight_loader
567
568 def create_weights(
569 self,
570 layer: RoutedExperts,
571 num_experts: int,
572 hidden_size: int,
573 intermediate_size_per_partition: int,
574 params_dtype: torch.dtype,
575 **extra_weight_attrs,
576 ):
577 self.num_experts = num_experts
578 weight_dtype = torch.uint8
579 scale_dtype = torch.uint8
580 mxfp4_block = 32
581
582 layer.params_dtype = params_dtype
583 layer.num_experts = num_experts
584 self.intermediate_size = intermediate_size_per_partition
585 self.hidden_size = hidden_size
586 weight_loader = extra_weight_attrs.pop("weight_loader")
587 scale_weight_loader = Mxfp4MoEMethod.get_scale_weight_loader(weight_loader)
588
589 # Fused gate_up_proj (column parallel)
590 w13_weight = torch.nn.Parameter(
591 torch.zeros(
592 num_experts,
593 self.moe.w13_num_shards * intermediate_size_per_partition,
594 hidden_size // 2,
595 dtype=weight_dtype,
596 ),
597 requires_grad=False,
598 )
599 layer.register_parameter("w13_weight", w13_weight)
600 set_weight_attrs(w13_weight, extra_weight_attrs)
601 set_weight_attrs(w13_weight, {"weight_loader": weight_loader})
602
603 w13_weight_scale = torch.nn.Parameter(
604 torch.zeros(
605 num_experts,
606 self.moe.w13_num_shards * intermediate_size_per_partition,
607 hidden_size // mxfp4_block,
608 dtype=scale_dtype,
609 ),
610 requires_grad=False,
611 )
612 layer.register_parameter("w13_weight_scale", w13_weight_scale)
613 set_weight_attrs(w13_weight_scale, extra_weight_attrs)
614 set_weight_attrs(w13_weight_scale, {"weight_loader": scale_weight_loader})
615 w13_weight_scale.quant_method = "block"
616
617 # down_proj (row parallel)
618 w2_weight = torch.nn.Parameter(
619 torch.zeros(
620 num_experts,
621 hidden_size,
622 intermediate_size_per_partition // 2,
623 dtype=weight_dtype,
624 ),
625 requires_grad=False,
626 )
627 layer.register_parameter("w2_weight", w2_weight)
628 set_weight_attrs(w2_weight, extra_weight_attrs)
629 set_weight_attrs(w2_weight, {"weight_loader": weight_loader})
630
631 w2_weight_scale = torch.nn.Parameter(
632 torch.zeros(
633 num_experts,
634 hidden_size,
635 intermediate_size_per_partition // mxfp4_block,
636 dtype=scale_dtype,
637 ),
638 requires_grad=False,
639 )
640 layer.register_parameter("w2_weight_scale", w2_weight_scale)
641 set_weight_attrs(w2_weight_scale, extra_weight_attrs)
642 set_weight_attrs(w2_weight_scale, {"weight_loader": scale_weight_loader})
643 w2_weight_scale.quant_method = "block"
644
645 if self.moe.has_bias:
646 w13_bias = torch.nn.Parameter(
647 torch.zeros(
648 num_experts,
649 self.moe.w13_num_shards * intermediate_size_per_partition,
650 dtype=torch.bfloat16,
651 ),
652 requires_grad=False,
653 )
654 layer.register_parameter("w13_bias", w13_bias)
655 set_weight_attrs(w13_bias, extra_weight_attrs)
656 set_weight_attrs(w13_bias, {"weight_loader": weight_loader})
657
658 w2_bias = torch.nn.Parameter(
659 torch.zeros(
660 num_experts,
661 hidden_size,
662 dtype=torch.bfloat16,
663 ),
664 requires_grad=False,
665 )
666 layer.register_parameter("w2_bias", w2_bias)
667 set_weight_attrs(w2_bias, extra_weight_attrs)
668 set_weight_attrs(w2_bias, {"weight_loader": weight_loader})
669
670 def _setup_kernel(
671 self,
672 layer: RoutedExperts,
673 w13: torch.Tensor,
674 w2: torch.Tensor,
675 w13_scale: torch.Tensor,
676 w2_scale: torch.Tensor,
677 w13_bias: torch.Tensor | None = None,
678 w2_bias: torch.Tensor | None = None,
679 ) -> None:
680 num_experts = self.num_experts
681 intermediate_size = self.intermediate_size
682 hidden_size = self.hidden_size
683 sf_block_size = 32
684
685 # Shape assertions — skipped for SITU since its kernel handles native
686 # (non-256-aligned) intermediate sizes without prior round-up.
687 from vllm.model_executor.layers.fused_moe.activation import MoEActivation
688
689 if self.moe.activation != MoEActivation.SITU:
690 assert (
691 w13.dim() == 3
692 and w13.shape[0] == num_experts
693 and w13.shape[1] == intermediate_size * self.moe.w13_num_shards
694 and w13.shape[2] == hidden_size // 2
695 )
696 assert (
697 w13_scale.dim() == 3
698 and w13_scale.shape[0] == num_experts
699 and w13_scale.shape[1] == intermediate_size * self.moe.w13_num_shards
700 and w13_scale.shape[2] == hidden_size // sf_block_size
701 )
702 assert (
703 w2.dim() == 3
704 and w2.shape[0] == num_experts
705 and w2.shape[1] == hidden_size
706 and w2.shape[2] == intermediate_size // 2
707 )
708 assert (
709 w2_scale.dim() == 3
710 and w2_scale.shape[1] == hidden_size
711 and w2_scale.shape[2] == intermediate_size // sf_block_size
712 )
713 if w13_bias is not None:
714 assert (
715 w13_bias.dim() == 2
716 and w13_bias.shape[0] == num_experts
717 and w13_bias.shape[1] == intermediate_size * self.moe.w13_num_shards
718 )
719 if w2_bias is not None:
720 assert (
721 w2_bias.dim() == 2
722 and w2_bias.shape[0] == num_experts
723 and w2_bias.shape[1] == hidden_size
724 )
725
726 # Convert weights to kernel format
727 w13, w2, w13_scale, w2_scale, w13_bias, w2_bias = (
728 convert_weight_to_mxfp4_moe_kernel_format(
729 mxfp4_backend=self.mxfp4_backend,
730 layer=layer,
731 w13_weight=w13,
732 w2_weight=w2,
733 w13_weight_scale=w13_scale,
734 w2_weight_scale=w2_scale,
735 w13_bias=w13_bias,
736 w2_bias=w2_bias,
737 _cache_permute_indices=self._cache_permute_indices,
738 activation=self.moe.activation,
739 use_separated_a4w4=self.moe.use_mxfp4_w4a4_dsv4,
740 )
741 )
742
743 # For TRITON backends, weights are wrapped tensors from triton_kernels
744 # that don't support .detach(). Manually assign parameters.
745 is_gfx1250 = False
746 if current_platform.is_rocm():
747 from vllm.platforms.rocm import on_gfx1250
748
749 is_gfx1250 = on_gfx1250()
750
751 uses_triton_weight_format = self.mxfp4_backend in TRITON_BACKENDS or (
752 self.mxfp4_backend == Mxfp4MoeBackend.AITER_MXFP4_BF16 and is_gfx1250
753 )
754 if not uses_triton_weight_format:
755 replace_parameter(layer, "w13_weight", w13)
756 replace_parameter(layer, "w2_weight", w2)
757 replace_parameter(layer, "w13_weight_scale", w13_scale)
758 replace_parameter(layer, "w2_weight_scale", w2_scale)
759 else:
760 layer.w13_weight = w13
761 layer.w2_weight = w2
762 self.w13_precision_config = w13_scale
763 self.w2_precision_config = w2_scale
764
765 if w13_bias is not None and w2_bias is not None:
766 replace_parameter(layer, "w13_bias", w13_bias)
767 replace_parameter(layer, "w2_bias", w2_bias)
768
769 # Build quant config
770 self._build_moe_kernel(layer)
771
772 def _build_moe_kernel(self, layer: RoutedExperts) -> None:
773 """Build the modular MoE kernel from the (already in-format) weights."""
774 self.moe_quant_config = self.get_fused_moe_quant_config(layer)
775 if self.moe_quant_config is not None and self.experts_cls is not None:
776 self.moe_kernel = make_mxfp4_moe_kernel(
777 moe_quant_config=self.moe_quant_config,
778 moe_config=self.moe,
779 mxfp4_backend=self.mxfp4_backend,
780 experts_cls=self.experts_cls,
781 routing_tables=layer._expert_routing_tables(),
782 )
783 self.moe_kernel.fused_experts.process_weights_after_loading(layer)
784
785 def process_weights_after_loading(self, layer):
786 if self.mxfp4_backend == Mxfp4MoeBackend.NONE:
787 return
788
789 if is_weights_pre_processed():
790 if self.mxfp4_backend != Mxfp4MoeBackend.FLASHINFER_TRTLLM_MXFP4_MXFP8:
791 raise RuntimeError(
792 "pre-processed weights require FLASHINFER_TRTLLM_MXFP4_MXFP8 "
793 f"moe backend, got {self.mxfp4_backend}"
794 )
795 self._build_moe_kernel(layer)
796 return
797
798 w13 = layer.w13_weight
799 w2 = layer.w2_weight
800 w13_scale = layer.w13_weight_scale
801 w2_scale = layer.w2_weight_scale
802 w13_bias = getattr(layer, "w13_bias", None)
803 w2_bias = getattr(layer, "w2_bias", None)
804
805 self._setup_kernel(layer, w13, w2, w13_scale, w2_scale, w13_bias, w2_bias)
806
807 def get_fused_moe_quant_config(
808 self,
809 layer: RoutedExperts,
810 ) -> FusedMoEQuantConfig | None:
811 w1_bias = getattr(layer, "w13_bias", None)
812 w2_bias = getattr(layer, "w2_bias", None)
813 swiglu_limit = getattr(layer, "swiglu_limit", None)
814
815 is_gfx1250 = False
816 if current_platform.is_rocm():
817 from vllm.platforms.rocm import on_gfx1250
818
819 is_gfx1250 = on_gfx1250()
820
821 if self.mxfp4_backend in TRITON_BACKENDS or (
822 self.mxfp4_backend == Mxfp4MoeBackend.AITER_MXFP4_BF16 and is_gfx1250
823 ):
824 # TRITON backends free w13/w2_weight_scale after swizzling; the
825 # swizzled scales live inside the precision configs instead.
826 assert self.w13_precision_config is not None
827 assert self.w2_precision_config is not None
828 w1_scale = self.w13_precision_config
829 w2_scale = self.w2_precision_config
830 else:
831 w1_scale = layer.w13_weight_scale
832 w2_scale = layer.w2_weight_scale
833
834 if self.mxfp4_backend == Mxfp4MoeBackend.EMULATION:
835 # Canonical ``mxfp4`` checkpoints are weight-only W4A16. The
836 # generic EMULATION config is W4A4, so preserve BF16 activations
837 # while the fallback dequantizes only the weights.
838 return mxfp4_w4a16_moe_quant_config(
839 w1_scale=w1_scale,
840 w2_scale=w2_scale,
841 w1_bias=w1_bias,
842 w2_bias=w2_bias,
843 gemm1_clamp_limit=swiglu_limit,
844 )
845
846 return make_mxfp4_moe_quant_config(
847 mxfp4_backend=self.mxfp4_backend,
848 w1_scale=w1_scale,
849 w2_scale=w2_scale,
850 w1_bias=w1_bias,
851 w2_bias=w2_bias,
852 swiglu_limit=swiglu_limit,
853 layer=layer,
854 )
855
856 def apply(
857 self,
858 layer: RoutedExperts,
859 x: torch.Tensor,
860 topk_weights: torch.Tensor,
861 topk_ids: torch.Tensor,
862 shared_experts: SharedExperts | None,
863 shared_experts_input: torch.Tensor | None,
864 ) -> torch.Tensor | UnfinalizedMoEOutput:
865 assert not self.is_monolithic
866 assert self.moe_kernel is not None
867 return self.moe_kernel.apply(
868 hidden_states=x,
869 w1=layer.w13_weight,
870 w2=layer.w2_weight,
871 topk_weights=topk_weights,
872 topk_ids=topk_ids,
873 activation=layer.activation,
874 global_num_experts=layer.global_num_experts,
875 apply_router_weight_on_input=layer.apply_router_weight_on_input,
876 expert_map=layer.expert_map,
877 shared_experts=shared_experts,
878 shared_experts_input=shared_experts_input,
879 )
880
881 def apply_monolithic(
882 self,
883 layer: RoutedExperts,
884 x: torch.Tensor,
885 router_logits: torch.Tensor,
886 input_ids: torch.Tensor | None = None,
887 ) -> torch.Tensor | UnfinalizedMoEOutput:
888 assert self.is_monolithic
889 assert self.moe_kernel is not None
890 return self.moe_kernel.apply_monolithic(
891 hidden_states=x,
892 w1=layer.w13_weight,
893 w2=layer.w2_weight,
894 router_logits=router_logits,
895 activation=layer.activation,
896 global_num_experts=layer.global_num_experts,
897 expert_map=layer.expert_map,
898 apply_router_weight_on_input=layer.apply_router_weight_on_input,
899 num_expert_group=layer.num_expert_group,
900 topk_group=layer.topk_group,
901 e_score_correction_bias=layer.e_score_correction_bias,
902 routed_scaling_factor=layer.routed_scaling_factor,
903 )