mlx-bun 0.5.0 → 1.0.0
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 +1449 -109
- package/THIRD_PARTY_NOTICES.md +882 -0
- package/bin/mlx-bun.convert.mjs +4 -0
- package/bin/mlx-bun.fuse.mjs +4 -0
- package/bin/mlx-bun.generate.mjs +4 -0
- package/bin/mlx-bun.lora.mjs +4 -0
- package/bin/mlx-bun.mjs +13 -21
- package/bin/mlx-bun.server.mjs +4 -0
- package/bin/mlx-bun.upload.mjs +4 -0
- package/dist/web/app.js +9584 -0
- package/package.json +50 -53
- package/src/cli/ane-install.ts +76 -0
- package/src/cli/args.ts +179 -0
- package/src/cli/engine-entry.ts +68 -0
- package/src/cli/inference.ts +214 -0
- package/src/cli/job-entry.ts +97 -0
- package/src/cli/library-host.ts +117 -0
- package/src/cli/main.ts +57 -0
- package/src/cli/memory-engine.ts +163 -0
- package/src/cli/memory.ts +36 -0
- package/src/cli/mlx-lm-aliases.ts +309 -0
- package/src/cli/model-choice.ts +33 -0
- package/src/cli/model-selection.ts +84 -0
- package/src/cli/module-host.ts +11 -0
- package/src/cli/module-verbs.ts +50 -0
- package/src/cli/numerical-policy.ts +12 -0
- package/src/cli/perplexity.ts +89 -0
- package/src/cli/serve-host.ts +423 -0
- package/src/cli/serve-isolated.ts +217 -0
- package/src/cli/serve-options.ts +106 -0
- package/src/cli/serve-state.ts +205 -0
- package/src/cli/serve.ts +500 -0
- package/src/cli/served-model-host.ts +102 -0
- package/src/cli/server-entry.ts +148 -0
- package/src/cli/serving-unit.ts +177 -0
- package/src/{tui.ts → cli/terminal.ts} +13 -8
- package/src/cli/worker-adapters.ts +34 -0
- package/src/cli/worker-entry.ts +272 -0
- package/src/cli/worker-unit.ts +179 -0
- package/src/engine/adapter-operation.ts +26 -0
- package/src/engine/cache-services.ts +114 -0
- package/src/engine/completion.ts +39 -0
- package/src/engine/generation-gateway.ts +358 -0
- package/src/engine/index.ts +59 -0
- package/src/engine/kv-budget.ts +120 -0
- package/src/engine/media-preparation.ts +232 -0
- package/src/engine/model-binding.ts +24 -0
- package/src/engine/model-host.ts +513 -0
- package/src/engine/model-serving.ts +89 -0
- package/src/{serve → engine}/preparation.ts +7 -5
- package/src/{serve → engine}/session-completion-engine.ts +6 -6
- package/src/engine/telemetry.ts +109 -0
- package/src/hub/downloads.ts +116 -0
- package/src/jobs/db.ts +16 -7
- package/src/jobs/events.ts +29 -0
- package/src/jobs/executable.ts +5 -0
- package/src/jobs/host.ts +51 -0
- package/src/jobs/in-process.ts +55 -0
- package/src/{contracts/jobs.ts → jobs/protocol.ts} +7 -9
- package/src/jobs/runner.ts +150 -231
- package/src/jobs/service.ts +75 -0
- package/src/jobs/sse.ts +59 -23
- package/src/jobs/worker-memory.ts +46 -0
- package/src/jobs/worker-process.ts +163 -0
- package/src/jobs/worker-supervisor.ts +258 -0
- package/src/modules.ts +58 -0
- package/src/publishing/catalog-hub.ts +18 -0
- package/src/publishing/credentials.ts +31 -0
- package/src/publishing/upload.ts +37 -0
- package/src/residency/leased-adapters.ts +21 -0
- package/src/residency/local-records.ts +16 -0
- package/src/residency/model-residency.ts +405 -0
- package/src/residency/record-index.ts +28 -0
- package/src/residency/resident-estimate.ts +50 -0
- package/src/{anthropic.ts → server/anthropic.ts} +3 -81
- package/src/server/cache-routes.ts +36 -0
- package/src/{serve → server}/chat-request.ts +10 -10
- package/src/{serve → server}/chat-stage.ts +14 -14
- package/src/{client.ts → server/client.ts} +28 -2
- package/src/{serve → server}/completion-executor.ts +33 -41
- package/src/server/decision-body.ts +55 -0
- package/src/server/decision-routes.ts +134 -0
- package/src/server/decision-wire.ts +228 -0
- package/src/{serve → server}/discovery-routes.ts +25 -82
- package/src/{serve → server}/http.ts +25 -13
- package/src/{serve → server}/inference-request.ts +5 -5
- package/src/server/job-routes.ts +20 -0
- package/src/server/media-prompt.ts +63 -0
- package/src/server/memory-completion-client.ts +46 -0
- package/src/server/model-routes.ts +192 -0
- package/src/{serve → server}/openai-wire.ts +3 -2
- package/src/{serve → server}/pipeline.ts +1 -1
- package/src/server/prompt-builder.ts +13 -0
- package/src/server/prompt-contracts.ts +17 -0
- package/src/server/proxy-routes.ts +187 -0
- package/src/server/publishing-routes.ts +41 -0
- package/src/{serve → server}/request-plan.ts +6 -8
- package/src/{serve → server}/request-prep.ts +47 -50
- package/src/{serve → server}/responses-client.ts +30 -13
- package/src/{responses.ts → server/responses.ts} +12 -93
- package/src/server/routes.ts +205 -0
- package/src/server/start.ts +72 -0
- package/src/server/status-routes.ts +108 -0
- package/src/{serve → server}/text-completion-stage.ts +25 -4
- package/src/server/text-prompt.ts +12 -0
- package/src/{serve → server}/token-streams.ts +36 -38
- package/src/server/worker-routes.ts +311 -0
- package/src/storage/paths.ts +61 -0
- package/src/web/assets.ts +37 -0
- package/src/web/{src → browser}/api.ts +5 -23
- package/src/web/browser/chat-host.ts +17 -0
- package/src/web/browser/installed-panels.ts +6 -0
- package/src/web/browser/main.ts +55 -0
- package/src/web/browser/markdown.ts +279 -0
- package/src/web/browser/memory-panel.ts +9 -0
- package/src/web/browser/palette.ts +64 -0
- package/src/web/browser/protocol.ts +13 -0
- package/src/web/browser/shell.ts +467 -0
- package/src/web/{src → browser}/status.ts +32 -36
- package/src/web/build.ts +51 -0
- package/src/web/public/app.html +1054 -0
- package/src/web/{sw.js → public/sw.js} +4 -3
- package/src/web/{vendor → public/vendor}/hljs-theme.css +2 -2
- package/src/web/{vendor → public/vendor}/hljs.js +2 -2
- package/THIRD_PARTY_LICENSES.md +0 -51
- package/docs/README.md +0 -107
- package/docs/archive/hlg-sampling.md +0 -350
- package/docs/archive/investigations/audio-input-plan.md +0 -356
- package/docs/archive/investigations/benchmark-ledger-through-v0.4.0.md +0 -3617
- package/docs/archive/investigations/chunk-finetune-experiment.md +0 -84
- package/docs/archive/investigations/colibri-glm52-port.md +0 -1486
- package/docs/archive/investigations/colibri-oracle-pin.md +0 -1284
- package/docs/archive/investigations/curve-bisector-routing.md +0 -123
- package/docs/archive/investigations/decode-roofline-lookagain.md +0 -348
- package/docs/archive/investigations/diffusion-gemma-port.md +0 -602
- package/docs/archive/investigations/dspark-handoff.md +0 -224
- package/docs/archive/investigations/expert-offload-single-user-moe.md +0 -292
- package/docs/archive/investigations/faithful-l1-consolidation.md +0 -310
- package/docs/archive/investigations/hlg-sampling-investigation.md +0 -970
- package/docs/archive/investigations/indexshare-performance-spike.md +0 -357
- package/docs/archive/investigations/kernel-perf-review-2026-07.md +0 -171
- package/docs/archive/investigations/lab-build-journal.md +0 -157
- package/docs/archive/investigations/minicpm5-decode-megakernel.md +0 -489
- package/docs/archive/investigations/openjarvis-fit.md +0 -198
- package/docs/archive/investigations/openwiki-evaluation.md +0 -249
- package/docs/archive/investigations/optimization_plan.md +0 -346
- package/docs/archive/investigations/orpo-base-uf-experiment-and-directions.md +0 -120
- package/docs/archive/investigations/orpo-flash-cce-pin-leak.md +0 -167
- package/docs/archive/investigations/orpo-uf-testing-handoff.md +0 -126
- package/docs/archive/investigations/pareto-specialized-runtime-findings.md +0 -92
- package/docs/archive/investigations/pi-builtin-investigation.md +0 -308
- package/docs/archive/investigations/pre-colibri-stabilization.md +0 -329
- package/docs/archive/investigations/qwen38-27b-campaign-log.md +0 -1092
- package/docs/archive/investigations/segmented-backward-handoff.md +0 -148
- package/docs/archive/investigations/starter-model-port-handoff.md +0 -342
- package/docs/archive/investigations/steel-flash-cce-handoff.md +0 -489
- package/docs/archive/investigations/the-dreaming-handoff.md +0 -141
- package/docs/archive/investigations/trainer-validation-experiment.md +0 -173
- package/docs/archive/planning/chat-ui-vision-memory-first.md +0 -199
- package/docs/archive/planning/chat-ui-vision.md +0 -50
- package/docs/archive/planning/chunk-task-roadmap.md +0 -77
- package/docs/archive/planning/curve-sampler-research-plan.md +0 -331
- package/docs/archive/planning/journal.md +0 -316
- package/docs/archive/planning/memory-docs-and-dag-plan.md +0 -194
- package/docs/archive/planning/pi-integration-review.md +0 -178
- package/docs/archive/planning/release-notes-v0.0.10.md +0 -57
- package/docs/archive/planning/release-notes-v0.0.11.md +0 -239
- package/docs/archive/planning/release-notes-v0.0.12.md +0 -80
- package/docs/archive/planning/release-notes-v0.0.13.md +0 -77
- package/docs/archive/planning/release-notes-v0.0.9.md +0 -122
- package/docs/archive/planning/release-notes-v0.2.0.md +0 -49
- package/docs/archive/planning/release-notes-v0.3.0.md +0 -35
- package/docs/archive/planning/release-notes-v0.4.0.md +0 -45
- package/docs/archive/planning/release-notes-v0.5.0.md +0 -37
- package/docs/archive/planning/web-ui-pass-plan.md +0 -213
- package/docs/archive/planning/website-readme-pass-plan.md +0 -224
- package/docs/design/batching.md +0 -2054
- package/docs/design/decode-speed-program.md +0 -2404
- package/docs/design/dreaming-nightly-pipeline.md +0 -684
- package/docs/design/generic-model-support.md +0 -821
- package/docs/design/kv-cache.md +0 -993
- package/docs/design/orpo-training.md +0 -680
- package/docs/design/speculative-decoding.md +0 -2129
- package/docs/design/turboquant.md +0 -2225
- package/docs/design/unified-engine-frontier-plan.md +0 -1987
- package/docs/design/web-chat-redesign.md +0 -695
- package/docs/planning/PRODUCT_ROADMAP.md +0 -593
- package/docs/planning/ResearchTopics.md +0 -119
- package/docs/planning/public-readiness-handoff.md +0 -244
- package/docs/planning/public-readiness.md +0 -149
- package/docs/planning/video-series.md +0 -421
- package/docs/reference/benchmarks.md +0 -3587
- package/docs/reference/cli.md +0 -646
- package/docs/reference/distribution.md +0 -380
- package/docs/reference/environment.md +0 -494
- package/docs/reference/glossary.md +0 -39
- package/docs/reference/library-api.md +0 -688
- package/docs/reference/memory.md +0 -260
- package/docs/reference/models.md +0 -346
- package/docs/reference/server-api.md +0 -1945
- package/docs/reference/server-config.md +0 -973
- package/docs/reference/training.md +0 -462
- package/docs/reference/troubleshooting.md +0 -158
- package/src/admission-plan.ts +0 -18
- package/src/audio/audiotoolbox.ts +0 -112
- package/src/audio/conformer.ts +0 -655
- package/src/audio/decode.ts +0 -133
- package/src/audio/features.ts +0 -202
- package/src/audio/mic-capture.ts +0 -98
- package/src/audio/silero-vad.ts +0 -379
- package/src/audio/transcode.ts +0 -67
- package/src/audio/whisper-decode.ts +0 -774
- package/src/audio/whisper-fast.ts +0 -732
- package/src/audio/whisper-format.ts +0 -73
- package/src/audio/whisper-mel.ts +0 -185
- package/src/audio/whisper-timing.ts +0 -274
- package/src/audio/whisper-tokenizer.ts +0 -239
- package/src/audio/whisper-transcribe.ts +0 -410
- package/src/backends/mlx/assistant-target.ts +0 -46
- package/src/backends/mlx/autoregressive-draft-rows.ts +0 -120
- package/src/backends/mlx/autoregressive.ts +0 -129
- package/src/backends/mlx/batch-group.ts +0 -1874
- package/src/backends/mlx/cache-attachment.ts +0 -67
- package/src/backends/mlx/cache-layout.ts +0 -31
- package/src/backends/mlx/checkpoint-state.ts +0 -53
- package/src/backends/mlx/continuation-persistence.ts +0 -107
- package/src/backends/mlx/continuation-request.ts +0 -43
- package/src/backends/mlx/continuation.ts +0 -77
- package/src/backends/mlx/diffusion.ts +0 -39
- package/src/backends/mlx/draft-target.ts +0 -32
- package/src/backends/mlx/encoder-cache.ts +0 -25
- package/src/backends/mlx/evaluation.ts +0 -105
- package/src/backends/mlx/fill-append.ts +0 -58
- package/src/backends/mlx/fill-group.ts +0 -497
- package/src/backends/mlx/full-kv-row-append.ts +0 -32
- package/src/backends/mlx/full-kv-row-donor.ts +0 -54
- package/src/backends/mlx/full-transitioning-kv-rows.ts +0 -65
- package/src/backends/mlx/gateway-binding.ts +0 -174
- package/src/backends/mlx/graph.ts +0 -38
- package/src/backends/mlx/kv-codec.ts +0 -9
- package/src/backends/mlx/kv-maintenance.ts +0 -161
- package/src/backends/mlx/kv-tensor-rows.ts +0 -212
- package/src/backends/mlx/memory-client.ts +0 -189
- package/src/backends/mlx/methods.ts +0 -90
- package/src/backends/mlx/mixed-iteration.ts +0 -62
- package/src/backends/mlx/model-prompt.ts +0 -178
- package/src/backends/mlx/model-serving.ts +0 -80
- package/src/backends/mlx/paged-cache-codec.ts +0 -27
- package/src/backends/mlx/prefill-cohort.ts +0 -100
- package/src/backends/mlx/prefill-policy.ts +0 -26
- package/src/backends/mlx/prefill-rows.ts +0 -244
- package/src/backends/mlx/prefill.ts +0 -33
- package/src/backends/mlx/projected-context-rows.ts +0 -72
- package/src/backends/mlx/prompt-input.ts +0 -29
- package/src/backends/mlx/qwen-prompt-input.ts +0 -40
- package/src/backends/mlx/request-policy.ts +0 -20
- package/src/backends/mlx/request-state-policy.ts +0 -54
- package/src/backends/mlx/rollback.ts +0 -92
- package/src/backends/mlx/row-sampling.ts +0 -49
- package/src/backends/mlx/serial-executor.ts +0 -251
- package/src/backends/mlx/speculative-group.ts +0 -438
- package/src/backends/mlx/speculative-round.ts +0 -161
- package/src/backends/mlx/speculative.ts +0 -131
- package/src/backends/mlx/state-rows.ts +0 -104
- package/src/backends/mlx/state-views.ts +0 -46
- package/src/backends/mlx/target-layout-capability.ts +0 -35
- package/src/backends/mlx/text-engine.ts +0 -73
- package/src/backends/mlx/transitioning-kv-rows.ts +0 -173
- package/src/chat-template.ts +0 -382
- package/src/cli.ts +0 -3490
- package/src/config.ts +0 -399
- package/src/contracts/completion.ts +0 -16
- package/src/contracts/execution.ts +0 -70
- package/src/contracts/generation.ts +0 -103
- package/src/contracts/host.ts +0 -7
- package/src/contracts/object-cache.ts +0 -8
- package/src/contracts/pi-provider.ts +0 -30
- package/src/contracts/pi.ts +0 -261
- package/src/contracts/prefix-cache.ts +0 -33
- package/src/contracts/resources.ts +0 -25
- package/src/contracts/sampling.ts +0 -23
- package/src/contracts/scheduling.ts +0 -54
- package/src/contracts/tsconfig.json +0 -14
- package/src/dataset/generators.ts +0 -1087
- package/src/dataset/index.ts +0 -12
- package/src/dataset/job.ts +0 -81
- package/src/dataset/llm.ts +0 -71
- package/src/dataset/registry.ts +0 -528
- package/src/diffusion/diffusion-generate.ts +0 -507
- package/src/download.ts +0 -505
- package/src/embed.ts +0 -77
- package/src/engine/admission.ts +0 -59
- package/src/engine/cancellation.ts +0 -38
- package/src/engine/engine.ts +0 -56
- package/src/engine/execution-coordinator.ts +0 -70
- package/src/engine/execution-plan.ts +0 -64
- package/src/engine/execution-tasks.ts +0 -44
- package/src/engine/namespaced-cache.ts +0 -13
- package/src/engine/resources.ts +0 -53
- package/src/engine/scheduler.ts +0 -92
- package/src/engine/session.ts +0 -247
- package/src/eval/README.md +0 -130
- package/src/eval/ifeval.ts +0 -81
- package/src/eval/kl-prompts.ts +0 -29
- package/src/eval/kl.ts +0 -590
- package/src/eval/perplexity.ts +0 -164
- package/src/eval/quality-db.ts +0 -92
- package/src/eval/reference-logits.ts +0 -207
- package/src/eval/runner.ts +0 -124
- package/src/eval/score.ts +0 -49
- package/src/eval/tasks/bfcl.ts +0 -535
- package/src/eval/tasks/gsm8k.ts +0 -98
- package/src/eval/tasks/hashhop.ts +0 -139
- package/src/eval/tasks/humaneval.ts +0 -275
- package/src/eval/tasks/ifeval.ts +0 -525
- package/src/eval/tasks/mmlu.ts +0 -157
- package/src/eval/ultrafeedback.ts +0 -60
- package/src/evaldb.ts +0 -199
- package/src/expert-io.ts +0 -393
- package/src/expert-offload-build.ts +0 -77
- package/src/expert-offload.ts +0 -76
- package/src/expert-residency.ts +0 -1134
- package/src/expert-usage.ts +0 -415
- package/src/fill/echo-index.ts +0 -188
- package/src/fill/fill-session.ts +0 -463
- package/src/fill/proposal.ts +0 -68
- package/src/fill/schema-rows.ts +0 -506
- package/src/fill/tool-boundary.ts +0 -103
- package/src/fit.ts +0 -249
- package/src/generate.ts +0 -1352
- package/src/grammar.ts +0 -604
- package/src/harness-pi.ts +0 -184
- package/src/hf-push.ts +0 -628
- package/src/hub-rest.ts +0 -242
- package/src/index.ts +0 -63
- package/src/inference/denoising.ts +0 -17
- package/src/inference/draft-acceptance.ts +0 -41
- package/src/inference/draft-output.ts +0 -49
- package/src/inference/graph.ts +0 -33
- package/src/inference/prefill.ts +0 -45
- package/src/inference/rollback.ts +0 -7
- package/src/jobs/index.ts +0 -31
- package/src/jobs/job-entry.ts +0 -107
- package/src/jobs/task-client.ts +0 -19
- package/src/jobs/types.ts +0 -2
- package/src/kv-scheme.ts +0 -279
- package/src/kv-store.ts +0 -1135
- package/src/lab/README.md +0 -22
- package/src/lab/curve/curve-designer.html +0 -303
- package/src/lab/curve/curve-sampler.ts +0 -122
- package/src/lab/expert-trace/expert-trace.ts +0 -69
- package/src/lab/paged-kv/paged-attention.ts +0 -117
- package/src/lab/paged-kv/paged-kv-rows.ts +0 -111
- package/src/lab/paged-kv/paged-kv.ts +0 -336
- package/src/library.ts +0 -40
- package/src/lora.ts +0 -408
- package/src/md-modules.d.ts +0 -54
- package/src/media-fetch.ts +0 -442
- package/src/memory/article.ts +0 -558
- package/src/memory/chunk-validation.ts +0 -150
- package/src/memory/chunk.ts +0 -318
- package/src/memory/cluster.ts +0 -351
- package/src/memory/coverage.ts +0 -134
- package/src/memory/crosslink.ts +0 -0
- package/src/memory/db.ts +0 -253
- package/src/memory/entity.ts +0 -203
- package/src/memory/gate.ts +0 -187
- package/src/memory/history.ts +0 -152
- package/src/memory/ingest.ts +0 -335
- package/src/memory/model.ts +0 -183
- package/src/memory/normalize.ts +0 -671
- package/src/memory/parse.ts +0 -22
- package/src/memory/pipeline.ts +0 -275
- package/src/memory/prompts.ts +0 -65
- package/src/memory/query.ts +0 -334
- package/src/memory/reconcile.ts +0 -734
- package/src/memory/redirect.ts +0 -108
- package/src/memory/reindex.ts +0 -325
- package/src/memory/resolve.ts +0 -307
- package/src/memory/rest.ts +0 -245
- package/src/memory/route.ts +0 -272
- package/src/memory/schedule.ts +0 -165
- package/src/memory/stages.ts +0 -777
- package/src/memory/synthesize.ts +0 -1638
- package/src/memory/tools.ts +0 -749
- package/src/memory/vault.ts +0 -1194
- package/src/memory/wikify.ts +0 -1064
- package/src/mlx/array.ts +0 -425
- package/src/mlx/autograd.ts +0 -304
- package/src/mlx/checkpoint.ts +0 -149
- package/src/mlx/compile.ts +0 -168
- package/src/mlx/custom-vjp.ts +0 -188
- package/src/mlx/ffi.ts +0 -531
- package/src/mlx/materialize.ts +0 -23
- package/src/mlx/metal-kernel.ts +0 -159
- package/src/mlx/normalized-argmax.ts +0 -116
- package/src/mlx/ops.ts +0 -1108
- package/src/mlx/quantized-rows.ts +0 -36
- package/src/mlx/token-bitmask.ts +0 -52
- package/src/mlx/turboquant-kv-decode.ts +0 -82
- package/src/mlx/turboquant-kv-inverse.ts +0 -122
- package/src/mlx/turboquant-ops.ts +0 -861
- package/src/mlx/turboquant-tables.ts +0 -237
- package/src/mmap.ts +0 -90
- package/src/model/artifact-identity.ts +0 -73
- package/src/model/batched-kv.ts +0 -235
- package/src/model/batched-mask.ts +0 -279
- package/src/model/batched-quant.ts +0 -212
- package/src/model/batched-quantized-kv.ts +0 -144
- package/src/model/batched-rotating-quant.ts +0 -353
- package/src/model/batched-rotating-state.ts +0 -149
- package/src/model/batched-rotating.ts +0 -338
- package/src/model/batched-row-storage.ts +0 -179
- package/src/model/batched-ssm.ts +0 -52
- package/src/model/batched-turboquant-kv.ts +0 -94
- package/src/model/compiled-decode.ts +0 -712
- package/src/model/decoded-kv-donor.ts +0 -12
- package/src/model/delayed-quantized-kv.ts +0 -72
- package/src/model/delayed-rotating-quantized-kv.ts +0 -93
- package/src/model/delayed-turboquant-kv.ts +0 -57
- package/src/model/diffusion-gemma.ts +0 -1026
- package/src/model/factory.ts +0 -215
- package/src/model/fingerprint.ts +0 -45
- package/src/model/flash-attention.ts +0 -694
- package/src/model/full-prefill-padding.ts +0 -56
- package/src/model/full-prefill-row.ts +0 -105
- package/src/model/gemma4-base.ts +0 -2370
- package/src/model/gemma4.ts +0 -1395
- package/src/model/generated/gemma4-12b.ts +0 -429
- package/src/model/generated/gemma4-26b.ts +0 -448
- package/src/model/generated/gemma4-e4b.ts +0 -717
- package/src/model/generated/index.ts +0 -18
- package/src/model/glm52-atlas.ts +0 -511
- package/src/model/glm52-cache.ts +0 -792
- package/src/model/glm52-config.ts +0 -262
- package/src/model/glm52-container.ts +0 -305
- package/src/model/glm52-coupling.ts +0 -388
- package/src/model/glm52-dsa.ts +0 -606
- package/src/model/glm52-expert-layout.ts +0 -253
- package/src/model/glm52-layout.ts +0 -152
- package/src/model/glm52-memory.ts +0 -500
- package/src/model/glm52-mla.ts +0 -792
- package/src/model/glm52-moe.ts +0 -223
- package/src/model/glm52-pilot.ts +0 -537
- package/src/model/glm52-reference.ts +0 -445
- package/src/model/glm52-residency.ts +0 -557
- package/src/model/glm52-resident-weights.ts +0 -135
- package/src/model/glm52-streamed-experts.ts +0 -453
- package/src/model/glm52-streamed-metal.ts +0 -610
- package/src/model/glm52-weights.ts +0 -503
- package/src/model/glm52.ts +0 -1132
- package/src/model/implementation.ts +0 -69
- package/src/model/kv-attention-view.ts +0 -70
- package/src/model/minicpm5.ts +0 -376
- package/src/model/profile.ts +0 -468
- package/src/model/quantized-append-attention.ts +0 -31
- package/src/model/qwen-append.ts +0 -12
- package/src/model/qwen-conv.ts +0 -52
- package/src/model/qwen3-delta-state.ts +0 -74
- package/src/model/qwen3-delta.ts +0 -451
- package/src/model/qwen3-moe.ts +0 -404
- package/src/model/qwen3-mrope.ts +0 -250
- package/src/model/qwen3.ts +0 -277
- package/src/model/qwen3_5-checkpoint.ts +0 -164
- package/src/model/qwen3_5.ts +0 -881
- package/src/model/rotating-kv-donor.ts +0 -35
- package/src/model/rotating-kv-layout.ts +0 -262
- package/src/model/rotating-row-transaction.ts +0 -35
- package/src/model/speculative-rotating-kv.ts +0 -101
- package/src/model/ssm-prefill-padding.ts +0 -39
- package/src/model/support.ts +0 -110
- package/src/model/token-groups.ts +0 -66
- package/src/model/trellis-balanced-scatter.ts +0 -51
- package/src/model/trellis-linear.ts +0 -672
- package/src/model/trellis-shared-m.ts +0 -151
- package/src/model/trellis-shared-scatter.ts +0 -144
- package/src/model/trellis-splitk-prefill.ts +0 -158
- package/src/model/trellis-tiled-prefill.ts +0 -136
- package/src/model/trellis-vector-expand.ts +0 -62
- package/src/model/trellis-wide-prefill.ts +0 -94
- package/src/model/turboquant-codec.ts +0 -89
- package/src/model/universal/archs.ts +0 -332
- package/src/model/universal/dense.ts +0 -612
- package/src/model/universal/modules.ts +0 -170
- package/src/model/universal/rope.ts +0 -381
- package/src/model/whisper.ts +0 -435
- package/src/native/expert_io.c +0 -523
- package/src/native/frame_extract.swift +0 -140
- package/src/native/mic_capture.swift +0 -92
- package/src/native-pack.ts +0 -163
- package/src/pi-provider.ts +0 -53
- package/src/pi-session.ts +0 -81
- package/src/pi-terminal.ts +0 -274
- package/src/pi-web.ts +0 -1940
- package/src/preflight.ts +0 -158
- package/src/prompt-cache.ts +0 -682
- package/src/quantize/allocator.ts +0 -627
- package/src/quantize/atomic-output.ts +0 -38
- package/src/quantize/calibration.ts +0 -181
- package/src/quantize/config-writer.ts +0 -145
- package/src/quantize/index.ts +0 -84
- package/src/quantize/job.ts +0 -162
- package/src/quantize/quantizer.ts +0 -666
- package/src/quantize/rotate.ts +0 -603
- package/src/quantize/safetensors-writer.ts +0 -249
- package/src/quantize/sensitivity.ts +0 -216
- package/src/quantize/trellis.ts +0 -586
- package/src/quantize/weight-transform.ts +0 -292
- package/src/registry.ts +0 -602
- package/src/runtime-config.ts +0 -77
- package/src/safetensors.ts +0 -165
- package/src/sampler.ts +0 -1012
- package/src/serve/admin-routes.ts +0 -184
- package/src/serve/assets/curve-terrain.html +0 -105
- package/src/serve/assets/training-inference-map.html +0 -575
- package/src/serve/audio-routes.ts +0 -323
- package/src/serve/aux-routes.ts +0 -132
- package/src/serve/batch-scheduler.ts +0 -8
- package/src/serve/checkpoint-identity.ts +0 -64
- package/src/serve/generation-gateway.ts +0 -537
- package/src/serve/isolate.ts +0 -609
- package/src/serve/kv-budget.ts +0 -14
- package/src/serve/lab-routes.ts +0 -341
- package/src/serve/lane-registry.ts +0 -45
- package/src/serve/model-admin-routes.ts +0 -155
- package/src/serve/model-binding.ts +0 -49
- package/src/serve/model-host.ts +0 -511
- package/src/serve/parent-application.ts +0 -56
- package/src/serve/prompt-response-trace.ts +0 -161
- package/src/serve/response-lease.ts +0 -24
- package/src/serve/session-search.ts +0 -326
- package/src/serve/static-routes.ts +0 -101
- package/src/serve/transcription-server.ts +0 -64
- package/src/serve/transcription-service.ts +0 -502
- package/src/server.ts +0 -1282
- package/src/spec/assistant-rows.ts +0 -93
- package/src/spec/assistant-source.ts +0 -126
- package/src/spec/cache-identity.ts +0 -22
- package/src/spec/deepspec-rows.ts +0 -29
- package/src/spec/deepspec-source.ts +0 -145
- package/src/spec/dflash-source.ts +0 -149
- package/src/spec/drafter.ts +0 -304
- package/src/spec/dspark/ab-stats.ts +0 -136
- package/src/spec/dspark/calibration.ts +0 -112
- package/src/spec/dspark/data-dflash.ts +0 -156
- package/src/spec/dspark/data.ts +0 -201
- package/src/spec/dspark/deepspec-module.ts +0 -776
- package/src/spec/dspark/generate-dflash.ts +0 -245
- package/src/spec/dspark/generate.ts +0 -290
- package/src/spec/dspark/loader.ts +0 -23
- package/src/spec/dspark/loss.ts +0 -213
- package/src/spec/dspark/module-dflash.ts +0 -560
- package/src/spec/dspark/module.ts +0 -540
- package/src/spec/dspark/quantize-drafter.ts +0 -91
- package/src/spec/dspark/sample.ts +0 -112
- package/src/spec/generate.ts +0 -276
- package/src/spec/glm52-mtp-graph.ts +0 -29
- package/src/spec/glm52-mtp-rows.ts +0 -132
- package/src/spec/glm52-mtp-source.ts +0 -318
- package/src/spec/glm52-mtp-state.ts +0 -33
- package/src/spec/ngram-source.ts +0 -226
- package/src/spec/projected-draft-rows.ts +0 -73
- package/src/spec/qwen-mtp-module.ts +0 -171
- package/src/spec/qwen-mtp-rows.ts +0 -230
- package/src/spec/qwen-mtp-source.ts +0 -386
- package/src/spec/qwen-mtp-state.ts +0 -30
- package/src/spec/serve-loop.ts +0 -587
- package/src/spec/source.ts +0 -228
- package/src/spec/two-model-rows.ts +0 -62
- package/src/spec/two-model.ts +0 -113
- package/src/ssd-cache.ts +0 -431
- package/src/ssd-durability.ts +0 -209
- package/src/storage/host-buffer.ts +0 -24
- package/src/storage/kv-writer.ts +0 -83
- package/src/storage/kv-writer.worker.js +0 -264
- package/src/storage/retention-policy.ts +0 -37
- package/src/tiered-prompt-cache.ts +0 -45
- package/src/tokenizer.ts +0 -335
- package/src/tool-approvals.ts +0 -131
- package/src/tool-call.ts +0 -695
- package/src/train/dataset.ts +0 -484
- package/src/train/diffusion-lora.ts +0 -216
- package/src/train/export.ts +0 -32
- package/src/train/flash-cce.ts +0 -1234
- package/src/train/forward.ts +0 -266
- package/src/train/fuse.ts +0 -243
- package/src/train/index.ts +0 -46
- package/src/train/job.ts +0 -205
- package/src/train/lora-params.ts +0 -254
- package/src/train/loss.ts +0 -1591
- package/src/train/merge.ts +0 -195
- package/src/train/optimizer.ts +0 -183
- package/src/train/prefix-shared.ts +0 -490
- package/src/train/rank.ts +0 -137
- package/src/train/segmented.ts +0 -1828
- package/src/train/steel-qmm-header.ts +0 -1461
- package/src/train/trainer.ts +0 -1221
- package/src/train/watch.ts +0 -542
- package/src/vision/diffusion-vision.ts +0 -620
- package/src/vision/embedder.ts +0 -176
- package/src/vision/preprocess.ts +0 -247
- package/src/vision/prompt.ts +0 -409
- package/src/vision/qwen3vl-preprocess.ts +0 -409
- package/src/vision/qwen3vl-prompt.ts +0 -233
- package/src/vision/qwen3vl-tower.ts +0 -566
- package/src/vision/siglip.ts +0 -607
- package/src/vision/video-frames.ts +0 -148
- package/src/web/app.html +0 -2155
- package/src/web/app.js +0 -5863
- package/src/web/skills/memory/SKILL.md +0 -120
- package/src/web/skills/web-research/SKILL.md +0 -46
- package/src/web/skills.ts +0 -97
- package/src/web/src/adapters-panel.ts +0 -288
- package/src/web/src/assistant.ts +0 -451
- package/src/web/src/chat.ts +0 -1060
- package/src/web/src/composer.ts +0 -962
- package/src/web/src/dataset.ts +0 -132
- package/src/web/src/finetune.ts +0 -267
- package/src/web/src/hub.ts +0 -428
- package/src/web/src/main.ts +0 -98
- package/src/web/src/markdown.ts +0 -0
- package/src/web/src/memory-panel.ts +0 -572
- package/src/web/src/model-picker.ts +0 -159
- package/src/web/src/palette.ts +0 -346
- package/src/web/src/protocol.ts +0 -26
- package/src/web/src/quantize.ts +0 -167
- package/src/web/src/rag.ts +0 -274
- package/src/web/src/sessions.ts +0 -469
- package/src/web/src/shell.ts +0 -804
- package/src/web/src/ui-catalog.ts +0 -78
- package/src/web/src/voice.ts +0 -141
- package/src/web/vendor/README.md +0 -61
- package/src/web-assets.ts +0 -47
- package/src/web-tools.ts +0 -478
- package/src/weights.ts +0 -255
- /package/src/{serve → server}/completion-sink.ts +0 -0
- /package/src/{serve → server}/generated-token-history.ts +0 -0
- /package/src/web/{icon.svg → public/icon.svg} +0 -0
- /package/src/web/{manifest.webmanifest → public/manifest.webmanifest} +0 -0
- /package/src/web/{vendor → public/vendor}/hljs-LICENSE +0 -0
package/README.md
CHANGED
|
@@ -1,135 +1,1475 @@
|
|
|
1
|
-
# mlx-bun
|
|
1
|
+
# mlx-bun app
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
application or run the local server and browser chat app. The numerical tests
|
|
6
|
-
compare logits bit-for-bit with mlx-lm for validated configurations.
|
|
3
|
+
mlx-bun 1.0.0 is beta and actively developed. APIs and behavior may change;
|
|
4
|
+
breaking changes are called out in release notes.
|
|
7
5
|
|
|
8
|
-
|
|
6
|
+
The runnable terminal app, server, and web surfaces live here. Default startup
|
|
7
|
+
loads a local model and serves the browser app, Pi web chat, and OpenAI-compatible
|
|
8
|
+
completions through one continuous scheduler. Model-management commands remain
|
|
9
|
+
available separately: `get`, `scan`, `ls`, `fit`, and `gc`.
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
After the root native setup, run
|
|
12
|
+
`bun apps/mlx-bun/bin/mlx-bun.mjs serve --model <cached-model-or-directory>`,
|
|
13
|
+
or use `mlx-bun` after the root's `bun run link-cli` step. The launcher checks
|
|
14
|
+
the app manifest's Bun minimum and Apple Silicon macOS before loading app code.
|
|
15
|
+
Help and version work before native setup; inference requires the staged natives.
|
|
16
|
+
Use `serve --help` for accepted options. A terminal session opens the browser
|
|
17
|
+
unless `--no-open` is supplied, focusing an already-open app tab in a running
|
|
18
|
+
Chrome, Arc, Brave, Edge, or Safari (AppleScript) before opening a new one.
|
|
19
|
+
Before selecting a model, startup refuses a TCP port that already serves one
|
|
20
|
+
and names its model (exit 1); port 0 is never checked. Startup without a model
|
|
21
|
+
selects a cached model; if none is supported, it downloads the starter, then hands the recommended model
|
|
22
|
+
to the app's download owner as a background transfer, visible on `GET /downloads`
|
|
23
|
+
and joined at shutdown. A signal before the app exists cancels selection (a
|
|
24
|
+
starter download stays resumable); a signal during model load closes the app as
|
|
25
|
+
soon as it is up. Shutdown handlers are installed as soon as the listener binds.
|
|
26
|
+
A route a surface does not mount returns 404, as do main's lab pages
|
|
27
|
+
(`/curves`, `/curve-terrain`, `/dag`, `/generate`, `/signal`): they are not
|
|
28
|
+
product surface. A request shape the engine cannot run returns a typed 501
|
|
29
|
+
(`UnsupportedExecutionError`); remaining work is tracked in
|
|
30
|
+
[PLAN](../../PLAN.md).
|
|
11
31
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
32
|
+
Main's admission and runtime flags keep their units and semantics: `--memory-budget`
|
|
33
|
+
(decimal GB) is the usable envelope for model load, request admission, the process
|
|
34
|
+
allocator limit, optional cache residency, and `/stats.admission.memory_budget_bytes`;
|
|
35
|
+
`--context-length` feeds the context reservation of runtimes that plan memory up front and other models ignore it;
|
|
36
|
+
`--force-wire` and `--allow-private-media` set their library runtime switches for
|
|
37
|
+
the serving process; `--expert-offload` builds `<model>/.mlx-bun-offload` on first
|
|
38
|
+
use and activates it before construction for MoE models, while dense models log
|
|
39
|
+
and continue; the `--hlg-*` family sets the HLG sampling default; and
|
|
40
|
+
`MLX_BUN_SHUTDOWN_TIMEOUT_MS` (finite, positive, else 120 s) bounds shutdown.
|
|
41
|
+
The process-wide settings an app applies (offload routing, the allocator limit,
|
|
42
|
+
the runtime switches) are restored after its engine releases the model, on close
|
|
43
|
+
and on startup failure, so a later app in the same process starts from what it
|
|
44
|
+
found; offload restore only redirects routing and never unmaps borrowed weights.
|
|
45
|
+
`serve` and `generate` accept numerical presets: `--l1` selects KV off
|
|
46
|
+
and unfused SDPA; `--l2` selects model-config KV and fused SDPA, and wins if both
|
|
47
|
+
are given. An explicit `--kv-quant` overrides the preset and sets the kernel
|
|
48
|
+
default (fused for `config`, unfused otherwise); `--fused-sdpa on|off` overrides
|
|
49
|
+
that default. The policy is part of the loaded model's composition, resolved before
|
|
50
|
+
its graph is built (including in isolated model workers); no process setting changes.
|
|
15
51
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
52
|
+
`--adapter <dir>` (alias `--adapter-path`) mounts a LoRA adapter right after the
|
|
53
|
+
model loads, before any request, under the directory's basename as its id; it
|
|
54
|
+
becomes the default for requests without an `adapter` field, an explicit
|
|
55
|
+
`adapter` (including `"none"`) still wins, `/v1/adapters` lists it, and a bad
|
|
56
|
+
directory fails startup with `adapter mount failed: …` after releasing the
|
|
57
|
+
model.
|
|
58
|
+
The opt-in [startup adapter test](tests/engine/startup-adapter.test.ts) produces
|
|
59
|
+
a three-step adapter with the fine-tune producer and serves with it; the opt-in
|
|
60
|
+
[DiffusionGemma adapter test](tests/engine/diffusion-adapter.test.ts) serves a
|
|
61
|
+
saved denoising adapter (hot mount and unmount, per-request selection, an
|
|
62
|
+
adapter row beside a base row, the startup default).
|
|
63
|
+
Main's speculative flags are restored with its validation: `--draft-model`
|
|
64
|
+
resolves like the main model (a query never downloads) and its kind is
|
|
65
|
+
auto-detected, `--draft-kind` overrides it (`ngram` is model-free; `mtp` alone
|
|
66
|
+
mounts the bundled companion), `--num-draft-tokens`, `--ngram-max`/`--ngram-min`
|
|
67
|
+
(ngram only; otherwise a warning), and `--mtp on|off` for checkpoints that carry their own draft head.
|
|
68
|
+
`--num-draft-tokens adaptive` chooses each round's draft count, up to the
|
|
69
|
+
drafter's own width, from recent acceptance and the graph's measured verify
|
|
70
|
+
costs; startup refuses it without a drafter or on a graph that declares no costs.
|
|
71
|
+
The opt-in [draft flags test](tests/engine/draft-flags.test.ts) serves with
|
|
72
|
+
ngram drafting and checks the speculation telemetry and exactness against a
|
|
73
|
+
plain run.
|
|
74
|
+
`--paged-kv` (env mirror `MLX_BUN_PAGED_KV=1`) with `--paged-kv-block-size`
|
|
75
|
+
(only alongside paging) composes the paged KV lego when the model loads: every
|
|
76
|
+
request, drafted, grammar, fill, media and adapter rows included, keeps its
|
|
77
|
+
full-attention keys and values in blocks. Pages store what `--kv-quant` names
|
|
78
|
+
for each layer: bf16, affine at the scheme's width (per layer under `config`),
|
|
79
|
+
or TurboQuant. `--paged-kv-direct` reads them with the direct Metal kernel
|
|
80
|
+
(Lab numerics) for decode and windows of up to eight queries; the kernel has
|
|
81
|
+
no compiled form, so that composition decodes without the compiled step.
|
|
82
|
+
The TurboQuant sign tables are generated for the served head dimensions (64, 96, 128,
|
|
83
|
+
256, 512; `packages/inference/scripts/gen-turboquant-signs.ts`, held to the generator by a
|
|
84
|
+
drift test), so `--kv-quant turbo` composes for every served model; the fused decode kernel
|
|
85
|
+
covers 64, 128, 256 and 512, and 96 decodes on the plain path.
|
|
86
|
+
The graphs that compose their own caches take the scheme and paging too:
|
|
87
|
+
GLM-5.2's compressed MLA state (the latent, RoPE key and DSA index key) is stored
|
|
88
|
+
as the scheme names and paged under `--paged-kv`, and DiffusionGemma's denoising
|
|
89
|
+
caches are the scheme's legos; main accepted the option and served GLM-5.2 in
|
|
90
|
+
bf16 while reporting the requested scheme.
|
|
91
|
+
`--kv-quant config` without the model's `kv_config.json` stays bf16.
|
|
92
|
+
The opt-in [paged KV test](tests/engine/paged-kv.test.ts) serves a Gemma4 and
|
|
93
|
+
a model of another family.
|
|
20
94
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
95
|
+
Shutdown stops background cache demotion, closes chat sessions, drains active
|
|
96
|
+
HTTP responses, then flushes caches and releases the engine. The CLI bounds this
|
|
97
|
+
with a 120-second deadline; cleanup failures or deadline expiry exit with code 1
|
|
98
|
+
(main exited 0 on timeout). SSD sub-options fail before model loading when saved
|
|
99
|
+
state is off (`--ssd-cache off` or `--prompt-cache 0`) instead of warning and being ignored.
|
|
100
|
+
`MLX_BUN_RD_CONTEXT_LIMIT` is main's enforced context cap, intersected with a
|
|
101
|
+
loaded runtime memory plan (or, under `--memory-budget`, the admission estimate's
|
|
102
|
+
safe context); no CLI flag sets that cap or read-only chat, and `--context-length`
|
|
103
|
+
only sizes a memory-planning runtime's reservation.
|
|
104
|
+
Programmatic composition still accepts explicit context and read-only policy.
|
|
25
105
|
|
|
26
|
-
|
|
106
|
+
`src/cli/main.ts` dispatches commands; `args.ts` owns accepted options and help;
|
|
107
|
+
the model-management verbs (`get`, `ls`, `scan`, `fit`, `gc`, `upload`) are the models module's; `terminal.ts` owns formatting.
|
|
108
|
+
`model-selection.ts` owns automatic selection policy; its pure choices live in
|
|
109
|
+
`model-choice.ts` ([`mlx-bun/selection`](#selection-entry-mlx-bunselection)).
|
|
110
|
+
`serve.ts` parses the serve flags and owns the process; its composition is
|
|
111
|
+
split in two halves:
|
|
112
|
+
`serve-state.ts` creates the persistent, CPU-only state that outlives a loaded
|
|
113
|
+
model (web assets, the download owner, Responses history, memory, jobs,
|
|
114
|
+
the modules that need job runners or sockets (chat among them), credentials, and their routes) and never imports the engine or a
|
|
115
|
+
native module at runtime; `serve-host.ts` creates the model-scoped host,
|
|
116
|
+
borrowing that state by parameter and lending it an execution lease, library
|
|
117
|
+
invalidation, and the bound port through an attached link. Its
|
|
118
|
+
`startModelHost` is the CLI's loader (runtime switches, expert offload, the
|
|
119
|
+
model context, the startup adapter) and hands the context to
|
|
120
|
+
`startContextHost`, which serves it and any other local model (below): the model
|
|
121
|
+
host, the Whisper companion, the model router, the Pi backend, and the listener.
|
|
122
|
+
A loaded model is a serving unit (`serving-unit.ts`: binding, caches, engine and
|
|
123
|
+
the routes bound to them); the host releases each by the ownership it is given.
|
|
124
|
+
`startApp` (`serve.ts`) composes the state and one host with one close in the
|
|
125
|
+
app's order; `startModelServer` and
|
|
126
|
+
[`mlx-bun/server`](#server-entry-mlx-bunserver) use it.
|
|
27
127
|
|
|
28
|
-
|
|
128
|
+
### Model host: residency by memory fit
|
|
29
129
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
130
|
+
`residency/model-residency.ts` implements the `modelHost` contract (`@mlx-bun/app-core`)
|
|
131
|
+
for models that generate, over units it does not know the kind of: a loaded model
|
|
132
|
+
(`--in-process`) or a worker process that holds one (the default,
|
|
133
|
+
[Runtime isolation](#runtime-isolation-the-default)). Residency is by memory, not count: a model that fits
|
|
134
|
+
the budget loads beside the others; otherwise the least recently used unpinned,
|
|
135
|
+
unleased model is drained (admission stops, work in flight ends), its saved
|
|
136
|
+
prompt/KV state is flushed durably under `MLX_BUN_HOME/kv`, and its weights are
|
|
137
|
+
released, all before the newcomer loads, so resident bytes never exceed the budget
|
|
138
|
+
during a swap. Acquiring it again reloads it, and the next request finds its
|
|
139
|
+
prefix by token match in the saved state (a swap-back follow-up reports the
|
|
140
|
+
prior conversation as `cached_tokens`). A request for a model that cannot fit
|
|
141
|
+
while every other model is busy waits, and never evicts one mid-request. A model
|
|
142
|
+
bigger than the whole budget is still served alone. The budget is `--model-budget`
|
|
143
|
+
(decimal GB; default 70% of the device's recommended working set, one rule for both
|
|
144
|
+
compositions: `defaultBudgetBytes` in `residency/resident-estimate.ts`, or of the RAM the
|
|
145
|
+
GPU can wire, as `/fit` does, when the device does not say); a model that is not loaded needs
|
|
146
|
+
its weights plus the KV and prefill working set of an 8k context (`residency/resident-estimate.ts`,
|
|
147
|
+
the `/fit` model) and, once loaded, what it
|
|
148
|
+
reports it holds (weights, projected KV, RAM prefix cache), floored by the process's measured
|
|
149
|
+
MLX active memory. A runtime that plans its own memory is served alone. The Whisper companion
|
|
150
|
+
counts against the budget and, when it loads, drains a chat model to make room;
|
|
151
|
+
`--whisper-resident` pins it (it is never a victim).
|
|
33
152
|
|
|
34
|
-
|
|
35
|
-
|
|
153
|
+
`server/model-routes.ts` routes by the request's `model`: an exact local id (the ids
|
|
154
|
+
`GET /v1/models` lists) leases that model, and the response body holds the lease until
|
|
155
|
+
it ends, so a model is never released under a stream. Anything else (no id, Pi's `local`,
|
|
156
|
+
a name another server would know) is the current model's, as is every other model-scoped
|
|
157
|
+
path (`/stats`, `/fit`, cache administration, adapters). The current model is the one the
|
|
158
|
+
server started with until `POST /api/hub/serve` (the Models panel) or another
|
|
159
|
+
switch makes a different one current; naming a model in a request never changes it. `GET /v1/models`
|
|
160
|
+
lists every local model with `resident` and `current` (the models module's `/library` marks `serving` and `resident` from the host's own residency); `GET /stats`
|
|
161
|
+
adds `models` (budget, resident bytes, each resident model's bytes, leases and last use).
|
|
162
|
+
Managed jobs and Whisper decoding pause every resident model (`pauseAll`) and hold back new
|
|
163
|
+
loads meanwhile. The host publishes `model.load` (with `resumed` when saved state was found),
|
|
164
|
+
`model.unload` (reason `evicted` or `requested`, whether the flush was durable) and `model.memory`
|
|
165
|
+
on the app's `events` bus, and each unit's engine publishes its request timings and samples through
|
|
166
|
+
`engine/telemetry.ts`; the persistent services' `modelHost` (`served-model-host.ts`) leases the
|
|
167
|
+
current model, whose id follows a switch.
|
|
36
168
|
|
|
37
|
-
|
|
38
|
-
|
|
169
|
+
`--draft-*`, `--adapter` and `--mtp` belong to the model named at startup and apply
|
|
170
|
+
whenever it is (re)loaded; the other models load plain. Adapters mounted at runtime are
|
|
171
|
+
lost when their model is evicted. `--expert-offload` routes the process's loads through one
|
|
172
|
+
file, so that model is all the process serves. A host built with `createServer` around a
|
|
173
|
+
caller's context serves only that context (never evicted); `POST /api/hub/serve` then still
|
|
174
|
+
answers that a restart is needed. The [residency tests](tests/residency/model-residency.test.ts),
|
|
175
|
+
[router tests](tests/server/model-routes.test.ts) and the opt-in
|
|
176
|
+
[native test](tests/engine/model-host-native.test.ts) (the default isolated mode and `--in-process`)
|
|
177
|
+
cover it.
|
|
39
178
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
179
|
+
Worker mode serves over a Unix socket a parent supplies, in another process.
|
|
180
|
+
`jobs/worker-process.ts` is the parent-side owner: it spawns the executable
|
|
181
|
+
captured at startup as `__worker` in the compiled binary or the entry script in
|
|
182
|
+
source runs (or `[...command, "__worker"]` for an explicit mlx-bun command),
|
|
183
|
+
writes the launch record as the first stdin line, reads the ready line from
|
|
184
|
+
stdout, and stops the worker with SIGTERM, then SIGKILL after a grace; the end
|
|
185
|
+
of stdin means the parent is gone. `cli/worker-entry.ts` takes two private
|
|
186
|
+
launch forms, and no flag selects either:
|
|
187
|
+
|
|
188
|
+
- The model form `{ version, socketPath, model, options }`, sent by the default
|
|
189
|
+
isolated `serve` (one worker per resident model), runs `startModelHost` alone,
|
|
190
|
+
for that one model, with the persistent services stubbed because the parent
|
|
191
|
+
owns them.
|
|
192
|
+
- The app form `{ kind: "app", version, socketPath, argv }` runs serve
|
|
193
|
+
arguments (`["--model", model, ...]`) through the CLI's `parseCommand` and
|
|
194
|
+
`runServe`, so the whole app listens on the socket instead of TCP.
|
|
195
|
+
`openIsolatedHost` ([`mlx-bun/engine`](#engine-entry-mlx-bunengine)) sends
|
|
196
|
+
it. `validateAppLaunchArgv` (`serve.ts`) is the CLI's strict
|
|
197
|
+
parse with three differences: `--host`, `--port`, and `--no-open` are
|
|
198
|
+
accepted and never steer the socket bind; the app always loads its models in
|
|
199
|
+
its own process (`--in-process` is implied and `--isolate` is ignored), because a nested
|
|
200
|
+
isolated app would bind TCP instead of the socket; and a
|
|
201
|
+
missing or empty model is refused, because automatic selection may download
|
|
202
|
+
the starter model. SIGTERM, SIGINT, or the end of stdin during startup
|
|
203
|
+
aborts through runServe's startup signal and announces nothing. After ready,
|
|
204
|
+
any of them runs runServe's shutdown under the CLI's deadline
|
|
205
|
+
(`MLX_BUN_SHUTDOWN_TIMEOUT_MS`, default 120 s), and its exit code is the
|
|
206
|
+
worker's. `openIsolatedHost` passes that budget to the supervisor as
|
|
207
|
+
`drainTimeoutMs` and `graceMs` (the supervisor's defaults, 10 s and 3 s,
|
|
208
|
+
serve the model form).
|
|
209
|
+
|
|
210
|
+
`server/worker-routes.ts` answers `GET /health`, `POST /admin/lease`,
|
|
211
|
+
`POST /admin/drain`, `POST /admin/memory/complete`, `POST /admin/adapters` and `GET /admin/events` (the
|
|
212
|
+
worker's own event stream, NDJSON) ahead of the routes on
|
|
213
|
+
the socket only: a TCP listener answers 404 for all of them, and `/engine`
|
|
214
|
+
under `--in-process` is 404 too. In the app form its `/health` replaces
|
|
215
|
+
discovery's, and the transcription-only app, which has no execution lease, has
|
|
216
|
+
no `/admin/lease` route (404). The memory route (see
|
|
217
|
+
[Memory synthesis](#memory-synthesis)) is the model form's: the app form's
|
|
218
|
+
synthesis runs in the app itself, so there it is 404.
|
|
219
|
+
|
|
220
|
+
Both launch forms and the ready line carry the app's package version (the one
|
|
221
|
+
`--version` prints). A worker exits 2 with `worker protocol version mismatch`
|
|
222
|
+
when the launch record carries a different package version. The check compares
|
|
223
|
+
package versions only: it detects a different package version, and two builds
|
|
224
|
+
with the same package version are not told apart. The parent rejects a ready
|
|
225
|
+
line that does not echo its version, and a worker's exit 2 (a refused launch
|
|
226
|
+
record) puts the worker's last stderr line into the rejection.
|
|
227
|
+
|
|
228
|
+
In the app form, memory synthesis runs on the app's own task model, as in
|
|
229
|
+
`serve`, and dataset jobs reach the model through the attached host's own
|
|
230
|
+
`/v1/chat/completions` over its socket (the request's
|
|
231
|
+
`127.0.0.1:<--port>` is a placeholder), so they call the same app, and so does the
|
|
232
|
+
chat module (its Pi SDK takes a base URL rather than a fetch, so it talks to the module's
|
|
233
|
+
private loopback, which leases the model host's `generate` per request, over the socket
|
|
234
|
+
here). An embedding host that forwards Request/Response pairs cannot carry `/ws/chat`,
|
|
235
|
+
because it is a WebSocket upgrade.
|
|
236
|
+
|
|
237
|
+
The app form opens the CLI's user stores under HOME. Its first jobs request
|
|
238
|
+
opens the jobs database and marks every queued or running job as a zombie,
|
|
239
|
+
including another server's. The vault, sessions, skills, and credentials are
|
|
240
|
+
shared without locks, and a worker's job children are not stopped when the
|
|
241
|
+
worker crashes. These behaviors predate the app form and match main. Tests
|
|
242
|
+
compose the app form with a temporary HOME and every storage override.
|
|
243
|
+
|
|
244
|
+
## Runtime isolation (the default)
|
|
245
|
+
|
|
246
|
+
`serve` runs one worker process per resident model and keeps the app up in this
|
|
247
|
+
process, which loads no model; `--in-process` opts out and loads the models in the
|
|
248
|
+
serving process (the composition every earlier release had). `--isolate` is
|
|
249
|
+
accepted, ignored, and warns: it is the default now. Crash isolation is the point: a
|
|
250
|
+
model that crashes the native runtime takes down its own worker, not the app or the
|
|
251
|
+
other models. It costs a process per model and a loopback hop per request.
|
|
252
|
+
|
|
253
|
+
**Process layout.** `serve.ts` resolves the model as usual, then `startModelServer`
|
|
254
|
+
composes `cli/serve-isolated.ts` instead of the direct host: the same persistent
|
|
255
|
+
`createAppState` (web app, download owner, Responses history, memory, jobs,
|
|
256
|
+
sessions, credentials, publishing), the model router and a proxy, and one worker per
|
|
257
|
+
resident model (`cli/worker-unit.ts`), each supervised by `jobs/worker-supervisor.ts`
|
|
258
|
+
over `jobs/worker-process.ts` on its own Unix socket. A worker's launch record pins
|
|
259
|
+
its model by path and carries the parsed serve options (draft and Whisper queries
|
|
260
|
+
already resolved, `isolate` cleared, `inProcess` set; `--draft-*`, `--adapter` and
|
|
261
|
+
`--mtp` go to the startup model's worker only), so a worker never re-resolves a query;
|
|
262
|
+
every exported `MLX_BUN_*` variable reaches it unchanged (that includes
|
|
263
|
+
`MLX_BUN_HOME`, so every worker shares `kv/`), and its output is forwarded to this
|
|
264
|
+
process's log with a `[worker]` prefix. Sockets live in a private `mlx-worker-*`
|
|
265
|
+
temp directory (0700, socket 0600) that this process removes on close. This process
|
|
266
|
+
loads no engine or native module: `serve.ts` imports the model half only inside the
|
|
267
|
+
in-process composition, and the [composition test](tests/serve-isolated.test.ts)
|
|
268
|
+
gates both the static closure and the runtime with tripwire mocks.
|
|
269
|
+
|
|
270
|
+
**Residency.** This process runs the same residency manager as `--in-process`
|
|
271
|
+
([Model host](#model-host-residency-by-memory-fit)) over workers. A model that
|
|
272
|
+
fits `--model-budget` gets a worker beside the others; otherwise the least recently
|
|
273
|
+
used unpinned, unleased model is drained (`POST /admin/drain`, then SIGTERM), its
|
|
274
|
+
worker flushes its saved state and exits, and only then does the newcomer's worker
|
|
275
|
+
spawn, so resident bytes never exceed the budget. A worker gets the CLI's shutdown
|
|
276
|
+
budget (`MLX_BUN_SHUTDOWN_TIMEOUT_MS`, default 120 s, for the drain and again for
|
|
277
|
+
the stop) and is never killed before its flush is durable; it exits 0 when the flush
|
|
278
|
+
was durable and 3 when it was not, which the `model.unload` event reports as
|
|
279
|
+
`flushed`. Naming an evicted model again spawns a worker that resumes from
|
|
280
|
+
`MLX_BUN_HOME/kv` (`cached_tokens` on the next turn). Each worker measures its own MLX
|
|
281
|
+
memory (active, cache and peak bytes, and the device's recommended working set), since
|
|
282
|
+
this process loads no native module: it reports them on `/health` (`memory`) and as a
|
|
283
|
+
`worker.memory` line in its `/admin/events` stream at connect, on each second it changed,
|
|
284
|
+
and after every finished request (the line is consumed here, never republished on the
|
|
285
|
+
bus). A worker counts as active plus cache bytes from its first report (never below the
|
|
286
|
+
weights it reported (the checkpoint size for a transcription companion): MLX maps weights
|
|
287
|
+
lazily, so a worker that has not run yet reads low),
|
|
288
|
+
in place of the `/fit` estimate, which stands only before that report (a model that has not
|
|
289
|
+
loaded yet needs its estimate) and again while the worker is down. Before it decides who
|
|
290
|
+
fits, the residency manager asks every resident worker for a current reading (`GET /health`),
|
|
291
|
+
so a reading the stream has not delivered yet still counts; `/engine` and `/health` list each
|
|
292
|
+
worker's `memory` (`active_bytes`, `cache_bytes`, `peak_bytes`, or `null`), and
|
|
293
|
+
`GET /stats` `models` counts the measured bytes. The default budget is 70% of the working
|
|
294
|
+
set the startup model's worker reports, the in-process rule (`--model-budget` overrides it).
|
|
295
|
+
A consumer that runs long on a worker holds that model's residency lease for as long as
|
|
296
|
+
it runs, so the worker is never drained under it: memory synthesis (a run holds the current
|
|
297
|
+
model's lease from its first stage call to its last; in-process it runs on its own task model and
|
|
298
|
+
holds none) and, in either composition, modules that generate through the model host (each
|
|
299
|
+
`modelHost` lease on the served model holds the residency lease until it is released). Whisper is a worker of its own (the
|
|
300
|
+
transcription-only server, `--whisper-resident` pins it), started on the first audio
|
|
301
|
+
request and drained like a chat model when memory is short.
|
|
302
|
+
Its worker preserves `--whisper-idle-unload` and `--whisper-resident`; a ready
|
|
303
|
+
worker can release idle Whisper weights. `/v1/models` lists the configured companion
|
|
304
|
+
before it starts, keeps its resolved id, and reports its weights' actual residency.
|
|
305
|
+
|
|
306
|
+
**Readiness.** The listener binds after the startup model's worker reports ready (up
|
|
307
|
+
to 15 minutes for large models, as main), so startup succeeds or fails the way the
|
|
308
|
+
in-process composition does: a worker that dies before its first ready line rejects
|
|
309
|
+
startup with its exit and is never retried; the browser opens once the model serves.
|
|
310
|
+
Other workers spawn when a request or the hub names their model, and that request
|
|
311
|
+
waits for the ready line. The Pi backend learns the current model's capabilities,
|
|
312
|
+
generation defaults, and enforced context window from its worker's `/v1/models` and
|
|
313
|
+
`/stats`, again after each switch, and again for a respawned worker.
|
|
314
|
+
|
|
315
|
+
**Application state.** The web app, Pi chat, the Responses history, jobs,
|
|
316
|
+
downloads, sessions, memory (the vault and the synthesis pipeline; its task
|
|
317
|
+
model is the current worker's), the chat module's tool approvals, and the models module (library, hub, adapters
|
|
318
|
+
and cache cleanup, which protects each resident model's reported snapshot uses) live here and survive worker restarts. The chat module runs in this process and reaches the model through the served model host over this listener's
|
|
319
|
+
proxy, so web chat works under isolation (main answered 501 on `/ws/chat`); its
|
|
320
|
+
`local` model is the current one. Managed jobs pause every resident worker through
|
|
321
|
+
each worker's execution lease.
|
|
322
|
+
|
|
323
|
+
**Routing and proxying.** `server/model-routes.ts` is the same router as in-process,
|
|
324
|
+
over worker units: a model-scoped POST is leased to the worker of the model its body
|
|
325
|
+
names (the lease lasts until the response ends, so a stream is never cut by an
|
|
326
|
+
eviction), anything else is the current model's, `GET /v1/models` and
|
|
327
|
+
`/stats` add residency, and `/v1/audio/*` and `/admin/transcription/*` go to Whisper's
|
|
328
|
+
worker. `server/proxy-routes.ts` is one route group mounted after the persistent groups:
|
|
329
|
+
the hop-by-hop headers are stripped, bodies stream through unchanged, a client abort
|
|
330
|
+
aborts the proxied request (`499`), and a worker that cannot take a request answers
|
|
331
|
+
`502 { error: { type: "engine_unavailable", state } }`. `POST /v1/responses` goes
|
|
332
|
+
through `server/responses-client.ts`: `previous_response_id` resolves against this
|
|
333
|
+
process's store, the worker receives the resolved conversation without one (and the
|
|
334
|
+
`x-mlx-bun-response-owner: parent` header), and the completed record is remembered
|
|
335
|
+
here, so conversations survive a worker restart. This process answers `GET /engine`
|
|
336
|
+
(`{ isolated, state, pid, restarts, socket, model, last_exit, response_store, workers }`,
|
|
337
|
+
the current model's worker first and `workers` listing every resident one with its
|
|
338
|
+
`id`, `role`, `pid`, `state`, `restarts` and `socket`; `state` is `starting`, `ready`,
|
|
339
|
+
`restarting`, `exhausted`, `closed`, or `none` when no worker is resident),
|
|
340
|
+
`GET /health` (`{ status: "ok", isolated, engine, workers }`), `GET /stats` (the current
|
|
341
|
+
worker's body with the residency block, this process's `response_store` and an `engine`
|
|
342
|
+
report; with no worker, 200 with only this process's part and an `unavailable` message),
|
|
343
|
+
and `/admin/lease`, `/admin/drain`, `/admin/memory/complete`, `/admin/events`, `/admin/adapters` and
|
|
344
|
+
`/v1/memory/synthesize` stay socket-only or this process's own and are not forwarded. The hub's `POST /api/hub/serve` is answered here: it acquires the
|
|
345
|
+
model (spawning its worker, draining another when memory is short) and makes it current;
|
|
346
|
+
`404` for an id that is not local, `502` for a failed spawn.
|
|
347
|
+
|
|
348
|
+
**Events.** Each worker publishes its engine's events (request timings, samples,
|
|
349
|
+
prefix and KV counters, load progress) on its own bus and serves them on
|
|
350
|
+
`GET /admin/events`; this process subscribes to every worker's stream and republishes
|
|
351
|
+
onto the app's bus, so the metrics module sees all of them. The residency manager
|
|
352
|
+
here reports `model.load`, `model.unload` and `model.memory` (a worker's own load and
|
|
353
|
+
unload events are dropped), and a respawned worker's stream is resubscribed.
|
|
354
|
+
|
|
355
|
+
**Crashes.** A worker's unexpected exit is respawned by its supervisor with that
|
|
356
|
+
worker's model (the one that was resident, not the startup model) within main's
|
|
357
|
+
budget: at most three restarts in a rolling 60-second window. A worker respawns at once
|
|
358
|
+
after a single crash, even one within 10 seconds of its spawn; a worker that dies within
|
|
359
|
+
10 seconds of its spawn again straight after such a death waits 5 seconds before the retry. The other workers
|
|
360
|
+
and the app are untouched. A managed job's execution lease is a connection-owned
|
|
361
|
+
`POST /admin/lease` inside each worker, so a respawn waits until it is released and a
|
|
362
|
+
reload never shares the GPU with a job. Requests in flight when a worker dies end with
|
|
363
|
+
`502`; an SSE response ends with the protocol's own error frame naming the cause
|
|
364
|
+
(OpenAI `data: {"error"}`, Anthropic `event: error`, Responses `event: error`), so a
|
|
365
|
+
client parser ends with an error rather than a silent truncation and Pi ends the turn
|
|
366
|
+
with an error frame instead of replaying the generation. While a worker is down,
|
|
367
|
+
new requests for its model answer 502 with the reason and `- retry shortly`; a worker
|
|
368
|
+
that exhausts its budget is reported `exhausted` and is replaced by a fresh spawn on
|
|
369
|
+
the next request that names its model (a broken unit is evicted, never leased). The
|
|
370
|
+
[supervisor](tests/jobs/worker-supervisor.test.ts), [proxy](tests/server/proxy-routes.test.ts)
|
|
371
|
+
and [composition](tests/serve-isolated.test.ts) tests drive these paths against a fake
|
|
372
|
+
worker over a real socket; the opt-in [native test](tests/engine/model-host-native.test.ts)
|
|
373
|
+
kills a real worker mid-request.
|
|
374
|
+
|
|
375
|
+
**Shutdown.** Synthesis runs, jobs, and downloads stop while the workers are alive, chat
|
|
376
|
+
sessions and HTTP responses drain, then every worker is drained, sent SIGTERM (killed
|
|
377
|
+
only after the CLI's shutdown budget), and joined, and the socket directory is removed; a
|
|
378
|
+
respawn in progress is joined too. A worker's own close aborts and joins its memory calls,
|
|
379
|
+
then closes its task model, before its engine.
|
|
380
|
+
|
|
381
|
+
**Deviations from main.** Main bound the listener before the engine loaded and
|
|
382
|
+
made every request wait on readiness, including across restarts, retrying bodyless
|
|
383
|
+
GET/HEAD once; here the listener binds after the first ready line and a request
|
|
384
|
+
during a restart fails fast with 502 so the UI can report the restart instead of
|
|
385
|
+
hanging, and nothing is retried. Main answered 501 on `/ws/chat`; Pi lives in the
|
|
386
|
+
parent here. Main's parent `/health` was the child's; here it is this process's with
|
|
387
|
+
the workers'. Pi's own SDK policy still retries a request refused with 502 before
|
|
388
|
+
generation started (three attempts, 2/4/8 s), which rides out a fast respawn; a
|
|
389
|
+
generation that started is never replayed. A Whisper checkpoint as the main model
|
|
390
|
+
serves the transcription-only server in this process, as before.
|
|
391
|
+
|
|
392
|
+
The CLI uses public library APIs. It does not own cache indexing, downloads,
|
|
393
|
+
fit calculations, model graphs, or numerical execution.
|
|
394
|
+
|
|
395
|
+
From the repository root, run `bun apps/mlx-bun/src/cli/main.ts --help`.
|
|
396
|
+
The [executable behavior examples](tests/hub-cli.test.ts) demonstrate scanning,
|
|
397
|
+
listing, fit estimates, and safe GC against a temporary synthetic cache with
|
|
398
|
+
native MLX blocked. Run them with `bun run --filter mlx-bun test`.
|
|
399
|
+
`bun scripts/verify-packages.ts --app-only` repeats those tests through an
|
|
400
|
+
installed package artifact, compiling only the app microphone helper and never
|
|
401
|
+
loading MLX or accessing audio.
|
|
402
|
+
|
|
403
|
+
`gc` previews changes unless `--yes` is supplied; `--dry-run` always prevents
|
|
404
|
+
deletion. Cache location and Hugging Face credentials follow the
|
|
405
|
+
[hub library](../../packages/hub/README.md). The app and public libraries use MIT
|
|
406
|
+
licensing and version 1.0.0 for the first workspace release. The root workspace,
|
|
407
|
+
website and transcription example app remain private.
|
|
408
|
+
|
|
409
|
+
## One-shot inference
|
|
410
|
+
|
|
411
|
+
`mlx-bun generate [query] --prompt "…"` (alias `gen`) prints generated text and
|
|
412
|
+
exits. It uses the model's chat template when available; `--raw` feeds the prompt
|
|
413
|
+
verbatim. Main's greedy sampling, 256-token default cap, optional sampler/KV
|
|
414
|
+
settings, and unfiltered generated text are CLI policy. Model-author serving
|
|
415
|
+
sampling defaults do not replace them. Tier and compiled-step switches are not
|
|
416
|
+
exposed (compiled decode is a composition field the loader resolves), and the
|
|
417
|
+
request runs on the same continuous engine at capacity one, a batch of one.
|
|
418
|
+
`generate --help` lists the accepted settings, including every `--kv-quant`
|
|
419
|
+
scheme `serve` takes and `--quantized-kv-start`.
|
|
420
|
+
|
|
421
|
+
`mlx-bun embed [query] --text "…"` prints a vector per text, or one OpenAI-style
|
|
422
|
+
list with `--json`. Without text it reads nonempty stdin lines; `--instruct`
|
|
423
|
+
retains the query instruction. Without a model query it selects the first cached
|
|
424
|
+
embedding model. Generate resolves a local directory or registry query directly.
|
|
425
|
+
Neither command downloads models, opens a listener, or starts the browser.
|
|
426
|
+
|
|
427
|
+
`cli/inference.ts` owns argument policy, model selection, and terminal output.
|
|
428
|
+
The model host allows a missing template for these consumers; serving still
|
|
429
|
+
requires one. Shared text prompt construction preserves the CLI's explicit
|
|
430
|
+
no-added-specials encoding and HTTP's existing duplicate-BOS correction.
|
|
431
|
+
Both commands close the owned engine after success, failure, or cancellation.
|
|
432
|
+
[CPU examples](tests/inference-cli.test.ts) cover behavior and composition and
|
|
433
|
+
run against the installed artifact in `verify-packages --app-only`;
|
|
434
|
+
real-weight correctness and speed remain separate verification.
|
|
435
|
+
|
|
436
|
+
## mlx-lm compatibility (`mlx-bun.<cmd>`)
|
|
437
|
+
|
|
438
|
+
mlx-bun is a superset of mlx-lm's command line under its own names. Each alias
|
|
439
|
+
runs the matching verb with `mlx_lm.<cmd>`'s argument spellings translated, and
|
|
440
|
+
still accepts the verb's own options; `mlx-bun.<cmd> --help` lists what it maps,
|
|
441
|
+
accepts without effect, and refuses. An explicit path behaves as in mlx-lm; a
|
|
442
|
+
default output path does not use the working directory (`mlx_model`,
|
|
443
|
+
`fused_model`, `adapters`) but stays under `~/.mlx-bun`, and every other default
|
|
444
|
+
is the verb's, not mlx-lm's (for example `generate` stops at 256 tokens, not 100).
|
|
445
|
+
|
|
446
|
+
| Alias | Verb | mlx_lm flags translated |
|
|
447
|
+
| --- | --- | --- |
|
|
448
|
+
| `mlx-bun.server` | `serve` | `--model --adapter-path --host --port --decode-concurrency --max-tokens --temp --top-p --top-k --draft-model --num-draft-tokens`; headless (`--no-open`) unless told otherwise |
|
|
449
|
+
| `mlx-bun.generate` | `generate` | `--model --prompt/-p (- reads stdin) --max-tokens/-m --temp --top-p --top-k --min-p --min-tokens-to-keep --xtc-probability --xtc-threshold --seed --system-prompt --adapter-path --ignore-chat-template --kv-bits (4 or 8, from token 5000) --trust-remote-code --verbose` |
|
|
450
|
+
| `mlx-bun.convert` | `convert` | `--hf-path/--model --mlx-path -q --q-bits --q-group-size --dtype -d/--dequantize --upload-repo --trust-remote-code` (4/8-bit, group 32/64, affine) |
|
|
451
|
+
| `mlx-bun.fuse` | `fuse` | `--model --adapter-path --save-path --dequantize --upload-repo` |
|
|
452
|
+
| `mlx-bun.lora` | `train` (SFT) | `--model --train --data --fine-tune-type lora --optimizer adam/adamw --num-layers --batch-size --iters --val-batches --learning-rate --steps-per-report --steps-per-eval --grad-accumulation-steps --resume-adapter-file --adapter-path --save-every --max-seq-length --grad-checkpoint --seed --mask-prompt -c/--config` (YAML) |
|
|
453
|
+
| `mlx-bun.upload` | `upload` | `--path --upload-repo` |
|
|
454
|
+
|
|
455
|
+
Not supported, each an error naming the mlx_lm flag: `generate` `--extra-eos-token
|
|
456
|
+
--prefill-response --use-default-chat-template --chat-template-config
|
|
457
|
+
--max-kv-size --prompt-cache-file --quantize-activations --draft-model
|
|
458
|
+
--num-draft-tokens`, `--kv-bits` other than 4/8, `--kv-group-size` other than 64;
|
|
459
|
+
`server` `--allowed-origins --log-level --chat-template --use-default-chat-template
|
|
460
|
+
--chat-template-args --min-p --prompt-concurrency --prefill-step-size
|
|
461
|
+
--prompt-cache-size --prompt-cache-bytes --pipeline`; `convert` `--quant-predicate`
|
|
462
|
+
and `--q-mode` other than affine, `--q-bits` other than 4/8, `--q-group-size` other
|
|
463
|
+
than 32/64; `fuse` `--export-gguf --gguf-path`; `lora` `--test --test-batches`,
|
|
464
|
+
`--fine-tune-type dora|full`, optimizers other than adam/adamw, `--clear-cache-threshold
|
|
465
|
+
--report-to --project-name`, and the YAML keys `lr_schedule` and `lora_parameters.keys`.
|
|
466
|
+
argparse's unique-prefix abbreviations (`--max-tok`) are not accepted. Two behaviors differ without an error: `mlx-bun.lora` always masks the prompt in the
|
|
467
|
+
loss (mlx-lm does so only with `--mask-prompt`), and `--trust-remote-code` is
|
|
468
|
+
accepted because mlx-bun never runs code from a model repository.
|
|
469
|
+
|
|
470
|
+
mlx-lm's other console scripts have no counterpart and get no alias: `chat` (no
|
|
471
|
+
terminal chat; use the web app), `benchmark`, `cache_prompt`, `evaluate`,
|
|
472
|
+
`perplexity` (use `mlx-bun perplexity` with a local data file), `manage`, `share`, and the quantizers `awq`, `dwq`, `dynamic_quant`
|
|
473
|
+
and `gptq` (`convert --target-bpw` is the mixed-precision path).
|
|
474
|
+
|
|
475
|
+
Delivery: the package's `bin` links `mlx-bun.<cmd>` to `bin/mlx-bun.<cmd>.mjs`, a
|
|
476
|
+
one-line file that runs the shared launcher (npm and Bun resolve a bin to its real
|
|
477
|
+
file, so the file's name is the invoked name); the standalone executable
|
|
478
|
+
dispatches on the name it was started as; `scripts/install.sh` links each alias
|
|
479
|
+
to the same executable beside `~/.local/bin/mlx-bun`, and `bun run link-cli`
|
|
480
|
+
links them into `~/.bun/bin`. [Alias tests](tests/mlx-lm-aliases.test.ts) run
|
|
481
|
+
mlx_lm's own argument forms through each alias and the launcher files;
|
|
482
|
+
[installer tests](tests/install.test.ts) cover the links.
|
|
44
483
|
|
|
45
|
-
|
|
46
|
-
ships a launcher and TypeScript source, then fetches the native runtime pack
|
|
47
|
-
on first use. See [distribution](./docs/reference/distribution.md) for details.
|
|
484
|
+
## Engine
|
|
48
485
|
|
|
49
|
-
|
|
486
|
+
`src/engine/` owns loaded model lifetimes, preparation admission and the shared
|
|
487
|
+
continuous scheduler. `createAppEngine` takes ownership of its model context;
|
|
488
|
+
closing drains execution before releasing compiled runners, adapters, drafts,
|
|
489
|
+
model constants and weights. Replacement models can provide their own binding.
|
|
490
|
+
HTTP parsing, response formatting and web policy belong above this boundary.
|
|
50
491
|
|
|
51
|
-
|
|
52
|
-
|
|
492
|
+
[Engine behavior tests](tests/engine/model-host.test.ts) verify injected model
|
|
493
|
+
loading and lifecycle without native MLX. The gateway and session tests beside
|
|
494
|
+
it cover cancellation, output delivery and the same scheduler path at capacity
|
|
495
|
+
one and greater. These are CPU checks, not real-weight numerical verification.
|
|
496
|
+
A request shape the loaded composition does not serve gets the typed capability
|
|
497
|
+
error; there is no other path to fall to. Diffusion models are served through the same
|
|
498
|
+
scheduler by the library's interleaved denoising method. An image request's pixels
|
|
499
|
+
ride its options, and the gateway releases them exactly once, whether the row
|
|
500
|
+
settles or fails before submission. Requests the method cannot apply (grammar,
|
|
501
|
+
draft, logprobs, logits processors, fill) receive the typed
|
|
502
|
+
capability error; its caches are the legos the composition's KV scheme and paging name.
|
|
503
|
+
|
|
504
|
+
`engine/cache-services` builds the loaded model's cache service (the library's
|
|
505
|
+
`state/cache-service`) from the serve flags: RAM prefixes, saved state, generation
|
|
506
|
+
checkpoints, `--kv-budget` admission and paging, behind one interface. Its
|
|
507
|
+
default is 8 GB of RAM with plain KV; a caller supplies `ssdCacheDir` for saved state.
|
|
508
|
+
`mlx-bun serve` supplies it by default (`MLX_BUN_HOME/kv`, `--ssd-cache <dir>` moves it,
|
|
509
|
+
`off` disables it): one directory per model identity under the root, and
|
|
510
|
+
`engine/kv-budget.ts` holds one byte budget across all of them (`--ssd-cache-max`,
|
|
511
|
+
default 20 GiB, 0 unlimited). A live store is lent what the other stores and idle
|
|
512
|
+
directories leave (never less than a quarter of the budget); a closed store's
|
|
513
|
+
directory is idle, and idle directories lose their oldest files first, at start and
|
|
514
|
+
after every close. A model that returns finds its directory, and its prefix, intact.
|
|
515
|
+
The hub's resolved model directory supplies the identity, so startup does not hash
|
|
516
|
+
weight files. Saved KV is disposable; after replacing weights in place, clear it
|
|
517
|
+
when you want fresh state.
|
|
518
|
+
Composition passes the returned `cache` and `adapterNamespace` as gateway options;
|
|
519
|
+
`/stats`, the cache routes, telemetry and the model host read the service's own
|
|
520
|
+
`report()`. The engine borrows the service; construction alone does not attach it.
|
|
521
|
+
Pass its `close` through the owned `beforeModelDispose` hook so shutdown
|
|
522
|
+
drains execution, flushes persistence, clears cache state, then frees the model.
|
|
523
|
+
`flush` also supports explicit durability checks while the app is running:
|
|
524
|
+
`server/cache-routes.ts` serves main's `POST /admin/cache/flush` (200 with the
|
|
525
|
+
durability counters plus `entries` and `longest_durable_prefix_tokens`, 503
|
|
526
|
+
with the same counters when anything remains pending or failed) and
|
|
527
|
+
`POST /admin/cache/session/close` (`closed` reports whether a string
|
|
528
|
+
`session_id` was supplied; closing drops the session's cache association and
|
|
529
|
+
never deletes checkpoints, and it never waits for a flush in progress).
|
|
530
|
+
Cache policy tests inject storage and allocator ports; real SSD/numerical runs
|
|
531
|
+
remain separate verification.
|
|
532
|
+
|
|
533
|
+
## Local decisions
|
|
534
|
+
|
|
535
|
+
`POST /v1/decisions` accepts the OpenAI Decisions request and returns its ordered
|
|
536
|
+
answer array (`predicate`, `choice`, `score`). `POST /v1/systemone` accepts Jev's
|
|
537
|
+
`state` and questions map and returns its keyed answers (`noul`, `choice`, `score`).
|
|
538
|
+
Both endpoints normalize into one handler and one numerical operation. Question
|
|
539
|
+
names identify responses and are excluded from the model input. Jev's structured
|
|
540
|
+
instructions/criteria, gzip/zstd requests and multipart/base64 image assets are
|
|
541
|
+
accepted; OpenAI accepts text and `input_image` parts through the existing media
|
|
542
|
+
preparation. Images require a graph with the corresponding media operations.
|
|
543
|
+
Box questions and denoising/pixel-input decision graphs are unsupported.
|
|
544
|
+
|
|
545
|
+
Point OpenAI's `baseURL` at `http://localhost:8080/v1`, or TypeSafe's `baseURL` at
|
|
546
|
+
`http://localhost:8080`, and set `model` to the exact local ID from `GET /v1/models`,
|
|
547
|
+
for example `mlx-community/gemma-4-e4b-it-OptiQ-4bit`. The residency host leases
|
|
548
|
+
that model, loading it when needed and sharing its resident weights with generation.
|
|
549
|
+
The existing current-model fallback also accepts cloud names for compatibility;
|
|
550
|
+
the local examples use the actual model ID. Responses identify the model that actually ran. The same loaded
|
|
551
|
+
model serves generation and decisions: each candidate is a scheduler task between
|
|
552
|
+
generation iterations, with private caches and its own adapter scope.
|
|
553
|
+
|
|
554
|
+
Each supplied answer is encoded as its complete JSON value (score levels use
|
|
555
|
+
their label), followed by the chat template's assistant-turn ending through its
|
|
556
|
+
first stop marker, including a next-role marker for templates that use that
|
|
557
|
+
boundary (the tokenizer's end token when no template ending is available). A teacher-forced forward
|
|
558
|
+
reads the prompt and supplied answer tokens. The response mask counts only
|
|
559
|
+
answer-token predictions, including the terminator. DPO's existing summed log
|
|
560
|
+
likelihood is divided by the answer token count, then normalized as
|
|
561
|
+
`softmax(-NLL / answer_token_count)` across the supplied candidates. No token is
|
|
562
|
+
sampled or generated. Scores use average token log likelihood, so longer answers
|
|
563
|
+
do not receive a multiplicative length penalty from summing token losses.
|
|
564
|
+
Scores come directly from the loaded model's answer likelihoods; there
|
|
565
|
+
is no tuning or calibration stage. Choice/score confidence is a statistic of
|
|
566
|
+
that distribution, computed with Jev's documented formulas.
|
|
567
|
+
Usage counts the tokens processed across candidate forwards and reports zero
|
|
568
|
+
output tokens. These operations currently keep no reusable prompt cache;
|
|
569
|
+
OpenAI cache counters are zero and cache hints do not change scoring.
|
|
570
|
+
Decision requests use bounded engine admission, returning 429 when its wait queue
|
|
571
|
+
is full. Image preparations hold the same media reservation used by chat until
|
|
572
|
+
the question finishes scoring or fails.
|
|
573
|
+
|
|
574
|
+
With a cached vision model, for example:
|
|
53
575
|
|
|
54
576
|
```sh
|
|
55
|
-
mlx-bun serve
|
|
577
|
+
mlx-bun serve mlx-community/gemma-4-e4b-it-OptiQ-4bit
|
|
578
|
+
bun apps/mlx-bun/scripts/decision-example.ts http://localhost:8080 mlx-community/gemma-4-e4b-it-OptiQ-4bit
|
|
579
|
+
node apps/mlx-bun/scripts/decision-website-examples.mjs http://localhost:8080 mlx-community/gemma-4-e4b-it-OptiQ-4bit /path/to/lane-scene.png
|
|
56
580
|
```
|
|
57
581
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
582
|
+
The [HTTP example](scripts/decision-example.ts) covers all three question types
|
|
583
|
+
through both endpoints and generation through the same model. The
|
|
584
|
+
[website examples](scripts/decision-website-examples.mjs) use the upstream OpenAI
|
|
585
|
+
and TypeSafe SDKs with local connection settings (Node 22+); a supplied image runs
|
|
586
|
+
the lane-choice scenario. OpenAI's lane widget is a scripted illustration, so use
|
|
587
|
+
a scene-only capture without the widget's displayed probabilities. The opt-in
|
|
588
|
+
[acceptance test](tests/engine/decisions-native.test.ts) runs these examples with
|
|
589
|
+
`MLX_BUN_TEST_NATIVE=1 MLX_BUN_APP_TEST_MODEL=<cached snapshot>` and optional
|
|
590
|
+
`MLX_BUN_DECISION_IMAGE=<lane capture.png>`; it also compares concurrent generation
|
|
591
|
+
against a solo greedy control. Tests without weights check SDK wire compatibility,
|
|
592
|
+
validation, compression, complete answer preservation, cancellation and disposal.
|
|
61
593
|
|
|
62
|
-
|
|
594
|
+
## Server seams
|
|
63
595
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
596
|
+
`server/routes.ts` composes chat/text completion, Anthropic Messages, Responses, embedding, and discovery
|
|
597
|
+
handlers over an injected engine. Its `handle(Request)` returns a response or
|
|
598
|
+
`null` for the next application surface; it never opens a socket or closes the
|
|
599
|
+
borrowed engine. Application startup owns those lifetimes.
|
|
600
|
+
|
|
601
|
+
Tool-approval settings are the chat module's. Confirmed cache cleanup (`/api/gc/*`) is the models module's,
|
|
602
|
+
over the hub library and the model host's resident models.
|
|
603
|
+
|
|
604
|
+
Inside `server/`, request parsing and prompt preparation precede the single-use
|
|
605
|
+
admission plan. The completion executor consumes the engine contract; the sink
|
|
606
|
+
and OpenAI wire modules own reasoning/tool/content events, JSON, and SSE.
|
|
607
|
+
`prompt-contracts.ts` describes owned media inputs; `media-prompt.ts` owns the
|
|
608
|
+
HTTP media policy (which content parts are present, the video guard, and the 400
|
|
609
|
+
text for each refusal) and delegates to the loaded model's media preparation.
|
|
610
|
+
`engine/media-preparation.ts` binds one route per family when the model loads;
|
|
611
|
+
each route borrows the context's tokenizer, template, token ids and lazily loaded
|
|
612
|
+
towers and calls the library's numerical input builders. The opt-in
|
|
613
|
+
[native media test](tests/engine/media-native.test.ts) (`MLX_BUN_TEST_NATIVE=1` plus
|
|
614
|
+
`MLX_BUN_APP_TEST_GEMMA4_AUDIO_MODEL`, `MLX_BUN_APP_TEST_GEMMA4_UNIFIED_MODEL`,
|
|
615
|
+
`MLX_BUN_APP_TEST_QWEN_VISION_MODEL` or `MLX_BUN_APP_TEST_DIFFUSION_MODEL`, each a
|
|
616
|
+
cached snapshot directory) prepares image, audio, mixed and video prompts through a
|
|
617
|
+
loaded model with synthesized media and a warm encoder cache; the opt-in
|
|
618
|
+
[media generation test](tests/engine/media-generation.test.ts) serves the same
|
|
619
|
+
Gemma4 checkpoint and generates from image, audio and mixed image+audio prompts
|
|
620
|
+
(determinism, streaming, rows released together, disconnect recovery). Grammar and media
|
|
621
|
+
work enter the engine's preparation domain before allocating native resources.
|
|
622
|
+
Text-only protocol work loads no MLX library. `anthropic.ts` translates Messages
|
|
623
|
+
requests and semantic completion events, including tools, thinking, and usage.
|
|
624
|
+
Its JSON and SSE paths use the same preparation, capability admission, scheduler,
|
|
625
|
+
and cancellation as chat completions; no second service is created.
|
|
626
|
+
`responses.ts` owns Responses translation and its process-local, one-hour,
|
|
627
|
+
32 MiB history. Successful JSON and SSE requests retain input, output, and
|
|
628
|
+
instructions for `previous_response_id`; failed or cancelled requests do not.
|
|
629
|
+
A generation error ends SSE with `response.failed`, without a misleading completion event. Composition
|
|
630
|
+
may supply `responseHistory` to replace the store; no isolated-worker forwarding
|
|
631
|
+
or duplicate completion service is involved.
|
|
632
|
+
|
|
633
|
+
The [request pipeline](tests/server/pipeline.test.ts) and
|
|
634
|
+
[HTTP examples](tests/server/routes.test.ts) execute with an injected engine,
|
|
635
|
+
including cancellation and ownership cleanup. Real-weight media and generation
|
|
636
|
+
verification remains separate; these tests prove the HTTP/engine boundary.
|
|
637
|
+
`server/start.ts` mounts HTTP, browser, and the modules' WebSocket handlers. Shutdown
|
|
638
|
+
stops the modules (the chat's sockets and turns), then closes connections, drains
|
|
639
|
+
execution, flushes caches, and releases the model. Its [listener tests](tests/server-start.test.ts) exercise real loopback
|
|
640
|
+
sockets without native MLX. The opt-in [model test](tests/engine/http-generation.test.ts)
|
|
641
|
+
uses `MLX_BUN_APP_TEST_MODEL=<cached-directory>` to exercise actual app startup,
|
|
642
|
+
lone/concurrent deterministic completion, live cache counters, stream cancellation,
|
|
643
|
+
Messages JSON, Responses JSON with a chained follow-up, and a real Pi WebSocket
|
|
644
|
+
turn with session persistence and replay. All chat, vault, and skill paths are
|
|
645
|
+
temporary; HTTP and WebSocket waits share a bounded network budget before shutdown.
|
|
646
|
+
The cached checkpoint used for this smoke is `mlx-community/MiniCPM5-1B-OptiQ-4bit`,
|
|
647
|
+
revision `664aabaed233c653f82716d8dc822234d0091f78`.
|
|
648
|
+
It never downloads weights; missing native libraries or an invalid supplied
|
|
649
|
+
checkpoint fail. This checks HTTP/Pi behavior, not quantize jobs, a compiled-binary
|
|
650
|
+
lifecycle, numerical parity, or performance.
|
|
651
|
+
|
|
652
|
+
The opt-in [paged KV test](tests/engine/paged-kv.test.ts) also covers Gemma4
|
|
653
|
+
HTTP cancellation for each paged reader: the gathered reader and the direct
|
|
654
|
+
reader that `--paged-kv-direct` composes (the composition's `pagedKv.direct`,
|
|
655
|
+
which `/stats` prints). A client leaves a greedy
|
|
656
|
+
`/v1/completions` stream mid-generation; the server observes the disconnect, the
|
|
657
|
+
row stops storing tokens before its natural end, and the scheduler drains. The
|
|
658
|
+
same server then answers another prompt and the abandoned one exactly as a fresh
|
|
659
|
+
paged server with the same reader does, and the frames the client received equal
|
|
660
|
+
a control stopped (`max_tokens`) at the token that published the last of them.
|
|
661
|
+
Every paged cache in the case uses the selected reader; both servers run without
|
|
662
|
+
the RAM prompt cache so every compared request prefills cold. Run with
|
|
663
|
+
`cd apps/mlx-bun && MLX_BUN_COMPILED_GEMMA_E4B=<cached Gemma4 snapshot directory> bun test tests/engine/paged-kv.test.ts -t "reader"`.
|
|
664
|
+
|
|
665
|
+
`server/status-routes.ts` borrows live cache, scheduler, model diagnostic and
|
|
666
|
+
Responses-history counters for `GET /stats`; `GET /fit` uses the public inference
|
|
667
|
+
fit functions (`@mlx-bun/inference/execution/fit`) and the served artifact metadata. Predictions remain advisory and do
|
|
668
|
+
not impose an admission limit. A runtime that plans its own memory reports that
|
|
669
|
+
plan (`/fit`'s `plan` object lists its streamed tiers and generation allowance;
|
|
670
|
+
`/stats.runtime` carries the runtime's own telemetry) instead of the generic
|
|
671
|
+
resident-weight estimate. `/stats.batch` reports the continuous scheduler
|
|
672
|
+
(`mode: "batch"`, `batched: true`) at every capacity, one included. Pending SSD counters include the generation
|
|
673
|
+
checkpoint queue as well as prompt-cache persistence work.
|
|
674
|
+
Historical EvalDB measurements have no migrated owner, so measurement fields
|
|
675
|
+
remain null; old machine-specific throughput constants are not reported as
|
|
676
|
+
measurements for the current server or CLI fit output. The library retains those
|
|
677
|
+
historical constants. The dashboard shows an unavailable marker
|
|
678
|
+
when no estimate exists. [Status tests](tests/server/status-routes.test.ts) use
|
|
679
|
+
synthetic counters and CPU fit inputs without a model or native MLX.
|
|
680
|
+
|
|
681
|
+
## Client entry (`mlx-bun/client`)
|
|
682
|
+
|
|
683
|
+
`mlx-bun/client` (`src/server/client.ts`) is main's embedding client. It
|
|
684
|
+
imports no other module, so it loads without native MLX. `mlx-bun/engine`
|
|
685
|
+
(below) re-exports it.
|
|
686
|
+
|
|
687
|
+
- `createCompletionClient({ baseUrl, headers?, host? })` posts to
|
|
688
|
+
`<baseUrl>/chat/completions` (or `route: "completions"`) with the caller's
|
|
689
|
+
`headers` merged with `content-type: application/json`, the body with
|
|
690
|
+
`stream: false` forced, and the call's `signal`. The transport is the supplied
|
|
691
|
+
host's `forward`, otherwise the global `fetch`; the host stays the caller's.
|
|
692
|
+
A POST is never retried: a non-2xx status rejects with the status and response
|
|
693
|
+
text, and a result without a `choices` array rejects.
|
|
694
|
+
- `createDirectHost(handler, shutdown?)` gives an in-process
|
|
695
|
+
`(Request) => Promise<Response>` handler the same host shape. It is an adapter,
|
|
696
|
+
not a model loader. `close()` refuses new work, waits for handlers in flight,
|
|
697
|
+
then calls `shutdown` once; a handler owns any response stream it returns, and
|
|
698
|
+
streams pass through unchanged.
|
|
699
|
+
- Types: `EngineHost`, `CompletionCall`, `CompletionResponse`, and main's
|
|
700
|
+
completion contracts `CompletionClient`, `BatchCompletionClient` (ordered
|
|
701
|
+
`completeBatch`), and `TaskClient` (`run` with progress and an optional
|
|
702
|
+
`Cancellation`).
|
|
703
|
+
|
|
704
|
+
The [client tests](tests/client.test.ts) import the entry through the export
|
|
705
|
+
map with native MLX blocked; `verify-packages` repeats them from the installed package.
|
|
706
|
+
|
|
707
|
+
## Engine entry (`mlx-bun/engine`)
|
|
708
|
+
|
|
709
|
+
`mlx-bun/engine` (`src/cli/engine-entry.ts`) is main's `src/library.ts`
|
|
710
|
+
without `initializeMlx`. It loads without native MLX and starts nothing on
|
|
711
|
+
import. It re-exports the client above with its contract types,
|
|
712
|
+
`createInferenceEngine` and `CancellationSource` with main's generation
|
|
713
|
+
contract types from `@mlx-bun/inference`, and adds `openIsolatedHost`. Main's
|
|
714
|
+
`initializeMlx` and its native bootstrap were removed deliberately: the native
|
|
715
|
+
runtime ships inside the packages and loads on first use, so there is no
|
|
716
|
+
acquisition step to call and no root (`.`) namespace to return. Import each
|
|
717
|
+
member from its owner instead (the migration table below). The root's selection helpers are
|
|
718
|
+
`mlx-bun/selection` (below), and main's in-process server is
|
|
719
|
+
[`mlx-bun/server`](#server-entry-mlx-bunserver).
|
|
720
|
+
|
|
721
|
+
```ts
|
|
722
|
+
import { createCompletionClient, openIsolatedHost } from "mlx-bun/engine";
|
|
723
|
+
|
|
724
|
+
const host = await openIsolatedHost("/path/to/hf-snapshot", { arguments: ["--max-tokens", "256"] });
|
|
725
|
+
try {
|
|
726
|
+
const client = createCompletionClient({ baseUrl: "http://engine/v1", host });
|
|
727
|
+
const result = await client.complete({ body: { messages: [{ role: "user", content: "Hello" }] } });
|
|
728
|
+
console.log(result.choices[0]?.message?.content);
|
|
729
|
+
} finally {
|
|
730
|
+
await host.close();
|
|
731
|
+
}
|
|
68
732
|
```
|
|
69
733
|
|
|
734
|
+
The caller owns the host: `openIsolatedHost` starts one worker process in the
|
|
735
|
+
app launch form above, and `close()` stops it and removes its private socket
|
|
736
|
+
directory after it exits. The function's JSDoc states the argument, command,
|
|
737
|
+
retry, and close rules.
|
|
738
|
+
|
|
739
|
+
- `arguments` are `mlx-bun serve` arguments. `--host`, `--port`, and
|
|
740
|
+
`--no-open` only concern a TCP listener and have no effect on the socket;
|
|
741
|
+
`--isolate` is ignored, as the app form is always in-process.
|
|
742
|
+
- `command` must run the mlx-bun CLI of the same package version: the
|
|
743
|
+
installed binary, `[bun, <package>/bin/mlx-bun.mjs]`, or
|
|
744
|
+
`[bun, <package>/src/cli/main.ts]`. A compiled consumer must supply it,
|
|
745
|
+
because its own executable is not the mlx-bun CLI. The version handshake
|
|
746
|
+
refuses another package version but cannot tell apart two builds with the
|
|
747
|
+
same version.
|
|
748
|
+
- The host forwards to the whole app, so the app form's limitations above
|
|
749
|
+
apply: the user stores under HOME are shared with other mlx-bun processes
|
|
750
|
+
without locks, and `/ws/chat` cannot pass through `forward`. These predate this entry.
|
|
751
|
+
|
|
752
|
+
The [engine tests](tests/engine-entry.test.ts) import the entry through the
|
|
753
|
+
export map with native MLX blocked and drive the host over a fake worker;
|
|
754
|
+
`verify-packages` repeats them from the installed package. The worker's own
|
|
755
|
+
refusal and startup behaviors are covered by the
|
|
756
|
+
[worker-entry](tests/worker-entry.test.ts) and
|
|
757
|
+
[worker-process](tests/jobs/worker-process.test.ts) tests. The opt-in
|
|
758
|
+
[native test](tests/engine/library-host.test.ts) (`MLX_BUN_TEST_NATIVE=1` with
|
|
759
|
+
`MLX_BUN_APP_TEST_MODEL` and/or `MLX_BUN_APP_TEST_WHISPER_MODEL`) runs real
|
|
760
|
+
consumers, including a standalone CLI as `command`, under a temporary HOME;
|
|
761
|
+
its real-HOME check is a names-only guard.
|
|
762
|
+
|
|
763
|
+
## Selection entry (`mlx-bun/selection`)
|
|
764
|
+
|
|
765
|
+
`mlx-bun/selection` (`src/cli/model-choice.ts`) is main's pure model-selection
|
|
766
|
+
helpers from `src/fit.ts`. It imports no module, so it loads without native MLX.
|
|
767
|
+
It exports `DEFAULT_REPO_ID`, `STARTER_REPO_ID`, `COEXIST_FRACTION`,
|
|
768
|
+
`chooseAutoModel(candidates, preferredRepo, fitsFull, fitsCoexist)` (the
|
|
769
|
+
automatic rule `model-selection.ts` applies), and
|
|
770
|
+
`largestRecommendedRepoId(ramBytes)` (the 26B at 48 GiB or more, the 12B at
|
|
771
|
+
24 GiB or more, otherwise `DEFAULT_REPO_ID`; an explicit opt-in that automatic
|
|
772
|
+
selection never calls). Migrating from main's root: `recommendedRepoId()` is
|
|
773
|
+
`DEFAULT_REPO_ID`; `largestRecommendedRepoId()` is
|
|
774
|
+
`largestRecommendedRepoId(totalmem())` (RAM is now required); `chooseAutoModel`,
|
|
775
|
+
`COEXIST_FRACTION` and `DEFAULT_REPO_ID` import from `mlx-bun/selection`.
|
|
776
|
+
|
|
777
|
+
The [selection tests](tests/selection-entry.test.ts) import the entry through the
|
|
778
|
+
export map with native MLX blocked; `verify-packages` repeats them from the
|
|
779
|
+
installed package.
|
|
780
|
+
|
|
781
|
+
## Server entry (`mlx-bun/server`)
|
|
782
|
+
|
|
783
|
+
`mlx-bun/server` (`src/cli/server-entry.ts`) restores main's in-process
|
|
784
|
+
`createServer` and `loadContext`. It loads without native MLX and starts nothing
|
|
785
|
+
on import. `createServer` composes the app `mlx-bun serve` composes (`startApp`,
|
|
786
|
+
then `startContextHost`) around a context the caller loaded, so it serves the
|
|
787
|
+
full app: every HTTP route, the browser app, Pi web chat on `/ws/chat`, jobs,
|
|
788
|
+
hub, memory, sessions, and publishing. It parses no CLI arguments, installs no
|
|
789
|
+
signal handler, opens no browser, and downloads nothing. The function's JSDoc
|
|
790
|
+
states the option, ownership, and close rules.
|
|
791
|
+
|
|
70
792
|
```ts
|
|
71
|
-
import
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
});
|
|
793
|
+
import { createServer, loadContext } from "mlx-bun/server";
|
|
794
|
+
|
|
795
|
+
const context = await loadContext("/path/to/hf-snapshot", "my-model");
|
|
796
|
+
const server = await createServer(context, 8080, { hostname: "127.0.0.1", memoryBudgetBytes: 12e9 });
|
|
797
|
+
try {
|
|
798
|
+
console.log(`API http://127.0.0.1:${server.port}/v1`);
|
|
799
|
+
} finally {
|
|
800
|
+
const { stopped, durability } = await server.close();
|
|
801
|
+
if (stopped) context.dispose();
|
|
802
|
+
if (!durability.durable) console.warn("cache persistence incomplete", durability);
|
|
803
|
+
}
|
|
77
804
|
```
|
|
78
805
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
806
|
+
- Options are serve's settings under their programmatic names (`capacity`,
|
|
807
|
+
`contextLimit`, `defaultGeneratedTokens`, `kvBudgetBytes`,
|
|
808
|
+
`memoryBudgetBytes`, `cache`, `request`, `whisper`, `readOnly`, and the
|
|
809
|
+
`memoryPaths`, `chatPaths`, and `storagePaths` store overrides). Defaults
|
|
810
|
+
follow main's library: port 0 binds an ephemeral port, an omitted `hostname`
|
|
811
|
+
binds every interface (tests pass `127.0.0.1`), capacity 8, bf16 KV, an 8 GB
|
|
812
|
+
RAM prompt cache, and `MLX_BUN_RD_CONTEXT_LIMIT` as the context cap when set.
|
|
813
|
+
User stores default to HOME and are shared with other mlx-bun processes
|
|
814
|
+
without locks, as for the CLI.
|
|
815
|
+
- A supplied graph: `binding` (`ModelBinding`) replaces the built-in numerics
|
|
816
|
+
and `buildPrompt` (`ModelPromptBuilder` returning `BuiltPrompt`) replaces
|
|
817
|
+
chat prompt construction. Both reach the engine and routes as given, with no
|
|
818
|
+
registry lookup, model class, or reload. A context without a chat template is
|
|
819
|
+
refused (`model <id> has no chat template`) unless `buildPrompt` builds its
|
|
820
|
+
prompts; such a builder returns `probeStableLen: false`. `artifact` (`path`,
|
|
821
|
+
`sizeBytes`, `expertsBytes`) only describes the weights for `/fit`, `/stats`,
|
|
822
|
+
and hub GC protection.
|
|
823
|
+
- Ownership: the context is borrowed by default, as in main. Close and every
|
|
824
|
+
failed start release what the server created and leave the context usable,
|
|
825
|
+
including for another server; the caller disposes it after close resolves
|
|
826
|
+
with `stopped: true`. `ownership: "owned"` makes the server dispose it exactly
|
|
827
|
+
once, after execution drains or when startup fails. The rule is
|
|
828
|
+
`createAppEngine`'s `ownership` option (`releaseContext`,
|
|
829
|
+
`engine/model-host.ts`), applied by the host and the entry until the engine
|
|
830
|
+
takes the context.
|
|
831
|
+
- `close({ timeoutMs = 120_000 })` resolves `{ stopped, timedOut, durability }`
|
|
832
|
+
with the final cache flush's result, `durable: false` included. One drain runs
|
|
833
|
+
however often it is called. At the deadline it resolves `stopped: false,
|
|
834
|
+
timedOut: true` with the pending counters and `durable: false` while the
|
|
835
|
+
drain continues; nothing is released under running execution. `flush()`
|
|
836
|
+
flushes cache persistence while serving.
|
|
837
|
+
- Process-wide state: the server applies no runtime switches and activates no
|
|
838
|
+
expert offload; those stay with the process's runtime configuration and the
|
|
839
|
+
CLI's loader. It sets the MLX allocator limit from a runtime memory plan or
|
|
840
|
+
`memoryBudgetBytes` and restores the value it found on close and on a failed
|
|
841
|
+
start. The limit is one per process: servers running at the same time share
|
|
842
|
+
it and the last applied wins. Sequential servers over one context are
|
|
843
|
+
supported. The Pi SDK's `proper-lockfile` registers `signal-exit`'s listeners
|
|
844
|
+
on SIGINT, SIGTERM, and other signals when the app's modules first load
|
|
845
|
+
(under the CLI too); they re-raise the signal when no other listener exists.
|
|
846
|
+
|
|
847
|
+
Migration from main (`02d723a`):
|
|
848
|
+
|
|
849
|
+
| main | `mlx-bun/server` |
|
|
850
|
+
| --- | --- |
|
|
851
|
+
| `createServer(ctx, port = 0, serverOptions)` returned Bun's `Server` | `await createServer(context, port = 0, options)` returns `{ port, flush, close }` |
|
|
852
|
+
| `shutdownServer(server, timeoutMs = 120_000)` → `{ stopped, timedOut, durability }` | `server.close({ timeoutMs })`, the same result; `server.stop()` becomes `await server.close()` |
|
|
853
|
+
| `flushServerCacheDurability(server)` | `server.flush()` |
|
|
854
|
+
| `loadContext(dir, id?, { implementations, profiles, … })` | the same, from `mlx-bun/server` |
|
|
855
|
+
| `ServerContext`, `ServingContext` | `ModelContext`, `LoadedModelContext` |
|
|
856
|
+
| `ServerContext.serving?: ModelServingBinding` | `options.binding` (`ModelBinding`) and `options.buildPrompt` for `serving.buildPrompt`; `createSerial` has no counterpart, since every request runs on the shared scheduler through `binding.gateway` |
|
|
857
|
+
| `ModelPromptBuilder`, `BuiltPrompt` | the same names |
|
|
858
|
+
| `batch` (1 pinned main's one-request executor) | `capacity` (1 is the continuous scheduler at capacity one) |
|
|
859
|
+
| `promptCacheBytes`, `kvQuant`, `quantizedKvStart`, `turboQuant`, `ssdCacheDir`, `ssdCacheMaxBytes`, `ssdDemoteIdleSec`, `ssdCacheVerify`, `generationCheckpointTokens` | `cache.*` |
|
|
860
|
+
| `pagedKv`, `defaultThinking`, `defaultTemperature`, `defaultTopP`, `defaultTopK`, `hlg` | `request.*` |
|
|
861
|
+
| `defaultMaxTokens` | `defaultGeneratedTokens` |
|
|
862
|
+
| `whisperModelDir`, `whisperModelId`, `whisperIdleUnloadSec`, `whisperResident` | `whisper.modelDir`, `.modelId`, `.idleUnloadSec`, `.resident` |
|
|
863
|
+
| `kvBudgetBytes`, `memoryBudgetBytes`, `hostname`, `defaultAdapter` | the same |
|
|
864
|
+
| `owner` | not an option; `/stats` reports `embedded` |
|
|
865
|
+
| `unixSocket` | not provided; the private worker launch forms own Unix sockets |
|
|
866
|
+
| `CompletionClient`, `BatchCompletionClient`, `TaskClient` (`mlx-bun/engine`) | the same, from `mlx-bun/engine` or `mlx-bun/client` |
|
|
867
|
+
| `mlx-bun/package.json` | the same subpath |
|
|
868
|
+
|
|
869
|
+
The [server entry tests](tests/server-entry.test.ts) import the entry through
|
|
870
|
+
the export map with native MLX blocked, then compose the real app over a
|
|
871
|
+
supplied binding in child processes with a temporary HOME: main's model
|
|
872
|
+
replacement shape (one context, five sequential servers), the full app and
|
|
873
|
+
`/ws/chat`, the template-less builder path, startup failures (options, state,
|
|
874
|
+
caches, bind) under both ownerships, and close's deadline, drain, and
|
|
875
|
+
durability evidence.
|
|
876
|
+
`verify-packages` repeats them from the installed package. The opt-in
|
|
877
|
+
[native test](tests/engine/in-process-server.test.ts) (`MLX_BUN_TEST_NATIVE=1`
|
|
878
|
+
with `MLX_BUN_APP_TEST_MODEL`) serves a real loaded context through a caller
|
|
879
|
+
binding under a temporary HOME, aborts a live stream during close, and reuses
|
|
880
|
+
the borrowed context for a second server.
|
|
881
|
+
|
|
882
|
+
## Web chat
|
|
883
|
+
|
|
884
|
+
The chat is [`@mlx-bun/module-chat`](../../packages/module-chat/README.md): the Pi backend, policy,
|
|
885
|
+
transcripts, tool approvals, `/ws/chat`, the session routes, and the browser panel. This app installs it
|
|
886
|
+
(`src/modules.ts`) and runs it in the persistent state (`cli/serve-state.ts`), which serves its socket on the
|
|
887
|
+
listener (`createModuleSockets`) and stops it, closing every chat, before the listener drains. The app decides
|
|
888
|
+
three things for it: `readOnly` (`ServeOptions.readOnly`), the working directory (`chatPaths.cwd`), and, through
|
|
889
|
+
`chatPaths` (`sessionDir`, `agentDir`, `toolApprovalsFile`), where an embedder put its stores instead of the
|
|
890
|
+
module's storage entries; these are composition options, not CLI switches. Memory reaches the chat through the
|
|
891
|
+
registry: `@mlx-bun/module-memory/chat` registers the vault's read-only tools (`chat.tool`) and their hint and skill
|
|
892
|
+
(`chat.guidance`), each offered only while the vault exists.
|
|
893
|
+
|
|
894
|
+
The [existing-user data test](tests/existing-user-data.test.ts) opens data a
|
|
895
|
+
prior version left under HOME (sessions, Pi settings, approvals, the saved
|
|
896
|
+
token, the vault and its Reference links, adapter stores, the nightly schedule)
|
|
897
|
+
through the app with HOME set to a clone and native MLX blocked, and requires
|
|
898
|
+
nothing lost: the supplied directory and its link targets unchanged, no
|
|
899
|
+
deletion, a byte-identical vault, and only the app's own databases
|
|
900
|
+
(`.mlx-bun/db`), its bundled skill, and append-only or message-preserving
|
|
901
|
+
session rewrites. The earlier `~/.cache/mlx-bun` jobs, memory and registry
|
|
902
|
+
databases are not carried over and must stay byte-identical; its adapter stores
|
|
903
|
+
must appear in the picker's catalog. Its default case builds main's formats
|
|
904
|
+
in-test; `MLX_BUN_APP_TEST_USER_DATA=<isolated copy laid out as a HOME>` runs it
|
|
905
|
+
on real data and prints what to preserve before an old checkout is deleted. It
|
|
906
|
+
refuses a copy holding, at any depth, a link that resolves into a live store
|
|
907
|
+
(`~/.mlx-bun`, `MLX_BUN_HOME`, `~/.cache`, `~/.pi`, `~/Library`) or a directory
|
|
908
|
+
link leaving the copy.
|
|
909
|
+
|
|
910
|
+
## Browser app
|
|
911
|
+
|
|
912
|
+
`src/web/browser/` preserves the existing model, training, quantization,
|
|
913
|
+
dataset and status UI as pages of the web shell; memory's module owns its persistent sidebar overlay alongside chat
|
|
914
|
+
([`@mlx-bun/web-shell`](../../packages/web-shell/README.md)), which owns navigation, routing, theme,
|
|
915
|
+
the command palette's chrome and the mounting of module panels. `main.ts` composes the shell, the panels of the
|
|
916
|
+
installed modules (`installed-panels.ts`, generated by `web/build.ts` from the host's installed modules, so the bundle
|
|
917
|
+
carries every panel the host installs and nothing else; the chat is the first, a workspace panel) and the pages that
|
|
918
|
+
have not moved into modules yet; `chat-host.ts` is the app's side of the chat panel (memory's provenance chips and
|
|
919
|
+
sidebar entry, the agent-tools settings) and `shell.ts` the app's glue around the shell (its routes, the Hugging Face
|
|
920
|
+
and agent-tools settings, the hamburger, overlays and key bindings that reach the chat panel, the connection pill).
|
|
921
|
+
Browser code imports only local browser modules, the shell package and the data-only job protocol. Unported backend features may return 501;
|
|
922
|
+
preserving their UI does not claim their backend is ready.
|
|
923
|
+
|
|
924
|
+
`src/web/assets.ts` provides `createWebHandler()`, which loads the static payloads
|
|
925
|
+
and returns a `Request → Response | null` handler for application composition.
|
|
926
|
+
It opens no listener. `bun run --filter mlx-bun build:web` creates ignored
|
|
927
|
+
`dist/web/app.js`; prepack generates and includes that file, so installed apps
|
|
928
|
+
need no build step. A source checkout with no generated bundle compiles the
|
|
929
|
+
browser entry in memory on startup, without writing installation files. The
|
|
930
|
+
build script and runtime fallback share `web/build.ts`. Static HTML, theme, manifest, worker, and icon live in
|
|
931
|
+
`src/web/public/`. The vendored highlight.js bundle retains its BSD license in
|
|
932
|
+
`public/vendor/hljs-LICENSE`; its existing header records upstream provenance.
|
|
933
|
+
|
|
934
|
+
[Browser behavior tests](tests/web/browser.test.ts) cover streaming rendering,
|
|
935
|
+
escaping, attachments, panels, and interactions without a live server.
|
|
936
|
+
[Boot tests](tests/web/boot.test.ts) run the built bundle against `app.html` (the shell's tabs and pages, a
|
|
937
|
+
module panel mounted through the shell). [Static tests](tests/web/assets.test.ts) exercise the built bundle and asset
|
|
938
|
+
headers. The packed consumer check verifies the same assets after installation.
|
|
939
|
+
|
|
940
|
+
Adapters are the models module's (`/v1/adapters*`, [module README](../../packages/module-models/README.md)): the routes
|
|
941
|
+
reach the served model through its `adapters` operation, which `engine/adapter-operation.ts` builds over the loaded context
|
|
942
|
+
(mount, unmount and merge run under the engine execution lock in the process that holds the model); no HTTP handler owns
|
|
943
|
+
tensors. Serving with an adapter uses the shared scheduler. Adapter discovery reports the graph's actual mount
|
|
944
|
+
targets; the loader binds each request's projection deltas before its forward runs.
|
|
945
|
+
|
|
946
|
+
## Memory vault
|
|
947
|
+
|
|
948
|
+
`@mlx-bun/module-memory/article` owns Markdown article structure; `vault.ts` owns vault
|
|
949
|
+
initialization, filesystem reads, search, links, and Git history. The default
|
|
950
|
+
vault is `~/.mlx-bun/wiki`, with `MLX_BUN_WIKI` as its override.
|
|
951
|
+
`@mlx-bun/module-memory/routes` exposes the read/init HTTP surface through
|
|
952
|
+
`createMemoryRoutes({ root })`; The app installs the memory module in persistent state and mounts its declared routes.
|
|
953
|
+
Initialization is explicit and idempotent, and its path stays confined to the
|
|
954
|
+
vault or temporary trees. Existing article/Talk directory links remain usable;
|
|
955
|
+
initialization confines its actual write targets. Reference seeding defaults to
|
|
956
|
+
none; composition can pass explicit `referenceSources` without inferring old
|
|
957
|
+
repository documentation paths. Merely starting the app does not create a vault.
|
|
958
|
+
|
|
959
|
+
[Route tests](tests/server/memory-routes.test.ts) use injected temporary vaults
|
|
960
|
+
and real local Git history; [article tests](../../packages/module-memory/tests/article.test.ts) cover
|
|
961
|
+
parsing and round trips. `query.ts` owns deterministic article navigation;
|
|
962
|
+
`tools.ts` owns the read-only Pi definitions and prompt hint. CLI composition
|
|
963
|
+
passes the same vault root to REST and to chat (through `@mlx-bun/module-memory/chat`) and supplies a skill directory
|
|
964
|
+
(default `~/.mlx-bun/skills`). `ServeOptions.memoryPaths` permits isolated app
|
|
965
|
+
composition without adding CLI flags. Missing vaults expose no memory tools and
|
|
966
|
+
create no skill files. Bundled skills are module package assets and are embedded in the standalone binary.
|
|
967
|
+
|
|
968
|
+
[Tool tests](../../packages/module-memory/tests/tools.test.ts) exercise temporary vaults, and the
|
|
969
|
+
[chat tools test](../../packages/module-memory/tests/chat-tools.test.ts) executes a memory tool through a real
|
|
970
|
+
read-only Pi session, contributed to the chat module through the registry, with a synthetic loopback model.
|
|
971
|
+
No read tool starts a synthesis run.
|
|
972
|
+
|
|
973
|
+
### Memory synthesis
|
|
974
|
+
|
|
975
|
+
Main's nightly pipeline lives under `packages/module-memory/src/` unchanged in prompts, stage
|
|
976
|
+
order, database schema (`db.ts`, `~/.mlx-bun/db/memory.sqlite`), vault
|
|
977
|
+
layout, Git usage, and the dedup/normalize/reconcile rules: `pipeline.ts` drives
|
|
978
|
+
the four resumable, chronological stage workers in `stages.ts` (SEGMENT via
|
|
979
|
+
`chunk.ts`, ENTITY-EXTRACT via `entity.ts` + `resolve.ts`, ROUTE via `route.ts`,
|
|
980
|
+
SYNTHESIZE via `synthesize.ts`/`cluster.ts`/`reconcile.ts`), then the
|
|
981
|
+
deterministic `crosslink.ts` pass and the `wikify.ts` editorial sweep. `events.ts`
|
|
982
|
+
holds the shared event contract so no stage imports the orchestrator.
|
|
983
|
+
|
|
984
|
+
The engine is reached only through `model.ts`'s `MemoryCompletionClient` seam.
|
|
985
|
+
The memory domain defines the interface; composition injects an
|
|
986
|
+
implementation. `cli/memory-engine.ts` is main's memory task model (Gemma-4
|
|
987
|
+
e4b and its `memory-chunk` adapter), loaded in-process by the first completion
|
|
988
|
+
over the app engine's continuous gateway: the `memory` verb and its nightly job
|
|
989
|
+
use it, and so does `serve`'s own synthesis, which keeps it resident until
|
|
990
|
+
shutdown, as main did. Under `serve` each task-model completion (or batch, once
|
|
991
|
+
all its rows join) holds the served engine's execution lease, taken before the
|
|
992
|
+
weights load, so memory work never overlaps a managed job; chat waits while a
|
|
993
|
+
memory stage call runs (main's in-server client ran beside chat under its own
|
|
994
|
+
locks). `server/memory-completion-client.ts` holds the private worker client; the module owns its loopback client. The
|
|
995
|
+
loopback client posts each stage call to a serving mlx-bun's own
|
|
996
|
+
`/v1/chat/completions` (raw greedy sampling, neutral logit processors and the
|
|
997
|
+
model template's thinking defaults, the stage's system/user turns,
|
|
998
|
+
`adapter: "memory-chunk"` for the chunk stage when
|
|
999
|
+
`~/.mlx-bun/adapters/memory-chunk` exists and `"none"` otherwise):
|
|
1000
|
+
`memory --host`/`--port` use it.
|
|
1001
|
+
|
|
1002
|
+
In the default isolated `serve` the parent, which loads no model, keeps the pipeline,
|
|
1003
|
+
vault, and SSE; the current model's worker owns the task model (the same
|
|
1004
|
+
in-process client, loaded by its first call and kept until that worker stops). The parent's worker client (also in
|
|
1005
|
+
`server/memory-completion-client.ts`) sends each `complete` or
|
|
1006
|
+
`completeBatch` as one `POST /admin/memory/complete` over the worker's
|
|
1007
|
+
socket (`{ call, snapshot, requests: [{ stage, input, maxTokens }] }`, answered
|
|
1008
|
+
`{ outputs }` in input order). The worker runs the call under its own execution
|
|
1009
|
+
lease, taken before the lazy load and released after every row joined, so it
|
|
1010
|
+
waits for a managed job's lease and a job waits for it; the parent takes no
|
|
1011
|
+
lease of its own for it. The parent selects the task model snapshot once per call
|
|
1012
|
+
(`locateTaskModel`), the call carries it, and the call that loads the task model
|
|
1013
|
+
loads exactly that directory; the parent keeps the selected snapshot out of hub GC
|
|
1014
|
+
for that worker's lifetime (until it is respawned or the server stops). A cancelled run, a client
|
|
1015
|
+
disconnect, or the parent's shutdown aborts the request, and the worker aborts
|
|
1016
|
+
and joins every row. A call is never retried: a worker that stops mid-call fails
|
|
1017
|
+
that call with an error, and a restart drops the task model with
|
|
1018
|
+
its worker. Every path rides a continuous-batching scheduler, where one call is a
|
|
1019
|
+
batch of one. `MLX_BUN_MEMORY_BATCH`
|
|
1020
|
+
(default 1) bounds the calls in flight per batched stage. Nothing in the memory
|
|
1021
|
+
domain loads a model. Each run receives its client
|
|
1022
|
+
and vault root explicitly; Meta policy reads use that same vault. A failed batch
|
|
1023
|
+
stops admission, cancels siblings, and joins them before returning its error.
|
|
1024
|
+
The memory module's `createMemorySynthesis` (`packages/module-memory/src/synthesis.ts`) owns active server runs: request/body cancellation
|
|
1025
|
+
aborts the run and its in-flight completions (or its wait for the lease), and
|
|
1026
|
+
server shutdown cancels and joins all synthesis alongside managed jobs and
|
|
1027
|
+
downloads, then closes the task model, before engine drain. Completed stage writes are resumable; cancellation does not roll them back.
|
|
1028
|
+
|
|
1029
|
+
`GET /v1/memory/synthesize[?dry=1]` streams the run as SSE (`stage`/`log`/`done`
|
|
1030
|
+
events, a `summary`, then `[DONE]`; a failure ends with an `error` event); it is
|
|
1031
|
+
mounted only when composition supplies the pipeline. `mlx-bun memory` exposes
|
|
1032
|
+
main's subcommands: `init`/`setup` (the wizard below), `status` (default),
|
|
1033
|
+
`open`, `list`, `search`, `toc`, `section`, `links`, `read`, `synthesize`
|
|
1034
|
+
(`--dry-run`; `--since`/`--model` parsed but not consumed, as in main), the
|
|
1035
|
+
stage workers `segment`, `extract`, `route`, `synthesize-stage` (`--limit`,
|
|
1036
|
+
`--convs`), `link`, and `schedule`/`unschedule`. Model-driven subcommands load
|
|
1037
|
+
the memory task model in-process on first use (`cli/memory-engine.ts`), as main
|
|
1038
|
+
did, or talk to the serving mlx-bun named by `--host`/`--port`. The entity gold main read from
|
|
1039
|
+
`goldens/dreaming-entities-gold.json` is a published dataset: without that
|
|
1040
|
+
file the resolver runs unseeded (store aliases still fold).
|
|
1041
|
+
|
|
1042
|
+
[Pipeline tests](../../packages/module-memory/tests/) port main's model-free suites with fake stage
|
|
1043
|
+
calls, in-test vaults, and an in-test entity gold; the
|
|
1044
|
+
[client test](tests/server/memory-completion-client.test.ts) and
|
|
1045
|
+
[verb test](tests/memory-cli.test.ts) use a fake fetch and a temporary HOME.
|
|
1046
|
+
|
|
1047
|
+
### Memory setup and nightly schedule
|
|
1048
|
+
|
|
1049
|
+
`mlx-bun memory init` (also `memory setup`) runs main's onboarding wizard.
|
|
1050
|
+
`mlx-bun setup` is main's true alias of `mlx-bun memory`: `mlx-bun setup init`
|
|
1051
|
+
is the same wizard and a bare `mlx-bun setup` reports status. The wizard
|
|
1052
|
+
initializes the vault through the same `setupVault` as `POST /api/memory/init`
|
|
1053
|
+
(idempotent; no reference seeding), offers to import an existing wiki's
|
|
1054
|
+
`articles/` (main's prompt; default yes once a path is given; the import is
|
|
1055
|
+
committed), and offers to install the nightly synthesis job (main's prompt;
|
|
1056
|
+
default no; then asks for the time, default 03:00). A non-TTY stdin answers
|
|
1057
|
+
every prompt with its default, so a scripted run initializes the vault and
|
|
1058
|
+
installs nothing.
|
|
1059
|
+
|
|
1060
|
+
`memory schedule [--at HH:MM]` writes main's launchd agent,
|
|
1061
|
+
`~/Library/LaunchAgents/com.mlx-bun.memory.plist` (label `com.mlx-bun.memory`,
|
|
1062
|
+
`StartCalendarInterval` at the local time, default 03:00, `RunAtLoad` off,
|
|
1063
|
+
`ProcessType` Background, logs in `~/.mlx-bun/logs/memory-synthesis.{out,err}.log`),
|
|
1064
|
+
then `launchctl unload` and `launchctl load -w` it; a failed load is reported,
|
|
1065
|
+
not thrown. `memory unschedule` runs `launchctl unload -w` and deletes the
|
|
1066
|
+
plist. The job runs `/bin/zsh -lc "exec <program> memory synthesize"`, where
|
|
1067
|
+
the program is the executable identity captured at startup
|
|
1068
|
+
(`jobs/executable.ts`) plus the CLI entry when running from source; it is
|
|
1069
|
+
never a PATH lookup and never `process.execPath` read later. The job's
|
|
1070
|
+
`memory synthesize` loads the memory task model itself, so it needs no running
|
|
1071
|
+
server; every status surface says so.
|
|
1072
|
+
|
|
1073
|
+
`scheduleStatus` reports `installed` (plist present), `loaded` (`launchctl
|
|
1074
|
+
list com.mlx-bun.memory` succeeds), `at` (read back from the plist; `null`
|
|
1075
|
+
when absent or hand-edited), and `note` (what the job runs and needs).
|
|
1076
|
+
`memory status` prints it as the `nightly` line, `GET /api/memory/status`
|
|
1077
|
+
returns it as `schedule` beside `status` (absent when no vault exists), and
|
|
1078
|
+
the `memory_status` tool prints its `nightly` line; the memory skill tells the
|
|
1079
|
+
assistant not to claim a scheduled run happened unless the last synthesis
|
|
1080
|
+
commit shows it.
|
|
1081
|
+
|
|
1082
|
+
`@mlx-bun/module-memory/schedule` owns the plist and takes the home directory and the
|
|
1083
|
+
`launchctl` runner as seams; `cli/memory.ts` adds the vault root, the job's
|
|
1084
|
+
program, and the prompt; the tool and route factories take a `schedule` probe.
|
|
1085
|
+
[Schedule tests](../../packages/module-memory/tests/schedule.test.ts) and the
|
|
1086
|
+
[setup verb test](tests/setup-cli.test.ts) drive every path under a temporary
|
|
1087
|
+
home with a recording `launchctl`, an injected vault root, and scripted
|
|
1088
|
+
answers; spawned runs use a temporary HOME and a non-TTY stdin. No test
|
|
1089
|
+
reaches the real launchd, `~/Library/LaunchAgents`, or `~/.mlx-bun`.
|
|
1090
|
+
|
|
1091
|
+
## Jobs, quantization, and fine-tuning
|
|
1092
|
+
|
|
1093
|
+
`storage/paths.ts` owns every default location the app writes, all under
|
|
1094
|
+
`MLX_BUN_HOME` (default `~/.mlx-bun`), read from the environment at call time:
|
|
1095
|
+
`models/` (convert, web quantize and fuse outputs, plain model directories),
|
|
1096
|
+
`adapters/` (train, web fine-tune, merge, memory stages), `exports/`,
|
|
1097
|
+
`datasets/`, `db/` (jobs, model index, memory), `jobs/` (job logs), and the
|
|
1098
|
+
wiki, skill, log and credential files, and the chat module's `sessions/`, `pi-sessions/` and `tool-approvals.json`. The Hugging Face cache
|
|
1099
|
+
holds downloads only; the model index (`openRegistry()`) scans it and
|
|
1100
|
+
`models/`, so `ls`, `/library`, `serve <name>` and the folder picker find both.
|
|
1101
|
+
Explicit paths (`--mlx-path`, `--save-path`, `--adapter`, request fields) win.
|
|
1102
|
+
Earlier versions' job history and model index under `~/.cache/mlx-bun` are not
|
|
1103
|
+
carried over (the index rebuilds by scan); their adapter stores stay listed.
|
|
1104
|
+
|
|
1105
|
+
`jobs/` owns the lazily opened SQLite store, durable NDJSON events, SSE tails,
|
|
1106
|
+
and managed subprocess lifetimes. `cli/job-entry.ts` resolves the producer in the child process: the installed
|
|
1107
|
+
module that registered the job kind. HTTP parsing and job wire responses stay in `server/job-routes.ts`.
|
|
1108
|
+
|
|
1109
|
+
Fine-tuning is the train module's ([`@mlx-bun/module-train`](../../packages/module-train/README.md): the
|
|
1110
|
+
`finetune` job, `/api/finetune/inspect-dataset` and `/submit`, the `train`, `train-watch`, `fuse` and `draft` verbs,
|
|
1111
|
+
`mlx-bun.lora` and `mlx-bun.fuse`, and the `adapters/` storage entry, with `models/` and `datasets/` shared with
|
|
1112
|
+
quantize and datasets). This app installs it (`src/modules.ts`) and supplies its core services in the
|
|
1113
|
+
persistent state the same way as quantize's. The verbs run through the verb table (`cli/module-verbs.ts`); a verb
|
|
1114
|
+
that names no model runs on the app's automatic choice (`resolveModelAuto`, the one `serve` makes), which the
|
|
1115
|
+
verb host supplies to the catalog. Adapter merge and export are the models module's, on the served model's `adapters`
|
|
1116
|
+
operation (below).
|
|
1117
|
+
|
|
1118
|
+
Quantization and `convert` are the quantize module's ([`@mlx-bun/module-quantize`](../../packages/module-quantize/README.md):
|
|
1119
|
+
the `quantize` job, `/api/quantize/*`, the `convert` verb and `mlx-bun.convert`). This app installs it
|
|
1120
|
+
(`src/modules.ts`) and supplies its core services in the persistent state (`cli/serve-state.ts`): its
|
|
1121
|
+
`process` runner is a child of the job host (`jobs/service.ts`, `jobs/runner.ts`), which holds the execution
|
|
1122
|
+
lease until the child's process group is gone; the child (`cli/job-entry.ts`) activates the module that
|
|
1123
|
+
registered the job kind and runs its runner. A one-shot `convert` (`cli/module-verbs.ts`) runs over a job
|
|
1124
|
+
store of its own, created on first use and removed when it ends, so its runs never appear in the job history.
|
|
1125
|
+
`convert`'s and `fuse`'s `--upload-repo` push through the modules' `catalog` service (`publishing/catalog-hub.ts`).
|
|
1126
|
+
`/api/model/resolve-folder`, the fine-tune wizard's folder picker, is the models module's over the same catalog.
|
|
1127
|
+
|
|
1128
|
+
Composition injects the engine execution lease. A job drains active inference
|
|
1129
|
+
and holds that lease until its child's process group is gone and output streams
|
|
1130
|
+
finish; inference then resumes. The child leads its own process group, so
|
|
1131
|
+
descendants that outlive it are stopped (SIGTERM, then SIGKILL after 3 s) before
|
|
1132
|
+
the job counts as joined. The wait is bounded: a process still alive 3 s after
|
|
1133
|
+
SIGKILL, or one outside the group still holding the output after another 3 s, is
|
|
1134
|
+
logged and left behind, and the lease is released. Terminal signals no longer reach a group leader, so the
|
|
1135
|
+
parent holds a pipe on the child's stdin and the child stops its group when that
|
|
1136
|
+
pipe ends (`MLX_BUN_JOB_PARENT_PIPE`, set by the runner). A row the runner cannot
|
|
1137
|
+
read after admission fails the job before anything is spawned. As in main's
|
|
1138
|
+
direct-process server, resident model weights and caches remain allocated while
|
|
1139
|
+
the child runs. Shutdown stops queued jobs, aborts admission waits, terminates
|
|
1140
|
+
the active child's process group, and awaits all of it (with the same bound) before closing the store
|
|
1141
|
+
and engine. Every job row records main's `ended_at` format
|
|
1142
|
+
(`YYYY-MM-DD HH:MM:SS`, UTC) and `Name: message` errors, in-process dataset jobs included. Opening the app does not create the job database until a job
|
|
1143
|
+
route is used. A fine-tuning job selects its own model path;
|
|
1144
|
+
the resident inference model's adapter/training capabilities do not gate it.
|
|
1145
|
+
|
|
1146
|
+
The [training CLI tests](tests/train-cli.test.ts) spawn the CLI with native MLX blocked and drive the module's verbs through the
|
|
1147
|
+
verb table: help (including `draft`'s subcommand paragraph), usage errors, refusals and a dry-run plan. The
|
|
1148
|
+
module's own tests (`packages/module-train/tests`) use injected dependencies. An opt-in native check saves a tiny
|
|
1149
|
+
drafter and resolves it like a model path ([draft CLI test](tests/draft-cli.test.ts)).
|
|
1150
|
+
|
|
1151
|
+
[Job lifecycle tests](tests/jobs/lifecycle.test.ts) exercise leases, crash/error
|
|
1152
|
+
paths, shutdown, HTTP/SSE, and a real CPU-only child with temporary storage;
|
|
1153
|
+
[process-group tests](tests/jobs/process-group.test.ts) use real child processes
|
|
1154
|
+
with descendants holding the job's output.
|
|
1155
|
+
The [module's tests](../../packages/module-quantize/tests) verify option forwarding and output
|
|
1156
|
+
naming with an injected numerical operation; they do not run or establish parity for actual
|
|
1157
|
+
checkpoint quantization.
|
|
1158
|
+
The [train module's policy tests](../../packages/module-train/tests/policy.test.ts) cover the app recipe,
|
|
1159
|
+
explicit overrides, dataset inspection, HTTP submission, progress, and resource
|
|
1160
|
+
cleanup with a fake native runtime. These CPU checks do not extend the numerical
|
|
1161
|
+
claims in the [training evidence](../../packages/training/README.md). The opt-in
|
|
1162
|
+
[fine-tune preservation test](tests/engine/finetune-preservation.test.ts)
|
|
1163
|
+
(`MLX_BUN_TEST_NATIVE=1 MLX_BUN_APP_TEST_FINETUNE_REFERENCES=<ref.json>[:…]`) runs
|
|
1164
|
+
this producer on each submit record main's producer ran, for any family or
|
|
1165
|
+
training path, and requires main's metrics, config files, adapter and checkpoint
|
|
1166
|
+
tensors, fresh-reload logits, and optionally its `fuse` output exactly; its
|
|
1167
|
+
header gives the commands that produce the references outside the repository.
|
|
1168
|
+
On 2026-09-28 it passed on the M1 Max against main `02d723a` run from source with
|
|
1169
|
+
this tree's staged native library, for mlx-community MiniCPM5-1B-OptiQ-4bit
|
|
1170
|
+
(`664aabae`). The records were SFT (8 iterations, batch 2, accumulation 2, dropout,
|
|
1171
|
+
rsLoRA, checkpoints, fused output), ORPO (default L3 heads, grad checkpointing)
|
|
1172
|
+
and DPO (LoRA+). A reference with a changed seed failed as expected.
|
|
1173
|
+
|
|
1174
|
+
Composition takes `storagePaths` (job store, saved token file, and an artifact
|
|
1175
|
+
root replacing `MLX_BUN_HOME` for models, adapters, exports and datasets) like
|
|
1176
|
+
`chatPaths` and `memoryPaths`, so embedded and test servers never touch the
|
|
1177
|
+
user's jobs, credentials or artifacts. The opt-in
|
|
1178
|
+
[managed-jobs acceptance](tests/engine/managed-jobs.test.ts) runs a real
|
|
1179
|
+
`mlx-bun serve` process under a temporary HOME and `HF_HUB_CACHE` with the
|
|
1180
|
+
cached model named by `MLX_BUN_APP_TEST_MODEL`: a quantize job whose artifact
|
|
1181
|
+
appears in the library and reloads for a short generation, inference after its
|
|
1182
|
+
lease, a three-step SFT job with a
|
|
1183
|
+
periodic checkpoint, a long job cancelled by shutdown (child terminated, row
|
|
1184
|
+
terminal, complete checkpoint metadata kept, no final adapter, bounded progress),
|
|
1185
|
+
and a restart on the same storage that mounts the preserved checkpoint; then a spawned `train` interrupted by SIGINT at a step
|
|
1186
|
+
boundary, and, with a cached bf16 snapshot named by
|
|
1187
|
+
`MLX_BUN_APP_TEST_BF16_MODEL`, a `convert` interrupted after its durable job log
|
|
1188
|
+
reaches the Probing/Sensitivity stage and a complete uniform conversion whose
|
|
1189
|
+
output reloads and generates. It downloads nothing.
|
|
1190
|
+
|
|
1191
|
+
A further case in the same file consumes finished fine-tune outputs as
|
|
1192
|
+
artifacts, with the RAM prompt cache off so every compared request prefills
|
|
1193
|
+
cold. Two SFT jobs train on sentences written at run time. Each finished
|
|
1194
|
+
adapter (not a checkpoint), and their merge through `POST /api/finetune/merge`,
|
|
1195
|
+
is mounted with `POST /v1/adapters` and selected per request. A selected
|
|
1196
|
+
adapter changes the greedy `/v1/completions` text; requests without one, and
|
|
1197
|
+
requests after `DELETE /v1/adapters/<id>`, return the base choice exactly, text
|
|
1198
|
+
and per-token logprobs, and an unmounted id is refused. A fresh process mounting
|
|
1199
|
+
the same two directories reproduces both adapters' choices exactly.
|
|
1200
|
+
`mlx-bun fuse` then folds one adapter into the base. The output keeps
|
|
1201
|
+
`config.json`, the tokenizer files, the tensor inventory (names, dtypes, shapes)
|
|
1202
|
+
and every tensor outside the folded modules byte-identical, changes only folded
|
|
1203
|
+
modules, and loads with `serve` and generates. Its output is not compared with
|
|
1204
|
+
the mounted adapter: `fuse` re-quantizes folded modules with their source spec,
|
|
1205
|
+
so the fused model and the adapter are not bit-exact by contract. Run with
|
|
1206
|
+
`cd apps/mlx-bun && MLX_BUN_APP_TEST_MODEL=<cached snapshot directory> bun test tests/engine/managed-jobs.test.ts -t "finished fine-tune outputs"`.
|
|
1207
|
+
|
|
1208
|
+
## Dataset jobs
|
|
1209
|
+
|
|
1210
|
+
Dataset generation is the datasets module's ([`@mlx-bun/module-datasets`](../../packages/module-datasets/README.md):
|
|
1211
|
+
the thirteen templates, `/api/dataset/templates` and `/api/dataset/submit`, the `dataset`
|
|
1212
|
+
job kind, the Docker Python verifier). This app installs it (`src/modules.ts`) and supplies
|
|
1213
|
+
its core services in the persistent state (`cli/serve-state.ts`), because a module that
|
|
1214
|
+
requires `jobs` runs beside the job store: `jobs/service.ts` runs its task runners on the
|
|
1215
|
+
job host, so its rows, logs and `/api/jobs` streams are the same as any job's;
|
|
1216
|
+
`storage` resolves the module's `datasets/` entry under `MLX_BUN_HOME` (the
|
|
1217
|
+
`artifactRoot` override replaces it, as for other produced artifacts); and
|
|
1218
|
+
`cli/served-model-host.ts` is the `modelHost` that leases the attached serving host's
|
|
1219
|
+
model for `generate`, sending the request to its own HTTP listener (its Unix socket in
|
|
1220
|
+
the worker app form; through the proxy in the default isolated mode), so jobs call the same app.
|
|
1221
|
+
|
|
1222
|
+
Adapter merge/export requests are the models module's. Merge is the served model's `adapters` operation: it runs the
|
|
1223
|
+
public training library under that model's engine execution lock, in the process that holds the model. The default isolated
|
|
1224
|
+
parent loads no MLX, so `cli/worker-unit.ts` reaches the worker through `cli/worker-adapters.ts` (one JSON call on the
|
|
1225
|
+
worker's private `POST /admin/adapters`, served by `server/worker-routes.ts` on the worker's own operation), and
|
|
1226
|
+
`residency/leased-adapters.ts` holds the model resident for each call; `--in-process` calls the unit directly. Export writes
|
|
1227
|
+
a CPU-only manifest without taking any lease. Merges land in
|
|
1228
|
+
`~/.mlx-bun/adapters/merged-…` and exports in `~/.mlx-bun/exports/export-…`,
|
|
1229
|
+
with unique suffixes so simultaneous requests cannot overwrite each other's
|
|
1230
|
+
artifacts.
|
|
1231
|
+
|
|
1232
|
+
`GET /v1/adapters/available` lists the catalog's adapters (`adapterStores()` in
|
|
1233
|
+
`storage/paths.ts`): `~/.mlx-bun/adapters`, then, read-only, the stores
|
|
1234
|
+
earlier versions wrote (`~/.cache/mlx-bun/adapters`,
|
|
1235
|
+
`~/.cache/mlx-bun/mlx-bun-finetunes`, `~/.cache/mlx-bun-finetunes`), each
|
|
1236
|
+
directory once. Nothing is moved. The [storage layout tests](tests/storage-layout.test.ts)
|
|
1237
|
+
drive each producer's real default (web fine-tune, merge, a `train` dry run,
|
|
1238
|
+
the memory stage reader) and require the app's catalog to list it; they
|
|
1239
|
+
check that a quantize job's and a default `convert` run's directories under
|
|
1240
|
+
`models/` are what the index, `serve <path|name>` and the folder picker read,
|
|
1241
|
+
that nothing lands in the hub cache, and that earlier `models--local--…`
|
|
1242
|
+
quantize snapshots there still resolve. Their tensor files are zeroed
|
|
1243
|
+
stand-ins; loading and generating from real artifacts is the opt-in
|
|
1244
|
+
managed-jobs and startup-adapter acceptance.
|
|
1245
|
+
|
|
1246
|
+
`publishing/credentials.ts` owns the app's `~/.mlx-bun/hf.json` token file (mode
|
|
1247
|
+
0600). Resolution prefers the saved token, then `HF_TOKEN`, then the shared HF
|
|
1248
|
+
cache through the hub resolver. Explicit paths and environment isolate tests
|
|
1249
|
+
and embedded consumers. Settings responses expose presence only.
|
|
1250
|
+
`publishing/upload.ts` selects an explicit source or the supplied job's output
|
|
1251
|
+
and passes resolved credentials to the public hub uploader. HTTP shapes and
|
|
1252
|
+
errors belong to `server/publishing-routes.ts`; CLI composition supplies the
|
|
1253
|
+
read-only job lookup. Quantized models and adapters publish as model repos;
|
|
1254
|
+
datasets publish as dataset repos. Uploads need no model execution lease.
|
|
1255
|
+
[Publishing tests](tests/server/publishing-routes.test.ts) use temporary token
|
|
1256
|
+
storage and an injected uploader; they never read installed credentials or
|
|
1257
|
+
publish to Hugging Face.
|
|
1258
|
+
|
|
1259
|
+
The `upload` verb, main's `mlx_lm.upload` counterpart, is the models module's:
|
|
1260
|
+
`mlx-bun upload --path <dir> --upload-repo <org/repo> [--private]` (`--path`
|
|
1261
|
+
is required; there is no working-directory default). It publishes through the catalog
|
|
1262
|
+
(`publishing/catalog-hub.ts`, which resolves the token through
|
|
1263
|
+
`publishing/credentials.ts`), fails before any request when the repo id,
|
|
1264
|
+
directory, or write token is missing, and pushes a model repo through the same
|
|
1265
|
+
public hub uploader with the commit message "Upload with mlx-bun". SIGINT or
|
|
1266
|
+
SIGTERM aborts the transfer; nothing is committed after an abort. The
|
|
1267
|
+
[upload CLI tests](tests/upload-cli.test.ts) spawn the real CLI against a local mock Hub with an isolated
|
|
1268
|
+
home directory and an invented token; the module's own tests inject the catalog.
|
|
1269
|
+
|
|
1270
|
+
## Web hub
|
|
1271
|
+
|
|
1272
|
+
The hub routes, the library and the Models panel are the models module's ([`@mlx-bun/module-models`](../../packages/module-models/README.md)),
|
|
1273
|
+
which this app installs in the persistent state (`placement: "app"`). `hub/downloads.ts` stays here: it owns web-started
|
|
1274
|
+
transfers, and `catalogTransfers` gives the catalog's `startDownload` and `downloads()` that owner. Admission
|
|
1275
|
+
is synchronous before the metadata request, so a duplicate submit answers 409;
|
|
1276
|
+
`GET /downloads` serves the owner's rows, one per transfer from admission (the
|
|
1277
|
+
panel shows "preparing…" until the listing and preflight finish) through the
|
|
1278
|
+
hub tracker's live row to done, or to an error when the listing fails or
|
|
1279
|
+
shutdown cancels it; completion re-indexes the catalog, which announces `catalog.changed` (the module's library rows and the
|
|
1280
|
+
host's model listing follow it); shutdown aborts and joins every transfer before the engine
|
|
1281
|
+
closes, leaving resumable partials and publishing nothing. Selecting a model
|
|
1282
|
+
(`POST /api/hub/serve`, the panel's Serve button) asks the model host to serve it (`ModelHost.serve`, which the serving host
|
|
1283
|
+
lends through its link): it loads beside the running models when it fits,
|
|
1284
|
+
otherwise the least recently used one is drained first (see the model host), and the
|
|
1285
|
+
answer is `{ ok: true, model }` (404 for an id that is not a local model, 502 when it
|
|
1286
|
+
cannot load). A host that serves one model answers `restart_required` with the
|
|
1287
|
+
`mlx-bun serve <id>` command.
|
|
1288
|
+
|
|
1289
|
+
## Audio transcription
|
|
1290
|
+
|
|
1291
|
+
Speech-to-text is the transcription module's ([`@mlx-bun/module-transcription`](../../packages/module-transcription/README.md):
|
|
1292
|
+
the service, `/v1/audio/*`, `/admin/transcription/unload`, `transcribe`, `dictate`).
|
|
1293
|
+
This app installs it (`src/modules.ts`) and supplies its core services:
|
|
1294
|
+
The in-process composition shares the state's `modelHost` with persistent and
|
|
1295
|
+
model-scoped modules. It forwards generation to the serving host and borrows
|
|
1296
|
+
Whisper's transcription operations and residency; the Whisper host still owns
|
|
1297
|
+
its weights and shutdown. Model-scoped modules activate after this link is attached.
|
|
1298
|
+
`serve-host.ts` builds the Whisper model host from `--whisper-model`,
|
|
1299
|
+
`--whisper-idle-unload` and `--whisper-resident`, gives it the generation
|
|
1300
|
+
gateway's exclusive lock so decoding never overlaps chat, mounts the module's
|
|
1301
|
+
routes where the audio routes were, and stops the module and then releases the
|
|
1302
|
+
weights ahead of the chat model. `--preload` and serving a Whisper checkpoint as
|
|
1303
|
+
the main model start the transcription-only server (the audio routes plus
|
|
1304
|
+
`/v1`, `/v1/models`, `/health` and `/stats`). `GET /v1/models` (which the web chat's
|
|
1305
|
+
`ready.transcription` flag, for the hold-to-talk mic in the chat composer, reads) reports the model host's default Whisper. The [serve
|
|
1306
|
+
tests](tests/serve-cli.test.ts) cover the flags, both `runServe` branches and
|
|
1307
|
+
both compositions over a fake Whisper backend; the [voice
|
|
1308
|
+
test](tests/engine/voice.test.ts) and the opt-in [transcription
|
|
1309
|
+
test](tests/engine/transcription.test.ts) run real weights through this app, and the
|
|
1310
|
+
opt-in [transcription parity test](tests/engine/transcription-parity.test.ts) checks the
|
|
1311
|
+
served transcripts against the oracle.
|
|
1312
|
+
|
|
1313
|
+
## Metrics and performance
|
|
1314
|
+
|
|
1315
|
+
The live view is the metrics module's ([`@mlx-bun/module-metrics`](../../packages/module-metrics/README.md):
|
|
1316
|
+
`/api/metrics/snapshot`, `/stream`, bench-serve runs and history). This app installs it and supplies what it
|
|
1317
|
+
subscribes to. `AppState.events` is the `events` bus; `startModelHost` measures the model load and
|
|
1318
|
+
`startContextHost` publishes it once the modules are active; `src/engine/telemetry.ts` wraps the completion engine
|
|
1319
|
+
(`engine.completion` is replaced by the observed one, so every route runs through it) to publish each request's
|
|
1320
|
+
queue wait, time to first token, prefill and decode rates and total time from the run's own stats, and samples the
|
|
1321
|
+
gateway and prompt cache every second (batch rows of capacity, queue depth, tokens per second, projected KV bytes
|
|
1322
|
+
against `--kv-budget`, prompt-cache bytes, hits and misses, per-model memory), quiet while idle and unchanged.
|
|
1323
|
+
Publishing only appends to bounded queues, so a slow subscriber never delays generation; nothing under the engine
|
|
1324
|
+
imports the bus. The module requires `jobs`, so it activates in the persistent state (`serve-state.ts`) with the state's bus;
|
|
1325
|
+
`jobs/service.ts` runs its `bench-serve` runner as a managed task (rows and logs in the job store, listed by
|
|
1326
|
+
`/api/jobs`), under the engine's execution lease because the runner declares `gpu: "exclusive"`. In the default isolated mode
|
|
1327
|
+
each worker's events are relayed to the parent's bus, so the view there covers every resident model. `/stats`, `/health` and `/fit` are unchanged. The status page loads the
|
|
1328
|
+
module's panel element while it is visible (`web/browser/status.ts`). The opt-in [real-weights
|
|
1329
|
+
test](tests/engine/metrics-native.test.ts) (`MLX_BUN_APP_TEST_MODEL=<snapshot directory>`) serves a model and requires
|
|
1330
|
+
the module's numbers to equal each response's `usage` and `/stats`.
|
|
1331
|
+
|
|
1332
|
+
## Benchmarks
|
|
1333
|
+
|
|
1334
|
+
Answer-quality evaluation is the benchmarks module's ([`@mlx-bun/module-benchmarks`](../../packages/module-benchmarks/README.md):
|
|
1335
|
+
`/api/benchmarks/tasks`, `/runs`, `/compare`, the `eval-serve` job kind). It orchestrates `scripts/eval-serve.ts`
|
|
1336
|
+
(the capability evaluation runner) and scores nothing itself. This app installs it and supplies what it requires in the
|
|
1337
|
+
persistent state (`cli/serve-state.ts`): the `eval-serve` runner runs on the job host under the engine's execution lease
|
|
1338
|
+
(`gpu: "exclusive"`), `storage` resolves its `benchmarks/` entries under `MLX_BUN_HOME` (datasets in `benchmarks/data`,
|
|
1339
|
+
placed by the user, never downloaded), `modelHost` names the served model, and `catalog` (the registry-backed one
|
|
1340
|
+
over the model index, native-free like the rest of the state) resolves it to its directory.
|
|
1341
|
+
The runner then starts its own server from `bin/mlx-bun.mjs serve --model <dir> --port <free>` in a sandboxed `HOME`;
|
|
1342
|
+
the app's served model stays resident and idle. The web shell mounts the module's panel (a Benchmarks tab, from its manifest). The opt-in [real-weights test](tests/engine/benchmarks-native.test.ts)
|
|
1343
|
+
(`MLX_BUN_APP_TEST_MODEL=<snapshot directory> MLX_BUN_EVAL_DATA=<pinned datasets>`) runs `gsm8k-50` and `mmlu` through
|
|
1344
|
+
the module and requires its history to equal a direct `eval-serve.ts run` of the same plan.
|
|
1345
|
+
|
|
1346
|
+
## Standalone bundle
|
|
1347
|
+
|
|
1348
|
+
After staging the root native setup and the microphone helper with
|
|
1349
|
+
`bun run --filter @mlx-bun/module-transcription build:native`, run `bun run build:binary` from the root.
|
|
1350
|
+
`dist/bundle/` contains the executable, native libraries/helpers, Pi's Photon
|
|
1351
|
+
WASM sidecar, the project license, and combined third-party notices: the MLX and
|
|
1352
|
+
inference package notices, Photon's installed Apache-2.0 license, the app's own
|
|
1353
|
+
[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md), and one section per npm package
|
|
1354
|
+
the build retained. [bundle-notices.ts](../../scripts/bundle-notices.ts) reads the
|
|
1355
|
+
metafiles of the executable and embedded browser builds: every input that
|
|
1356
|
+
contributed bytes maps to its nearest `package.json` `name@version`; workspace
|
|
1357
|
+
packages are excluded. A section copies the installed LICENSE, LICENCE, NOTICE and
|
|
1358
|
+
COPYING files verbatim. Packages that install none (Pi, XGrammar) must be named at
|
|
1359
|
+
that exact `name@version` in the app or package notices, which carry the upstream
|
|
1360
|
+
text with its source revision; otherwise the build fails. Reviewed vendored license
|
|
1361
|
+
headers are copied from the installed files. Curated sections of the app and
|
|
1362
|
+
inference notices carry the notices of code that Jiti's prebundle, XGrammar's WASM
|
|
1363
|
+
build and json-bigint's `lib/parse.js` embed; those packages' retained files are
|
|
1364
|
+
pinned by sha256, and each curated section by the sha256 of its text. Another
|
|
1365
|
+
version, a changed file, or a changed or missing section fails the build until
|
|
1366
|
+
reviewed. The notices still missing are listed under Release acceptance in PLAN.md.
|
|
1367
|
+
Move the whole directory together. Web assets and the memory skill
|
|
1368
|
+
are embedded; source checkouts retain their existing asset readers and browser
|
|
1369
|
+
build fallback. No terminal Pi assets are included.
|
|
1370
|
+
|
|
1371
|
+
`bun run verify:binary` builds into temporary storage, archives the bundle with
|
|
1372
|
+
the release preparation's tar command, and removes the build. The shared
|
|
1373
|
+
acceptance then extracts the archive to another directory, compiles a consumer
|
|
1374
|
+
into that copy, checks every notice section of a fresh build, and installs the
|
|
1375
|
+
unchanged archive through the curl installer using a local curl stub and
|
|
1376
|
+
temporary home, then upgrades once. It checks the actual CLI and managed child plus the consumer for
|
|
1377
|
+
web, memory, synthetic registry/fit, native path resolution, microphone helper
|
|
1378
|
+
help, and Photon initialization in the relocated, installed, and retained
|
|
1379
|
+
previous bundles.
|
|
1380
|
+
The default runs no model and makes no remote download. Each install ends with
|
|
1381
|
+
the installed executable's Neural Engine install step (`mlx-bun compile-ane`),
|
|
1382
|
+
which the check requires to run: it compiles this Mac's programs or reports none
|
|
1383
|
+
to compile, and where the Neural Engine cannot compile (a virtual machine) the
|
|
1384
|
+
installer warns and keeps the installation. Mac CI runs this check.
|
|
1385
|
+
With exclusive GPU access, `bun run verify:binary --model /path/to/cached-model`
|
|
1386
|
+
also starts the actual relocated executable, checks its web assets, `/stats`,
|
|
1387
|
+
`/fit`, and a chat completion, then requires a clean SIGTERM exit. It discovers
|
|
1388
|
+
the supplied weights through a temporary HF snapshot symlink and isolates the
|
|
1389
|
+
child's home, caches, and chat storage; it never copies or downloads weights.
|
|
1390
|
+
This is a local build artifact, not signing/notarization or release publishing.
|
|
1391
|
+
|
|
1392
|
+
[`scripts/install.sh`](../../scripts/install.sh) installs a complete release
|
|
1393
|
+
bundle under `${MLX_BUN_INSTALL_DIR:-$HOME/.mlx-bun}/app-install/` and links
|
|
1394
|
+
`~/.local/bin/mlx-bun` and, beside it, `mlx-bun.<cmd>` for each
|
|
1395
|
+
[mlx-lm alias](#mlx-lm-compatibility-mlx-buncmd) (each a link to the same
|
|
1396
|
+
executable; all are refused where a directory stands and switch with the app).
|
|
1397
|
+
`MLX_BUN_VERSION` selects `latest` or a pinned tag.
|
|
1398
|
+
The installer validates the files and version before switching its `current`
|
|
1399
|
+
symlink and retains the old app on failure. After switching it runs the
|
|
1400
|
+
installed `mlx-bun compile-ane` (the Neural Engine install step; a failure warns
|
|
1401
|
+
and keeps the installation). Successful updates keep the current
|
|
1402
|
+
and immediate previous bundles, plus any older bundle used by a running app.
|
|
1403
|
+
Other older owned bundles and stale stages are removed on the next install;
|
|
1404
|
+
vnode inspection keeps bundles used by apps launched through PATH or symlinks,
|
|
1405
|
+
and an inconclusive inspection retains the affected bundle. The CLI captures
|
|
1406
|
+
its executable identity at startup, so managed jobs launched after an upgrade
|
|
1407
|
+
still use that running app's original build. A later install prunes old bundles
|
|
1408
|
+
after their processes exit.
|
|
1409
|
+
Sessions, wiki, credentials, and legacy flat installation files
|
|
1410
|
+
outside `app-install/` stay intact. A custom `MLX_BUN_INSTALL_DIR` relocates only
|
|
1411
|
+
the installed bundle; application data still lives under `~/.mlx-bun`.
|
|
1412
|
+
A concurrent install is blocked by `app-install/lock`; after an interrupted
|
|
1413
|
+
installer, remove the reported absolute lock directory only after confirming
|
|
1414
|
+
its recorded PID is no longer an installer. Run the script with `--help` for usage.
|
|
1415
|
+
The public installer must not deploy before a compatible release bundle exists;
|
|
1416
|
+
older release archives lack the newly required license and notice files.
|
|
1417
|
+
|
|
1418
|
+
`bun scripts/prepare-homebrew.ts /path/to/mlx-bun-v<version>-arm64.tar.gz`
|
|
1419
|
+
prepares a local `mlx-bun.rb` beside the archive, with its version, release URL,
|
|
1420
|
+
and SHA256. The formula installs the entire bundle in `libexec` and symlinks its
|
|
1421
|
+
command into `bin`. Preparation checks archive members; release verification
|
|
1422
|
+
must additionally check that its binary version matches the archive filename.
|
|
1423
|
+
Preparation does not install, sign, notarize, publish, or
|
|
1424
|
+
update the tap. [Installer tests](tests/install.test.ts) use local archives and
|
|
1425
|
+
temporary homes, including reinstall, failure, and every refusal path (usage,
|
|
1426
|
+
tag, platform, archive layout, executable and version, command destination,
|
|
1427
|
+
app-root and `current` ownership), without network access.
|
|
1428
|
+
|
|
1429
|
+
## Release preparation
|
|
1430
|
+
|
|
1431
|
+
`bun scripts/prepare-release.ts prepare /new/output/directory` builds from staged
|
|
1432
|
+
natives, checks the executable against the app manifest version, packs workspace
|
|
1433
|
+
packages, and writes unsigned local archives, checksums, and a Homebrew formula.
|
|
1434
|
+
It then runs the model-free `verify:binary` acceptance on the unsigned archive
|
|
1435
|
+
and requires unchanged bundle hashes afterwards; a failure removes `unsigned/`
|
|
1436
|
+
and writes no `preparation.json`.
|
|
1437
|
+
`preparation.json` records bundle hashes, dependency-first package publication
|
|
1438
|
+
order, package archive hashes, source revision, and pending private/version decisions. Private
|
|
1439
|
+
example apps are excluded; an unpublished private main app still requires a decision. Nothing changes those decisions or
|
|
1440
|
+
publishes. The `unsigned/` output is for local verification only.
|
|
1441
|
+
|
|
1442
|
+
The same script has explicit `sign <directory> <Developer-ID-identity>`,
|
|
1443
|
+
`notarize <directory> <keychain-profile>`, and `package <directory>` stages.
|
|
1444
|
+
Signing handles every nested Mach-O before the executable, applies the preserved
|
|
1445
|
+
Bun JIT/library-validation entitlements, verifies each signature, and checks
|
|
1446
|
+
launch/version. It signs a temporary bundle copy and only replaces the original
|
|
1447
|
+
after every check succeeds, so a partial failure can be retried. Notarization submits to Apple and requires JSON status `Accepted`;
|
|
1448
|
+
an exit code of zero alone is insufficient. Packaging rejects changed bundle
|
|
1449
|
+
bytes and missing accepted evidence, then writes the versioned and stable arm64
|
|
1450
|
+
tarballs, matching checksum sidecars, and formula under `release/`.
|
|
1451
|
+
|
|
1452
|
+
Run `--help` for usage. Signing/notarization and publishing require Josh's release
|
|
1453
|
+
instruction; preparation and tests do not access identities or Apple services.
|
|
1454
|
+
The release entrypoints are ported from main: `scripts/release-binary.sh` builds,
|
|
1455
|
+
signs, notarizes and packages; `scripts/publish-release.sh` publishes GitHub assets,
|
|
1456
|
+
updates the tap, then publishes npm. `PUBLISH=1 ./scripts/release-binary.sh` runs
|
|
1457
|
+
that publication flow. After it completes, deploy the website and installer through
|
|
1458
|
+
the [manual Pages workflow](../website/README.md); its release tag must be GitHub's
|
|
1459
|
+
latest release and match the published application version.
|
|
1460
|
+
`BUILD_DIR` names the preparation directory, `OUT_DIR` holds
|
|
1461
|
+
the distributable archives, and `RELEASE_NOTES` names the reviewed release body.
|
|
1462
|
+
The publisher requires a clean checkout at `origin/main` matching the prepared
|
|
1463
|
+
source. Its npm step extends main's idempotent publication to all public workspace
|
|
1464
|
+
packages in dependency order, requiring matching archive integrity for an existing
|
|
1465
|
+
version. The formula is generated from the accepted archive, so no checked-in
|
|
1466
|
+
version/hash mirror needs another commit. `bun scripts/release-inputs.ts inspect
|
|
1467
|
+
<directory> <release-notes>` inspects artifacts without publishing. Parity and
|
|
1468
|
+
performance qualification belong to refactor acceptance; this flow does not run
|
|
1469
|
+
them. No preparation stage invokes publication. [Release tests](tests/release.test.ts)
|
|
1470
|
+
use captured mock signing/notary commands and a recording acceptance stand-in;
|
|
1471
|
+
they do not prove a real signature or Apple acceptance.
|
|
1472
|
+
|
|
1473
|
+
The Quantize (`/quantize`), Fine-tune (`/finetune`) and Build Dataset (`/dataset`) pages are the installed
|
|
1474
|
+
modules' self-contained panels. They preserve their wizard steps, job streams and publishing controls;
|
|
1475
|
+
leaving a page closes its stream, while returning to an unfinished job replays its stored progress.
|