Repository navigation
Conversation
The seam carries one round's partials between two Macs: pair.metal writes a frame, the host relay owns the exchange instead of spinning on device counters (which only works while both ranks share a launch group), and pair_sum completes the partial on the far rank. The payload is f32, the seam's accumulation, and simd_qmm grows the matching store. pair.zig splits into pair_layout and pair_loopback so each module stays under the 600-line rule. Nothing registered the seam before: three of the four kernels already ran under the older ABI on the shipped glm_dsa speed-up path, but there was no record and no spec, so no gate covered the kernels this stack changes. Add the four launch records, the seam spec with its four probes, the `metal pair ` route in the limits table, and the fp32-out simd_qmm variant, so the transport and family changes that follow have a SUMMARY to post. The glm_dsa call sites and the two Pipeline.load test call sites are forced by the shared signatures; the family changes themselves come later.
On a pair rank the embedding table is global while the vocabulary in the split config is the shard's width, so the gather bound took the shard's number and read the wrong rows; embedBound now takes the whole table and refuses a bound below it. Rank-sliced projections, the head seam, the GDN partials and the attention plan follow the same split, and the drafts-off path keeps the two ranks in lockstep. The regression test pins the old behaviour (124160) against the new (248320) and fails when the split width is used again.
This was referenced Oct 27, 2026
A supervised pair has to survive an unattended night: rank 0 now waits for rank 1's handshake before it advertises /health, the first turn is warmed so the client does not pay the load, and either rank exiting takes the other down instead of leaving it serving alone. The launch script starts and stops both, with the kill/restart drill recorded in the receipt. The benchmark and TF_DUMP_IDS receipts in server/ are the support code for those runs; dump.zig and input.zig are their two halves.
akol1
force-pushed
the
qwen27/pair-lifecycle
branch
from
October 27, 2026 03:58
95d2292 to
95c9452
Compare
akol1
marked this pull request as ready for review
October 27, 2026 04:32
Owner
|
Besides the token equality in #1432 and #1433:
|
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
Gives the pair a serving lifecycle. Rank 1 replays rank 0's rounds and both ranks stop when the link dies; rank 0 does not answer
/healthuntil rank 1's handshake completes, so a load balancer cannot send a request into a pair that is not up; the first turn is warmed over the link before the listener opens; and the pair names what it refuses instead of quietly serving something else —--drafter,--prompt-cache-gib,--learn, a request that samples, carries a grammar or asks for images all fail, because one Mac holds only its own halves of a slot's state and rank 1 has no drafter checkpoint.--speed-upopens the pair on the 27B and is refused by Nemotron and the 2B before any weight loads. The benchmark grows--speed-upfor its own serve child, anddump/inputmove out ofbenchmark.ziginto their own units.Refs #1236 and #808. #808 asked for prompt-splitting on two Macs with drafts and images kept; this splits the weights and the decode rounds and keeps neither, so it neither answers nor claims that request.
#1236tracks the mixed-backend group; this is two Metal Macs over a socket. Stacked on #1432 and #1433; the diff againstmaincarries them too, so review this pull request's own commit,95c94523.Receipt
8c36359dplus this commit (95c94523); no MLX or PyTorch; macOS 27.0.1 on an Apple M4 Pro (Mac16,11), Metal only (applegpu_g16, no tensor units), Apple metal version 32023.921; checkpointTensorFold/Qwen3.8-27B-MLX-4bitat revision22d8d538154e0e5b6f8dcb1fcc74a60a0798104c, 4-bit affine with group 64, the same checkpoint on both Macs; two Macs joined over Thunderbolt 5; the binary on both Macs issha256 2dda0e50fad3ca10487c215820f5cd9acee83d3914ab474955659333d9e81bdf, and later commits add only tests, tools and docs, so this is the native code this commit describes.tools/bench_concurrent.py --alone --serialortoken_shaagainst"draft": false: four fixture runs, temperature 0, seed 1234, each sent once per arm after a supervised restart.code-128f5c9409cfe40,code-2562b28aa2108deandchat-128b69b0269917fmatch the single-Mac oracle byte for byte;chat-256is the recorded58daf9743077, which is the bf16 reduction-order tie at token 236 thatdocs/known-issues.mdcarries —pair_accept: 3/4 cases match the oracle, 1 recorded as different, 0 unexpected. The one-Mac arm of the same binary reproduces all four oracle shas exactly,chat-256included (a06fab348fbe), which is the"draft": falseside of that comparison. Supervision, from the script's own log: both installs matched (binaries match: 2dda0e50fad3), the pair answered3s after rank 0 startedafter the first start and2s after rank 0 startedafter a restart, and rank 0's port refused 2 health probes while the handshake completed, so a client cannot lose a request to that delay.kill -9on rank 0 then produced[pair] rank 0 stopped answering: restarting both (restart 1)andstopped rank 1 (pid 22003),READY (2 up transitions) after 10s, and the same 3/4 acceptance afterwards. On TERM the supervisor stopped both ranks (stopped rank 0 (pid 18814),stopped rank 1 (pid 22290),supervisor gone after 3s) and left no serve process or listener on either host; one Thunderbolt-fabric line reports a refused signal as the link tears down, after both ranks had stopped answering.tools/bench_openai.py: one Mac's arm of the same binary serves the fixtures at 12.3 to 13.6 tok/s (128 and 256-token replies, 13.0, 12.3, 13.4 and 13.2 in order), and the pair serves them at 16.8 to 17.1 tok/s, about 1.2x on the one Mac against the two; two Macs buy capacity for a model that does not fit one, not two times the speed on a model that does, and this stack makes no capacity claim it could not test here.tools/bench_openai.pyis not yet pair-aware, so these numbers are the server's owntok/slines rather than that tool's.zig build test test-goldenat this tree: 7116 passed, 45 skipped, 0 failed of 7161, with 535 of 535 build steps; the 45 skips are the image-codec and pixel cases that need libjpeg-turbo 3.1.4.1 or libwebp 1.6.0, plusSKIP qwen27-tp-check: no --model DIR; the five tests this commit adds (benchmark_run_test's--speed-upchild and receipt case, and the served/lanes/dump/input cases) are part of the 7116, andSKIPis the same single skip. The same tree is green in CI onubuntu-latest(zig),windows-latest(zig-windows) andmacos-26(zig-macos-dev-tools): the two--speed-upcases assert whichever answer the build's flag table gives — on the CUDA build that is the managed server's refusal, not a skip — anddump.zigtakes the platform's default directory attributes wherefromModedoes not exist.zig-windowsjob and a reviewer's CUDA box carry those; the fulltf-kernel-contractcorpus stops at Gemma 4 31B (TensorUnitsRequired) on this host; and there is no maintainer-agreed gate for a tensor-parallel family —zig/gate.jsonhas no entries at8c36359d— so the exactness and speed numbers above are reported against the checked-in single-host oracle, not against an agreed threshold.Checklist
"draft": false, a resumed prompt equals a fresh one, and concurrent equals solo. — the pair refuses a drafter and keeps no prompt cache, so drafted output and resumed prompts are refused by name rather than served; concurrent requests on a pair are limited to one stream, also by name.docs/known-issues.mdsays which two contenders it was between and why the lower id won.SUMMARYline is in the receipt. — this commit launches no new kernel; the pair's four are registered in the first pull request, whose filteredSUMMARYis quoted there.nullfor the absent pair, and the benchmark tests cover--speed-uprefusals without loading weights.