Repository navigation
Flash Next: shared rounds by default on native Metal - #606
cwschroeder wants to merge 40 commits into
Conversation
Pack session windows into one target layer loop behind the shared lane core. Isolate recurrent and attention caches, normalize kept states with a fenced GPU copy, preserve draft and snapshot semantics, and keep the single-reply and TP drivers as defaults. Include CPU metadata contracts, checkpoint exactness/lifecycle gates and served concurrency comparisons.
5153d76 to
77ebc3f
Compare
|
Thank you, @cwschroeder. Running Flash Next's streams through the shared lane core is the right shape, and the receipts are careful (180 paired comparisons, the chained-MTP draft waits from 256 to 84). It isn't in 1.0.6 for two reasons. Concurrency on our engine is the default path for every family, not a flag, so |
|
Thanks, @ashhart. With 1.0.7 out I'd like to take you up on working this into the default path. A proposal for how to cut it, so each piece lands on its own:
Structured output and Three questions before I start:
|
|
A 256 GB M5 Ultra receipt with the release checkpoint, since the gate asks for one. Build. Serve. The same binary twice, one process at a time, greedy, thinking off, prompt cache off:
Decode,
The Exactness on the Receipts (ruler JSON, server logs, suite output): qwen38-flashnext-mlx6bit-m5-ultra/evidence/2026-10-11-pr606-parallel. The same repository has the release build's full ruler, prompt passes to 261k and the suite with thinking off, for the baseline. Happy to rerun on the default-path cut when it exists, and at larger windows if the slot budget moves. |
…ke the server past 70% of RAM, as GLM_LOAD_LIMIT_GB does for GLM
|
Follow-up: four sessions at the full 262k window, with one change to the slot budget. Why 65k refused. The batching host sizes slots from The change, one commit on your branch: With it,
Receipts: evidence/2026-10-11-pr606-parallel, the |
… token on the host from the head's logits The first token from the prompt pass's last row, each verify row from the window's logits, with the lane core's keyed sampler (top-k in one pass over bf16 keys, equal to qwen3_5's draw; families/flashnext/sample.zig, the same file as ashhart#719). Sampled replies are the same drafted and undrafted, alone and beside other streams; greedy replies are unchanged.
|
Sampling for the shared sessions, if you want it on this branch: What it does. The batching host stops reporting
Receipt, Mac Studio M5 Ultra (256 GB), macOS 27.0.1,
My fork serves this at |
|
Thanks, @BobClawblaw. The release-checkpoint receipt on your 256 GB Mac, plus the sampling and load-limit changes, give us concrete additions for #606. Please open two separate PRs against @ashhart, the default shared-lane path remains the target you set. I will keep the batch-MTP-chain experiment separate from that integration. The complementary hardware target here is M3 Ultra / 256 GB with the SIMD fallback in #679; I have not qualified these two additions on that hardware yet. |
|
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. A data point for making the shared path the default: as of today our production lane serves this branch's shared sessions. Before, it ran the Python build (0.6.5 plus our patches,
One request alone at the single-reply driver's speed. We gave the batch host a lone driver, LaneHost's
The replies are bit-identical across paths:
So The rest, measured as a sampled ladder. Temperature 0.7, ~200-token prompts, 400-token replies, aggregate tok/s and mean first token:
Happy to open any of these as PRs against |
|
@cwschroeder opened as asked, each with its one commit unchanged against
They don't depend on each other and merge cleanly in either order. #7's description has the greedy, seeded and mixed-stream receipts; #8's has the long-context ones. Neither has been run on M1 to M4. |
|
@sethforprivacy thanks for There, one request alone was erratic and mostly slow, 30 to 207 tok/s on identical requests. It traces to the residency set, not the lone driver. One request alone, 256 tokens, tok/s. Code and prose alternate, eight requests each run:
With the set off, the rest of the commit is a clear win here. One stream on the lone driver gives 207 / 136 tok/s against 175 / 128 on my current build. Its greedy and sampled replies equal the shared rounds' (4/4 greedy, 8/8 sampled across three settings before I stopped to debug). A fix might be to hold only the engine's own buffers and the tower, or to skip the set past a size. I haven't tried either. I can rerun any variant you want on this configuration. |
…ort of bf16 keys, not a full sort, the same draws top_k 0 (or above 256) went through the reference: every candidate in f64, sorted, then choose. Now a counting sort of the 65,536 bf16 keys gives choose's order (ids ascending within a key), top-p and min-p cut it as choose does with libm's exp and the same pairwise sum, and the keyed Gumbel draw runs in that order, passing over candidates whose noise is provably a nat short of the best score (one hash, no logs) and stopping once none can reach it. 248k vocabulary: 13.7-16.8 ms a row to 0.33-0.65 ms; the host test now also compares top_k 0, 300 and the whole row, top-p 1, min-p and wide logits.
|
@BobClawblaw, #7 and #8 are received. I am reviewing them separately, including the seeded sampler and the memory-limit arithmetic. Your original commits and hardware receipts will stay attributed to you; M3 qualification is still outstanding. @sethforprivacy, please open separate PRs against Bob's large-window counterexample should be part of the residency gate: both first-token latency after idle and sustained decode at the larger working set. His measurements isolate the residency setting in that configuration; the suggested 75% threshold is still a hypothesis. Please link that receipt from #732 when addressing it. For the handoff/session PRs, please retain the greedy and seeded solo/concurrent comparisons, the mid-reply handoff, and the growing-session cases in their descriptions. |
…dget is what the sessions leave under it The slot checks reserved a given cache against the selected limit while fit.budget still planned the cache against 70% of RAM, so an automatic cache could take the server past the limit and a raised limit did not raise a given cache's own gate. cache_fit.budgetUnder plans the cache under the same ceiling (fitUnder; budget and fit keep 70% for the other hosts), with a test for the automatic and the given budget under a limit. The recipe doc, the limit's comment and the OverMemoryLimit message name the contract.
…ng set On a 256 GB M5 Ultra a 195.8 GiB set (four 262K sessions) slowed the GPU-side rounds about threefold, alone or under --parallel 1, while sets under the working set held them at speed (BobClawblaw on ashhart#606). The host now holds the set only while its buffers, the prompt cache's budget and a 2 GiB margin fit recommendedMaxWorkingSetSize, and logs which way it went with the numbers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s working set On a 256 GB M5 Ultra a 195.8 GiB set (four 262K sessions) slowed the GPU-side rounds about threefold, while sets under the working set held them at speed (BobClawblaw on ashhart#606). Both hosts now hold the set only while its buffers, the prompt cache's budget and a 2 GiB margin fit recommendedMaxWorkingSetSize, and log which way it went. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… an error; the lane core sweeps streams a release ends; harness on Backend.init's capacities Review fixes (cwschroeder on #10): - slots.resumePaused: LaneMemory keeps a paused stream waiting; any other move failure (GpuFailed, StateNotSettled, ...) ends it through Backend.failed: slots.endPaused marks it finished with an error the host reports as a failed request, and its session frees at once. Fault-injection test: the last larger session frees, one paused stream's move fails on the GPU (ended), one finds none free (waits), one moves. - The lane core: a release can now end another stream, so Engine.sweep releases finished live streams until a pass ends none, from step's prune and from discard. Without it a stream ended during another's release could stay in the live list after the host freed its job (failAll, cancels and drop skip finished streams). Tests on the fake target, which gains a release hook: a stream ended by another's release in a step, and by a discard, leaves the live list. - zig/tests/flashnext_batch.zig: Backend.init takes the sessions' capacities (refusing more than MAXR as init did), and its two generateFrom calls pass the thinking budget (.{}) that 1.0.8 added, which already broke the harness on 04c8571.
|
Hello @cwschroeder and @sethforprivacy, Thank you for pricing shared Flash Next rounds from forwards timed at load, and for running every stream's drafts in one command buffer. The default server still serves one reply at a time. openFlashNext opens the batch host only when --parallel is fixed and above 1 (metal.zig:317). auto and --parallel 1 still take flashnext.open (metal.zig:331). Concurrent requests need to share lane rounds on that default path, with drafts on. FZ_BATCH_MTP_SERIAL=1 can stay the opt-out on the batch host (backend.zig:310). A push that serves shared rounds from the default flags is the update we need. Thank you again. |
|
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. @ashhart thanks, understood: shared rounds from the default flags, drafts on, with
One question: should an explicit @cwschroeder, #9 is merged, and #11 and #10 are mergeable on your branch. Do you want to make the routing change in #606 after they land, or should we open it against your branch as a fourth PR? We can do it either way. |
|
@ashhart @sethforprivacy I will take the default-routing change and its routing tests in #606. I am checking #10's new migration-failure and lane-cleanup changes and #11's merge with #9, then integrating against current main before pushing the combined update. For For explicit The new fork heads have green Linux host/format checks. Growing-session model parity on the corrected head is still outstanding, and the Mac split-checkpoint test remains an open validation issue, even though it reproduces on the previous base. I will keep those gates explicit. |
|
Thank you, @cwschroeder, for pinning down the explicit Explicit The split-checkpoint test passes in the 1.0.12 candidate's Mac suite. “checkpoint tensor text.weight stays in its file” is expected: the test deliberately checks Posted by Ash's AI Assistant Synthena |
|
@ashhart @sethforprivacy the combined update is pushed at The low-memory fallback transfers the already loaded engine to the single-reply host and retains its footprint limit. It does not load another model copy. I also added host-level regressions for a cancelled stream failing a paused peer, prefill cancellation, cleanup with a failing trace allocator, and a shared keep error after one stream has finished. The unfixed cancellation cases time out; the unfixed keep case aborts. The corrected cases pass and leave no live streams or duplicate terminal events. On an M5 Max, macOS 26.6.2, Zig 0.17.0: native build, whole-tree formatting, host test build, server goldens and Cortheon fuzz checks pass. The lean comparison is 527 inherited findings on each side, zero new. The expected DeferredTensor checkpoint test passes on this current-main integration too. Current-head remote CI is running; formatting has passed. I kept #606 as a draft for the remaining gates. For your next available lane window, please pin this combined head for default-flags solo/concurrent token parity, lone/shared handoff and growing-session migration. Earlier speed receipts remain attached to their older commits; we still need the new head's model and performance receipts. |
|
@ashhart @sethforprivacy I added the served-gate coverage for the default route at The recipe now runs Eight model-free regressions pass, including repeated auto blocks, retained failure receipts and compatibility with the MTP-chain helper. Astra caught a log-interval boundary issue; the new negative test failed before the fix and passes after it. The regressions now run in the No GPU workflow. Local whole-tree Zig tests, native build, formatting, server goldens and fuzz checks pass; lean has zero new findings. For the next hardware window, please use this head and the updated recipe. No model or speed receipt is claimed for it yet; #606 remains a draft. |
…ng set On a 256 GB M5 Ultra a 195.8 GiB set (four 262K sessions) slowed the GPU-side rounds about threefold, alone or under --parallel 1, while sets under the working set held them at speed (BobClawblaw on #606). The host now holds the set only while its buffers, the prompt cache's budget and a 2 GiB margin fit recommendedMaxWorkingSetSize, and logs which way it went with the numbers.
15d9bec to
8cee8ef
Compare
|
@ashhart @sethforprivacy Draft update at The new main samples single-request replies on the GPU. Shared windows now use that same Shared residency includes all fixed session buffers and the hidden plane, plus later snapshots. Its 70-percent limit remains separate from the configured load cap. The integration also retains the new legacy Learn/disk/sampling paths and ports sweep-before-free cleanup to the extracted host end paths. Removing that sweep breaks the existing failed-keep CPU regression on this tree. Local Zig 0.17.0 host/native/kernel/golden/fuzz validation passes 219/219 build steps; the exact-commit For the next exclusive GPU window, the compiled harness adds: zig-out/bin/tf-flashnext-batch --sampling-only
FZ_SAMPLE=1 FZ_N=128 zig-out/bin/tf-flashnext-batch MODEL_DIR /tmp/flashnext-prompts/*.jsonThe first uses synthetic logits and independent expected positions, including untouched greedy/spare rows. The second checks fixed seeds across legacy/shared, drafts, cancellation, slot reuse and prefix restoration. Neither has run yet. Served parity, migration/lone handoff, idle recovery and performance remain open; the PR stays a draft. |
|
@ashhart @sethforprivacy small follow-up at The full local host/kernel/golden/fuzz checks, eight served-helper regressions, whole-tree formatting and the pinned native build pass. Lean has zero new findings. Astra found no material issue in the transfer or error ownership. Start-time savings remain unmeasured. All four upstream checks and all six fork checks now pass on |
…the memory limit remedies An explicit --parallel N above one with --learn served one reply at a time without a word, while the same request with --speed-up refused. Both refuse now, from one tested predicate. The OverMemoryLimit message names its remedies again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@ashhart @sethforprivacy M3 Ultra receipt for Mac Studio M3 Ultra, 256 GB, macOS 27.0.1, Zig 0.17.0,
One observation: on the shared host the lone path's first token after idle is about 0.11 s slower than the single-reply host on the same Mac (0.25 s vs 0.14 s). Not a blocker, but worth a look before Not covered here: session growth under load and long-reply handoffs. Seth's M5 Ultra receipt covers those on |
|
@ashhart @sethforprivacy two questions from a review pass on What Prompt cache under
Two small fixes follow on the branch as
|
|
Thank you, @cwschroeder, the two 15:31 questions and M3 Ultra receipt make the default-route tradeoff concrete. We will reserve the single-host default prompt cache first when --prompt-cache-gib is unset, then size sessions from the remaining room and print both budgets at startup. The full-window fallback remains the warmed single host when fewer than two sessions fit. We checked 5164380: flashnext_batch_host.zig:63 currently reserves zero for the unset case. We can fold in the decided reservation/logging fix, default/auto and low-memory controls, then run the final growing-session, long-handoff and seeded cache/draft comparisons. The cache choice is settled; your existing served and idle receipts remain useful for that integration. Posted by Ash's AI Assistant Synthena |
Flash Next currently queues requests behind its single-reply host with the default flags. This branch routes default
--parallel autoto shared lane rounds with drafting, choosing up to 16 sessions from the available memory. A lone greedy request uses GPU-side rounds and yields when another request arrives. If fewer than two sessions fit, the warmed engine transfers to the single-reply host without reloading or warming again.Explicit
--parallel 1, learning and paired serving retain their supported paths. Shared sessions remain single-Mac.Candidate
ef85c8b0b8402fc5186a8123fcf2a4ef87042acd, upstream base46a08c18608153bfc566703a88acc883bc6140f4:Local validation on M5 Max/macOS 26.6.2 (25G83), Zig 0.17.0:
zig build native test-native tf-flashnext-batch -Dcpu=apple_m1 --summary all: 51/51 steps, 29 tests passed, one skipped. Restoring the old residency rule makes both new CPU regressions fail.Native SHA256:
568500c244ee1cbfb21e1bee4841177cc325eaa746a49e1355718be9bd04e6bb.External hardware receipt on the preceding
d0f61385head: Seth reports model parity, two streams outgrowing their sessions, eight concurrent streams and lone-driver handoffs on an M5 Ultra/256 GB, using the 6-bit checkpoint at45032a1. That run exposed the residency-limit mismatch fixed here. The new residency policy still needs a hardware retest.This PR remains a draft. All four upstream checks and all eight fork checks pass on this head. The compiled synthetic
--sampling-onlyand seededFZ_SAMPLE=1model gates have not run. Fresh idle recovery, cold prefill and performance receipts remain open; the external results above apply to their pinned earlier head. No deployment is claimed.