Repository navigation
[Engine] metal: the session no longer holds unused QSA K/V pools (6.4 GiB resident at 262K context) - #3536
Draft
oceanio wants to merge 2 commits into
Draft
[Engine] metal: the session no longer holds unused QSA K/V pools (6.4 GiB resident at 262K context)#3536oceanio wants to merge 2 commits into
oceanio wants to merge 2 commits into
Conversation
bsorensen110
added a commit
to bsorensen110/Strata
that referenced
this pull request
Oct 22, 2026
…cap; kv_plan slots==0 guard) — integ batch 19 [guard kept, integ's per-layer 'elastic' condition retained]
oceanio
force-pushed
the
pr/metal-session-kv-solo
branch
from
October 26, 2026 03:37
65befb3 to
145a62f
Compare
…keys (the decode window keeps its own) The Metal decode window (metal/src/engine/window.cpp) keeps its own int8 K/V and pooled indexer keys per QSA layer, sized by the same max cells, and never reads or writes the session's. On Metal a session buffer is one shared MTLBuffer in the residency set, resident in full whether touched or not, so those unused pools cost about 24.6 KB per context token. Under STRATA_GPU_METAL, kv_plan gives the session's QSA states no K/V slots and no pooled rows; their pointers stay null, and a conversation snapshot refuses such a state (n_slots == 0) instead of reading it. M5 Max, Qwen3.8-Flash-Next IQ3_S resident, phys_footprint 5 s after the engine is ready: 16.99 -> 10.61 GiB at --max-context 262144, 9.27 -> 7.68 GiB at 65536. Greedy tokens unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… was before the session K/V change Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
oceanio
force-pushed
the
pr/metal-session-kv-solo
branch
from
October 28, 2026 05:29
145a62f to
75a73bb
Compare
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.
Problem and result
On the Mac (Metal) engine the decode window (
metal/src/engine/window.cpp) keeps its own K/V and pooled indexer keys for the 12 QSA layers, sized by--max-context. It never reads or writes the session's QSA K/V pools, butkv_planstill sizes them for the whole context. On Metal a session buffer is one sharedMTLBufferin the residency set, so those pools are resident in full whether anything touches them or not: about 24.6 KB per context token.Measured on main 0.2.1.4 (phys_footprint of the engine 5 s after it is loaded, Qwen3.8-Flash-Next IQ3_S, M5 Max):
--max-contextThe same 6.4 GiB is still saved after 9 requests (24.91 → 18.53 GiB at 262K). Output and speed do not change (Validation).
Related discussion
None. This is a focused fix. Context: on Metal, parking and session save/restore are refused on main already (#2502). This PR does not change that.
Approach
src/core/layer.cpp,kv_plan: under#if defined(STRATA_GPU_METAL)a QSA state gets 0 K/V slots and 0 pooled indexer rows.qsa_state_initthen leaves the pool pointers andidx_poolednull, so they do not alias the page table orblock_posthat follow. The page table, the indexer tail and dead rows,block_posand the RoPE tables are carved as before.n_slots == 0, a conversation snapshot refuses the state (conversation_snapshot.cppvalid) instead of reading pools that are not there. On main, the memory guard already refuses parking and session save/restore on Metal (the log says so in both builds below), so nothing that works today goes through this path.docs/MAC.md: one line saying that the engine's own memory grows with--max-context, with the numbers before and after.unifdef -USTRATA_GPU_METAL src/core/layer.cppgives the same file before and after.Validation
Re-run on v0.2.7 (2026-10-28). Rebased onto main
5e8a4cca(v0.2.7) with no conflicts; the commits are the same lines. At the new tip75a73bbf, one round per context against v0.2.7's own build, the same procedure as below:--max-contextunifdef -USTRATA_GPU_METAL src/core/layer.cpp: identical to v0.2.7's.The rest of this section is the first run, on
9de04cd4.Environment: Apple M5 Max (128 GB), macOS 26.7.1, Apple clang 21.0.0. Both builds from the same checkout procedure (
cmake -S metal -B build-metal -G Ninja -DCMAKE_BUILD_TYPE=Release, targetstrata): main9de04cd4and this branch (9de04cd4+ 2 commits). Model: Qwen3.8-Flash-Next GSQ-RCO IQ3_S (SC117's abliterated build, two-file layout), resident,--expert-cache 64 --prefill auto --spec 2 --mtp <rt> --mmap-experts, through each tree's ownserve/server.py. Runs alternate main / PR / main / PR at each context, 30 s apart.Passed:
layer.cpp.unifdef -USTRATA_GPU_METAL src/core/layer.cpp: identical to main's (cmp).Failed:
None observed.
Not run:
layer.cppis compiled by every backend, so this stays a draft. Could someone build it on one of them? The change is entirely inside#if defined(STRATA_GPU_METAL), and the unifdef check above is my only evidence for the other backends.--max-contextabove 262K.Limits
🤖 Generated with Claude Code