Repository navigation
perf(rec_mlx): exact sub-4K prefix rewind, multi-branch LRU cache, and wired Metal residency - #50
Open
oniisanlabs wants to merge 1 commit into
Conversation
…d wired Metal residency
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.
Summary
Addresses four unaddressed bottlenecks in
rec_mlxprefill, prefix caching, and unified-memory residency on 48 GB Apple Silicon Macs while preserving 100% bit-exact (0 ULP/mx.array_equal) parity across all existing rollback, shared-KV, and grammar tests:1-Token-Exact Sub-Chunk Prefix Rewind (
rec_mlx/cache.py,rec_mlx/prefix.py):QATSession.prefill,PromptCache.checkpoint()is called before eachprefill_chunk_size(4,096-token) chunk. On the first chunk (begin = 0),self.prompt_cache.tokensis empty, so zero checkpoints exist below4,096tokens. Any two requests whose shared prefix (system prompt + tool schemas + repo context) is< 4,096tokens—or where a< 4,096-token prompt changes even its final user question—previously gotcheckpoint = Noneinselect_prefix()and fell back toreused = 0(cold prefill from scratch).FixedSlidingKVCachenow retains a bounded prompt history (_history_k/_history_v, capped atMAX_REWIND_TOKENS = 4096) and exposescan_rewind_to(target_pos)/rewind_to(target_pos). Whenprefix.checkpointsis empty (sub-4K prompts),select_prefix()rewinds all global and sliding layers to the exact common token offset (target = min(common, len(tokens) - 1)), lifting sub-4K prefix cache hit rates from0%to100%of the shared prefix with bit-exact KV restoration.Multi-Branch LRU
PrefixTreeCache& Batched Async KV Copies (rec_mlx/prefix.py,rec_mlx/cache.py,rec_mlx/quantized.py,rec_mlx/engine.py):PrefixTreeCache(max_branches=4)with compact per-layer global KV snapshots ([2, 4, L, 128],~28 KB/tokenacross all 14 global layers instead of copying the full1.78 GiBKVStoragebank) plus a pinnedbase_branchfor the shared system/tool preamble. Switching across files or sessions (Branch A -> Branch B -> Branch A) restores the full branch in< 10 ms(reused = len(branch_a)).copy_array_asyncandKVStorage.write_asyncsoPromptCache.fork(),PromptCache.checkpoint(), andQATSession.prefill's 35 sliding-window KV writes dispatch in a single batchedmx.evalinstead of 35–70 synchronous CPU→GPU barriers.Wired Metal Working Set, Bounded Cache Eviction & Idle Page-Residency Keep-Alive (
rec_mlx/engine.py,rec_mlx/scheduler.py):rec_mlxleft MLX's wired limit at0(mx.set_wired_limitwas never called) and calledmx.clear_cache()unconditionally after every request. On 48 GB Macs, pausing for 1–3 minutes between interactive agent turns allowed macOS to compress/page out the unwired22.9 GiBweight banks (RSS -> ~117 MB), causing a~2.6s–3.5spage-in stall on the next prefill even whencached_prompt_tokens > 0.Engine.__init__now pins the Metal working set viamx.set_wired_limit(min(max_recommended_working_set_size, memory - 8 GiB)), retains the2 GiBallocator cache across requests unlessmx.get_cache_memory() > 2 GiB, and runs a lightweighttouch_resident_pages()kernel (1word per16 KBpage across weight and KV banks in~3.5 ms) fromGenerator._next_requestevery3.0swhile idle.Memoized
ConstraintCompiler.toolsCompilation (rec_mlx/grammar.py):Constraintobjects by canonical JSON tool-schema key (maxsize = 64), eliminating~200–230 msof redundant XGrammarcompile_structural_tagCPU overhead on repeated tool-calling requests.Benchmarks (
RecursiveSI/flint-int4, Apple M5 Pro, 48 GB Unified Memory)Interactive Agent & Multi-File Switching Latency (
~1,800-token prompts,917-token shared system + tools prefix)mainPrefillA -> B)1,779(cached=917,new=862)~1,500–2,500 ms(reused=0)508.9 ms(reused=917)B -> A)1,839(cached=1,839,new=0)~1,500–2,500 ms(reused=0)7.0–11.7 ms(reused=1,839)1,840(cached=1,826,new=14)~1,500–2,500 ms(reused=0)68.4 ms(reused=1,826)1,592(cached=890,new=702)3,131 ms(OS page-out)~380–510 ms(wired)ConstraintCompiler.tools)~230 ms / req0.0 ms / req(memoized)Testing
cd engine && .venv/bin/pytest -v -rs: 71 passed in 15.26s (all 67 existing tests + 4 new unit tests):PrefixRollbackTests.test_subchunk_exact_rewind_without_checkpoints: verifies 1-token-exact sub-chunk rewind (common = 9 -> reused = 9) and bit-exact sliding-window KV parity (mx.array_equal) against cold prefill.PrefixRollbackTests.test_multi_branch_prefix_tree_cache_switch_and_restore: verifiesBranch A -> Branch B -> Branch Aswitching reuses the shared prefix onA -> Band restores 100% ofBranch AonB -> Awith bit-exact global and sliding KV parity.GrammarTests.test_tools_compilation_is_memoized: verifiesConstraintCompiler.toolsmemoizes compiled structural tags across equivalent tool schemas.SharedKVTests.test_write_async_batches_writes_and_touch_resident_pages: verifiesKVStorage.write_async+Engine.touch_resident_pages.uvx ruff check .&uvx ruff format --check .: All checks passed.