mlx-bun 0.0.11 → 0.0.13
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 +63 -27
- package/THIRD_PARTY_LICENSES.md +9 -0
- package/docs/design/colibri-glm52-port.md +1486 -0
- package/docs/design/pre-colibri-stabilization.md +329 -0
- package/docs/investigations/colibri-oracle-pin.md +1284 -0
- package/docs/investigations/indexshare-performance-spike.md +357 -0
- package/docs/planning/release-notes-v0.0.12.md +80 -0
- package/docs/planning/release-notes-v0.0.13.md +77 -0
- package/docs/reference/cli.md +28 -2
- package/docs/reference/distribution.md +25 -1
- package/docs/reference/embedding.md +5 -2
- package/docs/reference/features-matrix.md +5 -2
- package/docs/reference/library-api.md +41 -13
- package/docs/reference/models.md +49 -2
- package/docs/reference/server-api.md +113 -20
- package/docs/reference/server-config.md +21 -14
- package/docs/reference/training.md +10 -1
- package/package.json +1 -1
- package/src/anthropic.ts +54 -44
- package/src/chat-template.ts +189 -11
- package/src/cli.ts +127 -8
- package/src/config.ts +81 -26
- package/src/diffusion/diffusion-generate.ts +14 -8
- package/src/download.ts +103 -12
- package/src/eval/ifeval.ts +63 -120
- package/src/eval/runner.ts +14 -5
- package/src/eval/tasks/ifeval.ts +167 -31
- package/src/expert-io.ts +391 -0
- package/src/expert-residency.ts +1134 -0
- package/src/expert-usage.ts +415 -0
- package/src/generate.ts +114 -17
- package/src/hf-push.ts +1 -1
- package/src/index.ts +14 -2
- package/src/jobs/runner.ts +37 -18
- package/src/kv-store.ts +147 -8
- package/src/lora.ts +77 -14
- package/src/media-fetch.ts +198 -56
- package/src/mlx/array.ts +43 -0
- package/src/mlx/autograd.ts +51 -14
- package/src/mlx/checkpoint.ts +46 -13
- package/src/mlx/custom-vjp.ts +83 -35
- package/src/model/factory.ts +96 -5
- package/src/model/gemma4-base.ts +32 -0
- package/src/model/glm52-atlas.ts +511 -0
- package/src/model/glm52-cache.ts +706 -0
- package/src/model/glm52-config.ts +262 -0
- package/src/model/glm52-container.ts +305 -0
- package/src/model/glm52-coupling.ts +388 -0
- package/src/model/glm52-dsa.ts +602 -0
- package/src/model/glm52-expert-layout.ts +253 -0
- package/src/model/glm52-layout.ts +152 -0
- package/src/model/glm52-memory.ts +473 -0
- package/src/model/glm52-mla.ts +786 -0
- package/src/model/glm52-moe.ts +223 -0
- package/src/model/glm52-pilot.ts +537 -0
- package/src/model/glm52-reference.ts +445 -0
- package/src/model/glm52-residency.ts +557 -0
- package/src/model/glm52-resident-weights.ts +135 -0
- package/src/model/glm52-streamed-experts.ts +453 -0
- package/src/model/glm52-streamed-metal.ts +610 -0
- package/src/model/glm52-weights.ts +503 -0
- package/src/model/glm52.ts +1153 -0
- package/src/model/support.ts +8 -1
- package/src/native/expert_io.c +523 -0
- package/src/native-pack.ts +6 -4
- package/src/responses.ts +62 -49
- package/src/serve/batch-scheduler.ts +95 -16
- package/src/serve/generation-gateway.ts +41 -16
- package/src/server.ts +471 -93
- package/src/spec/glm52-mtp-source.ts +392 -0
- package/src/spec/serve-loop.ts +99 -18
- package/src/spec/source.ts +31 -6
- package/src/ssd-cache.ts +6 -2
- package/src/tool-call.ts +30 -2
- package/src/train/job.ts +0 -10
- package/src/train/lora-params.ts +17 -6
- package/src/train/trainer.ts +59 -26
- package/src/web/app.html +13 -2
- package/src/web/app.js +30 -3
- package/src/web/src/status.ts +51 -3
- package/src/web-tools.ts +54 -2
- package/src/weights.ts +82 -19
|
@@ -22,7 +22,7 @@ parity). **Tiers:** L1 = bit-exact vs mlx-lm · L2 = bit-exact vs mlx-optiq
|
|
|
22
22
|
| OpenAI Responses shim (`/v1/responses`) | on | both | — | — |
|
|
23
23
|
| Continuous batching (mlx-lm B=N parity) | **on** — cap 8 (default flipped 2026-07-05; a lone request runs the exact serial engine, so the cap engages only under real concurrency; `--batch 1` pins serial) | batch | L1 | `--batch <n>` |
|
|
24
24
|
| Prompt cache (prefix KV reuse) | on, 8 GB | both | — | `--prompt-cache <GB>` (0 = off) |
|
|
25
|
-
| **SSD KV cold tier** (cache survives eviction + restarts) | off | both | — | `--ssd-cache <dir>` (+ `-max`, `-verify`, `-demote-idle <sec>` — default 300s once `--ssd-cache` is on, `0` disables) |
|
|
25
|
+
| **SSD KV cold tier** (cache survives eviction + restarts; v3 includes GLM compressed MLA/DSA state without full-K/V reconstruction) | off | both | — | `--ssd-cache <dir>` (+ `-max`, `-verify`, `-demote-idle <sec>` — default 300s once `--ssd-cache` is on, `0` disables) |
|
|
26
26
|
| **Runtime isolation** (engine runs as a crash-isolated CHILD process behind a reverse-proxy parent; UI stays instant under GPU load and survives engine crashes — [runtime-isolation.md](../design/runtime-isolation.md)) | off | both | — | `--isolate` |
|
|
27
27
|
| **Model pool** (with `--isolate`: LRU-capped RESIDENT model engines; over the cap the least-recently-used engine demotes its cache to the SSD tier and exits, respawning on the next request) | 1 resident engine | both | — | `--model-pool <n>` (requires `--isolate`) |
|
|
28
28
|
| Mixed-precision KV (`kv_config.json`, optiq's scheme) | **off** — opt-in (default flipped to bf16 2026-07-05; quantized KV trades 5–20% decode for memory headroom) | serial + **batch** (per-layer configs batch on every shipped model — full-attention layers Phase 3.1, rotating layers milestone 2; uniform bits stay serial) | L2 | `--kv-quant config\|off\|4\|8`, `--l2` |
|
|
@@ -32,6 +32,7 @@ parity). **Tiers:** L1 = bit-exact vs mlx-lm · L2 = bit-exact vs mlx-optiq
|
|
|
32
32
|
| Fused SDPA (optiq-exact quantized-KV attention) | follows `--kv-quant`: on for `config`, off for uniform/bf16 | serial | L2 | `--fused-sdpa on\|off` |
|
|
33
33
|
| **Paged KV** (vLLM-style block pool: host-side block table + gather before the stock SDPA, no fused kernel in v1 — [paged-kv-cache.md](../design/paged-kv-cache.md)) | off | serial (pins `--batch 1` unless `--batch` given) | gated bit-exact vs the plain `KVCache` (`tests/paged-kv-parity.test.ts`) | `--paged-kv`, `--paged-kv-block-size <n>` (default 256) — v1 scope Gemma4-family bf16; refuses `--batch N>1`, `--kv-quant`, `--draft-model` |
|
|
34
34
|
| **Speculative decoding** (auto-detected drafter: two-model = mlx-lm parity/L1, Gemma `-assistant` = optiq/L2, DSpark local = Lab, DeepSpec released = DeepSpec-reference oracle) | off | serial (forces all-serial) | per-drafter oracle | `--draft-model <path\|query>`, `--draft-kind`, `--num-draft-tokens` |
|
|
35
|
+
| **GLM-5.2 checkpoint-native MTP** (bounded auxiliary expert tier, exact verify) | **on** for GLM-5.2 | serial (`usage.lane: serial+spec`) | Colibri/oracle trajectory + synthetic HTTP gate | `--mtp on\|off` (`off` enables ordinary GLM batching) |
|
|
35
36
|
| Speculative decoding, **model-free prompt lookup** (drafts copied from the request's own context; port of prompt-lookup decoding / vLLM `ngram`; zero weights) | off | serial (forces all-serial) | lossless by verify (gated vs non-spec greedy, tests/spec-ngram.test.ts) | `--draft-kind ngram` (no `--draft-model`), `--ngram-max`, `--ngram-min`, `--num-draft-tokens` (default 10) |
|
|
36
37
|
| Memory admission (refuse what can't fit; never GPU-OOM) | on (RAM × 0.75) | both | — | `--memory-budget <GB>` |
|
|
37
38
|
| Aggregate KV admission for batch rows (queue, don't OOM) | off | batch | — | `--kv-budget <GB>` |
|
|
@@ -48,7 +49,7 @@ parity). **Tiers:** L1 = bit-exact vs mlx-lm · L2 = bit-exact vs mlx-optiq
|
|
|
48
49
|
| `guided_grammar` (EBNF) / `guided_regex`¹ / `guided_choice` / `structured_outputs` | on | both | L2 | request fields |
|
|
49
50
|
| Structured output × speculative decoding | on (when both active) | serial | Lab (validity+equivalence) | — |
|
|
50
51
|
| Quantized KV × speculative decoding (any axis: uniform / config / turbo) | KV scheme wins — drafted requests decode serially WITHOUT speculation (spec lane is bf16-KV-only in v1; startup warning) | serial | — | omit `--kv-quant` to speculate |
|
|
51
|
-
| Tool calling (Gemma sentinel / CPM+Qwen XML) + `role:"tool"` loops | on | both | — | request `tools` |
|
|
52
|
+
| Tool calling (Gemma sentinel / CPM+Qwen XML / GLM `arg_key`+`arg_value`) + `role:"tool"` loops | on | both | — | request `tools` |
|
|
52
53
|
| Vision (`image_url` parts; PNG/JPEG/HEIC/AVIF/WebP/TIFF/GIF/BMP) | on (models with a tower); remote fetches refuse private/loopback/link-local hosts by default (SSRF guard) | serial | L1/L2 | `--allow-private-media` / `MLX_BUN_ALLOW_PRIVATE_MEDIA=1` re-permits LAN hosts |
|
|
53
54
|
| **Audio input** (`input_audio`/`audio`/`audio_url` parts; WAV native, mp3/m4a/flac/ogg/aiff via CoreAudio; ≤30 s / 750 tokens per clip; mixes with images) | on (models with `audio_config` + sidecar tower: e4b); remote fetches refuse private/loopback/link-local hosts by default (SSRF guard) | serial³ | L2 (greedy stream EXACT vs the optiq internal model) | `--allow-private-media` / `MLX_BUN_ALLOW_PRIVATE_MEDIA=1` re-permits LAN hosts |
|
|
54
55
|
| LoRA adapters (mount at start / hot-swap at runtime) | off | serial | — | `--adapter <dir>`, `POST /v1/adapters` |
|
|
@@ -68,6 +69,8 @@ parity). **Tiers:** L1 = bit-exact vs mlx-lm · L2 = bit-exact vs mlx-optiq
|
|
|
68
69
|
| MiniCPM5 (cpm5) | ✅ L1/L2 | ✅ | the starter model |
|
|
69
70
|
| Gemma 4 (1B/e4b/12B/26B, + vision e4b/12B, + audio e4b) | ✅ L1/L2 | ✅ | sliding+full interleaved; MoE 26B |
|
|
70
71
|
| Qwen3.5 (gated-DeltaNet hybrid) | ✅ L1/L2 | ✅ (SSM path) | `MLX_BUN_BATCH_SSM=0` reverts |
|
|
72
|
+
| Qwen3.8-27B (same qwen3_5 graph) | ✅ L1 (no L2 oracle) | ✅ (SSM path) | thinking depths + preserve_thinking; MTP/vision/video in bring-up |
|
|
73
|
+
| GLM-5.2 / Colibri (pre-release port gate) | ✅ chat/text, Messages, Responses, SSE, tools, grammar, logprobs, library/CLI | ✅ compressed MLA/DSA scheduler | native MTP defaults to serial+spec; `--mtp off` enables ordinary batching; artifact-aware CLI/API/status `fit` and live main/MTP expert-tier telemetry; embeddings, vision/audio, adapters, and training are explicitly unsupported; per-row batched MTP is post-release |
|
|
71
74
|
| DiffusionGemma-26B (non-autoregressive) | ✅ (own engine) | — serial always | first bit-exact non-AR port |
|
|
72
75
|
| Tier-0 universal (llama/qwen2/qwen3/olmo2/…, 11 archs) | ✅ L1 | ✅ plain full-attention archs² | gemma2-family/sliding universal → serial |
|
|
73
76
|
|
|
@@ -16,9 +16,7 @@ surface); direct library callers must do the same.
|
|
|
16
16
|
```ts
|
|
17
17
|
import {
|
|
18
18
|
ensureNativeRuntime,
|
|
19
|
-
|
|
20
|
-
Weights,
|
|
21
|
-
createModel, // dispatches to Gemma4Model / MiniCPM5Model / Qwen35Model
|
|
19
|
+
openModel, // artifact-aware dispatch, including bounded GLM-5.2
|
|
22
20
|
loadTokenizer,
|
|
23
21
|
ChatTemplate,
|
|
24
22
|
generate,
|
|
@@ -26,13 +24,15 @@ import {
|
|
|
26
24
|
|
|
27
25
|
await ensureNativeRuntime();
|
|
28
26
|
const dir = "/path/to/hf-snapshot"; // mlx-bun ls prints these
|
|
29
|
-
const
|
|
30
|
-
|
|
27
|
+
const model = await openModel(dir, {
|
|
28
|
+
// Used only by the direct Colibri GLM-5.2 artifact; other models ignore it.
|
|
29
|
+
contextTokens: 4096, maxGenerationTokens: 256, enableMtp: false,
|
|
30
|
+
});
|
|
31
31
|
const tok = await loadTokenizer(dir);
|
|
32
32
|
const template = await ChatTemplate.load(dir);
|
|
33
33
|
// Gemma4Model and MiniCPM5Model can still be imported directly from "mlx-bun"
|
|
34
34
|
// if you need the concrete type. Qwen35Model is NOT exported from the public
|
|
35
|
-
// package — use
|
|
35
|
+
// package — use openModel/RuntimeModel (preferred artifact-aware dispatch), or import from
|
|
36
36
|
// "./src/model/qwen3_5" in-repo.
|
|
37
37
|
|
|
38
38
|
const ids = tok.encode(template.render([{ role: "user", content: "hi" }]));
|
|
@@ -45,13 +45,15 @@ console.log(tok.decode(out, true));
|
|
|
45
45
|
console.log(gen.stats); // set once iteration ends
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
Ordinary safetensors loading is lazy (mmap + mlx native loader). GLM-5.2 first
|
|
49
|
+
runs its header-only exact process equation, then opens the bounded Colibri
|
|
50
|
+
resident/expert tiers; impossible plans fail before either tier is committed.
|
|
50
51
|
|
|
51
52
|
> **First run:** library consumers bypass the CLI's first-run step, so
|
|
52
53
|
> call `await ensureNativeRuntime()` once before constructing a model on
|
|
53
54
|
> a machine that may not have the MLX native runtime yet — it downloads
|
|
54
|
-
> the sha256-verified native pack
|
|
55
|
+
> the sha256-verified native pack (MLX plus the direct-Colibri expert-I/O
|
|
56
|
+
> helper) to `~/Library/Caches/mlx-bun/` and is
|
|
55
57
|
> a no-op when the runtime is already present (beside the executable,
|
|
56
58
|
> already cached, or installed via homebrew). `nativeRuntimeDir()`
|
|
57
59
|
> returns the resolved directory, or `null` on a fresh machine.
|
|
@@ -109,6 +111,24 @@ budget (pre-GPU, mmap-only check). The server exposes OpenAI chat
|
|
|
109
111
|
completions, Anthropic `/v1/messages`, OpenAI Responses, embeddings,
|
|
110
112
|
adapters, and `/stats` — [server-api.md](./server-api.md).
|
|
111
113
|
|
|
114
|
+
For GLM-5.2, pass its load-only resource controls under `glm`:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
const ctx = await loadContext(dir, "glm-5.2", {
|
|
118
|
+
glm: {
|
|
119
|
+
contextTokens: 4096,
|
|
120
|
+
maxGenerationTokens: 128,
|
|
121
|
+
batchSize: 8,
|
|
122
|
+
enableMtp: true, // default; native MTP routes requests serial+spec
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`openModel()` and `loadContext()` both select the streamed Colibri runtime;
|
|
128
|
+
callers do not use `Weights.open()` or `createModel()` for this artifact.
|
|
129
|
+
Direct `generate()` supports the full sampling/grammar/logprobs contract but
|
|
130
|
+
does not itself mount a drafter; native MTP is a serving-context feature.
|
|
131
|
+
|
|
112
132
|
## Text embeddings
|
|
113
133
|
|
|
114
134
|
```ts
|
|
@@ -154,16 +174,24 @@ under one adapter must not seed another's prefill).
|
|
|
154
174
|
```ts
|
|
155
175
|
import { saveKvCache, loadKvCache } from "./src/kv-store";
|
|
156
176
|
saveKvCache("/tmp/prefix.kv", tokens, caches); // page-aligned file
|
|
157
|
-
const { tokens, caches
|
|
158
|
-
|
|
159
|
-
|
|
177
|
+
const { tokens, caches } = loadKvCache("/tmp/prefix.kv", model, {
|
|
178
|
+
modelId, configFingerprint, tokenizerHash, verify: true,
|
|
179
|
+
});
|
|
180
|
+
// Restore streams one tensor at a time from a read-only mmap into MLX-owned
|
|
181
|
+
// storage, drops each clean file-page range, and unmaps before returning.
|
|
160
182
|
// Note: loadKvCache accepts anything with makeCache(): Cache[] — it validates
|
|
161
183
|
// the entry count against the cache list makeCache() returns (not
|
|
162
184
|
// model.layers.length, which is wrong for KV-shared models like e4b, whose
|
|
163
185
|
// makeCache() returns donors only). RuntimeModel qualifies directly, no cast.
|
|
164
186
|
```
|
|
165
187
|
|
|
166
|
-
|
|
188
|
+
Format v3 supports plain, rotating, quantized, TurboQuant, SSM, and GLM-5.2
|
|
189
|
+
checkpoint-native caches. GLM rows use `mla`, `mla-dsa`, and `mtp-mla` kinds and
|
|
190
|
+
store only compressed latent/RoPE/DSA state—never reconstructed full K/V.
|
|
191
|
+
`Glm52NativeMtpSource.clonePersistentCache()` returns an owned MTP snapshot;
|
|
192
|
+
`restorePersistentCache()` adopts a validated restored `mtp-mla` row. Writes
|
|
193
|
+
are page-aligned, hashed, atomic (`.tmp` + fsync + rename), and available in
|
|
194
|
+
both synchronous and event-loop-yielding `saveKvCacheAsync` forms.
|
|
167
195
|
|
|
168
196
|
## Registry + fit (model discovery and memory math)
|
|
169
197
|
|
package/docs/reference/models.md
CHANGED
|
@@ -35,6 +35,11 @@ mlx-bun get <substring> # no "/" = registry query, re-gets the match
|
|
|
35
35
|
- Plain HTTPS resolve/CDN (no Xet), sequential files, `Range`-resume of
|
|
36
36
|
partial blobs (`<blob>.incomplete`). Auth: `HF_TOKEN` env, then the
|
|
37
37
|
token `hf auth login` writes. 401/403 answers get a "gated repo" hint.
|
|
38
|
+
- Before the first payload request, mlx-bun totals the bytes still absent,
|
|
39
|
+
credits complete shared blobs and valid `.incomplete` prefixes, and checks
|
|
40
|
+
the target volume for that remainder plus a fixed 1 GiB safety reserve. An
|
|
41
|
+
impossible acquisition fails immediately with required/available GiB and a
|
|
42
|
+
rerun/resume instruction rather than filling the disk halfway through.
|
|
38
43
|
- A substring argument (no `/`) resolves against already-downloaded
|
|
39
44
|
repos — `mlx-bun get 12B` refreshes the 12B to upstream's latest. An
|
|
40
45
|
unknown substring errors with a pointer to `mlx-bun ls <q>`.
|
|
@@ -51,14 +56,56 @@ mlx-bun get <substring> # no "/" = registry query, re-gets the match
|
|
|
51
56
|
Every blob is checksummed **while streaming** (a resume re-hashes the
|
|
52
57
|
existing prefix in chunks — no whole-file allocation):
|
|
53
58
|
|
|
54
|
-
- **LFS files** (weights): sha256 must equal the API's `lfs.
|
|
55
|
-
is also
|
|
59
|
+
- **LFS files** (weights): sha256 must equal the API's current `lfs.sha256`
|
|
60
|
+
field (the older `lfs.oid` spelling is also accepted) — which is also the
|
|
61
|
+
blob's filename.
|
|
56
62
|
- **Small files** (configs, tokenizer): git blob identity,
|
|
57
63
|
`sha1("blob <size>\0" + content)`, must equal the API's `blobId`.
|
|
58
64
|
|
|
59
65
|
A mismatch deletes the partial (never resume corrupt bytes); a short
|
|
60
66
|
read keeps the `.incomplete` for resume.
|
|
61
67
|
|
|
68
|
+
## GLM-5.2 on a 32 GB Mac
|
|
69
|
+
|
|
70
|
+
The validated direct-container artifact is
|
|
71
|
+
`mateogrgic/GLM-5.2-colibri-int4-with-int8-mtp` at revision
|
|
72
|
+
`3cc8db99b1b13fc79325d987ba3c1c430766b3b8`. It is approximately 384 GB
|
|
73
|
+
decimal / 357 GiB on disk, so choose the Hugging Face cache volume before the
|
|
74
|
+
download. Fast internal NVMe is strongly preferred; an external volume also
|
|
75
|
+
needs to sustain the expert-streaming workload after acquisition.
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
# Optional: choose a large fast volume before downloading.
|
|
79
|
+
export HF_HUB_CACHE=/Volumes/FastSSD/huggingface/hub
|
|
80
|
+
|
|
81
|
+
mlx-bun get mateogrgic/GLM-5.2-colibri-int4-with-int8-mtp \
|
|
82
|
+
--revision 3cc8db99b1b13fc79325d987ba3c1c430766b3b8
|
|
83
|
+
mlx-bun scan
|
|
84
|
+
mlx-bun serve GLM-5.2-colibri \
|
|
85
|
+
--memory-budget 26.8435456 \
|
|
86
|
+
--context-length 4096 \
|
|
87
|
+
--max-tokens 128 \
|
|
88
|
+
--mtp on
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`26.8435456` decimal GB is exactly 25 GiB. The validated plan reserves about
|
|
92
|
+
19.9 GiB for the process and leaves the rest of the 32 GiB machine to macOS.
|
|
93
|
+
Startup runs the exact header-only resource equation before opening resident
|
|
94
|
+
weights or expert slabs. The server reports the resulting plan at
|
|
95
|
+
`GET /stats` under `glm52`.
|
|
96
|
+
|
|
97
|
+
If acquisition is interrupted, rerun the identical `get` command: mlx-bun
|
|
98
|
+
rehashes each existing prefix in bounded chunks, sends a Range request for the
|
|
99
|
+
remainder, and verifies the complete blob before publishing it. If the disk
|
|
100
|
+
preflight refuses, free space or point `HF_HUB_CACHE` at another volume and
|
|
101
|
+
rerun. No Python environment and no converted copy inside the mlx-bun checkout
|
|
102
|
+
are involved.
|
|
103
|
+
|
|
104
|
+
The artifact is an external MIT-licensed derivative of
|
|
105
|
+
`zai-org/GLM-5.2-FP8`, converted with Colibri and extended with int8 MTP
|
|
106
|
+
weights. Model weights are not bundled with mlx-bun; review the artifact model
|
|
107
|
+
card before redistribution.
|
|
108
|
+
|
|
62
109
|
## scan + the registry
|
|
63
110
|
|
|
64
111
|
`mlx-bun scan` walks `models--*/snapshots/*` reading only `config.json`
|
|
@@ -50,14 +50,25 @@ Request body (OpenAI chat schema; unknown fields ignored):
|
|
|
50
50
|
"tools": [ /* OpenAI function tools */ ],
|
|
51
51
|
"tool_choice": "auto", // "none" disables tools
|
|
52
52
|
"chat_template_kwargs": { // forwarded to the chat template
|
|
53
|
-
"enable_thinking": false
|
|
53
|
+
"enable_thinking": false, // MiniCPM5 / Qwen3.5/3.8 / GLM-5.2: <think>
|
|
54
|
+
// channel on/off (Qwen3.8 default: ON)
|
|
55
|
+
"preserve_thinking": true // Qwen3.8: keep think blocks from earlier
|
|
56
|
+
// assistant turns in the rendered prompt
|
|
57
|
+
// (template default true — better prompt-
|
|
58
|
+
// cache reuse in agent loops). Ignored by
|
|
59
|
+
// templates that don't read it.
|
|
54
60
|
},
|
|
55
|
-
"reasoning_effort": "medium", // "none"|"minimal"|"low"|"medium"|"high"
|
|
56
|
-
// gates enable_thinking on Qwen3.5/
|
|
57
|
-
// "none" → off, any other level →
|
|
58
|
-
// consulted when
|
|
59
|
-
// .enable_thinking is not explicitly
|
|
60
|
-
// an explicit enable_thinking always
|
|
61
|
+
"reasoning_effort": "medium", // "none"|"minimal"|"low"|"medium"|"high"|"xhigh"
|
|
62
|
+
// gates enable_thinking on Qwen3.5/3.8/
|
|
63
|
+
// MiniCPM5: "none" → off, any other level →
|
|
64
|
+
// on. Only consulted when chat_template_
|
|
65
|
+
// kwargs.enable_thinking is not explicitly
|
|
66
|
+
// set — an explicit enable_thinking always
|
|
67
|
+
// wins. On templates with a reasoning-depth
|
|
68
|
+
// variable (Qwen3.8) the level also maps
|
|
69
|
+
// into the template: minimal/low → low,
|
|
70
|
+
// medium → medium, high/xhigh → xhigh
|
|
71
|
+
// (Qwen3.8's default depth is xhigh).
|
|
61
72
|
"hlg": { // HLG tone-curve sampling (per request).
|
|
62
73
|
"enabled": true, // merged over --hlg-sampling server defaults.
|
|
63
74
|
"width": 4, // logit-width of the tone plateau
|
|
@@ -78,6 +89,11 @@ Sampling defaults follow the model author's `generation_config.json`
|
|
|
78
89
|
when a field is omitted (optiq serve's gen_config behavior); explicit
|
|
79
90
|
request values always win. MiniCPM5 defaults to the no-think direct
|
|
80
91
|
answer mode unless `chat_template_kwargs.enable_thinking` is `true`.
|
|
92
|
+
GLM-5.2 also defaults to no-think: its generation primer is
|
|
93
|
+
`<think></think>`. Enabling thinking renders `Reasoning Effort: Max` in the
|
|
94
|
+
system turn and leaves an open `<think>` primer for generation. The same
|
|
95
|
+
precedence applies through Chat Completions, Anthropic Messages, and Responses
|
|
96
|
+
because all three normalize into this one chat request path.
|
|
81
97
|
|
|
82
98
|
`stop` sequences are matched on **decoded text**, not token ids, so a
|
|
83
99
|
sequence that spans token boundaries still fires. Generation halts at
|
|
@@ -303,13 +319,16 @@ with `finish_reason: "stop"`. Works on both lanes — serial and
|
|
|
303
319
|
`--batch N` (per-row matchers).
|
|
304
320
|
|
|
305
321
|
Degrade path — a grammar that fails to compile is never rejected (no
|
|
306
|
-
400/500): chat prepends a system
|
|
307
|
-
|
|
308
|
-
`Warning` header (`grammar not enforced: …`);
|
|
309
|
-
chat template to inject into, so it emits the
|
|
310
|
-
`
|
|
311
|
-
|
|
312
|
-
|
|
322
|
+
400/500): chat prepends a string-valued system instruction for the
|
|
323
|
+
requested JSON schema, raw grammar, regex, or choice constraint, and the
|
|
324
|
+
response carries a `Warning` header (`grammar not enforced: …`);
|
|
325
|
+
`/v1/completions` has no chat template to inject into, so it emits the
|
|
326
|
+
`Warning` header only.
|
|
327
|
+
`MLX_BUN_GRAMMAR=0` disables mask compilation but does not silently
|
|
328
|
+
ignore a requested constraint: it uses this same degrade path (chat
|
|
329
|
+
prompt injection plus `Warning`; raw completions `Warning` only).
|
|
330
|
+
Design and fidelity notes:
|
|
331
|
+
[structured-output.md](../design/structured-output.md).
|
|
313
332
|
|
|
314
333
|
### Errors
|
|
315
334
|
|
|
@@ -325,11 +344,12 @@ All errors are `{ "error": { "message": …, ... } }`.
|
|
|
325
344
|
`top_logprobs must be of type int` / `at least 0` / `at most 11`;
|
|
326
345
|
`top_logprobs: -1` is the accepted "unset" sentinel).
|
|
327
346
|
- `400` with `"type": "memory_admission"`, `"code":
|
|
328
|
-
"context_over_budget"` —
|
|
329
|
-
|
|
330
|
-
the
|
|
331
|
-
|
|
332
|
-
|
|
347
|
+
"context_over_budget"` — the request cannot fit the memory budget's max
|
|
348
|
+
safe context. Generic budgeted serving rejects when `prompt + max_tokens`
|
|
349
|
+
exceeds the ceiling. GLM-5.2 instead caps a broad `max_tokens` upper bound
|
|
350
|
+
to the space remaining in its fixed planned context and returns this error
|
|
351
|
+
only when the prompt itself leaves no generation slot. The ceiling is visible
|
|
352
|
+
at `/stats`.
|
|
333
353
|
|
|
334
354
|
## POST /v1/completions (raw text completion)
|
|
335
355
|
|
|
@@ -486,6 +506,23 @@ The served model is FIRST, with extra capability fields:
|
|
|
486
506
|
// lazily loadable)
|
|
487
507
|
"audio": false, // input_audio content parts accepted
|
|
488
508
|
// (tower loaded or lazily loadable)
|
|
509
|
+
"batch_mode": "batch", // loaded cache capability; per-request
|
|
510
|
+
// usage.lane is authoritative
|
|
511
|
+
"tools": true,
|
|
512
|
+
"structured_output": true,
|
|
513
|
+
"embeddings": false,
|
|
514
|
+
"adapters": false,
|
|
515
|
+
"training": false,
|
|
516
|
+
"dsa": true, // GLM-5.2 only; false elsewhere
|
|
517
|
+
"mtp": true, // checkpoint-native GLM MTP is mounted
|
|
518
|
+
"capabilities": { // explicit discoverable surface
|
|
519
|
+
"chat_completions": true, "text_completions": true,
|
|
520
|
+
"anthropic_messages": true, "responses": true,
|
|
521
|
+
"streaming": true, "tools": true, "structured_output": true,
|
|
522
|
+
"logprobs": true, "embeddings": false,
|
|
523
|
+
"vision": false, "audio": false,
|
|
524
|
+
"adapters": false, "training": false
|
|
525
|
+
},
|
|
489
526
|
"gen_defaults": { // model-author sampling defaults
|
|
490
527
|
"temperature": 0.7, // (generation_config.json, with any
|
|
491
528
|
"top_p": 0.95, // --temperature/--top-p/--top-k server
|
|
@@ -518,6 +555,13 @@ matching `mlx_lm.server`.
|
|
|
518
555
|
`input_audio` content parts (tower loaded or lazily loadable — see
|
|
519
556
|
*Audio input* above for what audio requires), so clients can discover
|
|
520
557
|
capabilities instead of probing for a `400`.
|
|
558
|
+
The served-model row also includes `batch_mode: "off" | "serial" | "batch"`:
|
|
559
|
+
the configured cap and the loaded model's cache capability together. Per-turn
|
|
560
|
+
`usage.lane` remains authoritative for a particular request; for example,
|
|
561
|
+
native-MTP requests route serial even when the served GLM model reports
|
|
562
|
+
`batch_mode: "batch"` for ordinary requests.
|
|
563
|
+
For GLM-5.2, the non-generative fields are deliberately false: embeddings,
|
|
564
|
+
vision/audio, adapters, and training are not emulated by the serving port.
|
|
521
565
|
|
|
522
566
|
## GET /health
|
|
523
567
|
|
|
@@ -544,9 +588,40 @@ capabilities instead of probing for a `400`.
|
|
|
544
588
|
"usable_bytes": 0,
|
|
545
589
|
"weights_bytes": 0
|
|
546
590
|
},
|
|
591
|
+
// present only for the direct Colibri GLM-5.2 runtime:
|
|
592
|
+
"glm52": {
|
|
593
|
+
"preset": "g5-32gb-quality",
|
|
594
|
+
"planned_process_bytes": 0,
|
|
595
|
+
"process_limit_bytes": 0,
|
|
596
|
+
"context_tokens": 4096,
|
|
597
|
+
"max_generation_tokens": 128,
|
|
598
|
+
"batch_size": 1,
|
|
599
|
+
"dsa": true,
|
|
600
|
+
"mtp": true,
|
|
601
|
+
"mtp_draft_tokens": 3,
|
|
602
|
+
"resident_weight_bytes": 0,
|
|
603
|
+
"main_expert_slab_bytes": 0,
|
|
604
|
+
"mtp_expert_slab_bytes": 0,
|
|
605
|
+
"expert_runtime": {
|
|
606
|
+
"main_residency": { "resident": 0, "pinned": 0, "working": 0,
|
|
607
|
+
"hits": 0, "misses": 0, "evictions": 0 },
|
|
608
|
+
"mtp_residency": { "resident": 0, "pinned": 0, "working": 0,
|
|
609
|
+
"hits": 0, "misses": 0, "evictions": 0 },
|
|
610
|
+
"last_turn": {
|
|
611
|
+
"main": { "demand": { "hits": 0, "misses": 0,
|
|
612
|
+
"readBytes": 0 } },
|
|
613
|
+
"mtp": { "demand": { "hits": 0, "misses": 0,
|
|
614
|
+
"readBytes": 0 } },
|
|
615
|
+
"pilot": null
|
|
616
|
+
},
|
|
617
|
+
"last_repin": []
|
|
618
|
+
}
|
|
619
|
+
},
|
|
547
620
|
"batch": {
|
|
548
621
|
"configured": 1, // the --batch N value
|
|
549
|
-
"
|
|
622
|
+
"mode": "off", // "off" | "serial" | "batch": configured
|
|
623
|
+
// and model-cache capability together
|
|
624
|
+
"batched": false, // true only when mode == "batch"
|
|
550
625
|
"active_rows": 0, // rows currently decoding in the batch
|
|
551
626
|
"pending_rows": 0, // queued + mid-prefill rows waiting
|
|
552
627
|
"submitted_rows": 0, // cumulative rows routed to the batch lane since start
|
|
@@ -955,6 +1030,15 @@ banner and `mlx-bun fit`. When the eval DB has a real measured decode
|
|
|
955
1030
|
rate for this model snapshot, it is included and takes precedence over
|
|
956
1031
|
the prediction.
|
|
957
1032
|
|
|
1033
|
+
For the direct Colibri GLM-5.2 runtime this endpoint switches to the exact
|
|
1034
|
+
serve-time memory plan. The response separates the full streamed artifact on
|
|
1035
|
+
disk from resident weights, main/MTP expert slabs, KV, and other reserves.
|
|
1036
|
+
`measured_decode_tps` is the G5 warm quality-preserving measurement;
|
|
1037
|
+
`glm52.direct_oracle_warm_decode_tps` is the same-machine direct Colibri
|
|
1038
|
+
control, while `glm52.aspirational_decode_tps` is a target, not a release
|
|
1039
|
+
gate. The single-row SKU result describes the current machine because the
|
|
1040
|
+
generic all-resident SKU estimator is inapplicable.
|
|
1041
|
+
|
|
958
1042
|
```jsonc
|
|
959
1043
|
{
|
|
960
1044
|
"machine": { "chip": "M4 Pro", "ram_bytes": 0, "bandwidth_gbs": 0.0 },
|
|
@@ -973,6 +1057,15 @@ the prediction.
|
|
|
973
1057
|
"max_safe_context": 8192,
|
|
974
1058
|
"predicted_decode_tps": 0.0
|
|
975
1059
|
},
|
|
1060
|
+
// GLM-5.2 only:
|
|
1061
|
+
"glm52": {
|
|
1062
|
+
"artifact_disk_bytes": 0, // exact registry size; null if unavailable
|
|
1063
|
+
"main_expert_slab_bytes": 0,
|
|
1064
|
+
"mtp_expert_slab_bytes": 0,
|
|
1065
|
+
"max_generation_tokens": 128,
|
|
1066
|
+
"direct_oracle_warm_decode_tps": 0.27,
|
|
1067
|
+
"aspirational_decode_tps": 2.0
|
|
1068
|
+
},
|
|
976
1069
|
"sku_matrix_ctx": 32768,
|
|
977
1070
|
"sku_matrix": [{
|
|
978
1071
|
"sku": "M4 Pro 24 GB", "ram_gb": 24,
|
|
@@ -21,7 +21,7 @@ the design rationale is in
|
|
|
21
21
|
| --- | --- | --- | --- |
|
|
22
22
|
| `--host` | addr | `127.0.0.1` | Interface to bind. Loopback-only by default (mlx_lm.server parity); pass `--host 0.0.0.0` to expose the server on your network. |
|
|
23
23
|
| `--port` | n | `8080` | Listen port. A pre-flight probe refuses to start if the port is already serving. |
|
|
24
|
-
| `--memory-budget` | GB | machine RAM × 0.75 | Admission ceiling.
|
|
24
|
+
| `--memory-budget` | GB | machine RAM × 0.75; GLM-5.2: min(25 GiB, physical RAM) | Admission ceiling. Generic budgeted serving rejects requests whose `prompt + max_tokens` exceed the safe context with **400** (`type: memory_admission`) instead of risking an uncatchable GPU OOM. GLM-5.2 runs its exact process equation before opening resident weights or expert slabs and caps MLX to the plan's allocator reserve; because its context is fixed up front, a broad client `max_tokens` upper bound is capped to the remaining planned context, while a prompt that leaves no generation slot is still rejected. **Decimal GB (×10⁹).** |
|
|
25
25
|
| `--prompt-cache` | GB | `8` | RAM prefix-KV cache (byte-capped LRU; `0` disables). **PREFIX SHARING (2026-07-05): serves are NON-CONSUMING** — a hit hands out zero-copy clones and leaves the donor entry intact, so N agents (or a brand-new session) sharing a system prompt all reuse ONE prefill without destroying each other's conversation entries (the old consume-and-trim take cannibalized the donor). Extended entries supersede their prefix-ancestors (when trimmable), so a conversation stays one entry. Works on BOTH lanes and tiers over the SSD store (Layer 0). |
|
|
26
26
|
| `--isolate` | (bool) | off | Runtime isolation ([runtime-isolation.md](../design/runtime-isolation.md)): the inference engine runs as a CHILD process on a unix socket; this process stays a pure UI/API reverse proxy — instant under any GPU load, survives engine crashes (auto-respawn, in-flight requests get a clean 502). Measured cost ≈0 (−0.4% tok/s, +2 ms TTFT, per-token SSE granularity preserved). `/ws/chat` is not proxied yet (501). `/engine` reports child pid/restarts. |
|
|
27
27
|
| `--model-pool` | n | `1` | With `--isolate`: max RESIDENT model engines. Requests naming another model (**exact `/v1/models` id** — fuzzy strings keep mlx-lm's ignored-field semantics) spawn/route to that model's own engine child (SPAWN-OVERLAP: the new model loads while the old one keeps serving; nobody's stream is interrupted). Over the cap, the LRU engine drains, **demotes its prompt cache to the SSD tier**, and exits — switching back respawns it with state restored from disk. Measured (M1 Max, cpm5⇄qwen0.8b, pool 1): switch 1.5 s, switch-back 1.2 s with `cached_tokens` 103/104 — the conversation survived the eviction. The pool drives this eviction via `POST /admin/drain` — unix-socket-only (never exposed on the public TCP listener) — which quiesces the gateway and demotes the whole prompt cache to the SSD tier before eviction, responding `{ drained: true, demotions: N }`. |
|
|
@@ -38,11 +38,13 @@ the design rationale is in
|
|
|
38
38
|
| `--draft-kind` | `two-model`\|`assistant`\|`dspark`\|`deepspec`\|`ngram` | auto | Override the draft-artifact kind detection (`dspark.json` → dspark; `Gemma4DSparkModel` architecture stamp → deepspec; `*_assistant` config → assistant; else two-model). **`ngram` is never auto-detected — it has no artifact**: MODEL-FREE prompt lookup (drafts copied from the request's own prompt+generation when its trailing k-gram occurred earlier in the stream; port of prompt-lookup decoding / vLLM's `ngram` proposer). Mount it ALONE — `--draft-kind ngram` with a `--draft-model` is refused, as is any other kind without one. Zero weights/memory, lossless by the same verify (gated: serve-loop output token-identical to non-spec greedy on e4b, tests/spec-ngram.test.ts); a no-match round degrades to one plain target step. Best on agentic/RAG/code-edit traffic that re-emits context spans. |
|
|
39
39
|
| `--num-draft-tokens` | n | `3` (`ngram`: `10`) | Drafts per verify round (mlx_lm.server's default; `mlx_lm.generate`'s is 2). A DSpark draft pins this to its trained block width (`cfg.gamma`); ngram drafting is free so its default is the reference implementation's 10. |
|
|
40
40
|
| `--ngram-max` / `--ngram-min` | k | `3` / `1` | `--draft-kind ngram` only: longest/shortest trailing k-gram searched for a match (the reference values — longest first, first occurrence wins). |
|
|
41
|
-
| `--
|
|
41
|
+
| `--mtp` | `on`\|`off` | on for GLM-5.2 | Mount the checkpoint-native GLM MTP row as the server's drafter. It uses the bounded auxiliary expert tier and exact serial verify loop (`usage.lane: "serial+spec"`). `off` removes the draft so ordinary GLM requests can use continuous batching; per-row batched MTP is post-release. Mutually exclusive with an explicit `--draft-model`/`--draft-kind`. |
|
|
42
|
+
| `--context-length` | tokens | `4096` for GLM-5.2 | Context reserved by GLM's header-only resource equation. The same value becomes the request-admission ceiling and is reported in `/stats.glm52`; an impossible plan fails before committing model memory. |
|
|
43
|
+
| `--thinking` | `true`\|`false` | model's own (false for CPM and GLM-5.2) | Server-wide default for the chat template's `enable_thinking` (MiniCPM5/CPM, Qwen3.5, and GLM-5.2). Full precedence: an explicit request `chat_template_kwargs.enable_thinking` always wins; else `reasoning_effort` (`"none"` → off, any other level → on) if the request sent one; else this server default; else the model's own default. GLM renders disabled thinking as `<think></think>` and enabled thinking as an open `<think>` generation primer plus `Reasoning Effort: Max`. |
|
|
42
44
|
| `--temperature` | n ∈ [0,5] | `generation_config.json` | Server-wide sampling default. Per-request `temperature` still wins; the browser chat (sends none) inherits this. `--temp` is accepted as an alias (mlx_lm.server compat); explicit `--temperature` wins if both are given. **Migration note:** mlx_lm.server's `--temp` *default* is `0.0` (unset-temperature requests are greedy there); mlx-bun falls back to the model's `generation_config.json`, then `0.7` — pass `--temp 0` for mlx-lm's behavior. |
|
|
43
45
|
| `--top-p` | n ∈ [0,1] | `generation_config.json` | Server-wide top-p default (per-request `top_p` wins). |
|
|
44
46
|
| `--top-k` | n ∈ [0,1e6] | `generation_config.json` | Server-wide top-k default (per-request `top_k` wins). |
|
|
45
|
-
| `--max-tokens` | n | `65536` chat / `512` raw completion | Completion cap when a request omits `max_tokens` (mlx_lm.server flag; its default there is 512 — `--max-tokens 512` reproduces mlx_lm.server exactly). |
|
|
47
|
+
| `--max-tokens` | n | GLM-5.2: `128`; otherwise `65536` chat / `512` raw completion | Completion cap when a request omits `max_tokens` (mlx_lm.server flag; its default there is 512 — `--max-tokens 512` reproduces mlx_lm.server exactly). For GLM this is also reserved by the pre-open resource equation and must fit inside `--context-length`. |
|
|
46
48
|
| `--allow-private-media` | (bool) | off | Let `image_url`/`audio_url` content parts fetch from **private/loopback/link-local** hosts (a NAS, another LAN box). Off by default: a request's URL is attacker-controlled input, so remote media fetches (`src/media-fetch.ts`) refuse non-http(s) schemes and destinations in private/loopback/link-local/CGNAT ranges — including hosts that *resolve* there and every **redirect hop** (each hop re-passes the whole policy) — which keeps the server from being steered at cloud metadata (`169.254.169.254`) or LAN-internal services (SSRF). Independent of the flag, every remote fetch has a **10 s wall-clock timeout** and a **64 MB response cap** (streaming-enforced, not just Content-Length); violations surface as clean `400`s (`prompt build failed: …`). `data:` URLs decode locally and are never policy-checked. |
|
|
47
49
|
| `--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). |
|
|
48
50
|
| `--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). |
|
|
@@ -112,7 +114,8 @@ for paired A/B harnesses).
|
|
|
112
114
|
| `MLX_BUN_PAGED_KV` | `--paged-kv` | off (`==="1"`) | Paged KV cache (see the flag row above — v1: serial, Gemma4, bf16; refusals + prompt-cache bypass apply identically). |
|
|
113
115
|
| `MLX_BUN_ALLOW_PRIVATE_MEDIA` | `--allow-private-media` | off (`==="1"`) | Permit `image_url`/`audio_url` fetches to private/loopback/link-local hosts (the SSRF guard's LAN escape hatch — see the flag row above; timeout + size cap still apply). |
|
|
114
116
|
| `MLX_BUN_PREFILL_TAIL_SPLIT` | — | on (`!=="0"`) | Oracle prefill convention (mlx-lm `generate_step` and its server's batched engine): drain the prompt only to len−1, then compute step-0 logits from a separate **L=1 forward of the last prompt token**. Both lanes. **The SPEC lane (`--draft-model`) follows its own oracle's shape under the same flag** (mlx-lm `speculative_generate_step`, re-anchored 2026-07-07): both target AND draft drain to len−1 and there is **no separate step-0 at all** — the un-drained last prompt token heads the first verify window. Gated live: token-for-token vs the oracle venv incl. a knife-edge cell (tests/spec-serve.test.ts "L1 knife-edge"). (The standalone optiq-oracled assistant loop `specGenerate` deliberately keeps FULL-prompt prefill — optiq's own convention.) `=0` restores the pre-2026-07-07 full-final-chunk convention everywhere (A/B lever + kill switch) — that convention is ulp-different in bf16 at step 0 AND in the last prompt token's stored KV, which flips near-tie greedy streams vs mlx-lm (the 2026-07-07 12B completion-probe divergence). |
|
|
115
|
-
| `
|
|
117
|
+
| `MLX_BUN_GRAMMAR` | — | on (`!=="0"`) | `=0` disables structured-output mask compilation. Requested constraints still take the documented graceful-degrade route: chat prompt injection plus a `Warning` header; raw completions emit the header only. |
|
|
118
|
+
| `MLX_BUN_GRAMMAR_JUMP` | — | **off** (`==="1"`) | Jump-forward decoding for structured output (SGLang's technique via xgrammar's `findJumpForwardString`): when the grammar forces a unique continuation (JSON punctuation/keys; longest under `any_whitespace:false` fixed formatting), the SERIAL lane emits its retokenized ids with ONE multi-token forward instead of one masked forward per token. Lossless in STRING space and always grammar-valid, but the token stream (and, conditioning on it, content after a forced span) can legally differ from an unjumped run — no oracle, hence opt-in. Excluded automatically when either `logprobs` or `top_logprobs` is requested, because jumped tokens have no sampled distribution row; SentencePiece-family tokenizers whose raw encode can't reproduce a mid-stream span simply never jump (guard degrades to normal decode). The batch lane's per-row grammar (`#stepGrammar`) does not jump yet. Tests: tests/grammar-jump.test.ts (contract + Llama-3.2-1B e2e). |
|
|
116
119
|
| `MLX_BUN_SSD_WRITEBEHIND` | — | on (`!=="0"`) | `--ssd-cache`'s debounced write-behind snapshot (restart survival). `=0` disables it entirely — the paired-A/B lever + kill switch for the 2026-07-07 idle-gating fix (eviction/demotion spills still write). The flush is **idle-gated**: every per-tensor step (blocking GPU sync + `writeSync`) waits for the engine to go idle and pauses when a request arrives mid-flush, so durability work never taxes an active decode (pre-fix: a ~16k entry's flush overlapping cached ctx repeats cost e4b ~9% decode@ctx vs mlx-lm). The snapshot timer also never holds batch admission — while rows are active it re-arms instead of grabbing the exclusive. |
|
|
117
120
|
| `MLX_BUN_SSD_SPILL_QUEUE_GB` | — | 2 | Byte cap on the write-behind queue's PENDING spill clones (they pin the evicted entries' GPU memory until the idle-gated flush runs). Over cap, the oldest queued spill drops — its clones free immediately and the entry becomes a future cache miss (never a wrong result). `=0` keeps only the newest + in-flight clone pinned (the soft cap never drops the item just enqueued). `/stats.ssd_cache` reports `pending_spills` / `pending_spill_bytes` / `dropped_spills`. Note: there is deliberately **no shutdown flush** — exiting under traffic loses queued write-behind entries (best-effort tier; cost = one re-prefill). |
|
|
118
121
|
| `MLX_BUN_BATCH_NO_PIPELINE` | — | off (`==="1"`) | Disables the batch lane's pipelined token readback (the A/B lever for the shipped pipelining, like its peers `MLX_BUN_BATCH_EXTEND` / `MLX_BUN_BATCH_SSM` / `MLX_BUN_BATCH_VEC_SAMPLE`). Numerically equivalent, slower steps. |
|
|
@@ -211,7 +214,8 @@ inside it per the table above).
|
|
|
211
214
|
|
|
212
215
|
| Option | serial (`--batch 1`) | `--batch N` (N>1) |
|
|
213
216
|
| --- | --- | --- |
|
|
214
|
-
| `--kv-quant config
|
|
217
|
+
| `--kv-quant config` | ✅ applied to all requests | ✅ batches where the loaded cache capability supports the per-layer scheme; otherwise routes serial |
|
|
218
|
+
| `--kv-quant 4`/`8` | ✅ applied to all requests | ⚠️ uniform-threshold semantics route requests serial |
|
|
215
219
|
| `--kv-quant turbo[:k<bits>v<bits>]` | ✅ applied to all requests | ⚠️ applied, but forces **all** requests to the serial lane (no batching) |
|
|
216
220
|
| `--kv-quant off` | ✅ bf16 | ✅ bf16 (same as the implicit batch default) |
|
|
217
221
|
| *(kv-quant unset)* | bf16 (the L1 default) | **bf16** (Option B) — incl. serial-lane fallback requests |
|
|
@@ -227,6 +231,7 @@ inside it per the table above).
|
|
|
227
231
|
| `tools` / `stop` | ✅ | ✅ (batches) |
|
|
228
232
|
| structured output (`response_format`/`guided_*`) | ✅ (mask in the decode loop) | ✅ (batches; per-row matchers) |
|
|
229
233
|
| `--draft-model` | ✅ spec decode (grammar composes) | ⚠️ mounts, but routes **every** request serial — spec and batching are different modes |
|
|
234
|
+
| GLM `--mtp on` | ✅ native MTP spec decode | ⚠️ default-on MTP routes every request serial+spec; `--mtp off` exposes ordinary GLM batching |
|
|
230
235
|
| `--compiled-decode` | ✅ (serial decode route) | ✅ at **B=1 only** (Phase 3.2): a lone request's adopted serial-class caches replay the same compiled step, same kill switch; B>1 steps run the plain graph |
|
|
231
236
|
| `--fused-sdpa`/`--force-wire` | ✅ (serial decode route) | **n/a — compat mode, no perf flags by design** |
|
|
232
237
|
|
|
@@ -394,9 +399,9 @@ cells in one run).
|
|
|
394
399
|
`--expert-offload`. Inherently the serial recipe until batched
|
|
395
400
|
quantized KV lands.
|
|
396
401
|
|
|
397
|
-
**The two exclusions to remember:** batching
|
|
398
|
-
|
|
399
|
-
cache (v1 bypass; the §7.6 composition is the fix).
|
|
402
|
+
**The two exclusions to remember:** batching excludes uniform/TurboQuant KV
|
|
403
|
+
(per-layer `kv_config.json` does compose with batching), and spec excludes
|
|
404
|
+
prompt-cache reuse (v1 bypass; the §7.6 composition is the fix).
|
|
400
405
|
|
|
401
406
|
## Observability — `GET /stats`
|
|
402
407
|
|
|
@@ -405,9 +410,11 @@ lives in [server-api.md](server-api.md#get-stats) (kept in lockstep with
|
|
|
405
410
|
the code — this section only explains how to read it); highlights:
|
|
406
411
|
`prompt_cache` (8 GB default cap), `kv_quant.mode` (incl. `turbo kXvY`),
|
|
407
412
|
the conditional `ssd_cache` block, and `batch`
|
|
408
|
-
(`active_rows`/`pending_rows`/`submitted_rows`/`kv_bytes
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
413
|
+
(`mode`/`active_rows`/`pending_rows`/`submitted_rows`/`kv_bytes`/
|
|
414
|
+
`kv_budget_bytes`).
|
|
415
|
+
|
|
416
|
+
`batch.mode` is the truthful configured/capability state: `off` for
|
|
417
|
+
`--batch 1`, `serial` when a larger configured cap cannot batch the loaded
|
|
418
|
+
model's cache layout, and `batch` when the model is admitted. `batch.batched`
|
|
419
|
+
is the compatibility boolean for `mode == "batch"`; `active_rows` reports the
|
|
420
|
+
instantaneous live rows.
|
|
@@ -177,7 +177,7 @@ are `DEFAULT_TRAIN_CONFIG` (trainer.ts:89).
|
|
|
177
177
|
| `weight_decay` | float ≥0 | `0.01` | AdamW weight decay (β = `[0.9, 0.999]`, fixed) |
|
|
178
178
|
| `lora_dropout` | float [0,1) | `0.0` | LoRA-input dropout (PEFT-style), training-only regularizer for small sets. Recompute-safe (mask keyed by step+layer, so segmented/checkpointed backward reproduces it) |
|
|
179
179
|
| `rs_lora` | bool | `false` | rsLoRA — scale the update by α/√rank instead of α, so `rank_scaling` changes capacity not step size. Recorded in the adapter config; inference applies the same per-layer scale |
|
|
180
|
-
| `lora_plus_ratio` | float ≥1 | `1.0` | LoRA+ — LR multiplier for the B leaves (A stays at base LR). >1 speeds the B-driven early learning (B is zero-init). 1 = off.
|
|
180
|
+
| `lora_plus_ratio` | float ≥1 | `1.0` | LoRA+ — LR multiplier for the B leaves (A stays at base LR). >1 speeds the B-driven early learning (B is zero-init). 1 = off. The same optimizer policy applies to SFT, DPO, and ORPO |
|
|
181
181
|
| `grad_checkpoint` | bool | `false` | Recompute layer activations in backward (memory↔compute; bit-identical) |
|
|
182
182
|
| `mlp_split` | bool | `false` | Gradient-checkpoint MLP splitting (Gemma4-only, requires `grad_checkpoint`) |
|
|
183
183
|
| `segment_size` | int | `0` (off) | `>0` enables segmented backward — layers per segment (see below) |
|
|
@@ -295,6 +295,15 @@ A finished run writes a PEFT-compatible adapter directory:
|
|
|
295
295
|
- `optiq_lora_config.json` — mlx-bun/optiq adapter metadata (per-layer ranks)
|
|
296
296
|
- `adapter_config.json` — PEFT-compatible config
|
|
297
297
|
|
|
298
|
+
The save completes only after both config files are written, including
|
|
299
|
+
eval-step checkpoints, so a successful process exit always leaves an
|
|
300
|
+
immediately mountable directory. Serving accepts both mlx-lm tensor layout
|
|
301
|
+
(`[in, rank]` / `[rank, out]`) and standard PEFT layout
|
|
302
|
+
(`[rank, in]` / `[out, rank]`, including `base_model.model.*` names).
|
|
303
|
+
Standard PEFT `use_rslora` metadata is honored as
|
|
304
|
+
`lora_alpha / sqrt(rank)`, and saved rsLoRA adapters emit that metadata so
|
|
305
|
+
their training and serving scales agree.
|
|
306
|
+
|
|
298
307
|
When `save_checkpoints: true`, each eval step also writes
|
|
299
308
|
`checkpoints/step-<NNNNN>-val<loss>/` and a durable `metrics.json` (config,
|
|
300
309
|
wall seconds, peak GB, final/best train+val loss, full val trajectory).
|
package/package.json
CHANGED