From 56641867a278eaa1affdf587ef93726f22b51752 Mon Sep 17 00:00:00 2001 From: jpvillam Date: Fri, 31 Jul 2026 15:33:08 -0500 Subject: [PATCH 01/50] Triton version bump Signed-off-by: jpvillam Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- docker/Dockerfile.rocm_gfx1250 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile.rocm_gfx1250 b/docker/Dockerfile.rocm_gfx1250 index 0609c73c049f..8fec28fe67a9 100644 --- a/docker/Dockerfile.rocm_gfx1250 +++ b/docker/Dockerfile.rocm_gfx1250 @@ -732,7 +732,7 @@ RUN curl -k -O https://raw.githubusercontent.com/ROCm/vllm/refs/heads/gfx1250_be RUN pip3 uninstall -y triton && \ git clone https://github.com/triton-lang/triton.git && \ cd triton && \ - git checkout c517f38c && \ + git checkout 78420176f2d90c3270a48c9218e27e9b5d923c8d && \ TRITON_APPEND_CMAKE_ARGS="-DCMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH=FALSE" pip3 install . CMD ["/bin/bash"] From e1b8021507c2e0d68d1dca12de7d45d003e51b64 Mon Sep 17 00:00:00 2001 From: Jaden Mathias Date: Fri, 17 Jul 2026 23:45:43 +0000 Subject: [PATCH 02/50] Perf pass 1 Signed-off-by: jpvillam Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- vllm/_aiter_ops.py | 4 +- .../fused_moe/experts/aiter_mxfp4_w4a8_moe.py | 379 +++++++++++++++++- .../layers/quantization/utils/mxfp4_utils.py | 24 ++ vllm/model_executor/layers/utils.py | 13 +- vllm/model_executor/models/gpt_oss.py | 62 ++- .../backends/rocm_aiter_unified_attn.py | 63 ++- vllm/v1/sample/ops/topk_topp_sampler.py | 46 ++- vllm/v1/sample/sampler.py | 61 +++ vllm_benchserve.sh | 183 +++++++++ 9 files changed, 812 insertions(+), 23 deletions(-) create mode 100755 vllm_benchserve.sh diff --git a/vllm/_aiter_ops.py b/vllm/_aiter_ops.py index 44b89aeaf12c..658e58db9857 100644 --- a/vllm/_aiter_ops.py +++ b/vllm/_aiter_ops.py @@ -3668,8 +3668,8 @@ def do_qk_norm_rope_kvcache_update( Shared by the AITER FA and unified-attention impls. The caller splits kv_cache, since the unbind dim depends on the layout (e.g. the unified - encoder-decoder path is K/V-first), and passes use_shuffle_layout - (unified reads NHD and must pass False). + encoder-decoder path is K/V-first), and passes use_shuffle_layout to + match the layout its own read path expects. """ if kv_cache_dtype.startswith("fp8"): key_cache = key_cache.view(current_platform.fp8_dtype()) diff --git a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py index bc1a4af6c0f5..e85a84064804 100644 --- a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py +++ b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py @@ -61,12 +61,6 @@ def aiter_triton_kernel_w4a8_moe_forward( gating_output, topk, sm_first=not renormalize ) - # gfx1250: aiter's in-kernel gather is numerically broken - if on_gfx1250(): - gather_src = gather_idx.to(torch.long) // topk - hidden_states = hidden_states[gather_src] - gather_idx = None - return triton_kernel_fused_mxfp4_w4a8_experts( None, hidden_states, @@ -131,10 +125,16 @@ def triton_kernel_fused_mxfp4_w4a8_experts( from vllm.model_executor.layers.quantization.utils.mxfp4_utils import ( should_use_cdna4_mx_scale_swizzle, + should_use_gfx1250_mx_scale_swizzle, weight_mx_scale, ) - _swizzle_mx_scale = "CDNA4_SCALE" if should_use_cdna4_mx_scale_swizzle() else None + if should_use_gfx1250_mx_scale_swizzle(): + _swizzle_mx_scale = "GFX1250_SCALE" + elif should_use_cdna4_mx_scale_swizzle(): + _swizzle_mx_scale = "CDNA4_SCALE" + else: + _swizzle_mx_scale = None assert quant_config.w1_precision is not None, ( "w1_precision in quant config can't be None" @@ -309,3 +309,368 @@ def apply( unpadded_N_w2=self.moe_config.hidden_dim_unpadded, unpadded_K_w2=self.moe_config.intermediate_size_per_partition_unpadded, ) + + + +def _aiter_raw(t): + if t is None or isinstance(t, torch.Tensor): + return t + return t.storage.data if hasattr(t, "storage") else t + + +def _aiter_w4a16_silu_via_a8w4( + hidden_states: torch.Tensor, + w1_data, + w2_data, + w1_wscale, + w2_wscale, + w1_bias, + w2_bias, + routing_data, + gather_idx, + scatter_idx, + gammas, + apply_router_weight_on_input: bool, + swiglu_limit: float, + unpadded_N_w1, + unpadded_K_w1, + unpadded_N_w2, + unpadded_K_w2, +) -> torch.Tensor: + """ + MXFP4 w4a16 MoE with a SILU (concatenated ``[gate | up]``) activation. + """ + from aiter.ops.triton.fusions.fused_clamp_act_mul import fused_clamp_act_mul + from aiter.ops.triton.moe_op_gemm_a8w4 import moe_gemm_a8w4 + from aiter.ops.triton.quant import dynamic_mxfp8_quant + + from vllm.model_executor.layers.quantization.utils.mxfp4_utils import ( + should_use_cdna4_mx_scale_swizzle, + should_use_gfx1250_mx_scale_swizzle, + ) + + # gfx1250: _swizzle_mxfp4 GFX1250-swizzles the weight scales at load time + # (shuffle_scale_moe, preshuffle_factor=32), so the kernel must be told + # SWIZZLE_MX_SCALE="GFX1250_SCALE" to unswizzle them. + if should_use_gfx1250_mx_scale_swizzle(): + swz = "GFX1250_SCALE" + elif should_use_cdna4_mx_scale_swizzle(): + swz = "CDNA4_SCALE" + else: + swz = None + quant_dtype = torch.float8_e4m3fn + + g1_gammas = gammas if apply_router_weight_on_input else None + g2_gammas = None if apply_router_weight_on_input else gammas + + hidden_q, a1_scale = dynamic_mxfp8_quant(hidden_states, quant_dtype=quant_dtype) + raw_gate_up = moe_gemm_a8w4( + hidden_q, + w1_data, + a1_scale, + w1_wscale, + None, + None, + w1_bias, + routing_data, + gather_indx=gather_idx, + gammas=g1_gammas, + swizzle_mx_scale=swz, + out_dtype=torch.bfloat16, + apply_swiglu=False, + unpadded_N=unpadded_N_w1, + unpadded_K=unpadded_K_w1, + ) + if unpadded_N_w1 is not None: + raw_gate_up = raw_gate_up[:, :unpadded_N_w1] + + interim_fp8, a2_scale = fused_clamp_act_mul( + raw_gate_up, + swiglu_limit=swiglu_limit, + activation="silu", + dtype_quant=quant_dtype, + scale_dtype_fmt="ue8m0", + quant_block_size=32, + ) + + out = moe_gemm_a8w4( + interim_fp8, + w2_data, + a2_scale, + w2_wscale, + None, + None, + w2_bias, + routing_data, + scatter_indx=scatter_idx, + gammas=g2_gammas, + swizzle_mx_scale=swz, + unpadded_N=unpadded_N_w2, + unpadded_K=unpadded_K_w2, + ) + return out + + +def aiter_triton_kernel_w4a16_moe_forward( + hidden_states: torch.Tensor, + w1, + w2, + gating_output: torch.Tensor, + topk: int, + renormalize: bool, + activation: MoEActivation = MoEActivation.SWIGLUOAI, + quant_config: FusedMoEQuantConfig | None = None, + apply_router_weight_on_input: bool = False, + global_num_experts: int = -1, + expert_map: torch.Tensor | None = None, + unpadded_N_w1=None, + unpadded_K_w1=None, + unpadded_N_w2=None, + unpadded_K_w2=None, + num_expert_group: int | None = None, + topk_group: int | None = None, + e_score_correction_bias: torch.Tensor | None = None, + routed_scaling_factor: float | None = None, + score_mode: str | None = None, +): + assert quant_config is not None and rocm_aiter_ops.is_enabled() + from vllm.platforms.rocm import on_gfx1250 + + try: + from aiter.ops.triton.moe.moe_op_gemm_a16w4 import moe_gemm_a16w4 + from aiter.ops.triton.moe.moe_routing import routing as _routing_mod + except ImportError: + from aiter.ops.triton.moe.moe_op_gemm_a16w4 import moe_gemm_a16w4 + from aiter.ops.triton.moe_routing import routing as _routing_mod + + if on_gfx1250(): + _routing_mod.is_tdm_avail = lambda: False + aiter_routing = _routing_mod.routing + + # See context in #50859. + # TODO: Remove once https://github.com/ROCm/aiter/pull/4530 is merged, + # AITER released, and AITER pin in vLLM increased from 0.1.19. + gating_output = patch_gating_output(gating_output, global_num_experts) + + if score_mode is not None: + use_grouped_topk = num_expert_group is not None and num_expert_group > 1 + routing_data, gather_idx, scatter_idx = aiter_routing( + gating_output, + topk, + score_mode=score_mode, + bias=e_score_correction_bias, + renorm=renormalize, + routed_scaling_factor=( + routed_scaling_factor if routed_scaling_factor is not None else 1.0 + ), + use_grouped_topk=use_grouped_topk, + num_expert_group=num_expert_group, + topk_group=topk_group, + ) + else: + routing_data, gather_idx, scatter_idx = aiter_routing( + gating_output, topk, sm_first=not renormalize + ) + + assert quant_config.w1_precision is not None + assert quant_config.w2_precision is not None + + w1_data = _aiter_raw(w1) + w2_data = _aiter_raw(w2) + w1_wscale = _aiter_raw(quant_config.w1_precision.weight_scale) + w2_wscale = _aiter_raw(quant_config.w2_precision.weight_scale) + + gammas = routing_data.gate_scal if routing_data else None + + swiglu_alpha = ( + quant_config.gemm1_alpha if quant_config.gemm1_alpha is not None else 1.0 + ) + swiglu_limit = ( + quant_config.gemm1_clamp_limit + if quant_config.gemm1_clamp_limit is not None + else 7.0 + ) + + # SILU on gfx1250: use the verified a8w4 kernel (dynamic MXFP8); a16w4 faults. + if activation == MoEActivation.SILU and on_gfx1250(): + return _aiter_w4a16_silu_via_a8w4( + hidden_states, + w1_data, + w2_data, + w1_wscale, + w2_wscale, + quant_config.w1_bias, + quant_config.w2_bias, + routing_data, + gather_idx, + scatter_idx, + gammas, + apply_router_weight_on_input, + swiglu_limit, + unpadded_N_w1, + unpadded_K_w1, + unpadded_N_w2, + unpadded_K_w2, + ) + + # SILU: silu(gate) * up — same kernel, just no "+1" residual in swiglu. + swiglu_add_residual = activation != MoEActivation.SILU + + intermediate = moe_gemm_a16w4( + hidden_states, + w1_data, + None, + w1_wscale, + None, + None, + quant_config.w1_bias, + routing_data, + gather_indx=gather_idx, + gammas=gammas if apply_router_weight_on_input else None, + swizzle_mx_scale=None, + apply_swiglu=True, + alpha=swiglu_alpha, + limit=swiglu_limit, + swiglu_add_residual=swiglu_add_residual, + unpadded_N=unpadded_N_w1, + unpadded_K=unpadded_K_w1, + ) + + out = moe_gemm_a16w4( + intermediate, + w2_data, + None, + w2_wscale, + None, + None, + quant_config.w2_bias, + routing_data, + scatter_indx=scatter_idx, + gammas=None if apply_router_weight_on_input else gammas, + swizzle_mx_scale=None, + unpadded_N=unpadded_N_w2, + unpadded_K=unpadded_K_w2, + ) + + return out + + +class AiterW4A16ExpertsMonolithic(mk.FusedMoEExpertsMonolithic): + def __init__( + self, + moe_config: FusedMoEConfig, + quant_config: FusedMoEQuantConfig, + ): + super().__init__(moe_config, quant_config) + self.topk = moe_config.experts_per_token + self.renormalize = moe_config.routing_method in ( + RoutingMethodType.Renormalize, + RoutingMethodType.RenormalizeNaive, + RoutingMethodType.DeepseekV4, + ) + + @staticmethod + def activation_format() -> mk.FusedMoEActivationFormat: + return mk.FusedMoEActivationFormat.Standard + + @staticmethod + def _supports_current_device() -> bool: + if not rocm_aiter_ops.is_enabled(): + return False + from vllm.platforms.rocm import on_gfx950, on_gfx1250 + + return on_gfx950() or on_gfx1250() + + @staticmethod + def _supports_no_act_and_mul() -> bool: + return False + + @staticmethod + def _supports_quant_scheme( + weight_key: QuantKey | None, + activation_key: QuantKey | None, + ) -> bool: + return (weight_key, activation_key) == (kMxfp4Static, None) + + @staticmethod + def _supports_activation(activation: MoEActivation) -> bool: + return activation in (MoEActivation.SWIGLUOAI, MoEActivation.SILU) + + @staticmethod + def _supports_parallel_config( + moe_parallel_config: FusedMoEParallelConfig, + ) -> bool: + return ( + not moe_parallel_config.use_all2all_kernels + and not moe_parallel_config.enable_eplb + and moe_parallel_config.dp_size <= 1 + ) + + @staticmethod + def _supports_routing_method( + routing_method: RoutingMethodType, + weight_key: QuantKey | None, + activation_key: QuantKey | None, + ) -> bool: + return routing_method in [ + RoutingMethodType.Renormalize, + RoutingMethodType.RenormalizeNaive, + RoutingMethodType.DeepseekV4, + ] + + @staticmethod + def _supports_router_logits_dtype( + router_logits_dtype: torch.dtype | None, + routing_method: RoutingMethodType, + ) -> bool: + return True + + @property + def expects_unquantized_inputs(self) -> bool: + return True + + def apply( + self, + hidden_states: torch.Tensor, + w1: torch.Tensor, + w2: torch.Tensor, + router_logits: torch.Tensor, + activation: MoEActivation, + global_num_experts: int, + expert_map: torch.Tensor | None, + a1q_scale: torch.Tensor | None, + apply_router_weight_on_input: bool, + num_expert_group: int | None = None, + e_score_correction_bias: torch.Tensor | None = None, + routed_scaling_factor: float | None = None, + topk_group: int | None = None, + ) -> torch.Tensor: + assert self.moe_config.intermediate_size_per_partition_unpadded is not None + assert self.moe_config.hidden_dim_unpadded is not None + score_mode = ( + "sqrtsoftplus" + if self.moe_config.routing_method == RoutingMethodType.DeepseekV4 + else None + ) + return aiter_triton_kernel_w4a16_moe_forward( + hidden_states=hidden_states, + w1=w1, + w2=w2, + gating_output=router_logits, + topk=self.topk, + renormalize=self.renormalize, + activation=activation, + global_num_experts=global_num_experts, + expert_map=expert_map, + quant_config=self.quant_config, + apply_router_weight_on_input=apply_router_weight_on_input, + unpadded_N_w1=self.moe_config.intermediate_size_per_partition_unpadded * 2, + unpadded_K_w1=self.moe_config.hidden_dim_unpadded, + unpadded_N_w2=self.moe_config.hidden_dim_unpadded, + unpadded_K_w2=self.moe_config.intermediate_size_per_partition_unpadded, + num_expert_group=num_expert_group, + topk_group=topk_group, + e_score_correction_bias=e_score_correction_bias, + routed_scaling_factor=routed_scaling_factor, + score_mode=score_mode, + ) diff --git a/vllm/model_executor/layers/quantization/utils/mxfp4_utils.py b/vllm/model_executor/layers/quantization/utils/mxfp4_utils.py index 45562b12b1da..9e985d4688d4 100644 --- a/vllm/model_executor/layers/quantization/utils/mxfp4_utils.py +++ b/vllm/model_executor/layers/quantization/utils/mxfp4_utils.py @@ -13,6 +13,19 @@ logger = init_logger(__name__) +def should_use_gfx1250_mx_scale_swizzle() -> bool: + """Whether to pre-shuffle the mxfp4 scales into the gfx1250 layout. + + The weight-load swizzle in `_swizzle_mxfp4` and the SWIZZLE_MX_SCALE + kernel argument in `aiter_mxfp4_w4a8_moe` must agree. + """ + if not current_platform.is_rocm(): + return False + from vllm.platforms.rocm import on_gfx1250 + + return on_gfx1250() + + def should_use_cdna4_mx_scale_swizzle() -> bool: """Whether to use the CDNA4 swizzled scale layout for mxfp4 on gfx950. @@ -136,6 +149,17 @@ def _swizzle_mxfp4(quant_tensor, scale, num_warps=8): # transpose the tensor so that the quantization axis is on dim1 quant_tensor = quant_tensor.transpose(-2, -1) scale = scale.transpose(-2, -1) + + if should_use_gfx1250_mx_scale_swizzle(): + from aiter.ops.triton.utils.shuffle import shuffle_scale_moe + + assert ( + scale.dim() == 3 and scale.shape[-1] % 32 == 0 and scale.shape[-2] % 8 == 0 + ), f"GFX1250 scale swizzle needs (E,K_SCALE%8,N%32); got {tuple(scale.shape)}" + scale = shuffle_scale_moe( + scale, arch="gfx1250", preshuffle_factor=32, scale_kwidth=8 + ) + quant_tensor = convert_layout( wrap_torch_tensor(quant_tensor, dtype=FP4), value_layout, **value_layout_opts ) diff --git a/vllm/model_executor/layers/utils.py b/vllm/model_executor/layers/utils.py index d4ee153fb251..69373886ad0d 100644 --- a/vllm/model_executor/layers/utils.py +++ b/vllm/model_executor/layers/utils.py @@ -228,6 +228,8 @@ def cuda_flashinfer_bf16_gemm( def use_aiter_triton_gemm(n, m, k, dtype): + from vllm.platforms.rocm import on_gfx1250 + if ( not rocm_aiter_ops.is_triton_gemm_enabled() # MI300's - fp8nuz=True @@ -236,6 +238,9 @@ def use_aiter_triton_gemm(n, m, k, dtype): ): return False + if on_gfx1250(): + return True + # use hipblaslt for the larger GEMMs if n > 2048 and m > 512: return False @@ -291,7 +296,7 @@ def wvsplitkrc_dispatch(n: int, k: int, m: int, cu_count: int) -> tuple[int, boo def rocm_unquantized_gemm_impl( x: torch.Tensor, weight: torch.Tensor, bias: torch.Tensor | None = None ) -> torch.Tensor: - from vllm.platforms.rocm import on_gfx1x, on_gfx9, on_gfx950, on_gfx1250 + from vllm.platforms.rocm import on_gfx1x, on_gfx9, on_gfx950 n = x.numel() // x.size(-1) m = weight.shape[0] @@ -324,11 +329,7 @@ def rocm_unquantized_gemm_impl( x_view = x.reshape(-1, x.size(-1)).contiguous() return ops.wvSplitKrc(x_view, weight, cu_count, bias) - # gfx1250's aiter gemm_a16w16 uses the gluon backend, which requires - # K % 256 == 0 (it walks K with fixed-size descriptors and won't pad a - # partial last tile). Some whitelisted shapes have K=2880 (e.g. gpt-oss-120b - # hidden), so skip aiter there and fall back to the torch GEMM path below. - if use_aiter_triton_gemm(n, m, k, x.dtype) and not (on_gfx1250() and k % 256 != 0): + if use_aiter_triton_gemm(n, m, k, x.dtype): from aiter.ops.triton.gemm_a16w16 import gemm_a16w16 return gemm_a16w16(x, weight, bias) diff --git a/vllm/model_executor/models/gpt_oss.py b/vllm/model_executor/models/gpt_oss.py index 90286006a62b..a3316a76a450 100644 --- a/vllm/model_executor/models/gpt_oss.py +++ b/vllm/model_executor/models/gpt_oss.py @@ -50,6 +50,7 @@ from vllm.platforms import current_platform from vllm.sequence import IntermediateTensors from vllm.utils.math_utils import cdiv +from vllm.utils.torch_utils import direct_register_custom_op from vllm.v1.attention.backend import AttentionType from .interfaces import ( @@ -88,6 +89,45 @@ def _get_weight_loader(param: torch.Tensor) -> Callable[..., object]: return weight_loader +def _gpt_oss_rmsnorm_pad_add( + x: torch.Tensor, + residual: torch.Tensor, + weight: torch.Tensor, + eps: float, + pad_to: int, +) -> tuple[torch.Tensor, torch.Tensor]: + from aiter.ops.triton.normalization.fused_add_rmsnorm_pad import ( + fused_add_rmsnorm_pad, + ) + + out, res_out = fused_add_rmsnorm_pad(x, weight, eps, residual, pad_to) + + return out, res_out + + +def _gpt_oss_rmsnorm_pad_add_fake( + x: torch.Tensor, + residual: torch.Tensor, + weight: torch.Tensor, + eps: float, + pad_to: int, +) -> tuple[torch.Tensor, torch.Tensor]: + M, N = x.shape + n_out = ((N + pad_to - 1) // pad_to) * pad_to if pad_to > 0 else N + return ( + torch.empty((M, n_out), dtype=x.dtype, device=x.device), + torch.empty_like(residual), + ) + + +direct_register_custom_op( + op_name="gpt_oss_rmsnorm_pad_add", + op_func=_gpt_oss_rmsnorm_pad_add, + mutates_args=[], + fake_impl=_gpt_oss_rmsnorm_pad_add_fake, +) + + class OAIAttention(nn.Module): # Override to switch RoPE convention. gpt-oss uses NeoX (chunk halves); # privacy-filter and similar derivatives use GPT-J (interleaved pairs). @@ -449,6 +489,15 @@ def __init__( self.input_layernorm = RMSNorm(config.hidden_size, eps=1e-5) self.post_attention_layernorm = RMSNorm(config.hidden_size, eps=1e-5) + self.use_fused_rmsnorm_pad_add = False + if current_platform.is_rocm(): + from vllm.platforms.rocm import on_gfx1250 + + self.use_fused_rmsnorm_pad_add = ( + on_gfx1250() + and self.mlp.experts.moe_config.hidden_dim > config.hidden_size + ) + def forward( self, hidden_states: torch.Tensor, @@ -464,7 +513,18 @@ def forward( hidden_states = self.attn(hidden_states, positions) # Fully Connected - hidden_states, residual = self.post_attention_layernorm(hidden_states, residual) + if self.use_fused_rmsnorm_pad_add and residual is not None: + hidden_states, residual = torch.ops.vllm.gpt_oss_rmsnorm_pad_add( + hidden_states, + residual, + self.post_attention_layernorm.weight, + self.post_attention_layernorm.variance_epsilon, + self.mlp.experts.moe_config.hidden_dim, + ) + else: + hidden_states, residual = self.post_attention_layernorm( + hidden_states, residual + ) output = self.mlp(hidden_states) return output, residual diff --git a/vllm/v1/attention/backends/rocm_aiter_unified_attn.py b/vllm/v1/attention/backends/rocm_aiter_unified_attn.py index 0d4fd5519484..da42bddcba38 100644 --- a/vllm/v1/attention/backends/rocm_aiter_unified_attn.py +++ b/vllm/v1/attention/backends/rocm_aiter_unified_attn.py @@ -15,6 +15,7 @@ QuantKey, kFp8StaticTensorSym, ) +from vllm.platforms.rocm import on_gfx1250 from vllm.utils.torch_utils import is_quantized_kv_cache from vllm.v1.attention.backend import ( AttentionLayer, @@ -190,12 +191,37 @@ def __init__( # RDNA3 has no low precision MMA, preserve query precision self.supports_quant_query_input = not on_gfx11() + # Layout flag: must mirror get_kv_cache_shape, which is attn_type + # agnostic because the allocation is shared across layers. + self._shuffle_kv_layout = ( + rocm_aiter_ops.is_shuffle_kv_cache_enabled() and on_gfx1250() + ) + # Kernel flag: the shuffled reader/writer is causal decoder only. + self._use_shuffle_kv = ( + self._shuffle_kv_layout and self.attn_type != AttentionType.ENCODER_DECODER + ) + def _split_kv_cache( self, kv_cache: torch.Tensor ) -> tuple[torch.Tensor, torch.Tensor]: + if self._shuffle_kv_layout: + # (2, B, N, H, hs) -> ((B, N, H, hs), (B, N, H, hs)) + return kv_cache.unbind(0) # (B, H, N, 2*hs) -> ((B, N, H, hs), (B, N, H, hs)) return kv_cache.transpose(1, 2).split(self.head_size, dim=-1) + def _shuffle_kv_views( + self, key_cache: torch.Tensor, value_cache: torch.Tensor + ) -> tuple[torch.Tensor, torch.Tensor]: + # No-copy reinterpret of standalone-contiguous K/V (num_blocks, + # block_size, num_kv_heads, head_size) as the 5D shuffle layout the + # gluon kernel reads (the shuffle WRITER laid bytes in this order). + nb, bs, nkv, hd = key_cache.shape + x = 16 // key_cache.element_size() + kc = key_cache.reshape(nb, nkv, hd // x, bs, x) + vc = value_cache.reshape(nb, nkv, bs // x, hd, x) + return kc, vc + def forward( self, layer: torch.nn.Module, @@ -270,6 +296,12 @@ def forward( key_cache = key_cache.view(self.fp8_dtype) value_cache = value_cache.view(self.fp8_dtype) + # The shuffle writer pre-divides K/V by k/v_scale before the + # (scale=1.0) store, so the stored fp8 matches what + # reshape_and_cache_flash writes and descaling is unchanged. + if self._use_shuffle_kv: + key_cache, value_cache = self._shuffle_kv_views(key_cache, value_cache) + cu_seqlens_q = attn_metadata.query_start_loc seqused_k = attn_metadata.seq_lens max_seqlen_q = attn_metadata.max_query_len @@ -297,6 +329,7 @@ def forward( v_descale=layer._v_scale, sinks=self.sinks, output_scale=output_scale, + shuffled_kv_cache=self._use_shuffle_kv, ) else: # The aiter kernel is causal-only. Non-causal cross-attention @@ -345,6 +378,27 @@ def do_kv_cache_update( return key_cache, value_cache = self._split_kv_cache(kv_cache) + if self._use_shuffle_kv: + from vllm.v1.attention.backends.rocm_aiter_fa import ( + reshape_and_cache_shuffle_triton, + ) + + if is_quantized_kv_cache(self.kv_cache_dtype): + key_cache = key_cache.view(self.fp8_dtype) + value_cache = value_cache.view(self.fp8_dtype) + _dummy = torch.empty(1, dtype=torch.float32, device=key.device) + reshape_and_cache_shuffle_triton( + key, + value, + key_cache, + value_cache, + slot_mapping, + self.kv_cache_dtype, + _dummy, + _dummy, + ) + return + # Reshape the input keys and values and store them in the cache. ops.reshape_and_cache_flash( key, @@ -381,6 +435,8 @@ def do_qk_norm_rope_kvcache_update( kv_cache: torch.Tensor, layer_slot_mapping: torch.Tensor, ): + # _split_kv_cache picks the unbind dim per layout. The writer must + # match what forward() reads back, hence use_shuffle_layout below. key_cache, value_cache = self._split_kv_cache(kv_cache) rocm_aiter_ops.do_qk_norm_rope_kvcache_update( qkv=qkv, @@ -401,7 +457,7 @@ def do_qk_norm_rope_kvcache_update( k_scale=layer._k_scale_cpu, v_scale=layer._v_scale_cpu, kv_cache_dtype=self.kv_cache_dtype, - use_shuffle_layout=False, + use_shuffle_layout=self._use_shuffle_kv, ) def do_qk_norm_mrope_kvcache_update( @@ -462,13 +518,16 @@ def do_rope_and_kv_cache_update( # we use direct Q, K, V tensors without caching return key_cache, value_cache = self._split_kv_cache(kv_cache) - flash_layout = True is_fp8_kv_cache = is_quantized_kv_cache(self.kv_cache_dtype) if is_fp8_kv_cache: key_cache = key_cache.view(self.fp8_dtype) value_cache = value_cache.view(self.fp8_dtype) + if self._use_shuffle_kv: + key_cache, value_cache = self._shuffle_kv_views(key_cache, value_cache) + flash_layout = not self._use_shuffle_kv + rocm_aiter_ops.triton_rope_and_cache( query, key, diff --git a/vllm/v1/sample/ops/topk_topp_sampler.py b/vllm/v1/sample/ops/topk_topp_sampler.py index a3084a09c28e..6384c7093d98 100644 --- a/vllm/v1/sample/ops/topk_topp_sampler.py +++ b/vllm/v1/sample/ops/topk_topp_sampler.py @@ -34,11 +34,34 @@ def register_top_k_top_p_warmups() -> None: _topp_split_mask.register_warmup() -def _skip_aiter_sampler_on_gfx1250() -> bool: - # Lazy ROCm-only import; keeps arch detection out of import time on CUDA/CPU. - from vllm.platforms.rocm import on_gfx1250 +def _aiter_temp_gumbel_sample( + logits: torch.Tensor, generators: dict[int, torch.Generator] +) -> torch.Tensor: + """Fused temperature Gumbel-max sampling via aiter. - return on_gfx1250() + Logits are already temperature-scaled by the parent Sampler, so temps=1. + Returns int64 token ids of shape (num_tokens,). + """ + from aiter import mixed_sample_outer_exponential + + n, vocab = logits.shape + out = torch.empty(n, dtype=torch.int32, device=logits.device) + if not generators: + # Common case: shared (1, vocab) exponential noise (cheap RNG). + exp = ( + torch.empty((1, vocab), dtype=torch.float32, device=logits.device) + .exponential_() + .expand(n, vocab) + ) + else: + exp = torch.empty((n, vocab), dtype=torch.float32, device=logits.device) + if len(generators) != n: + exp.exponential_() + for i, g in generators.items(): + exp[i].exponential_(generator=g) + temps = torch.ones(n, dtype=torch.float32, device=logits.device) + mixed_sample_outer_exponential(out, logits, exp, temps, eps=1e-10) + return out.to(torch.int64) def _flashinfer_jit_unsupported_reason(capability: DeviceCapability) -> str | None: @@ -246,8 +269,12 @@ def __init__( elif ( logprobs_mode not in PROCESSED_LOGPROBS_MODES and rocm_aiter_ops.is_enabled() - and not _skip_aiter_sampler_on_gfx1250() # TODO (JPVILLAM): Enable ): + # Lazy ROCm-only import; keeps arch detection out of import time + # on CUDA/CPU. + from vllm.platforms.rocm import on_gfx1250 + + self.on_gfx1250 = on_gfx1250() self.aiter_ops = None self._aiter_ops_import_failed = False logger.info_once( @@ -376,6 +403,15 @@ def forward_hip( p: torch.Tensor | None, ) -> tuple[torch.Tensor, torch.Tensor | None]: """Optimized ROCm/aiter path (same structure as forward_cuda).""" + if self.on_gfx1250: + if ( + k is None + and p is None + and not self.use_fp64_gumbel + and self.logprobs_mode not in PROCESSED_LOGPROBS_MODES + ): + return _aiter_temp_gumbel_sample(logits, generators), None + return self.forward_native(logits, generators, k, p) if (k is None and p is None) or generators: if generators: logger.warning_once( diff --git a/vllm/v1/sample/sampler.py b/vllm/v1/sample/sampler.py index d19f65f6d81d..a9cb2f1a2375 100644 --- a/vllm/v1/sample/sampler.py +++ b/vllm/v1/sample/sampler.py @@ -5,6 +5,7 @@ import torch import torch.nn as nn +from vllm._aiter_ops import rocm_aiter_ops from vllm.config.model import LogprobsMode from vllm.platforms import current_platform from vllm.utils.gpu_sync_debug import gpu_sync_allowed @@ -19,6 +20,61 @@ _SAMPLING_EPS = 1e-5 +def mixed_sample_eligible(sm: SamplingMetadata, predict_bonus_token: bool) -> bool: + """Whether the fused aiter sample-from-raw-logits kernel can be used.""" + if not rocm_aiter_ops.is_enabled(): + return False + + from vllm.platforms.rocm import on_gfx1250 + + if not on_gfx1250(): + return False + if predict_bonus_token: + return False + if not sm.all_random or sm.temperature is None: + return False + if sm.top_k is not None or sm.top_p is not None: + return False + if sm.max_num_logprobs is not None or sm.logprob_token_ids: + return False + if ( + not sm.no_penalties + or sm.allowed_token_ids_mask is not None + or sm.bad_words_token_ids + ): + return False + lp = sm.logitsprocs + if lp.argmax_invariant or lp.non_argmax_invariant: + return False + h = sm.thinking_budget_state_holder + return h is None or not h.has_tracked_requests() + + +def _aiter_raw_fused_sample(logits: torch.Tensor, sm: SamplingMetadata) -> torch.Tensor: + """Sample token ids from raw (unscaled) logits via a fused aiter kernel. + + Returns int32 ids of shape (num_tokens, 1). + """ + from aiter import mixed_sample_outer_exponential + + n, vocab = logits.shape + out = torch.empty(n, dtype=torch.int32, device=logits.device) + gens = sm.generators + if not gens: + exp = ( + torch.empty((1, vocab), dtype=torch.float32, device=logits.device) + .exponential_() + .expand(n, vocab) + ) + else: + exp = torch.empty((n, vocab), dtype=torch.float32, device=logits.device) + exp.exponential_() + for i, g in gens.items(): + exp[i].exponential_(generator=g) + mixed_sample_outer_exponential(out, logits, exp, sm.temperature, eps=1e-10) + return out.unsqueeze(-1) + + class Sampler(nn.Module): """A layer that samples the next tokens from the model's outputs with the following steps in order: @@ -77,6 +133,11 @@ def forward( predict_bonus_token: bool = False, logprobs_mode_override: LogprobsMode | None = None, ) -> SamplerOutput: + if mixed_sample_eligible(sampling_metadata, predict_bonus_token): + return SamplerOutput( + sampled_token_ids=_aiter_raw_fused_sample(logits, sampling_metadata), + logprobs_tensors=None, + ) logprobs_mode = logprobs_mode_override or self.logprobs_mode # NOTE(woosuk): Use the original logits (before any penalties or # temperature scaling) for the top-k logprobs. diff --git a/vllm_benchserve.sh b/vllm_benchserve.sh new file mode 100755 index 000000000000..f17344a4c5a9 --- /dev/null +++ b/vllm_benchserve.sh @@ -0,0 +1,183 @@ +#!/bin/bash +# Smoke-test + vllm-bench-serve harness for the four gfx1250 models, using the +# exact serve recipes from the per-model scripts in this directory: +# +# gptoss <- gpt.sh (gpt-oss-120b-w-mxfp4-a-fp8) +# dsr1 <- dsr1.sh (DeepSeek-R1-0528-MXFP4) +# dsv4f <- dsr4_accurate.sh (DeepSeek-V4-Flash) +# minimax <- minimax.sh (MiniMax-M3-MXFP4) +# +# For each selected model it: starts vllm serve with that model's env+args +# (server + bench output stream to the terminal), waits for /health, then runs +# `vllm bench serve` with random 1k/1k ISL/OSL at concurrencies 1,4,8,32,64, +# then shuts the server down (freeing the GPU) before the next one. +# +# Usage: +# ./vllm_benchserve.sh # run all four, in order +# ./vllm_benchserve.sh --minimax # run only minimax +# ./vllm_benchserve.sh --gptoss /some/other/path# run gptoss from a custom path +# ./vllm_benchserve.sh --dsr1 --minimax # run a subset +# Each of --gptoss / --dsr1 / --dsv4f / --minimax takes an OPTIONAL model path. +set -uo pipefail + +PORT=8000 +CANONICAL_ORDER=(gptoss dsr1 dsv4f minimax) + +# --- bench-serve parameters --- +INPUT_LEN=1024 +OUTPUT_LEN=1024 +CONCURRENCIES=(64) # default: concurrency 1 only (--long for the full sweep) +LONG_CONCURRENCIES=(1 4 8 32 64) # used when --long is given +# prompts sent per concurrency = concurrency * PROMPTS_PER_CONC +PROMPTS_PER_CONC=10 + +# --- default model paths (from the per-model serve scripts) --- +declare -A MODEL_PATHS=( + [gptoss]="/data/models/gpt-oss-120b-w-mxfp4-a-fp8" + [dsr1]="/data/models/DeepSeek-R1-0528-MXFP4" + [dsv4f]="/data/models/DeepSeek-V4-Flash" + [minimax]="/data/models/MiniMax-M3-MXFP4" +) + +# --- serve-time environment (verbatim from each serve script) --- +declare -A MODEL_ENV=( + [gptoss]="HSA_ENABLE_SDMA=0 USE_SVM=0 HSA_XNACK=0 VLLM_ROCM_AITER_FUSED_MOE_TRITON_GEMM_A4W4=1 VLLM_ROCM_USE_AITER=1 VLLM_ROCM_USE_SKINNY_GEMM=0 VLLM_ROCM_USE_AITER_RMSNORM=0" + [dsr1]="VLLM_DISABLE_COMPILE_CACHE=1 VLLM_ROCM_USE_AITER=1 VLLM_ROCM_USE_AITER_MLA=0 HSA_ENABLE_SDMA=0 USE_SVM=0 HSA_XNACK=0 VLLM_ROCM_AITER_FUSED_MOE_TRITON_GEMM_A4W4=1 VLLM_ROCM_USE_AITER_UNIFIED_ATTENTION=1 VLLM_ROCM_USE_SKINNY_GEMM=0 VLLM_ROCM_USE_AITER_RMSNORM=0 VLLM_ROCM_USE_AITER_FP8BMM=0" + [dsv4f]="HSA_ENABLE_SDMA=0 USE_SVM=0 HSA_XNACK=0 VLLM_FORCE_TORCH_BLOCK_FP8=1 VLLM_ROCM_USE_AITER_LINEAR=0 VLLM_ROCM_AITER_FUSED_MOE_TRITON_GEMM_A4W4=0 VLLM_ROCM_USE_AITER=1 VLLM_ROCM_USE_AITER_TRITON_GEMM=1 VLLM_ROCM_USE_AITER_UNIFIED_ATTENTION=1 VLLM_ROCM_USE_SKINNY_GEMM=0 VLLM_ROCM_USE_AITER_RMSNORM=0" + [minimax]="VLLM_DISABLE_COMPILE_CACHE=1 VLLM_ROCM_USE_AITER=1 VLLM_ROCM_USE_AITER_MLA=0 HSA_ENABLE_SDMA=0 USE_SVM=0 HSA_XNACK=0 VLLM_ROCM_AITER_FUSED_MOE_TRITON_GEMM_A4W4=0 VLLM_ROCM_USE_AITER_UNIFIED_ATTENTION=1 VLLM_ROCM_USE_SKINNY_GEMM=0 VLLM_ROCM_USE_AITER_RMSNORM=0 VLLM_ROCM_USE_AITER_FP8BMM=0" +) + +# --- serve args (everything except --model/--host/--port, which we add). +# NOTE: JSON values must contain NO spaces (they ride through unquoted $VAR +# word-splitting); the dsr1 compilation-config below is space-free. --- +declare -A MODEL_SERVE=( + [gptoss]="--tensor-parallel-size 1 --gpu_memory_utilization 0.7 --attention-backend TRITON_ATTN" + [dsr1]="--trust-remote-code --no-enable-prefix-caching --no-enable-chunked-prefill --max-model-len 8192 --dtype auto --tensor-parallel-size 1 --distributed-executor-backend mp --max-num-batched-tokens 8192 --max-num-seqs 32 --gpu-memory-utilization 0.90 --compilation-config {\"pass_config\":{\"fuse_attn_quant\":true,\"eliminate_noops\":true,\"fuse_norm_quant\":true,\"fuse_mla_dual_rms_norm\":false,\"enable_qk_norm_rope_fusion\":false},\"cudagraph_mode\":\"FULL_AND_PIECEWISE\",\"custom_ops\":[\"+rms_norm\",\"+silu_and_mul\",\"+quant_fp8\"]}" + [dsv4f]="--tensor-parallel-size 1 --gpu_memory_utilization 0.7 --kv-cache-dtype fp8 --max-model-len 32768" + [minimax]="--trust-remote-code --language-model-only --skip-mm-profiling --block-size 128 --enforce-eager --no-enable-prefix-caching --no-enable-chunked-prefill --max-model-len 32768 --dtype auto --tensor-parallel-size 1 --distributed-executor-backend mp --max-num-batched-tokens 32768 --max-num-seqs 32 --gpu-memory-utilization 0.90 --reasoning-parser minimax_m3 --tool-call-parser minimax_m3 --enable-auto-tool-choice" +) + +usage() { + cat < vllm bench serve (random ${INPUT_LEN}/${OUTPUT_LEN} isl/osl @ conc ${CONCURRENCIES[*]}) -> shutdown. +Logs stream to this terminal. +EOF +} + +# --- parse args (each model flag takes an OPTIONAL path) --- +declare -A PATH_OVERRIDE=() +SELECTED=() +while [[ $# -gt 0 ]]; do + case "$1" in + --gptoss|--dsr1|--dsv4f|--minimax) + key="${1#--}" + SELECTED+=("$key") + if [[ $# -ge 2 && "$2" != -* ]]; then PATH_OVERRIDE[$key]="$2"; shift 2; else shift; fi + ;; + --long) CONCURRENCIES=("${LONG_CONCURRENCIES[@]}"); shift ;; + --port) PORT="$2"; shift 2 ;; + --list) for k in "${CANONICAL_ORDER[@]}"; do printf '%-9s -> %s\n' "$k" "${MODEL_PATHS[$k]}"; done; exit 0 ;; + -h|--help) usage; exit 0 ;; + *) echo "Unknown argument: $1" >&2; usage; exit 1 ;; + esac +done +[[ ${#SELECTED[@]} -eq 0 ]] && SELECTED=("${CANONICAL_ORDER[@]}") + +echo "Models: ${SELECTED[*]} | Port: $PORT (all server + bench logs stream to this terminal)" + +server_pid="" +stop_server() { + [[ -n "$server_pid" ]] && kill "$server_pid" 2>/dev/null + # vLLM renames workers to VLLM::EngineCore / VLLM::Worker; killing the launcher + # alone leaves them holding GPU memory, so sweep them too. + pkill -9 -f "vllm serve" 2>/dev/null + pkill -9 -f "VLLM::" 2>/dev/null + server_pid="" + # wait for GPU0 to actually release before the next model loads + if command -v rocm-smi >/dev/null 2>&1; then + for _ in $(seq 1 40); do + used=$(rocm-smi --showmeminfo vram 2>/dev/null | grep 'GPU\[0\]' | grep -i used | grep -oE '[0-9]+' | tail -1) + [[ -z "$used" ]] && break + (( used < 5000000000 )) && break # < ~5 GiB => free + sleep 3 + done + else + sleep 8 + fi +} +trap 'stop_server; exit 130' INT TERM +trap 'stop_server' EXIT + +declare -A RESULT + +run_model() { + local key="$1" + local model="${PATH_OVERRIDE[$key]:-${MODEL_PATHS[$key]}}" + local env="${MODEL_ENV[$key]}" serve="${MODEL_SERVE[$key]}" + + echo; echo "==================== $key : $model ====================" + if [[ ! -e "$model" ]]; then + echo "WARNING: skipping '$key' -- model path does not exist:" >&2 + echo " $model" >&2 + if [[ -n "${PATH_OVERRIDE[$key]:-}" ]]; then + echo " (this path was supplied via '--$key $model'; check the path/mount)" >&2 + else + echo " (this is the default path for '$key'; download the model there," >&2 + echo " or point it elsewhere with: --$key /path/to/model)" >&2 + fi + echo " Continuing with the remaining models." >&2 + RESULT[$key]="SKIP (model path not found: $model)" + return + fi + + # --- start server (logs stream to this terminal) --- + echo "[$key] starting vllm serve ..." + ( export $env; vllm serve --model "$model" --host localhost --port "$PORT" $serve ) & + server_pid=$! + + local ready=0 + for _ in $(seq 1 240); do # up to ~20 min for load + if curl -s "http://localhost:$PORT/health" >/dev/null 2>&1; then ready=1; break; fi + ps -p "$server_pid" >/dev/null 2>&1 || { echo "[$key] ERROR: server died (see output above)"; RESULT[$key]="FAIL (server died at load)"; stop_server; return; } + sleep 5 + done + [[ $ready -eq 1 ]] || { echo "[$key] ERROR: server not ready (timeout)"; RESULT[$key]="FAIL (server timeout)"; stop_server; return; } + echo "[$key] server ready." + + # --- vllm bench serve (random ISL/OSL, sweep concurrencies) --- + local rc=0 + for conc in "${CONCURRENCIES[@]}"; do + local num_prompts=$(( conc * PROMPTS_PER_CONC )) + echo "[$key] === vllm bench serve : conc=$conc isl=$INPUT_LEN osl=$OUTPUT_LEN num_prompts=$num_prompts ===" + if ! vllm bench serve \ + --model "$model" \ + --host localhost --port "$PORT" \ + --dataset-name random \ + --random-input-len "$INPUT_LEN" \ + --random-output-len "$OUTPUT_LEN" \ + --max-concurrency "$conc" \ + --num-prompts "$num_prompts" \ + --ignore-eos + then + echo "[$key] ERROR: bench serve failed at conc=$conc" >&2 + rc=1 + fi + done + if [[ $rc -eq 0 ]]; then RESULT[$key]="PASS (bench tables above)"; else RESULT[$key]="FAIL (bench serve error)"; fi + + stop_server +} + +for key in "${SELECTED[@]}"; do run_model "$key"; done + +echo; echo "==================== SUMMARY ====================" +for key in "${SELECTED[@]}"; do printf '%-9s : %s\n' "$key" "${RESULT[$key]:-UNKNOWN}"; done From 1ea8c623cf649d9f1f2c3b2b6e63ce76dad4ce2a Mon Sep 17 00:00:00 2001 From: jpvillam Date: Tue, 4 Aug 2026 17:04:20 +0000 Subject: [PATCH 03/50] experimental v4 preshuffle Signed-off-by: jpvillam Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- vllm/_aiter_ops.py | 73 +++++++++++++++++++ vllm/envs.py | 8 ++ .../kernels/linear/scaled_mm/aiter.py | 48 +++++++++++- vllm/models/deepseek_v4/amd/model.py | 12 +-- vllm/models/deepseek_v4/amd/rocm.py | 14 ++-- 5 files changed, 143 insertions(+), 12 deletions(-) diff --git a/vllm/_aiter_ops.py b/vllm/_aiter_ops.py index 658e58db9857..f5673fbcbd72 100644 --- a/vllm/_aiter_ops.py +++ b/vllm/_aiter_ops.py @@ -940,6 +940,39 @@ def _rocm_aiter_triton_gemm_a8w8_blockscale_fake( return Y +def _rocm_aiter_triton_gemm_a8w8_blockscale_preshuffle_impl( + A: torch.Tensor, + B: torch.Tensor, + As: torch.Tensor, + Bs: torch.Tensor, + output_dtype: torch.dtype = torch.bfloat16, +) -> torch.Tensor: + from aiter.ops.triton.gemm_a8w8_blockscale import gemm_a8w8_blockscale_preshuffle + + n, k = B.shape + # B is (16,16)-shuffled and the kernel consumes it as (N // 16, K * 16). + # is_x_scale_tranposed=False makes aiter read As' own strides, so As may be + # either row-major or column-major (the layout its configs are tuned for). + return gemm_a8w8_blockscale_preshuffle( + A, + B.reshape(n // 16, k * 16), + As, + Bs, + dtype=output_dtype, + is_x_scale_tranposed=False, + ) + + +def _rocm_aiter_triton_gemm_a8w8_blockscale_preshuffle_fake( + A: torch.Tensor, + B: torch.Tensor, + As: torch.Tensor, + Bs: torch.Tensor, + output_dtype: torch.dtype = torch.bfloat16, +) -> torch.Tensor: + return torch.empty(A.shape[0], B.shape[0], dtype=output_dtype, device=A.device) + + def _rocm_aiter_gemm_a8w8_blockscale_impl( A: torch.Tensor, B: torch.Tensor, @@ -2874,6 +2907,12 @@ def register_ops_once() -> None: fake_impl=_rocm_aiter_triton_gemm_a8w8_blockscale_fake, ) + direct_register_custom_op( + op_name="rocm_aiter_triton_gemm_a8w8_blockscale_preshuffle", + op_func=_rocm_aiter_triton_gemm_a8w8_blockscale_preshuffle_impl, + fake_impl=_rocm_aiter_triton_gemm_a8w8_blockscale_preshuffle_fake, + ) + direct_register_custom_op( op_name="rocm_aiter_gemm_a8w8_blockscale", op_func=_rocm_aiter_gemm_a8w8_blockscale_impl, @@ -3188,6 +3227,19 @@ def triton_gemm_a8w8_blockscale( A, B, As, Bs, output_dtype ) + @staticmethod + def triton_gemm_a8w8_blockscale_preshuffle( + A: torch.Tensor, + B: torch.Tensor, + As: torch.Tensor, + Bs: torch.Tensor, + block_size: list[int], + output_dtype: torch.dtype = torch.bfloat16, + ) -> torch.Tensor: + return torch.ops.vllm.rocm_aiter_triton_gemm_a8w8_blockscale_preshuffle( + A, B, As, Bs, output_dtype + ) + @staticmethod def gemm_a8w8_blockscale( A: torch.Tensor, @@ -3928,6 +3980,27 @@ def is_triton_gemm_w8a8_tuned(n: int, k: int) -> bool: except (AssertionError, ImportError): return False + @staticmethod + @if_aiter_supported + def is_blockscale_bpreshuffle_gemm_tuned(n: int, k: int) -> bool: + """Whether aiter ships a shape-specialized tuned config for the + B-preshuffled A8W8 blockscale GEMM at this (N, K) on the running arch. + + The non-preshuffled blockscale GEMM has no per-shape tuning on gfx1250, + so this decides whether preshuffling the weight buys tuned kernels. + """ + from pathlib import Path + + try: + import aiter.ops.triton as aiter_triton + from aiter.ops.triton.utils._triton.arch_info import get_arch + + name = f"{get_arch()}-GEMM-A8W8_BLOCKSCALE_PRESHUFFLED-N={n}-K={k}.json" + configs = Path(aiter_triton.__file__).parent / "configs" / "gemm" + except Exception: + return False + return (configs / name).is_file() or (configs / "gluon" / name).is_file() + @staticmethod @functools.cache def is_triton_gemm_afp4wfp4_presh_ws_tuned(n: int, k_bytes: int) -> bool: diff --git a/vllm/envs.py b/vllm/envs.py index 851741d5253b..95a1b9f5d97f 100755 --- a/vllm/envs.py +++ b/vllm/envs.py @@ -156,6 +156,7 @@ VLLM_ROCM_USE_AITER_UNIFIED_ATTENTION: bool = False VLLM_ROCM_USE_AITER_FUSION_SHARED_EXPERTS: bool = False VLLM_ROCM_USE_AITER_TRITON_GEMM: bool = True + VLLM_ROCM_USE_AITER_BLOCKSCALE_BPRESHUFFLE: bool = True VLLM_ROCM_USE_SKINNY_GEMM: bool = True VLLM_ROCM_FP8_PADDING: bool = True VLLM_ROCM_MOE_PADDING: bool = True @@ -1392,6 +1393,13 @@ def _resolve_rust_cli_path() -> str | None: "VLLM_ROCM_USE_AITER_TRITON_GEMM": lambda: ( os.getenv("VLLM_ROCM_USE_AITER_TRITON_GEMM", "True").lower() in ("true", "1") ), + # Whether to B-preshuffle fp8 block-scaled weights at load time so aiter + # dispatches to its tuned A8W8 blockscale preshuffle GEMMs (gfx1250). + # By default is enabled. + "VLLM_ROCM_USE_AITER_BLOCKSCALE_BPRESHUFFLE": lambda: ( + os.getenv("VLLM_ROCM_USE_AITER_BLOCKSCALE_BPRESHUFFLE", "True").lower() + in ("true", "1") + ), # use rocm skinny gemms "VLLM_ROCM_USE_SKINNY_GEMM": lambda: ( os.getenv("VLLM_ROCM_USE_SKINNY_GEMM", "True").lower() in ("true", "1") diff --git a/vllm/model_executor/kernels/linear/scaled_mm/aiter.py b/vllm/model_executor/kernels/linear/scaled_mm/aiter.py index 03e9be51cfa8..c11fd4cb0425 100644 --- a/vllm/model_executor/kernels/linear/scaled_mm/aiter.py +++ b/vllm/model_executor/kernels/linear/scaled_mm/aiter.py @@ -4,11 +4,13 @@ import torch +import vllm.envs as envs from vllm import _custom_ops as ops from vllm._aiter_ops import ( rocm_aiter_ops, ) from vllm.logger import init_logger +from vllm.model_executor.layers.quantization.input_quant_fp8 import QuantFP8 from vllm.model_executor.layers.quantization.utils.fp8_utils import ( _upcast_e8m0_to_fp32, ) @@ -399,6 +401,30 @@ def __init__(self, config: FP8ScaledMMLinearLayerConfig): rocm_aiter_ops.is_triton_gemm_w8a8_tuned(n, k) or _on_gfx1250 ) + # aiter only ships per-shape tuned A8W8 blockscale configs (and the + # gfx1250 gluon kernels) for the B-preshuffled layout, so shuffle the + # weight at load time and dispatch to the preshuffle GEMM. + self.use_bpreshuffle = ( + _on_gfx1250 + and envs.VLLM_ROCM_USE_AITER_BLOCKSCALE_BPRESHUFFLE + and n % 16 == 0 + and k % 128 == 0 + and bool(rocm_aiter_ops.is_blockscale_bpreshuffle_gemm_tuned(n, k)) + ) + if self.use_bpreshuffle: + # The tuned preshuffle configs expect a column-major group scale. + act_scale_descriptor = config.activation_quant_key.scale + self.quant_fp8 = QuantFP8( + static=act_scale_descriptor.static, + group_shape=act_scale_descriptor.group_shape, + num_token_padding=self.get_output_padding(), + use_ue8m0=False, + column_major_scales=True, + ) + logger.debug_once( + "Using aiter A8W8 blockscale B-preshuffle GEMM for N=%d K=%d", n, k + ) + def process_weights_after_loading(self, layer: torch.nn.Module) -> None: super().process_weights_after_loading(layer) @@ -410,6 +436,22 @@ def process_weights_after_loading(self, layer: torch.nn.Module) -> None: if ws is not None and ws.dtype == torch.float8_e8m0fnu: replace_parameter(layer, attr, _upcast_e8m0_to_fp32(ws).contiguous()) + # bmm layers consume the weight as a batch of matrices, not through + # apply_block_scaled_mm, so the shuffled layout does not apply. + if getattr(layer, "is_bmm", False): + self.use_bpreshuffle = False + + if self.use_bpreshuffle: + w = FP8BlockParams.from_layer(layer).weight + replace_parameter( + layer, + FP8BlockParams.WEIGHT, + rocm_aiter_ops.shuffle_weight(w.data, layout=(16, 16)), + ) + # Tells model code that also B-preshuffles (deepseek_v4) not to + # shuffle this weight a second time. + layer.aiter_bpreshuffled = True + @classmethod def is_supported(cls, compute_capability=None): if ( @@ -464,7 +506,11 @@ def apply_block_scaled_mm( Bs = Bs.to(torch.float32) out_dtype = self.config.out_dtype - if self.use_triton: + if self.use_bpreshuffle: + gemm_a8w8_blockscale_op = ( + rocm_aiter_ops.triton_gemm_a8w8_blockscale_preshuffle + ) + elif self.use_triton: gemm_a8w8_blockscale_op = rocm_aiter_ops.triton_gemm_a8w8_blockscale else: gemm_a8w8_blockscale_op = rocm_aiter_ops.gemm_a8w8_blockscale diff --git a/vllm/models/deepseek_v4/amd/model.py b/vllm/models/deepseek_v4/amd/model.py index 8b26fc0060e7..7a465ed48c0a 100644 --- a/vllm/models/deepseek_v4/amd/model.py +++ b/vllm/models/deepseek_v4/amd/model.py @@ -153,11 +153,13 @@ def prepare_gateup_preshuffle(self) -> None: return if ws.dtype == torch.float8_e8m0fnu: ws = _upcast_e8m0_to_fp32(ws).contiguous() - replace_parameter( - self.gate_up_proj, - "weight", - rocm_aiter_ops.shuffle_weight(w.data, layout=(16, 16)), - ) + # Skip when the linear kernel already B-preshuffled this weight. + if not getattr(self.gate_up_proj, "aiter_bpreshuffled", False): + replace_parameter( + self.gate_up_proj, + "weight", + rocm_aiter_ops.shuffle_weight(w.data, layout=(16, 16)), + ) self._gateup_scale = ws def forward(self, x): diff --git a/vllm/models/deepseek_v4/amd/rocm.py b/vllm/models/deepseek_v4/amd/rocm.py index c4c4d416b810..7eb17253b861 100644 --- a/vllm/models/deepseek_v4/amd/rocm.py +++ b/vllm/models/deepseek_v4/amd/rocm.py @@ -916,12 +916,14 @@ def _prep(linear) -> torch.Tensor | None: return None if ws.dtype == torch.float8_e8m0fnu: ws = _upcast_e8m0_to_fp32(ws).contiguous() - # Shuffle the weight in place (single weight, no unshuffled copy). - replace_parameter( - linear, - "weight", - rocm_aiter_ops.shuffle_weight(w.data, layout=(16, 16)), - ) + # Shuffle the weight in place (single weight, no unshuffled copy), + # unless the linear kernel already B-preshuffled it at load time. + if not getattr(linear, "aiter_bpreshuffled", False): + replace_parameter( + linear, + "weight", + rocm_aiter_ops.shuffle_weight(w.data, layout=(16, 16)), + ) return ws self._wqa_wkv_scale = _prep(self.fused_wqa_wkv) From 24b9fa0c6a70c44ef447d06f621298e267abbeb5 Mon Sep 17 00:00:00 2001 From: jpvillam Date: Mon, 10 Aug 2026 14:57:36 -0500 Subject: [PATCH 04/50] Push up rocm version Signed-off-by: jpvillam Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- docker/Dockerfile.rocm_base_gfx1250 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docker/Dockerfile.rocm_base_gfx1250 b/docker/Dockerfile.rocm_base_gfx1250 index 3fad6800ab59..c82ab01fac76 100644 --- a/docker/Dockerfile.rocm_base_gfx1250 +++ b/docker/Dockerfile.rocm_base_gfx1250 @@ -1,11 +1,11 @@ ARG BASE_IMAGE=ubuntu:24.04 ARG ROCM_WHEEL_INDEX=https://rocm.devreleases.amd.com/whl-multi-arch/ -ARG ROCM_SDK_VERSION=7.14.0a20260623 -ARG TORCH_VERSION=2.11.0+rocm7.14.0a20260623 -ARG TORCHVISION_VERSION=0.26.0+rocm7.14.0a20260623 -ARG TORCHAUDIO_VERSION=2.11.0+rocm7.14.0a20260623 -ARG TRITON_VERSION=3.7.1+git110cd8e2.rocm7.14.0a20260623 -ARG APEX_VERSION=1.11.0+rocm7.14.0a20260623 +ARG ROCM_SDK_VERSION=rocm7.15.0a20260712 +ARG TORCH_VERSION=2.11.0+rocm7.15.0a20260712 +ARG TORCHVISION_VERSION=0.26.0+rocm7.15.0a20260712 +ARG TORCHAUDIO_VERSION=2.11.0+rocm7.15.0a20260712 +ARG TRITON_VERSION=3.7.1+git110cd8e2.rocm7.15.0a20260712 +ARG APEX_VERSION=1.11.0+rocm7.15.0a20260712 ARG FA_BRANCH="jpvillam/gfx1250_wip" From 864ea324f230790e86f1f2e65675d66101960284 Mon Sep 17 00:00:00 2001 From: Jaden Mathias Date: Thu, 13 Aug 2026 17:49:09 -0400 Subject: [PATCH 05/50] Update docker args Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- docker/Dockerfile.rocm_base_gfx1250 | 6 +++--- docker/Dockerfile.rocm_gfx1250 | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/Dockerfile.rocm_base_gfx1250 b/docker/Dockerfile.rocm_base_gfx1250 index c82ab01fac76..b51e4cb9837c 100644 --- a/docker/Dockerfile.rocm_base_gfx1250 +++ b/docker/Dockerfile.rocm_base_gfx1250 @@ -1,8 +1,8 @@ ARG BASE_IMAGE=ubuntu:24.04 ARG ROCM_WHEEL_INDEX=https://rocm.devreleases.amd.com/whl-multi-arch/ -ARG ROCM_SDK_VERSION=rocm7.15.0a20260712 -ARG TORCH_VERSION=2.11.0+rocm7.15.0a20260712 -ARG TORCHVISION_VERSION=0.26.0+rocm7.15.0a20260712 +ARG ROCM_SDK_VERSION=7.15.0a20260712 +ARG TORCH_VERSION=2.12.0+rocm7.15.0a20260712 +ARG TORCHVISION_VERSION=0.27.0+rocm7.15.0a20260712 ARG TORCHAUDIO_VERSION=2.11.0+rocm7.15.0a20260712 ARG TRITON_VERSION=3.7.1+git110cd8e2.rocm7.15.0a20260712 ARG APEX_VERSION=1.11.0+rocm7.15.0a20260712 diff --git a/docker/Dockerfile.rocm_gfx1250 b/docker/Dockerfile.rocm_gfx1250 index 8fec28fe67a9..635457949aae 100644 --- a/docker/Dockerfile.rocm_gfx1250 +++ b/docker/Dockerfile.rocm_gfx1250 @@ -116,7 +116,7 @@ FROM base AS fetch_vllm_0 ONBUILD COPY ./ vllm/ FROM base AS fetch_vllm_1 ARG VLLM_REPO="https://github.com/ROCm/vllm.git" -ARG VLLM_BRANCH="455_wip" +ARG VLLM_BRANCH="jpvillam/gfx1250_perf1" ENV VLLM_REPO=${VLLM_REPO} ENV VLLM_BRANCH=${VLLM_BRANCH} ONBUILD RUN git clone ${VLLM_REPO} \ From cf53bc230c1573d74dd5d6a9edc3d799317e1cd6 Mon Sep 17 00:00:00 2001 From: Jaden Mathias Date: Thu, 13 Aug 2026 18:07:08 -0400 Subject: [PATCH 06/50] Narrow torch and rocm sdk device to 1250 Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- docker/Dockerfile.rocm_base_gfx1250 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile.rocm_base_gfx1250 b/docker/Dockerfile.rocm_base_gfx1250 index b51e4cb9837c..2df060fc41cd 100644 --- a/docker/Dockerfile.rocm_base_gfx1250 +++ b/docker/Dockerfile.rocm_base_gfx1250 @@ -119,10 +119,10 @@ ENV PYTHONPATH=${SITE_PACKAGES}/_rocm_sdk_core/share/amd_smi # which keeps the set consistent and avoids pip backtracking. RUN pip install --pre --index-url "${ROCM_WHEEL_INDEX}" \ --extra-index-url https://pypi.org/simple \ - "torch[device-all]==${TORCH_VERSION}" \ + "torch[device-gfx1250]==${TORCH_VERSION}" \ "torchvision==${TORCHVISION_VERSION}" \ "torchaudio==${TORCHAUDIO_VERSION}" \ - "rocm[libraries,devel,device-all]==${ROCM_SDK_VERSION}" && \ + "rocm[libraries,devel,device-gfx1250]==${ROCM_SDK_VERSION}" && \ rocm-sdk init # Torch runtime deps that may not be published on the ROCm wheel index; From e5c643842d7aea10afbace242b86f07b412d5b5b Mon Sep 17 00:00:00 2001 From: Jaden Mathias Date: Mon, 17 Aug 2026 15:32:01 -0400 Subject: [PATCH 07/50] Move gfx1250 SITU handling to oracle mxfp4 Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- .../layers/fused_moe/oracle/mxfp4.py | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py index 5463853caba1..1155c432b184 100644 --- a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py +++ b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py @@ -1719,6 +1719,51 @@ def convert_weight_to_mxfp4_moe_kernel_format( elif mxfp4_backend in TRITON_BACKENDS or ( mxfp4_backend == Mxfp4MoeBackend.AITER_MXFP4_BF16 and is_gfx1250 ): + if ( + mxfp4_backend == Mxfp4MoeBackend.AITER_MXFP4_BF16 + and is_gfx1250 + and activation == MoEActivation.SITU + ): + from aiter.ops.shuffle import moe_shuffle_scale, moe_shuffle_weight + + from vllm._aiter_ops import rocm_aiter_ops + + fp4_dtype = torch.float4_e2m1fn_x2 + e8m0_dtype = torch.float8_e8m0fnu + guinterleave = rocm_aiter_ops.is_fused_moe_situv2_a8w4_enabled() + w13_raw = w13_weight.data.view(fp4_dtype) + w2_raw = w2_weight.data.view(fp4_dtype) + w13_scale_raw = w13_weight_scale.data.view(e8m0_dtype) + w2_scale_raw = w2_weight_scale.data.view(e8m0_dtype) + w13_scale_raw = w13_scale_raw.view(-1, w13_scale_raw.shape[-1]) + w2_scale_raw = w2_scale_raw.view(-1, w2_scale_raw.shape[-1]) + + w13 = moe_shuffle_weight( + w13_raw, num_experts, + is_guinterleave=guinterleave, gate_up=True, + ) + w2 = moe_shuffle_weight( + w2_raw, num_experts, + is_guinterleave=False, gate_up=False, + ) + w13_scale = moe_shuffle_scale( + w13_scale_raw, num_experts, + is_guinterleave=guinterleave, gate_up=True, + ) + w2_scale = moe_shuffle_scale( + w2_scale_raw, num_experts, + is_guinterleave=False, gate_up=False, + ) + w13.is_shuffled = True + w2.is_shuffled = True + + if w13_bias is not None: + w13_bias = w13_bias.data.to(torch.float32) + if w2_bias is not None: + w2_bias = w2_bias.data.to(torch.float32) + + return (w13, w2, w13_scale, w2_scale, w13_bias, w2_bias) + if mxfp4_backend == Mxfp4MoeBackend.AITER_TRITON_MXFP4_BF16: # AITER moe_gemm_a16w4 needs gate/up interleaved def interleave_gate_up(w: torch.Tensor) -> torch.Tensor: From f7f6935d077859dca7fa10b3c4d4e4f7688c4171 Mon Sep 17 00:00:00 2001 From: Jaden Mathias Date: Mon, 17 Aug 2026 15:56:19 -0400 Subject: [PATCH 08/50] Use native AITER shuffle for all gfx1250 MXFP4 MoE activations Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- .../layers/fused_moe/oracle/mxfp4.py | 91 +++++++++---------- .../layers/quantization/mxfp4.py | 20 +--- 2 files changed, 46 insertions(+), 65 deletions(-) diff --git a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py index 1155c432b184..c62a7b5bff85 100644 --- a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py +++ b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py @@ -1622,6 +1622,49 @@ def convert_weight_to_mxfp4_moe_kernel_format( w2_bias, ) + elif mxfp4_backend == Mxfp4MoeBackend.AITER_MXFP4_BF16 and is_gfx1250: + from aiter.ops.shuffle import moe_shuffle_scale, moe_shuffle_weight + + fp4_dtype = torch.float4_e2m1fn_x2 + e8m0_dtype = torch.float8_e8m0fnu + guinterleave = True + if activation == MoEActivation.SITU: + from vllm._aiter_ops import rocm_aiter_ops + guinterleave = rocm_aiter_ops.is_fused_moe_situv2_a8w4_enabled() + + w13_raw = w13_weight.data.view(fp4_dtype) + w2_raw = w2_weight.data.view(fp4_dtype) + w13_scale_raw = w13_weight_scale.data.view(e8m0_dtype) + w2_scale_raw = w2_weight_scale.data.view(e8m0_dtype) + w13_scale_raw = w13_scale_raw.view(-1, w13_scale_raw.shape[-1]) + w2_scale_raw = w2_scale_raw.view(-1, w2_scale_raw.shape[-1]) + + w13 = moe_shuffle_weight( + w13_raw, num_experts, + is_guinterleave=guinterleave, gate_up=True, + ) + w2 = moe_shuffle_weight( + w2_raw, num_experts, + is_guinterleave=False, gate_up=False, + ) + w13_scale = moe_shuffle_scale( + w13_scale_raw, num_experts, + is_guinterleave=guinterleave, gate_up=True, + ) + w2_scale = moe_shuffle_scale( + w2_scale_raw, num_experts, + is_guinterleave=False, gate_up=False, + ) + w13.is_shuffled = True + w2.is_shuffled = True + + if w13_bias is not None: + w13_bias = w13_bias.data.to(torch.float32) + if w2_bias is not None: + w2_bias = w2_bias.data.to(torch.float32) + + return (w13, w2, w13_scale, w2_scale, w13_bias, w2_bias) + elif mxfp4_backend == Mxfp4MoeBackend.AITER_MXFP4_BF16 and not is_gfx1250: # Initially introduced for DeepSeekV4 @@ -1716,53 +1759,7 @@ def convert_weight_to_mxfp4_moe_kernel_format( w2_bias, ) - elif mxfp4_backend in TRITON_BACKENDS or ( - mxfp4_backend == Mxfp4MoeBackend.AITER_MXFP4_BF16 and is_gfx1250 - ): - if ( - mxfp4_backend == Mxfp4MoeBackend.AITER_MXFP4_BF16 - and is_gfx1250 - and activation == MoEActivation.SITU - ): - from aiter.ops.shuffle import moe_shuffle_scale, moe_shuffle_weight - - from vllm._aiter_ops import rocm_aiter_ops - - fp4_dtype = torch.float4_e2m1fn_x2 - e8m0_dtype = torch.float8_e8m0fnu - guinterleave = rocm_aiter_ops.is_fused_moe_situv2_a8w4_enabled() - w13_raw = w13_weight.data.view(fp4_dtype) - w2_raw = w2_weight.data.view(fp4_dtype) - w13_scale_raw = w13_weight_scale.data.view(e8m0_dtype) - w2_scale_raw = w2_weight_scale.data.view(e8m0_dtype) - w13_scale_raw = w13_scale_raw.view(-1, w13_scale_raw.shape[-1]) - w2_scale_raw = w2_scale_raw.view(-1, w2_scale_raw.shape[-1]) - - w13 = moe_shuffle_weight( - w13_raw, num_experts, - is_guinterleave=guinterleave, gate_up=True, - ) - w2 = moe_shuffle_weight( - w2_raw, num_experts, - is_guinterleave=False, gate_up=False, - ) - w13_scale = moe_shuffle_scale( - w13_scale_raw, num_experts, - is_guinterleave=guinterleave, gate_up=True, - ) - w2_scale = moe_shuffle_scale( - w2_scale_raw, num_experts, - is_guinterleave=False, gate_up=False, - ) - w13.is_shuffled = True - w2.is_shuffled = True - - if w13_bias is not None: - w13_bias = w13_bias.data.to(torch.float32) - if w2_bias is not None: - w2_bias = w2_bias.data.to(torch.float32) - - return (w13, w2, w13_scale, w2_scale, w13_bias, w2_bias) + elif mxfp4_backend in TRITON_BACKENDS: if mxfp4_backend == Mxfp4MoeBackend.AITER_TRITON_MXFP4_BF16: # AITER moe_gemm_a16w4 needs gate/up interleaved diff --git a/vllm/model_executor/layers/quantization/mxfp4.py b/vllm/model_executor/layers/quantization/mxfp4.py index 69ddd0045bbc..4b5c6cba4817 100644 --- a/vllm/model_executor/layers/quantization/mxfp4.py +++ b/vllm/model_executor/layers/quantization/mxfp4.py @@ -827,15 +827,7 @@ def _setup_kernel( # For TRITON backends, weights are wrapped tensors from triton_kernels # that don't support .detach(). Manually assign parameters. - is_gfx1250 = False - if current_platform.is_rocm(): - from vllm.platforms.rocm import on_gfx1250 - - is_gfx1250 = on_gfx1250() - - uses_triton_weight_format = self.mxfp4_backend in TRITON_BACKENDS or ( - self.mxfp4_backend == Mxfp4MoeBackend.AITER_MXFP4_BF16 and is_gfx1250 - ) + uses_triton_weight_format = self.mxfp4_backend in TRITON_BACKENDS if not uses_triton_weight_format: replace_parameter(layer, "w13_weight", w13) replace_parameter(layer, "w2_weight", w2) @@ -1036,15 +1028,7 @@ def get_fused_moe_quant_config( w2_bias = getattr(layer, "w2_bias", None) swiglu_limit = getattr(layer, "swiglu_limit", None) - is_gfx1250 = False - if current_platform.is_rocm(): - from vllm.platforms.rocm import on_gfx1250 - - is_gfx1250 = on_gfx1250() - - if self.mxfp4_backend in TRITON_BACKENDS or ( - self.mxfp4_backend == Mxfp4MoeBackend.AITER_MXFP4_BF16 and is_gfx1250 - ): + if self.mxfp4_backend in TRITON_BACKENDS: # TRITON backends free w13/w2_weight_scale after swizzling; the # swizzled scales live inside the precision configs instead. assert self.w13_precision_config is not None From 2612751d192014291fb39617ffe92b27d8698cc8 Mon Sep 17 00:00:00 2001 From: Jaden Mathias Date: Mon, 17 Aug 2026 16:33:28 -0400 Subject: [PATCH 09/50] Enable AITER a4w4 MoE on gfx1250 for DeepSeek R1 Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- .../fused_moe/experts/rocm_aiter_moe.py | 9 +++- .../layers/fused_moe/oracle/mxfp4.py | 54 ++++++++++++++++--- 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/vllm/model_executor/layers/fused_moe/experts/rocm_aiter_moe.py b/vllm/model_executor/layers/fused_moe/experts/rocm_aiter_moe.py index 07eddbf4a092..32abdb713c2c 100644 --- a/vllm/model_executor/layers/fused_moe/experts/rocm_aiter_moe.py +++ b/vllm/model_executor/layers/fused_moe/experts/rocm_aiter_moe.py @@ -501,8 +501,13 @@ def _supports_quant_scheme( if weight_key == kMxfp4Static: from vllm.platforms.rocm import on_gfx950, on_gfx1250 - if not on_gfx950() or on_gfx1250(): - return False + if on_gfx950(): + return True + + if on_gfx1250(): + return activation_key is None or activation_key == kMxfp4Dynamic + + return False return True @staticmethod diff --git a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py index c62a7b5bff85..3e7bf2647e09 100644 --- a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py +++ b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py @@ -883,6 +883,12 @@ def convert_gpt_oss_weight_to_mxfp4_moe_kernel_format( sf_block_size = 32 # mxfp4 block size + is_gfx1250 = False + if current_platform.is_rocm(): + from vllm.platforms.rocm import on_gfx1250 + + is_gfx1250 = on_gfx1250() + if mxfp4_backend == Mxfp4MoeBackend.HUMMING: from vllm.model_executor.layers.quantization.utils.humming import ( convert_to_humming_moe_kernel_format, @@ -1136,13 +1142,47 @@ def swap_every_two_rows(x, axis=-1): ) elif mxfp4_backend == Mxfp4MoeBackend.AITER_MXFP4_MXFP4: - from vllm._aiter_ops import rocm_aiter_ops - if w13_bias is not None: w13_bias = w13_bias.data.to(torch.float32) if w2_bias is not None: w2_bias = w2_bias.data.to(torch.float32) + fp4_dtype = torch.float4_e2m1fn_x2 + e8m0_dtype = torch.float8_e8m0fnu + + if is_gfx1250: + from aiter.ops.shuffle import moe_shuffle_scale, moe_shuffle_weight + + w13_raw = w13_weight.data.view(fp4_dtype) + w2_raw = w2_weight.data.view(fp4_dtype) + w13_scale_raw = w13_weight_scale.data.view(e8m0_dtype) + w2_scale_raw = w2_weight_scale.data.view(e8m0_dtype) + w13_scale_raw = w13_scale_raw.view(-1, w13_scale_raw.shape[-1]) + w2_scale_raw = w2_scale_raw.view(-1, w2_scale_raw.shape[-1]) + + w13 = moe_shuffle_weight( + w13_raw, num_experts, + is_guinterleave=True, gate_up=True, + ) + w2 = moe_shuffle_weight( + w2_raw, num_experts, + is_guinterleave=False, gate_up=False, + ) + w13_scale = moe_shuffle_scale( + w13_scale_raw, num_experts, + is_guinterleave=True, gate_up=True, + ) + w2_scale = moe_shuffle_scale( + w2_scale_raw, num_experts, + is_guinterleave=False, gate_up=False, + ) + w13.is_shuffled = True + w2.is_shuffled = True + + return (w13, w2, w13_scale, w2_scale, w13_bias, w2_bias) + + from vllm._aiter_ops import rocm_aiter_ops + # e8m0_shuffle on weight scales (GFX950 swizzle layout) from aiter.utility.fp4_utils import e8m0_shuffle @@ -1157,10 +1197,8 @@ def swap_every_two_rows(x, axis=-1): ) # View as native FP4 dtype - fp4_dtype = getattr(torch, "float4_e2m1fn_x2", None) - if fp4_dtype is not None: - w13_weight.data = w13_weight.data.view(fp4_dtype) - w2_weight.data = w2_weight.data.view(fp4_dtype) + w13_weight.data = w13_weight.data.view(fp4_dtype) + w2_weight.data = w2_weight.data.view(fp4_dtype) # Shuffle weights for AITER CK kernel shuffled_w13, shuffled_w2 = rocm_aiter_ops.shuffle_weights( @@ -1623,6 +1661,10 @@ def convert_weight_to_mxfp4_moe_kernel_format( ) elif mxfp4_backend == Mxfp4MoeBackend.AITER_MXFP4_BF16 and is_gfx1250: + import os + + os.environ["AITER_BF16_FP8_MOE_BOUND"] = "0" + from aiter.ops.shuffle import moe_shuffle_scale, moe_shuffle_weight fp4_dtype = torch.float4_e2m1fn_x2 From e8fdf03acde6004dbbdb6d0abf960619d6646c7a Mon Sep 17 00:00:00 2001 From: Jaden Mathias Date: Tue, 18 Aug 2026 13:34:06 -0400 Subject: [PATCH 10/50] Enable DSV4F Aiter MoE Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- vllm/model_executor/layers/fused_moe/oracle/mxfp4.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py index 3e7bf2647e09..82fb89980e65 100644 --- a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py +++ b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py @@ -1179,6 +1179,10 @@ def swap_every_two_rows(x, axis=-1): w13.is_shuffled = True w2.is_shuffled = True + if w13_bias is not None: + from aiter.ops.shuffle import interleave_gate_up_rows + w13_bias = interleave_gate_up_rows(w13_bias) + return (w13, w2, w13_scale, w2_scale, w13_bias, w2_bias) from vllm._aiter_ops import rocm_aiter_ops @@ -1702,6 +1706,9 @@ def convert_weight_to_mxfp4_moe_kernel_format( if w13_bias is not None: w13_bias = w13_bias.data.to(torch.float32) + if guinterleave: + from aiter.ops.shuffle import interleave_gate_up_rows + w13_bias = interleave_gate_up_rows(w13_bias) if w2_bias is not None: w2_bias = w2_bias.data.to(torch.float32) From 66669814112f3ad7937cb1626c0b29d2301072cf Mon Sep 17 00:00:00 2001 From: Omar Muhammad Date: Tue, 18 Aug 2026 19:06:30 -0400 Subject: [PATCH 11/50] gfx1250 tp4 e2e patch (#1223) Signed-off-by: Omar Muhammad Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- vllm/models/kimi_k3/amd/ops/third_party/kda/chunk.py | 5 ++++- .../flash_linear_attention/ops/chunk_delta_h.py | 11 ++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/vllm/models/kimi_k3/amd/ops/third_party/kda/chunk.py b/vllm/models/kimi_k3/amd/ops/third_party/kda/chunk.py index 2e975e9f7718..6c138ccae5e1 100644 --- a/vllm/models/kimi_k3/amd/ops/third_party/kda/chunk.py +++ b/vllm/models/kimi_k3/amd/ops/third_party/kda/chunk.py @@ -11,6 +11,7 @@ import torch +from vllm.platforms.rocm import on_gfx1250 as _on_gfx1250 from vllm.third_party.flash_linear_attention.ops.chunk_delta_h import ( chunk_gated_delta_rule_fwd_h, ) @@ -36,7 +37,9 @@ # 4-stage pipeline over it emits a third, consumer-less async copy into LDS that # the `w` loop then reuses; at num_warps=4 that races on gfx950 and `u` comes # back with non-deterministic O(1e38) garbage once a batch reaches 4096 tokens. -_RECOMPUTE_W_U_NUM_STAGES = [2, 3] + +# gfx1250: num_stages 1 needed for now on gfx1250 otherwise accuracy goes to 0 +_RECOMPUTE_W_U_NUM_STAGES = [1] if _on_gfx1250() else [2, 3] @triton.heuristics( diff --git a/vllm/third_party/flash_linear_attention/ops/chunk_delta_h.py b/vllm/third_party/flash_linear_attention/ops/chunk_delta_h.py index eb1c3af15297..9e3dc2ada3bf 100644 --- a/vllm/third_party/flash_linear_attention/ops/chunk_delta_h.py +++ b/vllm/third_party/flash_linear_attention/ops/chunk_delta_h.py @@ -18,7 +18,16 @@ NUM_WARPS = [2, 4, 8, 16] # Triton's AMD backend fails to lower this kernel with num_stages=4. -_CHUNK_DELTA_H_NUM_STAGES = [2, 3] if torch.version.hip else [2, 3, 4] +try: # gfx1250: num_stages 1 is needed for now otherwise accuracy goes to 0 + from vllm.platforms.rocm import on_gfx1250 as _on_gfx1250 + + _IS_GFX1250 = bool(torch.version.hip) and _on_gfx1250() +except Exception: + _IS_GFX1250 = False + +_CHUNK_DELTA_H_NUM_STAGES = ( + [1] if _IS_GFX1250 else ([2, 3] if torch.version.hip else [2, 3, 4]) +) @triton.heuristics( From 366cb28357b03eaaf96c8b4ab69e6e278c4eb0f5 Mon Sep 17 00:00:00 2001 From: Jaden Mathias Date: Tue, 25 Aug 2026 15:43:17 -0400 Subject: [PATCH 12/50] Add aiter Triton a4w4 class Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- .../fused_moe/experts/aiter_mxfp4_w4a8_moe.py | 269 ++++++++++++++++++ .../fused_moe/experts/rocm_aiter_moe.py | 2 +- .../layers/fused_moe/oracle/mxfp4.py | 55 ++-- 3 files changed, 298 insertions(+), 28 deletions(-) diff --git a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py index e85a84064804..54a690d0884b 100644 --- a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py +++ b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py @@ -15,11 +15,15 @@ from vllm.model_executor.layers.quantization.utils.quant_utils import ( QuantKey, kFp8StaticTensorSym, + kMxfp4Dynamic, kMxfp4Static, ) __all__ = [ + "AiterW4A4ExpertsMonolithic", "AiterW4A8ExpertsMonolithic", + "AiterW4A16ExpertsMonolithic", + "aiter_triton_kernel_w4a4_moe_forward", "aiter_triton_kernel_w4a8_moe_forward", ] @@ -674,3 +678,268 @@ def apply( routed_scaling_factor=routed_scaling_factor, score_mode=score_mode, ) + + +def aiter_triton_kernel_w4a4_moe_forward( + hidden_states: torch.Tensor, + w1, + w2, + gating_output: torch.Tensor, + topk: int, + renormalize: bool, + activation: MoEActivation = MoEActivation.SWIGLUOAI, + quant_config: FusedMoEQuantConfig | None = None, + apply_router_weight_on_input: bool = False, + global_num_experts: int = -1, + expert_map: torch.Tensor | None = None, + unpadded_N_w1=None, + unpadded_K_w1=None, + unpadded_N_w2=None, + unpadded_K_w2=None, + num_expert_group: int | None = None, + topk_group: int | None = None, + e_score_correction_bias: torch.Tensor | None = None, + routed_scaling_factor: float | None = None, + score_mode: str | None = None, +): + assert quant_config is not None and rocm_aiter_ops.is_enabled() + + from aiter.ops.triton.moe.moe_op_gemm_a4w4 import moe_gemm_a4w4, mxfp4_quant + + try: + from aiter.ops.triton.moe.moe_routing import routing as _routing_mod + except ImportError: + from aiter.ops.triton.moe_routing import routing as _routing_mod + + from vllm.platforms.rocm import on_gfx1250 + + if on_gfx1250(): + _routing_mod.is_tdm_avail = lambda: False + aiter_routing = _routing_mod.routing + + gating_output = patch_gating_output(gating_output, global_num_experts) + + if score_mode is not None: + use_grouped_topk = ( + num_expert_group is not None and num_expert_group > 1 + ) + routing_data, gather_idx, scatter_idx = aiter_routing( + gating_output, + topk, + score_mode=score_mode, + bias=e_score_correction_bias, + renorm=renormalize, + routed_scaling_factor=( + routed_scaling_factor + if routed_scaling_factor is not None + else 1.0 + ), + use_grouped_topk=use_grouped_topk, + num_expert_group=num_expert_group, + topk_group=topk_group, + ) + else: + routing_data, gather_idx, scatter_idx = aiter_routing( + gating_output, topk, sm_first=not renormalize + ) + + if on_gfx1250(): + gather_src = gather_idx.to(torch.long) // topk + hidden_states = hidden_states[gather_src] + gather_idx = None + + w1_data = _aiter_raw(w1) + w2_data = _aiter_raw(w2) + w1_scale = _aiter_raw(quant_config.w1_scale) + w2_scale = _aiter_raw(quant_config.w2_scale) + + gammas = routing_data.gate_scal if routing_data else None + + swiglu_alpha = ( + quant_config.gemm1_alpha + if quant_config.gemm1_alpha is not None + else 1.0 + ) + swiglu_limit = ( + quant_config.gemm1_clamp_limit + if quant_config.gemm1_clamp_limit is not None + else 7.0 + ) + swiglu_add_residual = activation != MoEActivation.SILU + + swizzle_mx_scale = "GFX1250_SCALE" if on_gfx1250() else None + + x_q, x_scale = mxfp4_quant(hidden_states.to(torch.bfloat16)) + + intermediate = moe_gemm_a4w4( + x_q, + w1_data, + x_scale, + w1_scale, + bias=quant_config.w1_bias, + routing_data=routing_data, + gather_indx=gather_idx, + gammas=gammas if apply_router_weight_on_input else None, + swizzle_mx_scale=swizzle_mx_scale, + apply_swiglu=True, + alpha=swiglu_alpha, + limit=swiglu_limit, + swiglu_add_residual=swiglu_add_residual, + out_dtype=torch.bfloat16, + unpadded_N=unpadded_N_w1, + unpadded_K=unpadded_K_w1, + ) + + if unpadded_N_w1 is not None: + intermediate = intermediate[:, : unpadded_N_w1 // 2] + + mid_q, mid_scale = mxfp4_quant(intermediate.to(torch.bfloat16)) + + out = moe_gemm_a4w4( + mid_q, + w2_data, + mid_scale, + w2_scale, + bias=quant_config.w2_bias, + routing_data=routing_data, + scatter_indx=scatter_idx, + gammas=None if apply_router_weight_on_input else gammas, + swizzle_mx_scale=swizzle_mx_scale, + apply_swiglu=False, + out_dtype=torch.bfloat16, + unpadded_N=unpadded_N_w2, + unpadded_K=unpadded_K_w2, + ) + + return out + + +class AiterW4A4ExpertsMonolithic(mk.FusedMoEExpertsMonolithic): + """Monolithic MXFP4 W4A4 expert using AITER Triton/Gluon kernels (gfx1250). + + Uses moe_gemm_a4w4 (auto-selects gluon backend on gfx1250) with dynamic + MXFP4 activation quantization via mxfp4_quant. + """ + + def __init__( + self, + moe_config: FusedMoEConfig, + quant_config: FusedMoEQuantConfig, + ): + super().__init__(moe_config, quant_config) + self.topk = moe_config.experts_per_token + self.renormalize = moe_config.routing_method in ( + RoutingMethodType.Renormalize, + RoutingMethodType.RenormalizeNaive, + RoutingMethodType.DeepseekV4, + ) + + @staticmethod + def activation_format() -> mk.FusedMoEActivationFormat: + return mk.FusedMoEActivationFormat.Standard + + @staticmethod + def _supports_current_device() -> bool: + if not rocm_aiter_ops.is_enabled(): + return False + from vllm.platforms.rocm import on_gfx1250 + + return on_gfx1250() + + @staticmethod + def _supports_no_act_and_mul() -> bool: + return False + + @staticmethod + def _supports_quant_scheme( + weight_key: QuantKey | None, + activation_key: QuantKey | None, + ) -> bool: + return (weight_key, activation_key) == (kMxfp4Static, kMxfp4Dynamic) + + @staticmethod + def _supports_activation(activation: MoEActivation) -> bool: + return activation in (MoEActivation.SWIGLUOAI, MoEActivation.SILU) + + @staticmethod + def _supports_parallel_config( + moe_parallel_config: FusedMoEParallelConfig, + ) -> bool: + return ( + not moe_parallel_config.use_all2all_kernels + and not moe_parallel_config.enable_eplb + and moe_parallel_config.dp_size <= 1 + ) + + @staticmethod + def _supports_routing_method( + routing_method: RoutingMethodType, + weight_key: QuantKey | None, + activation_key: QuantKey | None, + ) -> bool: + return routing_method in [ + RoutingMethodType.Renormalize, + RoutingMethodType.RenormalizeNaive, + RoutingMethodType.DeepseekV4, + ] + + @staticmethod + def _supports_router_logits_dtype( + router_logits_dtype: torch.dtype | None, + routing_method: RoutingMethodType, + ) -> bool: + return True + + @property + def expects_unquantized_inputs(self) -> bool: + return True + + def apply( + self, + hidden_states: torch.Tensor, + w1: torch.Tensor, + w2: torch.Tensor, + router_logits: torch.Tensor, + activation: MoEActivation, + global_num_experts: int, + expert_map: torch.Tensor | None, + a1q_scale: torch.Tensor | None, + apply_router_weight_on_input: bool, + num_expert_group: int | None = None, + e_score_correction_bias: torch.Tensor | None = None, + routed_scaling_factor: float | None = None, + topk_group: int | None = None, + ) -> torch.Tensor: + assert self.moe_config.intermediate_size_per_partition_unpadded is not None + assert self.moe_config.hidden_dim_unpadded is not None + score_mode = ( + "sqrtsoftplus" + if self.moe_config.routing_method == RoutingMethodType.DeepseekV4 + else None + ) + return aiter_triton_kernel_w4a4_moe_forward( + hidden_states=hidden_states, + w1=w1, + w2=w2, + gating_output=router_logits, + topk=self.topk, + renormalize=self.renormalize, + activation=activation, + global_num_experts=global_num_experts, + expert_map=expert_map, + quant_config=self.quant_config, + apply_router_weight_on_input=apply_router_weight_on_input, + unpadded_N_w1=( + self.moe_config.intermediate_size_per_partition_unpadded * 2 + ), + unpadded_K_w1=self.moe_config.hidden_dim_unpadded, + unpadded_N_w2=self.moe_config.hidden_dim_unpadded, + unpadded_K_w2=( + self.moe_config.intermediate_size_per_partition_unpadded + ), + num_expert_group=num_expert_group, + topk_group=topk_group, + e_score_correction_bias=e_score_correction_bias, + routed_scaling_factor=routed_scaling_factor, + score_mode=score_mode, + ) diff --git a/vllm/model_executor/layers/fused_moe/experts/rocm_aiter_moe.py b/vllm/model_executor/layers/fused_moe/experts/rocm_aiter_moe.py index 32abdb713c2c..a6762cf77f68 100644 --- a/vllm/model_executor/layers/fused_moe/experts/rocm_aiter_moe.py +++ b/vllm/model_executor/layers/fused_moe/experts/rocm_aiter_moe.py @@ -505,7 +505,7 @@ def _supports_quant_scheme( return True if on_gfx1250(): - return activation_key is None or activation_key == kMxfp4Dynamic + return activation_key is None return False return True diff --git a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py index 82fb89980e65..74b787229197 100644 --- a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py +++ b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py @@ -283,11 +283,14 @@ def backend_to_kernel_cls( return [AiterW4A8ExpertsMonolithic] elif backend == Mxfp4MoeBackend.AITER_MXFP4_MXFP4: + from vllm.model_executor.layers.fused_moe.experts.aiter_mxfp4_w4a8_moe import ( + AiterW4A4ExpertsMonolithic, + ) from vllm.model_executor.layers.fused_moe.experts.rocm_aiter_moe import ( AiterExperts, ) - return [AiterExperts] + return [AiterW4A4ExpertsMonolithic, AiterExperts] elif backend == Mxfp4MoeBackend.XPU: from vllm.model_executor.layers.fused_moe.experts.xpu_moe import XPUExpertsMxFp4 @@ -407,6 +410,7 @@ def _get_priority_backends() -> list[Mxfp4MoeBackend]: """ if current_platform.is_rocm(): return [ + Mxfp4MoeBackend.AITER_MXFP4_MXFP4, Mxfp4MoeBackend.AITER_MXFP4_BF16, Mxfp4MoeBackend.EMULATION, ] @@ -741,6 +745,7 @@ def select_deepseek_v4_mxfp4_moe_backend( and config.routing_method == RoutingMethodType.DeepseekV4 ): priority_backends = [ + Mxfp4MoeBackend.AITER_MXFP4_MXFP4, Mxfp4MoeBackend.AITER_MXFP4_BF16, Mxfp4MoeBackend.TRITON_UNFUSED, ] @@ -1151,39 +1156,35 @@ def swap_every_two_rows(x, axis=-1): e8m0_dtype = torch.float8_e8m0fnu if is_gfx1250: - from aiter.ops.shuffle import moe_shuffle_scale, moe_shuffle_weight + from aiter.ops.triton.utils.shuffle import shuffle_scale_moe + + # Triton moe_gemm_a4w4: uint8 column-major weights, + # GFX1250_SCALE swizzled scales. + w13_data = w13_weight.data.view(torch.uint8) + w2_data = w2_weight.data.view(torch.uint8) + + # Make column-major: transpose to [E, K, N] with K-contiguous + w13_data = w13_data.transpose(1, 2).contiguous() + w2_data = w2_data.transpose(1, 2).contiguous() - w13_raw = w13_weight.data.view(fp4_dtype) - w2_raw = w2_weight.data.view(fp4_dtype) w13_scale_raw = w13_weight_scale.data.view(e8m0_dtype) w2_scale_raw = w2_weight_scale.data.view(e8m0_dtype) - w13_scale_raw = w13_scale_raw.view(-1, w13_scale_raw.shape[-1]) - w2_scale_raw = w2_scale_raw.view(-1, w2_scale_raw.shape[-1]) - w13 = moe_shuffle_weight( - w13_raw, num_experts, - is_guinterleave=True, gate_up=True, - ) - w2 = moe_shuffle_weight( - w2_raw, num_experts, - is_guinterleave=False, gate_up=False, + w13_scale = shuffle_scale_moe( + w13_scale_raw.reshape(-1, w13_scale_raw.shape[-1]), + arch="gfx1250", + preshuffle_factor=32, + scale_kwidth=8, ) - w13_scale = moe_shuffle_scale( - w13_scale_raw, num_experts, - is_guinterleave=True, gate_up=True, + w2_scale = shuffle_scale_moe( + w2_scale_raw.reshape(-1, w2_scale_raw.shape[-1]), + arch="gfx1250", + preshuffle_factor=32, + scale_kwidth=8, ) - w2_scale = moe_shuffle_scale( - w2_scale_raw, num_experts, - is_guinterleave=False, gate_up=False, - ) - w13.is_shuffled = True - w2.is_shuffled = True - - if w13_bias is not None: - from aiter.ops.shuffle import interleave_gate_up_rows - w13_bias = interleave_gate_up_rows(w13_bias) - return (w13, w2, w13_scale, w2_scale, w13_bias, w2_bias) + return (w13_data, w2_data, w13_scale, w2_scale, + w13_bias, w2_bias) from vllm._aiter_ops import rocm_aiter_ops From d9820beb4f2b7ab84aba16b959aa75f79a61a745 Mon Sep 17 00:00:00 2001 From: Jaden Mathias Date: Tue, 25 Aug 2026 16:18:30 -0400 Subject: [PATCH 13/50] Fix transpose Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- vllm/model_executor/layers/fused_moe/oracle/mxfp4.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py index 74b787229197..380a6a58a99b 100644 --- a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py +++ b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py @@ -1163,9 +1163,10 @@ def swap_every_two_rows(x, axis=-1): w13_data = w13_weight.data.view(torch.uint8) w2_data = w2_weight.data.view(torch.uint8) - # Make column-major: transpose to [E, K, N] with K-contiguous - w13_data = w13_data.transpose(1, 2).contiguous() - w2_data = w2_data.transpose(1, 2).contiguous() + # Transpose to [E, K, N] — the view has stride(-2)==1 (column-major), + # which is what moe_gemm_a4w4 asserts. + w13_data = w13_data.transpose(1, 2) + w2_data = w2_data.transpose(1, 2) w13_scale_raw = w13_weight_scale.data.view(e8m0_dtype) w2_scale_raw = w2_weight_scale.data.view(e8m0_dtype) From bdb34b3cd32fd15eee98faf708885e7547091083 Mon Sep 17 00:00:00 2001 From: Jaden Mathias Date: Tue, 25 Aug 2026 16:23:30 -0400 Subject: [PATCH 14/50] Fix 2D shapes Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- vllm/model_executor/layers/fused_moe/oracle/mxfp4.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py index 380a6a58a99b..42f290ba14d2 100644 --- a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py +++ b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py @@ -1168,17 +1168,18 @@ def swap_every_two_rows(x, axis=-1): w13_data = w13_data.transpose(1, 2) w2_data = w2_data.transpose(1, 2) + # Scales are [E, N, K_scale]; shuffle_scale_moe expects [E, K_scale, N] w13_scale_raw = w13_weight_scale.data.view(e8m0_dtype) w2_scale_raw = w2_weight_scale.data.view(e8m0_dtype) w13_scale = shuffle_scale_moe( - w13_scale_raw.reshape(-1, w13_scale_raw.shape[-1]), + w13_scale_raw.transpose(1, 2), arch="gfx1250", preshuffle_factor=32, scale_kwidth=8, ) w2_scale = shuffle_scale_moe( - w2_scale_raw.reshape(-1, w2_scale_raw.shape[-1]), + w2_scale_raw.transpose(1, 2), arch="gfx1250", preshuffle_factor=32, scale_kwidth=8, From 0af9f6b3ba911345bcc2a108fc2739715a6a8d91 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 26 Aug 2026 10:27:51 -0400 Subject: [PATCH 15/50] enabling a4w4 moe triton path Signed-off-by: Daniel Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- .../fused_moe/experts/aiter_mxfp4_w4a8_moe.py | 16 +++++- .../layers/fused_moe/oracle/mxfp4.py | 49 +++++++++++-------- 2 files changed, 44 insertions(+), 21 deletions(-) diff --git a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py index 54a690d0884b..e849b4e520d1 100644 --- a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py +++ b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py @@ -1,6 +1,8 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project +import os + import torch import vllm.model_executor.layers.fused_moe.modular_kernel as mk @@ -315,6 +317,12 @@ def apply( ) +def a4w4_backend(): + #Override aiter's moe_gemm_a4w4 backend ("triton" | "gluon"). + #Default is "gluon" on gfx1250. Set VLLM_AITER_A4W4_BACKEND=triton to take triton + + return os.environ.get("VLLM_AITER_A4W4_BACKEND") or None + def _aiter_raw(t): if t is None or isinstance(t, torch.Tensor): @@ -767,7 +775,11 @@ def aiter_triton_kernel_w4a4_moe_forward( ) swiglu_add_residual = activation != MoEActivation.SILU - swizzle_mx_scale = "GFX1250_SCALE" if on_gfx1250() else None + # GFX1250_SCALE is a gluon-only scale layout. Triton kernel only branches on CDNA4_SCALE / None + # convert_gpt_oss_weight_to_mxfp4_moe_kernel_format. + swizzle_mx_scale = ( + "GFX1250_SCALE" if on_gfx1250() and a4w4_backend() != "triton" else None + ) x_q, x_scale = mxfp4_quant(hidden_states.to(torch.bfloat16)) @@ -788,6 +800,7 @@ def aiter_triton_kernel_w4a4_moe_forward( out_dtype=torch.bfloat16, unpadded_N=unpadded_N_w1, unpadded_K=unpadded_K_w1, + backend=a4w4_backend(), ) if unpadded_N_w1 is not None: @@ -809,6 +822,7 @@ def aiter_triton_kernel_w4a4_moe_forward( out_dtype=torch.bfloat16, unpadded_N=unpadded_N_w2, unpadded_K=unpadded_K_w2, + backend=a4w4_backend(), ) return out diff --git a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py index 42f290ba14d2..c3d81c1d2c97 100644 --- a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py +++ b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py @@ -1153,13 +1153,13 @@ def swap_every_two_rows(x, axis=-1): w2_bias = w2_bias.data.to(torch.float32) fp4_dtype = torch.float4_e2m1fn_x2 - e8m0_dtype = torch.float8_e8m0fnu if is_gfx1250: - from aiter.ops.triton.utils.shuffle import shuffle_scale_moe + from vllm.model_executor.layers.fused_moe.experts.aiter_mxfp4_w4a8_moe import ( + a4w4_backend, + ) - # Triton moe_gemm_a4w4: uint8 column-major weights, - # GFX1250_SCALE swizzled scales. + # Triton moe_gemm_a4w4: uint8 column-major weights w13_data = w13_weight.data.view(torch.uint8) w2_data = w2_weight.data.view(torch.uint8) @@ -1168,22 +1168,31 @@ def swap_every_two_rows(x, axis=-1): w13_data = w13_data.transpose(1, 2) w2_data = w2_data.transpose(1, 2) - # Scales are [E, N, K_scale]; shuffle_scale_moe expects [E, K_scale, N] - w13_scale_raw = w13_weight_scale.data.view(e8m0_dtype) - w2_scale_raw = w2_weight_scale.data.view(e8m0_dtype) - - w13_scale = shuffle_scale_moe( - w13_scale_raw.transpose(1, 2), - arch="gfx1250", - preshuffle_factor=32, - scale_kwidth=8, - ) - w2_scale = shuffle_scale_moe( - w2_scale_raw.transpose(1, 2), - arch="gfx1250", - preshuffle_factor=32, - scale_kwidth=8, - ) + # Scales are [E, N, K_scale]; the kernels index [E, K_scale, N], so + # transpose(1, 2) keeps K contiguous. Keep uint8: Triton has no + # float8_e8m0fnu binding, matching mxfp4_quant's uint8 x_scale + w13_scale = w13_weight_scale.data.view(torch.uint8).transpose(1, 2) + w2_scale = w2_weight_scale.data.view(torch.uint8).transpose(1, 2) + + # GFX1250_SCALE is gluon-only; the Triton kernel branches only on + # CDNA4_SCALE / None and would read a swizzled buffer as plain + # [E, K_scale, N], striding past the allocation. Keep in sync with + # swizzle_mx_scale in aiter_triton_kernel_w4a4_moe_forward. + if a4w4_backend() != "triton": + from aiter.ops.triton.utils.shuffle import shuffle_scale_moe + + w13_scale = shuffle_scale_moe( + w13_scale, + arch="gfx1250", + preshuffle_factor=32, + scale_kwidth=8, + ) + w2_scale = shuffle_scale_moe( + w2_scale, + arch="gfx1250", + preshuffle_factor=32, + scale_kwidth=8, + ) return (w13_data, w2_data, w13_scale, w2_scale, w13_bias, w2_bias) From 9194ecc9b079952f4c5bde93891e80e966244b46 Mon Sep 17 00:00:00 2001 From: Jaden Mathias Date: Wed, 26 Aug 2026 10:43:45 -0400 Subject: [PATCH 16/50] Match atom implementation Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- .../fused_moe/experts/aiter_mxfp4_w4a8_moe.py | 57 ++++++++++++------- .../layers/fused_moe/oracle/mxfp4.py | 25 +++++--- 2 files changed, 51 insertions(+), 31 deletions(-) diff --git a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py index e849b4e520d1..bc4d99a93f75 100644 --- a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py +++ b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py @@ -318,9 +318,15 @@ def apply( def a4w4_backend(): - #Override aiter's moe_gemm_a4w4 backend ("triton" | "gluon"). - #Default is "gluon" on gfx1250. Set VLLM_AITER_A4W4_BACKEND=triton to take triton + """Override aiter's moe_gemm_a4w4 backend ("triton" | "gluon"). + Default (None) lets aiter choose, which is "gluon" on gfx1250. Set + VLLM_AITER_A4W4_BACKEND=triton to take the non-gluon path, which avoids the + gfx1250 TDM descriptor API the installed aiter/Triton pair disagrees on. + + Read at weight-load time (oracle/mxfp4.py picks the w_scale layout) as well + as at forward time, so both must see the same value. + """ return os.environ.get("VLLM_AITER_A4W4_BACKEND") or None @@ -763,19 +769,15 @@ def aiter_triton_kernel_w4a4_moe_forward( gammas = routing_data.gate_scal if routing_data else None - swiglu_alpha = ( - quant_config.gemm1_alpha - if quant_config.gemm1_alpha is not None - else 1.0 - ) swiglu_limit = ( quant_config.gemm1_clamp_limit if quant_config.gemm1_clamp_limit is not None else 7.0 ) - swiglu_add_residual = activation != MoEActivation.SILU - - # GFX1250_SCALE is a gluon-only scale layout. Triton kernel only branches on CDNA4_SCALE / None + # GFX1250_SCALE is a gluon-only scale layout. The Triton kernel only + # branches on CDNA4_SCALE / None, so it would read a swizzled buffer with + # plain [E, K_scale, N] indexing and stride off the end of it. Keep this in + # sync with the swizzle decision in the oracle's # convert_gpt_oss_weight_to_mxfp4_moe_kernel_format. swizzle_mx_scale = ( "GFX1250_SCALE" if on_gfx1250() and a4w4_backend() != "triton" else None @@ -783,7 +785,9 @@ def aiter_triton_kernel_w4a4_moe_forward( x_q, x_scale = mxfp4_quant(hidden_states.to(torch.bfloat16)) - intermediate = moe_gemm_a4w4( + # GEMM1: gate+up projection. DeepSeek uses concatenated [gate | up] with + # SiLU activation applied externally (not fused), matching ATOM's approach. + raw_intermediate = moe_gemm_a4w4( x_q, w1_data, x_scale, @@ -793,19 +797,31 @@ def aiter_triton_kernel_w4a4_moe_forward( gather_indx=gather_idx, gammas=gammas if apply_router_weight_on_input else None, swizzle_mx_scale=swizzle_mx_scale, - apply_swiglu=True, - alpha=swiglu_alpha, - limit=swiglu_limit, - swiglu_add_residual=swiglu_add_residual, - out_dtype=torch.bfloat16, - unpadded_N=unpadded_N_w1, - unpadded_K=unpadded_K_w1, + apply_swiglu=False, backend=a4w4_backend(), ) if unpadded_N_w1 is not None: - intermediate = intermediate[:, : unpadded_N_w1 // 2] + raw_intermediate = raw_intermediate[:, :unpadded_N_w1] + + # SiLU(gate) * up on the concatenated [gate | up] halves + from aiter.ops.triton.fusions.fused_clamp_act_mul import fused_clamp_act_mul + + half_n = raw_intermediate.shape[-1] // 2 + intermediate = torch.empty( + raw_intermediate.shape[0], half_n, + dtype=raw_intermediate.dtype, + device=raw_intermediate.device, + ) + fused_clamp_act_mul( + raw_intermediate, + out=intermediate, + swiglu_limit=swiglu_limit, + activation="silu", + dtype_quant=None, + ) + # GEMM2: down projection with scatter-reduce mid_q, mid_scale = mxfp4_quant(intermediate.to(torch.bfloat16)) out = moe_gemm_a4w4( @@ -819,9 +835,6 @@ def aiter_triton_kernel_w4a4_moe_forward( gammas=None if apply_router_weight_on_input else gammas, swizzle_mx_scale=swizzle_mx_scale, apply_swiglu=False, - out_dtype=torch.bfloat16, - unpadded_N=unpadded_N_w2, - unpadded_K=unpadded_K_w2, backend=a4w4_backend(), ) diff --git a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py index c3d81c1d2c97..fbe91f5f83a4 100644 --- a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py +++ b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py @@ -1155,11 +1155,11 @@ def swap_every_two_rows(x, axis=-1): fp4_dtype = torch.float4_e2m1fn_x2 if is_gfx1250: - from vllm.model_executor.layers.fused_moe.experts.aiter_mxfp4_w4a8_moe import ( + from vllm.model_executor.layers.fused_moe.experts.aiter_mxfp4_w4a8_moe import ( # noqa: E501 a4w4_backend, ) - # Triton moe_gemm_a4w4: uint8 column-major weights + # Triton moe_gemm_a4w4: uint8 column-major weights. w13_data = w13_weight.data.view(torch.uint8) w2_data = w2_weight.data.view(torch.uint8) @@ -1168,16 +1168,23 @@ def swap_every_two_rows(x, axis=-1): w13_data = w13_data.transpose(1, 2) w2_data = w2_data.transpose(1, 2) - # Scales are [E, N, K_scale]; the kernels index [E, K_scale, N], so - # transpose(1, 2) keeps K contiguous. Keep uint8: Triton has no - # float8_e8m0fnu binding, matching mxfp4_quant's uint8 x_scale + # Scales are [E, N, K_scale]; both kernels index them as + # [E, K_scale, N] with K innermost, so transpose(1, 2) is the base + # layout (it leaves K contiguous, which the Triton kernel requires). + # Keep them uint8: the shuffle is a pure permute, and the Triton + # moe_gemm_a4w4 takes e8m0 scales as uint8 pointers (Triton has no + # binding for float8_e8m0fnu), matching mxfp4_quant's uint8 x_scale. w13_scale = w13_weight_scale.data.view(torch.uint8).transpose(1, 2) w2_scale = w2_weight_scale.data.view(torch.uint8).transpose(1, 2) - # GFX1250_SCALE is gluon-only; the Triton kernel branches only on - # CDNA4_SCALE / None and would read a swizzled buffer as plain - # [E, K_scale, N], striding past the allocation. Keep in sync with - # swizzle_mx_scale in aiter_triton_kernel_w4a4_moe_forward. + # GFX1250_SCALE is a gluon-only layout. The Triton kernel in + # aiter.ops.triton._triton_kernels.moe.moe_op_gemm_a4w4 only branches + # on CDNA4_SCALE / None, so handing it a swizzled buffer makes it + # index the scales as plain [E, K_scale, N] and stride far past the + # end of the allocation (GPU page fault at model dims, silent garbage + # at small ones). Only swizzle when gluon will actually consume it; + # aiter_triton_kernel_w4a4_moe_forward makes the matching choice for + # swizzle_mx_scale. if a4w4_backend() != "triton": from aiter.ops.triton.utils.shuffle import shuffle_scale_moe From fc22aa957328d776dc81da92ee0cb766bffde37e Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 27 Aug 2026 16:46:52 -0400 Subject: [PATCH 17/50] Fix DeepSeek-V4-Flash on gfx1250 by skipping bpreshuffle GEMM Signed-off-by: Daniel Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- .../layers/fused_moe/oracle/mxfp4.py | 22 ++++++------------- vllm/models/deepseek_v4/amd/model.py | 7 +++++- vllm/models/deepseek_v4/amd/rocm.py | 7 +++++- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py index fbe91f5f83a4..8925cad571d9 100644 --- a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py +++ b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py @@ -1169,22 +1169,14 @@ def swap_every_two_rows(x, axis=-1): w2_data = w2_data.transpose(1, 2) # Scales are [E, N, K_scale]; both kernels index them as - # [E, K_scale, N] with K innermost, so transpose(1, 2) is the base - # layout (it leaves K contiguous, which the Triton kernel requires). - # Keep them uint8: the shuffle is a pure permute, and the Triton - # moe_gemm_a4w4 takes e8m0 scales as uint8 pointers (Triton has no - # binding for float8_e8m0fnu), matching mxfp4_quant's uint8 x_scale. + # [E, K_scale, N] with K innermost, so transpose(1, 2) + # Keep them uint8: Triton moe_gemm_a4w4 takes e8m0 scales w13_scale = w13_weight_scale.data.view(torch.uint8).transpose(1, 2) w2_scale = w2_weight_scale.data.view(torch.uint8).transpose(1, 2) - # GFX1250_SCALE is a gluon-only layout. The Triton kernel in - # aiter.ops.triton._triton_kernels.moe.moe_op_gemm_a4w4 only branches - # on CDNA4_SCALE / None, so handing it a swizzled buffer makes it - # index the scales as plain [E, K_scale, N] and stride far past the - # end of the allocation (GPU page fault at model dims, silent garbage - # at small ones). Only swizzle when gluon will actually consume it; - # aiter_triton_kernel_w4a4_moe_forward makes the matching choice for - # swizzle_mx_scale. + # GFX1250_SCALE is a gluon-only layout + # The a4w4 gfx1250 kernels both hardcode SCALE_KWIDTH = 4 in their + # SWIZZLE_MX_SCALE == "GFX1250_SCALE" branch if a4w4_backend() != "triton": from aiter.ops.triton.utils.shuffle import shuffle_scale_moe @@ -1192,13 +1184,13 @@ def swap_every_two_rows(x, axis=-1): w13_scale, arch="gfx1250", preshuffle_factor=32, - scale_kwidth=8, + scale_kwidth=4, ) w2_scale = shuffle_scale_moe( w2_scale, arch="gfx1250", preshuffle_factor=32, - scale_kwidth=8, + scale_kwidth=4, ) return (w13_data, w2_data, w13_scale, w2_scale, diff --git a/vllm/models/deepseek_v4/amd/model.py b/vllm/models/deepseek_v4/amd/model.py index 7a465ed48c0a..f2bb9bbe5531 100644 --- a/vllm/models/deepseek_v4/amd/model.py +++ b/vllm/models/deepseek_v4/amd/model.py @@ -77,7 +77,7 @@ from vllm.models.deepseek_v4.amd.mega_moe import DeepseekV4AiterMegaMoEExperts from vllm.models.deepseek_v4.amd.rocm import DeepseekV4ROCMAiterMLAAttention from vllm.platforms import current_platform -from vllm.platforms.rocm import on_gfx950 +from vllm.platforms.rocm import on_gfx950, on_gfx1250 from vllm.sequence import IntermediateTensors from ..common.mm_preprocess import IMAGE_SENTINEL_BASE_ID @@ -138,6 +138,11 @@ def prepare_gateup_preshuffle(self) -> None: # B-preshuffle the gate_up_proj weight in place (single weight). if not self._gateup: return + # aiter's gemm_a8w8_blockscale_bpreshuffle has no working gfx1250 path + # Leaving _gateup_scale as None -> "not preshuffled" + # forward() falls back to the standard gate_up_proj linear + if on_gfx1250(): + return from vllm.model_executor.layers.quantization.utils.fp8_utils import ( _upcast_e8m0_to_fp32, get_fp8_block_weight_scale, diff --git a/vllm/models/deepseek_v4/amd/rocm.py b/vllm/models/deepseek_v4/amd/rocm.py index 7eb17253b861..248a4f164b95 100644 --- a/vllm/models/deepseek_v4/amd/rocm.py +++ b/vllm/models/deepseek_v4/amd/rocm.py @@ -23,7 +23,7 @@ DeepseekV4SparseMLAMetadataBuilder, ) from vllm.platforms import current_platform -from vllm.platforms.rocm import _ON_GFX950 +from vllm.platforms.rocm import _ON_GFX950, on_gfx1250 from vllm.triton_utils import tl, triton from vllm.utils.multi_stream_utils import execute_in_parallel from vllm.v1.attention.backend import ( @@ -898,6 +898,11 @@ def prepare_attn_preshuffle(self) -> None: if not rocm_aiter_ops.is_enabled(): return + # aiter's gemm_a8w8_blockscale_bpreshuffle has no working gfx1250 path. + # Leaving the block scales as None -> "not preshuffled" + # _fused_wqa_wkv_gemm and _o_proj both fall back to the standard linear path + if on_gfx1250(): + return from vllm.model_executor.layers.quantization.utils.fp8_utils import ( _upcast_e8m0_to_fp32, get_fp8_block_weight_scale, From d53c75355f737200770a479d7885c1af2054475c Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 27 Aug 2026 17:19:39 -0400 Subject: [PATCH 18/50] enable dsr1 path Signed-off-by: Daniel Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- .../fused_moe/experts/aiter_mxfp4_w4a8_moe.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py index bc4d99a93f75..c3b895815595 100644 --- a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py +++ b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py @@ -859,6 +859,7 @@ def __init__( RoutingMethodType.Renormalize, RoutingMethodType.RenormalizeNaive, RoutingMethodType.DeepseekV4, + RoutingMethodType.DeepSeekV3, ) @staticmethod @@ -908,6 +909,7 @@ def _supports_routing_method( RoutingMethodType.Renormalize, RoutingMethodType.RenormalizeNaive, RoutingMethodType.DeepseekV4, + RoutingMethodType.DeepSeekV3, ] @staticmethod @@ -939,11 +941,15 @@ def apply( ) -> torch.Tensor: assert self.moe_config.intermediate_size_per_partition_unpadded is not None assert self.moe_config.hidden_dim_unpadded is not None - score_mode = ( - "sqrtsoftplus" - if self.moe_config.routing_method == RoutingMethodType.DeepseekV4 - else None - ) + routing_method = self.moe_config.routing_method + if routing_method == RoutingMethodType.DeepseekV4: + score_mode = "sqrtsoftplus" + elif routing_method == RoutingMethodType.DeepSeekV3: + # DeepSeek-R1/V3: sigmoid scores + noaux_tc routing bias, grouped + # top-k and routed_scaling_factor + score_mode = "sigmoid" + else: + score_mode = None return aiter_triton_kernel_w4a4_moe_forward( hidden_states=hidden_states, w1=w1, From 55c71f2e412df9c9a09014c5d9c562ad21b8c015 Mon Sep 17 00:00:00 2001 From: jpvillam Date: Fri, 28 Aug 2026 19:48:47 +0000 Subject: [PATCH 19/50] remove amdsmi build, should be there by default Signed-off-by: jpvillam Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- docker/Dockerfile.rocm_base_gfx1250 | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/docker/Dockerfile.rocm_base_gfx1250 b/docker/Dockerfile.rocm_base_gfx1250 index 2df060fc41cd..e51ef1ba812d 100644 --- a/docker/Dockerfile.rocm_base_gfx1250 +++ b/docker/Dockerfile.rocm_base_gfx1250 @@ -111,7 +111,6 @@ ENV HIP_DEVICE_LIB_PATH=${SITE_PACKAGES}/_rocm_sdk_core/lib/llvm/amdgcn/bitcode ENV PATH=${ROCM_PATH}/bin:${ROCM_PATH}/llvm/bin:$PATH ENV LD_LIBRARY_PATH=${ROCM_PATH}/lib:${SITE_PACKAGES}/_rocm_sdk_core/lib ENV CMAKE_PREFIX_PATH=${ROCM_PATH}/lib/cmake:${SITE_PACKAGES}/torch/share/cmake -ENV PYTHONPATH=${SITE_PACKAGES}/_rocm_sdk_core/share/amd_smi # torch/torchvision/torchaudio must be pinned to mutually-consistent builds # (same +rocm... suffix) or the C++ ops break at import (ABI skew). The rocm @@ -200,15 +199,6 @@ ENV SCCACHE_S3_NO_CREDENTIALS=${USE_SCCACHE:+${SCCACHE_S3_NO_CREDENTIALS}} ENV SCCACHE_IDLE_TIMEOUT=${USE_SCCACHE:+0} -### -### AMD SMI Build -### -FROM base AS build_amdsmi -RUN cd ${SITE_PACKAGES}/_rocm_sdk_core/share/amd_smi \ - && pip wheel . --wheel-dir=dist -RUN mkdir -p /app/install && cp ${SITE_PACKAGES}/_rocm_sdk_core/share/amd_smi/dist/*.whl /app/install - - ### ### MORI Build TODO(Build needs fixing) ### @@ -278,8 +268,6 @@ FROM base AS debs_wheel_release RUN mkdir /app/debs RUN --mount=type=bind,from=build_fa,src=/app/install/,target=/install \ if ls /install/*.whl >/dev/null 2>&1; then cp /install/*.whl /app/debs; fi -RUN --mount=type=bind,from=build_amdsmi,src=/app/install/,target=/install \ - cp /install/*.whl /app/debs RUN --mount=type=bind,from=build_aiter,src=/app/install/,target=/install \ cp /install/*.whl /app/debs @@ -288,8 +276,6 @@ FROM base AS debs RUN mkdir /app/debs RUN --mount=type=bind,from=build_fa,src=/app/install/,target=/install \ if ls /install/*.whl >/dev/null 2>&1; then cp /install/*.whl /app/debs; fi -RUN --mount=type=bind,from=build_amdsmi,src=/app/install/,target=/install \ - cp /install/*.whl /app/debs RUN --mount=type=bind,from=build_aiter,src=/app/install/,target=/install \ cp /install/*.whl /app/debs RUN --mount=type=bind,from=build_mori,src=/app/install/,target=/install \ From a18b6f54d27ccafc03f58b99c59f526e3cb88c36 Mon Sep 17 00:00:00 2001 From: jpvillam Date: Fri, 28 Aug 2026 22:02:35 +0000 Subject: [PATCH 20/50] update rock version and add amdsmi pythonpath Signed-off-by: jpvillam Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- docker/Dockerfile.rocm_base_gfx1250 | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/docker/Dockerfile.rocm_base_gfx1250 b/docker/Dockerfile.rocm_base_gfx1250 index e51ef1ba812d..ea0404685c25 100644 --- a/docker/Dockerfile.rocm_base_gfx1250 +++ b/docker/Dockerfile.rocm_base_gfx1250 @@ -1,11 +1,11 @@ ARG BASE_IMAGE=ubuntu:24.04 -ARG ROCM_WHEEL_INDEX=https://rocm.devreleases.amd.com/whl-multi-arch/ -ARG ROCM_SDK_VERSION=7.15.0a20260712 -ARG TORCH_VERSION=2.12.0+rocm7.15.0a20260712 -ARG TORCHVISION_VERSION=0.27.0+rocm7.15.0a20260712 -ARG TORCHAUDIO_VERSION=2.11.0+rocm7.15.0a20260712 -ARG TRITON_VERSION=3.7.1+git110cd8e2.rocm7.15.0a20260712 -ARG APEX_VERSION=1.11.0+rocm7.15.0a20260712 +ARG ROCM_WHEEL_INDEX=https://rocm.nightlies.amd.com/whl-multi-arch/ +ARG ROCM_SDK_VERSION=10.1.0a20260811+bkc.20260820 +ARG TORCH_VERSION=2.11.0+rocm10.1.0a20260811.bkc.20260820 +ARG TORCHVISION_VERSION=0.26.0+rocm10.1.0a20260811.bkc.20260820 +ARG TORCHAUDIO_VERSION=2.11.0+rocm10.1.0a20260811.bkc.20260820 +ARG TRITON_VERSION=3.8.0+git4cff872c.rocm10.1.0a20260811.bkc.20260820 +ARG APEX_VERSION=1.11.0+rocm10.1.0a20260811.bkc.20260820 ARG FA_BRANCH="jpvillam/gfx1250_wip" @@ -69,6 +69,9 @@ RUN python${PYTHON_VERSION} -m venv "${VIRTUAL_ENV}" && \ "${VIRTUAL_ENV}/bin/python" -m pip install --upgrade pip setuptools PyYAML ENV PATH=${VIRTUAL_ENV}/bin:$PATH +# point to amdsmi python module +ENV PYTHONPATH=${SITE_PACKAGES}/_rocm_sdk_core/share/amd_smi + RUN pip install -U packaging 'cmake<4' ninja wheel 'setuptools<80' pybind11 Cython RUN apt-get update && apt-get install -y libjpeg-dev libsox-dev libsox-fmt-all sox && rm -rf /var/lib/apt/lists/* From e761f0422297fb294bbf643ee6b85502a7aedb6b Mon Sep 17 00:00:00 2001 From: Jaden Mathias Date: Fri, 28 Aug 2026 15:53:23 -0400 Subject: [PATCH 21/50] Fix GPT oss mxfp4 stride Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- .../layers/quantization/utils/mxfp4_utils.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/vllm/model_executor/layers/quantization/utils/mxfp4_utils.py b/vllm/model_executor/layers/quantization/utils/mxfp4_utils.py index 9e985d4688d4..7e2a638ac925 100644 --- a/vllm/model_executor/layers/quantization/utils/mxfp4_utils.py +++ b/vllm/model_executor/layers/quantization/utils/mxfp4_utils.py @@ -153,11 +153,14 @@ def _swizzle_mxfp4(quant_tensor, scale, num_warps=8): if should_use_gfx1250_mx_scale_swizzle(): from aiter.ops.triton.utils.shuffle import shuffle_scale_moe + # preshuffle_factor / scale_kwidth must match PRESHUFFLE_FACTOR and + # SCALE_KWIDTH in the gfx1250 a8w4 kernel's SWIZZLE_MX_SCALE == + # "GFX1250_SCALE" branch, which hardcodes 32 and 4. assert ( - scale.dim() == 3 and scale.shape[-1] % 32 == 0 and scale.shape[-2] % 8 == 0 - ), f"GFX1250 scale swizzle needs (E,K_SCALE%8,N%32); got {tuple(scale.shape)}" + scale.dim() == 3 and scale.shape[-1] % 32 == 0 and scale.shape[-2] % 4 == 0 + ), f"GFX1250 scale swizzle needs (E,K_SCALE%4,N%32); got {tuple(scale.shape)}" scale = shuffle_scale_moe( - scale, arch="gfx1250", preshuffle_factor=32, scale_kwidth=8 + scale, arch="gfx1250", preshuffle_factor=32, scale_kwidth=4 ) quant_tensor = convert_layout( From 239703f76faf7ea9237ae6aaf262e4561e049679 Mon Sep 17 00:00:00 2001 From: Jaden Mathias Date: Fri, 28 Aug 2026 17:38:06 -0400 Subject: [PATCH 22/50] Fix Minimax-m3 routing, activation and weight prep Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- .../fused_moe/experts/aiter_mxfp4_w4a8_moe.py | 86 +++++++++++++------ .../layers/fused_moe/oracle/mxfp4.py | 25 +++++- vllm_benchserve.sh | 8 +- 3 files changed, 90 insertions(+), 29 deletions(-) diff --git a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py index c3b895815595..f5f7d815fd67 100644 --- a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py +++ b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py @@ -774,6 +774,27 @@ def aiter_triton_kernel_w4a4_moe_forward( if quant_config.gemm1_clamp_limit is not None else 7.0 ) + # SWIGLUOAI needs the alpha/residual swiglu fused into the GEMM1 epilogue, + # which reads gate/up interleaved along N (see the oracle's weight prep). + fused_swiglu = activation in ( + MoEActivation.SWIGLUOAI, + MoEActivation.SWIGLUOAI_UNINTERLEAVE, + ) + swiglu_kwargs: dict[str, object] = {} + if fused_swiglu: + assert quant_config.gemm1_beta in (None, 1.0), ( + "aiter's fused swiglu hardcodes the residual to (up + 1); " + f"gemm1_beta={quant_config.gemm1_beta} cannot be expressed" + ) + swiglu_kwargs = { + "alpha": ( + quant_config.gemm1_alpha + if quant_config.gemm1_alpha is not None + else 1.0 + ), + "limit": swiglu_limit, + "swiglu_add_residual": quant_config.gemm1_beta == 1.0, + } # GFX1250_SCALE is a gluon-only scale layout. The Triton kernel only # branches on CDNA4_SCALE / None, so it would read a swizzled buffer with # plain [E, K_scale, N] indexing and stride off the end of it. Keep this in @@ -785,8 +806,7 @@ def aiter_triton_kernel_w4a4_moe_forward( x_q, x_scale = mxfp4_quant(hidden_states.to(torch.bfloat16)) - # GEMM1: gate+up projection. DeepSeek uses concatenated [gate | up] with - # SiLU activation applied externally (not fused), matching ATOM's approach. + # GEMM1: gate+up projection. raw_intermediate = moe_gemm_a4w4( x_q, w1_data, @@ -797,29 +817,38 @@ def aiter_triton_kernel_w4a4_moe_forward( gather_indx=gather_idx, gammas=gammas if apply_router_weight_on_input else None, swizzle_mx_scale=swizzle_mx_scale, - apply_swiglu=False, + apply_swiglu=fused_swiglu, backend=a4w4_backend(), + **swiglu_kwargs, ) - if unpadded_N_w1 is not None: - raw_intermediate = raw_intermediate[:, :unpadded_N_w1] + if fused_swiglu: + # The swiglu epilogue already halved N. + intermediate = ( + raw_intermediate + if unpadded_N_w1 is None + else raw_intermediate[:, : unpadded_N_w1 // 2] + ) + else: + if unpadded_N_w1 is not None: + raw_intermediate = raw_intermediate[:, :unpadded_N_w1] - # SiLU(gate) * up on the concatenated [gate | up] halves - from aiter.ops.triton.fusions.fused_clamp_act_mul import fused_clamp_act_mul + # SiLU(gate) * up on the concatenated [gate | up] halves + from aiter.ops.triton.fusions.fused_clamp_act_mul import fused_clamp_act_mul - half_n = raw_intermediate.shape[-1] // 2 - intermediate = torch.empty( - raw_intermediate.shape[0], half_n, - dtype=raw_intermediate.dtype, - device=raw_intermediate.device, - ) - fused_clamp_act_mul( - raw_intermediate, - out=intermediate, - swiglu_limit=swiglu_limit, - activation="silu", - dtype_quant=None, - ) + half_n = raw_intermediate.shape[-1] // 2 + intermediate = torch.empty( + raw_intermediate.shape[0], half_n, + dtype=raw_intermediate.dtype, + device=raw_intermediate.device, + ) + fused_clamp_act_mul( + raw_intermediate, + out=intermediate, + swiglu_limit=swiglu_limit, + activation="silu", + dtype_quant=None, + ) # GEMM2: down projection with scatter-reduce mid_q, mid_scale = mxfp4_quant(intermediate.to(torch.bfloat16)) @@ -860,6 +889,7 @@ def __init__( RoutingMethodType.RenormalizeNaive, RoutingMethodType.DeepseekV4, RoutingMethodType.DeepSeekV3, + RoutingMethodType.MiniMax2, ) @staticmethod @@ -887,7 +917,11 @@ def _supports_quant_scheme( @staticmethod def _supports_activation(activation: MoEActivation) -> bool: - return activation in (MoEActivation.SWIGLUOAI, MoEActivation.SILU) + return activation in ( + MoEActivation.SWIGLUOAI, + MoEActivation.SWIGLUOAI_UNINTERLEAVE, + MoEActivation.SILU, + ) @staticmethod def _supports_parallel_config( @@ -910,6 +944,7 @@ def _supports_routing_method( RoutingMethodType.RenormalizeNaive, RoutingMethodType.DeepseekV4, RoutingMethodType.DeepSeekV3, + RoutingMethodType.MiniMax2, ] @staticmethod @@ -944,9 +979,12 @@ def apply( routing_method = self.moe_config.routing_method if routing_method == RoutingMethodType.DeepseekV4: score_mode = "sqrtsoftplus" - elif routing_method == RoutingMethodType.DeepSeekV3: - # DeepSeek-R1/V3: sigmoid scores + noaux_tc routing bias, grouped - # top-k and routed_scaling_factor + elif routing_method in ( + RoutingMethodType.DeepSeekV3, + RoutingMethodType.MiniMax2, + ): + # Sigmoid scores + routing bias; grouped for DeepSeek-R1/V3, + # ungrouped for MiniMax-M3. score_mode = "sigmoid" else: score_mode = None diff --git a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py index 8925cad571d9..d675ca293470 100644 --- a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py +++ b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py @@ -844,6 +844,18 @@ def mxfp4_round_up_hidden_size_and_intermediate_size( return hidden_size, intermediate_size +def _interleave_gate_up_rows(t: torch.Tensor) -> torch.Tensor: + """Repack [E, 2n, X] from [all gates | all ups] to [g0, u0, g1, u1, ...].""" + num_experts, two_n, inner = t.shape + assert two_n % 2 == 0, f"expected an even gate/up row count, got {two_n}" + return ( + t.view(num_experts, 2, two_n // 2, inner) + .transpose(1, 2) + .contiguous() + .view(num_experts, two_n, inner) + ) + + def convert_gpt_oss_weight_to_mxfp4_moe_kernel_format( mxfp4_backend: Mxfp4MoeBackend, layer: torch.nn.Module, @@ -1162,6 +1174,17 @@ def swap_every_two_rows(x, axis=-1): # Triton moe_gemm_a4w4: uint8 column-major weights. w13_data = w13_weight.data.view(torch.uint8) w2_data = w2_weight.data.view(torch.uint8) + w13_scale_data = w13_weight_scale.data.view(torch.uint8) + + # SILU applies the activation outside the GEMM and keeps the + # checkpoint's packed halves; the SWIGLUOAI epilogue needs them + # interleaved. + if getattr(layer, "activation", None) in ( + MoEActivation.SWIGLUOAI, + MoEActivation.SWIGLUOAI_UNINTERLEAVE, + ): + w13_data = _interleave_gate_up_rows(w13_data) + w13_scale_data = _interleave_gate_up_rows(w13_scale_data) # Transpose to [E, K, N] — the view has stride(-2)==1 (column-major), # which is what moe_gemm_a4w4 asserts. @@ -1171,7 +1194,7 @@ def swap_every_two_rows(x, axis=-1): # Scales are [E, N, K_scale]; both kernels index them as # [E, K_scale, N] with K innermost, so transpose(1, 2) # Keep them uint8: Triton moe_gemm_a4w4 takes e8m0 scales - w13_scale = w13_weight_scale.data.view(torch.uint8).transpose(1, 2) + w13_scale = w13_scale_data.transpose(1, 2) w2_scale = w2_weight_scale.data.view(torch.uint8).transpose(1, 2) # GFX1250_SCALE is a gluon-only layout diff --git a/vllm_benchserve.sh b/vllm_benchserve.sh index f17344a4c5a9..5d20a51fd36c 100755 --- a/vllm_benchserve.sh +++ b/vllm_benchserve.sh @@ -42,9 +42,9 @@ declare -A MODEL_PATHS=( # --- serve-time environment (verbatim from each serve script) --- declare -A MODEL_ENV=( [gptoss]="HSA_ENABLE_SDMA=0 USE_SVM=0 HSA_XNACK=0 VLLM_ROCM_AITER_FUSED_MOE_TRITON_GEMM_A4W4=1 VLLM_ROCM_USE_AITER=1 VLLM_ROCM_USE_SKINNY_GEMM=0 VLLM_ROCM_USE_AITER_RMSNORM=0" - [dsr1]="VLLM_DISABLE_COMPILE_CACHE=1 VLLM_ROCM_USE_AITER=1 VLLM_ROCM_USE_AITER_MLA=0 HSA_ENABLE_SDMA=0 USE_SVM=0 HSA_XNACK=0 VLLM_ROCM_AITER_FUSED_MOE_TRITON_GEMM_A4W4=1 VLLM_ROCM_USE_AITER_UNIFIED_ATTENTION=1 VLLM_ROCM_USE_SKINNY_GEMM=0 VLLM_ROCM_USE_AITER_RMSNORM=0 VLLM_ROCM_USE_AITER_FP8BMM=0" - [dsv4f]="HSA_ENABLE_SDMA=0 USE_SVM=0 HSA_XNACK=0 VLLM_FORCE_TORCH_BLOCK_FP8=1 VLLM_ROCM_USE_AITER_LINEAR=0 VLLM_ROCM_AITER_FUSED_MOE_TRITON_GEMM_A4W4=0 VLLM_ROCM_USE_AITER=1 VLLM_ROCM_USE_AITER_TRITON_GEMM=1 VLLM_ROCM_USE_AITER_UNIFIED_ATTENTION=1 VLLM_ROCM_USE_SKINNY_GEMM=0 VLLM_ROCM_USE_AITER_RMSNORM=0" - [minimax]="VLLM_DISABLE_COMPILE_CACHE=1 VLLM_ROCM_USE_AITER=1 VLLM_ROCM_USE_AITER_MLA=0 HSA_ENABLE_SDMA=0 USE_SVM=0 HSA_XNACK=0 VLLM_ROCM_AITER_FUSED_MOE_TRITON_GEMM_A4W4=0 VLLM_ROCM_USE_AITER_UNIFIED_ATTENTION=1 VLLM_ROCM_USE_SKINNY_GEMM=0 VLLM_ROCM_USE_AITER_RMSNORM=0 VLLM_ROCM_USE_AITER_FP8BMM=0" + [dsr1]="VLLM_AITER_A4W4_BACKEND=triton VLLM_DISABLE_COMPILE_CACHE=1 VLLM_ROCM_USE_AITER=1 VLLM_ROCM_USE_AITER_MLA=0 HSA_ENABLE_SDMA=0 USE_SVM=0 HSA_XNACK=0 VLLM_ROCM_AITER_FUSED_MOE_TRITON_GEMM_A4W4=1 VLLM_ROCM_USE_AITER_UNIFIED_ATTENTION=1 VLLM_ROCM_USE_SKINNY_GEMM=0 VLLM_ROCM_USE_AITER_RMSNORM=0 VLLM_ROCM_USE_AITER_FP8BMM=0" + [dsv4f]="VLLM_AITER_A4W4_BACKEND=triton VLLM_FORCE_TORCH_BLOCK_FP8=1 VLLM_ROCM_USE_AITER_LINEAR=0 VLLM_ROCM_USE_AITER=1 VLLM_ROCM_USE_AITER_TRITON_GEMM=1 VLLM_ROCM_USE_AITER_UNIFIED_ATTENTION=1 VLLM_ROCM_USE_SKINNY_GEMM=0 VLLM_ROCM_USE_AITER_RMSNORM=0" + [minimax]="VLLM_AITER_A4W4_BACKEND=triton VLLM_DISABLE_COMPILE_CACHE=1 VLLM_ROCM_USE_AITER=1 VLLM_ROCM_USE_AITER_MLA=0 HSA_ENABLE_SDMA=0 USE_SVM=0 HSA_XNACK=0 VLLM_ROCM_AITER_FUSED_MOE_TRITON_GEMM_A4W4=0 VLLM_ROCM_USE_AITER_UNIFIED_ATTENTION=1 VLLM_ROCM_USE_SKINNY_GEMM=0 VLLM_ROCM_USE_AITER_RMSNORM=0 VLLM_ROCM_USE_AITER_FP8BMM=0" ) # --- serve args (everything except --model/--host/--port, which we add). @@ -52,7 +52,7 @@ declare -A MODEL_ENV=( # word-splitting); the dsr1 compilation-config below is space-free. --- declare -A MODEL_SERVE=( [gptoss]="--tensor-parallel-size 1 --gpu_memory_utilization 0.7 --attention-backend TRITON_ATTN" - [dsr1]="--trust-remote-code --no-enable-prefix-caching --no-enable-chunked-prefill --max-model-len 8192 --dtype auto --tensor-parallel-size 1 --distributed-executor-backend mp --max-num-batched-tokens 8192 --max-num-seqs 32 --gpu-memory-utilization 0.90 --compilation-config {\"pass_config\":{\"fuse_attn_quant\":true,\"eliminate_noops\":true,\"fuse_norm_quant\":true,\"fuse_mla_dual_rms_norm\":false,\"enable_qk_norm_rope_fusion\":false},\"cudagraph_mode\":\"FULL_AND_PIECEWISE\",\"custom_ops\":[\"+rms_norm\",\"+silu_and_mul\",\"+quant_fp8\"]}" + [dsr1]="--trust-remote-code --no-enable-prefix-caching --no-enable-chunked-prefill --max-model-len 8192 --dtype auto --tensor-parallel-size 1 --distributed-executor-backend mp --max-num-batched-tokens 8192 --max-num-seqs 32 --gpu-memory-utilization 0.90 --compilation-config {\"mode\":0,\"pass_config\":{\"fuse_attn_quant\":true,\"eliminate_noops\":true,\"fuse_norm_quant\":true,\"fuse_mla_dual_rms_norm\":false,\"enable_qk_norm_rope_fusion\":false},\"cudagraph_mode\":\"FULL_AND_PIECEWISE\",\"custom_ops\":[\"+rms_norm\",\"+silu_and_mul\",\"+quant_fp8\"]}" [dsv4f]="--tensor-parallel-size 1 --gpu_memory_utilization 0.7 --kv-cache-dtype fp8 --max-model-len 32768" [minimax]="--trust-remote-code --language-model-only --skip-mm-profiling --block-size 128 --enforce-eager --no-enable-prefix-caching --no-enable-chunked-prefill --max-model-len 32768 --dtype auto --tensor-parallel-size 1 --distributed-executor-backend mp --max-num-batched-tokens 32768 --max-num-seqs 32 --gpu-memory-utilization 0.90 --reasoning-parser minimax_m3 --tool-call-parser minimax_m3 --enable-auto-tool-choice" ) From 72f307b6a54a9452b42334c9cd03a9dc4a685e15 Mon Sep 17 00:00:00 2001 From: jpvillam Date: Mon, 31 Aug 2026 14:30:37 -0500 Subject: [PATCH 23/50] Clean up Signed-off-by: jpvillam Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- docker/Dockerfile.rocm_base_gfx1250 | 4 +- docker/Dockerfile.rocm_gfx1250 | 4 +- vllm/_aiter_ops.py | 54 +++++++++++-------- vllm/envs.py | 8 --- .../kernels/linear/scaled_mm/aiter.py | 9 ---- .../fused_moe/experts/aiter_mxfp4_w4a8_moe.py | 24 +-------- .../layers/fused_moe/oracle/mxfp4.py | 44 +++++++-------- .../layers/quantization/utils/mxfp4_utils.py | 8 --- .../backends/rocm_aiter_unified_attn.py | 8 --- 9 files changed, 54 insertions(+), 109 deletions(-) diff --git a/docker/Dockerfile.rocm_base_gfx1250 b/docker/Dockerfile.rocm_base_gfx1250 index ea0404685c25..c01301632480 100644 --- a/docker/Dockerfile.rocm_base_gfx1250 +++ b/docker/Dockerfile.rocm_base_gfx1250 @@ -69,9 +69,6 @@ RUN python${PYTHON_VERSION} -m venv "${VIRTUAL_ENV}" && \ "${VIRTUAL_ENV}/bin/python" -m pip install --upgrade pip setuptools PyYAML ENV PATH=${VIRTUAL_ENV}/bin:$PATH -# point to amdsmi python module -ENV PYTHONPATH=${SITE_PACKAGES}/_rocm_sdk_core/share/amd_smi - RUN pip install -U packaging 'cmake<4' ninja wheel 'setuptools<80' pybind11 Cython RUN apt-get update && apt-get install -y libjpeg-dev libsox-dev libsox-fmt-all sox && rm -rf /var/lib/apt/lists/* @@ -114,6 +111,7 @@ ENV HIP_DEVICE_LIB_PATH=${SITE_PACKAGES}/_rocm_sdk_core/lib/llvm/amdgcn/bitcode ENV PATH=${ROCM_PATH}/bin:${ROCM_PATH}/llvm/bin:$PATH ENV LD_LIBRARY_PATH=${ROCM_PATH}/lib:${SITE_PACKAGES}/_rocm_sdk_core/lib ENV CMAKE_PREFIX_PATH=${ROCM_PATH}/lib/cmake:${SITE_PACKAGES}/torch/share/cmake +ENV PYTHONPATH=${SITE_PACKAGES}/_rocm_sdk_core/share/amd_smi # torch/torchvision/torchaudio must be pinned to mutually-consistent builds # (same +rocm... suffix) or the C++ ops break at import (ABI skew). The rocm diff --git a/docker/Dockerfile.rocm_gfx1250 b/docker/Dockerfile.rocm_gfx1250 index 635457949aae..0fbf18c9c84a 100644 --- a/docker/Dockerfile.rocm_gfx1250 +++ b/docker/Dockerfile.rocm_gfx1250 @@ -115,8 +115,8 @@ WORKDIR ${COMMON_WORKDIR} FROM base AS fetch_vllm_0 ONBUILD COPY ./ vllm/ FROM base AS fetch_vllm_1 -ARG VLLM_REPO="https://github.com/ROCm/vllm.git" -ARG VLLM_BRANCH="jpvillam/gfx1250_perf1" +ARG VLLM_REPO="https://github.com/vllm-project/vllm.git" +ARG VLLM_BRANCH="main" ENV VLLM_REPO=${VLLM_REPO} ENV VLLM_BRANCH=${VLLM_BRANCH} ONBUILD RUN git clone ${VLLM_REPO} \ diff --git a/vllm/_aiter_ops.py b/vllm/_aiter_ops.py index f5673fbcbd72..0f3604acd67f 100644 --- a/vllm/_aiter_ops.py +++ b/vllm/_aiter_ops.py @@ -950,9 +950,6 @@ def _rocm_aiter_triton_gemm_a8w8_blockscale_preshuffle_impl( from aiter.ops.triton.gemm_a8w8_blockscale import gemm_a8w8_blockscale_preshuffle n, k = B.shape - # B is (16,16)-shuffled and the kernel consumes it as (N // 16, K * 16). - # is_x_scale_tranposed=False makes aiter read As' own strides, so As may be - # either row-major or column-major (the layout its configs are tuned for). return gemm_a8w8_blockscale_preshuffle( A, B.reshape(n // 16, k * 16), @@ -3720,8 +3717,8 @@ def do_qk_norm_rope_kvcache_update( Shared by the AITER FA and unified-attention impls. The caller splits kv_cache, since the unbind dim depends on the layout (e.g. the unified - encoder-decoder path is K/V-first), and passes use_shuffle_layout to - match the layout its own read path expects. + encoder-decoder path is K/V-first), and passes use_shuffle_layout + (unified reads NHD and must pass False). """ if kv_cache_dtype.startswith("fp8"): key_cache = key_cache.view(current_platform.fp8_dtype()) @@ -3983,23 +3980,36 @@ def is_triton_gemm_w8a8_tuned(n: int, k: int) -> bool: @staticmethod @if_aiter_supported def is_blockscale_bpreshuffle_gemm_tuned(n: int, k: int) -> bool: - """Whether aiter ships a shape-specialized tuned config for the - B-preshuffled A8W8 blockscale GEMM at this (N, K) on the running arch. - - The non-preshuffled blockscale GEMM has no per-shape tuning on gfx1250, - so this decides whether preshuffling the weight buys tuned kernels. - """ - from pathlib import Path - - try: - import aiter.ops.triton as aiter_triton - from aiter.ops.triton.utils._triton.arch_info import get_arch - - name = f"{get_arch()}-GEMM-A8W8_BLOCKSCALE_PRESHUFFLED-N={n}-K={k}.json" - configs = Path(aiter_triton.__file__).parent / "configs" / "gemm" - except Exception: - return False - return (configs / name).is_file() or (configs / "gluon" / name).is_file() + #TODO: Swap this to the proper AITER API for getting the gemm config + # rather then snapshot + gfx1250_tuned = { + (1536,4096), + (32768,1024), + (4096,2048), + (4096,4096), + (4096,81920), + (8192,1024), + (1536, 4096), + (1536, 7168), + (16384, 1536), + (2048, 7168), + (32768, 1024), + (4096, 2048), + (4096, 4096), + (4096, 8192), + (6144, 7168), + (65536, 1536), + (7168, 16384), + (7168, 3072), + (7168, 4096), + (7168, 768), + (8192, 1024), + (8192, 1536), + } + from vllm.platforms.rocm import on_gfx1250 + if on_gfx1250(): + return (n,k) in gfx1250_tuned + return False @staticmethod @functools.cache diff --git a/vllm/envs.py b/vllm/envs.py index 95a1b9f5d97f..851741d5253b 100755 --- a/vllm/envs.py +++ b/vllm/envs.py @@ -156,7 +156,6 @@ VLLM_ROCM_USE_AITER_UNIFIED_ATTENTION: bool = False VLLM_ROCM_USE_AITER_FUSION_SHARED_EXPERTS: bool = False VLLM_ROCM_USE_AITER_TRITON_GEMM: bool = True - VLLM_ROCM_USE_AITER_BLOCKSCALE_BPRESHUFFLE: bool = True VLLM_ROCM_USE_SKINNY_GEMM: bool = True VLLM_ROCM_FP8_PADDING: bool = True VLLM_ROCM_MOE_PADDING: bool = True @@ -1393,13 +1392,6 @@ def _resolve_rust_cli_path() -> str | None: "VLLM_ROCM_USE_AITER_TRITON_GEMM": lambda: ( os.getenv("VLLM_ROCM_USE_AITER_TRITON_GEMM", "True").lower() in ("true", "1") ), - # Whether to B-preshuffle fp8 block-scaled weights at load time so aiter - # dispatches to its tuned A8W8 blockscale preshuffle GEMMs (gfx1250). - # By default is enabled. - "VLLM_ROCM_USE_AITER_BLOCKSCALE_BPRESHUFFLE": lambda: ( - os.getenv("VLLM_ROCM_USE_AITER_BLOCKSCALE_BPRESHUFFLE", "True").lower() - in ("true", "1") - ), # use rocm skinny gemms "VLLM_ROCM_USE_SKINNY_GEMM": lambda: ( os.getenv("VLLM_ROCM_USE_SKINNY_GEMM", "True").lower() in ("true", "1") diff --git a/vllm/model_executor/kernels/linear/scaled_mm/aiter.py b/vllm/model_executor/kernels/linear/scaled_mm/aiter.py index c11fd4cb0425..bb1019c8af6d 100644 --- a/vllm/model_executor/kernels/linear/scaled_mm/aiter.py +++ b/vllm/model_executor/kernels/linear/scaled_mm/aiter.py @@ -4,7 +4,6 @@ import torch -import vllm.envs as envs from vllm import _custom_ops as ops from vllm._aiter_ops import ( rocm_aiter_ops, @@ -401,12 +400,8 @@ def __init__(self, config: FP8ScaledMMLinearLayerConfig): rocm_aiter_ops.is_triton_gemm_w8a8_tuned(n, k) or _on_gfx1250 ) - # aiter only ships per-shape tuned A8W8 blockscale configs (and the - # gfx1250 gluon kernels) for the B-preshuffled layout, so shuffle the - # weight at load time and dispatch to the preshuffle GEMM. self.use_bpreshuffle = ( _on_gfx1250 - and envs.VLLM_ROCM_USE_AITER_BLOCKSCALE_BPRESHUFFLE and n % 16 == 0 and k % 128 == 0 and bool(rocm_aiter_ops.is_blockscale_bpreshuffle_gemm_tuned(n, k)) @@ -436,8 +431,6 @@ def process_weights_after_loading(self, layer: torch.nn.Module) -> None: if ws is not None and ws.dtype == torch.float8_e8m0fnu: replace_parameter(layer, attr, _upcast_e8m0_to_fp32(ws).contiguous()) - # bmm layers consume the weight as a batch of matrices, not through - # apply_block_scaled_mm, so the shuffled layout does not apply. if getattr(layer, "is_bmm", False): self.use_bpreshuffle = False @@ -448,8 +441,6 @@ def process_weights_after_loading(self, layer: torch.nn.Module) -> None: FP8BlockParams.WEIGHT, rocm_aiter_ops.shuffle_weight(w.data, layout=(16, 16)), ) - # Tells model code that also B-preshuffles (deepseek_v4) not to - # shuffle this weight a second time. layer.aiter_bpreshuffled = True @classmethod diff --git a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py index f5f7d815fd67..5f9044c042f0 100644 --- a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py +++ b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py @@ -1,8 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project -import os - import torch import vllm.model_executor.layers.fused_moe.modular_kernel as mk @@ -317,19 +315,6 @@ def apply( ) -def a4w4_backend(): - """Override aiter's moe_gemm_a4w4 backend ("triton" | "gluon"). - - Default (None) lets aiter choose, which is "gluon" on gfx1250. Set - VLLM_AITER_A4W4_BACKEND=triton to take the non-gluon path, which avoids the - gfx1250 TDM descriptor API the installed aiter/Triton pair disagrees on. - - Read at weight-load time (oracle/mxfp4.py picks the w_scale layout) as well - as at forward time, so both must see the same value. - """ - return os.environ.get("VLLM_AITER_A4W4_BACKEND") or None - - def _aiter_raw(t): if t is None or isinstance(t, torch.Tensor): return t @@ -795,13 +780,8 @@ def aiter_triton_kernel_w4a4_moe_forward( "limit": swiglu_limit, "swiglu_add_residual": quant_config.gemm1_beta == 1.0, } - # GFX1250_SCALE is a gluon-only scale layout. The Triton kernel only - # branches on CDNA4_SCALE / None, so it would read a swizzled buffer with - # plain [E, K_scale, N] indexing and stride off the end of it. Keep this in - # sync with the swizzle decision in the oracle's - # convert_gpt_oss_weight_to_mxfp4_moe_kernel_format. swizzle_mx_scale = ( - "GFX1250_SCALE" if on_gfx1250() and a4w4_backend() != "triton" else None + "GFX1250_SCALE" if on_gfx1250() else None ) x_q, x_scale = mxfp4_quant(hidden_states.to(torch.bfloat16)) @@ -818,7 +798,6 @@ def aiter_triton_kernel_w4a4_moe_forward( gammas=gammas if apply_router_weight_on_input else None, swizzle_mx_scale=swizzle_mx_scale, apply_swiglu=fused_swiglu, - backend=a4w4_backend(), **swiglu_kwargs, ) @@ -864,7 +843,6 @@ def aiter_triton_kernel_w4a4_moe_forward( gammas=None if apply_router_weight_on_input else gammas, swizzle_mx_scale=swizzle_mx_scale, apply_swiglu=False, - backend=a4w4_backend(), ) return out diff --git a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py index d675ca293470..d427535bca39 100644 --- a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py +++ b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py @@ -1164,13 +1164,7 @@ def swap_every_two_rows(x, axis=-1): if w2_bias is not None: w2_bias = w2_bias.data.to(torch.float32) - fp4_dtype = torch.float4_e2m1fn_x2 - if is_gfx1250: - from vllm.model_executor.layers.fused_moe.experts.aiter_mxfp4_w4a8_moe import ( # noqa: E501 - a4w4_backend, - ) - # Triton moe_gemm_a4w4: uint8 column-major weights. w13_data = w13_weight.data.view(torch.uint8) w2_data = w2_weight.data.view(torch.uint8) @@ -1197,24 +1191,20 @@ def swap_every_two_rows(x, axis=-1): w13_scale = w13_scale_data.transpose(1, 2) w2_scale = w2_weight_scale.data.view(torch.uint8).transpose(1, 2) - # GFX1250_SCALE is a gluon-only layout - # The a4w4 gfx1250 kernels both hardcode SCALE_KWIDTH = 4 in their - # SWIZZLE_MX_SCALE == "GFX1250_SCALE" branch - if a4w4_backend() != "triton": - from aiter.ops.triton.utils.shuffle import shuffle_scale_moe - - w13_scale = shuffle_scale_moe( - w13_scale, - arch="gfx1250", - preshuffle_factor=32, - scale_kwidth=4, - ) - w2_scale = shuffle_scale_moe( - w2_scale, - arch="gfx1250", - preshuffle_factor=32, - scale_kwidth=4, - ) + from aiter.ops.triton.utils.shuffle import shuffle_scale_moe + + w13_scale = shuffle_scale_moe( + w13_scale, + arch="gfx1250", + preshuffle_factor=32, + scale_kwidth=4, + ) + w2_scale = shuffle_scale_moe( + w2_scale, + arch="gfx1250", + preshuffle_factor=32, + scale_kwidth=4, + ) return (w13_data, w2_data, w13_scale, w2_scale, w13_bias, w2_bias) @@ -1235,8 +1225,10 @@ def swap_every_two_rows(x, axis=-1): ) # View as native FP4 dtype - w13_weight.data = w13_weight.data.view(fp4_dtype) - w2_weight.data = w2_weight.data.view(fp4_dtype) + fp4_dtype = getattr(torch, "float4_e2m1fn_x2", None) + if fp4_dtype is not None: + w13_weight.data = w13_weight.data.view(fp4_dtype) + w2_weight.data = w2_weight.data.view(fp4_dtype) # Shuffle weights for AITER CK kernel shuffled_w13, shuffled_w2 = rocm_aiter_ops.shuffle_weights( diff --git a/vllm/model_executor/layers/quantization/utils/mxfp4_utils.py b/vllm/model_executor/layers/quantization/utils/mxfp4_utils.py index 7e2a638ac925..333fd4aa3ffc 100644 --- a/vllm/model_executor/layers/quantization/utils/mxfp4_utils.py +++ b/vllm/model_executor/layers/quantization/utils/mxfp4_utils.py @@ -14,11 +14,6 @@ def should_use_gfx1250_mx_scale_swizzle() -> bool: - """Whether to pre-shuffle the mxfp4 scales into the gfx1250 layout. - - The weight-load swizzle in `_swizzle_mxfp4` and the SWIZZLE_MX_SCALE - kernel argument in `aiter_mxfp4_w4a8_moe` must agree. - """ if not current_platform.is_rocm(): return False from vllm.platforms.rocm import on_gfx1250 @@ -153,9 +148,6 @@ def _swizzle_mxfp4(quant_tensor, scale, num_warps=8): if should_use_gfx1250_mx_scale_swizzle(): from aiter.ops.triton.utils.shuffle import shuffle_scale_moe - # preshuffle_factor / scale_kwidth must match PRESHUFFLE_FACTOR and - # SCALE_KWIDTH in the gfx1250 a8w4 kernel's SWIZZLE_MX_SCALE == - # "GFX1250_SCALE" branch, which hardcodes 32 and 4. assert ( scale.dim() == 3 and scale.shape[-1] % 32 == 0 and scale.shape[-2] % 4 == 0 ), f"GFX1250 scale swizzle needs (E,K_SCALE%4,N%32); got {tuple(scale.shape)}" diff --git a/vllm/v1/attention/backends/rocm_aiter_unified_attn.py b/vllm/v1/attention/backends/rocm_aiter_unified_attn.py index da42bddcba38..3c6c4025f15e 100644 --- a/vllm/v1/attention/backends/rocm_aiter_unified_attn.py +++ b/vllm/v1/attention/backends/rocm_aiter_unified_attn.py @@ -213,9 +213,6 @@ def _split_kv_cache( def _shuffle_kv_views( self, key_cache: torch.Tensor, value_cache: torch.Tensor ) -> tuple[torch.Tensor, torch.Tensor]: - # No-copy reinterpret of standalone-contiguous K/V (num_blocks, - # block_size, num_kv_heads, head_size) as the 5D shuffle layout the - # gluon kernel reads (the shuffle WRITER laid bytes in this order). nb, bs, nkv, hd = key_cache.shape x = 16 // key_cache.element_size() kc = key_cache.reshape(nb, nkv, hd // x, bs, x) @@ -296,9 +293,6 @@ def forward( key_cache = key_cache.view(self.fp8_dtype) value_cache = value_cache.view(self.fp8_dtype) - # The shuffle writer pre-divides K/V by k/v_scale before the - # (scale=1.0) store, so the stored fp8 matches what - # reshape_and_cache_flash writes and descaling is unchanged. if self._use_shuffle_kv: key_cache, value_cache = self._shuffle_kv_views(key_cache, value_cache) @@ -435,8 +429,6 @@ def do_qk_norm_rope_kvcache_update( kv_cache: torch.Tensor, layer_slot_mapping: torch.Tensor, ): - # _split_kv_cache picks the unbind dim per layout. The writer must - # match what forward() reads back, hence use_shuffle_layout below. key_cache, value_cache = self._split_kv_cache(kv_cache) rocm_aiter_ops.do_qk_norm_rope_kvcache_update( qkv=qkv, From d859357127ba2afff51d6f20657d9637b7746b12 Mon Sep 17 00:00:00 2001 From: jpvillam Date: Mon, 31 Aug 2026 14:32:02 -0500 Subject: [PATCH 24/50] Remove bench file Signed-off-by: jpvillam Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- vllm_benchserve.sh | 183 --------------------------------------------- 1 file changed, 183 deletions(-) delete mode 100755 vllm_benchserve.sh diff --git a/vllm_benchserve.sh b/vllm_benchserve.sh deleted file mode 100755 index 5d20a51fd36c..000000000000 --- a/vllm_benchserve.sh +++ /dev/null @@ -1,183 +0,0 @@ -#!/bin/bash -# Smoke-test + vllm-bench-serve harness for the four gfx1250 models, using the -# exact serve recipes from the per-model scripts in this directory: -# -# gptoss <- gpt.sh (gpt-oss-120b-w-mxfp4-a-fp8) -# dsr1 <- dsr1.sh (DeepSeek-R1-0528-MXFP4) -# dsv4f <- dsr4_accurate.sh (DeepSeek-V4-Flash) -# minimax <- minimax.sh (MiniMax-M3-MXFP4) -# -# For each selected model it: starts vllm serve with that model's env+args -# (server + bench output stream to the terminal), waits for /health, then runs -# `vllm bench serve` with random 1k/1k ISL/OSL at concurrencies 1,4,8,32,64, -# then shuts the server down (freeing the GPU) before the next one. -# -# Usage: -# ./vllm_benchserve.sh # run all four, in order -# ./vllm_benchserve.sh --minimax # run only minimax -# ./vllm_benchserve.sh --gptoss /some/other/path# run gptoss from a custom path -# ./vllm_benchserve.sh --dsr1 --minimax # run a subset -# Each of --gptoss / --dsr1 / --dsv4f / --minimax takes an OPTIONAL model path. -set -uo pipefail - -PORT=8000 -CANONICAL_ORDER=(gptoss dsr1 dsv4f minimax) - -# --- bench-serve parameters --- -INPUT_LEN=1024 -OUTPUT_LEN=1024 -CONCURRENCIES=(64) # default: concurrency 1 only (--long for the full sweep) -LONG_CONCURRENCIES=(1 4 8 32 64) # used when --long is given -# prompts sent per concurrency = concurrency * PROMPTS_PER_CONC -PROMPTS_PER_CONC=10 - -# --- default model paths (from the per-model serve scripts) --- -declare -A MODEL_PATHS=( - [gptoss]="/data/models/gpt-oss-120b-w-mxfp4-a-fp8" - [dsr1]="/data/models/DeepSeek-R1-0528-MXFP4" - [dsv4f]="/data/models/DeepSeek-V4-Flash" - [minimax]="/data/models/MiniMax-M3-MXFP4" -) - -# --- serve-time environment (verbatim from each serve script) --- -declare -A MODEL_ENV=( - [gptoss]="HSA_ENABLE_SDMA=0 USE_SVM=0 HSA_XNACK=0 VLLM_ROCM_AITER_FUSED_MOE_TRITON_GEMM_A4W4=1 VLLM_ROCM_USE_AITER=1 VLLM_ROCM_USE_SKINNY_GEMM=0 VLLM_ROCM_USE_AITER_RMSNORM=0" - [dsr1]="VLLM_AITER_A4W4_BACKEND=triton VLLM_DISABLE_COMPILE_CACHE=1 VLLM_ROCM_USE_AITER=1 VLLM_ROCM_USE_AITER_MLA=0 HSA_ENABLE_SDMA=0 USE_SVM=0 HSA_XNACK=0 VLLM_ROCM_AITER_FUSED_MOE_TRITON_GEMM_A4W4=1 VLLM_ROCM_USE_AITER_UNIFIED_ATTENTION=1 VLLM_ROCM_USE_SKINNY_GEMM=0 VLLM_ROCM_USE_AITER_RMSNORM=0 VLLM_ROCM_USE_AITER_FP8BMM=0" - [dsv4f]="VLLM_AITER_A4W4_BACKEND=triton VLLM_FORCE_TORCH_BLOCK_FP8=1 VLLM_ROCM_USE_AITER_LINEAR=0 VLLM_ROCM_USE_AITER=1 VLLM_ROCM_USE_AITER_TRITON_GEMM=1 VLLM_ROCM_USE_AITER_UNIFIED_ATTENTION=1 VLLM_ROCM_USE_SKINNY_GEMM=0 VLLM_ROCM_USE_AITER_RMSNORM=0" - [minimax]="VLLM_AITER_A4W4_BACKEND=triton VLLM_DISABLE_COMPILE_CACHE=1 VLLM_ROCM_USE_AITER=1 VLLM_ROCM_USE_AITER_MLA=0 HSA_ENABLE_SDMA=0 USE_SVM=0 HSA_XNACK=0 VLLM_ROCM_AITER_FUSED_MOE_TRITON_GEMM_A4W4=0 VLLM_ROCM_USE_AITER_UNIFIED_ATTENTION=1 VLLM_ROCM_USE_SKINNY_GEMM=0 VLLM_ROCM_USE_AITER_RMSNORM=0 VLLM_ROCM_USE_AITER_FP8BMM=0" -) - -# --- serve args (everything except --model/--host/--port, which we add). -# NOTE: JSON values must contain NO spaces (they ride through unquoted $VAR -# word-splitting); the dsr1 compilation-config below is space-free. --- -declare -A MODEL_SERVE=( - [gptoss]="--tensor-parallel-size 1 --gpu_memory_utilization 0.7 --attention-backend TRITON_ATTN" - [dsr1]="--trust-remote-code --no-enable-prefix-caching --no-enable-chunked-prefill --max-model-len 8192 --dtype auto --tensor-parallel-size 1 --distributed-executor-backend mp --max-num-batched-tokens 8192 --max-num-seqs 32 --gpu-memory-utilization 0.90 --compilation-config {\"mode\":0,\"pass_config\":{\"fuse_attn_quant\":true,\"eliminate_noops\":true,\"fuse_norm_quant\":true,\"fuse_mla_dual_rms_norm\":false,\"enable_qk_norm_rope_fusion\":false},\"cudagraph_mode\":\"FULL_AND_PIECEWISE\",\"custom_ops\":[\"+rms_norm\",\"+silu_and_mul\",\"+quant_fp8\"]}" - [dsv4f]="--tensor-parallel-size 1 --gpu_memory_utilization 0.7 --kv-cache-dtype fp8 --max-model-len 32768" - [minimax]="--trust-remote-code --language-model-only --skip-mm-profiling --block-size 128 --enforce-eager --no-enable-prefix-caching --no-enable-chunked-prefill --max-model-len 32768 --dtype auto --tensor-parallel-size 1 --distributed-executor-backend mp --max-num-batched-tokens 32768 --max-num-seqs 32 --gpu-memory-utilization 0.90 --reasoning-parser minimax_m3 --tool-call-parser minimax_m3 --enable-auto-tool-choice" -) - -usage() { - cat < vllm bench serve (random ${INPUT_LEN}/${OUTPUT_LEN} isl/osl @ conc ${CONCURRENCIES[*]}) -> shutdown. -Logs stream to this terminal. -EOF -} - -# --- parse args (each model flag takes an OPTIONAL path) --- -declare -A PATH_OVERRIDE=() -SELECTED=() -while [[ $# -gt 0 ]]; do - case "$1" in - --gptoss|--dsr1|--dsv4f|--minimax) - key="${1#--}" - SELECTED+=("$key") - if [[ $# -ge 2 && "$2" != -* ]]; then PATH_OVERRIDE[$key]="$2"; shift 2; else shift; fi - ;; - --long) CONCURRENCIES=("${LONG_CONCURRENCIES[@]}"); shift ;; - --port) PORT="$2"; shift 2 ;; - --list) for k in "${CANONICAL_ORDER[@]}"; do printf '%-9s -> %s\n' "$k" "${MODEL_PATHS[$k]}"; done; exit 0 ;; - -h|--help) usage; exit 0 ;; - *) echo "Unknown argument: $1" >&2; usage; exit 1 ;; - esac -done -[[ ${#SELECTED[@]} -eq 0 ]] && SELECTED=("${CANONICAL_ORDER[@]}") - -echo "Models: ${SELECTED[*]} | Port: $PORT (all server + bench logs stream to this terminal)" - -server_pid="" -stop_server() { - [[ -n "$server_pid" ]] && kill "$server_pid" 2>/dev/null - # vLLM renames workers to VLLM::EngineCore / VLLM::Worker; killing the launcher - # alone leaves them holding GPU memory, so sweep them too. - pkill -9 -f "vllm serve" 2>/dev/null - pkill -9 -f "VLLM::" 2>/dev/null - server_pid="" - # wait for GPU0 to actually release before the next model loads - if command -v rocm-smi >/dev/null 2>&1; then - for _ in $(seq 1 40); do - used=$(rocm-smi --showmeminfo vram 2>/dev/null | grep 'GPU\[0\]' | grep -i used | grep -oE '[0-9]+' | tail -1) - [[ -z "$used" ]] && break - (( used < 5000000000 )) && break # < ~5 GiB => free - sleep 3 - done - else - sleep 8 - fi -} -trap 'stop_server; exit 130' INT TERM -trap 'stop_server' EXIT - -declare -A RESULT - -run_model() { - local key="$1" - local model="${PATH_OVERRIDE[$key]:-${MODEL_PATHS[$key]}}" - local env="${MODEL_ENV[$key]}" serve="${MODEL_SERVE[$key]}" - - echo; echo "==================== $key : $model ====================" - if [[ ! -e "$model" ]]; then - echo "WARNING: skipping '$key' -- model path does not exist:" >&2 - echo " $model" >&2 - if [[ -n "${PATH_OVERRIDE[$key]:-}" ]]; then - echo " (this path was supplied via '--$key $model'; check the path/mount)" >&2 - else - echo " (this is the default path for '$key'; download the model there," >&2 - echo " or point it elsewhere with: --$key /path/to/model)" >&2 - fi - echo " Continuing with the remaining models." >&2 - RESULT[$key]="SKIP (model path not found: $model)" - return - fi - - # --- start server (logs stream to this terminal) --- - echo "[$key] starting vllm serve ..." - ( export $env; vllm serve --model "$model" --host localhost --port "$PORT" $serve ) & - server_pid=$! - - local ready=0 - for _ in $(seq 1 240); do # up to ~20 min for load - if curl -s "http://localhost:$PORT/health" >/dev/null 2>&1; then ready=1; break; fi - ps -p "$server_pid" >/dev/null 2>&1 || { echo "[$key] ERROR: server died (see output above)"; RESULT[$key]="FAIL (server died at load)"; stop_server; return; } - sleep 5 - done - [[ $ready -eq 1 ]] || { echo "[$key] ERROR: server not ready (timeout)"; RESULT[$key]="FAIL (server timeout)"; stop_server; return; } - echo "[$key] server ready." - - # --- vllm bench serve (random ISL/OSL, sweep concurrencies) --- - local rc=0 - for conc in "${CONCURRENCIES[@]}"; do - local num_prompts=$(( conc * PROMPTS_PER_CONC )) - echo "[$key] === vllm bench serve : conc=$conc isl=$INPUT_LEN osl=$OUTPUT_LEN num_prompts=$num_prompts ===" - if ! vllm bench serve \ - --model "$model" \ - --host localhost --port "$PORT" \ - --dataset-name random \ - --random-input-len "$INPUT_LEN" \ - --random-output-len "$OUTPUT_LEN" \ - --max-concurrency "$conc" \ - --num-prompts "$num_prompts" \ - --ignore-eos - then - echo "[$key] ERROR: bench serve failed at conc=$conc" >&2 - rc=1 - fi - done - if [[ $rc -eq 0 ]]; then RESULT[$key]="PASS (bench tables above)"; else RESULT[$key]="FAIL (bench serve error)"; fi - - stop_server -} - -for key in "${SELECTED[@]}"; do run_model "$key"; done - -echo; echo "==================== SUMMARY ====================" -for key in "${SELECTED[@]}"; do printf '%-9s : %s\n' "$key" "${RESULT[$key]:-UNKNOWN}"; done From 685fa05adfb6d1e19dfed7b4123291e310909613 Mon Sep 17 00:00:00 2001 From: jpvillam Date: Mon, 31 Aug 2026 15:17:58 -0500 Subject: [PATCH 25/50] pull bench, user aiter api for tunned gemms Signed-off-by: jpvillam Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- docker/Dockerfile.rocm_gfx1250 | 1 + vllm/_aiter_ops.py | 37 +++---------------- .../kernels/linear/scaled_mm/aiter.py | 2 +- 3 files changed, 8 insertions(+), 32 deletions(-) diff --git a/docker/Dockerfile.rocm_gfx1250 b/docker/Dockerfile.rocm_gfx1250 index 0fbf18c9c84a..9d86c02f9868 100644 --- a/docker/Dockerfile.rocm_gfx1250 +++ b/docker/Dockerfile.rocm_gfx1250 @@ -726,6 +726,7 @@ RUN echo "VLLM_BASE_IMAGE=${BASE_IMAGE}" >> ${COMMON_WORKDIR}/versions.txt \ # Download bench scripts for gfx1250 RUN curl -k -O https://raw.githubusercontent.com/ROCm/vllm/refs/heads/gfx1250_bench/vllm_smoketest.sh +RUN curl -k -O https://raw.githubusercontent.com/ROCm/vllm/refs/heads/gfx1250_bench/vllm_benchserve.sh ### Install triton from upstream for AITER Deps TODO: (JPVILLAM) If possible to get this on whls it would be better diff --git a/vllm/_aiter_ops.py b/vllm/_aiter_ops.py index 0f3604acd67f..39b6dcab1433 100644 --- a/vllm/_aiter_ops.py +++ b/vllm/_aiter_ops.py @@ -3979,37 +3979,12 @@ def is_triton_gemm_w8a8_tuned(n: int, k: int) -> bool: @staticmethod @if_aiter_supported - def is_blockscale_bpreshuffle_gemm_tuned(n: int, k: int) -> bool: - #TODO: Swap this to the proper AITER API for getting the gemm config - # rather then snapshot - gfx1250_tuned = { - (1536,4096), - (32768,1024), - (4096,2048), - (4096,4096), - (4096,81920), - (8192,1024), - (1536, 4096), - (1536, 7168), - (16384, 1536), - (2048, 7168), - (32768, 1024), - (4096, 2048), - (4096, 4096), - (4096, 8192), - (6144, 7168), - (65536, 1536), - (7168, 16384), - (7168, 3072), - (7168, 4096), - (7168, 768), - (8192, 1024), - (8192, 1536), - } - from vllm.platforms.rocm import on_gfx1250 - if on_gfx1250(): - return (n,k) in gfx1250_tuned - return False + def is_triton_blockscale_bpreshuffle_gemm_tuned(n: int, k: int) -> bool: + from aiter.ops.triton.utils.gemm_config_utils import ( + get_gemm_config, + ) + # Fake M value for now + return get_gemm_config("GEMM-A8W8_BLOCKSCALE_PRESHUFFLED", 128, N, K) @staticmethod @functools.cache diff --git a/vllm/model_executor/kernels/linear/scaled_mm/aiter.py b/vllm/model_executor/kernels/linear/scaled_mm/aiter.py index bb1019c8af6d..b666960a1ce0 100644 --- a/vllm/model_executor/kernels/linear/scaled_mm/aiter.py +++ b/vllm/model_executor/kernels/linear/scaled_mm/aiter.py @@ -404,7 +404,7 @@ def __init__(self, config: FP8ScaledMMLinearLayerConfig): _on_gfx1250 and n % 16 == 0 and k % 128 == 0 - and bool(rocm_aiter_ops.is_blockscale_bpreshuffle_gemm_tuned(n, k)) + and bool(rocm_aiter_ops.is_triton_blockscale_bpreshuffle_gemm_tuned(n, k)) ) if self.use_bpreshuffle: # The tuned preshuffle configs expect a column-major group scale. From 20262a74636a529d7289d349ee7d7f0601a70a3d Mon Sep 17 00:00:00 2001 From: jpvillam Date: Tue, 1 Sep 2026 11:22:02 -0500 Subject: [PATCH 26/50] gemm_config bug Signed-off-by: jpvillam Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- vllm/_aiter_ops.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vllm/_aiter_ops.py b/vllm/_aiter_ops.py index 39b6dcab1433..24ce62540afe 100644 --- a/vllm/_aiter_ops.py +++ b/vllm/_aiter_ops.py @@ -3984,7 +3984,10 @@ def is_triton_blockscale_bpreshuffle_gemm_tuned(n: int, k: int) -> bool: get_gemm_config, ) # Fake M value for now - return get_gemm_config("GEMM-A8W8_BLOCKSCALE_PRESHUFFLED", 128, N, K) + _, is_tuned = get_gemm_config( + "GEMM-A8W8_BLOCKSCALE_PRESHUFFLED", 1, n, k + ) + return is_tuned @staticmethod @functools.cache From 455839c9fc8bfcf4b2b0076cc4c36afef2ec9876 Mon Sep 17 00:00:00 2001 From: Jaden Mathias Date: Fri, 11 Sep 2026 17:26:55 -0400 Subject: [PATCH 27/50] use better rocmsdk10 version (#1296) Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- docker/Dockerfile.rocm_base_gfx1250 | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/docker/Dockerfile.rocm_base_gfx1250 b/docker/Dockerfile.rocm_base_gfx1250 index c01301632480..4208e70bba75 100644 --- a/docker/Dockerfile.rocm_base_gfx1250 +++ b/docker/Dockerfile.rocm_base_gfx1250 @@ -1,16 +1,16 @@ ARG BASE_IMAGE=ubuntu:24.04 ARG ROCM_WHEEL_INDEX=https://rocm.nightlies.amd.com/whl-multi-arch/ -ARG ROCM_SDK_VERSION=10.1.0a20260811+bkc.20260820 -ARG TORCH_VERSION=2.11.0+rocm10.1.0a20260811.bkc.20260820 -ARG TORCHVISION_VERSION=0.26.0+rocm10.1.0a20260811.bkc.20260820 -ARG TORCHAUDIO_VERSION=2.11.0+rocm10.1.0a20260811.bkc.20260820 -ARG TRITON_VERSION=3.8.0+git4cff872c.rocm10.1.0a20260811.bkc.20260820 -ARG APEX_VERSION=1.11.0+rocm10.1.0a20260811.bkc.20260820 +ARG ROCM_SDK_VERSION=10.1.0a20260822 +ARG TORCH_VERSION=2.11.0+rocm10.1.0a20260822 +ARG TORCHVISION_VERSION=0.26.0+rocm10.1.0a20260822 +ARG TORCHAUDIO_VERSION=2.11.0+rocm10.1.0a20260822 +ARG TRITON_VERSION=3.8.0+git4cff872c.rocm10.1.0a20260822 +ARG APEX_VERSION=1.11.0+rocm10.1.0a20260822 ARG FA_BRANCH="jpvillam/gfx1250_wip" ARG FA_REPO="https://github.com/jpvillam-amd/flash-attention.git" -ARG AITER_BRANCH="main" +ARG AITER_BRANCH="7bb449982" ARG AITER_REPO="https://github.com/ROCm/aiter.git" ARG MORI_BRANCH="v1.1.0" ARG MORI_REPO="https://github.com/ROCm/mori.git" @@ -244,7 +244,10 @@ FROM base AS build_aiter ARG AITER_BRANCH ARG AITER_REPO ARG USE_SCCACHE -RUN git clone --recursive --branch ${AITER_BRANCH} ${AITER_REPO} + +RUN git clone ${AITER_REPO} \ + && cd aiter \ + && git checkout --detach ${AITER_BRANCH} RUN cd aiter \ && git submodule update --init --recursive \ && pip install -r requirements.txt From 334fd924af74dae2e149a7656a85168f4dcb1b21 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 14 Sep 2026 18:42:11 -0400 Subject: [PATCH 28/50] deleting _get_padding_mask(), aiter commit addressing the issue already merged upstream Signed-off-by: Daniel Signed-off-by: Juan Villamizar --- .../layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py index 5f9044c042f0..054b49ba98c9 100644 --- a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py +++ b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py @@ -450,11 +450,6 @@ def aiter_triton_kernel_w4a16_moe_forward( _routing_mod.is_tdm_avail = lambda: False aiter_routing = _routing_mod.routing - # See context in #50859. - # TODO: Remove once https://github.com/ROCm/aiter/pull/4530 is merged, - # AITER released, and AITER pin in vLLM increased from 0.1.19. - gating_output = patch_gating_output(gating_output, global_num_experts) - if score_mode is not None: use_grouped_topk = num_expert_group is not None and num_expert_group > 1 routing_data, gather_idx, scatter_idx = aiter_routing( @@ -716,8 +711,6 @@ def aiter_triton_kernel_w4a4_moe_forward( _routing_mod.is_tdm_avail = lambda: False aiter_routing = _routing_mod.routing - gating_output = patch_gating_output(gating_output, global_num_experts) - if score_mode is not None: use_grouped_topk = ( num_expert_group is not None and num_expert_group > 1 From 1f8386a23ed79543d543f4d8c452f4343244b1a4 Mon Sep 17 00:00:00 2001 From: Jaden Mathias Date: Tue, 15 Sep 2026 12:31:11 -0400 Subject: [PATCH 29/50] Pin ainic version Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- docker/Dockerfile.rocm_gfx1250 | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/Dockerfile.rocm_gfx1250 b/docker/Dockerfile.rocm_gfx1250 index 9d86c02f9868..e18add97c840 100644 --- a/docker/Dockerfile.rocm_gfx1250 +++ b/docker/Dockerfile.rocm_gfx1250 @@ -15,6 +15,7 @@ ARG NIC_BACKEND=all # Users can specify a custom version compatible with their host drivers. # The default version has been tested with ionic-dkms=26.03.3.001 # (libionic1=54.0-187-1), required for MoRI-EP/NIXL on Pollara/AINIC. +# TODO (jmathias) : Check if we can revert this Pinned AINIC version ARG AINIC_VERSION=1.117.5-a-77 ARG UBUNTU_CODENAME=jammy From 3481bbc262397a0fae790b7b8557c61a586d4b46 Mon Sep 17 00:00:00 2001 From: jpvillam Date: Thu, 17 Sep 2026 11:09:15 -0500 Subject: [PATCH 30/50] Clean up Signed-off-by: jpvillam Signed-off-by: Juan Villamizar --- vllm/models/deepseek_v4/amd/model.py | 2 +- vllm/models/deepseek_v4/amd/rocm.py | 2 +- vllm/models/kimi_k3/amd/ops/third_party/kda/chunk.py | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/vllm/models/deepseek_v4/amd/model.py b/vllm/models/deepseek_v4/amd/model.py index f2bb9bbe5531..d550b0bfad01 100644 --- a/vllm/models/deepseek_v4/amd/model.py +++ b/vllm/models/deepseek_v4/amd/model.py @@ -138,7 +138,7 @@ def prepare_gateup_preshuffle(self) -> None: # B-preshuffle the gate_up_proj weight in place (single weight). if not self._gateup: return - # aiter's gemm_a8w8_blockscale_bpreshuffle has no working gfx1250 path + # aiter's gemm_a8w8_blockscale_bpreshuffle currently disabled on gfx1250. # Leaving _gateup_scale as None -> "not preshuffled" # forward() falls back to the standard gate_up_proj linear if on_gfx1250(): diff --git a/vllm/models/deepseek_v4/amd/rocm.py b/vllm/models/deepseek_v4/amd/rocm.py index 248a4f164b95..1467f3d6822e 100644 --- a/vllm/models/deepseek_v4/amd/rocm.py +++ b/vllm/models/deepseek_v4/amd/rocm.py @@ -898,7 +898,7 @@ def prepare_attn_preshuffle(self) -> None: if not rocm_aiter_ops.is_enabled(): return - # aiter's gemm_a8w8_blockscale_bpreshuffle has no working gfx1250 path. + # aiter's gemm_a8w8_blockscale_bpreshuffle currently disabled on gfx1250. # Leaving the block scales as None -> "not preshuffled" # _fused_wqa_wkv_gemm and _o_proj both fall back to the standard linear path if on_gfx1250(): diff --git a/vllm/models/kimi_k3/amd/ops/third_party/kda/chunk.py b/vllm/models/kimi_k3/amd/ops/third_party/kda/chunk.py index 6c138ccae5e1..a29a46a66006 100644 --- a/vllm/models/kimi_k3/amd/ops/third_party/kda/chunk.py +++ b/vllm/models/kimi_k3/amd/ops/third_party/kda/chunk.py @@ -37,8 +37,6 @@ # 4-stage pipeline over it emits a third, consumer-less async copy into LDS that # the `w` loop then reuses; at num_warps=4 that races on gfx950 and `u` comes # back with non-deterministic O(1e38) garbage once a batch reaches 4096 tokens. - -# gfx1250: num_stages 1 needed for now on gfx1250 otherwise accuracy goes to 0 _RECOMPUTE_W_U_NUM_STAGES = [1] if _on_gfx1250() else [2, 3] From 60fc8f4a4d58b27881580a3b94ec467b3144732d Mon Sep 17 00:00:00 2001 From: jpvillam Date: Tue, 22 Sep 2026 11:35:52 -0500 Subject: [PATCH 31/50] Drop K3 gfx1250 enablement, upstreaming separately These changes are being upstreamed from jmathis/enable-k3-gfx1250 as their own PR, so remove the duplicates here: - oracle/mxfp4.py: the AITER_MXFP4_BF16 + gfx1250 native-shuffle branch (3cd1a8689c, 0f5d4e5033); gfx1250 goes back to the Triton weight format. - quantization/mxfp4.py: restore the matching gfx1250 special case. - kda/chunk.py, chunk_delta_h.py: restore the upstream num_stages lists. The DSR1/a4w4 work (AITER_MXFP4_MXFP4 path, AiterExperts.supports) is unaffected. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: jpvillam Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- .../layers/fused_moe/oracle/mxfp4.py | 55 +------------------ .../layers/quantization/mxfp4.py | 20 ++++++- .../kimi_k3/amd/ops/third_party/kda/chunk.py | 3 +- .../ops/chunk_delta_h.py | 11 +--- 4 files changed, 23 insertions(+), 66 deletions(-) diff --git a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py index d427535bca39..e1dfb45a7e11 100644 --- a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py +++ b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py @@ -1690,56 +1690,6 @@ def convert_weight_to_mxfp4_moe_kernel_format( w2_bias, ) - elif mxfp4_backend == Mxfp4MoeBackend.AITER_MXFP4_BF16 and is_gfx1250: - import os - - os.environ["AITER_BF16_FP8_MOE_BOUND"] = "0" - - from aiter.ops.shuffle import moe_shuffle_scale, moe_shuffle_weight - - fp4_dtype = torch.float4_e2m1fn_x2 - e8m0_dtype = torch.float8_e8m0fnu - guinterleave = True - if activation == MoEActivation.SITU: - from vllm._aiter_ops import rocm_aiter_ops - guinterleave = rocm_aiter_ops.is_fused_moe_situv2_a8w4_enabled() - - w13_raw = w13_weight.data.view(fp4_dtype) - w2_raw = w2_weight.data.view(fp4_dtype) - w13_scale_raw = w13_weight_scale.data.view(e8m0_dtype) - w2_scale_raw = w2_weight_scale.data.view(e8m0_dtype) - w13_scale_raw = w13_scale_raw.view(-1, w13_scale_raw.shape[-1]) - w2_scale_raw = w2_scale_raw.view(-1, w2_scale_raw.shape[-1]) - - w13 = moe_shuffle_weight( - w13_raw, num_experts, - is_guinterleave=guinterleave, gate_up=True, - ) - w2 = moe_shuffle_weight( - w2_raw, num_experts, - is_guinterleave=False, gate_up=False, - ) - w13_scale = moe_shuffle_scale( - w13_scale_raw, num_experts, - is_guinterleave=guinterleave, gate_up=True, - ) - w2_scale = moe_shuffle_scale( - w2_scale_raw, num_experts, - is_guinterleave=False, gate_up=False, - ) - w13.is_shuffled = True - w2.is_shuffled = True - - if w13_bias is not None: - w13_bias = w13_bias.data.to(torch.float32) - if guinterleave: - from aiter.ops.shuffle import interleave_gate_up_rows - w13_bias = interleave_gate_up_rows(w13_bias) - if w2_bias is not None: - w2_bias = w2_bias.data.to(torch.float32) - - return (w13, w2, w13_scale, w2_scale, w13_bias, w2_bias) - elif mxfp4_backend == Mxfp4MoeBackend.AITER_MXFP4_BF16 and not is_gfx1250: # Initially introduced for DeepSeekV4 @@ -1834,8 +1784,9 @@ def convert_weight_to_mxfp4_moe_kernel_format( w2_bias, ) - elif mxfp4_backend in TRITON_BACKENDS: - + elif mxfp4_backend in TRITON_BACKENDS or ( + mxfp4_backend == Mxfp4MoeBackend.AITER_MXFP4_BF16 and is_gfx1250 + ): if mxfp4_backend == Mxfp4MoeBackend.AITER_TRITON_MXFP4_BF16: # AITER moe_gemm_a16w4 needs gate/up interleaved def interleave_gate_up(w: torch.Tensor) -> torch.Tensor: diff --git a/vllm/model_executor/layers/quantization/mxfp4.py b/vllm/model_executor/layers/quantization/mxfp4.py index 4b5c6cba4817..69ddd0045bbc 100644 --- a/vllm/model_executor/layers/quantization/mxfp4.py +++ b/vllm/model_executor/layers/quantization/mxfp4.py @@ -827,7 +827,15 @@ def _setup_kernel( # For TRITON backends, weights are wrapped tensors from triton_kernels # that don't support .detach(). Manually assign parameters. - uses_triton_weight_format = self.mxfp4_backend in TRITON_BACKENDS + is_gfx1250 = False + if current_platform.is_rocm(): + from vllm.platforms.rocm import on_gfx1250 + + is_gfx1250 = on_gfx1250() + + uses_triton_weight_format = self.mxfp4_backend in TRITON_BACKENDS or ( + self.mxfp4_backend == Mxfp4MoeBackend.AITER_MXFP4_BF16 and is_gfx1250 + ) if not uses_triton_weight_format: replace_parameter(layer, "w13_weight", w13) replace_parameter(layer, "w2_weight", w2) @@ -1028,7 +1036,15 @@ def get_fused_moe_quant_config( w2_bias = getattr(layer, "w2_bias", None) swiglu_limit = getattr(layer, "swiglu_limit", None) - if self.mxfp4_backend in TRITON_BACKENDS: + is_gfx1250 = False + if current_platform.is_rocm(): + from vllm.platforms.rocm import on_gfx1250 + + is_gfx1250 = on_gfx1250() + + if self.mxfp4_backend in TRITON_BACKENDS or ( + self.mxfp4_backend == Mxfp4MoeBackend.AITER_MXFP4_BF16 and is_gfx1250 + ): # TRITON backends free w13/w2_weight_scale after swizzling; the # swizzled scales live inside the precision configs instead. assert self.w13_precision_config is not None diff --git a/vllm/models/kimi_k3/amd/ops/third_party/kda/chunk.py b/vllm/models/kimi_k3/amd/ops/third_party/kda/chunk.py index a29a46a66006..2e975e9f7718 100644 --- a/vllm/models/kimi_k3/amd/ops/third_party/kda/chunk.py +++ b/vllm/models/kimi_k3/amd/ops/third_party/kda/chunk.py @@ -11,7 +11,6 @@ import torch -from vllm.platforms.rocm import on_gfx1250 as _on_gfx1250 from vllm.third_party.flash_linear_attention.ops.chunk_delta_h import ( chunk_gated_delta_rule_fwd_h, ) @@ -37,7 +36,7 @@ # 4-stage pipeline over it emits a third, consumer-less async copy into LDS that # the `w` loop then reuses; at num_warps=4 that races on gfx950 and `u` comes # back with non-deterministic O(1e38) garbage once a batch reaches 4096 tokens. -_RECOMPUTE_W_U_NUM_STAGES = [1] if _on_gfx1250() else [2, 3] +_RECOMPUTE_W_U_NUM_STAGES = [2, 3] @triton.heuristics( diff --git a/vllm/third_party/flash_linear_attention/ops/chunk_delta_h.py b/vllm/third_party/flash_linear_attention/ops/chunk_delta_h.py index 9e3dc2ada3bf..eb1c3af15297 100644 --- a/vllm/third_party/flash_linear_attention/ops/chunk_delta_h.py +++ b/vllm/third_party/flash_linear_attention/ops/chunk_delta_h.py @@ -18,16 +18,7 @@ NUM_WARPS = [2, 4, 8, 16] # Triton's AMD backend fails to lower this kernel with num_stages=4. -try: # gfx1250: num_stages 1 is needed for now otherwise accuracy goes to 0 - from vllm.platforms.rocm import on_gfx1250 as _on_gfx1250 - - _IS_GFX1250 = bool(torch.version.hip) and _on_gfx1250() -except Exception: - _IS_GFX1250 = False - -_CHUNK_DELTA_H_NUM_STAGES = ( - [1] if _IS_GFX1250 else ([2, 3] if torch.version.hip else [2, 3, 4]) -) +_CHUNK_DELTA_H_NUM_STAGES = [2, 3] if torch.version.hip else [2, 3, 4] @triton.heuristics( From 9e6be53766a445ad159d8989a01068a359c23ab7 Mon Sep 17 00:00:00 2001 From: Jaden Mathias Date: Thu, 24 Sep 2026 19:13:57 -0400 Subject: [PATCH 32/50] DSV4 Core dump fix Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- .../fused_moe/experts/aiter_mxfp4_w4a8_moe.py | 31 ++++++++++++++++++- vllm/model_executor/layers/fused_moe/layer.py | 1 + .../layers/fused_moe/modular_kernel.py | 27 +++++++++++++++- .../layers/fused_moe/routed_experts.py | 2 ++ .../layers/quantization/mxfp4.py | 4 +++ vllm/models/deepseek_v4/amd/rocm.py | 5 ++- 6 files changed, 67 insertions(+), 3 deletions(-) diff --git a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py index 054b49ba98c9..9466015cac70 100644 --- a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py +++ b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py @@ -695,6 +695,8 @@ def aiter_triton_kernel_w4a4_moe_forward( e_score_correction_bias: torch.Tensor | None = None, routed_scaling_factor: float | None = None, score_mode: str | None = None, + input_ids: torch.Tensor | None = None, + hash_indices_table: torch.Tensor | None = None, ): assert quant_config is not None and rocm_aiter_ops.is_enabled() @@ -711,7 +713,26 @@ def aiter_triton_kernel_w4a4_moe_forward( _routing_mod.is_tdm_avail = lambda: False aiter_routing = _routing_mod.routing - if score_mode is not None: + if hash_indices_table is not None: + assert input_ids is not None, "hash routing requires input_ids" + n_tokens, n_expts_tot = gating_output.shape + tokens_per_expt = max(1, n_tokens * topk // n_expts_tot) + block_m = max(16, min(1 << (tokens_per_expt - 1).bit_length(), 128)) + routing_data, gather_idx, scatter_idx = _routing_mod.routing_from_hash( + gating_output, + hash_indices_table, + input_ids.to(hash_indices_table.dtype), + topk, + block_m, + score_mode=score_mode or "sqrtsoftplus", + renorm=renormalize, + routed_scaling_factor=( + routed_scaling_factor + if routed_scaling_factor is not None + else 1.0 + ), + ) + elif score_mode is not None: use_grouped_topk = ( num_expert_group is not None and num_expert_group > 1 ) @@ -867,6 +888,10 @@ def __init__( def activation_format() -> mk.FusedMoEActivationFormat: return mk.FusedMoEActivationFormat.Standard + @staticmethod + def _supports_hash_routing() -> bool: + return True + @staticmethod def _supports_current_device() -> bool: if not rocm_aiter_ops.is_enabled(): @@ -944,6 +969,8 @@ def apply( e_score_correction_bias: torch.Tensor | None = None, routed_scaling_factor: float | None = None, topk_group: int | None = None, + input_ids: torch.Tensor | None = None, + hash_indices_table: torch.Tensor | None = None, ) -> torch.Tensor: assert self.moe_config.intermediate_size_per_partition_unpadded is not None assert self.moe_config.hidden_dim_unpadded is not None @@ -984,4 +1011,6 @@ def apply( e_score_correction_bias=e_score_correction_bias, routed_scaling_factor=routed_scaling_factor, score_mode=score_mode, + input_ids=input_ids, + hash_indices_table=hash_indices_table, ) diff --git a/vllm/model_executor/layers/fused_moe/layer.py b/vllm/model_executor/layers/fused_moe/layer.py index 294c538c7b02..746e0f3b9f9e 100644 --- a/vllm/model_executor/layers/fused_moe/layer.py +++ b/vllm/model_executor/layers/fused_moe/layer.py @@ -409,6 +409,7 @@ def FusedMoEFactory( # TODO get from router? needs to be truncated? e_score_correction_bias=e_score_correction_bias, apply_router_weight_on_input=apply_router_weight_on_input, + hash_indices_table=hash_indices_table, **routed_experts_args if routed_experts_args is not None else {}, ) diff --git a/vllm/model_executor/layers/fused_moe/modular_kernel.py b/vllm/model_executor/layers/fused_moe/modular_kernel.py index 7a4cbd423746..4473f1c9b911 100644 --- a/vllm/model_executor/layers/fused_moe/modular_kernel.py +++ b/vllm/model_executor/layers/fused_moe/modular_kernel.py @@ -567,7 +567,11 @@ def _make_reason(reason: str) -> str: return False, _make_reason( f"parallel config {moe_config.moe_parallel_config}" ) - elif moe_config.has_hash_routing and cls.is_monolithic(): + elif ( + moe_config.has_hash_routing + and cls.is_monolithic() + and not cls._supports_hash_routing() + ): return False, _make_reason("hash routing") elif not cls._supports_routing_method( moe_config.routing_method, weight_key, activation_key @@ -645,6 +649,15 @@ def _supports_routing_method( """ return True + @staticmethod + def _supports_hash_routing() -> bool: + """Whether a monolithic kernel implements hash routing itself. + + Kernels that return True receive `input_ids` and `hash_indices_table` + in `apply()` for layers with a hash-routing table. + """ + return False + @staticmethod def _supports_router_logits_dtype( router_logits_dtype: torch.dtype | None, @@ -1558,6 +1571,8 @@ def apply( topk_group: int | None = None, *, routing_sink: RoutedExpertsSink | None, + input_ids: torch.Tensor | None = None, + hash_indices_table: torch.Tensor | None = None, ) -> torch.Tensor | UnfinalizedMoEOutput: """Same as forward(), except uses router_logits as opposed to the topk_ids and topk_weights. This is used for kernels @@ -1579,6 +1594,11 @@ def apply( f"kernel {type(self.fused_experts).__name__}." ) routing_replay_out = routing_sink.buffer[: len(a1q)] + hash_kwargs = ( + {"input_ids": input_ids, "hash_indices_table": hash_indices_table} + if hash_indices_table is not None + else {} + ) fused_out = self.fused_experts.apply( hidden_states=a1q, w1=w1, @@ -1595,6 +1615,7 @@ def apply( routed_scaling_factor=routed_scaling_factor, topk_group=topk_group, routing_replay_out=routing_replay_out, + **hash_kwargs, ) if routing_sink is not None: routing_sink.capture_fn(routing_replay_out) @@ -1707,6 +1728,8 @@ def apply_monolithic( topk_group: int | None = None, *, routing_sink: RoutedExpertsSink | None, + input_ids: torch.Tensor | None = None, + hash_indices_table: torch.Tensor | None = None, ) -> torch.Tensor | UnfinalizedMoEOutput: assert isinstance(self.impl, FusedMoEKernelMonolithicImpl) return self.impl.apply( @@ -1723,6 +1746,8 @@ def apply_monolithic( routed_scaling_factor=routed_scaling_factor, topk_group=topk_group, routing_sink=routing_sink, + input_ids=input_ids, + hash_indices_table=hash_indices_table, ) def apply( diff --git a/vllm/model_executor/layers/fused_moe/routed_experts.py b/vllm/model_executor/layers/fused_moe/routed_experts.py index adc8a2701007..769f7c635f32 100644 --- a/vllm/model_executor/layers/fused_moe/routed_experts.py +++ b/vllm/model_executor/layers/fused_moe/routed_experts.py @@ -106,6 +106,7 @@ def __init__( swiglu_beta: float | None = None, e_score_correction_bias: torch.Tensor | None = None, apply_router_weight_on_input: bool = False, + hash_indices_table: torch.Tensor | None = None, ): super().__init__() self.layer_name = layer_name @@ -151,6 +152,7 @@ def __init__( # Set by bind_routed_experts_capturer for monolithic kernels. self.routing_sink: RoutedExpertsSink | None = None self.apply_router_weight_on_input = apply_router_weight_on_input + self.hash_indices_table = hash_indices_table # End random parameters self._loaded_expert_biases: set[str] = set() diff --git a/vllm/model_executor/layers/quantization/mxfp4.py b/vllm/model_executor/layers/quantization/mxfp4.py index 69ddd0045bbc..8b4253f672f0 100644 --- a/vllm/model_executor/layers/quantization/mxfp4.py +++ b/vllm/model_executor/layers/quantization/mxfp4.py @@ -476,6 +476,8 @@ def apply_monolithic( e_score_correction_bias=layer.e_score_correction_bias, routed_scaling_factor=layer.routed_scaling_factor, routing_sink=layer.routing_sink, + input_ids=input_ids, + hash_indices_table=layer.hash_indices_table, ) @@ -1125,4 +1127,6 @@ def apply_monolithic( e_score_correction_bias=layer.e_score_correction_bias, routed_scaling_factor=layer.routed_scaling_factor, routing_sink=layer.routing_sink, + input_ids=input_ids, + hash_indices_table=layer.hash_indices_table, ) diff --git a/vllm/models/deepseek_v4/amd/rocm.py b/vllm/models/deepseek_v4/amd/rocm.py index 1467f3d6822e..29e377f0aa76 100644 --- a/vllm/models/deepseek_v4/amd/rocm.py +++ b/vllm/models/deepseek_v4/amd/rocm.py @@ -1291,11 +1291,14 @@ def forward_mqa( # the rotation into the decode reduce would apply it twice. Only the # BF16 einsum path hands its rotation off to the decode. fuse_inv_rope = self._wo_a_fp8_weight is None + fuse_decode_inv_rope = fuse_inv_rope and not on_gfx1250() rotated = 0 if num_decodes > 0: rotated = self._forward_decode( q=q[:num_decode_tokens], - positions=positions[:num_decode_tokens] if fuse_inv_rope else None, + positions=( + positions[:num_decode_tokens] if fuse_decode_inv_rope else None + ), kv_cache=self_kv_cache, swa_metadata=swa_metadata, attn_metadata=rocm_metadata, From 3a173d29b8728f3bfd1bf0a899b13b6bd59bb072 Mon Sep 17 00:00:00 2001 From: Jaden Mathias Date: Tue, 29 Sep 2026 19:22:44 -0400 Subject: [PATCH 33/50] Split AiterW4A16ExpertsMonolithic and AiterW4A4ExpertsMonolithic out of aiter_mxfp4_w4a8_moe.py into their own files, keeping gfx1250 support. Signed-off-by: Jaden Mathias Co-Authored-By: Claude Opus 5.5 (1M context) Signed-off-by: Juan Villamizar --- .../experts/aiter_mxfp4_w4a16_moe.py | 10 +- .../fused_moe/experts/aiter_mxfp4_w4a4_moe.py | 372 +++++++++ .../fused_moe/experts/aiter_mxfp4_w4a8_moe.py | 705 ------------------ .../layers/fused_moe/oracle/mxfp4.py | 2 +- 4 files changed, 382 insertions(+), 707 deletions(-) create mode 100644 vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a4_moe.py diff --git a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a16_moe.py b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a16_moe.py index 5423a93b5fa7..d4550a845279 100644 --- a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a16_moe.py +++ b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a16_moe.py @@ -53,9 +53,17 @@ def _aiter_w4a16_silu_via_a8w4( from vllm.model_executor.layers.quantization.utils.mxfp4_utils import ( should_use_cdna4_mx_scale_swizzle, + should_use_gfx1250_mx_scale_swizzle, ) - swz = "CDNA4_SCALE" if should_use_cdna4_mx_scale_swizzle() else None + # gfx1250: _swizzle_mxfp4 GFX1250-swizzles the weight scales at load time, + # so the a8w4 kernel must be told to unswizzle them. + if should_use_gfx1250_mx_scale_swizzle(): + swz = "GFX1250_SCALE" + elif should_use_cdna4_mx_scale_swizzle(): + swz = "CDNA4_SCALE" + else: + swz = None quant_dtype = torch.float8_e4m3fn g1_gammas = gammas if apply_router_weight_on_input else None diff --git a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a4_moe.py b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a4_moe.py new file mode 100644 index 000000000000..68c77973ebe3 --- /dev/null +++ b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a4_moe.py @@ -0,0 +1,372 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project + +import torch + +import vllm.model_executor.layers.fused_moe.modular_kernel as mk +from vllm._aiter_ops import rocm_aiter_ops +from vllm.model_executor.layers.fused_moe.activation import MoEActivation +from vllm.model_executor.layers.fused_moe.config import ( + FusedMoEConfig, + FusedMoEParallelConfig, + FusedMoEQuantConfig, + RoutingMethodType, +) +from vllm.model_executor.layers.quantization.utils.quant_utils import ( + QuantKey, + kMxfp4Dynamic, + kMxfp4Static, +) + +__all__ = [ + "AiterW4A4ExpertsMonolithic", + "aiter_triton_kernel_w4a4_moe_forward", +] + + +def _aiter_raw(t): + if t is None or isinstance(t, torch.Tensor): + return t + return t.storage.data if hasattr(t, "storage") else t + + +def aiter_triton_kernel_w4a4_moe_forward( + hidden_states: torch.Tensor, + w1, + w2, + gating_output: torch.Tensor, + topk: int, + renormalize: bool, + activation: MoEActivation = MoEActivation.SWIGLUOAI, + quant_config: FusedMoEQuantConfig | None = None, + apply_router_weight_on_input: bool = False, + global_num_experts: int = -1, + expert_map: torch.Tensor | None = None, + unpadded_N_w1=None, + unpadded_K_w1=None, + unpadded_N_w2=None, + unpadded_K_w2=None, + num_expert_group: int | None = None, + topk_group: int | None = None, + e_score_correction_bias: torch.Tensor | None = None, + routed_scaling_factor: float | None = None, + score_mode: str | None = None, + input_ids: torch.Tensor | None = None, + hash_indices_table: torch.Tensor | None = None, +): + assert quant_config is not None and rocm_aiter_ops.is_enabled() + + from aiter.ops.triton.moe.moe_op_gemm_a4w4 import moe_gemm_a4w4, mxfp4_quant + + try: + from aiter.ops.triton.moe.moe_routing import routing as _routing_mod + except ImportError: + from aiter.ops.triton.moe_routing import routing as _routing_mod + + from vllm.platforms.rocm import on_gfx1250 + + if on_gfx1250(): + _routing_mod.is_tdm_avail = lambda: False + aiter_routing = _routing_mod.routing + + if hash_indices_table is not None: + assert input_ids is not None, "hash routing requires input_ids" + n_tokens, n_expts_tot = gating_output.shape + tokens_per_expt = max(1, n_tokens * topk // n_expts_tot) + block_m = max(16, min(1 << (tokens_per_expt - 1).bit_length(), 128)) + routing_data, gather_idx, scatter_idx = _routing_mod.routing_from_hash( + gating_output, + hash_indices_table, + input_ids.to(hash_indices_table.dtype), + topk, + block_m, + score_mode=score_mode or "sqrtsoftplus", + renorm=renormalize, + routed_scaling_factor=( + routed_scaling_factor + if routed_scaling_factor is not None + else 1.0 + ), + ) + elif score_mode is not None: + use_grouped_topk = ( + num_expert_group is not None and num_expert_group > 1 + ) + routing_data, gather_idx, scatter_idx = aiter_routing( + gating_output, + topk, + score_mode=score_mode, + bias=e_score_correction_bias, + renorm=renormalize, + routed_scaling_factor=( + routed_scaling_factor + if routed_scaling_factor is not None + else 1.0 + ), + use_grouped_topk=use_grouped_topk, + num_expert_group=num_expert_group, + topk_group=topk_group, + ) + else: + routing_data, gather_idx, scatter_idx = aiter_routing( + gating_output, topk, sm_first=not renormalize + ) + + if on_gfx1250(): + gather_src = gather_idx.to(torch.long) // topk + hidden_states = hidden_states[gather_src] + gather_idx = None + + w1_data = _aiter_raw(w1) + w2_data = _aiter_raw(w2) + w1_scale = _aiter_raw(quant_config.w1_scale) + w2_scale = _aiter_raw(quant_config.w2_scale) + + gammas = routing_data.gate_scal if routing_data else None + + swiglu_limit = ( + quant_config.gemm1_clamp_limit + if quant_config.gemm1_clamp_limit is not None + else 7.0 + ) + # SWIGLUOAI needs the alpha/residual swiglu fused into the GEMM1 epilogue, + # which reads gate/up interleaved along N (see the oracle's weight prep). + fused_swiglu = activation in ( + MoEActivation.SWIGLUOAI, + MoEActivation.SWIGLUOAI_UNINTERLEAVE, + ) + swiglu_kwargs: dict[str, object] = {} + if fused_swiglu: + assert quant_config.gemm1_beta in (None, 1.0), ( + "aiter's fused swiglu hardcodes the residual to (up + 1); " + f"gemm1_beta={quant_config.gemm1_beta} cannot be expressed" + ) + swiglu_kwargs = { + "alpha": ( + quant_config.gemm1_alpha + if quant_config.gemm1_alpha is not None + else 1.0 + ), + "limit": swiglu_limit, + "swiglu_add_residual": quant_config.gemm1_beta == 1.0, + } + swizzle_mx_scale = ( + "GFX1250_SCALE" if on_gfx1250() else None + ) + + x_q, x_scale = mxfp4_quant(hidden_states.to(torch.bfloat16)) + + # GEMM1: gate+up projection. + raw_intermediate = moe_gemm_a4w4( + x_q, + w1_data, + x_scale, + w1_scale, + bias=quant_config.w1_bias, + routing_data=routing_data, + gather_indx=gather_idx, + gammas=gammas if apply_router_weight_on_input else None, + swizzle_mx_scale=swizzle_mx_scale, + apply_swiglu=fused_swiglu, + **swiglu_kwargs, + ) + + if fused_swiglu: + # The swiglu epilogue already halved N. + intermediate = ( + raw_intermediate + if unpadded_N_w1 is None + else raw_intermediate[:, : unpadded_N_w1 // 2] + ) + else: + if unpadded_N_w1 is not None: + raw_intermediate = raw_intermediate[:, :unpadded_N_w1] + + # SiLU(gate) * up on the concatenated [gate | up] halves + from aiter.ops.triton.fusions.fused_clamp_act_mul import fused_clamp_act_mul + + half_n = raw_intermediate.shape[-1] // 2 + intermediate = torch.empty( + raw_intermediate.shape[0], half_n, + dtype=raw_intermediate.dtype, + device=raw_intermediate.device, + ) + fused_clamp_act_mul( + raw_intermediate, + out=intermediate, + swiglu_limit=swiglu_limit, + activation="silu", + dtype_quant=None, + ) + + # GEMM2: down projection with scatter-reduce + mid_q, mid_scale = mxfp4_quant(intermediate.to(torch.bfloat16)) + + out = moe_gemm_a4w4( + mid_q, + w2_data, + mid_scale, + w2_scale, + bias=quant_config.w2_bias, + routing_data=routing_data, + scatter_indx=scatter_idx, + gammas=None if apply_router_weight_on_input else gammas, + swizzle_mx_scale=swizzle_mx_scale, + apply_swiglu=False, + ) + + return out + + +class AiterW4A4ExpertsMonolithic(mk.FusedMoEExpertsMonolithic): + """Monolithic MXFP4 W4A4 expert using AITER Triton/Gluon kernels (gfx1250). + + Uses moe_gemm_a4w4 (auto-selects gluon backend on gfx1250) with dynamic + MXFP4 activation quantization via mxfp4_quant. + """ + + def __init__( + self, + moe_config: FusedMoEConfig, + quant_config: FusedMoEQuantConfig, + ): + super().__init__(moe_config, quant_config) + self.topk = moe_config.experts_per_token + self.renormalize = moe_config.routing_method in ( + RoutingMethodType.Renormalize, + RoutingMethodType.RenormalizeNaive, + RoutingMethodType.DeepseekV4, + RoutingMethodType.DeepSeekV3, + RoutingMethodType.MiniMax2, + ) + + @staticmethod + def activation_format() -> mk.FusedMoEActivationFormat: + return mk.FusedMoEActivationFormat.Standard + + @staticmethod + def _supports_hash_routing() -> bool: + return True + + @staticmethod + def _supports_current_device() -> bool: + if not rocm_aiter_ops.is_enabled(): + return False + from vllm.platforms.rocm import on_gfx1250 + + return on_gfx1250() + + @staticmethod + def _supports_no_act_and_mul() -> bool: + return False + + @staticmethod + def _supports_quant_scheme( + weight_key: QuantKey | None, + activation_key: QuantKey | None, + ) -> bool: + return (weight_key, activation_key) == (kMxfp4Static, kMxfp4Dynamic) + + @staticmethod + def _supports_activation(activation: MoEActivation) -> bool: + return activation in ( + MoEActivation.SWIGLUOAI, + MoEActivation.SWIGLUOAI_UNINTERLEAVE, + MoEActivation.SILU, + ) + + @staticmethod + def _supports_parallel_config( + moe_parallel_config: FusedMoEParallelConfig, + ) -> bool: + return ( + not moe_parallel_config.use_all2all_kernels + and not moe_parallel_config.enable_eplb + and moe_parallel_config.dp_size <= 1 + ) + + @staticmethod + def _supports_routing_method( + routing_method: RoutingMethodType, + weight_key: QuantKey | None, + activation_key: QuantKey | None, + ) -> bool: + return routing_method in [ + RoutingMethodType.Renormalize, + RoutingMethodType.RenormalizeNaive, + RoutingMethodType.DeepseekV4, + RoutingMethodType.DeepSeekV3, + RoutingMethodType.MiniMax2, + ] + + @staticmethod + def _supports_router_logits_dtype( + router_logits_dtype: torch.dtype | None, + routing_method: RoutingMethodType, + ) -> bool: + return True + + @property + def expects_unquantized_inputs(self) -> bool: + return True + + def apply( + self, + hidden_states: torch.Tensor, + w1: torch.Tensor, + w2: torch.Tensor, + router_logits: torch.Tensor, + activation: MoEActivation, + global_num_experts: int, + expert_map: torch.Tensor | None, + a1q_scale: torch.Tensor | None, + apply_router_weight_on_input: bool, + num_expert_group: int | None = None, + e_score_correction_bias: torch.Tensor | None = None, + routed_scaling_factor: float | None = None, + topk_group: int | None = None, + input_ids: torch.Tensor | None = None, + hash_indices_table: torch.Tensor | None = None, + ) -> torch.Tensor: + assert self.moe_config.intermediate_size_per_partition_unpadded is not None + assert self.moe_config.hidden_dim_unpadded is not None + routing_method = self.moe_config.routing_method + if routing_method == RoutingMethodType.DeepseekV4: + score_mode = "sqrtsoftplus" + elif routing_method in ( + RoutingMethodType.DeepSeekV3, + RoutingMethodType.MiniMax2, + ): + # Sigmoid scores + routing bias; grouped for DeepSeek-R1/V3, + # ungrouped for MiniMax-M3. + score_mode = "sigmoid" + else: + score_mode = None + return aiter_triton_kernel_w4a4_moe_forward( + hidden_states=hidden_states, + w1=w1, + w2=w2, + gating_output=router_logits, + topk=self.topk, + renormalize=self.renormalize, + activation=activation, + global_num_experts=global_num_experts, + expert_map=expert_map, + quant_config=self.quant_config, + apply_router_weight_on_input=apply_router_weight_on_input, + unpadded_N_w1=( + self.moe_config.intermediate_size_per_partition_unpadded * 2 + ), + unpadded_K_w1=self.moe_config.hidden_dim_unpadded, + unpadded_N_w2=self.moe_config.hidden_dim_unpadded, + unpadded_K_w2=( + self.moe_config.intermediate_size_per_partition_unpadded + ), + num_expert_group=num_expert_group, + topk_group=topk_group, + e_score_correction_bias=e_score_correction_bias, + routed_scaling_factor=routed_scaling_factor, + score_mode=score_mode, + input_ids=input_ids, + hash_indices_table=hash_indices_table, + ) diff --git a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py index 9466015cac70..95d58e8c201b 100644 --- a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py +++ b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py @@ -15,15 +15,11 @@ from vllm.model_executor.layers.quantization.utils.quant_utils import ( QuantKey, kFp8StaticTensorSym, - kMxfp4Dynamic, kMxfp4Static, ) __all__ = [ - "AiterW4A4ExpertsMonolithic", "AiterW4A8ExpertsMonolithic", - "AiterW4A16ExpertsMonolithic", - "aiter_triton_kernel_w4a4_moe_forward", "aiter_triton_kernel_w4a8_moe_forward", ] @@ -313,704 +309,3 @@ def apply( unpadded_N_w2=self.moe_config.hidden_dim_unpadded, unpadded_K_w2=self.moe_config.intermediate_size_per_partition_unpadded, ) - - -def _aiter_raw(t): - if t is None or isinstance(t, torch.Tensor): - return t - return t.storage.data if hasattr(t, "storage") else t - - -def _aiter_w4a16_silu_via_a8w4( - hidden_states: torch.Tensor, - w1_data, - w2_data, - w1_wscale, - w2_wscale, - w1_bias, - w2_bias, - routing_data, - gather_idx, - scatter_idx, - gammas, - apply_router_weight_on_input: bool, - swiglu_limit: float, - unpadded_N_w1, - unpadded_K_w1, - unpadded_N_w2, - unpadded_K_w2, -) -> torch.Tensor: - """ - MXFP4 w4a16 MoE with a SILU (concatenated ``[gate | up]``) activation. - """ - from aiter.ops.triton.fusions.fused_clamp_act_mul import fused_clamp_act_mul - from aiter.ops.triton.moe_op_gemm_a8w4 import moe_gemm_a8w4 - from aiter.ops.triton.quant import dynamic_mxfp8_quant - - from vllm.model_executor.layers.quantization.utils.mxfp4_utils import ( - should_use_cdna4_mx_scale_swizzle, - should_use_gfx1250_mx_scale_swizzle, - ) - - # gfx1250: _swizzle_mxfp4 GFX1250-swizzles the weight scales at load time - # (shuffle_scale_moe, preshuffle_factor=32), so the kernel must be told - # SWIZZLE_MX_SCALE="GFX1250_SCALE" to unswizzle them. - if should_use_gfx1250_mx_scale_swizzle(): - swz = "GFX1250_SCALE" - elif should_use_cdna4_mx_scale_swizzle(): - swz = "CDNA4_SCALE" - else: - swz = None - quant_dtype = torch.float8_e4m3fn - - g1_gammas = gammas if apply_router_weight_on_input else None - g2_gammas = None if apply_router_weight_on_input else gammas - - hidden_q, a1_scale = dynamic_mxfp8_quant(hidden_states, quant_dtype=quant_dtype) - raw_gate_up = moe_gemm_a8w4( - hidden_q, - w1_data, - a1_scale, - w1_wscale, - None, - None, - w1_bias, - routing_data, - gather_indx=gather_idx, - gammas=g1_gammas, - swizzle_mx_scale=swz, - out_dtype=torch.bfloat16, - apply_swiglu=False, - unpadded_N=unpadded_N_w1, - unpadded_K=unpadded_K_w1, - ) - if unpadded_N_w1 is not None: - raw_gate_up = raw_gate_up[:, :unpadded_N_w1] - - interim_fp8, a2_scale = fused_clamp_act_mul( - raw_gate_up, - swiglu_limit=swiglu_limit, - activation="silu", - dtype_quant=quant_dtype, - scale_dtype_fmt="ue8m0", - quant_block_size=32, - ) - - out = moe_gemm_a8w4( - interim_fp8, - w2_data, - a2_scale, - w2_wscale, - None, - None, - w2_bias, - routing_data, - scatter_indx=scatter_idx, - gammas=g2_gammas, - swizzle_mx_scale=swz, - unpadded_N=unpadded_N_w2, - unpadded_K=unpadded_K_w2, - ) - return out - - -def aiter_triton_kernel_w4a16_moe_forward( - hidden_states: torch.Tensor, - w1, - w2, - gating_output: torch.Tensor, - topk: int, - renormalize: bool, - activation: MoEActivation = MoEActivation.SWIGLUOAI, - quant_config: FusedMoEQuantConfig | None = None, - apply_router_weight_on_input: bool = False, - global_num_experts: int = -1, - expert_map: torch.Tensor | None = None, - unpadded_N_w1=None, - unpadded_K_w1=None, - unpadded_N_w2=None, - unpadded_K_w2=None, - num_expert_group: int | None = None, - topk_group: int | None = None, - e_score_correction_bias: torch.Tensor | None = None, - routed_scaling_factor: float | None = None, - score_mode: str | None = None, -): - assert quant_config is not None and rocm_aiter_ops.is_enabled() - from vllm.platforms.rocm import on_gfx1250 - - try: - from aiter.ops.triton.moe.moe_op_gemm_a16w4 import moe_gemm_a16w4 - from aiter.ops.triton.moe.moe_routing import routing as _routing_mod - except ImportError: - from aiter.ops.triton.moe.moe_op_gemm_a16w4 import moe_gemm_a16w4 - from aiter.ops.triton.moe_routing import routing as _routing_mod - - if on_gfx1250(): - _routing_mod.is_tdm_avail = lambda: False - aiter_routing = _routing_mod.routing - - if score_mode is not None: - use_grouped_topk = num_expert_group is not None and num_expert_group > 1 - routing_data, gather_idx, scatter_idx = aiter_routing( - gating_output, - topk, - score_mode=score_mode, - bias=e_score_correction_bias, - renorm=renormalize, - routed_scaling_factor=( - routed_scaling_factor if routed_scaling_factor is not None else 1.0 - ), - use_grouped_topk=use_grouped_topk, - num_expert_group=num_expert_group, - topk_group=topk_group, - ) - else: - routing_data, gather_idx, scatter_idx = aiter_routing( - gating_output, topk, sm_first=not renormalize - ) - - assert quant_config.w1_precision is not None - assert quant_config.w2_precision is not None - - w1_data = _aiter_raw(w1) - w2_data = _aiter_raw(w2) - w1_wscale = _aiter_raw(quant_config.w1_precision.weight_scale) - w2_wscale = _aiter_raw(quant_config.w2_precision.weight_scale) - - gammas = routing_data.gate_scal if routing_data else None - - swiglu_alpha = ( - quant_config.gemm1_alpha if quant_config.gemm1_alpha is not None else 1.0 - ) - swiglu_limit = ( - quant_config.gemm1_clamp_limit - if quant_config.gemm1_clamp_limit is not None - else 7.0 - ) - - # SILU on gfx1250: use the verified a8w4 kernel (dynamic MXFP8); a16w4 faults. - if activation == MoEActivation.SILU and on_gfx1250(): - return _aiter_w4a16_silu_via_a8w4( - hidden_states, - w1_data, - w2_data, - w1_wscale, - w2_wscale, - quant_config.w1_bias, - quant_config.w2_bias, - routing_data, - gather_idx, - scatter_idx, - gammas, - apply_router_weight_on_input, - swiglu_limit, - unpadded_N_w1, - unpadded_K_w1, - unpadded_N_w2, - unpadded_K_w2, - ) - - # SILU: silu(gate) * up — same kernel, just no "+1" residual in swiglu. - swiglu_add_residual = activation != MoEActivation.SILU - - intermediate = moe_gemm_a16w4( - hidden_states, - w1_data, - None, - w1_wscale, - None, - None, - quant_config.w1_bias, - routing_data, - gather_indx=gather_idx, - gammas=gammas if apply_router_weight_on_input else None, - swizzle_mx_scale=None, - apply_swiglu=True, - alpha=swiglu_alpha, - limit=swiglu_limit, - swiglu_add_residual=swiglu_add_residual, - unpadded_N=unpadded_N_w1, - unpadded_K=unpadded_K_w1, - ) - - out = moe_gemm_a16w4( - intermediate, - w2_data, - None, - w2_wscale, - None, - None, - quant_config.w2_bias, - routing_data, - scatter_indx=scatter_idx, - gammas=None if apply_router_weight_on_input else gammas, - swizzle_mx_scale=None, - unpadded_N=unpadded_N_w2, - unpadded_K=unpadded_K_w2, - ) - - return out - - -class AiterW4A16ExpertsMonolithic(mk.FusedMoEExpertsMonolithic): - def __init__( - self, - moe_config: FusedMoEConfig, - quant_config: FusedMoEQuantConfig, - ): - super().__init__(moe_config, quant_config) - self.topk = moe_config.experts_per_token - self.renormalize = moe_config.routing_method in ( - RoutingMethodType.Renormalize, - RoutingMethodType.RenormalizeNaive, - RoutingMethodType.DeepseekV4, - ) - - @staticmethod - def activation_format() -> mk.FusedMoEActivationFormat: - return mk.FusedMoEActivationFormat.Standard - - @staticmethod - def _supports_current_device() -> bool: - if not rocm_aiter_ops.is_enabled(): - return False - from vllm.platforms.rocm import on_gfx950, on_gfx1250 - - return on_gfx950() or on_gfx1250() - - @staticmethod - def _supports_no_act_and_mul() -> bool: - return False - - @staticmethod - def _supports_quant_scheme( - weight_key: QuantKey | None, - activation_key: QuantKey | None, - ) -> bool: - return (weight_key, activation_key) == (kMxfp4Static, None) - - @staticmethod - def _supports_activation(activation: MoEActivation) -> bool: - return activation in (MoEActivation.SWIGLUOAI, MoEActivation.SILU) - - @staticmethod - def _supports_parallel_config( - moe_parallel_config: FusedMoEParallelConfig, - ) -> bool: - return ( - not moe_parallel_config.use_all2all_kernels - and not moe_parallel_config.enable_eplb - and moe_parallel_config.dp_size <= 1 - ) - - @staticmethod - def _supports_routing_method( - routing_method: RoutingMethodType, - weight_key: QuantKey | None, - activation_key: QuantKey | None, - ) -> bool: - return routing_method in [ - RoutingMethodType.Renormalize, - RoutingMethodType.RenormalizeNaive, - RoutingMethodType.DeepseekV4, - ] - - @staticmethod - def _supports_router_logits_dtype( - router_logits_dtype: torch.dtype | None, - routing_method: RoutingMethodType, - ) -> bool: - return True - - @property - def expects_unquantized_inputs(self) -> bool: - return True - - def apply( - self, - hidden_states: torch.Tensor, - w1: torch.Tensor, - w2: torch.Tensor, - router_logits: torch.Tensor, - activation: MoEActivation, - global_num_experts: int, - expert_map: torch.Tensor | None, - a1q_scale: torch.Tensor | None, - apply_router_weight_on_input: bool, - num_expert_group: int | None = None, - e_score_correction_bias: torch.Tensor | None = None, - routed_scaling_factor: float | None = None, - topk_group: int | None = None, - ) -> torch.Tensor: - assert self.moe_config.intermediate_size_per_partition_unpadded is not None - assert self.moe_config.hidden_dim_unpadded is not None - score_mode = ( - "sqrtsoftplus" - if self.moe_config.routing_method == RoutingMethodType.DeepseekV4 - else None - ) - return aiter_triton_kernel_w4a16_moe_forward( - hidden_states=hidden_states, - w1=w1, - w2=w2, - gating_output=router_logits, - topk=self.topk, - renormalize=self.renormalize, - activation=activation, - global_num_experts=global_num_experts, - expert_map=expert_map, - quant_config=self.quant_config, - apply_router_weight_on_input=apply_router_weight_on_input, - unpadded_N_w1=self.moe_config.intermediate_size_per_partition_unpadded * 2, - unpadded_K_w1=self.moe_config.hidden_dim_unpadded, - unpadded_N_w2=self.moe_config.hidden_dim_unpadded, - unpadded_K_w2=self.moe_config.intermediate_size_per_partition_unpadded, - num_expert_group=num_expert_group, - topk_group=topk_group, - e_score_correction_bias=e_score_correction_bias, - routed_scaling_factor=routed_scaling_factor, - score_mode=score_mode, - ) - - -def aiter_triton_kernel_w4a4_moe_forward( - hidden_states: torch.Tensor, - w1, - w2, - gating_output: torch.Tensor, - topk: int, - renormalize: bool, - activation: MoEActivation = MoEActivation.SWIGLUOAI, - quant_config: FusedMoEQuantConfig | None = None, - apply_router_weight_on_input: bool = False, - global_num_experts: int = -1, - expert_map: torch.Tensor | None = None, - unpadded_N_w1=None, - unpadded_K_w1=None, - unpadded_N_w2=None, - unpadded_K_w2=None, - num_expert_group: int | None = None, - topk_group: int | None = None, - e_score_correction_bias: torch.Tensor | None = None, - routed_scaling_factor: float | None = None, - score_mode: str | None = None, - input_ids: torch.Tensor | None = None, - hash_indices_table: torch.Tensor | None = None, -): - assert quant_config is not None and rocm_aiter_ops.is_enabled() - - from aiter.ops.triton.moe.moe_op_gemm_a4w4 import moe_gemm_a4w4, mxfp4_quant - - try: - from aiter.ops.triton.moe.moe_routing import routing as _routing_mod - except ImportError: - from aiter.ops.triton.moe_routing import routing as _routing_mod - - from vllm.platforms.rocm import on_gfx1250 - - if on_gfx1250(): - _routing_mod.is_tdm_avail = lambda: False - aiter_routing = _routing_mod.routing - - if hash_indices_table is not None: - assert input_ids is not None, "hash routing requires input_ids" - n_tokens, n_expts_tot = gating_output.shape - tokens_per_expt = max(1, n_tokens * topk // n_expts_tot) - block_m = max(16, min(1 << (tokens_per_expt - 1).bit_length(), 128)) - routing_data, gather_idx, scatter_idx = _routing_mod.routing_from_hash( - gating_output, - hash_indices_table, - input_ids.to(hash_indices_table.dtype), - topk, - block_m, - score_mode=score_mode or "sqrtsoftplus", - renorm=renormalize, - routed_scaling_factor=( - routed_scaling_factor - if routed_scaling_factor is not None - else 1.0 - ), - ) - elif score_mode is not None: - use_grouped_topk = ( - num_expert_group is not None and num_expert_group > 1 - ) - routing_data, gather_idx, scatter_idx = aiter_routing( - gating_output, - topk, - score_mode=score_mode, - bias=e_score_correction_bias, - renorm=renormalize, - routed_scaling_factor=( - routed_scaling_factor - if routed_scaling_factor is not None - else 1.0 - ), - use_grouped_topk=use_grouped_topk, - num_expert_group=num_expert_group, - topk_group=topk_group, - ) - else: - routing_data, gather_idx, scatter_idx = aiter_routing( - gating_output, topk, sm_first=not renormalize - ) - - if on_gfx1250(): - gather_src = gather_idx.to(torch.long) // topk - hidden_states = hidden_states[gather_src] - gather_idx = None - - w1_data = _aiter_raw(w1) - w2_data = _aiter_raw(w2) - w1_scale = _aiter_raw(quant_config.w1_scale) - w2_scale = _aiter_raw(quant_config.w2_scale) - - gammas = routing_data.gate_scal if routing_data else None - - swiglu_limit = ( - quant_config.gemm1_clamp_limit - if quant_config.gemm1_clamp_limit is not None - else 7.0 - ) - # SWIGLUOAI needs the alpha/residual swiglu fused into the GEMM1 epilogue, - # which reads gate/up interleaved along N (see the oracle's weight prep). - fused_swiglu = activation in ( - MoEActivation.SWIGLUOAI, - MoEActivation.SWIGLUOAI_UNINTERLEAVE, - ) - swiglu_kwargs: dict[str, object] = {} - if fused_swiglu: - assert quant_config.gemm1_beta in (None, 1.0), ( - "aiter's fused swiglu hardcodes the residual to (up + 1); " - f"gemm1_beta={quant_config.gemm1_beta} cannot be expressed" - ) - swiglu_kwargs = { - "alpha": ( - quant_config.gemm1_alpha - if quant_config.gemm1_alpha is not None - else 1.0 - ), - "limit": swiglu_limit, - "swiglu_add_residual": quant_config.gemm1_beta == 1.0, - } - swizzle_mx_scale = ( - "GFX1250_SCALE" if on_gfx1250() else None - ) - - x_q, x_scale = mxfp4_quant(hidden_states.to(torch.bfloat16)) - - # GEMM1: gate+up projection. - raw_intermediate = moe_gemm_a4w4( - x_q, - w1_data, - x_scale, - w1_scale, - bias=quant_config.w1_bias, - routing_data=routing_data, - gather_indx=gather_idx, - gammas=gammas if apply_router_weight_on_input else None, - swizzle_mx_scale=swizzle_mx_scale, - apply_swiglu=fused_swiglu, - **swiglu_kwargs, - ) - - if fused_swiglu: - # The swiglu epilogue already halved N. - intermediate = ( - raw_intermediate - if unpadded_N_w1 is None - else raw_intermediate[:, : unpadded_N_w1 // 2] - ) - else: - if unpadded_N_w1 is not None: - raw_intermediate = raw_intermediate[:, :unpadded_N_w1] - - # SiLU(gate) * up on the concatenated [gate | up] halves - from aiter.ops.triton.fusions.fused_clamp_act_mul import fused_clamp_act_mul - - half_n = raw_intermediate.shape[-1] // 2 - intermediate = torch.empty( - raw_intermediate.shape[0], half_n, - dtype=raw_intermediate.dtype, - device=raw_intermediate.device, - ) - fused_clamp_act_mul( - raw_intermediate, - out=intermediate, - swiglu_limit=swiglu_limit, - activation="silu", - dtype_quant=None, - ) - - # GEMM2: down projection with scatter-reduce - mid_q, mid_scale = mxfp4_quant(intermediate.to(torch.bfloat16)) - - out = moe_gemm_a4w4( - mid_q, - w2_data, - mid_scale, - w2_scale, - bias=quant_config.w2_bias, - routing_data=routing_data, - scatter_indx=scatter_idx, - gammas=None if apply_router_weight_on_input else gammas, - swizzle_mx_scale=swizzle_mx_scale, - apply_swiglu=False, - ) - - return out - - -class AiterW4A4ExpertsMonolithic(mk.FusedMoEExpertsMonolithic): - """Monolithic MXFP4 W4A4 expert using AITER Triton/Gluon kernels (gfx1250). - - Uses moe_gemm_a4w4 (auto-selects gluon backend on gfx1250) with dynamic - MXFP4 activation quantization via mxfp4_quant. - """ - - def __init__( - self, - moe_config: FusedMoEConfig, - quant_config: FusedMoEQuantConfig, - ): - super().__init__(moe_config, quant_config) - self.topk = moe_config.experts_per_token - self.renormalize = moe_config.routing_method in ( - RoutingMethodType.Renormalize, - RoutingMethodType.RenormalizeNaive, - RoutingMethodType.DeepseekV4, - RoutingMethodType.DeepSeekV3, - RoutingMethodType.MiniMax2, - ) - - @staticmethod - def activation_format() -> mk.FusedMoEActivationFormat: - return mk.FusedMoEActivationFormat.Standard - - @staticmethod - def _supports_hash_routing() -> bool: - return True - - @staticmethod - def _supports_current_device() -> bool: - if not rocm_aiter_ops.is_enabled(): - return False - from vllm.platforms.rocm import on_gfx1250 - - return on_gfx1250() - - @staticmethod - def _supports_no_act_and_mul() -> bool: - return False - - @staticmethod - def _supports_quant_scheme( - weight_key: QuantKey | None, - activation_key: QuantKey | None, - ) -> bool: - return (weight_key, activation_key) == (kMxfp4Static, kMxfp4Dynamic) - - @staticmethod - def _supports_activation(activation: MoEActivation) -> bool: - return activation in ( - MoEActivation.SWIGLUOAI, - MoEActivation.SWIGLUOAI_UNINTERLEAVE, - MoEActivation.SILU, - ) - - @staticmethod - def _supports_parallel_config( - moe_parallel_config: FusedMoEParallelConfig, - ) -> bool: - return ( - not moe_parallel_config.use_all2all_kernels - and not moe_parallel_config.enable_eplb - and moe_parallel_config.dp_size <= 1 - ) - - @staticmethod - def _supports_routing_method( - routing_method: RoutingMethodType, - weight_key: QuantKey | None, - activation_key: QuantKey | None, - ) -> bool: - return routing_method in [ - RoutingMethodType.Renormalize, - RoutingMethodType.RenormalizeNaive, - RoutingMethodType.DeepseekV4, - RoutingMethodType.DeepSeekV3, - RoutingMethodType.MiniMax2, - ] - - @staticmethod - def _supports_router_logits_dtype( - router_logits_dtype: torch.dtype | None, - routing_method: RoutingMethodType, - ) -> bool: - return True - - @property - def expects_unquantized_inputs(self) -> bool: - return True - - def apply( - self, - hidden_states: torch.Tensor, - w1: torch.Tensor, - w2: torch.Tensor, - router_logits: torch.Tensor, - activation: MoEActivation, - global_num_experts: int, - expert_map: torch.Tensor | None, - a1q_scale: torch.Tensor | None, - apply_router_weight_on_input: bool, - num_expert_group: int | None = None, - e_score_correction_bias: torch.Tensor | None = None, - routed_scaling_factor: float | None = None, - topk_group: int | None = None, - input_ids: torch.Tensor | None = None, - hash_indices_table: torch.Tensor | None = None, - ) -> torch.Tensor: - assert self.moe_config.intermediate_size_per_partition_unpadded is not None - assert self.moe_config.hidden_dim_unpadded is not None - routing_method = self.moe_config.routing_method - if routing_method == RoutingMethodType.DeepseekV4: - score_mode = "sqrtsoftplus" - elif routing_method in ( - RoutingMethodType.DeepSeekV3, - RoutingMethodType.MiniMax2, - ): - # Sigmoid scores + routing bias; grouped for DeepSeek-R1/V3, - # ungrouped for MiniMax-M3. - score_mode = "sigmoid" - else: - score_mode = None - return aiter_triton_kernel_w4a4_moe_forward( - hidden_states=hidden_states, - w1=w1, - w2=w2, - gating_output=router_logits, - topk=self.topk, - renormalize=self.renormalize, - activation=activation, - global_num_experts=global_num_experts, - expert_map=expert_map, - quant_config=self.quant_config, - apply_router_weight_on_input=apply_router_weight_on_input, - unpadded_N_w1=( - self.moe_config.intermediate_size_per_partition_unpadded * 2 - ), - unpadded_K_w1=self.moe_config.hidden_dim_unpadded, - unpadded_N_w2=self.moe_config.hidden_dim_unpadded, - unpadded_K_w2=( - self.moe_config.intermediate_size_per_partition_unpadded - ), - num_expert_group=num_expert_group, - topk_group=topk_group, - e_score_correction_bias=e_score_correction_bias, - routed_scaling_factor=routed_scaling_factor, - score_mode=score_mode, - input_ids=input_ids, - hash_indices_table=hash_indices_table, - ) diff --git a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py index e1dfb45a7e11..c024202cbdf4 100644 --- a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py +++ b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py @@ -283,7 +283,7 @@ def backend_to_kernel_cls( return [AiterW4A8ExpertsMonolithic] elif backend == Mxfp4MoeBackend.AITER_MXFP4_MXFP4: - from vllm.model_executor.layers.fused_moe.experts.aiter_mxfp4_w4a8_moe import ( + from vllm.model_executor.layers.fused_moe.experts.aiter_mxfp4_w4a4_moe import ( AiterW4A4ExpertsMonolithic, ) from vllm.model_executor.layers.fused_moe.experts.rocm_aiter_moe import ( From 24df9ad43399711861d1b69306e5d5338e7905e6 Mon Sep 17 00:00:00 2001 From: Jaden Mathias Date: Wed, 23 Sep 2026 10:56:57 -0400 Subject: [PATCH 34/50] Backend selection fix (#1320) Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- vllm/model_executor/layers/quantization/quark/quark_moe.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vllm/model_executor/layers/quantization/quark/quark_moe.py b/vllm/model_executor/layers/quantization/quark/quark_moe.py index 262f9bdb0ae1..9ed0361e6cae 100644 --- a/vllm/model_executor/layers/quantization/quark/quark_moe.py +++ b/vllm/model_executor/layers/quantization/quark/quark_moe.py @@ -1644,8 +1644,10 @@ def __init__( # If no native backend available, use emulation. if self.mxfp4_backend is Mxfp4MoeBackend.NONE: self.mxfp4_backend = Mxfp4MoeBackend.EMULATION + self.experts_cls = None - self.experts_cls = backend_to_kernel_cls(self.mxfp4_backend)[0] + if self.experts_cls is None: + self.experts_cls = backend_to_kernel_cls(self.mxfp4_backend)[0] logger.info_once( f"Using {self.mxfp4_backend.value} backend for {self.ocp_mx_scheme}" From cedef943a36082bde5d94334f51accfa363ba9fc Mon Sep 17 00:00:00 2001 From: Daniel Chan Date: Tue, 6 Oct 2026 18:43:07 -0400 Subject: [PATCH 35/50] sanitize router logits and pass an fp32 bias to aiter W4A4 MoE routing (#1351) * sanitize router logits and pass an fp32 bias to aiter W4A4 MoE routing Signed-off-by: Daniel * changing triton and aiter pins Signed-off-by: Daniel --------- Signed-off-by: Daniel Co-authored-by: Daniel Signed-off-by: Juan Villamizar --- docker/Dockerfile.rocm_base_gfx1250 | 2 +- docker/Dockerfile.rocm_gfx1250 | 2 +- .../layers/fused_moe/experts/aiter_mxfp4_w4a4_moe.py | 8 +++++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docker/Dockerfile.rocm_base_gfx1250 b/docker/Dockerfile.rocm_base_gfx1250 index 4208e70bba75..6915797e4460 100644 --- a/docker/Dockerfile.rocm_base_gfx1250 +++ b/docker/Dockerfile.rocm_base_gfx1250 @@ -10,7 +10,7 @@ ARG APEX_VERSION=1.11.0+rocm10.1.0a20260822 ARG FA_BRANCH="jpvillam/gfx1250_wip" ARG FA_REPO="https://github.com/jpvillam-amd/flash-attention.git" -ARG AITER_BRANCH="7bb449982" +ARG AITER_BRANCH="ff3c62e069bb119bcd0" ARG AITER_REPO="https://github.com/ROCm/aiter.git" ARG MORI_BRANCH="v1.1.0" ARG MORI_REPO="https://github.com/ROCm/mori.git" diff --git a/docker/Dockerfile.rocm_gfx1250 b/docker/Dockerfile.rocm_gfx1250 index e18add97c840..634e522e0d62 100644 --- a/docker/Dockerfile.rocm_gfx1250 +++ b/docker/Dockerfile.rocm_gfx1250 @@ -734,7 +734,7 @@ RUN curl -k -O https://raw.githubusercontent.com/ROCm/vllm/refs/heads/gfx1250_be RUN pip3 uninstall -y triton && \ git clone https://github.com/triton-lang/triton.git && \ cd triton && \ - git checkout 78420176f2d90c3270a48c9218e27e9b5d923c8d && \ + git checkout aedd1f6 && \ TRITON_APPEND_CMAKE_ARGS="-DCMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH=FALSE" pip3 install . CMD ["/bin/bash"] diff --git a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a4_moe.py b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a4_moe.py index 68c77973ebe3..411ece40ac47 100644 --- a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a4_moe.py +++ b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a4_moe.py @@ -69,6 +69,8 @@ def aiter_triton_kernel_w4a4_moe_forward( _routing_mod.is_tdm_avail = lambda: False aiter_routing = _routing_mod.routing + gating_output = torch.nan_to_num(gating_output, nan=0.0, posinf=0.0, neginf=0.0) + if hash_indices_table is not None: assert input_ids is not None, "hash routing requires input_ids" n_tokens, n_expts_tot = gating_output.shape @@ -96,7 +98,11 @@ def aiter_triton_kernel_w4a4_moe_forward( gating_output, topk, score_mode=score_mode, - bias=e_score_correction_bias, + bias=( + e_score_correction_bias.float() + if e_score_correction_bias is not None + else None + ), renorm=renormalize, routed_scaling_factor=( routed_scaling_factor From e7d3a4f4557ad23735cc8ae9031eee8390ca4f2a Mon Sep 17 00:00:00 2001 From: Jaden Mathias Date: Wed, 7 Oct 2026 10:41:33 -0400 Subject: [PATCH 36/50] Pass routing replay to Monolithic A4W4 class Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- .../layers/fused_moe/experts/aiter_mxfp4_w4a4_moe.py | 1 + 1 file changed, 1 insertion(+) diff --git a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a4_moe.py b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a4_moe.py index 411ece40ac47..6c12025240e4 100644 --- a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a4_moe.py +++ b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a4_moe.py @@ -331,6 +331,7 @@ def apply( e_score_correction_bias: torch.Tensor | None = None, routed_scaling_factor: float | None = None, topk_group: int | None = None, + routing_replay_out: torch.Tensor | None = None, input_ids: torch.Tensor | None = None, hash_indices_table: torch.Tensor | None = None, ) -> torch.Tensor: From e9ff53903c3fb331b45c9fe9d0286b001883a649 Mon Sep 17 00:00:00 2001 From: jpvillam Date: Wed, 7 Oct 2026 13:47:49 -0500 Subject: [PATCH 37/50] small spelling error Signed-off-by: jpvillam Signed-off-by: Juan Villamizar --- vllm/_aiter_ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vllm/_aiter_ops.py b/vllm/_aiter_ops.py index 24ce62540afe..27722e3d4860 100644 --- a/vllm/_aiter_ops.py +++ b/vllm/_aiter_ops.py @@ -956,7 +956,7 @@ def _rocm_aiter_triton_gemm_a8w8_blockscale_preshuffle_impl( As, Bs, dtype=output_dtype, - is_x_scale_tranposed=False, + is_x_scale_transposed=False, ) From 7025e8643a659ce5952a63ce2f6d0207d767e952 Mon Sep 17 00:00:00 2001 From: jpvillam Date: Wed, 7 Oct 2026 13:59:22 -0500 Subject: [PATCH 38/50] Linter changes Signed-off-by: jpvillam Signed-off-by: Juan Villamizar --- vllm/_aiter_ops.py | 5 ++-- .../fused_moe/experts/aiter_mxfp4_w4a4_moe.py | 23 ++++++------------- .../layers/fused_moe/oracle/mxfp4.py | 9 ++++---- 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/vllm/_aiter_ops.py b/vllm/_aiter_ops.py index 27722e3d4860..7b5565d30b13 100644 --- a/vllm/_aiter_ops.py +++ b/vllm/_aiter_ops.py @@ -3983,10 +3983,9 @@ def is_triton_blockscale_bpreshuffle_gemm_tuned(n: int, k: int) -> bool: from aiter.ops.triton.utils.gemm_config_utils import ( get_gemm_config, ) + # Fake M value for now - _, is_tuned = get_gemm_config( - "GEMM-A8W8_BLOCKSCALE_PRESHUFFLED", 1, n, k - ) + _, is_tuned = get_gemm_config("GEMM-A8W8_BLOCKSCALE_PRESHUFFLED", 1, n, k) return is_tuned @staticmethod diff --git a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a4_moe.py b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a4_moe.py index 6c12025240e4..b8f26cdedf16 100644 --- a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a4_moe.py +++ b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a4_moe.py @@ -85,15 +85,11 @@ def aiter_triton_kernel_w4a4_moe_forward( score_mode=score_mode or "sqrtsoftplus", renorm=renormalize, routed_scaling_factor=( - routed_scaling_factor - if routed_scaling_factor is not None - else 1.0 + routed_scaling_factor if routed_scaling_factor is not None else 1.0 ), ) elif score_mode is not None: - use_grouped_topk = ( - num_expert_group is not None and num_expert_group > 1 - ) + use_grouped_topk = num_expert_group is not None and num_expert_group > 1 routing_data, gather_idx, scatter_idx = aiter_routing( gating_output, topk, @@ -105,9 +101,7 @@ def aiter_triton_kernel_w4a4_moe_forward( ), renorm=renormalize, routed_scaling_factor=( - routed_scaling_factor - if routed_scaling_factor is not None - else 1.0 + routed_scaling_factor if routed_scaling_factor is not None else 1.0 ), use_grouped_topk=use_grouped_topk, num_expert_group=num_expert_group, @@ -156,9 +150,7 @@ def aiter_triton_kernel_w4a4_moe_forward( "limit": swiglu_limit, "swiglu_add_residual": quant_config.gemm1_beta == 1.0, } - swizzle_mx_scale = ( - "GFX1250_SCALE" if on_gfx1250() else None - ) + swizzle_mx_scale = "GFX1250_SCALE" if on_gfx1250() else None x_q, x_scale = mxfp4_quant(hidden_states.to(torch.bfloat16)) @@ -193,7 +185,8 @@ def aiter_triton_kernel_w4a4_moe_forward( half_n = raw_intermediate.shape[-1] // 2 intermediate = torch.empty( - raw_intermediate.shape[0], half_n, + raw_intermediate.shape[0], + half_n, dtype=raw_intermediate.dtype, device=raw_intermediate.device, ) @@ -366,9 +359,7 @@ def apply( ), unpadded_K_w1=self.moe_config.hidden_dim_unpadded, unpadded_N_w2=self.moe_config.hidden_dim_unpadded, - unpadded_K_w2=( - self.moe_config.intermediate_size_per_partition_unpadded - ), + unpadded_K_w2=(self.moe_config.intermediate_size_per_partition_unpadded), num_expert_group=num_expert_group, topk_group=topk_group, e_score_correction_bias=e_score_correction_bias, diff --git a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py index c024202cbdf4..7ecbeaa610db 100644 --- a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py +++ b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py @@ -1186,7 +1186,7 @@ def swap_every_two_rows(x, axis=-1): w2_data = w2_data.transpose(1, 2) # Scales are [E, N, K_scale]; both kernels index them as - # [E, K_scale, N] with K innermost, so transpose(1, 2) + # [E, K_scale, N] with K innermost, so transpose(1, 2) # Keep them uint8: Triton moe_gemm_a4w4 takes e8m0 scales w13_scale = w13_scale_data.transpose(1, 2) w2_scale = w2_weight_scale.data.view(torch.uint8).transpose(1, 2) @@ -1206,14 +1206,13 @@ def swap_every_two_rows(x, axis=-1): scale_kwidth=4, ) - return (w13_data, w2_data, w13_scale, w2_scale, - w13_bias, w2_bias) - - from vllm._aiter_ops import rocm_aiter_ops + return (w13_data, w2_data, w13_scale, w2_scale, w13_bias, w2_bias) # e8m0_shuffle on weight scales (GFX950 swizzle layout) from aiter.utility.fp4_utils import e8m0_shuffle + from vllm._aiter_ops import rocm_aiter_ops + s0, s1, _ = w13_weight_scale.shape w13_weight_scale.data = e8m0_shuffle(w13_weight_scale.view(s0 * s1, -1)).view( s0, s1, -1 From 9e21a93bca45dbbb63ed8d0b0a86c05061e3cb8a Mon Sep 17 00:00:00 2001 From: Jaden Mathias Date: Wed, 7 Oct 2026 16:29:44 -0400 Subject: [PATCH 39/50] Change aiter pin commit to fix build Signed-off-by: Jaden Mathias Signed-off-by: Juan Villamizar --- docker/Dockerfile.rocm_base_gfx1250 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile.rocm_base_gfx1250 b/docker/Dockerfile.rocm_base_gfx1250 index 6915797e4460..ae41096a6ea8 100644 --- a/docker/Dockerfile.rocm_base_gfx1250 +++ b/docker/Dockerfile.rocm_base_gfx1250 @@ -10,7 +10,7 @@ ARG APEX_VERSION=1.11.0+rocm10.1.0a20260822 ARG FA_BRANCH="jpvillam/gfx1250_wip" ARG FA_REPO="https://github.com/jpvillam-amd/flash-attention.git" -ARG AITER_BRANCH="ff3c62e069bb119bcd0" +ARG AITER_BRANCH="f85b24b" ARG AITER_REPO="https://github.com/ROCm/aiter.git" ARG MORI_BRANCH="v1.1.0" ARG MORI_REPO="https://github.com/ROCm/mori.git" From ba5dd3f5b12db98dd02c731847f3f7cf48f671eb Mon Sep 17 00:00:00 2001 From: Juan Villamizar Date: Thu, 8 Oct 2026 15:39:41 -0500 Subject: [PATCH 40/50] Review and transposed needs to be misspelled Signed-off-by: Juan Villamizar --- vllm/_aiter_ops.py | 2 +- .../layers/fused_moe/experts/aiter_mxfp4_w4a16_moe.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/vllm/_aiter_ops.py b/vllm/_aiter_ops.py index 7b5565d30b13..7a9929eff304 100644 --- a/vllm/_aiter_ops.py +++ b/vllm/_aiter_ops.py @@ -956,7 +956,7 @@ def _rocm_aiter_triton_gemm_a8w8_blockscale_preshuffle_impl( As, Bs, dtype=output_dtype, - is_x_scale_transposed=False, + is_x_scale_tranposed=False, ) diff --git a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a16_moe.py b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a16_moe.py index d4550a845279..8ec3baf7c507 100644 --- a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a16_moe.py +++ b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a16_moe.py @@ -168,7 +168,11 @@ def aiter_triton_kernel_w4a16_moe_forward( gating_output, topk, score_mode=score_mode, - bias=e_score_correction_bias, + bias=( + e_score_correction_bias.float() + if e_score_correction_bias is not None + else None + ), renorm=renormalize, routed_scaling_factor=( routed_scaling_factor if routed_scaling_factor is not None else 1.0 From 6aecd06a8ece094fa25e9a7fe6e7f12c544556c0 Mon Sep 17 00:00:00 2001 From: Juan Villamizar Date: Thu, 8 Oct 2026 15:48:26 -0500 Subject: [PATCH 41/50] Avoid the misspell, pass positionally Signed-off-by: Juan Villamizar --- vllm/_aiter_ops.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/vllm/_aiter_ops.py b/vllm/_aiter_ops.py index 7a9929eff304..892b7c837cbb 100644 --- a/vllm/_aiter_ops.py +++ b/vllm/_aiter_ops.py @@ -955,8 +955,11 @@ def _rocm_aiter_triton_gemm_a8w8_blockscale_preshuffle_impl( B.reshape(n // 16, k * 16), As, Bs, - dtype=output_dtype, - is_x_scale_tranposed=False, + output_dtype, + None, # y + None, # config + False, # skip_reduce + False, # is_x_scale_transposed (misspelled in aiter) ) From 8561edaa0a2078950b12c0ff6fdd644f12e4f1af Mon Sep 17 00:00:00 2001 From: Juan Villamizar Date: Fri, 9 Oct 2026 10:49:59 -0500 Subject: [PATCH 42/50] Drop gpt-oss RMSNorm+pad custom op in favor of fuse_act_padding The existing RocmAiterTritonAddRMSNormPadFusionPass already matches on gfx1250 (gpt-oss hidden_dim pads 2880 -> 3072, a multiple of 256) when the AITER RMSNorm is enabled, so the model-specific op is redundant. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Juan Villamizar --- vllm/model_executor/models/gpt_oss.py | 62 +-------------------------- 1 file changed, 1 insertion(+), 61 deletions(-) diff --git a/vllm/model_executor/models/gpt_oss.py b/vllm/model_executor/models/gpt_oss.py index a3316a76a450..90286006a62b 100644 --- a/vllm/model_executor/models/gpt_oss.py +++ b/vllm/model_executor/models/gpt_oss.py @@ -50,7 +50,6 @@ from vllm.platforms import current_platform from vllm.sequence import IntermediateTensors from vllm.utils.math_utils import cdiv -from vllm.utils.torch_utils import direct_register_custom_op from vllm.v1.attention.backend import AttentionType from .interfaces import ( @@ -89,45 +88,6 @@ def _get_weight_loader(param: torch.Tensor) -> Callable[..., object]: return weight_loader -def _gpt_oss_rmsnorm_pad_add( - x: torch.Tensor, - residual: torch.Tensor, - weight: torch.Tensor, - eps: float, - pad_to: int, -) -> tuple[torch.Tensor, torch.Tensor]: - from aiter.ops.triton.normalization.fused_add_rmsnorm_pad import ( - fused_add_rmsnorm_pad, - ) - - out, res_out = fused_add_rmsnorm_pad(x, weight, eps, residual, pad_to) - - return out, res_out - - -def _gpt_oss_rmsnorm_pad_add_fake( - x: torch.Tensor, - residual: torch.Tensor, - weight: torch.Tensor, - eps: float, - pad_to: int, -) -> tuple[torch.Tensor, torch.Tensor]: - M, N = x.shape - n_out = ((N + pad_to - 1) // pad_to) * pad_to if pad_to > 0 else N - return ( - torch.empty((M, n_out), dtype=x.dtype, device=x.device), - torch.empty_like(residual), - ) - - -direct_register_custom_op( - op_name="gpt_oss_rmsnorm_pad_add", - op_func=_gpt_oss_rmsnorm_pad_add, - mutates_args=[], - fake_impl=_gpt_oss_rmsnorm_pad_add_fake, -) - - class OAIAttention(nn.Module): # Override to switch RoPE convention. gpt-oss uses NeoX (chunk halves); # privacy-filter and similar derivatives use GPT-J (interleaved pairs). @@ -489,15 +449,6 @@ def __init__( self.input_layernorm = RMSNorm(config.hidden_size, eps=1e-5) self.post_attention_layernorm = RMSNorm(config.hidden_size, eps=1e-5) - self.use_fused_rmsnorm_pad_add = False - if current_platform.is_rocm(): - from vllm.platforms.rocm import on_gfx1250 - - self.use_fused_rmsnorm_pad_add = ( - on_gfx1250() - and self.mlp.experts.moe_config.hidden_dim > config.hidden_size - ) - def forward( self, hidden_states: torch.Tensor, @@ -513,18 +464,7 @@ def forward( hidden_states = self.attn(hidden_states, positions) # Fully Connected - if self.use_fused_rmsnorm_pad_add and residual is not None: - hidden_states, residual = torch.ops.vllm.gpt_oss_rmsnorm_pad_add( - hidden_states, - residual, - self.post_attention_layernorm.weight, - self.post_attention_layernorm.variance_epsilon, - self.mlp.experts.moe_config.hidden_dim, - ) - else: - hidden_states, residual = self.post_attention_layernorm( - hidden_states, residual - ) + hidden_states, residual = self.post_attention_layernorm(hidden_states, residual) output = self.mlp(hidden_states) return output, residual From 480bc0c776ab2aedcd9afe13cec20fae85272275 Mon Sep 17 00:00:00 2001 From: Jaden Mathias Date: Fri, 9 Oct 2026 15:37:46 -0400 Subject: [PATCH 43/50] Seperate Priority backend list for gfx1250 (#1362) Signed-off-by: Jaden Mathias --- .../layers/fused_moe/oracle/mxfp4.py | 30 ++++++++++++++----- .../layers/quantization/utils/mxfp4_utils.py | 6 +++- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py index 9f0b08ba367f..2e34cfb42216 100644 --- a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py +++ b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py @@ -39,6 +39,9 @@ from vllm.model_executor.layers.quantization.utils.mxfp4_utils import ( _swizzle_mxfp4, ) +from vllm.model_executor.layers.quantization.utils.mxfp4_utils import ( + is_rocm_gfx1250 as _is_rocm_gfx1250, +) from vllm.model_executor.layers.quantization.utils.mxfp4_utils import ( mx_scale_kwargs as _mx_scale_kwargs, ) @@ -311,7 +314,9 @@ def backend_to_kernel_cls( AiterExperts, ) - return [AiterW4A4ExpertsMonolithic, AiterExperts] + if _is_rocm_gfx1250(): + return [AiterW4A4ExpertsMonolithic, AiterExperts] + return [AiterExperts] elif backend == Mxfp4MoeBackend.XPU: from vllm.model_executor.layers.fused_moe.experts.xpu_moe import XPUExpertsMxFp4 @@ -431,8 +436,13 @@ def _get_priority_backends() -> list[Mxfp4MoeBackend]: backend-level ``is_supported_config`` check filters by device capability). """ if current_platform.is_rocm(): + if _is_rocm_gfx1250(): + return [ + Mxfp4MoeBackend.AITER_MXFP4_MXFP4, + Mxfp4MoeBackend.AITER_MXFP4_BF16, + Mxfp4MoeBackend.EMULATION, + ] return [ - Mxfp4MoeBackend.AITER_MXFP4_MXFP4, Mxfp4MoeBackend.AITER_MXFP4_BF16, Mxfp4MoeBackend.EMULATION, ] @@ -792,11 +802,17 @@ def select_deepseek_v4_mxfp4_moe_backend( current_platform.is_rocm() and config.routing_method == RoutingMethodType.DeepseekV4 ): - priority_backends = [ - Mxfp4MoeBackend.AITER_MXFP4_MXFP4, - Mxfp4MoeBackend.AITER_MXFP4_BF16, - Mxfp4MoeBackend.TRITON_UNFUSED, - ] + if _is_rocm_gfx1250(): + priority_backends = [ + Mxfp4MoeBackend.AITER_MXFP4_MXFP4, + Mxfp4MoeBackend.AITER_MXFP4_BF16, + Mxfp4MoeBackend.TRITON_UNFUSED, + ] + else: + priority_backends = [ + Mxfp4MoeBackend.AITER_MXFP4_BF16, + Mxfp4MoeBackend.TRITON_UNFUSED, + ] else: priority_backends = _get_priority_backends() diff --git a/vllm/model_executor/layers/quantization/utils/mxfp4_utils.py b/vllm/model_executor/layers/quantization/utils/mxfp4_utils.py index 333fd4aa3ffc..1d0850491b4c 100644 --- a/vllm/model_executor/layers/quantization/utils/mxfp4_utils.py +++ b/vllm/model_executor/layers/quantization/utils/mxfp4_utils.py @@ -13,7 +13,7 @@ logger = init_logger(__name__) -def should_use_gfx1250_mx_scale_swizzle() -> bool: +def is_rocm_gfx1250() -> bool: if not current_platform.is_rocm(): return False from vllm.platforms.rocm import on_gfx1250 @@ -21,6 +21,10 @@ def should_use_gfx1250_mx_scale_swizzle() -> bool: return on_gfx1250() +def should_use_gfx1250_mx_scale_swizzle() -> bool: + return is_rocm_gfx1250() + + def should_use_cdna4_mx_scale_swizzle() -> bool: """Whether to use the CDNA4 swizzled scale layout for mxfp4 on gfx950. From 8158cbc0fb67a1efde6a53bdc48f83f550e70428 Mon Sep 17 00:00:00 2001 From: Juan Villamizar Date: Mon, 12 Oct 2026 15:32:46 -0500 Subject: [PATCH 44/50] linter after merge Signed-off-by: Juan Villamizar --- vllm/model_executor/layers/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vllm/model_executor/layers/utils.py b/vllm/model_executor/layers/utils.py index eff0d348aa5a..4c341c7be5fd 100644 --- a/vllm/model_executor/layers/utils.py +++ b/vllm/model_executor/layers/utils.py @@ -300,7 +300,7 @@ def rocm_unquantized_gemm_impl( return linear_batch_invariant(x, weight, bias) - from vllm.platforms.rocm import on_gfx1x, on_gfx9, on_gfx950, on_gfx1250 + from vllm.platforms.rocm import on_gfx1x, on_gfx9, on_gfx950 n = x.numel() // x.size(-1) m = weight.shape[0] From 63040d705da9cfc46ca2dc6022640b53eae03144 Mon Sep 17 00:00:00 2001 From: Juan Villamizar Date: Tue, 13 Oct 2026 10:02:30 -0500 Subject: [PATCH 45/50] Fixing merge Signed-off-by: Juan Villamizar --- vllm/model_executor/layers/fused_moe/oracle/mxfp4.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py index 11eb2c2e6d96..cc51896c939c 100644 --- a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py +++ b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py @@ -315,8 +315,8 @@ def backend_to_kernel_cls( ) if _is_rocm_gfx1250(): - return [AiterW4A4ExpertsMonolithic, AiterExperts] - return [AiterExperts] + return [AiterW4A4ExpertsMonolithic, AiterMxfp4Experts] + return [AiterMxfp4Experts] elif backend == Mxfp4MoeBackend.XPU: from vllm.model_executor.layers.fused_moe.experts.xpu_moe import XPUExpertsMxFp4 From f319a98dc50af1721fb966c1fc0a178c49d85601 Mon Sep 17 00:00:00 2001 From: Jaden Mathias Date: Tue, 13 Oct 2026 14:29:28 -0400 Subject: [PATCH 46/50] Add new Enum to handle gfx1250 backends (#1365) Signed-off-by: Jaden Mathias --- tests/kernels/moe/test_ocp_mx_moe.py | 15 +- vllm/config/kernel.py | 3 + .../layers/fused_moe/oracle/mxfp4.py | 149 +++++++++--------- 3 files changed, 91 insertions(+), 76 deletions(-) diff --git a/tests/kernels/moe/test_ocp_mx_moe.py b/tests/kernels/moe/test_ocp_mx_moe.py index 0a8f5c29d034..968781a188de 100644 --- a/tests/kernels/moe/test_ocp_mx_moe.py +++ b/tests/kernels/moe/test_ocp_mx_moe.py @@ -42,13 +42,15 @@ ROCM_TRITON_KERNELS_AVAILABLE = False ROCM_AITER_AVAILABLE = is_aiter_found() ROCM_GFX950 = False +ROCM_GFX1250 = False if ROCM_AVAILABLE: - from vllm.platforms.rocm import on_gfx950 + from vllm.platforms.rocm import on_gfx950, on_gfx1250 from vllm.utils.import_utils import has_triton_kernels ROCM_TRITON_KERNELS_AVAILABLE = has_triton_kernels() ROCM_GFX950 = on_gfx950() + ROCM_GFX1250 = on_gfx1250() if ROCM_AITER_AVAILABLE: from aiter.ops.triton.quant import dynamic_mxfp4_quant @@ -1568,6 +1570,15 @@ def test_trtllm_gen_mxfp8_block_scale_moe( "requires_aiter": True, "requires_gfx950": True, }, + "AITER_TRITON_MXFP4_MXFP4": { + "activation": "SILU", + "act_type": "mxfp4", + "rtol": 1.0, + "percent": 0.8, + "requires_aiter": True, + "requires_gfx950": False, + "requires_gfx1250": True, + }, } @@ -1607,6 +1618,8 @@ def test_rocm_mxfp4_moe_oracle( pytest.skip(f"Backend {backend_name} requires AITER") if config["requires_gfx950"] and not ROCM_GFX950: pytest.skip(f"Backend {backend_name} requires GFX950") + if config.get("requires_gfx1250") and not ROCM_GFX1250: + pytest.skip(f"Backend {backend_name} requires GFX1250") import vllm.distributed.parallel_state as ps from vllm.config import VllmConfig, set_current_vllm_config diff --git a/vllm/config/kernel.py b/vllm/config/kernel.py index 5c4b877fd795..a1b141237f3d 100644 --- a/vllm/config/kernel.py +++ b/vllm/config/kernel.py @@ -139,6 +139,7 @@ def with_default( "aiter", "aiter_mega_moe", "aiter_triton_mxfp4_bf16", + "aiter_triton_mxfp4_mxfp4", "flydsl", "hpc", "emulation", @@ -329,6 +330,8 @@ class KernelConfig: DeepSeek-V4 only) - "aiter_triton_mxfp4_bf16": Use the AITER Triton MXFP4 W4A16 (moe_gemm_a16w4) MoE kernel (ROCm gfx942/gfx950/gfx1250) + - "aiter_triton_mxfp4_mxfp4": Use the AITER Triton/Gluon MXFP4 W4A4 + (moe_gemm_a4w4) MoE kernel (ROCm gfx1250) - "flydsl": Use AMD FlyDSL kernels (ROCm only) - "rdna3": Use the fused RDNA3 W4A16 HIP kernel (ROCm gfx1100 only) - "hpc": Use HPC kernels (FP8 and Hopper only) diff --git a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py index cc51896c939c..61ed0e858872 100644 --- a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py +++ b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py @@ -39,9 +39,6 @@ from vllm.model_executor.layers.quantization.utils.mxfp4_utils import ( _swizzle_mxfp4, ) -from vllm.model_executor.layers.quantization.utils.mxfp4_utils import ( - is_rocm_gfx1250 as _is_rocm_gfx1250, -) from vllm.model_executor.layers.quantization.utils.mxfp4_utils import ( mx_scale_kwargs as _mx_scale_kwargs, ) @@ -155,7 +152,9 @@ class Mxfp4MoeBackend(Enum): # W4A16: aiter Triton moe_gemm_a16w4 kernel (gfx942/gfx950/gfx1250) AITER_TRITON_MXFP4_BF16 = "AITER_TRITON_MXFP4_BF16" AITER_MXFP4_FP8 = "AITER_MXFP4_FP8" # W4A8: triton kernel - AITER_MXFP4_MXFP4 = "AITER_MXFP4_MXFP4" # W4A4: CK kernel + AITER_MXFP4_MXFP4 = "AITER_MXFP4_MXFP4" # W4A4: CK kernel (gfx950) + # W4A4: aiter Triton/Gluon moe_gemm_a4w4 kernel (gfx1250) + AITER_TRITON_MXFP4_MXFP4 = "AITER_TRITON_MXFP4_MXFP4" # Triton TRITON = "TRITON" TRITON_UNFUSED = "TRITON_UNFUSED" @@ -307,17 +306,19 @@ def backend_to_kernel_cls( return [AiterW4A8ExpertsMonolithic] elif backend == Mxfp4MoeBackend.AITER_MXFP4_MXFP4: - from vllm.model_executor.layers.fused_moe.experts.aiter_mxfp4_w4a4_moe import ( - AiterW4A4ExpertsMonolithic, - ) from vllm.model_executor.layers.fused_moe.experts.rocm_aiter_moe import ( AiterMxfp4Experts, ) - if _is_rocm_gfx1250(): - return [AiterW4A4ExpertsMonolithic, AiterMxfp4Experts] return [AiterMxfp4Experts] + elif backend == Mxfp4MoeBackend.AITER_TRITON_MXFP4_MXFP4: + from vllm.model_executor.layers.fused_moe.experts.aiter_mxfp4_w4a4_moe import ( + AiterW4A4ExpertsMonolithic, + ) + + return [AiterW4A4ExpertsMonolithic] + elif backend == Mxfp4MoeBackend.XPU: from vllm.model_executor.layers.fused_moe.experts.xpu_moe import XPUExpertsMxFp4 @@ -378,9 +379,11 @@ def map_mxfp4_backend(runner_backend: MoEBackend) -> list[Mxfp4MoeBackend]: Mxfp4MoeBackend.AITER_TRITON_MXFP4_BF16, Mxfp4MoeBackend.AITER_MXFP4_FP8, Mxfp4MoeBackend.AITER_MXFP4_MXFP4, + Mxfp4MoeBackend.AITER_TRITON_MXFP4_MXFP4, ], "aiter_triton_mxfp4_bf16": [Mxfp4MoeBackend.AITER_TRITON_MXFP4_BF16], "aiter_mxfp4_fp8": [Mxfp4MoeBackend.AITER_MXFP4_FP8], + "aiter_triton_mxfp4_mxfp4": [Mxfp4MoeBackend.AITER_TRITON_MXFP4_MXFP4], "aiter_mxfp4_mxfp4": [Mxfp4MoeBackend.AITER_MXFP4_MXFP4], "xpu": [Mxfp4MoeBackend.XPU], "cpu": [Mxfp4MoeBackend.CPU], @@ -404,6 +407,7 @@ def _get_priority_backends_for_gpt_oss() -> list[Mxfp4MoeBackend]: Mxfp4MoeBackend.AITER_TRITON_MXFP4_BF16, Mxfp4MoeBackend.AITER_MXFP4_FP8, Mxfp4MoeBackend.AITER_MXFP4_MXFP4, + Mxfp4MoeBackend.AITER_TRITON_MXFP4_MXFP4, Mxfp4MoeBackend.TRITON, Mxfp4MoeBackend.FLASHINFER_CUTLASS_MXFP4_BF16, Mxfp4MoeBackend.FLASHINFER_CUTLASS_MXFP4_MXFP8, @@ -436,13 +440,10 @@ def _get_priority_backends() -> list[Mxfp4MoeBackend]: backend-level ``is_supported_config`` check filters by device capability). """ if current_platform.is_rocm(): - if _is_rocm_gfx1250(): - return [ - Mxfp4MoeBackend.AITER_MXFP4_MXFP4, - Mxfp4MoeBackend.AITER_MXFP4_BF16, - Mxfp4MoeBackend.EMULATION, - ] + # AITER_TRITON_MXFP4_MXFP4 only supports gfx1250, so other ROCm archs + # fall through to AITER_MXFP4_BF16. return [ + Mxfp4MoeBackend.AITER_TRITON_MXFP4_MXFP4, Mxfp4MoeBackend.AITER_MXFP4_BF16, Mxfp4MoeBackend.EMULATION, ] @@ -478,6 +479,7 @@ def _backend_activation_key(backend: Mxfp4MoeBackend) -> QuantKey | None: return kFp8StaticTensorSym if backend in ( Mxfp4MoeBackend.AITER_MXFP4_MXFP4, + Mxfp4MoeBackend.AITER_TRITON_MXFP4_MXFP4, Mxfp4MoeBackend.CUTLASS_MXFP4_MXFP4, ): return kMxfp4Dynamic @@ -802,17 +804,13 @@ def select_deepseek_v4_mxfp4_moe_backend( current_platform.is_rocm() and config.routing_method == RoutingMethodType.DeepseekV4 ): - if _is_rocm_gfx1250(): - priority_backends = [ - Mxfp4MoeBackend.AITER_MXFP4_MXFP4, - Mxfp4MoeBackend.AITER_MXFP4_BF16, - Mxfp4MoeBackend.TRITON_UNFUSED, - ] - else: - priority_backends = [ - Mxfp4MoeBackend.AITER_MXFP4_BF16, - Mxfp4MoeBackend.TRITON_UNFUSED, - ] + # AITER_TRITON_MXFP4_MXFP4 only supports gfx1250, so other ROCm archs + # fall through to AITER_MXFP4_BF16. + priority_backends = [ + Mxfp4MoeBackend.AITER_TRITON_MXFP4_MXFP4, + Mxfp4MoeBackend.AITER_MXFP4_BF16, + Mxfp4MoeBackend.TRITON_UNFUSED, + ] else: priority_backends = _get_priority_backends() @@ -973,12 +971,6 @@ def convert_gpt_oss_weight_to_mxfp4_moe_kernel_format( sf_block_size = 32 # mxfp4 block size - is_gfx1250 = False - if current_platform.is_rocm(): - from vllm.platforms.rocm import on_gfx1250 - - is_gfx1250 = on_gfx1250() - if mxfp4_backend == Mxfp4MoeBackend.HUMMING: from vllm.model_executor.layers.quantization.utils.humming import ( convert_to_humming_moe_kernel_format, @@ -1231,55 +1223,60 @@ def swap_every_two_rows(x, axis=-1): w2_bias, ) - elif mxfp4_backend == Mxfp4MoeBackend.AITER_MXFP4_MXFP4: + elif mxfp4_backend == Mxfp4MoeBackend.AITER_TRITON_MXFP4_MXFP4: if w13_bias is not None: w13_bias = w13_bias.data.to(torch.float32) if w2_bias is not None: w2_bias = w2_bias.data.to(torch.float32) - if is_gfx1250: - # Triton moe_gemm_a4w4: uint8 column-major weights. - w13_data = w13_weight.data.view(torch.uint8) - w2_data = w2_weight.data.view(torch.uint8) - w13_scale_data = w13_weight_scale.data.view(torch.uint8) - - # SILU applies the activation outside the GEMM and keeps the - # checkpoint's packed halves; the SWIGLUOAI epilogue needs them - # interleaved. - if getattr(layer, "activation", None) in ( - MoEActivation.SWIGLUOAI, - MoEActivation.SWIGLUOAI_UNINTERLEAVE, - ): - w13_data = _interleave_gate_up_rows(w13_data) - w13_scale_data = _interleave_gate_up_rows(w13_scale_data) - - # Transpose to [E, K, N] — the view has stride(-2)==1 (column-major), - # which is what moe_gemm_a4w4 asserts. - w13_data = w13_data.transpose(1, 2) - w2_data = w2_data.transpose(1, 2) - - # Scales are [E, N, K_scale]; both kernels index them as - # [E, K_scale, N] with K innermost, so transpose(1, 2) - # Keep them uint8: Triton moe_gemm_a4w4 takes e8m0 scales - w13_scale = w13_scale_data.transpose(1, 2) - w2_scale = w2_weight_scale.data.view(torch.uint8).transpose(1, 2) - - from aiter.ops.triton.utils.shuffle import shuffle_scale_moe - - w13_scale = shuffle_scale_moe( - w13_scale, - arch="gfx1250", - preshuffle_factor=32, - scale_kwidth=4, - ) - w2_scale = shuffle_scale_moe( - w2_scale, - arch="gfx1250", - preshuffle_factor=32, - scale_kwidth=4, - ) + # Triton moe_gemm_a4w4: uint8 column-major weights. + w13_data = w13_weight.data.view(torch.uint8) + w2_data = w2_weight.data.view(torch.uint8) + w13_scale_data = w13_weight_scale.data.view(torch.uint8) + + # SILU applies the activation outside the GEMM and keeps the + # checkpoint's packed halves; the SWIGLUOAI epilogue needs them + # interleaved. + if getattr(layer, "activation", None) in ( + MoEActivation.SWIGLUOAI, + MoEActivation.SWIGLUOAI_UNINTERLEAVE, + ): + w13_data = _interleave_gate_up_rows(w13_data) + w13_scale_data = _interleave_gate_up_rows(w13_scale_data) + + # Transpose to [E, K, N] — the view has stride(-2)==1 (column-major), + # which is what moe_gemm_a4w4 asserts. + w13_data = w13_data.transpose(1, 2) + w2_data = w2_data.transpose(1, 2) + + # Scales are [E, N, K_scale]; both kernels index them as + # [E, K_scale, N] with K innermost, so transpose(1, 2) + # Keep them uint8: Triton moe_gemm_a4w4 takes e8m0 scales + w13_scale = w13_scale_data.transpose(1, 2) + w2_scale = w2_weight_scale.data.view(torch.uint8).transpose(1, 2) + + from aiter.ops.triton.utils.shuffle import shuffle_scale_moe + + w13_scale = shuffle_scale_moe( + w13_scale, + arch="gfx1250", + preshuffle_factor=32, + scale_kwidth=4, + ) + w2_scale = shuffle_scale_moe( + w2_scale, + arch="gfx1250", + preshuffle_factor=32, + scale_kwidth=4, + ) + + return (w13_data, w2_data, w13_scale, w2_scale, w13_bias, w2_bias) - return (w13_data, w2_data, w13_scale, w2_scale, w13_bias, w2_bias) + elif mxfp4_backend == Mxfp4MoeBackend.AITER_MXFP4_MXFP4: + if w13_bias is not None: + w13_bias = w13_bias.data.to(torch.float32) + if w2_bias is not None: + w2_bias = w2_bias.data.to(torch.float32) # e8m0_shuffle on weight scales (GFX950 swizzle layout) from aiter.utility.fp4_utils import e8m0_shuffle @@ -2005,6 +2002,7 @@ def shuffle_weight(w: torch.Tensor) -> torch.Tensor: ) elif mxfp4_backend in ( Mxfp4MoeBackend.AITER_MXFP4_MXFP4, + Mxfp4MoeBackend.AITER_TRITON_MXFP4_MXFP4, Mxfp4MoeBackend.AITER_MXFP4_FP8, ): return convert_gpt_oss_weight_to_mxfp4_moe_kernel_format( @@ -2158,6 +2156,7 @@ def make_mxfp4_moe_quant_config( ) elif mxfp4_backend in ( Mxfp4MoeBackend.AITER_MXFP4_MXFP4, + Mxfp4MoeBackend.AITER_TRITON_MXFP4_MXFP4, Mxfp4MoeBackend.CUTLASS_MXFP4_MXFP4, ): return ocp_mx_moe_quant_config( From ddf7eb627c2388ddc9e886bd5ba6bd02e1a8ae01 Mon Sep 17 00:00:00 2001 From: Juan Villamizar Date: Wed, 14 Oct 2026 11:21:59 -0500 Subject: [PATCH 47/50] Review comments Signed-off-by: Juan Villamizar --- .../layers/quantization/utils/mxfp4_utils.py | 6 +----- vllm/v1/attention/backends/rocm_aiter_unified_attn.py | 9 +++++++-- vllm/v1/sample/ops/topk_topp_sampler.py | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/vllm/model_executor/layers/quantization/utils/mxfp4_utils.py b/vllm/model_executor/layers/quantization/utils/mxfp4_utils.py index 1d0850491b4c..333fd4aa3ffc 100644 --- a/vllm/model_executor/layers/quantization/utils/mxfp4_utils.py +++ b/vllm/model_executor/layers/quantization/utils/mxfp4_utils.py @@ -13,7 +13,7 @@ logger = init_logger(__name__) -def is_rocm_gfx1250() -> bool: +def should_use_gfx1250_mx_scale_swizzle() -> bool: if not current_platform.is_rocm(): return False from vllm.platforms.rocm import on_gfx1250 @@ -21,10 +21,6 @@ def is_rocm_gfx1250() -> bool: return on_gfx1250() -def should_use_gfx1250_mx_scale_swizzle() -> bool: - return is_rocm_gfx1250() - - def should_use_cdna4_mx_scale_swizzle() -> bool: """Whether to use the CDNA4 swizzled scale layout for mxfp4 on gfx950. diff --git a/vllm/v1/attention/backends/rocm_aiter_unified_attn.py b/vllm/v1/attention/backends/rocm_aiter_unified_attn.py index d42623dd40b2..4a9f67a6374e 100644 --- a/vllm/v1/attention/backends/rocm_aiter_unified_attn.py +++ b/vllm/v1/attention/backends/rocm_aiter_unified_attn.py @@ -29,7 +29,12 @@ RocmAttentionMetadata, RocmAttentionMetadataBuilder, ) -from vllm.v1.kv_cache_interface import AttentionSpec, KVCacheLayout +from vllm.v1.kv_cache_interface import ( + AttentionSpec, + KVCacheLayout, + KVQuantMode, + get_kv_quant_mode, +) logger = init_logger(__name__) @@ -371,7 +376,7 @@ def do_kv_cache_update( reshape_and_cache_shuffle_triton, ) - if is_quantized_kv_cache(self.kv_cache_dtype): + if get_kv_quant_mode(self.kv_cache_dtype) == KVQuantMode.FP8_PER_TENSOR: key_cache = key_cache.view(self.fp8_dtype) value_cache = value_cache.view(self.fp8_dtype) _dummy = torch.empty(1, dtype=torch.float32, device=key.device) diff --git a/vllm/v1/sample/ops/topk_topp_sampler.py b/vllm/v1/sample/ops/topk_topp_sampler.py index fb1da14089fb..d38497b2ff10 100644 --- a/vllm/v1/sample/ops/topk_topp_sampler.py +++ b/vllm/v1/sample/ops/topk_topp_sampler.py @@ -402,7 +402,7 @@ def forward_hip( k: torch.Tensor | None, p: torch.Tensor | None, ) -> tuple[torch.Tensor, torch.Tensor | None]: - """Optimized ROCm/aiter path (same structure as forward_cuda).""" + """Optimized ROCm/aiter path.""" if self.on_gfx1250: if ( k is None From 8d048d81efee7d6633deb32c9fc235a09ccb0691 Mon Sep 17 00:00:00 2001 From: Juan Villamizar Date: Wed, 14 Oct 2026 13:39:10 -0500 Subject: [PATCH 48/50] More review comments Signed-off-by: Juan Villamizar --- .../kernels/linear/scaled_mm/aiter.py | 2 +- .../experts/aiter_mxfp4_w4a16_moe.py | 12 ++------ .../fused_moe/experts/aiter_mxfp4_w4a4_moe.py | 5 +++- .../fused_moe/experts/aiter_mxfp4_w4a8_moe.py | 10 ++----- .../fused_moe/experts/rocm_aiter_moe.py | 28 ------------------- .../layers/quantization/utils/mxfp4_utils.py | 9 ++++++ 6 files changed, 18 insertions(+), 48 deletions(-) diff --git a/vllm/model_executor/kernels/linear/scaled_mm/aiter.py b/vllm/model_executor/kernels/linear/scaled_mm/aiter.py index b666960a1ce0..23a5886ab73b 100644 --- a/vllm/model_executor/kernels/linear/scaled_mm/aiter.py +++ b/vllm/model_executor/kernels/linear/scaled_mm/aiter.py @@ -416,7 +416,7 @@ def __init__(self, config: FP8ScaledMMLinearLayerConfig): use_ue8m0=False, column_major_scales=True, ) - logger.debug_once( + logger.info_once( "Using aiter A8W8 blockscale B-preshuffle GEMM for N=%d K=%d", n, k ) diff --git a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a16_moe.py b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a16_moe.py index 8ec3baf7c507..0d25510aa67a 100644 --- a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a16_moe.py +++ b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a16_moe.py @@ -52,18 +52,10 @@ def _aiter_w4a16_silu_via_a8w4( from aiter.ops.triton.moe_op_gemm_a8w4 import moe_gemm_a8w4 from vllm.model_executor.layers.quantization.utils.mxfp4_utils import ( - should_use_cdna4_mx_scale_swizzle, - should_use_gfx1250_mx_scale_swizzle, + get_aiter_mx_scale_swizzle, ) - # gfx1250: _swizzle_mxfp4 GFX1250-swizzles the weight scales at load time, - # so the a8w4 kernel must be told to unswizzle them. - if should_use_gfx1250_mx_scale_swizzle(): - swz = "GFX1250_SCALE" - elif should_use_cdna4_mx_scale_swizzle(): - swz = "CDNA4_SCALE" - else: - swz = None + swz = get_aiter_mx_scale_swizzle() quant_dtype = torch.float8_e4m3fn g1_gammas = gammas if apply_router_weight_on_input else None diff --git a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a4_moe.py b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a4_moe.py index b8f26cdedf16..a40d8184fb10 100644 --- a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a4_moe.py +++ b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a4_moe.py @@ -63,6 +63,9 @@ def aiter_triton_kernel_w4a4_moe_forward( except ImportError: from aiter.ops.triton.moe_routing import routing as _routing_mod + from vllm.model_executor.layers.quantization.utils.mxfp4_utils import ( + get_aiter_mx_scale_swizzle, + ) from vllm.platforms.rocm import on_gfx1250 if on_gfx1250(): @@ -150,7 +153,7 @@ def aiter_triton_kernel_w4a4_moe_forward( "limit": swiglu_limit, "swiglu_add_residual": quant_config.gemm1_beta == 1.0, } - swizzle_mx_scale = "GFX1250_SCALE" if on_gfx1250() else None + swizzle_mx_scale = get_aiter_mx_scale_swizzle() x_q, x_scale = mxfp4_quant(hidden_states.to(torch.bfloat16)) diff --git a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py index 95d58e8c201b..f7d2be94aad2 100644 --- a/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py +++ b/vllm/model_executor/layers/fused_moe/experts/aiter_mxfp4_w4a8_moe.py @@ -124,17 +124,11 @@ def triton_kernel_fused_mxfp4_w4a8_experts( from aiter.ops.triton.quant_moe import downcast_to_static_fp8 from vllm.model_executor.layers.quantization.utils.mxfp4_utils import ( - should_use_cdna4_mx_scale_swizzle, - should_use_gfx1250_mx_scale_swizzle, + get_aiter_mx_scale_swizzle, weight_mx_scale, ) - if should_use_gfx1250_mx_scale_swizzle(): - _swizzle_mx_scale = "GFX1250_SCALE" - elif should_use_cdna4_mx_scale_swizzle(): - _swizzle_mx_scale = "CDNA4_SCALE" - else: - _swizzle_mx_scale = None + _swizzle_mx_scale = get_aiter_mx_scale_swizzle() assert quant_config.w1_precision is not None, ( "w1_precision in quant config can't be None" diff --git a/vllm/model_executor/layers/fused_moe/experts/rocm_aiter_moe.py b/vllm/model_executor/layers/fused_moe/experts/rocm_aiter_moe.py index 50668302ddab..5c506eae123a 100644 --- a/vllm/model_executor/layers/fused_moe/experts/rocm_aiter_moe.py +++ b/vllm/model_executor/layers/fused_moe/experts/rocm_aiter_moe.py @@ -482,34 +482,6 @@ def _supports_current_device() -> bool: def _supports_no_act_and_mul() -> bool: return False - @staticmethod - def _supports_quant_scheme( - weight_key: QuantKey | None, - activation_key: QuantKey | None, - ) -> bool: - SUPPORTED_W_A = [ - (None, None), - (kFp8Static128BlockSym, kFp8Dynamic128Sym), - (kFp8StaticTensorSym, kFp8StaticTensorSym), - (kFp8StaticTensorSym, kFp8DynamicTensorSym), - (kFp8StaticChannelSym, kFp8DynamicTokenSym), - (kMxfp4Static, None), - (kMxfp4Static, kMxfp4Dynamic), - ] - if (weight_key, activation_key) not in SUPPORTED_W_A: - return False - if weight_key == kMxfp4Static: - from vllm.platforms.rocm import on_gfx950, on_gfx1250 - - if on_gfx950(): - return True - - if on_gfx1250(): - return activation_key is None - - return False - return True - @staticmethod def _supports_activation(activation: MoEActivation) -> bool: return activation in [ diff --git a/vllm/model_executor/layers/quantization/utils/mxfp4_utils.py b/vllm/model_executor/layers/quantization/utils/mxfp4_utils.py index 333fd4aa3ffc..074ee9c95fa1 100644 --- a/vllm/model_executor/layers/quantization/utils/mxfp4_utils.py +++ b/vllm/model_executor/layers/quantization/utils/mxfp4_utils.py @@ -35,6 +35,15 @@ def should_use_cdna4_mx_scale_swizzle() -> bool: return on_gfx950() and get_tensor_model_parallel_world_size() <= 2 +def get_aiter_mx_scale_swizzle() -> str | None: + """AITER `swizzle_mx_scale` arg matching the load-time scale swizzle.""" + if should_use_gfx1250_mx_scale_swizzle(): + return "GFX1250_SCALE" + if should_use_cdna4_mx_scale_swizzle(): + return "CDNA4_SCALE" + return None + + def weight_mx_scale(precision_config): """mxfp4 weight scale from a triton_kernels PrecisionConfig: 3.8 exposes it as b_mx_scale, 3.5.1/3.6 as weight_scale. From f66ef0b0505b00cdf65f93716a05815005b07318 Mon Sep 17 00:00:00 2001 From: Jaden Mathias Date: Wed, 14 Oct 2026 16:05:38 -0400 Subject: [PATCH 49/50] Remove from priority backend (#1374) Signed-off-by: Jaden Mathias --- vllm/model_executor/layers/fused_moe/oracle/mxfp4.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py index 61ed0e858872..ee433447731e 100644 --- a/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py +++ b/vllm/model_executor/layers/fused_moe/oracle/mxfp4.py @@ -407,7 +407,6 @@ def _get_priority_backends_for_gpt_oss() -> list[Mxfp4MoeBackend]: Mxfp4MoeBackend.AITER_TRITON_MXFP4_BF16, Mxfp4MoeBackend.AITER_MXFP4_FP8, Mxfp4MoeBackend.AITER_MXFP4_MXFP4, - Mxfp4MoeBackend.AITER_TRITON_MXFP4_MXFP4, Mxfp4MoeBackend.TRITON, Mxfp4MoeBackend.FLASHINFER_CUTLASS_MXFP4_BF16, Mxfp4MoeBackend.FLASHINFER_CUTLASS_MXFP4_MXFP8, @@ -440,10 +439,7 @@ def _get_priority_backends() -> list[Mxfp4MoeBackend]: backend-level ``is_supported_config`` check filters by device capability). """ if current_platform.is_rocm(): - # AITER_TRITON_MXFP4_MXFP4 only supports gfx1250, so other ROCm archs - # fall through to AITER_MXFP4_BF16. return [ - Mxfp4MoeBackend.AITER_TRITON_MXFP4_MXFP4, Mxfp4MoeBackend.AITER_MXFP4_BF16, Mxfp4MoeBackend.EMULATION, ] @@ -804,10 +800,7 @@ def select_deepseek_v4_mxfp4_moe_backend( current_platform.is_rocm() and config.routing_method == RoutingMethodType.DeepseekV4 ): - # AITER_TRITON_MXFP4_MXFP4 only supports gfx1250, so other ROCm archs - # fall through to AITER_MXFP4_BF16. priority_backends = [ - Mxfp4MoeBackend.AITER_TRITON_MXFP4_MXFP4, Mxfp4MoeBackend.AITER_MXFP4_BF16, Mxfp4MoeBackend.TRITON_UNFUSED, ] From 48d006a72aeb0259fd2fd110d092a67aace40f20 Mon Sep 17 00:00:00 2001 From: Juan Villamizar Date: Wed, 14 Oct 2026 15:14:37 -0500 Subject: [PATCH 50/50] Review meeting comments Signed-off-by: Juan Villamizar --- vllm/models/deepseek_v4/amd/rocm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vllm/models/deepseek_v4/amd/rocm.py b/vllm/models/deepseek_v4/amd/rocm.py index 08992e2c2aa8..5353b13ebc94 100644 --- a/vllm/models/deepseek_v4/amd/rocm.py +++ b/vllm/models/deepseek_v4/amd/rocm.py @@ -1305,9 +1305,9 @@ def forward_mqa( # the rotation into the decode reduce would apply it twice. Only the # BF16 einsum path hands its rotation off to the decode. fuse_inv_rope = self._wo_a_fp8_weight is None - fuse_decode_inv_rope = fuse_inv_rope and not on_gfx1250() rotated = 0 if num_decodes > 0: + fuse_decode_inv_rope = fuse_inv_rope and not on_gfx1250() rotated = self._forward_decode( q=q[:num_decode_tokens], positions=(