from __future__ import annotations from typing import TYPE_CHECKING, NamedTuple, Optional import torch from sglang.srt.distributed import ( get_tp_group, ) from sglang.srt.distributed.device_communicators.pynccl_allocator import ( use_symmetric_memory, ) from sglang.srt.layers.dp_attention import ( get_dp_global_num_tokens, get_local_dp_buffer, is_allocation_symmetric, ) from sglang.srt.layers.moe.moe_runner.base import MoeRunnerConfig from sglang.srt.layers.moe.token_dispatcher.base import ( BaseDispatcher, CombineInput, CombineInputFormat, DispatchOutput, DispatchOutputFormat, ) from sglang.srt.layers.moe.topk import StandardTopKOutput, TopKOutput, TopKOutputChecker from sglang.srt.layers.moe.utils import ( get_moe_a2a_backend, get_moe_runner_backend, should_use_flashinfer_cutlass_moe_fp4_allgather, ) from sglang.srt.runtime_context import get_parallel from sglang.srt.utils.common import ( get_bool_env_var, get_device, is_hip, ) _is_hip = is_hip() _use_aiter = get_bool_env_var("SGLANG_USE_AITER") and _is_hip if TYPE_CHECKING: from sglang.srt.layers.moe.topk import TopKOutput try: from flashinfer import ( nvfp4_block_scale_interleave as nvfp4_block_scale_interleave_flashinfer, ) from sglang.srt.layers.quantization.modelopt_quant import ( fp4_quantize as fp4_quantize_flashinfer, ) except ImportError: fp4_quantize_flashinfer = None nvfp4_block_scale_interleave_flashinfer = None class StandardDispatchOutput(NamedTuple): """Standard dispatch output.""" hidden_states: torch.Tensor hidden_states_scale: Optional[torch.Tensor] topk_output: TopKOutput @property def format(self) -> DispatchOutputFormat: return DispatchOutputFormat.STANDARD assert isinstance(StandardDispatchOutput, DispatchOutput) class StandardCombineInput(NamedTuple): """Standard combine input.""" hidden_states: torch.Tensor @property def format(self) -> CombineInputFormat: return CombineInputFormat.STANDARD assert isinstance(StandardCombineInput, CombineInput) class StandardDispatcher(BaseDispatcher): def __init__(self, moe_runner_config: MoeRunnerConfig): super().__init__() self.moe_ep_size = get_parallel().moe_ep_size backend = get_moe_runner_backend() self.enable_flashinfer_cutlass_moe = backend.is_flashinfer_cutlass() self.enable_flashinfer_mxfp4_moe = backend.is_flashinfer_mxfp4() self.enable_flashinfer_trtllm_routed_moe = backend.is_flashinfer_trtllm_routed() # AITER fast paths can be on while the MoE runner stays Triton; only the # AITER runner keeps global expert IDs, so Triton must remap to local range. self.use_aiter_moe_runner = backend.is_aiter() or ( backend.is_auto() and _use_aiter and get_moe_a2a_backend().supports_aiter() ) # Skip local expert mapping when the backend handles EP with global expert IDs: # - cutlass / cutedsl / trtllm_routed handle EP internally # - mxfp4 dispatcher mapping is already global self.skip_local_expert_mapping = ( backend.is_flashinfer_cutlass() or backend.is_flashinfer_cutedsl() or backend.is_flashinfer_trtllm() or backend.is_experimental_sgl_trtllm() or backend.is_flashinfer_trtllm_routed() or self.enable_flashinfer_mxfp4_moe ) self.num_experts = moe_runner_config.num_experts self.num_local_experts = moe_runner_config.num_local_experts self.num_local_shared_experts = moe_runner_config.num_fused_shared_experts self.num_local_routed_experts = ( self.num_local_experts - self.num_local_shared_experts ) self.moe_ep_rank = get_parallel().moe_ep_rank self.local_expert_mapping = None self.expert_mask_gpu = None def dispatch( self, hidden_states: torch.Tensor, topk_output: TopKOutput ) -> StandardDispatchOutput: if should_use_flashinfer_cutlass_moe_fp4_allgather(): # all-gather fp4 hidden states if ( fp4_quantize_flashinfer is None or nvfp4_block_scale_interleave_flashinfer is None ): raise RuntimeError( "FlashInfer fp4_quantize and nvfp4_block_scale_interleave " "are required for the flashinfer_cutlass FP4 all-gather " "path." ) global_scale = self.quant_config.get("input_global_scale", None) assert global_scale is not None, "input_global_scale is not set" topk_weights, topk_ids = topk_output.topk_weights, topk_output.topk_ids # Quantize before comm, swizzle after. with use_symmetric_memory( get_tp_group(), disabled=not is_allocation_symmetric() ): if hidden_states.shape[0] > 0: x, x_sf = fp4_quantize_flashinfer( hidden_states, global_scale, is_sf_swizzled_layout=False ) else: x_col = hidden_states.shape[1] x = torch.zeros( 0, x_col // 2, dtype=torch.uint8, device=hidden_states.device ) x_sf = torch.zeros( 0, x_col // 16, dtype=torch.uint8, device=hidden_states.device ) topk_weights, topk_ids, x, x_sf = get_tp_group().all_gatherv( [topk_weights, topk_ids, x, x_sf], sizes=get_dp_global_num_tokens() ) # TODO: fuse into cutlass moe x_sf = nvfp4_block_scale_interleave_flashinfer(x_sf) hidden_states = x hidden_states_scale = x_sf topk_output = StandardTopKOutput( topk_weights=topk_weights, topk_ids=topk_ids, router_logits=topk_output.router_logits, # never tested ) else: hidden_states = hidden_states hidden_states_scale = None if ( self.moe_ep_size > 1 and not self.skip_local_expert_mapping and TopKOutputChecker.format_is_standard(topk_output) ): if self.local_expert_mapping is None: device = get_device() self.local_expert_mapping = torch.full( (self.num_experts,), -1, dtype=torch.int32, device=device ) self.local_expert_mapping[ self.moe_ep_rank * self.num_local_routed_experts : (self.moe_ep_rank + 1) * self.num_local_routed_experts ] = torch.arange( 0, self.num_local_routed_experts, dtype=torch.int32, device=device ) if self.num_local_shared_experts > 0: self.local_expert_mapping[-self.num_local_shared_experts :] = ( torch.arange( self.num_local_routed_experts, self.num_local_routed_experts + self.num_local_shared_experts, dtype=torch.int32, device="cpu", ) ) if self.local_expert_mapping is not None and not self.skip_local_expert_mapping: if self.use_aiter_moe_runner: self.expert_mask_gpu = ( ( (self.local_expert_mapping >= 0) & (self.local_expert_mapping < self.num_local_experts) ) .to(torch.int32) .to(device="cuda") ) else: if TopKOutputChecker.format_is_standard(topk_output): topk_output = topk_output._replace( topk_ids=self.local_expert_mapping[topk_output.topk_ids] ) elif TopKOutputChecker.format_is_triton_kernels(topk_output): raise NotImplementedError() return StandardDispatchOutput( hidden_states=hidden_states, hidden_states_scale=hidden_states_scale, topk_output=topk_output, ) def combine(self, combine_input: StandardCombineInput) -> torch.Tensor: (hidden_states,) = combine_input if should_use_flashinfer_cutlass_moe_fp4_allgather(): hidden_states, global_hidden_states = ( get_local_dp_buffer(get_tp_group()), hidden_states, ) get_tp_group().reduce_scatterv( global_hidden_states, output=hidden_states, sizes=get_dp_global_num_tokens(), ) return hidden_states