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
…und's plan and hidden rows (Linux and macOS) zig/src/fabric/stage is the first step of ashhart#2005: each rank listens on its own address only, connects to the next rank and accepts the one before. A hello checks that both ends of every link agree on the world, on being neighbours, and on the checkpoint's and layout's fingerprint. Frames carry a 32-byte header (magic, version, kind, round, rows, payload bytes) and every send and receive has a deadline. A frame of another kind or round, or past the receiver's buffer, is refused, and any failed call leaves the link broken, since its stream may be mid-frame. A rank that fails closes both links, so its neighbours see it and the ring ends as one. It uses libc sockets only, with no CUDA or Metal module. fabric/tp's control plane imports cuda through config.zig, so this does not reuse it. TF_STAGE_RANK, TF_STAGE_WORLD, TF_STAGE_PEERS and TF_STAGE_TIMEOUT_S name a rank's settings. `zig build test-stage` runs rings of two and three ranks on loopback threads, and `test-stage-compile` builds the tests for aarch64 Linux and macOS.
… any frame A stage rank opens a port, so a ring needs a key: TF_STAGE_SECRET_FILE names a file of at least 16 bytes that only its owner may read, and the key is its SHA-256. When a link forms, both ends send a fresh nonce from /dev/urandom, then an HMAC-SHA256 over a label, their role (connector or acceptor), rank, world, the fingerprint and both nonces. Each end checks the other's proof in constant time, so the key never crosses the wire and a proof can't be replayed. A rank without the key is refused by both its neighbours (error.BadSecret), and a stranger that connects and talks first is refused at its first frame. This authenticates the ring when it forms. It doesn't encrypt, and it doesn't protect each frame after that, so it's for a trusted network.
…wildcard listen and a key file over 4 KiB are refused
…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
|
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 |
…ytes on the wire read TFSP; fixtures rewritten
|
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 |
Part of #2005 (step 1b, the protocol layer of section 6.4). Draft, stacked on #2012: the first three commits are #2012's (
c55fd8d3c,442f61776,c68421b38), and the last three are this PR. #2012 has since gainedfe6aeebc7(its frame magic now readsTFSTon the wire). Once #2012 lands, I'll rebase this PR onto it so it carries only its own commits.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/: 43 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: 33 named tests (18 from 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, 15 new), all pass. The runner reports 35/35 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.