Skip to content

fabric/tp: multirank RoCE over a switchless ring, each peer's stripes on the HCAs that reach it - #1516

Merged
ashhart merged 7 commits into
ashhart:mainfrom
Bizuayeu:pr/roce-peer-devices
Oct 29, 2026
Merged

ashhart merged 7 commits into
ashhart:mainfrom
Bizuayeu:pr/roce-peer-devices

Conversation

@Bizuayeu

@Bizuayeu Bizuayeu commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

What this changes

On a switchless ring of three, where each cable is its own pair of /24s, roce_multi refused at connect (BadRecord, #1288): it paired HCA h with every peer's HCA h, as on a switch. Each peer's stripes now go over the HCAs that share its /24 (roce_multi_pairs.zig): a switch keeps HCA h to every peer's HCA h over two stripes, as before, and a ring gives each peer its link's HCA over one. With three ranks or more, roce_pick picks ports toward each peer alone. Every peer gets the same number of stripes, since the kernel counts that many flags from each source, so unequal stripes, a peer out of reach and an HCA that reaches nobody refuse before any queue pair moves. Named HCAs (TF_TP_ROCE_HCA) are paired the same way. The record, the layout, the kernel and the two-rank wire are unchanged; the kernel's n_hca is now the stripe count, which on a switch is the HCA count as before.

It also adds the three tests #1288 asked of the multirank wire, each with a case that fails without its property:

  • a rank that stops posting ends its peers' next gathers at the timeout, not in a hang (roce_multi_wait_test.zig: three ranks on host threads over the shm wire, with a watchdog that turns a hang into error.Aborted);
  • a failed wait's report names this rank, the seq and the rank it waited on (check's report moves, unchanged, into describeFailure, which the test reads after writing the kernel's record);
  • one rank's probe mismatch drops the wire on every rank, which falls back to NCCL (the session's vote moves, unchanged, into keepIfAll, which three host ranks run).

The two rules #1495's review set for this move hold here. A rank that refuses still joins the agreement: a connect that refuses is a false vote in Roce.init's agreement, as a failed pick is in the session's, not an early return. And nothing is paired by index across peers: every peer's stripes come from the /24s alone, settled for all of them before any queue pair moves, so a peer with no shared /24 refuses (the pick then opens the first usable ports only so that every rank refuses at connect).

Part of #1288, route (b), and the roce_multi side of #1495's review. GLM's tp_group borrowing this wire follows in its own pull request, opt-in for worlds above two.

