fix(engine): stop making the unused vision encoder resident (852 MB, every tier) - #176
Merged
Merged
Conversation
…every tier) The checkpoint ships a vision encoder. qwisp is a text-only engine and never reads one of those tensors — but WeightStore.residentNonExperts() filtered only on `.switch_mlp.`, so all of `vision_tower.*` was handed to MLX.eval() and made resident on every run of every tier. 333 tensors, 852 MB. Measured, byte-exact against the prediction: 8GB tier active 7,071 -> 6,219 MB 16GB tier active 11,391 -> 10,539 MB which confirms the decomposition those numbers were predicted from: active = arena(40 x budgetC x 1.6875) + language_model non-expert 1,325 + scratch 574. Filtered at load, not at eval: keeping the MLXArray alive holds its mmap slice, so dropping it from the eval list alone would not have returned the bytes. residentAll() becomes correct for free, since the tensors are simply not in the store. Why this matters beyond the byte count: - The 8GB tier needs ~761 MB to hold coverage at or above the ~103 expert per-layer footprint, which is the LOOPY-safety line. 852 MB is more than the entire deficit, and it costs nothing in quality. The session had been measuring whether non-expert weights survive 2-bit to find those bytes — they do not (GDN loses 31 points of teacher-forced agreement below its own round-trip floor), and quantizing every non-expert tensor would only have freed 589 MB anyway. The bytes were already there, being wasted. - On 16GB the #169 OOM cliff was measured at two experts per layer, about 150 MB. 852 MB is 5.7x that margin. RAWTESTS 100/100 (new locked test asserts the predicate keeps engine tensors and drops vision ones; a regression silently costs 852 MB again), BENCHBATCHTEST PASS, COMPTEST 97/97, CBGUARD PASS. Co-Authored-By: Claude <noreply@anthropic.com>
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.
The checkpoint ships a vision encoder. qwisp is a text-only engine and never reads one of those tensors — but
WeightStore.residentNonExperts()filtered only on.switch_mlp., so all ofvision_tower.*went toMLX.eval()and was made resident on every run, on every tier. 333 tensors, 852 MB.Measured
Byte-exact against the prediction made before running it:
Which confirms the decomposition those predictions came from:
Output unchanged (it was never read).
Filtered at load, not at eval
Keeping the
MLXArrayalive holds its mmap slice, so removing it from the eval list alone would not have returned the bytes.residentAll()becomes correct for free — the tensors are simply not in the store.Why this matters beyond the byte count
!) output above ~2–6K prompt tokens #169 OOM cliff was measured at two experts per layer, ~150 MB (coverage 228 passes, 230 fails). 852 MB is 5.7x that margin.Not claimed here
Whether this makes the 8GB tier actually fit a real 8GB Mac, and whether it moves the #169 threshold, are follow-up measurements — both need the wired-limit emulation and are not in this PR. This PR is the byte reclamation and its proof.
Gates
RAWTESTS 100/100 · BENCHBATCHTEST PASS · COMPTEST 97/97 · CBGUARD PASS
The new locked test asserts the predicate keeps engine tensors (embed, lm_head, switch_mlp, linear_attn, mlp.gate) and drops vision ones — a regression would silently cost 852 MB again. Runs without a model or GPU.
🤖 Generated with Claude Code