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
|
@@ -1,1945 +0,0 @@
|
|
|
1
|
-
# Server API
|
|
2
|
-
|
|
3
|
-
`mlx-bun serve` exposes an OpenAI-compatible HTTP API on one model. The
|
|
4
|
-
request's `model` field is ignored; the loaded model's id is echoed back.
|
|
5
|
-
The default concurrency cap is eight. Eligible requests use continuous batching;
|
|
6
|
-
`--batch 1` pins serial execution. Both use the shared generation session for
|
|
7
|
-
output, cancellation and cleanup. Each model implementation supplies its own
|
|
8
|
-
execution methods; protocol handlers do not select a concrete model class.
|
|
9
|
-
Embedding execution also acquires the shared model/GPU lease, so it waits for
|
|
10
|
-
active generation or model mutation before running.
|
|
11
|
-
|
|
12
|
-
This is the canonical wire reference: every route the server registers is
|
|
13
|
-
listed below, and every field documented here exists in the code that
|
|
14
|
-
serves it (`src/server.ts`, `src/serve/*-routes.ts`, `src/serve/isolate.ts`,
|
|
15
|
-
`src/pi-web.ts`). Start flags (`--port`, `--memory-budget`,
|
|
16
|
-
`--prompt-cache`, `--batch`, `--kv-quant`, `--thinking`, sampling
|
|
17
|
-
defaults) and which combinations compose live in
|
|
18
|
-
[server-config.md](./server-config.md); the CLI in [cli.md](./cli.md).
|
|
19
|
-
|
|
20
|
-
## Route index
|
|
21
|
-
|
|
22
|
-
Dispatch order matters only for `/ws/chat` (upgrade) and the static shell;
|
|
23
|
-
every other path is unique. Unmatched paths return
|
|
24
|
-
`404 {"error":{"message":"not found"}}`.
|
|
25
|
-
|
|
26
|
-
| Method | Path | Section |
|
|
27
|
-
| --- | --- | --- |
|
|
28
|
-
| POST | `/v1/chat/completions` | [Chat completions](#post-v1chatcompletions) |
|
|
29
|
-
| POST | `/v1/completions` | [Text completions](#post-v1completions-raw-text-completion) |
|
|
30
|
-
| POST | `/v1/messages` | [Anthropic Messages](#post-v1messages-anthropic-messages-api) |
|
|
31
|
-
| POST | `/v1/responses` | [OpenAI Responses](#post-v1responses-openai-responses-api) |
|
|
32
|
-
| POST | `/v1/embeddings` | [Embeddings](#post-v1embeddings-openai-embeddings-api) |
|
|
33
|
-
| POST | `/v1/audio/transcriptions`, `/v1/audio/translations` | [Audio transcription](#post-v1audiotranscriptions--post-v1audiotranslations-speech-to-text) |
|
|
34
|
-
| POST | `/v1/audio/sessions`, `/v1/audio/sessions/<id>/audio`, `/v1/audio/sessions/<id>/finish` | [Streaming sessions](#streaming-transcription-sessions-v1audiosessions) |
|
|
35
|
-
| DELETE | `/v1/audio/sessions/<id>` | [Streaming sessions](#streaming-transcription-sessions-v1audiosessions) |
|
|
36
|
-
| POST | `/admin/transcription/unload` | [Audio transcription](#post-v1audiotranscriptions--post-v1audiotranslations-speech-to-text) |
|
|
37
|
-
| GET | `/v1` | [API index](#get-v1) |
|
|
38
|
-
| GET | `/v1/models`, `/v1/models/<id>` | [Models](#get-v1models) |
|
|
39
|
-
| GET / POST | `/v1/adapters` | [Adapters](#adapters-lora-hot-swap) |
|
|
40
|
-
| GET | `/v1/adapters/available` | [Adapters](#adapters-lora-hot-swap) |
|
|
41
|
-
| DELETE | `/v1/adapters/<id>` | [Adapters](#adapters-lora-hot-swap) |
|
|
42
|
-
| GET | `/health` | [Health](#get-health) |
|
|
43
|
-
| GET | `/stats` | [Stats](#get-stats) |
|
|
44
|
-
| GET | `/fit` | [Fit](#get-fit) |
|
|
45
|
-
| GET | `/library` | [Library](#get-library) |
|
|
46
|
-
| GET | `/downloads` | [Downloads](#get-downloads) |
|
|
47
|
-
| POST | `/admin/cache/session/close` | [Close cache session](#post-admincachesessionclose) |
|
|
48
|
-
| POST | `/admin/cache/flush` | [SSD cache flush](#post-admincacheflush) |
|
|
49
|
-
| GET | `/api/jobs`, `/api/jobs/<id>`, `/api/jobs/<id>/stream` | [Jobs](#jobs-apijobs) |
|
|
50
|
-
| POST | `/api/finetune/submit`, `/inspect-dataset`, `/merge`, `/export` | [Fine-tune](#fine-tune-apifinetune) |
|
|
51
|
-
| POST | `/api/quantize/inspect`, `/submit`, `/resolve-folder` | [Quantize](#quantize-apiquantize) |
|
|
52
|
-
| GET / POST | `/api/dataset/templates`, `/api/dataset/submit` | [Dataset](#dataset-apidataset) |
|
|
53
|
-
| POST | `/api/model/resolve-folder` | [Quantize](#quantize-apiquantize) |
|
|
54
|
-
| POST | `/api/{quantize,finetune,dataset}/push` | [Push to Hub](#post-apiquantizefinetunedatasetpush) |
|
|
55
|
-
| GET / POST | `/api/gc/plan`, `/api/gc/execute` | [GC](#get-apigcplan--post-apigcexecute) |
|
|
56
|
-
| GET / POST | `/api/memory/*` | [Memory](#get-apimemory--post-apimemoryinit) |
|
|
57
|
-
| GET | `/v1/memory/synthesize` | [Memory synthesis](#get-v1memorysynthesize) |
|
|
58
|
-
| GET / POST / DELETE | `/api/settings/hf-token`, `/api/settings/tool-approvals` | [Settings](#settings-apisettings) |
|
|
59
|
-
| GET / POST | `/api/hub/local`, `/search`, `/download`, `/serve` | [Model Hub](#model-hub-apihub) |
|
|
60
|
-
| GET | `/api/sessions/search`, `/api/sessions/export` | [Sessions](#get-apisessionssearch--get-apisessionsexport) |
|
|
61
|
-
| GET | `/`, `/assets/*`, `/manifest.webmanifest`, `/sw.js`, `/curves`, `/curve-terrain`, `/dag` | [Static](#web-app-static-routes--pwa-installability) |
|
|
62
|
-
| GET | `/status`, `/chat`, `/quantize`, `/finetune`, `/dataset` | 302 to `/#<path>` ([Static](#web-app-static-routes--pwa-installability)) |
|
|
63
|
-
| POST | `/signal`, `/generate` | [Curve Designer](#curve-designer-post-signal--post-generate) |
|
|
64
|
-
| WS | `/ws/chat` | [Web chat WebSocket](#web-chat-websocket-wschat) |
|
|
65
|
-
|
|
66
|
-
Internal routes (not part of the client contract):
|
|
67
|
-
|
|
68
|
-
| Method | Path | Where | Notes |
|
|
69
|
-
| --- | --- | --- | --- |
|
|
70
|
-
| POST | `/admin/drain` | `src/server.ts` | Only registered when the server is bound to a unix socket (`--unix`, i.e. an `--isolate` engine child). Quiesces the gateway and demotes the whole prompt cache to the SSD tier; returns `{ "drained": true, "demotions": N }`. Never exposed on TCP. |
|
|
71
|
-
| POST | `/admin/lease` | `src/server.ts` | Unix-socket workers only. Flushes durability, then holds the gateway's native execution lease while the response connection stays open. The isolation parent uses it for managed GPU jobs; disconnect releases the lease. Never exposed on TCP. |
|
|
72
|
-
| GET | `/engine` | `src/serve/isolate.ts` | Only on the `--isolate` parent proxy: `{ isolated: true, pid, restarts, socket, response_store, pool?: { resident, default } }`. Worker fields describe the current default worker, or are `null` while it is evicted; inspection never loads it. |
|
|
73
|
-
|
|
74
|
-
Under `--isolate`, the parent proxies everything to the engine child;
|
|
75
|
-
`/ws/chat` is answered `501` there (web chat needs a non-isolated server),
|
|
76
|
-
`/v1/chat/completions`, `/v1/completions`, `/v1/messages`, `/v1/responses`,
|
|
77
|
-
and `/v1/embeddings` are routed by the body's `model` field when a
|
|
78
|
-
`--model-pool` is configured, an unreachable child is
|
|
79
|
-
`502 {"error":{"type":"engine_unavailable"}}`, and a client disconnect
|
|
80
|
-
mid-proxy is `499`.
|
|
81
|
-
|
|
82
|
-
## Diagnostics: `x-mlx-bun-trace-id` and `MLX_BUN_P2R_TRACE`
|
|
83
|
-
|
|
84
|
-
`/v1/chat/completions` and `/v1/completions` read an optional
|
|
85
|
-
`x-mlx-bun-trace-id` request header. With `MLX_BUN_P2R_TRACE=1` set on the
|
|
86
|
-
server, each of those requests emits one JSON record to stderr, prefixed
|
|
87
|
-
`[p2r] `, when it finishes (`src/serve/prompt-response-trace.ts`). Without
|
|
88
|
-
the env var nothing is allocated and the header is ignored.
|
|
89
|
-
|
|
90
|
-
```jsonc
|
|
91
|
-
{
|
|
92
|
-
"version": 1,
|
|
93
|
-
"traceId": "<x-mlx-bun-trace-id, or the request id when absent>",
|
|
94
|
-
"requestId": "chatcmpl-…" | "cmpl-…",
|
|
95
|
-
"route": "/v1/chat/completions" | "/v1/completions",
|
|
96
|
-
"clock": "monotonic-ms",
|
|
97
|
-
"outcome": "success" | "error" | "abort",
|
|
98
|
-
"totalMs": 0.0,
|
|
99
|
-
"events": [ // sorted by startMs
|
|
100
|
-
{ "phase": "request.body_parse", "startMs": 0.0, "durationMs": 0.0,
|
|
101
|
-
"attributes": { /* optional string|number|boolean|null map */ } }
|
|
102
|
-
],
|
|
103
|
-
"attributes": { "stage": "body_parse" | "prepare_completion" } // on some errors
|
|
104
|
-
}
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
Phases: `request.body_parse`, `request.prompt_prepare`, `completion.total`,
|
|
108
|
-
`completion.placement`, `engine.admission_wait`, `cache.lookup_restore`,
|
|
109
|
-
`prefill.total`, `prefill.batch_setup`, `prefill.chunk`,
|
|
110
|
-
`prefill.kv_maintenance`, `token_zero.total`, `token_zero.forward`,
|
|
111
|
-
`token_zero.head`, `token_zero.sample`, `response.first_write`,
|
|
112
|
-
`response.final_write`. Marks (`response.first_write`,
|
|
113
|
-
`response.final_write`) have `durationMs: 0`.
|
|
114
|
-
|
|
115
|
-
## POST /v1/chat/completions
|
|
116
|
-
|
|
117
|
-
Request body (OpenAI chat schema; unknown fields ignored):
|
|
118
|
-
|
|
119
|
-
```jsonc
|
|
120
|
-
{
|
|
121
|
-
"messages": [ /* role: system | user | assistant | tool */ ],
|
|
122
|
-
"stream": false,
|
|
123
|
-
"max_tokens": 1024, // or max_completion_tokens (wins); unset =
|
|
124
|
-
// no cap — generate until EOS or the
|
|
125
|
-
// an explicit operator limit is reached;
|
|
126
|
-
// --max-tokens sets a server default
|
|
127
|
-
"temperature": 0.7, // 0 = greedy
|
|
128
|
-
"top_p": 0, "top_k": 0, // 0 = off
|
|
129
|
-
"seed": 1234, // omit for time-derived
|
|
130
|
-
"min_p": 0.05, // min-p sampling (0 = off)
|
|
131
|
-
"xtc_probability": 0.5, // XTC sampling: chance per step of dropping
|
|
132
|
-
"xtc_threshold": 0.1, // every token above threshold except the
|
|
133
|
-
// least likely (EOS + newline exempt,
|
|
134
|
-
// matching mlx_lm.server)
|
|
135
|
-
"logit_bias": { "42": -5.0 }, // additive per-token-id logit bias
|
|
136
|
-
"repetition_penalty": 1.1, // optional
|
|
137
|
-
"repetition_context_size": 20, // recent-token window (0 = whole history)
|
|
138
|
-
"presence_penalty": 0.0, // subtracted once if the token occurred
|
|
139
|
-
"presence_context_size": 20, // in the window (mlx-lm extension)
|
|
140
|
-
"frequency_penalty": 0.0, // subtracted per occurrence in the window
|
|
141
|
-
"frequency_context_size": 20, // (mlx-lm extension)
|
|
142
|
-
"logprobs": false, // BOOL (mlx_lm.server's type, even on
|
|
143
|
-
// /v1/completions): return each emitted
|
|
144
|
-
// token's logprob
|
|
145
|
-
"top_logprobs": -1, // int in [0, 11] or -1 = unset (mlx-lm's
|
|
146
|
-
// validation — its cap is 11, not
|
|
147
|
-
// OpenAI's 20); k > 0 returns the top-k
|
|
148
|
-
// (token, logprob) pairs per position
|
|
149
|
-
"stop": "\n\n", // or ["###", "\n\n"] (spec: up to 4)
|
|
150
|
-
"tools": [ /* OpenAI function tools */ ],
|
|
151
|
-
"tool_choice": "auto", // "none" disables tools; "auto" and the
|
|
152
|
-
// {type:"function", function:{name}}
|
|
153
|
-
// object are accepted and treated as auto
|
|
154
|
-
"chat_template_kwargs": { // forwarded to the chat template
|
|
155
|
-
"enable_thinking": false, // MiniCPM5 / Qwen3.5/3.8 / GLM-5.2: <think>
|
|
156
|
-
// channel on/off (Qwen3.8 default: ON)
|
|
157
|
-
"preserve_thinking": true // Qwen3.8: keep think blocks from earlier
|
|
158
|
-
// assistant turns in the rendered prompt
|
|
159
|
-
// (template default true — better prompt-
|
|
160
|
-
// cache reuse in agent loops). Ignored by
|
|
161
|
-
// templates that don't read it.
|
|
162
|
-
},
|
|
163
|
-
"reasoning_effort": "medium", // "none"|"minimal"|"low"|"medium"|"high"|"xhigh"
|
|
164
|
-
// gates enable_thinking on Qwen3.5/3.8/
|
|
165
|
-
// MiniCPM5: "none" → off, any other level →
|
|
166
|
-
// on. Only consulted when chat_template_
|
|
167
|
-
// kwargs.enable_thinking is not explicitly
|
|
168
|
-
// set — an explicit enable_thinking always
|
|
169
|
-
// wins. On templates with a reasoning-depth
|
|
170
|
-
// variable (Qwen3.8) the level also maps
|
|
171
|
-
// into the template: minimal/low → low,
|
|
172
|
-
// medium → medium, high/xhigh → xhigh.
|
|
173
|
-
// Any OTHER string is a 400.
|
|
174
|
-
"hlg": { // HLG tone-curve sampling (per request).
|
|
175
|
-
"enabled": true, // merged over --hlg-sampling server defaults.
|
|
176
|
-
"width": 4, // logit-width of the tone plateau
|
|
177
|
-
"shoulder": 4, // top shoulder width
|
|
178
|
-
"toe": 6, // bottom toe width
|
|
179
|
-
"pivot_offset": 6 // pivot point offset from top
|
|
180
|
-
},
|
|
181
|
-
"response_format": // structured output (grammar-constrained
|
|
182
|
-
{ "type": "json_object" }, // decoding) — also guided_grammar,
|
|
183
|
-
// guided_regex, guided_choice,
|
|
184
|
-
// structured_outputs; see "Structured
|
|
185
|
-
// output" below
|
|
186
|
-
"adapter": "id" // LoRA: "id", stacked "a+b", or "none"
|
|
187
|
-
}
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
Sampling defaults follow the model author's `generation_config.json`
|
|
191
|
-
when a field is omitted (optiq serve's gen_config behavior); explicit
|
|
192
|
-
request values always win. MiniCPM5 defaults to the no-think direct
|
|
193
|
-
answer mode unless `chat_template_kwargs.enable_thinking` is `true`.
|
|
194
|
-
|
|
195
|
-
Assistant history accepts `reasoning` as an alias for `reasoning_content`,
|
|
196
|
-
including Pi's reasoning on tool-call messages. An explicit `reasoning_content`
|
|
197
|
-
string takes precedence, even when empty. Normalization preserves the original
|
|
198
|
-
message and exposes the canonical field to templates, so Qwen's
|
|
199
|
-
`preserve_thinking: true` retains that history across tool turns.
|
|
200
|
-
|
|
201
|
-
GLM-5.2 also defaults to no-think: its generation primer is
|
|
202
|
-
`<think></think>`. Enabling thinking renders `Reasoning Effort: Max` in the
|
|
203
|
-
system turn and leaves an open `<think>` primer for generation. The same
|
|
204
|
-
precedence applies through Chat Completions, Anthropic Messages, and Responses
|
|
205
|
-
because all three normalize into this one chat request path.
|
|
206
|
-
|
|
207
|
-
`stop` sequences are matched on **decoded text**, not token ids, so a
|
|
208
|
-
sequence that spans token boundaries still fires. Generation halts at
|
|
209
|
-
the first match; the stop sequence itself is excluded from the content
|
|
210
|
-
and `finish_reason` is `"stop"`.
|
|
211
|
-
|
|
212
|
-
Message `content` is a string or an array of parts:
|
|
213
|
-
`{ "type": "text", "text": ... }` and
|
|
214
|
-
`{ "type": "image_url", "image_url": { "url": "data:image/png;base64,..." } }`
|
|
215
|
-
(`{ "type": "image", ... }` is accepted as an alias; http/https URLs also
|
|
216
|
-
accepted; PNG, JPEG, HEIC, AVIF, WebP, TIFF, GIF, BMP via native OS codecs;
|
|
217
|
-
requires a model with the vision sidecar).
|
|
218
|
-
|
|
219
|
-
Remote (http/https) media URLs — image, audio, and video alike — go
|
|
220
|
-
through a destination policy (`src/media-fetch.ts`): **private/loopback/
|
|
221
|
-
link-local hosts are refused by default** (the request URL is
|
|
222
|
-
attacker-controlled input — SSRF), every redirect hop is re-validated, and
|
|
223
|
-
the fetch has a **10 s timeout** and a **64 MB response cap** (video:
|
|
224
|
-
**256 MB** — clips are legitimately larger; frame sampling truncates long
|
|
225
|
-
clips server-side). Violations are clean `400`s
|
|
226
|
-
(`prompt build failed: image url rejected: …`). Serving media from a LAN
|
|
227
|
-
host (a NAS, another machine) is the opt-in
|
|
228
|
-
[`--allow-private-media`](server-config.md#start-flags) flag (or
|
|
229
|
-
`MLX_BUN_ALLOW_PRIVATE_MEDIA=1`). `data:` URLs are decoded locally and
|
|
230
|
-
never policy-checked.
|
|
231
|
-
|
|
232
|
-
### Video input (Qwen3.5-family models)
|
|
233
|
-
|
|
234
|
-
Video rides the same content-part array:
|
|
235
|
-
|
|
236
|
-
```jsonc
|
|
237
|
-
{ "type": "video_url", "video_url": { "url": "data:video/mp4;base64,…|http(s)://…" } }
|
|
238
|
-
{ "type": "video", "data": "<base64>" } // alias
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
The container decodes through macOS's own AVFoundation codecs (mp4/mov,
|
|
242
|
-
H.264/HEVC/ProRes/… — whatever this macOS plays) via the
|
|
243
|
-
`mlx-bun-frame-extract` helper that ships beside the binary and in the
|
|
244
|
-
native pack; from a source tree it compiles on first use with the Xcode
|
|
245
|
-
CLT. Frames are sampled at **2 fps, capped at 768 frames**
|
|
246
|
-
(`src/vision/video-frames.ts`), then run the gated Qwen3-VL video pipeline
|
|
247
|
-
(temporal frame pairs, per-frame-group attention, 3D mRoPE positions).
|
|
248
|
-
Qwen3.5-family models only; video never combines with audio parts
|
|
249
|
-
(`400 "video and audio content parts cannot be combined"`), and a non-Qwen
|
|
250
|
-
model answers video parts with a `400` naming the model.
|
|
251
|
-
|
|
252
|
-
### Audio input
|
|
253
|
-
|
|
254
|
-
Audio rides the same content-part array (OpenAI-canonical shape plus two
|
|
255
|
-
aliases):
|
|
256
|
-
|
|
257
|
-
```jsonc
|
|
258
|
-
{ "type": "input_audio", "input_audio": { "data": "<base64>", "format": "wav" } }
|
|
259
|
-
{ "type": "audio", "data": "<base64>" } // alias
|
|
260
|
-
{ "type": "audio_url", "audio_url": { "url": "data:…|http(s)://…" } } // alias
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
(`audio_url` http/https fetches follow the same destination policy,
|
|
264
|
-
timeout, and size cap as `image_url` — see above.)
|
|
265
|
-
|
|
266
|
-
```bash
|
|
267
|
-
curl -s http://localhost:8080/v1/chat/completions \
|
|
268
|
-
-H 'content-type: application/json' \
|
|
269
|
-
-d "$(jq -n --arg b64 "$(base64 -i clip.wav)" '{
|
|
270
|
-
messages: [{ role: "user", content: [
|
|
271
|
-
{ type: "input_audio", input_audio: { data: $b64, format: "wav" } },
|
|
272
|
-
{ type: "text", text: "Transcribe this audio." }
|
|
273
|
-
]}],
|
|
274
|
-
max_tokens: 64, temperature: 0
|
|
275
|
-
}')"
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
- **Models**: any model whose `config.json` carries an `audio_config`
|
|
279
|
-
AND whose `optiq_vision.safetensors` sidecar ships the audio-tower
|
|
280
|
-
tensors — gemma-4 **e4b** today. Auto-detected, no flags (mirrors
|
|
281
|
-
vision). A request WITH audio on any other model gets an explicit
|
|
282
|
-
`400` naming the model — never a silent text-only answer.
|
|
283
|
-
- **Formats**: WAV is decoded natively (PCM 16/24/32-bit + float32, any
|
|
284
|
-
channel count / sample rate). mp3, m4a/AAC, FLAC, ogg(-CAF), AIFF and
|
|
285
|
-
anything else CoreAudio reads transcode via macOS `afconvert`
|
|
286
|
-
(`src/audio/transcode.ts`); undecodable bytes are a `400`.
|
|
287
|
-
- **Internal format**: 16 kHz mono — multi-channel input is mean-mixed,
|
|
288
|
-
other rates resample. Clips truncate at **30 s** (the oracle
|
|
289
|
-
processor's 480 000-sample cap); one clip costs
|
|
290
|
-
`ceil(duration_ms / 40)` prompt tokens, at most **750**
|
|
291
|
-
(`src/audio/decode.ts`).
|
|
292
|
-
- Multiple clips per message and **mixed image+audio** requests work;
|
|
293
|
-
media splices in document order.
|
|
294
|
-
- **Routing**: audio requests always run on the **serial lane** (never
|
|
295
|
-
batched, even under `--batch N`) and skip the prompt cache — soft
|
|
296
|
-
tokens are identical placeholder ids, so prefix matching across
|
|
297
|
-
different clips would false-hit.
|
|
298
|
-
- The Anthropic surface (`/v1/messages`) has no audio block type in its
|
|
299
|
-
protocol; audio blocks there are a `400` pointing back to this
|
|
300
|
-
endpoint.
|
|
301
|
-
|
|
302
|
-
Non-streaming response:
|
|
303
|
-
|
|
304
|
-
```jsonc
|
|
305
|
-
{
|
|
306
|
-
"id": "chatcmpl-…", "object": "chat.completion", "created": 1760000000,
|
|
307
|
-
"model": "<loaded model id>",
|
|
308
|
-
"choices": [{
|
|
309
|
-
"index": 0,
|
|
310
|
-
"message": {
|
|
311
|
-
"role": "assistant",
|
|
312
|
-
"content": "…", // null when the reply is ONLY tool calls
|
|
313
|
-
"reasoning": "…", // present only when the model emitted a
|
|
314
|
-
// <think> channel (thinking-capable
|
|
315
|
-
// templates with thinking on)
|
|
316
|
-
"tool_calls": [{ // present when the model called tools
|
|
317
|
-
"id": "call_…", "type": "function",
|
|
318
|
-
"function": { "name": "…", "arguments": "{…json…}" }
|
|
319
|
-
}]
|
|
320
|
-
},
|
|
321
|
-
"logprobs": { /* see below; only when requested */ },
|
|
322
|
-
"finish_reason": "stop" | "length" | "tool_calls"
|
|
323
|
-
}],
|
|
324
|
-
"usage": {
|
|
325
|
-
"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0,
|
|
326
|
-
"prompt_tokens_details": { "cached_tokens": 0 }, // prompt-cache reuse
|
|
327
|
-
"lane": "serial", // "serial" | "serial+spec" | "batched" —
|
|
328
|
-
// which execution lane served THIS
|
|
329
|
-
// request. Chat completions and text
|
|
330
|
-
// completions (streaming: final usage
|
|
331
|
-
// chunk).
|
|
332
|
-
"speculation": { // only when a draft source is mounted
|
|
333
|
-
"drafted": 0, // draft tokens proposed
|
|
334
|
-
"accepted": 0, // drafts accepted by the verify step
|
|
335
|
-
"targetCalls": 0 // target-model forward calls
|
|
336
|
-
},
|
|
337
|
-
"fill": { // only under MLX_BUN_FILL=strict, when a
|
|
338
|
-
// fill table was armed for the request
|
|
339
|
-
"events": 0, // injected spans
|
|
340
|
-
"injected": 0, // tokens appended by the engine
|
|
341
|
-
"strict": 0, // …of those, from schema/template rows
|
|
342
|
-
"echo": 0, // …from the echo index (MLX_BUN_FILL=echo)
|
|
343
|
-
"spanLens": [], // length of each span, in order
|
|
344
|
-
"wastedSamples": 0, // in-flight samples dropped at an ASSERT fill
|
|
345
|
-
"parseFallback": 0, // tool-call parses rejected after a fill
|
|
346
|
-
"indexTruncated": 0, // spans cut by EOS / delimiter / budget / span
|
|
347
|
-
"decodeSteps": 0, // tokens the model actually sampled
|
|
348
|
-
"verifyEvents": 0, // verify-policy spans that reached the forward
|
|
349
|
-
"verifyAccepted": 0, // …tokens the model's own argmax agreed with
|
|
350
|
-
"verifyRejected": 0, // …tokens dropped and rewound
|
|
351
|
-
"verifyUnsupported": 0, // verify spans dropped: caches cannot rewind
|
|
352
|
-
"checkpointMs": 0, // time in cache checkpoint / rollback
|
|
353
|
-
"branchStops": 0 // echo spans stopped by a history fork
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
```
|
|
358
|
-
|
|
359
|
-
`usage.lane` is server-driven, never client-inferred (the web chat's perf-strip
|
|
360
|
-
lane badge reads it, not a heuristic): `"batched"` when `--batch N` picked up
|
|
361
|
-
the request, `"serial+spec"` when a mounted draft source actually
|
|
362
|
-
contributed accepted draft tokens to this reply, `"serial"` otherwise. It
|
|
363
|
-
rides the SAME usage block on both streaming (final chunk before `[DONE]`) and
|
|
364
|
-
non-streaming responses.
|
|
365
|
-
|
|
366
|
-
`usage.completion_tokens` counts the stopping EOS token in both native and
|
|
367
|
-
speculative serial generation, matching mlx-lm. EOS is excluded from response
|
|
368
|
-
content. An early callback stop or token budget does not count a later EOS
|
|
369
|
-
that happened to be evaluated in the same speculative burst.
|
|
370
|
-
An observed EOS reports `finish_reason: "stop"`, including when its counted
|
|
371
|
-
position equals `max_tokens`.
|
|
372
|
-
|
|
373
|
-
Speculative decoding is a **server-level mode** (`serve --draft-model`,
|
|
374
|
-
or the model-free `serve --draft-kind ngram`, which mounts no draft
|
|
375
|
-
model); there is no per-request draft field. The `speculation` usage
|
|
376
|
-
extension appears on chat and text completions alike, non-streaming and on
|
|
377
|
-
the final stream chunk. Spec-eligible requests are text-only on base weights.
|
|
378
|
-
In the shared executor, Qwen grouped MTP, prompt lookup and standalone drafting support logprobs and
|
|
379
|
-
bf16, uniform KV4/KV8 or per-layer affine KV, or TurboQuant. Qwen MTP and prompt lookup also support
|
|
380
|
-
positive library conversion thresholds for uniform KV4/KV8. Per-layer mixed KV and
|
|
381
|
-
adapters remain incompatible with speculation. Strict legacy serial execution
|
|
382
|
-
retains its narrower bf16/KV4 policy. The KV4 compatibility control is
|
|
383
|
-
`MLX_BUN_QWEN_SPEC_KV4=0`.
|
|
384
|
-
Ineligible requests decode normally and omit the field. Qwen MTP reuses paired
|
|
385
|
-
target/draft prefill and completed decode state through the shared RAM/SSD
|
|
386
|
-
cache by default in v0.4.0, reported in `cached_tokens`. Configured SSD persistence
|
|
387
|
-
supports reuse after restart; `MLX_BUN_MTP_PROMPT_CACHE=0` disables reuse.
|
|
388
|
-
Generated checkpoints cover processed tokens and use queued SSD persistence.
|
|
389
|
-
Long-conversation performance acceptance remains open. Qwen prompt lookup also
|
|
390
|
-
retains prefill and completed-output state in the common RAM/SSD cache, with
|
|
391
|
-
its committed token history stored as a companion attachment. Standalone drafting
|
|
392
|
-
on Qwen targets stores the draft model’s attention/recurrent state and pending
|
|
393
|
-
last draft token as companion state, including restoration after restart. Qualified Qwen
|
|
394
|
-
MTP, prompt lookup and standalone drafting use shared execution at one or several active rows and
|
|
395
|
-
report `usage.lane: "batched"`. Supported full-attention and rotating targets, including
|
|
396
|
-
Llama, MiniCPM and Gemma, use shared bf16, uniform KV4/KV8 or per-layer affine KV or TurboQuant lookup and standalone drafting, with
|
|
397
|
-
logprobs and generated RAM/SSD checkpoints. Delayed and per-layer affine rotating speculation uses the same row lifecycle; positive conversion thresholds are a library setting, while served schemes default to zero. Gemma assistant drafting also shares execution with bf16, uniform KV4/KV8 or per-layer affine KV or TurboQuant target storage and stores its
|
|
398
|
-
last true target hidden alongside generated RAM/SSD prefixes. Donor attention
|
|
399
|
-
handles row validity independently of the draft graph. DeepSpec and DSpark also use these shared target layouts and the common RAM/SSD cache
|
|
400
|
-
for projected context, with independent confidence-based proposal lengths.
|
|
401
|
-
Other provider/target combinations still
|
|
402
|
-
use the serial executor.
|
|
403
|
-
Prompt-lookup concurrent performance acceptance remains open. Ineligible requests can use continuous ordinary execution
|
|
404
|
-
when their features support it, even while a draft is mounted.
|
|
405
|
-
|
|
406
|
-
`usage.fill` reports **token fast-forwarding** (`MLX_BUN_FILL=strict|echo`).
|
|
407
|
-
Strict mode appends structural spans from the request's tools and chat
|
|
408
|
-
template at qualified parser boundaries, with no draft, verification or
|
|
409
|
-
rollback. The model advances its hidden states and caches through the span,
|
|
410
|
-
using its qualified append chunk size, then resumes sampling
|
|
411
|
-
(design: [speculative-decoding.md](../design/speculative-decoding.md) §"Token
|
|
412
|
-
fast-forwarding"). Injected tokens are billed in `completion_tokens` like any
|
|
413
|
-
other generated token; `decodeSteps` counts ordinary sampling steps, so
|
|
414
|
-
`injected / (injected + decodeSteps)` is the share emitted through fill.
|
|
415
|
-
Strict spans bypass sampling; echo spans still undergo sampled verification.
|
|
416
|
-
Injected positions still run through the model layers. The field is absent when the feature is off or the
|
|
417
|
-
request's shape excludes it, including `logprobs`, structured output or media.
|
|
418
|
-
Strict shared fill supports bf16, affine KV and
|
|
419
|
-
TurboQuant layouts and retains absolute sampling positions for explicit seeds.
|
|
420
|
-
Its `usage.fill` statistics use the same fields as explicit serial execution.
|
|
421
|
-
Known tokens still advance model state. All-known steps skip intermediate
|
|
422
|
-
vocabulary heads and use the model-qualified append geometry, including the
|
|
423
|
-
specialized Qwen B1 append. Wider Qwen rows retain one-position arithmetic. Compiled
|
|
424
|
-
replay, paging and interruption checkpoints remain outside this shared method.
|
|
425
|
-
Explicit serial fill still excludes user-fixed seeds.
|
|
426
|
-
|
|
427
|
-
Under `MLX_BUN_FILL=echo` a second, weaker source joins: spans copied from
|
|
428
|
-
earlier in the same session, carried under policy `verify`. Those ride the same
|
|
429
|
-
single forward, are checked by the request's sampler on its logits, and have
|
|
430
|
-
their rejected tail rewound — so `verifyAccepted + verifyRejected` is what the
|
|
431
|
-
echo index proposed and `injected` is what survived. `wastedSamples` counts
|
|
432
|
-
only ASSERT fills (a verify fill consumes the in-flight sample as its first
|
|
433
|
-
position's check instead of discarding it).
|
|
434
|
-
|
|
435
|
-
Qwen MTP can alternate learned draft rounds with verified echo rounds. The
|
|
436
|
-
provider consumes accepted copied tokens and target context before drafting
|
|
437
|
-
again. Its asserted template spans remain ordinary sampled output. Learned
|
|
438
|
-
draft counts, acceptance histograms and `speculation.rounds` exclude echo;
|
|
439
|
-
`targetCalls`, `tokensPerForward` and `forwardsSaved` cover both round types.
|
|
440
|
-
Echo activity remains in `usage.fill`, including matching prefixes established
|
|
441
|
-
by ordinary MTP samples, so it is not additive with learned-draft counts.
|
|
442
|
-
A request fully served by echoes can
|
|
443
|
-
have zero learned drafts. Other mounted providers do not yet support this
|
|
444
|
-
combination.
|
|
445
|
-
|
|
446
|
-
An explicit `seed` controls a request-local random stream and can use
|
|
447
|
-
continuous execution. Repeating a request reproduces sampling at the same
|
|
448
|
-
model arithmetic and batch composition. Different batch shapes can change
|
|
449
|
-
floating-point results even with identical random draws; `--batch 1` retains
|
|
450
|
-
the existing fixed-shape control. This differs from mlx-lm's policy of routing
|
|
451
|
-
explicit seeds to its single-request generator.
|
|
452
|
-
|
|
453
|
-
### logprobs / top_logprobs
|
|
454
|
-
|
|
455
|
-
With `"logprobs": true` and/or `"top_logprobs": k` (1–11), the
|
|
456
|
-
non-streaming response's `choices[0].logprobs` carries
|
|
457
|
-
**`mlx_lm.server`'s block, not OpenAI's** (server.py
|
|
458
|
-
`generate_response`): entries are keyed by token *id*, token strings
|
|
459
|
-
are the raw vocab pieces (`convert_ids_to_tokens` — e.g. `"▁Hello"`),
|
|
460
|
-
and there is no `bytes` field. The same block appears on
|
|
461
|
-
`/v1/completions` (mlx-lm shares the response builder; there is no
|
|
462
|
-
legacy `{tokens, token_logprobs}` text-completion shape).
|
|
463
|
-
|
|
464
|
-
```jsonc
|
|
465
|
-
// top_logprobs: k > 0 (wins over logprobs when both are set — mlx-lm's if/elif)
|
|
466
|
-
"logprobs": { "content": [
|
|
467
|
-
{ "id": 9906, "token": "▁Hello", "logprob": -0.02, // = top-1, merged from
|
|
468
|
-
"top_logprobs": [ // dict(top[0], top_logprobs=top)
|
|
469
|
-
{ "id": 9906, "token": "▁Hello", "logprob": -0.02 },
|
|
470
|
-
{ "id": 13347, "token": "▁Hi", "logprob": -4.1 } // … k entries, sorted desc
|
|
471
|
-
] },
|
|
472
|
-
// … one entry per generated token (reasoning/tool tokens included)
|
|
473
|
-
] }
|
|
474
|
-
|
|
475
|
-
// logprobs: true only
|
|
476
|
-
"logprobs": { "content": [ { "id": 9906, "logprob": -0.02 }, … ] }
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
The distribution matches mlx-lm `generate_step` exactly: full-vocab
|
|
480
|
-
log-softmax of the logits **after** logits processors (logit_bias,
|
|
481
|
-
penalties), **before** the sampler's temperature/top-p/top-k/min-p/XTC.
|
|
482
|
-
mlx-lm's top-k order is unspecified (argpartition); ours is sorted
|
|
483
|
-
descending — the same set, so the entry is deterministically the argmax.
|
|
484
|
-
Stream chunks never carry logprobs (mlx-lm's streaming responses don't
|
|
485
|
-
either). Requests with logprobs can use ordinary continuous execution under
|
|
486
|
-
`--batch N`; other feature exclusions still apply. Invalid values are rejected
|
|
487
|
-
with mlx-lm's exact messages (see Errors).
|
|
488
|
-
|
|
489
|
-
### Streaming
|
|
490
|
-
|
|
491
|
-
`"stream": true` is SSE: `data: <chunk>\n\n` per event, terminated by
|
|
492
|
-
`data: [DONE]`. Chunks are `chat.completion.chunk` objects whose
|
|
493
|
-
`choices[0].delta` carries `{role: "assistant", content: ""}` first, then
|
|
494
|
-
`{reasoning}` increments while the model is inside its `<think>` channel,
|
|
495
|
-
then `{content}` increments (multi-byte sequences are held back until
|
|
496
|
-
decodable; text that could begin a `stop` sequence is held back until
|
|
497
|
-
disambiguated, so no part of a stop sequence is ever streamed), then for
|
|
498
|
-
tool calls a `{tool_calls: [{index, id, type, function}]}` delta; the last
|
|
499
|
-
chunk carries `finish_reason` and `usage`. A generation error mid-stream is
|
|
500
|
-
sent as `data: {"error":{"message":…}}` and the stream closes. While a
|
|
501
|
-
large tool-call body is buffered for parsing, the server sends SSE comment
|
|
502
|
-
heartbeats (`: keep-alive`) so client idle timers do not terminate the call.
|
|
503
|
-
|
|
504
|
-
### Tool calls
|
|
505
|
-
|
|
506
|
-
Qwen's `<parameter=...>` values are raw text. The parser preserves HTML
|
|
507
|
-
entities, indentation and JSON string escapes, removing only one framing
|
|
508
|
-
newline at either end. Array/object parameters are JSON-decoded according to
|
|
509
|
-
the tool schema. The outer Qwen markup is parsed before generic JSON repair,
|
|
510
|
-
so a nested edit array is not mistaken for the tool-call envelope.
|
|
511
|
-
|
|
512
|
-
Tool round-trip: send the assistant message with its `tool_calls` back,
|
|
513
|
-
followed by `{ "role": "tool", "tool_call_id": …, "content": … }`
|
|
514
|
-
messages; multi-turn prompt prefixes reuse the KV prompt cache
|
|
515
|
-
automatically.
|
|
516
|
-
|
|
517
|
-
Tool-call parsing is per model family (`selectToolStreamMode` in
|
|
518
|
-
`src/server.ts`, parsers in `src/tool-call.ts`). Gemma 4 uses its native
|
|
519
|
-
`<|tool_call>`…`<tool_call|>` sentinel tokens. Every other family parses
|
|
520
|
-
**decoded text** once tools are active: OpenAI-JSON
|
|
521
|
-
`<tool_call>{…}</tool_call>`, Qwen-style
|
|
522
|
-
`<tool_call><function=name><parameter=key>value</parameter></function></tool_call>`,
|
|
523
|
-
GLM-5.2's `name<arg_key>…<arg_value>…`, and MiniCPM5's
|
|
524
|
-
`<function name="…"><param name="…">…</param></function>` (CDATA-wrapped
|
|
525
|
-
values supported). Content before the tool markup still streams live; only
|
|
526
|
-
the markup is withheld and converted to `tool_calls`. Argument values are
|
|
527
|
-
decoded against the tool's JSON schema (string-typed params stay strings).
|
|
528
|
-
|
|
529
|
-
When strict parsing fails, a repair pass runs before giving up: a
|
|
530
|
-
```` ```json ```` fence or prose prefix is stripped, single quotes and
|
|
531
|
-
trailing commas are fixed, truncated braces are closed, a function name
|
|
532
|
-
that landed inside `arguments` is lifted into the envelope, and Python
|
|
533
|
-
literals `True`/`False`/`None` **outside string values** are coerced to
|
|
534
|
-
`true`/`false`/`null` (`pythonLiteralsToJson`, `src/tool-call.ts` — Qwen3.5
|
|
535
|
-
in particular emits Python booleans). The repair is not reported on the
|
|
536
|
-
wire; markup that still fails to parse falls back to plain content.
|
|
537
|
-
|
|
538
|
-
### Structured output (grammar-constrained decoding)
|
|
539
|
-
|
|
540
|
-
Both `/v1/chat/completions` and `/v1/completions` accept the
|
|
541
|
-
OpenAI/oMLX/vLLM structured-output fields (snake_case on the wire; the
|
|
542
|
-
field names are `GrammarRequest` in `src/grammar.ts`):
|
|
543
|
-
|
|
544
|
-
```jsonc
|
|
545
|
-
{
|
|
546
|
-
"response_format": // OpenAI: {type:"json_object"} = any valid
|
|
547
|
-
{ "type": "json_schema", // JSON; {type:"json_schema"} constrains
|
|
548
|
-
"json_schema": { // to the schema; {type:"text"} = no-op
|
|
549
|
-
"name": "…", "schema": { /* JSON schema */ }, "strict": true,
|
|
550
|
-
"any_whitespace": true } }, // false = compact separators (no
|
|
551
|
-
// whitespace freedom in the grammar —
|
|
552
|
-
// escape hatch for base models that
|
|
553
|
-
// greedily emit whitespace to max_tokens)
|
|
554
|
-
"guided_grammar": "root ::= …", // raw EBNF grammar string (vLLM/oMLX)
|
|
555
|
-
"guided_regex": "[A-Z][a-z]*", // regex — the regex∩EBNF subset ONLY
|
|
556
|
-
// (classes, |, *, +, ?, parens);
|
|
557
|
-
// regex-only syntax (\d, ., anchors)
|
|
558
|
-
// hits the degrade path — a known gap
|
|
559
|
-
"guided_choice": ["yes", "no"], // output is exactly one of these strings
|
|
560
|
-
"structured_outputs": { /* … */ } // bare JSON schema (oMLX/vLLM alias)
|
|
561
|
-
}
|
|
562
|
-
```
|
|
563
|
-
|
|
564
|
-
| Field | Compiles to (xgrammar) | Output |
|
|
565
|
-
| --- | --- | --- |
|
|
566
|
-
| `response_format: {type:"json_object"}` | builtin JSON grammar | any valid JSON |
|
|
567
|
-
| `response_format: {type:"json_schema", json_schema:{name, schema, strict?, any_whitespace?}}` | `compileJSONSchema(schema)` | schema-conformant JSON |
|
|
568
|
-
| `guided_grammar` | `Grammar.fromEBNF` | text matching the grammar |
|
|
569
|
-
| `guided_regex` | EBNF-wrapped regex | text matching the regex subset |
|
|
570
|
-
| `guided_choice` | `root ::= ("a" \| "b" \| …)` | exactly one of the strings |
|
|
571
|
-
| `structured_outputs` | bare schema (alias for `json_schema.schema`) | schema-conformant JSON |
|
|
572
|
-
|
|
573
|
-
Precedence when several are set (`resolveGrammarRequest`): `guided_grammar`
|
|
574
|
-
> `response_format` json_schema > json_object > `structured_outputs` >
|
|
575
|
-
`guided_regex` > `guided_choice`. `response_format: {type:"text"}` or no
|
|
576
|
-
field at all means no constraint.
|
|
577
|
-
|
|
578
|
-
Enforcement is a per-step token bitmask at the sampler (xgrammar):
|
|
579
|
-
invalid next tokens are masked to −inf **after** the logits processors,
|
|
580
|
-
so valid-token numerics are untouched. Generation halts as soon as the
|
|
581
|
-
grammar is satisfied (e.g. the closing `}` of a complete JSON object)
|
|
582
|
-
with `finish_reason: "stop"`. Works on both lanes — serial and
|
|
583
|
-
`--batch N` (per-row matchers).
|
|
584
|
-
|
|
585
|
-
Degrade path — a grammar that fails to compile is never rejected (no
|
|
586
|
-
400/500): chat prepends a string-valued system instruction for the
|
|
587
|
-
requested JSON schema, raw grammar, regex, or choice constraint, and the
|
|
588
|
-
response carries a `Warning` header (`grammar not enforced: …`);
|
|
589
|
-
`/v1/completions` has no chat template to inject into, so it emits the
|
|
590
|
-
`Warning` header only. `MLX_BUN_GRAMMAR=0` disables mask compilation but
|
|
591
|
-
does not silently ignore a requested constraint: it uses this same degrade
|
|
592
|
-
path with the hint `grammar compilation disabled by MLX_BUN_GRAMMAR=0`.
|
|
593
|
-
Design and fidelity notes: [docs/reference/server-api.md](./server-api.md).
|
|
594
|
-
|
|
595
|
-
### Errors
|
|
596
|
-
|
|
597
|
-
Memory estimates do not reject or shorten requests by default. Actual runtime
|
|
598
|
-
failures use the request/stream error path when recoverable; native Metal
|
|
599
|
-
allocation failure can terminate the process. Saved generation checkpoints
|
|
600
|
-
remain available for an identical retry.
|
|
601
|
-
|
|
602
|
-
All errors are `{ "error": { "message": …, ... } }`.
|
|
603
|
-
|
|
604
|
-
- `400` — malformed JSON (`invalid JSON body`), empty `messages`
|
|
605
|
-
(`messages required`), unknown adapter id, vision request on a model
|
|
606
|
-
without a sidecar (`model has no vision sidecar`), audio request on a
|
|
607
|
-
model without an audio tower (message names the model), video on a
|
|
608
|
-
non-Qwen3.5 model or combined with audio, prompt build failures
|
|
609
|
-
(`prompt build failed: …`, which includes media fetch/transcode
|
|
610
|
-
errors), non-numeric `logit_bias` keys/values (`logit_bias must be a
|
|
611
|
-
dict of int to float`, mlx-lm's coercion error), invalid logprobs params
|
|
612
|
-
(mlx-lm's exact validation: `logprobs must be of type bool`,
|
|
613
|
-
`top_logprobs must be of type int` / `at least 0` / `at most 11`;
|
|
614
|
-
`top_logprobs: -1` is the accepted "unset" sentinel), invalid
|
|
615
|
-
`reasoning_effort` (`reasoning_effort must be one of 'none', 'minimal',
|
|
616
|
-
'low', 'medium', 'high', 'xhigh'`).
|
|
617
|
-
- `400` with `"type": "memory_admission"`, `"code":
|
|
618
|
-
"context_over_budget"` — the PROMPT itself leaves no generation slot
|
|
619
|
-
within an explicitly configured context/budget limit (`src/serve/request-plan.ts`). A prompt that
|
|
620
|
-
fits is never rejected for a broad `max_tokens`: the upper bound is
|
|
621
|
-
capped to the remaining room (`max_tokens` is a ceiling, not a promise)
|
|
622
|
-
and generation proceeds. This is opt-in via an operator budget/context cap
|
|
623
|
-
(or the fixed GLM-5.2 layout), never enforced from default fit estimates.
|
|
624
|
-
`/stats.admission.enforced_context_tokens` is null when no cap applies.
|
|
625
|
-
- `429` with `"type": "resource_admission"`, `"code": "queue_full"` when
|
|
626
|
-
a preparation or generation queue has 64 waiting requests. If SSE headers
|
|
627
|
-
are already sent, the stream emits its terminal error instead.
|
|
628
|
-
- `500` — a generation failure after admission; the stack is logged
|
|
629
|
-
server-side, the body keeps only the message.
|
|
630
|
-
|
|
631
|
-
## POST /v1/completions (raw text completion)
|
|
632
|
-
|
|
633
|
-
`mlx_lm.server`'s text-completion endpoint: **no chat template** — the
|
|
634
|
-
`prompt` string is tokenized directly (the tokenizer's own BOS handling,
|
|
635
|
-
exactly mlx-lm's `tokenizer.encode(request.prompt)`) and the model
|
|
636
|
-
continues it. Same generation gateway, admission control, prompt cache,
|
|
637
|
-
and adapter selection as chat.
|
|
638
|
-
|
|
639
|
-
```jsonc
|
|
640
|
-
{
|
|
641
|
-
"prompt": "Once upon a time", // REQUIRED, non-empty string only (token
|
|
642
|
-
// arrays rejected, matching mlx_lm.server)
|
|
643
|
-
"max_tokens": 512, // unset = no cap: generate until EOS or the
|
|
644
|
-
// an explicit operator limit (DEVIATION: mlx_lm.server
|
|
645
|
-
// defaults 512 — pass --max-tokens 512 to
|
|
646
|
-
// reproduce); max_completion_tokens wins
|
|
647
|
-
"stream": false,
|
|
648
|
-
"stop": "\n\n", // string or array, decoded-text matching
|
|
649
|
-
// plus every sampling/penalty field from /v1/chat/completions:
|
|
650
|
-
// temperature, top_p, top_k, seed, min_p, xtc_probability,
|
|
651
|
-
// xtc_threshold, logit_bias, repetition_penalty,
|
|
652
|
-
// repetition_context_size, presence_penalty, presence_context_size,
|
|
653
|
-
// frequency_penalty, frequency_context_size, hlg, adapter,
|
|
654
|
-
// logprobs (BOOL, mlx-lm's type — not OpenAI's legacy int),
|
|
655
|
-
// top_logprobs (0-11 or -1) — same response block as chat, see
|
|
656
|
-
// "logprobs / top_logprobs" above (mlx-lm shares the builder),
|
|
657
|
-
// response_format / guided_grammar / guided_regex / guided_choice /
|
|
658
|
-
// structured_outputs — structured output, same semantics and
|
|
659
|
-
// precedence as chat (see "Structured output" above); the degrade
|
|
660
|
-
// path here emits the Warning header only (no template to inject a
|
|
661
|
-
// system prompt into)
|
|
662
|
-
}
|
|
663
|
-
```
|
|
664
|
-
|
|
665
|
-
Response is the OpenAI text-completion object:
|
|
666
|
-
|
|
667
|
-
```jsonc
|
|
668
|
-
{
|
|
669
|
-
"id": "cmpl-…", "object": "text_completion", "created": 1760000000,
|
|
670
|
-
"model": "<loaded model id>",
|
|
671
|
-
"choices": [{ "index": 0, "text": "…", "finish_reason": "stop" | "length",
|
|
672
|
-
"logprobs": { /* only when requested */ } }],
|
|
673
|
-
"usage": { "prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0,
|
|
674
|
-
"prompt_tokens_details": { "cached_tokens": 0 },
|
|
675
|
-
"lane": "serial" }
|
|
676
|
-
// + "speculation" under --draft-model, as on chat
|
|
677
|
-
}
|
|
678
|
-
```
|
|
679
|
-
|
|
680
|
-
Streaming (`"stream": true`) is SSE: `text_completion` chunks whose
|
|
681
|
-
`choices[0].text` carries the delta; the final chunk carries
|
|
682
|
-
`finish_reason` and `usage` (always attached — mlx-lm gates usage behind
|
|
683
|
-
`stream_options.include_usage`; ours is an additive superset), then
|
|
684
|
-
`data: [DONE]`. There is no `echo` parameter (mlx_lm.server has none).
|
|
685
|
-
`400` on a missing/empty/non-string `prompt`, plus the same logit_bias,
|
|
686
|
-
logprobs, and admission errors as chat.
|
|
687
|
-
|
|
688
|
-
## POST /v1/messages (Anthropic Messages API)
|
|
689
|
-
|
|
690
|
-
Anthropic-protocol surface over the same engine — on by default, like
|
|
691
|
-
`optiq serve`. Point any Anthropic-SDK tool at the server
|
|
692
|
-
(`ANTHROPIC_BASE_URL=http://localhost:8080`, any `x-api-key`) — Claude
|
|
693
|
-
Code works as a client this way. Translation lives in `src/anthropic.ts`;
|
|
694
|
-
the request funnels into the chat handler above.
|
|
695
|
-
|
|
696
|
-
- `system` (string or text blocks), `messages` with string or
|
|
697
|
-
content-block arrays; `tool_use` / `tool_result` blocks map to the
|
|
698
|
-
native tool-calling path; `image` blocks (base64 or url source) hit the
|
|
699
|
-
vision path on sidecar models, including image blocks nested inside a
|
|
700
|
-
`tool_result` (the computer-use shape). Other image source types are a
|
|
701
|
-
`400`. The protocol has no audio input block; audio-shaped blocks
|
|
702
|
-
(`input_audio` / `audio` / `audio_url`) are rejected with a `400` pointing
|
|
703
|
-
to `/v1/chat/completions`. Prior-turn `thinking` blocks are dropped on
|
|
704
|
-
re-ingest.
|
|
705
|
-
- `tools` (`{name, description, input_schema}`) map to function tools;
|
|
706
|
-
entries without `name` + `input_schema` (server-tool types such as
|
|
707
|
-
web_search) are dropped silently.
|
|
708
|
-
- `max_tokens`, `temperature`, `top_p`, `top_k`, `stop_sequences`,
|
|
709
|
-
`stream` as in the Anthropic spec. The mlx-lm sampler/penalty
|
|
710
|
-
extensions (`min_p`, `xtc_probability`, `xtc_threshold`,
|
|
711
|
-
`repetition_penalty`, `presence_penalty`, `frequency_penalty` + their
|
|
712
|
-
`*_context_size` windows) pass through as extras; `logit_bias` does
|
|
713
|
-
not exist in this protocol and is not accepted here.
|
|
714
|
-
- Response: `{id: "msg_…", type: "message", role: "assistant", content:
|
|
715
|
-
[{type: "text"} | {type: "tool_use"}…], stop_reason, stop_sequence,
|
|
716
|
-
usage: {input_tokens, output_tokens, cache_read_input_tokens?}}` —
|
|
717
|
-
`cache_read_input_tokens` is present when the prompt cache contributed.
|
|
718
|
-
- Streaming follows the Anthropic event grammar exactly:
|
|
719
|
-
`message_start → content_block_start/delta/stop (text_delta,
|
|
720
|
-
input_json_delta) → message_delta (stop_reason + usage) →
|
|
721
|
-
message_stop`. Errors are `event: error` frames.
|
|
722
|
-
- Errors: `{type: "error", error: {type: "invalid_request_error" |
|
|
723
|
-
"api_error", message}}` — the chat handler's `4xx`/`5xx` are re-wrapped
|
|
724
|
-
into this shape.
|
|
725
|
-
|
|
726
|
-
## POST /v1/responses (OpenAI Responses API)
|
|
727
|
-
|
|
728
|
-
Responses-protocol surface (Codex, Cursor, Continue, Cline, and the
|
|
729
|
-
OpenAI SDK speak this now). Oracle: optiq responses shim; port in
|
|
730
|
-
`src/responses.ts`.
|
|
731
|
-
|
|
732
|
-
- `input` (string or item array: `message`, `function_call`,
|
|
733
|
-
`function_call_output`), `instructions` (merged with any
|
|
734
|
-
system/developer items into one leading system message),
|
|
735
|
-
`max_output_tokens` (or `max_tokens`), `temperature`, `top_p`, `top_k`,
|
|
736
|
-
flat `tools`/`tool_choice` (built-in tool types — web_search, file_search,
|
|
737
|
-
mcp, computer_use — dropped silently; `{type:"function", name}` maps to
|
|
738
|
-
the chat object form, `"auto"`/`"none"`/`"required"` pass through),
|
|
739
|
-
`stream`. The mlx-lm sampler/penalty extensions (`min_p`,
|
|
740
|
-
`xtc_probability`, `xtc_threshold`, `logit_bias`, `repetition_penalty`,
|
|
741
|
-
`presence_penalty`, `frequency_penalty` + `*_context_size`) pass through
|
|
742
|
-
as extras.
|
|
743
|
-
- **`previous_response_id` resumption**: pass a prior response id
|
|
744
|
-
instead of resending the conversation; the server splices the stored
|
|
745
|
-
input + output back in (instructions carry forward when omitted;
|
|
746
|
-
reasoning items are not replayed). Store is per-process, 1 h TTL,
|
|
747
|
-
32 MiB byte-capped LRU — observable at `GET /stats` (`response_store`).
|
|
748
|
-
Unknown/expired id → `404`.
|
|
749
|
-
- Response: `{id: "resp_…", object: "response", status: "completed" |
|
|
750
|
-
"incomplete", output: [{type: "reasoning", summary: [{type:
|
|
751
|
-
"summary_text", text}]}?, {type: "message"}, {type: "function_call"}…],
|
|
752
|
-
usage: {input_tokens, output_tokens, total_tokens,
|
|
753
|
-
output_tokens_details: {reasoning_tokens}}, previous_response_id}`. A
|
|
754
|
-
`reasoning` item is present only when the model emitted a think channel.
|
|
755
|
-
- Streaming event chain: `response.created → response.in_progress →
|
|
756
|
-
response.output_item.added → response.content_part.added →
|
|
757
|
-
response.output_text.delta… → response.output_text.done →
|
|
758
|
-
response.content_part.done → response.output_item.done →
|
|
759
|
-
response.completed` (+ `response.function_call_arguments.delta/.done`
|
|
760
|
-
for tool calls; a reasoning output item precedes the message when the
|
|
761
|
-
model thinks).
|
|
762
|
-
- Errors: `{error: {message, type: "invalid_request_error" |
|
|
763
|
-
"server_error", param: null, code: null}}`.
|
|
764
|
-
|
|
765
|
-
## POST /v1/embeddings (OpenAI Embeddings API)
|
|
766
|
-
|
|
767
|
-
Text embeddings, available when the **served model is an embedding model**
|
|
768
|
-
(plain Qwen3 / Qwen3-Embedding) — consistent with the single-model server:
|
|
769
|
-
`mlx-bun serve mlx-community/Qwen3-Embedding-4B-4bit-DWQ`, then POST here. On
|
|
770
|
-
any other served model the route returns `400 invalid_request_error`
|
|
771
|
-
(`src/serve/model-admin-routes.ts`).
|
|
772
|
-
|
|
773
|
-
- `input` (string or array of strings; anything else is a `400`), `model`
|
|
774
|
-
(ignored — the served model is used), and a **non-standard**
|
|
775
|
-
`instruction` (optional): applies Qwen3-Embedding's query format
|
|
776
|
-
`Instruct: {instruction}\nQuery:{text}`, which steers *which* similarity
|
|
777
|
-
axis the geometry reflects. Omit it for plain document embeddings.
|
|
778
|
-
- Pooling is last-token + L2-normalization (the vectors are unit-length);
|
|
779
|
-
similarity is the dot product. Bit-exact vs the mlx-lm `qwen3` reference
|
|
780
|
-
(`tests/parity/qwen3-embed-parity.test.ts`).
|
|
781
|
-
- Response: `{ "object": "list", "data": [{ "object": "embedding", "index":
|
|
782
|
-
0, "embedding": [float…] }, …], "model": "<served id>", "usage": {
|
|
783
|
-
"prompt_tokens": N, "total_tokens": N } }`. Embedding is a single forward
|
|
784
|
-
(no decode loop), so it runs inline — not through the generation gateway.
|
|
785
|
-
|
|
786
|
-
```sh
|
|
787
|
-
curl localhost:8080/v1/embeddings -H 'content-type: application/json' \
|
|
788
|
-
-d '{"input": ["the cat sat on the mat", "a kitten by the window"]}'
|
|
789
|
-
```
|
|
790
|
-
|
|
791
|
-
## POST /v1/audio/transcriptions · POST /v1/audio/translations (speech-to-text)
|
|
792
|
-
|
|
793
|
-
OpenAI-compatible Whisper transcription (`translations` = the same route with
|
|
794
|
-
task `translate`, English output). Served by every `mlx-bun serve` beside the
|
|
795
|
-
chat model — the Whisper checkpoint (`--whisper-model`, default: the first
|
|
796
|
-
downloaded `whisper` model) loads for each take and is released right after
|
|
797
|
-
it unless `--whisper-idle-unload`/`--whisper-resident` say otherwise — and by the transcription-only server that
|
|
798
|
-
`serve <whisper checkpoint>` starts ([server-config.md](server-config.md)).
|
|
799
|
-
Model support and the parity contract: [models.md](models.md#supported-models);
|
|
800
|
-
CLI equivalent: [`mlx-bun transcribe`](cli.md#transcribe--speech-to-text).
|
|
801
|
-
|
|
802
|
-
**Request** — `multipart/form-data` (OpenAI's shape) or JSON:
|
|
803
|
-
|
|
804
|
-
| Field | Meaning |
|
|
805
|
-
| --- | --- |
|
|
806
|
-
| `file` | audio upload (multipart) or base64 / `data:` URL string (JSON; `input_audio.data` and `audio` are accepted aliases). WAV decodes through the exact PCM parser; any other CoreAudio-readable container (mp3/m4a/aac/flac/ogg/aiff/caf, mp4/mov audio tracks…) decodes in-process through AudioToolbox (`src/audio/audiotoolbox.ts`, 3.7 ms for an 11 s mp3). Undecodable bytes are a `400`. |
|
|
807
|
-
| `model` | ignored — the configured Whisper checkpoint is used |
|
|
808
|
-
| `language` | ISO code or name; omitted / `auto` detects from the first 30 s |
|
|
809
|
-
| `prompt` | initial prompt (vocabulary, style); the last 223 tokens are kept |
|
|
810
|
-
| `response_format` | `json` (default) · `verbose_json` · `text` · `srt` · `vtt` |
|
|
811
|
-
| `temperature` | one temperature in [0, 1]; omitted = mlx-whisper's `(0, 0.2, …, 1.0)` fallback ladder |
|
|
812
|
-
| `stream` | `true` → server-sent events (below) |
|
|
813
|
-
| `timestamp_granularities[]` | `segment` (default); `word` adds `words` (`word, start, end, probability`) to `verbose_json` segments via cross-attention DTW on the checkpoint's alignment heads — one extra decoder pass per window |
|
|
814
|
-
| `beam_size` | **non-standard**: beam search width (default greedy); requires temperature 0 |
|
|
815
|
-
| `vocabulary` | **non-standard**: array (JSON) or comma-separated (multipart) recognition hints, fitted whole-term into the 223-token prompt budget after `prompt`; usage is reported |
|
|
816
|
-
| `condition_on_previous_text` | **non-standard**, default `true` |
|
|
817
|
-
| `no_speech_threshold`, `without_timestamps` | **non-standard** mlx-whisper decode options |
|
|
818
|
-
| `vad`, `vad_threshold`, `vad_min_speech_ms`, `vad_trim` | **non-standard**: Silero VAD gate (v6.2, `ggml-org/whisper-vad` weights in the HF cache). No detected speech → `{"text": ""}` with `mlx_bun.vad.speech = false` and Whisper never runs (≈25 ms for 11 s). `vad_trim` crops to the speech span ± 0.5 s first; timestamps stay clip-relative. Segments are reported in `mlx_bun.vad.segments` (seconds). Defaults: threshold 0.5, min speech 250 ms |
|
|
819
|
-
| `faithful` | **non-standard**: `true` runs the oracle-parity graph instead of the fast path (fused attention, compiled decoder step); same tokens up to documented near-ties, slower |
|
|
820
|
-
| `audio_ctx` | **non-standard, Lab**: encode only the first n of 1500 encoder positions (whisper.cpp `-ac`). Cheaper for short clips; measured hallucinated repeats below ~1024 on the fixture clips — off unless set |
|
|
821
|
-
|
|
822
|
-
**Response** — `json`: `{ "text": "…", "mlx_bun": { "model", "language",
|
|
823
|
-
"duration", "timings": { "load_ms", "transcribe_ms", "total_ms" },
|
|
824
|
-
"vocabulary"?: { "included", "omitted", "token_count", "token_budget" } } }`.
|
|
825
|
-
`verbose_json` adds `task`, `language`, `duration` and openai-whisper's
|
|
826
|
-
`segments` (`id, seek, start, end, text, tokens, temperature, avg_logprob,
|
|
827
|
-
compression_ratio, no_speech_prob`). `text`/`srt`/`vtt` return the bare body
|
|
828
|
-
(`text/plain`, `application/x-subrip`, `text/vtt`) with `x-mlx-bun-language`
|
|
829
|
-
and `x-mlx-bun-timings` headers. `load_ms` is non-zero exactly when the
|
|
830
|
-
request paged the weights in.
|
|
831
|
-
|
|
832
|
-
**Streaming** (`stream=true`): `text/event-stream` with `transcript.text.delta`
|
|
833
|
-
`{ "delta": "<segment text>", "segment": { "id", "start", "end" } }` per
|
|
834
|
-
finalized segment, `transcript.progress` `{ "done", "total" }` (mel frames),
|
|
835
|
-
and a final `transcript.text.done` carrying the non-streaming body.
|
|
836
|
-
|
|
837
|
-
**Errors**: `400 invalid_request_error` (fields, undecodable audio, clips
|
|
838
|
-
shorter than 0.1 s), `415` (content type), `499` on client cancel, and
|
|
839
|
-
`503 model_unavailable` when no Whisper checkpoint is on disk (the message
|
|
840
|
-
carries the `mlx-bun get` command).
|
|
841
|
-
|
|
842
|
-
`POST /admin/transcription/unload` pages the weights out now and returns
|
|
843
|
-
`{ "unloaded": bool, "resident", "loads", "unloads", "requests",
|
|
844
|
-
"last_load_ms", "idle_unload_sec" }` — the same block `/health` and
|
|
845
|
-
`/stats` report as `transcription` on the transcription-only server.
|
|
846
|
-
Transcriptions run one at a time and never overlap chat generation on the
|
|
847
|
-
GPU (the generation gateway's exclusive lock).
|
|
848
|
-
|
|
849
|
-
```sh
|
|
850
|
-
curl localhost:8080/v1/audio/transcriptions -F file=@meeting.m4a -F language=en -F response_format=srt
|
|
851
|
-
curl localhost:8080/v1/audio/transcriptions -H 'content-type: application/json' \
|
|
852
|
-
-d '{"file":"<base64 wav>","beam_size":5,"vocabulary":["Sotto","SwiftUI","Metal"]}'
|
|
853
|
-
```
|
|
854
|
-
|
|
855
|
-
## Streaming transcription sessions (`/v1/audio/sessions`)
|
|
856
|
-
|
|
857
|
-
For audio that is being recorded while it is sent (dictation). Every
|
|
858
|
-
completed 30 s window is transcribed as soon as it exists, under the same
|
|
859
|
-
exclusive lock as everything else, so `finish` costs one window regardless
|
|
860
|
-
of take length (measured: a 62 s take that needs 1165 ms in batch returns
|
|
861
|
-
327 ms after its last chunk, identical text — M1 Max). With `vad: true` the
|
|
862
|
-
Silero gate runs on the stream: nothing is transcribed until speech has
|
|
863
|
-
been seen, and a take with no speech finishes as `{"text": ""}` without
|
|
864
|
-
ever running Whisper. The one-shot parameters (`language`, `prompt`,
|
|
865
|
-
`vocabulary`, `beam_size`, `temperature`, `vad*`, `timestamp_granularities`,
|
|
866
|
-
`faithful`) apply; `response_format` is `json` or `verbose_json`.
|
|
867
|
-
|
|
868
|
-
| Route | Body / response |
|
|
869
|
-
| --- | --- |
|
|
870
|
-
| `POST /v1/audio/sessions` | JSON params → `{ "id", "model", "vocabulary" }` (the weights load here if they were paged out) |
|
|
871
|
-
| `POST /v1/audio/sessions/<id>/audio` | one chunk: `content-type: audio/pcm;rate=16000` raw little-endian float32 mono (sotto's format, no decode), or any CoreAudio container. → `{ "samples", "duration", "speech", "segments": [...] }` with every segment finalized so far |
|
|
872
|
-
| `POST /v1/audio/sessions/<id>/finish` | → the one-shot response body (`json`/`verbose_json` with `mlx_bun`), closes the session |
|
|
873
|
-
| `DELETE /v1/audio/sessions/<id>` | discard, `204` |
|
|
874
|
-
|
|
875
|
-
Chunk order is the caller's responsibility (send sequentially and await
|
|
876
|
-
each receipt). Unknown ids are `404`; a finished session is `409`; at most
|
|
877
|
-
64 sessions are open at once (`429`). Windows fed early are normalized
|
|
878
|
-
against the audio received so far (the log-mel clamp is relative to the
|
|
879
|
-
clip maximum), which can differ from a batch transcription in near-silent
|
|
880
|
-
mel bins — the fixture takes decode identically.
|
|
881
|
-
|
|
882
|
-
## GET /v1
|
|
883
|
-
|
|
884
|
-
API index for discovery tooling:
|
|
885
|
-
|
|
886
|
-
```jsonc
|
|
887
|
-
{ "name": "mlx-bun", "version": "<package version>", "model": "<served model id>",
|
|
888
|
-
"endpoints": ["POST /v1/chat/completions", "POST /v1/completions", "POST /v1/messages",
|
|
889
|
-
"POST /v1/responses", "POST /v1/embeddings", "POST /v1/audio/transcriptions",
|
|
890
|
-
"POST /v1/audio/translations", "GET /v1/models",
|
|
891
|
-
"GET/POST/DELETE /v1/adapters", "GET /health", "GET /stats", "GET /fit",
|
|
892
|
-
"GET /library", "GET /downloads"] }
|
|
893
|
-
```
|
|
894
|
-
|
|
895
|
-
(The list is a curated subset; this document is the full inventory.)
|
|
896
|
-
|
|
897
|
-
## GET /v1/models
|
|
898
|
-
|
|
899
|
-
`{ "object": "list", "data": [{ "id": "<model id>", "object": "model",
|
|
900
|
-
"created": <unix s>, … }] }` — `created` is the server start time for every
|
|
901
|
-
row.
|
|
902
|
-
|
|
903
|
-
The served model is FIRST, with extra capability fields:
|
|
904
|
-
|
|
905
|
-
```jsonc
|
|
906
|
-
{
|
|
907
|
-
"id": "<served model id>", "object": "model", "created": 1760000000,
|
|
908
|
-
"owned_by": "mlx-bun",
|
|
909
|
-
"context_window": 32768,
|
|
910
|
-
"reasoning": false, // switchable thinking channel (gates the
|
|
911
|
-
// web chat's thinking toggle)
|
|
912
|
-
"vision": false, // image input accepted (tower loaded or
|
|
913
|
-
// lazily loadable)
|
|
914
|
-
"audio": false, // input_audio content parts accepted
|
|
915
|
-
// (tower loaded or lazily loadable)
|
|
916
|
-
"batch_mode": "batch", // loaded cache capability; per-request
|
|
917
|
-
// usage.lane is authoritative
|
|
918
|
-
"tools": true,
|
|
919
|
-
"structured_output": true,
|
|
920
|
-
"embeddings": false,
|
|
921
|
-
"adapters": false,
|
|
922
|
-
"training": false,
|
|
923
|
-
"dsa": true, // GLM-5.2 only; false elsewhere
|
|
924
|
-
"mtp": true, // checkpoint-native GLM MTP is mounted
|
|
925
|
-
"capabilities": { // explicit discoverable surface
|
|
926
|
-
"chat_completions": true, "text_completions": true,
|
|
927
|
-
"anthropic_messages": true, "responses": true,
|
|
928
|
-
"streaming": true, "tools": true, "structured_output": true,
|
|
929
|
-
"logprobs": true, "embeddings": false,
|
|
930
|
-
"vision": false, "audio": false,
|
|
931
|
-
"adapters": false, "training": false
|
|
932
|
-
},
|
|
933
|
-
"gen_defaults": { // model-author sampling defaults
|
|
934
|
-
"temperature": 0.7, // (generation_config.json, with any
|
|
935
|
-
"top_p": 0.95, // --temperature/--top-p/--top-k server
|
|
936
|
-
"top_k": 0 // override applied); null when neither
|
|
937
|
-
// the model nor the server set one
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
```
|
|
941
|
-
|
|
942
|
-
Followed by every other servable model the local registry knows (mlx-lm
|
|
943
|
-
scans the HF cache here; the registry is that scan, filtered to supported
|
|
944
|
-
architectures, one row per repo via `listCanonical()` — duplicate snapshots
|
|
945
|
-
from upstream re-pushes are not separate entries):
|
|
946
|
-
|
|
947
|
-
```jsonc
|
|
948
|
-
{ "id": "<repo id>", "object": "model", "created": 1760000000,
|
|
949
|
-
"vision": false, // visionCapable(): sidecar OR encoder-free
|
|
950
|
-
// unified vision config
|
|
951
|
-
"tier": "targeted" | "generic" // supportTier(): dedicated/generated forward
|
|
952
|
-
// ("targeted") vs the Tier-0 universal
|
|
953
|
-
// module ("generic"); entries with no tier
|
|
954
|
-
// (unsupported) are filtered out entirely
|
|
955
|
-
}
|
|
956
|
-
```
|
|
957
|
-
|
|
958
|
-
`GET /v1/models/<id>` filters the list to that id — same list shape,
|
|
959
|
-
matching `mlx_lm.server`.
|
|
960
|
-
|
|
961
|
-
`vision` / `audio` say whether the served model accepts `image_url` /
|
|
962
|
-
`input_audio` content parts (tower loaded or lazily loadable — see
|
|
963
|
-
*Audio input* above for what audio requires), so clients can discover
|
|
964
|
-
capabilities instead of probing for a `400`.
|
|
965
|
-
The served-model row also includes `batch_mode: "off" | "serial" | "batch"`:
|
|
966
|
-
the configured cap and the loaded model's cache capability together. Per-turn
|
|
967
|
-
`usage.lane` remains authoritative for a particular request; for example,
|
|
968
|
-
native-MTP requests route serial even when the served GLM model reports
|
|
969
|
-
`batch_mode: "batch"` for ordinary requests.
|
|
970
|
-
For GLM-5.2, the non-generative fields are deliberately false: embeddings,
|
|
971
|
-
vision/audio, adapters, and training are not emulated by the serving port.
|
|
972
|
-
|
|
973
|
-
## GET /health
|
|
974
|
-
|
|
975
|
-
`{"status": "ok"}` — byte-for-byte what `mlx_lm.server` returns.
|
|
976
|
-
|
|
977
|
-
## GET /stats
|
|
978
|
-
|
|
979
|
-
```jsonc
|
|
980
|
-
{
|
|
981
|
-
"server": { "owner": "serve" | "pi-session" | "embedded", "model": "...", "started_at": 0 },
|
|
982
|
-
"prompt_cache": { "entries": 0, "bytes": 0, "max_bytes": 0, "hits": 0, "misses": 0, "object_hits": 0, "object_misses": 0, "object_restores": 0 },
|
|
983
|
-
"response_store": { "entries": 0, "bytes": 0, "max_bytes": 33554432, "ttl_ms": 3600000 },
|
|
984
|
-
"kv_quant": { "mode": "bf16" | "uniform-kv8" | "turbo k8v3" | "mixed (kv_config.json)",
|
|
985
|
-
"layers": { "kv4": 8, "bf16": 40 }, // turbo: { "turbo-k8v3": 8, "bf16": 40 }
|
|
986
|
-
"attention": { "global": 10, "sliding_window": 38 }, "recurrent_layers": 0 },
|
|
987
|
-
// present only when --ssd-cache is on:
|
|
988
|
-
"ssd_cache": { "dir": "...", "entries": 0, "bytes": 0, "max_bytes": 0,
|
|
989
|
-
"restores": 0, "spills": 0, "restore_ms_last": 0, "demotions": 0,
|
|
990
|
-
// bounded write-behind queue (MLX_BUN_SSD_SPILL_QUEUE_GB):
|
|
991
|
-
"pending_snapshots": 0, "pending_spills": 0,
|
|
992
|
-
"pending_spill_bytes": 0, "dropped_spills": 0,
|
|
993
|
-
"failed_spills": 0, "longest_durable_prefix_tokens": 0 },
|
|
994
|
-
"admission": {
|
|
995
|
-
"max_safe_context": 0, // advisory fit estimate, tokens
|
|
996
|
-
"enforced_context_tokens": null, // explicit cap or fixed layout; null = none
|
|
997
|
-
"memory_budget_bytes": null, // explicit budget, or null
|
|
998
|
-
"usable_bytes": 0,
|
|
999
|
-
"weights_bytes": 0
|
|
1000
|
-
},
|
|
1001
|
-
// present only for the direct Colibri GLM-5.2 runtime:
|
|
1002
|
-
"glm52": {
|
|
1003
|
-
"preset": "g5-32gb-quality",
|
|
1004
|
-
"planned_process_bytes": 0,
|
|
1005
|
-
"process_limit_bytes": 0,
|
|
1006
|
-
"context_tokens": 4096,
|
|
1007
|
-
"max_generation_tokens": 128,
|
|
1008
|
-
"batch_size": 1,
|
|
1009
|
-
"dsa": true,
|
|
1010
|
-
"mtp": true,
|
|
1011
|
-
"mtp_draft_tokens": 3,
|
|
1012
|
-
"resident_weight_bytes": 0,
|
|
1013
|
-
"main_expert_slab_bytes": 0,
|
|
1014
|
-
"mtp_expert_slab_bytes": 0,
|
|
1015
|
-
"expert_runtime": { // null when the expert runtime is off
|
|
1016
|
-
"main_residency": { "resident": 0, "pinned": 0, "working": 0,
|
|
1017
|
-
"hits": 0, "misses": 0, "evictions": 0 },
|
|
1018
|
-
"mtp_residency": { /* same shape */ } | null,
|
|
1019
|
-
"last_turn": { "main": { "demand": { "hits": 0, "misses": 0, "readBytes": 0 } },
|
|
1020
|
-
"mtp": { "demand": { "hits": 0, "misses": 0, "readBytes": 0 } },
|
|
1021
|
-
"pilot": null },
|
|
1022
|
-
"last_repin": []
|
|
1023
|
-
}
|
|
1024
|
-
},
|
|
1025
|
-
"batch": {
|
|
1026
|
-
"configured": 1, // the --batch N value
|
|
1027
|
-
"mode": "off", // "off" | "serial" | "batch": configured
|
|
1028
|
-
// and model-cache capability together
|
|
1029
|
-
"batched": false, // true only when mode == "batch"
|
|
1030
|
-
"active_rows": 0, // rows currently decoding in the batch
|
|
1031
|
-
"pending_rows": 0, // queued + mid-prefill rows waiting
|
|
1032
|
-
"submitted_rows": 0, // cumulative rows routed to the batch lane since start
|
|
1033
|
-
"kv_bytes": 0, // projected aggregate KV bytes of admitted rows
|
|
1034
|
-
"kv_budget_bytes": null // --kv-budget cap, or null (uncapped)
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
```
|
|
1038
|
-
|
|
1039
|
-
`kv_quant.mode` reflects the `--kv-quant` flag the server was started
|
|
1040
|
-
with; the default is `bf16` (no KV quantization). `mixed (kv_config.json)`
|
|
1041
|
-
appears only when `--kv-quant config` was passed explicitly and the
|
|
1042
|
-
checkpoint ships a per-layer `kv_config.json` — per-layer KV quantization
|
|
1043
|
-
is never selected automatically.
|
|
1044
|
-
|
|
1045
|
-
`kv_quant.layers` counts configured attention KV codecs; `attention` separates
|
|
1046
|
-
full and sliding attention. `recurrent_layers` counts linear/recurrent layers
|
|
1047
|
-
whose convolution and recurrent state retain their own precision. They do not
|
|
1048
|
-
count as bf16 or quantized attention KV. These are model/policy counts, not
|
|
1049
|
-
live allocation measurements or a per-request conversion-progress report.
|
|
1050
|
-
|
|
1051
|
-
`pending_snapshots` counts prompt-cache entries scheduled for persistence but
|
|
1052
|
-
not yet confirmed by the atomic SSD store. `longest_durable_prefix_tokens` is
|
|
1053
|
-
the longest prefix already present in the SSD index, not merely queued.
|
|
1054
|
-
|
|
1055
|
-
## GET /fit
|
|
1056
|
-
|
|
1057
|
-
Fit assessment for the loaded model on this machine, plus a capability
|
|
1058
|
-
matrix across Apple SKUs. Used by the status page. Experts bytes come
|
|
1059
|
-
from the registry so MoE active-parameter predictions match the serve
|
|
1060
|
-
banner and `mlx-bun fit`. When the eval DB has a real measured decode
|
|
1061
|
-
rate for this model snapshot, it is included and takes precedence over
|
|
1062
|
-
the prediction.
|
|
1063
|
-
|
|
1064
|
-
For the direct Colibri GLM-5.2 runtime this endpoint switches to the exact
|
|
1065
|
-
serve-time memory plan. The response separates the full streamed artifact on
|
|
1066
|
-
disk from resident weights, main/MTP expert slabs, KV, and other reserves.
|
|
1067
|
-
`measured_decode_tps` is the G5 warm quality-preserving measurement;
|
|
1068
|
-
`glm52.direct_oracle_warm_decode_tps` is the same-machine direct Colibri
|
|
1069
|
-
control, while `glm52.aspirational_decode_tps` is a target, not a release
|
|
1070
|
-
gate. The single-row SKU result describes the current machine because the
|
|
1071
|
-
generic all-resident SKU estimator is inapplicable
|
|
1072
|
-
(`report.predicted_decode_tps` is `null` there).
|
|
1073
|
-
|
|
1074
|
-
```jsonc
|
|
1075
|
-
{
|
|
1076
|
-
"machine": { "chip": "M4 Pro", "ram_bytes": 0, "bandwidth_gbs": 0.0 },
|
|
1077
|
-
"context_tokens": 8192, // estimated context capacity
|
|
1078
|
-
"typical_context_tokens": 8192, // min(8192, context_tokens)
|
|
1079
|
-
"typical_decode_tps": 0.0, // predicted at typical_context_tokens
|
|
1080
|
-
"measured_decode_tps": null, // real number from eval DB, or null
|
|
1081
|
-
"measured_at": null, // unix ms of measurement, or null
|
|
1082
|
-
"report": {
|
|
1083
|
-
"fits": true,
|
|
1084
|
-
"weights_bytes": 0,
|
|
1085
|
-
"kv_bytes": 0,
|
|
1086
|
-
"transient_bytes": 0,
|
|
1087
|
-
"total_bytes": 0,
|
|
1088
|
-
"usable_bytes": 0,
|
|
1089
|
-
"max_safe_context": 8192,
|
|
1090
|
-
"predicted_decode_tps": 0.0
|
|
1091
|
-
},
|
|
1092
|
-
// GLM-5.2 only:
|
|
1093
|
-
"glm52": {
|
|
1094
|
-
"artifact_disk_bytes": 0, // exact registry size; null if unavailable
|
|
1095
|
-
"main_expert_slab_bytes": 0,
|
|
1096
|
-
"mtp_expert_slab_bytes": 0,
|
|
1097
|
-
"max_generation_tokens": 128,
|
|
1098
|
-
"direct_oracle_warm_decode_tps": 0.27,
|
|
1099
|
-
"aspirational_decode_tps": 2.0
|
|
1100
|
-
},
|
|
1101
|
-
"sku_matrix_ctx": 32768,
|
|
1102
|
-
"sku_matrix": [{
|
|
1103
|
-
"sku": "M4 Pro 24 GB", "ram_gb": 24,
|
|
1104
|
-
"fits": true, "max_context": 32768, "decode_tps": 0.0
|
|
1105
|
-
}]
|
|
1106
|
-
}
|
|
1107
|
-
```
|
|
1108
|
-
|
|
1109
|
-
## Adapters (LoRA hot-swap)
|
|
1110
|
-
|
|
1111
|
-
Routes in `src/serve/model-admin-routes.ts`; the manager is
|
|
1112
|
-
`AdapterManager` in `src/lora.ts`. Requests selecting the same ordered adapter
|
|
1113
|
-
set can share a continuous group. Different sets wait for the current group
|
|
1114
|
-
to finish and then use the same executor. Prefix-cache identity is resolved
|
|
1115
|
-
under the execution lease, so replacing an adapter while a request is queued
|
|
1116
|
-
cannot reuse state from its previous weights.
|
|
1117
|
-
|
|
1118
|
-
- `GET /v1/adapters` — `{ adapters: [{ id, path, rank, scale, size_bytes,
|
|
1119
|
-
mounted_layers, ram_bytes }] }` — currently-mounted adapters only.
|
|
1120
|
-
`ram_bytes` is the actual resident size of the adapter's mounted
|
|
1121
|
-
`lora_a`/`lora_b` arrays (summed `MlxArray.nbytes` — real RAM cost while
|
|
1122
|
-
mounted, not a guess from the on-disk file size).
|
|
1123
|
-
- `GET /v1/adapters/available` — `{ adapters: [{ id, path, rank, scale,
|
|
1124
|
-
base_model, mounted, compatible }] }` — every adapter found on disk
|
|
1125
|
-
(`~/.cache/mlx-bun-finetunes`, `~/.cache/mlx-bun/adapters`), unfiltered.
|
|
1126
|
-
`compatible` is true when the adapter's recorded base model matches the
|
|
1127
|
-
currently-served model (compared by bare repo name) or the adapter
|
|
1128
|
-
recorded no base at all; `mounted` is true when it's already loaded.
|
|
1129
|
-
The web chat's adapter chip uses `compatible` to gray out entries it
|
|
1130
|
-
won't let you select rather than hiding them.
|
|
1131
|
-
- `POST /v1/adapters` — `{ "id": "...", "path": "/dir" }` (both required,
|
|
1132
|
-
else `400 "id and path required"`); mounts through the generation queue
|
|
1133
|
-
(never races a forward pass). Response:
|
|
1134
|
-
`{ id, mounted_layers, rank, scale, ram_bytes }`. `400` on shape/compat
|
|
1135
|
-
mismatch — validation is all-or-nothing.
|
|
1136
|
-
- `DELETE /v1/adapters/<id>` — unmount; `{ id, removed_layers }` on
|
|
1137
|
-
success, `404 {"error":{"message":"adapter <id> not mounted"}}` otherwise.
|
|
1138
|
-
|
|
1139
|
-
Select per request with the `adapter` body field (chat, text completions;
|
|
1140
|
-
the Anthropic and Responses surfaces pass it through as an extra), which
|
|
1141
|
-
also accepts a composed spec — `"a+b"` (or `"a,b"`) stacks two mounted
|
|
1142
|
-
adapters, their LoRA residuals summed in order
|
|
1143
|
-
(`AdapterManager.resolveSpec` / `parseAdapterSpec` in `src/lora.ts`);
|
|
1144
|
-
every named id must already be mounted or the request 400s with the
|
|
1145
|
-
unknown id named. Prompt-cache entries are namespaced per adapter spec
|
|
1146
|
-
(including composed ones), so switching adapters — or switching which
|
|
1147
|
-
ones are stacked — never reuses another combination's KV.
|
|
1148
|
-
|
|
1149
|
-
`serve --adapter <dir>` (alias `--adapter-path`, mlx_lm.server's
|
|
1150
|
-
spelling) mounts an adapter at startup through this same machinery and
|
|
1151
|
-
makes it the default for requests that send no `adapter` field; an
|
|
1152
|
-
explicit `adapter` (including `"none"`) always wins, and hot-swap via
|
|
1153
|
-
these endpoints is unchanged.
|
|
1154
|
-
|
|
1155
|
-
## Cache session affinity
|
|
1156
|
-
|
|
1157
|
-
Completion requests may supply `session_id` or `prompt_cache_key` in the JSON
|
|
1158
|
-
body, or `x-session-affinity` / `session_id` in a header. Precedence is body
|
|
1159
|
-
`session_id`, body `prompt_cache_key`, `x-session-affinity`, then `session_id`.
|
|
1160
|
-
This applies to chat completions, text completions, Responses and Messages.
|
|
1161
|
-
The shared mlx-bun Pi provider sends session-affinity headers automatically.
|
|
1162
|
-
|
|
1163
|
-
A session indexes its latest published checkpoint within each numerical cache
|
|
1164
|
-
namespace. Compatible continuations use that checkpoint directly; edited,
|
|
1165
|
-
branched or missing histories use ordinary prefix matching. The full intended
|
|
1166
|
-
input is still required: a session label does not append omitted messages,
|
|
1167
|
-
isolate access, change model settings or bypass token compatibility.
|
|
1168
|
-
|
|
1169
|
-
The cache prefers session-referenced checkpoints when choosing RAM residents,
|
|
1170
|
-
but can evict them when required. SSD persistence remains independent. Session
|
|
1171
|
-
indexes are process-local; after restart, the first request uses ordinary SSD
|
|
1172
|
-
lookup and re-establishes the association. Entries remain shareable across
|
|
1173
|
-
sessions and requests without session metadata. `MLX_BUN_SESSION_CACHE` provides
|
|
1174
|
-
the A/B control described in server-config.md.
|
|
1175
|
-
|
|
1176
|
-
`GET /stats` adds `prompt_cache.session_hits`, `session_misses` and
|
|
1177
|
-
`prefix_scans`. A session hit counts direct RAM checkpoint selection, including
|
|
1178
|
-
state prepared from SSD before execution. A session miss counts execution
|
|
1179
|
-
lookups with session metadata that need ordinary candidate search. Prefix scans
|
|
1180
|
-
count ordinary execution searches, not tokenization or preparation probes.
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
Qwen image/video encoder reuse reports `prompt_cache.object_hits`,
|
|
1184
|
-
`object_misses` and `object_restores`. A hit borrows an exact RAM tensor object;
|
|
1185
|
-
a miss may restore from SSD or recompute. Object entries and bytes are included
|
|
1186
|
-
in the existing cache totals and share its RAM budget and persistence queue.
|
|
1187
|
-
These counters do not count language-model KV prefix hits. Media prompt KV
|
|
1188
|
-
reuse remains disabled because token IDs alone do not identify media contents.
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
## POST /admin/cache/session/close
|
|
1192
|
-
|
|
1193
|
-
Send `{ "session_id": "agent-42" }` to remove that session's cache association
|
|
1194
|
-
and retention preference. Returns `{ "closed": true }` when an ID is supplied,
|
|
1195
|
-
or `{ "closed": false }` otherwise. Closing does not delete checkpoints from
|
|
1196
|
-
RAM or SSD. Applications can close idle or completed sessions; a later request
|
|
1197
|
-
with the same ID establishes a new association.
|
|
1198
|
-
|
|
1199
|
-
## POST /admin/cache/flush
|
|
1200
|
-
|
|
1201
|
-
Forces every pending SSD prompt-cache snapshot through the serial write queue
|
|
1202
|
-
and waits for the atomic temp-file/fsync/rename boundary. The response is `200`
|
|
1203
|
-
only when no snapshot or spill remains pending and no write failed during this
|
|
1204
|
-
flush; otherwise it is `503` with the counters that explain why.
|
|
1205
|
-
A snapshot missing from both RAM and usable SSD coverage remains pending
|
|
1206
|
-
across repeated flushes. Retrying does not turn a missing snapshot into a
|
|
1207
|
-
successful durability result. Already-durable prefixes avoid another clone
|
|
1208
|
-
and write.
|
|
1209
|
-
|
|
1210
|
-
```jsonc
|
|
1211
|
-
{
|
|
1212
|
-
"durable": true,
|
|
1213
|
-
"flushedSnapshots": 1,
|
|
1214
|
-
"missingSnapshots": 0,
|
|
1215
|
-
"pendingSnapshots": 0,
|
|
1216
|
-
"pendingSpills": 0,
|
|
1217
|
-
"pendingSpillBytes": 0,
|
|
1218
|
-
"droppedSpills": 0,
|
|
1219
|
-
"failedSpills": 0,
|
|
1220
|
-
"elapsedMs": 842,
|
|
1221
|
-
"entries": 2,
|
|
1222
|
-
"longest_durable_prefix_tokens": 16384
|
|
1223
|
-
}
|
|
1224
|
-
```
|
|
1225
|
-
|
|
1226
|
-
The standard benchmark calls this endpoint before its restart leg and records
|
|
1227
|
-
the result. Normal `mlx-bun serve` shutdown on `SIGINT` or `SIGTERM` performs
|
|
1228
|
-
the same flush after active requests drain, with a 120-second default timeout.
|
|
1229
|
-
Without `--ssd-cache` the route still answers (`entries: 0`).
|
|
1230
|
-
|
|
1231
|
-
## GET /library
|
|
1232
|
-
|
|
1233
|
-
`?refresh=1` bypasses the short response cache, including after a parent-managed job finishes.
|
|
1234
|
-
|
|
1235
|
-
Returns all models found in the local HuggingFace hub cache (via the
|
|
1236
|
-
registry scan), each annotated with a fit assessment for this machine.
|
|
1237
|
-
Response is cached for 30 seconds (registry scan + config reads; no
|
|
1238
|
-
tensor bytes are read). Handler: `src/serve/discovery-routes.ts`.
|
|
1239
|
-
|
|
1240
|
-
```jsonc
|
|
1241
|
-
{
|
|
1242
|
-
"models": [{
|
|
1243
|
-
"repo_id": "…",
|
|
1244
|
-
"model_type": "gemma3" | "minicpm5" | "qwen3" | …,
|
|
1245
|
-
"size_bytes": 0,
|
|
1246
|
-
"quant_bits": 4,
|
|
1247
|
-
"vision": false, // visionCapable(): sidecar OR encoder-free
|
|
1248
|
-
// unified vision config (not sidecar-only)
|
|
1249
|
-
"audio": false, // audio-capable (audio_config + sidecar audio-tower tensors)
|
|
1250
|
-
"supported": true, // supportTier() !== null (recognized family)
|
|
1251
|
-
"support_tier": "targeted" | "generic" | null, // dedicated/generated
|
|
1252
|
-
// forward vs the Tier-0 universal module;
|
|
1253
|
-
// null only when supported is false
|
|
1254
|
-
"serving": false, // currently loaded in this server
|
|
1255
|
-
"assessment": { // null if config unreadable
|
|
1256
|
-
"fits": true,
|
|
1257
|
-
"max_safe_context": 8192,
|
|
1258
|
-
"predicted_decode_tps": 0.0
|
|
1259
|
-
}
|
|
1260
|
-
}]
|
|
1261
|
-
}
|
|
1262
|
-
```
|
|
1263
|
-
|
|
1264
|
-
One row per repo (`listCanonical()`; duplicate snapshots from upstream
|
|
1265
|
-
re-pushes surface only via the CLI's `mlx-bun ls --all-revisions`).
|
|
1266
|
-
|
|
1267
|
-
## GET /downloads
|
|
1268
|
-
|
|
1269
|
-
Snapshot of the last 5 model downloads (active, completed, or errored)
|
|
1270
|
-
initiated via `mlx-bun download`, the web library panel, or
|
|
1271
|
-
`POST /api/hub/download`.
|
|
1272
|
-
|
|
1273
|
-
```jsonc
|
|
1274
|
-
{
|
|
1275
|
-
"downloads": [{
|
|
1276
|
-
"repo_id": "…",
|
|
1277
|
-
"state": "active" | "done" | "error",
|
|
1278
|
-
"current_file": "model.safetensors" | null,
|
|
1279
|
-
"received_bytes": 0,
|
|
1280
|
-
"total_bytes": 0,
|
|
1281
|
-
"files_done": 0,
|
|
1282
|
-
"files_total": 0,
|
|
1283
|
-
"bytes_per_sec": 0, // rolling ~5 s window
|
|
1284
|
-
"started_at": 1760000000,
|
|
1285
|
-
"finished_at": null,
|
|
1286
|
-
"error": "…" // present on state "error"
|
|
1287
|
-
}]
|
|
1288
|
-
}
|
|
1289
|
-
```
|
|
1290
|
-
|
|
1291
|
-
## Jobs (`/api/jobs`)
|
|
1292
|
-
|
|
1293
|
-
The web wizards (quantize, fine-tune, dataset) run as jobs: a SQLite row
|
|
1294
|
-
plus an NDJSON event log (`src/jobs/`). Submit routes return a `job_id`;
|
|
1295
|
-
these routes read progress. Loopback-served admin routes, no separate auth
|
|
1296
|
-
(same posture as every other `/api/*` route). Handler:
|
|
1297
|
-
`src/serve/admin-routes.ts`.
|
|
1298
|
-
|
|
1299
|
-
`GET /api/jobs?limit=50&kind=quantize|finetune|dataset` — most recent
|
|
1300
|
-
jobs, newest first: `{ "ok": true, "jobs": [JobRow, …] }`.
|
|
1301
|
-
|
|
1302
|
-
`GET /api/jobs/<id>` — one row, or `404 { "ok": false, "error": "job not
|
|
1303
|
-
found" }`:
|
|
1304
|
-
|
|
1305
|
-
```jsonc
|
|
1306
|
-
{ "ok": true, "job": {
|
|
1307
|
-
"id": "…", "kind": "quantize" | "finetune" | "dataset",
|
|
1308
|
-
"status": "queued" | "running" | "done" | "failed" | "zombie",
|
|
1309
|
-
"config_json": "{…}", // the submit body as stored
|
|
1310
|
-
"progress": 0.0, // 0..1, from the latest stage/metric event
|
|
1311
|
-
"message": "…" | null,
|
|
1312
|
-
"log_path": "…", "output_path": "…" | null, "error": "…" | null,
|
|
1313
|
-
"started_at": "<ISO>", "ended_at": "<ISO>" | null
|
|
1314
|
-
} }
|
|
1315
|
-
```
|
|
1316
|
-
|
|
1317
|
-
`GET /api/jobs/<id>/stream` — EventSource-compatible SSE tail of the job's
|
|
1318
|
-
log: a `retry: 1500` line first, then one `data: <JobEvent>` frame per
|
|
1319
|
-
event, polling while the job is non-terminal, then `event: end` +
|
|
1320
|
-
`data: {}` and close. An unknown id streams a single `failed` event then
|
|
1321
|
-
`end`. Events:
|
|
1322
|
-
|
|
1323
|
-
```jsonc
|
|
1324
|
-
{ "type": "started", "ts": 0 }
|
|
1325
|
-
{ "type": "log", "line": "…" }
|
|
1326
|
-
{ "type": "stage", "stage": "…", "progress": 0.5, "message": "…" }
|
|
1327
|
-
{ "type": "metric", "kind": "train" | "val", "step": 0, "loss": 0.0 } // + grad_norm, learning_rate, tokens_per_sec, …
|
|
1328
|
-
{ "type": "done", "ts": 0, "output_dir": "…", "summary": { } }
|
|
1329
|
-
{ "type": "failed", "error": "…", "ts": 0 }
|
|
1330
|
-
```
|
|
1331
|
-
|
|
1332
|
-
## Fine-tune (`/api/finetune/*`)
|
|
1333
|
-
|
|
1334
|
-
The HTTP entry point to LoRA training (`src/serve/lab-routes.ts` →
|
|
1335
|
-
`src/train/job.ts`). It runs the same runner the CLI's `mlx-bun train`
|
|
1336
|
-
drives, as a subprocess job (crash isolation + one GPU lease across
|
|
1337
|
-
concurrent server requests). Data formats, every config knob, and the
|
|
1338
|
-
methodology are in [training.md](./training.md); this section is the wire
|
|
1339
|
-
shape.
|
|
1340
|
-
|
|
1341
|
-
`POST /api/finetune/submit` — body is `FinetuneSubmit` (`src/train/job.ts`).
|
|
1342
|
-
`model_dir` and `data_dir` are required at the route (`400 "model_dir and
|
|
1343
|
-
data_dir required"`); `adapter_path` defaults to
|
|
1344
|
-
`~/.cache/mlx-bun/adapters/adapter-<timestamp>` when omitted. Everything
|
|
1345
|
-
else is optional and falls back to `DEFAULT_TRAIN_CONFIG`:
|
|
1346
|
-
|
|
1347
|
-
```jsonc
|
|
1348
|
-
{
|
|
1349
|
-
"model_dir": "/path/to/snapshot", // required
|
|
1350
|
-
"data_dir": "/path/to/dataset", // required: train.jsonl (+ optional valid.jsonl)
|
|
1351
|
-
"adapter_path": "/path/to/output", // optional (default above)
|
|
1352
|
-
"method": "sft" | "dpo" | "orpo",
|
|
1353
|
-
"rank": 16, "scale": 20, "rank_scaling": "…", "target_modules": ["…"], "num_layers": 0,
|
|
1354
|
-
"iters": 300, "learning_rate": 2e-4, "max_seq_length": 2048, "batch_size": 1,
|
|
1355
|
-
"grad_accumulation_steps": 1, "seed": 0, "steps_per_report": 1, "steps_per_eval": 0,
|
|
1356
|
-
"weight_decay": 0.0, "lora_dropout": 0.0, "rs_lora": false, "lora_plus_ratio": 1,
|
|
1357
|
-
"grad_checkpoint": false, "mlp_split": false, "segment_size": 0,
|
|
1358
|
-
"save_checkpoints": true, "grad_clip_norm": 0, "val_max_examples": 0,
|
|
1359
|
-
"dpo_beta": 0.1, "dpo_warmup_iters": 0, "dpo_lr_schedule": "constant" | "cosine",
|
|
1360
|
-
"orpo_lambda": 0.1, "orpo_warmup_iters": 0, "orpo_lr_schedule": "constant" | "cosine",
|
|
1361
|
-
"orpo_chunk_size": 0, "orpo_fused_ce": true, "orpo_flash_ce": true, "orpo_prefix_shared": true,
|
|
1362
|
-
"sft_scope": "full" | "response",
|
|
1363
|
-
"warm_start_adapter": "/path/to/adapter"
|
|
1364
|
-
}
|
|
1365
|
-
```
|
|
1366
|
-
|
|
1367
|
-
Response: `{ "ok": true, "job_id": "…", "adapter_path": "…" }`. Then
|
|
1368
|
-
follow `GET /api/jobs/<job_id>/stream` for `metric` events (`kind:
|
|
1369
|
-
"train"|"val"`, `step`, `loss`, …) and the terminal `done`/`failed`.
|
|
1370
|
-
Config validation errors (an invalid `sft_scope`, a missing `adapter_path`
|
|
1371
|
-
at the runner) surface as the job's `failed` event, not as a `4xx`.
|
|
1372
|
-
|
|
1373
|
-
```bash
|
|
1374
|
-
curl -s localhost:8080/api/finetune/submit -X POST -H 'content-type: application/json' -d '{
|
|
1375
|
-
"model_dir": "/path/to/snapshot", "data_dir": "/path/to/dataset",
|
|
1376
|
-
"adapter_path": "/path/to/output-adapter",
|
|
1377
|
-
"method": "sft", "rank": 16, "iters": 300, "learning_rate": 2e-4, "max_seq_length": 2048
|
|
1378
|
-
}'
|
|
1379
|
-
# → {"ok":true,"job_id":"…","adapter_path":"/path/to/output-adapter"}
|
|
1380
|
-
curl -N localhost:8080/api/jobs/<job_id>/stream
|
|
1381
|
-
```
|
|
1382
|
-
|
|
1383
|
-
`POST /api/finetune/inspect-dataset` with `{"path": "<data_dir>"}` — probe
|
|
1384
|
-
a dataset directory before submitting: `{ "ok": true, "n_train": N,
|
|
1385
|
-
"n_valid": N, "format": "messages" | "prompt-completion" | "text" | "dpo" |
|
|
1386
|
-
"unknown" }`, or `{ "ok": false, …, "error": "<train.jsonl> not found" }`
|
|
1387
|
-
(HTTP 200 either way).
|
|
1388
|
-
|
|
1389
|
-
`POST /api/finetune/merge` with `{"adapter_a": "/dir", "adapter_b":
|
|
1390
|
-
"/dir", "scales"?: [a, b]}` — fold two adapters into one (`mergeAdapters`
|
|
1391
|
-
in `src/train/merge.ts`; per-source scales default to each adapter's own
|
|
1392
|
-
config). Output goes to `~/.cache/mlx-bun/adapters/merged-<timestamp>`:
|
|
1393
|
-
`{ "ok": true, "merged_path": "…", "stats": { "layersMerged", "layersOnlyInOne",
|
|
1394
|
-
"totalKeysOut", "sources", "scales" } }`; `400 { "ok": false, "error" }` on
|
|
1395
|
-
a missing field or merge failure.
|
|
1396
|
-
|
|
1397
|
-
`POST /api/finetune/export` with `{"base_model": "…", "adapter_path":
|
|
1398
|
-
"/dir", "method"?: "…"}` — writes a `manifest.json` describing the adapter
|
|
1399
|
-
into `~/.cache/mlx-bun/exports/export-<timestamp>`:
|
|
1400
|
-
`{ "ok": true, "export_path": "…", "manifest": { "version": 1, "base_model",
|
|
1401
|
-
"adapter_path", "method"?, "created_at" } }`; `400` on a missing field.
|
|
1402
|
-
|
|
1403
|
-
## Quantize (`/api/quantize/*`)
|
|
1404
|
-
|
|
1405
|
-
`POST /api/quantize/inspect` with `{"model_id": "<repo id | path>"}` —
|
|
1406
|
-
resolve a model through the registry and report whether the quantizer can
|
|
1407
|
-
walk it (`inspectModel`, `src/quantize/job.ts`): `{ "ok": true, "model_id",
|
|
1408
|
-
"arch": "<model_type>" | null, "support": true, "size_gb": 0.0 }`, or
|
|
1409
|
-
`{ "ok": false, …, "error" }` (HTTP 200 either way).
|
|
1410
|
-
|
|
1411
|
-
`POST /api/quantize/submit` — starts a quantize job whose output lands in
|
|
1412
|
-
the HF hub cache as a synthetic repo `<org>/<name>-OptiQ-<suffix>`
|
|
1413
|
-
(`refs/main` written so the registry picks it up; `<suffix>` is
|
|
1414
|
-
`<bits>bit` or `mixed-<target_bpw>bpw`, plus `-rot<seed>` when rotating):
|
|
1415
|
-
|
|
1416
|
-
```jsonc
|
|
1417
|
-
{
|
|
1418
|
-
"model_id": "org/name | /path", // required (400 "model_id required")
|
|
1419
|
-
"bits": 4, "group_size": 64,
|
|
1420
|
-
"target_bpw": 3.5, // mixed-precision target; wins over bits in the name
|
|
1421
|
-
"candidate_bits": [2, 3, 4, 8],
|
|
1422
|
-
"reference": "…", "calibration_mix": "…", "n_calibration": 0,
|
|
1423
|
-
"rotate_weights": false, "rotation_seed": 42
|
|
1424
|
-
}
|
|
1425
|
-
```
|
|
1426
|
-
|
|
1427
|
-
Response: `{ "ok": true, "job_id": "…", "output_dir": "…" }`. The library
|
|
1428
|
-
cache is invalidated when the job completes.
|
|
1429
|
-
|
|
1430
|
-
`POST /api/quantize/resolve-folder` and `POST /api/model/resolve-folder`
|
|
1431
|
-
(same handler) with `{"folder_name"?: "…", "rel_path"?: "…"}` — turn a
|
|
1432
|
-
folder the browser picked (a `models--org--name` hub dir, a snapshot hash
|
|
1433
|
-
dir, or a bare model dir under the hub cache / `~/.cache/mlx-bun`) into a
|
|
1434
|
-
servable path: `{ "ok": true, "path": "…", "repo_id"?: "…", "model_type"?:
|
|
1435
|
-
"…" }`, or `{ "ok": false, "error": "Couldn't locate this folder on disk —
|
|
1436
|
-
paste the path instead." }` (HTTP 200).
|
|
1437
|
-
|
|
1438
|
-
## Dataset (`/api/dataset/*`)
|
|
1439
|
-
|
|
1440
|
-
`GET /api/dataset/templates` — the dataset-builder template registry
|
|
1441
|
-
(`src/dataset/registry.ts`): `{ "templates": [{ "id", "label",
|
|
1442
|
-
"description", "output_format": "messages" | "prompt_completion" | "dpo" |
|
|
1443
|
-
"text", "needs_llm": bool, "fields": [{ "name", "type": "text" | "textarea" |
|
|
1444
|
-
"number", "label", "hint"?, "required"?, "default"? }] }] }`.
|
|
1445
|
-
|
|
1446
|
-
`POST /api/dataset/submit` with `{"template_id": "…", "inputs": {…},
|
|
1447
|
-
"model_name"?: "…"}` — runs the template in-process as a job; LLM-backed
|
|
1448
|
-
templates call back into this server's own `/v1` (`api_url` =
|
|
1449
|
-
`http://127.0.0.1:<port>`). Output dir is
|
|
1450
|
-
`~/.cache/mlx-bun/datasets/dataset-<template>-<timestamp>`. Response:
|
|
1451
|
-
`{ "ok": true, "job_id": "…", "output_dir": "…" }`; `400 { "ok": false,
|
|
1452
|
-
"error": "unknown template …" }` for an unknown id.
|
|
1453
|
-
|
|
1454
|
-
## POST /api/{quantize,finetune,dataset}/push
|
|
1455
|
-
|
|
1456
|
-
Upload a job's output (or any folder) to the Hugging Face Hub with the
|
|
1457
|
-
saved write token (`src/hf-push.ts`). Body: `{"repo_id": "org/name",
|
|
1458
|
-
"job_id"?: "…", "source_path"?: "/dir", "private"?: false}` — `repo_id` is
|
|
1459
|
-
required; the source is `source_path`, else the job's `output_path`. The
|
|
1460
|
-
`dataset` variant pushes as a dataset repo, the others as model repos.
|
|
1461
|
-
Response `{ "ok": true, "url": "…" }`; `400 { "ok": false, "error" }` when
|
|
1462
|
-
no token is saved, no source directory resolves, or the upload fails.
|
|
1463
|
-
|
|
1464
|
-
## GET /api/gc/plan · POST /api/gc/execute
|
|
1465
|
-
|
|
1466
|
-
Reclaim disk from superseded snapshots + orphaned blobs in the local HF
|
|
1467
|
-
cache — the web-UI equivalent of `mlx-bun gc` (thin wrappers over
|
|
1468
|
-
`src/registry.ts`'s `planGc`/`executeGc`; same planner the CLI uses). Every
|
|
1469
|
-
downloaded revision the HF cache keeps a `snapshots/<commit>` dir for; a
|
|
1470
|
-
snapshot no `refs/*` points at is superseded and, once no surviving snapshot
|
|
1471
|
-
symlinks to a blob, that blob is dead too. Loopback-served admin routes, no
|
|
1472
|
-
separate auth (same bind as every other `/api/*` route).
|
|
1473
|
-
|
|
1474
|
-
`GET /api/gc/plan` — read-only (config.json + safetensors index reads only,
|
|
1475
|
-
no tensor bytes, no deletion):
|
|
1476
|
-
|
|
1477
|
-
```jsonc
|
|
1478
|
-
{
|
|
1479
|
-
"ok": true,
|
|
1480
|
-
"superseded": [{
|
|
1481
|
-
"repo_id": "…",
|
|
1482
|
-
"prune_snapshots": 1, // unreferenced, safe to delete
|
|
1483
|
-
"skipped_snapshots": 0, // unreferenced but hold files the kept
|
|
1484
|
-
// revision lacks — needs --force via the
|
|
1485
|
-
// CLI to actually prune; not deletable here
|
|
1486
|
-
"dead_blobs": 3,
|
|
1487
|
-
"reclaim_bytes": 0
|
|
1488
|
-
}],
|
|
1489
|
-
"reclaim_bytes": 0 // sum across every repo
|
|
1490
|
-
}
|
|
1491
|
-
```
|
|
1492
|
-
|
|
1493
|
-
`POST /api/gc/execute` with body `{"yes": true}` — deletes exactly what a
|
|
1494
|
-
fresh plan would show (recomputed at execute time, not the caller's stale
|
|
1495
|
-
plan); `{"yes": true}` is required or the request 400s, mirroring the CLI's
|
|
1496
|
-
`--yes` gate so a stray call can't delete anything by accident:
|
|
1497
|
-
|
|
1498
|
-
```jsonc
|
|
1499
|
-
{ "ok": true, "snapshots": 1, "blobs": 3, "reclaimed_bytes": 0 }
|
|
1500
|
-
```
|
|
1501
|
-
|
|
1502
|
-
Errors follow the same `{ "ok": false, "error": "…" }` shape as the other
|
|
1503
|
-
`/api/*` job routes.
|
|
1504
|
-
|
|
1505
|
-
## GET /api/memory/\* · POST /api/memory/init
|
|
1506
|
-
|
|
1507
|
-
Thin loopback JSON wrappers over `src/memory/vault.ts` for the web chat's
|
|
1508
|
-
Memory panel — same loopback-only admin-route posture as `/api/gc/*`, no
|
|
1509
|
-
separate auth. Handlers live in `src/memory/rest.ts`; they never touch the
|
|
1510
|
-
agent-tool surface (`src/memory/tools.ts`), which stays read-only. Every
|
|
1511
|
-
route below degrades gracefully — a missing vault, an unknown article, or
|
|
1512
|
-
a bad param returns `{ "ok": false, ... }` (with an appropriate HTTP
|
|
1513
|
-
status), never a 500.
|
|
1514
|
-
|
|
1515
|
-
When no vault exists yet, every `GET` route (except `/init`) returns:
|
|
1516
|
-
|
|
1517
|
-
```jsonc
|
|
1518
|
-
{ "ok": false, "enabled": false, "error": "no memory vault yet", "root": "…" }
|
|
1519
|
-
```
|
|
1520
|
-
|
|
1521
|
-
`GET /api/memory/status` — `vaultStatus()` as JSON:
|
|
1522
|
-
|
|
1523
|
-
```jsonc
|
|
1524
|
-
{ "ok": true, "enabled": true, "status": { "root": "…", "exists": true, "articleCount": 12, "referenceCount": 9, "isGitRepo": true, "recentArticles": [{ "article": "…", "mtimeMs": 0 }] } }
|
|
1525
|
-
```
|
|
1526
|
-
|
|
1527
|
-
`GET /api/memory/list` — article stems plus `Reference/*` doc ids, kept
|
|
1528
|
-
separate (mirrors `memory_list`):
|
|
1529
|
-
|
|
1530
|
-
```jsonc
|
|
1531
|
-
{ "ok": true, "articles": ["Alpha", "Beta"], "reference": ["Reference/mlx-bun_README"] }
|
|
1532
|
-
```
|
|
1533
|
-
|
|
1534
|
-
`GET /api/memory/search?q=<query>&scope=all|articles|reference&limit=50` —
|
|
1535
|
-
wraps `searchArticles` (`limit` capped at 200; `400` without `q`); same
|
|
1536
|
-
summaries/hits shape the `memory_search` tool formats from:
|
|
1537
|
-
|
|
1538
|
-
```jsonc
|
|
1539
|
-
{ "ok": true, "summaries": [{ "article": "…", "occurrences": 3, "matched_terms": ["…"] }], "hits": [{ "article": "…", "anchor": "…", "line": 10, "excerpt": "…" }] }
|
|
1540
|
-
```
|
|
1541
|
-
|
|
1542
|
-
`GET /api/memory/article?name=<article>` — rendered source plus the
|
|
1543
|
-
deterministic metadata the panel needs (infobox, lead, series banner,
|
|
1544
|
-
section skeleton); `404 { "ok": false }` for an unknown name:
|
|
1545
|
-
|
|
1546
|
-
```jsonc
|
|
1547
|
-
{ "ok": true, "name": "…", "path": "…", "content": "# …", "infobox": { "type": "…", "entityKind": "thing", "fields": [] }, "lead": "…", "series": null, "structure": [] }
|
|
1548
|
-
```
|
|
1549
|
-
|
|
1550
|
-
`GET /api/memory/links?name=<article>` — inbound/outbound wikilinks (same
|
|
1551
|
-
data `memory_links` resolves); articles only, not `Reference/*`:
|
|
1552
|
-
|
|
1553
|
-
```jsonc
|
|
1554
|
-
{ "ok": true, "name": "…", "outbound": ["…"], "inbound": ["…"] }
|
|
1555
|
-
```
|
|
1556
|
-
|
|
1557
|
-
`GET /api/memory/history?name=<article>&limit=50` — the article's git log
|
|
1558
|
-
(read-only `git log` plumbing on the vault's own repo, via `Bun.spawn` —
|
|
1559
|
-
never a shell string; `limit` capped at 200):
|
|
1560
|
-
|
|
1561
|
-
```jsonc
|
|
1562
|
-
{ "ok": true, "name": "…", "isGitRepo": true, "entries": [{ "hash": "…", "date": "YYYY-MM-DD", "subject": "…" }] }
|
|
1563
|
-
```
|
|
1564
|
-
|
|
1565
|
-
`GET /api/memory/diff?name=<article>&rev=<hash>` — that commit's diff for
|
|
1566
|
-
the article (`git show <rev> -- <path>`). `rev` must match
|
|
1567
|
-
`/^[0-9a-f]{4,40}$/` (a bare commit hash — never a ref expression like
|
|
1568
|
-
`HEAD~1`) or the request 400s before touching git; an unknown `name` or a
|
|
1569
|
-
revision with no diff for it 404s:
|
|
1570
|
-
|
|
1571
|
-
```jsonc
|
|
1572
|
-
{ "ok": true, "name": "…", "rev": "…", "diff": "diff --git a/articles/…" }
|
|
1573
|
-
```
|
|
1574
|
-
|
|
1575
|
-
`POST /api/memory/init` with optional body `{"path"?: string}` — the
|
|
1576
|
-
first-run consent-card backend. Delegates to the same `setupVault()` the
|
|
1577
|
-
CLI's `mlx-bun memory init` calls (idempotent: create dirs, write README +
|
|
1578
|
-
Meta pages only if missing, git init + initial commit); omits the CLI's
|
|
1579
|
-
interactive extras. `path`, if given, must resolve under the default vault
|
|
1580
|
-
root or the OS temp directory, else `400`:
|
|
1581
|
-
|
|
1582
|
-
```jsonc
|
|
1583
|
-
{ "ok": true, "result": { "root": "…", "created": ["…"], "gitInitialized": true, "alreadySetUp": false }, "status": { "root": "…", "exists": true, "articleCount": 0, "referenceCount": 9, "isGitRepo": true, "recentArticles": [] } }
|
|
1584
|
-
```
|
|
1585
|
-
|
|
1586
|
-
## GET /v1/memory/synthesize
|
|
1587
|
-
|
|
1588
|
-
Runs the memory synthesis pipeline (`runSynthesis`, `src/memory/pipeline.ts`
|
|
1589
|
-
— the same job `mlx-bun memory synthesize` runs) and streams its progress
|
|
1590
|
-
as SSE. `?dry=1` plans the stages without model calls or writes. This is a
|
|
1591
|
-
long GPU job over the whole store on a real run; it is registered under
|
|
1592
|
-
`/v1/` for historical reasons and is not part of the OpenAI surface.
|
|
1593
|
-
|
|
1594
|
-
```
|
|
1595
|
-
data: {"type":"stage","stage":"ingest","message":"…"}
|
|
1596
|
-
data: {"type":"log","message":"…"}
|
|
1597
|
-
data: {"type":"done","message":"synthesis done — …"}
|
|
1598
|
-
data: {"type":"summary","implemented":true,"stages":["ingest","segment","extract","route","create","section-route","patch","link","wikify"],"note":"…"}
|
|
1599
|
-
data: [DONE]
|
|
1600
|
-
```
|
|
1601
|
-
|
|
1602
|
-
A failure emits `data: {"type":"error","message":"…"}` and closes without
|
|
1603
|
-
`[DONE]`.
|
|
1604
|
-
|
|
1605
|
-
## Settings (`/api/settings/*`)
|
|
1606
|
-
|
|
1607
|
-
`GET /api/settings/hf-token` → `{ "ok": true, "hasToken": bool }`;
|
|
1608
|
-
`POST /api/settings/hf-token` with `{"token": "hf_…"}` saves the Hugging
|
|
1609
|
-
Face write token to `~/.mlx-bun/hf.json` (mode 0600) →
|
|
1610
|
-
`{ "ok": true }`; `400 "token required"` when empty. The token drives the
|
|
1611
|
-
push routes above and the `upload` CLI command ([cli.md](./cli.md)).
|
|
1612
|
-
|
|
1613
|
-
`GET /api/settings/tool-approvals` / `DELETE /api/settings/tool-approvals`
|
|
1614
|
-
— the web chat's durable "always allow this tool" list for gated tools
|
|
1615
|
-
(`bash`/`edit`/`write`), backed by `~/.mlx-bun/tool-approvals.json`
|
|
1616
|
-
(`src/tool-approvals.ts`), keyed by stable tool NAME, not per-call
|
|
1617
|
-
arguments. These two routes are read/forget only: granting an always-allow
|
|
1618
|
-
happens through the approval card itself (the `approval` WS frame on
|
|
1619
|
-
`/ws/chat` with `alwaysAllow: true`), never a bare settings POST.
|
|
1620
|
-
|
|
1621
|
-
```jsonc
|
|
1622
|
-
// GET
|
|
1623
|
-
{ "ok": true, "alwaysAllow": ["bash", "edit"] }
|
|
1624
|
-
// DELETE with body {"tool": "bash"} — idempotent; returns the updated set
|
|
1625
|
-
{ "ok": true, "alwaysAllow": ["edit"] }
|
|
1626
|
-
```
|
|
1627
|
-
|
|
1628
|
-
`DELETE` without `tool` is `400 "tool required"`.
|
|
1629
|
-
|
|
1630
|
-
## Model Hub (`/api/hub/*`)
|
|
1631
|
-
|
|
1632
|
-
The web chat's Model Hub panel: browse downloaded models, search Hugging
|
|
1633
|
-
Face, and kick off downloads — all loopback-served, no separate auth.
|
|
1634
|
-
Handlers live in `src/hub-rest.ts` (pure functions, no loaded-model
|
|
1635
|
-
dependency).
|
|
1636
|
-
|
|
1637
|
-
`GET /api/hub/local` — every model in the local registry (same source
|
|
1638
|
-
`/library` reads), each with a `/fit`-computed hardware verdict at a fixed
|
|
1639
|
-
8k context:
|
|
1640
|
-
|
|
1641
|
-
```jsonc
|
|
1642
|
-
{
|
|
1643
|
-
"ok": true,
|
|
1644
|
-
"models": [{
|
|
1645
|
-
"repo_id": "…",
|
|
1646
|
-
"model_type": "gemma3" | "minicpm5" | "qwen3" | …,
|
|
1647
|
-
"size_bytes": 0,
|
|
1648
|
-
"quant_bits": 4,
|
|
1649
|
-
"quant_group_size": 64,
|
|
1650
|
-
"vision": false,
|
|
1651
|
-
"supported": true,
|
|
1652
|
-
"support_tier": "targeted" | "generic" | null,
|
|
1653
|
-
"assessment": { "fits": true, "max_safe_context": 8192, "predicted_decode_tps": 0.0 } | null
|
|
1654
|
-
}]
|
|
1655
|
-
}
|
|
1656
|
-
```
|
|
1657
|
-
|
|
1658
|
-
`GET /api/hub/search?q=<query>` — server-side Hugging Face model search,
|
|
1659
|
-
filtered to the `mlx` library tag so results are MLX-compatible by
|
|
1660
|
-
construction. Never downloads anything. `400` without `q`. Degrades on any
|
|
1661
|
-
network failure (DNS, timeout, non-2xx) to an explicit offline state
|
|
1662
|
-
rather than a 500:
|
|
1663
|
-
|
|
1664
|
-
```jsonc
|
|
1665
|
-
{ "ok": true, "offline": false, "results": [{ "id": "mlx-community/…", "downloads": 0, "likes": 0, "size_estimate": null }] }
|
|
1666
|
-
```
|
|
1667
|
-
|
|
1668
|
-
```jsonc
|
|
1669
|
-
{ "ok": true, "offline": true, "error": "…", "results": [] }
|
|
1670
|
-
```
|
|
1671
|
-
|
|
1672
|
-
`size_estimate` is always `null` — the search endpoint doesn't return
|
|
1673
|
-
per-repo file sizes; the fit badge lands once a model is actually
|
|
1674
|
-
downloaded (`/api/hub/local`).
|
|
1675
|
-
|
|
1676
|
-
`POST /api/hub/download` with body `{"repo": "org/name"}` — starts a
|
|
1677
|
-
background download via `downloadModel()` and returns immediately;
|
|
1678
|
-
progress is visible via [`GET /downloads`](#get-downloads). Refuses a
|
|
1679
|
-
duplicate kick-off for a repo that's already downloading:
|
|
1680
|
-
|
|
1681
|
-
```jsonc
|
|
1682
|
-
{ "ok": true, "repo": "org/name", "started": true }
|
|
1683
|
-
```
|
|
1684
|
-
|
|
1685
|
-
```jsonc
|
|
1686
|
-
{ "ok": false, "error": "a download for org/name is already in progress" } // 409
|
|
1687
|
-
```
|
|
1688
|
-
|
|
1689
|
-
`POST /api/hub/serve` with body `{"model": "org/name"}` — **always answers
|
|
1690
|
-
`restart_required`, never performs a live in-process swap.** The process a
|
|
1691
|
-
web chat session is attached to has exactly one model loaded and no
|
|
1692
|
-
drop-weights-and-reload seam reachable from `/ws/chat` (the `--isolate`
|
|
1693
|
-
proxy's `ModelPool` is a real spawn-overlap swap, but it lives on the
|
|
1694
|
-
isolated `/v1/*` surface only — `src/serve/isolate.ts`). The honest answer
|
|
1695
|
-
is the restart command (HTTP 200, `ok: false`):
|
|
1696
|
-
|
|
1697
|
-
```jsonc
|
|
1698
|
-
{ "ok": false, "restart_required": true, "command": "mlx-bun serve org/name" }
|
|
1699
|
-
```
|
|
1700
|
-
|
|
1701
|
-
## GET /api/sessions/search · GET /api/sessions/export
|
|
1702
|
-
|
|
1703
|
-
Full-text search across web-chat session message **bodies** and chat
|
|
1704
|
-
export. Same loopback-only, no-separate-auth posture as `/api/memory/*`.
|
|
1705
|
-
Handlers live in `src/serve/session-search.ts` (pure, read-only, no
|
|
1706
|
-
loaded-model dependency). Both routes read the same session directory the
|
|
1707
|
-
web chat's `PiWebSession` writes to (`~/.mlx-bun/sessions`, pi's own
|
|
1708
|
-
JSONL-per-session format) and are JSONL-tolerant — a corrupt or mid-write
|
|
1709
|
-
line is skipped, never thrown.
|
|
1710
|
-
|
|
1711
|
-
`GET /api/sessions/search?q=<query>` — case-insensitive substring scan (no
|
|
1712
|
-
index — personal-chat-corpus scale) over every session file's
|
|
1713
|
-
user/assistant message text, capped at 50 matches total (10 per session).
|
|
1714
|
-
Each match reports a plain-text ±60-char snippet plus the match's own
|
|
1715
|
-
`[start,end)` offsets **into that snippet** — offsets, not pre-rendered
|
|
1716
|
-
HTML, so the frontend escapes the snippet then inserts its own `<mark>`:
|
|
1717
|
-
|
|
1718
|
-
```jsonc
|
|
1719
|
-
{
|
|
1720
|
-
"ok": true,
|
|
1721
|
-
"results": [{
|
|
1722
|
-
"sessionPath": "/Users/…/.mlx-bun/sessions/20260706_…jsonl",
|
|
1723
|
-
"sessionTitle": "Chicken recipes",
|
|
1724
|
-
"matches": [{ "snippet": "…tell me about rosemary and thyme…", "ranges": [[14, 22]], "role": "user" }]
|
|
1725
|
-
}]
|
|
1726
|
-
}
|
|
1727
|
-
```
|
|
1728
|
-
|
|
1729
|
-
400s `{"ok": false, "error": "q is required"}` without a query.
|
|
1730
|
-
|
|
1731
|
-
`GET /api/sessions/export?path=<session file>` — the raw session JSONL,
|
|
1732
|
-
parsed into an array of entries (one per line, in file order). `path` must
|
|
1733
|
-
resolve under the session directory:
|
|
1734
|
-
|
|
1735
|
-
```jsonc
|
|
1736
|
-
{ "ok": true, "path": "…", "entries": [{ "type": "session", "id": "…", "cwd": "…" }, { "type": "message", "message": { "role": "user", "content": "…" } }] }
|
|
1737
|
-
```
|
|
1738
|
-
|
|
1739
|
-
400s without `path`; 403s a `path` outside the session directory; 404s a
|
|
1740
|
-
path inside the directory that doesn't exist on disk.
|
|
1741
|
-
|
|
1742
|
-
## Web app static routes + PWA installability
|
|
1743
|
-
|
|
1744
|
-
The web chat (`GET /`) and everything it loads same-origin, no CDN, ever
|
|
1745
|
-
(`src/serve/static-routes.ts`; GET only):
|
|
1746
|
-
|
|
1747
|
-
| Route | Content | Notes |
|
|
1748
|
-
| --- | --- | --- |
|
|
1749
|
-
| `GET /` | `src/web/app.html` | The unified SPA shell. |
|
|
1750
|
-
| `GET /assets/app.js` | `src/web/app.js` | GENERATED from `src/web/src/*.ts` by `bun scripts/build-web.ts` — see `tests/using/web-build.test.ts`'s freshness gate. |
|
|
1751
|
-
| `GET /assets/hljs.js`, `GET /assets/hljs.css` | `src/web/vendor/hljs*` | Vendored syntax highlighting, no CDN. |
|
|
1752
|
-
| `GET /manifest.webmanifest` | `src/web/manifest.webmanifest` | PWA manifest. |
|
|
1753
|
-
| `GET /assets/icon.svg` | `src/web/icon.svg` | A single inline SVG app icon; no binary PNGs are shipped. |
|
|
1754
|
-
| `GET /sw.js` | `src/web/sw.js` | Service worker, served `cache-control: no-store` at the root scope. |
|
|
1755
|
-
| `GET /curves` | `src/lab/curve/curve-designer.html` | The HLG Curve Designer page (drives `/signal` + `/generate` below); read from disk when available, embedded copy in compiled binaries. `no-store`. |
|
|
1756
|
-
| `GET /curve-terrain` | `src/serve/assets/curve-terrain.html` | Curve terrain visualization; `404` when the artifact isn't on disk. |
|
|
1757
|
-
| `GET /dag` | `src/serve/assets/training-inference-map.html` | Training/inference DAG map; `404` when the artifact isn't on disk. |
|
|
1758
|
-
| `GET /status`, `/chat`, `/quantize`, `/finetune`, `/dataset` | — | Legacy page paths: `302` to `/#<path>` (the SPA's hash routes). |
|
|
1759
|
-
|
|
1760
|
-
`/assets/*` and the manifest are served with `cache-control: public,
|
|
1761
|
-
max-age=3600`; `/sw.js`, `/curves`, `/curve-terrain`, `/dag` are `no-store`.
|
|
1762
|
-
|
|
1763
|
-
**PWA scope:** the manifest + service worker exist for *installability and
|
|
1764
|
-
an instant static-shell paint* — "Add to Home Screen" / a browser "Install
|
|
1765
|
-
app" prompt, and the next visit's HTML/JS/CSS loading from cache while the
|
|
1766
|
-
WebSocket connects. This is explicitly **not** offline chat:
|
|
1767
|
-
`src/web/sw.js`'s fetch handler only intercepts the exact shell files
|
|
1768
|
-
(`/`, `/assets/app.js`, `/assets/hljs.js`, `/assets/hljs.css`) and lets
|
|
1769
|
-
every dynamic route (`/api/*`, `/v1/*`, `/ws/chat`, `/downloads`, …) fall
|
|
1770
|
-
straight through to the network unconditionally. Registration is guarded
|
|
1771
|
-
to secure contexts (`https:` or `localhost`/`127.0.0.1`).
|
|
1772
|
-
|
|
1773
|
-
## Curve Designer: POST /signal · POST /generate
|
|
1774
|
-
|
|
1775
|
-
Backends for the `/curves` page (HLG tone-curve sampling research tool,
|
|
1776
|
-
`src/lab/curve/curve-sampler.ts`). Both answer `OPTIONS` and send permissive CORS
|
|
1777
|
-
headers (`access-control-allow-origin: *`) so the page can be opened from
|
|
1778
|
-
a file or another origin. Both take the GPU lock (`gateway.runExclusive`)
|
|
1779
|
-
— they never interleave with a serving request.
|
|
1780
|
-
|
|
1781
|
-
`POST /signal` with `{"prompt": "…"}` — one prefill forward of the prompt
|
|
1782
|
-
rendered as a single user turn; bins the next-token log-prob distribution
|
|
1783
|
-
into 80 buckets over the curve's x-axis so the editor can draw the signal
|
|
1784
|
-
under the curve: `{ "bins": [80 ints], "vocab": V }`. `400 {"error":
|
|
1785
|
-
"invalid JSON"}`; `500 {"error": "signal failed: …"}`.
|
|
1786
|
-
|
|
1787
|
-
`POST /generate` with `{"prompt", "curve"?: {points: [...]}, "n"?: 3,
|
|
1788
|
-
"max_tokens"?: 80, "seed"?: 0, "default"?: false}` — samples `n` (1–8)
|
|
1789
|
-
completions of up to `max_tokens` (1–256) tokens. When `curve` has ≥2
|
|
1790
|
-
points and `default` is not `true`, the drawn log-prob transfer curve
|
|
1791
|
-
replaces temperature+softmax entirely; otherwise the model's default chat
|
|
1792
|
-
recipe (temperature/top-p/top-k) is used. A non-monotone curve is a `400`.
|
|
1793
|
-
|
|
1794
|
-
```jsonc
|
|
1795
|
-
{ "mode": "curve" | "default",
|
|
1796
|
-
"recipe": { "temperature": 0.7, "topP": 0, "topK": 0 }, // only in default mode
|
|
1797
|
-
"n": 3, "seed": 0,
|
|
1798
|
-
"samples": [{ "text": "…", "junk": false }] } // junk = coherence heuristic tripped
|
|
1799
|
-
```
|
|
1800
|
-
|
|
1801
|
-
## Web chat WebSocket (`/ws/chat`)
|
|
1802
|
-
|
|
1803
|
-
`GET /ws/chat` upgrades to a WebSocket (`426` when the upgrade header is
|
|
1804
|
-
missing; `501` under `--isolate`). It carries the web chat's pi
|
|
1805
|
-
`AgentSession` — the model is driven through pi, which calls back into
|
|
1806
|
-
this server's own loopback `/v1`. Frames are JSON objects with a `type`
|
|
1807
|
-
field; the exact TypeScript unions are `ClientMessage` and `ServerMessage`
|
|
1808
|
-
in `src/pi-web.ts` (the web frontend imports them type-only, so both ends
|
|
1809
|
-
are compile-checked).
|
|
1810
|
-
|
|
1811
|
-
Client → server: `prompt {text, images?: [{data, mimeType}]}`, `abort`,
|
|
1812
|
-
`approval {callId, decision, editedArgs?, alwaysAllow?}`, `set_thinking
|
|
1813
|
-
{enabled}`, `set_coding_tools {enabled}`, `set_adapter {id | null}`,
|
|
1814
|
-
`set_sampling {scope?: "session" | "next_turn", temperature?, top_p?,
|
|
1815
|
-
top_k?, min_p?, xtc_probability?, xtc_threshold?, repetition_penalty?,
|
|
1816
|
-
repetition_context_size?, presence_penalty?, frequency_penalty?, seed?}`
|
|
1817
|
-
(null = server default), `set_system_prompt {text | null}`, `new_session`,
|
|
1818
|
-
`list_sessions`, `open_session {path}`, `fork_session {path}`,
|
|
1819
|
-
`delete_session {path}`, `regenerate`, `edit_resend {text}`,
|
|
1820
|
-
`switch_sibling {entryId}`, `context {context}` (app-aware assistant, below).
|
|
1821
|
-
|
|
1822
|
-
Server → client: `ready {model, vision, audio, thinking, genDefaults, transcription}`
|
|
1823
|
-
(`transcription` is true when a Whisper checkpoint serves [`/v1/audio/sessions`](#streaming-transcription-sessions-v1audiosessions);
|
|
1824
|
-
the web composer then shows a hold-to-talk mic that streams 16 kHz float32
|
|
1825
|
-
into a session and inserts the transcript into the message box — voice
|
|
1826
|
-
input for any chat model),
|
|
1827
|
-
`turn_start`, `text_delta {delta}`, `thinking_delta {delta}`, `tool_start
|
|
1828
|
-
{callId, tool, args}`, `tool_approval_request {callId, tool, args}`,
|
|
1829
|
-
`tool_update {callId, chunk}`, `tool_end {callId, ok, result}`, `turn_end
|
|
1830
|
-
{lane?}` (the serving lane the turn ran on, from `usage.lane` via the lane
|
|
1831
|
-
registry — never guessed client-side), `queue_update {steering, followUp}`,
|
|
1832
|
-
`history {items}`, `sessions {items, activePath?}`, `siblings {entryId?,
|
|
1833
|
-
index, count, siblingIds}`, `context {tokens, contextWindow, percent}`
|
|
1834
|
-
(context-window usage — a different frame from the client→server
|
|
1835
|
-
`context`), `coding_tools {active, pending}`, `tool_approvals
|
|
1836
|
-
{alwaysAllow}`, `ui_navigate {route}`, `ui_spotlight {ref?, label?,
|
|
1837
|
-
selector?, target?, message?, route?}`, `error {message}`.
|
|
1838
|
-
|
|
1839
|
-
### App-aware assistant
|
|
1840
|
-
|
|
1841
|
-
The web chat agent can see and act on the app it lives in. No screenshots
|
|
1842
|
-
and no vision model are involved: the browser sends a structured DOM
|
|
1843
|
-
snapshot as a WS frame, and the model gets three tools that
|
|
1844
|
-
read/navigate/highlight that snapshot. The three tools are always in the
|
|
1845
|
-
web chat's tool allowlist (read-only-on-the-machine, never gated by the
|
|
1846
|
-
approval card; `APP_AWARE_TOOL_NAMES` in `src/pi-web.ts`):
|
|
1847
|
-
|
|
1848
|
-
- **`get_current_app_context`** — returns the last context the browser
|
|
1849
|
-
pushed: `{ route, view?, step?, snapshot }`. `snapshot` is a capped
|
|
1850
|
-
(~120) list of visible interactive elements as `{ref, label, kind,
|
|
1851
|
-
role?, selector, spotlightId?}`, agent chrome excluded.
|
|
1852
|
-
- **`navigate_app({ route | page })`** — validates against the route
|
|
1853
|
-
catalog (`APP_ROUTE_IDS`: `chat`, `quantize`, `finetune`, `dataset`,
|
|
1854
|
-
`status`; unknown routes are a tool error, never sent to the browser)
|
|
1855
|
-
and emits a `ui_navigate` frame.
|
|
1856
|
-
- **`spotlight_ui({ ref | label | selector | target, route?, message? })`**
|
|
1857
|
-
— emits a `ui_spotlight` frame; the browser resolves the target (ref
|
|
1858
|
-
from its last snapshot, then live selector, then fuzzy label match, then
|
|
1859
|
-
a curated catalog id) and shows a brief non-blocking highlight.
|
|
1860
|
-
|
|
1861
|
-
Client → server WS frame (pushed by the browser on every route change and
|
|
1862
|
-
every wizard-step change, never on a timer):
|
|
1863
|
-
|
|
1864
|
-
```jsonc
|
|
1865
|
-
{ "type": "context", "context": { "route": "quantize", "step": { "index": 1, "count": 4, "label": "Configure" }, "snapshot": { "route": "quantize", "capturedAt": "…", "elements": [/* … */] } } }
|
|
1866
|
-
```
|
|
1867
|
-
|
|
1868
|
-
Server → client frames the two action tools produce:
|
|
1869
|
-
|
|
1870
|
-
```jsonc
|
|
1871
|
-
{ "type": "ui_navigate", "route": "quantize" }
|
|
1872
|
-
{ "type": "ui_spotlight", "target": "quantize-source", "message": "Paste a Hugging Face repo id or local path here" }
|
|
1873
|
-
```
|
|
1874
|
-
|
|
1875
|
-
The server also auto-prepends a compact one-line ambient context (e.g.
|
|
1876
|
-
`[user is on: Quantize · step 2/4]`) to the system prompt on every turn —
|
|
1877
|
-
never a full snapshot dump. Approval-gated form-fill / job-start actions
|
|
1878
|
-
are not wired; only navigate/spotlight are.
|
|
1879
|
-
|
|
1880
|
-
## Client setup: pi
|
|
1881
|
-
|
|
1882
|
-
The supported one-command path:
|
|
1883
|
-
|
|
1884
|
-
```bash
|
|
1885
|
-
mlx-bun harness pi # install; then: pi --provider mlx-bun
|
|
1886
|
-
mlx-bun harness pi --remove # undo
|
|
1887
|
-
```
|
|
1888
|
-
|
|
1889
|
-
It writes a self-contained extension to
|
|
1890
|
-
`~/.pi/agent/extensions/mlx-bun-provider.ts` that registers the `mlx-bun`
|
|
1891
|
-
provider with live discovery: at pi startup it fetches `/v1/models` from the
|
|
1892
|
-
running server, so the stable `mlx-bun/local` model handle always resolves to
|
|
1893
|
-
whatever the server is actually serving (context window, reasoning, and
|
|
1894
|
-
vision capability included), and never goes stale across model swaps. The
|
|
1895
|
-
model list baked at install time is only the fallback for when the server is
|
|
1896
|
-
down.
|
|
1897
|
-
|
|
1898
|
-
Manual fallback — `~/.pi/agent/models.json`:
|
|
1899
|
-
|
|
1900
|
-
```jsonc
|
|
1901
|
-
{
|
|
1902
|
-
"providers": {
|
|
1903
|
-
"mlx-bun": {
|
|
1904
|
-
"baseUrl": "http://127.0.0.1:8080/v1",
|
|
1905
|
-
"api": "openai-completions",
|
|
1906
|
-
"apiKey": "sk-anything-nonempty",
|
|
1907
|
-
"models": [{ "id": "<model id from /v1/models>" }]
|
|
1908
|
-
}
|
|
1909
|
-
}
|
|
1910
|
-
}
|
|
1911
|
-
```
|
|
1912
|
-
|
|
1913
|
-
Note the manual route bakes a concrete model id, which goes stale when you
|
|
1914
|
-
serve a different model — the staleness `harness pi`'s `local` id exists to
|
|
1915
|
-
solve.
|
|
1916
|
-
|
|
1917
|
-
Any OpenAI SDK works the same way: `baseURL: "http://127.0.0.1:8080/v1"`,
|
|
1918
|
-
any non-empty `apiKey`.
|
|
1919
|
-
|
|
1920
|
-
Under `--isolate`, the parent owns Responses continuation history and exposes its
|
|
1921
|
-
bounded store counters in `GET /engine` as `response_store`. Model-worker restart
|
|
1922
|
-
or eviction preserves that history; restarting the parent clears it. Direct
|
|
1923
|
-
serving retains the same process-local TTL and byte limits.
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
Supported grouped TurboQuant drafting retains `usage.speculation`, seeded
|
|
1927
|
-
sampling, logprobs, structured output and generated RAM/SSD continuations.
|
|
1928
|
-
The cache codec does not select the scheduler or sampler implementation.
|
|
1929
|
-
Composition coverage: [batching design](../design/batching.md).
|
|
1930
|
-
|
|
1931
|
-
With paged KV enabled, supported Gemma4 bf16 text requests report the batched lane when submitted through continuous scheduling. Seeded sampling, logprobs and grammar use the same interfaces. These requests bypass prompt-cache lookup/publication; paging does not provide SSD persistence.
|
|
1932
|
-
|
|
1933
|
-
Ordinary adapter checkpoints are isolated by ordered adapter IDs and mounted
|
|
1934
|
-
content revisions. Base-model requests cannot restore or remove an adapter checkpoint.
|
|
1935
|
-
With generation checkpointing enabled, repeating an identical eligible request
|
|
1936
|
-
replays its saved completion prefix and resumes from the saved pending token.
|
|
1937
|
-
Cancellation retains queued snapshots; successful completion removes them.
|
|
1938
|
-
`POST /admin/cache/flush` drains both prompt spills and generation checkpoints,
|
|
1939
|
-
and its elapsed time and durability statistics include both queues. A hard
|
|
1940
|
-
process exit can lose snapshots that have not drained.
|
|
1941
|
-
|
|
1942
|
-
For resumed generation checkpoints, `usage.prompt_tokens_details.cached_tokens`
|
|
1943
|
-
counts only the reused original prompt, bounded by `usage.prompt_tokens`. Saved
|
|
1944
|
-
completion tokens are replayed as completion output and do not increase that
|
|
1945
|
-
prompt-cache usage counter.
|