mlx-bun 0.0.4
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/LICENSE +21 -0
- package/README.md +362 -0
- package/THIRD_PARTY_LICENSES.md +41 -0
- package/bin/mlx-bun.mjs +32 -0
- package/docs/curve-designer.html +303 -0
- package/docs/design/adapters-end-to-end.md +201 -0
- package/docs/design/compat-cli-surface-design.md +227 -0
- package/docs/design/docs-reorg-plan.md +152 -0
- package/docs/design/hlg-sampling.md +340 -0
- package/docs/design/memory-system.md +339 -0
- package/docs/design/optimization_plan.md +344 -0
- package/docs/design/parallel-slots.md +515 -0
- package/docs/design/runtime-isolation.md +247 -0
- package/docs/design/segmented-backward-training.md +508 -0
- package/docs/investigations/chunk-finetune-experiment.md +84 -0
- package/docs/investigations/curve-bisector-routing.md +123 -0
- package/docs/investigations/curve-runs/bisector-route.json +98 -0
- package/docs/investigations/curve-runs/bisector-trace.json +1393 -0
- package/docs/investigations/curve-runs/curve-terrain.json +1179 -0
- package/docs/investigations/curve-runs/curve-terrain.svg +42 -0
- package/docs/investigations/curve-terrain.html +105 -0
- package/docs/investigations/expert-offload-single-user-moe.md +292 -0
- package/docs/investigations/hlg-ab.html +34 -0
- package/docs/investigations/hlg-explorer.html +238 -0
- package/docs/investigations/hlg-figs/cliff.svg +1 -0
- package/docs/investigations/hlg-figs/cross.svg +1 -0
- package/docs/investigations/hlg-figs/edges.svg +1 -0
- package/docs/investigations/hlg-figs/k20-grids.svg +1 -0
- package/docs/investigations/hlg-figs/k20-peaks.svg +1 -0
- package/docs/investigations/hlg-figs/lw-curve.svg +1 -0
- package/docs/investigations/hlg-figs/terrain.svg +1 -0
- package/docs/investigations/hlg-report.html +262 -0
- package/docs/investigations/hlg-runs/hlg-ab.json +149 -0
- package/docs/investigations/hlg-runs/hlg-blind-prompts.md +87 -0
- package/docs/investigations/hlg-runs/hlg-blind.json +107 -0
- package/docs/investigations/hlg-runs/hlg-cliff.json +197 -0
- package/docs/investigations/hlg-runs/hlg-cross.json +505 -0
- package/docs/investigations/hlg-runs/hlg-edge.json +388 -0
- package/docs/investigations/hlg-runs/hlg-eval1.json +25 -0
- package/docs/investigations/hlg-runs/hlg-frontier.json +86 -0
- package/docs/investigations/hlg-runs/hlg-grid.json +242 -0
- package/docs/investigations/hlg-runs/hlg-grid2.json +343 -0
- package/docs/investigations/hlg-runs/hlg-lw-coarse.json +110 -0
- package/docs/investigations/hlg-runs/hlg-map.json +383 -0
- package/docs/investigations/hlg-runs/run0-original.log +159 -0
- package/docs/investigations/hlg-runs/runA-recommended.log +131 -0
- package/docs/investigations/hlg-runs/runAA-cliff-cpm5.log +26 -0
- package/docs/investigations/hlg-runs/runAB-grid2-e4b.log +18 -0
- package/docs/investigations/hlg-runs/runAC-peak-e4b-partial.log +25 -0
- package/docs/investigations/hlg-runs/runAD-ab-samples.log +8 -0
- package/docs/investigations/hlg-runs/runAE-edge-e4b.log +51 -0
- package/docs/investigations/hlg-runs/runAF-logit-calibration.log +10 -0
- package/docs/investigations/hlg-runs/runAG-eval-config2.log +16 -0
- package/docs/investigations/hlg-runs/runB-gain-variants.log +192 -0
- package/docs/investigations/hlg-runs/runC-pivots.log +162 -0
- package/docs/investigations/hlg-runs/runD-oetf.log +132 -0
- package/docs/investigations/hlg-runs/runE-eotf.log +162 -0
- package/docs/investigations/hlg-runs/runF-shaper-working.log +86 -0
- package/docs/investigations/hlg-runs/runG-shaper-sweep.log +101 -0
- package/docs/investigations/hlg-runs/runH-wsweep.log +176 -0
- package/docs/investigations/hlg-runs/runI-asweep.log +176 -0
- package/docs/investigations/hlg-runs/runJ-knobmap.log +49 -0
- package/docs/investigations/hlg-runs/runK-frontier.log +19 -0
- package/docs/investigations/hlg-runs/runL-widemap.log +65 -0
- package/docs/investigations/hlg-runs/runM-frontier-emb.log +15 -0
- package/docs/investigations/hlg-runs/runN-verify.log +116 -0
- package/docs/investigations/hlg-runs/runO-frontier-seed5000.log +15 -0
- package/docs/investigations/hlg-runs/runP-grid-AxSm.log +15 -0
- package/docs/investigations/hlg-runs/runQ-grid-tgW.log +15 -0
- package/docs/investigations/hlg-runs/runR-blind-transcript.log +79 -0
- package/docs/investigations/hlg-runs/runS-cross-coherence.log +33 -0
- package/docs/investigations/hlg-runs/runT-cross-cpm5.log +33 -0
- package/docs/investigations/hlg-runs/runU-cross-12b.log +33 -0
- package/docs/investigations/hlg-runs/runV-lw-e4b.log +22 -0
- package/docs/investigations/hlg-runs/runW-lw-12b.log +22 -0
- package/docs/investigations/hlg-runs/runX-lw-cpm5.log +22 -0
- package/docs/investigations/hlg-runs/runY-cliff-e4b.log +26 -0
- package/docs/investigations/hlg-runs/runZ-cliff-12b.log +26 -0
- package/docs/investigations/hlg-sampling-investigation.md +970 -0
- package/docs/investigations/lab-build-journal.md +157 -0
- package/docs/investigations/pi-builtin-investigation.md +308 -0
- package/docs/investigations/starter-model-port-handoff.md +323 -0
- package/docs/planning/IDEAS.md +26 -0
- package/docs/planning/PRODUCT_ROADMAP.md +426 -0
- package/docs/planning/ResearchTopics.md +119 -0
- package/docs/planning/chunk-task-roadmap.md +77 -0
- package/docs/planning/journal.md +155 -0
- package/docs/reference/distribution.md +121 -0
- package/docs/reference/embedding.md +168 -0
- package/docs/reference/library-api.md +153 -0
- package/docs/reference/server-api.md +224 -0
- package/docs/reference/server-config.md +243 -0
- package/docs/segmented-backward-handoff.md +148 -0
- package/package.json +68 -0
- package/src/anthropic.ts +491 -0
- package/src/chat-template.ts +186 -0
- package/src/cli.ts +1075 -0
- package/src/config.ts +229 -0
- package/src/curve-sampler.ts +122 -0
- package/src/dataset/generators.ts +1087 -0
- package/src/dataset/index.ts +12 -0
- package/src/dataset/job.ts +81 -0
- package/src/dataset/llm.ts +71 -0
- package/src/dataset/registry.ts +528 -0
- package/src/download.ts +283 -0
- package/src/eval/README.md +86 -0
- package/src/eval/kl-prompts.ts +29 -0
- package/src/eval/kl.ts +294 -0
- package/src/eval/quality-db.ts +92 -0
- package/src/eval/runner.ts +102 -0
- package/src/eval/score.ts +49 -0
- package/src/eval/tasks/bfcl.ts +500 -0
- package/src/eval/tasks/gsm8k.ts +85 -0
- package/src/eval/tasks/hashhop.ts +109 -0
- package/src/eval/tasks/humaneval.ts +252 -0
- package/src/eval/tasks/ifeval.ts +354 -0
- package/src/eval/tasks/mmlu.ts +145 -0
- package/src/evaldb.ts +199 -0
- package/src/expert-offload-build.ts +77 -0
- package/src/expert-offload.ts +61 -0
- package/src/expert-trace.ts +67 -0
- package/src/fit.ts +254 -0
- package/src/generate.ts +451 -0
- package/src/harness-pi.ts +177 -0
- package/src/hf-push.ts +628 -0
- package/src/index.ts +22 -0
- package/src/jobs/db.ts +139 -0
- package/src/jobs/index.ts +29 -0
- package/src/jobs/job-entry.ts +106 -0
- package/src/jobs/runner.ts +304 -0
- package/src/jobs/sse.ts +114 -0
- package/src/jobs/types.ts +58 -0
- package/src/kv-store.ts +185 -0
- package/src/lora.ts +314 -0
- package/src/md-modules.d.ts +7 -0
- package/src/mlx/array.ts +237 -0
- package/src/mlx/autograd.ts +267 -0
- package/src/mlx/checkpoint.ts +116 -0
- package/src/mlx/compile.ts +158 -0
- package/src/mlx/custom-vjp.ts +140 -0
- package/src/mlx/ffi.ts +433 -0
- package/src/mlx/metal-kernel.ts +130 -0
- package/src/mlx/ops.ts +707 -0
- package/src/mmap.ts +90 -0
- package/src/model/batched-mask.ts +204 -0
- package/src/model/batched-rotating.ts +300 -0
- package/src/model/compiled-decode.ts +681 -0
- package/src/model/factory.ts +27 -0
- package/src/model/fingerprint.ts +33 -0
- package/src/model/flash-attention.ts +674 -0
- package/src/model/fused-decode-kernel.ts +305 -0
- package/src/model/fused-geglu-kernel.ts +87 -0
- package/src/model/gemma4-base.ts +1616 -0
- package/src/model/gemma4.ts +1012 -0
- package/src/model/generated/gemma4-12b.ts +526 -0
- package/src/model/generated/gemma4-26b.ts +500 -0
- package/src/model/generated/gemma4-e4b.ts +757 -0
- package/src/model/generated/index.ts +18 -0
- package/src/model/minicpm5.ts +278 -0
- package/src/model/qwen3-delta.ts +224 -0
- package/src/model/qwen3_5.ts +429 -0
- package/src/model/support.ts +46 -0
- package/src/native-pack.ts +152 -0
- package/src/pi-provider.ts +95 -0
- package/src/pi-terminal.ts +264 -0
- package/src/pi-web.ts +924 -0
- package/src/preflight.ts +123 -0
- package/src/prompt-cache.ts +113 -0
- package/src/quantize/allocator.ts +627 -0
- package/src/quantize/calibration.ts +180 -0
- package/src/quantize/config-writer.ts +132 -0
- package/src/quantize/index.ts +57 -0
- package/src/quantize/job.ts +136 -0
- package/src/quantize/quantizer.ts +462 -0
- package/src/quantize/safetensors-writer.ts +171 -0
- package/src/quantize/sensitivity.ts +234 -0
- package/src/registry.ts +336 -0
- package/src/responses.ts +774 -0
- package/src/safetensors.ts +158 -0
- package/src/sampler.ts +517 -0
- package/src/serve/batch-scheduler.ts +362 -0
- package/src/serve/generation-gateway.ts +160 -0
- package/src/server.ts +1891 -0
- package/src/spec/drafter.ts +261 -0
- package/src/spec/generate.ts +221 -0
- package/src/status-page.html +497 -0
- package/src/tokenizer.ts +38 -0
- package/src/tool-call.ts +242 -0
- package/src/train/dataset.ts +461 -0
- package/src/train/export.ts +32 -0
- package/src/train/forward.ts +229 -0
- package/src/train/index.ts +43 -0
- package/src/train/job.ts +148 -0
- package/src/train/lora-params.ts +194 -0
- package/src/train/loss.ts +342 -0
- package/src/train/merge.ts +195 -0
- package/src/train/optimizer.ts +176 -0
- package/src/train/rank.ts +101 -0
- package/src/train/segmented.ts +520 -0
- package/src/train/trainer.ts +600 -0
- package/src/tui.ts +161 -0
- package/src/vision/embedder.ts +168 -0
- package/src/vision/preprocess.ts +247 -0
- package/src/vision/prompt.ts +158 -0
- package/src/vision/siglip.ts +599 -0
- package/src/web/app.html +2471 -0
- package/src/web/skills/web-research/SKILL.md +44 -0
- package/src/web/skills.ts +59 -0
- package/src/web-tools.ts +426 -0
- package/src/weights.ts +91 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Build journal — mlx-bun Lab (web UI + native quantize/train/dataset + pi web chat)
|
|
2
|
+
|
|
3
|
+
*Branch `web-ui-and-native-lab`, 2026-06-13/14. Visual summary:
|
|
4
|
+
[archive/mlx-bun-lab-report.html](../../archive/mlx-bun-lab-report.html). Evidence
|
|
5
|
+
artifact: `benchmarks/lab-verification.json` (regenerable, gitignored — produced by
|
|
6
|
+
`scripts/verify-lab.ts`).*
|
|
7
|
+
|
|
8
|
+
## Goal
|
|
9
|
+
|
|
10
|
+
Re-implement the whole mlx-bun web surface as one beautiful, keynote-grade SPA
|
|
11
|
+
with five sections — a **full pi-agent chat**, plus **Quantize**, **Fine-tune**,
|
|
12
|
+
and **Build Dataset** ported from the OptIQ "lab" — while keeping the status
|
|
13
|
+
page. Compute is **native Bun/MLX (no Python)** per Josh's decision.
|
|
14
|
+
|
|
15
|
+
## The decisions that shaped it
|
|
16
|
+
|
|
17
|
+
1. **The big fork (asked up front).** Quantize + fine-tune compute could be (a)
|
|
18
|
+
native via mlx-c, (b) orchestrate the installed OptIQ Python, or (c) proxy
|
|
19
|
+
the running `optiq lab`. Josh chose **(a) native** — truest to mlx-bun's "the
|
|
20
|
+
buggy layer doesn't need Python" thesis. Chat = **full pi agent** (all tools).
|
|
21
|
+
Verification = **real tiny runs, pausing for go-ahead** before heavy jobs.
|
|
22
|
+
|
|
23
|
+
2. **Gate the riskiest unknown first.** Native LoRA needs autograd across the
|
|
24
|
+
FFI boundary. Rather than discover mid-build that it's infeasible, the first
|
|
25
|
+
thing built was `spikes/phase-train-vag.ts`: a tiny LoRA-shaped loss
|
|
26
|
+
(`mean((x + (x@A)@B − y)²)`) differentiated by `mlx_value_and_grad` through a
|
|
27
|
+
`bun:ffi` JSCallback closure, checked against central finite differences.
|
|
28
|
+
**PASS (<1e-2), exactly 2 grads returned (base frozen).** Only then was the
|
|
29
|
+
trainer worth writing. This single check de-risked the entire subsystem.
|
|
30
|
+
|
|
31
|
+
3. **One SPA, hash-routed, text-imported.** Kept the exact single-binary
|
|
32
|
+
embedding (`with { type: "text" }`) the status page already used — no
|
|
33
|
+
bundler, no asset-resolution to patch in `build-binary.sh`. Design tokens
|
|
34
|
+
lifted from `status-page.html` so the five sections feel like one product.
|
|
35
|
+
|
|
36
|
+
4. **Mixed job-execution model by safety class.** Async Metal command-buffer
|
|
37
|
+
OOM is **uncatchable** (it `std::terminate`s). So GPU jobs (quantize,
|
|
38
|
+
finetune) run as **crash-isolated child `mlx-bun` subprocesses** behind a GPU
|
|
39
|
+
lease; dataset jobs (pure JS + loopback HTTP) run **in-process**. Both write
|
|
40
|
+
the same NDJSON event log + SQLite row, so the SSE/tail path is uniform.
|
|
41
|
+
|
|
42
|
+
5. **Open-book ports, not inventions.** Each engine follows a readable source:
|
|
43
|
+
dataset → `optiq/lab/dataset_templates.py` (13 templates, field-for-field);
|
|
44
|
+
trainer → `optiq/lora/{trainer,dpo,config}.py` + mlx-lm `default_loss`;
|
|
45
|
+
quantize → mlx-lm `convert.py` rules + the on-disk OptIQ model format; jobs →
|
|
46
|
+
`optiq/lab/{jobs,db}.py`; chat → pi SDK `examples/sdk/12-full-control.ts`.
|
|
47
|
+
|
|
48
|
+
## How it was built — parallel orchestration
|
|
49
|
+
|
|
50
|
+
After laying the shared job contract (`src/jobs/types.ts`), six subsystems were
|
|
51
|
+
fanned out to concurrent sub-agents on **non-overlapping files**, then
|
|
52
|
+
integrated centrally:
|
|
53
|
+
|
|
54
|
+
| subsystem | files | how verified |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| shared jobs | `src/jobs/*` | 12 tests (lifecycle, GPU lease, crash isolation, zombie, SSE) |
|
|
57
|
+
| dataset | `src/dataset/*` | 32 tests (generators, CSV, 90/10 split, 13 templates) |
|
|
58
|
+
| quantize | `src/quantize/*` | 6 tests + real-model e2e |
|
|
59
|
+
| trainer | `src/train/*`, `src/mlx/autograd.ts` | 19 fast + real-model e2e |
|
|
60
|
+
| web SPA | `src/web/app.html` | 7 in-process route tests |
|
|
61
|
+
| pi embed | `src/pi-web.ts` | 9 unit + live chat turn |
|
|
62
|
+
|
|
63
|
+
Shared/owned-by-me files were edited centrally to avoid conflicts:
|
|
64
|
+
`src/server.ts` (websocket + Lab routes + serve the SPA), `src/mlx/{ffi,ops}.ts`
|
|
65
|
+
(autograd/random/save bindings), `src/cli.ts`, `package.json` (pinned
|
|
66
|
+
`@earendil-works/pi-coding-agent@0.79.3`).
|
|
67
|
+
|
|
68
|
+
## Key technical findings
|
|
69
|
+
|
|
70
|
+
- **`mlx_save_safetensors` exists** (mlx-c `io.h`) — the quantizer writes real
|
|
71
|
+
HF-layout shards natively; no hand-rolled byte writer, no dtype/alignment bugs.
|
|
72
|
+
- **`mlx_value_and_grad` + `mlx_closure_new_func_payload`** are drivable from a
|
|
73
|
+
`bun:ffi` JSCallback (the repo already uses one for the mlx error handler).
|
|
74
|
+
The closure swaps the differentiated primals into `LoraWeights.{a,b}`, builds
|
|
75
|
+
the loss graph, and restores; mlx's graph keeps its own refs, so only A/B
|
|
76
|
+
differentiate and the quantized base stays frozen.
|
|
77
|
+
- **No optimizer in mlx-c** → AdamW hand-rolled as array ops (m/v EMA, bias
|
|
78
|
+
correction, decoupled weight decay), each step swapping the leaf handle back
|
|
79
|
+
into the `LoraWeights` so the next forward sees the update.
|
|
80
|
+
- **pi tool-approval gate** is a real pre-execution hook: an inline extension
|
|
81
|
+
via `DefaultResourceLoader.extensionFactories` registers `pi.on("tool_call")`
|
|
82
|
+
returning `{block:true}` to deny — read-only tools auto-allow, bash/edit/write
|
|
83
|
+
await a browser decision (120s auto-deny).
|
|
84
|
+
|
|
85
|
+
## Bugs found and fixed (during the real-model runs)
|
|
86
|
+
|
|
87
|
+
- **LoRA init key shape** — `mlx_random_normal` got a `[1,2]` key (a row slice of
|
|
88
|
+
the split-keys array) but mlx wants `[2]`. Reshaped the per-module subkey.
|
|
89
|
+
- **Quantize e2e path** — the gated test passed a model *id* to
|
|
90
|
+
`quantizeModelDir` instead of a resolved snapshot *path* (the job runner
|
|
91
|
+
resolves via the registry; the direct call didn't). Pointed it at
|
|
92
|
+
`SNAPSHOT_MINICPM5`.
|
|
93
|
+
- **Flaky chat test** — the agent sometimes takes a tool-call turn before
|
|
94
|
+
emitting text; the test resolved on the first `turn_end` and saw no tokens.
|
|
95
|
+
Fixed to settle on the first turn that actually streams text. The embed was
|
|
96
|
+
always correct (a debug run showed 25+ text deltas + a tool execution).
|
|
97
|
+
|
|
98
|
+
## Measured evidence (M4 Pro, MiniCPM5-1B, on-disk)
|
|
99
|
+
|
|
100
|
+
- **Quantize:** 170 modules → **4.50 bits/weight**, **0.62 GB** dir, **7.0 s**,
|
|
101
|
+
reopened correctly through the loader (quantized embed_tokens as `uint32`).
|
|
102
|
+
- **Train:** 20 SFT iters, **loss 4.48 → 0.68** in **4.6 s**, 168 adapters
|
|
103
|
+
mounted. Behavior changed — on "Tell me about your day": base replies normally,
|
|
104
|
+
the UPPERCASE-fixture adapter replies `"MY DAY AHEAD. APPROVE AND PROPERLY…"`.
|
|
105
|
+
- **Chat:** a real WebSocket turn streamed text + executed a tool to completion.
|
|
106
|
+
- **Tests:** 85+ new, all green; `tsc --noEmit` clean; existing server suite
|
|
107
|
+
17/17 (no regressions).
|
|
108
|
+
|
|
109
|
+
## Scope: shipped vs designed-seam
|
|
110
|
+
|
|
111
|
+
- **Shipped (v1):** full SPA (5 sections), pi web chat with tools + approval
|
|
112
|
+
gate, native uniform-affine quantize, native SFT + DPO LoRA, 12/13 dataset
|
|
113
|
+
templates, shared job system, real tiny-run verification.
|
|
114
|
+
- **Designed seams (follow-ups):** ~~OptIQ sensitivity+knapsack mixed-precision
|
|
115
|
+
quantization; batched (B>1) training; `hf_dataset_import`; HF push-to-hub;
|
|
116
|
+
folding pi's assets into the compiled single binary.~~ **ALL DONE in Round 2 —
|
|
117
|
+
see below.**
|
|
118
|
+
|
|
119
|
+
## Round 2 — the leftover seams, ported 1:1 (2026-06-14)
|
|
120
|
+
|
|
121
|
+
Josh: "all of those, port, one to one functionality." Built the five remaining
|
|
122
|
+
items as faithful ports of the OptIQ / mlx-lm source (open book), in parallel on
|
|
123
|
+
non-overlapping files, integrated centrally (server routes + one `app.html` UI
|
|
124
|
+
pass), and verified.
|
|
125
|
+
|
|
126
|
+
- **Mixed-precision quantization** (`src/quantize/{allocator,sensitivity,
|
|
127
|
+
calibration}.ts`) — `optimize_mixed_precision` ported **1:1** from
|
|
128
|
+
`optiq/core/optimizer.py` (greedy knapsack + block-aware floor + protected
|
|
129
|
+
layers + block-run guard + the uniform-vs-bf16 `_kl_reduction` sign detection).
|
|
130
|
+
Verified **bit-identical to OptIQ** on shared synthetic inputs by spawning the
|
|
131
|
+
venv Python in the test (4/4 cases). Per-layer KL sensitivity
|
|
132
|
+
(`analyze_sensitivity_exact`) runs on our graph: dequantize each layer's weight
|
|
133
|
+
to bf16, re-quantize at each candidate bit, swap into the running
|
|
134
|
+
`QuantizedLinear`, forward on a WikiText-2 calibration set, KL vs reference.
|
|
135
|
+
Real e2e: MiniCPM5-1B at target 5.0 bpw → 80×4-bit + 90×8-bit, **5.19 bpw, 32 s**.
|
|
136
|
+
- **Batched (B>1) training** (`src/train/{dataset,forward,loss}.ts`) — ragged
|
|
137
|
+
padding + a `[B,1,L,L]` causal-AND-key-validity mask via a cache wrapper (no
|
|
138
|
+
model/op edits); B=1 stays a separate bit-identical path. Bug found: comparing a
|
|
139
|
+
length-4 reference to a length-6 batched row conflated mask correctness with
|
|
140
|
+
bf16 sequence-length rounding — the corrected shape-held parity test passes,
|
|
141
|
+
confirming the mask isolates rows.
|
|
142
|
+
- **`hf_dataset_import`** (`src/dataset/generators.ts`) — the 13th template via
|
|
143
|
+
the HF datasets-server REST API (pagination, config resolve, label/min-chars/
|
|
144
|
+
cap filters, three output formats).
|
|
145
|
+
- **HF push-to-hub** (`src/hf-push.ts`) — native Hub upload (create repo → LFS
|
|
146
|
+
preupload → S3 PUT → NDJSON commit) for model/adapter/dataset repos, token at
|
|
147
|
+
`~/.mlx-bun/hf.json`. Mock-server tested end-to-end (11 tests); real uploads
|
|
148
|
+
untested by design (would publish under the user's account).
|
|
149
|
+
- **Single-binary web chat** (`scripts/build-binary.sh`) — pi's JS already
|
|
150
|
+
bundles under `bun build --compile`; only `photon_rs_bg.wasm` is sidecarred.
|
|
151
|
+
The compiled binary's headless pi session was verified to construct.
|
|
152
|
+
|
|
153
|
+
UI: one `app.html` pass enabled the Uniform/Mixed quantize toggle + target-bpw
|
|
154
|
+
slider, live merge/export/push on the fine-tune done step, push on quantize &
|
|
155
|
+
dataset, and an HF-token settings gear. New server routes:
|
|
156
|
+
`/api/finetune/{merge,export}`, `/api/{quantize,finetune,dataset}/push`,
|
|
157
|
+
`/api/settings/hf-token`, + mixed-precision params on quantize submit.
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# Investigation: pi as a "built-in" terminal for mlx-bun
|
|
2
|
+
|
|
3
|
+
*2026-06-12. Question (PRODUCT_ROADMAP "Harness UX" / PLAN direction A):
|
|
4
|
+
can `mlx-bun pi` drop the user into a working pi agent session against
|
|
5
|
+
the locally running model — and what's the best mechanism? Sources:
|
|
6
|
+
https://pi.dev/docs/latest (sdk, tui, rpc, custom-provider pages) plus
|
|
7
|
+
direct inspection of the installed packages
|
|
8
|
+
(`~/.bun/install/global/node_modules/@earendil-works/*`, v0.79.1).*
|
|
9
|
+
|
|
10
|
+
> **Outcome (2026-06-14, what actually shipped).** This investigation
|
|
11
|
+
> proposed shipping the subprocess launcher first and promoting to an
|
|
12
|
+
> embedded flagship later. We went straight to the embedded design and
|
|
13
|
+
> then **dropped the subprocess path entirely** (Option 2 + the
|
|
14
|
+
> `--external-pi` fallback in P4): `mlx-bun pi` runs pi's own
|
|
15
|
+
> `InteractiveMode` **in-process** (src/pi-terminal.ts) with a custom
|
|
16
|
+
> mlx-bun coding-agent prompt, full coding tools + web tools, and pi's
|
|
17
|
+
> native approval prompts — nothing to install. A user who already runs
|
|
18
|
+
> pi connects it to the local model with **`mlx-bun harness pi`**
|
|
19
|
+
> (Option 1, src/harness-pi.ts) — that's the only "their pi" path, since
|
|
20
|
+
> launching a pi they already have was pure duplication. The web-chat
|
|
21
|
+
> half (Option 4) ships via src/pi-web.ts. The analysis below is the
|
|
22
|
+
> point-in-time reasoning that led here.
|
|
23
|
+
|
|
24
|
+
## TL;DR
|
|
25
|
+
|
|
26
|
+
**Yes, and better than hoped.** pi is not just embeddable — it is a
|
|
27
|
+
Bun-native, MIT-licensed npm package whose own release binary is built
|
|
28
|
+
with `bun build --compile`, the exact toolchain mlx-bun's Phase 5
|
|
29
|
+
single-binary bundle already uses. The full interactive TUI (editor,
|
|
30
|
+
chat history, all built-in commands) is an exported SDK class
|
|
31
|
+
(`InteractiveMode`), not CLI-only internals. Four integration options
|
|
32
|
+
exist, they are not mutually exclusive, and the recommended path is:
|
|
33
|
+
ship the cheap subprocess launcher now, promote to a true in-binary
|
|
34
|
+
embed as the flagship. pi's RPC mode separately gives us the protocol
|
|
35
|
+
for the built-in web chat UI, so this investigation pays for direction
|
|
36
|
+
A's part 1 as well.
|
|
37
|
+
|
|
38
|
+
## What pi is, technically
|
|
39
|
+
|
|
40
|
+
- Four MIT-licensed packages, all v0.79.1, all from
|
|
41
|
+
`github.com/earendil-works/pi`:
|
|
42
|
+
`pi-coding-agent` (13 MB, the agent + CLI + SDK), `pi-ai` (4.9 MB,
|
|
43
|
+
provider/streaming layer), `pi-agent-core` (1.3 MB), `pi-tui`
|
|
44
|
+
(1.9 MB, differential-rendering TUI lib). ~21 MB total on disk
|
|
45
|
+
before tree-shaking — negligible next to model weights, modest next
|
|
46
|
+
to mlx-bun's current binary.
|
|
47
|
+
- **Bun-compile native**: `pi-coding-agent`'s `build:binary` script is
|
|
48
|
+
literally `bun build --compile ./dist/bun/cli.js ...` — upstream
|
|
49
|
+
ships a Bun single-file executable themselves. Embedding it in our
|
|
50
|
+
Bun bundle is a supported configuration, not an experiment.
|
|
51
|
+
- Engines: node ≥ 22.19 (pi-tui uses `/v`-flag regexes — fails under
|
|
52
|
+
node 18, fine under Bun ≥ 1.3.x, which mlx-bun already pins).
|
|
53
|
+
- Assets that must ride along in a binary (upstream's
|
|
54
|
+
`copy-binary-assets` is the manifest): theme JSONs, a few PNGs,
|
|
55
|
+
export-html templates, `photon_rs_bg.wasm` (image ops), and pi-tui's
|
|
56
|
+
prebuilt `.node` natives (`darwin-arm64`/`darwin-x64` ship in the
|
|
57
|
+
package).
|
|
58
|
+
- Local-model support is **already proven on this machine**:
|
|
59
|
+
`~/.pi/agent/models.json` has an `mlx` provider
|
|
60
|
+
(`api: "openai-completions"`, baseUrl + `sk-optiq-` key, compat
|
|
61
|
+
flags) serving Qwen quants. mlx-bun's OpenAI surface is what pi
|
|
62
|
+
expects.
|
|
63
|
+
|
|
64
|
+
## The four integration options
|
|
65
|
+
|
|
66
|
+
### Option 1 — `mlx-bun harness pi`: configure the user's existing pi
|
|
67
|
+
|
|
68
|
+
Write/merge the provider entry into `~/.pi/agent/models.json` (or drop
|
|
69
|
+
a tiny extension that calls `pi.registerProvider(...)` — the
|
|
70
|
+
documented programmatic route, which can even discover models
|
|
71
|
+
dynamically from our `/v1/models`). Validate with one tiny request,
|
|
72
|
+
print the launch command.
|
|
73
|
+
|
|
74
|
+
- **Effort**: hours. **Risk**: low (must merge, not clobber, user
|
|
75
|
+
config — the docs' extension route avoids touching models.json at
|
|
76
|
+
all). **Verdict**: do regardless; this is the harness-adapter
|
|
77
|
+
pattern from the roadmap and it's nearly free.
|
|
78
|
+
|
|
79
|
+
**Pros**: nearly free; respects the user's pi setup, sessions, themes,
|
|
80
|
+
extensions; zero version coupling; the dynamic-discovery extension
|
|
81
|
+
keeps the model list in sync with our server automatically.
|
|
82
|
+
**Cons**: requires pi to already be installed; not "built-in" — the
|
|
83
|
+
appliance story still has two moving parts; config-merge bugs are the
|
|
84
|
+
classic way to annoy users (the extension route mostly avoids this).
|
|
85
|
+
|
|
86
|
+
### Option 2 — `mlx-bun pi` (v1): spawn the user's installed pi
|
|
87
|
+
|
|
88
|
+
Start our server, then spawn `pi` with a session-scoped provider
|
|
89
|
+
config (extension dir or env) pointing at it. If pi isn't installed,
|
|
90
|
+
print the install one-liner.
|
|
91
|
+
|
|
92
|
+
- **Effort**: a day with polish. **Risk**: version skew with whatever
|
|
93
|
+
pi the user has; not "built-in" — two installs.
|
|
94
|
+
- **Verdict**: the right v1. Real UX win immediately, zero coupling.
|
|
95
|
+
- **Bonus**: unrecognized flags pass straight through, so
|
|
96
|
+
`mlx-bun pi -p "summarize @notes.md"` (pi's non-interactive print
|
|
97
|
+
mode), `--mode json/rpc`, `--continue`, `--resume` etc. all work on
|
|
98
|
+
day one without us implementing anything.
|
|
99
|
+
|
|
100
|
+
**Pros**: fast to ship; full pi feature surface via passthrough;
|
|
101
|
+
upgrades to pi arrive for free; failure modes are simple (no pi →
|
|
102
|
+
print install hint). **Cons**: still two installs; we can't control
|
|
103
|
+
the pi version (an upstream flag/behavior change can break our
|
|
104
|
+
launcher); per-run provider injection needs care so we don't pollute
|
|
105
|
+
the user's global config; process lifecycle (server + child TUI)
|
|
106
|
+
needs clean shutdown handling.
|
|
107
|
+
|
|
108
|
+
### Option 3 — SDK embed (the real "built-in"): pi inside the mlx-bun binary
|
|
109
|
+
|
|
110
|
+
This is the one the question was really about, and the SDK is
|
|
111
|
+
explicitly designed for it:
|
|
112
|
+
|
|
113
|
+
- `createAgentSession({...})` builds a session; the
|
|
114
|
+
`examples/sdk/12-full-control.ts` example shows **zero-discovery**
|
|
115
|
+
embedding: in-memory `SessionManager`/`SettingsManager`/
|
|
116
|
+
`ModelRegistry`, custom `ResourceLoader`, explicit tool list,
|
|
117
|
+
runtime API keys — nothing leaks from/to `~/.pi` unless we want it
|
|
118
|
+
to.
|
|
119
|
+
- `InteractiveMode` is exported: "full TUI interactive mode with
|
|
120
|
+
editor, chat history, and all built-in commands", constructed from
|
|
121
|
+
an `AgentSessionRuntime`. We do not rebuild a chat TUI from pi-tui
|
|
122
|
+
primitives (the tui docs are explicit that components are for
|
|
123
|
+
extensions, not a full-app kit) — we run *pi's own* TUI in-process.
|
|
124
|
+
- Provider wiring: register an `openai-completions` provider at our
|
|
125
|
+
own server's baseUrl (loopback HTTP into our already-running
|
|
126
|
+
`Bun.serve` — simplest and keeps one code path for external
|
|
127
|
+
clients), models listed from our registry with real context-window
|
|
128
|
+
numbers per device profile.
|
|
129
|
+
- Flow for `mlx-bun pi`: detect profile → ensure model → start server
|
|
130
|
+
in-process → `createAgentSession` against `localhost` →
|
|
131
|
+
`new InteractiveMode(runtime).run()`.
|
|
132
|
+
|
|
133
|
+
Open engineering questions (all look tractable, none look fatal):
|
|
134
|
+
|
|
135
|
+
1. **One process, two workloads**: the TUI event loop and the
|
|
136
|
+
server/generation queue share the JS thread; mlx compute happens in
|
|
137
|
+
native code but graph-build is JS-side (~ms-scale per decode step).
|
|
138
|
+
TUI rendering is differential and cheap; needs a smoke test for
|
|
139
|
+
input latency during decode, not a redesign.
|
|
140
|
+
2. **Bundle plumbing**: embed pi's asset list + pi-tui `.node`
|
|
141
|
+
prebuilds + photon wasm into the Phase 5 compile. Upstream's own
|
|
142
|
+
binary build proves each piece works under `bun build --compile`;
|
|
143
|
+
our work is making *their* asset-path resolution find files inside
|
|
144
|
+
*our* bundle.
|
|
145
|
+
3. **Version pinning**: pi moves fast (0.79.x); pin exact, upgrade
|
|
146
|
+
deliberately, keep Option 2 as the fallback path when upstream
|
|
147
|
+
breaks SDK surface.
|
|
148
|
+
4. **Identity**: embedded pi should read/write its own session dir
|
|
149
|
+
(`~/.mlx-bun/pi-sessions` or similar) and not fight a user's
|
|
150
|
+
standalone pi install. The full-control example shows every knob
|
|
151
|
+
needed (`agentDir`, custom storage paths).
|
|
152
|
+
|
|
153
|
+
- **Effort**: ~1–2 weeks including bundle work and latency testing.
|
|
154
|
+
- **Verdict**: feasible and high-value; this is the appliance moment —
|
|
155
|
+
one binary, one command, a real agent on a local model. `-p` and
|
|
156
|
+
`--mode rpc/json` remain supported natively: `runPrintMode` and
|
|
157
|
+
`runRpcMode` are SDK exports, so the embedded CLI keeps pi's
|
|
158
|
+
headless modes.
|
|
159
|
+
|
|
160
|
+
**Pros**: the true appliance — one binary, no prior installs; we pin
|
|
161
|
+
and test the exact pi version we ship; full control of defaults
|
|
162
|
+
(session dir, tools, system prompt, model list per device profile);
|
|
163
|
+
in-process events shared with the web UI; offline-clean.
|
|
164
|
+
**Cons**: most engineering (asset bundling, latency validation); we
|
|
165
|
+
own the upgrade treadmill for a fast-moving 0.x dependency; embedded
|
|
166
|
+
pi diverges from the user's own pi config/extensions unless we
|
|
167
|
+
deliberately opt into discovery; binary grows ~20 MB.
|
|
168
|
+
|
|
169
|
+
### Option 4 — RPC / JSON event-stream mode (not for the terminal — for the web UI)
|
|
170
|
+
|
|
171
|
+
`pi --mode rpc` speaks JSONL over stdin/stdout: `prompt`, `steer`,
|
|
172
|
+
`abort`, model control, state/stats queries, plus a streamed event
|
|
173
|
+
model (`message_update` deltas, `tool_execution_*`, `turn_*`) and even
|
|
174
|
+
an extension-UI sub-protocol. The docs name "custom frontends (web
|
|
175
|
+
dashboards…)" as a primary use case.
|
|
176
|
+
|
|
177
|
+
- **Verdict**: wrong tool for `mlx-bun pi` (we'd be rebuilding pi's
|
|
178
|
+
TUI on top of pi), but the **right backbone for the built-in web
|
|
179
|
+
chat UI**: `mlx-bun` serves a page; the server bridges
|
|
180
|
+
WebSocket ↔ RPC events (or drives `AgentSession.subscribe()`
|
|
181
|
+
in-process — same event shapes, no subprocess). Tool calls,
|
|
182
|
+
steering, and progress come for free instead of us re-implementing
|
|
183
|
+
an agent loop in the browser.
|
|
184
|
+
|
|
185
|
+
**Pros**: documented, versioned JSONL protocol; UI-agnostic (web,
|
|
186
|
+
IDE, bots); steering/abort/stats built in; works with either the
|
|
187
|
+
subprocess or the embedded session as the engine. **Cons**: not a
|
|
188
|
+
terminal UX at all; strict framing rules to honor; the extension-UI
|
|
189
|
+
sub-protocol must be bridged if extensions prompt the user.
|
|
190
|
+
|
|
191
|
+
## Does this make sense as a feature?
|
|
192
|
+
|
|
193
|
+
Yes — it is arguably the highest-leverage product move available:
|
|
194
|
+
|
|
195
|
+
- It converts mlx-bun from "a server you point things at" into a
|
|
196
|
+
complete local-AI appliance, which is the PRODUCT_ROADMAP north
|
|
197
|
+
star verbatim.
|
|
198
|
+
- The marginal cost is small because the hard parts are already done
|
|
199
|
+
on both sides: our OpenAI surface is SDK-verified, pi's SDK is
|
|
200
|
+
designed for embedding, and both projects compile with the same Bun
|
|
201
|
+
toolchain. MIT licensing removes the bundling question.
|
|
202
|
+
- One investigation, both deliverables: the same `AgentSession` event
|
|
203
|
+
stream powers the embedded TUI *and* the web chat UI, so direction
|
|
204
|
+
A's two halves share a core.
|
|
205
|
+
- Caveats worth stating: we take on pi's release cadence as a
|
|
206
|
+
dependency; the agent quality ceiling is the local model's (12B
|
|
207
|
+
OptiQ tool-calling is decent but this becomes the showcase — the
|
|
208
|
+
Lucien-direction evals would tell us exactly how good); and the
|
|
209
|
+
embedded TUI must not degrade decode throughput (measure, per house
|
|
210
|
+
rules).
|
|
211
|
+
|
|
212
|
+
## Implementation plan
|
|
213
|
+
|
|
214
|
+
To be explicit about the recommendation: **the locally installed pi is
|
|
215
|
+
the v1 stepping stone, not the destination.** Users who have their own
|
|
216
|
+
pi always get first-class support (P1); the flagship `mlx-bun pi` ends
|
|
217
|
+
as the embedded, single-binary experience (P3/P4).
|
|
218
|
+
|
|
219
|
+
### P1 — `mlx-bun harness pi` (hours–1 day)
|
|
220
|
+
|
|
221
|
+
1. Detect pi (`which pi`, `~/.bun/bin/pi`, npm global); report version.
|
|
222
|
+
2. Generate an extension file (e.g. `~/.pi/agent/extensions/mlx-bun.ts`)
|
|
223
|
+
that calls `pi.registerProvider("mlx-bun", ...)` with **dynamic
|
|
224
|
+
discovery**: fetch our `/v1/models` at pi startup so the model list
|
|
225
|
+
tracks the server. No models.json surgery; trivially reversible
|
|
226
|
+
(delete one file).
|
|
227
|
+
3. Validate: tiny completion against the running server through pi's
|
|
228
|
+
provider path; print `pi --provider mlx-bun` launch hint.
|
|
229
|
+
4. Failure modes: pi missing → print install one-liner; server down →
|
|
230
|
+
offer to start it.
|
|
231
|
+
|
|
232
|
+
### P2 — `mlx-bun pi` v1: subprocess launcher (~1–2 days)
|
|
233
|
+
|
|
234
|
+
1. Ensure server: reuse a running instance if healthy, else start
|
|
235
|
+
in-process and wait for readiness.
|
|
236
|
+
2. Resolve the pi binary; if absent, offer the installer.
|
|
237
|
+
3. Spawn pi with stdio inherited and a **session-scoped provider**: a
|
|
238
|
+
generated `-e <tmp-extension>.ts` registering the provider for this
|
|
239
|
+
run only (no global config writes), plus
|
|
240
|
+
`--provider mlx-bun --model <profile default>`.
|
|
241
|
+
4. **Pass through all remaining argv** — this is where `mlx-bun pi -p
|
|
242
|
+
"..."`, `--mode rpc`, `--continue`, `@file` args work for free.
|
|
243
|
+
5. Lifecycle: child exit → optionally keep the server warm (flag);
|
|
244
|
+
SIGINT goes to the child (pi handles double-ctrl-C), server shuts
|
|
245
|
+
down cleanly after.
|
|
246
|
+
|
|
247
|
+
### P3 — embed spike (≤1 week, run via `bun run`, no bundling yet)
|
|
248
|
+
|
|
249
|
+
1. Add pi packages as pinned deps (exact `0.79.x`).
|
|
250
|
+
2. Wire `createAgentSession` full-control style: in-memory
|
|
251
|
+
registry/settings, our provider at the loopback baseUrl, session
|
|
252
|
+
dir under `~/.mlx-bun/`, default tools, our system prompt.
|
|
253
|
+
3. Run `new InteractiveMode(runtime)` in the same process as the
|
|
254
|
+
server.
|
|
255
|
+
4. **Gates** (house rule — numbers on this machine): editor latency
|
|
256
|
+
during 12B decode subjectively clean + decode tok/s within noise
|
|
257
|
+
of server-only; tool-call round-trip (read/bash/edit) works against
|
|
258
|
+
the 12B; clean teardown.
|
|
259
|
+
5. Decide discovery policy: default isolated; `--pi-config` opt-in to
|
|
260
|
+
the user's `~/.pi` extensions/skills/themes.
|
|
261
|
+
|
|
262
|
+
### P4 — single binary + shared event plumbing (~1 week)
|
|
263
|
+
|
|
264
|
+
1. Fold pi's asset manifest (theme JSONs, PNGs, export-html templates,
|
|
265
|
+
photon wasm, pi-tui darwin `.node` prebuilds) into the Phase 5
|
|
266
|
+
`bun build --compile`; patch/verify pi's asset-path resolution
|
|
267
|
+
inside the bundle (upstream's own binary build is the existence
|
|
268
|
+
proof).
|
|
269
|
+
2. CLI surface: `mlx-bun pi [pi-args]` → embedded `InteractiveMode`;
|
|
270
|
+
`-p` → `runPrintMode`; `--mode rpc` → `runRpcMode`. Subprocess
|
|
271
|
+
path (P2) remains as `--external-pi` fallback.
|
|
272
|
+
3. Web chat UI rides the same `AgentSession.subscribe()` events over
|
|
273
|
+
WebSocket (Option 4 shape, in-process — no subprocess needed).
|
|
274
|
+
4. Upgrade policy: bump pi deliberately per release, re-run the P3
|
|
275
|
+
gates as the regression suite.
|
|
276
|
+
|
|
277
|
+
### First-run starter model (folds into P2/P4; model gated on Phase 14)
|
|
278
|
+
|
|
279
|
+
The first-run experience must not be "watch a progress bar for 10
|
|
280
|
+
minutes." Plan: a sub-gigabyte **starter model** downloads first and is
|
|
281
|
+
chatting within ~1 minute — with a docs-grounded "tour guide" system
|
|
282
|
+
prompt (README + server-api + pi guide preloaded, so a 1B doesn't
|
|
283
|
+
hallucinate about mlx-bun) and a banner: *"the ideal model for your Mac
|
|
284
|
+
is downloading in the background."* The profile model streams behind it
|
|
285
|
+
(sequenced, not parallel, so downloads don't split bandwidth); swap
|
|
286
|
+
when ready.
|
|
287
|
+
|
|
288
|
+
Measured sizes (HF tree API, 2026-06-12):
|
|
289
|
+
|
|
290
|
+
| Candidate | Download | Note |
|
|
291
|
+
|---|---|---|
|
|
292
|
+
| gemma-4-e2b-it-OptiQ-4bit | 5.26 GB | smallest Gemma — disqualifies the family for the starter slot |
|
|
293
|
+
| gemma-4-e4b / 12B OptiQ-4bit | 7.0 / 8.4 GB | profile models, not starters |
|
|
294
|
+
| **Qwen3.5-0.8B-OptiQ-4bit** | **0.89 GB** | **preferred**: rides the Phase 14 Qwen port; doubles as the first tier-a parity target |
|
|
295
|
+
| MiniCPM5-1B-OptiQ-4bit | 0.92 GB | strong (52% MMLU, +4.44 OptiQ delta) but a third architecture family — A/B later |
|
|
296
|
+
|
|
297
|
+
Decisions: starter is **downloaded, never bundled** (a ~900 MB binary
|
|
298
|
+
kills brew install); it stays on disk afterward (future
|
|
299
|
+
speculative-decoding draft + always-works fallback). Hot-swap (or
|
|
300
|
+
offer-to-swap) when the profile model lands is the only new runtime
|
|
301
|
+
work. Dependency: a runnable sub-GB model requires the Phase 14 Qwen
|
|
302
|
+
bring-up — the starter feature is the reason the 0.8B goes first there.
|
|
303
|
+
|
|
304
|
+
## Recommended sequence
|
|
305
|
+
|
|
306
|
+
P1 + P2 now (days, immediate demo value, zero coupling) → P3 spike
|
|
307
|
+
with its latency gate → P4 single-binary flagship, reusing the event
|
|
308
|
+
plumbing for the built-in web UI.
|