Skip to content

[RCCL] [JIRA ID : ROCM-25479] LL128: generate separate kernels for UBR - #9347

Merged
wenkaidu merged 9 commits into
ROCm:developfrom
wenkaidu:ll128-reg-split
Aug 6, 2026
Merged

wenkaidu merged 9 commits into
ROCm:developfrom
wenkaidu:ll128-reg-split

Conversation

@wenkaidu

Copy link
Copy Markdown
Contributor

Motivation

LL128: generate separate registered / non-registered user-buffer kernels

Technical Details

Registered user buffers must be accessed with system-scope cache-bypassing
loads/stores for cross-GPU coherence, but that path forces the compiler into
heavy AGPR usage and spilling. Non-registered buffers only need the cheaper
plain/non-temporal path. Selecting between them at runtime kept both code paths
(and the AGPR-spilling one) live in every LL128 kernel, hurting occupancy.

Make user-buffer registration a compile-time dimension for LL128 so each launch
runs a single, clean code path:

  • device.h / generate.py: add a UserRegMode kernel dimension (1=registered,
    2=non-registered) and emit two LL128 variants per collective (ring + tree for
    AllReduce, ring for AllGather/Broadcast/Reduce/ReduceScatter). Kernel key
    packing, device/host tables and the total function count are updated
    accordingly.
  • enqueue.cc: pick the registered vs non-registered LL128 function id at launch
    time from work->regUsed / netRegUsed.
  • primitives.h / prims_ll128.h: thread UserRegMode through Primitives; the
    registered specialization keeps the system-scope cache-bypass path, the
    non-registered specialization compiles only the plain/non-temporal path (no
    dead cache-bypass code, no AGPR spill).
  • common.h / add_unroll.sh: forward UserRegMode through RunWorkBatch/RunWorkColl
    and the runRing/runTreeSplit dispatch helpers.

Result on gfx950: the common non-registered LL128 kernels drop from AGPR=13-32
to AGPR=1 and shed ~30 VGPR (e.g. AllReduce ring f32 197->165, AllGather i8
245->181), improving occupancy, while the registered specialization retains the
required system-scope path in isolation.

LL (ProtoLL) is intentionally left at the f2af4e8 baseline: it does not benefit
from the split and the non-temporal FIFO / runtime-split experiments regressed
it, so its user-buffer and FIFO accesses are unchanged.

Issue Tracking

ROCM-25479

Test Plan

Test Result

Submission Checklist

…er kernels

Registered user buffers must be accessed with system-scope cache-bypassing
loads/stores for cross-GPU coherence, but that path forces the compiler into
heavy AGPR usage and spilling. Non-registered buffers only need the cheaper
plain/non-temporal path. Selecting between them at runtime kept both code paths
(and the AGPR-spilling one) live in every LL128 kernel, hurting occupancy.

Make user-buffer registration a compile-time dimension for LL128 so each launch
runs a single, clean code path:

- device.h / generate.py: add a UserRegMode kernel dimension (1=registered,
  2=non-registered) and emit two LL128 variants per collective (ring + tree for
  AllReduce, ring for AllGather/Broadcast/Reduce/ReduceScatter). Kernel key
  packing, device/host tables and the total function count are updated
  accordingly.
- enqueue.cc: pick the registered vs non-registered LL128 function id at launch
  time from work->regUsed / netRegUsed.
- primitives.h / prims_ll128.h: thread UserRegMode through Primitives; the
  registered specialization keeps the system-scope cache-bypass path, the
  non-registered specialization compiles only the plain/non-temporal path (no
  dead cache-bypass code, no AGPR spill).
- common.h / add_unroll.sh: forward UserRegMode through RunWorkBatch/RunWorkColl
  and the runRing/runTreeSplit dispatch helpers. The add_unroll.sh regexes use
  `template\s*<` so they tolerate clang-format's `template <` spacing.
- test: add a unit test verifying the LL128 registered/non-registered kernel split.

LL (ProtoLL) is intentionally left at the baseline: it does not benefit from the
split, so its user-buffer and FIFO accesses are unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
@wenkaidu
wenkaidu requested a review from a team as a code owner July 27, 2026 23:15
@therock-pr-bot

therock-pr-bot Bot commented Jul 27, 2026 •

Copy link
Copy Markdown

❌ PR Check — Action Required

