mlx-bun 0.0.10 → 0.0.12
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 +107 -32
- package/THIRD_PARTY_LICENSES.md +10 -0
- package/docs/archive/mlx-bun-lab-report.html +275 -0
- package/docs/archive/wwdc-mlx-bun.html +848 -0
- package/docs/design/audio-input-plan.md +356 -0
- package/docs/design/batching-perf-path.md +58 -35
- package/docs/design/batching-v2-plan.md +23 -9
- package/docs/design/bucketing-stage.md +2 -1
- package/docs/design/colibri-glm52-port.md +1479 -0
- package/docs/design/compat-cli-surface-design.md +4 -3
- package/docs/design/decode-speed-program.md +172 -0
- package/docs/design/docs-reorg-plan.md +1 -1
- package/docs/design/dspark-serving-program.md +371 -0
- package/docs/design/dspark-speculative-decoding.md +167 -5
- package/docs/design/faithful-l1-consolidation.md +310 -0
- package/docs/design/generic-model-support.md +8 -5
- package/docs/design/grammar-spec-batching-integration.md +301 -0
- package/docs/design/minicpm5-decode-megakernel.md +16 -11
- package/docs/design/mlx-lm-tool-parity-plan.md +11 -1
- package/docs/design/omlx-adoption-map.md +14 -6
- package/docs/design/orpo-training.md +34 -30
- package/docs/design/paged-kv-cache.md +214 -0
- package/docs/design/parallel-slots.md +82 -49
- package/docs/design/parity-tier-dag.md +43 -33
- package/docs/design/pre-colibri-stabilization.md +329 -0
- package/docs/design/repo-cleanup-plan.md +189 -0
- package/docs/design/runtime-isolation.md +19 -3
- package/docs/design/segmented-backward-training.md +3 -2
- package/docs/design/ssd-kv-cold-tier.md +130 -0
- package/docs/design/structured-output.md +484 -0
- package/docs/design/the-dreaming-master-plan.md +2 -3
- package/docs/design/turboquant-kv.md +235 -0
- package/docs/design/unified-engine-frontier-plan.md +774 -0
- package/docs/design/web-chat-beat-matrix.md +315 -0
- package/docs/design/web-chat-redesign.md +1112 -0
- package/docs/investigations/colibri-oracle-pin.md +1284 -0
- package/docs/investigations/dspark-handoff.md +182 -16
- package/docs/investigations/indexshare-performance-spike.md +357 -0
- package/docs/investigations/lab-build-journal.md +2 -2
- package/docs/investigations/openwiki-evaluation.md +249 -0
- package/docs/planning/IDEAS.md +14 -0
- package/docs/planning/PRODUCT_ROADMAP.md +2 -3
- package/docs/planning/chat-ui-vision-memory-first.md +199 -0
- package/docs/planning/chat-ui-vision.md +50 -0
- package/docs/planning/memory-docs-and-dag-plan.md +15 -6
- package/docs/planning/release-notes-v0.0.11.md +239 -0
- package/docs/planning/release-notes-v0.0.12.md +80 -0
- package/docs/reference/cli.md +63 -18
- package/docs/reference/distribution.md +37 -6
- package/docs/reference/embedding.md +40 -16
- package/docs/reference/features-matrix.md +99 -0
- package/docs/reference/library-api.md +69 -20
- package/docs/reference/memory.md +26 -6
- package/docs/reference/models.md +60 -3
- package/docs/reference/orpo-quickstart.md +3 -4
- package/docs/reference/server-api.md +717 -24
- package/docs/reference/server-config.md +265 -129
- package/docs/reference/training.md +24 -16
- package/package.json +5 -3
- package/src/anthropic.ts +67 -44
- package/src/audio/conformer.ts +647 -0
- package/src/audio/decode.ts +133 -0
- package/src/audio/features.ts +202 -0
- package/src/audio/transcode.ts +67 -0
- package/src/chat-template.ts +155 -10
- package/src/cli.ts +483 -113
- package/src/config.ts +116 -24
- package/src/diffusion/diffusion-generate.ts +14 -8
- package/src/download.ts +143 -12
- package/src/eval/README.md +7 -6
- package/src/eval/ifeval.ts +63 -120
- package/src/eval/kl.ts +59 -3
- package/src/eval/quality-db.ts +2 -2
- package/src/eval/runner.ts +17 -8
- package/src/eval/tasks/ifeval.ts +167 -31
- package/src/expert-io.ts +391 -0
- package/src/expert-offload.ts +15 -2
- package/src/expert-residency.ts +1134 -0
- package/src/expert-usage.ts +415 -0
- package/src/flags.ts +13 -0
- package/src/generate.ts +438 -35
- package/src/grammar.ts +585 -0
- package/src/hf-push.ts +1 -1
- package/src/hub-rest.ts +242 -0
- package/src/index.ts +14 -2
- package/src/jobs/runner.ts +37 -18
- package/src/kv-store.ts +586 -93
- package/src/lora.ts +85 -14
- package/src/md-modules.d.ts +42 -0
- package/src/media-fetch.ts +426 -0
- package/src/memory/rest.ts +245 -0
- package/src/memory/vault.ts +74 -0
- package/src/mlx/array.ts +149 -19
- package/src/mlx/autograd.ts +53 -16
- package/src/mlx/checkpoint.ts +46 -13
- package/src/mlx/custom-vjp.ts +83 -35
- package/src/mlx/ffi.ts +27 -3
- package/src/mlx/metal-kernel.ts +7 -4
- package/src/mlx/ops.ts +153 -23
- package/src/mlx/turboquant-ops.ts +831 -0
- package/src/mlx/turboquant-tables.ts +237 -0
- package/src/model/batched-mask.ts +62 -1
- package/src/model/batched-quant.ts +206 -0
- package/src/model/batched-rotating-quant.ts +346 -0
- package/src/model/batched-rotating.ts +46 -1
- package/src/model/compiled-decode.ts +4 -15
- package/src/model/factory.ts +103 -6
- package/src/model/gemma4-base.ts +395 -39
- package/src/model/gemma4.ts +122 -33
- package/src/model/generated/gemma4-12b.ts +9 -13
- package/src/model/generated/gemma4-26b.ts +10 -5
- package/src/model/generated/gemma4-e4b.ts +14 -9
- 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/minicpm5.ts +58 -102
- package/src/model/paged-kv.ts +253 -0
- package/src/model/qwen3-delta.ts +76 -15
- package/src/model/qwen3-moe.ts +404 -0
- package/src/model/qwen3.ts +9 -8
- package/src/model/qwen3_5.ts +88 -23
- package/src/model/support.ts +19 -1
- package/src/model/universal/dense.ts +40 -5
- package/src/model/universal/rope.ts +36 -21
- package/src/native/expert_io.c +523 -0
- package/src/native-pack.ts +6 -4
- package/src/pi-web.ts +1299 -82
- package/src/preflight.ts +27 -8
- package/src/prompt-cache.ts +293 -33
- package/src/quantize/quantizer.ts +16 -1
- package/src/registry.ts +57 -2
- package/src/responses.ts +62 -49
- package/src/serve/batch-scheduler.ts +1050 -78
- package/src/serve/generation-gateway.ts +252 -24
- package/src/serve/isolate.ts +417 -0
- package/src/serve/lane-registry.ts +44 -0
- package/src/serve/session-search.ts +326 -0
- package/src/server.ts +1701 -175
- package/src/spec/assistant-source.ts +150 -0
- package/src/spec/deepspec-source.ts +143 -0
- package/src/spec/dflash-source.ts +124 -0
- package/src/spec/dspark/ab-stats.ts +136 -0
- package/src/spec/dspark/calibration.ts +112 -0
- package/src/spec/dspark/deepspec-module.ts +801 -0
- package/src/spec/dspark/generate-dflash.ts +72 -20
- package/src/spec/dspark/loader.ts +23 -0
- package/src/spec/dspark/module-dflash.ts +278 -36
- package/src/spec/dspark/quantize-drafter.ts +91 -0
- package/src/spec/generate.ts +59 -25
- package/src/spec/glm52-mtp-source.ts +392 -0
- package/src/spec/ngram-source.ts +132 -0
- package/src/spec/serve-loop.ts +617 -0
- package/src/spec/source.ts +118 -0
- package/src/spec/two-model.ts +171 -0
- package/src/ssd-cache.ts +111 -35
- package/src/tokenizer.ts +288 -2
- package/src/tool-approvals.ts +131 -0
- package/src/tool-call.ts +438 -22
- package/src/train/flash-cce.ts +2 -2
- package/src/train/job.ts +0 -12
- package/src/train/lora-params.ts +17 -6
- package/src/train/trainer.ts +60 -30
- package/src/vision/prompt.ts +217 -48
- package/src/web/app.html +1197 -1791
- package/src/web/app.js +5694 -0
- package/src/web/icon.svg +13 -0
- package/src/web/manifest.webmanifest +24 -0
- package/src/web/src/adapters-panel.ts +288 -0
- package/src/web/src/api.ts +57 -0
- package/src/web/src/assistant.ts +485 -0
- package/src/web/src/chat.ts +1050 -0
- package/src/web/src/composer.ts +959 -0
- package/src/web/src/dataset.ts +132 -0
- package/src/web/src/finetune.ts +267 -0
- package/src/web/src/hub.ts +428 -0
- package/src/web/src/main.ts +98 -0
- package/src/web/src/markdown.ts +0 -0
- package/src/web/src/memory-panel.ts +572 -0
- package/src/web/src/model-picker.ts +159 -0
- package/src/web/src/palette.ts +346 -0
- package/src/web/src/protocol.ts +62 -0
- package/src/web/src/quantize.ts +167 -0
- package/src/web/src/rag.ts +274 -0
- package/src/web/src/sessions.ts +469 -0
- package/src/web/src/shell.ts +804 -0
- package/src/web/src/status.ts +300 -0
- package/src/web/src/ui-catalog.ts +92 -0
- package/src/web/sw.js +71 -0
- package/src/web/vendor/README.md +61 -0
- package/src/web/vendor/hljs-LICENSE +29 -0
- package/src/web/vendor/hljs-theme.css +59 -0
- package/src/web/vendor/hljs.js +26 -0
- package/src/web-tools.ts +54 -2
- package/src/weights.ts +82 -19
- package/src/model/fused-decode-kernel.ts +0 -312
- package/src/model/fused-geglu-kernel.ts +0 -198
- package/src/model/fused-mlp-kernel.ts +0 -169
- package/src/model/fused-swiglu-kernel.ts +0 -240
- package/src/model/steel-linear-kernel.ts +0 -96
- package/src/status-page.html +0 -497
package/src/cli.ts
CHANGED
|
@@ -12,13 +12,20 @@
|
|
|
12
12
|
import { existsSync } from "node:fs";
|
|
13
13
|
import { basename, join, resolve } from "node:path";
|
|
14
14
|
import { Registry } from "./registry";
|
|
15
|
-
import { loadModelConfig } from "./config";
|
|
15
|
+
import { loadModelConfig, parseTurboQuantScheme, type TurboQuantScheme } from "./config";
|
|
16
16
|
import { fit, skuMatrix, thisMachine } from "./fit";
|
|
17
17
|
import { EvalDB } from "./evaldb";
|
|
18
18
|
import pkg from "../package.json" with { type: "json" };
|
|
19
19
|
import { renderHelp } from "./tui";
|
|
20
|
-
import { isSupportedModelRecord } from "./model/support";
|
|
21
|
-
import {
|
|
20
|
+
import { isGlm52Config, isSupportedModelRecord } from "./model/support";
|
|
21
|
+
import {
|
|
22
|
+
GLM52_G5_ASPIRATIONAL_DECODE_TPS,
|
|
23
|
+
GLM52_G5_DEFAULT_MAX_GENERATION_TOKENS,
|
|
24
|
+
GLM52_G5_DEFAULT_PROCESS_LIMIT_BYTES,
|
|
25
|
+
GLM52_G5_DIRECT_ORACLE_WARM_DECODE_TPS,
|
|
26
|
+
GLM52_G5_MEASURED_WARM_DECODE_TPS,
|
|
27
|
+
planGlm52MemoryForArtifact,
|
|
28
|
+
} from "./model/glm52-memory";
|
|
22
29
|
import type { JobEvent } from "./jobs/types";
|
|
23
30
|
|
|
24
31
|
const argv = process.argv.slice(2);
|
|
@@ -36,7 +43,7 @@ Usage: mlx-bun [command] [options] (no command = serve)
|
|
|
36
43
|
|
|
37
44
|
Commands:
|
|
38
45
|
pi Launch a pi coding-agent session on a local model (the appliance path)
|
|
39
|
-
serve Start the OpenAI/Anthropic-compatible server +
|
|
46
|
+
serve Start the OpenAI/Anthropic-compatible server + web app
|
|
40
47
|
get Download a model from Hugging Face (resumable, verified)
|
|
41
48
|
ls List downloaded models
|
|
42
49
|
gc Reclaim superseded model snapshots + dead blobs from the HF cache
|
|
@@ -63,7 +70,7 @@ Options:
|
|
|
63
70
|
Examples:
|
|
64
71
|
mlx-bun # download (first run) + serve + open the chat UI
|
|
65
72
|
mlx-bun pi # start (download if needed) and chat with an agent
|
|
66
|
-
mlx-bun serve 12B # serve the 12B;
|
|
73
|
+
mlx-bun serve 12B # serve the 12B; web app at http://localhost:8080/
|
|
67
74
|
mlx-bun train e4b --data ./prefs # ORPO LoRA fine-tune on {prompt,chosen,rejected} data
|
|
68
75
|
mlx-bun get mlx-community/gemma-4-12B-it-OptiQ-4bit`;
|
|
69
76
|
|
|
@@ -78,7 +85,12 @@ const SERVER_FLAGS = `Server options:
|
|
|
78
85
|
--memory-budget <GB> Admission-control memory budget; requests that
|
|
79
86
|
cannot fit are rejected instead of crashing the
|
|
80
87
|
GPU [default: machine RAM × 0.75, check-only]
|
|
81
|
-
--
|
|
88
|
+
--kv-budget <GB> Aggregate KV budget across concurrent batch rows
|
|
89
|
+
(decimal GB): joiners whose projected KV
|
|
90
|
+
(prompt + max_tokens) would exceed it queue until
|
|
91
|
+
rows finish; a request over the budget alone is
|
|
92
|
+
rejected. Off unless set.
|
|
93
|
+
--prompt-cache <GB> Prompt (KV) cache byte cap [default: 8 GB;
|
|
82
94
|
--prompt-cache 0 disables the cache]
|
|
83
95
|
--ssd-cache <dir> SSD cold tier for the prompt cache: prefix KV
|
|
84
96
|
spills to disk on eviction and persists across
|
|
@@ -86,18 +98,44 @@ const SERVER_FLAGS = `Server options:
|
|
|
86
98
|
full re-prefill to a zero-copy mmap restore).
|
|
87
99
|
Off unless set.
|
|
88
100
|
--ssd-cache-max <GB> SSD tier byte cap [default: 32 GB]
|
|
101
|
+
--ssd-demote-idle <sec> Demote prompt-cache entries idle this long to the
|
|
102
|
+
SSD tier, freeing their GPU memory (prefixes stay
|
|
103
|
+
reachable via zero-copy restore). 0 disables.
|
|
104
|
+
[default: 300 when --ssd-cache is on]
|
|
89
105
|
--ssd-cache-verify Verify tensor hashes on every restore (reads all
|
|
90
106
|
bytes eagerly — integrity paranoia only)
|
|
91
|
-
--
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
107
|
+
--isolate Run the inference engine as a CHILD process and
|
|
108
|
+
keep this process a pure UI/API proxy — the UI
|
|
109
|
+
stays instant under GPU load and survives engine
|
|
110
|
+
crashes (respawned). /ws/chat not proxied yet.
|
|
111
|
+
Requests naming another model (exact /v1/models
|
|
112
|
+
id) spawn/route to THAT model's engine child.
|
|
113
|
+
--model-pool <n> With --isolate: max RESIDENT model engines
|
|
114
|
+
[default: 1]. Over the cap, the least-recently-
|
|
115
|
+
used engine drains, demotes its caches to the
|
|
116
|
+
SSD tier, and exits — switching back respawns
|
|
117
|
+
it with all state restored from disk.
|
|
118
|
+
--unix <path> (internal) listen on a unix socket instead of a
|
|
119
|
+
TCP port — the engine half of --isolate.
|
|
120
|
+
--batch <n> Max concurrent requests decoded together
|
|
121
|
+
[default: 8]. A LONE request runs the exact
|
|
122
|
+
serial engine (same bits, same speed); the cap
|
|
123
|
+
engages only when concurrent requests arrive
|
|
124
|
+
(agent fan-out). 1 pins strict serial.
|
|
95
125
|
--decode-concurrency is accepted as the
|
|
96
126
|
mlx_lm.server alias (semantics differ; see
|
|
97
127
|
docs/reference/server-config.md)
|
|
98
128
|
--no-open Don't open the chat UI in your browser on start.
|
|
99
129
|
By default an interactive terminal opens
|
|
100
130
|
http://<host>:<port>/#/chat once the server is up.
|
|
131
|
+
--allow-private-media Let image_url/audio_url content parts fetch from
|
|
132
|
+
private/loopback/link-local hosts (NAS, another
|
|
133
|
+
LAN box). Blocked by default: a request's URL is
|
|
134
|
+
attacker-controlled input, and the default keeps
|
|
135
|
+
the server from being steered at cloud metadata
|
|
136
|
+
or LAN-internal services (SSRF). data: URLs are
|
|
137
|
+
always fine; the 10 s timeout and 64 MB response
|
|
138
|
+
cap apply either way.
|
|
101
139
|
|
|
102
140
|
Model & quality:
|
|
103
141
|
--adapter <dir> Mount a LoRA adapter at startup and use it as the
|
|
@@ -105,12 +143,53 @@ Model & quality:
|
|
|
105
143
|
request's \`adapter\` field, incl. "none", wins).
|
|
106
144
|
--adapter-path is accepted as the mlx_lm.server
|
|
107
145
|
alias. Hot-swap via POST /v1/adapters unchanged.
|
|
146
|
+
--draft-model <query> Speculative decoding: a drafter proposes tokens
|
|
147
|
+
the main model verifies (exact results, faster
|
|
148
|
+
decode when drafts land). The artifact's kind is
|
|
149
|
+
auto-detected — a full same-tokenizer model
|
|
150
|
+
(mlx_lm.server parity), a Gemma "-assistant"
|
|
151
|
+
KV-borrowing drafter, or a DSpark checkpoint.
|
|
152
|
+
Serial lane only — with --batch N a mounted draft
|
|
153
|
+
routes every request serial, like mlx_lm.server.
|
|
154
|
+
--draft-kind <kind> Override draft-artifact detection:
|
|
155
|
+
two-model | assistant | dspark | deepspec | ngram
|
|
156
|
+
(deepspec = DeepSeek's released Gemma4DSparkModel
|
|
157
|
+
drafters; ngram = MODEL-FREE prompt lookup — no
|
|
158
|
+
--draft-model, drafts copied from the request's
|
|
159
|
+
own context, lossless) [default: auto]
|
|
160
|
+
--num-draft-tokens <n> Drafts per verify round [default: 3; ngram: 10;
|
|
161
|
+
DSpark pins to its trained block width]
|
|
162
|
+
--ngram-max <k> Prompt-lookup longest match window [default: 3]
|
|
163
|
+
--ngram-min <k> Prompt-lookup shortest match window [default: 1]
|
|
164
|
+
--mtp <on|off> GLM-5.2 checkpoint-native MTP. On by default;
|
|
165
|
+
routes requests through the exact serial verify
|
|
166
|
+
lane. off enables ordinary continuous batching.
|
|
167
|
+
--context-length <n> GLM-5.2 resource-plan context reservation
|
|
168
|
+
[default: 4096]
|
|
108
169
|
--kv-quant <mode> KV cache quantization: config (per-layer
|
|
109
170
|
kv_config.json when the model ships one), off
|
|
110
|
-
(bf16),
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
171
|
+
(bf16), 4 / 8 (uniform bits), or turbo[:k<bits>v
|
|
172
|
+
<bits>] (rotation-based TurboQuant — default
|
|
173
|
+
k8v3; kBits in {2,4,5,8}, vBits in {2,3,4,5,8};
|
|
174
|
+
docs/design/turboquant-kv.md)
|
|
175
|
+
[default: off — quantized KV measured 5-20% slower
|
|
176
|
+
decode than bf16 at ≤16k (on mlx-lm too); it buys
|
|
177
|
+
memory headroom, so opt in when context is tight.
|
|
178
|
+
The batched engine (--batch N) is bf16-only: an
|
|
179
|
+
explicit --kv-quant routes those requests serial.
|
|
180
|
+
turbo is solo-only in v1 (full-attention layers
|
|
181
|
+
only; sliding-window layers stay bf16)]
|
|
182
|
+
--paged-kv OPTIONAL vLLM-style paged KV storage (block pool +
|
|
183
|
+
gather before the stock SDPA; env MLX_BUN_PAGED_KV=1).
|
|
184
|
+
v1: serial only (pins --batch 1 unless --batch is
|
|
185
|
+
given), Gemma4-family, bf16 — refuses --batch N>1,
|
|
186
|
+
--kv-quant, --draft-model; bypasses the prompt
|
|
187
|
+
cache; runs uncompiled decode. Bit-exact with the
|
|
188
|
+
plain path; expect a small decode cost at batch=1
|
|
189
|
+
(the gather copy). docs/design/paged-kv-cache.md
|
|
190
|
+
[default: off]
|
|
191
|
+
--paged-kv-block-size <n> Tokens per KV block [default: 256 = the plain
|
|
192
|
+
cache's growth step]
|
|
114
193
|
--thinking <true|false> Default for the chat template's enable_thinking
|
|
115
194
|
variable (CPM and other hybrid-reasoning models);
|
|
116
195
|
a request's chat_template_kwargs overrides it
|
|
@@ -124,7 +203,8 @@ Model & quality:
|
|
|
124
203
|
[default: the model's generation_config.json]
|
|
125
204
|
--max-tokens <n> Completion cap when a request omits max_tokens
|
|
126
205
|
(mlx_lm.server flag; its default is 512 there)
|
|
127
|
-
[default: 65536 chat /
|
|
206
|
+
[default: 128 GLM-5.2; otherwise 65536 chat /
|
|
207
|
+
512 raw completion]
|
|
128
208
|
--hlg-sampling on|off Piecewise tone-curve sampling (HLG): rolls off the
|
|
129
209
|
top, boosts the mids, gentles the tail. Gain folds
|
|
130
210
|
from --temperature. [default: off]
|
|
@@ -134,23 +214,28 @@ Model & quality:
|
|
|
134
214
|
--hlg-toe <nats> HLG shadow rolloff scale [default: 6]
|
|
135
215
|
--hlg-pivot-offset <nats> HLG pivot: nats below the top token [default: 6]
|
|
136
216
|
|
|
137
|
-
Parity tier (
|
|
138
|
-
|
|
217
|
+
Parity tier (an ALIAS for the per-fork flags below; each expands to the fastest
|
|
218
|
+
set of forks that still holds the guarantee — a per-fork flag overrides one):
|
|
219
|
+
--l1 Bit-for-bit IDENTICAL to mlx-lm (bf16 KV, compiled
|
|
220
|
+
activations — the faithful @mx.compile kernels).
|
|
221
|
+
No tier given = --l1 (decided 2026-07-05: the L1
|
|
222
|
+
faithful kernels match mlx-lm 1.00x on every model;
|
|
223
|
+
anything output-changing must beat that baseline in
|
|
224
|
+
a paired A/B to earn a default).
|
|
139
225
|
--l2 Bit-for-bit IDENTICAL to mlx-optiq (quantized KV +
|
|
140
226
|
fused prefill SDPA, stock unfused decode — the
|
|
141
|
-
optiq-golden composition
|
|
142
|
-
--l3 Best performance, no bit-exact oracle (KL + test
|
|
143
|
-
gated; adds the envelope-gated perf kernel).
|
|
144
|
-
No tier given = the per-flag defaults below,
|
|
145
|
-
which equal --l3 today.
|
|
227
|
+
optiq-golden composition)
|
|
146
228
|
|
|
147
|
-
|
|
229
|
+
Kill switches (bit-exact A/B levers — each selects a slower same-parity path):
|
|
148
230
|
--compiled-decode on|off Compiled decode graphs [default: on]
|
|
149
|
-
--
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
--fused-sdpa on|off Fused SDPA path [default:
|
|
231
|
+
--compiled-activations on|off
|
|
232
|
+
Compiled geglu/swiglu (mlx-lm's @mx.compile kernel —
|
|
233
|
+
bit-exact AND fewer dispatches). off = the uncompiled
|
|
234
|
+
composition (same parity, slower) [default: on]
|
|
235
|
+
--fused-sdpa on|off Fused SDPA path (quantized KV only) [default:
|
|
236
|
+
follows --kv-quant — on for config (the optiq
|
|
237
|
+
composition), off for uniform 4/8 (the mlx-lm
|
|
238
|
+
composition) and bf16]
|
|
154
239
|
--force-wire Wire weights into memory at load
|
|
155
240
|
--expert-offload MoE only: serve experts from a page-aligned file
|
|
156
241
|
mmap (built on first use) — keeps the model out of
|
|
@@ -199,7 +284,7 @@ ${SERVER_FLAGS}
|
|
|
199
284
|
|
|
200
285
|
Endpoints: /v1/chat/completions, /v1/completions, /v1/messages, /v1/responses,
|
|
201
286
|
/v1/models, /v1/adapters, /health, /stats, /fit, /library, /downloads —
|
|
202
|
-
|
|
287
|
+
web app at / (lands on chat) — /chat, /status, /quantize, /finetune, /dataset deep-link its views`,
|
|
203
288
|
|
|
204
289
|
get: `mlx-bun get — download a model from Hugging Face
|
|
205
290
|
|
|
@@ -214,7 +299,9 @@ Options:
|
|
|
214
299
|
|
|
215
300
|
Resumable (Range requests against partial blobs) and verified (sha256
|
|
216
301
|
for LFS blobs). Re-running after an interruption continues where it
|
|
217
|
-
stopped.
|
|
302
|
+
stopped. Before payload transfer, complete blobs and partial prefixes are
|
|
303
|
+
credited and the target volume must fit the remainder plus a 1 GiB reserve.
|
|
304
|
+
Uses HF_TOKEN / hf auth login credentials when present.
|
|
218
305
|
When upstream pushed a new revision, the previous snapshot stays on
|
|
219
306
|
disk — \`mlx-bun gc\` reclaims it.`,
|
|
220
307
|
|
|
@@ -305,11 +392,11 @@ Options:
|
|
|
305
392
|
--tokens <n> Tokens to decode per run [default: 256]
|
|
306
393
|
--runs <n> Runs (median reported) [default: 3]
|
|
307
394
|
--prompt-tokens <n> Pad the prompt to ~n tokens (long-context decode)
|
|
308
|
-
--kv-quant <mode> config | off | 4 | 8
|
|
309
|
-
historical baseline]
|
|
395
|
+
--kv-quant <mode> config | off | 4 | 8 | turbo[:k<bits>v<bits>]
|
|
396
|
+
[default: off, the historical baseline]
|
|
310
397
|
|
|
311
|
-
|
|
312
|
-
|
|
398
|
+
Kill switches (--compiled-decode, --compiled-activations, --fused-sdpa)
|
|
399
|
+
apply to the run — A/B by running twice.`,
|
|
313
400
|
|
|
314
401
|
embed: `mlx-bun embed — text embeddings from a local embedding model
|
|
315
402
|
|
|
@@ -358,9 +445,9 @@ Options:
|
|
|
358
445
|
--top-k <n> Top-k sampling
|
|
359
446
|
--seed <n> Sampler seed (reproducible runs)
|
|
360
447
|
|
|
361
|
-
Decode-path levers mirror serve: --l1/--l2
|
|
362
|
-
(--
|
|
363
|
-
The tier's KV scheme applies here too (--l2
|
|
448
|
+
Decode-path levers mirror serve: --l1/--l2 and the per-fork flags
|
|
449
|
+
(--fused-sdpa/--compiled-decode/--compiled-activations/--kv-quant).
|
|
450
|
+
The tier's KV scheme applies here too (--l2 = the model's kv_config,
|
|
364
451
|
same tokens as serve); with no tier/--kv-quant the KV cache stays bf16
|
|
365
452
|
(the bit-exact mlx-lm greedy path). mlx-lm compat = --l1.
|
|
366
453
|
|
|
@@ -720,69 +807,96 @@ function openChatUi(url: string, hostPort: string): void {
|
|
|
720
807
|
/** Server/runtime flags shared by every mode that loads a model
|
|
721
808
|
* (serve, pi). Env levers are set here so they're in place before the
|
|
722
809
|
* generate/compiled-decode modules read them. */
|
|
723
|
-
/** Resolve the decode ROUTE: a tier alias (--l1/--l2
|
|
724
|
-
* and an explicit per-fork flag (--
|
|
725
|
-
*
|
|
726
|
-
*
|
|
810
|
+
/** Resolve the decode ROUTE: a tier alias (--l1/--l2) sets the whole route,
|
|
811
|
+
* and an explicit per-fork flag (--kv-quant/--fused-sdpa/…) overrides the
|
|
812
|
+
* alias. Sets the decode env levers and returns the kv-quant mode. See
|
|
813
|
+
* docs/design/parity-tier-dag.md + unified-engine-frontier-plan.md.
|
|
727
814
|
* Each tier is a GUARANTEE about which reference you reproduce bit-for-bit:
|
|
728
815
|
* --l1 bit-for-bit IDENTICAL to mlx-lm — drop-in replacement for mlx-lm
|
|
729
|
-
*
|
|
730
|
-
*
|
|
731
|
-
*
|
|
732
|
-
*
|
|
733
|
-
*
|
|
734
|
-
*
|
|
735
|
-
*
|
|
736
|
-
*
|
|
737
|
-
|
|
738
|
-
* tests, because mlx-optiq has no analogy for what L3 does.
|
|
739
|
-
* L2 ⊂ L3 (L3 = L2 + perf kernel). L1 is a separate target (bf16, unfused). */
|
|
740
|
-
function applyDecodeRoute(): { kvQuant?: "off" | "config" | number } {
|
|
816
|
+
* (bf16 KV; THE naked default since 2026-07-05)
|
|
817
|
+
* --l2 bit-for-bit IDENTICAL to mlx-optiq — drop-in replacement for
|
|
818
|
+
* mlx-optiq: quantized KV per kv_config.json + fused N-tiled SDPA for
|
|
819
|
+
* L>1 + STOCK unfused L=1 decode (the optiq-golden composition,
|
|
820
|
+
* scripts/regen-kvq-goldens.ts + regen-mixed-kv-goldens.ts).
|
|
821
|
+
* There is no --l3: output-changing experiments live in the Lab (env flags
|
|
822
|
+
* with a bench + expiry) until one beats the L1 baseline in a paired A/B —
|
|
823
|
+
* docs/design/unified-engine-frontier-plan.md §6-7. */
|
|
824
|
+
function applyDecodeRoute(): { kvQuant?: "off" | "config" | number; turboQuant?: TurboQuantScheme } {
|
|
741
825
|
const onOff = (name: string): boolean | null => {
|
|
742
826
|
const v = opt(name); if (v == null) return null;
|
|
743
827
|
if (v === "on" || v === "1" || v === "true") return true;
|
|
744
828
|
if (v === "off" || v === "0" || v === "false") return false;
|
|
745
829
|
console.error(`--${name} expects on|off (got "${v}")`); process.exit(1);
|
|
746
830
|
};
|
|
747
|
-
|
|
831
|
+
if (flag("l3")) {
|
|
832
|
+
console.error(
|
|
833
|
+
"--l3 was removed 2026-07-05: no output-changing kernel beat the L1 baseline " +
|
|
834
|
+
"in a paired A/B, so the tier had no content. Use --l2 for the optiq route, " +
|
|
835
|
+
"or the Lab (docs/design/unified-engine-frontier-plan.md) for experiments.",
|
|
836
|
+
);
|
|
837
|
+
process.exit(1);
|
|
838
|
+
}
|
|
839
|
+
type Preset = { kv: "off" | "config"; fusedSdpa: boolean; compiled: boolean; compiledAct: boolean };
|
|
748
840
|
const TIERS: Record<string, Preset> = {
|
|
749
841
|
// A tier is the GUARANTEE; within it we default to the FAST kernel that still
|
|
750
842
|
// holds it, and expose the slow one as an opt-in. compiled-decode is BIT-EXACT
|
|
751
843
|
// with uncompiled (compiled-decode.test: on==off) → free speed, so it's ON in
|
|
752
|
-
// EVERY tier (--compiled-decode off is the slow, same-tier opt-in).
|
|
753
|
-
//
|
|
754
|
-
//
|
|
755
|
-
//
|
|
756
|
-
//
|
|
757
|
-
//
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
// FEATURES (HLG sampler, expert offload, batched mixed-precision).
|
|
761
|
-
l1: { kv: "off", perf: false, fusedSdpa: false, compiled: true, fusedDecode: false }, // = mlx-lm bit-for-bit (bf16)
|
|
762
|
-
l2: { kv: "config", perf: false, fusedSdpa: true, compiled: true, fusedDecode: false }, // = mlx-optiq bit-for-bit
|
|
763
|
-
l3: { kv: "config", perf: true, fusedSdpa: true, compiled: true, fusedDecode: false }, // best perf (envelope-gated decode)
|
|
844
|
+
// EVERY tier (--compiled-decode off is the slow, same-tier opt-in). compiled
|
|
845
|
+
// ACTIVATIONS (geglu/swiglu) go through the SAME libmlx as mlx-lm's `@mx.compile`
|
|
846
|
+
// → same kernel, bit-exact AND faster → ON in every tier (--compiled-activations
|
|
847
|
+
// off is the slower same-parity opt-in). fused-sdpa matches OPTIQ bit-for-bit
|
|
848
|
+
// (tier-a goldens, fused-sdpa.test) and is quantized-KV only → it's the L2 bridge,
|
|
849
|
+
// a no-op on L1's bf16.
|
|
850
|
+
l1: { kv: "off", fusedSdpa: false, compiled: true, compiledAct: true }, // = mlx-lm bit-for-bit (bf16)
|
|
851
|
+
l2: { kv: "config", fusedSdpa: true, compiled: true, compiledAct: true }, // = mlx-optiq bit-for-bit
|
|
764
852
|
};
|
|
765
|
-
|
|
766
|
-
|
|
853
|
+
// NAKED DEFAULT = L1 (decided 2026-07-05): with no tier flag you get the
|
|
854
|
+
// mlx-lm bit-for-bit route. The 2026-07-05 benchmark pass showed the L1
|
|
855
|
+
// faithful kernel set matches mlx-lm 1.00× on every model while every
|
|
856
|
+
// output-changing lever failed to beat it in a paired A/B (the losers were
|
|
857
|
+
// deleted the same day — Phase 1 of unified-engine-frontier-plan.md).
|
|
858
|
+
// Quantized KV costs 5–20% decode at ≤16k on BOTH stacks and pays only in
|
|
859
|
+
// memory/context headroom → explicit opt-in (--kv-quant / --l2).
|
|
860
|
+
const tier = flag("l2") ? "l2" : "l1";
|
|
861
|
+
const p = TIERS[tier]!;
|
|
862
|
+
const kv = opt("kv-quant"); // explicit --kv-quant overrides the tier
|
|
767
863
|
const pick = (name: string, base: boolean | undefined): boolean | null => {
|
|
768
864
|
const ex = onOff(name); return ex !== null ? ex : (base ?? null); // explicit flag wins, else tier preset
|
|
769
865
|
};
|
|
770
866
|
const set = (env: string, val: boolean | null, invert = false) => {
|
|
771
867
|
if (val !== null) process.env[env] = (invert ? !val : val) ? "1" : "0";
|
|
772
868
|
};
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
const
|
|
869
|
+
// Explicit --kv-quant picks the composition its ORACLE uses: config (the
|
|
870
|
+
// optiq mixed scheme) runs optiq's fused SDPA (the L2 golden composition);
|
|
871
|
+
// uniform 4/8 runs UNFUSED (mlx-lm's quantized_scaled_dot_product_attention
|
|
872
|
+
// — the bit-exact L1-eligible scheme). --fused-sdpa still overrides either.
|
|
873
|
+
const fusedSdpaBase = kv === "config" ? true : kv != null ? false : p.fusedSdpa;
|
|
874
|
+
set("MLX_BUN_COMPILED_DECODE", pick("compiled-decode", p.compiled));
|
|
875
|
+
set("MLX_BUN_NO_FUSED_SDPA", pick("fused-sdpa", fusedSdpaBase), true); // inverted env
|
|
876
|
+
// Compiled activations (the faithful geglu/swiglu — mlx-lm's @mx.compile kernel).
|
|
877
|
+
// One fork drives both env vars; qwen3/qwen3.5/universal compile unconditionally,
|
|
878
|
+
// so this toggles gemma geglu + minicpm5 swiglu (the only unfused-capable sites).
|
|
879
|
+
set("MLX_BUN_COMPILED_GEGLU", pick("compiled-activations", p.compiledAct));
|
|
880
|
+
set("MLX_BUN_COMPILED_SWIGLU", pick("compiled-activations", p.compiledAct));
|
|
778
881
|
if (kv === "off") return { kvQuant: "off" };
|
|
779
882
|
if (kv === "config") return { kvQuant: "config" };
|
|
780
883
|
if (kv) {
|
|
884
|
+
// TurboQuant (docs/design/turboquant-kv.md): a distinct axis from the
|
|
885
|
+
// affine kvQuant above — dequantize-on-fetch, stock unfused ops.sdpa,
|
|
886
|
+
// no kvQuant value at all (mutually exclusive with kvBits/kvConfig).
|
|
887
|
+
let turboQuant: TurboQuantScheme | null;
|
|
888
|
+
try {
|
|
889
|
+
turboQuant = parseTurboQuantScheme(kv);
|
|
890
|
+
} catch (e) {
|
|
891
|
+
console.error((e as Error).message);
|
|
892
|
+
process.exit(1);
|
|
893
|
+
}
|
|
894
|
+
if (turboQuant) return { turboQuant };
|
|
781
895
|
const bits = Number(kv);
|
|
782
|
-
if (![4, 8].includes(bits)) { console.error(`--kv-quant expects config|off|4|8 (got "${kv}")`); process.exit(1); }
|
|
896
|
+
if (![4, 8].includes(bits)) { console.error(`--kv-quant expects config|off|4|8|turbo (got "${kv}")`); process.exit(1); }
|
|
783
897
|
return { kvQuant: bits };
|
|
784
898
|
}
|
|
785
|
-
return
|
|
899
|
+
return { kvQuant: p.kv };
|
|
786
900
|
}
|
|
787
901
|
|
|
788
902
|
function serverRuntimeFlags(): { port: number; serverOptions: import("./server").ServerOptions } {
|
|
@@ -794,14 +908,26 @@ function serverRuntimeFlags(): { port: number; serverOptions: import("./server")
|
|
|
794
908
|
console.error(`--${name} expects on|off (got "${v}")`);
|
|
795
909
|
process.exit(1);
|
|
796
910
|
};
|
|
797
|
-
const route = applyDecodeRoute(); // --l1/--l2
|
|
911
|
+
const route = applyDecodeRoute(); // --l1/--l2 tier alias, with per-fork flags overriding
|
|
798
912
|
if (flag("force-wire")) process.env.MLX_BUN_FORCE_WIRE = "1";
|
|
913
|
+
// Remote image_url/audio_url media fetches block private/loopback/link-
|
|
914
|
+
// local destinations by default (SSRF — src/media-fetch.ts); this is the
|
|
915
|
+
// LAN-hosts escape hatch.
|
|
916
|
+
if (flag("allow-private-media")) process.env.MLX_BUN_ALLOW_PRIVATE_MEDIA = "1";
|
|
799
917
|
|
|
800
918
|
const serverOptions: import("./server").ServerOptions = {};
|
|
801
919
|
const budgetGB = Number(opt("memory-budget", "0"));
|
|
802
920
|
if (budgetGB > 0) serverOptions.memoryBudgetBytes = budgetGB * 1e9;
|
|
921
|
+
// Aggregate KV budget across batch rows (batching-perf-path P3 admission).
|
|
922
|
+
const kvBudgetGB = Number(opt("kv-budget", "0"));
|
|
923
|
+
if (kvBudgetGB > 0) serverOptions.kvBudgetBytes = kvBudgetGB * 1e9;
|
|
803
924
|
const pcRaw = opt("prompt-cache"); // null if absent; an explicit value (incl. 0) wins → `--prompt-cache 0` DISABLES the cache
|
|
804
925
|
if (pcRaw !== null) serverOptions.promptCacheBytes = Math.max(0, Number(pcRaw)) * 2 ** 30;
|
|
926
|
+
// Engine-child mode (runtime-isolation.md): listen on a unix socket
|
|
927
|
+
// instead of TCP — the isolated parent proxies here. Internal flag,
|
|
928
|
+
// spawned by --isolate; usable directly for socket-level integration.
|
|
929
|
+
const unixRaw = opt("unix");
|
|
930
|
+
if (unixRaw !== null) serverOptions.unixSocket = unixRaw;
|
|
805
931
|
// SSD cold tier (docs/design/ssd-kv-cold-tier.md) — off unless a dir is given.
|
|
806
932
|
const ssdDir = opt("ssd-cache");
|
|
807
933
|
if (ssdDir !== null) {
|
|
@@ -809,13 +935,21 @@ function serverRuntimeFlags(): { port: number; serverOptions: import("./server")
|
|
|
809
935
|
const maxRaw = opt("ssd-cache-max");
|
|
810
936
|
if (maxRaw !== null) serverOptions.ssdCacheMaxBytes = Math.max(1, Number(maxRaw)) * 2 ** 30;
|
|
811
937
|
if (flag("ssd-cache-verify")) serverOptions.ssdCacheVerify = true;
|
|
938
|
+
const demoteRaw = opt("ssd-demote-idle");
|
|
939
|
+
if (demoteRaw !== null) serverOptions.ssdDemoteIdleSec = Math.max(0, Number(demoteRaw));
|
|
940
|
+
} else {
|
|
941
|
+
// Refuse-loudly house rule: sub-flags of an absent tier must not vanish.
|
|
942
|
+
for (const f of ["ssd-cache-max", "ssd-demote-idle"] as const)
|
|
943
|
+
if (opt(f) !== null) console.warn(`--${f} has no effect without --ssd-cache — ignored`);
|
|
944
|
+
if (flag("ssd-cache-verify"))
|
|
945
|
+
console.warn("--ssd-cache-verify has no effect without --ssd-cache — ignored");
|
|
812
946
|
}
|
|
813
947
|
// --batch N: max concurrent requests batched through the mlx-lm-parity
|
|
814
948
|
// engine (N=1 = today's serial path). --decode-concurrency is accepted for
|
|
815
949
|
// drop-in compatibility with mlx_lm.server, but the semantics differ: there
|
|
816
950
|
// it caps per-BatchGenerator decode parallelism (default 32); in mlx-bun it
|
|
817
|
-
//
|
|
818
|
-
//
|
|
951
|
+
// caps continuous batching (default 8; `--batch 1` pins the serial path —
|
|
952
|
+
// the unified engine is bit-parity with it and costs ~nothing at B=1).
|
|
819
953
|
const batchRaw = opt("batch") ?? opt("decode-concurrency");
|
|
820
954
|
if (batchRaw !== null) {
|
|
821
955
|
const n = Number(batchRaw);
|
|
@@ -826,6 +960,19 @@ function serverRuntimeFlags(): { port: number; serverOptions: import("./server")
|
|
|
826
960
|
serverOptions.batch = n;
|
|
827
961
|
}
|
|
828
962
|
if (route.kvQuant !== undefined) serverOptions.kvQuant = route.kvQuant;
|
|
963
|
+
if (route.turboQuant !== undefined) serverOptions.turboQuant = route.turboQuant;
|
|
964
|
+
// --paged-kv (env: MLX_BUN_PAGED_KV=1): OPTIONAL vLLM-style block-pool KV
|
|
965
|
+
// storage, default off (docs/design/paged-kv-cache.md). Serial-only in v1:
|
|
966
|
+
// with no explicit --batch, pin --batch 1 (the default is 8); an explicit
|
|
967
|
+
// --batch N>1 is refused loudly by createServer rather than downgraded.
|
|
968
|
+
if (flag("paged-kv") || process.env.MLX_BUN_PAGED_KV === "1") {
|
|
969
|
+
const bsRaw = opt("paged-kv-block-size");
|
|
970
|
+
serverOptions.pagedKv = bsRaw !== null ? { blockSize: Number(bsRaw) } : {};
|
|
971
|
+
if (batchRaw === null) {
|
|
972
|
+
serverOptions.batch = 1;
|
|
973
|
+
console.log("[paged-kv] serial-only in v1 — pinning --batch 1");
|
|
974
|
+
}
|
|
975
|
+
}
|
|
829
976
|
// Bind loopback unless asked otherwise (mlx_lm.server parity); --host
|
|
830
977
|
// 0.0.0.0 is the explicit opt-in for LAN exposure. The chat-UI open and
|
|
831
978
|
// `mlx-bun pi` attach both go through localhost, so loopback-only is
|
|
@@ -876,6 +1023,39 @@ function serverRuntimeFlags(): { port: number; serverOptions: import("./server")
|
|
|
876
1023
|
return { port: Number(opt("port", "8080")), serverOptions };
|
|
877
1024
|
}
|
|
878
1025
|
|
|
1026
|
+
/** Direct Colibri runtime defaults shared by `serve` and the embedded `pi`
|
|
1027
|
+
* server. Other model families ignore this load-only option block. */
|
|
1028
|
+
function glm52RuntimeFlags(
|
|
1029
|
+
serverOptions: import("./server").ServerOptions,
|
|
1030
|
+
): import("./model/factory").Glm52RuntimeOpenOptions {
|
|
1031
|
+
const mtpRaw = opt("mtp");
|
|
1032
|
+
let enableMtp: boolean | undefined;
|
|
1033
|
+
if (mtpRaw !== null) {
|
|
1034
|
+
if (["on", "1", "true"].includes(mtpRaw)) enableMtp = true;
|
|
1035
|
+
else if (["off", "0", "false"].includes(mtpRaw)) enableMtp = false;
|
|
1036
|
+
else {
|
|
1037
|
+
console.error(`--mtp expects on|off (got "${mtpRaw}")`);
|
|
1038
|
+
process.exit(1);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
const contextRaw = opt("context-length");
|
|
1042
|
+
let contextTokens: number | undefined;
|
|
1043
|
+
if (contextRaw !== null) {
|
|
1044
|
+
contextTokens = Number(contextRaw);
|
|
1045
|
+
if (!Number.isSafeInteger(contextTokens) || contextTokens < 1) {
|
|
1046
|
+
console.error(`--context-length expects an integer >= 1 (got "${contextRaw}")`);
|
|
1047
|
+
process.exit(1);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
return {
|
|
1051
|
+
memoryBudgetBytes: serverOptions.memoryBudgetBytes,
|
|
1052
|
+
batchSize: serverOptions.batch ?? 8,
|
|
1053
|
+
maxGenerationTokens: serverOptions.defaultMaxTokens ?? 128,
|
|
1054
|
+
...(contextTokens !== undefined ? { contextTokens } : {}),
|
|
1055
|
+
...(enableMtp !== undefined ? { enableMtp } : {}),
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
|
|
879
1059
|
/** `--adapter <dir>` (alias `--adapter-path`, mlx_lm.server's spelling):
|
|
880
1060
|
* mount a LoRA adapter at startup — the same machinery as POST /v1/adapters —
|
|
881
1061
|
* and make it the default for requests that don't select one (a request's
|
|
@@ -904,11 +1084,11 @@ async function mountStartupAdapter(
|
|
|
904
1084
|
|
|
905
1085
|
/** One-line summary of the active runtime levers for the ready card. */
|
|
906
1086
|
function runtimeSummary(o: import("./server").ServerOptions): string {
|
|
907
|
-
const kv = o.
|
|
1087
|
+
const kv = o.turboQuant ? `turbo-k${o.turboQuant.kBits}v${o.turboQuant.vBits}`
|
|
1088
|
+
: o.kvQuant === "off" ? "off" : typeof o.kvQuant === "number" ? `kv${o.kvQuant}` : "config";
|
|
908
1089
|
const lever = (env: string, dflt: string) => process.env[env] ?? dflt;
|
|
909
1090
|
return `kv-quant ${kv} · compiled-decode ${lever("MLX_BUN_COMPILED_DECODE", "1") === "1" ? "on" : "off"}` +
|
|
910
|
-
|
|
911
|
-
(lever("MLX_BUN_FUSED_DECODE", "0") === "1" ? " · fused-decode on" : "") +
|
|
1091
|
+
(lever("MLX_BUN_COMPILED_GEGLU", "1") === "0" ? " · compiled-activations off" : "") +
|
|
912
1092
|
(o.batch && o.batch > 1 ? ` · batch ${o.batch}` : "") +
|
|
913
1093
|
(o.defaultThinking !== undefined ? ` · thinking ${o.defaultThinking ? "on" : "off"}` : "") +
|
|
914
1094
|
(o.defaultTemperature !== undefined ? ` · temp ${o.defaultTemperature}` : "") +
|
|
@@ -958,7 +1138,7 @@ async function resolveModelAuto(query: string | null): Promise<{ m: import("./re
|
|
|
958
1138
|
await reg.scan();
|
|
959
1139
|
if (recRepo !== starterRepo) {
|
|
960
1140
|
console.log(` ${style.dim(`downloading ${recRepo} (recommended for this Mac) in the background —`)}`);
|
|
961
|
-
console.log(` ${style.dim("it becomes the default next run · progress
|
|
1141
|
+
console.log(` ${style.dim("it becomes the default next run · progress in the web app's Status view · resumable")}`);
|
|
962
1142
|
downloadModel(recRepo)
|
|
963
1143
|
.then(async () => {
|
|
964
1144
|
await new Registry().scan();
|
|
@@ -1201,9 +1381,69 @@ switch (cmd) {
|
|
|
1201
1381
|
if (reg.list().length === 0) await reg.scan();
|
|
1202
1382
|
const m = reg.resolve(query);
|
|
1203
1383
|
const config = await loadModelConfig(m.path);
|
|
1204
|
-
const ctx = Number(opt("ctx", "8192"));
|
|
1205
|
-
const r = fit(config, m.sizeBytes, ctx, thisMachine(), undefined, m.expertsBytes);
|
|
1206
1384
|
const { box, table, style, h1, gradient } = await import("./tui");
|
|
1385
|
+
const glm = isGlm52Config(config);
|
|
1386
|
+
const ctx = Number(opt("ctx", glm ? "4096" : "8192"));
|
|
1387
|
+
if (glm) {
|
|
1388
|
+
const machine = thisMachine();
|
|
1389
|
+
let plan;
|
|
1390
|
+
try {
|
|
1391
|
+
plan = await planGlm52MemoryForArtifact(m.path, {
|
|
1392
|
+
machineBytes: machine.ramBytes,
|
|
1393
|
+
processLimitBytes: Math.min(
|
|
1394
|
+
GLM52_G5_DEFAULT_PROCESS_LIMIT_BYTES,
|
|
1395
|
+
machine.ramBytes,
|
|
1396
|
+
),
|
|
1397
|
+
contextTokens: ctx,
|
|
1398
|
+
maxGenerationTokens: Math.min(
|
|
1399
|
+
ctx,
|
|
1400
|
+
GLM52_G5_DEFAULT_MAX_GENERATION_TOKENS,
|
|
1401
|
+
),
|
|
1402
|
+
batchSize: 1,
|
|
1403
|
+
enableMtp: true,
|
|
1404
|
+
});
|
|
1405
|
+
} catch (error) {
|
|
1406
|
+
console.error(
|
|
1407
|
+
`GLM-5.2 resource plan refused: ` +
|
|
1408
|
+
`${error instanceof Error ? error.message : String(error)}`,
|
|
1409
|
+
);
|
|
1410
|
+
process.exitCode = 1;
|
|
1411
|
+
break;
|
|
1412
|
+
}
|
|
1413
|
+
const gib = (bytes: number): string => `${(bytes / 2 ** 30).toFixed(2)} GiB`;
|
|
1414
|
+
const li = plan.lineItems;
|
|
1415
|
+
const kvBytes = li.targetKvBytes + li.mtpKvBytes;
|
|
1416
|
+
const otherBytes = plan.plannedProcessBytes - li.residentWeightsBytes -
|
|
1417
|
+
li.mainExpertSlabBytes - li.mtpExpertSlabBytes - kvBytes;
|
|
1418
|
+
h1("will it fit?");
|
|
1419
|
+
console.log(` ${style.bold(m.repoId)} ${style.dim(`@ ${ctx.toLocaleString()} context · streamed GLM plan`)}`);
|
|
1420
|
+
console.log();
|
|
1421
|
+
box([
|
|
1422
|
+
`artifact on disk ${gib(m.sizeBytes).padStart(10)} ${style.dim("(streamed, not resident)")}`,
|
|
1423
|
+
`resident weights ${gib(li.residentWeightsBytes).padStart(10)}`,
|
|
1424
|
+
`main expert slab ${gib(li.mainExpertSlabBytes).padStart(10)}`,
|
|
1425
|
+
`MTP expert slab ${gib(li.mtpExpertSlabBytes).padStart(10)}`,
|
|
1426
|
+
`target + MTP KV ${gib(kvBytes).padStart(10)}`,
|
|
1427
|
+
`runtime reserves ${gib(otherBytes).padStart(10)}`,
|
|
1428
|
+
`process plan ${gib(plan.plannedProcessBytes).padStart(10)} ${style.dim(`of ${gib(plan.processLimitBytes)}`)} ${style.green(style.bold("FITS"))}`,
|
|
1429
|
+
`macOS headroom ${gib(plan.machineHeadroomBytes).padStart(10)}`,
|
|
1430
|
+
"",
|
|
1431
|
+
`max safe context ${style.bold(plan.contextTokens.toLocaleString())} tokens`,
|
|
1432
|
+
`max generation ${style.bold(plan.maxGenerationTokens.toLocaleString())} tokens`,
|
|
1433
|
+
`measured warm ${gradient(`${GLM52_G5_MEASURED_WARM_DECODE_TPS.toFixed(3)} tok/s`)} ${style.dim("(quality-preserving, MTP on)")}`,
|
|
1434
|
+
`direct oracle ${GLM52_G5_DIRECT_ORACLE_WARM_DECODE_TPS.toFixed(2)} tok/s`,
|
|
1435
|
+
`aspirational ${GLM52_G5_ASPIRATIONAL_DECODE_TPS.toFixed(1)} tok/s ${style.dim("(not a fit prediction or release gate)")}`,
|
|
1436
|
+
]);
|
|
1437
|
+
if (flag("skus")) {
|
|
1438
|
+
console.log(style.dim(
|
|
1439
|
+
" GLM-5.2's direct-container preset is validated on the M1 Max 32 GB; " +
|
|
1440
|
+
"the generic resident-weight SKU projection does not apply.",
|
|
1441
|
+
));
|
|
1442
|
+
}
|
|
1443
|
+
console.log();
|
|
1444
|
+
break;
|
|
1445
|
+
}
|
|
1446
|
+
const r = fit(config, m.sizeBytes, ctx, thisMachine(), undefined, m.expertsBytes);
|
|
1207
1447
|
h1("will it fit?");
|
|
1208
1448
|
console.log(` ${style.bold(m.repoId)} ${style.dim(`@ ${ctx.toLocaleString()} context · this machine`)}`);
|
|
1209
1449
|
console.log();
|
|
@@ -1248,7 +1488,7 @@ switch (cmd) {
|
|
|
1248
1488
|
console.log(style.dim(" no command given — starting the server · mlx-bun --help for all commands"));
|
|
1249
1489
|
const rt = serverRuntimeFlags();
|
|
1250
1490
|
// Friendly collision check before loading gigabytes of weights.
|
|
1251
|
-
{
|
|
1491
|
+
if (!rt.serverOptions.unixSocket) {
|
|
1252
1492
|
const { probeServer } = await import("./harness-pi");
|
|
1253
1493
|
const running = await probeServer(`http://localhost:${rt.port}/v1`);
|
|
1254
1494
|
if (running) {
|
|
@@ -1258,6 +1498,71 @@ switch (cmd) {
|
|
|
1258
1498
|
process.exit(1);
|
|
1259
1499
|
}
|
|
1260
1500
|
}
|
|
1501
|
+
// Runtime isolation (--isolate, runtime-isolation.md): THIS process
|
|
1502
|
+
// becomes a pure reactor — it binds the TCP port and reverse-proxies to
|
|
1503
|
+
// an engine CHILD (the full server on a unix socket, spawned from the
|
|
1504
|
+
// same argv minus parent-only flags). Zero MLX calls in the parent: the
|
|
1505
|
+
// UI stays instant under any GPU load, and an engine crash respawns
|
|
1506
|
+
// without taking the session down.
|
|
1507
|
+
if (!flag("isolate") && opt("model-pool") !== null)
|
|
1508
|
+
console.warn("--model-pool has no effect without --isolate (child-per-model pool) — ignored");
|
|
1509
|
+
if (flag("isolate")) {
|
|
1510
|
+
const { startProxyServer, engineArgvForModel, defaultSocketPath } = await import("./serve/isolate");
|
|
1511
|
+
const sock = defaultSocketPath();
|
|
1512
|
+
// Re-spawn ourselves: compiled binary = execPath alone (argv[1] is a
|
|
1513
|
+
// $bunfs virtual path); dev = bun + the script path.
|
|
1514
|
+
const selfArgv = process.argv[1] && !process.argv[1].includes("$bunfs")
|
|
1515
|
+
? [process.execPath, process.argv[1]]
|
|
1516
|
+
: [process.execPath];
|
|
1517
|
+
const rawArgs = process.argv.slice(2);
|
|
1518
|
+
// Resolve the DEFAULT model in the parent (registry lookup — no
|
|
1519
|
+
// weights load) so the pool has its key and the child gets a pinned
|
|
1520
|
+
// --model path instead of re-running fuzzy resolution.
|
|
1521
|
+
const { m: m0 } = await resolveModelAuto(opt("model") ?? positional(0) ?? opt("query"));
|
|
1522
|
+
// STRICT resolver for the pool (P2): only EXACT /v1/models ids
|
|
1523
|
+
// switch models. Fuzzy matching here would hijack drop-in clients
|
|
1524
|
+
// that send model:"gpt-4"-style strings (which must keep today's
|
|
1525
|
+
// ignored-field semantics and ride the default engine).
|
|
1526
|
+
const { Registry } = await import("./registry");
|
|
1527
|
+
const poolResolve = (query: string): { repoId: string; path: string } | null => {
|
|
1528
|
+
if (query === m0.repoId) return { repoId: m0.repoId, path: m0.path };
|
|
1529
|
+
try {
|
|
1530
|
+
const reg = new Registry();
|
|
1531
|
+
try {
|
|
1532
|
+
const hit = reg.listCanonical().find((r) => r.repoId === query);
|
|
1533
|
+
return hit ? { repoId: hit.repoId, path: hit.path } : null;
|
|
1534
|
+
} finally {
|
|
1535
|
+
reg.close();
|
|
1536
|
+
}
|
|
1537
|
+
} catch {
|
|
1538
|
+
return null;
|
|
1539
|
+
}
|
|
1540
|
+
};
|
|
1541
|
+
const poolMaxRaw = opt("model-pool");
|
|
1542
|
+
const poolMax = Math.max(1, Number(poolMaxRaw ?? 1) || 1);
|
|
1543
|
+
const { engine, pool } = startProxyServer({
|
|
1544
|
+
port: rt.port,
|
|
1545
|
+
...(rt.serverOptions.hostname ? { hostname: rt.serverOptions.hostname } : {}),
|
|
1546
|
+
engine: {
|
|
1547
|
+
argv: [...selfArgv, ...engineArgvForModel(rawArgs, sock, m0.path)],
|
|
1548
|
+
socketPath: sock,
|
|
1549
|
+
},
|
|
1550
|
+
pool: {
|
|
1551
|
+
rawArgs, selfArgv, poolMax,
|
|
1552
|
+
resolve: poolResolve,
|
|
1553
|
+
defaultKey: m0.repoId,
|
|
1554
|
+
},
|
|
1555
|
+
});
|
|
1556
|
+
const shownHost0 = rt.serverOptions.hostname ?? "localhost";
|
|
1557
|
+
console.log(style.dim(` isolated mode: engine child starting (socket ${sock})`));
|
|
1558
|
+
console.log(` ${style.green("●")} ${style.bold("proxy up")} http://${shownHost0}:${rt.port} ${style.dim(`· ${m0.repoId} loading behind it · model pool ${poolMax}`)}`);
|
|
1559
|
+
const onExit = () => { pool?.stopAll(); engine.stop(); process.exit(0); };
|
|
1560
|
+
process.on("SIGINT", onExit);
|
|
1561
|
+
process.on("SIGTERM", onExit);
|
|
1562
|
+
await engine.ready;
|
|
1563
|
+
console.log(` ${style.green("●")} ${style.bold("engine ready")} ${style.dim(`pid ${engine.pid}`)}`);
|
|
1564
|
+
await new Promise(() => {}); // serve until killed
|
|
1565
|
+
}
|
|
1261
1566
|
// --model <path-or-query> is the explicit override (mlx_lm.server's
|
|
1262
1567
|
// spelling — previously accepted-but-IGNORED here, which silently
|
|
1263
1568
|
// auto-picked the wrong model). A directory with config.json loads
|
|
@@ -1285,10 +1590,66 @@ switch (cmd) {
|
|
|
1285
1590
|
sOff.done(`experts mmap'd ${style.dim(dir)} ${style.dim("· phys_footprint ≈ core")}`);
|
|
1286
1591
|
}
|
|
1287
1592
|
}
|
|
1593
|
+
// Speculative decoding (mlx_lm.server parity): --draft-model <path|query>
|
|
1594
|
+
// resolves like the main model; --num-draft-tokens defaults to 3 (the
|
|
1595
|
+
// server's default upstream — mlx_lm.generate's is 2). Serial-lane-only:
|
|
1596
|
+
// with --batch N, a mounted draft routes every request serial (upstream
|
|
1597
|
+
// is_batchable = draft is None).
|
|
1598
|
+
const draftQuery = opt("draft-model");
|
|
1599
|
+
let draftModelDir: string | undefined;
|
|
1600
|
+
if (draftQuery !== null) {
|
|
1601
|
+
const { m: dm } = await resolveModelAuto(draftQuery);
|
|
1602
|
+
draftModelDir = dm.path;
|
|
1603
|
+
}
|
|
1604
|
+
const numDraftRaw = opt("num-draft-tokens");
|
|
1605
|
+
const numDraftTokens = numDraftRaw !== null ? Number(numDraftRaw) : undefined;
|
|
1606
|
+
if (numDraftTokens !== undefined && (!Number.isInteger(numDraftTokens) || numDraftTokens < 1)) {
|
|
1607
|
+
console.error(`--num-draft-tokens expects an integer >= 1 (got "${numDraftRaw}")`);
|
|
1608
|
+
process.exit(1);
|
|
1609
|
+
}
|
|
1610
|
+
const draftKindRaw = opt("draft-kind");
|
|
1611
|
+
const draftKind = (draftKindRaw ?? undefined) as
|
|
1612
|
+
| "dspark" | "deepspec" | "assistant" | "two-model" | "ngram" | undefined;
|
|
1613
|
+
if (draftKind !== undefined && !["dspark", "deepspec", "assistant", "two-model", "ngram"].includes(draftKind)) {
|
|
1614
|
+
console.error(`--draft-kind expects two-model|assistant|dspark|deepspec|ngram (got "${draftKindRaw}")`);
|
|
1615
|
+
process.exit(1);
|
|
1616
|
+
}
|
|
1617
|
+
// Prompt-lookup window bounds (ngram kind only; validated here so a typo
|
|
1618
|
+
// fails the launch, not the first request).
|
|
1619
|
+
const ngramInt = (name: string): number | undefined => {
|
|
1620
|
+
const raw = opt(name);
|
|
1621
|
+
if (raw === null) return undefined;
|
|
1622
|
+
const v = Number(raw);
|
|
1623
|
+
if (!Number.isInteger(v) || v < 1) {
|
|
1624
|
+
console.error(`--${name} expects an integer >= 1 (got "${raw}")`);
|
|
1625
|
+
process.exit(1);
|
|
1626
|
+
}
|
|
1627
|
+
return v;
|
|
1628
|
+
};
|
|
1629
|
+
const ngramMax = ngramInt("ngram-max");
|
|
1630
|
+
const ngramMin = ngramInt("ngram-min");
|
|
1631
|
+
if (ngramMax !== undefined && ngramMin !== undefined && ngramMin > ngramMax) {
|
|
1632
|
+
console.error(`--ngram-min (${ngramMin}) must be <= --ngram-max (${ngramMax})`);
|
|
1633
|
+
process.exit(1);
|
|
1634
|
+
}
|
|
1635
|
+
if ((ngramMax !== undefined || ngramMin !== undefined) && draftKind !== "ngram")
|
|
1636
|
+
console.warn("--ngram-max/--ngram-min only apply with --draft-kind ngram — ignored");
|
|
1288
1637
|
const sLoad = step("loading weights");
|
|
1289
1638
|
const t0 = performance.now();
|
|
1290
|
-
const ctx = await loadContext(m.path, m.repoId, {
|
|
1291
|
-
|
|
1639
|
+
const ctx = await loadContext(m.path, m.repoId, {
|
|
1640
|
+
memoryBudgetBytes: rt.serverOptions.memoryBudgetBytes,
|
|
1641
|
+
glm: glm52RuntimeFlags(rt.serverOptions),
|
|
1642
|
+
...(draftModelDir || draftKind === "ngram"
|
|
1643
|
+
? {
|
|
1644
|
+
...(draftModelDir ? { draftModelDir } : {}),
|
|
1645
|
+
numDraftTokens,
|
|
1646
|
+
...(draftKind ? { draftKind } : {}),
|
|
1647
|
+
...(ngramMax !== undefined ? { ngramMax } : {}),
|
|
1648
|
+
...(ngramMin !== undefined ? { ngramMin } : {}),
|
|
1649
|
+
}
|
|
1650
|
+
: {}),
|
|
1651
|
+
});
|
|
1652
|
+
sLoad.done(`weights loaded ${style.dim(`in ${(performance.now() - t0).toFixed(0)} ms`)}${draftModelDir ? style.dim(` · draft: ${draftModelDir.split("/").filter(Boolean).at(-1)}`) : draftKind === "ngram" ? style.dim(" · draft: ngram (prompt lookup)") : ""}`);
|
|
1292
1653
|
await mountStartupAdapter(ctx, rt.serverOptions);
|
|
1293
1654
|
const server = createServer(ctx, rt.port, { ...rt.serverOptions, owner: "serve" });
|
|
1294
1655
|
const shownHost = rt.serverOptions.hostname ?? "localhost";
|
|
@@ -1321,7 +1682,7 @@ switch (cmd) {
|
|
|
1321
1682
|
// three entry points (raw / OpenAI API / chat UI) differ only in how params
|
|
1322
1683
|
// are POPULATED; here every param is explicit. Decode-path levers mirror
|
|
1323
1684
|
// serve so you can pin the route (mlx-lm compat = --l1).
|
|
1324
|
-
// --l1/--l2
|
|
1685
|
+
// --l1/--l2 + per-fork overrides set the decode env levers; the
|
|
1325
1686
|
// returned KV scheme is applied below so `generate --l2` runs the same
|
|
1326
1687
|
// quantized-KV route (and produces the same tokens) as `serve --l2`.
|
|
1327
1688
|
const route = applyDecodeRoute();
|
|
@@ -1330,18 +1691,24 @@ switch (cmd) {
|
|
|
1330
1691
|
if (!prompt) {
|
|
1331
1692
|
console.error('usage: mlx-bun generate [query] --prompt "…" [--raw] [--max-tokens N]');
|
|
1332
1693
|
console.error(' sampling: --temperature N (alias --temp) --top-p N --top-k N --seed N');
|
|
1333
|
-
console.error(' decode path: --l1 (= mlx-lm compat) / --l2
|
|
1694
|
+
console.error(' decode path: --l1 (= mlx-lm compat, the default) / --l2, or per-fork flags');
|
|
1334
1695
|
process.exit(1);
|
|
1335
1696
|
}
|
|
1336
1697
|
const { loadTaskModel, generateText } = await import("./eval/runner");
|
|
1337
|
-
const
|
|
1698
|
+
const maxTokens = Number(opt("max-tokens", "256"));
|
|
1699
|
+
const tm = await loadTaskModel(
|
|
1700
|
+
positional(0) ?? opt("query") ?? "",
|
|
1701
|
+
undefined,
|
|
1702
|
+
{ maxGenerationTokens: maxTokens, enableMtp: false },
|
|
1703
|
+
);
|
|
1338
1704
|
// Resolve the tier/--kv-quant KV scheme exactly the way serve does
|
|
1339
1705
|
// (server.ts kvScheme): "config" = the model's kv_config.json (absent →
|
|
1340
1706
|
// bf16, same silent fallback as serve), N = uniform bits from decode
|
|
1341
1707
|
// start. Unset (no tier, no --kv-quant) keeps generate's historical
|
|
1342
1708
|
// default: bf16 bit-exact greedy (generateText's parity path).
|
|
1343
1709
|
const kvScheme =
|
|
1344
|
-
route.
|
|
1710
|
+
route.turboQuant ? { turboQuant: route.turboQuant, quantizedKvStart: 0 }
|
|
1711
|
+
: route.kvQuant === "off" ? {}
|
|
1345
1712
|
: route.kvQuant === "config"
|
|
1346
1713
|
? (tm.config.kvQuant?.length ? { kvConfig: tm.config.kvQuant } : {})
|
|
1347
1714
|
: typeof route.kvQuant === "number"
|
|
@@ -1349,7 +1716,7 @@ switch (cmd) {
|
|
|
1349
1716
|
: undefined;
|
|
1350
1717
|
const num = (n: string): number | undefined => { const v = opt(n); return v == null ? undefined : Number(v); };
|
|
1351
1718
|
const text = await generateText(tm, prompt, {
|
|
1352
|
-
maxTokens
|
|
1719
|
+
maxTokens,
|
|
1353
1720
|
useChat: !flag("raw"),
|
|
1354
1721
|
...(kvScheme !== undefined ? { kvScheme } : {}),
|
|
1355
1722
|
sampler: { temperature: num("temperature") ?? num("temp"), topP: num("top-p"), topK: num("top-k"), seed: num("seed") },
|
|
@@ -1427,7 +1794,7 @@ switch (cmd) {
|
|
|
1427
1794
|
case "benchmark": {
|
|
1428
1795
|
const { banner, step, box, style } = await import("./tui");
|
|
1429
1796
|
banner(pkg.version);
|
|
1430
|
-
serverRuntimeFlags(); // env levers (--compiled-decode, --
|
|
1797
|
+
serverRuntimeFlags(); // env levers (--compiled-decode, --fused-sdpa, ...) apply to the run
|
|
1431
1798
|
const { m, picked } = await resolveModelAuto(opt("model") ?? positional(0) ?? opt("query"));
|
|
1432
1799
|
const tokens = Number(opt("tokens", "256"));
|
|
1433
1800
|
const runs = Number(opt("runs", "3"));
|
|
@@ -1462,7 +1829,16 @@ switch (cmd) {
|
|
|
1462
1829
|
const rendered = template.render([{ role: "user", content: userMsg }]);
|
|
1463
1830
|
const ids = tok.encode(rendered);
|
|
1464
1831
|
const promptIds = ids[0] === ids[1] && ids[0] === tok.bosTokenId ? ids.slice(1) : ids;
|
|
1465
|
-
|
|
1832
|
+
let turboScheme: TurboQuantScheme | null;
|
|
1833
|
+
try {
|
|
1834
|
+
turboScheme = parseTurboQuantScheme(kvMode);
|
|
1835
|
+
} catch (e) {
|
|
1836
|
+
console.error((e as Error).message);
|
|
1837
|
+
process.exit(1);
|
|
1838
|
+
}
|
|
1839
|
+
const kvOptions = turboScheme
|
|
1840
|
+
? { turboQuant: turboScheme, quantizedKvStart: 0 }
|
|
1841
|
+
: kvMode === "config"
|
|
1466
1842
|
? (() => {
|
|
1467
1843
|
if (!config.kvQuant?.length) { console.error("model has no kv_config.json (--kv-quant config)"); process.exit(1); }
|
|
1468
1844
|
return { kvConfig: config.kvQuant };
|
|
@@ -1516,7 +1892,7 @@ switch (cmd) {
|
|
|
1516
1892
|
`decode ${style.green(style.bold(`${median(decodes).toFixed(1)} tok/s`))} ${style.dim(`median of ${runs} · [${decodes.map((d) => d.toFixed(1)).join(", ")}]`)}`,
|
|
1517
1893
|
`prefill ${style.bold(`${median(prefills).toFixed(0)} tok/s`)} ${style.dim(`· ${lastStats.promptTokens} prompt tokens`)}`,
|
|
1518
1894
|
`peak mem ${style.bold(gb(peak))} ${style.dim("(generation only)")}`,
|
|
1519
|
-
`levers ${style.dim(runtimeSummary({ kvQuant: kvMode === "off" ? "off" : kvMode === "config" ? undefined : Number(kvMode) }))}`,
|
|
1895
|
+
`levers ${style.dim(runtimeSummary(turboScheme ? { turboQuant: turboScheme } : { kvQuant: kvMode === "off" ? "off" : kvMode === "config" ? undefined : Number(kvMode) }))}`,
|
|
1520
1896
|
"",
|
|
1521
1897
|
style.dim("recorded to the eval DB — see mlx-bun evals"),
|
|
1522
1898
|
]);
|
|
@@ -1616,14 +1992,17 @@ switch (cmd) {
|
|
|
1616
1992
|
// (src/pi-terminal.ts) — pi is bundled, nothing to install. Users who
|
|
1617
1993
|
// already run their own pi connect it with `mlx-bun harness pi`.
|
|
1618
1994
|
const OURS_VAL = new Set([
|
|
1619
|
-
"--query", "-q", "--model", "--port", "--host", "--memory-budget", "--prompt-cache",
|
|
1620
|
-
"--ssd-cache", "--ssd-cache-max", "--kv-quant",
|
|
1995
|
+
"--query", "-q", "--model", "--port", "--host", "--memory-budget", "--kv-budget", "--prompt-cache",
|
|
1996
|
+
"--ssd-cache", "--ssd-cache-max", "--ssd-demote-idle", "--kv-quant", "--unix",
|
|
1621
1997
|
"--batch", "--decode-concurrency", "--adapter", "--adapter-path",
|
|
1622
|
-
"--
|
|
1998
|
+
"--draft-model", "--num-draft-tokens", "--draft-kind", "--ngram-max", "--ngram-min",
|
|
1999
|
+
"--compiled-decode", "--compiled-activations", "--fused-sdpa", "--thinking",
|
|
1623
2000
|
"--temperature", "--temp", "--top-p", "--top-k", "--max-tokens",
|
|
2001
|
+
"--mtp", "--context-length",
|
|
1624
2002
|
"--hlg-sampling", "--hlg-width", "--hlg-shoulder", "--hlg-toe", "--hlg-pivot-offset",
|
|
2003
|
+
"--paged-kv-block-size",
|
|
1625
2004
|
]);
|
|
1626
|
-
const OURS_BOOL = new Set(["--force-wire", "--expert-offload", "--no-open", "--ssd-cache-verify", "--l1", "--l2", "--l3"]);
|
|
2005
|
+
const OURS_BOOL = new Set(["--force-wire", "--expert-offload", "--no-open", "--ssd-cache-verify", "--l1", "--l2", "--l3", "--isolate", "--paged-kv"]);
|
|
1627
2006
|
const passthrough: string[] = [];
|
|
1628
2007
|
for (let i = 1; i < argv.length; i++) {
|
|
1629
2008
|
const a = argv[i]!;
|
|
@@ -1685,7 +2064,10 @@ switch (cmd) {
|
|
|
1685
2064
|
const { createServer, loadContext } = await import("./server");
|
|
1686
2065
|
const sLoad = step(`loading ${m.repoId}`);
|
|
1687
2066
|
const t0 = performance.now();
|
|
1688
|
-
const ctx = await loadContext(m.path, m.repoId, {
|
|
2067
|
+
const ctx = await loadContext(m.path, m.repoId, {
|
|
2068
|
+
memoryBudgetBytes: rt.serverOptions.memoryBudgetBytes,
|
|
2069
|
+
glm: glm52RuntimeFlags(rt.serverOptions),
|
|
2070
|
+
});
|
|
1689
2071
|
await mountStartupAdapter(ctx, rt.serverOptions);
|
|
1690
2072
|
createServer(ctx, port, { ...rt.serverOptions, owner: "pi-session" });
|
|
1691
2073
|
startedServer = true;
|
|
@@ -1813,14 +2195,7 @@ switch (cmd) {
|
|
|
1813
2195
|
|
|
1814
2196
|
const { m, picked } = await resolveModelAuto(query);
|
|
1815
2197
|
|
|
1816
|
-
// Detect Gemma/e4b and set its required training env flags BEFORE the
|
|
1817
|
-
// trainer is imported (perfKernelEnabled / fused-gelu read them lazily at
|
|
1818
|
-
// forward time, so setting them here — like the launcher — takes effect).
|
|
1819
2198
|
const isGemma = (await Bun.file(`${m.path}/config.json`).text()).toLowerCase().includes("gemma");
|
|
1820
|
-
if (isGemma) {
|
|
1821
|
-
process.env.MLX_BUN_PERF_KERNEL ??= "0";
|
|
1822
|
-
process.env.MLX_BUN_FUSED_GELU ??= "0";
|
|
1823
|
-
}
|
|
1824
2199
|
|
|
1825
2200
|
const modelTag = isGemma ? "e4b" : "cpm5";
|
|
1826
2201
|
const adapter = opt("adapter") ?? `${process.env.HOME}/.cache/mlx-bun/mlx-bun-finetunes/${method}-${modelTag}`;
|
|
@@ -1874,7 +2249,7 @@ switch (cmd) {
|
|
|
1874
2249
|
if (!ds.ok) { console.error(`dataset: ${ds.error}`); process.exit(1); }
|
|
1875
2250
|
|
|
1876
2251
|
const planLines = [
|
|
1877
|
-
`${style.green("●")} ${style.bold(`train ${method}`)} ${style.dim(`· ${m.repoId}${picked ? " (auto-picked)" : ""}${isGemma ? " · e4b
|
|
2252
|
+
`${style.green("●")} ${style.bold(`train ${method}`)} ${style.dim(`· ${m.repoId}${picked ? " (auto-picked)" : ""}${isGemma ? " · e4b defaults" : ""}`)}`,
|
|
1878
2253
|
"",
|
|
1879
2254
|
`data ${style.bold(`${ds.n_train} train`)}${ds.n_valid ? ` · ${ds.n_valid} valid` : ""} ${style.dim(`· format ${ds.format}`)}`,
|
|
1880
2255
|
`loop ${style.dim(`iters ${iters} · lr ${cfg.learning_rate} · rank ${cfg.rank} · scale ${cfg.scale} · seq ${seq} · batch ${cfg.batch_size}`)}`,
|
|
@@ -2258,11 +2633,6 @@ switch (cmd) {
|
|
|
2258
2633
|
if (reg.list().length === 0) await reg.scan();
|
|
2259
2634
|
modelDir = reg.resolve(modelArg).path;
|
|
2260
2635
|
}
|
|
2261
|
-
// Gemma/e4b: same forward env flags the trainer sets (full-sequence pass).
|
|
2262
|
-
if ((await Bun.file(`${modelDir}/config.json`).text()).toLowerCase().includes("gemma")) {
|
|
2263
|
-
process.env.MLX_BUN_PERF_KERNEL ??= "0";
|
|
2264
|
-
process.env.MLX_BUN_FUSED_GELU ??= "0";
|
|
2265
|
-
}
|
|
2266
2636
|
const sLoad = step(`loading ${modelDir}`);
|
|
2267
2637
|
const { Weights } = await import("./weights");
|
|
2268
2638
|
const { createModel } = await import("./model/factory");
|