Repository navigation
qwen27: score and decide passes queued onto the lane host's idle engine thread - #1189
grantoverton wants to merge 16 commits into
Conversation
… 32-over-2 attention WIP: the text path matches an fp32-routing reference at every depth on the 4-bit checkpoint (KL 0.0023 at 48 layers; decode top-1 98-100%); host registration, DFlash2 and images follow.
…t taps and a block of 16 (Ornith's)
…routed block (route, experts) and TF_PROFILE_PROMPT
…sor units' product
…d five of Ornith's eight); selector options by env
…erts' kernel (core: indexed_extra row kernel)
… the reference config and the CUDA family read it
…ne thread Ornith serves /v1/decisions and /v1/systemone while ordinary text submits keep running: info.decides stays off and the hooks appear only when a decider is set. A lane host's runner is owned by its engine thread, so HTTP threads cannot run the Metal passes themselves. engine_api gains a DecisionTask/Decider pair; the host queues a Work per score/decide call, the round and generic driver loops run the oldest once no stream is admitted, filling or active, and shutdown or a stale device fails every waiter instead of sleeping past the thread's end. qwen27's passes run one prompt each on the existing LM head path: score reads the labels' logits at the row after its prompt plus the vocabulary logsumexp, and decide keeps the trunk's slot state once, restoring it per branch so every branch's last-row label logits stand on the same trunk. Pictures and requests past the context window are refused.
ashhart
left a comment
There was a problem hiding this comment.
Thanks @grantoverton. Your two commits don't need the #1125 Ornith stack, so we rebased just them onto current main (one conflict in lane_host.zig, plus main's new spares argument to saveSlot). Host suites pass, including the three decide tests. Main has since landed its own decision-score queue (lane_host_score.zig), so:
- Please build on that queue instead of adding a second one (
decisions/decided): give the qwen27 lanes backend ascoreentry (ascuda_bound.zigdoes) and adddecidetolane_host_score.zig. - The decider is set for every qwen27 host (
qwen27_served.zig), so default Qwen3.8-27B serving starts answering/v1/systemoneand/v1/decisionsinstead of 404. New routes need to be opt-in. lane_host_decide.zigwaits with no cancel or timeout, so a client that disconnects can't abandon a decision, and decisions can wait indefinitely under load.- Smaller:
settle()errors are dropped inqwen27_decide.zig, andscorechecksr.capacitywhiledecidecheckswindow.
Once reworked, we'll check on a Mac that text tokens are unchanged, each branch's label logits equal a score of trunk+branch bit for bit, streams during a decision equal their solo runs, and the next reply after a decision equals a fresh server's.
|
Review of the qwen27 decision stack (#1189 → #1191 → #1192 → #1215, tracking #1190) against main 3f408b6. It's about 860 commits behind but #1189's own two commits apply cleanly, and host checks pass on the rebase (
|
|
Thanks @grantoverton for the decision queue. We're closing this for now, as our 26 Oct review hasn't had a reply. Please do reopen it, or send a fresh PR, once it builds on main's score queue as an opt-in with cancel and timeout, ideally converging with #1361's shared decision path. |
Part of #1190.
Stacked on #1125 (the qwen27 Metal family); base for review: 30e5dcb, the #1125 head. Review the last two commits: 146a658 (the queue and the pass) and 227b961 (lean-check comments).
The qwen27 Metal lane engines take decision work beside text generation: an HTTP thread enqueues a
scoreordecidetask and the lane host's own engine thread runs the pass when the engine is idle. One resident model serves both streams.What changes
core/engine_api.zig:DecisionTask(.score/.decide),Decision(trunk + branches + counts + label/logit spans), and aDecidervtable entry on the lane host's backend (runonly;readyarrives in the follow-up).core/lane_host_decide.zig(new): the host-side queue. Callers append a task and wait onh.decided;steptakes the queue's head only when the engine is idle (no admitted or filling streams, nothing active on the lane core) and runs it on the engine thread.core/lane_host.zig/lane_rounds.zig:stepis driven from the host's idle points; a stale host fails the waiters instead of hanging them.native/qwen27_decide.zig(new): the 27B lane backend's pass. Ascoreprefills the prompt and readslabels' logits at the row after it (a prefill's.lasthead leaves them in logits row 0). Adecideprefills the shared trunk once, keeps its state, and restores it a branch at a time so every branch's rows stand on the same trunk state.native/qwen27_served.zig: the served engine registers the decider.core/lane_host_decide_test.zig(new): queue/wait/step/fail coverage (3 tests).Checks
zig build native -Dcpu=apple_m1 -Dversion=1.0.0passes clean on the head.zig build test test-golden -Dcpu=apple_m1: test-golden 0 unexpected differences (306/319 + 26/26 equal). Theteststep's device-dependent binaries fail on this machine while its GPU is serving a model — the same test binaries fail identically on the base head 30e5dcb (run for comparison), so the failure is inherited, not introduced by this diff.tools/zig/lean_check.py: 444 findings, as on the base (zero added).Receipts (serving validation,
receipts/DECIDE-RECEIPTS.mdin the work's record; Apple M3 Ultra, 256 GB, macOS 26.4.1, zig 0.17.0; local Ornith-1.5-122B A1-affine 4-bit checkpoint + local DFlash2 drafter, temperature 1.0, no-think; recorded at feature-branch 98a0ce4f, which this stack replays with comment-only changes)Known limits (deliberate)
Not run
No GPU jobs were run for this PR cut: the numbers above are cited from the recorded serving validation named in Receipts. CONTRIBUTING's performance receipts (bench_openai / bench_concurrent / cold prefill tables) are unchanged from #1125's run — this PR adds no decode-path work; they would be re-run at file time if the maintainer wants them on this exact head.