b12x.py 26 KB

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