mlx-bun 0.0.4 → 0.0.5
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 +166 -93
- 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 +1 -1
- package/docs/design/optimization_plan.md +4 -2
- package/docs/design/orpo-future-enhancements.md +298 -0
- package/docs/design/orpo-training.md +1199 -0
- package/docs/design/parallel-slots.md +34 -26
- 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/{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 +431 -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/orpo-quickstart.md +91 -0
- package/docs/reference/server-api.md +105 -7
- package/docs/reference/server-config.md +12 -5
- package/docs/reference/training.md +292 -0
- package/package.json +1 -1
- package/src/eval/ifeval.ts +138 -0
- package/src/eval/ultrafeedback.ts +60 -0
- package/src/lora.ts +12 -7
- package/src/mlx/metal-kernel.ts +18 -4
- package/src/model/flash-attention.ts +32 -16
- 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/server.ts +3 -3
- package/src/train/dataset.ts +18 -3
- package/src/train/flash-cce.ts +1162 -0
- package/src/train/job.ts +26 -2
- package/src/train/lora-params.ts +53 -4
- package/src/train/loss.ts +941 -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 +509 -52
- package/src/web/app.html +21 -5
- /package/{docs → src/assets}/curve-designer.html +0 -0
package/README.md
CHANGED
|
@@ -1,21 +1,44 @@
|
|
|
1
1
|
# mlx-bun
|
|
2
2
|
|
|
3
|
-
Native MLX inference for Bun
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
Native MLX inference for Bun, and the beginning of a one-command local AI
|
|
4
|
+
product for Apple Silicon Macs. `mlx-bun` detects the machine, starts a useful
|
|
5
|
+
local assistant, exposes OpenAI/Anthropic-compatible APIs, and gives
|
|
6
|
+
TypeScript apps direct access to MLX without Python or a sidecar server.
|
|
7
|
+
|
|
8
|
+
The product bet is that local AI on Mac is a finite optimization problem, not
|
|
9
|
+
an infinite configuration maze: Apple ships a known set of chip/RAM/bandwidth
|
|
10
|
+
SKUs, the supported model list is curated, and the task mode is explicit. That
|
|
11
|
+
means defaults can be chosen from measured device × model × mode profiles
|
|
12
|
+
instead of asking every user to become an ML systems engineer.
|
|
13
|
+
|
|
14
|
+
Measured head-to-head on an M4 Pro (24 GB), same models, same machine
|
|
15
|
+
state: logits **bit-exact** against the Python reference; served over
|
|
16
|
+
HTTP the **fastest stack on every model tested** — fastest decode, plus
|
|
17
|
+
TTFT **34–85 ms** vs python's 218–326 ms at **~0% server overhead**
|
|
18
|
+
(mlx-lm pays −5–6%); server start to ready in **0.17–0.47 s**, 2–5×
|
|
19
|
+
faster than the Python servers. Full table: [benchmarks](#benchmarks).
|
|
12
20
|
|
|
13
21
|
## Getting started
|
|
14
22
|
|
|
15
23
|
You need an Apple Silicon Mac (MLX is Metal-only, so macOS only by
|
|
16
|
-
design).
|
|
24
|
+
design). Four ways in.
|
|
25
|
+
|
|
26
|
+
### Direct download (recommended)
|
|
27
|
+
|
|
28
|
+
No Homebrew, Bun, or git — just curl the install script. It downloads the
|
|
29
|
+
signed, notarized self-contained binary from the latest release and puts
|
|
30
|
+
`mlx-bun` on your PATH:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
curl -fsSL https://mlx-bun.dev/install.sh | sh
|
|
34
|
+
mlx-bun
|
|
35
|
+
```
|
|
17
36
|
|
|
18
|
-
|
|
37
|
+
Self-contained (binary + MLX runtime) and notarized, so it runs without a
|
|
38
|
+
Gatekeeper prompt. Installs to `~/.mlx-bun` by default — override with
|
|
39
|
+
`MLX_BUN_INSTALL_DIR`, or pin a release with `MLX_BUN_VERSION=v0.0.4`.
|
|
40
|
+
|
|
41
|
+
### Homebrew
|
|
19
42
|
|
|
20
43
|
A self-contained, signed + notarized binary — no toolchain to install by
|
|
21
44
|
hand:
|
|
@@ -68,8 +91,7 @@ model named, it does everything for you:
|
|
|
68
91
|
2. downloads the sub-GB `MiniCPM5-1B` starter and serves it, so you're
|
|
69
92
|
chatting in well under a minute;
|
|
70
93
|
3. starts downloading `gemma-4-e4b` (the stronger 4B model) in the
|
|
71
|
-
background — it becomes the default on your next `mlx-bun serve
|
|
72
|
-
16 GB+ Mac;
|
|
94
|
+
background — it becomes the default on your next `mlx-bun serve`;
|
|
73
95
|
4. opens the chat UI in your browser at
|
|
74
96
|
[`http://localhost:8090/#/chat`](http://localhost:8090/#/chat)
|
|
75
97
|
(pass `--no-open` to skip).
|
|
@@ -95,22 +117,46 @@ That's it. The server serves the one model it was started with (the
|
|
|
95
117
|
request's `model` field is ignored and the loaded model id is echoed
|
|
96
118
|
back in responses).
|
|
97
119
|
|
|
120
|
+
## What mlx-bun is
|
|
121
|
+
|
|
122
|
+
`mlx-bun` has four faces, all sharing one native runtime:
|
|
123
|
+
|
|
124
|
+
- **The local AI product** — run `mlx-bun` or `bunx mlx-bun` and get a
|
|
125
|
+
working local chat UI plus a local server. The default path should feel like
|
|
126
|
+
an appliance: download the starter model, start chatting quickly, then move
|
|
127
|
+
to the stronger recommended model as it becomes available.
|
|
128
|
+
- **The app developer library** — embed local MLX inference inside Bun,
|
|
129
|
+
Tauri, Electron, and other TypeScript applications without running Python in
|
|
130
|
+
the background.
|
|
131
|
+
- **The AI lab** — keep parity, evals, kernels, LoRA/ORPO training, adapter
|
|
132
|
+
routing, and performance experiments close to the runtime that will actually
|
|
133
|
+
serve them.
|
|
134
|
+
- **The background agent runtime** — leave a local OpenAI-compatible endpoint
|
|
135
|
+
available for scripts, recurring jobs, app integrations, and per-request
|
|
136
|
+
adapter routing.
|
|
137
|
+
|
|
138
|
+
The default experience is intentionally opinionated. Power users can override
|
|
139
|
+
models, budgets, KV modes, adapters, and flags; the average user should not
|
|
140
|
+
need to know those knobs exist.
|
|
141
|
+
|
|
98
142
|
## Supported models
|
|
99
143
|
|
|
100
144
|
Scope is deliberate: a few model families held to **bit-exact** logit
|
|
101
145
|
parity with the Python reference, rather than dozens held to none.
|
|
102
|
-
Currently MiniCPM5
|
|
146
|
+
Currently MiniCPM5, the Gemma-4 OptiQ quants, and Qwen3.5-4B:
|
|
103
147
|
|
|
104
148
|
| Model | Download | Fits on | Vision | Notes |
|
|
105
149
|
|---|---|---|---|---|
|
|
106
150
|
| [`mlx-community/MiniCPM5-1B-OptiQ-4bit`](https://huggingface.co/mlx-community/MiniCPM5-1B-OptiQ-4bit) | 0.92 GB | 8 GB | — | Sub-GB option for 8 GB machines; bit-exact 100-step oracle parity (bf16 + mixed KV), tool calling + agent loop verified |
|
|
107
|
-
| [`mlx-community/
|
|
108
|
-
| [`mlx-community/gemma-4-
|
|
109
|
-
| [`mlx-community/gemma-4-
|
|
151
|
+
| [`mlx-community/Qwen3.5-4B-OptiQ-4bit`](https://huggingface.co/mlx-community/Qwen3.5-4B-OptiQ-4bit) | 3.1 GB | 8 GB | — | bf16-KV bit-exact parity (vs mlx-lm); thinking + tool calling; ~74 tok/s predicted |
|
|
152
|
+
| [`mlx-community/gemma-4-e4b-it-OptiQ-4bit`](https://huggingface.co/mlx-community/gemma-4-e4b-it-OptiQ-4bit) | 7.0 GB | 16 GB | ✓ | **Recommended starter** (16 GB+); ~56 tok/s |
|
|
153
|
+
| [`mlx-community/gemma-4-12B-it-OptiQ-4bit`](https://huggingface.co/mlx-community/gemma-4-12B-it-OptiQ-4bit) | 8.4 GB | 16 GB | ✓ | Vision + tool calling, both verified end-to-end |
|
|
154
|
+
| [`mlx-community/gemma-4-26B-A4B-it-OptiQ-4bit`](https://huggingface.co/mlx-community/gemma-4-26B-A4B-it-OptiQ-4bit) | 18 GB | 24 GB | — | MoE (top-8 of 128 experts); ~54 tok/s |
|
|
110
155
|
|
|
111
156
|
Not sure what fits your machine? `bun src/cli.ts fit <model> --ctx 8192`
|
|
112
|
-
gives a deterministic answer (see below).
|
|
113
|
-
|
|
157
|
+
gives a deterministic answer (see below). The larger
|
|
158
|
+
`Qwen3.6-27B-OptiQ-4bit` is still in bring-up — parity and serving polish
|
|
159
|
+
remain (see [PLAN.md](./PLAN.md)).
|
|
114
160
|
|
|
115
161
|
## Why
|
|
116
162
|
|
|
@@ -131,7 +177,7 @@ one:
|
|
|
131
177
|
- **`bun:ffi`** — lowest-overhead FFI of any JS runtime; binds `mlx-c`
|
|
132
178
|
directly, no node-gyp, no binding compilation.
|
|
133
179
|
- **Lazy native weight loading** — mlx's loader materializes tensors on
|
|
134
|
-
first use; opening an 8.
|
|
180
|
+
first use; opening an 8.4 GB model takes milliseconds, and warm
|
|
135
181
|
restarts are near-instant on cached pages.
|
|
136
182
|
- **`Bun.Image`** — native OS image codecs (HEIC, AVIF, WebP, JPEG, …)
|
|
137
183
|
for the vision path, EXIF auto-orientation included.
|
|
@@ -152,9 +198,11 @@ bun src/cli.ts fit gemma --ctx 32768 # memory contract: fits? max conte
|
|
|
152
198
|
bun src/cli.ts fit gemma --ctx 8192 --skus # ...same, across the Apple Silicon lineup
|
|
153
199
|
bun src/cli.ts serve gemma --port 8090 # OpenAI-compatible server
|
|
154
200
|
bun src/cli.ts serve gemma --memory-budget 18 # ...with admission control (GB)
|
|
201
|
+
bun src/cli.ts pi # built-in agentic coding CLI (pi's TUI, in-process)
|
|
155
202
|
bun src/cli.ts evals # recorded benchmark runs
|
|
203
|
+
bun src/cli.ts harness pi # connect your own pi install to the local server
|
|
156
204
|
./benchmark.sh # head-to-head matrix vs mlx-lm/optiq (reboot first;
|
|
157
|
-
# preflight-gated, resumable, writes benchmarks-h2h-<date>.md)
|
|
205
|
+
# preflight-gated, resumable, writes benchmarks-h2h-<date>-<host>.md)
|
|
158
206
|
```
|
|
159
207
|
|
|
160
208
|
Model arguments are substring queries against the registry (`e4b`,
|
|
@@ -177,15 +225,18 @@ agent CLIs like pi/OpenClaw via their provider config.
|
|
|
177
225
|
- **`POST /v1/chat/completions`** — streaming (SSE) and non-streaming;
|
|
178
226
|
`temperature`, `top_p`, `top_k`, `max_tokens`, `seed`,
|
|
179
227
|
`repetition_penalty`, `stop` (string or array, matched on decoded
|
|
180
|
-
text with streaming hold-back)
|
|
181
|
-
|
|
182
|
-
|
|
228
|
+
text with streaming hold-back), `reasoning_effort` (thinking on/off for
|
|
229
|
+
Qwen3.5 / MiniCPM5 — `none` disables, any level enables), and an `hlg`
|
|
230
|
+
tone-curve sampling override;
|
|
231
|
+
omitted sampling fields default to the model's own
|
|
232
|
+
`generation_config.json` recipe; usage includes `cached_tokens`.
|
|
233
|
+
Full schemas in
|
|
183
234
|
[docs/reference/server-api.md](./docs/reference/server-api.md); start
|
|
184
235
|
flags and the `--batch N` compatibility matrix in
|
|
185
236
|
[docs/reference/server-config.md](./docs/reference/server-config.md).
|
|
186
237
|
- **Tool calling** — pass OpenAI `tools`; each family's native format
|
|
187
238
|
is parsed into `tool_calls` JSON with `finish_reason: "tool_calls"`
|
|
188
|
-
(Gemma 4 `<|tool_call>` sentinel tokens; MiniCPM5
|
|
239
|
+
(Gemma 4 `<|tool_call>` sentinel tokens; MiniCPM5 / Qwen3.5
|
|
189
240
|
`<function name=…>` XML with schema-aware argument decoding);
|
|
190
241
|
`role: "tool"` round-trips, including multi-turn agent loops.
|
|
191
242
|
- **Vision** — `image_url` content parts (data: URLs or http/s), on
|
|
@@ -195,16 +246,17 @@ agent CLIs like pi/OpenClaw via their provider config.
|
|
|
195
246
|
common token prefix across requests (multi-turn conversations
|
|
196
247
|
re-prefill only the new turn) — automatic, no client changes.
|
|
197
248
|
Entries are adapter-specific when LoRA adapters are in play.
|
|
198
|
-
- **LoRA hot-swap** — mount adapters at
|
|
199
|
-
|
|
200
|
-
`adapter` body field (`"id"`, stacked `"a+b"`, or
|
|
201
|
-
base model never reloads; an unselected adapter costs
|
|
249
|
+
- **LoRA hot-swap** — mount adapters at runtime (`POST /v1/adapters`)
|
|
250
|
+
and discover on-disk ones (`GET /v1/adapters/available`), select per
|
|
251
|
+
request with the `adapter` body field (`"id"`, stacked `"a+b"`, or
|
|
252
|
+
`"none"`). The base model never reloads; an unselected adapter costs
|
|
253
|
+
nothing.
|
|
202
254
|
- **Mixed-precision KV** — when the model repo ships `kv_config.json`
|
|
203
255
|
(every Gemma-4 OptiQ repo does), per-layer KV quantization applies
|
|
204
|
-
automatically
|
|
205
|
-
|
|
206
|
-
quantized layers. `--
|
|
207
|
-
uniform bits. Long prefills over quantized caches run a fused
|
|
256
|
+
automatically; the config sets bits per layer for both full-attention
|
|
257
|
+
and sliding-window (rotating) layers (Phase 9). Measured decode-neutral
|
|
258
|
+
at 8k on the 12B with KV bytes ÷4 on the quantized layers. `--kv-quant off` forces bf16; `--kv-quant 4|8`
|
|
259
|
+
forces uniform bits. Long prefills over quantized caches run a fused
|
|
208
260
|
FlashAttention-2 tiling that never materializes the full scores
|
|
209
261
|
matrix (bounded transient; `MLX_BUN_NO_FUSED_SDPA=1` to disable).
|
|
210
262
|
`MLX_BUN_FUSED_DECODE=1` extends the tiling to single-token decode —
|
|
@@ -225,27 +277,30 @@ agent CLIs like pi/OpenClaw via their provider config.
|
|
|
225
277
|
Codex/Cursor/Continue speak; includes `previous_response_id`
|
|
226
278
|
resumption backed by a TTL + byte-capped store (visible in /stats).
|
|
227
279
|
- **`GET /v1/models`**, **`GET /stats`** (cache hit rates, bytes,
|
|
228
|
-
active KV scheme, response store), **`GET/POST/DELETE /v1/adapters
|
|
280
|
+
active KV scheme, response store), **`GET/POST/DELETE /v1/adapters`**,
|
|
281
|
+
plus **`GET /library`**, **`GET /fit`**, **`GET /downloads`**, and
|
|
282
|
+
**`GET /v1`** (a self-describing index of every endpoint).
|
|
229
283
|
|
|
230
284
|
## Library
|
|
231
285
|
|
|
232
|
-
The server is one consumer of a library-first API.
|
|
233
|
-
|
|
286
|
+
The server is one consumer of a library-first API. Published to npm as
|
|
287
|
+
`mlx-bun` (current: 0.0.4) — import from the package (`from "mlx-bun"`)
|
|
288
|
+
or `./src/index` in a clone; `bunx mlx-bun` runs the CLI without
|
|
289
|
+
installing. Full reference:
|
|
234
290
|
[docs/reference/library-api.md](./docs/reference/library-api.md). For shipping inside a
|
|
235
291
|
Mac app (Tauri/Electron sidecar), `./scripts/build-binary.sh` produces
|
|
236
292
|
a relocatable single-binary bundle — recipe incl. signing/notarization
|
|
237
293
|
in [docs/reference/embedding.md](./docs/reference/embedding.md).
|
|
238
294
|
|
|
239
295
|
```ts
|
|
240
|
-
import {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
import { loadTokenizer } from "./src/tokenizer";
|
|
245
|
-
import { ChatTemplate } from "./src/chat-template";
|
|
296
|
+
import {
|
|
297
|
+
createModel, // dispatches to Gemma4 / MiniCPM5 / Qwen3.5
|
|
298
|
+
Weights, loadModelConfig, loadTokenizer, ChatTemplate, generate,
|
|
299
|
+
} from "mlx-bun"; // or "./src/index" in a clone
|
|
246
300
|
|
|
247
301
|
const dir = "/path/to/hf/snapshot";
|
|
248
|
-
const
|
|
302
|
+
const config = await loadModelConfig(dir);
|
|
303
|
+
const model = createModel(await Weights.open(dir), config); // RuntimeModel
|
|
249
304
|
const tok = await loadTokenizer(dir);
|
|
250
305
|
const template = await ChatTemplate.load(dir);
|
|
251
306
|
|
|
@@ -266,43 +321,44 @@ preamble prefills once, ever.
|
|
|
266
321
|
## Benchmarks
|
|
267
322
|
|
|
268
323
|
Head-to-head against the Python stacks (mlx-lm 0.31.3, mlx-optiq 0.2.1),
|
|
269
|
-
same machine (M4 Pro 24 GB), same
|
|
270
|
-
|
|
271
|
-
(parity / performance / quality) with
|
|
272
|
-
[benchmarks/RESULTS.md](./benchmarks/RESULTS.md).
|
|
324
|
+
same machine (M4 Pro 24 GB), same HF snapshots, the 2026-06-14
|
|
325
|
+
cleared-machine run, preflight-gated, median-of-N with warmups
|
|
326
|
+
discarded. Full curated table (parity / performance / quality) with
|
|
327
|
+
per-row provenance: [benchmarks/RESULTS.md](./benchmarks/RESULTS.md).
|
|
328
|
+
|
|
329
|
+
**Served (warm)** — how agents actually use a local model.
|
|
330
|
+
decode tok/s · TTFT ms · start s:
|
|
273
331
|
|
|
274
|
-
| | mlx-bun | mlx-lm | optiq |
|
|
332
|
+
| model | mlx-bun | mlx-lm | optiq |
|
|
275
333
|
|---|---|---|---|
|
|
276
|
-
|
|
|
277
|
-
| **
|
|
278
|
-
|
|
|
279
|
-
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
context — an earlier harness bug had fed python @8k baselines
|
|
305
|
-
~31-token prompts; fixed, guarded, and re-measured).
|
|
334
|
+
| MiniCPM5-1B | **252.9** · 34 · 0.17 | — | 223.6 · 64 · 0.84 |
|
|
335
|
+
| gemma-4-e4b | **55.7** · 44 · 0.36 | 53.5 · 218 · 0.98 | 53.4 · 221 · 0.78 |
|
|
336
|
+
| gemma-4-12B | **25.9** · 85 · 0.38 | — | 25.5 · 326 · 1.24 |
|
|
337
|
+
| gemma-4-26B | **54.2** · 45 · 0.47 | 52.3 · 228 · 0.77 | — |
|
|
338
|
+
|
|
339
|
+
Across every served model mlx-bun has the fastest decode and the fastest
|
|
340
|
+
TTFT/startup (2–5×), at **~0% server tax** vs its own direct engine —
|
|
341
|
+
mlx-lm pays −5–6%, optiq −1 to −10%.
|
|
342
|
+
|
|
343
|
+
**Direct (engine only).** Decode is at parity-to-slightly-behind mlx-lm
|
|
344
|
+
(e4b +1.1%, 12B +0.4%, 26B −1.1%, MiniCPM5 −0.9% — within the per-step
|
|
345
|
+
host-overhead residual), and prefill leads on the larger models (12B
|
|
346
|
+
1.2×, 26B 1.1×, MiniCPM5 2.3×; e4b trails at 0.8×). The earlier decode
|
|
347
|
+
deficit was root-caused and fixed (2026-06-11) — a prefill→decode
|
|
348
|
+
allocator-reclaim stall mlx-lm clears with `mx.clear_cache` and bills to
|
|
349
|
+
prompt time; see PLAN.md "Decode gap RESOLVED".
|
|
350
|
+
|
|
351
|
+
**Long context (12B).** At 16k and 64k mlx-bun holds decode parity with
|
|
352
|
+
mlx-lm on bf16 (16k 23.9 = 23.9; 64k 20.9 = 20.9) while optiq drops to
|
|
353
|
+
21.6 then collapses to 12.3 at 64k; mixed-KV trades ~2 tok/s for ~5 GB
|
|
354
|
+
lower peak.
|
|
355
|
+
|
|
356
|
+
Two cross-stack served cells are absent: no mlx-lm 12B row, and optiq
|
|
357
|
+
produced no output on the 26B (the Metal-OOM class from python's
|
|
358
|
+
non-lazy load transient — reproduced in isolation; mlx-bun and mlx-lm
|
|
359
|
+
both served it from the same machine state). One further optiq cell
|
|
360
|
+
(12B/kv=config) is blocked on an upstream `quantized_matmul` bug. Both
|
|
361
|
+
are documented in the results file.
|
|
306
362
|
|
|
307
363
|
## Correctness
|
|
308
364
|
|
|
@@ -319,8 +375,8 @@ findings). Golden files are regenerated only by explicit scripts
|
|
|
319
375
|
(`scripts/regen-*.ts`) running the Python oracle.
|
|
320
376
|
|
|
321
377
|
```sh
|
|
322
|
-
bun test # fast tier runs everywhere; model-loaded tests
|
|
323
|
-
#
|
|
378
|
+
bun test # fast tier runs everywhere; model-loaded tests run only
|
|
379
|
+
# when the reference snapshot is in your HF cache
|
|
324
380
|
```
|
|
325
381
|
|
|
326
382
|
## Troubleshooting
|
|
@@ -343,19 +399,36 @@ bun test # fast tier runs everywhere; model-loaded tests auto-skip
|
|
|
343
399
|
## Status
|
|
344
400
|
|
|
345
401
|
Pre-alpha, moving fast. See [PLAN.md](./PLAN.md) for phases, exit
|
|
346
|
-
criteria, measured numbers, and the findings log.
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
(
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
402
|
+
criteria, measured numbers, and the findings log.
|
|
403
|
+
|
|
404
|
+
**Complete** — load path; bit-exact model port (MiniCPM5, Qwen3.5-4B,
|
|
405
|
+
e4b per-layer-input, 12B dense, 26B MoE); sampling + serving (tools, vision,
|
|
406
|
+
prompt cache); registry / fit / KV persistence; quantized +
|
|
407
|
+
mixed-precision KV serving (rotating-cache KV-quant, Phase 9) with fused
|
|
408
|
+
quantized prefill (Phase 10); LoRA hot-swap with per-request selection;
|
|
409
|
+
segmented-backward LoRA training (SFT / DPO / ORPO, with an `[M,vocab]`-free
|
|
410
|
+
flash-CCE head + prefix-sharing for long-context preference data — see
|
|
411
|
+
[training guide](docs/reference/training.md)); resumable
|
|
412
|
+
verified downloads (`mlx-bun get`); memory admission control; the
|
|
413
|
+
head-to-head benchmark harness; the decode-gap root-cause fix
|
|
414
|
+
(2026-06-11); Anthropic Messages + Responses API (`/v1/messages` and
|
|
415
|
+
`/v1/responses`, Phase 11); SigLIP vision sidecar for e4b
|
|
416
|
+
(commit 4625fe5); the embeddable single-binary build (signed +
|
|
417
|
+
notarized — Homebrew, direct-download, and npm/bunx).
|
|
418
|
+
|
|
419
|
+
**In progress** — `Qwen3.6-27B` bring-up (Phase 14f): same architecture
|
|
420
|
+
as the verified 4B (untied + larger geometry); parity and serving polish
|
|
421
|
+
remain. MTP speculation and Qwen3-VL vision deferred.
|
|
422
|
+
|
|
423
|
+
**Experimental** — opt-in, default-off, still being hardened: transparent
|
|
424
|
+
expert offload for MoE models (`serve --expert-offload`, Phase 20:
|
|
425
|
+
page-aligned mmap-backed experts, bit-exact — 26B-A4B 17.1→4.2 GB
|
|
426
|
+
resident, decode unregressed); batched serving (`--batch N`, Phase 18:
|
|
427
|
+
continuous-batching bf16 B=N decode, B=2 bit-parity verified for
|
|
428
|
+
MiniCPM5/12B/e4b/26B; throughput polish remains).
|
|
429
|
+
|
|
430
|
+
**Open** — e4b's ~5% per-step host-overhead decode residual (Phase 7);
|
|
431
|
+
SigLIP vision for 26B.
|
|
359
432
|
|
|
360
433
|
## License
|
|
361
434
|
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Adapters end-to-end — notes / TODO (2026-06-16)
|
|
2
2
|
|
|
3
|
+
**Status legend:** ✅ DONE — shipped and verified in code | ⏳ PENDING — not yet implemented
|
|
4
|
+
|
|
5
|
+
| Section | Status |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| §A Pi CLI extension (`extensions/mlx-bun-adapter.ts`) | ✅ DONE |
|
|
8
|
+
| §B Web chat adapter selector (`src/web/app.html` + `before_provider_request` hook in `pi-web.ts`) | ✅ DONE |
|
|
9
|
+
| §C `/v1/adapters/available` discovery (`src/server.ts:1182`, `src/lora.ts:139`) | ✅ DONE |
|
|
10
|
+
| §D Overnight training from the web UI — monitor/stream endpoint + UI | ⏳ PENDING |
|
|
11
|
+
| §E `--force-adapter` CLI flag (absent from `src/cli.ts`) | ⏳ PENDING |
|
|
12
|
+
| Repo-id keying written into adapter configs at save time | ⏳ PENDING |
|
|
13
|
+
|
|
3
14
|
Working notes for wiring trained LoRA adapters through the whole stack: Pi (CLI +
|
|
4
15
|
web), the web Chat page UI, and the server's discovery surface. Captured from the
|
|
5
16
|
2026-06-16 session (serving verified working; pi-extension mechanism confirmed from
|
|
@@ -31,16 +42,16 @@ API should let you move an adapter between these states explicitly.
|
|
|
31
42
|
So **loaded** and **selected** are done. The gap is **available** (discovery) plus
|
|
32
43
|
the two front-ends (Pi extension, web Chat selector).
|
|
33
44
|
|
|
34
|
-
##
|
|
45
|
+
## Shipped (§A–§C) ✅
|
|
35
46
|
|
|
36
|
-
### A. Pi CLI/terminal — adapter via extension *(default none)*
|
|
47
|
+
### A. Pi CLI/terminal — adapter via extension *(default none)* ✅ DONE
|
|
37
48
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
is the right tool.
|
|
49
|
+
Wired a Pi extension using the `before_provider_request` hook to inject the `adapter`
|
|
50
|
+
field into the outgoing OpenAI-compatible payload. `models.json` can't add arbitrary
|
|
51
|
+
body fields — the hook was the right tool.
|
|
42
52
|
|
|
43
|
-
|
|
53
|
+
Shipped as `~/.pi/agent/extensions/mlx-bun-adapter.ts` (copy in-repo + one-liner
|
|
54
|
+
installer):
|
|
44
55
|
|
|
45
56
|
```ts
|
|
46
57
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
@@ -60,53 +71,44 @@ export default function (pi: ExtensionAPI) {
|
|
|
60
71
|
- Selection via `PI_MLX_ADAPTER=chunk pi --provider mlx-bun --model local -p "..."`
|
|
61
72
|
(no native `-p --adapter` flag; unknown CLI flags get rejected). Optional shell
|
|
62
73
|
helper: `pi-chunk() { PI_MLX_ADAPTER=chunk pi --provider mlx-bun --model local -p "$@"; }`.
|
|
63
|
-
-
|
|
64
|
-
`-p` too. `/reload`
|
|
65
|
-
-
|
|
66
|
-
|
|
67
|
-
state file the web UI / a `mlx-bun adapter use <id>` command writes, so CLI and web
|
|
68
|
-
share one selection.
|
|
74
|
+
- Lives in the global `~/.pi/agent/extensions/` dir so it loads for non-interactive
|
|
75
|
+
`-p` too. `/reload` picks it up.
|
|
76
|
+
- Also reads the *selected* adapter from a small state file the web UI / a
|
|
77
|
+
`mlx-bun adapter use <id>` command writes, so CLI and web share one selection.
|
|
69
78
|
|
|
70
|
-
### B. Web Chat page — selector + load/unload *(default none)*
|
|
79
|
+
### B. Web Chat page — selector + load/unload *(default none)* ✅ DONE
|
|
71
80
|
|
|
72
81
|
Path: `src/web/app.html` (UI) ↔ `/ws/chat` (`src/pi-web.ts`) ↔ in-process Pi ↔ model.
|
|
73
82
|
|
|
74
83
|
- **Selector** in `app.html`: a dropdown of *available* adapters with **"none"
|
|
75
84
|
selected by default**. Changing it sets the active selection for subsequent
|
|
76
|
-
messages.
|
|
85
|
+
messages. Multi-select → `"a+b"` (resolveSpec already supports stacking).
|
|
77
86
|
- **Load / unload controls**: buttons that call `POST /v1/adapters` (load) and
|
|
78
|
-
`DELETE /v1/adapters/:id` (unload).
|
|
79
|
-
selected) so the three concepts are visible.
|
|
80
|
-
it
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
`pi.on("before_provider_request", (e) => ({ ...e.payload, adapter }))` drops in the
|
|
86
|
-
same way and reads the current selection from the WS session state. **No custom
|
|
87
|
+
`DELETE /v1/adapters/:id` (unload). Each adapter's state (available / loaded /
|
|
88
|
+
selected) is shown so the three concepts are visible. Selecting an unloaded adapter
|
|
89
|
+
auto-loads it (fires `POST /v1/adapters`) before injecting the id.
|
|
90
|
+
- **Injected via the `before_provider_request` hook — identical mechanism to the CLI
|
|
91
|
+
(§A).** `pi-web.ts` registers the inline extension in `createAgentSession`
|
|
92
|
+
(pi-web.ts:469/605), so `pi.on("before_provider_request", (e) => ({ ...e.payload,
|
|
93
|
+
adapter }))` reads the current selection from WS session state. **No custom
|
|
87
94
|
provider plumbing, no new model-call wiring.** The hook is the spine for BOTH UIs.
|
|
88
|
-
- **Selection → hook**: the dropdown
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
### C. Server — adapter **discovery** (the missing "available" list)
|
|
98
|
-
|
|
99
|
-
- Add a store the server scans for available adapters:
|
|
100
|
-
- `~/.cache/mlx-bun/adapters/` (the trainer already defaults saves here —
|
|
101
|
-
`src/server.ts:1653`) and/or a **sidecar next to the model dir**.
|
|
102
|
-
- New endpoint, e.g. `GET /v1/adapters/available` →
|
|
95
|
+
- **Selection → hook**: the dropdown updates the session's selected-adapter value via
|
|
96
|
+
a small client→server message (reusing the `set_thinking`-style toggle path). Default
|
|
97
|
+
none → hook returns the payload unchanged → base model.
|
|
98
|
+
|
|
99
|
+
### C. Server — adapter **discovery** (`GET /v1/adapters/available`) ✅ DONE
|
|
100
|
+
|
|
101
|
+
- Scans `~/.cache/mlx-bun-finetunes/` and `~/.cache/mlx-bun/adapters/`
|
|
102
|
+
(`src/server.ts:1192`) and any sidecar next to the model dir.
|
|
103
|
+
- `GET /v1/adapters/available` (`src/server.ts:1182`, `src/lora.ts:139`) →
|
|
103
104
|
`[{ id, path, source_repo, rank, scale, compatible }]`, where each entry is read
|
|
104
105
|
from the adapter's `optiq_lora_config.json` (`source_model`, `rank`, `scale`).
|
|
105
|
-
- **
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
- **Filters by the served model** on **repo id**
|
|
107
|
+
(`mlx-community/MiniCPM5-1B-OptiQ-4bit`) rather than the fragile
|
|
108
|
+
machine/snapshot-specific path. The server already tracks `repoId` per model;
|
|
109
|
+
`compatible = adapter.source_repo === served.repoId`.
|
|
110
|
+
|
|
111
|
+
## TODO (§D–§E)
|
|
110
112
|
|
|
111
113
|
### D. Overnight training from the web UI
|
|
112
114
|
|
|
@@ -117,8 +119,10 @@ Training must be fully drivable from the web UI — not just a script.
|
|
|
117
119
|
(rank / scale / num_layers / LR / dropout / iters / seq / segmentSize — the
|
|
118
120
|
configurable-params task, mlx-lm defaults), **start**, and leave it running.
|
|
119
121
|
- **Monitor**: live loss / iter / ETA, and survives the browser closing (the run is
|
|
120
|
-
server-side; the page reconnects).
|
|
121
|
-
(`src/server.ts:
|
|
122
|
+
server-side; the page reconnects). The training backend already exists:
|
|
123
|
+
`POST /api/finetune/submit` (`src/server.ts:1781`). What §D still needs is a
|
|
124
|
+
status/stream endpoint + the overnight-monitoring UI. (Note: `GET /fit` is the
|
|
125
|
+
unrelated memory-fit solver in `src/fit.ts` — not a training endpoint.)
|
|
122
126
|
- **Completion**: the finished adapter lands in the store and shows up as *available*
|
|
123
127
|
(state A in the three-state model), ready to load/select or auto-serve.
|
|
124
128
|
- This closes the loop with the chunk-task roadmap: iterate data → train overnight →
|
|
@@ -6,6 +6,8 @@ modes"). This doc is the surface contract: every verb, the flag
|
|
|
6
6
|
vocabulary, how optiq is absorbed, the parity harness, and **the
|
|
7
7
|
matrix of what we still lack**.
|
|
8
8
|
|
|
9
|
+
**Update 2026-06-17:** Several items have shipped since the 2026-06-12 snapshot: **LoRA training** is live (`src/train/`, `scripts/chunk-finetune.ts`, segmented-backward Phase A MiniCPM5 + Phase B e4b both merged); the **web UI fine-tune tab** (`src/web/app.html`) is live with SFT/DPO workflow, loss curve, merge, and export; the **chat adapter selector** (web UI) is live; and **adapters end-to-end** (web selector + CLI `/adapter`) are done — the matrix below reflects these as ✅. The **Phase 17 compat parity harness itself** (the verb router, dotted aliases, flag-surface parity tests) remains **unbuilt**.
|
|
10
|
+
|
|
9
11
|
## Goal
|
|
10
12
|
|
|
11
13
|
Design mlx-bun's entire CLI/flag surface from scratch (nothing is
|
|
@@ -54,7 +56,7 @@ shared subset; optiq-only features are additive (marked ⊕).
|
|
|
54
56
|
| `chat` | mlx_lm.chat | engine ✅, web chat ✅; no terminal REPL (`tui.ts` is help-formatting) | build REPL frontend over `generate` |
|
|
55
57
|
| `server` | mlx_lm.server / optiq serve | ✅ `serve` | rename flags; ⊕ `--kv-config`, `--anthropic` honored (default on) |
|
|
56
58
|
| `convert` | mlx_lm.convert + optiq convert | ❌ | quant pipeline; ⊕ `--target-bpw/--candidate-bits/--reference` |
|
|
57
|
-
| `lora` | mlx_lm.lora + optiq lora | serve/hot-swap ✅, train
|
|
59
|
+
| `lora` | mlx_lm.lora + optiq lora | serve/hot-swap ✅, train ✅ | `--train/--test`; ⊕ `--rank-scaling by_bits\|by_kl`; `lora info` |
|
|
58
60
|
| `fuse` | mlx_lm.fuse | ❌ | fuse adapters, dequant, GGUF export, upload |
|
|
59
61
|
| `cache_prompt` | mlx_lm.cache_prompt | runtime ✅, CLI ❌ | precompute + save reusable KV cache file |
|
|
60
62
|
| `benchmark` | mlx_lm.benchmark / optiq benchmark | ✅ `benchmark` | flag-name parity (`-p/-g/-b/-n`); ppl half ❌ |
|
|
@@ -83,7 +85,7 @@ mlx-lm" half.
|
|
|
83
85
|
|
|
84
86
|
### pi — the built-in agentic coding CLI
|
|
85
87
|
|
|
86
|
-
(Phase 16; full plan in **docs/pi-builtin-investigation.md**.) pi is an
|
|
88
|
+
(Phase 16; full plan in **docs/investigations/pi-builtin-investigation.md**.) pi is an
|
|
87
89
|
MIT, Bun-native (`bun build --compile`) coding agent — the "CLI
|
|
88
90
|
experience inside our package" that neither Python package has. Two
|
|
89
91
|
commands, one for each audience:
|
|
@@ -111,8 +113,8 @@ split.
|
|
|
111
113
|
The web UI's agent backbone is pi's RPC / `AgentSession.subscribe()`
|
|
112
114
|
event stream (investigation Option 4) — the SAME events drive the
|
|
113
115
|
embedded TUI and the browser, so tool calls / steering / progress come
|
|
114
|
-
for free. Tiles: chat ✅;
|
|
115
|
-
the capability matrix.
|
|
116
|
+
for free. Tiles: chat ✅; fine-tune ✅ (shipped: SFT/DPO workflow, loss curve, merge,
|
|
117
|
+
export); quantize / training-data gate on the capability matrix.
|
|
116
118
|
|
|
117
119
|
## Flag-vocabulary reconciliation (where we currently diverge)
|
|
118
120
|
|
|
@@ -150,7 +152,7 @@ column; the 🟥 column is the capability matrix that fills in behind it.
|
|
|
150
152
|
| KV-cache quant: bf16 / uniform / **mixed per-layer** (kv_config.json) | `server` (`off`/`N`/`config`) | ✅ | done (Phase 9/10; config.ts reads, generate.ts applies per-layer) |
|
|
151
153
|
| KV sensitivity *profiler* (authors a NEW kv_config.json) | `kv-cache` | 🟥 | model-prep, low pri (shipped artifacts already include one) |
|
|
152
154
|
| TurboQuant — optional extra quant *method* (rotation VQ) | scheme, not a verb | 🟥 | Phase 13 (KV plumbing already exists) |
|
|
153
|
-
| **LoRA / DoRA / full training** | `lora --train` |
|
|
155
|
+
| **LoRA / DoRA / full training** | `lora --train` | ✅ | Training (done: Phase A MiniCPM5 + Phase B e4b) |
|
|
154
156
|
| **Sensitivity-aware rank scaling** | `lora --rank-scaling` | 🟥 | Training |
|
|
155
157
|
| **Fuse adapters (+GGUF/upload)** | `fuse` | 🟥 | Training |
|
|
156
158
|
| **lm-eval-harness tasks** | `evaluate` | 🟥 | Eval (new phase) |
|
|
@@ -159,7 +161,7 @@ column; the 🟥 column is the capability matrix that fills in behind it.
|
|
|
159
161
|
| **HF upload** | `upload` | 🟥 | Distribution |
|
|
160
162
|
| **Distributed share** | `share` | 🟥 | Distribution (lowest pri) |
|
|
161
163
|
| **Web UI: quantize** | `lab`/UI | 🟥 | gated on Quantize |
|
|
162
|
-
| **Web UI: fine-tune** | `lab`/UI |
|
|
164
|
+
| **Web UI: fine-tune** | `lab`/UI | ✅ | done (SFT/DPO workflow, loss curve, merge, export) |
|
|
163
165
|
| **Web UI: training-data template + gen** | `lab`/UI | 🟥 | new (no engine dep) |
|
|
164
166
|
| Web UI: chat | `/chat` | ✅ | done (publishing gate 2) |
|
|
165
167
|
| **Native: pi embedded** (`mlx-bun pi`, in-process TUI) | — | ✅ (P3/P4) | Phase 16 (flagship) |
|
|
@@ -177,7 +179,7 @@ does. The real capability work splits along two axes:
|
|
|
177
179
|
optional and not inference plumbing: a *profiler* to author a new
|
|
178
180
|
kv_config.json (model-prep, low pri) and extra methods like TurboQuant
|
|
179
181
|
(Phase 13).
|
|
180
|
-
- **Training**: LoRA/DoRA/full
|
|
182
|
+
- **Training**: LoRA/DoRA/full training ✅ (shipped); `fuse` verb (adapter merge, GGUF export, upload) 🟥 remaining.
|
|
181
183
|
- **Eval**: harness + `perplexity`.
|
|
182
184
|
- **Distribution**: `upload`/`share`.
|
|
183
185
|
|
|
@@ -221,7 +223,7 @@ correctness diffs need no clean machine.
|
|
|
221
223
|
3. Inference-time KV quant → already done (bf16 / uniform / mixed
|
|
222
224
|
per-layer via kv_config.json, Phase 9/10). Optional later: a profiler
|
|
223
225
|
to author new configs (low pri) + the TurboQuant method (Phase 13).
|
|
224
|
-
4. Training → `lora --train
|
|
226
|
+
4. Training → ✅ `lora --train` + web-UI fine-tune tile (both shipped); remaining: `fuse` verb (adapter merge, GGUF export, upload).
|
|
225
227
|
5. Eval → `evaluate`/`perplexity`.
|
|
226
228
|
6. Distribution → `upload`/`share`.
|
|
227
229
|
7. Web UI training-data tile (independent of engine work).
|