Skip to content

fix(rccl): roll back a GIN activation that fails after connect - #14231

Closed
dlamd1dai wants to merge 4 commits into
ROCm:developfrom
dlamd1dai:users/dondai/rccl-gin-activation-rollback
Closed

dlamd1dai wants to merge 4 commits into
ROCm:developfrom
dlamd1dai:users/dondai/rccl-gin-activation-rollback

Conversation

@dlamd1dai

@dlamd1dai dlamd1dai commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

Follow-up to #13231, raised by Argus in its review there. Depends on #14321, which replaced #13231 with the same change on an upstream branch. This branch is stacked on #13231's commits from before the rebase, so only the last commit, fix(rccl): roll back a GIN activation that fails after connect, belongs here. I will rebase once #14321 merges.

#14321 sets devr->ginEnabled only after ncclGinConnectOnce succeeds. After that, ncclDevrCommCreateInternal still registers every existing memory with GIN and then updates every existing window's GIN handles. If anything fails in that stretch (a symMemoryRegisterGin call, ncclGinDevCommSetup, stream creation, or symWindowInitGin), GIN stays latched:

  • The memory that failed to register has its ginSegmentInfos freed, and memories later in the list were never registered.
  • Windows never got their GIN handles, because the window update runs after ncclGinDevCommSetup.
  • The next create computes ginActivated false, skips both the registration and the window update, and still builds a full GIN devcomm. A later ncclCommWindowRegister on one of those memories then indexes the ginSegmentInfos that was freed.

Technical Details

  • ncclDevrCommCreateInternal treats connect, registering the existing memories, and updating the existing windows as one activation. It records completion in ginActivationDone, set once the window update loop finishes.
  • On any failure before that point:
    • symGinActivationRollbackWindows (on the fail_stream path, while the stream is still alive) frees the segment windows attached to existing windows, clears their GIN handles, and copies the cleared window to the device.
    • symGinActivationRollbackMemories (on the fail path) deregisters every memory holding ginSegmentInfos, frees them, resets numGinSegments to 1 as for memory obtained before GIN, and clears ginEnabled.
  • Rolling back everything GIN holds is safe because nothing had GIN state before this activation. ginActivated is only true while ginEnabled was false, and ginSegmentInfos and a window's ginMultiSegmentWins are only set once GIN is enabled.
  • ncclGinConnectOnce keeps connected true, so the retry does not reconnect. It re-registers the memories and updates the windows again.
  • Failures after the window update are unchanged. GIN is then fully activated for every existing memory and window, and keeping it latched is correct.
  • As with the connect retry in fix(rccl): latch GIN only after a successful connect #14321, the rollback assumes the failure happened on every rank.

Issue Tracking

JIRA ID: AICOMRCCL-2879

Test Plan

  • New DevrCommCreateInternalTest.GinRegisterFailsAfterConnect_RollsBackActivation: connect succeeds, and two GIN-capable memories are on memHead. The first registers and the second fails. The test checks:
    • ginEnabled is already true while registration runs.
    • After the failure, the first memory is deregistered, neither memory keeps ginSegmentInfos, and ginEnabled is false.
    • A second create connects and registers again, then rolls back the same way.
  • Full host-test pipeline (run_host_tests.sh deps through run) in rocm/dev-ubuntu-22.04:7.2.

Test Result

  • rccl-UnitTestsMicro: 1664 of 1664 passed, and every other host binary passed.
  • With the dev_runtime.cc rollback reverted, the new test fails on the ginEnabled check after the first create.
  • With devr->ginEnabled = true; removed, the new test fails on the check inside the register hook.
  • Not covered by host tests: the window rollback, and failures in ncclGinDevCommSetup or stream creation. The fixture cannot reach the devcomm build past the registration loop.

Submission Checklist

dlamd1dai and others added 4 commits October 10, 2026 01:44
ncclDevrCommCreateInternal set devr->ginEnabled before ncclGinConnectOnce
and never cleared it. A failed connect left ginCommCount at 0, so a retry
skipped the reconnect and divided by zero in ginDevCommSetupWithBackend.
Set the latch only once connect succeeds, so the next create reconnects.

