[ROCm][Kimi-K3] aiter moe environment variable cleanup (#50582)

Signed-off-by: Hongxia Yang <hongxia.yang@amd.com>

Hongxia Yang committed Aug 3, 2026 at 14:54 UTC 8f50685c48d6bc88d199d9560ab67615e1b2c2a4
5 files changed +71 -56
vllm/_aiter_ops.py
+10
@@ -1530,6 +1530,7 @@ class rocm_aiter_ops:
1530 VLLM_ROCM_USE_AITER_FP4_ASM_GEMM: Controls FP4 assembly GEMM.
1531 VLLM_ROCM_USE_AITER_TRITON_ROPE: Controls Triton rotary embeddings.
1532 VLLM_ROCM_USE_AITER_FUSION_SHARED_EXPERTS: Controls shared expert fusion.
1533 + VLLM_ROCM_USE_AITER_MOE_SITUV2_A8W4: Controls a8w4 SiTU fused MoE variant.
1534 VLLM_ROCM_USE_AITER_TRITON_GEMM: Controls Triton unquantized GEMM.
1535
1536 Note:
@@ -1598,6 +1599,7 @@ class rocm_aiter_ops:
1599 # TODO: Consolidate under VLLM_ROCM_USE_AITER_ROPE
1600 _TRITON_ROTARY_EMBED = envs.VLLM_ROCM_USE_AITER_TRITON_ROPE
1601 _MOE_SHARED_EXPERTS_ENABLED = envs.VLLM_ROCM_USE_AITER_FUSION_SHARED_EXPERTS
1602 + _MOE_SITUV2_A8W4 = envs.VLLM_ROCM_USE_AITER_MOE_SITUV2_A8W4
1603 # TODO: Consolidate under _LINEAR_ENABLED
1604 _TRITON_UNQUANT_GEMM = envs.VLLM_ROCM_USE_AITER_TRITON_GEMM
1605 # Lazily probed: whether aiter.topk_softmax supports the
@@ -1627,6 +1629,7 @@ class rocm_aiter_ops:
1629 cls._FP4_GEMM_DYNAMIC_QUANT_ASM = envs.VLLM_ROCM_USE_AITER_FP4_ASM_GEMM
1630 cls._TRITON_ROTARY_EMBED = envs.VLLM_ROCM_USE_AITER_TRITON_ROPE
1631 cls._MOE_SHARED_EXPERTS_ENABLED = envs.VLLM_ROCM_USE_AITER_FUSION_SHARED_EXPERTS
1632 + cls._MOE_SITUV2_A8W4 = envs.VLLM_ROCM_USE_AITER_MOE_SITUV2_A8W4
1633 cls._TRITON_UNQUANT_GEMM = envs.VLLM_ROCM_USE_AITER_TRITON_GEMM
1634
1635 @staticmethod
@@ -1719,6 +1722,13 @@ class rocm_aiter_ops:
1722 def is_fusion_moe_shared_experts_enabled(cls) -> bool:
1723 return cls.is_fused_moe_enabled() and cls._MOE_SHARED_EXPERTS_ENABLED
1724
1725 + @classmethod
1726 + @if_aiter_supported
1727 + def is_fused_moe_situv2_a8w4_enabled(cls) -> bool:
1728 + # _MOE_SITUV2_A8W4 is a variant of aiter fused moe, so aiter
1729 + # fused moe must be enabled as well.
1730 + return cls.is_fused_moe_enabled() and cls._MOE_SITUV2_A8W4
1731 +
1732 @classmethod
1733 @if_aiter_supported
1734 def topk_softmax_supports_fused_sigmoid(cls) -> bool:
vllm/envs.py
+5 -4
@@ -130,7 +130,7 @@ if TYPE_CHECKING:
130 VLLM_ROCM_USE_AITER_LINEAR_HIPBMM: bool = False
131 VLLM_ROCM_USE_AITER_MOE: bool = True
132 VLLM_ROCM_AITER_MOE_DISPATCH_POLICY: int = 0
133 - AITER_SITUV2_A8W4: bool = False
133 + VLLM_ROCM_USE_AITER_MOE_SITUV2_A8W4: bool = False
134 VLLM_ROCM_USE_AITER_RMSNORM: bool = True
135 VLLM_ROCM_USE_AITER_MLA: bool = True
136 VLLM_ROCM_USE_AITER_MHA: bool = True
@@ -1211,9 +1211,10 @@ environment_variables: dict[str, Callable[[], Any]] = {
1211 ),
1212 # Route K3 SiTU MXFP4 MoE through the a8w4 (fp8 activation) gate/up-
1213 # interleaved flydsl kernels instead of the default a16w4 separated path.
1214 - # Shared with the AITER runtime, which reads the same env var directly.
1215 - "AITER_SITUV2_A8W4": lambda: (
1216 - os.getenv("AITER_SITUV2_A8W4", "0").lower() in ("true", "1")
1214 + # This is the only flag users need: vLLM picks the kernels by passing
1215 + # gate_mode to AITER and sets the AITER-side workaround env at init.
1216 + "VLLM_ROCM_USE_AITER_MOE_SITUV2_A8W4": lambda: (
1217 + os.getenv("VLLM_ROCM_USE_AITER_MOE_SITUV2_A8W4", "0").lower() in ("true", "1")
1218 ),
1219 # MoE sorting dispatch policy for AITER fused MoE kernels.
1220 # 0 = auto (default): single-pass for small batches, multi-pass
vllm/model_executor/layers/fused_moe/experts/rocm_aiter_moe.py
+4 -4
@@ -5,7 +5,6 @@ from functools import lru_cache
5
6 import torch
7
8 -import vllm.envs as envs
8 import vllm.model_executor.layers.fused_moe.modular_kernel as mk
9 from vllm._aiter_ops import rocm_aiter_ops
10 from vllm.model_executor.layers.fused_moe.activation import MoEActivation
@@ -376,11 +375,12 @@ def rocm_aiter_fused_experts(
375
376 gate_mode = ""
377 if activation == MoEActivation.SITU:
379 - # a8w4 (AITER_SITUV2_A8W4=1) uses the gate/up-interleaved (_gui_)
380 - # fp8 flydsl kernels; default a16w4 SiTU stays separated.
378 + # a8w4 (VLLM_ROCM_USE_AITER_MOE_SITUV2_A8W4=1) uses the gate/up-
379 + # interleaved (_gui_) fp8 flydsl kernels; default a16w4 SiTU stays
380 + # separated.
381 gate_mode = (
382 GateMode.INTERLEAVE.value
383 - if envs.AITER_SITUV2_A8W4
383 + if rocm_aiter_ops.is_fused_moe_situv2_a8w4_enabled()
384 else GateMode.SEPARATED.value
385 )
386 elif quant_config.use_mxfp4_w4a16:
vllm/model_executor/layers/fused_moe/oracle/mxfp4.py
+13 -2
@@ -14,6 +14,7 @@ from vllm.model_executor.layers.fused_moe import (
14 FusedMoEConfig,
15 RoutedExperts,
16 )
17 +from vllm.model_executor.layers.fused_moe.activation import MoEActivation
18 from vllm.model_executor.layers.fused_moe.all2all_utils import (
19 maybe_make_prepare_finalize,
20 )
@@ -634,10 +635,20 @@ def select_deepseek_v4_mxfp4_moe_backend(
635
636
637 def mxfp4_round_up_hidden_size_and_intermediate_size(
637 - backend: Mxfp4MoeBackend, hidden_size: int, intermediate_size: int
638 + backend: Mxfp4MoeBackend,
639 + hidden_size: int,
640 + intermediate_size: int,
641 + activation: MoEActivation | None = None,
642 ) -> tuple[int, int]:
643 """Round up hidden_size and intermediate_size based on backend requirements."""
640 - if backend == Mxfp4MoeBackend.EMULATION:
644 + if backend == Mxfp4MoeBackend.AITER_MXFP4_BF16 and activation == MoEActivation.SITU:
645 + # K3's AITER A16W4 SiTU kernel handles K3's native intermediate size
646 + # (moe_intermediate 3072; e.g. 384/partition at TP8). Align to 128 (a
647 + # no-op for K3's shapes) rather than the generic ROCm 256 round-up,
648 + # which would inflate weights and OOM.
649 + intermediate_size = round_up(intermediate_size, 128)
650 + hidden_size = round_up(hidden_size, 128)
651 + elif backend == Mxfp4MoeBackend.EMULATION:
652 # Emulation has no kernel tile; it only needs OCP MX block alignment so the
653 # per-block scale buffers (`dim // OCP_MX_BLOCK_SIZE`) aren't floor-truncated
654 # by a non-block-aligned TP/DP shard (e.g. 2880 // 4 = 720).
vllm/model_executor/layers/quantization/mxfp4.py
+39 -46
@@ -1,9 +1,10 @@
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 +import os
5 +
6 import torch
7
6 -import vllm.envs as envs
8 from vllm.logger import init_logger
9 from vllm.model_executor.layers.attention import Attention
10 from vllm.model_executor.layers.fused_moe import (
@@ -515,6 +516,14 @@ class Mxfp4MoEMethod(FusedMoEMethodBase):
516 self.mxfp4_backend = Mxfp4MoeBackend.AITER_MXFP4_BF16
517 self.experts_cls = backend_to_kernel_cls(self.mxfp4_backend)[0]
518 logger.info_once("Using AITER_MXFP4_BF16 for Kimi-K3 SiTU MXFP4 MoE.")
519 + from vllm._aiter_ops import rocm_aiter_ops
520 +
521 + if rocm_aiter_ops.is_fused_moe_situv2_a8w4_enabled():
522 + # AITER keeps bf16 activations below this token count, which
523 + # would not match the fp8 a8w4 kernels the interleaved SiTU
524 + # path is tuned for. The a16w4 path never reads it.
525 + # TODO: Remove once AITER takes this as a kernel argument.
526 + os.environ["AITER_BF16_FP8_MOE_BOUND"] = "0"
527 else:
528 self.mxfp4_backend, self.experts_cls = select_deepseek_v4_mxfp4_moe_backend(
529 moe
@@ -560,13 +569,11 @@ class Mxfp4MoEMethod(FusedMoEMethodBase):
569 act_dtype=act_dtype,
570 moe_parallel_config=moe_parallel_config,
571 )
563 - if self.is_k3_situ_aiter:
564 - # K3's AITER A16W4 kernel handles K3's native intermediate size
565 - # (moe_intermediate 3072; e.g. 384/partition at TP8); the generic
566 - # 256 round-up would inflate weights and OOM.
567 - return hidden_size, intermediate_size_per_partition
572 return mxfp4_round_up_hidden_size_and_intermediate_size(
569 - self.mxfp4_backend, hidden_size, intermediate_size_per_partition
573 + self.mxfp4_backend,
574 + hidden_size,
575 + intermediate_size_per_partition,
576 + activation=self.moe.activation,
577 )
578
579 def create_weights(
@@ -716,19 +723,24 @@ class Mxfp4MoEMethod(FusedMoEMethodBase):
723 )
724
725 # Convert weights to kernel format
719 - w13, w2, w13_scale, w2_scale, w13_bias, w2_bias = (
720 - convert_weight_to_mxfp4_moe_kernel_format(
721 - mxfp4_backend=self.mxfp4_backend,
722 - layer=layer,
723 - w13_weight=w13,
724 - w2_weight=w2,
725 - w13_weight_scale=w13_scale,
726 - w2_weight_scale=w2_scale,
727 - w13_bias=w13_bias,
728 - w2_bias=w2_bias,
729 - _cache_permute_indices=self._cache_permute_indices,
726 + if self.is_k3_situ_aiter:
727 + w13, w2, w13_scale, w2_scale = (
728 + self._convert_k3_situ_weight_to_kernel_format(layer)
729 + )
730 + else:
731 + w13, w2, w13_scale, w2_scale, w13_bias, w2_bias = (
732 + convert_weight_to_mxfp4_moe_kernel_format(
733 + mxfp4_backend=self.mxfp4_backend,
734 + layer=layer,
735 + w13_weight=w13,
736 + w2_weight=w2,
737 + w13_weight_scale=w13_scale,
738 + w2_weight_scale=w2_scale,
739 + w13_bias=w13_bias,
740 + w2_bias=w2_bias,
741 + _cache_permute_indices=self._cache_permute_indices,
742 + )
743 )
731 - )
744
745 # For TRITON backends, weights are wrapped tensors from triton_kernels
746 # that don't support .detach(). Manually assign parameters.
@@ -774,7 +786,9 @@ class Mxfp4MoEMethod(FusedMoEMethodBase):
786 layer=layer,
787 )
788
777 - def _setup_kernel_k3_situ(self, layer: RoutedExperts) -> None:
789 + def _convert_k3_situ_weight_to_kernel_format(
790 + self, layer: RoutedExperts
791 + ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
792 # K3's AITER A16W4 kernel wants the separated ([gate_all, up_all])
793 # stage-1 layout, unlike the interleaved gpt-oss/DeepSeek path in
794 # convert_weight_to_mxfp4_moe_kernel_format. Preshuffle once here.
@@ -786,10 +800,10 @@ class Mxfp4MoEMethod(FusedMoEMethodBase):
800 e8m0_dtype = torch.float8_e8m0fnu
801 num_experts = layer.w13_weight.shape[0]
802
789 - # a8w4 (AITER_SITUV2_A8W4=1) uses the gate/up-interleaved (_gui_) fp8
790 - # flydsl kernels, which need w13 weight+scale in interleave layout.
791 - # Default a16w4 keeps the separated layout.
792 - guinterleave = envs.AITER_SITUV2_A8W4
803 + # a8w4 (VLLM_ROCM_USE_AITER_MOE_SITUV2_A8W4=1) uses the gate/up-
804 + # interleaved (_gui_) fp8 flydsl kernels, which need w13 weight+scale
805 + # in interleave layout. Default a16w4 keeps the separated layout.
806 + guinterleave = rocm_aiter_ops.is_fused_moe_situv2_a8w4_enabled()
807 w13 = rocm_aiter_ops.shuffle_weight_a16w4(
808 layer.w13_weight.data.view(fp4_dtype), 16, guinterleave
809 )
@@ -802,30 +816,9 @@ class Mxfp4MoEMethod(FusedMoEMethodBase):
816 w13_scale_raw.view(-1, w13_scale_raw.shape[-1]), num_experts, guinterleave
817 )
818 w2_scale = e8m0_shuffle(w2_scale_raw.view(-1, w2_scale_raw.shape[-1]))
805 -
806 - replace_parameter(layer, "w13_weight", w13)
807 - replace_parameter(layer, "w2_weight", w2)
808 - replace_parameter(layer, "w13_weight_scale", w13_scale)
809 - replace_parameter(layer, "w2_weight_scale", w2_scale)
810 - layer.w13_weight.is_shuffled = True
811 - layer.w2_weight.is_shuffled = True
812 -
813 - self.moe_quant_config = self.get_fused_moe_quant_config(layer)
814 - if self.moe_quant_config is not None and self.experts_cls is not None:
815 - self.moe_kernel = make_mxfp4_moe_kernel(
816 - moe_quant_config=self.moe_quant_config,
817 - moe_config=self.moe,
818 - mxfp4_backend=self.mxfp4_backend,
819 - experts_cls=self.experts_cls,
820 - routing_tables=layer._expert_routing_tables(),
821 - layer=layer,
822 - )
819 + return w13, w2, w13_scale, w2_scale
820
821 def process_weights_after_loading(self, layer):
825 - if self.is_k3_situ_aiter:
826 - self._setup_kernel_k3_situ(layer)
827 - return
828 -
822 w13 = layer.w13_weight
823 w2 = layer.w2_weight
824 w13_scale = layer.w13_weight_scale