mlx-bun 0.2.0 → 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 -588
- package/docs/README.md +91 -0
- package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
- package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
- package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
- package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
- package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
- package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
- package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
- 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 +2 -2
- package/docs/reference/benchmarks.md +1162 -0
- package/docs/reference/cli.md +394 -159
- package/docs/reference/distribution.md +321 -182
- package/docs/reference/environment.md +348 -0
- package/docs/reference/glossary.md +39 -0
- package/docs/reference/library-api.md +566 -168
- package/docs/reference/memory.md +227 -64
- package/docs/reference/models.md +104 -6
- package/docs/reference/server-api.md +886 -425
- package/docs/reference/server-config.md +638 -330
- package/docs/reference/training.md +393 -254
- package/docs/reference/troubleshooting.md +166 -0
- package/package.json +9 -4
- 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 +58 -25
- 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 +236 -3
- package/src/eval/reference-logits.ts +207 -0
- package/src/eval/runner.ts +8 -96
- 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 +1 -1
- package/src/generate.ts +656 -268
- package/src/grammar.ts +12 -7
- package/src/harness-pi.ts +14 -33
- package/src/hub-rest.ts +4 -4
- package/src/index.ts +7 -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 +12 -12
- 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-store.ts +109 -49
- 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} +5 -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 +1 -1
- package/src/memory/article.ts +1 -1
- package/src/memory/chunk.ts +1 -1
- package/src/memory/model.ts +30 -169
- 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 +1 -3
- package/src/mlx/array.ts +9 -10
- package/src/mlx/compile.ts +12 -2
- package/src/mlx/ffi.ts +58 -11
- package/src/mlx/materialize.ts +23 -0
- package/src/mlx/metal-kernel.ts +10 -11
- package/src/mlx/ops.ts +33 -13
- 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 +8 -2
- package/src/model/batched-quant.ts +1 -1
- package/src/model/batched-rotating-quant.ts +2 -2
- package/src/model/batched-rotating.ts +1 -1
- package/src/model/compiled-decode.ts +1 -1
- package/src/model/diffusion-gemma.ts +8 -8
- package/src/model/factory.ts +76 -33
- package/src/model/fingerprint.ts +1 -1
- package/src/model/flash-attention.ts +3 -3
- package/src/model/gemma4-base.ts +26 -9
- package/src/model/gemma4.ts +6 -6
- package/src/model/generated/gemma4-12b.ts +2 -2
- package/src/model/generated/gemma4-26b.ts +2 -2
- package/src/model/generated/gemma4-e4b.ts +2 -2
- package/src/model/glm52-dsa.ts +13 -9
- package/src/model/glm52-memory.ts +1 -1
- package/src/model/implementation.ts +69 -0
- package/src/model/minicpm5.ts +6 -8
- package/src/model/profile.ts +12 -2
- package/src/model/qwen-append.ts +12 -0
- package/src/model/qwen-conv.ts +52 -0
- package/src/model/qwen3_5-checkpoint.ts +164 -0
- package/src/model/qwen3_5.ts +140 -56
- 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-pack.ts +6 -5
- package/src/pi-provider.ts +6 -64
- package/src/pi-web.ts +8 -315
- package/src/prompt-cache.ts +96 -53
- package/src/quantize/allocator.ts +1 -1
- package/src/quantize/config-writer.ts +10 -3
- package/src/quantize/rotate.ts +4 -4
- package/src/quantize/trellis.ts +586 -0
- package/src/responses.ts +28 -1
- package/src/runtime-config.ts +18 -4
- package/src/sampler.ts +4 -4
- package/src/serve/batch-scheduler.ts +8 -1671
- 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 +15 -7
- package/src/serve/discovery-routes.ts +12 -13
- package/src/serve/generation-gateway.ts +154 -193
- 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/lab-routes.ts +3 -0
- package/src/serve/lane-registry.ts +2 -1
- package/src/serve/model-admin-routes.ts +7 -5
- 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/request-plan.ts +19 -15
- 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 +5 -5
- package/src/serve/text-completion-stage.ts +104 -0
- package/src/serve/token-streams.ts +519 -0
- package/src/server.ts +317 -2649
- package/src/spec/assistant-source.ts +10 -44
- package/src/spec/deepspec-source.ts +6 -6
- package/src/spec/dflash-source.ts +8 -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 +4 -3
- 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 +1 -1
- package/src/spec/ngram-source.ts +1 -1
- package/src/spec/prefix-state.ts +45 -0
- package/src/spec/qwen-mtp-source.ts +166 -69
- package/src/spec/serve-loop.ts +154 -156
- package/src/spec/source.ts +42 -10
- package/src/spec/two-model.ts +1 -1
- package/src/ssd-cache.ts +80 -12
- package/src/ssd-durability.ts +25 -13
- package/src/tool-call.ts +51 -14
- package/src/train/flash-cce.ts +2 -2
- package/src/train/forward.ts +6 -0
- package/src/train/loss.ts +1 -1
- package/src/train/prefix-shared.ts +5 -1
- package/src/train/segmented.ts +5 -3
- package/src/train/trainer.ts +6 -6
- package/src/vision/prompt.ts +1 -1
- package/src/vision/qwen3vl-tower.ts +52 -10
- package/src/web/app.html +1 -1
- package/src/web/app.js +5 -3
- package/src/web/src/adapters-panel.ts +3 -3
- package/src/web/src/assistant.ts +2 -36
- package/src/web/src/chat.ts +4 -4
- 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 +55 -9
- package/docs/archive/investigations/curve-runs/bisector-route.json +0 -98
- package/docs/archive/investigations/curve-runs/bisector-trace.json +0 -1393
- package/docs/archive/investigations/curve-runs/curve-terrain.json +0 -1179
- package/docs/archive/investigations/curve-runs/curve-terrain.svg +0 -42
- package/docs/archive/investigations/hlg-ab.html +0 -34
- package/docs/archive/investigations/hlg-explorer.html +0 -238
- package/docs/archive/investigations/hlg-figs/cliff.svg +0 -1
- package/docs/archive/investigations/hlg-figs/cross.svg +0 -1
- package/docs/archive/investigations/hlg-figs/edges.svg +0 -1
- package/docs/archive/investigations/hlg-figs/k20-grids.svg +0 -1
- package/docs/archive/investigations/hlg-figs/k20-peaks.svg +0 -1
- package/docs/archive/investigations/hlg-figs/lw-curve.svg +0 -1
- package/docs/archive/investigations/hlg-figs/terrain.svg +0 -1
- package/docs/archive/investigations/hlg-report.html +0 -262
- package/docs/archive/investigations/hlg-runs/hlg-ab.json +0 -149
- package/docs/archive/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
- package/docs/archive/investigations/hlg-runs/hlg-blind.json +0 -107
- package/docs/archive/investigations/hlg-runs/hlg-cliff.json +0 -197
- package/docs/archive/investigations/hlg-runs/hlg-cross.json +0 -505
- package/docs/archive/investigations/hlg-runs/hlg-edge.json +0 -388
- package/docs/archive/investigations/hlg-runs/hlg-eval1.json +0 -25
- package/docs/archive/investigations/hlg-runs/hlg-frontier.json +0 -86
- package/docs/archive/investigations/hlg-runs/hlg-grid.json +0 -242
- package/docs/archive/investigations/hlg-runs/hlg-grid2.json +0 -343
- package/docs/archive/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
- package/docs/archive/investigations/hlg-runs/hlg-map.json +0 -383
- package/docs/archive/investigations/hlg-runs/run0-original.log +0 -159
- package/docs/archive/investigations/hlg-runs/runA-recommended.log +0 -131
- package/docs/archive/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
- package/docs/archive/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
- package/docs/archive/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
- package/docs/archive/investigations/hlg-runs/runAD-ab-samples.log +0 -8
- package/docs/archive/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
- package/docs/archive/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
- package/docs/archive/investigations/hlg-runs/runAG-eval-config2.log +0 -16
- package/docs/archive/investigations/hlg-runs/runB-gain-variants.log +0 -192
- package/docs/archive/investigations/hlg-runs/runC-pivots.log +0 -162
- package/docs/archive/investigations/hlg-runs/runD-oetf.log +0 -132
- package/docs/archive/investigations/hlg-runs/runE-eotf.log +0 -162
- package/docs/archive/investigations/hlg-runs/runF-shaper-working.log +0 -86
- package/docs/archive/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
- package/docs/archive/investigations/hlg-runs/runH-wsweep.log +0 -176
- package/docs/archive/investigations/hlg-runs/runI-asweep.log +0 -176
- package/docs/archive/investigations/hlg-runs/runJ-knobmap.log +0 -49
- package/docs/archive/investigations/hlg-runs/runK-frontier.log +0 -19
- package/docs/archive/investigations/hlg-runs/runL-widemap.log +0 -65
- package/docs/archive/investigations/hlg-runs/runM-frontier-emb.log +0 -15
- package/docs/archive/investigations/hlg-runs/runN-verify.log +0 -116
- package/docs/archive/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
- package/docs/archive/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
- package/docs/archive/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
- package/docs/archive/investigations/hlg-runs/runR-blind-transcript.log +0 -79
- package/docs/archive/investigations/hlg-runs/runS-cross-coherence.log +0 -33
- package/docs/archive/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
- package/docs/archive/investigations/hlg-runs/runU-cross-12b.log +0 -33
- package/docs/archive/investigations/hlg-runs/runV-lw-e4b.log +0 -22
- package/docs/archive/investigations/hlg-runs/runW-lw-12b.log +0 -22
- package/docs/archive/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
- package/docs/archive/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
- package/docs/archive/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
- 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 -318
- 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-serving-execution-seam.md +0 -81
- 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 -231
- 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 -415
- 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/turboquant-weights.md +0 -467
- 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/planning/IDEAS.md +0 -69
- package/docs/reference/embedding.md +0 -197
- package/docs/reference/features-matrix.md +0 -101
- package/docs/reference/orpo-quickstart.md +0 -135
- package/src/flags.ts +0 -11
- /package/docs/{design → archive/investigations}/colibri-glm52-port.md +0 -0
- /package/docs/{planning → archive/planning}/chunk-task-roadmap.md +0 -0
- /package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +0 -0
- /package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +0 -0
- /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.2.0.md +0 -0
- /package/src/{memory-plan.ts → admission-plan.ts} +0 -0
- /package/{docs/archive/investigations → src/serve/assets}/curve-terrain.html +0 -0
- /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { MlxArray } from "../../mlx/array";
|
|
2
|
+
import * as ops from "../../mlx/ops";
|
|
3
|
+
import type { Cache } from "../../model/gemma4";
|
|
4
|
+
import type { RuntimeModel } from "../../model/factory";
|
|
5
|
+
import type { Sampler } from "../../sampler";
|
|
6
|
+
import type { DraftProvider, DraftSource } from "../../spec/source";
|
|
7
|
+
import { runtimeConfig, type RuntimeConfig } from "../../runtime-config";
|
|
8
|
+
import type { MlxModelMemory } from "./autoregressive";
|
|
9
|
+
import type { SpeculativeTransaction } from "../../inference/rollback";
|
|
10
|
+
import { bindCacheRollback } from "./rollback";
|
|
11
|
+
import type { GraphDescriptor } from "../../inference/graph";
|
|
12
|
+
import { bindLegacyDraftTarget } from "./draft-target";
|
|
13
|
+
|
|
14
|
+
/** Bound target operations and draft construction for one speculative run.
|
|
15
|
+
* A replacement graph supplies this entire port, including any hidden taps.
|
|
16
|
+
* The verifier never needs a RuntimeModel or an artifact-family check. */
|
|
17
|
+
export interface MlxSpeculativeBinding {
|
|
18
|
+
readonly runtime?: RuntimeConfig;
|
|
19
|
+
readonly memory?: MlxModelMemory;
|
|
20
|
+
readonly descriptor: GraphDescriptor;
|
|
21
|
+
readonly eosTokenIds: readonly number[];
|
|
22
|
+
readonly prefillTailSplit: boolean;
|
|
23
|
+
makeCache(): Cache[];
|
|
24
|
+
openDraft(sampler: Sampler, caches: Cache[]): DraftSource;
|
|
25
|
+
bindRollback(caches: Cache[]): SpeculativeTransaction;
|
|
26
|
+
forward(ids: MlxArray, caches: Cache[], tapLayers?: number[]):
|
|
27
|
+
Promise<{ hidden: MlxArray; ctxML: MlxArray | null }>;
|
|
28
|
+
projectLogits(hidden: MlxArray): MlxArray;
|
|
29
|
+
/** Establish the implementation's verify kernel context; restore on close. */
|
|
30
|
+
pinVerify?(): { close(): void };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Legacy mutable tap/kernel fields require the gateway's exclusive lease. */
|
|
34
|
+
export function bindLegacySpeculativeModel(model: RuntimeModel, provider: DraftProvider): MlxSpeculativeBinding {
|
|
35
|
+
const runtime = runtimeConfig();
|
|
36
|
+
return {
|
|
37
|
+
runtime,
|
|
38
|
+
memory: model,
|
|
39
|
+
descriptor: Object.freeze({ id: `legacy-spec:${model.config.modelType}`, backend: "mlx",
|
|
40
|
+
graphAbi: "mlx-hidden-bsh-v1", stateAbi: "legacy-cache-array-v1", artifact: "legacy-resident-model" }),
|
|
41
|
+
eosTokenIds: model.config.eosTokenIds,
|
|
42
|
+
prefillTailSplit: runtime.flag("MLX_BUN_PREFILL_TAIL_SPLIT", true),
|
|
43
|
+
makeCache: model.makeCache.bind(model),
|
|
44
|
+
openDraft: (sampler, caches) => provider.open({ sampler, target: bindLegacyDraftTarget(model, caches) }),
|
|
45
|
+
bindRollback: bindCacheRollback,
|
|
46
|
+
forward: (ids, caches, tapLayers) => legacyForwardWithTaps(model, ids, caches, tapLayers),
|
|
47
|
+
projectLogits: model.logitsFromHidden.bind(model),
|
|
48
|
+
...("setSpecKernelPinned" in model ? {
|
|
49
|
+
pinVerify() {
|
|
50
|
+
model.setSpecKernelPinned(true);
|
|
51
|
+
return { close() { model.setSpecKernelPinned(false); } };
|
|
52
|
+
},
|
|
53
|
+
} : {}),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function assertMlxSpeculativeBinding(binding: MlxSpeculativeBinding): void {
|
|
58
|
+
const descriptor = binding.descriptor;
|
|
59
|
+
if (descriptor.backend !== "mlx" || descriptor.graphAbi !== "mlx-hidden-bsh-v1" ||
|
|
60
|
+
descriptor.stateAbi !== "legacy-cache-array-v1")
|
|
61
|
+
throw new Error(`speculative binding ${descriptor.id} has an incompatible backend, graph, or state ABI`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Run one target forward, and when `tapLayers` is set (⟹ a Gemma4 target — a
|
|
65
|
+
* DSpark-style source), also return the captured multi-layer context
|
|
66
|
+
* [1,L,m*H] (tapLayers concatenated on the feature axis; index nLayers is the
|
|
67
|
+
* post-finalNorm sentinel). Non-tapping sources get ctxML=null and never
|
|
68
|
+
* touch model.hiddenTap. Mirrors generate-dflash.ts forwardTapped. */
|
|
69
|
+
async function legacyForwardWithTaps(
|
|
70
|
+
model: RuntimeModel,
|
|
71
|
+
ids: MlxArray,
|
|
72
|
+
caches: Cache[],
|
|
73
|
+
tapLayers: number[] | undefined,
|
|
74
|
+
): Promise<{ hidden: MlxArray; ctxML: MlxArray | null }> {
|
|
75
|
+
if (!tapLayers) {
|
|
76
|
+
const asyncModel = model as RuntimeModel & {
|
|
77
|
+
forwardHiddenAsync?: (
|
|
78
|
+
ids: MlxArray,
|
|
79
|
+
caches: Cache[],
|
|
80
|
+
) => Promise<MlxArray>;
|
|
81
|
+
};
|
|
82
|
+
const hidden = typeof asyncModel.forwardHiddenAsync === "function"
|
|
83
|
+
? await asyncModel.forwardHiddenAsync(ids, caches)
|
|
84
|
+
: model.forwardHidden(ids, caches);
|
|
85
|
+
return { hidden, ctxML: null };
|
|
86
|
+
}
|
|
87
|
+
if (!("hiddenTap" in model)) throw new Error("target graph does not support hidden taps");
|
|
88
|
+
const m = model;
|
|
89
|
+
const previousTap = m.hiddenTap;
|
|
90
|
+
const cap = new Map<number, MlxArray>();
|
|
91
|
+
m.hiddenTap = { layers: new Set(tapLayers), captured: cap };
|
|
92
|
+
let hidden: MlxArray | null = null;
|
|
93
|
+
try {
|
|
94
|
+
hidden = model.forwardHidden(ids, caches);
|
|
95
|
+
const perLayer = tapLayers.map((li) => {
|
|
96
|
+
const a = cap.get(li);
|
|
97
|
+
if (!a) throw new Error(`spec tap: layer ${li} not captured`);
|
|
98
|
+
return a;
|
|
99
|
+
});
|
|
100
|
+
const ctxML = ops.concatAxis(perLayer, 2); // [1,L,m*H]
|
|
101
|
+
for (const [, a] of cap) a.dispose();
|
|
102
|
+
cap.clear(); // consumed — the finally must not double-dispose
|
|
103
|
+
const out = { hidden, ctxML };
|
|
104
|
+
hidden = null; // ownership returned to the caller
|
|
105
|
+
return out;
|
|
106
|
+
} finally {
|
|
107
|
+
// On any throw (forward mid-capture, missing layer, concat), free the
|
|
108
|
+
// partially-captured tap tensors and the orphaned hidden. On success both
|
|
109
|
+
// are already gone (cap cleared, hidden nulled).
|
|
110
|
+
hidden?.dispose();
|
|
111
|
+
for (const [, a] of cap) a.dispose();
|
|
112
|
+
m.hiddenTap = previousTap;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { StateView } from "../../contracts/resources";
|
|
2
|
+
import { disposeResources, ownResource } from "../../engine/resources";
|
|
3
|
+
import type { MlxArray } from "../../mlx/array";
|
|
4
|
+
import type { Cache } from "../../model/gemma4-base";
|
|
5
|
+
|
|
6
|
+
/** Compatibility boundary for legacy state(). Numerical code consumes a lease,
|
|
7
|
+
* never the legacy ownership marker. No copy, evaluation or fence is added. */
|
|
8
|
+
export function leaseCacheState(cache: Cache): StateView<MlxArray> {
|
|
9
|
+
return ownResource<readonly MlxArray[]>(cache.state(), cache.stateNeedsDispose ? disposeResources : () => {});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Preserve the existing prefill evaluation order: temporary views first,
|
|
13
|
+
* borrowed state second. Partial acquisition releases every acquired view. */
|
|
14
|
+
export function leaseCacheStates(caches: readonly Cache[]): StateView<MlxArray> {
|
|
15
|
+
const leases: StateView<MlxArray>[] = [];
|
|
16
|
+
const close = () => disposeResources(leases.map((lease) => ({ dispose: () => lease.close() })));
|
|
17
|
+
try {
|
|
18
|
+
for (const owned of [true, false]) {
|
|
19
|
+
for (const cache of caches) if (!!cache.stateNeedsDispose === owned) leases.push(leaseCacheState(cache));
|
|
20
|
+
}
|
|
21
|
+
return ownResource(leases.flatMap((lease) => [...lease.borrow()]), close);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
try { close(); } catch (cleanupError) { throw new AggregateError([error, cleanupError], "state view acquisition failed"); }
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createCompletionMethod } from "../../serve/session-completion-engine";
|
|
2
|
+
import type { ExecutionPlanner, InferenceEngine, Timer } from "../../contracts/generation";
|
|
3
|
+
import { createInferenceEngine, type EngineOptions } from "../../engine/engine";
|
|
4
|
+
import { throwIfCancelled } from "../../engine/cancellation";
|
|
5
|
+
import type { GenerateOptions, GenerateStats } from "../../generate";
|
|
6
|
+
import type { GenerationGateway, RequestShape } from "../../serve/generation-gateway";
|
|
7
|
+
|
|
8
|
+
/** Data-only API for text requests whose policy/shape was already
|
|
9
|
+
* resolved by the caller. Native request resources still belong to the prepared
|
|
10
|
+
* completion API; they are deliberately absent from this token API. */
|
|
11
|
+
export type TextGenerationOptions = Omit<GenerateOptions,
|
|
12
|
+
"signal" | "cache" | "grammar" | "fill" | "adapters" | "promptEmbeddings" |
|
|
13
|
+
"imageMask" | "multimodalMask" | "visionPixels" | "onPrefillDone" |
|
|
14
|
+
"onDecodeCheckpoint" | "initialPendingToken" | "initialGeneratedTokens" |
|
|
15
|
+
"originalPromptTokens" | "snapshotAt" | "checkpointEveryTokens"
|
|
16
|
+
>;
|
|
17
|
+
|
|
18
|
+
export interface TextGenerationRequest {
|
|
19
|
+
readonly promptIds: readonly number[];
|
|
20
|
+
readonly options: TextGenerationOptions;
|
|
21
|
+
readonly shape: RequestShape;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const hostTimer: Timer = {
|
|
25
|
+
after(milliseconds, callback) {
|
|
26
|
+
const timer = setTimeout(callback, milliseconds);
|
|
27
|
+
return () => { clearTimeout(timer); };
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** Reuses the gateway's placement, exclusive lease, prompt cache, batching,
|
|
32
|
+
* and quant-specific execution. The portable session never imports them.
|
|
33
|
+
* Closing this engine cancels its sessions; it does not dispose a shared gateway. */
|
|
34
|
+
export function createTextInferenceEngine(
|
|
35
|
+
gateway: Pick<GenerationGateway, "place" | "run">,
|
|
36
|
+
options: Omit<EngineOptions, "timer"> & { timer?: Timer } = {},
|
|
37
|
+
): InferenceEngine<TextGenerationRequest, GenerateStats> {
|
|
38
|
+
const planner: ExecutionPlanner<TextGenerationRequest, GenerateStats> = {
|
|
39
|
+
async plan(request, cancellation) {
|
|
40
|
+
throwIfCancelled(cancellation);
|
|
41
|
+
if (request.shape.hasVision || request.shape.hasAdapters || request.shape.hasGrammar)
|
|
42
|
+
throw new Error("native request resources require the prepared completion API");
|
|
43
|
+
const placement = gateway.place(request.shape, request.options);
|
|
44
|
+
const maxTokens = request.options.maxTokens ?? 512;
|
|
45
|
+
return {
|
|
46
|
+
id: `text:${placement.mechanism}`, outputTokenLimit: maxTokens,
|
|
47
|
+
method: createCompletionMethod(gateway, {
|
|
48
|
+
prompt: [...request.promptIds], options: request.options,
|
|
49
|
+
shape: request.shape, placement,
|
|
50
|
+
}),
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
const engine = createInferenceEngine(planner, { ...options, timer: options.timer ?? hostTimer });
|
|
55
|
+
return {
|
|
56
|
+
open(request, control) {
|
|
57
|
+
// Snapshot before demand-start: callers may reuse/mutate request arrays.
|
|
58
|
+
// TextGenerationOptions contains data only; no native handles or callbacks.
|
|
59
|
+
const snapshot = structuredClone(request);
|
|
60
|
+
if (!snapshot.promptIds.length || snapshot.promptIds.some((id) => !Number.isInteger(id) || id < 0 || id > 0xffffffff))
|
|
61
|
+
return Promise.reject(new Error("prompt must contain valid token ids"));
|
|
62
|
+
const maxTokens = snapshot.options.maxTokens ?? 512;
|
|
63
|
+
if (!Number.isSafeInteger(maxTokens) || maxTokens <= 0)
|
|
64
|
+
return Promise.reject(new Error("text generation requires a positive token limit"));
|
|
65
|
+
if ((snapshot.options.topLogprobs ?? 0) > (options.maxTopLogprobs ?? 20))
|
|
66
|
+
return Promise.reject(new Error("top logprobs exceed session delivery capacity"));
|
|
67
|
+
if (control.output === "collect" && (snapshot.options.logprobs || snapshot.options.topLogprobs))
|
|
68
|
+
return Promise.reject(new Error("token logprobs require stream consumption"));
|
|
69
|
+
return engine.open(snapshot, control);
|
|
70
|
+
},
|
|
71
|
+
close: () => engine.close(),
|
|
72
|
+
};
|
|
73
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -35,6 +35,14 @@ import {
|
|
|
35
35
|
} from "./runtime-config";
|
|
36
36
|
|
|
37
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
|
+
|
|
38
46
|
// The appliance path: naked `mlx-bun` (or only options, e.g.
|
|
39
47
|
// `mlx-bun --port 9000`) runs `serve` — first run downloads a model and
|
|
40
48
|
// opens the chat UI. `--help`/`--version` and explicit subcommands still win.
|
|
@@ -103,13 +111,19 @@ const SERVER_FLAGS = `Server options:
|
|
|
103
111
|
restarts (long-context agent TTFT drops from a
|
|
104
112
|
full re-prefill to a zero-copy mmap restore).
|
|
105
113
|
Off unless set.
|
|
106
|
-
--ssd-cache-max <GB> SSD tier byte cap
|
|
114
|
+
--ssd-cache-max <GB> Optional SSD tier byte cap; 0 = unlimited
|
|
115
|
+
[default: unlimited]
|
|
107
116
|
--ssd-demote-idle <sec> Demote prompt-cache entries idle this long to the
|
|
108
117
|
SSD tier, freeing their GPU memory (prefixes stay
|
|
109
118
|
reachable via zero-copy restore). 0 disables.
|
|
110
119
|
[default: 300 when --ssd-cache is on]
|
|
111
120
|
--ssd-cache-verify Verify tensor hashes on every restore (reads all
|
|
112
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.
|
|
113
127
|
--isolate Run the inference engine as a CHILD process and
|
|
114
128
|
keep this process a pure UI/API proxy — the UI
|
|
115
129
|
stays instant under GPU load and survives engine
|
|
@@ -181,7 +195,7 @@ Model & quality:
|
|
|
181
195
|
(bf16), 4 / 8 (uniform bits), or turbo[:k<bits>v
|
|
182
196
|
<bits>] (rotation-based TurboQuant — default
|
|
183
197
|
k8v3; kBits in {2,4,5,8}, vBits in {2,3,4,5,8};
|
|
184
|
-
docs/design/turboquant
|
|
198
|
+
docs/design/turboquant.md)
|
|
185
199
|
[default: off — quantized KV measured 5-20% slower
|
|
186
200
|
decode than bf16 at ≤16k (on mlx-lm too); it buys
|
|
187
201
|
memory headroom, so opt in when context is tight.
|
|
@@ -196,7 +210,7 @@ Model & quality:
|
|
|
196
210
|
--kv-quant, --draft-model; bypasses the prompt
|
|
197
211
|
cache; runs uncompiled decode. Bit-exact with the
|
|
198
212
|
plain path; expect a small decode cost at batch=1
|
|
199
|
-
(the gather copy). docs/design/
|
|
213
|
+
(the gather copy). docs/design/kv-cache.md
|
|
200
214
|
[default: off]
|
|
201
215
|
--paged-kv-block-size <n> Tokens per KV block [default: 256 = the plain
|
|
202
216
|
cache's growth step]
|
|
@@ -212,13 +226,14 @@ Model & quality:
|
|
|
212
226
|
pass --temp 0 for its unset-request behavior)
|
|
213
227
|
[default: the model's generation_config.json]
|
|
214
228
|
--max-tokens <n> Completion cap when a request omits max_tokens
|
|
215
|
-
(mlx_lm.server flag;
|
|
216
|
-
[default: 128 GLM-5.2
|
|
217
|
-
|
|
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]
|
|
218
233
|
--hlg-sampling on|off Piecewise tone-curve sampling (HLG): rolls off the
|
|
219
234
|
top, boosts the mids, gentles the tail. Gain folds
|
|
220
235
|
from --temperature. [default: off]
|
|
221
|
-
See docs/
|
|
236
|
+
See docs/archive/hlg-sampling.md
|
|
222
237
|
--hlg-width <nats> HLG mid-region half-width [default: 4]
|
|
223
238
|
--hlg-shoulder <nats> HLG highlight rolloff scale [default: 4]
|
|
224
239
|
--hlg-toe <nats> HLG shadow rolloff scale [default: 6]
|
|
@@ -391,7 +406,7 @@ Usage: mlx-bun benchmark [query] [options]
|
|
|
391
406
|
|
|
392
407
|
Runs mlx-bun by itself (no other systems required) and records the
|
|
393
408
|
result to the eval DB. For quotable, cross-stack numbers use
|
|
394
|
-
|
|
409
|
+
bun scripts/bench-serve.ts all from the repo — it preflight-gates on an idle machine
|
|
395
410
|
and runs the mlx-lm/optiq comparison legs.
|
|
396
411
|
|
|
397
412
|
[query] Model to benchmark (--query <q> is accepted for the
|
|
@@ -475,7 +490,7 @@ Options:
|
|
|
475
490
|
--clear Archive the eval DB (timestamped backup) and start fresh
|
|
476
491
|
|
|
477
492
|
Table view: when, model, bench kind, KV mode, decode tok/s, TTFT,
|
|
478
|
-
peak memory, commit. Runs are written by
|
|
493
|
+
peak memory, commit. Runs are written by bun scripts/bench-serve.ts all.`,
|
|
479
494
|
|
|
480
495
|
train: `mlx-bun train — fine-tune a LoRA adapter on your data
|
|
481
496
|
|
|
@@ -825,14 +840,14 @@ function openChatUi(url: string, hostPort: string): void {
|
|
|
825
840
|
/** Resolve the decode ROUTE: a tier alias (--l1/--l2) sets the whole route,
|
|
826
841
|
* and an explicit per-fork flag (--kv-quant/--fused-sdpa/…) overrides the
|
|
827
842
|
* alias. Installs decode runtime options and returns the kv-quant mode. See
|
|
828
|
-
* docs/design/
|
|
843
|
+
* docs/design/unified-engine-frontier-plan.md + unified-engine-frontier-plan.md.
|
|
829
844
|
* Each tier is a GUARANTEE about which reference you reproduce bit-for-bit:
|
|
830
845
|
* --l1 bit-for-bit IDENTICAL to mlx-lm — drop-in replacement for mlx-lm
|
|
831
846
|
* (bf16 KV; THE naked default since 2026-07-05)
|
|
832
847
|
* --l2 bit-for-bit IDENTICAL to mlx-optiq — drop-in replacement for
|
|
833
848
|
* mlx-optiq: quantized KV per kv_config.json + fused N-tiled SDPA for
|
|
834
849
|
* L>1 + STOCK unfused L=1 decode (the optiq-golden composition,
|
|
835
|
-
* scripts/regen
|
|
850
|
+
* scripts/regen.ts kvq + regen-mixed-kv-goldens.ts).
|
|
836
851
|
* There is no --l3: output-changing experiments live in the Lab (env flags
|
|
837
852
|
* with a bench + expiry) until one beats the L1 baseline in a paired A/B —
|
|
838
853
|
* docs/design/unified-engine-frontier-plan.md §6-7. */
|
|
@@ -897,7 +912,7 @@ function applyDecodeRoute(): { kvQuant?: "off" | "config" | number; turboQuant?:
|
|
|
897
912
|
if (kv === "off") return { kvQuant: "off" };
|
|
898
913
|
if (kv === "config") return { kvQuant: "config" };
|
|
899
914
|
if (kv) {
|
|
900
|
-
// TurboQuant (docs/design/turboquant
|
|
915
|
+
// TurboQuant (docs/design/turboquant.md): a distinct axis from the
|
|
901
916
|
// affine kvQuant above — dequantize-on-fetch, stock unfused ops.sdpa,
|
|
902
917
|
// no kvQuant value at all (mutually exclusive with kvBits/kvConfig).
|
|
903
918
|
let turboQuant: TurboQuantScheme | null;
|
|
@@ -940,23 +955,41 @@ function serverRuntimeFlags(): { port: number; serverOptions: import("./server")
|
|
|
940
955
|
if (kvBudgetGB > 0) serverOptions.kvBudgetBytes = kvBudgetGB * 1e9;
|
|
941
956
|
const pcRaw = opt("prompt-cache"); // null if absent; an explicit value (incl. 0) wins → `--prompt-cache 0` DISABLES the cache
|
|
942
957
|
if (pcRaw !== null) serverOptions.promptCacheBytes = Math.max(0, Number(pcRaw)) * 2 ** 30;
|
|
943
|
-
// Engine-child mode (
|
|
958
|
+
// Engine-child mode (docs/reference/server-config.md): listen on a unix socket
|
|
944
959
|
// instead of TCP — the isolated parent proxies here. Internal flag,
|
|
945
960
|
// spawned by --isolate; usable directly for socket-level integration.
|
|
946
961
|
const unixRaw = opt("unix");
|
|
947
962
|
if (unixRaw !== null) serverOptions.unixSocket = unixRaw;
|
|
948
|
-
// SSD cold tier (docs/design/
|
|
963
|
+
// SSD cold tier (docs/design/kv-cache.md) — off unless a dir is given.
|
|
949
964
|
const ssdDir = opt("ssd-cache");
|
|
950
965
|
if (ssdDir !== null) {
|
|
951
966
|
serverOptions.ssdCacheDir = ssdDir;
|
|
952
967
|
const maxRaw = opt("ssd-cache-max");
|
|
953
|
-
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
|
+
}
|
|
954
978
|
if (flag("ssd-cache-verify")) serverOptions.ssdCacheVerify = true;
|
|
955
979
|
const demoteRaw = opt("ssd-demote-idle");
|
|
956
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
|
+
}
|
|
957
990
|
} else {
|
|
958
991
|
// Refuse-loudly house rule: sub-flags of an absent tier must not vanish.
|
|
959
|
-
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)
|
|
960
993
|
if (opt(f) !== null) console.warn(`--${f} has no effect without --ssd-cache — ignored`);
|
|
961
994
|
if (flag("ssd-cache-verify"))
|
|
962
995
|
console.warn("--ssd-cache-verify has no effect without --ssd-cache — ignored");
|
|
@@ -979,7 +1012,7 @@ function serverRuntimeFlags(): { port: number; serverOptions: import("./server")
|
|
|
979
1012
|
if (route.kvQuant !== undefined) serverOptions.kvQuant = route.kvQuant;
|
|
980
1013
|
if (route.turboQuant !== undefined) serverOptions.turboQuant = route.turboQuant;
|
|
981
1014
|
// --paged-kv (env: MLX_BUN_PAGED_KV=1): OPTIONAL vLLM-style block-pool KV
|
|
982
|
-
// storage, default off (docs/design/
|
|
1015
|
+
// storage, default off (docs/design/kv-cache.md). Serial-only in v1:
|
|
983
1016
|
// with no explicit --batch, pin --batch 1 (the default is 8); an explicit
|
|
984
1017
|
// --batch N>1 is refused loudly by createServer rather than downgraded.
|
|
985
1018
|
if (flag("paged-kv") || runtimeValue("MLX_BUN_PAGED_KV") === "1") {
|
|
@@ -1026,7 +1059,7 @@ function serverRuntimeFlags(): { port: number; serverOptions: import("./server")
|
|
|
1026
1059
|
const maxTok = numFlag("max-tokens", 1, 10_000_000);
|
|
1027
1060
|
if (maxTok !== null) serverOptions.defaultMaxTokens = Math.floor(maxTok);
|
|
1028
1061
|
// HLG sampling — piecewise tone curve on the logprobs (default off). Knobs in
|
|
1029
|
-
// nats; the mid gain folds from --temperature. docs/
|
|
1062
|
+
// nats; the mid gain folds from --temperature. docs/archive/hlg-sampling.md.
|
|
1030
1063
|
if (onOff("hlg-sampling") === true) {
|
|
1031
1064
|
serverOptions.hlg = {
|
|
1032
1065
|
enabled: true,
|
|
@@ -1538,7 +1571,7 @@ switch (cmd) {
|
|
|
1538
1571
|
process.exit(1);
|
|
1539
1572
|
}
|
|
1540
1573
|
}
|
|
1541
|
-
// Runtime isolation (--isolate,
|
|
1574
|
+
// Runtime isolation (--isolate, docs/reference/server-config.md): THIS process
|
|
1542
1575
|
// becomes a pure reactor — it binds the TCP port and reverse-proxies to
|
|
1543
1576
|
// an engine CHILD (the full server on a unix socket, spawned from the
|
|
1544
1577
|
// same argv minus parent-only flags). Zero MLX calls in the parent: the
|
|
@@ -1580,7 +1613,7 @@ switch (cmd) {
|
|
|
1580
1613
|
};
|
|
1581
1614
|
const poolMaxRaw = opt("model-pool");
|
|
1582
1615
|
const poolMax = Math.max(1, Number(poolMaxRaw ?? 1) || 1);
|
|
1583
|
-
const { engine,
|
|
1616
|
+
const { engine, close } = startProxyServer({
|
|
1584
1617
|
port: rt.port,
|
|
1585
1618
|
...(rt.serverOptions.hostname ? { hostname: rt.serverOptions.hostname } : {}),
|
|
1586
1619
|
engine: {
|
|
@@ -1596,7 +1629,7 @@ switch (cmd) {
|
|
|
1596
1629
|
const shownHost0 = rt.serverOptions.hostname ?? "localhost";
|
|
1597
1630
|
console.log(style.dim(` isolated mode: engine child starting (socket ${sock})`));
|
|
1598
1631
|
console.log(` ${style.green("●")} ${style.bold("proxy up")} http://${shownHost0}:${rt.port} ${style.dim(`· ${m0.repoId} loading behind it · model pool ${poolMax}`)}`);
|
|
1599
|
-
const onExit = () => {
|
|
1632
|
+
const onExit = () => { void close().finally(() => process.exit(0)); };
|
|
1600
1633
|
process.on("SIGINT", onExit);
|
|
1601
1634
|
process.on("SIGTERM", onExit);
|
|
1602
1635
|
await engine.ready;
|
|
@@ -1886,7 +1919,7 @@ switch (cmd) {
|
|
|
1886
1919
|
const promptTokens = Number(opt("prompt-tokens", "0"));
|
|
1887
1920
|
const kvMode = opt("kv-quant", "off")!;
|
|
1888
1921
|
console.log(` ${style.dim("measures THIS machine, OUR stack only — no other systems needed.")}`);
|
|
1889
|
-
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.")}`);
|
|
1890
1923
|
const sNative = step("native runtime");
|
|
1891
1924
|
await ensureNative(sNative);
|
|
1892
1925
|
sNative.done("native runtime ready");
|
|
@@ -1931,7 +1964,7 @@ switch (cmd) {
|
|
|
1931
1964
|
: kvMode !== "off" ? { kvBits: Number(kvMode), quantizedKvStart: 0 } : {};
|
|
1932
1965
|
|
|
1933
1966
|
// Warmup materializes weights so prefill timing measures prefill,
|
|
1934
|
-
// not lazy page-in (same protocol as
|
|
1967
|
+
// not lazy page-in (same protocol as bench.ts (deleted 2026-08-23; git history)).
|
|
1935
1968
|
const sWarm = step("warmup");
|
|
1936
1969
|
{
|
|
1937
1970
|
const wCache = model.makeCache();
|
|
@@ -2006,7 +2039,7 @@ switch (cmd) {
|
|
|
2006
2039
|
}
|
|
2007
2040
|
const { style } = await import("./tui");
|
|
2008
2041
|
if (rows.length === 0) {
|
|
2009
|
-
console.log("no eval runs recorded yet —
|
|
2042
|
+
console.log("no eval runs recorded yet — bun scripts/bench-serve.ts all writes them");
|
|
2010
2043
|
break;
|
|
2011
2044
|
}
|
|
2012
2045
|
const ago = (ts: number) => {
|
|
@@ -2078,7 +2111,7 @@ switch (cmd) {
|
|
|
2078
2111
|
// already run their own pi connect it with `mlx-bun harness pi`.
|
|
2079
2112
|
const OURS_VAL = new Set([
|
|
2080
2113
|
"--query", "-q", "--model", "--port", "--host", "--memory-budget", "--kv-budget", "--prompt-cache",
|
|
2081
|
-
"--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",
|
|
2082
2115
|
"--batch", "--decode-concurrency", "--adapter", "--adapter-path",
|
|
2083
2116
|
"--draft-model", "--num-draft-tokens", "--draft-kind", "--ngram-max", "--ngram-min",
|
|
2084
2117
|
"--compiled-decode", "--compiled-activations", "--fused-sdpa", "--thinking",
|
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
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -75,6 +75,17 @@ export interface QuantSpec {
|
|
|
75
75
|
bits: number;
|
|
76
76
|
groupSize: number;
|
|
77
77
|
mode: string;
|
|
78
|
+
/** `mode: "trellis"` (packed trellis-coded weights, src/model/trellis-linear.ts):
|
|
79
|
+
* bits = k per weight, groupSize = block T, plus the codec geometry. */
|
|
80
|
+
trellis?: TrellisSpec;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface TrellisSpec {
|
|
84
|
+
L: number;
|
|
85
|
+
code: string;
|
|
86
|
+
/** 1 = coded along the input dim (stored [out, in·k/32]); 0 = along the
|
|
87
|
+
* output dim (stored [in, out·k/32], i.e. Wᵀ coded along its last axis). */
|
|
88
|
+
axis: 0 | 1;
|
|
78
89
|
}
|
|
79
90
|
|
|
80
91
|
export interface QuantizationConfig {
|
|
@@ -89,12 +100,12 @@ export interface KvQuantSpec {
|
|
|
89
100
|
groupSize: number;
|
|
90
101
|
}
|
|
91
102
|
|
|
92
|
-
/** TurboQuant scheme (docs/design/turboquant
|
|
103
|
+
/** TurboQuant scheme (docs/design/turboquant.md): rotation-based KV
|
|
93
104
|
* quantization, a distinct axis from the uniform/per-layer affine kvQuant
|
|
94
105
|
* above (composes with it in principle; v1 ships as a standalone CLI-only
|
|
95
106
|
* runtime lever, same class as uniform kvBits — see
|
|
96
107
|
* src/generate.ts maybeQuantizeKv). Valid kBits: {2,4,5,8}; vBits:
|
|
97
|
-
* {2,3,4,5,8} (docs/design/turboquant
|
|
108
|
+
* {2,3,4,5,8} (docs/design/turboquant.md's supported Lloyd-Max tables). */
|
|
98
109
|
export interface TurboQuantScheme {
|
|
99
110
|
kBits: number;
|
|
100
111
|
vBits: number;
|
|
@@ -162,11 +173,17 @@ export function parseQuantization(raw: Record<string, any> | undefined): Quantiz
|
|
|
162
173
|
for (const [key, value] of Object.entries(raw)) {
|
|
163
174
|
if (key === "bits" || key === "group_size" || key === "mode") continue;
|
|
164
175
|
if (typeof value === "object" && value !== null) {
|
|
165
|
-
|
|
176
|
+
const spec: QuantSpec = {
|
|
166
177
|
bits: (value as any).bits ?? def.bits,
|
|
167
178
|
groupSize: (value as any).group_size ?? def.groupSize,
|
|
168
179
|
mode: (value as any).mode ?? def.mode,
|
|
169
|
-
}
|
|
180
|
+
};
|
|
181
|
+
const tr = (value as any).trellis;
|
|
182
|
+
if (spec.mode === "trellis") {
|
|
183
|
+
if (!tr || typeof tr !== "object") throw new Error(`quantization.${key}: mode "trellis" needs a trellis {L, code, axis} block`);
|
|
184
|
+
spec.trellis = { L: Number(tr.L), code: String(tr.code ?? "1mad"), axis: tr.axis === 0 ? 0 : 1 };
|
|
185
|
+
}
|
|
186
|
+
perLayer.set(key, spec);
|
|
170
187
|
} else if (value === false) {
|
|
171
188
|
// mlx convention: `"layer": false` means not quantized
|
|
172
189
|
perLayer.set(key, { bits: 0, groupSize: 0, mode: "none" });
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Cancellation } from "./generation";
|
|
2
|
+
|
|
3
|
+
/** Application-facing calls. The implementation owns tokenization, numerical
|
|
4
|
+
* method and transport; callers provide request data and consume results. */
|
|
5
|
+
export interface CompletionClient<Request, Result> {
|
|
6
|
+
complete(request: Request): Promise<Result>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface BatchCompletionClient<Request, Result> extends CompletionClient<Request, Result> {
|
|
10
|
+
/** Results preserve input order; the implementation chooses execution groups. */
|
|
11
|
+
completeBatch(requests: readonly Request[]): Promise<Result[]>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface TaskClient<Request, Progress, Result> {
|
|
15
|
+
run(request: Request, report: (progress: Progress) => void, cancellation?: Cancellation): Promise<Result>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** Request facts only. Native resources remain with the preparation owner. */
|
|
2
|
+
export interface ExecutionRequirements {
|
|
3
|
+
readonly hasVision: boolean;
|
|
4
|
+
readonly hasAdapters: boolean;
|
|
5
|
+
readonly hasRepetitionPenalty: boolean;
|
|
6
|
+
readonly userSeed: boolean;
|
|
7
|
+
readonly kvQuant: boolean;
|
|
8
|
+
readonly turboQuant: boolean;
|
|
9
|
+
readonly hasLogitsExtras: boolean;
|
|
10
|
+
readonly hasGrammar: boolean;
|
|
11
|
+
readonly wantsLogprobs: boolean;
|
|
12
|
+
readonly hasDraft: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ExecutionCapabilities {
|
|
16
|
+
readonly method: "autoregressive" | "denoising";
|
|
17
|
+
readonly continuous: boolean;
|
|
18
|
+
readonly quantizedBatch: boolean;
|
|
19
|
+
readonly grammarBatch: boolean;
|
|
20
|
+
readonly checkpoints: boolean;
|
|
21
|
+
/** Model-qualified speculative execution can retain this request's KV codec. */
|
|
22
|
+
readonly speculativeKvQuant?: boolean;
|
|
23
|
+
/** A graph-owned compiled step exists; cache geometry can still decline it. */
|
|
24
|
+
readonly compiledDecode?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ExecutionFeatures {
|
|
28
|
+
readonly pagedKv: boolean;
|
|
29
|
+
readonly fill: boolean;
|
|
30
|
+
readonly compiledDecode?: boolean;
|
|
31
|
+
readonly grammarJump?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Selected once; consumers execute/report these values without reselecting. */
|
|
35
|
+
export interface ResolvedExecution {
|
|
36
|
+
/** Implementation-owned method ID. The built-in planner retains its known
|
|
37
|
+
* methods; another model may register a different set without editing this contract. */
|
|
38
|
+
readonly method: string;
|
|
39
|
+
readonly mechanism: "serial" | "continuous";
|
|
40
|
+
readonly pagedKv: boolean;
|
|
41
|
+
readonly promptCache: boolean;
|
|
42
|
+
readonly checkpoint: boolean;
|
|
43
|
+
readonly fill: boolean;
|
|
44
|
+
/** Permission to attempt the bound compiled step, never a promise that
|
|
45
|
+
* every dynamic row/state shape supports replay. */
|
|
46
|
+
readonly compiledDecode: boolean;
|
|
47
|
+
readonly grammarJump: boolean;
|
|
48
|
+
readonly reasons: readonly string[];
|
|
49
|
+
}
|