# SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project import threading from dataclasses import dataclass from typing import Any import torch import torch.distributed as dist import vllm.envs as envs from vllm.distributed import get_dp_group, get_ep_group from vllm.distributed.utils import StatelessProcessGroup from vllm.forward_context import get_forward_context from vllm.logger import init_logger from vllm.utils.flashinfer import ( has_flashinfer_nvlink_one_sided, has_flashinfer_nvlink_two_sided, ) from vllm.utils.import_utils import has_deep_ep, has_deep_ep_v2, has_mori from .base_device_communicator import All2AllManagerBase, Cache if has_flashinfer_nvlink_two_sided(): from flashinfer.comm import Mapping # type: ignore[import-not-found] from flashinfer.comm.mnnvl import MnnvlConfig # type: ignore[import-not-found] from flashinfer.comm.trtllm_alltoall import ( MnnvlMoe, # type: ignore[import-not-found] ) if has_flashinfer_nvlink_one_sided(): from flashinfer.comm import Mapping # type: ignore[import-not-found] from flashinfer.comm.mnnvl import MnnvlConfig # type: ignore[import-not-found] from flashinfer.comm.trtllm_moe_alltoall import ( MoeAlltoAll, # type: ignore[import-not-found] moe_a2a_get_workspace_size_per_rank, ) logger = init_logger(__name__) class AgRsAll2AllManager(All2AllManagerBase): """ An implementation of all2all communication based on all-gather (dispatch) and reduce-scatter (combine). """ def __init__(self, cpu_group, tcp_store_group=None): super().__init__(cpu_group, tcp_store_group) def dispatch_router_logits( self, hidden_states: torch.Tensor, router_logits: torch.Tensor, is_sequence_parallel: bool = False, extra_tensors: list[torch.Tensor] | None = None, ) -> ( tuple[torch.Tensor, torch.Tensor] | tuple[torch.Tensor, torch.Tensor, list[torch.Tensor]] ): """ Gather hidden_states and router_logits from all dp ranks. """ dp_metadata = get_forward_context().dp_metadata assert dp_metadata is not None sizes = dp_metadata.get_chunk_sizes_across_dp_rank() assert sizes is not None dist_group = get_ep_group() if is_sequence_parallel else get_dp_group() assert sizes[dist_group.rank_in_group] == hidden_states.shape[0] tensors_to_gather = [hidden_states, router_logits] if extra_tensors is not None: tensors_to_gather.extend(extra_tensors) gathered_tensors = dist_group.all_gatherv( tensors_to_gather, dim=0, sizes=sizes, ) if extra_tensors is not None: return (gathered_tensors[0], gathered_tensors[1], gathered_tensors[2:]) return gathered_tensors[0], gathered_tensors[1] def dispatch( self, hidden_states: torch.Tensor, topk_weights: torch.Tensor, topk_ids: torch.Tensor, is_sequence_parallel: bool = False, extra_tensors: list[torch.Tensor] | None = None, ) -> ( tuple[torch.Tensor, torch.Tensor, torch.Tensor] | tuple[torch.Tensor, torch.Tensor, torch.Tensor, list[torch.Tensor]] ): """ Gather hidden_states and router_logits from all dp ranks. """ dp_metadata = get_forward_context().dp_metadata assert dp_metadata is not None sizes = dp_metadata.get_chunk_sizes_across_dp_rank() assert sizes is not None dist_group = get_ep_group() if is_sequence_parallel else get_dp_group() assert sizes[dist_group.rank_in_group] == hidden_states.shape[0] tensors_to_gather = [hidden_states, topk_weights, topk_ids] if extra_tensors is not None: tensors_to_gather.extend(extra_tensors) gathered_tensors = dist_group.all_gatherv( tensors_to_gather, dim=0, sizes=sizes, ) hidden_states = gathered_tensors[0] topk_weights = gathered_tensors[1] topk_ids = gathered_tensors[2] if extra_tensors is None: return hidden_states, topk_weights, topk_ids return hidden_states, topk_weights, topk_ids, gathered_tensors[3:] def combine( self, hidden_states: torch.Tensor, is_sequence_parallel: bool = False ) -> torch.Tensor: """ Reduce-scatter hidden_states across all dp ranks. """ dp_metadata = get_forward_context().dp_metadata assert dp_metadata is not None sizes = dp_metadata.get_chunk_sizes_across_dp_rank() assert sizes is not None dist_group = get_ep_group() if is_sequence_parallel else get_dp_group() hidden_states = dist_group.reduce_scatterv(hidden_states, dim=0, sizes=sizes) return hidden_states def destroy(self): pass class DeepEPAll2AllManagerBase(All2AllManagerBase): """ All2All communication based on DeepEP High-Throughput kernels. """ def __init__(self, cpu_group, tcp_store_group=None): assert has_deep_ep(), ( "DeepEP kernels not found. Please follow https://github.com/vllm-project/vllm/blob/main/tools/ep_kernels/README.md" " to install DeepEP kernels." ) # noqa super().__init__(cpu_group, tcp_store_group) self.handle_cache = Cache() # This is the DeepEP default. Stick to it till we can establish # reasonable defaults based on profiling. self.num_sms = 20 def get_handle(self, kwargs): raise NotImplementedError def dispatch_router_logits( self, hidden_states: torch.Tensor, router_logits: torch.Tensor, is_sequence_parallel: bool = False, extra_tensors: list[torch.Tensor] | None = None, ) -> tuple[torch.Tensor, torch.Tensor]: raise NotImplementedError def dispatch( self, hidden_states: torch.Tensor, topk_weights: torch.Tensor, topk_ids: torch.Tensor, is_sequence_parallel: bool = False, extra_tensors: list[torch.Tensor] | None = None, ) -> ( tuple[torch.Tensor, torch.Tensor, torch.Tensor] | tuple[torch.Tensor, torch.Tensor, torch.Tensor, list[torch.Tensor]] ): raise NotImplementedError def combine( self, hidden_states: torch.Tensor, is_sequence_parallel: bool = False ) -> torch.Tensor: raise NotImplementedError def destroy(self): with self.handle_cache._lock: for _, handle in self.handle_cache._cache.items(): handle.destroy() self.handle_cache._cache.clear() class DeepEPHTAll2AllManager(DeepEPAll2AllManagerBase): """ All2All communication based on DeepEP High-Throughput kernels. """ def __init__(self, cpu_group, tcp_store_group=None): super().__init__(cpu_group, tcp_store_group) def _make_all2all_kwargs(self) -> dict[Any, Any]: # Defaults for internode and intranode are taken from DeepEP tests. num_nvl_bytes = envs.VLLM_DEEPEP_BUFFER_SIZE_MB * 1024 * 1024 num_rdma_bytes = None num_qps_per_rank = None if self.internode and not envs.VLLM_DEEPEP_HIGH_THROUGHPUT_FORCE_INTRA_NODE: num_rdma_bytes = envs.VLLM_DEEPEP_BUFFER_SIZE_MB * 1024 * 1024 num_qps_per_rank = self.num_sms // 2 else: num_rdma_bytes = 0 num_qps_per_rank = 1 assert num_rdma_bytes is not None assert num_qps_per_rank is not None # TODO: remove platform-specific logic # once ROCm DeepEP is updated with the latest APIs. kwargs = dict( group=self.cpu_group, num_nvl_bytes=num_nvl_bytes, num_rdma_bytes=num_rdma_bytes, low_latency_mode=False, num_qps_per_rank=num_qps_per_rank, explicitly_destroy=True, ) return kwargs def get_handle(self, kwargs): assert len(kwargs) == 0, ( "DeepEPHTAll2AllManager expects no arguments. All the required " "args are computed in the Manager itself." ) import deep_ep # type: ignore[import-not-found] buffer_kwargs = self._make_all2all_kwargs() logger.debug("DeepEP all2all args %s", buffer_kwargs) handle: deep_ep.Buffer = self.handle_cache.get_or_create( buffer_kwargs, deep_ep.Buffer ) return handle def set_num_sms(self, num_sms: int): import deep_ep # type: ignore[import-not-found] # Right now the buffers are sized for only what the kernels were # created with. So we can only reduce the number of SMS used # but not increase it. if num_sms > self.num_sms: num_sms = self.num_sms deep_ep.Buffer.set_num_sms(num_sms) class DeepEPLLAll2AllManager(DeepEPAll2AllManagerBase): """ All2All communication based on DeepEP Low-Latency kernels. """ _buffer: Any = None _mask: torch.Tensor | None = None _last_mask: torch.Tensor | None = None def __init__(self, cpu_group, tcp_store_group=None): super().__init__(cpu_group, tcp_store_group) self.support_fault_tolerance = False # TODO: set to True when FT is supported. def _make_all2all_kwargs( self, max_num_tokens_per_dp_rank: int, token_hidden_size: int, num_ep_ranks: int, num_global_experts: int, num_local_experts: int, ) -> dict[Any, Any]: """ max_num_tokens_per_dp_rank : the maximum number of tokens a DP rank can dispatch all the ranks must hold the same value. token_hidden_size: the hidden dimension of each token. num_ep_ranks: the number of EP group ranks. num_global_experts: Number of experts in the model. num_local_experts: Number of experts in an EP rank. """ import deep_ep # type: ignore[import-not-found] # Defaults for internode and intranode are taken from DeepEP tests. num_nvl_bytes = envs.VLLM_DEEPEP_BUFFER_SIZE_MB * 1024 * 1024 num_qps_per_rank = num_local_experts num_rdma_bytes = deep_ep.Buffer.get_low_latency_rdma_size_hint( num_max_dispatch_tokens_per_rank=max_num_tokens_per_dp_rank, hidden=token_hidden_size, num_ranks=num_ep_ranks, num_experts=num_global_experts, ) assert num_rdma_bytes is not None # TODO: remove platform-specific logic # once ROCm DeepEP is updated with the latest APIs. kwargs = dict( group=self.cpu_group, num_nvl_bytes=num_nvl_bytes, num_rdma_bytes=num_rdma_bytes, low_latency_mode=True, num_qps_per_rank=num_qps_per_rank, allow_nvlink_for_low_latency_mode=True, allow_mnnvl=envs.VLLM_DEEPEP_LOW_LATENCY_USE_MNNVL, explicitly_destroy=True, enable_shrink=self.support_fault_tolerance, ) return kwargs def get_handle(self, kwargs): """ The kwargs for DeepEPLLAll2AllManager is dictated by _make_all2all_kwargs. """ import deep_ep # type: ignore[import-not-found] buffer_kwargs = self._make_all2all_kwargs(**kwargs) logger.debug("DeepEP all2all args %s", buffer_kwargs) handle: deep_ep.Buffer = self.handle_cache.get_or_create( buffer_kwargs, deep_ep.Buffer ) DeepEPLLAll2AllManager._buffer = handle return handle # DeepEP LL uses RDMA so no SMs are used for communication def max_sms_used(self) -> int | None: return 0 def query_active_mask(self) -> torch.Tensor: buf = DeepEPLLAll2AllManager._buffer assert buf is not None if DeepEPLLAll2AllManager._mask is None: DeepEPLLAll2AllManager._mask = torch.zeros( self.world_size, device="cuda", dtype=torch.int32 ) buf.low_latency_query_mask_buffer(DeepEPLLAll2AllManager._mask) return DeepEPLLAll2AllManager._mask def query_fault(self) -> torch.Tensor: current = self.query_active_mask() if DeepEPLLAll2AllManager._last_mask is None: DeepEPLLAll2AllManager._last_mask = torch.zeros_like(current) has_fault = (current != DeepEPLLAll2AllManager._last_mask).any() return has_fault @dataclass class _NixlEPBufferState: buffer: Any connected_ep_size: int active_ep_size: int class NixlEPAll2AllManager(All2AllManagerBase): """ All2All communication based on NIXL EP kernels. This backend supports elastic EP with dynamic rank connection/disconnection. """ _buffer: _NixlEPBufferState | None = None _lock = threading.RLock() _mask: torch.Tensor | None = None _last_mask: torch.Tensor | None = None def __init__(self, cpu_group, tcp_store_group=None): if tcp_store_group is None: tcp_store_group = StatelessProcessGroup( rank=cpu_group.rank(), world_size=cpu_group.size(), store=dist.PrefixStore("nixl_ep", cpu_group.get_group_store()), ) super().__init__(cpu_group, tcp_store_group) self.support_fault_tolerance = True self.max_num_ep_ranks = envs.VLLM_NIXL_EP_MAX_NUM_RANKS def _init_buffer( self, max_num_tokens_per_dp_rank: int, token_hidden_size: int, num_experts_per_rank: int, ) -> None: from nixl_ep import Buffer # type: ignore[import-not-found] max_num_global_experts = self.max_num_ep_ranks * num_experts_per_rank num_rdma_bytes = Buffer.get_rdma_size_hint( num_max_dispatch_tokens_per_rank=max_num_tokens_per_dp_rank, hidden=token_hidden_size, num_ranks=self.max_num_ep_ranks, num_experts=max_num_global_experts, ) assert NixlEPAll2AllManager._buffer is None, ( "NIXL EP buffer already initialized" ) buffer = Buffer( rank=self.rank, tcp_store_group=self.tcp_store_group.store, ) buffer.update_memory_buffers( num_ranks=self.max_num_ep_ranks, num_experts_per_rank=num_experts_per_rank, num_rdma_bytes=num_rdma_bytes, ) ranks_to_connect = list(range(self.world_size)) buffer.connect_ranks(ranks_to_connect) NixlEPAll2AllManager._buffer = _NixlEPBufferState( buffer=buffer, connected_ep_size=self.world_size, active_ep_size=self.world_size, ) def _connect_to_ep_size(self, ep_size: int, *, make_active: bool) -> None: assert NixlEPAll2AllManager._buffer is not None state = NixlEPAll2AllManager._buffer if ep_size <= state.connected_ep_size: return state.buffer.set_tcp_store_group(self.tcp_store_group.store) ranks_to_connect = list(range(state.connected_ep_size, ep_size)) state.buffer.connect_ranks(ranks_to_connect, activate=make_active) state.connected_ep_size = ep_size if make_active: state.active_ep_size = ep_size def _disconnect_to_ep_size(self, ep_size: int) -> None: assert NixlEPAll2AllManager._buffer is not None state = NixlEPAll2AllManager._buffer if ep_size >= state.connected_ep_size: return state.buffer.set_tcp_store_group(self.tcp_store_group.store) ranks_to_disconnect = list(range(ep_size, state.connected_ep_size)) state.buffer.disconnect_ranks(ranks_to_disconnect) state.connected_ep_size = ep_size state.active_ep_size = min(state.active_ep_size, ep_size) def _unmask_connected_ranks(self, target_ep_size: int) -> None: assert NixlEPAll2AllManager._buffer is not None state = NixlEPAll2AllManager._buffer state.buffer.set_tcp_store_group(self.tcp_store_group.store) if target_ep_size <= state.active_ep_size: return assert state.connected_ep_size >= target_ep_size for rank in range(state.active_ep_size, target_ep_size): state.buffer.update_mask_buffer(rank, mask=False) state.active_ep_size = target_ep_size def _stage_ep_size(self) -> None: assert NixlEPAll2AllManager._buffer is not None state = NixlEPAll2AllManager._buffer target_ep_size = self.world_size # Scale-up can safely connect standby ranks while leaving them masked. # Scale-down must not disconnect active ranks until commit. if target_ep_size > state.connected_ep_size: self._connect_to_ep_size(target_ep_size, make_active=False) def commit_staged_state(self) -> None: """Commit staged NIXL EP state to the active communication set.""" with NixlEPAll2AllManager._lock: assert NixlEPAll2AllManager._buffer is not None state = NixlEPAll2AllManager._buffer target_ep_size = self.world_size if target_ep_size < state.connected_ep_size: self._disconnect_to_ep_size(target_ep_size) elif target_ep_size > state.connected_ep_size: self._connect_to_ep_size(target_ep_size, make_active=True) self._unmask_connected_ranks(target_ep_size) def _ensure_ep_size(self, *, stage: bool) -> None: if stage: self._stage_ep_size() else: self.commit_staged_state() def get_handle(self, kwargs): with NixlEPAll2AllManager._lock: stage = bool(kwargs.get("stage", False)) state = NixlEPAll2AllManager._buffer if state is None: assert not stage, ( "NIXL EP staged initialization requires an existing buffer" ) max_num_tokens_per_dp_rank = kwargs["max_num_tokens_per_dp_rank"] num_experts_per_rank = ( kwargs["num_global_experts"] // kwargs["num_ep_ranks"] ) self._init_buffer( max_num_tokens_per_dp_rank=max_num_tokens_per_dp_rank, token_hidden_size=kwargs["token_hidden_size"], num_experts_per_rank=num_experts_per_rank, ) else: self._ensure_ep_size(stage=stage) assert NixlEPAll2AllManager._buffer is not None handle = NixlEPAll2AllManager._buffer.buffer return handle def dispatch( self, hidden_states: torch.Tensor, topk_weights: torch.Tensor, topk_ids: torch.Tensor, is_sequence_parallel: bool = False, extra_tensors: list[torch.Tensor] | None = None, ) -> ( tuple[torch.Tensor, torch.Tensor, torch.Tensor] | tuple[torch.Tensor, torch.Tensor, torch.Tensor, list[torch.Tensor]] ): raise NotImplementedError def combine( self, hidden_states: torch.Tensor, is_sequence_parallel: bool = False ) -> torch.Tensor: raise NotImplementedError def destroy(self): # NOTE(yongji): NIXLEPAll2AllManager instance is recreated during # scale-up/down, so we cannot destroy the persistent buffer here. assert NixlEPAll2AllManager._buffer is not None buffer = NixlEPAll2AllManager._buffer.buffer buffer.set_tcp_store_group(None) # NIXL EP uses RDMA so no SMs are used for communication def max_sms_used(self) -> int | None: return 0 def query_active_mask(self) -> torch.Tensor: state = NixlEPAll2AllManager._buffer assert state is not None if NixlEPAll2AllManager._mask is None: NixlEPAll2AllManager._mask = torch.zeros( self.max_num_ep_ranks, device="cuda", dtype=torch.int32 ) state.buffer.query_mask_buffer(NixlEPAll2AllManager._mask) return NixlEPAll2AllManager._mask[: state.active_ep_size] def query_fault(self) -> torch.Tensor: current = self.query_active_mask() last = NixlEPAll2AllManager._last_mask if last is None or last.shape != current.shape: NixlEPAll2AllManager._last_mask = torch.zeros_like(current) last = NixlEPAll2AllManager._last_mask has_fault = (current != last).any() return has_fault class FlashInferNVLinkTwoSidedManager(All2AllManagerBase): """ All2All communication based on flashinfer all2allv/two-sided NVLink kernels. """ # This type lint could be removed after all of the work in # https://github.com/vllm-project/vllm/issues/26533 done. rank: int world_size: int def __init__(self, cpu_group, tcp_store_group=None): assert has_flashinfer_nvlink_two_sided(), ( "flashinfer all2all module not found. Please install/check flashinfer" ) # noqa super().__init__(cpu_group, tcp_store_group) logger.debug( "Initialize for flashinfer All2All rank=%d, world size=%d", self.rank, self.world_size, ) self.initialized = False self.alltoall_info = None def initialize( self, world_size: int, rank: int, gpus_per_node: int, ): """Initialize workspace""" if self.initialized: return self.cleanup() logger.debug("making map: rank=%d, world size=%d", rank, world_size) self.mapping = Mapping( world_size, rank, gpus_per_node, tp_size=world_size, ) from vllm.distributed.device_communicators.mnnvl_compat import ( CustomCommunicator, ) # MNNVL workspace is allocated per rank in the comm_backend's group; the # flashinfer kernel asserts workspace.size(0) == moe_ep_size, so the backend # must span the EP group (= DP*PCP*TP), not the DP group. ep_config = MnnvlConfig( comm_backend=CustomCommunicator(self.cpu_group), fabric_page_size=1 << 29, # 512MB allocation_granularity=0, # Auto-detect ) self.workspace_tensor = MnnvlMoe.get_moe_workspaces(self.mapping, ep_config) self.prepare_workspace_tensor = MnnvlMoe.get_moe_prepare_workspace( self.mapping, ep_config ) self.world_size = world_size self.rank = rank self.gpus_per_node = gpus_per_node self.initialized = True logger.info( "FlashInfer All2All initialized for rank %s, size %s", rank, world_size ) def ensure_alltoall_workspace_initialized(self): """Ensure workspace is initialized""" if not has_flashinfer_nvlink_two_sided(): return False if self.world_size <= 1: return False if not self.initialized: self.initialize( world_size=self.world_size, rank=self.rank, gpus_per_node=torch.accelerator.device_count, ) return self.initialized def get_handle(self, kwargs): return self def cleanup(self): """Clean up workspace""" if ( self.initialized and self.workspace_tensor is not None and self.prepare_workspace_tensor is not None ): try: del self.workspace_tensor del self.prepare_workspace_tensor except Exception as e: logger.warning("Failed to cleanup FlashInfer workspace: %s", e) finally: self.workspace_tensor = None self.prepare_workspace_tensor = None self.mapping = None self.initialized = False class FlashInferNVLinkOneSidedManager(All2AllManagerBase): """ All2All communication based on FlashInfer's MoeAlltoAll/One-sided NVLink kernel. This is a newer kernel from trtllm that should perform better than the kernel used by flashinfer_nvlink_two_sided. """ rank: int world_size: int def __init__(self, cpu_group): assert has_flashinfer_nvlink_one_sided(), ( "flashinfer trtllm_moe_alltoall module not found. " "Please install/check flashinfer" ) super().__init__(cpu_group) logger.debug( "Initialize FlashInfer One-sided NVLink rank=%d, world size=%d", self.rank, self.world_size, ) self.initialized = False self.moe_alltoall: MoeAlltoAll | None = None self.mapping = None self.workspace_size = 0 self.max_num_tokens = 0 self.top_k = 0 self.num_experts = 0 def initialize( self, max_num_tokens: int, top_k: int, num_experts: int, hidden_size: int, dispatch_dtype_bytes_per_elem: int = 0, dispatch_scale_bytes_per_token: int = 0, ): """Initialize (or grow) the MoeAlltoAll workspace.""" if dispatch_dtype_bytes_per_elem == 0: hidden_bytes = hidden_size // 2 else: hidden_bytes = hidden_size * dispatch_dtype_bytes_per_elem total_dispatch_payload_size_per_token = ( hidden_bytes + dispatch_scale_bytes_per_token + top_k * 4 # int32 topks ids + top_k * 4 # float32 topk weights ) combine_payload_size_per_token = hidden_size * 2 # bf16 hidden states needed_workspace_size = moe_a2a_get_workspace_size_per_rank( ep_size=self.world_size, max_num_tokens=max_num_tokens, total_dispatch_payload_size_per_token=total_dispatch_payload_size_per_token, combine_payload_size_per_token=combine_payload_size_per_token, ) # workspace_size and max_num_tokens are kernel-side max-bounds, so # heterogeneous MoE layers (e.g. NVFP4 base + bf16 MTP head) only # need the shared workspace grown to the union. top_k and num_experts # must match across layers: top_k is a strict-equality assert at # dispatch (FlashInfer csrc/trtllm_moe_alltoall.cu), and num_experts # feeds the expert-to-rank routing math, so any mismatch would crash # or silently corrupt routing. All ranks see the same MoE layers in # the same order with identical shapes, so the skip / rebuild # branches are taken consistently across ranks. if self.initialized: assert top_k == self.top_k, ( "FlashInfer one-sided MoeAlltoAll does not support " f"heterogeneous top_k across MoE layers (got {top_k}, " f"was built with {self.top_k})" ) assert num_experts == self.num_experts, ( "FlashInfer one-sided MoeAlltoAll does not support " f"heterogeneous num_experts across MoE layers (got " f"{num_experts}, was built with {self.num_experts})" ) if ( needed_workspace_size <= self.workspace_size and max_num_tokens <= self.max_num_tokens ): return self.workspace_size = max(self.workspace_size, needed_workspace_size) self.max_num_tokens = max(self.max_num_tokens, max_num_tokens) self.top_k = top_k self.num_experts = num_experts self.cleanup() from vllm.platforms.interface import get_assigned_physical_gpu_ids assigned_physical_gpu_ids = get_assigned_physical_gpu_ids() gpus_per_node = ( len(assigned_physical_gpu_ids) if assigned_physical_gpu_ids is not None else torch.accelerator.device_count() ) logger.debug( "Making One-sided NVLink mapping: rank=%d, world size=%d", self.rank, self.world_size, ) self.mapping = Mapping( self.world_size, self.rank, gpus_per_node, tp_size=self.world_size, moe_ep_size=self.world_size, ) from vllm.distributed.device_communicators.mnnvl_compat import ( CustomCommunicator, ) # MNNVL workspace is allocated per rank in the comm_backend's group; the # flashinfer kernel asserts workspace.size(0) == moe_ep_size, so the backend # must span the EP group (= DP*PCP*TP), not the DP group. ep_config = MnnvlConfig( comm_backend=CustomCommunicator(self.cpu_group), ) self.moe_alltoall = MoeAlltoAll( mapping=self.mapping, max_num_tokens=self.max_num_tokens, top_k=self.top_k, num_experts=self.num_experts, workspace_size_per_rank=self.workspace_size, mnnvl_config=ep_config, ) self.gpus_per_node = gpus_per_node self.initialized = True logger.info( "FlashInfer One-sided NVLink initialized for rank %s, size %s", self.rank, self.world_size, ) # Scope barrier to the EP group: with PP, different EP groups can # rebuild a different number of times if their MoE layers have # different shape sequences, so a world-level barrier would deadlock. dist.barrier(group=self.cpu_group) def get_handle(self, kwargs): return self def cleanup(self): """Clean up resources.""" if self.initialized and self.moe_alltoall is not None: try: del self.moe_alltoall except Exception as e: logger.warning( "Failed to cleanup FlashInfer One-sided NVLink workspace: %s", e ) finally: self.moe_alltoall = None self.mapping = None self.initialized = False class MoriAll2AllManager(All2AllManagerBase): def __init__(self, cpu_group, all2all_backend: str): assert has_mori(), ( "MoRI kernels not found. Please follow https://github.com/ROCm/mori/blob/main/README.md" " to install MoRI kernels." ) # noqa assert all2all_backend in ( "mori_high_throughput", "mori_low_latency", ), f"unsupported MoRI all2all backend: {all2all_backend!r}" import mori super().__init__(cpu_group) self._all2all_backend = all2all_backend self.handle_cache = Cache() torch._C._distributed_c10d._register_process_group("mori", cpu_group) mori.shmem.shmem_torch_process_group_init("mori") def _make_all2all_kwargs( self, rank: int, num_ep_ranks: int, input_dtype: torch.dtype, quant_dtype: torch.dtype, token_hidden_size: int, scale_dim: int, scale_type_size: int, max_num_tokens_per_dp_rank: int, num_local_experts: int, num_experts_per_token: int, ): import mori # type: ignore[import-not-found] from vllm.platforms.rocm import on_gfx942, on_gfx950 assert on_gfx942() or on_gfx950(), ( "mori currently only support arch gfx942 and gfx950" ) if not self.internode: # single node kernel_type = mori.ops.EpDispatchCombineKernelType.IntraNode rdma_block_num = 0 warp_num_per_block = 16 block_num = 80 else: # Multi-node: kernel follows --all2all-backend (mirrors deepep_* split). # mori_low_latency → InterNodeV1LL; mori_high_throughput → V1. if self._all2all_backend == "mori_low_latency": kernel_type = mori.ops.EpDispatchCombineKernelType.InterNodeV1LL else: kernel_type = mori.ops.EpDispatchCombineKernelType.InterNodeV1 if on_gfx942(): warp_num_per_block = 16 block_num = 32 rdma_block_num = 16 elif on_gfx950(): warp_num_per_block = 8 block_num = 64 rdma_block_num = 32 else: raise NotImplementedError( "mori currently only support arch gfx942 and gfx950" ) return dict( rank=rank, world_size=num_ep_ranks, data_type=quant_dtype, hidden_dim=token_hidden_size, scale_dim=scale_dim, scale_type_size=scale_type_size, max_token_type_size=input_dtype.itemsize, max_num_inp_token_per_rank=max_num_tokens_per_dp_rank, num_experts_per_rank=num_local_experts, num_experts_per_token=num_experts_per_token, warp_num_per_block=warp_num_per_block, block_num=block_num, kernel_type=kernel_type, rdma_block_num=rdma_block_num, gpu_per_node=min(8, num_ep_ranks), ) def _make_handle(self, **kwargs): import mori # type: ignore[import-not-found] mori_config = mori.ops.EpDispatchCombineConfig(**kwargs) handle = mori.ops.EpDispatchCombineOp(mori_config) return handle def get_handle(self, kwargs): import mori # type: ignore[import-not-found] mori_kwargs = self._make_all2all_kwargs(**kwargs) logger.debug("MoRI all2all args %s", mori_kwargs) handle: mori.ops.EpDispatchCombineOp = self.handle_cache.get_or_create( mori_kwargs, self._make_handle ) return handle class DeepEPV2All2AllManager(All2AllManagerBase): """ All2All communication based on DeepEP v2 ElasticBuffer (unified API). Uses NCCL Gin backend with analytical SM calculation. """ def __init__(self, cpu_group, tcp_store_group=None, device_group=None): assert has_deep_ep_v2(), ( "DeepEP v2 (ElasticBuffer) not available. Requires DeepEP >= 2.0 " "(https://github.com/deepseek-ai/DeepEP) and NCCL >= 2.30.4." ) super().__init__(cpu_group, tcp_store_group) self._device_group = device_group self.handle_cache = Cache() self._num_sms: int | None = None def _make_all2all_kwargs( self, num_max_tokens_per_rank: int, hidden: int, num_topk: int, use_fp8_dispatch: bool, ) -> dict: return dict( group=self._device_group if self._device_group is not None else self.cpu_group, num_max_tokens_per_rank=num_max_tokens_per_rank, hidden=hidden, num_topk=num_topk, use_fp8_dispatch=use_fp8_dispatch, allow_hybrid_mode=envs.VLLM_DEEPEP_V2_ALLOW_HYBRID_MODE, prefer_overlap_with_compute=envs.VLLM_DEEPEP_V2_PREFER_OVERLAP, allow_multiple_reduction=(envs.VLLM_DEEPEP_V2_ALLOW_MULTIPLE_REDUCTION), explicitly_destroy=True, ) def get_handle(self, kwargs): import deep_ep # type: ignore[import-not-found] num_experts = kwargs.pop("num_experts", 256) buffer_kwargs = self._make_all2all_kwargs(**kwargs) logger.debug("DeepEP v2 all2all args %s", buffer_kwargs) handle: deep_ep.ElasticBuffer = self.handle_cache.get_or_create( buffer_kwargs, deep_ep.ElasticBuffer ) if self._num_sms is None: self._num_sms = handle.get_theoretical_num_sms( num_experts=num_experts, num_topk=kwargs["num_topk"], ) return handle def max_sms_used(self) -> int | None: return self._num_sms def destroy(self): with self.handle_cache._lock: for _, handle in self.handle_cache._cache.items(): handle.destroy() self.handle_cache._cache.clear()