mlx-bun 0.0.13 → 0.3.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 +71 -578
- package/bin/mlx-bun.mjs +1 -1
- package/docs/README.md +91 -0
- package/docs/{design → archive}/hlg-sampling.md +3 -3
- package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
- package/docs/{design → archive/investigations}/colibri-glm52-port.md +1 -1
- package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
- package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
- package/docs/{investigations → archive/investigations}/hlg-sampling-investigation.md +1 -1
- package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
- package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
- package/docs/archive/investigations/pareto-specialized-runtime-findings.md +92 -0
- package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
- package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
- package/docs/{planning → archive/planning}/chat-ui-vision.md +1 -1
- package/docs/{planning → archive/planning}/chunk-task-roadmap.md +1 -1
- package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +4 -4
- package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +1 -1
- package/docs/{planning → archive/planning}/release-notes-v0.0.9.md +1 -1
- package/docs/archive/planning/release-notes-v0.2.0.md +49 -0
- package/docs/{planning → archive/planning}/website-readme-pass-plan.md +1 -1
- package/docs/design/batching.md +569 -0
- package/docs/design/decode-speed-program.md +3178 -169
- package/docs/design/dreaming-nightly-pipeline.md +675 -282
- package/docs/design/generic-model-support.md +579 -334
- package/docs/design/kv-cache.md +680 -0
- package/docs/design/orpo-training.md +673 -1239
- package/docs/design/speculative-decoding.md +1627 -0
- package/docs/design/turboquant.md +2102 -0
- package/docs/design/unified-engine-frontier-plan.md +1831 -727
- package/docs/design/web-chat-redesign.md +666 -1083
- package/docs/planning/PRODUCT_ROADMAP.md +34 -1
- package/docs/planning/release-notes-v0.3.0.md +35 -0
- package/docs/planning/video-series.md +421 -0
- package/docs/reference/benchmarks.md +1162 -0
- package/docs/reference/cli.md +395 -149
- package/docs/reference/distribution.md +321 -181
- package/docs/reference/environment.md +348 -0
- package/docs/reference/glossary.md +39 -0
- package/docs/reference/library-api.md +580 -142
- package/docs/reference/memory.md +227 -64
- package/docs/reference/models.md +137 -6
- package/docs/reference/server-api.md +938 -422
- package/docs/reference/server-config.md +652 -320
- package/docs/reference/training.md +393 -254
- package/docs/reference/troubleshooting.md +166 -0
- package/package.json +10 -5
- package/src/admission-plan.ts +18 -0
- package/src/anthropic.ts +92 -3
- package/src/audio/decode.ts +2 -2
- package/src/audio/features.ts +2 -2
- package/src/backends/mlx/autoregressive.ts +108 -0
- package/src/backends/mlx/batch-group.ts +1646 -0
- package/src/backends/mlx/diffusion.ts +39 -0
- package/src/backends/mlx/draft-target.ts +47 -0
- package/src/backends/mlx/evaluation.ts +105 -0
- package/src/backends/mlx/fill-append.ts +45 -0
- package/src/backends/mlx/gateway-binding.ts +71 -0
- package/src/backends/mlx/graph.ts +38 -0
- package/src/backends/mlx/memory-client.ts +189 -0
- package/src/backends/mlx/memory-guard.ts +40 -0
- package/src/backends/mlx/methods.ts +90 -0
- package/src/backends/mlx/model-prompt.ts +164 -0
- package/src/backends/mlx/model-serving.ts +79 -0
- package/src/backends/mlx/prefill.ts +33 -0
- package/src/backends/mlx/request-policy.ts +20 -0
- package/src/backends/mlx/rollback.ts +51 -0
- package/src/backends/mlx/serial-executor.ts +273 -0
- package/src/backends/mlx/speculative.ts +114 -0
- package/src/backends/mlx/state-views.ts +26 -0
- package/src/backends/mlx/text-engine.ts +73 -0
- package/src/cli.ts +189 -61
- package/src/client.ts +66 -0
- package/src/config.ts +21 -4
- package/src/contracts/completion.ts +16 -0
- package/src/contracts/execution.ts +49 -0
- package/src/contracts/generation.ts +103 -0
- package/src/contracts/host.ts +7 -0
- package/src/contracts/jobs.ts +66 -0
- package/src/contracts/pi-provider.ts +30 -0
- package/src/contracts/pi.ts +259 -0
- package/src/contracts/resources.ts +18 -0
- package/src/contracts/scheduling.ts +27 -0
- package/src/contracts/tsconfig.json +14 -0
- package/src/diffusion/diffusion-generate.ts +218 -153
- package/src/embed.ts +1 -1
- package/src/engine/admission.ts +59 -0
- package/src/engine/cancellation.ts +38 -0
- package/src/engine/engine.ts +56 -0
- package/src/engine/execution-coordinator.ts +70 -0
- package/src/engine/execution-plan.ts +51 -0
- package/src/engine/resources.ts +42 -0
- package/src/engine/scheduler.ts +67 -0
- package/src/engine/session.ts +247 -0
- package/src/eval/README.md +44 -1
- package/src/eval/kl.ts +263 -23
- package/src/eval/reference-logits.ts +207 -0
- package/src/eval/runner.ts +10 -79
- package/src/eval/tasks/bfcl.ts +2 -1
- package/src/eval/tasks/gsm8k.ts +2 -1
- package/src/eval/tasks/hashhop.ts +2 -1
- package/src/eval/tasks/humaneval.ts +5 -4
- package/src/eval/tasks/ifeval.ts +2 -1
- package/src/eval/tasks/mmlu.ts +2 -1
- package/src/expert-io.ts +3 -1
- package/src/expert-offload.ts +3 -1
- package/src/fill/echo-index.ts +188 -0
- package/src/fill/fill-session.ts +452 -0
- package/src/fill/proposal.ts +68 -0
- package/src/fill/schema-rows.ts +506 -0
- package/src/fill/tool-boundary.ts +103 -0
- package/src/fit.ts +24 -43
- package/src/generate.ts +740 -337
- package/src/grammar.ts +14 -8
- package/src/harness-pi.ts +15 -34
- package/src/hub-rest.ts +4 -4
- package/src/index.ts +27 -0
- package/src/inference/denoising.ts +17 -0
- package/src/inference/graph.ts +33 -0
- package/src/inference/prefill.ts +39 -0
- package/src/inference/rollback.ts +7 -0
- package/src/jobs/db.ts +4 -0
- package/src/jobs/index.ts +2 -0
- package/src/jobs/job-entry.ts +15 -14
- package/src/jobs/runner.ts +98 -13
- package/src/jobs/task-client.ts +19 -0
- package/src/jobs/types.ts +2 -58
- package/src/kv-scheme.ts +199 -0
- package/src/kv-store.ts +459 -234
- package/src/lab/README.md +18 -0
- package/src/{assets → lab/curve}/curve-designer.html +2 -2
- package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
- package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +7 -5
- package/src/{model → lab/paged-kv}/paged-kv.ts +9 -6
- package/src/library.ts +40 -0
- package/src/lora.ts +18 -0
- package/src/media-fetch.ts +20 -4
- package/src/memory/article.ts +1 -1
- package/src/memory/chunk.ts +1 -1
- package/src/memory/model.ts +32 -170
- package/src/memory/prompts.ts +1 -1
- package/src/memory/rest.ts +1 -1
- package/src/memory/schedule.ts +1 -1
- package/src/memory/stages.ts +4 -1
- package/src/memory/vault.ts +3 -4
- package/src/mlx/array.ts +9 -10
- package/src/mlx/compile.ts +12 -2
- package/src/mlx/ffi.ts +66 -22
- package/src/mlx/materialize.ts +23 -0
- package/src/mlx/metal-kernel.ts +10 -11
- package/src/mlx/ops.ts +64 -19
- package/src/mlx/quantized-rows.ts +36 -0
- package/src/mlx/token-bitmask.ts +52 -0
- package/src/mlx/turboquant-kv-decode.ts +82 -0
- package/src/mlx/turboquant-kv-inverse.ts +122 -0
- package/src/mlx/turboquant-ops.ts +33 -3
- package/src/mlx/turboquant-tables.ts +2 -2
- package/src/model/batched-mask.ts +9 -3
- package/src/model/batched-quant.ts +2 -2
- package/src/model/batched-rotating-quant.ts +45 -99
- package/src/model/batched-rotating-state.ts +73 -0
- package/src/model/batched-rotating.ts +57 -106
- package/src/model/batched-row-storage.ts +127 -0
- package/src/model/compiled-decode.ts +4 -3
- package/src/model/diffusion-gemma.ts +8 -8
- package/src/model/factory.ts +87 -49
- package/src/model/fingerprint.ts +1 -1
- package/src/model/flash-attention.ts +3 -3
- package/src/model/gemma4-base.ts +187 -17
- package/src/model/gemma4.ts +6 -6
- package/src/model/generated/gemma4-12b.ts +52 -148
- package/src/model/generated/gemma4-26b.ts +34 -94
- package/src/model/generated/gemma4-e4b.ts +28 -76
- package/src/model/glm52-cache.ts +19 -9
- package/src/model/glm52-dsa.ts +13 -9
- package/src/model/glm52-memory.ts +28 -1
- package/src/model/implementation.ts +69 -0
- package/src/model/minicpm5.ts +7 -8
- package/src/model/profile.ts +443 -0
- package/src/model/qwen-append.ts +12 -0
- package/src/model/qwen-conv.ts +52 -0
- package/src/model/qwen3-delta.ts +98 -2
- package/src/model/qwen3-mrope.ts +252 -0
- package/src/model/qwen3_5-checkpoint.ts +164 -0
- package/src/model/qwen3_5.ts +336 -60
- package/src/model/support.ts +1 -1
- package/src/model/trellis-balanced-scatter.ts +51 -0
- package/src/model/trellis-linear.ts +672 -0
- package/src/model/trellis-shared-m.ts +151 -0
- package/src/model/trellis-shared-scatter.ts +144 -0
- package/src/model/trellis-splitk-prefill.ts +158 -0
- package/src/model/trellis-tiled-prefill.ts +136 -0
- package/src/model/trellis-vector-expand.ts +62 -0
- package/src/model/trellis-wide-prefill.ts +94 -0
- package/src/model/universal/dense.ts +1 -1
- package/src/model/universal/modules.ts +8 -1
- package/src/native/frame_extract.swift +140 -0
- package/src/native-pack.ts +15 -7
- package/src/pi-provider.ts +6 -64
- package/src/pi-web.ts +14 -320
- package/src/prompt-cache.ts +97 -54
- package/src/quantize/allocator.ts +1 -1
- package/src/quantize/atomic-output.ts +38 -0
- package/src/quantize/calibration.ts +2 -1
- package/src/quantize/config-writer.ts +16 -3
- package/src/quantize/index.ts +28 -1
- package/src/quantize/job.ts +11 -0
- package/src/quantize/quantizer.ts +265 -100
- package/src/quantize/rotate.ts +603 -0
- package/src/quantize/safetensors-writer.ts +79 -1
- package/src/quantize/sensitivity.ts +41 -59
- package/src/quantize/trellis.ts +586 -0
- package/src/quantize/weight-transform.ts +292 -0
- package/src/responses.ts +130 -3
- package/src/runtime-config.ts +81 -0
- package/src/sampler.ts +276 -4
- package/src/serve/admin-routes.ts +184 -0
- package/src/serve/aux-routes.ts +132 -0
- package/src/serve/batch-scheduler.ts +8 -1558
- package/src/serve/chat-request.ts +391 -0
- package/src/serve/chat-stage.ts +195 -0
- package/src/serve/checkpoint-identity.ts +64 -0
- package/src/serve/completion-executor.ts +354 -0
- package/src/serve/completion-sink.ts +157 -0
- package/src/serve/discovery-routes.ts +204 -0
- package/src/serve/generation-gateway.ts +211 -243
- package/src/serve/http.ts +159 -0
- package/src/serve/inference-request.ts +79 -0
- package/src/serve/isolate.ts +297 -105
- package/src/serve/kv-budget.ts +14 -0
- package/src/serve/lab-routes.ts +341 -0
- package/src/serve/lane-registry.ts +2 -1
- package/src/serve/model-admin-routes.ts +155 -0
- package/src/serve/model-binding.ts +43 -0
- package/src/serve/model-host.ts +512 -0
- package/src/serve/openai-wire.ts +103 -0
- package/src/serve/parent-application.ts +56 -0
- package/src/serve/pipeline.ts +33 -0
- package/src/serve/preparation.ts +33 -0
- package/src/serve/prompt-response-trace.ts +147 -0
- package/src/serve/request-plan.ts +162 -0
- package/src/serve/request-prep.ts +346 -0
- package/src/serve/response-lease.ts +24 -0
- package/src/serve/responses-client.ts +69 -0
- package/src/serve/session-completion-engine.ts +110 -0
- package/src/serve/session-search.ts +1 -1
- package/src/serve/static-routes.ts +101 -0
- package/src/serve/text-completion-stage.ts +104 -0
- package/src/serve/token-streams.ts +519 -0
- package/src/server.ts +523 -3551
- package/src/spec/assistant-source.ts +10 -44
- package/src/spec/deepspec-source.ts +6 -6
- package/src/spec/dflash-source.ts +9 -8
- package/src/spec/dspark/ab-stats.ts +3 -3
- package/src/spec/dspark/calibration.ts +2 -2
- package/src/spec/dspark/deepspec-module.ts +2 -2
- package/src/spec/dspark/generate-dflash.ts +2 -2
- package/src/spec/dspark/generate.ts +2 -2
- package/src/spec/dspark/loader.ts +1 -1
- package/src/spec/dspark/module-dflash.ts +5 -4
- package/src/spec/dspark/module.ts +3 -3
- package/src/spec/dspark/quantize-drafter.ts +1 -1
- package/src/spec/generate.ts +1 -1
- package/src/spec/glm52-mtp-source.ts +9 -1
- package/src/spec/ngram-source.ts +1 -1
- package/src/spec/prefix-state.ts +45 -0
- package/src/spec/qwen-mtp-source.ts +528 -0
- package/src/spec/serve-loop.ts +179 -213
- package/src/spec/source.ts +42 -10
- package/src/spec/two-model.ts +1 -1
- package/src/ssd-cache.ts +95 -24
- package/src/ssd-durability.ts +207 -0
- package/src/tool-call.ts +51 -14
- package/src/train/flash-cce.ts +14 -13
- package/src/train/forward.ts +38 -1
- package/src/train/loss.ts +3 -2
- package/src/train/prefix-shared.ts +5 -1
- package/src/train/segmented.ts +9 -6
- package/src/train/trainer.ts +9 -8
- package/src/vision/preprocess.ts +1 -1
- package/src/vision/prompt.ts +48 -2
- package/src/vision/qwen3vl-preprocess.ts +409 -0
- package/src/vision/qwen3vl-prompt.ts +196 -0
- package/src/vision/qwen3vl-tower.ts +555 -0
- package/src/vision/video-frames.ts +148 -0
- package/src/web/app.html +1 -1
- package/src/web/app.js +15 -6
- package/src/web/src/adapters-panel.ts +3 -3
- package/src/web/src/assistant.ts +2 -36
- package/src/web/src/chat.ts +16 -9
- package/src/web/src/composer.ts +4 -4
- package/src/web/src/hub.ts +1 -1
- package/src/web/src/markdown.ts +0 -0
- package/src/web/src/memory-panel.ts +1 -1
- package/src/web/src/model-picker.ts +1 -1
- package/src/web/src/palette.ts +1 -1
- package/src/web/src/protocol.ts +3 -39
- package/src/web/src/sessions.ts +1 -1
- package/src/web/src/ui-catalog.ts +4 -18
- package/src/web-assets.ts +47 -0
- package/src/weights.ts +134 -33
- package/docs/archive/mlx-bun-lab-report.html +0 -275
- package/docs/archive/wwdc-mlx-bun.html +0 -848
- package/docs/design/adapters-end-to-end.md +0 -205
- package/docs/design/batching-perf-path.md +0 -255
- package/docs/design/batching-v2-plan.md +0 -529
- package/docs/design/bucketing-stage.md +0 -197
- package/docs/design/compat-cli-surface-design.md +0 -230
- package/docs/design/docs-reorg-plan.md +0 -165
- package/docs/design/dspark-serving-program.md +0 -371
- package/docs/design/dspark-speculative-decoding.md +0 -268
- package/docs/design/grammar-spec-batching-integration.md +0 -301
- package/docs/design/memory-inference-path.md +0 -422
- package/docs/design/memory-synthesis.md +0 -287
- package/docs/design/memory-system.md +0 -237
- package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
- package/docs/design/omlx-adoption-map.md +0 -98
- package/docs/design/orpo-dynamic-lambda.md +0 -220
- package/docs/design/orpo-future-enhancements.md +0 -340
- package/docs/design/paged-kv-cache.md +0 -214
- package/docs/design/parallel-slots.md +0 -567
- package/docs/design/parity-tier-dag.md +0 -161
- package/docs/design/repo-cleanup-plan.md +0 -189
- package/docs/design/runtime-isolation.md +0 -263
- package/docs/design/segmented-backward-training.md +0 -543
- package/docs/design/spec-decode-larger-targets.md +0 -229
- package/docs/design/ssd-kv-cold-tier.md +0 -288
- package/docs/design/structured-output.md +0 -484
- package/docs/design/tauri-desktop-app.md +0 -127
- package/docs/design/the-dreaming-master-plan.md +0 -445
- package/docs/design/turboquant-kv.md +0 -235
- package/docs/design/w4a16-compute-precision-spike.md +0 -232
- package/docs/design/web-chat-beat-matrix.md +0 -315
- package/docs/design/write-pipeline-entity.md +0 -468
- package/docs/investigations/curve-runs/bisector-route.json +0 -98
- package/docs/investigations/curve-runs/bisector-trace.json +0 -1393
- package/docs/investigations/curve-runs/curve-terrain.json +0 -1179
- package/docs/investigations/curve-runs/curve-terrain.svg +0 -42
- package/docs/investigations/hlg-ab.html +0 -34
- package/docs/investigations/hlg-explorer.html +0 -238
- package/docs/investigations/hlg-figs/cliff.svg +0 -1
- package/docs/investigations/hlg-figs/cross.svg +0 -1
- package/docs/investigations/hlg-figs/edges.svg +0 -1
- package/docs/investigations/hlg-figs/k20-grids.svg +0 -1
- package/docs/investigations/hlg-figs/k20-peaks.svg +0 -1
- package/docs/investigations/hlg-figs/lw-curve.svg +0 -1
- package/docs/investigations/hlg-figs/terrain.svg +0 -1
- package/docs/investigations/hlg-report.html +0 -262
- package/docs/investigations/hlg-runs/hlg-ab.json +0 -149
- package/docs/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
- package/docs/investigations/hlg-runs/hlg-blind.json +0 -107
- package/docs/investigations/hlg-runs/hlg-cliff.json +0 -197
- package/docs/investigations/hlg-runs/hlg-cross.json +0 -505
- package/docs/investigations/hlg-runs/hlg-edge.json +0 -388
- package/docs/investigations/hlg-runs/hlg-eval1.json +0 -25
- package/docs/investigations/hlg-runs/hlg-frontier.json +0 -86
- package/docs/investigations/hlg-runs/hlg-grid.json +0 -242
- package/docs/investigations/hlg-runs/hlg-grid2.json +0 -343
- package/docs/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
- package/docs/investigations/hlg-runs/hlg-map.json +0 -383
- package/docs/investigations/hlg-runs/run0-original.log +0 -159
- package/docs/investigations/hlg-runs/runA-recommended.log +0 -131
- package/docs/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
- package/docs/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
- package/docs/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
- package/docs/investigations/hlg-runs/runAD-ab-samples.log +0 -8
- package/docs/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
- package/docs/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
- package/docs/investigations/hlg-runs/runAG-eval-config2.log +0 -16
- package/docs/investigations/hlg-runs/runB-gain-variants.log +0 -192
- package/docs/investigations/hlg-runs/runC-pivots.log +0 -162
- package/docs/investigations/hlg-runs/runD-oetf.log +0 -132
- package/docs/investigations/hlg-runs/runE-eotf.log +0 -162
- package/docs/investigations/hlg-runs/runF-shaper-working.log +0 -86
- package/docs/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
- package/docs/investigations/hlg-runs/runH-wsweep.log +0 -176
- package/docs/investigations/hlg-runs/runI-asweep.log +0 -176
- package/docs/investigations/hlg-runs/runJ-knobmap.log +0 -49
- package/docs/investigations/hlg-runs/runK-frontier.log +0 -19
- package/docs/investigations/hlg-runs/runL-widemap.log +0 -65
- package/docs/investigations/hlg-runs/runM-frontier-emb.log +0 -15
- package/docs/investigations/hlg-runs/runN-verify.log +0 -116
- package/docs/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
- package/docs/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
- package/docs/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
- package/docs/investigations/hlg-runs/runR-blind-transcript.log +0 -79
- package/docs/investigations/hlg-runs/runS-cross-coherence.log +0 -33
- package/docs/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
- package/docs/investigations/hlg-runs/runU-cross-12b.log +0 -33
- package/docs/investigations/hlg-runs/runV-lw-e4b.log +0 -22
- package/docs/investigations/hlg-runs/runW-lw-12b.log +0 -22
- package/docs/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
- package/docs/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
- package/docs/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
- package/docs/planning/IDEAS.md +0 -69
- package/docs/reference/embedding.md +0 -197
- package/docs/reference/features-matrix.md +0 -100
- package/docs/reference/orpo-quickstart.md +0 -135
- package/src/flags.ts +0 -13
- /package/docs/{investigations → archive/investigations}/chunk-finetune-experiment.md +0 -0
- /package/docs/{investigations → archive/investigations}/colibri-oracle-pin.md +0 -0
- /package/docs/{investigations → archive/investigations}/curve-bisector-routing.md +0 -0
- /package/docs/{investigations → archive/investigations}/decode-roofline-lookagain.md +0 -0
- /package/docs/{investigations → archive/investigations}/dspark-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/expert-offload-single-user-moe.md +0 -0
- /package/docs/{investigations → archive/investigations}/indexshare-performance-spike.md +0 -0
- /package/docs/{investigations → archive/investigations}/kernel-perf-review-2026-07.md +0 -0
- /package/docs/{investigations → archive/investigations}/lab-build-journal.md +0 -0
- /package/docs/{investigations → archive/investigations}/openwiki-evaluation.md +0 -0
- /package/docs/{investigations → archive/investigations}/orpo-base-uf-experiment-and-directions.md +0 -0
- /package/docs/{investigations → archive/investigations}/orpo-flash-cce-pin-leak.md +0 -0
- /package/docs/{investigations → archive/investigations}/orpo-uf-testing-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/pi-builtin-investigation.md +0 -0
- /package/docs/{investigations → archive/investigations}/segmented-backward-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/starter-model-port-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/steel-flash-cce-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/trainer-validation-experiment.md +0 -0
- /package/docs/{planning → archive/planning}/chat-ui-vision-memory-first.md +0 -0
- /package/docs/{planning → archive/planning}/journal.md +0 -0
- /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.0.10.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.0.11.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.0.12.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.0.13.md +0 -0
- /package/docs/{planning → archive/planning}/web-ui-pass-plan.md +0 -0
- /package/{docs/investigations → src/serve/assets}/curve-terrain.html +0 -0
- /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
package/src/cli.ts
CHANGED
|
@@ -13,7 +13,8 @@ import { existsSync } from "node:fs";
|
|
|
13
13
|
import { basename, join, resolve } from "node:path";
|
|
14
14
|
import { Registry } from "./registry";
|
|
15
15
|
import { loadModelConfig, parseTurboQuantScheme, type TurboQuantScheme } from "./config";
|
|
16
|
-
import { fit, skuMatrix, thisMachine } from "./fit";
|
|
16
|
+
import { fit, skuMatrix, thisMachine, type FitKvScheme } from "./fit";
|
|
17
|
+
import { resolveKvScheme } from "./kv-scheme";
|
|
17
18
|
import { EvalDB } from "./evaldb";
|
|
18
19
|
import pkg from "../package.json" with { type: "json" };
|
|
19
20
|
import { renderHelp } from "./tui";
|
|
@@ -27,8 +28,21 @@ import {
|
|
|
27
28
|
planGlm52MemoryForArtifact,
|
|
28
29
|
} from "./model/glm52-memory";
|
|
29
30
|
import type { JobEvent } from "./jobs/types";
|
|
31
|
+
import {
|
|
32
|
+
configureRuntime,
|
|
33
|
+
runtimeValue,
|
|
34
|
+
type RuntimeKey,
|
|
35
|
+
} from "./runtime-config";
|
|
30
36
|
|
|
31
37
|
const argv = process.argv.slice(2);
|
|
38
|
+
// Private self-exec protocol for managed jobs in the single-file binary.
|
|
39
|
+
// Literal imports in job-entry keep the numerical runners inside the bundle.
|
|
40
|
+
if (argv[0] === "__job") {
|
|
41
|
+
const { runJobEntry } = await import("./jobs/job-entry");
|
|
42
|
+
await runJobEntry(argv[1]);
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
|
|
32
46
|
// The appliance path: naked `mlx-bun` (or only options, e.g.
|
|
33
47
|
// `mlx-bun --port 9000`) runs `serve` — first run downloads a model and
|
|
34
48
|
// opens the chat UI. `--help`/`--version` and explicit subcommands still win.
|
|
@@ -97,13 +111,19 @@ const SERVER_FLAGS = `Server options:
|
|
|
97
111
|
restarts (long-context agent TTFT drops from a
|
|
98
112
|
full re-prefill to a zero-copy mmap restore).
|
|
99
113
|
Off unless set.
|
|
100
|
-
--ssd-cache-max <GB> SSD tier byte cap
|
|
114
|
+
--ssd-cache-max <GB> Optional SSD tier byte cap; 0 = unlimited
|
|
115
|
+
[default: unlimited]
|
|
101
116
|
--ssd-demote-idle <sec> Demote prompt-cache entries idle this long to the
|
|
102
117
|
SSD tier, freeing their GPU memory (prefixes stay
|
|
103
118
|
reachable via zero-copy restore). 0 disables.
|
|
104
119
|
[default: 300 when --ssd-cache is on]
|
|
105
120
|
--ssd-cache-verify Verify tensor hashes on every restore (reads all
|
|
106
121
|
bytes eagerly — integrity paranoia only)
|
|
122
|
+
--generation-checkpoint <tokens>
|
|
123
|
+
Snapshot an in-flight serial generation to the SSD
|
|
124
|
+
cache every N emitted tokens. Repeating the same
|
|
125
|
+
request after restart replays and resumes it.
|
|
126
|
+
Requires --ssd-cache and --batch 1.
|
|
107
127
|
--isolate Run the inference engine as a CHILD process and
|
|
108
128
|
keep this process a pure UI/API proxy — the UI
|
|
109
129
|
stays instant under GPU load and survives engine
|
|
@@ -118,10 +138,10 @@ const SERVER_FLAGS = `Server options:
|
|
|
118
138
|
--unix <path> (internal) listen on a unix socket instead of a
|
|
119
139
|
TCP port — the engine half of --isolate.
|
|
120
140
|
--batch <n> Max concurrent requests decoded together
|
|
121
|
-
[default: 8]. A
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
141
|
+
[default: 8]. A lone scheduler request uses the
|
|
142
|
+
parity- and latency-gated B=1 fast path; concurrent
|
|
143
|
+
requests can form B=N (agent fan-out). 1 pins the
|
|
144
|
+
preserved strict serial executor.
|
|
125
145
|
--decode-concurrency is accepted as the
|
|
126
146
|
mlx_lm.server alias (semantics differ; see
|
|
127
147
|
docs/reference/server-config.md)
|
|
@@ -152,9 +172,13 @@ Model & quality:
|
|
|
152
172
|
Serial lane only — with --batch N a mounted draft
|
|
153
173
|
routes every request serial, like mlx_lm.server.
|
|
154
174
|
--draft-kind <kind> Override draft-artifact detection:
|
|
155
|
-
two-model | assistant | dspark | deepspec | ngram
|
|
175
|
+
two-model | assistant | dspark | deepspec | mtp | ngram
|
|
156
176
|
(deepspec = DeepSeek's released Gemma4DSparkModel
|
|
157
|
-
drafters;
|
|
177
|
+
drafters; mtp = a NATIVE multi-token-prediction
|
|
178
|
+
head split from the target's own release, e.g.
|
|
179
|
+
mlx-community/Qwen3.8-27B-MTP-bf16 — auto-detected
|
|
180
|
+
from model_type *_mtp;
|
|
181
|
+
ngram = MODEL-FREE prompt lookup — no
|
|
158
182
|
--draft-model, drafts copied from the request's
|
|
159
183
|
own context, lossless) [default: auto]
|
|
160
184
|
--num-draft-tokens <n> Drafts per verify round [default: 3; ngram: 10;
|
|
@@ -171,7 +195,7 @@ Model & quality:
|
|
|
171
195
|
(bf16), 4 / 8 (uniform bits), or turbo[:k<bits>v
|
|
172
196
|
<bits>] (rotation-based TurboQuant — default
|
|
173
197
|
k8v3; kBits in {2,4,5,8}, vBits in {2,3,4,5,8};
|
|
174
|
-
docs/design/turboquant
|
|
198
|
+
docs/design/turboquant.md)
|
|
175
199
|
[default: off — quantized KV measured 5-20% slower
|
|
176
200
|
decode than bf16 at ≤16k (on mlx-lm too); it buys
|
|
177
201
|
memory headroom, so opt in when context is tight.
|
|
@@ -186,7 +210,7 @@ Model & quality:
|
|
|
186
210
|
--kv-quant, --draft-model; bypasses the prompt
|
|
187
211
|
cache; runs uncompiled decode. Bit-exact with the
|
|
188
212
|
plain path; expect a small decode cost at batch=1
|
|
189
|
-
(the gather copy). docs/design/
|
|
213
|
+
(the gather copy). docs/design/kv-cache.md
|
|
190
214
|
[default: off]
|
|
191
215
|
--paged-kv-block-size <n> Tokens per KV block [default: 256 = the plain
|
|
192
216
|
cache's growth step]
|
|
@@ -202,13 +226,14 @@ Model & quality:
|
|
|
202
226
|
pass --temp 0 for its unset-request behavior)
|
|
203
227
|
[default: the model's generation_config.json]
|
|
204
228
|
--max-tokens <n> Completion cap when a request omits max_tokens
|
|
205
|
-
(mlx_lm.server flag;
|
|
206
|
-
[default: 128 GLM-5.2
|
|
207
|
-
|
|
229
|
+
(mlx_lm.server flag; --max-tokens 512 reproduces
|
|
230
|
+
its default) [default: 128 GLM-5.2 (memory-plan
|
|
231
|
+
reservation); otherwise none — a defaulted
|
|
232
|
+
request runs to EOS or the admitted context]
|
|
208
233
|
--hlg-sampling on|off Piecewise tone-curve sampling (HLG): rolls off the
|
|
209
234
|
top, boosts the mids, gentles the tail. Gain folds
|
|
210
235
|
from --temperature. [default: off]
|
|
211
|
-
See docs/
|
|
236
|
+
See docs/archive/hlg-sampling.md
|
|
212
237
|
--hlg-width <nats> HLG mid-region half-width [default: 4]
|
|
213
238
|
--hlg-shoulder <nats> HLG highlight rolloff scale [default: 4]
|
|
214
239
|
--hlg-toe <nats> HLG shadow rolloff scale [default: 6]
|
|
@@ -343,6 +368,8 @@ Usage: mlx-bun fit <query> [options]
|
|
|
343
368
|
|
|
344
369
|
Options:
|
|
345
370
|
--ctx <tokens> Context size to assess [default: 8192]
|
|
371
|
+
--kv-quant <mode> Bill the KV cache quantized: 4 | 8 | config | off
|
|
372
|
+
(mirrors serve --kv-quant; default bf16)
|
|
346
373
|
--skus Also print the Apple silicon SKU matrix
|
|
347
374
|
|
|
348
375
|
Solves weights + KV growth + prefill transient against wired memory and
|
|
@@ -379,7 +406,7 @@ Usage: mlx-bun benchmark [query] [options]
|
|
|
379
406
|
|
|
380
407
|
Runs mlx-bun by itself (no other systems required) and records the
|
|
381
408
|
result to the eval DB. For quotable, cross-stack numbers use
|
|
382
|
-
|
|
409
|
+
bun scripts/bench-serve.ts all from the repo — it preflight-gates on an idle machine
|
|
383
410
|
and runs the mlx-lm/optiq comparison legs.
|
|
384
411
|
|
|
385
412
|
[query] Model to benchmark (--query <q> is accepted for the
|
|
@@ -463,7 +490,7 @@ Options:
|
|
|
463
490
|
--clear Archive the eval DB (timestamped backup) and start fresh
|
|
464
491
|
|
|
465
492
|
Table view: when, model, bench kind, KV mode, decode tok/s, TTFT,
|
|
466
|
-
peak memory, commit. Runs are written by
|
|
493
|
+
peak memory, commit. Runs are written by bun scripts/bench-serve.ts all.`,
|
|
467
494
|
|
|
468
495
|
train: `mlx-bun train — fine-tune a LoRA adapter on your data
|
|
469
496
|
|
|
@@ -576,6 +603,9 @@ knapsack per-layer bit allocation; implies quantization, no -q needed):
|
|
|
576
603
|
--candidate-bits <l> Comma list the knapsack may pick from [default: 4,8]
|
|
577
604
|
--calibration-mix <m> "optiq" or a JSONL path [default: optiq]
|
|
578
605
|
--n-calibration <n> Calibration samples [default: 2]
|
|
606
|
+
--rotate-weights Fold the model's offline TurboQuant rotation before
|
|
607
|
+
quantization (auto-detects Llama/Qwen3.5/Qwen MTP)
|
|
608
|
+
--rotation-seed <n> Deterministic rotation seed [default: 42]
|
|
579
609
|
|
|
580
610
|
Not supported (mlx_lm.convert flags we don't implement — the command
|
|
581
611
|
exits with an error rather than guessing): --dtype, -d/--dequantize,
|
|
@@ -805,19 +835,19 @@ function openChatUi(url: string, hostPort: string): void {
|
|
|
805
835
|
}
|
|
806
836
|
|
|
807
837
|
/** Server/runtime flags shared by every mode that loads a model
|
|
808
|
-
* (serve, pi).
|
|
838
|
+
* (serve, pi). Runtime overrides are installed here before the
|
|
809
839
|
* generate/compiled-decode modules read them. */
|
|
810
840
|
/** Resolve the decode ROUTE: a tier alias (--l1/--l2) sets the whole route,
|
|
811
841
|
* and an explicit per-fork flag (--kv-quant/--fused-sdpa/…) overrides the
|
|
812
|
-
* alias.
|
|
813
|
-
* docs/design/
|
|
842
|
+
* alias. Installs decode runtime options and returns the kv-quant mode. See
|
|
843
|
+
* docs/design/unified-engine-frontier-plan.md + unified-engine-frontier-plan.md.
|
|
814
844
|
* Each tier is a GUARANTEE about which reference you reproduce bit-for-bit:
|
|
815
845
|
* --l1 bit-for-bit IDENTICAL to mlx-lm — drop-in replacement for mlx-lm
|
|
816
846
|
* (bf16 KV; THE naked default since 2026-07-05)
|
|
817
847
|
* --l2 bit-for-bit IDENTICAL to mlx-optiq — drop-in replacement for
|
|
818
848
|
* mlx-optiq: quantized KV per kv_config.json + fused N-tiled SDPA for
|
|
819
849
|
* L>1 + STOCK unfused L=1 decode (the optiq-golden composition,
|
|
820
|
-
* scripts/regen
|
|
850
|
+
* scripts/regen.ts kvq + regen-mixed-kv-goldens.ts).
|
|
821
851
|
* There is no --l3: output-changing experiments live in the Lab (env flags
|
|
822
852
|
* with a bench + expiry) until one beats the L1 baseline in a paired A/B —
|
|
823
853
|
* docs/design/unified-engine-frontier-plan.md §6-7. */
|
|
@@ -863,8 +893,9 @@ function applyDecodeRoute(): { kvQuant?: "off" | "config" | number; turboQuant?:
|
|
|
863
893
|
const pick = (name: string, base: boolean | undefined): boolean | null => {
|
|
864
894
|
const ex = onOff(name); return ex !== null ? ex : (base ?? null); // explicit flag wins, else tier preset
|
|
865
895
|
};
|
|
866
|
-
const set = (
|
|
867
|
-
if (val !== null)
|
|
896
|
+
const set = (name: RuntimeKey, val: boolean | null, invert = false) => {
|
|
897
|
+
if (val !== null)
|
|
898
|
+
configureRuntime({ [name]: (invert ? !val : val) ? "1" : "0" });
|
|
868
899
|
};
|
|
869
900
|
// Explicit --kv-quant picks the composition its ORACLE uses: config (the
|
|
870
901
|
// optiq mixed scheme) runs optiq's fused SDPA (the L2 golden composition);
|
|
@@ -874,14 +905,14 @@ function applyDecodeRoute(): { kvQuant?: "off" | "config" | number; turboQuant?:
|
|
|
874
905
|
set("MLX_BUN_COMPILED_DECODE", pick("compiled-decode", p.compiled));
|
|
875
906
|
set("MLX_BUN_NO_FUSED_SDPA", pick("fused-sdpa", fusedSdpaBase), true); // inverted env
|
|
876
907
|
// Compiled activations (the faithful geglu/swiglu — mlx-lm's @mx.compile kernel).
|
|
877
|
-
// One fork drives both
|
|
908
|
+
// One fork drives both runtime options; qwen3/qwen3.5/universal compile unconditionally,
|
|
878
909
|
// so this toggles gemma geglu + minicpm5 swiglu (the only unfused-capable sites).
|
|
879
910
|
set("MLX_BUN_COMPILED_GEGLU", pick("compiled-activations", p.compiledAct));
|
|
880
911
|
set("MLX_BUN_COMPILED_SWIGLU", pick("compiled-activations", p.compiledAct));
|
|
881
912
|
if (kv === "off") return { kvQuant: "off" };
|
|
882
913
|
if (kv === "config") return { kvQuant: "config" };
|
|
883
914
|
if (kv) {
|
|
884
|
-
// TurboQuant (docs/design/turboquant
|
|
915
|
+
// TurboQuant (docs/design/turboquant.md): a distinct axis from the
|
|
885
916
|
// affine kvQuant above — dequantize-on-fetch, stock unfused ops.sdpa,
|
|
886
917
|
// no kvQuant value at all (mutually exclusive with kvBits/kvConfig).
|
|
887
918
|
let turboQuant: TurboQuantScheme | null;
|
|
@@ -909,11 +940,12 @@ function serverRuntimeFlags(): { port: number; serverOptions: import("./server")
|
|
|
909
940
|
process.exit(1);
|
|
910
941
|
};
|
|
911
942
|
const route = applyDecodeRoute(); // --l1/--l2 tier alias, with per-fork flags overriding
|
|
912
|
-
if (flag("force-wire"))
|
|
943
|
+
if (flag("force-wire")) configureRuntime({ MLX_BUN_FORCE_WIRE: "1" });
|
|
913
944
|
// Remote image_url/audio_url media fetches block private/loopback/link-
|
|
914
945
|
// local destinations by default (SSRF — src/media-fetch.ts); this is the
|
|
915
946
|
// LAN-hosts escape hatch.
|
|
916
|
-
if (flag("allow-private-media"))
|
|
947
|
+
if (flag("allow-private-media"))
|
|
948
|
+
configureRuntime({ MLX_BUN_ALLOW_PRIVATE_MEDIA: "1" });
|
|
917
949
|
|
|
918
950
|
const serverOptions: import("./server").ServerOptions = {};
|
|
919
951
|
const budgetGB = Number(opt("memory-budget", "0"));
|
|
@@ -923,29 +955,47 @@ function serverRuntimeFlags(): { port: number; serverOptions: import("./server")
|
|
|
923
955
|
if (kvBudgetGB > 0) serverOptions.kvBudgetBytes = kvBudgetGB * 1e9;
|
|
924
956
|
const pcRaw = opt("prompt-cache"); // null if absent; an explicit value (incl. 0) wins → `--prompt-cache 0` DISABLES the cache
|
|
925
957
|
if (pcRaw !== null) serverOptions.promptCacheBytes = Math.max(0, Number(pcRaw)) * 2 ** 30;
|
|
926
|
-
// Engine-child mode (
|
|
958
|
+
// Engine-child mode (docs/reference/server-config.md): listen on a unix socket
|
|
927
959
|
// instead of TCP — the isolated parent proxies here. Internal flag,
|
|
928
960
|
// spawned by --isolate; usable directly for socket-level integration.
|
|
929
961
|
const unixRaw = opt("unix");
|
|
930
962
|
if (unixRaw !== null) serverOptions.unixSocket = unixRaw;
|
|
931
|
-
// SSD cold tier (docs/design/
|
|
963
|
+
// SSD cold tier (docs/design/kv-cache.md) — off unless a dir is given.
|
|
932
964
|
const ssdDir = opt("ssd-cache");
|
|
933
965
|
if (ssdDir !== null) {
|
|
934
966
|
serverOptions.ssdCacheDir = ssdDir;
|
|
935
967
|
const maxRaw = opt("ssd-cache-max");
|
|
936
|
-
if (maxRaw !== null)
|
|
968
|
+
if (maxRaw !== null) {
|
|
969
|
+
const maxGb = Number(maxRaw);
|
|
970
|
+
if (!Number.isFinite(maxGb) || maxGb < 0) {
|
|
971
|
+
console.error(`--ssd-cache-max expects a number >= 0 (got "${maxRaw}")`);
|
|
972
|
+
process.exit(1);
|
|
973
|
+
}
|
|
974
|
+
serverOptions.ssdCacheMaxBytes = maxGb === 0
|
|
975
|
+
? Number.POSITIVE_INFINITY
|
|
976
|
+
: maxGb * 2 ** 30;
|
|
977
|
+
}
|
|
937
978
|
if (flag("ssd-cache-verify")) serverOptions.ssdCacheVerify = true;
|
|
938
979
|
const demoteRaw = opt("ssd-demote-idle");
|
|
939
980
|
if (demoteRaw !== null) serverOptions.ssdDemoteIdleSec = Math.max(0, Number(demoteRaw));
|
|
981
|
+
const checkpointRaw = opt("generation-checkpoint");
|
|
982
|
+
if (checkpointRaw !== null) {
|
|
983
|
+
const n = Number(checkpointRaw);
|
|
984
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
985
|
+
console.error(`--generation-checkpoint expects an integer >= 1 (got "${checkpointRaw}")`);
|
|
986
|
+
process.exit(1);
|
|
987
|
+
}
|
|
988
|
+
serverOptions.generationCheckpointTokens = n;
|
|
989
|
+
}
|
|
940
990
|
} else {
|
|
941
991
|
// Refuse-loudly house rule: sub-flags of an absent tier must not vanish.
|
|
942
|
-
for (const f of ["ssd-cache-max", "ssd-demote-idle"] as const)
|
|
992
|
+
for (const f of ["ssd-cache-max", "ssd-demote-idle", "generation-checkpoint"] as const)
|
|
943
993
|
if (opt(f) !== null) console.warn(`--${f} has no effect without --ssd-cache — ignored`);
|
|
944
994
|
if (flag("ssd-cache-verify"))
|
|
945
995
|
console.warn("--ssd-cache-verify has no effect without --ssd-cache — ignored");
|
|
946
996
|
}
|
|
947
997
|
// --batch N: max concurrent requests batched through the mlx-lm-parity
|
|
948
|
-
// engine (N=1
|
|
998
|
+
// engine (N=1 pins the strict serial executor). --decode-concurrency is accepted for
|
|
949
999
|
// drop-in compatibility with mlx_lm.server, but the semantics differ: there
|
|
950
1000
|
// it caps per-BatchGenerator decode parallelism (default 32); in mlx-bun it
|
|
951
1001
|
// caps continuous batching (default 8; `--batch 1` pins the serial path —
|
|
@@ -962,10 +1012,10 @@ function serverRuntimeFlags(): { port: number; serverOptions: import("./server")
|
|
|
962
1012
|
if (route.kvQuant !== undefined) serverOptions.kvQuant = route.kvQuant;
|
|
963
1013
|
if (route.turboQuant !== undefined) serverOptions.turboQuant = route.turboQuant;
|
|
964
1014
|
// --paged-kv (env: MLX_BUN_PAGED_KV=1): OPTIONAL vLLM-style block-pool KV
|
|
965
|
-
// storage, default off (docs/design/
|
|
1015
|
+
// storage, default off (docs/design/kv-cache.md). Serial-only in v1:
|
|
966
1016
|
// with no explicit --batch, pin --batch 1 (the default is 8); an explicit
|
|
967
1017
|
// --batch N>1 is refused loudly by createServer rather than downgraded.
|
|
968
|
-
if (flag("paged-kv") ||
|
|
1018
|
+
if (flag("paged-kv") || runtimeValue("MLX_BUN_PAGED_KV") === "1") {
|
|
969
1019
|
const bsRaw = opt("paged-kv-block-size");
|
|
970
1020
|
serverOptions.pagedKv = bsRaw !== null ? { blockSize: Number(bsRaw) } : {};
|
|
971
1021
|
if (batchRaw === null) {
|
|
@@ -1009,7 +1059,7 @@ function serverRuntimeFlags(): { port: number; serverOptions: import("./server")
|
|
|
1009
1059
|
const maxTok = numFlag("max-tokens", 1, 10_000_000);
|
|
1010
1060
|
if (maxTok !== null) serverOptions.defaultMaxTokens = Math.floor(maxTok);
|
|
1011
1061
|
// HLG sampling — piecewise tone curve on the logprobs (default off). Knobs in
|
|
1012
|
-
// nats; the mid gain folds from --temperature. docs/
|
|
1062
|
+
// nats; the mid gain folds from --temperature. docs/archive/hlg-sampling.md.
|
|
1013
1063
|
if (onOff("hlg-sampling") === true) {
|
|
1014
1064
|
serverOptions.hlg = {
|
|
1015
1065
|
enabled: true,
|
|
@@ -1086,7 +1136,7 @@ async function mountStartupAdapter(
|
|
|
1086
1136
|
function runtimeSummary(o: import("./server").ServerOptions): string {
|
|
1087
1137
|
const kv = o.turboQuant ? `turbo-k${o.turboQuant.kBits}v${o.turboQuant.vBits}`
|
|
1088
1138
|
: o.kvQuant === "off" ? "off" : typeof o.kvQuant === "number" ? `kv${o.kvQuant}` : "config";
|
|
1089
|
-
const lever = (
|
|
1139
|
+
const lever = (name: RuntimeKey, dflt: string) => runtimeValue(name) ?? dflt;
|
|
1090
1140
|
return `kv-quant ${kv} · compiled-decode ${lever("MLX_BUN_COMPILED_DECODE", "1") === "1" ? "on" : "off"}` +
|
|
1091
1141
|
(lever("MLX_BUN_COMPILED_GEGLU", "1") === "0" ? " · compiled-activations off" : "") +
|
|
1092
1142
|
(o.batch && o.batch > 1 ? ` · batch ${o.batch}` : "") +
|
|
@@ -1376,7 +1426,7 @@ switch (cmd) {
|
|
|
1376
1426
|
|
|
1377
1427
|
case "fit": {
|
|
1378
1428
|
const query = positional(0);
|
|
1379
|
-
if (!query) { console.error("usage: mlx-bun fit <query> [--ctx N] [--skus]"); process.exit(1); }
|
|
1429
|
+
if (!query) { console.error("usage: mlx-bun fit <query> [--ctx N] [--kv-quant 4|8|config] [--skus]"); process.exit(1); }
|
|
1380
1430
|
const reg = new Registry();
|
|
1381
1431
|
if (reg.list().length === 0) await reg.scan();
|
|
1382
1432
|
const m = reg.resolve(query);
|
|
@@ -1443,9 +1493,32 @@ switch (cmd) {
|
|
|
1443
1493
|
console.log();
|
|
1444
1494
|
break;
|
|
1445
1495
|
}
|
|
1446
|
-
|
|
1496
|
+
// --kv-quant mirrors serve: bill the quantized cache's true bytes so
|
|
1497
|
+
// the reported window matches what serving with the same flag admits.
|
|
1498
|
+
const kvQuantOpt = opt("kv-quant");
|
|
1499
|
+
let fitKvScheme: FitKvScheme | undefined;
|
|
1500
|
+
if (kvQuantOpt === "4" || kvQuantOpt === "8") {
|
|
1501
|
+
fitKvScheme = resolveKvScheme({ override: Number(kvQuantOpt) }).fitOptions;
|
|
1502
|
+
} else if (kvQuantOpt === "config") {
|
|
1503
|
+
if (!config.kvQuant?.length) {
|
|
1504
|
+
console.error(`${m.repoId} ships no per-layer kv-quant config — use --kv-quant 4|8`);
|
|
1505
|
+
process.exit(1);
|
|
1506
|
+
}
|
|
1507
|
+
fitKvScheme = resolveKvScheme({
|
|
1508
|
+
override: "config",
|
|
1509
|
+
config: config.kvQuant,
|
|
1510
|
+
missingConfig: "error",
|
|
1511
|
+
}).fitOptions;
|
|
1512
|
+
} else if (kvQuantOpt != null && kvQuantOpt !== "off") {
|
|
1513
|
+
console.error(`--kv-quant ${kvQuantOpt}: expected 4, 8, config, or off`);
|
|
1514
|
+
process.exit(1);
|
|
1515
|
+
}
|
|
1516
|
+
const r = fit(config, m.sizeBytes, ctx, thisMachine(), undefined, m.expertsBytes, undefined, fitKvScheme);
|
|
1447
1517
|
h1("will it fit?");
|
|
1448
|
-
|
|
1518
|
+
const kvNote = fitKvScheme
|
|
1519
|
+
? style.dim(` · kv-quant ${fitKvScheme.kvBits ? `${fitKvScheme.kvBits}-bit` : "config"}`)
|
|
1520
|
+
: "";
|
|
1521
|
+
console.log(` ${style.bold(m.repoId)} ${style.dim(`@ ${ctx.toLocaleString()} context · this machine`)}${kvNote}`);
|
|
1449
1522
|
console.log();
|
|
1450
1523
|
const expertsNote = config.text.enableMoeBlock && m.expertsBytes > 0
|
|
1451
1524
|
? style.dim(` (experts ${gb(m.expertsBytes)}; top ${config.text.topKExperts}/${config.text.numExperts} read per token)`)
|
|
@@ -1470,7 +1543,7 @@ switch (cmd) {
|
|
|
1470
1543
|
{ header: "fits", paint: (c) => (c.includes("fits") ? style.green(c) : style.dim(c)) },
|
|
1471
1544
|
{ header: "max context", align: "right" }, { header: "decode", align: "right" },
|
|
1472
1545
|
],
|
|
1473
|
-
skuMatrix(config, m.sizeBytes, ctx, m.expertsBytes).map((row) => [
|
|
1546
|
+
skuMatrix(config, m.sizeBytes, ctx, m.expertsBytes, fitKvScheme).map((row) => [
|
|
1474
1547
|
row.sku, `${row.ramGB} GB`, row.fits ? "fits" : "—",
|
|
1475
1548
|
row.fits ? row.maxContext.toLocaleString() : "—",
|
|
1476
1549
|
row.fits ? `~${row.decodeTps.toFixed(0)} tok/s` : "—",
|
|
@@ -1498,7 +1571,7 @@ switch (cmd) {
|
|
|
1498
1571
|
process.exit(1);
|
|
1499
1572
|
}
|
|
1500
1573
|
}
|
|
1501
|
-
// Runtime isolation (--isolate,
|
|
1574
|
+
// Runtime isolation (--isolate, docs/reference/server-config.md): THIS process
|
|
1502
1575
|
// becomes a pure reactor — it binds the TCP port and reverse-proxies to
|
|
1503
1576
|
// an engine CHILD (the full server on a unix socket, spawned from the
|
|
1504
1577
|
// same argv minus parent-only flags). Zero MLX calls in the parent: the
|
|
@@ -1540,7 +1613,7 @@ switch (cmd) {
|
|
|
1540
1613
|
};
|
|
1541
1614
|
const poolMaxRaw = opt("model-pool");
|
|
1542
1615
|
const poolMax = Math.max(1, Number(poolMaxRaw ?? 1) || 1);
|
|
1543
|
-
const { engine,
|
|
1616
|
+
const { engine, close } = startProxyServer({
|
|
1544
1617
|
port: rt.port,
|
|
1545
1618
|
...(rt.serverOptions.hostname ? { hostname: rt.serverOptions.hostname } : {}),
|
|
1546
1619
|
engine: {
|
|
@@ -1556,7 +1629,7 @@ switch (cmd) {
|
|
|
1556
1629
|
const shownHost0 = rt.serverOptions.hostname ?? "localhost";
|
|
1557
1630
|
console.log(style.dim(` isolated mode: engine child starting (socket ${sock})`));
|
|
1558
1631
|
console.log(` ${style.green("●")} ${style.bold("proxy up")} http://${shownHost0}:${rt.port} ${style.dim(`· ${m0.repoId} loading behind it · model pool ${poolMax}`)}`);
|
|
1559
|
-
const onExit = () => {
|
|
1632
|
+
const onExit = () => { void close().finally(() => process.exit(0)); };
|
|
1560
1633
|
process.on("SIGINT", onExit);
|
|
1561
1634
|
process.on("SIGTERM", onExit);
|
|
1562
1635
|
await engine.ready;
|
|
@@ -1573,10 +1646,29 @@ switch (cmd) {
|
|
|
1573
1646
|
const config = await loadModelConfig(m.path);
|
|
1574
1647
|
const report = fit(config, m.sizeBytes, 8192, undefined, undefined, m.expertsBytes);
|
|
1575
1648
|
sFit.done(`${style.bold(m.repoId)} ${style.dim(`· ${gb(m.sizeBytes)} · ~${report.predictedDecodeTps.toFixed(0)} tok/s predicted`)}${picked ? style.dim(" · auto-picked (override: mlx-bun serve <query>)") : ""}`);
|
|
1649
|
+
// Near-ceiling advisory (admission doctrine: advise, never refuse). A
|
|
1650
|
+
// model near the DEFAULT Metal wired ceiling (~75% of RAM) dies with an
|
|
1651
|
+
// UNCATCHABLE async-GPU-OOM on long prefills (M4 24 GB × 17 GB model,
|
|
1652
|
+
// 2026-08-20) — surface the standard remedy up front instead.
|
|
1653
|
+
{
|
|
1654
|
+
const { maxRecommendedWorkingSetSize } = await import("./mlx/ffi");
|
|
1655
|
+
const ceiling = maxRecommendedWorkingSetSize();
|
|
1656
|
+
const sysctlRaw = Bun.spawnSync(["sysctl", "-n", "iogpu.wired_limit_mb"]).stdout.toString().trim();
|
|
1657
|
+
const limitIsDefault = sysctlRaw === "0" || sysctlRaw === "";
|
|
1658
|
+
if (limitIsDefault && ceiling > 0 && m.sizeBytes > 0.8 * ceiling) {
|
|
1659
|
+
const ramGb = Number(Bun.spawnSync(["sysctl", "-n", "hw.memsize"]).stdout.toString().trim()) / 2 ** 30;
|
|
1660
|
+
const suggestMb = Math.floor((ramGb - 2.5) * 1024);
|
|
1661
|
+
console.error(style.dim(
|
|
1662
|
+
` ! ${gb(m.sizeBytes)} model vs ~${gb(ceiling)} default GPU ceiling — long prompts can\n` +
|
|
1663
|
+
` hit an uncatchable Metal OOM. If that happens, raise the limit first:\n` +
|
|
1664
|
+
` sudo sysctl iogpu.wired_limit_mb=${suggestMb} (resets on reboot)`,
|
|
1665
|
+
));
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1576
1668
|
const sNative = step("native runtime");
|
|
1577
1669
|
await ensureNative(sNative);
|
|
1578
1670
|
sNative.done("native runtime ready");
|
|
1579
|
-
const { createServer, loadContext } = await import("./server");
|
|
1671
|
+
const { createServer, loadContext, shutdownServer } = await import("./server");
|
|
1580
1672
|
if (flag("expert-offload")) {
|
|
1581
1673
|
if (m.expertsBytes === 0) {
|
|
1582
1674
|
console.error(style.dim("--expert-offload ignored: this model has no experts (dense)"));
|
|
@@ -1609,9 +1701,9 @@ switch (cmd) {
|
|
|
1609
1701
|
}
|
|
1610
1702
|
const draftKindRaw = opt("draft-kind");
|
|
1611
1703
|
const draftKind = (draftKindRaw ?? undefined) as
|
|
1612
|
-
| "dspark" | "deepspec" | "assistant" | "two-model" | "ngram" | undefined;
|
|
1613
|
-
if (draftKind !== undefined && !["dspark", "deepspec", "assistant", "two-model", "ngram"].includes(draftKind)) {
|
|
1614
|
-
console.error(`--draft-kind expects two-model|assistant|dspark|deepspec|ngram (got "${draftKindRaw}")`);
|
|
1704
|
+
| "dspark" | "deepspec" | "assistant" | "two-model" | "ngram" | "mtp" | undefined;
|
|
1705
|
+
if (draftKind !== undefined && !["dspark", "deepspec", "assistant", "two-model", "ngram", "mtp"].includes(draftKind)) {
|
|
1706
|
+
console.error(`--draft-kind expects two-model|assistant|dspark|deepspec|mtp|ngram (got "${draftKindRaw}")`);
|
|
1615
1707
|
process.exit(1);
|
|
1616
1708
|
}
|
|
1617
1709
|
// Prompt-lookup window bounds (ngram kind only; validated here so a typo
|
|
@@ -1639,7 +1731,10 @@ switch (cmd) {
|
|
|
1639
1731
|
const ctx = await loadContext(m.path, m.repoId, {
|
|
1640
1732
|
memoryBudgetBytes: rt.serverOptions.memoryBudgetBytes,
|
|
1641
1733
|
glm: glm52RuntimeFlags(rt.serverOptions),
|
|
1642
|
-
|
|
1734
|
+
// ngram is model-free; mtp resolves the bundled <model>/mtp/ companion
|
|
1735
|
+
// in loadContext when no --draft-model is given (cli.md contract —
|
|
1736
|
+
// this gate silently dropped that case until 2026-08-20).
|
|
1737
|
+
...(draftModelDir || draftKind === "ngram" || draftKind === "mtp"
|
|
1643
1738
|
? {
|
|
1644
1739
|
...(draftModelDir ? { draftModelDir } : {}),
|
|
1645
1740
|
numDraftTokens,
|
|
@@ -1652,6 +1747,30 @@ switch (cmd) {
|
|
|
1652
1747
|
sLoad.done(`weights loaded ${style.dim(`in ${(performance.now() - t0).toFixed(0)} ms`)}${draftModelDir ? style.dim(` · draft: ${draftModelDir.split("/").filter(Boolean).at(-1)}`) : draftKind === "ngram" ? style.dim(" · draft: ngram (prompt lookup)") : ""}`);
|
|
1653
1748
|
await mountStartupAdapter(ctx, rt.serverOptions);
|
|
1654
1749
|
const server = createServer(ctx, rt.port, { ...rt.serverOptions, owner: "serve" });
|
|
1750
|
+
let shutdownStarted = false;
|
|
1751
|
+
const onServeSignal = (signal: "SIGINT" | "SIGTERM"): void => {
|
|
1752
|
+
if (shutdownStarted) return;
|
|
1753
|
+
shutdownStarted = true;
|
|
1754
|
+
const rawTimeout = Number(runtimeValue("MLX_BUN_SHUTDOWN_TIMEOUT_MS"));
|
|
1755
|
+
const timeoutMs = Number.isFinite(rawTimeout) && rawTimeout > 0 ? rawTimeout : 120_000;
|
|
1756
|
+
console.log(`\n[server] ${signal}: flushing prompt-cache durability`);
|
|
1757
|
+
void shutdownServer(server, timeoutMs).then((result) => {
|
|
1758
|
+
if (!result.durability.durable) {
|
|
1759
|
+
const d = result.durability;
|
|
1760
|
+
console.warn(
|
|
1761
|
+
`[server] cache flush incomplete after ${Math.round(d.elapsedMs)} ms: ` +
|
|
1762
|
+
`${d.pendingSnapshots} snapshots, ${d.pendingSpills} spills, ` +
|
|
1763
|
+
`${d.droppedSpills} dropped, ${d.failedSpills} failed`,
|
|
1764
|
+
);
|
|
1765
|
+
}
|
|
1766
|
+
process.exit(0);
|
|
1767
|
+
}).catch((error) => {
|
|
1768
|
+
console.warn(`[server] shutdown flush failed: ${(error as Error).message}`);
|
|
1769
|
+
process.exit(0);
|
|
1770
|
+
});
|
|
1771
|
+
};
|
|
1772
|
+
process.once("SIGINT", () => onServeSignal("SIGINT"));
|
|
1773
|
+
process.once("SIGTERM", () => onServeSignal("SIGTERM"));
|
|
1655
1774
|
const shownHost = rt.serverOptions.hostname ?? "localhost";
|
|
1656
1775
|
console.log();
|
|
1657
1776
|
box([
|
|
@@ -1682,7 +1801,7 @@ switch (cmd) {
|
|
|
1682
1801
|
// three entry points (raw / OpenAI API / chat UI) differ only in how params
|
|
1683
1802
|
// are POPULATED; here every param is explicit. Decode-path levers mirror
|
|
1684
1803
|
// serve so you can pin the route (mlx-lm compat = --l1).
|
|
1685
|
-
// --l1/--l2 + per-fork overrides set the decode
|
|
1804
|
+
// --l1/--l2 + per-fork overrides set the decode runtime options; the
|
|
1686
1805
|
// returned KV scheme is applied below so `generate --l2` runs the same
|
|
1687
1806
|
// quantized-KV route (and produces the same tokens) as `serve --l2`.
|
|
1688
1807
|
const route = applyDecodeRoute();
|
|
@@ -1706,13 +1825,12 @@ switch (cmd) {
|
|
|
1706
1825
|
// bf16, same silent fallback as serve), N = uniform bits from decode
|
|
1707
1826
|
// start. Unset (no tier, no --kv-quant) keeps generate's historical
|
|
1708
1827
|
// default: bf16 bit-exact greedy (generateText's parity path).
|
|
1709
|
-
const kvScheme =
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
? { kvBits: route.kvQuant, quantizedKvStart: 0 }
|
|
1828
|
+
const kvScheme = route.turboQuant || route.kvQuant !== undefined
|
|
1829
|
+
? resolveKvScheme({
|
|
1830
|
+
override: route.kvQuant,
|
|
1831
|
+
turboQuant: route.turboQuant,
|
|
1832
|
+
config: tm.config.kvQuant,
|
|
1833
|
+
}).generationOptions
|
|
1716
1834
|
: undefined;
|
|
1717
1835
|
const num = (n: string): number | undefined => { const v = opt(n); return v == null ? undefined : Number(v); };
|
|
1718
1836
|
const text = await generateText(tm, prompt, {
|
|
@@ -1794,14 +1912,14 @@ switch (cmd) {
|
|
|
1794
1912
|
case "benchmark": {
|
|
1795
1913
|
const { banner, step, box, style } = await import("./tui");
|
|
1796
1914
|
banner(pkg.version);
|
|
1797
|
-
serverRuntimeFlags(); //
|
|
1915
|
+
serverRuntimeFlags(); // decode options (--compiled-decode, --fused-sdpa, ...) apply to the run
|
|
1798
1916
|
const { m, picked } = await resolveModelAuto(opt("model") ?? positional(0) ?? opt("query"));
|
|
1799
1917
|
const tokens = Number(opt("tokens", "256"));
|
|
1800
1918
|
const runs = Number(opt("runs", "3"));
|
|
1801
1919
|
const promptTokens = Number(opt("prompt-tokens", "0"));
|
|
1802
1920
|
const kvMode = opt("kv-quant", "off")!;
|
|
1803
1921
|
console.log(` ${style.dim("measures THIS machine, OUR stack only — no other systems needed.")}`);
|
|
1804
|
-
console.log(` ${style.dim("numbers on a loaded machine are not quotable;
|
|
1922
|
+
console.log(` ${style.dim("numbers on a loaded machine are not quotable; bun scripts/bench-serve.ts all is the preflight-gated harness.")}`);
|
|
1805
1923
|
const sNative = step("native runtime");
|
|
1806
1924
|
await ensureNative(sNative);
|
|
1807
1925
|
sNative.done("native runtime ready");
|
|
@@ -1846,7 +1964,7 @@ switch (cmd) {
|
|
|
1846
1964
|
: kvMode !== "off" ? { kvBits: Number(kvMode), quantizedKvStart: 0 } : {};
|
|
1847
1965
|
|
|
1848
1966
|
// Warmup materializes weights so prefill timing measures prefill,
|
|
1849
|
-
// not lazy page-in (same protocol as
|
|
1967
|
+
// not lazy page-in (same protocol as bench.ts (deleted 2026-08-23; git history)).
|
|
1850
1968
|
const sWarm = step("warmup");
|
|
1851
1969
|
{
|
|
1852
1970
|
const wCache = model.makeCache();
|
|
@@ -1921,7 +2039,7 @@ switch (cmd) {
|
|
|
1921
2039
|
}
|
|
1922
2040
|
const { style } = await import("./tui");
|
|
1923
2041
|
if (rows.length === 0) {
|
|
1924
|
-
console.log("no eval runs recorded yet —
|
|
2042
|
+
console.log("no eval runs recorded yet — bun scripts/bench-serve.ts all writes them");
|
|
1925
2043
|
break;
|
|
1926
2044
|
}
|
|
1927
2045
|
const ago = (ts: number) => {
|
|
@@ -1993,7 +2111,7 @@ switch (cmd) {
|
|
|
1993
2111
|
// already run their own pi connect it with `mlx-bun harness pi`.
|
|
1994
2112
|
const OURS_VAL = new Set([
|
|
1995
2113
|
"--query", "-q", "--model", "--port", "--host", "--memory-budget", "--kv-budget", "--prompt-cache",
|
|
1996
|
-
"--ssd-cache", "--ssd-cache-max", "--ssd-demote-idle", "--kv-quant", "--unix",
|
|
2114
|
+
"--ssd-cache", "--ssd-cache-max", "--ssd-demote-idle", "--generation-checkpoint", "--kv-quant", "--unix",
|
|
1997
2115
|
"--batch", "--decode-concurrency", "--adapter", "--adapter-path",
|
|
1998
2116
|
"--draft-model", "--num-draft-tokens", "--draft-kind", "--ngram-max", "--ngram-min",
|
|
1999
2117
|
"--compiled-decode", "--compiled-activations", "--fused-sdpa", "--thinking",
|
|
@@ -2446,6 +2564,12 @@ switch (cmd) {
|
|
|
2446
2564
|
console.error(`--candidate-bits expects a comma list of integers in [2, 8] (got "${opt("candidate-bits")}")`);
|
|
2447
2565
|
process.exit(1);
|
|
2448
2566
|
}
|
|
2567
|
+
const rotateWeights = flag("rotate-weights");
|
|
2568
|
+
const rotationSeed = Number(opt("rotation-seed", "42"));
|
|
2569
|
+
if (!Number.isInteger(rotationSeed)) {
|
|
2570
|
+
console.error(`--rotation-seed expects an integer (got "${opt("rotation-seed")}")`);
|
|
2571
|
+
process.exit(1);
|
|
2572
|
+
}
|
|
2449
2573
|
const mlxPath = opt("mlx-path", "mlx_model")!;
|
|
2450
2574
|
const { existsSync } = await import("node:fs");
|
|
2451
2575
|
if (existsSync(mlxPath)) {
|
|
@@ -2487,7 +2611,7 @@ switch (cmd) {
|
|
|
2487
2611
|
? `quantizing (mixed, target ${targetBpw} bpw — sensitivity sweep, ~minutes)`
|
|
2488
2612
|
: `quantizing (${qBits}-bit, group ${qGroup})`,
|
|
2489
2613
|
);
|
|
2490
|
-
const { quantizeModelDir } = await import("./quantize");
|
|
2614
|
+
const { quantizeModelDir, automaticRotationWeightTransform } = await import("./quantize");
|
|
2491
2615
|
try {
|
|
2492
2616
|
const r = await quantizeModelDir(
|
|
2493
2617
|
srcDir,
|
|
@@ -2500,6 +2624,9 @@ switch (cmd) {
|
|
|
2500
2624
|
...(candidateBits ? { candidateBits } : {}),
|
|
2501
2625
|
...(opt("calibration-mix") ? { calibrationMix: opt("calibration-mix")! } : {}),
|
|
2502
2626
|
...(opt("n-calibration") ? { nCalibration: Number(opt("n-calibration")) } : {}),
|
|
2627
|
+
...(rotateWeights
|
|
2628
|
+
? { weightTransform: automaticRotationWeightTransform({ seed: rotationSeed }) }
|
|
2629
|
+
: {}),
|
|
2503
2630
|
},
|
|
2504
2631
|
(e) => sQ.update(e.message),
|
|
2505
2632
|
);
|
|
@@ -2511,6 +2638,7 @@ switch (cmd) {
|
|
|
2511
2638
|
`source ${style.dim(srcDir)}`,
|
|
2512
2639
|
`model ${style.bold(r.outDir)} ${style.dim(`· ${gb(r.write.totalSize)}`)}`,
|
|
2513
2640
|
`quant ${style.dim(targetBpw !== undefined ? `mixed ${r.achievedBpw.toFixed(2)} bpw (target ${targetBpw})` : `${qBits}-bit g${qGroup} affine`)}`,
|
|
2641
|
+
...(rotateWeights ? [`transform ${style.dim(`TurboQuant rotation seed ${rotationSeed}`)}`] : []),
|
|
2514
2642
|
"",
|
|
2515
2643
|
`serve it ${style.accent(`mlx-bun serve ${r.outDir}`)}`,
|
|
2516
2644
|
]);
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { CompletionClient } from "./contracts/completion";
|
|
2
|
+
import type { EngineHost } from "./contracts/host";
|
|
3
|
+
|
|
4
|
+
export interface CompletionCall {
|
|
5
|
+
readonly body: Readonly<Record<string, unknown>>;
|
|
6
|
+
readonly route?: "chat/completions" | "completions";
|
|
7
|
+
readonly signal?: AbortSignal;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface CompletionResponse {
|
|
11
|
+
choices: Array<{ text?: string; message?: { content?: string | null; [key: string]: unknown }; [key: string]: unknown }>;
|
|
12
|
+
usage?: Record<string, unknown>;
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** A borrowed transport. Direct services, HTTP and isolated workers receive
|
|
17
|
+
* the same request and return the same result; POSTs are never retried here. */
|
|
18
|
+
export function createCompletionClient(options: {
|
|
19
|
+
baseUrl: string;
|
|
20
|
+
headers?: Readonly<Record<string, string>>;
|
|
21
|
+
host?: Pick<EngineHost<Request, Response>, "forward">;
|
|
22
|
+
}): CompletionClient<CompletionCall, CompletionResponse> {
|
|
23
|
+
const baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
24
|
+
const headers = { ...options.headers, "content-type": "application/json" };
|
|
25
|
+
const send = options.host ? options.host.forward.bind(options.host) : fetch;
|
|
26
|
+
return {
|
|
27
|
+
async complete({ body, route = "chat/completions", signal }) {
|
|
28
|
+
signal?.throwIfAborted();
|
|
29
|
+
const response = await send(new Request(`${baseUrl}/${route}`, {
|
|
30
|
+
method: "POST", headers, body: JSON.stringify({ ...body, stream: false }), signal,
|
|
31
|
+
}));
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
const detail = await response.text();
|
|
34
|
+
throw new Error(`completion failed (${response.status}): ${detail}`);
|
|
35
|
+
}
|
|
36
|
+
const result = await response.json() as CompletionResponse;
|
|
37
|
+
if (!Array.isArray(result.choices)) throw new Error("completion response has no choices");
|
|
38
|
+
return result;
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Adapts an in-process request handler. Closing waits for active handlers;
|
|
44
|
+
* a handler owns any response stream it returns. */
|
|
45
|
+
export function createDirectHost(
|
|
46
|
+
handler: (request: Request) => Promise<Response>, shutdown: () => Promise<void> = async () => {},
|
|
47
|
+
): EngineHost<Request, Response> {
|
|
48
|
+
let closed = false;
|
|
49
|
+
let closing: Promise<void> | undefined;
|
|
50
|
+
const active = new Set<Promise<Response>>();
|
|
51
|
+
return {
|
|
52
|
+
ready: Promise.resolve(),
|
|
53
|
+
forward(request) {
|
|
54
|
+
if (closed) return Promise.reject(new Error("engine host is closed"));
|
|
55
|
+
if (request.signal.aborted) return Promise.reject(request.signal.reason);
|
|
56
|
+
const work = Promise.resolve().then(() => handler(request));
|
|
57
|
+
active.add(work);
|
|
58
|
+
void work.then(() => active.delete(work), () => active.delete(work));
|
|
59
|
+
return work;
|
|
60
|
+
},
|
|
61
|
+
close() {
|
|
62
|
+
closed = true;
|
|
63
|
+
return closing ??= (async () => { await Promise.allSettled(active); await shutdown(); })();
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|