Check Status Details
📝 PR Description ❌ Fail Error: PR description must reference a JIRA ID, ISSUE ID, or a GitHub closing keyword.
Expected: include a JIRA ID / ISSUE ID line (separator : or -, or omitted; value may be a JIRA key, a number with/without #, or a link), OR a closing keyword + issue reference. Accepted examples:
• JIRA ID : TESTAUTO-6039
• JIRA ID - #330
• JIRA ID #330
• JIRA ID (on separate line)
ROCM-25757
• ISSUE ID : TESTUTO-3334
• ISSUE ID #3334
• ISSUE ID - TESTAUTO-3433
• ISSUE ID (on separate line)
AIRUNTIME-2352
• ISSUE ID : https://github.com/<org_name>/<repo_name>/issues/1234
• Closes #10
• Fixes octo-org/octo-repo#100
• Resolves: #123
• #123
• https://github.com/<org_name>/<repo_name>/issues/123
Current: no valid JIRA/ISSUE/closing-keyword reference found
⛔ Forbidden Files ✅ Pass —
🧪 Unit Test ✅ Pass —
🔎 pre-commit ⏳ Pending ⏳ Still running…
🚫 Draft PR 🔜 To Be Enabled —
🚩 Feature Flag 🔜 To Be Enabled —
📊 Code Coverage 🔜 To Be Enabled —

⚠️ 1 policy check(s) failed. Please address the issues above before this PR can be Reviewed.

🚫 Please fix the failed policies

  • ❌ PR Description

The Not ready to Review label was added to this PR. Once all policies pass, the label is removed automatically.

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

🙋 Wish to Override Policy?

@therock-pr-bot

therock-pr-bot Bot commented Jul 27, 2026 •

Copy link
Copy Markdown

🚫 Please fix the failed policies before requesting reviews.

The following policy checks failed:

  • ❌ PR Description

The Not ready to Review label has been added to this PR.
Once all policies pass, the label will be removed automatically.

Comment thread projects/rccl/src/device/generate.py Resolved
Comment thread projects/rccl/src/enqueue.cc Resolved
wenkaidu and others added 5 commits August 1, 2026 08:57
The ll128-reg-split change made device-function symbol names conditional:
fn_sym() appends the user-buffer registration field (_1/_2) only for the
LL128 reg-variant collectives and omits it entirely when reg=="0". The
forward declarations, function-pointer table, and DEFINE_ncclDevFunc were
all updated to use fn_sym(), but the pure-RDC binary-search dispatcher
(emitted under !USE_INDIRECT_FUNCTION_CALL && !RCCL_DEVICE_LINKER) still
built the name from paste("_", "ncclDevFunc", *fn), which unpacks all
fields including reg. For every non-reg-variant kernel it emitted calls to
ncclDevFunc_..._0() -- symbols that are never declared or defined -- so the
-fgpu-rdc (--no-device-linker) link failed. The default device-linker build
was unaffected because it dispatches through the function-pointer table.

Make the pure-RDC dispatcher use fn_sym() like the other three sites so the
called symbols match the declared ones. Add a regression test asserting that
every symbol called by a pure-RDC Caller leaf is forward-declared (exercised
with a reg-variant collective so the reg-suffix logic is covered).

Co-authored-by: Cursor <cursoragent@cursor.com>
clang-format the over-length reg-variant device-func selection call to
wrap at the column limit.

Co-authored-by: Cursor <cursoragent@cursor.com>
Align the LL128 reg-variant test filename with the Tests.cpp naming
convention used by the rest of the suite. Update the CMakeLists reference.

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

# Conflicts:
#	projects/rccl/src/device/generate.py
#	projects/rccl/src/include/device.h
Comment thread projects/rccl/src/enqueue.cc Resolved
Comment thread projects/rccl/src/device/prims_ll128.h Resolved
@pvallem

pvallem commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

please post coherence soak results: force NCCL_PROTO=LL128, small sizes, high rank counts, many iterations, exact-integer datatypes, on gfx942 + gfx950 if possible

Comment thread projects/rccl/src/enqueue.cc Resolved
Comment thread projects/rccl/src/device/prims_ll128.h Resolved
Comment thread projects/rccl/src/include/device.h Resolved
Resolve pvallem's review feedback on the LL128 registered/non-registered
kernel split (PR ROCm#9347):

- enqueue.cc / device.h: factor the registration->UserRegMode mapping into
  a shared ncclDevFuncLL128RegMode() helper and add an Enqueue/LL128 test that
  pins it -- a registered (or net-registered) buffer must resolve to the reg=1
  kernel and a non-registered one to reg=2, so inverting the 1:2 mapping now
  fails instead of passing on the "ids are distinct" checks alone.
- enqueue.cc: on id<0 in ncclTasksRegAndEnqueue, WARN and return
  ncclInvalidUsage (mirroring the ncclPrepareTasks path) instead of silently
  keeping the reg=2 placeholder and losing cache-bypass for a registered buffer.
- prims_ll128.h: emit store128Plain as an explicit 128-bit vector store
  (global_store_dwordx4) instead of two scalar u64 stores, so the
  data-visible-no-later-than-flag ordering is enforced rather than left to
  backend coalescing; expand the comment to note it is correctness-load-bearing.
- prims_ll128.h: add braces to userBypass()/loadUser128()/storeUser128().
- LL128RegVariantKernelTests.cpp: assert ReduceScatter has neither a reg=1 nor
  a reg=2 LL128 kernel, confirming the split is scoped.
- test_generate_device_table.py: match the reg field specifically (reg=1 and
  reg=2 pair) so the check can't pass on a reg=0 kernel's unroll suffix.
- device.h: wrap the ncclDevFuncId() signature under the 120-column limit.

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

wenkaidu commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

please post coherence soak results: force NCCL_PROTO=LL128, small sizes, high rank counts, many iterations, exact-integer datatypes, on gfx942 + gfx950 if possible

gfx942 results. gfx950 pending.

LL128 coherence soak @ 2026-08-06T02:25:34Z
# jobid=212249 nodes=16 np=128 ppn=8 nodelist=useocpm2m-097-014:8,useocpm2m-097-027:8,useocpm2m-097-043:8,useocpm2m-097-045:8,useocpm2m-097-047:8,useocpm2m-097-048:8,useocpm2m-097-049:8,useocpm2m-097-070:8,useocpm2m-097-083:8,useocpm2m-097-084:8,useocpm2m-097-114:8,useocpm2m-097-123:8,useocpm2m-097-124:8,useocpm2m-097-137:8,useocpm2m-097-139:8,useocpm2m-097-144:8
# rccl_lib   : /home/wenkaidu/rccl_libs/librccl.so.1.0.pr9347
# collective : all_reduce_perf  op=sum  NCCL_PROTO=LL128
# oob        : NCCL_SOCKET_IFNAME=eth0 UCX_NET_DEVICES=eth0 UCX_TLS=tcp,sm,self
# sweep      : -b 8 -e 64K -f 2 -w 5 -n 30 -c 5 -N 1
# dtypes     : int8 uint8 int32 uint32 int64 uint64
# reg modes  : 0 1  (0=non-registered, 1=registered user buffers)
# cycles     : 4
#
# status legend: OK=clean exit, 0 wrong | OK*=validated 0 wrong but teardown hang (killed) |
#                WRONG=coherence mismatch (>0 wrong) | HANG=no sweep completed
#
# cycle  reg  dtype      exit sweeps   #wrong    ok?
  01/4   0    int8          0      1        0     OK
  01/4   0    uint8         0      1        0     OK
  01/4   0    int32         0      1        0     OK
  01/4   0    uint32        0      1        0     OK
  01/4   0    int64         0      1        0     OK
  01/4   0    uint64        0      1        0     OK
  01/4   1    int8          0      1        0     OK
  01/4   1    uint8         0      1        0     OK
  01/4   1    int32         0      1        0     OK
  01/4   1    uint32        0      1        0     OK
  01/4   1    int64         0      1        0     OK
  01/4   1    uint64        0      1        0     OK
  02/4   0    int8          0      1        0     OK
  02/4   0    uint8         0      1        0     OK
  02/4   0    int32         0      1        0     OK
  02/4   0    uint32        0      1        0     OK
  02/4   0    int64         0      1        0     OK
  02/4   0    uint64        0      1        0     OK
  02/4   1    int8          0      1        0     OK
  02/4   1    uint8         0      1        0     OK
  02/4   1    int32         0      1        0     OK
  02/4   1    uint32        0      1        0     OK
  02/4   1    int64         0      1        0     OK
  02/4   1    uint64        0      1        0     OK
  03/4   0    int8          0      1        0     OK
  03/4   0    uint8         0      1        0     OK
  03/4   0    int32         0      1        0     OK
  03/4   0    uint32        0      1        0     OK
  03/4   0    int64         0      1        0     OK
  03/4   0    uint64        0      1        0     OK
  03/4   1    int8          0      1        0     OK
  03/4   1    uint8         0      1        0     OK
  03/4   1    int32         0      1        0     OK
  03/4   1    uint32        0      1        0     OK
  03/4   1    int64         0      1        0     OK
  03/4   1    uint64        0      1        0     OK
  04/4   0    int8          0      1        0     OK
  04/4   0    uint8         0      1        0     OK
  04/4   0    int32         0      1        0     OK
  04/4   0    uint32        0      1        0     OK
  04/4   0    int64         0      1        0     OK
  04/4   0    uint64        0      1        0     OK
  04/4   1    int8          0      1        0     OK
  04/4   1    uint8         0      1        0     OK
  04/4   1    int32         0      1        0     OK
  04/4   1    uint32        0      1        0     OK
  04/4   1    int64         0      1        0     OK
  04/4   1    uint64        0      1        0     OK
#
# runs=48  validated_sweeps=48  total_wrong=0
# coherence: PASS (0 wrong across all runs)
# done -> /home/wenkaidu/rccl_ll128_soak_pr9347_16n_212249

@wenkaidu

wenkaidu commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

please post coherence soak results: force NCCL_PROTO=LL128, small sizes, high rank counts, many iterations, exact-integer datatypes, on gfx942 + gfx950 if possible

gfx942 results. gfx950 pending.

LL128 coherence soak @ 2026-08-06T02:25:34Z
# jobid=212249 nodes=16 np=128 ppn=8 nodelist=useocpm2m-097-014:8,useocpm2m-097-027:8,useocpm2m-097-043:8,useocpm2m-097-045:8,useocpm2m-097-047:8,useocpm2m-097-048:8,useocpm2m-097-049:8,useocpm2m-097-070:8,useocpm2m-097-083:8,useocpm2m-097-084:8,useocpm2m-097-114:8,useocpm2m-097-123:8,useocpm2m-097-124:8,useocpm2m-097-137:8,useocpm2m-097-139:8,useocpm2m-097-144:8
# rccl_lib   : /home/wenkaidu/rccl_libs/librccl.so.1.0.pr9347
# collective : all_reduce_perf  op=sum  NCCL_PROTO=LL128
# oob        : NCCL_SOCKET_IFNAME=eth0 UCX_NET_DEVICES=eth0 UCX_TLS=tcp,sm,self
# sweep      : -b 8 -e 64K -f 2 -w 5 -n 30 -c 5 -N 1
# dtypes     : int8 uint8 int32 uint32 int64 uint64
# reg modes  : 0 1  (0=non-registered, 1=registered user buffers)
# cycles     : 4
#
# status legend: OK=clean exit, 0 wrong | OK*=validated 0 wrong but teardown hang (killed) |
#                WRONG=coherence mismatch (>0 wrong) | HANG=no sweep completed
#
# cycle  reg  dtype      exit sweeps   #wrong    ok?
  01/4   0    int8          0      1        0     OK
  01/4   0    uint8         0      1        0     OK
  01/4   0    int32         0      1        0     OK
  01/4   0    uint32        0      1        0     OK
  01/4   0    int64         0      1        0     OK
  01/4   0    uint64        0      1        0     OK
  01/4   1    int8          0      1        0     OK
  01/4   1    uint8         0      1        0     OK
  01/4   1    int32         0      1        0     OK
  01/4   1    uint32        0      1        0     OK
  01/4   1    int64         0      1        0     OK
  01/4   1    uint64        0      1        0     OK
  02/4   0    int8          0      1        0     OK
  02/4   0    uint8         0      1        0     OK
  02/4   0    int32         0      1        0     OK
  02/4   0    uint32        0      1        0     OK
  02/4   0    int64         0      1        0     OK
  02/4   0    uint64        0      1        0     OK
  02/4   1    int8          0      1        0     OK
  02/4   1    uint8         0      1        0     OK
  02/4   1    int32         0      1        0     OK
  02/4   1    uint32        0      1        0     OK
  02/4   1    int64         0      1        0     OK
  02/4   1    uint64        0      1        0     OK
  03/4   0    int8          0      1        0     OK
  03/4   0    uint8         0      1        0     OK
  03/4   0    int32         0      1        0     OK
  03/4   0    uint32        0      1        0     OK
  03/4   0    int64         0      1        0     OK
  03/4   0    uint64        0      1        0     OK
  03/4   1    int8          0      1        0     OK
  03/4   1    uint8         0      1        0     OK
  03/4   1    int32         0      1        0     OK
  03/4   1    uint32        0      1        0     OK
  03/4   1    int64         0      1        0     OK
  03/4   1    uint64        0      1        0     OK
  04/4   0    int8          0      1        0     OK
  04/4   0    uint8         0      1        0     OK
  04/4   0    int32         0      1        0     OK
  04/4   0    uint32        0      1        0     OK
  04/4   0    int64         0      1        0     OK
  04/4   0    uint64        0      1        0     OK
  04/4   1    int8          0      1        0     OK
  04/4   1    uint8         0      1        0     OK
  04/4   1    int32         0      1        0     OK
  04/4   1    uint32        0      1        0     OK
  04/4   1    int64         0      1        0     OK
  04/4   1    uint64        0      1        0     OK
#
# runs=48  validated_sweeps=48  total_wrong=0
# coherence: PASS (0 wrong across all runs)
# done -> /home/wenkaidu/rccl_ll128_soak_pr9347_16n_212249

Coherence soak — forced NCCL_PROTO=LL128 (PR #9347, ll128-reg-split @ cfd6bb2)

Setup

  • Build: RCCL from this PR head (cfd6bb2) built for gfx950 (ONLY_FUNCS="AllReduce * * * *", so all LL128 reg/non-reg AllReduce variants compiled). rccl-tests all_reduce_perf (MPICH), launched with srun --mpi=pmi2 --cpu-bind=none.
  • Cluster: ruby (cv350-rck-*, Broadcom bnxt_re RoCE fabric). All ruby nodes are gfx950 — there are no gfx942 nodes on this cluster, so the gfx942 leg could not be run here (needs a separate MI300 host).
  • Protocol forced: NCCL_PROTO=LL128 on every rank. Confirmed selected, e.g. AllReduce: 8 Bytes -> Algo TREE proto LL128.
  • Cross-node transport confirmed over RoCE: NET/IB : Using [0]bnxt_re0:1/RoCE [1]bnxt_re1:1/RoCE [2]bnxt_re2:1/RoCE [3]bnxt_re3:1/RoCE [4]bnxt_re4:1/RoCE [5]bnxt_re5:1/RoCE [6]bnxt_re6:1/RoCE [7]bnxt_re7:1/RoCE...
  • Coherence check: exact-integer datatypes (int8/uint8/int32/uint32/int64/uint64) make the reduction bit-exact, so rccl-tests -c 1 validation turns any cross-GPU coherence slip into #wrong > 0 / Out of bounds. Both buffer-registration modes swept so both new kernels are hit: -R 1→ reg=1 (registered, system-scope cache-bypass), -R 0→ reg=2 (non-registered).
  • Sizes: small, -b 8 -e 256K -f 2. Iters: -w 20 -n 100 per size per cycle.

Results — 16-node (16×8 = 128 ranks): PASS

  • 240 runs (6 dtypes × 2 reg-modes × 20 cycles), total rccl-tests #wrong = 0.
# ==== per (dtype,reg) clean-cycle tallies ====
# int8    R0  : 20/20 clean
# int8    R1  : 20/20 clean
# uint8   R0  : 20/20 clean
# uint8   R1  : 20/20 clean
# int32   R0  : 20/20 clean
# int32   R1  : 20/20 clean
# uint32  R0  : 20/20 clean
# uint32  R1  : 20/20 clean
# int64   R0  : 20/20 clean
# int64   R1  : 20/20 clean
# uint64  R0  : 20/20 clean
# uint64  R1  : 20/20 clean
# overall: PASS

Results — 2-node (16 ranks) cross-node IB pre-check: PASS

  • 72 runs (6 dtypes × 2 reg-modes × 6 cycles), total #wrong = 0.

Verdict: No coherence regressions — 0 #wrong across all sizes, datatypes, register modes and cycles at both 16 and 128 ranks with LL128 forced on gfx950.

Logs: /home/wenkaidu/logs/ll128soak/run_24390, /home/wenkaidu/logs/ll128soak/run_24392 on ruby.

@wenkaidu
wenkaidu merged commit d9773e4 into ROCm:develop Aug 6, 2026
54 of 61 checks passed
@wenkaidu
wenkaidu deleted the ll128-reg-split branch August 6, 2026 13:39
alexander-sannikov added a commit that referenced this pull request Oct 30, 2026
Backported PRs:
- #10670 fix(rccl): LL protocol explicit 128-bit vector loads/stores
- #10339 fix(rccl): default cheap post-send fence off for gfx950
- #9336 LL: cache-bypass user buffers, plain/non-temporal comm FIFO

Partially backported PRs:
- #9347 [RCCL] [JIRA ID : ROCM-25479] LL128: generate separate kernels for UBR

Aligned with develop:
- RCCL_GFX9_CHEAP_FENCE_OFF default

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