mlx-bun 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +175 -93
- package/docs/dag/training-inference-map.html +575 -0
- package/docs/design/adapters-end-to-end.md +50 -46
- package/docs/design/compat-cli-surface-design.md +10 -8
- package/docs/design/docs-reorg-plan.md +14 -1
- package/docs/design/hlg-sampling.md +12 -2
- package/docs/design/memory-system.md +193 -295
- package/docs/design/optimization_plan.md +4 -2
- package/docs/design/orpo-dynamic-lambda.md +179 -0
- package/docs/design/orpo-future-enhancements.md +340 -0
- package/docs/design/orpo-training.md +1212 -0
- package/docs/design/parallel-slots.md +34 -26
- package/docs/design/parity-tier-dag.md +143 -0
- package/docs/design/runtime-isolation.md +1 -1
- package/docs/design/segmented-backward-training.md +58 -39
- package/docs/design/tauri-desktop-app.md +127 -0
- package/docs/design/w4a16-compute-precision-spike.md +232 -0
- package/docs/investigations/orpo-base-uf-experiment-and-directions.md +120 -0
- package/docs/investigations/orpo-flash-cce-pin-leak.md +167 -0
- package/docs/investigations/orpo-uf-testing-handoff.md +126 -0
- package/docs/{segmented-backward-handoff.md → investigations/segmented-backward-handoff.md} +1 -1
- package/docs/investigations/starter-model-port-handoff.md +32 -13
- package/docs/investigations/steel-flash-cce-handoff.md +489 -0
- package/docs/investigations/trainer-validation-experiment.md +173 -0
- package/docs/planning/IDEAS.md +29 -0
- package/docs/planning/PRODUCT_ROADMAP.md +147 -27
- package/docs/planning/chunk-task-roadmap.md +1 -1
- package/docs/planning/journal.md +161 -0
- package/docs/reference/distribution.md +61 -5
- package/docs/reference/library-api.md +29 -14
- package/docs/reference/memory.md +77 -0
- package/docs/reference/orpo-quickstart.md +130 -0
- package/docs/reference/server-api.md +105 -7
- package/docs/reference/server-config.md +12 -5
- package/docs/reference/training.md +296 -0
- package/package.json +1 -1
- package/src/cli.ts +651 -18
- package/src/eval/ifeval.ts +138 -0
- package/src/eval/runner.ts +76 -6
- package/src/eval/tasks/bfcl.ts +49 -15
- package/src/eval/tasks/gsm8k.ts +21 -9
- package/src/eval/tasks/hashhop.ts +33 -4
- package/src/eval/tasks/humaneval.ts +30 -8
- package/src/eval/tasks/ifeval.ts +52 -18
- package/src/eval/tasks/mmlu.ts +85 -74
- package/src/eval/ultrafeedback.ts +60 -0
- package/src/jobs/runner.ts +6 -0
- package/src/lora.ts +12 -7
- package/src/memory/chunk.ts +25 -0
- package/src/memory/cluster.ts +24 -0
- package/src/memory/db.ts +114 -0
- package/src/memory/ingest.ts +22 -0
- package/src/memory/pipeline.ts +63 -0
- package/src/memory/prompts.ts +23 -0
- package/src/memory/schedule.ts +163 -0
- package/src/memory/synthesize.ts +29 -0
- package/src/memory/tools.ts +399 -0
- package/src/memory/vault.ts +694 -0
- package/src/memory/wikify.ts +23 -0
- package/src/mlx/metal-kernel.ts +31 -4
- package/src/model/flash-attention.ts +37 -19
- package/src/model/fused-geglu-kernel.ts +111 -0
- package/src/model/gemma4-base.ts +39 -1
- package/src/model/gemma4.ts +196 -21
- package/src/model/minicpm5.ts +44 -3
- package/src/pi-provider.ts +29 -13
- package/src/pi-session.ts +81 -0
- package/src/pi-terminal.ts +13 -24
- package/src/pi-web.ts +246 -166
- package/src/quantize/job.ts +17 -2
- package/src/quantize/quantizer.ts +26 -2
- package/src/server.ts +216 -47
- package/src/tokenizer.ts +6 -2
- package/src/train/dataset.ts +29 -6
- package/src/train/flash-cce.ts +1175 -0
- package/src/train/job.ts +64 -3
- package/src/train/lora-params.ts +53 -4
- package/src/train/loss.ts +947 -0
- package/src/train/optimizer.ts +10 -3
- package/src/train/prefix-shared.ts +435 -0
- package/src/train/rank.ts +36 -0
- package/src/train/segmented.ts +1148 -3
- package/src/train/steel-qmm-header.ts +1461 -0
- package/src/train/trainer.ts +570 -54
- package/src/train/watch.ts +542 -0
- package/src/web/app.html +285 -19
- package/src/web/skills/memory/SKILL.md +97 -0
- package/src/web/skills/web-research/SKILL.md +3 -1
- package/src/web/skills.ts +46 -8
- /package/{docs → src/assets}/curve-designer.html +0 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Trainer validation experiment
|
|
2
|
+
|
|
3
|
+
**Status:** in progress (started 2026-06-20). **Goal:** prove the from-scratch
|
|
4
|
+
SFT/DPO/ORPO trainer actually works before trusting any downstream result — general
|
|
5
|
+
capability (Goal 1) or task-specific (Goal 2).
|
|
6
|
+
|
|
7
|
+
## Why this exists — the smoking gun
|
|
8
|
+
|
|
9
|
+
The margin/accuracy is recorded in `metrics.json` (`valTrajectory[].margin`,
|
|
10
|
+
`bestMargin`), not the stdout log. The prior `cpm5-uf` ORPO run (λ=0.1) shows:
|
|
11
|
+
|
|
12
|
+
| step | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 |
|
|
13
|
+
|---|---|---|---|---|---|---|---|---|
|
|
14
|
+
| accuracy | .507 | .514 | .534 | .541 | .534 | .520 | .547 | .547 |
|
|
15
|
+
| margin | .132 | .124 | .110 | .120 | .136 | .128 | .118 | .123 |
|
|
16
|
+
|
|
17
|
+
**Preference accuracy is ~0.52 (near the 0.50 coin-flip) and the margin is flat.**
|
|
18
|
+
ORPO's defining job is to push accuracy up and widen the margin. This is *not* a clean
|
|
19
|
+
"it works" signal. Caveats: 800 steps ≈ 0.05 epoch (undertrained), provenance
|
|
20
|
+
(from-base vs warm-start) unconfirmed. But it's enough to make validation mandatory:
|
|
21
|
+
**we cannot currently claim our ORPO learns the preference at all.**
|
|
22
|
+
|
|
23
|
+
## Method-decomposition (the validation key)
|
|
24
|
+
|
|
25
|
+
`ORPO_loss = NLL(chosen) + λ·L_OR`. NLL is exactly the SFT objective; L_OR is the
|
|
26
|
+
reference-free odds-ratio preference term. So we validate the building blocks, and ORPO
|
|
27
|
+
is covered by its parts plus a small ORPO-specific surface (the OR term + λ).
|
|
28
|
+
|
|
29
|
+
mlx-lm has **no DPO/ORPO** — so the oracle strategy is:
|
|
30
|
+
- **SFT/NLL** → numeric parity vs **mlx-lm tuner** (oracle env exists).
|
|
31
|
+
- **DPO + OR loss** → **numeric unit test vs the paper's equations** on a fixed tiny
|
|
32
|
+
input (hand-computed expected loss + grad). No external dep. Optional stronger
|
|
33
|
+
cross-check: **TRL** `DPOTrainer`/`ORPOTrainer` loss (PyTorch; multi-GB env →
|
|
34
|
+
approve-first, not default).
|
|
35
|
+
|
|
36
|
+
Every positive control is designed so "weak data / weak lever" is **not** an available
|
|
37
|
+
excuse — success must be unambiguous.
|
|
38
|
+
|
|
39
|
+
## PILLAR 1 — Prove the trainer (gate for everything)
|
|
40
|
+
|
|
41
|
+
- **1a · SFT positive control** *(infra GREEN, behavioral check WEAK)*. Train
|
|
42
|
+
`fixtures/adapters/data-upper` from scratch on MiniCPM5; assert generations are
|
|
43
|
+
actually uppercased (oracle: the repo's pre-trained `upper` adapter). Existing
|
|
44
|
+
`train-e2e.test.ts` only checks loss↓ — add the behavioral assert. *(seconds)*
|
|
45
|
+
- **1b · DPO positive control** *(MISSING)*. Trivial preference (chosen uppercase,
|
|
46
|
+
rejected lowercase). Assert **margin increases** and **preference accuracy → ~1.0**.
|
|
47
|
+
- **1c · ORPO positive control + the real question.** (i) Same trivial preference →
|
|
48
|
+
accuracy → ~1.0. (ii) **From-BASE ORPO on UF**, multiple epochs, watch margin widen
|
|
49
|
+
and accuracy climb well above chance — directly resolves the near-chance finding
|
|
50
|
+
(undertraining vs warm-start vs bug).
|
|
51
|
+
- **1d · Loss-formula parity.** SFT NLL vs mlx-lm; DPO/ORPO loss + grad vs hand-computed
|
|
52
|
+
paper equations (unit test). The training-side analogue of the inference logit-parity
|
|
53
|
+
oracle — currently MISSING.
|
|
54
|
+
- **1e · vs the paper.** Internal proxy first: preference accuracy robustly > chance on
|
|
55
|
+
UF. Then (approve-first, LLM-judge) AlpacaEval/MT-Bench **direction** vs base (not
|
|
56
|
+
absolute — paper used 7B). optiq suite (incl. IFEval) is an already-local downstream.
|
|
57
|
+
|
|
58
|
+
> **Gate:** until 1a–1c are green, Pillars 2 & 3 sit on an unverified trainer.
|
|
59
|
+
|
|
60
|
+
## PILLAR 2 — Measure general-capability impact (optiq suite)
|
|
61
|
+
|
|
62
|
+
All 6 datasets are present locally (`~/.cache/mlx-bun/eval-data/`: mmlu, gsm8k, ifeval,
|
|
63
|
+
bfcl, humaneval, hashhop); harness = `scripts/eval.ts capability`.
|
|
64
|
+
- **2a · Eval parity:** run on base CPM5, reproduce optiq's **30.28** within tolerance
|
|
65
|
+
(validates the *measurement* before trusting deltas; confirm the README's open BFCL
|
|
66
|
+
"no-calls" / MMLU "at-chance" flags aren't broken on our side).
|
|
67
|
+
- **2b · Delta:** trained checkpoint vs base, on the 6-task mean.
|
|
68
|
+
- **2c · Targeting:** mean is floored by **BFCL 0.0 / GSM8K 2.7 / HashHop 4.0**; UF hits
|
|
69
|
+
the already-strong IFEval, so expect small mean movement. Highest-leverage Goal-1 lever
|
|
70
|
+
is **BFCL** (0.0 is format-trainable; 0→30 = +5 on the mean).
|
|
71
|
+
|
|
72
|
+
## PILLAR 3 — Task-specific (separate; gated on Pillar 1)
|
|
73
|
+
|
|
74
|
+
Chunk segmenter via `scripts/chunk-eval.ts` (boundary/label accuracy), using the
|
|
75
|
+
hard-negative ORPO data (remove-a-cut / add-a-cut, balanced to kill the length
|
|
76
|
+
shortcut). Measured by chunk accuracy, not val loss / capability. Credibility depends on
|
|
77
|
+
Pillar 1.
|
|
78
|
+
|
|
79
|
+
## Sequencing & gates
|
|
80
|
+
|
|
81
|
+
1. Pillar 1 positive controls (1a→1b→1c-trivial) — fast, "does the machinery work."
|
|
82
|
+
2. Pillar 1 real replication (1c from-base UF) + 1d loss-formula parity.
|
|
83
|
+
3. Pillar 2a eval-parity (reproduce 30.28) → 2b deltas.
|
|
84
|
+
4. Pillar 3 — once the trainer is trusted.
|
|
85
|
+
|
|
86
|
+
## Execution log
|
|
87
|
+
|
|
88
|
+
### 2026-06-20 — Pillar 1 quick validations: PASS
|
|
89
|
+
|
|
90
|
+
**Machinery + gradient correctness — GREEN.** `MLX_BUN_TEST_TRAIN=1 bun test
|
|
91
|
+
train-autograd train-e2e train-orpo-e2e` → 8 pass / 0 fail, real training on
|
|
92
|
+
MiniCPM5 (11.3s). `train-autograd` confirms the backward matches autograd.
|
|
93
|
+
|
|
94
|
+
**ORPO positive control (prefer-uppercase, 12 train / 4 val, 120 iters, lr 1e-3) — PASS.**
|
|
95
|
+
Held-out preference accuracy **1.000 by step 10**, margin **5.5 → 4.0** (noisy at lr=1e-3
|
|
96
|
+
but accuracy solid). Logged: `runs/orpo-pc.{json,csv}`.
|
|
97
|
+
|
|
98
|
+
**DPO positive control (same data) — PASS, cleaner.** Accuracy **1.000 from step 10**,
|
|
99
|
+
margin **monotonic 6.46 → 9.67**. Logged: `runs/dpo-pc.{json,csv}`.
|
|
100
|
+
|
|
101
|
+
**Conclusion: the preference machinery is validated.** On a learnable preference both
|
|
102
|
+
DPO and ORPO drive accuracy to 1.0 and the margin wide open (vs UF's flat ~0.12). A
|
|
103
|
+
broken trainer could not. ⇒ **The UF near-chance result is the DATA, not a bug** — UF
|
|
104
|
+
pairs are genuinely subtle for a 1B (both responses plausible); "prefer uppercase" is
|
|
105
|
+
trivially separable. The smoking gun is explained: implementation sound, lever weak.
|
|
106
|
+
|
|
107
|
+
**Gotcha found (recorded so we don't repeat it):** `AdapterManager.mount` (the *serving*
|
|
108
|
+
hot-swap path) does **not** reflect into `branchLogpMean` (the *training* forward) — an
|
|
109
|
+
external base-vs-adapter `orpoMetrics` measurement via mount returns bit-identical
|
|
110
|
+
numbers (measured: 0.500/0.1463 both). Measure training-side metrics via the
|
|
111
|
+
`trainLora` emit / loraState path (`scripts/experiments/pref-control.ts`), not mount.
|
|
112
|
+
|
|
113
|
+
**Logging for plotting:** `runs/<stem>.{json,csv}` — per-step train `{loss, accuracy,
|
|
114
|
+
margin}` + per-eval val `{loss, accuracy, margin}`. CSV columns: `phase,step,loss,
|
|
115
|
+
accuracy,margin`. Tools: `scripts/experiments/pref-control.ts` (train+log),
|
|
116
|
+
`scripts/experiments/measure-pref.ts` (standalone adapter eval — superseded by the
|
|
117
|
+
emit path for training metrics; see gotcha).
|
|
118
|
+
|
|
119
|
+
### 2026-06-20 — Pillar 2a: capability-harness parity. Two bugs found + fixed.
|
|
120
|
+
|
|
121
|
+
**The first-measure step caught two harness bugs** (exactly its purpose — never trust a
|
|
122
|
+
delta until the harness reproduces the published baseline):
|
|
123
|
+
|
|
124
|
+
1. **Thinking mode.** The eval rendered the chat template's *default* (thinking-on for
|
|
125
|
+
MiniCPM5); optiq's numbers are **non-thinking** (HF card: IFEval 64.7 is non-thinking).
|
|
126
|
+
Symptom: IFEval **36.7%** vs 64.7. **Fix:** `src/eval/runner.ts` `generateText` now
|
|
127
|
+
renders `enableThinking: false` by default (env `MLX_BUN_EVAL_THINK=1` to re-enable).
|
|
128
|
+
→ IFEval **73.3%** (n=30), optiq ballpark.
|
|
129
|
+
2. **HumanEval oracle-python path.** `src/eval/tasks/humaneval.ts` hardcoded
|
|
130
|
+
`/Users/joshrossi/Code/mlx-lm/.venv/bin/python` — wrong on this machine (it's
|
|
131
|
+
`mlx-lm-example`), so every code-exec spawned a missing interpreter → **0%**. **Fix:**
|
|
132
|
+
env-overridable `MLX_BUN_ORACLE_PYTHON` (committed default = reference box; never
|
|
133
|
+
re-commit the path). → HumanEval **53.3%** (n=15), optiq 57.9.
|
|
134
|
+
|
|
135
|
+
**Post-fix parity (base CPM5, non-thinking, n=20 — all ballpark):**
|
|
136
|
+
|
|
137
|
+
| | MMLU | GSM8K | IFEval | BFCL | HumanEval | HashHop |
|
|
138
|
+
|---|---|---|---|---|---|---|
|
|
139
|
+
| ours | 55.0 | 10.0 | 75.0 | 0.0 | ~53 | 0.0 |
|
|
140
|
+
| optiq | 52.4 | 2.7 | 64.7 | 0.0 | 57.9 | 4.0 |
|
|
141
|
+
|
|
142
|
+
⇒ **harness reproduces optiq.** Small-n inflation on GSM8K/IFEval will settle toward
|
|
143
|
+
optiq on the full set. **Exp-1 baseline harness VALIDATED.**
|
|
144
|
+
|
|
145
|
+
### Ready-to-fire sequence (everything staged; only GPU time remains)
|
|
146
|
+
|
|
147
|
+
All measurement tools validated. `scripts/eval.ts` now takes `--adapter <dir>` (mounts the
|
|
148
|
+
trained LoRA → x2). `ORC=/Users/joshrossi/Code/mlx-lm-example/.venv/bin/python` below.
|
|
149
|
+
|
|
150
|
+
1. **x1** (running): `MLX_BUN_ORACLE_PYTHON=$ORC bun scripts/eval.ts capability --candidate MiniCPM5`
|
|
151
|
+
2. **Train** (user): from-base ORPO/UF → `./adapters/cpm5-uf-frombase` (command in chat).
|
|
152
|
+
3. **x2 (Exp 1 after):** `MLX_BUN_ORACLE_PYTHON=$ORC bun scripts/eval.ts capability --candidate MiniCPM5 --adapter ./adapters/cpm5-uf-frombase` → compare to x1.
|
|
153
|
+
4. **Exp 2 gen (GPU):** `bun scripts/experiments/prefbench.ts gen mtbench ./adapters/cpm5-uf-frombase mtbench-uf` (and `gen alpaca … alpaca-uf 200` for a subset).
|
|
154
|
+
5. **Exp 2 judge (codex, anytime):** `bun scripts/experiments/prefbench.ts judge mtbench-uf` → TRAINED win-rate vs BASE.
|
|
155
|
+
|
|
156
|
+
### Next
|
|
157
|
+
- **Full baseline:** see step 1 (running). **(long → user-launched)**
|
|
158
|
+
- **Train (the "do"):** from-base ORPO on UF (`pref-control.ts`, command in chat). **(long → user-launched)**
|
|
159
|
+
- **Re-measure → x2**, compare to x1. Exp 1 done.
|
|
160
|
+
- **Exp 2 judge — VALIDATED (keyless).** No OpenAI key needed: the judge shells out to
|
|
161
|
+
`codex exec -m gpt-5.4-mini` (codex's own auth), forcing structured `{"winner":...}` via
|
|
162
|
+
`--output-schema` and reading the clean verdict from `-o`. `scripts/experiments/llm-judge.ts`
|
|
163
|
+
— `judgeOnce` + `judgePair` (judges BOTH orders to cancel small-judge position bias).
|
|
164
|
+
Positive control: **3/3 obvious good-vs-bad pairs, good won both orders, zero flips.**
|
|
165
|
+
1bx vs 1bx1; consistency (same judge both sides), not absolute comparability to the paper's GPT-4.
|
|
166
|
+
- **Exp 2 harness — STAGED.** Prompts downloaded: `~/.cache/mlx-bun/eval-data/{mt_bench.jsonl
|
|
167
|
+
(80), alpaca_eval.json (805)}`. Two-phase runner `scripts/experiments/prefbench.ts`:
|
|
168
|
+
`gen <mtbench|alpaca> <adapterDir> <stem> [limit]` (GPU — base then trained, non-thinking)
|
|
169
|
+
→ `runs/<stem>.responses.jsonl`; `judge <stem> [limit]` (codex) → TRAINED win-rate vs BASE,
|
|
170
|
+
judging trained-vs-base **directly** (head-to-head, not vs a fixed reference). Only the `gen`
|
|
171
|
+
phase needs GPU; everything else is validated. Fire after the from-base training produces
|
|
172
|
+
`./adapters/cpm5-uf-frombase`.
|
|
173
|
+
- **1c real / 1d parity** — still open (lower priority now the positive controls + harness parity are green).
|
package/docs/planning/IDEAS.md
CHANGED
|
@@ -8,6 +8,35 @@ work in flight lives in [PLAN.md](../../PLAN.md).
|
|
|
8
8
|
|
|
9
9
|
## Positioning: what mlx-bun is becoming
|
|
10
10
|
|
|
11
|
+
### The finite-matrix local AI appliance
|
|
12
|
+
|
|
13
|
+
`mlx-bun` can be a local AI product in a way generic LLM runtimes cannot. On
|
|
14
|
+
MLX, the hardware universe is Apple Silicon Macs: a finite list of real shipped
|
|
15
|
+
chip/RAM/bandwidth combinations. The supported model universe is curated, and
|
|
16
|
+
the runtime usually knows the task mode: chat, tool use, vision, eval,
|
|
17
|
+
training, adapter routing, or background automation.
|
|
18
|
+
|
|
19
|
+
That means the default path can be a table of measured choices instead of a
|
|
20
|
+
pile of user-facing knobs:
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
physical Mac SKU + model checksum + task mode -> optimized execution plan
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The plan can choose the model, context budget, KV policy, adapter policy,
|
|
27
|
+
kernel route, compile route, and memory envelope before the user has to care.
|
|
28
|
+
The user runs `mlx-bun`; the system gives them the most useful local assistant
|
|
29
|
+
their Mac can comfortably run. Power users still get the stick-shift controls,
|
|
30
|
+
but the product default is curated.
|
|
31
|
+
|
|
32
|
+
This is also why optimization work belongs in the product vision, not just the
|
|
33
|
+
lab notebook. Loop unrolling, branch deletion, DAG extraction, custom kernels,
|
|
34
|
+
compiled graph tables, Cut Cross Entropy, Liger-style fused heads, and
|
|
35
|
+
ORPO-trained adapters matter when they move a real device/model profile from
|
|
36
|
+
"cute demo" to "leave it running because it is useful."
|
|
37
|
+
|
|
38
|
+
### Local in the deeper sense
|
|
39
|
+
|
|
11
40
|
Two phrases that capture the thesis — especially **mlx-bun × Lucien**:
|
|
12
41
|
|
|
13
42
|
1. **"A truly local model in a deeper sense."**
|
|
@@ -19,9 +19,34 @@ provider config before they can try local AI. They should install one
|
|
|
19
19
|
binary, run one command, wait for one resumable model download, and get a
|
|
20
20
|
working local chat UI plus a local AI server.
|
|
21
21
|
|
|
22
|
+
The intended first-run shape is product, not demo: start with the tiny
|
|
23
|
+
welcoming model quickly, begin the stronger recommended model download in the
|
|
24
|
+
background, and graduate the user into the best default profile for their
|
|
25
|
+
actual Mac without requiring them to learn the model zoo first.
|
|
26
|
+
|
|
22
27
|
The runtime can still expose every advanced knob. The default path should
|
|
23
28
|
not require them.
|
|
24
29
|
|
|
30
|
+
## Four product faces
|
|
31
|
+
|
|
32
|
+
The same runtime has to support four related but distinct jobs:
|
|
33
|
+
|
|
34
|
+
1. **Local AI product** — the one-command experience for someone who just wants
|
|
35
|
+
useful local AI. This path should be opinionated, benchmarked, and boringly
|
|
36
|
+
reliable.
|
|
37
|
+
2. **Library for app developers** — the embeddable TypeScript/MLX layer for
|
|
38
|
+
Bun, Tauri, Electron, and desktop tools that want local inference without a
|
|
39
|
+
Python sidecar.
|
|
40
|
+
3. **AI lab for researchers** — the place where parity, evals, kernels,
|
|
41
|
+
compiled graphs, ORPO, LoRA adapters, and model experiments can happen in
|
|
42
|
+
the same stack that serves the result.
|
|
43
|
+
4. **Autonomous background agent runtime** — the always-available local server
|
|
44
|
+
for scripts, recurring tasks, personal tools, and per-request adapter
|
|
45
|
+
routing.
|
|
46
|
+
|
|
47
|
+
These are not separate products. They are one runtime with progressive
|
|
48
|
+
disclosure: appliance defaults at the front, sharp instruments underneath.
|
|
49
|
+
|
|
25
50
|
## The three modes (the organizing design law)
|
|
26
51
|
|
|
27
52
|
mlx-bun has not published anything: the entire CLI, command, and flag
|
|
@@ -72,7 +97,7 @@ verbs and one flag vocabulary, not three separate CLIs:
|
|
|
72
97
|
serving, latency prediction, the Lab UI) graft onto the mlx-lm spine
|
|
73
98
|
as additive flags and a few new verbs.
|
|
74
99
|
|
|
75
|
-
### Status (2026-06-12)
|
|
100
|
+
### Status (2026-06-12; compat surface design remains open as of 2026-06-17)
|
|
76
101
|
|
|
77
102
|
The compat layer does not exist yet: today's commands (`serve`, `fit`,
|
|
78
103
|
`benchmark`, `evals`, `get`/`scan`/`ls`, `pi`, `harness`) are ad-hoc,
|
|
@@ -83,27 +108,42 @@ reconcile the existing commands into it, and fill the capability matrix
|
|
|
83
108
|
behind it (convert/quantize, LoRA training, eval/perplexity, fuse,
|
|
84
109
|
cache_prompt, upload/share). Decomposed into phases in PLAN.md.
|
|
85
110
|
|
|
86
|
-
## Core idea
|
|
111
|
+
## Core idea: finite matrix runtime
|
|
87
112
|
|
|
88
|
-
The local-AI problem is not continuous for Mac users.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
and preselect the best known model artifact,
|
|
92
|
-
memory budget, and runtime
|
|
113
|
+
The local-AI problem is not continuous for Mac users. MLX means Apple Silicon,
|
|
114
|
+
and Apple ships a finite set of real chip/RAM/bandwidth combinations. The
|
|
115
|
+
supported model universe is also curated. For each physical device profile and
|
|
116
|
+
model snapshot, we can research and preselect the best known model artifact,
|
|
117
|
+
context target, KV mode, memory budget, kernel route, graph route, and runtime
|
|
118
|
+
flags.
|
|
93
119
|
|
|
94
|
-
That means first-run behavior can be a lookup table, not a wizard or
|
|
95
|
-
benchmark:
|
|
120
|
+
That means first-run behavior can be a measured lookup table, not a wizard or
|
|
121
|
+
an exploratory benchmark:
|
|
96
122
|
|
|
97
123
|
```text
|
|
98
|
-
detect Mac profile
|
|
99
|
-
|
|
124
|
+
detect exact Mac profile
|
|
125
|
+
fingerprint the model snapshot
|
|
126
|
+
choose the task mode: chat, vision, tool loop, adapter, training, eval
|
|
127
|
+
look up the measured execution plan
|
|
100
128
|
download/resume the exact model artifact
|
|
101
129
|
start the tuned local server
|
|
102
130
|
open the built-in UI
|
|
103
131
|
show how to connect external tools and harnesses
|
|
104
132
|
```
|
|
105
133
|
|
|
106
|
-
The
|
|
134
|
+
The current `fit` endpoint is the nucleus of this idea: it already reasons over
|
|
135
|
+
real Apple Silicon profiles, model bytes, KV bytes, context, and predicted
|
|
136
|
+
decode. The product version should evolve from "does this fit?" into:
|
|
137
|
+
|
|
138
|
+
```text
|
|
139
|
+
device profile + model checksum + task mode + live memory pressure
|
|
140
|
+
-> recommended execution plan
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Execution plans can eventually include specialized graph variants, fused kernel
|
|
144
|
+
routes, prompt-cache policy, adapter policy, safe context limits, expected
|
|
145
|
+
decode/TTFT, and user-facing rationale. The research work feeds the table. The
|
|
146
|
+
table feeds the user.
|
|
107
147
|
|
|
108
148
|
## Recommended profiles
|
|
109
149
|
|
|
@@ -143,6 +183,31 @@ changes a device-tier decision, unlocks a larger useful context window,
|
|
|
143
183
|
keeps decode above a user-visible threshold, improves quality at the same
|
|
144
184
|
budget, or prevents failure. Saving memory for its own sake is secondary.
|
|
145
185
|
|
|
186
|
+
## Specialization strategy
|
|
187
|
+
|
|
188
|
+
For the curated product path, it is acceptable to trade more work up front for a
|
|
189
|
+
better hot path later. The Mac is known, the model snapshot is known, and the
|
|
190
|
+
task mode is known. That lets us precompute or cache exact plans instead of
|
|
191
|
+
shipping every decision to runtime.
|
|
192
|
+
|
|
193
|
+
Candidate plan fields:
|
|
194
|
+
|
|
195
|
+
- Model checksum and architecture fingerprint.
|
|
196
|
+
- Apple Silicon profile: chip, unified memory, bandwidth tier, GPU/CPU shape.
|
|
197
|
+
- Context buckets and fixed-shape cache layouts.
|
|
198
|
+
- Kernel choices: MLX built-ins, MLX fast paths, custom Metal kernels, or
|
|
199
|
+
eager fallback.
|
|
200
|
+
- Graph choices: eager, compiled exact-shape graph, compiled bucketed graph,
|
|
201
|
+
or prebuilt graph table.
|
|
202
|
+
- Adapter choices: none, single LoRA, stacked LoRA, task-routed LoRA.
|
|
203
|
+
- Measured results: TTFT, decode tok/s, peak memory, failure envelope.
|
|
204
|
+
|
|
205
|
+
This is where loop unrolling, branch deletion, static DAG extraction, fused
|
|
206
|
+
kernels, and shape-specialized compiled graphs become product features rather
|
|
207
|
+
than abstract optimizations. They matter when they let the default profile be
|
|
208
|
+
faster, larger, more stable, or better aligned without asking the user to tune
|
|
209
|
+
anything.
|
|
210
|
+
|
|
146
211
|
## First-run UX
|
|
147
212
|
|
|
148
213
|
The no-args command should be the friendly path:
|
|
@@ -153,17 +218,40 @@ mlx-bun
|
|
|
153
218
|
|
|
154
219
|
Likely behavior:
|
|
155
220
|
|
|
156
|
-
1. Detect the hardware profile.
|
|
157
|
-
2. Pick the recommended runtime profile.
|
|
158
|
-
3. Show the selected model and why it was chosen.
|
|
159
|
-
4. Start or resume the background download.
|
|
160
|
-
5. Start the local server when ready.
|
|
161
|
-
6. Open or serve the built-in chat UI.
|
|
162
|
-
7. Show connection details for OpenAI/Anthropic-compatible clients.
|
|
163
|
-
8. Offer harness setup actions for Pi, Hermes, OpenClaw, and others.
|
|
221
|
+
1. Detect the hardware profile. *(not yet: `recommendedRepoId()` at `src/fit.ts:86` returns `DEFAULT_REPO_ID` unconditionally — device-profile lookup table is unimplemented)*
|
|
222
|
+
2. Pick the recommended runtime profile. *(same caveat as above)*
|
|
223
|
+
3. Show the selected model and why it was chosen. *(not yet: model is printed, rationale is not)*
|
|
224
|
+
4. Start or resume the background download. **Shipped** — auto-download on first run; resumable.
|
|
225
|
+
5. Start the local server when ready. **Shipped.**
|
|
226
|
+
6. Open or serve the built-in chat UI. **Shipped** — browser tab opened automatically; URL printed either way; `--no-open` skips the tab.
|
|
227
|
+
7. Show connection details for OpenAI/Anthropic-compatible clients. **Shipped** — printed at startup.
|
|
228
|
+
8. Offer harness setup actions for Pi, Hermes, OpenClaw, and others. *Partially shipped: `mlx-bun harness pi` configures an existing Pi install; Hermes and OpenClaw adapters do not exist.*
|
|
164
229
|
|
|
165
230
|
This should feel like an appliance, not a framework tutorial.
|
|
166
231
|
|
|
232
|
+
## Adapter catalog vision
|
|
233
|
+
|
|
234
|
+
The average user should not have to train adapters to benefit from them. The
|
|
235
|
+
product path can ship or offer a curated catalog of lightweight LoRA adapters:
|
|
236
|
+
assistant, coding, writing, research, planning, personal-ops, and other
|
|
237
|
+
well-scoped modes. A user can install one small adapter and get a better local
|
|
238
|
+
assistant without changing the base model.
|
|
239
|
+
|
|
240
|
+
Advanced users can still train their own adapters with mlx-bun. The broader
|
|
241
|
+
product loop is:
|
|
242
|
+
|
|
243
|
+
```text
|
|
244
|
+
curate conversations and task data
|
|
245
|
+
train / ORPO task adapters
|
|
246
|
+
publish small adapter artifacts
|
|
247
|
+
route requests through the right adapter per task
|
|
248
|
+
keep the base model resident
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
This makes ORPO performance directly product-relevant. If training is cheap and
|
|
252
|
+
repeatable enough, the project can keep improving small local models through
|
|
253
|
+
adapter updates instead of asking every user to download a larger base model.
|
|
254
|
+
|
|
167
255
|
## Built-in UI
|
|
168
256
|
|
|
169
257
|
Bun can serve the UI directly from the same executable. The default UI
|
|
@@ -213,6 +301,27 @@ The UI should not hide that this is local. It should make "local" feel
|
|
|
213
301
|
easy: predictable memory, transparent downloads, offline-capable serving,
|
|
214
302
|
and clear integration instructions.
|
|
215
303
|
|
|
304
|
+
### SigLIP vision sidecar — image input for gemma e4b (shipped, 2026-06-17)
|
|
305
|
+
|
|
306
|
+
The Gemma 4 e4b vision encoder (`src/vision/siglip.ts`) is a faithful port
|
|
307
|
+
of the optiq SigLIP tower. It auto-loads on first image input and falls back
|
|
308
|
+
to text-only mode when no image is present, so the chat UI gains image support
|
|
309
|
+
with no startup cost. Features are within ~1% rel-RMSE of the optiq oracle;
|
|
310
|
+
every MLX primitive (rms_norm, gelu, matmul, SDPA, RoPE, pooling) is bit-exact.
|
|
311
|
+
The residual is sub-bf16 floating-point accumulation across the deep encoder,
|
|
312
|
+
not a porting gap. L1/L2/L3 paths all work; vision falls back to the monolith
|
|
313
|
+
path on L1 and L2.
|
|
314
|
+
|
|
315
|
+
### Adapter web selector and CLI /v1/adapters (shipped)
|
|
316
|
+
|
|
317
|
+
Trained LoRA adapters can be selected from the chat UI via a per-conversation
|
|
318
|
+
picker (`#chat-adapter` in `src/web/app.html`). The picker lists adapters
|
|
319
|
+
discovered by `GET /v1/adapters/available`, mounts the chosen one via
|
|
320
|
+
`POST /v1/adapters`, and sends a `set_adapter` message to Pi-web so that
|
|
321
|
+
every subsequent turn is routed through the right adapter — including the
|
|
322
|
+
`before_provider_request` hook in `src/pi-web.ts`. Adapters are base-model-scoped
|
|
323
|
+
and dropped on model swap.
|
|
324
|
+
|
|
216
325
|
### The UI must never lag while the AI is busy (candidate, 2026-06-16)
|
|
217
326
|
|
|
218
327
|
A product guarantee: **the AI may crash, the UI never may** — and the UI
|
|
@@ -413,14 +522,25 @@ reuse across pipeline stages.
|
|
|
413
522
|
|
|
414
523
|
## Open questions
|
|
415
524
|
|
|
416
|
-
- What exact profile table should ship first?
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
- What is the minimum first-run UI that feels good enough?
|
|
421
|
-
- What is the right install path: Homebrew, curl installer, GitHub
|
|
422
|
-
release, or all of the above?
|
|
525
|
+
- What exact profile table should ship first? *(The device-profile lookup
|
|
526
|
+
table is unimplemented — `recommendedRepoId()` returns `DEFAULT_REPO_ID`
|
|
527
|
+
for every SKU. The profile shape in this doc is the spec; the research
|
|
528
|
+
work and the fill remain open.)*
|
|
423
529
|
- How should background downloads interact with server startup and UI
|
|
424
530
|
readiness?
|
|
425
531
|
- How should profile recommendations be versioned as benchmarks improve?
|
|
426
532
|
- When Qwen lands, what evals decide whether it replaces a Gemma default?
|
|
533
|
+
|
|
534
|
+
### Resolved
|
|
535
|
+
|
|
536
|
+
- **Should no-args open the UI automatically, print a local URL, or both?**
|
|
537
|
+
Both: the browser tab is opened automatically on start; the URL is always
|
|
538
|
+
printed; `--no-open` suppresses the tab without hiding the URL.
|
|
539
|
+
- **Which UI stack should be embedded? / How much of Pi should be bundled?**
|
|
540
|
+
Resolved by harness-pi + pi-web shipping: the web UI is the bundled
|
|
541
|
+
`src/web/app.html` chat page talking to Pi via `pi-web.ts`; Pi is
|
|
542
|
+
configured externally via `mlx-bun harness pi`, or launched embedded via
|
|
543
|
+
`mlx-bun pi`.
|
|
544
|
+
- **What is the right install path?** All of the above — shipped: npm
|
|
545
|
+
publish (v0.0.4, `bunx mlx-bun`), Homebrew release pipeline (sign,
|
|
546
|
+
notarize, `joshuarossi/homebrew-tap`), and GitHub release binary.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Chunk-task quality roadmap (2026-06-17)
|
|
2
2
|
|
|
3
3
|
Where the MiniCPM5 chunking adapter stands and what raises real segmentation
|
|
4
|
-
quality. Companion to
|
|
4
|
+
quality. Companion to the chunk-eval metric vs. accuracy finding (score = well-formedness, not gold accuracy) and
|
|
5
5
|
[`docs/investigations/chunk-finetune-experiment.md`](../investigations/chunk-finetune-experiment.md).
|
|
6
6
|
|
|
7
7
|
## Where we are
|
package/docs/planning/journal.md
CHANGED
|
@@ -153,3 +153,164 @@ the what/when (phases, exit criteria); this is the narrative behind it.
|
|
|
153
153
|
3. **Tool param values were JSON-coerced regardless of schema.** `<param name="path">2025</param>` became the number 2025. Oracle (`_decode_tool_parameter_value` + `_tool_parameter_schema`) keeps string-typed params as raw text. Ported; also made the `<param>` regex CDATA-safe (a `</param>` inside CDATA no longer truncates the value). Unit tests added.
|
|
154
154
|
4. **Sampling ignored the model's `generation_config.json`.** The server hardcoded temp 0.7 / no top-p; optiq serve injects the author-recommended sampling (`optiq/runtime/gen_config.py`) as defaults. Ported: `loadContext` reads `generation_config.json` (MiniCPM5: temp 0.9, top_p 0.95; Gemma: temp 1.0, top_k 64, top_p 0.95) and `toOptions` uses it when the request omits the field. Explicit request values still win. This was the likely "quality" gap on a 1B model.
|
|
155
155
|
- Verification: MiniCPM5 parity (bf16 + mixed-KV) still 100/100 bit-exact; minicpm5-tools, tool-call, chat-template, tools-template, parity (Gemma 12B), server-tools, and server suites all green. Live two-turn agent round-trip (tool call → tool result → final answer) confirmed working in-process.
|
|
156
|
+
|
|
157
|
+
## 2026-06-16 — Segmented-backward LoRA training (PRs #9–10)
|
|
158
|
+
|
|
159
|
+
Long-context LoRA training was OOMing: MiniCPM5 @4096 spiked to ~25.5 GB full
|
|
160
|
+
backward because all activations had to stay live simultaneously. The
|
|
161
|
+
segmented-backward design threads the backward segment-by-segment so only one
|
|
162
|
+
segment's activations are resident at a time.
|
|
163
|
+
|
|
164
|
+
The key implementation decision was to drive the per-segment backward with
|
|
165
|
+
`mlx_vjp` (a new `Vjp` binding in `autograd.ts`) rather than a surrogate
|
|
166
|
+
`value_and_grad` over a reconstructed loss. The surrogate path leaked roughly
|
|
167
|
+
one activation buffer per segment per step because mlx `eval` does not detach;
|
|
168
|
+
`mlx_vjp` is explicit about what it differentiates and stops the leak.
|
|
169
|
+
Segment-boundary tensors are materialized as graph-free leaves via
|
|
170
|
+
`fromBytesCopy` for the same reason.
|
|
171
|
+
|
|
172
|
+
Phase A (MiniCPM5, PR #9) landed first: grads bit-exact vs the full backward
|
|
173
|
+
(relNorm 0.000%), peak 10.91 → 3.29 GB @2048, no leak, trains end-to-end via
|
|
174
|
+
`TrainConfig.segmentSize`. The default training attention is `ops.sdpa`
|
|
175
|
+
(correct gradients, one fused kernel); `MLX_BUN_TRAIN_ATTN=flash` is an
|
|
176
|
+
opt-in for bit-exact grad comparison against the full backward only, because
|
|
177
|
+
`ops.sdpa` has a pre-existing eager-vs-autograd asymmetry in bf16 (~6%) that
|
|
178
|
+
is unrelated to the segmentation mechanism — the segmented path works
|
|
179
|
+
correctly under either setting.
|
|
180
|
+
|
|
181
|
+
Phase B (e4b / Gemma4, PR #10) was harder: e4b's KV-shared donor pattern
|
|
182
|
+
means multiple sharer segments reference the same K/V tensors produced by a
|
|
183
|
+
single producer segment, requiring cotangent accumulation across sharers before
|
|
184
|
+
the producer vjp can run. Implemented `SegmentedBackwardGemma4` with a
|
|
185
|
+
two-boundary stream (per-layer-input + donor K/V) and a multi-output producer
|
|
186
|
+
vjp `[h, donorK, donorV]` with accumulated `[dh, dKV.k, dKV.v]` cotangents.
|
|
187
|
+
Grads are bit-exact for single-consumer donor reuse and 0.97% bf16-class for
|
|
188
|
+
multi-consumer accumulation (confirmed the full backward also sums in bf16, so
|
|
189
|
+
this is order-of-operations noise, not a logic bug). e4b trains at 8 K context
|
|
190
|
+
(~17.5 GB, segSize=2) where the full backward would OOM (~70 GB).
|
|
191
|
+
|
|
192
|
+
A contiguity fix was also required: donor K/V are transposed views, so
|
|
193
|
+
`detachLeaf` must row-major-copy them before `rawBytes` reads linearly.
|
|
194
|
+
|
|
195
|
+
## 2026-06-16 — Qwen3.5 / MiniCPM5 reasoning-channel fix (PR #12)
|
|
196
|
+
|
|
197
|
+
Two serving-layer bugs left reasoning-capable models unusable in chat:
|
|
198
|
+
|
|
199
|
+
The first was a role-name mismatch. pi-ai renames the system role to
|
|
200
|
+
`developer` for reasoning models. Our chat templates only accepted
|
|
201
|
+
system/user/assistant/tool and returned 400. The turn completed with
|
|
202
|
+
`stopReason: "error"` without throwing, so the browser saw an empty turn with no
|
|
203
|
+
error shown. Fix: `normalizeMessages` maps developer→system (genuine OpenAI
|
|
204
|
+
compat — OpenAI itself uses the developer role), and `mapEventToFrames` surfaces
|
|
205
|
+
a `stopReason:"error"` turn as a visible error frame instead of silently
|
|
206
|
+
discarding it.
|
|
207
|
+
|
|
208
|
+
The second was reasoning leaking into content. Qwen3.5 and MiniCPM5 templates
|
|
209
|
+
prime an *open* `<think>` tag in the generation prompt, so the model's first
|
|
210
|
+
token is the closing `</think>` — the opening tag never appears in the decoded
|
|
211
|
+
stream. `ThinkingTagSplitter` was waiting for the open tag before routing
|
|
212
|
+
reasoning content into the OpenAI `reasoning_content` channel, so all of it
|
|
213
|
+
leaked into `content` instead. Fix: `promptEndsInOpenThink` detects this
|
|
214
|
+
condition and seeds `ThinkingTagSplitter` via `startInThinking` so the splitter
|
|
215
|
+
begins in the reasoning state.
|
|
216
|
+
|
|
217
|
+
## 2026-06-16 — Adapters end-to-end (PR #13)
|
|
218
|
+
|
|
219
|
+
The goal was to make LoRA adapter switching a first-class operation in both the
|
|
220
|
+
web chat UI and the Pi CLI, without building a custom protocol — lean on Pi's
|
|
221
|
+
`before_provider_request` hook instead.
|
|
222
|
+
|
|
223
|
+
Server-side: `GET /v1/adapters/available` scans the two adapter stores
|
|
224
|
+
(`~/.cache/mlx-bun-finetunes`, `~/.cache/mlx-bun/adapters`) and returns
|
|
225
|
+
`{id, path, rank, scale, mounted}` for weight-bearing checkpoints (dataset-only
|
|
226
|
+
dirs are skipped). `lora.ts` gained `listAvailableAdapters` for the scan and
|
|
227
|
+
base-model detection so the web selector can filter to compatible adapters only.
|
|
228
|
+
|
|
229
|
+
Pi hook: `pi-web.ts` registers `pi.on("before_provider_request")` to inject the
|
|
230
|
+
per-connection selected adapter into every outgoing provider payload using the
|
|
231
|
+
existing inline-extension pattern. A `set_adapter` WebSocket message sets the
|
|
232
|
+
selection; default none leaves the payload unchanged so the base model runs.
|
|
233
|
+
|
|
234
|
+
Web chat: an adapter dropdown was added next to the Thinking pill, populated
|
|
235
|
+
from `/v1/adapters/available` on ready. Selecting fires an idempotent `POST
|
|
236
|
+
/v1/adapters` then a `set_adapter` WS message.
|
|
237
|
+
|
|
238
|
+
CLI: `extensions/mlx-bun-adapter.ts` is a shipped Pi extension exposing
|
|
239
|
+
`/adapter <id> | off | list` and the same hook. Both UIs control adapters
|
|
240
|
+
through Pi; no separate plumbing.
|
|
241
|
+
|
|
242
|
+
## 2026-06-17 — First-run onboarding UX
|
|
243
|
+
|
|
244
|
+
The previous startup story required knowing the right model ID, running a
|
|
245
|
+
separate download command, and manually opening a browser. The new path:
|
|
246
|
+
`mlx-bun serve` (or bare `mlx-bun`) auto-downloads the starter if absent,
|
|
247
|
+
starts the server, and opens the chat UI. If a localhost tab is already open it
|
|
248
|
+
focuses that tab rather than opening a duplicate (`--no-open` to skip).
|
|
249
|
+
|
|
250
|
+
The web chat empty state was updated with mlx-bun-centric starter chips and a
|
|
251
|
+
download-aware greeting. A live background-download pill shows progress. The
|
|
252
|
+
system prompt briefs the model on mlx-bun and its own identity, with framing
|
|
253
|
+
appropriate to MiniCPM5 being a fast starter while a larger model optionally
|
|
254
|
+
loads in the background.
|
|
255
|
+
|
|
256
|
+
`chat-template.ts` also tightened `supportsThinking` to require the `<think>`
|
|
257
|
+
channel we actually split, so e4b / harmony-format models no longer leak raw
|
|
258
|
+
reasoning markers into content for users who haven't configured a thinkingFormat.
|
|
259
|
+
|
|
260
|
+
The runtime-isolation design doc (`docs/design/runtime-isolation.md`) was also
|
|
261
|
+
written this session: inference running in-process on the Bun event loop is a
|
|
262
|
+
known UI-lag footgun when the GPU is busy. The doc proposes moving inference to
|
|
263
|
+
a subprocess; the work is explicitly deferred (design, not started) — written
|
|
264
|
+
now so the root-cause analysis is available cold.
|
|
265
|
+
|
|
266
|
+
## 2026-06-17 — SigLIP vision sidecar for gemma-4 e4b
|
|
267
|
+
|
|
268
|
+
Ported optiq's `vlm/gemma4` SigLIP encoder as `src/vision/siglip.ts`: a 16-layer
|
|
269
|
+
vision transformer with clippable linears (trained clip bounds), manual-f32
|
|
270
|
+
RMS norms on q/k/v, on-device 2D RoPE, fused SDPA at scale=1.0, and GeGLU →
|
|
271
|
+
3×3 avg-pool → `MultimodalEmbedder` → `/embed_scale`.
|
|
272
|
+
|
|
273
|
+
The tower selection is driven by `vision_config.model_type`: e2b/e4b get
|
|
274
|
+
SigLIP, the 12B gets the existing encoder-free tower. Lazy loading means
|
|
275
|
+
text-only sessions never pay for it. `gemma4.ts`'s `forwardEmbeddings` was
|
|
276
|
+
extended to support per-layer-input models (e2b/e4b) — it previously threw for
|
|
277
|
+
that path — threading zeroed image-token ids into the per-layer forward matching
|
|
278
|
+
optiq's `where(text_mask, ids, 0)`.
|
|
279
|
+
|
|
280
|
+
Parity: every mlx primitive is bit-exact vs the optiq oracle (verified
|
|
281
|
+
model-free via `op-parity-{dump.py,check.ts}` — rms_norm, gelu, matmul, clip,
|
|
282
|
+
cos, sin, full multidim RoPE, sdpa, pool). The residual ~1% feature divergence
|
|
283
|
+
is sub-bf16 full-graph composition order amplified by the encoder's scale=1.0
|
|
284
|
+
peaked softmax (the topped-out softmax distribution is highly sensitive to small
|
|
285
|
+
pre-softmax shifts in bf16). The acceptance bar was exact spliced ids + greedy
|
|
286
|
+
prefix + grounded output, which passes. Driving the residual to zero would
|
|
287
|
+
require matching optiq's exact op ordering across the full encoder; deferred as
|
|
288
|
+
a TODO.
|
|
289
|
+
|
|
290
|
+
## 2026-06-17 — Distribution: npm, bunx, Homebrew signed/notarized, direct download
|
|
291
|
+
|
|
292
|
+
The question was how to ship a standalone binary that runs on a user's Mac
|
|
293
|
+
without requiring Bun, Python, or anything else. We settled on four parallel
|
|
294
|
+
install paths rather than picking one.
|
|
295
|
+
|
|
296
|
+
**npm / bunx**: `bunx mlx-bun` for users already in the Bun ecosystem. A
|
|
297
|
+
`publish-release.sh` script and `bun run publish` alias handle the npm side.
|
|
298
|
+
Published as v0.0.4.
|
|
299
|
+
|
|
300
|
+
**Homebrew**: `scripts/release-binary.sh` builds a standalone binary, signs it
|
|
301
|
+
with a Developer ID (nested Mach-O first, then the executable with JIT
|
|
302
|
+
entitlements from `packaging/entitlements.plist`), notarizes via `AC_PROFILE`, and
|
|
303
|
+
produces a clean tarball + sha256 for the formula.
|
|
304
|
+
`packaging/homebrew/mlx-bun.rb` is the formula source of truth (self-contained
|
|
305
|
+
`libexec` bundle + `bin` symlink; Bun `realpath`s `execPath` so sibling dylibs
|
|
306
|
+
resolve after relocation). The tap lives at `joshuarossi/homebrew-tap`.
|
|
307
|
+
`scripts/publish-release.sh` auto-syncs the formula sha256 on each release.
|
|
308
|
+
One fix was required: `--disable-library-validation` in the entitlements so
|
|
309
|
+
brew-relocated dylibs load without `CS_RESTRICT` blocking them.
|
|
310
|
+
|
|
311
|
+
**Direct download**: the notarized tarball passes Gatekeeper even when
|
|
312
|
+
downloaded via browser + Finder (quarantine worst case), so a stable versionless
|
|
313
|
+
URL (`releases/latest/download/mlx-bun-arm64.tar.gz`) is published alongside the
|
|
314
|
+
versioned asset and documented as a one-liner curl install.
|
|
315
|
+
|
|
316
|
+
**Embedded library**: existing path, unchanged (`docs/reference/embedding.md`).
|