Repository navigation
fix(rccl): return ncclInProgress from non-blocking group init when a rank is missing - #10796
Conversation
✅ All Policy Checks Passed
📖 Need help? See the Policy FAQ for details on every check and how to fix failures. |
05d508f to
c71e91b
Compare
|
Adjacent to this change, in That last line unconditionally sets Note a sticky guard ( |
|
Host-only microtest for the TEST_F(GroupEndAsyncSettleTest, InitJobAlongsideCollective_SettlesCommAtSuccess) {
ncclGroupStartInternal();
ncclGroupCommJoin(collComm_.get(), ncclGroupTaskTypeCollective);
QueueInitJobFor(initComm_.get());
ncclGroupBlocking = 0;
const ncclResult_t settled = EndGroupAndSettle(initComm_.get());
// Anchor that the collective arm really ran -- otherwise this passes for the contrast case's reason.
EXPECT_EQ(1, g_ncclLaunchFinishCalls);
EXPECT_EQ(1, g_ncclTasksRegAndEnqueueCalls);
EXPECT_EQ(ncclSuccess, COMPILER_ATOMIC_LOAD(&collComm_->asyncResult, std::memory_order_acquire));
EXPECT_EQ(ncclSuccess, settled);
}
// Contrast: identical setup minus the collective comm. groupCommHeadMain[Collective] is nullptr,
// the drain's clear fires, and the same comm settles -- pinning that conjunct as the sole cause.
TEST_F(GroupEndAsyncSettleTest, InitJobAlone_SettlesCommAtSuccess) {
ncclGroupStartInternal();
QueueInitJobFor(initComm_.get());
ncclGroupBlocking = 0;
const ncclResult_t settled = EndGroupAndSettle(initComm_.get());
EXPECT_EQ(0, g_ncclLaunchFinishCalls);
EXPECT_EQ(ncclSuccess, settled);
}
Dropping the Needs four supporting changes, the first being the one that matters: the |
|
Host-only microtest for the // Already-joined path: L100-133 is skipped, so only the L134 store can move ncclGroupBlocking.
TEST_F(GroupBlockingGuardTest, DestroyFlagGuardSurvivesAlreadyJoinedCommJoin) {
ncclGroupStartInternal();
ncclGroupCommJoin(joinComm_.get(), kColl);
ASSERT_EQ(joinComm_.get(), ncclGroupCommHead[kColl]);
ASSERT_EQ(nullptr, joinComm_->groupNext[kColl]); // sentinel consumed => next join skips the body
LaunchDestroyJob();
ASSERT_EQ(1, ncclGroupBlocking);
ncclGroupCommJoin(joinComm_.get(), kColl);
EXPECT_EQ(joinComm_.get(), ncclGroupCommHead[kColl]);
EXPECT_EQ(nullptr, joinComm_->groupNext[kColl]) << "body must not have re-inserted the comm";
EXPECT_EQ(1, ncclGroupBlocking) << "the teardown guard must be preserved across ncclGroupCommJoin";
}
// Contrast: with no teardown job in flight the same store is doing its legitimate job.
TEST_F(GroupBlockingGuardTest, CommJoinAdoptsCommBlockingModeWhenNoDestroyJob) {
joinComm_->config.blocking = 1; // distinguishable from both the -1 initial value and 0
ncclGroupStartInternal();
ASSERT_EQ(-1, ncclGroupBlocking);
ncclGroupCommJoin(joinComm_.get(), kColl);
EXPECT_EQ(joinComm_.get(), ncclGroupCommHead[kColl]);
EXPECT_EQ(1, ncclGroupBlocking);
}The first fails today with Two fixture details if you adapt it: No shared-fake changes needed — new fixture in |
|
One more on the newly-live non-blocking path, at The branch publishes ASan on a probe of that path: Offset 108 of the 160-byte Only reachable if thread construction throws, so it's latent — but this fail path used to be live only for preconnect groups and now runs for every non-blocking init. A microtest, red today ( TEST_F(GroupEndAsyncSettleTest, ThreadCreateFailure_LeavesNoDanglingGroupJob) {
// ... one async job, no collective comm; g_forceThreadCreateFailure armed after adoption
EXPECT_TRUE(adoptedBeforeFailure); // the comm did take the job
EXPECT_EQ(ncclSystemError, ncclGroupEndInternal()); // the fail path really ran
EXPECT_EQ(ncclSystemError, initComm_->asyncResult); // groupCleanup processed this comm
EXPECT_EQ(nullptr, initComm_->groupJob); // <-- fails today: 0x338b31c0
}One thing worth calling out about the fix: nulling only the Separately, the new |
alex-breslow-amd
left a comment
There was a problem hiding this comment.
From what I can tell, this looks good. Going to stamp before I forget. Please follow up on Praneeth's feedback.
There was a problem hiding this comment.
Pull request overview
This PR fixes a non-blocking communicator-init hang in RCCL by ensuring ncclGroupEndInternal correctly selects the non-blocking path when pending async init work exists, returning ncclInProgress promptly (so callers can poll/timeout/abort). It also adds a host-only microtest suite (plus supporting fakes/stubs and build/test wiring) to lock in the intended blocking vs non-blocking behavior.
Changes:
- Fix
ncclGroupEndInternalnon-blocking predicate to inspectgroupJob->asyncJobsafter the async-job queue transfer. - Add
GroupEndInternalTesthost-only microtests that assert non-blocking groups returnncclInProgressand publish a group job, while blocking groups run synchronously. - Extend host microtest build/test infrastructure (CMake sources/defines, new fakes/stubs, and micro test category inclusion).
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| projects/rccl/src/group.cc | One-line fix to non-blocking selection logic after async-job queue transfer. |
| projects/rccl/test/host/group-test.cc | New host-only microtests exercising ncclGroupEndInternal blocking vs non-blocking behavior. |
| projects/rccl/test/host/fakes/comm_fakes.h | New header providing a controllable seam for ncclCommSetAsyncError. |
| projects/rccl/test/host/fakes/comm_fakes.cc | New comm/utility/profiling/roctx fakes used by host-only microtests. |
| projects/rccl/test/host/fakes/collective_stubs.cc | New fail-loud stubs to satisfy collective/transport symbols not expected to run in these tests. |
| projects/rccl/test/host/CMakeLists.txt | Wire new group microtest + fakes into rccl-UnitTestsMicro and pass GROUP_CC_PATH. |
| projects/rccl/test/test_categories_micro.yaml | Include GroupEndInternalTest.* in the microtest category. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
c71e91b to
45a3166
Compare
RCCL Perf-Regression Gate:
|
| group | keys | regressions | inconclusive |
|---|---|---|---|
| all_gather_perf-d=bfloat16-default | 0 | 0 | 0 |
| all_gather_perf-d=float-default | 0 | 0 | 0 |
| all_reduce_perf-d=bfloat16-default | 0 | 0 | 0 |
| all_reduce_perf-d=float-default | 0 | 0 | 0 |
| broadcast_perf-d=bfloat16-default | 0 | 0 | 0 |
| broadcast_perf-d=float-default | 0 | 0 | 0 |
| reduce_scatter_perf-d=bfloat16-default | 0 | 0 | 0 |
| reduce_scatter_perf-d=float-default | 0 | 0 | 0 |
d0d605f to
4071f51
Compare
|
@pvallem I addressed the UAF comment in this PR, and created a follow up ticket for the group.h:134 clobber. |
…ing rank ncclGroupEndInternal moves pending async jobs out of ncclAsyncJobs into groupJob->asyncJobs via ncclIntruQueueTransfer, which empties ncclAsyncJobs. The subsequent non-blocking-vs-blocking decision still inspected the now-empty ncclAsyncJobs, so ncclCommInitRankConfig with config.blocking = 0 fell through to the blocking path and waited for a missing rank instead of returning ncclInProgress. Inspect groupJob->asyncJobs (which owns the jobs after the move) when deciding the path, so a non-blocking init with pending jobs returns ncclInProgress. Co-authored-by: Pi Agent
Add a host-only microtest (rccl-UnitTestsMicro) that pins the branch
selection in ncclGroupEndInternal: a non-blocking group (config.blocking
== 0) carrying pending async init jobs must return ncclInProgress and
publish a group job on the communicator, rather than running the jobs
synchronously on the caller. That is the guarantee
ncclCommInitRankConfig(blocking=0) relies on so a caller can poll / time
out / abort when a peer rank never appears.
group-test.cc #includes the hipified src/group.cc so the file-static
launch helpers are reachable, and installs its group-specific behaviour
(the ncclCommSetAsyncError result) from the test fixture. The externals
group.cc references are satisfied by fakes organised by the module being
faked, not by a per-unit file:
* comm_fakes.{cc,h} -- the controllable ncclCommSetAsyncError seam plus
the comm/util globals and recorder/nvtx/profiler instrumentation that
are real in the init unit (init.cc / utils.cc) and so cannot live in
the shared nccl_fakes.cc.
* collective_stubs.cc -- self-contained fail-loud floor for the
collective launch/registration pipeline and the transport connect
entry points the tested path never executes. Kept independent of the
init binary's transport_stubs.cc, whose NVLS/P2P stubs now route
through test-driven seam globals defined in the init test's own TUs.
* param_redirect.h -- routes group.cc's NCCL_PARAM sites through
g_loadParam (the shared mechanism p2p-test already uses), so the
production ncclLoadParam signature is irrelevant.
Reverting the one-line fix in the hipified tree turns the two non-blocking
assertions red while the blocking-path contrast test stays green,
confirming the tests guard the fix and the setup is not over-broad.
Co-authored-by: Pi Agent
…stubs Co-authored-by: Pi Agent
…ion signature Co-authored-by: Pi Agent
…ogress on the comm Co-authored-by: Pi Agent
The non-blocking branch of ncclGroupEndInternal publishes comm->groupJob = groupJob on every participating comm before creating the background thread. If STDTHREADCREATE_GOTO fails, the fail: path runs groupCleanup and deletes groupJob but never clears comm->groupJob. Since comm->groupJob is only cleared elsewhere on success (init.cc, gated on asyncError == ncclSuccess), a later ncclCommAbort -> ncclCommEnsureReady -> ncclGroupJobAbort dereferences the freed group job (heap-use-after-free). Previously this fail path was only live for preconnect groups; the non-blocking init predicate fix makes it run for every non-blocking ncclCommInitRankConfig, so the latent UAF became reachable. Add groupJobUnlinkComms to null comm->groupJob for every comm that adopted this exact groupJob before it is destroyed, walking both the async-jobs list (an init/split comm is only reachable there, never on the groupCommHead chains) and the groupCommHead chains. Scoped to the ncclGroupEndInternal fail path only -- groupLaunch's own groupCleanup runs on the background thread after a successful thread create, where comm->groupJob is legitimately in use and must survive for the caller to join/abort. Cover it with a host-only microtest that arms a redefinable STDTHREADCREATE_GOTO to force the thread-create failure and asserts comm->groupJob is cleared. Red before the fix, green after. Co-authored-by: Pi Agent
4071f51 to
fd4447c
Compare
TheRock Submodule Bump ActivityNewest first
|
## Summary Enable nccl2 communicator reconfigure on ROCm by dropping the `USE_ROCM` compile-time exclusion around `ncclCommRevoke` and `supportsReconfigure()`. Version gates, defined once as `NCCL_HAS_COMM_REVOKE` in `nccl2/NcclApi.hpp`: - CUDA: unchanged, NCCL 2.28.0 - ROCm: RCCL 2.30.7 (ROCm 10.1) The constructor rejects `enable_reconfigure` on builds below the gate with `TORCH_CHECK(!enable_reconfigure || supportsReconfigure())`. ## Why RCCL 2.30.7 and not 2.30.4 RCCL 2.30.4 (ROCm 7.14 and 10.0) is the first RCCL with `ncclCommRevoke`, but it hangs on the missing-rank nonblocking init path that reconfigure uses (`ncclGroupEndInternal` still inspects emptied `ncclAsyncJobs` after jobs were moved to `groupJob->asyncJobs`). A reconfigure with an absent rank blocks there instead of honoring `ReconfigureOptions::timeout`. The fix is [ROCm/rocm-systems#10796](ROCm/rocm-systems#10796) (`7621e6c`, 2026-08-28), which ships in ROCm 10.1 / RCCL 2.30.7. On RCCL 2.30.4, `supportsReconfigure()` stays false and `enable_reconfigure=True` fails in the constructor, as before this PR. 2.30.7 is not an exact marker for the fix: the 2.30.7 sync reached rocm-systems `develop` on 2026-08-14 ([ROCm/rocm-systems#9924](ROCm/rocm-systems#9924)), so 10.1 nightlies built between 2026-08-14 and 2026-08-28 report 2.30.7 without it. No released ROCm is affected. ## Tests `Nccl2FaultToleranceTest` skips as a class on ROCm when `torch.cuda.nccl.version() < (2, 30, 7)` and otherwise runs in full, including `test_reconfigure_timeout_is_retryable`. The class also needs 3 GPUs (`world_size = 3`). In upstream CI the ROCm 10.0 jobs skip the class; `rocm-preview` (ROCm 10.2.0a20260916, RCCL 2.30.7) runs it on 3+ GPU runners. ## Test plan - [x] Local 3-GPU MI355X (`gfx950`), ROCm `10.2.0a20260916` / RCCL 2.30.7, at `e795687abe21` - `test/distributed/test_c10d_fault_tolerance.py`: 34 tests, OK, 3 expected Gloo skips - `Nccl2FaultToleranceTest.test_reconfigure_timeout_is_retryable`: 3/3 ok, about 5 s each - With the RCCL version patched to `(2, 30, 4)`, the class is skipped with "nccl2 reconfigure requires RCCL 2.30.7 or later" - [x] Preprocessor check of `NCCL_HAS_COMM_REVOKE`: undefined for RCCL 2.30.4 and CUDA NCCL 2.27.7, defined for RCCL 2.30.7 / 2.31.2 and CUDA NCCL 2.28.0 / 2.30.4 - [x] Local 3-GPU MI355X against `rocm==10.1.0a20260902` / RCCL 2.30.7 at `ccbac3faaec`: timeout test ok in 4.647 s (hung on RCCL 2.30.4 until OS timeout) - [x] CI on `e795687abe21` - [x] [UTs pass on the ROCk CI for gfx942 with 8 GPU runner](https://github.com/ROCm/TheRock/actions/runs/36291283110/job/108541764587) (earlier head) - [x] [UTs pass on ROCk CI for gfx950 with 8 GPU runner](https://github.com/ROCm/TheRock/actions/runs/36291292204/job/108652014622) (earlier head) Pull Request resolved: pytorch#194774 Approved by: https://github.com/pragupta, https://github.com/kapilsh, https://github.com/jeffdaily
…rank is missing (#10796) ## What `ncclCommInitRankConfig` with `ncclConfig_t.blocking = 0` could block inside the call instead of returning promptly. In the simplest case — a 2-rank communicator where rank 1 never starts — rank 0's non-blocking init should return at once with `ncclInProgress` so the caller can poll, time out, or abort. On stock RCCL it instead waited for the missing rank and hung. ## Why / root cause In `ncclGroupEndInternal` (`src/group.cc`), the pending async jobs are moved out of `ncclAsyncJobs` into `groupJob->asyncJobs` via `ncclIntruQueueTransfer`, which empties `ncclAsyncJobs`. The subsequent non-blocking-vs-blocking decision still inspected the now-empty `ncclAsyncJobs`, so a non-blocking init with pending work mis-selected the blocking path and ran the jobs synchronously on the caller — blocking forever when a peer rank is absent. The check must look at `groupJob->asyncJobs`, which owns the jobs after the move. The fix is a one-line change to inspect `groupJob->asyncJobs` in that predicate. ## Testing - Ran the reproducer provided on the ticket on an 8xgfx950 machine: it hangs on `develop` (reproducing the bug) and passes with this fix, returning `ncclInProgress` promptly. - Added a host-only microtest (`GroupEndInternalTest` in `rccl-UnitTestsMicro`) that pins the branch selection: a non-blocking group carrying a pending init job returns `ncclInProgress` and publishes a group job on the communicator, while the blocking-group contrast test still runs synchronously and returns `ncclSuccess`. - Proved the guard is real: reverting the one-line fix in the hipified tree turns the two non-blocking assertions red and leaves the blocking test green. - Full `test/host/run_host_tests.sh all` pipeline passes (`rccl-UnitTestsMicro` 33 tests, plus the HostUnitTests and init micro-test binaries). - The externals `group.cc` reaches are satisfied by module-organised fakes (`comm_fakes`, `enqueue_stubs`, reused `transport_stubs`, `param_redirect.h`). This branch also cherry-picks the `AMDSMI_FABRIC_DIRECT` / `HIP_FABRIC_API` init-target defines so the host micro-test binaries build cleanly. JIRA ID : ROCM-30165
…rank is missing (#10796) ## What `ncclCommInitRankConfig` with `ncclConfig_t.blocking = 0` could block inside the call instead of returning promptly. In the simplest case — a 2-rank communicator where rank 1 never starts — rank 0's non-blocking init should return at once with `ncclInProgress` so the caller can poll, time out, or abort. On stock RCCL it instead waited for the missing rank and hung. ## Why / root cause In `ncclGroupEndInternal` (`src/group.cc`), the pending async jobs are moved out of `ncclAsyncJobs` into `groupJob->asyncJobs` via `ncclIntruQueueTransfer`, which empties `ncclAsyncJobs`. The subsequent non-blocking-vs-blocking decision still inspected the now-empty `ncclAsyncJobs`, so a non-blocking init with pending work mis-selected the blocking path and ran the jobs synchronously on the caller — blocking forever when a peer rank is absent. The check must look at `groupJob->asyncJobs`, which owns the jobs after the move. The fix is a one-line change to inspect `groupJob->asyncJobs` in that predicate. ## Testing - Ran the reproducer provided on the ticket on an 8xgfx950 machine: it hangs on `develop` (reproducing the bug) and passes with this fix, returning `ncclInProgress` promptly. - Added a host-only microtest (`GroupEndInternalTest` in `rccl-UnitTestsMicro`) that pins the branch selection: a non-blocking group carrying a pending init job returns `ncclInProgress` and publishes a group job on the communicator, while the blocking-group contrast test still runs synchronously and returns `ncclSuccess`. - Proved the guard is real: reverting the one-line fix in the hipified tree turns the two non-blocking assertions red and leaves the blocking test green. - Full `test/host/run_host_tests.sh all` pipeline passes (`rccl-UnitTestsMicro` 33 tests, plus the HostUnitTests and init micro-test binaries). - The externals `group.cc` reaches are satisfied by module-organised fakes (`comm_fakes`, `enqueue_stubs`, reused `transport_stubs`, `param_redirect.h`). This branch also cherry-picks the `AMDSMI_FABRIC_DIRECT` / `HIP_FABRIC_API` init-target defines so the host micro-test binaries build cleanly. JIRA ID : ROCM-30165
## Summary Enable nccl2 communicator reconfigure on ROCm by dropping the `USE_ROCM` compile-time exclusion around `ncclCommRevoke` and `supportsReconfigure()`. Version gates, defined once as `NCCL_HAS_COMM_REVOKE` in `nccl2/NcclApi.hpp`: - CUDA: unchanged, NCCL 2.28.0 - ROCm: RCCL 2.30.7 (ROCm 10.1) The constructor rejects `enable_reconfigure` on builds below the gate with `TORCH_CHECK(!enable_reconfigure || supportsReconfigure())`. ## Why RCCL 2.30.7 and not 2.30.4 RCCL 2.30.4 (ROCm 7.14 and 10.0) is the first RCCL with `ncclCommRevoke`, but it hangs on the missing-rank nonblocking init path that reconfigure uses (`ncclGroupEndInternal` still inspects emptied `ncclAsyncJobs` after jobs were moved to `groupJob->asyncJobs`). A reconfigure with an absent rank blocks there instead of honoring `ReconfigureOptions::timeout`. The fix is [ROCm/rocm-systems#10796](ROCm/rocm-systems#10796) (`7621e6c`, 2026-08-28), which ships in ROCm 10.1 / RCCL 2.30.7. On RCCL 2.30.4, `supportsReconfigure()` stays false and `enable_reconfigure=True` fails in the constructor, as before this PR. 2.30.7 is not an exact marker for the fix: the 2.30.7 sync reached rocm-systems `develop` on 2026-08-14 ([ROCm/rocm-systems#9924](ROCm/rocm-systems#9924)), so 10.1 nightlies built between 2026-08-14 and 2026-08-28 report 2.30.7 without it. No released ROCm is affected. ## Tests `Nccl2FaultToleranceTest` skips as a class on ROCm when `torch.cuda.nccl.version() < (2, 30, 7)` and otherwise runs in full, including `test_reconfigure_timeout_is_retryable`. The class also needs 3 GPUs (`world_size = 3`). In upstream CI the ROCm 10.0 jobs skip the class; `rocm-preview` (ROCm 10.2.0a20260916, RCCL 2.30.7) runs it on 3+ GPU runners. ## Test plan - [x] Local 3-GPU MI355X (`gfx950`), ROCm `10.2.0a20260916` / RCCL 2.30.7, at `e795687abe21` - `test/distributed/test_c10d_fault_tolerance.py`: 34 tests, OK, 3 expected Gloo skips - `Nccl2FaultToleranceTest.test_reconfigure_timeout_is_retryable`: 3/3 ok, about 5 s each - With the RCCL version patched to `(2, 30, 4)`, the class is skipped with "nccl2 reconfigure requires RCCL 2.30.7 or later" - [x] Preprocessor check of `NCCL_HAS_COMM_REVOKE`: undefined for RCCL 2.30.4 and CUDA NCCL 2.27.7, defined for RCCL 2.30.7 / 2.31.2 and CUDA NCCL 2.28.0 / 2.30.4 - [x] Local 3-GPU MI355X against `rocm==10.1.0a20260902` / RCCL 2.30.7 at `ccbac3faaec`: timeout test ok in 4.647 s (hung on RCCL 2.30.4 until OS timeout) - [x] CI on `e795687abe21` - [x] [UTs pass on the ROCk CI for gfx942 with 8 GPU runner](https://github.com/ROCm/TheRock/actions/runs/36291283110/job/108541764587) (earlier head) - [x] [UTs pass on ROCk CI for gfx950 with 8 GPU runner](https://github.com/ROCm/TheRock/actions/runs/36291292204/job/108652014622) (earlier head) Pull Request resolved: pytorch#194774 Approved by: https://github.com/pragupta, https://github.com/kapilsh, https://github.com/jeffdaily
…rank is missing (#10796) ## What `ncclCommInitRankConfig` with `ncclConfig_t.blocking = 0` could block inside the call instead of returning promptly. In the simplest case — a 2-rank communicator where rank 1 never starts — rank 0's non-blocking init should return at once with `ncclInProgress` so the caller can poll, time out, or abort. On stock RCCL it instead waited for the missing rank and hung. ## Why / root cause In `ncclGroupEndInternal` (`src/group.cc`), the pending async jobs are moved out of `ncclAsyncJobs` into `groupJob->asyncJobs` via `ncclIntruQueueTransfer`, which empties `ncclAsyncJobs`. The subsequent non-blocking-vs-blocking decision still inspected the now-empty `ncclAsyncJobs`, so a non-blocking init with pending work mis-selected the blocking path and ran the jobs synchronously on the caller — blocking forever when a peer rank is absent. The check must look at `groupJob->asyncJobs`, which owns the jobs after the move. The fix is a one-line change to inspect `groupJob->asyncJobs` in that predicate. ## Testing - Ran the reproducer provided on the ticket on an 8xgfx950 machine: it hangs on `develop` (reproducing the bug) and passes with this fix, returning `ncclInProgress` promptly. - Added a host-only microtest (`GroupEndInternalTest` in `rccl-UnitTestsMicro`) that pins the branch selection: a non-blocking group carrying a pending init job returns `ncclInProgress` and publishes a group job on the communicator, while the blocking-group contrast test still runs synchronously and returns `ncclSuccess`. - Proved the guard is real: reverting the one-line fix in the hipified tree turns the two non-blocking assertions red and leaves the blocking test green. - Full `test/host/run_host_tests.sh all` pipeline passes (`rccl-UnitTestsMicro` 33 tests, plus the HostUnitTests and init micro-test binaries). - The externals `group.cc` reaches are satisfied by module-organised fakes (`comm_fakes`, `enqueue_stubs`, reused `transport_stubs`, `param_redirect.h`). This branch also cherry-picks the `AMDSMI_FABRIC_DIRECT` / `HIP_FABRIC_API` init-target defines so the host micro-test binaries build cleanly. JIRA ID : ROCM-30165
…rank is missing (#10796) ## What `ncclCommInitRankConfig` with `ncclConfig_t.blocking = 0` could block inside the call instead of returning promptly. In the simplest case — a 2-rank communicator where rank 1 never starts — rank 0's non-blocking init should return at once with `ncclInProgress` so the caller can poll, time out, or abort. On stock RCCL it instead waited for the missing rank and hung. ## Why / root cause In `ncclGroupEndInternal` (`src/group.cc`), the pending async jobs are moved out of `ncclAsyncJobs` into `groupJob->asyncJobs` via `ncclIntruQueueTransfer`, which empties `ncclAsyncJobs`. The subsequent non-blocking-vs-blocking decision still inspected the now-empty `ncclAsyncJobs`, so a non-blocking init with pending work mis-selected the blocking path and ran the jobs synchronously on the caller — blocking forever when a peer rank is absent. The check must look at `groupJob->asyncJobs`, which owns the jobs after the move. The fix is a one-line change to inspect `groupJob->asyncJobs` in that predicate. ## Testing - Ran the reproducer provided on the ticket on an 8xgfx950 machine: it hangs on `develop` (reproducing the bug) and passes with this fix, returning `ncclInProgress` promptly. - Added a host-only microtest (`GroupEndInternalTest` in `rccl-UnitTestsMicro`) that pins the branch selection: a non-blocking group carrying a pending init job returns `ncclInProgress` and publishes a group job on the communicator, while the blocking-group contrast test still runs synchronously and returns `ncclSuccess`. - Proved the guard is real: reverting the one-line fix in the hipified tree turns the two non-blocking assertions red and leaves the blocking test green. - Full `test/host/run_host_tests.sh all` pipeline passes (`rccl-UnitTestsMicro` 33 tests, plus the HostUnitTests and init micro-test binaries). - The externals `group.cc` reaches are satisfied by module-organised fakes (`comm_fakes`, `enqueue_stubs`, reused `transport_stubs`, `param_redirect.h`). This branch also cherry-picks the `AMDSMI_FABRIC_DIRECT` / `HIP_FABRIC_API` init-target defines so the host micro-test binaries build cleanly. JIRA ID : ROCM-30165
…rank is missing (#10796) ## What `ncclCommInitRankConfig` with `ncclConfig_t.blocking = 0` could block inside the call instead of returning promptly. In the simplest case — a 2-rank communicator where rank 1 never starts — rank 0's non-blocking init should return at once with `ncclInProgress` so the caller can poll, time out, or abort. On stock RCCL it instead waited for the missing rank and hung. ## Why / root cause In `ncclGroupEndInternal` (`src/group.cc`), the pending async jobs are moved out of `ncclAsyncJobs` into `groupJob->asyncJobs` via `ncclIntruQueueTransfer`, which empties `ncclAsyncJobs`. The subsequent non-blocking-vs-blocking decision still inspected the now-empty `ncclAsyncJobs`, so a non-blocking init with pending work mis-selected the blocking path and ran the jobs synchronously on the caller — blocking forever when a peer rank is absent. The check must look at `groupJob->asyncJobs`, which owns the jobs after the move. The fix is a one-line change to inspect `groupJob->asyncJobs` in that predicate. ## Testing - Ran the reproducer provided on the ticket on an 8xgfx950 machine: it hangs on `develop` (reproducing the bug) and passes with this fix, returning `ncclInProgress` promptly. - Added a host-only microtest (`GroupEndInternalTest` in `rccl-UnitTestsMicro`) that pins the branch selection: a non-blocking group carrying a pending init job returns `ncclInProgress` and publishes a group job on the communicator, while the blocking-group contrast test still runs synchronously and returns `ncclSuccess`. - Proved the guard is real: reverting the one-line fix in the hipified tree turns the two non-blocking assertions red and leaves the blocking test green. - Full `test/host/run_host_tests.sh all` pipeline passes (`rccl-UnitTestsMicro` 33 tests, plus the HostUnitTests and init micro-test binaries). - The externals `group.cc` reaches are satisfied by module-organised fakes (`comm_fakes`, `enqueue_stubs`, reused `transport_stubs`, `param_redirect.h`). This branch also cherry-picks the `AMDSMI_FABRIC_DIRECT` / `HIP_FABRIC_API` init-target defines so the host micro-test binaries build cleanly. JIRA ID : ROCM-30165
…rank is missing (#10796) `ncclCommInitRankConfig` with `ncclConfig_t.blocking = 0` could block inside the call instead of returning promptly. In the simplest case — a 2-rank communicator where rank 1 never starts — rank 0's non-blocking init should return at once with `ncclInProgress` so the caller can poll, time out, or abort. On stock RCCL it instead waited for the missing rank and hung. In `ncclGroupEndInternal` (`src/group.cc`), the pending async jobs are moved out of `ncclAsyncJobs` into `groupJob->asyncJobs` via `ncclIntruQueueTransfer`, which empties `ncclAsyncJobs`. The subsequent non-blocking-vs-blocking decision still inspected the now-empty `ncclAsyncJobs`, so a non-blocking init with pending work mis-selected the blocking path and ran the jobs synchronously on the caller — blocking forever when a peer rank is absent. The check must look at `groupJob->asyncJobs`, which owns the jobs after the move. The fix is a one-line change to inspect `groupJob->asyncJobs` in that predicate. - Ran the reproducer provided on the ticket on an 8xgfx950 machine: it hangs on `develop` (reproducing the bug) and passes with this fix, returning `ncclInProgress` promptly. - Added a host-only microtest (`GroupEndInternalTest` in `rccl-UnitTestsMicro`) that pins the branch selection: a non-blocking group carrying a pending init job returns `ncclInProgress` and publishes a group job on the communicator, while the blocking-group contrast test still runs synchronously and returns `ncclSuccess`. - Proved the guard is real: reverting the one-line fix in the hipified tree turns the two non-blocking assertions red and leaves the blocking test green. - Full `test/host/run_host_tests.sh all` pipeline passes (`rccl-UnitTestsMicro` 33 tests, plus the HostUnitTests and init micro-test binaries). - The externals `group.cc` reaches are satisfied by module-organised fakes (`comm_fakes`, `enqueue_stubs`, reused `transport_stubs`, `param_redirect.h`). This branch also cherry-picks the `AMDSMI_FABRIC_DIRECT` / `HIP_FABRIC_API` init-target defines so the host micro-test binaries build cleanly. JIRA ID : ROCM-30165
What
ncclCommInitRankConfigwithncclConfig_t.blocking = 0could block inside thecall instead of returning promptly. In the simplest case — a 2-rank
communicator where rank 1 never starts — rank 0's non-blocking init should
return at once with
ncclInProgressso the caller can poll, time out, or abort.On stock RCCL it instead waited for the missing rank and hung.
Why / root cause
In
ncclGroupEndInternal(src/group.cc), the pending async jobs are moved outof
ncclAsyncJobsintogroupJob->asyncJobsviancclIntruQueueTransfer, whichempties
ncclAsyncJobs. The subsequent non-blocking-vs-blocking decision stillinspected the now-empty
ncclAsyncJobs, so a non-blocking init with pending workmis-selected the blocking path and ran the jobs synchronously on the caller —
blocking forever when a peer rank is absent. The check must look at
groupJob->asyncJobs, which owns the jobs after the move.The fix is a one-line change to inspect
groupJob->asyncJobsin that predicate.Testing
develop(reproducing the bug) and passes with this fix, returningncclInProgresspromptly.GroupEndInternalTestinrccl-UnitTestsMicro)that pins the branch selection: a non-blocking group carrying a pending init
job returns
ncclInProgressand publishes a group job on the communicator,while the blocking-group contrast test still runs synchronously and returns
ncclSuccess.turns the two non-blocking assertions red and leaves the blocking test green.
test/host/run_host_tests.sh allpipeline passes (rccl-UnitTestsMicro33 tests, plus the HostUnitTests and init micro-test binaries).
group.ccreaches are satisfied by module-organised fakes(
comm_fakes,enqueue_stubs, reusedtransport_stubs,param_redirect.h).This branch also cherry-picks the
AMDSMI_FABRIC_DIRECT/HIP_FABRIC_APIinit-target defines so the host micro-test binaries build cleanly.
JIRA ID : ROCM-30165