Skip to content

fix(rccl): restore ncclScatter bandwidth on AINIC with two channels per peer - #13468

Merged
arvindcheru merged 2 commits into
developfrom
users/nikitaxgusev/rocm-31938-ainic-scatter-single-channel
Oct 16, 2026
Merged

arvindcheru merged 2 commits into
developfrom
users/nikitaxgusev/rocm-31938-ainic-scatter-single-channel

Conversation

@nikitaxgusev

@nikitaxgusev nikitaxgusev commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

Commit 242e0a9 (#11635) raised p2pnChannelsPerPeer from 1 to 2 on AINIC for jobs of up to 8 nodes, so that a single peer can saturate a NIC. That helps point-to-point traffic, but ncclScatter regressed by 15 to 21% from 32 MB upward on multi-node AINIC jobs compared with 7.14. Scatter already drives all nRanks - 1 peers from one root, so splitting each peer into two parts adds no bandwidth and costs some.

This PR restores scatter performance without changing the channel count for the other collectives.

Technical Details

  • New helper rcclP2pTaskParts(comm, collAPI) returns how many of the p2pnChannelsPerPeer parts a P2P task uses and connects. For ncclFuncScatter on multi-node AINIC it returns the largest power of two not above p2pnChannels / (nRanks - 1), capped at p2pnChannelsPerPeer and at least 1, so the root's flows still fit the P2P channel pool. A user-set NCCL_NCHANNELS_PER_NET_PEER is kept, as in ncclTopoComputeP2pChannels. Otherwise it returns p2pnChannelsPerPeer.
  • The decision depends only on collAPI and comm-wide sizes, so sender and receiver always agree on the number of parts.
  • Limited to AINIC: it is the only NIC path that raises p2pnChannelsPerPeer back to 2 on multi-node (fix(rccl): fixed default behavior on AINIC #11635), and the only one measured. On other NICs the halving loop in ncclTopoComputeP2pChannels already reduces the parts per peer when the peers do not fit the pool.
  • Used in three places:
    • channel count selection (rcclP2pPolicyChannels), also with P2P disabled, since the limit is about network flows;
    • the connector checks and buffer registration in addP2pToPlan;
    • preconnect (p2pTaskAppend, postTuneP2pRecordPreconnect).
  • NIC buffer registration (postTuneP2pRegisterBuffer) keeps p2pnChannelsPerPeer: Scatter tasks have allowUB off and never reach it.
  • When only some parts are connected, sendSeen / recvSeen stay clear, so a later task to the same peer that uses all parts still connects the rest.
  • For every other collective and for send/recv the helper returns p2pnChannelsPerPeer, which equals nChannelsMax in addP2pToPlan, so their code path takes the same decisions as before.
  • Host microtests:
    • EnqueueMicrotest.P2pTaskParts_*: the part rule (pools of 2, 3, 4, 8 and 16 nodes, the pool boundary, power-of-two rounding, the one-part floor, non-AINIC, single node, user-set count);
    • EnqueueMicrotest.P2pPolicyChannels_ScatterBelowPerPeerReturnsItsParts: the channel count;
    • TaskPostTuningMicrotest.P2pRecordPreconnect_ScatterWithFewerParts_ClaimsItsPartsAndLeavesTheRestForLater: the partial-to-full preconnect on both sides.

Limiting only the channel count is not enough. When scatter sends on part 0 but connects both parts itself, it stays at or below the two-channel speed in most runs (4 of 5 runs at about 110 GB/s instead of 137 GB/s). Not connecting part 1 makes the result stable. Part 1 connected later by another collective on the same communicator (alltoall) does not slow scatter down. The exact runtime cause of the slowdown is not identified yet: the connection setup logs of fast and slow runs are identical.

With NCCL_PXN_DISABLE=1 there is no regression, because the root is limited by its own NIC.

Issue Tracking

JIRA ID : ROCM-31938

Test Plan

  • 4 nodes x 8 MI355X (32 ranks), AINIC (NCCL_NET=ROCM-IB, RCCL_AINIC_ROCE=1), PXN on.
  • rccl-tests with data check on (-c 1), -n 20 -w 5, 5 cycles per run.
  • Three builds from the same base, run back to back in every repeat:
    • revert: develop without the AINIC p2pnChannelsPerPeer raise from 242e0a9 (matches 7.14);
    • develop;
    • this PR.
  • All P2P collectives (scatter, gather, alltoall, alltoallv, all_gather, reduce_scatter) up to 16 GB, 2 repeats.
  • All collectives (also sendrecv, all_reduce, broadcast, reduce) up to 2 GB, 3 repeats.
  • Scatter on one communicator before and after alltoall and after ring sendrecv, with every received byte checked.
  • Scatter with NCCL_PXN_DISABLE=1 and on 2 nodes.
  • Scatter 16M–1G, root 0, on 2, 3, 4, 5 and 8 nodes, interleaved with develop and the first version of this PR (one part per peer whenever the parts did not fit); all roots on 3 and 4 nodes (from fix(rccl): fit AINIC scatter parts to the P2P channel pool #13528).
  • Host microtests EnqueueMicrotest.* and TaskPostTuningMicrotest.*.

Test Result

Scatter, 4 nodes, 32 ranks, median time over 10 samples. Measured with the first version of this PR; on 4 nodes the current rule picks the same single part.

Size revert (us) develop (us) this PR (us) revert busbw (GB/s) develop busbw (GB/s) this PR busbw (GB/s) develop / revert this PR / revert
8M 48.9 49.9 48.6 166.2 162.9 167.2 1.02 0.99
16M 69.9 73.3 70.1 232.5 221.7 231.9 1.05 1.00
32M 109.2 132.2 111.0 297.7 245.9 292.8 1.21 1.02
64M 190.4 228.6 190.7 341.4 284.4 340.9 1.20 1.00
128M 346.9 413.8 347.5 374.8 314.2 374.2 1.19 1.00
256M 655.5 768.9 655.3 396.7 338.2 396.8 1.17 1.00
512M 1281.6 1490.8 1278.1 405.8 348.9 406.9 1.16 1.00
1G 2537.6 2951.9 2534.4 409.9 352.4 410.4 1.16 1.00
2G 5048.6 5865.6 5050.6 412.1 354.7 411.9 1.16 1.00
4G 10082.9 11698.2 10084.8 412.7 355.7 412.6 1.16 1.00
8G 20167.1 23342.6 20175.5 412.6 356.5 412.5 1.16 1.00
16G 40433.2 46671.4 40393.4 411.6 356.6 412.0 1.15 1.00

Scatter by node count, root 0, time ratio vs develop over 16M–1G (from #13528, merged here):

Nodes Parts per peer (develop / first version / this PR) first version this PR
2 2 / 2 / 2 0.99–1.01 0.96–1.00
3 4 / 1 / 2 1.04–1.13 0.85–0.87
4 2 / 1 / 1 0.80–0.86 0.79–0.86
5 2 / 1 / 1 0.92–1.11 0.91–1.08
8 2 / 1 / 1 0.97–1.02 0.94–1.00
  • Scatter matches revert from 8 MB to 16 GB. Develop is 15 to 21% slower from 32 MB up.
  • 3 nodes: the first version dropped 4 parts to 1 and left two-thirds of the 64-channel pool idle; this PR keeps the 2 parts that fit. All roots on 3 nodes: median 0.82–0.90 vs develop (first version: 0.99–1.10). All roots on 4 nodes: both at 0.84–0.90.
  • Scatter after alltoall on the same communicator stays at revert level (1 GB: about 2530 us, develop about 2980 us).
  • With root != 0, scatter shows run-to-run bimodality (about 2.5 vs about 3.5 ms at 1 GB), as in the revert build (pre-242e0a93afa, one part per peer); develop, with two parts per peer, does not show it. The median over all roots is 0.82–0.89 of develop, and root 0 is stable at revert level in every run.
  • gather, all_gather, reduce_scatter, all_reduce, broadcast, reduce, sendrecv: no difference from develop beyond run-to-run noise. Outliers above 5% only at latency-bound sizes from 1 KB to 1 MB (a few us at a 20 to 30 us spread), also in collectives that do not go through the changed code.
  • alltoall and alltoallv show the same difference against revert in develop and in this PR (alltoall: develop 1.05, this PR 1.02 to 1.045 at 256 MB to 16 GB). This PR does not add to it.
  • 2 nodes: no regression in develop, this PR is unchanged.
  • NCCL_PXN_DISABLE=1: all builds at about 64 GB/s at 256 MB, no regression.
  • Joint validation on ROCm 10.2 (branch ROCM-31938-validation = develop + fix(rccl): drop the hipHostMallocUncached fence from LL store<> #13451 + this PR + fix(rccl): index topology GPUs by SMI index, not HIP ordinal #13536, 4 nodes, median of 10 from two launches in opposite build order): scatter from 16 MB 0.78–0.86 of develop; against 7.2.3 16 MB 1.13, 32 MB 1.07, from 128 MB 0.95–0.98. The other collectives match develop within noise; on alltoallv two copies of the same develop build differ by up to 6% (A/A run), and validation is 0.98–1.04 of develop over 20 measurements.
  • Correctness: #wrong = 0 and no out-of-bounds values in every run.
  • Host microtests EnqueueMicrotest.* and TaskPostTuningMicrotest.* pass.

Submission Checklist

@therock-pr-bot

therock-pr-bot Bot commented Oct 14, 2026 •

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/enqueue/enqueue.cc, projects/rccl/src/enqueue/task_prep/task_posttuning.cc, projects/rccl/src/include/channel.h, projects/rccl/test/host/enqueue-test.cc, projects/rccl/test/host/fakes/enqueue_fakes.cc (+2 more); 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?

Comment thread projects/rccl/src/enqueue/enqueue.cc Resolved
Comment thread projects/rccl/src/enqueue/enqueue.cc Resolved
Comment thread projects/rccl/src/enqueue/enqueue.cc Resolved

Copilot AI 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.

🟡 Changes recommended

Existing host microtests have incompatible calls and lack the new helper definition, blocking compilation and linking.

3 open findings
What changed in this PR

Restores RCCL Scatter performance on multi-node AINIC configurations affected by the earlier increase to two channels per peer, while preserving other operations’ channel policy.

Changes:

  • Adds a shared rule limiting Scatter to one part when peers exceed available channels.
  • Applies the rule to planning, preconnection, and buffer registration.
  • Documents the performance fix.
File Description
projects/​rccl/​src/​include/​channel.h Declares the shared part-selection helper.
projects/​rccl/​src/​enqueue/​task_prep/​task_posttuning.cc Applies part limits during preconnection and registration.
projects/​rccl/​src/​enqueue/​enqueue.cc Implements the rule and integrates it into planning and connection setup.
projects/​rccl/​CHANGELOG.md Records the Scatter performance fix.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread projects/rccl/src/enqueue/enqueue.cc Resolved
i-kosarev added a commit that referenced this pull request Oct 15, 2026
Drop p2pnChannelsPerPeerScatter and its computation in
ncclTopoComputeP2pChannels: rcclP2pTaskParts derives the limit itself from
comm-wide values, like the rule it replaces. Multi-node AINIC scatter takes
the largest power of two not above p2pnChannels / (nRanks - 1), capped at
p2pnChannelsPerPeer.

Review fixes on #13468:
- keep a user-set nChannelsPerNetPeer (NCCL_NCHANNELS_PER_NET_PEER), as
  ncclTopoComputeP2pChannels does;
- shorten the rcclP2pTaskParts comment, move the P2P-disable comment next to
  its check and fix the gfx110 note about Scatter;
- postTuneP2pRegisterBuffer again uses p2pnChannelsPerPeer: Scatter never
  reaches it (allowUB is false).

The host tests now drive the real rule through g_rcclUseAinicValue, covering
2, 3, 4 and 8 nodes, power-of-two rounding, the one-part floor and the
nChannelsPerNetPeer override. The TaskPrep fake returns p2pnChannelsPerPeer,
and its Scatter test installs the limit.

JIRA ID : ROCM-31938

Co-authored-by: Cursor <cursoragent@cursor.com>
@i-kosarev
i-kosarev marked this pull request as ready for review October 15, 2026 10:31
@i-kosarev
i-kosarev requested review from a team, amd-arozanov and i-kosarev and a balanced review from Copilot October 15, 2026 10:31

Copilot AI 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.

🔵 Needs a closer look

Distributed connection-state changes warrant human review, especially with eight-node hardware validation still outstanding.

0 open findings

3 resolved since last review

🧠 Review effort: Balanced

i-kosarev added a commit that referenced this pull request Oct 15, 2026
…nel) for ROCM-31938 validation

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

# Conflicts:
#	projects/rccl/CHANGELOG.md

@amd-arozanov amd-arozanov 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.

Please update the PR description before merge: it still says rcclP2pTaskParts returns 1, lists postTuneP2pRegisterBuffer among the uses (reverted), and says there is no unit test. The 3-node result from #13528 would also be good to have here.

@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

5 more finding(s), not posted inline
  • projects/rccl/CHANGELOG.md:59 (Low) CHANGELOG Scatter entry says 15 to 30% from 16 MB, but the PR's own data shows 15 to 21% from 32 MB
  • projects/rccl/src/enqueue/enqueue.cc:1455 (Low) addP2pToPlan part skip (part >= taskParts[dir]) has no test; reverting it passes every test
  • projects/rccl/test/host/enqueue-test.cc:371 (Low) P2pTaskParts case table never has the pool fit exceed perPeer, so dropping std::min in rcclP2pTaskParts still passes
  • projects/rccl/test/host/task-posttuning-test.cc:1403 (Nit) Scatter transition test never checks that the second, full-part call requests a preconnect (needPreconnect still true)
  • projects/rccl/src/enqueue/enqueue.cc:3884 (Nit) Wrapped if/for bodies without braces at enqueue.cc:3883, enqueue.cc:1392 and task_posttuning.cc:168, which the RCCL guide requires braced

Bottom line: Looks approval ready.

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

@charest
charest requested a review from wenkaidu October 15, 2026 18:50
// on values both ends of a pair share, or they disagree on the parts.
int rcclP2pTaskParts(struct ncclComm* comm, ncclFunc_t collAPI) {
if (collAPI == ncclFuncScatter && comm->nNodes > 1 && comm->config.nChannelsPerNetPeer == NCCL_CONFIG_UNDEF_INT &&
rcclUseAinic()) {

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.

is this only impact AINIC?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, we enabled this only for AINIC, as the regression was observed exclusively on AINIC nodes. We want to avoid introducing unnecessary changes to other hardware configurations.

@wenkaidu wenkaidu 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.

LGTM. Is it necessary to limit fix to AINIC only?

@i-kosarev i-kosarev 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.

LGTM

@nikitaxgusev
nikitaxgusev force-pushed the users/nikitaxgusev/rocm-31938-ainic-scatter-single-channel branch from 8efe3b0 to 5d26669 Compare October 16, 2026 09:43
@nikitaxgusev

nikitaxgusev commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor Author

CI is failing due to infra issue: Error: File was unable to be removed Error: EBUSY: resource busy or locked, unlink '/shared_inference/rccl-ci/ghr/oci-runner-1/_work/rocm-systems/rocm-systems/.nfs000000000f3c943b00003dbc
I will try to re run it once I can do that.

nikitaxgusev and others added 2 commits October 16, 2026 21:28
…er peer

242e0a9 raised p2pnChannelsPerPeer to 2 on AINIC up to 8 nodes so one
peer can saturate a NIC. Scatter already drives all nRanks-1 peers from one
root and lost 15-30% of bandwidth from 8 MB up (ROCM-31938).

Scatter now uses and connects only the first part per peer when the peers
cannot each own p2pnChannelsPerPeer channels. The decision depends only on
collAPI and comm-wide sizes, so both ends of a pair agree. Other collectives
and point-to-point keep two channels; for them the code path is unchanged.

Limiting only the channel count is not enough: when scatter itself connects
the second part, it stays at or below the two-channel speed in most runs.

Measured on 4 MI355X nodes (32 ranks), AINIC:
- scatter 32M-16G: dev/revert 1.16-1.25, fix/revert 1.00
- scatter after alltoall on the same comm: fix stays at revert level
- all other collectives up to 2G: unchanged, 0 wrong results
On multi-node AINIC, rcclP2pTaskParts gives Scatter the largest power of two
not above p2pnChannels / (nRanks - 1) parts per peer, capped at
p2pnChannelsPerPeer, instead of one part whenever the peers oversubscribe the
pool. Same parts as before on 2, 4, 5 and 8 nodes of 8 GPUs; 2 instead of 1 on
3 nodes, where one part left two-thirds of the pool idle (scatter 0.85-0.87 of
develop instead of 1.04-1.13).

Review fixes: a user-set NCCL_NCHANNELS_PER_NET_PEER is kept, as in
ncclTopoComputeP2pChannels; shorter comments, the P2P-disable comment next to
its check and the gfx110 note fixed; postTuneP2pRegisterBuffer back to
p2pnChannelsPerPeer, since Scatter never reaches it.

Host microtests: rccl-UnitTestsMicroTaskPrep builds again (collAPI argument,
rcclP2pTaskParts fake), with tests for the part rule and for the
partial-to-full preconnect on both sides.

JIRA ID : ROCM-31938

Co-authored-by: Cursor <cursoragent@cursor.com>
@nikitaxgusev
nikitaxgusev force-pushed the users/nikitaxgusev/rocm-31938-ainic-scatter-single-channel branch from 5d26669 to 02f846f Compare October 16, 2026 19:35
@arvindcheru

arvindcheru commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Requesting an override of RCCL PR gate only on 02f846fc7a34: the pending Ruby job is unrelated to this diff

@arvindcheru
arvindcheru merged commit 096bfc1 into develop Oct 16, 2026
69 of 73 checks passed
@arvindcheru
arvindcheru deleted the users/nikitaxgusev/rocm-31938-ainic-scatter-single-channel branch October 16, 2026 23:24
@systems-assistant

Copy link
Copy Markdown
Contributor

TheRock Submodule Bump Activity

Newest first

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.

6 participants