# CURRENT PHASE — Phase 23: The offline LLM matching tier (free local-model grinder + the data flywheel) **Generation:** Gen2 (15th phase of the arc) · **Started:** 2026-06-29 · **Effort:** Max (synthesis/decisions/debug) ↔ xHigh (mechanical) · **Plan:** set up at PhaseEnd_Phase22 close (Drew); substance was already built across the 2026-06-29 session. > Per-task crash-recovery log (P3). This file is the committable state a fresh session resumes from. Phase 22 (giants) closed → `PhaseEnd_Phase22.md` + `phase-ends/logs/Phase22.md`. The deep design + measured results of this phase live in `docs/gen2-mips-matching-model.md` (+ `docs/history/cheap-tier-ab-experiment.md`); the working-knowledge in the `cheap-tier-ab-validated` memory. ## Goal Stand up a **free, local, fine-tuned matching model** that grinds the small/medium-function bulk and banks byte-verified matches for **$0** — with (a) a **data flywheel** (banks grow the corpus → retrain → the model improves *during* the run), (b) **escalation** (the permuter `grinder.py` closes the regalloc/schedule near-misses the model leaves; bigger models / your Opus take the tail), and (c) the **whole-binary byte-gate as the incorruptible sole arbiter** (a weak model is a *throughput* risk only, never correctness). **On-demand, NOT 24/7** (run only when Drew says go). ## Decisions (Drew) - **On-demand runs**, bounded — never a standing 24/7 daemon. Queue a large batch, run a single shot, measure, scale the next. - **Serve the model on the GPU via `tools/serve_local.py`** (Unsloth/torch, OpenAI endpoint at `http://127.0.0.1:1234/v1`) — **LM Studio was ejected 2026-06-30**; the prebuilt `llama-cpp-python` CUDA wheel SIGILLs on this no-AVX-512 CPU, so the Unsloth path is the reliable one (no build, loads in ~6 s). - **Measure before investing** (the discipline that killed the v1 dead-end): every corpus/model change is **retested free on the 7B first**, and only scaled to a dense model if the cheap retest pays. *(Now satisfied for the 14B step: v3-on-7B paid.)* - **Corpus-v3 DONE** (macro bodies + struct types; v3 banks the small/setter bulk, fleet→63.82%). The **14B gate is satisfied** → **corpus-v4 (struct-giant types) + a dense Qwen2.5-Coder-14B on cloud** for the >15-ins/giant band is the warranted next investment — Drew's call vs. path A (reach≥2 with v3). ## The operating loop (when running) A. `lora_grind.py` (GPU) drafts open ≤N-ins stubs with the served fine-tuned model → banks via `gate_stage` → near-misses to the backlog. B. `grinder.py` (CPU permuter) closes the backlog's regalloc/schedule near-misses → banks. **Run A+B concurrently** (GPU vs CPU; the histogram already shows 94 schedule/regalloc nears waiting). C. Periodic propagate sweep (`dedup_propagate`) multiplies each bank fleet-wide. D. Periodically: `export_pairs → format_finetune → train_lora → redeploy` (the model learns its own new wins); read the near-by-class histogram → pick the corpus-v3 / recovery-tooling target; raise `--max-nins` as the band lifts. ## Tasks - [x] **T1 — Cheap-tier A/B** (Opus orchestrator + cheap agents) — Haiku 4.8× more matches/$ than Opus on the bulk; tools `tools/workflows/ab_match.js` + `tools/ab_score.py`. (`docs/history/cheap-tier-ab-experiment.md`.) - [x] **T2 — Stock-local floor** — Qwen3.6-35B-A3B (LM Studio): structurally smart but **0 reliable byte-matches** (can't refine to byte-exact); format-robust; full-cookbook context made it *worse* (dilution). The floor to beat. - [x] **T3 — The LoRA specialist pipeline** — `export_pairs.py` (mine banked asm↔C from build objects) → `format_finetune.py` (Qwen chat-template, compile-filtered) → `train_lora.py` (Unsloth QLoRA, Qwen2.5-Coder-7B, GPU on the 3080 Ti) → `eval_lora.py` (held-out gate-true). Corpus `datasets/match_pairs/` (gitignored). - [x] **T4 — Corpus-v2 = the extern-block fix (THE unlock)** — capture the `extern D_xxx;` block the src declares above each def (correct byte-verified types) → self-contained completions, compile 52%→92%, train 638→1111 (non-trivial 257→813). Same 7B retrained: held-out **6–15 ins 0%→85%**, non-trivial 0→26, meaningful(>15) 0→3. **Data was the bottleneck, confirmed.** - [x] **T5 — First REAL banks on open stubs** — the v2 model drafted real open ov_SC01_077 stubs (LEAN, raw `.s` — model is format-robust, no bridge needed); whole-binary gate **banked 4** (func_80160B34, func_8015CC74, func_8016084C, func_801705C0; @commit:0320). Sample rate: 9/22 match_one proxy → **4/22 (18%) whole-binary** (the proxy→gate TU-plumbing gap). - [x] **T6 — The mass-run driver** — `tools/lora_grind.py`: rotates every binary (config/check.*.sha), drafts the served model, banks via gate_stage (commit), defers/periodic-propagates, writes the **classified near-miss histogram** (the flywheel "missing idioms" signal). STOP/heartbeat/stats like grinder.py. - [x] **T7 — Calibration run + the 0/222 puzzle — DEBUGGED + FIXED (2026-06-30).** Root cause = TWO independent harness bugs in `lora_grind`'s use of `gate_stage.run_gate` (R14, by reading the code + the run's backlog — which resolved a direct contradiction between two scout agents): **Bug A** — `good_sha()` passed the whole sha1sum line `" "` vs harvest_verify's bare `sha1()` → **0 banks for EVERY binary incl. 077** (the "0/12" was a bug artifact, NOT an exhausted tail — the prior "`.sha` files carry the filename" note was the unfollowed thread); **Bug B** — the gate call left `src/asm/out` at the hardcoded ov_SC01_077 defaults → non-077 drafts dropped at the 077 stub-filter, **silently** (and the asm mis-resolution contaminated the backlog near-miss classes). Fix (`tools/gate_stage.py`): `run_gate` is binary-agnostic (resolve src/asm/out/good_sha from `binary`; good_sha bare-hash normalized) + a **loud negative-control guard**; `lora_grind.good_sha` fixed at source; byte-neutral (check-all 136/136). **Proof:** ov_SC01_000 spot-run banked **7/15 (47%) byte-identical** (was 0; @commit:0322). **ROI finding:** 6/7 banks are reach-1 (overlay-unique ×1) → broad rotation = high bank-RATE, low fleet-% ROI; the fleet lever is reach≥2 targeting (T9) + corpus-v3 (T8). Full write-up: `docs/gen2-mips-matching-model.md` → "T7 RESULT". - [x] **T8 — Corpus-v3 — DONE (2026-06-30).** Mined the **1623 `engine_core.h` `DEFINE_func` macro bodies** (the shared setters/return-const the model was blind to — 96.6% of v2 was overlay-unique) + `format_finetune` inlines `engine_types.h` structs → corpus **1312→2891**, trainable **2534+291** (2.5× v2). **v3 trained** (loss 1.275→0.085), held-out eval **23/40 (57.5%)**; production batch banked ~352 fns + 45 shared groups → fleet **63.67→63.82%** ($0). The empty-leaf/setter class v2 couldn't draft is now banked. Struct-GIANT types deferred → **corpus-v4** (with a 14B). Details: `docs/gen2-mips-matching-model.md`. - [x] **T9 — Operating loop — WIRED + RUN (2026-06-30).** Local GPU serving stood up (`tools/serve_local.py`, Unsloth — LM Studio ejected); the retrain cycle (`export_pairs → format_finetune → train_lora → serve_local`) + reach≥2 targeting (`--min-reach`) + propagate sweeps all run end-to-end. Grinder concurrent = 0 banks (permuter tail exhausted, Phase-22 reality — the LLM is the value now). **Refinements (NEXT):** `--min-reach 2` with v3 so setter banks propagate ×134; the dedup-collapse; raise `--max-nins` as v3 lifts the band. - [ ] **T10 — Progress honesty + PhaseEnd** — track fleet % + bank-rate; PhaseEnd_Phase23 at a clean checkpoint (Tier-1 Max). ## ▶ RESUME HERE (fresh session) **State:** Phase 23 in progress (NOT a phase end). **v3 is the current model** — `bfm-match-7b-v3` (Qwen2.5-Coder-7B QLoRA on **corpus-v3**), adapter at `models/bfm-match-7b-v3` (v2 kept as fallback at `models/bfm-match-7b`). **LM Studio is EJECTED** — serve via **`tools/serve_local.py`** (Unsloth GPU, OpenAI endpoint), NOT LM Studio. The **8-hour autonomous run (2026-06-30)** built local serving + the prompt fix + corpus-v3 + v3 + a production batch → **fleet 63.82%** (+502 byte-identical, $0), 136/136 byte-clean, 27 commits this session (local — **Drew pushes**, R6). Pipeline validated end-to-end: a free local model banks the small/setter bulk, including the empty-leaf class v2 couldn't. Corpus `datasets/match_pairs/` + `.venv-train` gitignored. (Phase 22 close `PhaseEnd_Phase22.md` is committed `commit:0325`.) **NEXT TASK — DECIDED (Drew, 2026-06-30): do A first, THEN B.** (Sequencing rationale: A is $0 + immediate + realizes the ×134 lever this run set up, and A's new banks enrich the corpus that B trains on — so A-then-B compounds.) **① START THE FRESH SESSION HERE — (A) more LLM harvesting with v3, the fleet-% lever.** Run **`lora_grind --min-reach 2` with v3**: now that v3 banks the shared setters, target the **shared (reach≥2)** ones so each bank propagates **×134** instead of re-banking inline per binary (what capped this run's % at +0.15). Pair with the **dedup-collapse** of the per-binary inline setters → shared `engine_core.h` macros, and run the **data flywheel** (the run's ~352 new banks grow the corpus → retrain a v3.1; raise `--max-nins` as the band lifts). Serve v3 via `serve_local.py` (command below). Cheapest, immediate, $0. Close A when its flywheel plateaus. **② THEN — (B) train a 14B for the giant band.** The measure-before-investing gate is **SATISFIED** (v3-on-7B paid), so a dense **Qwen2.5-Coder-14B** for the **>15-ins / struct-giant** band is the warranted investment. Do **corpus-v4 first** (emit the struct-giant types the >40-ins fns need — the band v3 still compile-fails), then `train_lora.py --base unsloth/Qwen2.5-Coder-14B-Instruct-bnb-4bit --rank 32`. Needs ~16 GB → a **cloud A100/H100** (the 3080 Ti is 12 GB; 4-bit + offload locally works but slow). The byte-gate makes a wrong 14B a throughput risk only. **Serve + run (when Drew says go):** ``` # 1) serve v3 on the GPU (replaces LM Studio): LD_LIBRARY_PATH=$(ls -d .venv-train/lib/python3.12/site-packages/nvidia/*/lib | tr '\n' :) \ .venv-train/bin/python tools/serve_local.py --adapter models/bfm-match-7b-v3 --name bfm-match-7b-v3 --port 1234 & # 2) bounded mass-run (reach≥2 targeting, the fleet-% lever): API_BASE=http://127.0.0.1:1234/v1 MODEL=bfm-match-7b-v3 GATE_PHASE=phase-23 \ .venv/bin/python -u tools/lora_grind.py --min-reach 2 --max-nins 15 --batch 15 --iters 2 --max-batches N # stop anytime: touch .run/auto/STOP ; retrain: export_pairs -> format_finetune -> train_lora --out models/bfm-match-7b-v4 ``` ## Verification invariant (every bank) The **whole-binary byte-gate** (`gate_stage`/`harvest_verify`, G3/P9) is the sole arbiter — a wrong/weak draft can NEVER bank (it reverts to the stub). `make check-all` 136/136 byte-identical from a clean tree (R22); `dedup-check` 0 failed; the `db.*.gbf` churn is R23 restart-noise (do NOT stage). The fine-tuned model only affects *throughput*, never correctness. ## Reuse (no rewrites) *New (this phase):* **`tools/serve_local.py`** (Unsloth GPU serving, OpenAI endpoint — **replaces LM Studio**) · `api_draft.py` (LEAN drafter + the "translate every instruction, never empty" prompt fix) · `ab_match.js`/`ab_score.py` (the cost A/B) · `export_pairs.py` (now mines the `engine_core.h` macro bodies — corpus-v3) / `format_finetune.py` (inlines `engine_types.h` structs) / `train_lora.py` / `eval_lora.py` (the LoRA pipeline) · `lora_grind.py` (the mass-run; `--min-reach`). *Existing (now Phase-23 binary-agnostic):* `gate_stage` (`GATE_PHASE`-tagged + resolves src/asm/out/good_sha from `binary`) / `grinder.py` (per-binary + grouped gating) + `auto_supervisor.sh` / `dedup_propagate` / `backlog` (fleet-aware `load_best`) / `harvest_verify` / `match_one`; cookbook §17–§28. ## Guardrails - **On-demand only** — never leave a 24/7 daemon running; bounded `--max-batches`, STOP-sentinel safe-exit. - **Measure before investing** — retest every corpus/model change free on the 7B before any cloud spend. - **Disk** — GGUF conversion writes ~30 GB intermediates onto the WSL vhdx (C:); `train_lora` now auto-cleans them; keep only the q4 GGUF. (A disk-full crash cost a session on 2026-06-29.) - **Serve on GPU via `tools/serve_local.py`** (Unsloth/torch, the in-repo replacement for the ejected LM Studio) — the prebuilt llama-cpp-python CUDA wheel SIGILLs on this no-AVX-512 CPU; don't try it. - Model size matches data size — no "massive" models on ~1–2k examples (overfit); dense > MoE for a limited-data LoRA. ## Blockers - **(cleared) T7 — the 0/222 broad-rotation banking.** Root-caused to two harness bugs (good_sha sha1sum-format + the src/asm/out ov_SC01_077-default) and fixed + proven (ov_SC01_000 7/15 byte-identical, check-all 136/136). No blockers; a bounded mass-run is safe to size. Open levers (not blockers): corpus-v3 struct types (T8) + reach≥2 targeting (T9). ## Progress log - 2026-06-29: **Phase opened at PhaseEnd_Phase22 close (Drew).** Built across this session: cheap-tier A/B (T1, Haiku 4.8×/$), stock-local floor (T2, 0), the LoRA pipeline (T3) + corpus-v2 extern-fix (T4, 6–15 ins 0%→85%), first 4 real open-stub banks (T5, @commit:0320), the `lora_grind` mass-run driver (T6). Calibration run (T7) launched (500 fns) — **18% on ov_SC01_077 but 0/222 broad rotation → #1 debug.** gate_stage commit tag made phase-agnostic. The whole arc + measured numbers: `docs/gen2-mips-matching-model.md`; memory `cheap-tier-ab-validated`. NEXT: T7 debug, then bounded mass-runs + corpus-v3. - 2026-06-30: **T7 DEBUGGED + FIXED.** 3 Explore scouts (tooling / run-evidence / corpus) + a direct code read (R14 — which resolved a flat contradiction between two scouts) found **two independent bugs** in `lora_grind`'s gate path: **(A)** `good_sha()` passed `" "` vs harvest_verify's bare `sha1()` → 0 banks for ALL binaries incl. 077 (so 077's "0/12" was a bug artifact); **(B)** `src/asm/out` defaulted to ov_SC01_077 → non-077 drafts dropped at the 077 stub-filter, silently. Fixed `gate_stage.run_gate` (binary-agnostic resolution + bare-hash normalize + a loud negative-control guard) + `lora_grind.good_sha`; byte-neutral (check-all 136/136). ov_SC01_000 spot-run **banked 7/15 (47%) byte-identical** (@commit:0322) → reach-2 `func_8017CE24` propagated ×2. **ROI:** 6/7 reach-1 → broad rotation is high bank-rate / low fleet-% ROI; the fleet lever is **reach≥2 targeting** + corpus-v3. Backlog now correctly classified (4× close=1 = grinder fuel). NEXT: **T8 corpus-v3** (struct types) + **T9 reach≥2 selection** + concurrent grinder. - 2026-06-30 (cont.): **T9 reach≥2 targeting built + measured.** Added `lora_grind --min-reach N` (lazy sig-based reach oracle == `dedup_propagate`, validated 0-mismatch/60 + the func_8017CE24=2 ground truth; `--min-reach 2` ranks high-reach-first, naturally restricts to overlays). Bounded reach≥2 mass-run: ov_SC01_000's 15 reach≥2 (shared) stubs banked **0/15** (vs the reach-1 spot-run's 7/15) — the model is **weakest on shared code** (corpus skipped the `DEFINE_func` bodies + it's the regalloc/schedule tail). But **5/15 are close≤3 reach-134 near-misses** (3× close=1 = func_8012E27C/BF4C/AD64) → high-value permuter fuel (×134 each). **FINDING: reach≥2 model-only ≠ a fleet lever; the lever is reach≥2-draft → grinder-close (×134)**, which needs `grinder.py`'s per-binary fix (same class as T7) + a backlog `binary` field. (A foreground mass-run hit the 10-min Bash cap mid-2nd-batch; tree recovered clean via `git checkout`, check-all 136/136.) Details: `docs/gen2-mips-matching-model.md` "T9 RESULT". NEXT: the grinder per-binary fix (realize the reach-134 ×134 fuel), then corpus-v3. - 2026-06-30 (cont.): **Grinder per-binary fix (5-layer) — built + validated; reach≥2 fuel re-characterized.** The grinder/backlog were ov_SC01_077-hardcoded **5 layers deep** — fixed all: `gate_stage` records `binary`, `backlog.FIELDS` keeps it, `backlog.load_best`/`_open_stubs` fleet-aware (stuck-local fns surface via their overlay record), `p16_permute.setup` takes asm-subdir, `grinder` per-binary resolution + grouped gating + None-`nins` allow. Validated: the 3 fresh reach-134 close=1 ov_SC01_000 fns now surface + resolve + gate per-000. **FINDINGS (byte-evidenced):** (a) the reach≥2 close=1 fuel is **MODEL semantic-misses** (7B drafts empty `void f(void){}` for trivial `return 1`/setters), NOT permuter fuel — a corrected draft banks them (**+3 byte-identical** via the fixed gate, @commit:0326); (b) ×reach is **propagation-capped** (inline-matched in `ov_SC01_077_a.c` → dedup "nothing to propagate" → banked ×1). So the reach-134 ×134 payoff routes through **corpus-v3** (leaf variety) + the **dedup-collapse**, NOT the permuter. check-all 136/136 throughout. Details: `docs/gen2-mips-matching-model.md` (grinder fix). NEXT: corpus-v3 + dedup-collapse. - 2026-06-30 (**8-hour autonomous run, Drew away**): prompt-fix + local serving + corpus-v3 + v3 + big batch. **LM Studio ejected** → built **`tools/serve_local.py`** (Unsloth GPU serving as an OpenAI endpoint; the prebuilt llama-cpp-python CUDA wheel SIGILLs on this no-AVX-512 CPU, so the Unsloth/torch path is the reliable one). **PROMPT FIX** (`api_draft.LEAN_SYS` + `format_finetune.SYS`, synced): "translate EVERY instruction, never an empty body" — the v2 empty-leaf overfit, small-leaf band **0/3→2/3**, banked 3 on a fresh ov_SC01_001 batch. **CORPUS-V3**: `export_pairs` now mines the **1623 engine_core.h `DEFINE_func` macros** (the shared setters/return-const the model was blind to — 96.6% of v2 was overlay-unique) + `format_finetune` inlines `engine_types.h` structs → corpus **1312→2891**, trainable **2534+291** (2.5× v2). **v3 trained** (Qwen2.5-Coder-7B QLoRA, loss 1.275→0.085, ~2h), held-out gate-true eval **MATCH 23/40 (57.5%)** (generalizing; v2's mixed-set rate was lower). Grinder concurrent during training = 0 banks (permuter tail exhausted, Phase-22 reality). **Big batch** (v3, broad rotation, 25 binaries, propagate-every-3, **$0 LLM**): banked **~352 fns inline + 45 new shared groups** (1633→1678) → fleet **63.67% → 63.82%** (+502 byte-identical), **136/136 byte-clean**, 25 auto-commits. v3 repeatedly banked the **empty-leaf/setter class v2 couldn't** (func_8012E27C=return 1, 8012AD64/BF4C=setters). **Pipeline validated end-to-end:** a free local fine-tuned model harvests the small/setter bulk at $0, gated identically (G3/P9). Details: `docs/gen2-mips-matching-model.md` ("Corpus-v3 ... 8-hour autonomous run"). **NEXT (fresh session):** (1) **shared/reach≥2 targeting** (`lora_grind --min-reach 2` with v3 — so the setter banks propagate ×134 instead of re-banking inline per binary — the fleet-% lever); (2) **dedup-collapse** the per-binary inline setters; (3) **corpus-v4** = struct-giant types; raise `--max-nins` as the band lifts. Serve: `tools/serve_local.py --adapter models/bfm-match-7b-v3` (R21 / SETUP §Tooling inventory).