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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Josh Rossi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
# mlx-bun
|
|
2
|
+
|
|
3
|
+
Native MLX inference for Bun. Run quantized LLMs on Apple Silicon from
|
|
4
|
+
TypeScript — no Python, no sidecar server, one runtime.
|
|
5
|
+
|
|
6
|
+
Measured head-to-head on an M4 Pro (24 GB), same models, same day:
|
|
7
|
+
logits **bit-exact** against the Python reference; served over HTTP the
|
|
8
|
+
**fastest stack on every model tested** (TTFT 45–89 ms vs python's
|
|
9
|
+
220–327 ms, zero server overhead vs mlx-lm's −5–6%); prefill up to
|
|
10
|
+
**1.8×** mlx-lm at 8k context; cold start → first token of a cached
|
|
11
|
+
prompt in **394 ms**. Full table: [benchmarks](#benchmarks).
|
|
12
|
+
|
|
13
|
+
## Getting started
|
|
14
|
+
|
|
15
|
+
You need an Apple Silicon Mac (MLX is Metal-only, so macOS only by
|
|
16
|
+
design). Three ways in.
|
|
17
|
+
|
|
18
|
+
### Homebrew (recommended)
|
|
19
|
+
|
|
20
|
+
A self-contained, signed + notarized binary — no toolchain to install by
|
|
21
|
+
hand:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
brew install joshuarossi/tap/mlx-bun
|
|
25
|
+
mlx-bun
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
That's the whole thing. The bottle already bundles the MLX native
|
|
29
|
+
runtime, so the only thing the first run fetches is the model.
|
|
30
|
+
|
|
31
|
+
### bunx (no install)
|
|
32
|
+
|
|
33
|
+
Already have [Bun](https://bun.sh) ≥ 1.3.14? Run it straight from npm,
|
|
34
|
+
nothing to install:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
bunx mlx-bun
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
First run fetches the MLX native runtime (~52 MB) and the model into your
|
|
41
|
+
caches, then serves. (Bun only — `npx mlx-bun` under Node exits with a
|
|
42
|
+
"requires Bun" notice by design.)
|
|
43
|
+
|
|
44
|
+
### From source
|
|
45
|
+
|
|
46
|
+
Needs [Bun](https://bun.sh) ≥ 1.3.14 — no Python, no native library to
|
|
47
|
+
install by hand:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
# 1. Install Bun
|
|
51
|
+
curl -fsSL https://bun.sh/install | bash
|
|
52
|
+
exec $SHELL -l
|
|
53
|
+
|
|
54
|
+
# 2. Clone, install, link the CLI
|
|
55
|
+
git clone https://github.com/joshuarossi/mlx-bun.git && cd mlx-bun
|
|
56
|
+
bun install
|
|
57
|
+
bun run link-cli # adds the `mlx-bun` command — or run `bun src/cli.ts <verb>`
|
|
58
|
+
|
|
59
|
+
# 3. Run it — that's the whole thing
|
|
60
|
+
mlx-bun
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Bare `mlx-bun` is an alias for `mlx-bun serve`. On its first run, with no
|
|
64
|
+
model named, it does everything for you:
|
|
65
|
+
|
|
66
|
+
1. pulls the MLX native runtime (~52 MB) into `~/Library/Caches/mlx-bun/`
|
|
67
|
+
(skipped on the Homebrew install — the runtime ships in the bottle);
|
|
68
|
+
2. downloads the sub-GB `MiniCPM5-1B` starter and serves it, so you're
|
|
69
|
+
chatting in well under a minute;
|
|
70
|
+
3. starts downloading `gemma-4-e4b` (the stronger 4B model) in the
|
|
71
|
+
background — it becomes the default on your next `mlx-bun serve`, on a
|
|
72
|
+
16 GB+ Mac;
|
|
73
|
+
4. opens the chat UI in your browser at
|
|
74
|
+
[`http://localhost:8090/#/chat`](http://localhost:8090/#/chat)
|
|
75
|
+
(pass `--no-open` to skip).
|
|
76
|
+
|
|
77
|
+
Want a specific model instead? Name it — `mlx-bun serve e4b` (substring
|
|
78
|
+
match against your downloaded models), grabbing it first with
|
|
79
|
+
`mlx-bun get <repo-id>` if you don't have it. See
|
|
80
|
+
[Supported models](#supported-models) for the full list and what fits
|
|
81
|
+
your machine.
|
|
82
|
+
|
|
83
|
+
**Prefer the API?** It's OpenAI-compatible — any OpenAI client works:
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
curl http://localhost:8090/v1/chat/completions \
|
|
87
|
+
-H 'Content-Type: application/json' \
|
|
88
|
+
-d '{
|
|
89
|
+
"messages": [{"role": "user", "content": "Hello!"}],
|
|
90
|
+
"max_tokens": 128, "temperature": 0.7
|
|
91
|
+
}'
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
That's it. The server serves the one model it was started with (the
|
|
95
|
+
request's `model` field is ignored and the loaded model id is echoed
|
|
96
|
+
back in responses).
|
|
97
|
+
|
|
98
|
+
## Supported models
|
|
99
|
+
|
|
100
|
+
Scope is deliberate: a few model families held to **bit-exact** logit
|
|
101
|
+
parity with the Python reference, rather than dozens held to none.
|
|
102
|
+
Currently MiniCPM5 plus the Gemma-4 OptiQ quants:
|
|
103
|
+
|
|
104
|
+
| Model | Download | Fits on | Vision | Notes |
|
|
105
|
+
|---|---|---|---|---|
|
|
106
|
+
| [`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/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+); ~54 tok/s |
|
|
108
|
+
| [`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 sidecar + tool calling, both verified end-to-end |
|
|
109
|
+
| [`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 — the python servers crash loading it on 24 GB |
|
|
110
|
+
|
|
111
|
+
Not sure what fits your machine? `bun src/cli.ts fit <model> --ctx 8192`
|
|
112
|
+
gives a deterministic answer (see below). Qwen 3.x is next on the
|
|
113
|
+
roadmap (see [PLAN.md](./PLAN.md)).
|
|
114
|
+
|
|
115
|
+
## Why
|
|
116
|
+
|
|
117
|
+
MLX is Apple's ML framework: hand-tuned Metal kernels for Apple Silicon,
|
|
118
|
+
with official bindings for Python, C++, Swift, and C — but no JavaScript
|
|
119
|
+
story. Today, a JS/TS app that wants local MLX inference must shell out to
|
|
120
|
+
a Python server (mlx-lm, optiq) and accept that stack's fragility: venv
|
|
121
|
+
setup, brittle download tooling, segfaults on exit, monkey-patched HTTP
|
|
122
|
+
layers.
|
|
123
|
+
|
|
124
|
+
The performance-critical work — every matmul, every attention pass — lives
|
|
125
|
+
in MLX's C++/Metal core and is exposed through `mlx-c`. The Python layer on
|
|
126
|
+
top is pure orchestration: model loading, tokenization, the sampling loop,
|
|
127
|
+
serving. That layer is performance-neutral (the GPU dominates), so it can
|
|
128
|
+
be rewritten in any runtime without losing speed — and Bun is the right
|
|
129
|
+
one:
|
|
130
|
+
|
|
131
|
+
- **`bun:ffi`** — lowest-overhead FFI of any JS runtime; binds `mlx-c`
|
|
132
|
+
directly, no node-gyp, no binding compilation.
|
|
133
|
+
- **Lazy native weight loading** — mlx's loader materializes tensors on
|
|
134
|
+
first use; opening an 8.9 GB model takes milliseconds, and warm
|
|
135
|
+
restarts are near-instant on cached pages.
|
|
136
|
+
- **`Bun.Image`** — native OS image codecs (HEIC, AVIF, WebP, JPEG, …)
|
|
137
|
+
for the vision path, EXIF auto-orientation included.
|
|
138
|
+
- **`bun:sqlite`** — built-in storage for the model registry and eval DB.
|
|
139
|
+
- **One binary, one toolchain** — runtime, package manager, test runner.
|
|
140
|
+
|
|
141
|
+
## CLI
|
|
142
|
+
|
|
143
|
+
Commands are shown as `bun src/cli.ts <verb>` (runs straight from a
|
|
144
|
+
clone). Once installed/linked, `mlx-bun <verb>` is the identical command.
|
|
145
|
+
|
|
146
|
+
```sh
|
|
147
|
+
bun src/cli.ts get mlx-community/gemma-4-12B-it-OptiQ-4bit # resumable, verified download
|
|
148
|
+
bun src/cli.ts scan # index your HF cache into the registry
|
|
149
|
+
bun src/cli.ts ls # list models (size, params, quant, capabilities)
|
|
150
|
+
bun src/cli.ts ls --vision --max-size 10GB
|
|
151
|
+
bun src/cli.ts fit gemma --ctx 32768 # memory contract: fits? max context? predicted tok/s
|
|
152
|
+
bun src/cli.ts fit gemma --ctx 8192 --skus # ...same, across the Apple Silicon lineup
|
|
153
|
+
bun src/cli.ts serve gemma --port 8090 # OpenAI-compatible server
|
|
154
|
+
bun src/cli.ts serve gemma --memory-budget 18 # ...with admission control (GB)
|
|
155
|
+
bun src/cli.ts evals # recorded benchmark runs
|
|
156
|
+
./benchmark.sh # head-to-head matrix vs mlx-lm/optiq (reboot first;
|
|
157
|
+
# preflight-gated, resumable, writes benchmarks-h2h-<date>.md)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Model arguments are substring queries against the registry (`e4b`,
|
|
161
|
+
`26B`, `12B-it`); a query matching more than one model errors out
|
|
162
|
+
listing the candidates — just make it more specific.
|
|
163
|
+
|
|
164
|
+
`fit` is deterministic, not vibes: weights bytes from safetensors
|
|
165
|
+
headers, KV bytes/token from the config (sliding-window layers capped,
|
|
166
|
+
MoE active-expert bytes for decode), calibrated prefill transient, and
|
|
167
|
+
the machine's wired-memory ceiling. Predictions are recorded next to
|
|
168
|
+
measured peaks in the eval DB.
|
|
169
|
+
|
|
170
|
+
## HTTP API
|
|
171
|
+
|
|
172
|
+
`serve` exposes an OpenAI-compatible API. Anything that speaks the
|
|
173
|
+
OpenAI protocol can point at it — the OpenAI SDK
|
|
174
|
+
(`baseURL: "http://localhost:8090/v1"`, any non-empty `apiKey`), or
|
|
175
|
+
agent CLIs like pi/OpenClaw via their provider config.
|
|
176
|
+
|
|
177
|
+
- **`POST /v1/chat/completions`** — streaming (SSE) and non-streaming;
|
|
178
|
+
`temperature`, `top_p`, `top_k`, `max_tokens`, `seed`,
|
|
179
|
+
`repetition_penalty`, `stop` (string or array, matched on decoded
|
|
180
|
+
text with streaming hold-back); omitted sampling fields default to
|
|
181
|
+
the model's own `generation_config.json` recipe; usage includes
|
|
182
|
+
`cached_tokens`. Full schemas in
|
|
183
|
+
[docs/reference/server-api.md](./docs/reference/server-api.md); start
|
|
184
|
+
flags and the `--batch N` compatibility matrix in
|
|
185
|
+
[docs/reference/server-config.md](./docs/reference/server-config.md).
|
|
186
|
+
- **Tool calling** — pass OpenAI `tools`; each family's native format
|
|
187
|
+
is parsed into `tool_calls` JSON with `finish_reason: "tool_calls"`
|
|
188
|
+
(Gemma 4 `<|tool_call>` sentinel tokens; MiniCPM5
|
|
189
|
+
`<function name=…>` XML with schema-aware argument decoding);
|
|
190
|
+
`role: "tool"` round-trips, including multi-turn agent loops.
|
|
191
|
+
- **Vision** — `image_url` content parts (data: URLs or http/s), on
|
|
192
|
+
models with the vision sidecar. PNG, JPEG, HEIC, AVIF, WebP, TIFF,
|
|
193
|
+
GIF, BMP via native OS codecs.
|
|
194
|
+
- **Prompt caching** — a byte-capped LRU KV cache reuses the longest
|
|
195
|
+
common token prefix across requests (multi-turn conversations
|
|
196
|
+
re-prefill only the new turn) — automatic, no client changes.
|
|
197
|
+
Entries are adapter-specific when LoRA adapters are in play.
|
|
198
|
+
- **LoRA hot-swap** — mount adapters at startup (`--adapter id=dir`)
|
|
199
|
+
or at runtime (`POST /v1/adapters`), select per request with the
|
|
200
|
+
`adapter` body field (`"id"`, stacked `"a+b"`, or `"none"`). The
|
|
201
|
+
base model never reloads; an unselected adapter costs nothing.
|
|
202
|
+
- **Mixed-precision KV** — when the model repo ships `kv_config.json`
|
|
203
|
+
(every Gemma-4 OptiQ repo does), per-layer KV quantization applies
|
|
204
|
+
automatically (full-attention layers; sliding layers stay bf16).
|
|
205
|
+
Measured decode-neutral at 8k on the 12B with KV bytes ÷4 on the
|
|
206
|
+
quantized layers. `--no-kv-quant` forces bf16; `--kv-bits N` forces
|
|
207
|
+
uniform bits. Long prefills over quantized caches run a fused
|
|
208
|
+
FlashAttention-2 tiling that never materializes the full scores
|
|
209
|
+
matrix (bounded transient; `MLX_BUN_NO_FUSED_SDPA=1` to disable).
|
|
210
|
+
`MLX_BUN_FUSED_DECODE=1` extends the tiling to single-token decode —
|
|
211
|
+
experimental, default off (measured ~4% slower on Gemma @8k kv8;
|
|
212
|
+
kept for model families where it may win).
|
|
213
|
+
- **Memory admission control** — `--memory-budget <GB>` refuses to load
|
|
214
|
+
a model that can't serve within the budget and rejects requests whose
|
|
215
|
+
`prompt + max_tokens` exceed the budget's max safe context with a 400
|
|
216
|
+
(`type: "memory_admission"`) *before* generating. The GPU
|
|
217
|
+
out-of-memory crash it prevents is uncatchable by design — rejection
|
|
218
|
+
up front is the only defense. Ceiling visible at `GET /stats`.
|
|
219
|
+
- **Anthropic Messages API** (`POST /v1/messages`, on by default) —
|
|
220
|
+
point any Anthropic-SDK tool at the server
|
|
221
|
+
(`ANTHROPIC_BASE_URL=http://localhost:8090`); Claude Code runs
|
|
222
|
+
against it as a fully local backend. Streaming event grammar, native
|
|
223
|
+
tool_use/tool_result mapping, image blocks via the vision path.
|
|
224
|
+
- **OpenAI Responses API** (`POST /v1/responses`) — the protocol
|
|
225
|
+
Codex/Cursor/Continue speak; includes `previous_response_id`
|
|
226
|
+
resumption backed by a TTL + byte-capped store (visible in /stats).
|
|
227
|
+
- **`GET /v1/models`**, **`GET /stats`** (cache hit rates, bytes,
|
|
228
|
+
active KV scheme, response store), **`GET/POST/DELETE /v1/adapters`**.
|
|
229
|
+
|
|
230
|
+
## Library
|
|
231
|
+
|
|
232
|
+
The server is one consumer of a library-first API. Not yet published to
|
|
233
|
+
npm — clone the repo and import from `src/`. Full reference:
|
|
234
|
+
[docs/reference/library-api.md](./docs/reference/library-api.md). For shipping inside a
|
|
235
|
+
Mac app (Tauri/Electron sidecar), `./scripts/build-binary.sh` produces
|
|
236
|
+
a relocatable single-binary bundle — recipe incl. signing/notarization
|
|
237
|
+
in [docs/reference/embedding.md](./docs/reference/embedding.md).
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
import { loadModelConfig } from "./src/config";
|
|
241
|
+
import { Weights } from "./src/weights";
|
|
242
|
+
import { Gemma4Model } from "./src/model/gemma4";
|
|
243
|
+
import { generate } from "./src/generate";
|
|
244
|
+
import { loadTokenizer } from "./src/tokenizer";
|
|
245
|
+
import { ChatTemplate } from "./src/chat-template";
|
|
246
|
+
|
|
247
|
+
const dir = "/path/to/hf/snapshot";
|
|
248
|
+
const model = new Gemma4Model(await Weights.open(dir), await loadModelConfig(dir));
|
|
249
|
+
const tok = await loadTokenizer(dir);
|
|
250
|
+
const template = await ChatTemplate.load(dir);
|
|
251
|
+
|
|
252
|
+
const ids = tok.encode(template.render([{ role: "user", content: "Hi!" }]));
|
|
253
|
+
const gen = generate(model, ids, {
|
|
254
|
+
maxTokens: 256,
|
|
255
|
+
temperature: 0.7, topP: 0.95, seed: 42, // reproducible sampling
|
|
256
|
+
kvBits: 8, quantizedKvStart: 4096, // optional: quantized KV past 4k
|
|
257
|
+
});
|
|
258
|
+
for await (const t of gen) process.stdout.write(tok.decode([t.token], true));
|
|
259
|
+
console.log(gen.stats); // prompt/decode tok/s, cached tokens, ...
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
KV caches can be persisted to disk (page-aligned files that reload as
|
|
263
|
+
zero-copy GPU-safe mmaps — see `src/kv-store.ts`) so a standard agent
|
|
264
|
+
preamble prefills once, ever.
|
|
265
|
+
|
|
266
|
+
## Benchmarks
|
|
267
|
+
|
|
268
|
+
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 day, same HF snapshots, preflight-gated
|
|
270
|
+
clean machine, median-of-N with warmups discarded. Full curated table
|
|
271
|
+
(parity / performance / quality) with per-row provenance:
|
|
272
|
+
[benchmarks/RESULTS.md](./benchmarks/RESULTS.md).
|
|
273
|
+
|
|
274
|
+
| | mlx-bun | mlx-lm | optiq |
|
|
275
|
+
|---|---|---|---|
|
|
276
|
+
| **TTFT, served (warm)** | **45–90 ms** | 219–224 ms | 222–331 ms |
|
|
277
|
+
| **server start → ready** | **0.36–0.47 s** | 0.76–0.98 s | 0.79–1.00 s |
|
|
278
|
+
| **decode through HTTP** (e4b / 12B / 26B) | **54.5** / 25.2 / **54.9** | 53.5 / — / 52.2 | 53.5 / **25.5** / † |
|
|
279
|
+
| **server tax vs own direct decode** | **≈ 0%** | −5…−7% | ≈ 0% |
|
|
280
|
+
| **direct decode** (engine only) | −1.9…−4.4% vs mlx-lm | baseline | −0.8…−1.2% |
|
|
281
|
+
| **12B decode @8k context** | 23.3 (23.0 kv-mixed) | **24.4** | 23.2 kv-mixed |
|
|
282
|
+
|
|
283
|
+
Honest negatives, same table: in this matrix our direct decode trailed
|
|
284
|
+
mlx-lm on every model (12B −1.9%, 26B −2.9%, e4b −4.4% at short
|
|
285
|
+
context; the 12B gap grew to −4.5% @8k), and optiq's served 12B edges
|
|
286
|
+
ours by ~1% (25.5 vs 25.2) while paying 3.7× the TTFT. **Post-matrix
|
|
287
|
+
(2026-06-11) the decode gap was root-caused and fixed** — a
|
|
288
|
+
prefill→decode allocator-reclaim stall that mlx-lm clears with
|
|
289
|
+
`mx.clear_cache` and bills to prompt time (we billed it to decode);
|
|
290
|
+
after the reference-faithful fix, same-session paired runs put the 12B
|
|
291
|
+
*ahead* at short context (25.1 vs 24.0) and at parity @8k (23.8 vs
|
|
292
|
+
23.9). Clean-machine re-measure pending; e4b retains a ~5% per-step
|
|
293
|
+
host-overhead residual (see PLAN.md "Decode gap RESOLVED"). Served through HTTP — how agents actually use a local
|
|
294
|
+
model — mlx-bun has the fastest decode on e4b and the 26B, and the
|
|
295
|
+
fastest TTFT and startup everywhere by 2–5×. † = cell failed: optiq
|
|
296
|
+
serve produced no output on the 26B (the Metal OOM crash class from
|
|
297
|
+
python's non-lazy load transient — reproduced in isolation; mlx-bun
|
|
298
|
+
and mlx-lm both served the same model from the same machine state).
|
|
299
|
+
One further optiq cell is blocked on an upstream optiq bug; both are
|
|
300
|
+
documented in the results file.
|
|
301
|
+
|
|
302
|
+
These numbers are the 2026-06-11 cleared-machine re-run with the
|
|
303
|
+
long-context guard active (every @8k row verified at its requested
|
|
304
|
+
context — an earlier harness bug had fed python @8k baselines
|
|
305
|
+
~31-token prompts; fixed, guarded, and re-measured).
|
|
306
|
+
|
|
307
|
+
## Correctness
|
|
308
|
+
|
|
309
|
+
Logit parity with mlx-lm (same weights, Python reference) is the
|
|
310
|
+
project's oracle. The test suite holds the forward pass **bit-exact**
|
|
311
|
+
against it — including every quantized-KV configuration (kv8, kv4, and
|
|
312
|
+
the 26B's mixed per-layer scheme) and the fused quantized-attention
|
|
313
|
+
prefill, which is bit-exact against optiq's reference implementation.
|
|
314
|
+
Every ported helper follows the reference implementation's exact op
|
|
315
|
+
composition, down to constants built at load time (the one latent
|
|
316
|
+
divergence ever found — rope frequencies computed host-side instead of
|
|
317
|
+
on-device — was root-caused and fixed in Phase 10; see PLAN.md
|
|
318
|
+
findings). Golden files are regenerated only by explicit scripts
|
|
319
|
+
(`scripts/regen-*.ts`) running the Python oracle.
|
|
320
|
+
|
|
321
|
+
```sh
|
|
322
|
+
bun test # fast tier runs everywhere; model-loaded tests auto-skip
|
|
323
|
+
# unless the reference snapshot is in your HF cache
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Troubleshooting
|
|
327
|
+
|
|
328
|
+
- **`dlopen` / `libmlxc.dylib` not found** — the MLX runtime
|
|
329
|
+
auto-downloads on first `serve` into `~/Library/Caches/mlx-bun/`; if a
|
|
330
|
+
download was interrupted, just rerun `serve` (it resumes). To point at
|
|
331
|
+
your own copy instead, set `MLX_BUN_LIBMLXC=/path/to/libmlxc.dylib`.
|
|
332
|
+
- **`no models match`** — run `bun src/cli.ts scan` after downloading;
|
|
333
|
+
models must be in the standard HF cache (`~/.cache/huggingface/hub`).
|
|
334
|
+
- **HF download stalls at 0%** — use `bun src/cli.ts get <org/repo>`
|
|
335
|
+
(plain HTTPS, no Xet, resumes where it left off); for the Python CLI,
|
|
336
|
+
`HF_HUB_DISABLE_XET=1` before the download command.
|
|
337
|
+
- **Slow decode on a model near your RAM ceiling** — close memory-heavy
|
|
338
|
+
apps; a model that doesn't fit the wired-memory budget pages weights
|
|
339
|
+
every token (check with `fit`).
|
|
340
|
+
- **Bun version** — requires ≥ 1.3.14 (`bun upgrade`); pinned for
|
|
341
|
+
`Bun.Image` and verified FFI behavior.
|
|
342
|
+
|
|
343
|
+
## Status
|
|
344
|
+
|
|
345
|
+
Pre-alpha, moving fast. See [PLAN.md](./PLAN.md) for phases, exit
|
|
346
|
+
criteria, measured numbers, and the findings log. Complete: load path,
|
|
347
|
+
bit-exact model port (12B dense / e4b per-layer-input / 26B MoE),
|
|
348
|
+
sampling + serving (tools, vision, prompt cache), registry/fit/KV
|
|
349
|
+
persistence, quantized + mixed-precision KV serving (rotating-cache
|
|
350
|
+
KV-quant included, Phase 9) with fused quantized prefill (Phase 10),
|
|
351
|
+
LoRA hot-swap with per-request selection, resumable verified downloads
|
|
352
|
+
(`mlx-bun get`), memory admission control, and the head-to-head
|
|
353
|
+
benchmark harness, and the decode-gap root-cause fix (clear_cache
|
|
354
|
+
placement + boundary accounting, 2026-06-11 — 12B now at-or-above
|
|
355
|
+
mlx-lm decode in paired runs). Open: e4b's ~5% host-overhead decode
|
|
356
|
+
residual (Phase 7), Anthropic messages + Responses API (Phase 11),
|
|
357
|
+
SigLIP vision for e4b/26B, Qwen 3.x, the embeddable single-binary
|
|
358
|
+
build.
|
|
359
|
+
|
|
360
|
+
## License
|
|
361
|
+
|
|
362
|
+
MIT. Third-party attributions: [THIRD_PARTY_LICENSES.md](./THIRD_PARTY_LICENSES.md).
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Third-party licenses
|
|
2
|
+
|
|
3
|
+
mlx-bun is MIT-licensed. It builds on, links against, and ports code
|
|
4
|
+
from the following projects.
|
|
5
|
+
|
|
6
|
+
## Linked libraries
|
|
7
|
+
|
|
8
|
+
| Project | License | Use |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| [MLX](https://github.com/ml-explore/mlx) (Apple) | MIT | All GPU compute — Metal kernels, lazy eval engine (`libmlx`) |
|
|
11
|
+
| [mlx-c](https://github.com/ml-explore/mlx-c) (Apple) | MIT | C API we bind via `bun:ffi` (`libmlxc.dylib`, installed via Homebrew) |
|
|
12
|
+
|
|
13
|
+
## Ported code
|
|
14
|
+
|
|
15
|
+
These files are TypeScript ports of Python sources; the port preserves
|
|
16
|
+
the upstream algorithm and is a derivative work of the original.
|
|
17
|
+
|
|
18
|
+
| Source | License | Ported into |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| [mlx-lm](https://github.com/ml-explore/mlx-lm) (Apple, MIT) — `models/gemma4_text.py`, `models/cache.py`, `models/base.py`, `models/rope_utils.py`, `sample_utils.py`, `tool_parsers/gemma4.py` | MIT | `src/model/gemma4.ts`, `src/sampler.ts`, `src/tool-call.ts` |
|
|
21
|
+
| [mlx-vlm](https://github.com/Blaizzy/mlx-vlm) (BSD-3-Clause) — Gemma-4 unified `VisionEmbedder`, `MultimodalEmbedder`, image preprocessing (as vendored in mlx-optiq) | BSD-3-Clause | `src/vision/embedder.ts`, `src/vision/preprocess.ts` |
|
|
22
|
+
| mlx-optiq (MIT) — vision sidecar wiring, fused quantized SDPA orchestration | MIT | `src/vision/`, quantized-KV paths in `src/model/gemma4.ts` |
|
|
23
|
+
| [Pillow](https://github.com/python-pillow/Pillow) (MIT-CMU / HPND) — `ImagingResample` bicubic convolution algorithm | HPND | `resizeBicubic` in `src/vision/preprocess.ts` (algorithm port) |
|
|
24
|
+
|
|
25
|
+
## Runtime npm dependencies
|
|
26
|
+
|
|
27
|
+
| Package | License | Use |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| `@huggingface/tokenizers` | Apache-2.0 | Pure-JS tokenizer (tokenizer.json) |
|
|
30
|
+
| `@huggingface/jinja` | MIT | Chat-template rendering |
|
|
31
|
+
| `fast-png` | MIT | PNG decode (raw pixels for the vision path) |
|
|
32
|
+
|
|
33
|
+
Run `bun pm licenses` (or inspect `node_modules/*/LICENSE`) for the full
|
|
34
|
+
text of each npm dependency's license, including transitive ones.
|
|
35
|
+
|
|
36
|
+
## Models
|
|
37
|
+
|
|
38
|
+
Model weights (e.g. `mlx-community/gemma-4-12B-it-OptiQ-4bit`) are not
|
|
39
|
+
part of this repository and are governed by their own licenses and use
|
|
40
|
+
policies (e.g. Google's Gemma Terms of Use). Check the model card on
|
|
41
|
+
Hugging Face before redistribution.
|
package/bin/mlx-bun.mjs
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// npm-bin launcher. Plain .mjs so that *any* runtime can parse it and the
|
|
3
|
+
// runtime guard below fires before anything touches bun:ffi / bun:sqlite
|
|
4
|
+
// (a guard inside cli.ts would never run under Node — its static imports
|
|
5
|
+
// hoist and fail on the bun: scheme first).
|
|
6
|
+
|
|
7
|
+
const MIN_BUN = "1.3.14"; // Bun.Image + verified FFI behavior (see README)
|
|
8
|
+
|
|
9
|
+
if (typeof Bun === "undefined") {
|
|
10
|
+
console.error(
|
|
11
|
+
`mlx-bun requires Bun >= ${MIN_BUN} (this is ${process.release?.name ?? "an unknown runtime"} ${process.version}).\n` +
|
|
12
|
+
`Install it: curl -fsSL https://bun.sh/install | bash\n` +
|
|
13
|
+
`Then run: bunx mlx-bun`,
|
|
14
|
+
);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const older = (a, b) => {
|
|
19
|
+
const x = a.split(".").map(Number), y = b.split(".").map(Number);
|
|
20
|
+
for (let i = 0; i < 3; i++) if ((x[i] ?? 0) !== (y[i] ?? 0)) return (x[i] ?? 0) < (y[i] ?? 0);
|
|
21
|
+
return false;
|
|
22
|
+
};
|
|
23
|
+
if (older(Bun.version, MIN_BUN)) {
|
|
24
|
+
console.error(`mlx-bun requires Bun >= ${MIN_BUN} (you have ${Bun.version}). Run: bun upgrade`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
if (process.platform !== "darwin" || process.arch !== "arm64") {
|
|
28
|
+
console.error("mlx-bun runs on Apple Silicon Macs only (MLX is Metal-only by design).");
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
await import("../src/cli.ts");
|