Skip to content

docs: verify NCCL selection for switchless GLM TP4 - #2993

Open
vdalesandro wants to merge 3 commits into
ashhart:mainfrom
vdalesandro:docs/glm-switchless-nccl-binding
Open

vdalesandro wants to merge 3 commits into
ashhart:mainfrom
vdalesandro:docs/glm-switchless-nccl-binding

Conversation

@vdalesandro

@vdalesandro vdalesandro commented Nov 12, 2026 •

Copy link
Copy Markdown

Scope

Docs-only follow-up to #1971: the NCCL routing support needed for switchless RoCE links on separate IPv4 subnets. No engine/runtime change and no claim that ring or split prefill now works.

Public version references

The first public upstream release with these two settings is NVIDIA NCCL v2.30.7-1:

  • Introducing commit: “Add subnet-aware routing for DGX Spark multi-node ring connection.”
  • Definitions in the release: routing defaults to 0, IPv4 prefix length defaults to 24. The earlier public 2.30.3/2.30.4 tags lack them.
  • Official NVIDIA ring playbook: builds this tag and enables subnet-aware routing for three Sparks. That is a public availability reference, not a four-rank qualification claim.

The tested private binary reports 2.30.7 and demonstrates the routing below; its upstream commit/build provenance is not established, so I am not calling that binary an unmodified NVIDIA release.

Review changes

Reduced the note to eight lines. Removed the engine-loader gap and redundant closing disclaimer. Selection is now checked with every rank’s startup version and NCCL INFO routing records, not process maps/file hashes. The separate loader correction will be linked here once published; until that fix lands, the recipe retains a process-local library-path override.

Archived run evidence (anonymized)

TensorFold native 1.2.6, GLM EXL3, TP=4, four switchless-ring GB10 hosts, TF_GLM_PREFILL_REDUCE=gather in both attempts. The library change plus the two routing settings fixed startup/peer-warm-up: the 2.28.9 attempt timed out in ibv_modify_qp entering RTR before readiness; the 2.30.7 attempt initialized all four ranks and completed the existing five short functional checks. This does not qualify ring/split prefill or establish a performance improvement. No new run was started for this revision.

Hostnames, IP addresses, process/thread IDs and communicator ID below are replaced with placeholders; the startup/routing text is otherwise preserved. Historical failure excerpts are the retained tail, not a reconstructed version log.

Failure, NCCL 2.28.9 (one retained line per rank):

# rank 0
host-0:<pid>:<tid> [0] NCCL INFO Call to ibv_modify_qp failed with 110 Connection timed out, on dev rocep1s0f0:1, curr state INIT, next state RTR, local GID index 3, local GID ::ffff:<address>, remote GID ::ffff:<address>, retrying 1/35 after 100 msec of sleep
# rank 1
host-1:<pid>:<tid> [0] NCCL INFO Call to ibv_modify_qp failed with 110 Connection timed out, on dev rocep1s0f1:1, curr state INIT, next state RTR, local GID index 3, local GID ::ffff:<address>, remote GID ::ffff:<address>, retrying 1/35 after 100 msec of sleep
# rank 2
host-2:<pid>:<tid> [0] NCCL INFO Call to ibv_modify_qp failed with 110 Connection timed out, on dev rocep1s0f0:1, curr state INIT, next state RTR, local GID index 3, local GID ::ffff:<address>, remote GID ::ffff:<address>, retrying 1/35 after 100 msec of sleep
# rank 3
host-3:<pid>:<tid> [0] NCCL INFO Call to ibv_modify_qp failed with 110 Connection timed out, on dev rocep1s0f1:1, curr state INIT, next state RTR, local GID index 4, local GID ::ffff:<address>, remote GID ::ffff:<address>, retrying 1/35 after 100 msec of sleep

Success: each rank's startup version, NIC list and observed routing overrides (NCCL_DEBUG=INFO):

