Repository navigation
prefix cache: caching on = caching off at temperature 0 on SSM-hybrid models (fixes #158) - #163
maplepaladin73 wants to merge 14 commits into
Conversation
|
All contributors have signed the CLA. Thank you! |
52af812 to
29b77dd
Compare
|
Thanks. That's exactly the shape we asked for. Test status, plus review notes from the AMD/UMA side. Runs
Review notes
|
|
GB10 battery, first results: the default grid passes on NVFP4. Reiner job 693: main 4405bce +
I'll post 696 here when it lands. On defaults: this result is with F=0, so reuse needs prompts ≥ 4096 tokens. That's fine for agentic coding histories; short tool preambles get nothing (note 2 in my review). |
|
GB10 agentic replay A/B: exact, but 29% slower at the default grid. Reiner job 697, 2026-10-04. Flash-Next NVFP4, job 655's recipe (prefix caching on, MTP), the full 2.5 h leg (20 trajectories / 1,007 turns, temp 0, seed 42, single stream), harness on reiner.
Attribution runs next on reiner (each is the same replay on this PR's build):
Together with the battery results, they decide the shape we'd want to merge. Exactness is the requirement; at the defaults its cost is a 1.29× wall on the agentic leg. |
|
Battery complete on GB10 NVFP4: caching on = off on every arm, including the fine grid and MTP. Reiner jobs 693 + 698, bin
|
|
Thanks, Azeez. Going through the review notes in order:
On the 1.29× wall: agreed, exactness alone isn't enough to merge. Once 701 shows how the time splits, I'll work on whichever side dominates (restore at the previous prompt's tail when that point is itself a cut, a smaller G, or an incremental drafter rebuild). Also, for the record: the commits were re-authored for the CLA, so SHAs cited on #158 changed. The trees are identical. |
|
Snapshot-slot sweep at C=36 (reiner 702), for the "redo the winning arm on this grid" follow-up. Result: no winner, so nothing to redo. Setup: Qwen3.6-35B-A3B NVFP4, MLPerf agentic load, 32 slots, 30 min per arm, prefix cache on,
More snapshots cut full re-prefills from 28% to 21%. But the slots come out of the KV budget (−37–41% KV tokens), and throughput drops about 16%. The slot count isn't the reuse lever at this load. The remaining 21% are turns whose history fell between snapshots or got evicted. @maplepaladin73, your 701 isolation:
|
|
strix (gfx1151, Linux) battery on Qwen3.8-27B-NVFP4 — #1639302533 vs plain-main control. Both arms same battery (12 cases × 2 rounds, r2 hits the warm path), jobs 175 (PR) and 178 (baseline):
Reads:
KV blocks moved 5907→6114 (baseline→PR) on the same recipe. Faults zero both sides. Out dirs: strix |
|
Thanks for both batteries. A correction and a question. Correction: the 4096-token restorable prefix on strix isn't the 512 MB anchor budget. It's the grid. With G=4096, a warm turn can only restore at a cut, and the deepest cut below a 6240-token prompt is 4096. The MTP anchor budget only sizes the host-side drafter rows and doesn't affect how much prefix is restored. The trade-off this exposes: a warm turn re-prefills up to G tokens past the last cut, by design, because restoring anywhere else changes the pass layout and reintroduces #158. Restoring at the previous turn's tail would only help when that tail happens to be a cut, which is rare, so I'm not pursuing it. The real options are:
Which would you accept? I lean to (2) given 701 and 702. On |
|
GB10 battery at the moved head (1d33249, job 717, 2026-10-06): warm path works and is 2.6-7.7x faster, but cache-on identity is 12-13/24 — worse than strix-27B's 21/24 and winbox's 24/24. Recipe: 4405bce + pull/163/head (fetched by refspec — head moved past 9302533 to 1d33249 during the day). Flash-Next NVFP4, util 0.88 seq 34000 / MTP pair util 0.90 seq 8192, 126 rules + 12 tool-cases, r1/r2.
Diffs concentrate on r2 tool turns (Tokyo r1+r2, Ottawa r2, Lima r2) — same r2-skew signature as the strix-27B battery. Cross-host picture now:
|
|
Correction: the GB10 battery in the comment above (job 717, '12-13/24') did not test this PR. The job fetched two refspecs ( Confirmation: reiner 723 ran the same battery on main alone and reproduced 717 exactly (LONG 12/24, LONG-MTP 13/24, identical diff list). So 12-13/24 is main's GB10 baseline, not a regression from this PR, and this PR has not yet been measured on GB10. That's consistent with what the PR targets: on main, cache-on saves a mid-prompt SSM checkpoint (token 6224 of a 6254-token prompt) and splits the prefill there, while cache-off runs it as one chunk. Even cold r1 requests differ, which is the layout mismatch Queued on reiner:
Results here when they land. 🤖 Generated with Claude Code |
|
GB10 battery with this PR actually merged: PASS 24/24 (reiner 726). Tree
For comparison, main (723) is 12/24 and 13/24 on the same battery, and is boot-to-boot deterministic: 724's controls reproduced 717 at 24/24. So the cache-on gap is main's, and this PR closes it on GB10. Warm restores land at the 4096 grid cut (cached r2 = 4096), and the warm round trip is ~1.6x faster. 724 also ruled out a chunk-boundary explanation for main's gap. On main, cache-on runs the cold 6254-token prompt as one chunk and captures the 6224 checkpoint inside the pass, so it never splits the compute. Forcing cache-off to split there matched cache-on worse (r1 6/12 vs 9/12). That's consistent with main's cache-on path using a different GDN kernel (what GB10 adds a PASS alongside strix and winbox. #157 and #149 are blocked on this PR. 🤖 Generated with Claude Code |
|
Thanks, and good catch on the FETCH_HEAD mix-up. 726 at 24/24 on both arms, with main at 12–13/24 on the same battery, is the clean GB10 result we were missing. Given 726, I'd revise my lean from yesterday. On this battery the grid is a net win (warm 1.6× faster), so the cost only shows on the long agentic replay (701). I'd keep the grid on by default and take 701's remaining gap as a follow-up, unless you'd rather gate it before merge. Since #157 and #149 are blocked on this, tell me which you prefer and I'll make the change today. |
|
@maplepaladin73 thanks. Before we pick a default, I'm not yet sure the grid pays for itself on the long agentic leg. Here's 701 on GB10 (Flash-Next, 1007-turn replay, prefix caching on, same box; #163 at
The score difference is within noise for one run of this leg (+0.013, a few turns out of 1006), so I wouldn't count it as an accuracy win. The cost is not noise: 28% more wall time and ~2.7x TTFT. From the serve logs, the more frequent restores don't help much. Main mostly resumes from the end-of-turn snapshot and replays ~470 tokens. #163 restores at the last 4096 cut and replays everything after it, ~2.4k tokens per turn on average, so prefill per turn is ~5x main's. That's the price of warm == cold layout. The correctness gain is real (24/24 vs main's 12-13/24 on the battery, the #158 class), but on this workload it costs a lot. Questions before merge:
The same A/B is running now at the current head Separately, two more GB10 PASSes at
🤖 Generated with Claude Code |
|
Thanks, that settles what to measure.
|
|
@maplepaladin73 the A/B at the current head landed, and it's worse than 701. Flash-Next NVFP4, 1007-turn agentic replay, prefix caching on, same harness yaml and recipe:
The two arms ran on different GB10s. spark2 was not slower on the battery (warm r2 4.5 s vs reiner's 4.8 s), and the replay-per-turn figure is identical to 701's same-box run, so this is the cut-only restore, not the box. So at the default coarse grid the exactness costs about half again the wall time on our flagship cache-on workload. I don't think it should merge as default-on in this form. My questions from above stand, especially the fine-grid setting and anything else that keeps the end-of-turn resume cheap while staying exact. Tell me the knobs and I'll run them against the same arm A. 🤖 Generated with Claude Code |
|
Agreed, it shouldn't merge default-on at G=4096. The knobs to run against arm A are in my comment just above yours: |
|
@maplepaladin73 grid sweep on the same 1007-turn leg (Flash-Next NVFP4, prefix caching on,
All 1007/1007, 0 failed, 0 serve errors. Scores are within noise of main. G=1024 recovers most of the cost (+53% → +11%). G=512 is slower than 1024 despite less replay, probably because snapshotting every 512 tokens costs more during prefill. But it ran on the other box, so I'm swapping boxes now (512 on reiner, 1024 on spark2) to separate grid from box. If 1024 holds same-box, I'd propose G=1024 as the default (or at least for the agentic recipes) and merge on that basis. What's the snapshot-memory cost per cut at 1024, and does 🤖 Generated with Claude Code |
|
Thanks, that's the number we needed. +11% at G=1024 with replay at ~2× main is about what the grid should cost.
Pushed: |
|
@maplepaladin73 the box-swapped sweep is in, and it corrects my previous comment. spark2 runs this leg ~15–18% slower than reiner, so the "+53%" compared a spark2 arm against a reiner main and overstated the cost. Wall time, Flash-Next 1007-turn replay,
All 1007/1007, 0 failed, 0 serve errors. Scores 0.5096–0.5175, all within noise of main's 0.5053. What holds:
I'd still propose G=1024 (or 512) as the default; the remaining ~+8–10% is the price of exact warm == cold. Pending your answer on snapshot memory per cut and whether 🤖 Generated with Claude Code |
|
GB10 prefix-cache battery — attribution resolved (better than expected) Same 12-case ×2-round-trip LONG/LONG-MTP battery across three builds (Devin-queue jobs 723/726/730 + earlier 717):
Net on GB10: the anchor budget converts a pre-existing correctness drift into a clean PASS. Combined with the strix-hip 27B battery (same pre-existing-drift story, hits 12→23) and ST-995 on the #163 build (85.13/78.30, in noise), this stack is correctness-clean on both platforms. |
|
@maplepaladin73 thanks for The spark2 box control for the last table landed. main on spark2 = 5211 s (2018, 1007/1007, score 0.5089). So the same-box picture is:
So 1024 is the right default for the exact mode, with a +3–11% cost depending on box. One nit on the new head: the Validating
🤖 Generated with Claude Code |
|
@maplepaladin73 first GB10 battery at
At So which reference does
If (b), the flag needs to keep the cold path on main's layout. If (a), the help text should say so, and an exact-mode user's outputs will differ from a default-mode user's at temp 0. To separate "wrong reference" from "G=1024 breaks it", reiner 739 is running OFF+flag vs ON+flag at G=1024 and at 🤖 Generated with Claude Code |
#178) chunk_fwd_o's output WMMA loaded uc rows kb+k for all 64 rows of a chunk, but chunk_delta_h_vfused writes uc only for rows < ce. On the last partial chunk the remaining rows are whatever an earlier pass or request left in gdn_fla_scratch. kq is 0 for those rows, but 0 x NaN/Inf is NaN, so a non-finite stale bit pattern poisons the chunk's output. Mask the rows to 0 at the B-fragment load. With zeroed scratch the unmasked kernel already read 0 there, so the masked kernel computes exactly what the unmasked one does on clean scratch. The GB10 kernel already zeroes these rows when it stages uc into smem. Adds the gdn_fla_scratch_garbage GPU example (cuda,gpu-examples): the production ops::gdn_prefill_fla pipeline at Qwen3.8-27B GDN dims must give byte-identical output and final h_state for every scratch fill. Removes one strix-only source of #158 (cold output depending on request history). Bit-exact cache-on == cache-off additionally needs #163's fixed prefill grid, and a separate request-order dependence of cold output remains on main, identical with and without this change. Evidence (AzeezStrix, gfx1151, Qwen3.8-27B NVFP4, 2026-10-16, single runs; details on PR #178): - out/mt-178-20261016T0622Z: unmasked control fails on NaN/Inf/rand fills, localized to uc; masked passes every fill at T in {107,179,1075}. - out/bat-178on7ff-27b-20261016T0651Z (7ffd183 vs 7ffd183 + this diff, no spec, temp 0): 0 faults on all arms; the mask changes 3-4/24 end-of-prefill SSM states per order and 0/24 responses.
271b34e to
80d1429
Compare
|
Rebased onto main Conflicts with #174, and how they were resolved:
One behaviour change to flag: fix 7 is now off under multi-rank (TP/EP), the same as fix 2 already was. The drafter rows live on the head only, so a head-side decline after Checked on x86 without GPU kernels: |
|
Hardware check of the rebased branch ( Setup: engine built from this branch (main
The warm requests did use the cache:
Not covered: fixes 2 and 7 at TP=2 (off there by design for now), single-GPU, and the FP4-Local checkpoint (main can't load its scales yet). |
|
Data point for this PR from today, on FP4-Local ( Probe: 15 prompts (chat / tool / agent, 144–5,440 tokens), each sent twice at temp 0. Score = cold == warm output (content, reasoning, tool name + args; tool-call ids excluded):
So on this checkpoint, any restore that replays a gap with a different pass layout changes the greedy answer on most prompts, and that already happens by default on every unaligned retry. That matches this PR's "cache-on ≠ cache-off" premise, and it is why I kept the shallower-snapshot fallback local rather than proposing it as a default. 🤖 Generated with Claude Code |
80d1429 to
eedd503
Compare
|
Thanks @AzeezIsh. It's useful to have FP4-Local numbers now that #195 loads it. Your runs are on main in default mode, so #163 isn't in them. Here is how each row maps to the PR:
#207 and #208. Neither overlaps with #163 in effect. Under the flag with default switches, the deepest anchor is always strictly below the prompt end, because the grid saves no leaf or decode snapshot. (The opt-in finish-leaf switches aren't gated on the grid yet; I have a small gate for that too.) So the exact full-prompt hit that #207 resumes from never happens there, and neither does the Merging #163 with #207 and #208 gives no textual conflicts. I also tried #163 on top of #210 ( Two notes on #210 under
On the line cap: after #163 + #210, Two ways to push. The moved block contains #210's three lines, so the split commit is slightly different depending on whether #210 is in. I have the split and the follow-ups (the two guard commits and the finish-leaf gate) ready in two forms:
The three follow-up commits are the same patches in both ( Rebased onto current main
Not yet run on GB10 hardware at this head. Next test. Your 15-prompt probe on FP4-Local with main If it helps, I'm happy to push the guards and the This comment was prepared with AI assistance (OpenClaw agent) and reviewed by me. |
|
@AzeezIsh a question on order. This PR is still mergeable on |
|
@maplepaladin73 #198 is merged ( |
…ix 1, ATLAS_PREFILL_GRID=0 restores old) (cherry picked from commit 0f21eb1abb09989877fbeb639238277346f081ae)
…d) privately (R11 fix 2, ATLAS_PREFIX_KV_CAP=0 restores old) (cherry picked from commit df1ee32cb681e84b605690a532d1534132937d6f)
… F below G (ATLAS_PREFILL_GRID_FINE); planner tests state the warm=cold-suffix obligation (cherry picked from commit 3b281547340a0c35cd69d285138aae6504f33ccb)
…nchor; no checkpoint saves with caching off (R11 fix 4, ATLAS_NORM_ALL_CHUNKS=1 restores old) (cherry picked from commit 5e15f07f3414be2a3efba24288db54d56448cbdb)
…ix 5, ATLAS_PREFILL_CHUNK_ALIGN=0 restores old) (cherry picked from commit 97ff52be5a2b232ea6ecb31eb44e0ac701851447)
…d WY4 exact-replay path (R11 fix 6, ATLAS_GRID_EXACT_REPLAY=1 restores old) (cherry picked from commit 2673957feded298c4a3975aa1b4ad87a06d6ef9e)
…ckpoint, rebuild the cold whole-prompt drafter prefill on a warm hit, decline hits without them (R11 fix 7, ATLAS_MTP_ANCHOR=0 restores carry) (cherry picked from commit 2ee8466aef2338d2db02565fcb4ffb3bb410359d)
…efix_lookup under the 500-LoC cap (no behaviour change)
…x-5 chunk alignment into Cuts::align_chunk with a test for --max-prefill-tokens below the grid
…cache-on keeps main's layout The prefill grid (and fixes 2 and 4, which only make sense with it) now run only with --exact-prefix-cache or an explicit ATLAS_PREFILL_GRID. On GB10 Flash-Next the default G=4096 grid cost +28-53% wall on the 1007-turn agentic replay (warm turns re-prefill ~2.4k tokens vs main's ~470), so it should not be on silently.
…play: +11% wall vs main at 1024, +53% at 4096)
…y default, WY4 fallback), not WY4
…as-Inf#174) Moves the grid split, the grid cache cap and the KV cap (fix 2) into meta/grid.rs and meta/kv_cap.rs, and shortens one doc comment. The KV cap's re-acquire now goes through lookup_whole_blocks, which main's guard test requires; snap_tok is block-aligned there, so the match is the same.
eedd503 to
c171350
Compare
|
Rebased onto main
|
Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Fixes #158. With prefix caching on, hybrid-SSM models (Flash-Next, Qwen3.8-27B) could give different temperature-0 answers than with caching off. This PR adds an exact mode that makes caching on and caching off identical when both use the flag. It's opt-in:
--exact-prefix-cache(default off), because exactness costs warm-turn throughput on long agentic replays. Exact-mode outputs differ from default-mode outputs at temperature 0, because the grid changes the cold layout too.Base:
main4405bce3. One commit per fix, as requested on #158.Default behaviour
main. The grid is off, and fixes 1, 2, 4, 5, 6 and 7 are inactive (all gated on the grid).--exact-prefix-cache: prefill passes and SSM checkpoints sit on a fixed 1024-token grid, so a warm turn replays exactly the passes a cold run does.ATLAS_PREFILL_GRID=Nsets the grid explicitly, flag or not (0= off).Commits
ATLAS_PREFILL_GRID=0ATLAS_PREFILL_GRID_FINE=<F>(opt-in; only affects prompts shorter than G)[snap, matched)privatelyATLAS_PREFIX_KV_CAP=0ATLAS_NORM_ALL_CHUNKS=1Cuts::align_chunk, tested for--max-prefill-tokensbelow G)ATLAS_PREFILL_CHUNK_ALIGN=0gdn_exact_replaypathATLAS_GRID_EXACT_REPLAY=1ATLAS_MTP_ANCHOR=0--exact-prefix-cacheopt-in (48a6d04a); flag grid 1024 (b1659003)Memory: a sequence keeps at most the last two cuts below its prompt end as SSM checkpoints, each the size of a main Marconi snapshot, so G changes where snapshots sit, not how many. Fix 7 adds host RAM only: ~5 KiB per anchored token, capped by
ATLAS_MTP_ANCHOR_MAX_MB(default 512, kept small for UMA hosts). An evicted anchor declines the hit (full recompute), never a wrong answer.Measured (@AzeezIsh's runs, caching on vs off, temp 0)
Throughput, GB10 Flash-Next, 1007-turn agentic replay, caching on (all 1007/1007, 0 errors, scores within noise of main):
Same-box correction (spark2 runs this leg ~15–18% slower than reiner; earlier cross-box “+53%” overstated it):
At
b1659003(merged onto1d3324989):The flag stays opt-in. G=1024 is the exact-mode grid.
Earlier measurements on our EXL3 build (GB10, Flash-Next EXL3 3.05 bpw, one lane, G=4096): 26/26 identical on a tool battery, identical md5 at 30K and 100K cold vs warm, follow-up TTFT 1.0 s at 30K vs 25.8 s cold.
Not verified
MTP on vs off is not a valid equality check on this engine (separate verify-kernel numerics, #130).
This PR was prepared with AI assistance (OpenClaw agent) and reviewed by me.