mirror of
https://github.com/deepseek-ai/DeepEP.git
synced 2026-11-05 00:47:32 +00:00
[PR #794] Pack channel tail signals by runtime rank count (fix ~2x v2.5 hybrid dispatch regression) #789
Labels
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
deepseek-ai/DeepEP#789
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/deepseek-ai/DeepEP/pull/794
Author: @sssssakuraaaaa
Created: 10/23/2026
Status: 🔄 Open
Base:
main← Head:fix/dense-tail-signal-layout📝 Commits (1)
f04a242Pack channel tail signals by runtime rank count (fix v2.5 hybrid dispatch regression)📊 Changes
1 file changed (+10 additions, -2 deletions)
View changed files
📝
deep_ep/include/deep_ep/layout/ep/workspace.cuh(+10 -2)📄 Description
Summary
The v2.5 refactor (#763, commit
def8651) replaced the hand-laid, runtime-stridedtail-signal buffers with static 2D arrays in
EPSignals(
deep_ep/include/deep_ep/layout/ep/workspace.cuh):and the accessors index them directly as
[channel_idx][rank_idx]. The row stride istherefore the compile-time inner dimension
kNumMaxRanks = 1024, so consecutivechannels' tail signals are
kNumMaxRankselements apart — 8 KiB for theint64_tscaleout tail.
These tails are a producer/consumer handshake: the scaleout warp does an RDMA remote
atomic-add (
gin.red_add_rel) on the peer'sscaleout_channel_signaled_tail[channel][rank],and the forward warp
ld_acquire-polls the same counter before consuming each chunk.With a small runtime rank count (e.g. EP16 →
num_scaleout_ranks = 2), the per-channelcounters end up spread 8 KiB apart. That stride reaches the wire unchanged —
red_add_relcomputes
ptr - lsa_base_ptr,ncclGincarries it as a window offset, and the GDAKI backendplaces it directly into the
raddrof theATOMIC_FAWQE — and the responder NIC'ssustained atomic rate collapses as the per-counter stride grows. The forward warp's
tail-wait (
fwdwait) balloons ~2.5x and hybrid inter-node dispatch time roughly doubles.This is a NIC atomic-rate / remote-address problem, not a bandwidth problem (payload bandwidth
is unchanged), and raising
num_smsdoes not help.This matches the independent, hardware-level finding in #793, whose standalone reproducer (32
RC QPs issuing 8 B
IBV_WR_ATOMIC_FETCH_AND_ADD, no GPU / NCCL / DeepEP involved) shows theresponder atomic rate dropping off a cliff between 1 KiB and 4 KiB of per-counter stride (e.g.
~17 Mops at 256 B → ~1.3 Mops at 4–8 KiB), and reproduces even in loopback with no switch.
Fix
Pack both tail arrays densely by the runtime rank count, restoring the pre-#763 (v2.1.0)
layout. This reuses the exact indexing idiom already present in this file
(
get_scaleout_rank_count_ptr:scaleout_rank_idx * num_scaleup_ranks + scaleup_rank_idx):The max flat index
kNumMaxChannels * num_scaleout_ranksstays within thekNumMaxChannels * kNumMaxRanksbacking storage, sincenum_scaleout_ranksandnum_scaleup_ranksare both<= kNumMaxRanks. All current callers already go through thesetwo accessors, so no other call sites change.
Benchmark
B300 + NCCL 2.32.3 + CUDA 13.0, EP16 (2 nodes x 8 GPU) hybrid,
num_sms=32,EP_OVERRIDE_RDMA_SL=162, token sweep 1024→16384:back on par with v2.1.0 (dispatch SO recovers to ~82/102/132/157/171 GB/s).
Repro
--allow-hybrid-mode 1)tests/ep/test_ep.py, experts=256, hidden=7168, topk=8, num_sms=32,EP_OVERRIDE_RDMA_SL=162, token sweep🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.