# rank 0
info: rank 0 startup: entering NCCL init (4 ranks, id <comm-id>, NCCL 2.30.7, CUDA device 0 000F:01:00.0)
host-0:<pid>:<tid> [0] NCCL INFO NET/IB : Using [0]rocep1s0f0:1/RoCE [1]rocep1s0f1:1/RoCE [2]roceP2p1s0f0:1/RoCE [3]roceP2p1s0f1:1/RoCE [RO]; OOB tailscale0:<address><0>
host-0:<pid>:<tid> [0] NCCL INFO NET/IB: Subnet-aware routing: overriding dev 0 with dev 1
host-0:<pid>:<tid> [0] NCCL INFO NET/IB: Subnet-aware routing: overriding dev 2 with dev 1
# rank 1
info: rank 1 startup: entering NCCL init (4 ranks, id <comm-id>, NCCL 2.30.7, CUDA device 0 000F:01:00.0)
host-1:<pid>:<tid> [0] NCCL INFO NET/IB : Using [0]rocep1s0f0:1/RoCE [1]rocep1s0f1:1/RoCE [2]roceP2p1s0f0:1/RoCE [3]roceP2p1s0f1:1/RoCE [RO]; OOB tailscale0:<address><0>
host-1:<pid>:<tid> [0] NCCL INFO NET/IB: Subnet-aware routing: overriding dev 1 with dev 0
host-1:<pid>:<tid> [0] NCCL INFO NET/IB: Subnet-aware routing: overriding dev 3 with dev 0
# rank 2
info: rank 2 startup: entering NCCL init (4 ranks, id <comm-id>, NCCL 2.30.7, CUDA device 0 000F:01:00.0)
host-2:<pid>:<tid> [0] NCCL INFO NET/IB : Using [0]rocep1s0f0:1/RoCE [1]rocep1s0f1:1/RoCE [2]roceP2p1s0f0:1/RoCE [3]roceP2p1s0f1:1/RoCE [RO]; OOB tailscale0:<address><0>
host-2:<pid>:<tid> [0] NCCL INFO NET/IB: Subnet-aware routing: overriding dev 0 with dev 1
host-2:<pid>:<tid> [0] NCCL INFO NET/IB: Subnet-aware routing: overriding dev 2 with dev 1
# rank 3
info: rank 3 startup: entering NCCL init (4 ranks, id <comm-id>, NCCL 2.30.7, CUDA device 0 000F:01:00.0)
host-3:<pid>:<tid> [0] NCCL INFO NET/IB : Using [0]rocep1s0f0:1/RoCE [1]rocep1s0f1:1/RoCE [2]roceP2p1s0f0:1/RoCE [3]roceP2p1s0f1:1/RoCE [RO]; OOB tailscale0:<address><0>
host-3:<pid>:<tid> [0] NCCL INFO NET/IB: Subnet-aware routing: overriding dev 1 with dev 0
host-3:<pid>:<tid> [0] NCCL INFO NET/IB: Subnet-aware routing: overriding dev 3 with dev 0

One completed capital-city request, fields extracted from its captured result (not a new or synthetic response):

{
  "http_status": 200,
  "content": "Paris",
  "finish_reason": "stop",
  "done_seen": true,
  "usage": {
    "completion_tokens": 2,
    "completion_tokens_details": {
      "reasoning_tokens": 0
    },
    "prompt_tokens": 21,
    "prompt_tokens_details": {
      "cached_tokens": 0
    },
    "total_tokens": 23
  }
}

Validation

Eight-line note, public links/source definitions checked; archived four-rank logs and completed-response fields rechecked against their original receipts; privacy screening and git diff --check pass. No new GPU run, model request, deployment or baseline repetition.

@ashhart

ashhart commented Nov 12, 2026

Copy link
Copy Markdown
Owner

Thanks @vdalesandro, and welcome. A note on which NCCL library the switchless ring needs is useful, you were careful about what it doesn't claim, and you found a real gap: GLM ignores TF_NCCL_LIB. Some changes before it goes in:

  1. A public reference for the two settings. Please link the NCCL documentation or release notes for NCCL_IB_SUBNET_AWARE_ROUTING and NCCL_IB_SUBNET_PREFIX_LEN, and give the first release that has them. Then the recipe can say "NCCL X.Y.Z or later" in place of "2.28.9 lacks it" and "the inspected 2.30.4 source lacked it" (docs/recipes/glm-5.3-flash-cuda.md:187-189).
  2. What it changes on the ring, and the run behind it. The section already says TP=4 starts and serves there with TF_GLM_PREFILL_REDUCE=gather, and that ring and split don't start. What failed with 2.28.9 that worked with 2.30.7 and these settings? Which prefill mode was it, and did a request complete? Please put that run in the description, with host names and addresses removed: each rank's start-up line, the NCCL_DEBUG=INFO routing lines and one finished request. Then give the result in one sentence in the recipe.
  3. TF_NCCL_LIB. You're right that GLM doesn't read it. GLM's start-up calls cuda.nccl.Library.open() (zig/src/families/glm/cuda_native.zig:264), which opens libnccl.so.2 by name (zig/src/cuda/nccl.zig:80), and only the shared tensor-parallel session reads TF_NCCL_LIB (zig/src/fabric/tp/config.zig:119, zig/src/fabric/tp/tp.zig:56). That's a gap in the engine, and we'd rather fix it than document it. Please drop that sentence and the source link (:203-205). Making GLM honour TF_NCCL_LIB belongs in its own small PR, which you're welcome to send. Otherwise we'll pick it up. After that the recipe can point at TF_NCCL_LIB rather than LD_LIBRARY_PATH.
  4. A simpler check. Each rank already logs the NCCL version it loaded at start-up: rank N startup: entering NCCL init (..., NCCL 2.30.7, ...) (zig/src/cuda/startup_log.zig:75), and 1.2.6 has it too. Please check that line on every rank, plus NCCL_DEBUG=INFO for the NIC, in place of reading /proc/PID/maps and hashing the file (:207-211).
  5. Shorter. The recipe's other notes run a few lines each. With 1-4 done, this one could be about eight: the NCCL version needed, how to point a rank at it, the two settings and the prefix rule, and the check. The closing disclaimer can go, because the paragraph above it already says ring and split don't start.

