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
|
@@ -54,7 +54,7 @@ and notarized as one artifact.
|
|
|
54
54
|
This runs [build-binary.sh](../../scripts/build-binary.sh), then:
|
|
55
55
|
- signs every nested Mach-O (dylibs, any `.node`) with the hardened
|
|
56
56
|
runtime, then the executable with the JIT
|
|
57
|
-
[entitlements](../../
|
|
57
|
+
[entitlements](../../packaging/entitlements.plist) (JavaScriptCore JITs —
|
|
58
58
|
without `allow-jit` the binary is killed on launch);
|
|
59
59
|
- notarizes the zipped bundle via `AC_PROFILE` and waits;
|
|
60
60
|
- emits `dist-release/mlx-bun-v<ver>-arm64.tar.gz` (+ `.sha256`) and
|
|
@@ -75,19 +75,20 @@ gh release create v0.0.4 dist-release/mlx-bun-v0.0.4-arm64.tar.gz \
|
|
|
75
75
|
|
|
76
76
|
## The tap
|
|
77
77
|
|
|
78
|
-
Homebrew taps must be a repo named `homebrew-<name
|
|
78
|
+
Homebrew taps must be a repo named `homebrew-<name>`. The canonical tap is
|
|
79
|
+
**`joshuarossi/homebrew-tap`** (Homebrew shorthand: `joshuarossi/tap`).
|
|
79
80
|
|
|
80
|
-
1. Create a public repo **`joshuarossi/homebrew-
|
|
81
|
+
1. Create a public repo **`joshuarossi/homebrew-tap`** (one-time).
|
|
81
82
|
2. Copy [packaging/homebrew/mlx-bun.rb](../../packaging/homebrew/mlx-bun.rb)
|
|
82
83
|
to `Formula/mlx-bun.rb` in it, updating `version`/`url`/`sha256` from
|
|
83
84
|
the release-script output. Commit and push.
|
|
84
85
|
3. Install:
|
|
85
86
|
```sh
|
|
86
|
-
brew install joshuarossi/
|
|
87
|
+
brew install joshuarossi/tap/mlx-bun
|
|
87
88
|
mlx-bun --version
|
|
88
89
|
```
|
|
89
90
|
|
|
90
|
-
`brew audit --strict --new joshuarossi/
|
|
91
|
+
`brew audit --strict --new joshuarossi/tap/mlx-bun` before pushing
|
|
91
92
|
catches most formula issues; `brew install --build-from-source` +
|
|
92
93
|
`brew test` runs the formula's `test do` locally.
|
|
93
94
|
|
|
@@ -119,3 +120,58 @@ Users then upgrade with:
|
|
|
119
120
|
```sh
|
|
120
121
|
brew upgrade joshuarossi/tap/mlx-bun
|
|
121
122
|
```
|
|
123
|
+
|
|
124
|
+
## Direct download (stable URL)
|
|
125
|
+
|
|
126
|
+
`publish-release.sh` uploads two assets per release: the versioned tarball
|
|
127
|
+
(`mlx-bun-v<ver>-arm64.tar.gz`) **and** a versionless stable copy
|
|
128
|
+
(`mlx-bun-arm64.tar.gz`) pinned to `releases/latest`. This lets the
|
|
129
|
+
install script target a URL that never changes:
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
https://github.com/joshuarossi/mlx-bun/releases/latest/download/mlx-bun-arm64.tar.gz
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The user-facing install path is the script at
|
|
136
|
+
[`website/public/install.sh`](../../website/public/install.sh) (served at
|
|
137
|
+
`mlx-bun.dev/install.sh`), which resolves that URL, extracts the bundle, and
|
|
138
|
+
links `mlx-bun` onto `PATH`:
|
|
139
|
+
```sh
|
|
140
|
+
curl -fsSL https://mlx-bun.dev/install.sh | sh
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Without the script, the same stable URL streams straight to `tar`:
|
|
144
|
+
```sh
|
|
145
|
+
curl -fsSL https://github.com/joshuarossi/mlx-bun/releases/latest/download/mlx-bun-arm64.tar.gz \
|
|
146
|
+
| tar -xz -C /usr/local/bin mlx-bun
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Note: **browser downloads and `curl`-to-file** (e.g. `curl -O ...`) are
|
|
150
|
+
quarantined by Gatekeeper — macOS sets the `com.apple.quarantine` xattr on
|
|
151
|
+
the saved file. The piped one-liner above streams directly to `tar` and
|
|
152
|
+
never writes a quarantined file, so it runs without any Gatekeeper prompt.
|
|
153
|
+
For the browser/curl-to-file path, the notarization ticket resolves the
|
|
154
|
+
Gatekeeper check online; `xattr -d com.apple.quarantine mlx-bun` is the
|
|
155
|
+
manual escape hatch if offline.
|
|
156
|
+
|
|
157
|
+
## npm / bunx
|
|
158
|
+
|
|
159
|
+
mlx-bun is published to npm as `mlx-bun` (current: 0.0.4). Install or
|
|
160
|
+
run without a permanent install:
|
|
161
|
+
|
|
162
|
+
```sh
|
|
163
|
+
# one-off (no install)
|
|
164
|
+
bunx mlx-bun
|
|
165
|
+
|
|
166
|
+
# permanent global install
|
|
167
|
+
bun install -g mlx-bun
|
|
168
|
+
mlx-bun --version
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The npm package wraps the same signed binary via a postinstall download
|
|
172
|
+
step (it does not bundle the ~228 MB dylibs/metallib in the npm tarball
|
|
173
|
+
itself). Publish to npm as part of a release:
|
|
174
|
+
|
|
175
|
+
```sh
|
|
176
|
+
bun publish # from repo root; package.json version must already be bumped
|
|
177
|
+
```
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
The HTTP server is one consumer of a library-first API. Everything it
|
|
4
4
|
does — generation, sampling, KV schemes, LoRA, vision, caching, memory
|
|
5
|
-
admission — is importable directly into a Bun process.
|
|
6
|
-
|
|
5
|
+
admission — is importable directly into a Bun process. Published as
|
|
6
|
+
`mlx-bun` on npm (0.0.4); `bunx mlx-bun` works out of the box, or
|
|
7
|
+
`import … from "mlx-bun"` for programmatic use.
|
|
7
8
|
|
|
8
9
|
One rule everywhere: **one generation at a time per process** (one GPU,
|
|
9
10
|
batch=1). The server serializes through a queue; do the same.
|
|
@@ -11,17 +12,24 @@ batch=1). The server serializes through a queue; do the same.
|
|
|
11
12
|
## Quick start
|
|
12
13
|
|
|
13
14
|
```ts
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
import {
|
|
16
|
+
loadModelConfig,
|
|
17
|
+
Weights,
|
|
18
|
+
createModel, // dispatches to Gemma4Model / MiniCPM5Model / Qwen35Model
|
|
19
|
+
loadTokenizer,
|
|
20
|
+
ChatTemplate,
|
|
21
|
+
generate,
|
|
22
|
+
} from "mlx-bun"; // or "./src/index" in-repo
|
|
20
23
|
|
|
21
24
|
const dir = "/path/to/hf-snapshot"; // mlx-bun ls prints these
|
|
22
|
-
const
|
|
25
|
+
const config = await loadModelConfig(dir);
|
|
26
|
+
const model = createModel(await Weights.open(dir), config); // returns RuntimeModel
|
|
23
27
|
const tok = await loadTokenizer(dir);
|
|
24
28
|
const template = await ChatTemplate.load(dir);
|
|
29
|
+
// Gemma4Model and MiniCPM5Model can still be imported directly from "mlx-bun"
|
|
30
|
+
// if you need the concrete type. Qwen35Model is NOT exported from the public
|
|
31
|
+
// package — use createModel/RuntimeModel (preferred dispatch), or import from
|
|
32
|
+
// "./src/model/qwen35" in-repo.
|
|
25
33
|
|
|
26
34
|
const ids = tok.encode(template.render([{ role: "user", content: "hi" }]));
|
|
27
35
|
const promptIds = ids[0] === ids[1] && ids[0] === tok.bosTokenId ? ids.slice(1) : ids;
|
|
@@ -67,7 +75,7 @@ prompt time), the decode clock from there.
|
|
|
67
75
|
## Serving pieces
|
|
68
76
|
|
|
69
77
|
```ts
|
|
70
|
-
import { loadContext, createServer } from "
|
|
78
|
+
import { loadContext, createServer } from "mlx-bun";
|
|
71
79
|
|
|
72
80
|
const ctx = await loadContext(dir, "my-model", { memoryBudgetBytes: 12e9 });
|
|
73
81
|
const server = createServer(ctx, 8090, {
|
|
@@ -84,6 +92,12 @@ completions, Anthropic `/v1/messages`, OpenAI Responses, adapters, and
|
|
|
84
92
|
|
|
85
93
|
## Prompt cache (in-process prefix reuse)
|
|
86
94
|
|
|
95
|
+
> **Note:** `PromptCache`, `AdapterManager`, `saveKvCache`, and
|
|
96
|
+
> `loadKvCache` are **not** re-exported from the public `"mlx-bun"`
|
|
97
|
+
> entry point (`src/index.ts`). Import them from their internal paths
|
|
98
|
+
> as shown below; this is intentional — they are implementation-level
|
|
99
|
+
> pieces rather than stable public API surface.
|
|
100
|
+
|
|
87
101
|
```ts
|
|
88
102
|
import { PromptCache } from "./src/prompt-cache";
|
|
89
103
|
const cache = new PromptCache(2e9); // BYTE-capped LRU
|
|
@@ -102,9 +116,11 @@ under one adapter must not seed another's prefill).
|
|
|
102
116
|
```ts
|
|
103
117
|
import { saveKvCache, loadKvCache } from "./src/kv-store";
|
|
104
118
|
saveKvCache("/tmp/prefix.kv", tokens, caches); // page-aligned file
|
|
105
|
-
const { tokens, caches, mmap } = loadKvCache("/tmp/prefix.kv", model);
|
|
119
|
+
const { tokens, caches, mmap } = loadKvCache("/tmp/prefix.kv", model as Gemma4Model);
|
|
106
120
|
// reload is a zero-copy MAP_PRIVATE mmap straight to the GPU (~1 ms);
|
|
107
121
|
// keep `mmap` referenced as long as the caches live
|
|
122
|
+
// Note: loadKvCache is typed to accept Gemma4Model, but only uses model.layers.length
|
|
123
|
+
// at runtime — a cast from RuntimeModel is safe for any model that has a layers array.
|
|
108
124
|
```
|
|
109
125
|
|
|
110
126
|
Quantized caches are not persistable yet (documented gap).
|
|
@@ -112,8 +128,7 @@ Quantized caches are not persistable yet (documented gap).
|
|
|
112
128
|
## Registry + fit (model discovery and memory math)
|
|
113
129
|
|
|
114
130
|
```ts
|
|
115
|
-
import { Registry } from "
|
|
116
|
-
import { fit, skuMatrix } from "./src/fit";
|
|
131
|
+
import { Registry, fit, skuMatrix } from "mlx-bun";
|
|
117
132
|
|
|
118
133
|
const reg = new Registry(); // bun:sqlite, scans the HF cache
|
|
119
134
|
await reg.scan();
|
|
@@ -150,4 +165,4 @@ any amount of switching.
|
|
|
150
165
|
handler → process death). Use `fit`/admission before generating, not
|
|
151
166
|
try/catch around it.
|
|
152
167
|
- Never read a typed array that native code wrote in a hot path; use
|
|
153
|
-
the `read.*` helpers (bun#32054 — see PLAN.md Phase 4 findings).
|
|
168
|
+
the `read.*` helpers (bun#32054 — see PLAN-archive.md Phase 4 findings).
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Memory
|
|
2
|
+
|
|
3
|
+
mlx-bun memory is a local Markdown wiki that the built-in pi agents can read as durable user context.
|
|
4
|
+
|
|
5
|
+
Core rule:
|
|
6
|
+
|
|
7
|
+
> Chat agents read memory articles. `mlx-bun memory synthesize` is the only writer.
|
|
8
|
+
|
|
9
|
+
The current release includes the vault, read path, Obsidian open flow, and scheduler. Synthesis is scaffolded but still a no-op M1 stub. The planned synthesis engine uses the same local models you chat with, switched into an editorial pipeline with stage-specific LoRA adapters.
|
|
10
|
+
|
|
11
|
+
## Storage
|
|
12
|
+
|
|
13
|
+
- Vault: `~/.mlx-bun/wiki/`
|
|
14
|
+
- User articles: `~/.mlx-bun/wiki/articles/*.md`
|
|
15
|
+
- Read-only reference docs: `~/.mlx-bun/wiki/Reference/*.md`
|
|
16
|
+
- Meta pages: `~/.mlx-bun/wiki/Meta/*.md`
|
|
17
|
+
- Talk pages: `~/.mlx-bun/wiki/Talk/`
|
|
18
|
+
- Override: `MLX_BUN_WIKI=/path/to/wiki`
|
|
19
|
+
- Derived synthesis DB: `~/.cache/mlx-bun/memory.sqlite`
|
|
20
|
+
|
|
21
|
+
The vault is plain Markdown, git-tracked, and opens directly in Obsidian. `Reference/` contains read-only symlinks to mlx-bun's own docs so memory has useful built-in residents on day one; synthesis must never write them.
|
|
22
|
+
|
|
23
|
+
## CLI
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
mlx-bun memory init # create the wiki; optional import + schedule
|
|
27
|
+
mlx-bun memory status # path, article count, git, schedule, recent articles
|
|
28
|
+
mlx-bun memory open # open wiki in Obsidian/Finder
|
|
29
|
+
mlx-bun memory open <article> # open a specific article
|
|
30
|
+
mlx-bun memory list # list article stems + Reference/* docs
|
|
31
|
+
mlx-bun memory search <query> # search articles
|
|
32
|
+
mlx-bun memory toc <article> # headings + anchors
|
|
33
|
+
mlx-bun memory section <article> <anchor>
|
|
34
|
+
mlx-bun memory links <article> # outbound/inbound wikilinks
|
|
35
|
+
mlx-bun memory read <article> # print article body
|
|
36
|
+
mlx-bun memory synthesize --dry-run
|
|
37
|
+
mlx-bun memory schedule --at 03:00
|
|
38
|
+
mlx-bun memory unschedule
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`memory synthesize` is currently safe and side-effect-free: it reports that M1 stage bodies are not implemented and writes nothing. In the full flow, `memory init` will be able to offer the HF-hosted memory adapter pack download so synthesis can run locally.
|
|
42
|
+
|
|
43
|
+
## Agent tools
|
|
44
|
+
|
|
45
|
+
Both `mlx-bun pi` and the web chat expose these read-only tools:
|
|
46
|
+
|
|
47
|
+
- `memory_search`
|
|
48
|
+
- `memory_read`
|
|
49
|
+
- `memory_toc`
|
|
50
|
+
- `memory_section`
|
|
51
|
+
- `memory_links`
|
|
52
|
+
- `memory_list` — user articles plus read-only `Reference/*` docs
|
|
53
|
+
- `memory_status` — vault path, setup state, article count, git state, last-synthesis placeholder, schedule state, recent changed articles
|
|
54
|
+
|
|
55
|
+
The tools are always registered. If no vault exists, they return a setup message instead of disappearing. Search/read results prefixed with `Reference/` are mlx-bun docs, not user memory articles.
|
|
56
|
+
|
|
57
|
+
## Obsidian
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
mlx-bun memory open
|
|
61
|
+
mlx-bun memory open Archie_Project
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The first command opens the vault. The second opens one article. Obsidian is preferred; Finder/default Markdown app is the fallback.
|
|
65
|
+
|
|
66
|
+
## Synthesis roadmap
|
|
67
|
+
|
|
68
|
+
M1/M2 will turn saved pi sessions into articles using the local model plus four specialized LoRA adapters trained by Josh from existing Lucien-scale pipeline data and distributed from Hugging Face:
|
|
69
|
+
|
|
70
|
+
1. deterministic ingest of saved web/terminal pi session JSONL;
|
|
71
|
+
2. **memory-chunk LoRA** — conversation → topic chunks (already trained as a WIP artifact);
|
|
72
|
+
3. **memory-bucket LoRA** — chunk → existing bucket or new article proposal;
|
|
73
|
+
4. **memory-synthesis LoRA** — chunks + existing article → conservative article update candidate;
|
|
74
|
+
5. **memory-editor LoRA** — candidate article → polished wiki Markdown;
|
|
75
|
+
6. deterministic footnote/wikilink normalization, quality gates, and git commit.
|
|
76
|
+
|
|
77
|
+
The adapters propose; the pipeline validates and writes. The pipeline writes only `articles/`; `Reference/` is immutable context. Until synthesis lands, memory works with imported/hand-written user articles plus the built-in read-only mlx-bun reference docs.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# ORPO training quickstart — the full stack, preconfigured
|
|
2
|
+
|
|
3
|
+
One launcher runs the complete new system — **flash-CCE Metal head** (steel fwd+bwd,
|
|
4
|
+
`[M,V]`-free) + **segmented backward** (gradient-checkpointed layer activations) +
|
|
5
|
+
**prefix-sharing** (one forward over `[prompt; chosen; rejected]`) + LoRA. Each feature
|
|
6
|
+
**falls back and logs** if a precondition isn't met (e.g. a row whose chosen/rejected
|
|
7
|
+
prompts aren't identical → two-forward for that row).
|
|
8
|
+
|
|
9
|
+
## Command — `mlx-bun train` (recommended)
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
mlx-bun train <model> --data <dir>
|
|
13
|
+
```
|
|
14
|
+
- `<model>` is a query or snapshot path (e.g. `e4b`, `MiniCPM5`); omit it to auto-pick.
|
|
15
|
+
- `--data <dir>` is a directory with `train.jsonl` (+ optional `valid.jsonl`); rows are
|
|
16
|
+
`{"prompt": "...", "chosen": "...", "rejected": "..."}`.
|
|
17
|
+
- The command **auto-detects e4b/Gemma** and sets its required training env flags
|
|
18
|
+
(`MLX_BUN_PERF_KERNEL=0`, `MLX_BUN_FUSED_GELU=0`) before loading the model — you don't
|
|
19
|
+
set them yourself. The full stack (flash head + prefix-share + segmented) is on by default.
|
|
20
|
+
- It prints a plan box (dataset counts, format, the resolved knobs), streams per-step loss
|
|
21
|
+
+ peak memory, and saves a **mountable** adapter (`mlx-bun serve <model> --adapter <dir>`).
|
|
22
|
+
- `--dry-run` inspects the dataset and prints the plan without training. `mlx-bun help train`
|
|
23
|
+
lists every flag.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# e4b ORPO on your preference data, checkpointing every 200 steps; run it detached
|
|
27
|
+
nohup mlx-bun train e4b --data ./prefs --iters 1000 --save-every 200 > train.log 2>&1 &
|
|
28
|
+
# resume from a checkpoint (warm-start the LoRA weights)
|
|
29
|
+
mlx-bun train e4b --data ./prefs --resume ~/.cache/mlx-bun/mlx-bun-finetunes/orpo-e4b/checkpoints/step-00200
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
| flag | default | meaning |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| `--method` | `orpo` | `sft` · `dpo` · `orpo` |
|
|
35
|
+
| `--iters` | 100 | iterations |
|
|
36
|
+
| `--lr` | orpo 1e-5 · dpo 5e-5 · sft 2e-4 | learning rate (cosine + short warmup) |
|
|
37
|
+
| `--rank` / `--scale` | orpo 16 / 2.0 | LoRA rank (by_bits scaled) / scale |
|
|
38
|
+
| `--seq` | e4b 8192 · cpm 4096 | max sequence length |
|
|
39
|
+
| `--lambda` | 0.1 | ORPO odds-ratio weight |
|
|
40
|
+
| `--seg` | 2 | layers/segment (segmented backward); `--no-segment` to disable |
|
|
41
|
+
| `--save-every` | off | mountable checkpoint every N steps |
|
|
42
|
+
| `--resume <dir>` | — | warm-start LoRA weights from a checkpoint/adapter |
|
|
43
|
+
| `--no-flash` / `--no-prefix` | (on) | drop the flash Metal head / prefix-sharing |
|
|
44
|
+
| `--adapter <dir>` | `~/.cache/mlx-bun/mlx-bun-finetunes/<method>-<model>` | output dir |
|
|
45
|
+
|
|
46
|
+
> **Long runs:** launch detached from your own shell (`nohup … &`). Foreground is fine for
|
|
47
|
+
> short runs; the command blocks and streams until done.
|
|
48
|
+
|
|
49
|
+
## The launcher (env-var alternative)
|
|
50
|
+
|
|
51
|
+
`scripts/train-orpo.ts` is the same stack driven by env vars — handy for scripting:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
MODEL=/path/to/snapshot DATA=/path/to/datadir bun scripts/train-orpo.ts
|
|
55
|
+
```
|
|
56
|
+
- `DATA` is a directory with `train.jsonl` (+ optional `valid.jsonl`).
|
|
57
|
+
- Auto-detects e4b/Gemma and sets the training env flags, same as the CLI.
|
|
58
|
+
|
|
59
|
+
### Preconfigured defaults (override via env)
|
|
60
|
+
| env | default | meaning |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `SEQ` | e4b 8192 · cpm 4096 | max sequence length |
|
|
63
|
+
| `SEG` | 2 | segment size (layers/segment); the validated e4b@8192 sweet spot |
|
|
64
|
+
| `ITERS` | 100 | iterations |
|
|
65
|
+
| `LR` | 1e-5 | learning rate (cosine + short warmup) |
|
|
66
|
+
| `RANK` / `SCALE` | 16 / 2.0 | LoRA rank (by_bits scaled) / scale |
|
|
67
|
+
| `LAMBDA` | 0.1 | ORPO odds-ratio weight |
|
|
68
|
+
| `FLASH=0` | (on) | use the MLX fused head instead of the flash Metal head |
|
|
69
|
+
| `PREFIX=0` | (on) | disable prefix-sharing (plain two-forward) |
|
|
70
|
+
| `SEGOFF=1` | (off) | disable the segmented backward (more memory) |
|
|
71
|
+
| `ADAPTER` | `~/.cache/mlx-bun/mlx-bun-finetunes/orpo-<model>` | output dir (the finetunes cache, not the repo) |
|
|
72
|
+
|
|
73
|
+
Example — e4b overnight at 8192:
|
|
74
|
+
```bash
|
|
75
|
+
MODEL=~/.cache/huggingface/hub/models--mlx-community--gemma-4-e4b-it-OptiQ-4bit/snapshots/<snap> \
|
|
76
|
+
DATA=./data/mydpo ITERS=200 bun scripts/train-orpo.ts
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## What to expect — performance (measured, M1 Max 32 GB)
|
|
80
|
+
|
|
81
|
+
### e4b (Gemma4, H=2560, V=262144) — full stack
|
|
82
|
+
| seq | config | peak GB | s/step |
|
|
83
|
+
|---|---|---|---|
|
|
84
|
+
| 8192 | flash + segmented(2) + **prefix-share** (prompt-dominant data) | **13.3** | **~70** |
|
|
85
|
+
| 8192 | flash + segmented(2), no prefix | 16.1 | ~175 |
|
|
86
|
+
| 4096 | flash + segmented(2), no prefix | 11.7 | ~81 |
|
|
87
|
+
| 2048 | flash + segmented(2), no prefix | 10.2 | ~40 |
|
|
88
|
+
| 1024 | flash + segmented(2), no prefix | 8.7 | ~21 |
|
|
89
|
+
|
|
90
|
+
- **Fits comfortably in 32 GB at 8192** (the historical "e4b OOMs ≥2048" ceiling is gone).
|
|
91
|
+
- **Prefix-sharing is a real win when the prompt dominates** — at 8192 with a long shared
|
|
92
|
+
prompt it cut memory 16→13 GB *and* step time 175→70 s (prompt encoded once, not twice).
|
|
93
|
+
On response-dominant data the saving shrinks toward zero (and it falls back per-row when
|
|
94
|
+
prompts differ); it never hurts correctness.
|
|
95
|
+
- Memory is **linear in sequence length** (~+1.5 GB / 1024 tokens) — segmenting + flash
|
|
96
|
+
attention keep it linear, not quadratic. The head itself is **flat 0.93 GB** regardless
|
|
97
|
+
of vocab/seq.
|
|
98
|
+
- An ~8 h run at 8192 ≈ 100–400 steps depending on the prompt/response split.
|
|
99
|
+
|
|
100
|
+
### MiniCPM5 (1B, H=1536, V=130560) — full stack
|
|
101
|
+
- Fits easily (~1–2 GB at short seq). ~0.4–1 s/step at seq 256; scales with seq.
|
|
102
|
+
- Everything composes (flash / segmented / prefix-share, in any combination).
|
|
103
|
+
|
|
104
|
+
### The flash-CCE head in isolation (the piece that made large-vocab affordable)
|
|
105
|
+
- Forward 180 ms, backward **754 ms** (e4b) / 224 ms (cpm) — exact (dh 0.40% / 0.28%
|
|
106
|
+
bf16-class), `[M,V]`-free, peak **0.93 GB flat at M=8192**. The backward is **5× the old
|
|
107
|
+
hand-rolled kernel** (3687 ms) via the verbatim MLX steel GEMM + fused dequant.
|
|
108
|
+
|
|
109
|
+
## Fallback behavior (logged)
|
|
110
|
+
- **Prefix-share, per row**: if `chosen`/`rejected` prompts aren't byte-identical →
|
|
111
|
+
two-forward for that row. The first occurrence logs `prefix-share: row prompt mismatch
|
|
112
|
+
→ two-forward fallback`; the saving is lost only on mismatched rows.
|
|
113
|
+
- **`FLASH=0`** → the MLX fused linear-CE head (`quantizedMatmul` both ways; exact,
|
|
114
|
+
`[chunk,V]` transient) instead of the Metal kernel.
|
|
115
|
+
- **`SEGOFF=1`** → all layer activations resident (only viable at short seq on e4b).
|
|
116
|
+
- Models other than MiniCPM5 / Gemma4 with `orpoPrefixShared` error out (not silently).
|
|
117
|
+
|
|
118
|
+
## Optional / experimental
|
|
119
|
+
- The Apple-CCE backward skips (`MLX_BUN_CCE_BWD_BLOCK_EPS=1e-5`, the lossless vocab-block
|
|
120
|
+
skip; `MLX_BUN_CCE_BWD_FILTER_EPS=1e-5`, the coeff filter) are **off by default** — on
|
|
121
|
+
the now-fast steel kernel they give little and the coeff filter costs gradient accuracy.
|
|
122
|
+
The block skip *can* help on long genuinely-peaked real text; A/B it on your data and
|
|
123
|
+
watch that the loss doesn't move.
|
|
124
|
+
|
|
125
|
+
## Caveats (honest)
|
|
126
|
+
- The e4b numbers above are from **short probe runs** (2–3 steps); they confirm the
|
|
127
|
+
pipeline runs, fits memory, and produces finite/decreasing loss — **not** training
|
|
128
|
+
quality (that's your data + hyperparameters).
|
|
129
|
+
- e4b gradients are **bf16-class** (~1–3% vs an exact reference) — expected for the fused
|
|
130
|
+
bf16 path; MiniCPM5 is tighter (forward bit-exact, grads ~1%).
|
|
@@ -30,7 +30,18 @@ Request body (OpenAI chat schema; unknown fields ignored):
|
|
|
30
30
|
"tools": [ /* OpenAI function tools */ ],
|
|
31
31
|
"tool_choice": "auto", // "none" disables tools
|
|
32
32
|
"chat_template_kwargs": { // forwarded to the chat template
|
|
33
|
-
"enable_thinking": false // MiniCPM5: <think> channel on/off
|
|
33
|
+
"enable_thinking": false // MiniCPM5 / Qwen3.5: <think> channel on/off
|
|
34
|
+
},
|
|
35
|
+
"reasoning_effort": "medium", // "none"|"minimal"|"low"|"medium"|"high"
|
|
36
|
+
// gates enable_thinking on Qwen3.5/MiniCPM5:
|
|
37
|
+
// "none" → off, any other level → on.
|
|
38
|
+
// Overrides chat_template_kwargs.enable_thinking.
|
|
39
|
+
"hlg": { // HLG tone-curve sampling (per request).
|
|
40
|
+
"enabled": true, // merged over --hlg-sampling server defaults.
|
|
41
|
+
"width": 4, // logit-width of the tone plateau
|
|
42
|
+
"shoulder": 4, // top shoulder width
|
|
43
|
+
"toe": 6, // bottom toe width
|
|
44
|
+
"pivot_offset": 6 // pivot point offset from top
|
|
34
45
|
},
|
|
35
46
|
"adapter": "id" // LoRA: "id", stacked "a+b", or "none"
|
|
36
47
|
}
|
|
@@ -92,12 +103,15 @@ messages; multi-turn prompt prefixes reuse the KV prompt cache
|
|
|
92
103
|
automatically.
|
|
93
104
|
|
|
94
105
|
Tool-call parsing is per model family. Gemma 4 uses its native
|
|
95
|
-
`<|tool_call>`…`<tool_call|>` sentinel tokens.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
106
|
+
`<|tool_call>`…`<tool_call|>` sentinel tokens. Qwen3.5 emits decoded
|
|
107
|
+
text wrapped in `<tool_call><function=name><parameter=key>value` XML
|
|
108
|
+
equals-style blocks. MiniCPM5 emits XML in decoded text
|
|
109
|
+
(`<function name="…"><param name="…">…`, CDATA-wrapped values
|
|
110
|
+
supported). For both Qwen3.5 and MiniCPM5 content before the tool
|
|
111
|
+
markup still streams live, only the markup is withheld and converted to
|
|
112
|
+
`tool_calls`. Argument values are decoded against the tool's JSON schema
|
|
113
|
+
(string-typed params stay strings); markup that fails to parse falls
|
|
114
|
+
back to plain content.
|
|
101
115
|
|
|
102
116
|
### Errors
|
|
103
117
|
|
|
@@ -191,6 +205,90 @@ OpenAI SDK speak this now). Oracle: optiq responses shim.
|
|
|
191
205
|
}
|
|
192
206
|
```
|
|
193
207
|
|
|
208
|
+
## GET /library
|
|
209
|
+
|
|
210
|
+
Returns all models found in the local HuggingFace hub cache (via the
|
|
211
|
+
registry scan), each annotated with a fit assessment for this machine.
|
|
212
|
+
Response is cached for 30 seconds (registry scan + config reads; no
|
|
213
|
+
tensor bytes are read).
|
|
214
|
+
|
|
215
|
+
```jsonc
|
|
216
|
+
{
|
|
217
|
+
"models": [{
|
|
218
|
+
"repo_id": "…",
|
|
219
|
+
"model_type": "gemma3" | "minicpm5" | "qwen3" | …,
|
|
220
|
+
"size_bytes": 0,
|
|
221
|
+
"quant_bits": 4,
|
|
222
|
+
"vision": false, // has vision sidecar
|
|
223
|
+
"supported": true, // recognized model family
|
|
224
|
+
"serving": false, // currently loaded in this server
|
|
225
|
+
"assessment": { // null if config unreadable
|
|
226
|
+
"fits": true,
|
|
227
|
+
"max_safe_context": 8192,
|
|
228
|
+
"predicted_decode_tps": 0.0
|
|
229
|
+
}
|
|
230
|
+
}]
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## GET /downloads
|
|
235
|
+
|
|
236
|
+
Snapshot of the last 5 model downloads (active, completed, or errored)
|
|
237
|
+
initiated via `mlx-bun download` or the web library panel.
|
|
238
|
+
|
|
239
|
+
```jsonc
|
|
240
|
+
{
|
|
241
|
+
"downloads": [{
|
|
242
|
+
"repo_id": "…",
|
|
243
|
+
"state": "active" | "done" | "error",
|
|
244
|
+
"current_file": "model.safetensors" | null,
|
|
245
|
+
"received_bytes": 0,
|
|
246
|
+
"total_bytes": 0,
|
|
247
|
+
"files_done": 0,
|
|
248
|
+
"files_total": 0,
|
|
249
|
+
"bytes_per_sec": 0, // rolling ~5 s window
|
|
250
|
+
"started_at": 1760000000,
|
|
251
|
+
"finished_at": null,
|
|
252
|
+
"error": "…" // present on state "error"
|
|
253
|
+
}]
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## GET /fit
|
|
258
|
+
|
|
259
|
+
Fit assessment for the loaded model on this machine, plus a capability
|
|
260
|
+
matrix across Apple SKUs. Used by the status page. Experts bytes come
|
|
261
|
+
from the registry so MoE active-parameter predictions match the serve
|
|
262
|
+
banner and `mlx-bun fit`. When the eval DB has a real measured decode
|
|
263
|
+
rate for this model snapshot, it is included and takes precedence over
|
|
264
|
+
the prediction.
|
|
265
|
+
|
|
266
|
+
```jsonc
|
|
267
|
+
{
|
|
268
|
+
"machine": { "chip": "M4 Pro", "ram_bytes": 0, "bandwidth_gbs": 0.0 },
|
|
269
|
+
"context_tokens": 8192, // current admission ceiling
|
|
270
|
+
"typical_context_tokens": 8192, // min(8192, context_tokens)
|
|
271
|
+
"typical_decode_tps": 0.0, // predicted at typical_context_tokens
|
|
272
|
+
"measured_decode_tps": null, // real number from eval DB, or null
|
|
273
|
+
"measured_at": null, // unix ms of measurement, or null
|
|
274
|
+
"report": {
|
|
275
|
+
"fits": true,
|
|
276
|
+
"weights_bytes": 0,
|
|
277
|
+
"kv_bytes": 0,
|
|
278
|
+
"transient_bytes": 0,
|
|
279
|
+
"total_bytes": 0,
|
|
280
|
+
"usable_bytes": 0,
|
|
281
|
+
"max_safe_context": 8192,
|
|
282
|
+
"predicted_decode_tps": 0.0
|
|
283
|
+
},
|
|
284
|
+
"sku_matrix_ctx": 32768,
|
|
285
|
+
"sku_matrix": [{
|
|
286
|
+
"sku": "M4 Pro 24 GB", "ram_gb": 24,
|
|
287
|
+
"fits": true, "max_context": 32768, "decode_tps": 0.0
|
|
288
|
+
}]
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
194
292
|
## Adapters (LoRA hot-swap)
|
|
195
293
|
|
|
196
294
|
- `GET /v1/adapters` — `{ adapters: [{ id, path, rank, scale, size_bytes, mounted_layers }] }`
|
|
@@ -25,11 +25,18 @@ the design rationale is in
|
|
|
25
25
|
| `--prompt-cache` | GB | `2` (≈2e9 bytes) | Byte cap for the prompt (prefix-reuse KV) cache. **Binary GiB (×2³⁰)** on input. `0`/unset ⇒ default; there is no flag to disable the cache outright. |
|
|
26
26
|
| `--batch` | n | `1` (serial) | Max concurrent requests batched through the mlx-lm-parity engine. `>1` switches the **whole server** into bf16 continuous batching — a *mode*, not a load fallback. See [Execution modes](#execution-modes-serial-vs---batch-n). Alias: `--decode-concurrency` (mlx_lm.server drop-in). |
|
|
27
27
|
| `--kv-quant` | `config`\|`off`\|`4`\|`8` | `config` serial / `off`(bf16) under `--batch N` | KV-cache quantization. `config` = per-layer `kv_config.json` (optiq parity); `off` = bf16; `4`/`8` = uniform bits (group 64, start 0). Under `--batch N`, an explicit value routes those requests to the **serial** lane (batched is bf16-only). |
|
|
28
|
-
| `--thinking` | `true`\|`false` | model's own (false for CPM) | Server-wide default for the chat template's `enable_thinking` (MiniCPM5/CPM hybrid reasoning). A request's `chat_template_kwargs.enable_thinking` overrides it. |
|
|
28
|
+
| `--thinking` | `true`\|`false` | model's own (false for CPM) | Server-wide default for the chat template's `enable_thinking` (MiniCPM5/CPM and Qwen3.5 hybrid reasoning). A request's `chat_template_kwargs.enable_thinking` overrides it. |
|
|
29
29
|
| `--temperature` | n ∈ [0,5] | `generation_config.json` | Server-wide sampling default. Per-request `temperature` still wins; the browser chat (sends none) inherits this. |
|
|
30
30
|
| `--top-p` | n ∈ [0,1] | `generation_config.json` | Server-wide top-p default (per-request `top_p` wins). |
|
|
31
31
|
| `--top-k` | n ∈ [0,1e6] | `generation_config.json` | Server-wide top-k default (per-request `top_k` wins). |
|
|
32
|
-
| `--
|
|
32
|
+
| `--no-open` | (bool) | off | Skip the automatic browser open on start. By default an interactive terminal session opens `http://<host>:<port>/#/chat` once the server is ready; pass this flag to suppress it (e.g. headless or non-TTY environments already skip it). |
|
|
33
|
+
| `--hlg-sampling` | `on`\|`off` | off | Piecewise tone-curve (HLG) sampling: rolls off the top-token region, boosts the mids, gentles the tail. The overall gain folds from `--temperature`. See [docs/design/hlg-sampling.md](../design/hlg-sampling.md). |
|
|
34
|
+
| `--hlg-width` | nats | `4` | HLG mid-region half-width (nats). Only meaningful with `--hlg-sampling on`. |
|
|
35
|
+
| `--hlg-shoulder` | nats | `4` | HLG highlight rolloff scale (nats). Only meaningful with `--hlg-sampling on`. |
|
|
36
|
+
| `--hlg-toe` | nats | `6` | HLG shadow rolloff scale (nats). Only meaningful with `--hlg-sampling on`. |
|
|
37
|
+
| `--hlg-pivot-offset` | nats | `6` | HLG pivot point: nats below the top token. Only meaningful with `--hlg-sampling on`. |
|
|
38
|
+
| `--expert-offload` | (bool) | off | **MoE models only.** Serve experts from a page-aligned file mmap (built on first use). Keeps the model out of memory pressure — physical footprint ≈ active params. Ignored with a warning on dense models. Bit-exact with the resident path. |
|
|
39
|
+
| `--compiled-decode` | on\|off | on | Replay the per-step decode graph in C++ (`MLX_BUN_COMPILED_DECODE`). Bit-exact A/B lever. **Serial lane only** (see note below). **Gemma4-dense only** — LoRA, MoE, and non-Gemma4 models (MiniCPM5 / Qwen3.5) run eager; an unsupported step falls back to eager for the rest of that generation. |
|
|
33
40
|
| `--perf-kernel` | on\|off | **on** | Fused quantized-KV decode-SDPA Metal kernel (`MLX_BUN_PERF_KERNEL`), the perf side of the compat A/B. Engages on quantized caches at decode. **Serial lane only.** |
|
|
34
41
|
| `--fused-decode` | on\|off | off | Experimental: tile the quantized decode SDPA (`MLX_BUN_FUSED_DECODE`). **Serial lane only.** |
|
|
35
42
|
| `--fused-sdpa` | on\|off | on | Fused SDPA path for quantized prefill/continuation (inverted env `MLX_BUN_NO_FUSED_SDPA`). **Serial lane only.** |
|
|
@@ -40,7 +47,7 @@ winners; flip them to compare. They are set as `MLX_BUN_*` env vars
|
|
|
40
47
|
before the model loads, so they apply to `mlx-bun pi` too. They affect
|
|
41
48
|
the **serial** decode path (`generate()`); the batched scheduler calls
|
|
42
49
|
`model.forwardHidden` directly and so is unaffected by all of them — see
|
|
43
|
-
[Levers that don't reach the batched lane](
|
|
50
|
+
[Levers that don't reach the batched lane](#--batch-n-is-compat-mode--perf-flags-dont-apply-by-design).
|
|
44
51
|
|
|
45
52
|
> **Note — `--perf-kernel` default.** The code defaults it **on**
|
|
46
53
|
> (`perfKernelEnabled()` returns true unless `MLX_BUN_PERF_KERNEL=0`, and
|
|
@@ -129,7 +136,7 @@ concurrently with each other.
|
|
|
129
136
|
| `--thinking` / `enable_thinking` | ✅ batches (template-render concern, lane-independent) |
|
|
130
137
|
| multi-turn / long prompt | ✅ batches, but **no prompt-cache reuse** (`cached_tokens=0`) |
|
|
131
138
|
|
|
132
|
-
|
|
139
|
+
All three model families — full-attention (CPM), sliding-window (Gemma), and hybrid gated-DeltaNet (Qwen3.5) — batch; the
|
|
133
140
|
scheduler assembles each layer's cache by attention type.
|
|
134
141
|
|
|
135
142
|
## Compatibility matrix
|
|
@@ -216,7 +223,7 @@ The live config and batch state:
|
|
|
216
223
|
|
|
217
224
|
```jsonc
|
|
218
225
|
{
|
|
219
|
-
"server": { "owner": "serve", "model": "...", "started_at": 0 },
|
|
226
|
+
"server": { "owner": "serve" | "pi-session" | "embedded", "model": "...", "started_at": 0 },
|
|
220
227
|
"prompt_cache": { "entries": 0, "bytes": 0, "max_bytes": 2000000000, "hits": 0, "misses": 0 },
|
|
221
228
|
"response_store": { "entries": 0, "bytes": 0, "max_bytes": 33554432, "ttl_ms": 3600000 },
|
|
222
229
|
"kv_quant": {
|