AICOMRCCL-2879

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: amd-argus[bot] <321830313+amd-argus[bot]@users.noreply.github.com>
…est fast

The suggestion apply left both comment lines in place, and EXPECT_FALSE let a latched ginEnabled fall through into a host-test divide by zero.

Co-authored-by: Cursor <cursoragent@cursor.com>
ncclDevrCommCreateInternal latches ginEnabled once ncclGinConnectOnce
succeeds, then registers existing memories with GIN and updates existing
windows. A failure in between left GIN latched with some memories
unregistered and some windows missing their GIN handles. The next create
saw ginEnabled, skipped activation, and built a GIN devcomm over them.

Until the window update finishes, a failure now deregisters every memory
this activation registered, frees the segment windows it attached, and
clears the latch, so the next create activates GIN again from scratch.

AICOMRCCL-2879

Co-authored-by: Cursor <cursoragent@cursor.com>
@therock-pr-bot

Copy link
Copy Markdown

✅ All Policy Checks Passed

Check Status Details
📝 PR Description ✅ Pass —
⛔ Forbidden Files ✅ Pass —
🧪 Unit Test ⚠️ Warning Error: Source/code files changed without an accompanying unit test.
Expected: add at least one test file named like test_<name>.py / test_<name>.cpp (or <name>_test.*).
Current: code file(s) changed: projects/rccl/src/dev_runtime.cc, projects/rccl/test/host/dev-runtime-test.cc, projects/rccl/test/host/fakes/dev_runtime_micro_fakes.cc, projects/rccl/test/host/fakes/dev_runtime_micro_fakes.h; no test file found
🚫 Draft PR 🔜 To Be Enabled —
🚩 Feature Flag 🔜 To Be Enabled —
📊 Code Coverage 🔜 To Be Enabled —

🎉 All policy checks passed!

📖 Need help? See the Policy FAQ for details on every check and how to fix failures.

🙋 Wish to Override Policy?

@dlamd1dai
dlamd1dai marked this pull request as ready for review October 26, 2026 04:58
@dlamd1dai
dlamd1dai requested review from a team October 26, 2026 04:58
@dlamd1dai dlamd1dai self-assigned this Oct 26, 2026

@amd-argus amd-argus Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ARGUS

Findings

3 more finding(s), not posted inline
  • projects/rccl/src/dev_runtime.cc:861 (Low) symGinActivationRollbackMemories is a third copy of the GIN teardown in symMemoryRegisterGin's fail path (793-801) and symMemoryUnregister (817-819); one shared helper would keep them in step.
  • projects/rccl/test/host/dev-runtime-test.cc:5646 (Low) No assertion that the rollback clears mems[0].ginHostWins, which caches goodHostWin; add one.
  • projects/rccl/test/host/dev-runtime-test.cc:5659 (Nit) Comment says a later valid create still connects, but the test runs one create; describe what it checks.

Bottom line: Worth settling before approval: the window rollback at dev_runtime.cc:2250 has no test; zeroing the requirement-builder fakes' output would let one reach it.

Comment @argus review to re-run, or @argus re-review after pushing.

}
symMemoryDropRef(comm, mem);
fail_stream:
if (ginActivated && !ginActivationDone) symGinActivationRollbackWindows(devr, stream);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium

symGinActivationRollbackWindows has no test, so the suite passes with this call removed. The fixture stops early because the requirement-builder fakes at dev_runtime_micro_fakes.cc:498 leave the stack ncclDevResourceRequirements uninitialised, and line 2109 writes through outBufferHandle. Could those fakes zero their output? A test could then fail g_devrAllocAndPopulateSegmentWindows on a second GIN window. The first window's hook must return a shadow-pool object, since the default nullptr (dev_runtime_micro_fakes.cc:398) is skipped at line 838.


// Undo an incomplete GIN activation in ncclDevrCommCreateInternal. Memories and
// windows that existed before it carried no GIN state, so everything GIN holds
// now came from this activation.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low

Could this header fit the one-line budget? The safety argument still fits:

Suggested change
// now came from this activation.
// Undo a partial GIN activation; memories and windows held no GIN state before it.

