Repository navigation
fabric stage: the protocol layer, boot description, round plan, rows layout and keep-alive, with golden bytes for every message (#2005 step 1b, stacked on #2012) - #2060
Conversation
|
Thanks @jschmied, this is a careful protocol layer: every length is checked before it's used, every mismatch has its own error, and the golden files mean a CUDA and a Metal stage can be built separately against the same bytes. I read commits 4 and 5 on current main, with #2012's three commits under them. Everything applies, the lean check is unchanged, and nothing outside
We'll run |
|
Thanks @ashhart. All four are in, at
Checks on |
|
Thanks @jschmied, all four are in. The magic's little-endian bytes now read |
|
Thanks @ashhart. The frame magic is fixed in #2012, at
Once #2012 lands, I'll rebase this PR onto it so it carries only its own commits. |
|
Thanks @jschmied. Nothing is left on this PR's own commits. One thing before it can land: CI's |
|
Thanks @jschmied. The state generation is a good addition: a missed rollback now fails by name instead of as wrong rows. With the rewritten fixtures it's still nothing more to do on your side. Once #2012 lands and this is rebased down to its own commits, we'll run |
…nd tf-qwen35-cuda stage cuda_stage.zig drives the stage link (ashhart#2012) with the protocol (ashhart#2060): at boot each stage's description and ready report go round the ring and rank 0 checks every one and the layout; each round rank 0's plan goes round (rank 0 checks the last stage's echo byte for byte), then the rows, h and r as bf16 planes. Slots is the same code on every rank: open takes a free slot at generation 0, continue keeps the previous window's first `accepted` rows (a keep advances the generation) and checks the plan's generation and position, close frees it; resume is refused until the prompt cache spans stages. Rank 0's Lead predicts each entry's position and generation, sends the plan, then applies it through Slots like every follower. tf-qwen35-cuda <model> stage <out> runs one scenario (a two-chunk prompt, greedy steps, a second stream beside it, drafted windows kept to 3, 5, 0 and 2 rows, a keep-alive) on one engine or a ring. Qwen3.8-27B MLX 4-bit on one GB10: 2 stages (0:32, 32:64) and 3 (0:20, 20:44, 44:64) give the one engine's 48 logits rows byte for byte, bf16 and fp8 KV; device bytes 16.98 GiB whole, 9.04 + 7.64 GiB in two, 6.61 + 6.03 + 5.22 GiB in three. A follower killed mid-scenario ends rank 0 with Closed 0.4 s later.
9f679f8 to
ecd50ea
Compare
|
Rebased onto main now that #2012 has landed. The PR is down to its own commits, on main
On the GB10, on this head: |
|
Thanks @jschmied for the clean rebase and the explained fixture bytes. |
|
That's what we said we'd run, so it goes in the landing queue now. Thanks @jschmied. |
|
/atc exchange 857d45f91fc8134b6269482657215227afc86094a13ba4741e8fb2ba2e63d3d7 |
…y report, rank 0's round plan, the rows layout and the keep-alive, each mismatch refused by its name
…yte-stable and each refused by its name, for a CUDA or Metal stage
…ytes on the wire read TFSP; fixtures rewritten
…d the keep-alive beats mid-round too RFC ashhart#2005 section 6.1 has a stage refuse rows whose generation is not its own, but the plan carried none. A stream is now 28 bytes: the generation u32 at 24, 0 for an open (BadOpen otherwise), and plan.sameGeneration refuses another by name (OtherGeneration). The keep-alive is due once 2 s pass without a plan, not only when idle, so a stage's long forward elsewhere in the ring never reads as a dead ring; a stage already counts any frame as heard. Ready's warm means executable for the declared shapes, not graphs captured. The plan fixtures are rewritten (140 bytes for the round) and plan_open_generation added.
… prompt chunk, so a stage picks the engine's kernels A Qwen stage runs a prompt chunk through the prompt GEMMs and a window through the one-row-exact path, and the bits differ, so a follower must know which one rank 0 runs. A keep-alive is a window; any other mode byte is BadMode. Fixtures plan_prompt, plan_bad_mode and plan_keepalive_mode added; plan_bad_reserved pokes byte 21.
ecd50ea to
fa42127
Compare
|
Thank you @jschmied. This is merged into main with your commits under your own name, so you're listed as a TensorFold contributor. It ships in the next release, credited to you in the changelog and the release notes. |
Part of #2005 (step 1b, the protocol layer of section 6.4). On main
9200a3064now that #2012 has landed: the six commits are this PR's own, headecd50eaa5. The last one rewrites the three rows fixtures for #2012's mergedTFSTframe magic (2 bytes each).Host code only: no engine and no GPU. It defines what a pipeline stage and rank 0 say to each other on #2012's link, and ships golden bytes of every message, so a CUDA and a Metal stage can be built separately against the same bytes.
What it adds (
zig/src/fabric/stage/)payload.zig: every description, ready and plan payload starts with an 8-byte prefix: magic0x50534654(its little-endian bytes readTFSP), a u16 protocol version, the message and a reserved byte. fabric: the pipeline stage link, a TCP ring that carries each round's plan and hidden rows, with a shared key (Linux and macOS, #2005 step 1) #2012's frame header stays at version 1.boot.zig: a stage's boot description and its ready report, and rank 0's checks:--parallel, context), owned tap layers, and capacity (KV rows, recurrent states);checkrefuses the first mismatch by its name:OtherRank,OtherVersion,OtherBackend,OtherKvDtype,OtherRange,OtherWidth,OtherDtype,OtherPlanes,OtherDraftWidth,OtherShapes,OtherTaps,ShortCapacity;RangeGap,RangeOverlap,RangeShort,RangeBeyond.plan.zig:keepalive.zig: rank 0 sends an empty plan every 2 s when idle, and a stage that misses three declares the ring dead. Pure state, tested with injected time.fixtures/: 47 golden files, each good message plus planted bad ones, and a README with every layout and the error each bad file must give.fixtures_test.zigre-encodes each good file byte for byte and checks that each bad one is refused with its named error.The choices section 6.4 left open are written into RFC #2005, section 6.4, under "v0's decisions": half-open ranges, stream order, what a chunk cut and a resume point are, one engine version string, and the boot capacity floor (KV rows ≥ the context, recurrent states ≥
--parallel) thatcheckenforces.Checks (GB10)
zig build test-stage: all pass onecd50eaa5. The runner reports 37/37 because it also counts two unnamedtest {}blocks that only import the other files.test-stage-compilebuilds for Linux and macOS on aarch64.zig build test, the CI set (test-kernel-contract test-golden test-server-security test-pull-security fuzz-cortheon) andgit diff --checkpass.Since the review (
de6c3bdae, after ThinkOffApp's questions on #2005): a plan's stream is 28 bytes and carries its slot's state generation (0 for anopen;plan.sameGenerationrefuses another asOtherGeneration); the keep-alive is due whenever 2 s pass without a plan, mid-round too; ready's warm means executable for the declared shapes. The plan fixtures are rewritten andplan_open_generationadded. #2012's later commits came in with the rebase.Mode byte in v0 (
9f679f852, from #2115 as asked): byte 20 of a plan names the round's mode, 0 a window (every row's state recorded, as decode and a verify run) and 1 a prompt chunk, so a CUDA or Metal stage picks the same kernels as rank 0. A keep-alive is a window, and any other value is refused asBadMode. New fixturesplan_prompt,plan_bad_modeandplan_keepalive_mode;plan_bad_reservednow pokes byte 21.