mirror of
https://github.com/deepseek-ai/DeepEP.git
synced 2026-11-05 00:47:32 +00:00
[PR #727] utils: only reuse PyTorch's NCCL comm when _comm_ptr() is non-null #734
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#734
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/727
Author: @dmvevents
Created: 8/14/2026
Status: 🔄 Open
Base:
main← Head:fix/null-guard-reused-comm-ptr📝 Commits (4)
13dab41utils: only reuse PyTorch's NCCL comm when _comm_ptr() is non-null0ef4e11utils: make the PyTorch-comm reuse decision group-uniform9cad80btests: cover the reuse escape hatch and the cache-hit path080984autils: make comm.py yapf-clean so the format check can pass📊 Changes
2 files changed (+167 additions, -5 deletions)
View changed files
📝
deep_ep/utils/comm.py(+30 -5)➕
tests/utils/test_comm_reuse.py(+137 -0)📄 Description
Fixes #726.
get_nccl_comm_handle's reuse branch trustsbackend._comm_ptr()blindly, but PyTorch creates NCCL communicators lazily —_comm_ptr()is a passive accessor that returns 0 when the ProcessGroup+device has no communicator yet (comms materialize at the first collective on that group+device; eager creation only happens wheninit_process_groupreceiveddevice_id=...).A consumer that constructs
ElasticBufferbefore any collective has run on the EP group getsNCCLCommHandle(0), andcalculate_elastic_buffer_sizereachesget_physical_domain_size→ncclTeamWorld(nullptr)→comm->nRanksnull-deref. vLLM's expert-parallel serve path is a live reproduction: deterministic segfault on all 16 ranks (DP16/EP16) at serve init — full chain in #726.Change: only take the borrowed-comm path when the pointer is non-null; otherwise fall through to the existing create-own-comm path. On a cold group the fallback's
all_gather_objectis itself a collective on that group, so it lazily materializes torch's comm as a side effect and every rank takes the same branch — no rank divergence on the cold-boot case.Why the test suite doesn't catch it:
tests/utilsinit_distpassesdevice_id=...toinit_process_group, so torch eager-creates comms and_comm_ptr()is always valid there. The null case is only reachable from lazy-init consumers.Validated E2E on the vLLM path (Qwen3-30B-A3B-FP8, DP16/EP16, 2 nodes over EFA): with the create-own-comm path taken (behaviorally identical to this guard's fall-through on a cold group), serve comes up and sustains a c=1→64 load sweep with 0 errors.
Update: the guard is now an explicit all-rank-consistent decision — every rank gathers every rank's communicator-nullness before any rank branches, so reuse happens only when all ranks hold a live communicator and no rank can be left behind in a collective. Covered by
tests/utils/test_comm_reuse.py.🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.