Repository navigation
fabric stage: one stage driver over an engine binding, so CUDA, Metal and XPU stages share the boot and the rounds (#2005, stacked on #2060) - #2241
Open
jschmied wants to merge 10 commits into
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
…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.
…e passes it on to the next recv marked the link broken on any error, a received stop too, so the rank that got it could only hang up: in a ring of three the last stage saw Closed where rank 0 had stopped cleanly. A stop is a whole frame; only other errors break the link now.
… binding, so CUDA, Metal and XPU stages share them driver.zig takes the backend-neutral half of ashhart#2115's Qwen CUDA stage: the boot over the ring (descriptions, then ready reports, rank 0 checking each), the fingerprint, the slots that apply a plan's open, keep, close and generation checks alike on every rank, rank 0's rounds with the plan's echo, and a follower's serve loop. `Driver(B)` is generic over a binding that names its engine, cache and forward part and supplies the boundary, the shapes, the cache's position, window and rollback, and the forward of its layer range given the round's mode. driver_test.zig runs it over a fake engine whose every layer mixes its index and a per-stream state into the rows: 2- and 3-stage loopback rings give one engine's bytes, a stage one layer short and a rollback that moves the position but not the state both change the bytes, a dropped rollback is refused as OtherPosition, and a range described against the layout is refused at boot as OtherRange.
Owner
|
Thanks @jschmied. Pulling the boot and rounds out of #2115 into a driver with no CUDA in it, and testing it over a fake engine whose output depends on layer order and stream state, is a clean way to share it. This is host-side only, so it can follow #2060 when that lands. The engine bindings that would use it, #2115 and any Metal or XPU stage, still wait on the decision on #2005, so this one isn't urgent. |
3 of 4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
Before this PR, the boot and round loop of a pipeline stage lived in #2115's
qwen3_5/cuda_stage.zig, written against the Qwen CUDA engine. This PR moves that code, which has no CUDA calls in it, intozig/src/fabric/stage/driver.zigasDriver(B), generic over an engine bindingB. A Metal or XPU stage then writes only its binding.9f679f852, the protocol with the mode byte). This PR adds two commits:8e3dbcfa3: a stop that a stage receives no longer marks its link broken, so a middle stage passes the stop on. Without it, the last stage of a ring of three exits withClosed. This commit was in qwen3_5 cuda: the 27B as a pipeline stage, logits byte-equal to the whole model at 2 and 3 stages (RFC #2005 step 2, stacked on #2241) #2115 and moves here, because the driver's 3-stage test needs it.27e4d7e33: the driver and its test.atc overlap --json:driver.ziganddriver_test.zigare new files. Of the base stack's 66 changed files, one conflicts, with Implement GLP control vectors with a fused Metal kernel for Qwen3.8-27B #1835, and that file comes from the base stack, not from these commits. Most other overlaps areunknownbecause of the tool's PR-files limit. Receipt:atc-overlap-stage-driver.json.What the driver holds (
driver.zig)bootRing: the boot over the ring, in rounds 1 and 2. Each stage appends its description, then its ready report, and rank 0 checks every one against the layout withboot.checkandcheckLayout.fingerprint(version, config, ranges).Slots: the same code on every rank, so all ranks apply a plan's open, keep, close and generation checks alike.Lead: rank 0's rounds. It encodes the plan, applies it to its own slots, sends it, checks the last stage's echo, and runs the forward with the hop.serve: a follower's loop. It receives a plan, passes it on, applies it, and runs the forward with the hop.Hop,WindowandEntry.What a binding provides
Engine,CacheandPart. AParthascacheandrowsfields.version,backendandmost_streams.boundary(e),shapes(parallel, context)andkvDtype(e).openandclosefor a slot's cache, andresetfor a stream opened on that slot.position(cache)andwindow(cache), the cache's last window that no plan has settled yet.rollback(e, cache, accepted), which keeps that window's firstacceptedrows.forward(e, parts, ids, mode, hop): the binding's layer range over the round's rows.modeis the plan's window or prompt, andhopis null on a single engine.Checks on this head
27e4d7e33zig test zig/src/fabric/stage/stage.zig -lc, thetest-stagemodule: 44/44 pass. That is 41 named tests: 35 from the base and 6 new indriver_test.zig.zig build test: RC 0, 0 errorstest-kernel-contract test-golden test-server-security test-pull-security fuzz-cortheon: RC 0, 0 errors.git diff --check: clean, 0 lines.--test-no-exec, which is whattest-stage-compiledoes). Not run on a Mac.driver_test.zig: a fake engine on loopback ringsThe fake engine's layer
lmixesland a per-stream running state into every row, so the output depends on the layers' order and on each stream's state. The test runs 13 rounds:What it shows:
0:3,3:6) and from 3 stages (0:2,2:5,5:6) equal those of one engine.OtherPosition, and rank 0 ends;OtherRange.Receipt
Checklist
Questions
bootRingexpects every rank on one backend, as qwen3_5 cuda: the 27B as a pipeline stage, logits byte-equal to the whole model at 2 and 3 stages (RFC #2005 step 2, stacked on #2241) #2115 did. A mixed CUDA and Metal ring would need a backend for each rank in the layout. Should that go into the layout setting, or into the description alone?Lead.steptakes the plan's mode, and the binding maps it to its own keep: the last row's logits for a prompt, every row's for a window. Rank-0 sampling on the GPU would need a third keep. Should that be a binding option, or should it wait for the step that adds it?