Repository navigation
qwen27 metal: restore the long-copy driver for a lone greedy stream on M5 (#3013) - #3069
Merged
Merged
Conversation
The narrow tensor-unit default installs the existing greedy callback again. Sampled and shared rounds retain their lane path, and explicit lane overrides keep their meaning. Every copied token still goes through target verification. Refs #3013
Use the lane selector sibling policy for the default narrow callback, and require a confident suffix before appending copy rows. Explicit serial overrides retain their original head, admission and submissions. Keep dependent matching, taps and preparation of the same keep at the existing verify tail, preserving target operators, budgets and EOS. Refs #3013
ashhart
force-pushed
the
lead/q27-long-copy-default
branch
from
November 13, 2026 11:37
394ef08 to
97ed07a
Compare
ashhart
added a commit
to Bizuayeu/TensorFold
that referenced
this pull request
Nov 13, 2026
ashhart
added a commit
that referenced
this pull request
Nov 13, 2026
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
Since 0b44bd1, Qwen3.8-27B with DFlash2 on M5 Macs sends one greedy stream through the 16-row lane loop, where copies are off. A copy-heavy reply, such as a whole-file rewrite, lost its long-copy tail: about 7 tokens a round instead of about 47, and about 70% slower. Tokens were exact.
After this change, an eligible lone greedy request at the M5 16-row default uses the long-copy driver again. Every copied token is still verified by the target. Any arrival hands the stream back to the lane loop. Sampled requests, shared rounds, wider windows and M1-M4 Macs keep the lane loop, as on main.
Three changes keep prose and code level with main on this path:
Explicit settings keep their meaning.
TF_QWEN27_LANES=1keeps every request on lanes.TF_QWEN27_LANES=0selects the lone driver with its old proposal (4 siblings, 5-token match, separate submission) on either device class. No kernel, arithmetic, budget, EOS or sampling change.atc overlap --json: incomplete (time limit). Merges cleanly with lfm2: DSpark drafting with owned context and confidence scheduling #1394, Implement GLP control vectors with a fused Metal kernel for Qwen3.8-27B #1835, XPU 27B decode attention: tree rows of a key slice run back to back (bit-identical, +23 % at 32K, +33 % at 131K) #2595, fix(qwen27): warn when Metal startup exceeds available memory #2886, dsv41 metal: prompts in pieces between the other streams' rounds (stacked on #1720) #2950, flashnext cuda: at --tp 2 a prompt pass reduce-scatters its routed experts' slots by rows and gathers their sums #2977, flashnext cuda: at --tp 2 on one host, collectives run on a blocking split of the nonblocking communicator #2978 and flashnext cuda: the opt-in --tp 2 decode graph takes the dense split and keeps its graph across drafted verifies #2982. Text conflicts with qwen27: serve the 27B pair, refuse what a pair cannot keep, and die with the peer #1434 (1 file) and Qwen3.5-9B DFlash drafter in Zig engine #1851 (3 files). Seven others not checked.Checks on this head
python3 tools/zig/lean_check.py, on the base and on this head: 345 on the base, 345 on this head, 0 new.zig fmt --checkon the 9 changed Zig files: clean.zig build test-qwen27-window-host test-qwen27-facts-host test-qwen27-draft-tree-host test-qwen27-bank-boundary-host test-qwen27-equality-host test-qwen27-pass-host test-qwen27-keep-host test-qwen27-startup-host test-qwen27-reply-cache-host test-qwen27-shared-allocation-host test-qwen27-grants-host check-qwen27-speed-host: 45/45 steps succeeded; 505 passed, 0 failed, 1 skipped. Window 20, facts 18, draft tree 57, bank boundary 4, equality 2, pass 9, keep 3, startup 2 + 2, reply cache 3, shared allocation 360 + 1 skipped, grants 25; the speed check compiles the served host.tf-qwen27-dflash-served-checkandtf-qwen27-streamspass on both main and this branch. The streams check compares 252 shared BF16 logit rows with solo over 25 rounds.Receipt
No cell is slower with separated ranges. The three small one-stream losses overlap main's range. On the rewrite, tokens a round go from 6.84 to 46.73. Main with
TF_QWEN27_LANES=0gives 795.72 tok/s on the rewrite (794.42-796.18), and 0b44bd1's parent gave 796.1 in #3013.TF_QWEN27_LANESkeeps its values (unset, 0, 1). Only its unset meaning on M5 at 16 rows changes.selectionreturns the same options), M1-M4 defaults, wider windows, andTF_QWEN27_LANES=0or1.TF_QWEN27_LANES=0on this head.tools/bench_concurrent.py --alone --serialortoken_shaagainst"draft": false: every reply has the same token sequence for its case and temperature: main and branch, one and four streams, measured and warm-up. That is 996,834 tokens: 664,556 measured and 332,278 warm-up, including the workaround runs. So four streams equal one. This receipt compares with main's drafted replies, not with"draft": false.tools/bench_openai.py: the table above. It comes from a served-request driver, nottools/bench_openai.py. Each side ran two fresh server processes, in main, branch, branch, main order. Each case discards one complete warm-up reply, then keeps two complete natural-stop replies a stream. Rates are per-request native decode rates, as medians of four waves. Four-stream rates are per request, not aggregate.Checklist
core/lane_host_lone.zig), as it did before 0b44bd1 changed the M5 default: the lane host installs the driver for one lone greedy stream and takes the stream back on any arrival. Sampled, constrained and shared work stay on the lane rounds;TF_QWEN27_LANES=0keeps its meaning."draft": false, a resumed prompt equals a fresh one, and concurrent equals solo. Concurrent equals solo, and every reply equals main. Not compared with"draft": false; slot resume was off.--drafter, no slower than without it, greedy and sampled. Measured at one and four streams with the drafter, greedy and sampled. The widest admitted width and runs without the drafter were not measured.