@dlamd1dai

Copy link
Copy Markdown
Contributor Author

Closing in favor of #14325. It has the same commits (head ad48d9d) on an upstream branch, so the RCCL coco PR gate can run without a fork override.

@dlamd1dai dlamd1dai closed this Oct 27, 2026
dlamd1dai added a commit that referenced this pull request Oct 27, 2026
## Motivation

Replaces #13231 with the same commits on an upstream branch, so the RCCL
coco PR gate runs without a fork override. The earlier review, including
the Argus threads and Aurelien's and Kapil's approvals, is on #13231.

Found while reviewing #13228. If `ncclGinConnectOnce` fails, the next
`ncclDevrCommCreateInternal` on the same communicator can divide by zero
in `ginDevCommSetupWithBackend`.

`ncclDevrCommCreateInternal` set `devr->ginEnabled = true` as soon as
GIN was requested, before trying to connect, and no failure path cleared
it. A connect that fails before `backends[].ginCommCount` is assigned
leaves that count at 0, as it was at plugin registration. On retry,
`ginEnabled` is already true, so connect is skipped. Setup then runs
`ROUNDUP(nContextsTotal, backend->ginCommCount)` with a count of 0.

Two argument checks after the latch (a world GIN barrier on a rail
connection, and a custom stride of 0) returned with GIN already latched.
That left the communicator in the same state.

## Technical Details

- `ncclDevrCommCreateInternal` now sets `devr->ginEnabled` only after
`ncclGinConnectOnce` succeeds. A failed connect, or an early argument
rejection, leaves GIN unlatched, so the next create connects again.
`ncclGinConnectOnce` already closes partial connections and leaves
`connected` false on failure, so calling it again is safe.
- The comment on the latch starts with `RCCL:` and notes that upstream
latches before connect, so a future NCCL sync keeps it after connect.
- `ginDevCommSetupWithBackend` is unchanged. After this change, a
`ginCommCount` of 0 means connect never completed, and clamping it there
would hide that.
- Test fakes: `dev_runtime_micro_fakes` gets a `g_devrGinConnectOnce`
hook, modeled on `g_devrRmaProxyConnectOnce`, so a test can make connect
fail.

Not changed: if connect succeeds and a later step fails (GIN
registration of existing memory, `ncclGinDevCommSetup`, or the window
GIN-handle update), GIN stays latched as before. #14231 handles that
case.

## Issue Tracking

JIRA ID: AICOMRCCL-2879

## Test Plan

- New `DevrCommCreateInternalTest.GinConnectFails_RetryConnectsAgain`:
connect fails twice. The test checks that `ginEnabled` stays false and
that the second create calls connect again.
- New
`DevrCommCreateInternalTest.WorldGinBarrierOnRail_RejectedWithoutLatchingGin`:
the early rejection does not call connect and does not latch GIN.
- Full host-test suite, built host-only with `run_host_tests.sh` in
`rocm/dev-ubuntu-22.04:7.2`, after rebasing onto `develop`.

## Test Result

- After the rebase: 7909 of 7909 host tests passed across 32 binaries,
including both new tests and `develop`'s
`GinSignalRegions_UseLargestRoundedContextCount`.
- With the `dev_runtime.cc` change reverted,
`GinConnectFails_RetryConnectsAgain` fails on the `ginEnabled` check.

## Submission Checklist

- [x] Look over the contributing guidelines at
https://github.com/ROCm/rocm-systems/blob/develop/CONTRIBUTING.md.

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Aurelien Bouteiller <aurelien.bouteiller@amd.com>
Co-authored-by: amd-argus[bot] <321830313+amd-argus[bot]@users.noreply.github.com>
dlamd1dai added a commit that referenced this pull request Oct 30, 2026
## Motivation

Replaces #14231 with the same commits on an upstream branch, so the RCCL
coco PR gate runs without a fork override. The earlier review is on
#14231.

Follow-up to #14321 (merged), raised by Argus in its review of #13231.

