MLX port of #107: incremental decode (Step 1) + fused DFlash spec-decode engine (Step 2)#109
Draft
FluffyAIcode wants to merge 10 commits into
Draft
MLX port of #107: incremental decode (Step 1) + fused DFlash spec-decode engine (Step 2)#109FluffyAIcode wants to merge 10 commits into
FluffyAIcode wants to merge 10 commits into
Conversation
…lapse Port CUDA Gap-A to MLX. The existing MLX restored-attention dispatch already calls cache.update_and_fetch/cache.offset, so the per-token re-forward collapse is fixed by prefilling WITH a cache then decoding incrementally: - restored_prefill_cache: prefill once with restored-K/V injection into the model's native hybrid cache (full/global layers -> exact own K/V (S5); sliding -> f_theta-restored, window-bounded by RotatingKVCache). - restored_incremental_generate: greedy decode via mlx_lm generate_step over the prefilled cache (O(L)/token, async-pipelined). Recall carried by S5 full-attn. - k3_integrated_niah_eval_mac.py: --incremental flag selects the new path. - docs/mlx-port-lessons.md: Step 1 marked implemented + Mac validation command. Linux: compiles, funcs import (mlx lazy), MLX helper tests pass. End-to-end decode requires Apple Silicon -> Mac validation. Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com>
Inject fake mlx/mlx_lm modules (monkeypatch.setitem, auto-reverted) to exercise the wrapper control flow on Linux without Apple Silicon: - restored_prefill_cache: inject-config targets only has_kv source layers with restored K/V (sharers/missing skipped), make_prompt_cache threaded + returned, evicted-position clamping, attention class restored + configs cleared on exit. - restored_incremental_generate: argmax first token, max_tokens<=1 early-exit, first-token EOS stop, stream-until-EOS, stream-until-max_tokens. restored_prefill_cache (371-423) and restored_incremental_generate (425-455) are now 100% line-covered. MLX-kernel paths (dispatch internals, capture_own_kv, restored_logits forwards) remain Mac-validated. 16/16 MLX tests pass. Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com>
Full port of #107's fused spec-decode to the hybrid MLX-verifier + PyTorch-drafter path (inference_engine/backends/mlx/fused_specdecode.py): - Component A: capture_aux_hidden + MLXRestoredIncrementalVerifier.forward_block patch Gemma-4 DecoderLayer.__call__ to record aux-layer outputs (no MLX output_hidden_states), bridged to torch for the drafter. - Component B: reuse the PyTorch drafter make/extend_context_kv + draft_block_cached. - Component C: MLXRestoredIncrementalVerifier — prefill = Gap-A restored cache; commit_or_truncate rolls back rejected tokens via mlx_lm trim_prompt_cache. - fused_specdecode_generate: per-block O(L) accept/reject loop. - make_bridge_embed_lm_head: Gap-B unscaled drafting embed + softcapped lm_head. - k3_integrated_niah_eval_mac.py: --fused-specdecode + --block-size. - docs/mlx-port-lessons.md: Steps 3-4 marked implemented + Mac command. Linux: compiles; fused_specdecode.py 100% line-covered by new UTs (engine loop accept/reject/commit/extend, aux indexing, adapter prefill/verify/trim/append, bridge embed/lm_head). 47 MLX tests pass. MLX-kernel paths need Mac validation. Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com>
- scripts/review_mlx_port_on_mac.sh: one-shot Step 1 (incremental) + Step 2 (fused) Mac validation; prints recall vs oracle, tok/s + speedup_vs_AR, KV savings, and PASS/FAIL gates. All knobs env-overridable. - k3_integrated_niah_eval_mac.py: report now includes throughput.oracle_native_ar and throughput.cross_model_speedup_vs_oracle_ar so the AR comparison is in JSON. Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com>
Route the Mac S5 adaptive path through native MLX cache behavior when DFlash acceptance is too low, preserving the forced fused path while removing avoidable restoration and bridge overhead from the default smoke path. Co-authored-by: Cursor <cursoragent@cursor.com>
Seed the Gemma4 content channel for direct-answer NIAH smokes so short generations measure retrieval instead of spending their budget in the thought channel, and record cross/oracle parity evidence. Co-authored-by: Cursor <cursoragent@cursor.com>
Warm the MLX decode path before cross/oracle comparisons and stop on Gemma4 turn-end tokens so the Mac validation gate measures steady decode behavior without wasting budget past the answer. Co-authored-by: Cursor <cursoragent@cursor.com>
Use fair e2e prefill+decode timing for cross/oracle comparisons, chunk long-context MLX prefill paths, and record ctx280 n=5/gen32 evidence showing Step 2 recall parity and speedup under the corrected gate. Co-authored-by: Cursor <cursoragent@cursor.com>
inference_engine/bench/k3_report_gate.py — machine-checkable rules for K3 Mac acceptance reports (schema 2): fused runs must execute blocks>0; native-baseline runs cannot occupy the SUT slot or claim recall/speedup; headline speedups require n>=5/arm, median decode tokens >=32, decode-only medians, generate_step oracle decode, and <=3x within-arm prefill spread; analytical S5 memory tables must describe the run. Harness (k3_integrated_niah_eval_mac.py): - --fused-specdecode now ALWAYS runs the fused engine; the silent greedy fallback that produced blocks=0 'fused' reports is gone - --native-baseline-bypass replaces the implicit adaptive path and labels the run system_under_test=native_ar_baseline - per-sample restoration_active/decode_loop/prefill_s/decode_s rows on every mode; oracle decode via mlx_lm generate_step (anti-pattern per-token mx.eval loop removed); speedup withheld with reasons when inadmissible; measured mx peak memory; schema_version 2; harness validates its own report and exits 2 on violation CI: scripts/validate_k3_reports.py re-validates all committed reports (schema-1 grandfathered as non-evidence); new gate module is in the Linux 100%-coverage include (68 new unit tests). Docs: ctx280 validation note marks 2.584x / 89.8% claims superseded; lessons doc points at the gate. Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com>
cursor Bot
pushed a commit
that referenced
this pull request
Jun 12, 2026
Live run exposed the overlay gap: validate_k3_reports.py + k3_report_gate.py existed only on the PR #109 branch, so requests built from a client checkout without them produced a request branch whose on-Mac evidence-gate step crashed (exit 2, file not found). The gate is part of the bridge's evidence discipline (BRIDGE_FILES lists it) — it now lives on this branch with its 68-test suite. Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com>
…t architecture evidence Per 2026-06-13 directive: Step-1 / native-cache recall comes from Gemma-4's native full-attn layers + sliding eviction, never exercising f_theta/proposer KV restoration -> structurally incapable of testing the architecture. Forbidden for architecture validation; re-validate on a pure sliding-window model (Qwen3). See ADR 0012 revision 2026-06-13. Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com>
This was referenced Jun 13, 2026
Closed
FluffyAIcode
added a commit
that referenced
this pull request
Jun 13, 2026
…idated → main) (#117) * MLX port (Step 1): incremental restored decode to kill throughput collapse Port CUDA Gap-A to MLX. The existing MLX restored-attention dispatch already calls cache.update_and_fetch/cache.offset, so the per-token re-forward collapse is fixed by prefilling WITH a cache then decoding incrementally: - restored_prefill_cache: prefill once with restored-K/V injection into the model's native hybrid cache (full/global layers -> exact own K/V (S5); sliding -> f_theta-restored, window-bounded by RotatingKVCache). - restored_incremental_generate: greedy decode via mlx_lm generate_step over the prefilled cache (O(L)/token, async-pipelined). Recall carried by S5 full-attn. - k3_integrated_niah_eval_mac.py: --incremental flag selects the new path. - docs/mlx-port-lessons.md: Step 1 marked implemented + Mac validation command. Linux: compiles, funcs import (mlx lazy), MLX helper tests pass. End-to-end decode requires Apple Silicon -> Mac validation. Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * Add Linux UTs for MLX incremental restored-decode wrappers Inject fake mlx/mlx_lm modules (monkeypatch.setitem, auto-reverted) to exercise the wrapper control flow on Linux without Apple Silicon: - restored_prefill_cache: inject-config targets only has_kv source layers with restored K/V (sharers/missing skipped), make_prompt_cache threaded + returned, evicted-position clamping, attention class restored + configs cleared on exit. - restored_incremental_generate: argmax first token, max_tokens<=1 early-exit, first-token EOS stop, stream-until-EOS, stream-until-max_tokens. restored_prefill_cache (371-423) and restored_incremental_generate (425-455) are now 100% line-covered. MLX-kernel paths (dispatch internals, capture_own_kv, restored_logits forwards) remain Mac-validated. 16/16 MLX tests pass. Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * MLX port (Step 2): fused DFlash spec-decode engine (A+B+C) Full port of #107's fused spec-decode to the hybrid MLX-verifier + PyTorch-drafter path (inference_engine/backends/mlx/fused_specdecode.py): - Component A: capture_aux_hidden + MLXRestoredIncrementalVerifier.forward_block patch Gemma-4 DecoderLayer.__call__ to record aux-layer outputs (no MLX output_hidden_states), bridged to torch for the drafter. - Component B: reuse the PyTorch drafter make/extend_context_kv + draft_block_cached. - Component C: MLXRestoredIncrementalVerifier — prefill = Gap-A restored cache; commit_or_truncate rolls back rejected tokens via mlx_lm trim_prompt_cache. - fused_specdecode_generate: per-block O(L) accept/reject loop. - make_bridge_embed_lm_head: Gap-B unscaled drafting embed + softcapped lm_head. - k3_integrated_niah_eval_mac.py: --fused-specdecode + --block-size. - docs/mlx-port-lessons.md: Steps 3-4 marked implemented + Mac command. Linux: compiles; fused_specdecode.py 100% line-covered by new UTs (engine loop accept/reject/commit/extend, aux indexing, adapter prefill/verify/trim/append, bridge embed/lm_head). 47 MLX tests pass. MLX-kernel paths need Mac validation. Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * Add Mac mini validation script + oracle(native-AR) throughput in report - scripts/review_mlx_port_on_mac.sh: one-shot Step 1 (incremental) + Step 2 (fused) Mac validation; prints recall vs oracle, tok/s + speedup_vs_AR, KV savings, and PASS/FAIL gates. All knobs env-overridable. - k3_integrated_niah_eval_mac.py: report now includes throughput.oracle_native_ar and throughput.cross_model_speedup_vs_oracle_ar so the AR comparison is in JSON. Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * Optimize MLX adaptive S5 native smoke path Route the Mac S5 adaptive path through native MLX cache behavior when DFlash acceptance is too low, preserving the forced fused path while removing avoidable restoration and bridge overhead from the default smoke path. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix Gemma4 NIAH recall smoke prompting Seed the Gemma4 content channel for direct-answer NIAH smokes so short generations measure retrieval instead of spending their budget in the thought channel, and record cross/oracle parity evidence. Co-authored-by: Cursor <cursoragent@cursor.com> * Stabilize MLX Step 2 throughput gate Warm the MLX decode path before cross/oracle comparisons and stop on Gemma4 turn-end tokens so the Mac validation gate measures steady decode behavior without wasting budget past the answer. Co-authored-by: Cursor <cursoragent@cursor.com> * Address PR109 Mac validation review Use fair e2e prefill+decode timing for cross/oracle comparisons, chunk long-context MLX prefill paths, and record ctx280 n=5/gen32 evidence showing Step 2 recall parity and speedup under the corrected gate. Co-authored-by: Cursor <cursoragent@cursor.com> * Mac bridge M1: cloud-agent access to kakeya-mac-m4 over the git bus - docs/design/mac-bridge-cloud-agent-access.md: three-transport design (M1 git-bus implemented; M2 tailnet SSH + M3 fleet membership designed) + evaluation of folding the bridge into the ADR 0009 distributed-inference plane (WAN = control/tool plane, LAN = data plane; remote-executor as CAPABILITY_ROLE_TOOL) - inference_engine/bridge/manifest.py: preset allowlist (8 presets, typed+bounded params, ${ENV:} placeholders resolved on the runner, argv-only — no shell), manifest schema + validation - scripts/mac_bridge/: run_preset.py executor (logs, summary, evidence-gate pass on K3 reports), request_run.py git-bus client (branch+manifest+overlay+push), fetch_results.py read-only poller - .github/workflows/mac-bridge.yaml: push-on-mac-bridge/** executor on [self-hosted, macOS, ARM64, kakeya-mac-m4], serialized, commits results back to the request branch + uploads artifacts - CI: bridge tests in the Linux gate, inference_engine/bridge/* at 100% coverage, import smoke - docs/ops/mac-m4-runner-setup.md: bridge operator section Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * Mac bridge: one-click install + run - scripts/mac_bridge/setup_mac.sh: idempotent Mac-side installer (host shape, deps, Actions runner install/registration with kakeya-mac-m4 labels, model-location + HF-cache checks, bridge self-test, optional --with-tailscale for M2) - scripts/mac_bridge/kakeya_mac.py: cloud-agent front door (doctor / run --wait / status); auto-detects AgentMemory branch policy and requests via AgentMemory/mac-bridge-<preset>-<nonce>-<sfx> - workflow accepts both mac-bridge/** and AgentMemory/mac-bridge-* - request_run.py: --branch-prefix/--branch-suffix; returns worktree to the original branch after pushing (one-click UX) - docs: one-click sections in design doc + runner runbook Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * mac-bridge client: refuse dirty trees; =-joined branch-policy args Live testing caught both: a leading-dash branch suffix (-b876) was parsed as an option flag, and request_run's 'git add -A' silently absorbed unrelated uncommitted edits into the request branch (they vanished from the original branch on switch-back). Requests are now always built from a committed state. Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * Fix Mac setup: drop the stale transformers <5.0 hard-pin scripts/setup_mac.sh enforced transformers <5.0 ('hard-pin to 4.x') from the legacy Qwen3 MDLM era, while requirements.txt had already dropped the upper bound — the K3 critical path (Gemma 4 verifier, DFlash drafter, current mlx-lm) requires transformers >= 5.0. On a current Mac install (transformers 5.11.0) verify_imports aborted with '5.11.0 >= forbidden upper 5.0'. - verify_imports: transformers bound is now (>=4.45, no upper); comment points legacy-MDLM users at a dedicated 4.x venv (same guidance as requirements.txt) - header/docs updated to the real venv rationale - scripts/mac_bridge/setup_mac.sh: install deps into the runner's plain python3 (the interpreter Actions jobs actually use; the .venv-mac built by scripts/setup_mac.sh is for interactive dev) and report transformers K3-readiness explicitly Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * Mac evidence: k3 gate sync iterC block4 ignoreturn n5 gen64 Co-authored-by: Cursor <cursoragent@cursor.com> * mac-bridge: ~/kakeya-models/ as the stable runner-local model location First live k3 preset run failed fast (2.3s, logs round-tripped): the repo-relative verifier default does not exist in the runner workspace and HF_HUB_OFFLINE turned the fallback lookup into a hard error. Default resolution is now: repo Actions variable > ~/kakeya-models/<name> (documented symlink convention on the runner host) > repo-relative. Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * mac-bridge: checkout with lfs:true (k3 presets load LFS checkpoints) Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * mac-bridge: force LFS materialization + pointer guard checkout@v4 lfs:true is not sufficient on a reused self-hosted workspace: a prior non-LFS checkout leaves pointer-content files that git does not re-smudge (blob unchanged), observed live as torch.load 'Unsupported operand 118'. git lfs pull + a pointer scan make k3 checkpoint loading deterministic. Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * mac-bridge: ship the K3 evidence gate with the bridge Live run exposed the overlay gap: validate_k3_reports.py + k3_report_gate.py existed only on the PR #109 branch, so requests built from a client checkout without them produced a request branch whose on-Mac evidence-gate step crashed (exit 2, file not found). The gate is part of the bridge's evidence discipline (BRIDGE_FILES lists it) — it now lives on this branch with its 68-test suite. Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * Sync Mac-local hardened harness (schema 2 + ignore-turn) used for iterC Co-authored-by: Cursor <cursoragent@cursor.com> * k3 presets: --ignore-turn-stop so evidence runs decode the full budget Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * mac-bridge results: AgentMemory/mac-bridge-k3-step1-incremental-1781268308-dc400e-b876 * Step-2 rescue: all-MLX DFlash drafter (zero per-block bridge crossings) iterC (PR #109) proved the hybrid fused engine correct (recall 5/5 @ctx280, accept_len 2.1-2.9/4) but 0.028x decode-only: each block paid 4+ mx<->torch crossings plus a float32 CPU-torch drafter forward. - inference_engine/backends/mlx/dflash_drafter.py: 1:1 MLX port of the torch DFlashDrafter fast path (same DFlashConfig, same checkpoint weights via mx.load, explicit fp32 RoPE tables, GQA via mx.fast.scaled_dot_product_attention, fc/hidden_norm/norm fusion, make/extend_context_kv + draft_block_cached) + native embed/lm_head (Gap-B preserved: no sqrt(hidden) scale; softcap on logits) - fused_specdecode_generate: accepted-path aux expansion now routes through cat_aux_fn (runtime-agnostic; torch semantics unchanged) - harness --all-mlx-drafter: native drafter + native embed/lm_head + identity aux bridge; requires --s5-exact-full-attn; drafter_runtime recorded per sample - scripts/research/k3_mlx_drafter_parity.py: token-parity gate vs the torch reference on real verifier aux (blocks throughput claims) - bridge presets: k3-drafter-parity, k3-step2-fused-allmlx Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * parity: fp32 discriminator mode for the all-MLX drafter First Mac parity run: bf16 MLX vs fp32 torch = 94.79% (91/96) token agreement, prefix-consistent mismatches only, MLX draft 3.2x faster already. fp32-vs-fp32 must be exact to rule out port bugs vs dtype near-tie flips. Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * lessons: Step-2 rescue status — all-MLX drafter at 0.476x AR (17x over hybrid), parity-proven Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * kv-quant eval: affine (mlx-native) vs KakeyaLattice rate-distortion + recall Five arms over the SAME captured full-attn own K/V at ctx280 scale: identity (machinery control), affine 8/4-bit (mx.quantize, group 64 — the QuantizedKVCache storage format), KL D4/E8 (torch codec round trip, eval-time only). Per arm: measured bits/value, energy-weighted rel_mse, and REAL recall via lossy injection + incremental restored decode. Printed verdict: KL justifies an MLX port only if it beats affine4 on rel_mse at <= its rate without losing recall. Bridge preset: k3-kv-quant-eval. Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * lessons: kv-quant verdict — affine4 passes recall at 25x margin; KL MLX port shelved Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * Step-2 levers 1+2+3: single-sync all-MLX fused loop fused_specdecode_generate_mlx — one host sync per block: - (2) draft ids stay lazy mx tensors and feed the verifier forward in-graph (drafter.draft_block_ids + adapter.forward_block_lazy) - (1) in-graph greedy acceptance (cumprod leading-match) + lazy gather of the next-position logits row; per block mx.eval materialises only the accept count and candidate ids; drafter-context extensions go through mx.async_eval - (3) no correction forward: the gathered next-row makes the verifier's correction the next block's carried bonus, verified (and aux-captured) as position 0 of the next batched forward — guaranteed-accepted by construction, so every block commits >= 1 token and the loop can never run below AR pace Harness uses the new loop automatically on --all-mlx-drafter. Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * fused mlx loop: two-phase eval (drafter graph || verifier graph) Live block-1 diagnostic proved levers (1)(3) correct (64/64 tokens, 17.5 tok/s carried-greedy); the fully fused drafter+26B graph was the failure (Metal command-buffer pathology: 143s evals, stream divergence). Materialise the small drafter graph first, keep in-graph acceptance + carried correction; 2 syncs/block vs eager 6+L. Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * fused mlx loop v3: rollback+carry replaces trim (correctness fix) Root cause of the v2 stream divergence (and retroactively iterC's 23-token sample): trim_prompt_cache is unsound on Gemma-4's hybrid cache once the sliding RotatingKVCache has wrapped — rejected draft K/V linger in the ring. v3: O(1) reference snapshot before each verify forward; on partial acceptance the WHOLE forward rolls back and the stream-committed tokens carry into the next candidate (guaranteed re-accept, K/V+aux recomputed correctly). Happy path (full accept) costs nothing. block-1 live diagnostic validated the carried-bonus machinery (64/64, 17.5 tok/s). Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * lessons: levers 1-3 verdict — trim bug exposed; true acceptance caps fused at ~0.6-0.7x Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * Add k3-fused-allmlx-natural preset (natural-stop acceptance probe) All-MLX fused but WITHOUT --ignore-turn-stop, so generation ends at the real answer. For comparing mean_accept_len (natural-stop) vs the forced over-generation of k3-step2-fused-allmlx, to confirm on the real Mac that the low '2.13' accept is a forced-over-gen artifact, not a drafter/quant/restoration deficiency. Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * Add code-completion workload (--code-prompts) + k3-fused-allmlx-code preset Honest spec-decode throughput probe: all-MLX fused on naturally-long, predictable code-completion prompts (the spec-decode sweet spot), natural stop. Reports decode-only tok/s (fused vs oracle AR) + acceptance. --code-prompts skips the NIAH recall gate (recall N/A by design). Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * CUDA-parity rollback (Option 2): all-KVCache + native trim (keep accepted, drop rejected) Eliminates the v3 carry re-forward. Root cause: RotatingKVCache not trimmable once wrapped (is_trimmable -> offset<max_size), so v3 rolls the block back + re-forwards carried accepted tokens. Fix: prefill all-KVCache layout (sliding on full KVCache too -- byte-exact, window mask applies regardless of capacity) -> trim_prompt_cache is a sound O(1) slice on every layer. - restored_prefill_cache: +cache_factory; fused_specdecode.make_full_kv_prompt_cache; fused_specdecode_generate_mlx_trim (forward L, keep accepted, trim L-k, no carry); adapter.prefill +full_kv; harness --cuda-trim; manifest k3-fused-allmlx-code-trim. Linux: compiles; +1 UT; 4 pre-existing b876 failures unchanged. Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * Add single-fused probe to classify the Metal two-phase instability fused_specdecode_generate_mlx_trim(single_fused=True): skip the two-phase eval so drafter+26B fuse into ONE graph (the b876-pathological path); report per-block eval times (first8/max/mean). Harness --single-fused + preset k3-fused-singlefused-probe (n=2,gen=16 so a pathological block is bounded). Classifies fundamental command-buffer limit (eval scales w/ graph) vs fixable SDPA fallback (eval huge even at small scale). Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * README: Kakeya Inference Engine for Mac — MLX spec-decode port journey (K3 beta baseline) Records the decode-throughput journey from ~0.09x AR (O(T^2) collapse) -> ~0.2x (cross-runtime bridge) -> ~0.5x (all-MLX + CUDA-parity trim rollback) -> ~0.7x (block-4) -> ~1.0x (block-8, AR parity) on Gemma-4-26B-A4B / Mac M4, with each binding problem + fix, the ruled-out non-levers (quant/length/alignment/sync/ forced-over-gen artifact), the honest >AR-is-CUDA-favoured ceiling, and the evaluation environment (Mac bridge git-bus + self-hosted runner + evidence gate + H200). Recall 1.0 throughout; bounded S5 KV. Cross-links ADR 0009/0012/0013. Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * Beta: update preset allowlist test for the code/trim/natural/probe presets Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> Co-authored-by: fluffy314 <fluffy314@fluffy314s-Mac-mini.local> Co-authored-by: kakeya-mac-bridge <mac-bridge@users.noreply.github.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.
Goal
Port the full #107 K3 GPU beta to the MLX backend: Step 1 kills the decode throughput collapse (incremental restored decode), Step 2 ports the entire fused/aggregate spec-decode engine (Components A+B+C). Hybrid runtime: verifier = MLX (Gemma-4 26B-A4B 4-bit), DFlash drafter + f_θ = PyTorch (MPS/CPU), bridged once per block.
Step 1 — Incremental restored decode (throughput collapse fix)
Root cause: MLX restored verify did a full re-forward per token (O(T²)). The existing MLX dispatch already calls
cache.update_and_fetch/cache.offset, so the fix is to prefill with a cache then decode incrementally.restored_prefill_cache— prefill once with restored-K/V injection into the model's native hybrid cache (full/global = exact own K/V → S5 recall; sliding = f_θ-restored + window-bounded).restored_incremental_generate— decode viamlx_lm.generate_stepover the prefilled cache (O(L)/token, async-pipelined).--incremental.Step 2 — Fused DFlash spec-decode engine (A+B+C)
inference_engine/backends/mlx/fused_specdecode.py, mirroring CUDArestored_specdecode_fusedper-block O(L):capture_aux_hidden+MLXRestoredIncrementalVerifier.forward_blockpatch the Gemma-4DecoderLayer.__call__to record aux-layer outputs (MLX has nooutput_hidden_states), bridged to torch. hidden-states indexing matches HF (hs[a]= output of layera-1).make_context_kv/extend_context_kv/draft_block_cached(built once from prompt aux, extended per committed token — no O(C) recompute).MLXRestoredIncrementalVerifier— prefill = Gap-A restored cache;commit_or_truncaterolls back rejected tokens via mlx_lm's nativetrim_prompt_cache(the same primitive mlx_lm's own spec-decode uses).fused_specdecode_generate— the per-block draft → verify+aux → accept/reject → commit-correction → extend-context loop.make_bridge_embed_lm_head— Gap-B: draftingembed_fnis a plain shared-embed lookup (no×sqrt(hidden));lm_head_fn= tied-embed +final_logit_softcapping.--fused-specdecode --block-size N.Files
inference_engine/backends/mlx/cross_model_dlm_verifier.py—restored_prefill_cache,restored_incremental_generate.inference_engine/backends/mlx/fused_specdecode.py— full fused engine (A+B+C + bridge + loop).scripts/research/k3_integrated_niah_eval_mac.py—--incremental,--fused-specdecode,--block-size.tests/backends/mlx/test_restored_incremental_decode.py,tests/backends/mlx/test_fused_specdecode.py— Linux UTs (fakemlx/mlx_lm).docs/mlx-port-lessons.md— plan Steps 1, 3, 4 marked implemented + Mac commands.Validation status
fused_specdecode.pyand the two Step-1 functions.Reference (#107 H200): incremental = 1.0× AR (KV 16.9–43.9× smaller), fused 1.27× AR, recall 1.0.
🔍 Reliability Re-evaluation (latest commits
1f6e58c/491d460/c0c5d3c+ committed Mac JSONs)Verdict: correctness (recall) is good; the performance evidence does NOT support "the Mac collapse is fixed."
Committed Mac numbers (n=1, gen=8)
Critical issues
eval_fused_specdecode,t0is set afterbuild_restoration(build_s),capture_aux_hidden(aux_s) andprefill(prefill_s);latstherefore measures decode only. The oracle (eval_free_gen_oracle) setst0before its prefill → its tok/s includes prefill+decode. So the reported number compares fused-decode-only vs oracle-prefill+decode. The honest per-sample fused wall isbuild_s + aux_s + prefill_s + decode_s; onlydecode_sis in the reported tok/s. Given incremental's 0.148× (same restoration prefill), the true end-to-end fused throughput is almost certainly not > AR.restored_prefill_cache(injection), the new adaptive-native bypass (mlx_model(mx.array([pid]), cache=cache)), and the oracle prefill all do a single full-prompt forward. They only survive because the smokes are ~1.5k tokens; at the ctx280 lengths (~5–6k) that caused the original collapse, these will OOM. The fix is untested at the lengths that motivated it.build_restorationthen discards it for a native cache → wasted compute hidden from the reported tok/s.Fair credit
Required to make the throughput claim trustworthy
build + aux + prefill + decode) for fused and oracle identically (or decode-only for both); report end-to-end tok/s.build_restorationin adaptive-native mode (pure waste).Remaining (perf, non-blocking)
Plan item 2 (fold the clean aux-capture /
build_restorationforwards into prefill) is still open — a build-latency optimization, separate from decode throughput.🔍 Reliability Re-evaluation #2 (commit
894c76a"Address PR109 Mac validation review" + ctx280 report)Verdict: recall is now validated at ctx280 scale (real milestone); the new "2.584× speedup" is NOT reliable — it is system-variance noise on an identical-prefill path, with the fused engine inactive.
Review corrections that landed (credit)
--prefill-chunk-size 512) on both cross and oracle → completed at 4406–5810 tokens without OOM.e2e_prefill_plus_decode+ per-sampleprefill_s/decode_s/e2e_s.build_restoration(build_restoration_s=0.0).Why the 2.584× is not trustworthy
① It's run-order / thermal variance on the same prefill path. Both cross and oracle use the identical chunked
native_prefill, yet per-sample prefill times:Sample 3: the same operation took 21.5s (cross, runs first) vs 146.3s (oracle, runs second) — 7×. Oracle prefill alone varies 4× within one run. n=5 means are meaningless under this variance.
② The fused spec-decode engine never executed. Every sample reports
blocks: 0,mean_accept_len: 0.0,adaptive_mode: "restored_greedy"→ the DFlash drafter was bypassed and it did plain greedy decode. "Step 2 fused" here = the greedy fallback; the fused mechanism contributed nothing and remains unvalidated.③ Throughput is ~95% prefill. prefill ≈ 33s/sample vs decode ≈ 0.4–5.5s for ~8 tokens (early stop). The reported tok/s is a prefill-time ratio under noise, not a decode measurement. Also the oracle decode is a hand-rolled per-token
mx.evalloop (serialized anti-pattern) vs cross's async decode → an unfair oracle baseline even for the decode portion.Required for a trustworthy perf number
native_prefill) and noise-dominated; including it only injects variance.generate_step, not the hand-rolled per-tokenmx.evalloop.--force-fused-specdecodeor raise the adaptive threshold) soblocks > 0.Net
Recall at ctx280 is a genuine milestone and should be highlighted. The throughput conclusion should be withdrawn/recaveated: it is measurement noise on an identical-prefill path with the fused engine inactive. The collapse-vs-AR question is still open and needs a decode-isolated, fused-active, variance-controlled rerun.
🛡️ Evidence gate landed (commit
0a6fb19) — review constraints are now codeThe reliability re-evaluations above are no longer advisory. The review's findings are enforced mechanically at three layers:
1. Library:
inference_engine/bench/k3_report_gate.py(Linux gate, 100% coverage, 68 tests)Machine-checkable rules over every K3 Mac acceptance report (schema ≥ 2):
FUSED_NEVER_RANBASELINE_AS_SUT/BASELINE_RECALL_CLAIMRECALL_SCOPErestoration_active=trueon every sampleSPEEDUP_SELF_COMPARISONSPEEDUP_SAMPLES/SPEEDUP_DECODE_TOKENSSPEEDUP_DECODE_ONLY_MISSING/SPEEDUP_SCOPE_MISMATCHSPEEDUP_ORACLE_LOOPmx.evalanti-pattern instead ofgenerate_stepSPEEDUP_PREFILL_VARIANCEMEMORY_CLAIM_MISMATCHReplaying the committed ctx280 report through the gate at schema 2 yields 10 violations — every issue from both re-evaluations, caught mechanically.
2. Harness:
k3_integrated_niah_eval_mac.pyself-validates (schema 2)--fused-specdecodenow always executes the fused engine — the silent greedy fallback that producedblocks=0"fused" reports is unreachable. The native path is explicit--native-baseline-bypassand is labelledsystem_under_test=native_ar_baseline(cannot claim recall/speedup).restoration_active/decode_loop/prefill_s/decode_son every mode (incl. Step 1 incremental — its decode tok/s is finally measurable separately from build/prefill).generate_step(same primitive as the cross path).mxpeak memory in the report; analytical S5 table carriesformula_matches_run.3. CI:
scripts/validate_k3_reports.pyre-validates every committed reportNew Linux CI step fails the build if any schema-2 report violates the rules. The 8 committed schema-1 reports are grandfathered as explicit NON-EVIDENCE warnings — including the 2.584×/89.8% ctx280 run, whose claims are formally withdrawn in
docs/pr109-mac-ctx280-validation.md.What's still required for the port's perf claims (unchanged, now enforced)
--fused-specdecode(engine must execute;blocks>0), n≥5, gen≥64, ctx280 — Step 2 validation.--incremental, gen≥64 — Step 1 decode-only tok/s vsgenerate_steporacle.Note:
tests/backends/mlx/test_fused_specdecode.pyhas 2 pre-existing failures when run on Linux against the fake-mlx fixtures (present at894c76a, before this commit); they are outside the Linux CI gate and unaffected by this change.✅ Final gate-clean evidence (2026-06-12, via the Mac bridge PR #111)
All three runs: n=5, gen=64 (
--ignore-turn-stop), ctx280 (4406–5810 tokens), seed 42, schema 2,evidence_violations: [], validated by the gate on-device AND independently re-validated.generate_step)k3_mac_bridge_k3_step1_incremental.json(branchAgentMemory/mac-bridge-k3-step1-incremental-1781268308-dc400e-b876, commit7f8722b)k3_mlx_gate_sync_iterC_…json(branchpr-109-mlx-incremental)Memory (measured): peak 16.9–19.1 GB on the 24 GB M4; S5 resident KV 132.92 MB vs 1308.88 MB naive @t=5810 (89.8 % savings,
formula_matches_run: true).Net (retracted re: architecture — see top banner): Step 1's decode/recall/memory numbers are honest, but "Step 1 ships" is not architectural validation (Gemma-4 coupon). Step 2 is correct but performance-rejected on Mac in its hybrid cross-runtime form. The earlier 0.148× (n=1, build-dominated) and 2.584×/3.46× (baseline self-comparison / scope mismatch) numbers are all superseded.
Evidence was produced remotely from a Linux cloud agent through the Mac bridge (PR #111); the runs also hardened the bridge live: stable
~/kakeya-models/model resolution, deterministic LFS materialization + pointer guard, and the evidence gate now ships with the bridge.