main
py 770 lines 26.4 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """b12x modular tensor-parallel fused MoE backend."""
4
5 from collections.abc import Iterable
6 from typing import Any
7
8 import torch
9
10 import vllm.model_executor.layers.fused_moe.modular_kernel as mk
11 from vllm.logger import init_logger
12 from vllm.model_executor.layers.fused_moe.activation import MoEActivation
13 from vllm.model_executor.layers.fused_moe.config import (
14 FusedMoEParallelConfig,
15 FusedMoEQuantConfig,
16 )
17 from vllm.model_executor.layers.fused_moe.topk_weight_and_reduce import (
18 TopKWeightAndReduceNoOP,
19 )
20 from vllm.model_executor.layers.quantization.utils.quant_utils import (
21 QuantKey,
22 kMxfp4Static,
23 kMxfp8Dynamic,
24 kNvfp4Dynamic,
25 kNvfp4Static,
26 )
27 from vllm.model_executor.utils import replace_parameter
28 from vllm.platforms import current_platform
29 from vllm.utils.b12x import (
30 B12xWarmupUnit,
31 get_b12x_fused_moe,
32 reuse_packed_weight_storage,
33 )
34
35 logger = init_logger(__name__)
36
37 _B12X_MOE_MODES: dict[
38 tuple[torch.dtype | str | None, torch.dtype | str | None],
39 tuple[str, str, str],
40 ] = {
41 ("mxfp4", "mxfp8"): ("w4a8_mx", "fp4_e8m0_k32", "w31"),
42 ("mxfp4", None): ("w4a16", "fp4_e8m0_k32", "w31"),
43 ("nvfp4", "nvfp4"): ("nvfp4", "modelopt_nvfp4", "w31"),
44 ("nvfp4", "mxfp8"): ("w4a8_nvfp4", "modelopt_nvfp4", "w31"),
45 ("nvfp4", None): ("w4a16", "modelopt_nvfp4", "w13"),
46 }
47
48
49 def _require_b12x_fused_moe() -> Any:
50 fused_moe = get_b12x_fused_moe()
51 assert fused_moe is not None
52 return fused_moe
53
54
55 def _b12x_activation_name(activation: MoEActivation) -> str:
56 if activation == MoEActivation.SILU:
57 return "silu"
58 if activation in (MoEActivation.RELU2, MoEActivation.RELU2_NO_MUL):
59 return "relu2"
60 return activation.value
61
62
63 def _b12x_scratch_nbytes(plan: Any) -> int:
64 specs = plan.scratch_specs()
65 if len(specs) != 1:
66 raise RuntimeError(f"expected one b12x MoE scratch buffer, got {len(specs)}")
67 spec = specs[0]
68 if spec.dtype != torch.uint8:
69 raise TypeError(f"expected b12x MoE scratch dtype uint8, got {spec.dtype}")
70 return int(spec.shape[0])
71
72
73 def _b12x_moe_execution_plan(
74 *,
75 tokens: int,
76 topk: int,
77 prepared: Any,
78 quant_mode: str,
79 apply_router_weight_on_input: bool,
80 swiglu_limit: float | None,
81 swiglu_alpha: float | None,
82 swiglu_beta: float | None,
83 ) -> Any:
84 fused_moe = _require_b12x_fused_moe()
85
86 return fused_moe.plan_execution(
87 num_tokens=max(int(tokens), 1),
88 num_topk=int(topk),
89 device=prepared.w1_fp4.device,
90 weight_plan=prepared.plan,
91 quant_mode=quant_mode,
92 apply_router_weight_on_input=apply_router_weight_on_input,
93 swiglu_limit=swiglu_limit,
94 swiglu_alpha=swiglu_alpha,
95 swiglu_beta=swiglu_beta,
96 )
97
98
99 def _run_b12x_moe_plan(
100 *,
101 plan: Any,
102 scratch: torch.Tensor,
103 hidden_states: torch.Tensor,
104 prepared: Any,
105 topk_weights: torch.Tensor,
106 topk_ids: torch.Tensor,
107 output: torch.Tensor,
108 unit_scale_contract: bool,
109 ) -> None:
110 fused_moe = _require_b12x_fused_moe()
111
112 binding = fused_moe.bind(
113 plan,
114 scratch=scratch,
115 a=hidden_states,
116 experts=prepared,
117 topk_weights=topk_weights,
118 topk_ids=topk_ids,
119 output=output,
120 input_scales_static=True,
121 unit_scale_contract=unit_scale_contract,
122 )
123 fused_moe.run(binding=binding)
124
125
126 def _is_current_stream_capturing() -> bool:
127 is_capturing = getattr(torch.cuda, "is_current_stream_capturing", None)
128 return bool(is_capturing is not None and is_capturing())
129
130
131 def _normalize_topk_ids(topk_ids: torch.Tensor) -> torch.Tensor:
132 if topk_ids.dtype == torch.int32 and topk_ids.is_contiguous():
133 return topk_ids
134 if _is_current_stream_capturing():
135 raise RuntimeError(
136 "b12x MoE topk_ids normalization would allocate during CUDA capture"
137 )
138 return topk_ids.to(dtype=torch.int32).contiguous()
139
140
141 def _normalize_topk_weights(topk_weights: torch.Tensor) -> torch.Tensor:
142 if topk_weights.dtype == torch.float32 and topk_weights.is_contiguous():
143 return topk_weights
144 if _is_current_stream_capturing():
145 raise RuntimeError(
146 "b12x MoE topk_weights normalization would allocate during CUDA capture"
147 )
148 return topk_weights.to(dtype=torch.float32).contiguous()
149
150
151 def _workspace_as_b12x_scratch(
152 workspace: torch.Tensor | None,
153 plan: Any,
154 ) -> torch.Tensor:
155 if workspace is None:
156 raise RuntimeError("b12x MoE requires workspace2 scratch")
157 if not workspace.is_contiguous():
158 raise ValueError("b12x MoE workspace2 must be contiguous")
159 scratch = workspace.view(-1).view(torch.uint8)
160 required_nbytes = _b12x_scratch_nbytes(plan)
161 if scratch.numel() < required_nbytes:
162 raise ValueError(
163 "b12x MoE workspace2 is too small: "
164 f"have={scratch.numel()} bytes, need={required_nbytes} bytes"
165 )
166 return scratch
167
168
169 def _replace_parameter_with_empty(
170 layer: torch.nn.Module,
171 name: str,
172 ) -> torch.Tensor | None:
173 parameter = getattr(layer, name, None)
174 if not isinstance(parameter, torch.Tensor):
175 return None
176 empty = torch.empty((0,), dtype=parameter.dtype, device=parameter.device)
177 replace_parameter(layer, name, empty)
178 return getattr(layer, name)
179
180
181 def _normalize_expert_scale(scale: torch.Tensor) -> torch.Tensor:
182 if scale.ndim == 2:
183 if scale.shape[1] not in (1, 2):
184 raise ValueError(
185 "expected an expert scale with one or two columns, got "
186 f"{tuple(scale.shape)}"
187 )
188 scale = scale[:, 0]
189 return scale.to(dtype=torch.float32).contiguous()
190
191
192 def _canonicalize_fp4_zero_signs_(packed: torch.Tensor) -> None:
193 """Clear sign bits from packed FP4 zero values in place."""
194 packed = packed.view(torch.uint8)
195 magnitude = packed & 0x77
196 nonzero = (magnitude | (magnitude >> 1) | (magnitude >> 2)) & 0x11
197 packed.bitwise_and_(0x77 | (nonzero << 3))
198
199
200 class B12xExperts(mk.FusedMoEExpertsModular):
201 """FP4 MoE experts backed by the b12x SM12x planned API."""
202
203 def __init__(
204 self,
205 moe_config: mk.FusedMoEConfig,
206 quant_config: FusedMoEQuantConfig,
207 ):
208 super().__init__(moe_config, quant_config)
209 if quant_config.weight_quant_dtype not in ("mxfp4", "nvfp4"):
210 raise ValueError(
211 "b12x MoE requires MXFP4 or NVFP4 weights, got "
212 f"{quant_config.weight_quant_dtype}"
213 )
214 scheme = (
215 quant_config.weight_quant_dtype,
216 quant_config.quant_dtype,
217 )
218 try:
219 self._quant_mode, self._source_format, self._w13_layout = _B12X_MOE_MODES[
220 scheme
221 ]
222 except KeyError as exc:
223 raise ValueError(
224 f"unsupported b12x MoE quantization scheme {scheme}"
225 ) from exc
226 self._prepared_experts: Any | None = None
227 self._source_parameters_released = False
228 self._unit_scales: dict[torch.device, torch.Tensor] = {}
229 self._plans: dict[tuple[int, int, MoEActivation, bool], Any] = {}
230 self._apply_router_weight_on_input = False
231
232 def _unit_scale(self, device: torch.device, num_experts: int) -> torch.Tensor:
233 scale = self._unit_scales.get(device)
234 if scale is None or scale.numel() != num_experts:
235 scale = torch.ones(num_experts, dtype=torch.float32, device=device)
236 self._unit_scales[device] = scale
237 return scale
238
239 def _weight_global_scale(
240 self,
241 device: torch.device,
242 num_experts: int,
243 scale: torch.Tensor | None,
244 name: str,
245 ) -> torch.Tensor:
246 if self._source_format != "modelopt_nvfp4":
247 return self._unit_scale(device, num_experts)
248 if scale is None:
249 raise ValueError(f"b12x NVFP4 MoE requires {name}")
250 scale = _normalize_expert_scale(scale)
251 if scale.numel() != num_experts:
252 raise ValueError(
253 f"b12x NVFP4 MoE expected {num_experts} {name} values, "
254 f"got {scale.numel()}"
255 )
256 return scale.to(device=device)
257
258 def _swiglu_params(
259 self,
260 activation: MoEActivation,
261 ) -> tuple[float | None, float | None, float | None]:
262 if activation in (
263 MoEActivation.SITU,
264 MoEActivation.RELU2,
265 MoEActivation.RELU2_NO_MUL,
266 ):
267 return None, None, None
268
269 limit = self.quant_config.gemm1_clamp_limit
270 if limit is None:
271 limit = self.moe_config.swiglu_limit
272 if activation != MoEActivation.SWIGLUOAI_UNINTERLEAVE:
273 return limit, None, None
274
275 alpha = self.quant_config.gemm1_alpha
276 if alpha is None:
277 alpha = self.moe_config.swiglu_alpha
278 beta = self.quant_config.gemm1_beta
279 if beta is None:
280 beta = self.moe_config.swiglu_beta
281 return limit, alpha, beta
282
283 def _prepare_experts(
284 self,
285 *,
286 w1: torch.Tensor,
287 w2: torch.Tensor,
288 activation: MoEActivation,
289 params_dtype: torch.dtype,
290 ) -> Any:
291 quant_mode = self._quant_mode
292 if _is_current_stream_capturing():
293 raise RuntimeError(
294 "b12x MoE weights must be prepared before CUDA graph capture"
295 )
296 if self.w1_scale is None or self.w2_scale is None:
297 raise ValueError("b12x MoE requires w1 and w2 block scales")
298
299 _canonicalize_fp4_zero_signs_(w1)
300 _canonicalize_fp4_zero_signs_(w2)
301
302 fused_moe = _require_b12x_fused_moe()
303
304 num_experts = int(w1.shape[0])
305 hidden_size = int(w2.shape[1])
306 intermediate_size = int(w2.shape[2]) * 2
307 unit_scale = self._unit_scale(w1.device, num_experts)
308 w1_global_scale = self._weight_global_scale(
309 w1.device, num_experts, self.g1_alphas, "w1 global scales"
310 )
311 w2_global_scale = self._weight_global_scale(
312 w2.device, num_experts, self.g2_alphas, "w2 global scales"
313 )
314
315 if quant_mode in ("nvfp4", "w4a8_nvfp4"):
316 if self.a1_gscale is None or self.a2_gscale is None:
317 raise ValueError("b12x NVFP4 MoE requires activation global scales")
318 a1_gscale = _normalize_expert_scale(self.a1_gscale).to(w1.device)
319 a2_gscale = _normalize_expert_scale(self.a2_gscale).to(w2.device)
320 else:
321 a1_gscale = unit_scale
322 a2_gscale = unit_scale
323
324 weight_plan = fused_moe.plan_weights(
325 quant_modes=quant_mode,
326 source_format=self._source_format,
327 activation=_b12x_activation_name(activation),
328 params_dtype=params_dtype,
329 num_experts=num_experts,
330 hidden_size=hidden_size,
331 intermediate_size=intermediate_size,
332 w13_layout=self._w13_layout,
333 )
334 return fused_moe.prepare_weights(
335 plan=weight_plan,
336 w1_fp4=w1,
337 w1_blockscale=self.w1_scale,
338 w1_global_scale=w1_global_scale,
339 a1_gscale=a1_gscale,
340 w2_fp4=w2,
341 w2_blockscale=self.w2_scale,
342 w2_global_scale=w2_global_scale,
343 a2_gscale=a2_gscale,
344 params_dtype=params_dtype,
345 )
346
347 def _refresh_quant_config(self, layer: torch.nn.Module) -> None:
348 self.quant_config._w1.scale = layer.w13_weight_scale
349 self.quant_config._w2.scale = layer.w2_weight_scale
350 if self._source_format != "modelopt_nvfp4":
351 return
352
353 self.quant_config._w1.alpha_or_gscale = layer.w13_weight_scale_2
354 self.quant_config._w2.alpha_or_gscale = layer.w2_weight_scale_2
355 if self._quant_mode in ("nvfp4", "w4a8_nvfp4"):
356 self.quant_config._a1.alpha_or_gscale = 1.0 / layer.w13_input_scale
357 self.quant_config._a2.alpha_or_gscale = 1.0 / layer.w2_input_scale
358
359 def _release_source_parameters(self, layer: torch.nn.Module) -> None:
360 if self._source_parameters_released:
361 return
362 w1_scale = _replace_parameter_with_empty(layer, "w13_weight_scale")
363 w2_scale = _replace_parameter_with_empty(layer, "w2_weight_scale")
364 if w1_scale is not None:
365 self.quant_config._w1.scale = w1_scale
366 if w2_scale is not None:
367 self.quant_config._w2.scale = w2_scale
368 _replace_parameter_with_empty(layer, "w13_weight")
369 _replace_parameter_with_empty(layer, "w2_weight")
370 self._source_parameters_released = True
371
372 def _reuse_prepared_storage(self, layer: torch.nn.Module, prepared: Any) -> Any:
373 previous = getattr(layer, "_b12x_prepared_experts", None)
374 prepared = reuse_packed_weight_storage(previous, prepared)
375 if prepared is not previous:
376 self._plans.clear()
377 self._prepared_experts = prepared
378 layer._b12x_prepared_experts = prepared
379 return prepared
380
381 def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
382 self._apply_router_weight_on_input = layer.apply_router_weight_on_input
383 if self._apply_router_weight_on_input and self._quant_mode != "w4a16":
384 raise ValueError(
385 "b12x MoE supports apply_router_weight_on_input only with W4A16"
386 )
387 self._source_parameters_released = False
388 self._refresh_quant_config(layer)
389 prepared = self._prepare_experts(
390 w1=layer.w13_weight,
391 w2=layer.w2_weight,
392 activation=layer.activation,
393 params_dtype=self.moe_config.in_dtype,
394 )
395 prepared = self._reuse_prepared_storage(layer, prepared)
396 if prepared.plan.discards_source_parameters:
397 self._release_source_parameters(layer)
398 layer.b12x_warmup_provider = self
399
400 @staticmethod
401 def is_supported_config(
402 cls: type[mk.FusedMoEExperts],
403 moe_config: mk.FusedMoEConfig,
404 weight_key: QuantKey | None,
405 activation_key: QuantKey | None,
406 activation_format: mk.FusedMoEActivationFormat,
407 ) -> tuple[bool, str | None]:
408 if moe_config.has_bias:
409 return False, "kernel does not support expert biases"
410 if moe_config.in_dtype not in (torch.float16, torch.bfloat16):
411 return (
412 False,
413 f"kernel does not support {moe_config.in_dtype} input/output dtype",
414 )
415 if moe_config.activation == MoEActivation.SITU and (
416 moe_config.activation_situ_beta != 4.0
417 or moe_config.activation_situ_linear_beta != 25.0
418 ):
419 return False, "kernel supports only SiTU beta=4 and linear_beta=25"
420 if (
421 activation_key is not None
422 and moe_config.activation == MoEActivation.SWIGLUOAI_UNINTERLEAVE
423 ):
424 return (
425 False,
426 "kernel does not support swigluoai_uninterleave with W4A8",
427 )
428 unpadded_intermediate_size = (
429 moe_config.intermediate_size_per_partition_unpadded
430 or moe_config.intermediate_size_per_partition
431 )
432 if weight_key == kMxfp4Static and unpadded_intermediate_size % 32 != 0:
433 return (
434 False,
435 "MXFP4 requires the per-rank intermediate size to be divisible by 32",
436 )
437 if weight_key == kMxfp4Static and activation_key == kMxfp8Dynamic:
438 if moe_config.activation not in (
439 MoEActivation.SILU,
440 MoEActivation.SITU,
441 ):
442 return False, "MXFP4 W4A8 supports only SiLU and SiTU"
443 if (
444 moe_config.hidden_dim % 256 != 0
445 or moe_config.intermediate_size_per_partition % 32 != 0
446 ):
447 return (
448 False,
449 "MXFP4 W4A8 requires hidden size divisible by 256 and "
450 "per-rank intermediate size divisible by 32",
451 )
452 return mk.FusedMoEExperts.is_supported_config(
453 cls, moe_config, weight_key, activation_key, activation_format
454 )
455
456 @staticmethod
457 def _supports_current_device() -> bool:
458 if not (
459 current_platform.is_cuda()
460 and current_platform.is_device_capability_family(120)
461 ):
462 return False
463 fused_moe = get_b12x_fused_moe()
464 if fused_moe is None:
465 return False
466 return fused_moe.is_supported()
467
468 @staticmethod
469 def _supports_no_act_and_mul() -> bool:
470 return True
471
472 @staticmethod
473 def _supports_quant_scheme(
474 weight_key: QuantKey | None,
475 activation_key: QuantKey | None,
476 ) -> bool:
477 return (weight_key, activation_key) in (
478 (kMxfp4Static, kMxfp8Dynamic),
479 (kMxfp4Static, None),
480 (kNvfp4Static, kNvfp4Dynamic),
481 (kNvfp4Static, kMxfp8Dynamic),
482 (kNvfp4Static, None),
483 )
484
485 @staticmethod
486 def _supports_activation(activation: MoEActivation) -> bool:
487 return activation in (
488 MoEActivation.SILU,
489 MoEActivation.SITU,
490 MoEActivation.SWIGLUOAI_UNINTERLEAVE,
491 MoEActivation.RELU2_NO_MUL,
492 )
493
494 @staticmethod
495 def _supports_parallel_config(
496 moe_parallel_config: FusedMoEParallelConfig,
497 ) -> bool:
498 return (
499 not moe_parallel_config.use_ep
500 and moe_parallel_config.ep_size == 1
501 and not moe_parallel_config.use_all2all_kernels
502 and not moe_parallel_config.enable_eplb
503 )
504
505 @staticmethod
506 def activation_format() -> mk.FusedMoEActivationFormat:
507 return mk.FusedMoEActivationFormat.Standard
508
509 @property
510 def expects_unquantized_inputs(self) -> bool:
511 return True
512
513 def supports_expert_map(self) -> bool:
514 return False
515
516 def finalize_weight_and_reduce_impl(self) -> mk.TopKWeightAndReduce:
517 return TopKWeightAndReduceNoOP()
518
519 def _prepared(self) -> Any:
520 if self._prepared_experts is None:
521 raise RuntimeError(
522 "b12x MoE weights must be prepared by process_weights_after_loading"
523 )
524 return self._prepared_experts
525
526 def moe_problem_size(
527 self,
528 a1: torch.Tensor,
529 w1: torch.Tensor,
530 w2: torch.Tensor,
531 topk_ids: torch.Tensor,
532 ) -> tuple[int, int, int, int, int]:
533 if w1.numel() and w2.numel():
534 return super().moe_problem_size(a1, w1, w2, topk_ids)
535 prepared = self._prepared()
536 tokens = int(a1.shape[0] if a1.ndim == 2 else a1.shape[1])
537 return (
538 int(prepared.num_experts),
539 tokens,
540 int(prepared.intermediate_size) * 2,
541 int(a1.shape[-1]),
542 int(topk_ids.shape[1]),
543 )
544
545 def _plan(
546 self,
547 *,
548 tokens: int,
549 topk: int,
550 activation: MoEActivation,
551 apply_router_weight_on_input: bool = False,
552 ) -> Any:
553 fused_moe = _require_b12x_fused_moe()
554
555 key = (
556 max(int(tokens), 1),
557 int(topk),
558 activation,
559 bool(apply_router_weight_on_input),
560 )
561 plan = self._plans.get(key)
562 if plan is not None:
563 return plan
564 if _is_current_stream_capturing():
565 raise RuntimeError("b12x MoE plans must be created before CUDA capture")
566
567 limit, alpha, beta = self._swiglu_params(activation)
568 prepared = self._prepared()
569 plan = fused_moe.plan(
570 fused_moe.Caps(
571 max_tokens=key[0],
572 num_topk=key[1],
573 device=prepared.w1_fp4.device,
574 weight_plan=prepared.plan,
575 core_token_counts=(key[0],),
576 route_num_experts=0,
577 quant_mode=self._quant_mode,
578 apply_router_weight_on_input=key[3],
579 swiglu_limit=limit,
580 swiglu_alpha=alpha,
581 swiglu_beta=beta,
582 frozen=True,
583 )
584 )
585 self._plans[key] = plan
586 return plan
587
588 def get_b12x_warmup_unit(
589 self,
590 layer: torch.nn.Module,
591 token_counts: tuple[int, ...],
592 output_dtype: torch.dtype,
593 ) -> B12xWarmupUnit:
594 assert output_dtype == self.moe_config.in_dtype
595 prepared = self._prepared()
596 activation = layer.activation
597 limit, alpha, beta = self._swiglu_params(activation)
598
599 def compile() -> None:
600 self.warmup_launches(layer, token_counts=token_counts)
601
602 return B12xWarmupUnit(
603 name="MoE",
604 key=(
605 type(self),
606 prepared.w1_fp4.device,
607 output_dtype,
608 self._quant_mode,
609 self._source_format,
610 self._w13_layout,
611 int(prepared.num_experts),
612 int(prepared.hidden_size),
613 int(prepared.intermediate_size),
614 int(self.moe_config.experts_per_token),
615 _b12x_activation_name(activation),
616 bool(layer.apply_router_weight_on_input),
617 limit,
618 alpha,
619 beta,
620 ),
621 compile=compile,
622 )
623
624 @torch.inference_mode()
625 def warmup_launches(
626 self,
627 layer: torch.nn.Module,
628 *,
629 token_counts: Iterable[int],
630 ) -> int:
631 """Compile one representative launch for every planned regime."""
632 activation = layer.activation
633 dtype = self.moe_config.in_dtype
634 topk = int(self.moe_config.experts_per_token)
635 apply_router_weight_on_input = bool(layer.apply_router_weight_on_input)
636 limit, alpha, beta = self._swiglu_params(activation)
637 prepared = self._prepared()
638 device = prepared.w1_fp4.device
639 launch_tokens: dict[tuple[Any, ...], int] = {}
640 for tokens in sorted({int(count) for count in token_counts if int(count) > 0}):
641 execution_plan = _b12x_moe_execution_plan(
642 tokens=tokens,
643 topk=topk,
644 prepared=prepared,
645 quant_mode=self._quant_mode,
646 apply_router_weight_on_input=apply_router_weight_on_input,
647 swiglu_limit=limit,
648 swiglu_alpha=alpha,
649 swiglu_beta=beta,
650 )
651 signature = (execution_plan.implementation, execution_plan.execution)
652 launch_tokens.setdefault(signature, tokens)
653
654 for tokens in launch_tokens.values():
655 hidden_states = torch.zeros(
656 (tokens, int(prepared.hidden_size)),
657 dtype=dtype,
658 device=device,
659 )
660 output = torch.empty_like(hidden_states)
661 topk_ids = (
662 torch.arange(topk, device=device, dtype=torch.int32)
663 .unsqueeze(0)
664 .expand(tokens, -1)
665 .contiguous()
666 )
667 topk_ids.remainder_(int(prepared.num_experts))
668 topk_weights = torch.full(
669 (tokens, topk),
670 1.0 / topk,
671 dtype=torch.float32,
672 device=device,
673 )
674 plan = self._plan(
675 tokens=tokens,
676 topk=topk,
677 activation=activation,
678 apply_router_weight_on_input=apply_router_weight_on_input,
679 )
680 scratch = torch.empty(
681 (_b12x_scratch_nbytes(plan),),
682 dtype=torch.uint8,
683 device=device,
684 )
685 _run_b12x_moe_plan(
686 plan=plan,
687 scratch=scratch,
688 hidden_states=hidden_states,
689 prepared=prepared,
690 topk_weights=topk_weights,
691 topk_ids=topk_ids,
692 output=output,
693 unit_scale_contract=self._quant_mode == "w4a16",
694 )
695 return len(launch_tokens)
696
697 def workspace_shapes(
698 self,
699 M: int,
700 N: int,
701 K: int,
702 topk: int,
703 global_num_experts: int,
704 local_num_experts: int,
705 expert_tokens_meta: mk.ExpertTokensMetadata | None,
706 activation: MoEActivation,
707 ) -> tuple[tuple[int, ...], tuple[int, ...], tuple[int, ...]]:
708 del N, global_num_experts, local_num_experts, expert_tokens_meta
709 plan = self._plan(
710 tokens=M,
711 topk=topk,
712 activation=activation,
713 apply_router_weight_on_input=self._apply_router_weight_on_input,
714 )
715 itemsize = self.moe_config.in_dtype.itemsize
716 scratch_elements = max(
717 1, (_b12x_scratch_nbytes(plan) + itemsize - 1) // itemsize
718 )
719 return (0,), (scratch_elements,), (M, K)
720
721 def apply(
722 self,
723 output: torch.Tensor,
724 hidden_states: torch.Tensor,
725 w1: torch.Tensor,
726 w2: torch.Tensor,
727 topk_weights: torch.Tensor,
728 topk_ids: torch.Tensor,
729 activation: MoEActivation,
730 global_num_experts: int,
731 expert_map: torch.Tensor | None,
732 a1q_scale: torch.Tensor | None,
733 a2_scale: torch.Tensor | None,
734 workspace13: torch.Tensor | None,
735 workspace2: torch.Tensor | None,
736 expert_tokens_meta: mk.ExpertTokensMetadata | None,
737 apply_router_weight_on_input: bool | None,
738 ) -> None:
739 del w1, w2, global_num_experts
740 del a1q_scale, a2_scale, workspace13, expert_tokens_meta
741 if expert_map is not None:
742 raise ValueError("b12x TP MoE does not support expert maps")
743 if bool(apply_router_weight_on_input) != self._apply_router_weight_on_input:
744 raise ValueError(
745 "apply_router_weight_on_input does not match the prepared b12x MoE plan"
746 )
747 prepared = self._prepared()
748 topk_ids = _normalize_topk_ids(topk_ids)
749 topk_weights = _normalize_topk_weights(topk_weights)
750 plan = self._plan(
751 tokens=int(hidden_states.shape[0]),
752 topk=int(topk_ids.shape[1]),
753 activation=activation,
754 apply_router_weight_on_input=bool(apply_router_weight_on_input),
755 )
756 scratch = _workspace_as_b12x_scratch(workspace2, plan)
757
758 _run_b12x_moe_plan(
759 plan=plan,
760 scratch=scratch,
761 hidden_states=hidden_states,
762 prepared=prepared,
763 topk_weights=topk_weights,
764 topk_ids=topk_ids,
765 output=output,
766 unit_scale_contract=self._quant_mode == "w4a16",
767 )
768
769 def moe_sum(self, input: torch.Tensor, output: torch.Tensor) -> None:
770 raise NotImplementedError("LoRA is not supported for B12xExperts")