diff --git a/python/mscclpp/ep/README.md b/python/mscclpp/ep/README.md index 968c5e0f..210c0057 100644 --- a/python/mscclpp/ep/README.md +++ b/python/mscclpp/ep/README.md @@ -60,11 +60,11 @@ class MoECommunicatorConfig: hidden_size: int = 0 topk: int = 0 max_tokens_per_rank: int = 0 - max_recv_tokens_per_rank: Optional[int] = None # Runtime mode and output layout mode: MoEMode = MoEMode.LOW_LATENCY output_layout: Optional[DispatchLayout] = None # default is derived from mode + token_major_init_padding: bool = False # Quantization defaults quant: Optional[QuantConfig] = None @@ -137,8 +137,8 @@ a later version can add an explicit `expert_map` for arbitrary placement. |---|---| | `mode` | Backend selection (`MoEMode.LOW_LATENCY` or `MoEMode.HIGH_THROUGHPUT`) | | `output_layout` | MLP input layout returned by dispatch | +| `token_major_init_padding` | Initialize token-major padding metadata for fixed-capacity Triton kernels | | `max_tokens_per_rank` | dispatch capacity | -| `max_recv_tokens_per_rank` | recv buffer capacity | | scratch buffers | internally sized from mode, capacity, topology, and shape | | `num_sms` | backend launch/resource tuning | | `dispatch_config`, `combine_config` | backend-specific tuning configs | @@ -310,6 +310,7 @@ class TokenMajorCombineContext: hidden_size: int source_token_ids: torch.Tensor num_tokens_per_rank: torch.Tensor + rank_offsets: torch.Tensor num_max_dispatch_tokens_per_rank: int @@ -514,9 +515,24 @@ For token-major LL layout: output_buffer: [world_size * max_tokens_per_rank, hidden] ``` -The token-major rows are grouped into fixed source-rank regions. For source rank -`r`, only the first `dispatch_out.layout.num_tokens_per_rank[r]` rows in region -`[r * max_tokens_per_rank : (r + 1) * max_tokens_per_rank]` are valid. +The caller chooses `max_tokens_per_rank`, allocates that fixed capacity, and may +pass the resulting row capacity directly to a Triton kernel. This avoids a CPU +synchronization while keeping a static CUDA Graph shape. All valid rows are +compacted into one contiguous prefix. For source rank `r`, its rows are: + +```python +begin = dispatch_out.layout.offsets[r] +end = dispatch_out.layout.offsets[r + 1] +``` + +`offsets[-1]` is the total number of valid rows. + +Rows after `offsets[-1]` are padding. Set +`token_major_init_padding=True` when a fixed-capacity Triton kernel should +process the entire allocation: padding `topk_ids` are then initialized to `-1` +and weights to `0`, so the kernel can skip a row when all expert IDs are `-1`. +The option defaults to `False` to avoid initialization overhead when the MLP +uses the compact valid length. The dtype must match the dispatch output dtype. For BF16 dispatch it is BF16. For FP8 dispatch it is FP8 and the returned `DispatchOutput.quant` carries the @@ -560,8 +576,10 @@ dispatch_out.topk_ids # [world_size * max_tokens_per_rank, K], int32 lo dispatch_out.weights # [world_size * max_tokens_per_rank, K], float32 ``` -Non-local top-k entries use expert ID `-1` and weight `0`. The valid row count in each -source-rank region is returned in `dispatch_out.layout.num_tokens_per_rank`. +Only the prefix ending at `dispatch_out.layout.offsets[-1]` contains valid +tokens. Non-local entries always use expert ID `-1` and weight `0`; padding uses +the same sentinel values when `token_major_init_padding=True`. Per-source-rank +counts are returned in `dispatch_out.layout.num_tokens_per_rank`. For expert-major output, only the first `dispatch_out.layout.num_tokens_per_expert[i]` slots are valid: diff --git a/python/mscclpp/ep/low_latency.py b/python/mscclpp/ep/low_latency.py index 56a35f55..84de0987 100644 --- a/python/mscclpp/ep/low_latency.py +++ b/python/mscclpp/ep/low_latency.py @@ -54,11 +54,19 @@ class LowLatencyRuntime: hidden: int, num_experts: int, num_topk: int, + initialize_token_major_padding: bool, ) -> None: self.rank: int = comm.my_rank self.group_size: int = comm.nranks self.comm = comm - self.cpp_runtime = MoERuntime(comm.communicator, max_tokens_per_rank, hidden, num_experts, num_topk) + self.cpp_runtime = MoERuntime( + comm.communicator, + max_tokens_per_rank, + hidden, + num_experts, + num_topk, + initialize_token_major_padding, + ) def is_available(self) -> bool: return self.cpp_runtime.is_available() @@ -90,6 +98,7 @@ class LowLatencyBackend: self.num_blocks = config.low_latency_num_blocks self.num_sms = self.num_blocks - 2 self.combine_mode = config.low_latency_combine_mode + self.token_major_init_padding = config.token_major_init_padding self.enable_overlap = config.enable_overlap if self.output_layout not in (DispatchLayout.EXPERT_MAJOR, DispatchLayout.TOKEN_MAJOR): @@ -100,6 +109,10 @@ class LowLatencyBackend: raise ValueError("low_latency_num_blocks must be between world_size + 2 and 130") if not isinstance(self.combine_mode, CombineMode): raise TypeError("low_latency_combine_mode must be a CombineMode") + if not isinstance(self.token_major_init_padding, bool): + raise TypeError("token_major_init_padding must be a bool") + if self.token_major_init_padding and self.output_layout != DispatchLayout.TOKEN_MAJOR: + raise ValueError("token_major_init_padding requires TOKEN_MAJOR output") if self.output_layout == DispatchLayout.TOKEN_MAJOR and self.combine_mode != CombineMode.RANK_LOCAL_REDUCE: raise ValueError("TOKEN_MAJOR output requires RANK_LOCAL_REDUCE combine") @@ -111,8 +124,6 @@ class LowLatencyBackend: local_expert_start=config.local_expert_start, ) - if config.max_recv_tokens_per_rank not in (None, self.max_tokens_per_rank): - raise NotImplementedError("low-latency mode currently uses max_tokens_per_rank as recv capacity") self.dispatch_data_type = _resolve_dispatch_data_type(config.quant) self._dispatch_scales: Optional[torch.Tensor] = None @@ -128,6 +139,7 @@ class LowLatencyBackend: hidden=self.hidden_size, num_experts=self.num_experts, num_topk=self.topk, + initialize_token_major_padding=self.token_major_init_padding, ) self._is_internode = self._runtime.is_internode_available() @@ -188,8 +200,15 @@ class LowLatencyBackend: ) if self.output_layout == DispatchLayout.EXPERT_MAJOR: layout_info = DispatchLayoutInfo(kind=self.output_layout, num_tokens_per_expert=count) + elif self.output_layout == DispatchLayout.TOKEN_MAJOR: + assert layout_range is not None + layout_info = DispatchLayoutInfo( + kind=self.output_layout, + num_tokens_per_rank=count, + offsets=layout_range, + ) else: - layout_info = DispatchLayoutInfo(kind=self.output_layout, num_tokens_per_rank=count) + raise ValueError(f"unsupported low-latency output layout: {self.output_layout}") output_info = DispatchOutputInfo(layout=layout_info, quant=output_quant) dispatch_out = DispatchOutput( tokens=out_buf, @@ -213,7 +232,8 @@ class LowLatencyBackend: num_max_dispatch_tokens_per_rank=self.max_tokens_per_rank, ), ) - else: + elif self.output_layout == DispatchLayout.TOKEN_MAJOR: + assert layout_range is not None handle = TokenMajorDispatchHandle( output_info=output_info, combine_context=TokenMajorCombineContext( @@ -223,9 +243,12 @@ class LowLatencyBackend: hidden_size=self.hidden_size, source_token_ids=src_info, num_tokens_per_rank=count, + rank_offsets=layout_range, num_max_dispatch_tokens_per_rank=self.max_tokens_per_rank, ), ) + else: + raise ValueError(f"unsupported low-latency output layout: {self.output_layout}") return dispatch_out, handle def combine( @@ -246,7 +269,7 @@ class LowLatencyBackend: context = handle.combine_context topk_weights = None src_info = context.source_token_ids - layout_range = None + layout_range = context.rank_offsets else: raise ValueError("DispatchHandle does not contain low-latency combine context") if out is None: @@ -292,17 +315,19 @@ class LowLatencyBackend: (self.num_local_experts, num_scales, slots_per_expert), dtype=torch.float32, device=device ) self._dispatch_scales = scale_storage.transpose(1, 2) - else: + elif self.output_layout == DispatchLayout.TOKEN_MAJOR: token_capacity = self.world_size * self.max_tokens_per_rank self._dispatch_src_info = torch.empty((token_capacity,), dtype=torch.int32, device=device) self._dispatch_topk_ids = torch.empty((token_capacity, self.topk), dtype=torch.int32, device=device) self._dispatch_weights = torch.empty((token_capacity, self.topk), dtype=torch.float32, device=device) - self._dispatch_layout_range = None + self._dispatch_layout_range = torch.empty((self.world_size + 1,), dtype=torch.int64, device=device) self._dispatch_count = torch.empty((self.world_size,), dtype=torch.int32, device=device) if self.dispatch_data_type == DispatchDataType.FP8_E4M3: self._dispatch_scales = torch.empty( (token_capacity, self.hidden_size // 128), dtype=torch.float32, device=device ) + else: + raise ValueError(f"unsupported low-latency output layout: {self.output_layout}") assert self._dispatch_src_info is not None assert self._dispatch_count is not None return ( @@ -346,8 +371,10 @@ class LowLatencyBackend: slots_per_expert = self.world_size * self.max_tokens_per_rank if self.output_layout == DispatchLayout.EXPERT_MAJOR: expected_shape = (self.num_local_experts, slots_per_expert, self.hidden_size) - else: + elif self.output_layout == DispatchLayout.TOKEN_MAJOR: expected_shape = (self.world_size * self.max_tokens_per_rank, self.hidden_size) + else: + raise ValueError(f"unsupported low-latency output layout: {self.output_layout}") if output_buffer.dim() != len(expected_shape) or not output_buffer.is_contiguous(): raise ValueError(f"output_buffer must be a contiguous {self.output_layout} tensor") expected_dtype = torch.float8_e4m3fn if self.dispatch_data_type == DispatchDataType.FP8_E4M3 else torch.bfloat16 @@ -371,8 +398,12 @@ class LowLatencyBackend: slots_per_expert = self.world_size * self.max_tokens_per_rank if handle.output_info.layout.kind == DispatchLayout.EXPERT_MAJOR: expected_shape = (self.num_local_experts, slots_per_expert, self.hidden_size) - else: + elif handle.output_info.layout.kind == DispatchLayout.TOKEN_MAJOR: expected_shape = (self.world_size * self.max_tokens_per_rank, self.hidden_size) + if handle.output_info.layout.offsets is None: + raise ValueError("TOKEN_MAJOR DispatchHandle is missing compact rank offsets") + else: + raise ValueError(f"unsupported low-latency output layout: {handle.output_info.layout.kind}") if expert_output.dim() != len(expected_shape) or not expert_output.is_contiguous(): raise ValueError("expert_output must keep dispatch output's contiguous layout") if tuple(expert_output.shape) != expected_shape: diff --git a/python/mscclpp/ep/types.py b/python/mscclpp/ep/types.py index 6c53b652..54abb212 100644 --- a/python/mscclpp/ep/types.py +++ b/python/mscclpp/ep/types.py @@ -47,11 +47,11 @@ class MoECommunicatorConfig: hidden_size: int = 0 topk: int = 0 max_tokens_per_rank: int = 0 - max_recv_tokens_per_rank: Optional[int] = None # Runtime mode and output layout mode: MoEMode = MoEMode.LOW_LATENCY output_layout: Optional[DispatchLayout] = None + token_major_init_padding: bool = False # Quantization defaults quant: Optional[QuantConfig] = None @@ -127,6 +127,7 @@ class TokenMajorCombineContext: hidden_size: int source_token_ids: torch.Tensor num_tokens_per_rank: torch.Tensor + rank_offsets: torch.Tensor num_max_dispatch_tokens_per_rank: int diff --git a/src/core/include/unix_socket.hpp b/src/core/include/unix_socket.hpp index 4e848f77..d5d03bc0 100644 --- a/src/core/include/unix_socket.hpp +++ b/src/core/include/unix_socket.hpp @@ -24,6 +24,7 @@ class UnixSocketServer { int registerFd(int fd); void unregisterFd(int fd); std::string getSocketPath() const; + ~UnixSocketServer(); private: int listenUnixSockFd_ = -1; @@ -36,6 +37,7 @@ class UnixSocketServer { UnixSocketServer(); void mainLoop(int listenUnixSockFd); + void shutdown(); }; class UnixSocketClient { diff --git a/src/core/unix_socket.cc b/src/core/unix_socket.cc index 381f7b4d..46c9875c 100644 --- a/src/core/unix_socket.cc +++ b/src/core/unix_socket.cc @@ -140,9 +140,13 @@ void UnixSocketServer::start() { } void UnixSocketServer::stop() { + if (mainThread_.joinable()) INFO(MSCCLPP_INIT, "Stopping unix socket server"); + shutdown(); +} + +void UnixSocketServer::shutdown() { *abortFlag_ = 1; if (mainThread_.joinable()) { - INFO(MSCCLPP_INIT, "Stopping unix socket server"); mainThread_.join(); } ::close(listenUnixSockFd_); @@ -252,6 +256,8 @@ void UnixSocketServer::mainLoop(int listenUnixSockFd) { UnixSocketServer::UnixSocketServer() : abortFlagStorage_(new uint32_t(0)), abortFlag_(abortFlagStorage_.get()) {} +UnixSocketServer::~UnixSocketServer() { shutdown(); } + std::string UnixSocketServer::getSocketPath() const { return listenUnixSockPath_; } UnixSocketClient& UnixSocketClient::instance() { diff --git a/src/ext/ep/README.md b/src/ext/ep/README.md index 3f8d05b2..0f415078 100644 --- a/src/ext/ep/README.md +++ b/src/ext/ep/README.md @@ -34,8 +34,12 @@ LL dispatch supports two user-visible layouts: - `EXPERT_MAJOR`: one row per `(token, local expert)`. - `TOKEN_MAJOR`: one row per `(token, destination rank)`, plus local top-k expert - IDs, routing weights, source-token IDs, and per-source-rank counts. The caller - must produce one pre-weighted local partial per row before combine. + IDs, routing weights, source-token IDs, per-source-rank counts, and exclusive + offsets. Valid rows occupy a compact prefix of the caller-provided capacity + buffer. With `token_major_init_padding=True`, padding rows have top-k IDs + `-1`, allowing fixed-capacity Triton kernels to skip them without a CPU count + synchronization. The option is disabled by default. The caller must produce + one pre-weighted local partial per valid row before combine. ### High throughput diff --git a/src/ext/ep/bindings.cpp b/src/ext/ep/bindings.cpp index d28411e1..b41523b5 100644 --- a/src/ext/ep/bindings.cpp +++ b/src/ext/ep/bindings.cpp @@ -57,8 +57,9 @@ NB_MODULE(mscclpp_ep_cpp, m) { .value("MXFP8_E4M3", mscclpp::ep::low_latency::DispatchDataType::MXFP8_E4M3); nb::class_(m, "MoERuntime") - .def(nb::init(), nb::arg("comm"), nb::arg("max_tokens_per_rank"), - nb::arg("hidden"), nb::arg("num_experts"), nb::arg("num_topk")) + .def(nb::init(), nb::arg("comm"), + nb::arg("max_tokens_per_rank"), nb::arg("hidden"), nb::arg("num_experts"), nb::arg("num_topk"), + nb::arg("initialize_token_major_padding")) .def("is_available", &mscclpp::ep::MoERuntime::isAvailable) .def("is_internode_available", &mscclpp::ep::MoERuntime::isInternodeAvailable) .def( diff --git a/src/ext/ep/include/api.cuh b/src/ext/ep/include/api.cuh index e6ae4e44..08b5a020 100644 --- a/src/ext/ep/include/api.cuh +++ b/src/ext/ep/include/api.cuh @@ -144,6 +144,8 @@ struct Workload { int maxTokensPerRank_; /// User-visible dispatch output layout. DispatchLayout outputLayout_; + /// Whether token-major padding metadata is initialized to sentinel values. + bool initializeTokenMajorPadding_; /// Dispatch payload data format. DispatchDataType dispatchDataType_; }; @@ -183,7 +185,7 @@ size_t workspaceSize(int numRanks, int numExperts); /// @param[out] outputTopkWeights Token-major routing weights /// [num_ranks * max_tokens_per_rank, num_topk], or nullptr. /// @param[out] outputLayout Per-[local expert, source rank] packed count and offset for expert-major output, or -/// nullptr. +/// token-major exclusive source-rank offsets [num_ranks + 1]. /// @param[out] outputCount Per-local-expert counts for expert-major output or per-source-rank counts for token-major. /// @param[in] input Local input tokens [num_tokens, hidden]. /// @param[in] topkIdx Global expert indices [num_tokens, num_topk]. @@ -206,7 +208,8 @@ void dispatch(void* output, float* outputScales, int* outputSrcInfo, int* output /// @param[in] topkIdx Global expert indices [num_tokens, num_topk]. /// @param[in] topkWeights Routing weights [num_tokens, num_topk], or nullptr for unit weights. /// @param[in] srcInfo Original source-token index for every packed expert row. -/// @param[in] layoutRange Per-[local expert, source rank] packed count and offset. +/// @param[in] layoutRange Per-[local expert, source rank] packed count and offset for expert-major input, or +/// token-major exclusive source-rank offsets [num_ranks + 1]. /// @param[in] workload Per-call workload dimensions. /// @param[in,out] recvBuffer Current symmetric ping-pong buffer receiving partials or expert rows. /// @param[in] dispatchRecvBuffer Previous dispatch buffer containing rewritten routing metadata. diff --git a/src/ext/ep/low_latency/combine.cu b/src/ext/ep/low_latency/combine.cu index cab3c625..eb408e89 100644 --- a/src/ext/ep/low_latency/combine.cu +++ b/src/ext/ep/low_latency/combine.cu @@ -191,7 +191,8 @@ MSCCLPP_DEVICE_INLINE void sendRankReducedPartials(const void* expertOutput, int } template