Repository navigation
Flash Next shared rounds priced from forwards timed at load; every stream's drafts in one command buffer - #9
Conversation
…ream's drafts in one command buffer The batch backend gave the lane core no costs, so each shared round filled up to 16 rows with drafts however few landed, and each stream's head ran in its own command buffers (a wait per draft). - batch_costs.zig times, on the last session before any stream holds it, a shared forward at 1..16 rows (the fastest of four, after six warm-up windows) and a chained head step (absorb plus 8 drafts against absorb plus 1, in one command buffer). Backend.facts reports them: shared_costs for the allocator, and a lone stream's window_costs and mtp_step_ms for its depth (FZ_LONE_UNPRICED=1: the old heuristic). - The lane core prices each draft past a stream's first at that step (Model.draft_step_ms; allocatePriced, with a unit test), and leaves those steps out of the round's overhead estimate. - Backend.draftTogether encodes every request's absorb and chained drafts into one command buffer (Session encodeDrafts / heldDrafts) and waits once; FZ_BATCH_MTP_SERIAL=1 keeps the per-stream path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cwschroeder
left a comment
There was a problem hiding this comment.
Thanks, @sethforprivacy. This looks right to me and I'd like to merge it after a few small changes:
batch_costs.zig:36-43always prefills 192 tokens and then runs 16-row windows without checking the session's capacity. With a small--context(e.g. 128, last session 160 tokens) calibration writes past the session's KV. Please bound the scratch prompt bys.m.capor skip calibration when it cannot fit, with a boundary test.native/flashnext_batch_host.zig:47always calibrates, soFZ_BATCH_MTP_SERIAL=1andFZ_LONE_UNPRICED=1restore the serving paths but not the startup path. One switch that skips calibration entirely (and keeps the old allocator) would give a full escape hatch if calibration ever fails on a machine.- The pricing test in
core/lanes/allocate.zig:124-134catches pricing that is ignored entirely, but still passes if the first draft is charged too. Could you add asymmetric streams, the first-draft exemption and the zero-cost case? - Style: the multi-line doc comments (
batch_costs.zig:1-3,session.zig:145-146) should be single lines per CONTRIBUTING.
Minor, not blocking: allocate.zig:30-43 still picks candidates by probability alone, so with unequal marginal costs it can miss a cheaper split (e.g. [1,1] beats [2,0] for one example we worked through). That's efficiency only, not acceptance; fine as a follow-up.
I have merged #8 and #7 and updated the branch to current main, so #9 now needs a rebase (a small conflict in backend.zig).
ae9f4dc to
3f24b00
Compare
|
Posted by Claude (Anthropic's AI assistant) on behalf of @sethforprivacy, as part of our local-inference tuning work on a Mac Studio M5 Ultra. @cwschroeder thanks for the reviews. First, the rebase you asked for: onto the branch's new tip
The two don't touch each other's paths. Sampled tokens are drawn from the target forward's logits, and Checks on the M5 Ultra (macOS 27.0, Zig 0.17.0,
I haven't rerun it live, so the served receipts in the description are from the pre-rebase head. Each branch still sits directly on the batching branch, so they can be reviewed and merged in any order. Your review is next, as separate commits on top: calibration bounded by |
… run, FZ_BATCH_UNPRICED=1 skips timing, fuller pricing tests Review fixes (cwschroeder on cwschroeder#9): - batch_costs.time prefills a 192-token scratch prompt and runs windows of up to MAXR rows past it, so a session needs batch_costs.ROWS (209) rows. timeCosts now checks fits(s.m.cap) first and serves unpriced (the allocator as before) when the session is smaller, as at --context 128; time itself refuses with SessionTooSmall. Boundary test at ROWS - 1 and ROWS. - FZ_BATCH_UNPRICED=1 skips the timing entirely: no costs, so the allocator, draft budgets and the lone depth rule are the unpriced ones from before this PR. - allocate.zig: tests for the first-draft exemption (a 100 ms step still takes a stream's first draft), asymmetric streams (priced [3, 1] against unpriced [3, 3]) and a zero step (priced equals allocate). - Doc comments in batch_costs.zig, session.zig and backend.zig cut to one line. - batch_costs.zig joins the core test root (tensorfold.zig), so its boundary test runs.
|
Posted by Claude (Anthropic's AI assistant) on behalf of @sethforprivacy, as part of our local-inference tuning work on a Mac Studio M5 Ultra. @cwschroeder your review points are in
I'll leave the cheaper-split allocator change for a follow-up, as you suggested. One more fix: M5 Ultra, Zig 0.17.0: |
|
@sethforprivacy thanks, Every pair of #9, #10 and #11 conflicts where both add code at the same spot ( |
Conflicts: backend.zig keeps both the think import and batch_costs, and both the lone handoff counter and #9's serial-drafts and costs fields; flashnext_batch_host.zig's startup line names both the drafts mode and the lone driver; tensorfold.zig's test root takes both backend.zig and batch_costs.zig.
…ssions Conflicts: backend.zig keeps the sized-session fields beside #9's serial-drafts and costs; session.zig keeps moveTo beside #9's encodeDrafts and heldDrafts; flashnext_batch_host.zig sets the sized sessions, then times the costs (on a session too small to time, #9 serves unpriced), and its startup line lists the sessions and the drafts mode; tensorfold.zig's test root takes both slots.zig and batch_costs.zig.
Posted by Claude (Anthropic's AI assistant) on behalf of @sethforprivacy, as part of our local-inference tuning work on a Mac Studio M5 Ultra.
As asked on ashhart#606: the measured round costs, alone against
feat/flashnext-continuous-batching. It was rebased onto04c8571(1.0.8 with #7 and #8), with the review fixes as a second commit. It needs neither of the other two PRs (sized sessions, the lone driver). The receipts below are from77ebc3f, before the rebase.What it does
batch_costs.zigtimes, at load, a shared forward at 1..16 rows and a chained head step, on the last session before any stream holds it. On this Mac: 10.3 / 12.1 / 15.1 / 21.0 / 32.2 ms at 1 / 2 / 4 / 8 / 16 rows, and 0.43 ms a chained step.batch_costs.ROWS(209) rows. On a smaller one (a--contextunder 177, since a session holds the window plus 32 rows) the host serves the shared rounds unpriced.FZ_BATCH_UNPRICED=1skips the timing altogether, and the allocator, draft budgets and lone depth are then the ones from before this PR.Backend.factshands them to the lane core:shared_costsfor the allocator, and a lone stream'swindow_costsandmtp_step_msfor its depth (FZ_LONE_UNPRICED=1keeps the heuristic).Model.draft_step_ms,allocatePricedwith a unit test) and leaves those steps out of the round's overhead estimate.draftTogetherruns every stream's absorb and chained drafts in one command buffer, with one wait (FZ_BATCH_MTP_SERIAL=1keeps the per-stream path).Receipts on this branch. Mac Studio M5 Ultra, 256 GB, macOS 27.0,
TensorFold/Qwen3.8-Flash-Next-MLX-6bit-MTP@45032a1.--parallel 4 --context 16384 --prompt-cache-gib 0, greedy, the base and this branch served in turn.77ebc3fbb176f18, prosea49a7c9f, edit3d5a75d5).On our combined build (this branch's change plus sampling, images, sized sessions and the lone driver;
add3da3onsethforprivacy:flashnext-lane-parity), with 16 sessions:zig fmt --checkpasses on the changed files.zig build testatdd86083passes 1522/1530 (8 skipped), including the new pricing and timing-boundary tests. One step fails:core.checkpoint_metal's split-checkpoint test, which fails the same way on04c8571here.🤖 Generated with Claude Code