CI on this head is waiting for our approval, and we'll start it.

  • Synthena (Ash's Agent)

@vdalesandro

Copy link
Copy Markdown
Author

Updated in 3d0e9f3.

  • The recipe note is now eight lines, with public NVIDIA references for both routing variables. The earliest official public tag I verified is v2.30.7-1; I do not infer an earlier release or private-backport history.
  • The PR description now includes anonymized startup/version and routing excerpts for all four ranks, plus a completed request. This was gather, not ring/split. The private library reports 2.30.7; I do not claim it is an unmodified upstream build.
  • Removed the TF_NCCL_LIB workaround and its source link from the recipe. Version verification now uses each rank's startup log and NCCL_DEBUG=INFO, not mapped-library paths or file hashes. Removed the closing disclaimer.

The separate shared-loader fix is implemented and independently reviewed: Library.open() resolves TF_NCCL_LIB through the existing path validator, so GLM and sibling callers honor it. Unset/blank retains the default; explicit paths preserve their bytes; invalid paths fail before loading. CPU checks passed: 8/8 loader tests, 328/328 runtime tests and 23/23 environment tests. No GPU qualification is claimed.

I stopped before opening that second PR because ATC's merge comparison found conflicts in the shared environment registry/test table with #1441, #1442, #1443, #1444, #1445, #1446, #1522, #2008, #2415, #2551, #2648, #2762, #2919, #2935, #2937 and #2938. The complete discovery snapshot enumerated 293 open PRs and compared all 53 same-file heads. The fix itself is only three existing files, 32 added lines and two removed.

Which landing order or coordination target would you prefer for the environment-table entries? I have not opened a competing PR or changed the running machines.

@ashhart

ashhart commented Nov 12, 2026

Copy link
Copy Markdown
Owner

Thanks @vdalesandro. 3d0e9f37e covers all five points. The note is down to eight lines and gives "NCCL 2.30.7 or later" with public links. The check now uses each rank's start-up line and NCCL_DEBUG=INFO. The TF_NCCL_LIB workaround is gone. The description has the anonymized start-up, routing and request excerpts, and it says plainly that the run was gather prefill on a build reporting 2.30.7.

Two small edits. We'll make them when we land unless you'd rather:

  • The other lines in this recipe wrap at 120 columns. Three of the new lines run to 130-187 characters, so we'll rewrap them.
  • "Point each rank at a private libnccl.so.2" would read better as "a newer libnccl.so.2".

CI on this head is waiting for our approval again, and we'll start it. It's docs only, so no device run is needed.

On the TF_NCCL_LIB fix: please open it against main now, and don't wait on the PRs that touch the same table. A conflict in a shared table is routine. Whichever PR lands second keeps both entries, and we sort it out at landing if needed. nccl.Library.open() (zig/src/cuda/nccl.zig:80) is called by GLM (zig/src/families/glm/cuda_native.zig:264), Flash Next's --tp 2 collective (zig/src/families/flashnext/cuda_collective.zig:25), MiniMax M3 (zig/src/families/minimax_m3/cuda_native.zig:159) and zig/src/cuda/tp_group.zig:35. Please name those callers in the description. Before it lands, we'll start GLM and Flash Next at --tp 2 on two GB10s with TF_NCCL_LIB unset and set. Once it's in, this recipe can point at TF_NCCL_LIB in place of LD_LIBRARY_PATH.

  • Synthena (Ash's Agent)

@vdalesandro

Copy link
Copy Markdown
Author

Opened the loader fix against main as #3106 (three files, +32/−2; callers of nccl.Library.open() listed in the description). Leaving the two recipe line-wrap edits to you, as offered.

@ashhart

ashhart commented Nov 14, 2026

Copy link
Copy Markdown
Owner

Thanks @vdalesandro. The public version references, four-rank gather excerpts, simpler startup check and shorter recipe answer all five points. Opening #3106 also closes the loader follow-up.

This goes to the landing queue. We'll make the two editorial changes you left with us: wrap the long lines and change “private” to “newer”. No device run is needed for this docs change; #3106 keeps its separate checks.

  • Synthena (Ash's Agent)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants