Skip to content

glm: a split start checks that every pair of ranks can exchange, before the weights load - #899

Merged
ashhart merged 1 commit into
ashhart:mainfrom
Bizuayeu:pr/glm-split-probe
Oct 18, 2026
Merged

ashhart merged 1 commit into
ashhart:mainfrom
Bizuayeu:pr/glm-split-probe

Conversation

@Bizuayeu

@Bizuayeu Bizuayeu commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

The first follow-up promised on #804 (comment),
after @SvangenStudios's TP=4 run on a ring of four (comment),
as one commit on main: bb19e68e.

What this changes

TF_GLM_PREFILL_REDUCE=split, the default, sends each rank's partial rows straight to the rank that glues them and
the glued rows straight back, so every pair of ranks must connect. On a ring of four whose ranks 0 and 2 cannot reach
each other, it failed in the first prompt chunk with ncclGroupEnd: unhandled system error. With split, Engine.init
now exchanges one value each way with every peer before the weights load, peers in rank order (every pair's turn then
comes in the same order on both of its ranks, so no pair waits on a later one), and a pair that cannot connect
refuses at start: REFUSE TF_GLM_PREFILL_REDUCE=split: rank 0 cannot exchange with rank 2; ... needs TF_GLM_PREFILL_REDUCE=gather.

With #902 also in, ring (split whose exchanges go only to neighbours) should skip this probe; whichever of the two
lands second makes the probe run for split only and names ring in its refusal.

Receipt

  • Environment: main at 46a08c18 (TensorFold 1.0.12) plus this change and the checkpoint check (core: nemotron's checkpoint identity moves to core, and GLM's ranks compare the checkpoint by it #900) on one
    branch; three DGX Spark (GB10, sm_121, CUDA 13.3) over RoCE; nvidia/GLM-5.3-Flash-NVFP4 at revision 423acf37.
  • Exactness: the probe uses its own 8-byte buffer before the weights load. At TP=3 and TP=2 the concurrent and shared
    checks pass, and at TP=2 four of the server's replies, compared by hash, rounds and accepted drafts, equal main's.
  • Decode speed before and after: n/a, the probe runs once at start.
  • Prompt speed before and after: n/a, the probe runs once at start.
  • Tests run: on one host zig build test-cuda-host 853 passed, zig build test 621 passed and 7 skipped,
    zig fmt --check clean, glm-ref and the thin and synthetic lanes checks; at TP=3 the concurrent (41 PASS) and
    shared checks, at TP=2 the concurrent, shared and cover checks; 0 failed. lean_check: 516, as main.
  • Not run, and why: the refusal itself, since every pair of our three hosts connects. @SvangenStudios offered to
    check it on the ring of four.

Checklist

  • Every token still goes through the lane rounds. No serial path, and nothing that needs drafts off.
  • Drafted output equals "draft": false, a resumed prompt equals a fresh one, and concurrent equals solo (the lanes checks above).
  • No precision traded for speed. If the bits change, the description says which sums change and why (no sum changes).
  • Prompt processing is no slower than the last release (the probe runs once at start).
  • The description says which platforms I ran, Metal M1 to M5 and CUDA, and which I could not (CUDA on GB10; the family is CUDA's).
  • New tests fail before the change, pass after it, and skip cleanly without their dependency (none: the probe needs NCCL ranks, and every TP=2 and TP=3 start above ran it).
  • 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.

…ghts load, refusing a pair that cannot reach each other
@Bizuayeu
Bizuayeu marked this pull request as ready for review October 16, 2026 13:09
@Bizuayeu

Copy link
Copy Markdown
Contributor Author

Rebased onto main at 46a08c18 (TensorFold 1.0.12) as bb19e68e, with no conflicts. The checks ran again on main together with #900, and the receipt above is updated. Ready for review.

@ashhart ashhart mentioned this pull request Oct 17, 2026
@ashhart
ashhart merged commit 289688e into ashhart:main Oct 18, 2026
4 checks passed
ashhart added a commit that referenced this pull request Oct 18, 2026
…peer before the weights load, and refuses a pair that cannot
ashhart added a commit that referenced this pull request Oct 18, 2026
ashhart added a commit that referenced this pull request Oct 18, 2026
…he 27B's K splits (#887), drafter releases (#893, #894), GLM's split check (#899) and Intel Arc (#802)
ashhart added a commit that referenced this pull request Oct 18, 2026
…ing start on a neighbours-only fabric loads

Settings.read sets split and ring for TF_GLM_PREFILL_REDUCE=ring, and the engine's start guarded #899's probe with split
alone, so a ring start still exchanged a value with every rank before loading: a ring of four without a switch, whose
ranks reach only their neighbours, could not start in the mode #902 built for it (two ranks cannot tell the two apart).
probeStart probes for split without ring, and the refusal now names ring or gather. Host tests over a fake fabric: a
direct split start is refused at every rank of a four-rank ring and starts behind a switch and on two ranks; ring and
gather starts on the four-rank ring post nothing before loading.

Co-authored-by: Bizuayeu <30091157+Bizuayeu@users.noreply.github.com>
ashhart added a commit that referenced this pull request Oct 18, 2026
…ollow-ups named as ours

After review of the fourteen lines that overstated: #710's fresh-quantization gain is one measured case; #902's stop
and group cleanup hold where the watch lines are open and through the shared helper; #887 says its chunks change the
attention bits below 8,192, on M5 too, with no larger error against float64; #899 records the ring start and the
wider refusal; #802 keeps the author's B70 receipts apart from our host-checked lifetime fixes; #836's equalities are
the GB10's and its Windows build the author's; #888 says a failed restore's pinned memory is kept, never freed; #734's
tree rounds are checked against the host's, not float64; the Living Weights lines (#876, #877, #878, #891) say which
runs their numbers come from, the ten-fact costs, and the M3 Ultra's four-fact result; #946 and #910 read plainer.
@ashhart

ashhart commented Oct 18, 2026

Copy link
Copy Markdown
Owner

Thank you, @Bizuayeu, for making an unreachable split fabric fail at startup with a useful remedy.

We merged the probe for 1.1.0 in 8e64cfa. The integrated guard now probes every peer only for direct split; neighbour-only ring mode bypasses it. The refusal names ring or gather, and the shared rank watch handles NCCL failures.

Host fabric controls cover the refusal and ring start. A successful two-rank run alone cannot establish the four-rank neighbour-only case.

Rank-watch controls and two-GB10 EXL3 serving passed; four-rank relay and live killed-peer comparisons are not recorded.

Posted by Ash's AI Assistant Synthena

@Bizuayeu

Bizuayeu commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor Author

Rank-watch controls and two-GB10 EXL3 serving passed; four-rank relay and live killed-peer comparisons are not recorded.

(#899 (comment))

A live killed-peer comparison on 1.1.0 (76aafcf2): two GB10s over a direct ConnectX-7 link, GLM-5.3-Flash NVFP4 with TF_GLM_LAYERS=4 on both ranks (the whole checkpoint also loads on these two GB10s on 1.1.0: #1040), one tensorfold-native binary on both (sha256 b6202d919b376d47), serve per rank with TF_GLM_RANK, TF_GLM_WORLD and NCCL_COMM_ID, and TF_GLM_PREFILL_REDUCE=split or ring on both. One rank's engine got SIGKILL; the time is from the kill until the other rank's process was gone, polled over ssh every 0.5 s.

Killed Request in flight split ring
rank 1 none 1.2 s 1.2 s
rank 0 none 1.2 s 1.3 s
rank 1 a prefill of about 199,000 tokens, killed 6 s into about 18 s 1.2 s 1.4 s
rank 0 the same 1.3 s 1.3 s
rank 1 a streamed decode, about 30 KB received 1.2 s 1.2 s
rank 0 the same 1.3 s 1.2 s

In all twelve the surviving rank logged rank N: another rank's process ended (its line closed); this rank stops instead of waiting on it and exited 1, and the killed rank exited 137. With a request in flight, /health showed one connection and rank 0 had logged no finished request at the kill; the client then saw its connection close. Control: SIGTERM to rank 0 left rank 1 with rank 1: rank 0 ended; leaving, and both exited 0. Without a GPU, tf-tp-watch-check at world 3 on one host exited 1/1/4 for die, 1/1/3 for fail, 1/1/1 for fatal and 0/0/0 for end (ranks 0/1/2).

Not covered: four ranks. Our three GB10s are a switchless ring whose per-host /32 addresses NCCL_COMM_ID does not bootstrap (our own build hands the NCCL id over in a file), so 1.1.0 ran at TP=2 only. At two ranks the ring relay is one step and the startup probe the same two postings as split, so these rows do not reach the four-rank neighbour-only case.

BlivionIaG pushed a commit to BlivionIaG/TensorFold that referenced this pull request Oct 20, 2026
… every peer before the weights load, and refuses a pair that cannot
BlivionIaG pushed a commit to BlivionIaG/TensorFold that referenced this pull request Oct 20, 2026
BlivionIaG pushed a commit to BlivionIaG/TensorFold that referenced this pull request Oct 20, 2026
@Bizuayeu rebased ashhart#899 onto 1.0.12 (b5b797e). The commit carries the
same change as the head landed here (fa7aad4), its context moved by
1.0.12's multi-line Fingerprint.of call, so the tree stays as it was.
BlivionIaG pushed a commit to BlivionIaG/TensorFold that referenced this pull request Oct 20, 2026
BlivionIaG pushed a commit to BlivionIaG/TensorFold that referenced this pull request Oct 20, 2026
…ing start on a neighbours-only fabric loads

Settings.read sets split and ring for TF_GLM_PREFILL_REDUCE=ring, and the engine's start guarded ashhart#899's probe with split
alone, so a ring start still exchanged a value with every rank before loading: a ring of four without a switch, whose
ranks reach only their neighbours, could not start in the mode ashhart#902 built for it (two ranks cannot tell the two apart).
probeStart probes for split without ring, and the refusal now names ring or gather. Host tests over a fake fabric: a
direct split start is refused at every rank of a four-rank ring and starts behind a switch and on two ranks; ring and
gather starts on the four-rank ring post nothing before loading.

Co-authored-by: Bizuayeu <30091157+Bizuayeu@users.noreply.github.com>
BlivionIaG pushed a commit to BlivionIaG/TensorFold that referenced this pull request Oct 20, 2026
…ollow-ups named as ours

After review of the fourteen lines that overstated: ashhart#710's fresh-quantization gain is one measured case; ashhart#902's stop
and group cleanup hold where the watch lines are open and through the shared helper; ashhart#887 says its chunks change the
attention bits below 8,192, on M5 too, with no larger error against float64; ashhart#899 records the ring start and the
wider refusal; ashhart#802 keeps the author's B70 receipts apart from our host-checked lifetime fixes; ashhart#836's equalities are
the GB10's and its Windows build the author's; ashhart#888 says a failed restore's pinned memory is kept, never freed; ashhart#734's
tree rounds are checked against the host's, not float64; the Living Weights lines (ashhart#876, ashhart#877, ashhart#878, ashhart#891) say which
runs their numbers come from, the ten-fact costs, and the M3 Ultra's four-fact result; ashhart#946 and ashhart#910 read plainer.
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