Receipt

  • Environment: TensorFold 82fa993a6 on main 13255c97d (the head, 6256add49, only keeps two host tests to Linux); three GB10 cabled in a ring of three QSFP cables with no switch, each port seen as two RoCE v2 interfaces on that link's two /24s; tf-tp-test built ReleaseSafe for sm_121.
  • Exactness: tf-tp-test suite at world 3 over the ring, roce with TF_TP_ROCE_FALLBACK=error. Main: every rank refuses at connect (BadRecord, then RoceUnavailable), no check runs. This branch: every rank connects, each peer over its link's HCA (rank 0 to rank 1 on its HCA 0, to rank 2 on its HCA 1), and the suite passes 402, 402 and 328 checks on ranks 0, 1 and 2, every one exact, 0 failed; nccl on the same binary passes 201, 201 and 164, 0 failed. An 8 KiB exchange (decode_window, 86 in a row) takes 10.74 µs a time on the wire against 26.32 µs on NCCL in the same run.
  • Decode speed before and after: n/a. No family takes the multirank wire by default and none of their paths change; GLM's opt-in borrowing comes with its decode receipts in the next pull request (GLM-5.3-Flash at TP=3: its small all-gathers stay on NCCL, and fabric/tp's three-to-eight-rank RoCE (#1209) refuses a switchless ring with BadRecord. Which route should GLM take, and would per-peer devices be in scope? #1288's body has TP=3's: +1.5 to 2.2% decode).
  • Prompt speed before and after: n/a, for the same reason. GLM's next pull request carries the cold-prompt receipt GLM-5.3-Flash at TP=3: its small all-gathers stay on NCCL, and fabric/tp's three-to-eight-rank RoCE (#1209) refuses a switchless ring with BadRecord. Which route should GLM take, and would per-peer devices be in scope? #1288 asks for before worlds above two leave opt-in.
  • Tests run (Linux x86_64): test-roce-multi-host 40/40, test-tp 130/131 (1 skipped: the two-rank verbs run, which needs TF_TP_ROCE_TEST_HCA), compile-config-server 7/7, test-cuda-host 262/262 steps (3,748 of 3,757 tests, 9 skipped), zig build test 428/428 steps (1,580 of 1,596, 16 skipped), zig fmt --check, and tools/zig/lean_check.py with nothing new (428 against main 13255c97d's 428, and 425 against 7c0688951's 425 merged with it). Each new test fails with its property taken out, and only that test: without the timeout check in hostGatherAt the wait test ends in the watchdog's error.Aborted; a report built from last_seq and this rank fails the report test; a vote on each rank's own probe leaves ranks 0 and 2 on the wire and fails the probe test. Every commit passes test-roce-multi-host and test-tp. At 6256add49 the two tests that run the shm wire skip off Linux at comptime: built for aarch64-macos, test-roce-multi-host compiles (not run: no Mac here), where 82fa993a6 failed on sched_setaffinity.
  • Not run, and why: Metal (nothing here reaches it); a switched multirank network (no switch here; the fake-verbs test "on a switch connect pairs HCA h with every peer's HCA h over two stripes, as before" keeps that path); a ring of four (a fourth node arrives in early November, and the plan refuses it today as the tests show).

Checklist

  • Every token still goes through the lane rounds. No serial path, and nothing that needs drafts off. (No lane or family path changes.)
  • Drafted output equals "draft": false, a resumed prompt equals a fresh one, and concurrent equals solo. (No family path changes; the suite's ops are byte-equal to NCCL's.)
  • No precision traded for speed. If the bits change, the description says which sums change and why. (No bits change.)
  • A new or changed GPU kernel is registered with the kernel contract (docs/kernel-contract.md); each backend's SUMMARY line is in the receipt. (No kernel changes; n_hca carries the stripe count.)
  • Prompt processing is no slower than the last release. (No family path changes; the wire stays opt-in.)
  • The description says which platforms I ran, Metal M1 to M5 and CUDA, and which I could not. (CUDA on three GB10 and the host tests on Linux; Metal not run, as above.)
  • New tests fail before the change, pass after it, and skip cleanly without their dependency. (Each fails with its property taken out, as above; they need no device, and the two that run the shm wire skip off Linux.)
  • Comments and docstrings are one line. No measurements or history in the source.
  • No personal data, machine names, internal hosts or local paths. No AI attribution lines.

@ashhart

ashhart commented Oct 28, 2026

Copy link
Copy Markdown
Owner

Thanks @Bizuayeu. This is the shape we asked for on #1288, and it keeps both rules from #1495's review. We read it through:

Next we'll run tf-tp-test suite at world 2 over the wire on a pair of GB10s against main, to confirm the two-rank pick and wire are unchanged. TP=3 and TP=4 receipts stay with your ring, as we agreed on #1288, and worlds above two stay opt-in until the cold-prompt receipt.

@Bizuayeu

Copy link
Copy Markdown
Contributor Author

Mac host tests. test-tp and test-roce-multi-host no longer compile on a Mac. They reach proxyLoop → pin, which uses std.os.linux.sched_setaffinity (roce_multi.zig:548), and they need /dev/shm. These come with #1516's commits, so please keep those tests to Linux there.

#1524 (comment)

Done in 6256add49. The two tests that run the shm wire on host threads, both added here (the probe vote's in roce_multi_session.zig and the stopped rank's in roce_multi_wait_test.zig), now skip off Linux at comptime, as roce_inflight_test.zig does. With test-roce-multi-host built for aarch64-macos (compiled, not run: no Mac here), 82fa993a6 fails on sched_setaffinity and 6256add49 compiles; on Linux test-roce-multi-host passes 40/40 and test-tp 130/131 (1 skipped), as before. The body's Tests line now says so.

Bizuayeu and others added 7 commits October 29, 2026 23:44
…, so a switchless ring connects

roce_multi paired HCA h with every peer's HCA h, which assumes a switch: on a ring of three, where each cable is its
own pair of /24s, the connect step refused (BadRecord). A plan now pairs our k-th HCA on a /24 with the peer's k-th
on it (roce_multi_pairs.zig), so a switch keeps HCA h to every peer's HCA h over two stripes and a ring gives each
peer its link's HCA over one. Every peer gets the same number of stripes, the flags the kernel counts from each
source, so unequal stripes, a peer out of reach and an HCA that reaches nobody refuse before any queue pair moves.
Named HCAs (TF_TP_ROCE_HCA) are paired the same way. The kernel's n_hca is the stripe count.
…alone

With two ranks the pick takes the ports on a /24 the peer shares. With more, one /24 need not reach every peer: on a
switchless ring each neighbour sits on its own link. choosePeers runs that pick toward each peer alone and keeps the
most ports a peer whose union fits, so a ring picks one port toward each neighbour and a switch the same two ports to
every peer.
… peers' gathers at the timeout

Three ranks on host threads over the shm wire: the last stops after ten gathers, and the others' next gather must
return error.Timeout once the timeout has passed, and well before ten of them. A watchdog aborts the wires when the
timeout does not end the wait, so the test fails with error.Aborted instead of hanging; without the timeout check in
hostGatherAt it does.
…lure, and a test reads the report

The report is unchanged; as a function, a test can read it (logs are quiet under test). The test writes the kernel's
record of a wait that timed out, seq 7 from rank 2, and expects error.PeerFailed and a report naming rank 1's wait
for seq 7 from rank 2. A report built from other numbers fails it.
…e rank's mismatch drops the wire on every rank

The vote is unchanged; as its own function it runs on host threads over a host-only wire. Three ranks vote with rank
1's probe failing, and every rank ends without the wire, falling back to NCCL; with every probe matching, every rank
keeps it. A vote on each rank's own probe alone leaves ranks 0 and 2 on the wire and fails the test.
@ashhart
ashhart force-pushed the pr/roce-peer-devices branch from 6256add to f3feba5 Compare October 29, 2026 23:44
@ashhart
ashhart merged commit f12d326 into ashhart:main Oct 29, 2026
9 checks passed
@ashhart

ashhart commented Oct 31, 2026

Copy link
Copy Markdown
Owner

Thank you @Bizuayeu. This is in TensorFold 1.2.0, released today: https://github.com/ashhart/TensorFold/releases/tag/v1.2.0

It landed with your commits under your own name, so you're listed as a TensorFold contributor, and it's credited to you in the release notes and the changelog.

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