Repository navigation
Conversation
95d2292 to
95c9452
Compare
|
Besides the token equality in #1432 and #1433:
|
0269c7b to
277929c
Compare
|
All five are in the refreshed head
|
|
Thanks @akol1: all five earlier points are in. Rank 1's refusal comes before any backend call and has a test. The watchdog fails closed.
This also needs #1432's rebase. |
ec2dc03 to
03d0de3
Compare
|
Refreshed and force-pushed:
Gates on |
|
Thanks @akol1. Both are in on |
4c14ed9 to
c7dcbce
Compare
|
Thanks — both are in, and they did not move under the rebases. On the current head
Fresh receipts on The commits above this one add the tp-check option, the oracle tooling and the docs; the four files behind the cap and the refusals are unchanged except for step 5's |
c7dcbce to
5d2db8e
Compare
|
Rebased onto the current
The rebase also moved this commit's refusal words into one small module, This room is one Mac now: the second Mac the pair receipts were recorded on is gone (the cluster was broken), so a fresh two-Mac |
|
Thanks @akol1. All the earlier points are in, and the one-Mac path looks unchanged: with no link the refusals do nothing, the chunk cap and the cache, rewind and learn wiring stay as on main, and moving the window timing into
Once those are in, this lands in order after #1432 and #1433. |
5d2db8e to
ebb337d
Compare
|
Rebased onto Both review points are in:
Numbers from this head, run here, not carried over: The paths that need two Macs are not re-run here: the second Mac the pair receipts were recorded on is gone. The host test pins the resume rule on one Mac, and step 4's loopback gate compares the pair against the single Mac byte for byte. |
|
Thanks @akol1. Everything from our earlier review is answered, and nothing changes on a single Mac: every new branch needs
To fix it:
Until then, the changelog line and the description shouldn't say |
ebb337d to
6febdc2
Compare
|
Both parts of the
Numbers from this head, run here, not carried over: Two Macs are still not re-run: the second Mac the pair receipts were recorded on is gone, so the two-Mac |
6febdc2 to
6db850c
Compare
|
Thanks @akol1. The One more change before this lands, on the single-Mac path:
Everything else from our earlier reviews is still in place after the rebase, and this applies cleanly on current |
e550d50 to
161b323
Compare
|
Both the
Numbers from this head, run here, not carried over: Two Macs are still not re-run: the second Mac the pair receipts were recorded on is gone, so the two-Mac |
161b323 to
573105e
Compare
Step 2 of 6, stacked on step 1 (`qwen27/pair-seam`) and main (`40cb15b4`); step 3 stacks on this commit. A rank's layer program now carries one `Reduce` interface instead of assuming the whole matrix. Attention's o_proj and the MLP's down_proj seam this rank's output columns, gather the whole frame, and let the producer kernel reduce over full K on its own rank; the head seams the last row's logits and takes the pair's argmax over the whole vocab, so the picked token is the single Mac's token, not rank 0's half-vocab pick. On one Mac `reduce` is null and every seam is compiled out of the program: the single-Mac program, its digests and its tests are the ones that ran before. The rank-local K-split is fixed while the slice is. A projection's split is keyed on the checkpoint tensor's rows, not the view's rows: `Triple` carries `whole` (the matrix's row count) and `prepare` derives `tensorRows` from it, while the M5 and prompt tables keep the checkpoint rows they always keyed. `in_proj_qkv` loads as three blocks of one tensor (10,240 rows), so its gate/up-style stack keeps main's 4 K slices on 6,144 rows instead of reading the q block's 2,048 rows and splitting into 8; every width main served on one Mac keeps main's split, and host tests "a rank's row slice keeps the checkpoint matrix's K split" and "every one-Mac stack keeps main's K split" cover the eight stacks (qkv 10,240 rows: 4, q 6,144: 4, against the 2,048-row view's 8). `Part` drops its unused `k0`, `Triple` its `k`/`k0`, and `Shard`/the family's private `cols`/`attentionCols`/`gdnCols`/`downCols` and the column-window code `prepare`/`packTiles` used them for are gone, with `config_test.zig`'s column-span assertions deleted and the row fields left as they were. `simd_qmm.call` takes the rank's `whole` width and `chunks(n, whole)` picks the chunk fence from it, so a rank's K tree is its own full matrix's while a one-Mac stack passes 0 and keeps exactly main's fence (the core test pins `chunks` on 6,144/12,288 and 5,120/10,240 at 8, and on 6,144 and 5,120 alone at 16, which is the pair's own-width split the `gn` forwarding is there to avoid). `quant_gpu.strided` forwards the `gn` of the linear it serves. `q27_attn_merge` takes the rank's head count (buffer 11) and strides a row by that many heads instead of 24 (`G * 4`), so the non-prompt multi-row rounds a served chat feeds every earlier reply through merge the rank's own rows; `attention.zig` binds `g.heads`. The buffered form is only what a pair runs. One Mac has its 24 heads at compile time, and main's kernel used them there (`G * 4`, and `PG * 4` in the prompt merge), so each site now has a second instance: `q27_attn_merge_single` and `q27_attn_prompt_merge_single` carry main's body verbatim — same reduction, same float arithmetic, main's indexing, no `heads` or `kv_heads` buffer — and the encoder takes a trailing `split` and picks `io[4]`/`prompt[1]` for a pair and `io[10]`/`prompt[2]` for one Mac (`decode_round.zig` passes `r.reduce != null`). The two instances compute the same values, so only the emitted code differs. A function constant would need `MTLFunctionConstantValues`, which the runtime library never creates (`PipelineBuild` would fail the contract's first gate), so the second compiled instance is the route that needs no new Metal machinery. The config gains its shard parse (`config_test.zig` covers it), the embedding table stays whole on every rank — only the head is sliced — with `embedBound` reading the table's bound rather than the family's `vocab_size`, and the session keeps the pair's pick as the round's token and surfaces `error.PairFailed` from the head seam's sentinel rather than returning `maxInt(u32)`. The seam's `Reduce` is threaded through `forward.encodeTp`/`session`, and the prompt attention kernel's signature follows the seam's reopened encoder.
…ith the peer Step 3 of 6, stacked on steps 1-2 (`qwen27/pair-seam`, `qwen27/rank-slice`) and main (`40cb15b4`); step 4 stacks on this commit. The serving path learns the pair: a rank reads its rank-local shard, the serve loop routes every seam through the link, and the head's pick is the reply. A pair refuses what it cannot keep: vision, a rank count the shard does not match, and a drafter whose state only rank 0 holds, and it fails closed when its watchdog cannot be started. The slot accounting understands that both ranks hold the frame. A pair also keeps three serving rules. Its prefill chunk is capped at the seam window's rows (`pair.chunk`), because each chunk stages a seam and the window holds `batch_rows` of them; the host test pins that the served chunk times the widest seam fits `pairBytes()`. A sampled or grammar-constrained request is refused at the head of `prefillFn` (`error.PairGreedyOnly`) instead of being answered greedily and failing on the second token, with a name in `words()` and a host test. And a pair never resumes a slot in place: rank 1 resets the slot at each round's `begin` (`link.follow`), so `attach` passes the switch `Metal.init` read through `lb.slotResume(init, pair)`, which keeps `TF_QWEN27_SLOT_RESUME=0` off on one Mac and turns resume off for a pair (`lb.resumesInPlace`), or the ranks would part at the resumed offset; the host test pins that composition on both paths. A start-up step that fails after the watchdog is up is undone: the two threads live in `qwen27_link.Workers`, so `attach` sets their `quit`, stops following and joins the watchdog, and a host already started is stopped; `close` joins both before the pair it watched is freed, so neither thread reads a pair that `open` has already freed. `qwen27_link.zig` is the pair's link over the existing relay, and holds the watchdog and rank 1's follower in one `Workers`; `qwen27_pair.zig` holds the rank-local shard and the seam transport for the serve loop; main's `qwen27_costs.zig` times the window both ranks share; and `qwen27_served.zig`/`qwen27_slots.zig`/`qwen27_lanes.zig` the accounting a shard adds. GLM-5.3's transport is not touched: `pair.zig`/`pair.metal` are as on main, and no `glm_dsa` file changes in this series.
573105e to
eb8f417
Compare
|
Rebased onto
Run here, on this head, not carried over: Two Macs are still not re-run: the second Mac the pair receipts were recorded on is gone, so the two-Mac The PR body is refreshed with these same receipts. |
What this changes
Step 3 of 6 of the two-Mac Qwen3.8-27B series, rebased onto
main(94b90c9db), stacked on step 2 (qwen27/rank-slice,1af209fce); step 1 (#1432) is merged and its content arrived in the squash merge3f0a715c. Step 4 stacks on this commit.The serving path learns the pair.
qwen27_pair.zigis the transport's types and the seam ABI the engine calls, with the window one exchange may carry:seamBytes(rows) = rows * 8,704 * 2,chunk(requested) = min(requested, batch_rows),batch_rows = 128, and a host test that pinsseamBytes(chunk(promptRows())) <= pairBytes()andseamBytes(512) > pairBytes()— the served pass asks for 512 rows on tensor units, and only the cap keeps its widest seam (this rank's half of the 17,408-column activated frame) inside the window.qwen27_link.zigis the link's liveness: rank 1 replays rank 0's four GPU operations a pair round is made of (begin,chunk,step,end), and both ranks stop when the link dies instead of one serving alone. The watchdog and rank 1's follower are the two threads ofqwen27_link.Workers;closesets theirquitand joins both, so neither outlives the pair it watched.qwen27_slots.zigunderstands that both ranks hold the frame: a slot's opening, fill, step and close announce the link'sbegin,chunk,stepandend(glm_dsa's order), so both ranks stage the same round; the window timing staysmain'sqwen27_costs.zig, unchanged.zig/build/qwen27_host.zigadds thetest-qwen27-served-hoststep andbuild.zigmakes it part ofzig build test, so the refusals' words are pinned on a CPU alone. A pair never resumes a slot in place: rank 1 resets the slot at each round'sbegin(link.follow), soattachpasses the switchMetal.initread throughlb.slotResume(init, pair), which keepsTF_QWEN27_SLOT_RESUME=0off on one Mac and turns resume off for a pair (lb.resumesInPlace), or the ranks would part at the resumed offset; the host test pins that composition on both paths.A pair refuses what it cannot keep, by name, before the backend moves any round state: rank 1 answers "speed-up mode: this Mac runs rank 0's requests; send requests to rank 0" (
FollowsPeer), a sampled or grammar request is refused at the head ofprefillFninqwen27_lanes.zig(the check ispair_mod.refuseSampling, which lives inqwen27_pair.zigso the lane host stays inside the 600-line rule) with "speed-up mode: a pair answers greedy only; send the request with sampling and grammar off" (PairGreedyOnly) rather than being answered greedily for one token and failing on the next,--visionwith a message that starts "--vision does not run on a pair" (PairNoVision), a second stream ("--speed-up serves one greedy stream: pass --parallel 1",PairOneStream), a drafter, read from the flag the user passes rather than from the loaded object (PairNoDrafter), a prompt cache or--learn(PairNoCache,PairNoLearn), and a watchdog that would not start (PairNoWatchdog, so a half cannot keep serving after its peer dies) — all named in one place,qwen27_pair_flags.zig;qwen27_pair.zig'srefuseSamplingandrefuseFollowercall them from the head ofprefillFn, andqwen27_served.zigcallsrefusePairFlagsbefore the backend moves any round state. For the drafter that call isrefusePairFlagsinopen's first statement — beforePair.init, the shard or the vision tower loads — and inattach's first statement, before the loaded drafter is read; the host test buildsOptions{ .pair, .drafter }and hands it to the same helper, with a bound drafter as its second case, so a refusal that ever stops firing turns that test red.qwen27_pair_tests.zigis imported byqwen27_served.ziginto the CPU-onlytest-qwen27-served-hoststep, so these words are pinned without shaders or a GPU; the tests leave theMetalandStreamfields undefined except the ones the guard reads, which proves the refusal happens before any other step.A rank's verify never runs a tail.
forward.zig'sfinishruns the head seam first and hands the reopened encoder back to the tail (zig/src/families/qwen27/forward.zig:230,:241); when the seam ishead == .noneit is skipped and the tail still runs, which is the one-Mac path. Because the head writes only this rank's vocabulary columns while the tail's matcher reads the whole row,decode_round.zig:221refuses a round that has both witherror.TailOnRankbefore the cache checks or any GPU work;zig/qwen27_bank_boundary_host.zig:53pins that refusal with model-free stubs, and from step 3 on a pair refuses--drafterat the front door, so the two never meet.Receipt
eb8f417cf0c01a7021784a3295f4595f693d2cfd) onmain(94b90c9db65dcb71bbc39715901d6fb1f00407d0) plus step 2; 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. The host refusals and the window arithmetic need no checkpoint; the point where this commit needs weights (a served pair) is not exercised here: step 4's gate runs two rank-local shards over the seam's loopback link on one Mac, and step 5's oracle is the two-Mac acceptance.tools/bench_concurrent.py --alone --serialortoken_shaagainst"draft": false: n/a at this commit — it adds the serving pair but no model gate; the exactness evidence it makes possible is step 4'stf-qwen27-tp-check(per-token shas against the single Mac) and step 5'spair_accept.pyoracle. Nothing here changes a sum: the chunk cap can only lower a request's row count to the window's 128 rows.tools/bench_openai.py: n/a at this commit; the pair's decode rate on two Macs is step 4's and step 5's receipt, and a loopback figure would be one Mac's.--chunkgate pins the bound). The cap is a correctness bound on the exchange, not a throughput claim. The series-level cold-prefill A/B was recorded on the earlier tree and is not re-run at this commit; a pair's prefill is capped by construction rather than measured here.zig build test:582/582 steps succeeded; 8771/8824 tests passed (53 skipped)(run here with a cold local cache, so these are the counts of the whole suite; every step succeeded), exit 0. The CPU-only pair tests are in that run: rank 1's refusal, the sampled and grammar refusal with the exact words, the chunk-times-seam window test, the--visionrefusal and the--drafterwiring test that passesOptionswith.drafterset through the same helperopencalls. A third test pinsslotResume: a pair refuses in-place resume at either rank, a single Mac keeps the switchMetal.initread, andTF_QWEN27_SLOT_RESUME=0stays off, so a refusal that put the user's switch back on turns it red.zig build test-kernel-contract:20/20 steps succeeded; 141/141 tests passed, exit 0.zig build dev-tools:219/219 steps succeeded.zig build test-qwen27-served-host:7/7 steps succeeded; 48/49 tests passed (1 skipped), exit 0; the CPU-only pair tests it runs are the ones above.zig build tf-qwen27-tp-check:no such stepat this commit (step 4 adds it).zig fmt --check build.zig zig: clean. Filtered contract runs:SUMMARY 4 kernels: 4 pass gates 1-4, 0 fail, 0 known findings; 6 controls: 6 red as planted, 0 missed; 0 limit problems; gates 33 ms, limits 121 msandSUMMARY 4 kernels: 4 pass gates 1-4, 0 fail, 0 known findings; 0 controls: 0 red as planted, 0 missed; 0 limit problems; gates 30077 ms, limits 121 ms. Lean rule:lean: 424 problems, 424 on the base, 0 new(tools/zig/lean_new.py, exit 0).tf-qwen27-tp-checkarrives in step 4); the unfiltered kernel corpus (UNSUPPORTED metal moss image); CUDA, HIP/RDNA, M1-M3, M5 and Windows are not in this room.Checklist
"draft": false, a resumed prompt equals a fresh one, and concurrent equals solo. — a pair refuses--drafterfrom the flag the user passes, before the loaded object and refuses sampling/grammar, so there is no drafted pair output to compare; the one-Mac equality gates aremain's and steps 4-5 add the pair's.SUMMARYline is in the receipt. — this commit adds no GPU kernel; it drives the four pair launches that core: add the row-split pair seam and split simd_qmm over K #1432 put onmainand the family's existing kernels. The filtered contract runs in the receipt are the regression check for the kernels it talks to.applegpu_g16); the host-only tests need no GPU; no CUDA, HIP, M1-M3, M5 or Windows.qwen27_pair_tests.zig, noPairGreedyOnly, nopairChunk; those tests fail to build there, and they skip nothing because they run on CPU alone.lean: 424 problems, 424 on the base, 0 new.Stacked on:
qwen27/rank-slice(step 2). This commit is step 3 of 6.