#14321 sets `devr->ginEnabled` only after `ncclGinConnectOnce` succeeds.
After that, `ncclDevrCommCreateInternal` still registers every existing
memory with GIN and then updates every existing window's GIN handles. If
anything fails in that stretch (a `symMemoryRegisterGin` call,
`ncclGinDevCommSetup`, stream creation, or `symWindowInitGin`), GIN
stays latched:

- The memory that failed to register has its `ginSegmentInfos` freed,
and memories later in the list were never registered.
- Windows never got their GIN handles, because the window update runs
after `ncclGinDevCommSetup`.
- The next create computes `ginActivated` false, skips both the
registration and the window update, and still builds a full GIN devcomm.
A later `ncclCommWindowRegister` on one of those memories then indexes
the `ginSegmentInfos` that was freed.

## Technical Details

- `ncclDevrCommCreateInternal` treats connect, registering the existing
memories, and updating the existing windows as one activation. It
records completion in `ginActivationDone`, set once the window update
loop finishes.
- On any failure before that point:
- `symGinActivationRollbackWindows` (on the `fail_stream` path, while
the stream is still alive) frees the segment windows attached to
existing windows, clears their GIN handles, and copies the cleared
window to the device.
- `symGinActivationRollbackMemories` (on the `fail` path) deregisters
every memory holding `ginSegmentInfos`, frees them, resets
`numGinSegments` to 1 as for memory obtained before GIN, and clears
`ginEnabled`.
- When the window rollback runs, `fail_stream` synchronizes the stream
before destroying it. A retry rebuilds those windows on a new stream,
which is not ordered after this one.
- Rolling back everything GIN holds is safe because nothing had GIN
state before this activation. `ginActivated` is only true while
`ginEnabled` was false, and `ginSegmentInfos` and a window's
`ginMultiSegmentWins` are only set once GIN is enabled.
- `ncclGinConnectOnce` keeps `connected` true, so the retry does not
reconnect. It re-registers the memories and updates the windows again.
- Failures after the window update are unchanged. GIN is then fully
activated for every existing memory and window, and keeping it latched
is correct.
- As with the connect retry in #14321, the rollback assumes the failure
happened on every rank.

## Issue Tracking

JIRA ID: AICOMRCCL-2879

## Test Plan

- New
`DevrCommCreateInternalTest.GinRegisterFailsAfterConnect_RollsBackActivation`:
connect succeeds, and two GIN-capable memories are on `memHead`. The
first registers and the second fails. The test checks:
  - `ginEnabled` is already true while registration runs.
- After the failure, the first memory is deregistered, neither memory
keeps `ginSegmentInfos`, and `ginEnabled` is false.
- A second create connects and registers again, then rolls back the same
way.
- New
`DevrCommCreateInternalTest.StreamCreateFailsAfterGinRegister_RollsBackActivation`:
connect succeeds and both memories register, then stream creation fails.
The test checks that both memories are deregistered, neither keeps
`ginSegmentInfos` or `ginHostWins`, and `ginEnabled` is false.
- Full host-test pipeline (`run_host_tests.sh` deps through run) in
`rocm/dev-ubuntu-22.04:7.2`.

## Test Result

- On `develop` at `060a14eb23`: `rccl-UnitTestsMicro` 2232 of 2232
passed, and every other host binary passed.
- With `ginActivationDone = true;` moved right after the register loop,
`StreamCreateFailsAfterGinRegister_RollsBackActivation` fails (no
deregistrations, `ginEnabled` still true), while
`GinRegisterFailsAfterConnect_RollsBackActivation` still passes.
- Before the rebase, with the `dev_runtime.cc` rollback reverted,
`GinRegisterFailsAfterConnect_RollsBackActivation` failed on the
`ginEnabled` check after the first create. With `devr->ginEnabled =
true;` removed, it failed on the check inside the register hook.
- Not covered by host tests: the window rollback, which needs a window
in `winSorted` that this fixture does not build, and a failure inside
`ncclGinDevCommSetup`, which the fixture fakes as always succeeding.

## Submission Checklist

- [x] Look over the contributing guidelines at
https://github.com/ROCm/rocm-systems/blob/develop/CONTRIBUTING.md.


Made with [Cursor](https://cursor.com)

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants