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
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { DenoisingGraph } from "../../inference/denoising";
|
|
2
|
+
import type { MlxArray } from "../../mlx/array";
|
|
3
|
+
import type { Cache } from "../../model/gemma4-base";
|
|
4
|
+
import type { DiffusionGemmaModel } from "../../model/diffusion-gemma";
|
|
5
|
+
import type { MlxModelMemory } from "./autoregressive";
|
|
6
|
+
import { disposeResources } from "../../engine/resources";
|
|
7
|
+
import { runtimeConfig, type RuntimeConfig } from "../../runtime-config";
|
|
8
|
+
|
|
9
|
+
export interface MlxDenoisingBinding<State = Cache[]> {
|
|
10
|
+
readonly runtime?: RuntimeConfig;
|
|
11
|
+
readonly graph: DenoisingGraph<MlxArray, State>;
|
|
12
|
+
readonly memory: MlxModelMemory;
|
|
13
|
+
readonly adapters?: { active: string[] };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function bindLegacyDenoisingModel(model: DiffusionGemmaModel): MlxDenoisingBinding {
|
|
17
|
+
return {
|
|
18
|
+
runtime: runtimeConfig(),
|
|
19
|
+
memory: model, adapters: model.loraState,
|
|
20
|
+
graph: {
|
|
21
|
+
descriptor: Object.freeze({ id: "legacy-diffusion-gemma", backend: "mlx",
|
|
22
|
+
graphAbi: "mlx-denoising-v1", stateAbi: "legacy-cache-array-v1", artifact: "legacy-resident-model" }),
|
|
23
|
+
vocabSize: model.config.text.vocabSize, canvasLength: model.canvasLength, embedScale: model.embedScale,
|
|
24
|
+
prefill: (ids, vision) => vision ? model.prefillVision(ids, vision) : model.prefill(ids),
|
|
25
|
+
extendPrefill: model.extendPrefill.bind(model),
|
|
26
|
+
decoderLogits: model.decoderLogits.bind(model),
|
|
27
|
+
dequantEmbedWeight: model.dequantEmbedWeight.bind(model),
|
|
28
|
+
softEmbeddings: model.softEmbeddings.bind(model),
|
|
29
|
+
closeState: disposeResources,
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function assertMlxDenoisingGraph<State>(graph: DenoisingGraph<MlxArray, State>): void {
|
|
35
|
+
const descriptor = graph.descriptor;
|
|
36
|
+
if (descriptor.backend !== "mlx" || descriptor.graphAbi !== "mlx-denoising-v1" ||
|
|
37
|
+
!descriptor.stateAbi)
|
|
38
|
+
throw new Error(`denoising graph ${descriptor.id} has an incompatible backend, graph, or state ABI`);
|
|
39
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { RuntimeModel } from "../../model/factory";
|
|
2
|
+
import { Gemma4Model, type Cache } from "../../model/gemma4";
|
|
3
|
+
import { Qwen35Model } from "../../model/qwen3_5";
|
|
4
|
+
import type { MlxArray } from "../../mlx/array";
|
|
5
|
+
import * as ops from "../../mlx/ops";
|
|
6
|
+
import type { TargetView, AssistantTarget } from "../../spec/source";
|
|
7
|
+
|
|
8
|
+
/** Class checks and live cache layout stay at the compatibility boundary.
|
|
9
|
+
* A replacement graph supplies the same ports without becoming these classes. */
|
|
10
|
+
export function bindLegacyDraftTarget(model: RuntimeModel, caches: Cache[]): TargetView {
|
|
11
|
+
if (model instanceof Gemma4Model) {
|
|
12
|
+
let sliding = -1, full = -1;
|
|
13
|
+
for (let i = 0; i < model.numDonors; i++) {
|
|
14
|
+
if (model.layers[i]!.layerType === "sliding_attention") sliding = i;
|
|
15
|
+
else full = i;
|
|
16
|
+
}
|
|
17
|
+
const view = (index: number): [MlxArray, MlxArray] => {
|
|
18
|
+
const cache = caches[index];
|
|
19
|
+
if (!cache || !("temporalView" in cache) || typeof cache.temporalView !== "function")
|
|
20
|
+
throw new Error("assistant target has no plain donor view");
|
|
21
|
+
return cache.temporalView();
|
|
22
|
+
};
|
|
23
|
+
const assistant: AssistantTarget | undefined = sliding >= 0 && full >= 0 ? {
|
|
24
|
+
position: () => caches[0]!.offset - 1,
|
|
25
|
+
embedScaled(token) {
|
|
26
|
+
const ids = ops.fromInt32([token], [1, 1]);
|
|
27
|
+
let embedded: MlxArray | undefined;
|
|
28
|
+
try { embedded = model.embed.encode(ids); return ops.mulScalar(embedded, model.embedScale); }
|
|
29
|
+
finally { ids.dispose(); embedded?.dispose(); }
|
|
30
|
+
},
|
|
31
|
+
readDonors() {
|
|
32
|
+
const slidingView = view(sliding);
|
|
33
|
+
try { return { sliding: slidingView, full: view(full) }; }
|
|
34
|
+
catch (error) { for (const array of slidingView) array.dispose(); throw error; }
|
|
35
|
+
},
|
|
36
|
+
} : undefined;
|
|
37
|
+
return Object.freeze({ identity: model, assistant, gemmaTaps: Object.freeze({
|
|
38
|
+
layerCount: model.layers.length,
|
|
39
|
+
projection: Object.freeze({ embed: model.embed, logitsFromHidden: model.logitsFromHidden.bind(model) }),
|
|
40
|
+
}) });
|
|
41
|
+
}
|
|
42
|
+
if (model instanceof Qwen35Model) return Object.freeze({ identity: model, qwenMtp: Object.freeze({
|
|
43
|
+
hiddenSize: model.config.text.hiddenSize, layerCount: model.config.text.numHiddenLayers,
|
|
44
|
+
embed: model.embed.encode.bind(model.embed), logitsFromHidden: model.logitsFromHidden.bind(model),
|
|
45
|
+
}) });
|
|
46
|
+
return Object.freeze({ identity: model });
|
|
47
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { TaskModel, GenOpts } from "../../eval/runner";
|
|
2
|
+
import type { CompletionClient } from "../../contracts/completion";
|
|
3
|
+
import { generate } from "../../generate";
|
|
4
|
+
import { clearCache } from "../../mlx/ffi";
|
|
5
|
+
import * as ops from "../../mlx/ops";
|
|
6
|
+
import { runtimeValue } from "../../runtime-config";
|
|
7
|
+
import { DiffusionGemmaModel } from "../../model/diffusion-gemma";
|
|
8
|
+
|
|
9
|
+
/** Bit-exact greedy decode: raw `model.forward` + argmax loop, which matches mlx-lm
|
|
10
|
+
* token-for-token (gen8k: 8000/8000). The product `generate()` decode wrapper
|
|
11
|
+
* (forwardHidden/logitsFromHidden + pipelined sampler) diverges from the raw forward
|
|
12
|
+
* on near-ties past ~32 tokens, so the EVAL decodes bit-exactly itself rather than
|
|
13
|
+
* alter the serving path. Greedy + full-precision KV only. */
|
|
14
|
+
export function greedyDecodeBitExact(tm: TaskModel, ids: number[], maxTokens: number): string {
|
|
15
|
+
if (tm.activeAdapters) tm.model.loraState.active = tm.activeAdapters; // apply the mounted adapter
|
|
16
|
+
const cache = tm.model.makeCache();
|
|
17
|
+
try {
|
|
18
|
+
// Chunked, LM-head-free prefill. The old single-shot `forward(prompt)`
|
|
19
|
+
// materialized [1, L, vocab] logits just to discard them — at a 248k
|
|
20
|
+
// vocab that's ~1 GB per 1k prompt tokens, and with no allocator-cache
|
|
21
|
+
// clears the Metal buffer cache retained every giant freed tensor
|
|
22
|
+
// (the 27B capability-suite 73 GB swap-thrash). Prefill only needs the
|
|
23
|
+
// cache state, which forwardHidden advances identically (the LM head
|
|
24
|
+
// never touches the cache) — bit-exact by construction. Chunk size and
|
|
25
|
+
// clear cadence mirror mlx-lm generate_step / our generate().
|
|
26
|
+
const PREFILL_CHUNK = 2048;
|
|
27
|
+
const prompt = ids.slice(0, -1);
|
|
28
|
+
for (let s = 0; s < prompt.length; s += PREFILL_CHUNK) {
|
|
29
|
+
const chunk = prompt.slice(s, s + PREFILL_CHUNK);
|
|
30
|
+
const cIds = ops.fromInt32(chunk, [1, chunk.length]);
|
|
31
|
+
tm.model.forwardHidden(cIds, cache).dispose();
|
|
32
|
+
cIds.dispose();
|
|
33
|
+
clearCache();
|
|
34
|
+
}
|
|
35
|
+
let last = ids[ids.length - 1]!;
|
|
36
|
+
const eos = new Set(tm.config.eosTokenIds);
|
|
37
|
+
const out: number[] = [];
|
|
38
|
+
for (let i = 0; i < maxTokens; i++) {
|
|
39
|
+
const lg = tm.model.forward([last], cache);
|
|
40
|
+
const am = ops.argmaxAxis(lg, -1); // GPU argmax (mlx-lm greedy: lowest-index tie-break)
|
|
41
|
+
lg.dispose();
|
|
42
|
+
const best = ops.itemUint32(am);
|
|
43
|
+
am.dispose();
|
|
44
|
+
if (i % 256 === 0) clearCache(); // mlx-lm decode cadence
|
|
45
|
+
if (eos.has(best)) break; // mlx-lm halts on EOS and excludes it from the output
|
|
46
|
+
out.push(best);
|
|
47
|
+
last = best;
|
|
48
|
+
}
|
|
49
|
+
return tm.tokenizer.decode(out, true);
|
|
50
|
+
} finally {
|
|
51
|
+
for (const c of cache) c.dispose();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Complete `body` via the model's real quantized-KV path. Greedy by default;
|
|
56
|
+
* pass `opts.sampler` to drive temperature/HLG/etc. */
|
|
57
|
+
async function generateTextNative(tm: TaskModel, body: string, opts: GenOpts = {}): Promise<string> {
|
|
58
|
+
const maxTokens = opts.maxTokens ?? 256;
|
|
59
|
+
const enableThinking = opts.enableThinking ?? runtimeValue("MLX_BUN_EVAL_THINK") === "1";
|
|
60
|
+
const templated = opts.useChat !== false && tm.template !== null;
|
|
61
|
+
const text = templated
|
|
62
|
+
? tm.template!.render([{ role: "user", content: body }], { addGenerationPrompt: true, enableThinking })
|
|
63
|
+
: body;
|
|
64
|
+
// The chat template ALREADY emits the BOS; encoding it with add_special_tokens on
|
|
65
|
+
// would prepend a SECOND BOS (<s><s>…), corrupting generation. Raw bodies (no
|
|
66
|
+
// template) get the BOS added as normal.
|
|
67
|
+
const ids = tm.tokenizer.encode(text, /* addSpecialTokens */ !templated);
|
|
68
|
+
|
|
69
|
+
// optiq's PUBLISHED eval generates with a FULL-PRECISION KV cache — its eval calls
|
|
70
|
+
// plain mlx_lm.generate (kv-quant lives in the serving runtime, not the eval). So
|
|
71
|
+
// for like-for-like parity we default the eval to UNQUANTIZED KV; MMLU's argmax
|
|
72
|
+
// path was already unquantized, which is why it matched while generation didn't.
|
|
73
|
+
// MLX_BUN_EVAL_KV_QUANT=1 generates through the model's quantized KV (serving
|
|
74
|
+
// config); an explicit opts.kvScheme (cli generate's resolved tier route) wins.
|
|
75
|
+
const envKv: GenOpts["kvScheme"] | undefined =
|
|
76
|
+
(runtimeValue("MLX_BUN_EVAL_KV_QUANT") === "1" && tm.config.kvQuant?.length)
|
|
77
|
+
? { kvConfig: tm.config.kvQuant, quantizedKvStart: 0 } : undefined;
|
|
78
|
+
const kvScheme = opts.kvScheme ?? envKv;
|
|
79
|
+
const kvActive = !!(kvScheme && (kvScheme.kvBits || kvScheme.kvConfig?.length || kvScheme.turboQuant));
|
|
80
|
+
|
|
81
|
+
// Parity default — greedy + full-precision KV + no sampler arm: decode bit-exactly
|
|
82
|
+
// via the raw forward (matches mlx-lm token-for-token). Sampler arms / kv-quant fall
|
|
83
|
+
// through to the product generate(). DiffusionGemma is non-autoregressive (no AR
|
|
84
|
+
// forward) — always go through generate(), which routes it to the denoising engine.
|
|
85
|
+
const isDiffusion = tm.model instanceof DiffusionGemmaModel;
|
|
86
|
+
if (!isDiffusion && !kvActive && !opts.sampler && !tm.samplerOverride) {
|
|
87
|
+
return greedyDecodeBitExact(tm, ids, maxTokens);
|
|
88
|
+
}
|
|
89
|
+
const gen = generate(tm.model, ids, {
|
|
90
|
+
maxTokens,
|
|
91
|
+
temperature: 0, // greedy default — deterministic head-to-head arms
|
|
92
|
+
adapters: tm.activeAdapters ?? [], // per-request activation of the mounted eval-adapter
|
|
93
|
+
...(kvActive ? kvScheme : {}),
|
|
94
|
+
...(opts.sampler ?? {}), // overrides temperature when provided
|
|
95
|
+
...(tm.samplerOverride ?? {}), // arm override wins over the task's own sampler
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const out: number[] = [];
|
|
99
|
+
for await (const { token } of gen) out.push(token);
|
|
100
|
+
return tm.tokenizer.decode(out, true);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function createEvalCompletionClient(tm: TaskModel): CompletionClient<{ body: string; options?: GenOpts }, string> {
|
|
104
|
+
return { complete: ({ body, options }) => generateTextNative(tm, body, options) };
|
|
105
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { MlxArray } from "../../mlx/array";
|
|
2
|
+
import * as ops from "../../mlx/ops";
|
|
3
|
+
import type { Cache } from "../../model/gemma4";
|
|
4
|
+
import { withResource } from "../../engine/resources";
|
|
5
|
+
import { leaseCacheStates } from "./state-views";
|
|
6
|
+
|
|
7
|
+
/** Append an already accepted span without intermediate vocabulary heads.
|
|
8
|
+
* Chunking changes the graph shape, so callers must qualify its numerics for
|
|
9
|
+
* their model. A verify round must use chunkSize=0: recurrent rollback records
|
|
10
|
+
* one forward per round. The complete append precedes emission/cancellation
|
|
11
|
+
* boundaries, preserving the cache-covered prefix on a consumer break.
|
|
12
|
+
*/
|
|
13
|
+
export async function appendFillHidden(
|
|
14
|
+
forward: (ids: MlxArray, cache: Cache[]) => MlxArray | Promise<MlxArray>,
|
|
15
|
+
cache: Cache[],
|
|
16
|
+
ids: number[],
|
|
17
|
+
chunkSize: (cache: readonly Cache[]) => number,
|
|
18
|
+
): Promise<MlxArray> {
|
|
19
|
+
const input = ops.fromInt32(ids, [1, ids.length]);
|
|
20
|
+
const hidden: MlxArray[] = [];
|
|
21
|
+
try {
|
|
22
|
+
for (let start = 0; start < ids.length;) {
|
|
23
|
+
const limit = chunkSize(cache);
|
|
24
|
+
if (!Number.isSafeInteger(limit) || limit < 1)
|
|
25
|
+
throw new Error("Model append chunk limit must be a positive integer");
|
|
26
|
+
const end = Math.min(ids.length, start + limit);
|
|
27
|
+
if (start === 0 && end === ids.length) return await forward(input, cache);
|
|
28
|
+
const part = input.slice([0, start], [1, end]);
|
|
29
|
+
try {
|
|
30
|
+
const h = await forward(part, cache);
|
|
31
|
+
hidden.push(h);
|
|
32
|
+
// Bound transient graphs while retaining all positions for an optional
|
|
33
|
+
// diagnostic trace. Normal generation projects only the last position.
|
|
34
|
+
withResource(leaseCacheStates(cache), (state) => ops.evalAll([h, ...state]));
|
|
35
|
+
} finally {
|
|
36
|
+
part.dispose();
|
|
37
|
+
}
|
|
38
|
+
start = end;
|
|
39
|
+
}
|
|
40
|
+
return ops.concatAxis(hidden, 1);
|
|
41
|
+
} finally {
|
|
42
|
+
for (const h of hidden) h.dispose();
|
|
43
|
+
input.dispose();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { ModelConfig } from "../../config";
|
|
2
|
+
import type { KvScheme } from "../../kv-scheme";
|
|
3
|
+
import type { RuntimeModel } from "../../model/factory";
|
|
4
|
+
import { DiffusionGemmaModel } from "../../model/diffusion-gemma";
|
|
5
|
+
import { UniversalDenseModel } from "../../model/universal/dense";
|
|
6
|
+
import { KVCache, RotatingKVCache, isBatchableCache, isPlainKvCache, isRotatingPlainCache } from "../../model/gemma4-base";
|
|
7
|
+
import { SSMCache } from "../../model/qwen3-delta";
|
|
8
|
+
import { Qwen35Model } from "../../model/qwen3_5";
|
|
9
|
+
import { runtimeConfig, type RuntimeConfig } from "../../runtime-config";
|
|
10
|
+
import { disposeResources } from "../../engine/resources";
|
|
11
|
+
import { legacyCompiledDecodeAvailable } from "./autoregressive";
|
|
12
|
+
import { MlxBatchExecutionGroup, type MlxBatchExecutionGroupOptions } from "./batch-group";
|
|
13
|
+
import type { GenerateOptions } from "../../generate";
|
|
14
|
+
import type { ExecutionRequirements, ResolvedExecution } from "../../contracts/execution";
|
|
15
|
+
import { resolveExecution } from "../../engine/execution-plan";
|
|
16
|
+
|
|
17
|
+
export interface MlxBatchGroup extends Pick<MlxBatchExecutionGroup,
|
|
18
|
+
"activeRows" | "pendingRows" | "projectedKvBytes" | "kvBudgetBytes" | "submit" | "kick" | "close"> {}
|
|
19
|
+
|
|
20
|
+
/** A model implementation owns capability checks and the execution group.
|
|
21
|
+
* Scheduling never inspects concrete model/cache classes. */
|
|
22
|
+
export interface MlxGatewayBinding {
|
|
23
|
+
readonly config: ModelConfig;
|
|
24
|
+
readonly runtime: RuntimeConfig;
|
|
25
|
+
plan(request: ExecutionRequirements, options: GenerateOptions,
|
|
26
|
+
scheduling: { continuous: boolean; quantizedBatch: boolean; checkpoints: boolean }): ResolvedExecution;
|
|
27
|
+
cachesBatchable(): boolean;
|
|
28
|
+
kvBatchable(scheme: KvScheme): boolean;
|
|
29
|
+
createBatchGroup(options: MlxBatchExecutionGroupOptions): MlxBatchGroup;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function bindMlxGateway(model: RuntimeModel): MlxGatewayBinding {
|
|
33
|
+
const runtime = runtimeConfig();
|
|
34
|
+
return {
|
|
35
|
+
config: model.config, runtime,
|
|
36
|
+
plan(request, options, scheduling) {
|
|
37
|
+
return resolveExecution(request, {
|
|
38
|
+
...scheduling,
|
|
39
|
+
method: model instanceof DiffusionGemmaModel ? "denoising" : "autoregressive",
|
|
40
|
+
compiledDecode: legacyCompiledDecodeAvailable(model),
|
|
41
|
+
grammarBatch: runtime.value("MLX_BUN_GRAMMAR_BATCH") !== "0",
|
|
42
|
+
speculativeKvQuant: model instanceof Qwen35Model && options.kvBits === 4 &&
|
|
43
|
+
!options.kvConfig?.length && options.quantizedKvStart === 0 &&
|
|
44
|
+
runtime.flag("MLX_BUN_QWEN_SPEC_KV4", false),
|
|
45
|
+
}, {
|
|
46
|
+
pagedKv: !!options.pagedKv, fill: !!options.fill,
|
|
47
|
+
compiledDecode: runtime.flag("MLX_BUN_COMPILED_DECODE", true),
|
|
48
|
+
grammarJump: runtime.flag("MLX_BUN_GRAMMAR_JUMP", false),
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
cachesBatchable() {
|
|
52
|
+
if (model instanceof UniversalDenseModel)
|
|
53
|
+
return !model.args.maskArray && !model.args.layerTypes?.includes("sliding_attention");
|
|
54
|
+
const caches = model.makeCache();
|
|
55
|
+
try {
|
|
56
|
+
const ssm = runtime.value("MLX_BUN_BATCH_SSM") !== "0";
|
|
57
|
+
return caches.every((cache) => cache instanceof KVCache || cache instanceof RotatingKVCache ||
|
|
58
|
+
isBatchableCache(cache) || (ssm && cache instanceof SSMCache));
|
|
59
|
+
} finally { disposeResources(caches); }
|
|
60
|
+
},
|
|
61
|
+
kvBatchable(scheme) {
|
|
62
|
+
if (scheme.kind !== "affine-config") return false;
|
|
63
|
+
const caches = model.makeCache();
|
|
64
|
+
try {
|
|
65
|
+
return scheme.batchable(model.config,
|
|
66
|
+
(layer) => isPlainKvCache(caches[layer]) || isRotatingPlainCache(caches[layer]));
|
|
67
|
+
} finally { disposeResources(caches); }
|
|
68
|
+
},
|
|
69
|
+
createBatchGroup: (options) => new MlxBatchExecutionGroup(model, options),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AutoregressiveGraph, GraphDescriptor, LogitSelection } from "../../inference/graph";
|
|
2
|
+
import type { MlxArray } from "../../mlx/array";
|
|
3
|
+
|
|
4
|
+
/** Structural input: generated and hand-written graphs need no union membership. */
|
|
5
|
+
export interface MlxGraphOperations<State> {
|
|
6
|
+
forwardHidden(ids: MlxArray, state: State): MlxArray;
|
|
7
|
+
forwardHiddenAsync?: (ids: MlxArray, state: State) => Promise<MlxArray>;
|
|
8
|
+
logitsFromHidden(hidden: MlxArray): MlxArray;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Bind once. No array wrapper, await, readback, or synchronization is added
|
|
12
|
+
* to synchronous forward calls. Existing quant-specific kernels stay inside
|
|
13
|
+
* the supplied operations. This adapter does not take ownership of weights. */
|
|
14
|
+
export function bindMlxGraph<State>(
|
|
15
|
+
operations: MlxGraphOperations<State>,
|
|
16
|
+
descriptor: Omit<GraphDescriptor, "backend" | "graphAbi">,
|
|
17
|
+
): AutoregressiveGraph<MlxArray, State, MlxArray> {
|
|
18
|
+
const forward = operations.forwardHiddenAsync
|
|
19
|
+
? operations.forwardHiddenAsync.bind(operations)
|
|
20
|
+
: operations.forwardHidden.bind(operations);
|
|
21
|
+
const project = operations.logitsFromHidden.bind(operations);
|
|
22
|
+
return {
|
|
23
|
+
descriptor: Object.freeze({ ...descriptor, backend: "mlx", graphAbi: "mlx-hidden-bsh-v1" }),
|
|
24
|
+
forwardHidden: forward,
|
|
25
|
+
projectLogits(hidden: MlxArray, selection: LogitSelection) {
|
|
26
|
+
if (selection.type === "all") return project(hidden);
|
|
27
|
+
const shape = hidden.shape;
|
|
28
|
+
if (shape.length !== 3) throw new Error("MLX hidden state must have shape [batch, positions, width]");
|
|
29
|
+
const [batch, positions, width] = shape as [number, number, number];
|
|
30
|
+
const start = selection.type === "last" ? positions - 1 : selection.start;
|
|
31
|
+
const end = selection.type === "last" ? positions : selection.end;
|
|
32
|
+
if (!Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end <= start || end > positions)
|
|
33
|
+
throw new Error("invalid logits position selection");
|
|
34
|
+
const selected = hidden.slice([0, start, 0], [batch, end, width]);
|
|
35
|
+
try { return project(selected); } finally { selected.dispose(); }
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { loadTaskModel, greedyDecodeBitExact, type TaskModel } from "../../eval/runner";
|
|
3
|
+
import type { ChatTemplate } from "../../chat-template";
|
|
4
|
+
import { MlxBatchExecutionGroup as BatchScheduler, type RowSampler } from "./batch-group";
|
|
5
|
+
import * as ops from "../../mlx/ops";
|
|
6
|
+
import type { MlxArray } from "../../mlx/array";
|
|
7
|
+
import { adapterDirFor, memoryBatchSize, memoryPromptIds, MODEL_ID,
|
|
8
|
+
type LocalInput, type MemoryCompletionClient } from "../../memory/model";
|
|
9
|
+
|
|
10
|
+
class AsyncMutex {
|
|
11
|
+
#tail: Promise<void> = Promise.resolve();
|
|
12
|
+
acquire(): Promise<() => void> {
|
|
13
|
+
let release!: () => void;
|
|
14
|
+
const gate = new Promise<void>((r) => { release = r; });
|
|
15
|
+
const wait = this.#tail;
|
|
16
|
+
this.#tail = this.#tail.then(() => gate);
|
|
17
|
+
return wait.then(() => release);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface MemoryRuntime {
|
|
22
|
+
tm: TaskModel;
|
|
23
|
+
template: ChatTemplate;
|
|
24
|
+
scheduler: BatchScheduler;
|
|
25
|
+
/** True once the `memory-chunk` adapter is mounted on the shared model. */
|
|
26
|
+
hasChunkAdapter: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
function requireModel(): void {
|
|
31
|
+
if (!existsSync(`${MODEL_ID}/config.json`))
|
|
32
|
+
throw new Error(`memory: Gemma-4-e4b is not downloaded (looked under ${MODEL_ID}). Fetch it first: HF_HUB_DISABLE_XET=1 hf download mlx-community/gemma-4-e4b-it-OptiQ-4bit`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let runtimePromise: Promise<MemoryRuntime> | null = null;
|
|
36
|
+
const opLock = new AsyncMutex(); // serializes whole memory ops (loraState ownership)
|
|
37
|
+
const schedLock = new AsyncMutex(); // the scheduler's exclusive-GPU lock
|
|
38
|
+
|
|
39
|
+
/** Lazily load the ONE shared model + mount memory-chunk + build the scheduler.
|
|
40
|
+
* Never auto-downloads; throws (with the fetch hint) until the snapshot lands. */
|
|
41
|
+
async function getRuntime(): Promise<MemoryRuntime> {
|
|
42
|
+
if (!runtimePromise) {
|
|
43
|
+
runtimePromise = (async () => {
|
|
44
|
+
requireModel();
|
|
45
|
+
const tm = await loadTaskModel(MODEL_ID); // base e4b, no adapter
|
|
46
|
+
if (!tm.template) throw new Error(`memory: ${MODEL_ID} has no chat template`);
|
|
47
|
+
let hasChunkAdapter = false;
|
|
48
|
+
const chunkDir = adapterDirFor("chunk");
|
|
49
|
+
if (chunkDir) {
|
|
50
|
+
// Mount ONCE; activation is per-call via loraState.active (see decodeGreedy
|
|
51
|
+
// / batchDecode). Mounting alone runs base — it must be activated.
|
|
52
|
+
const { AdapterManager } = await import("../../lora");
|
|
53
|
+
await new AdapterManager(tm.model).mount("memory-chunk", chunkDir);
|
|
54
|
+
hasChunkAdapter = true;
|
|
55
|
+
}
|
|
56
|
+
const scheduler = new BatchScheduler(tm.model, {
|
|
57
|
+
maxBatch: memoryBatchSize(),
|
|
58
|
+
lock: { acquire: () => schedLock.acquire() },
|
|
59
|
+
});
|
|
60
|
+
return { tm, template: tm.template, scheduler, hasChunkAdapter };
|
|
61
|
+
})();
|
|
62
|
+
}
|
|
63
|
+
return runtimePromise;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The adapters to activate for a stage. Only `chunk` is adapter-bound (and only
|
|
67
|
+
* when `memory-chunk` is mounted); every base stage runs with `active = []`. */
|
|
68
|
+
function activeFor(stage: string, rt: MemoryRuntime): string[] {
|
|
69
|
+
return stage === "chunk" && rt.hasChunkAdapter ? ["memory-chunk"] : [];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** One bit-exact greedy decode for a stage (raw-forward argmax, runner parity).
|
|
73
|
+
* Sets the stage's adapter for the call and resets `active` to [] after. */
|
|
74
|
+
function decodeGreedy(rt: MemoryRuntime, stage: string, input: LocalInput, maxTokens: number): string {
|
|
75
|
+
const active = activeFor(stage, rt);
|
|
76
|
+
rt.tm.model.loraState.active = active;
|
|
77
|
+
const ids = memoryPromptIds(stage, input, rt.tm.tokenizer, rt.template);
|
|
78
|
+
try {
|
|
79
|
+
return greedyDecodeBitExact(
|
|
80
|
+
{ ...rt.tm, activeAdapters: active.length ? active : undefined },
|
|
81
|
+
ids,
|
|
82
|
+
maxTokens,
|
|
83
|
+
);
|
|
84
|
+
} finally {
|
|
85
|
+
rt.tm.model.loraState.active = [];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Batched decode for N independent inputs of ONE stage. Sets the stage adapter
|
|
90
|
+
* globally for the whole batch (the scheduler reads `loraState.active` per
|
|
91
|
+
* forward, so all rows get it uniformly — a batch is necessarily single-adapter),
|
|
92
|
+
* submits every row to the shared scheduler with a greedy per-row sampler, and
|
|
93
|
+
* resets `active` after all rows finish. Order-preserving: out[i] ↔ inputs[i].
|
|
94
|
+
*
|
|
95
|
+
* Decode-path caveat: this samples via forwardHidden/logitsFromHidden (the
|
|
96
|
+
* scheduler), which can diverge from the runner's raw-forward greedy on near-ties
|
|
97
|
+
* past ~32 tokens. For the JSON/binary memory stages (validated by
|
|
98
|
+
* well-formedness) this is acceptable; the single-call fallback stays bit-exact. */
|
|
99
|
+
async function batchDecode(
|
|
100
|
+
rt: MemoryRuntime,
|
|
101
|
+
stage: string,
|
|
102
|
+
inputs: LocalInput[],
|
|
103
|
+
maxTokens: number,
|
|
104
|
+
): Promise<string[]> {
|
|
105
|
+
const active = activeFor(stage, rt);
|
|
106
|
+
rt.tm.model.loraState.active = active;
|
|
107
|
+
const eos = rt.tm.config.eosTokenIds;
|
|
108
|
+
const greedySample: RowSampler = (logits1V: MlxArray) => ops.argmaxAxis(logits1V, -1);
|
|
109
|
+
try {
|
|
110
|
+
return await Promise.all(
|
|
111
|
+
inputs.map((inp) => {
|
|
112
|
+
const ids = memoryPromptIds(stage, inp, rt.tm.tokenizer, rt.template);
|
|
113
|
+
const toks: number[] = [];
|
|
114
|
+
return rt.scheduler
|
|
115
|
+
.submit({
|
|
116
|
+
promptIds: ids,
|
|
117
|
+
maxTokens,
|
|
118
|
+
eosTokenIds: eos,
|
|
119
|
+
sample: greedySample,
|
|
120
|
+
onToken: (t) => { toks.push(t); },
|
|
121
|
+
})
|
|
122
|
+
.then(() => rt.tm.tokenizer.decode(toks, true));
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
125
|
+
} finally {
|
|
126
|
+
rt.tm.model.loraState.active = [];
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// Public seams
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
/** Call the local model for a pipeline `stage` with a {system?, user} input.
|
|
135
|
+
* Renders through the shared chat-template path, runs the MODEL precheck, and
|
|
136
|
+
* decodes ONE completion on the bit-exact greedy path (the safe fallback used by
|
|
137
|
+
* every single-call site). NEVER auto-downloads. */
|
|
138
|
+
export async function callLocal(
|
|
139
|
+
stage: string,
|
|
140
|
+
input: LocalInput,
|
|
141
|
+
opts?: { maxTokens?: number },
|
|
142
|
+
): Promise<string> {
|
|
143
|
+
const rt = await getRuntime();
|
|
144
|
+
const release = await opLock.acquire();
|
|
145
|
+
try {
|
|
146
|
+
return decodeGreedy(rt, stage, input, opts?.maxTokens ?? 256);
|
|
147
|
+
} finally {
|
|
148
|
+
release();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** N independent calls for ONE stage (homogeneous adapter by construction).
|
|
153
|
+
* Routed through the shared in-process BatchScheduler when batch>1 AND N>1, else
|
|
154
|
+
* looped on the bit-exact greedy fallback. Order-preserving: out[i] ↔ inputs[i]. */
|
|
155
|
+
export async function callLocalBatch(
|
|
156
|
+
stage: string,
|
|
157
|
+
inputs: LocalInput[],
|
|
158
|
+
opts?: { maxTokens?: number },
|
|
159
|
+
): Promise<string[]> {
|
|
160
|
+
if (inputs.length === 0) return [];
|
|
161
|
+
const rt = await getRuntime();
|
|
162
|
+
const maxTokens = opts?.maxTokens ?? 256;
|
|
163
|
+
const release = await opLock.acquire();
|
|
164
|
+
try {
|
|
165
|
+
if (inputs.length === 1 || memoryBatchSize() <= 1) {
|
|
166
|
+
const out: string[] = [];
|
|
167
|
+
for (const inp of inputs) out.push(decodeGreedy(rt, stage, inp, maxTokens));
|
|
168
|
+
return out;
|
|
169
|
+
}
|
|
170
|
+
return await batchDecode(rt, stage, inputs, maxTokens);
|
|
171
|
+
} finally {
|
|
172
|
+
release();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export const memoryCompletionClient: MemoryCompletionClient = {
|
|
177
|
+
complete: ({ stage, input, maxTokens }) => callLocal(stage, input, { maxTokens }),
|
|
178
|
+
completeBatch(requests) {
|
|
179
|
+
if (!requests.length) return Promise.resolve([]);
|
|
180
|
+
const first = requests[0]!;
|
|
181
|
+
if (requests.some((request) => request.stage !== first.stage || request.maxTokens !== first.maxTokens))
|
|
182
|
+
return (async () => {
|
|
183
|
+
const results: string[] = [];
|
|
184
|
+
for (const request of requests) results.push(await this.complete(request));
|
|
185
|
+
return results;
|
|
186
|
+
})();
|
|
187
|
+
return callLocalBatch(first.stage, requests.map((request) => request.input), { maxTokens: first.maxTokens });
|
|
188
|
+
},
|
|
189
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { DEFAULT_CHUNK, TRANSIENT_PER_TOKEN } from "../../fit";
|
|
2
|
+
import { kvBytesAt, type KvSchemeOptions } from "../../kv-scheme";
|
|
3
|
+
import { activeMemory, clearCache, maxRecommendedWorkingSetSize } from "../../mlx/ffi";
|
|
4
|
+
import type { RuntimeModel } from "../../model/factory";
|
|
5
|
+
import { Qwen35Model } from "../../model/qwen3_5";
|
|
6
|
+
import type { PromptCache } from "../../prompt-cache";
|
|
7
|
+
import { runtimeValue } from "../../runtime-config";
|
|
8
|
+
|
|
9
|
+
/** Admission's ceiling and the cache that can release memory under the GPU lease. */
|
|
10
|
+
export interface MlxMemoryBudget {
|
|
11
|
+
readonly usableBytes: number;
|
|
12
|
+
readonly kvOptions: KvSchemeOptions;
|
|
13
|
+
readonly promptCache: Pick<PromptCache, "relievePressure" | "totalBytes">;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Both execution paths enter after acquiring the request's reusable prefix.
|
|
17
|
+
* The caller owns the GPU lease and must close the scope on every exit. */
|
|
18
|
+
export function enterMlxMemoryGuard(model: RuntimeModel, budget: MlxMemoryBudget,
|
|
19
|
+
promptTokens: number, prefillChunkSize?: number): { check(): void; close(): void } {
|
|
20
|
+
const reserve = (prefillChunkSize ?? DEFAULT_CHUNK) * TRANSIENT_PER_TOKEN +
|
|
21
|
+
kvBytesAt(model.config, promptTokens, budget.kvOptions);
|
|
22
|
+
const ceiling = Math.min(budget.usableBytes, maxRecommendedWorkingSetSize());
|
|
23
|
+
const check = () => {
|
|
24
|
+
if (activeMemory() + reserve <= ceiling) return;
|
|
25
|
+
clearCache();
|
|
26
|
+
if (runtimeValue("MLX_BUN_PREFILL_MEM_LOG") === "1")
|
|
27
|
+
console.error(`[cache-memory] active=${activeMemory()} prompt=${budget.promptCache.totalBytes} reserve=${reserve} ceiling=${ceiling}`);
|
|
28
|
+
const count = budget.promptCache.relievePressure(() => {
|
|
29
|
+
clearCache();
|
|
30
|
+
return activeMemory() + reserve > ceiling;
|
|
31
|
+
});
|
|
32
|
+
if (count) console.log(`[prompt-cache] demoted ${count} entries for GPU headroom`);
|
|
33
|
+
if (activeMemory() + reserve > ceiling)
|
|
34
|
+
throw new Error("insufficient GPU headroom after prompt-cache demotion; saved generation checkpoints are retained");
|
|
35
|
+
};
|
|
36
|
+
const qwen = model instanceof Qwen35Model ? model : undefined;
|
|
37
|
+
const previous = qwen?.prefillMemoryGuard;
|
|
38
|
+
if (qwen) qwen.prefillMemoryGuard = check;
|
|
39
|
+
return { check, close() { if (qwen) qwen.prefillMemoryGuard = previous ?? null; } };
|
|
40
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { runtimeConfig, withRuntimeConfig, type RuntimeConfig } from "../../runtime-config";
|
|
2
|
+
import { snapshotGenerationPolicy } from "./request-policy";
|
|
3
|
+
import type { GenerationOutput, InferenceMethod, MethodResult } from "../../contracts/generation";
|
|
4
|
+
import { throwIfCancelled } from "../../engine/cancellation";
|
|
5
|
+
import { generateAutoregressive, generateDenoising, type GenerateOptions, type GenerateStats } from "../../generate";
|
|
6
|
+
import { specRun } from "../../spec/serve-loop";
|
|
7
|
+
import type { MlxAutoregressiveBinding } from "./autoregressive";
|
|
8
|
+
import type { MlxSpeculativeBinding } from "./speculative";
|
|
9
|
+
import type { MlxDenoisingBinding } from "./diffusion";
|
|
10
|
+
|
|
11
|
+
/** The native algorithm owns cleanup in its finally. This host adapter owns
|
|
12
|
+
* cancellation subscriptions and waits for the algorithm before closing. */
|
|
13
|
+
function nativeMethod<Metrics>(
|
|
14
|
+
id: string,
|
|
15
|
+
execute: (output: GenerationOutput, signal: AbortSignal) => Promise<MethodResult<Metrics>>,
|
|
16
|
+
runtime: RuntimeConfig,
|
|
17
|
+
): InferenceMethod<Metrics> {
|
|
18
|
+
return {
|
|
19
|
+
id,
|
|
20
|
+
async createRun(cancellation) {
|
|
21
|
+
throwIfCancelled(cancellation);
|
|
22
|
+
const abort = new AbortController();
|
|
23
|
+
const unsubscribe = cancellation.subscribe((reason) => abort.abort(
|
|
24
|
+
new DOMException(`generation cancelled: ${reason}`, "AbortError"),
|
|
25
|
+
));
|
|
26
|
+
let work: Promise<MethodResult<Metrics>> | undefined;
|
|
27
|
+
let closing: Promise<void> | undefined;
|
|
28
|
+
return {
|
|
29
|
+
execute(output) {
|
|
30
|
+
if (work || closing) return Promise.reject(new Error("method run can execute only once"));
|
|
31
|
+
work = withRuntimeConfig(runtime, () => execute(output, abort.signal));
|
|
32
|
+
return work;
|
|
33
|
+
},
|
|
34
|
+
close() {
|
|
35
|
+
return closing ??= (async () => {
|
|
36
|
+
abort.abort(new DOMException("method closed", "AbortError"));
|
|
37
|
+
try { await work; } catch { /* execute owns the algorithm's error */ }
|
|
38
|
+
finally { unsubscribe(); }
|
|
39
|
+
})();
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Each plan supplies its prepared per-run resources. Caches/media in options
|
|
47
|
+
* retain the same ownership contract as generateAutoregressive. */
|
|
48
|
+
export function createAutoregressiveMethod(
|
|
49
|
+
binding: MlxAutoregressiveBinding, promptIds: readonly number[], options: GenerateOptions,
|
|
50
|
+
): InferenceMethod<GenerateStats> {
|
|
51
|
+
const prompt = [...promptIds];
|
|
52
|
+
options = snapshotGenerationPolicy(options);
|
|
53
|
+
return nativeMethod("mlx-autoregressive", async (output, signal) => {
|
|
54
|
+
const generation = generateAutoregressive(binding, prompt, { ...options, signal });
|
|
55
|
+
for await (const item of generation)
|
|
56
|
+
if (await output.commit([item.token], item.logprobs ? [item.logprobs] : undefined) === false) break;
|
|
57
|
+
const metrics = generation.stats;
|
|
58
|
+
if (!metrics) throw new Error("AR method did not settle its metrics");
|
|
59
|
+
return { finishReason: metrics.finishReason ?? (metrics.generatedTokens >= (options.maxTokens ?? 512) ? "length" : "stop"), metrics };
|
|
60
|
+
}, binding.runtime ?? runtimeConfig());
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function createSpeculativeMethod(
|
|
64
|
+
binding: MlxSpeculativeBinding, numDraftTokens: number,
|
|
65
|
+
promptIds: readonly number[], options: GenerateOptions,
|
|
66
|
+
): InferenceMethod<GenerateStats> {
|
|
67
|
+
const prompt = [...promptIds];
|
|
68
|
+
options = snapshotGenerationPolicy(options);
|
|
69
|
+
return nativeMethod("mlx-speculative", async (output, signal) => {
|
|
70
|
+
const metrics = await specRun(binding, numDraftTokens, prompt, { ...options, signal },
|
|
71
|
+
(token) => output.commit([token]));
|
|
72
|
+
return { finishReason: metrics.finishReason ?? (metrics.generatedTokens >= (options.maxTokens ?? 512) ? "length" : "stop"), metrics };
|
|
73
|
+
}, binding.runtime ?? runtimeConfig());
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The canvas stays private. This method publishes only the finished result. */
|
|
77
|
+
export function createDenoisingMethod<State>(
|
|
78
|
+
binding: MlxDenoisingBinding<State>, promptIds: readonly number[], options: GenerateOptions,
|
|
79
|
+
): InferenceMethod<GenerateStats> {
|
|
80
|
+
const prompt = [...promptIds];
|
|
81
|
+
options = snapshotGenerationPolicy(options);
|
|
82
|
+
return nativeMethod("mlx-denoising", async (output, signal) => {
|
|
83
|
+
const generation = generateDenoising(binding, prompt, { ...options, signal });
|
|
84
|
+
for await (const item of generation)
|
|
85
|
+
if (await output.commit([item.token]) === false) break;
|
|
86
|
+
const metrics = generation.stats;
|
|
87
|
+
if (!metrics) throw new Error("denoising method did not settle its metrics");
|
|
88
|
+
return { finishReason: metrics.generatedTokens >= (options.maxTokens ?? 256) ? "length" : "stop", metrics };
|
|
89
|
+
}, binding.runtime ?? runtimeConfig());
|
|
90
|
+
}
|