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
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
//
|
|
5
|
-
// the
|
|
6
|
-
//
|
|
7
|
-
//
|
|
1
|
+
import { AdmissionPool } from "../engine/admission";
|
|
2
|
+
import { acquireReservation } from "./preparation";
|
|
3
|
+
import { cleanupFailure, disposeResources } from "../engine/resources";
|
|
4
|
+
// GenerationGateway is the scheduling seam between CompletionExecutor and
|
|
5
|
+
// the inference runtime. It declares one mechanism per request: the preserved
|
|
6
|
+
// strict/dedicated serial executor, or the continuous scheduler (`--batch N`).
|
|
7
|
+
// The scheduler itself chooses its B=1 fast path or B=N step from its active
|
|
8
|
+
// row count; placement does not predict a batch size. The executor owns each
|
|
9
|
+
// request's semantic token sink and passes its callback plus one immutable
|
|
10
|
+
// placement into run(). Each request keeps its own callback, so the scheduler
|
|
11
|
+
// can fan tokens out to the matching response stream.
|
|
8
12
|
//
|
|
9
13
|
// The two lanes are MUTUALLY EXCLUSIVE on the GPU (and on shared model state
|
|
10
14
|
// like loraState, which generate() mutates per-generation assuming a serialized
|
|
@@ -31,19 +35,20 @@
|
|
|
31
35
|
// AND sliding-window (Gemma) models both batch — the scheduler assembles each
|
|
32
36
|
// layer's cache by type. Prompt-cache prefix reuse works on the batch lane
|
|
33
37
|
// too (Phase 3.2): joiners take() at admission; never-merged lone rows put()
|
|
34
|
-
// back on finish
|
|
38
|
+
// back zero-copy on finish; merged rows with ≥256 prompt tokens are extracted
|
|
39
|
+
// and put back by BatchScheduler.#extractAndPut (see docs/design/batching.md).
|
|
35
40
|
|
|
36
41
|
import { MlxArray } from "../mlx/array";
|
|
37
|
-
import * as ops from "../mlx/ops";
|
|
38
42
|
import type { RuntimeModel } from "../model/factory";
|
|
39
|
-
import { DiffusionGemmaModel } from "../model/diffusion-gemma";
|
|
40
|
-
import { KVCache, RotatingKVCache, isBatchableCache } from "../model/gemma4-base";
|
|
41
|
-
import { SSMCache } from "../model/qwen3-delta";
|
|
42
|
-
import { UniversalDenseModel } from "../model/universal/dense";
|
|
43
43
|
import type { GenerateOptions, GenerateStats, TokenLogprobs } from "../generate";
|
|
44
|
-
import type {
|
|
45
|
-
import {
|
|
46
|
-
import {
|
|
44
|
+
import type { KvScheme } from "../kv-scheme";
|
|
45
|
+
import { makeStepSampler } from "../sampler";
|
|
46
|
+
import type { RowPromptCache } from "../backends/mlx/batch-group";
|
|
47
|
+
import type { MlxMemoryBudget } from "../backends/mlx/memory-guard";
|
|
48
|
+
import { bindMlxGateway, type MlxGatewayBinding, type MlxBatchGroup } from "../backends/mlx/gateway-binding";
|
|
49
|
+
import type { RuntimeConfig } from "../runtime-config";
|
|
50
|
+
import type { ExecutionRequirements, ResolvedExecution } from "../contracts/execution";
|
|
51
|
+
import type { PromptResponseTrace } from "./prompt-response-trace";
|
|
47
52
|
|
|
48
53
|
/** Async mutex: acquire() resolves to a release fn; releases run FIFO. */
|
|
49
54
|
class AsyncMutex {
|
|
@@ -53,18 +58,38 @@ class AsyncMutex {
|
|
|
53
58
|
get locked(): boolean {
|
|
54
59
|
return this.#pending > 0;
|
|
55
60
|
}
|
|
56
|
-
acquire(): Promise<() => void> {
|
|
61
|
+
acquire(signal?: AbortSignal): Promise<() => void> {
|
|
62
|
+
if (signal?.aborted) return Promise.reject(signal.reason);
|
|
57
63
|
this.#pending++;
|
|
58
64
|
let release!: () => void;
|
|
59
65
|
const gate = new Promise<void>((r) => { release = r; });
|
|
60
66
|
const wait = this.#tail;
|
|
61
67
|
this.#tail = this.#tail.then(() => gate);
|
|
62
68
|
let released = false;
|
|
63
|
-
|
|
69
|
+
const unlock = () => {
|
|
64
70
|
if (released) return; // idempotent: a double release must not skew #pending
|
|
65
71
|
released = true;
|
|
66
72
|
this.#pending--;
|
|
67
73
|
release();
|
|
74
|
+
};
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
let settled = false;
|
|
77
|
+
const abort = () => {
|
|
78
|
+
if (settled) return;
|
|
79
|
+
settled = true;
|
|
80
|
+
signal?.removeEventListener("abort", abort);
|
|
81
|
+
// Release this waiter's gate, not its predecessor's. Later acquirers
|
|
82
|
+
// still wait for the active holder, preserving mutual exclusion.
|
|
83
|
+
unlock();
|
|
84
|
+
reject(signal!.reason);
|
|
85
|
+
};
|
|
86
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
87
|
+
wait.then(() => {
|
|
88
|
+
if (settled) return;
|
|
89
|
+
settled = true;
|
|
90
|
+
signal?.removeEventListener("abort", abort);
|
|
91
|
+
resolve(unlock);
|
|
92
|
+
});
|
|
68
93
|
});
|
|
69
94
|
}
|
|
70
95
|
}
|
|
@@ -77,12 +102,15 @@ export type Vision = {
|
|
|
77
102
|
embeddings: MlxArray;
|
|
78
103
|
/** bool [L] image-token mask for the bidirectional attention overlay.
|
|
79
104
|
* Absent when the prompt carries ANY audio — audio(-containing) prompts
|
|
80
|
-
* run fully causal (
|
|
105
|
+
* run fully causal (docs/design/generic-model-support.md §3.3 Q1). */
|
|
81
106
|
imageMask?: MlxArray;
|
|
82
107
|
/** bool [L] union multimodal soft-token mask (image | audio) for
|
|
83
108
|
* per-layer-input id zeroing. Absent on the legacy vision-only shape,
|
|
84
109
|
* where zeroing falls back to imageMask. */
|
|
85
110
|
multimodalMask?: MlxArray;
|
|
111
|
+
/** Qwen3.5/3.8 vision: the request's mRoPE positions + decode delta,
|
|
112
|
+
* installed on the model for exactly this serial run (server-side). */
|
|
113
|
+
mrope?: import("../model/qwen3-mrope").MropeRequestState;
|
|
86
114
|
};
|
|
87
115
|
|
|
88
116
|
/** Per-token sink: returning `false` halts this generation (stop sequence).
|
|
@@ -100,87 +128,73 @@ export type SerialRun = (
|
|
|
100
128
|
options: GenerateOptions & { stopSequences?: string[] },
|
|
101
129
|
onToken: OnToken,
|
|
102
130
|
vision?: Vision,
|
|
131
|
+
trace?: PromptResponseTrace,
|
|
132
|
+
execution?: ResolvedExecution,
|
|
103
133
|
) => Promise<GenerateStats>;
|
|
104
134
|
|
|
105
135
|
/** What the batchable decision needs from a request (cheap to compute). */
|
|
106
|
-
export interface RequestShape {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
turboQuant: boolean;
|
|
122
|
-
/** Any of the mlx-lm sampler/processor extensions is active: min_p, XTC,
|
|
123
|
-
* logit_bias, presence/frequency penalty. INFORMATIONAL ONLY: since the
|
|
124
|
-
* batched lane grew per-row samplers/logits processors these all batch —
|
|
125
|
-
* willBatch() deliberately does not gate on this field (see its
|
|
126
|
-
* repetitionPenalty note). Kept so /stats and lane tracing can show what
|
|
127
|
-
* a request carries. */
|
|
128
|
-
hasLogitsExtras: boolean;
|
|
129
|
-
/** A grammar controller compiled for this request (response_format /
|
|
130
|
-
* guided_*). Degrade-path requests (compile failed → prompt injection)
|
|
131
|
-
* have NO controller and stay batchable — the injection already happened
|
|
132
|
-
* at the prompt level. B0 routes grammar to serial; B1 makes it batchable
|
|
133
|
-
* (per-row matchers), with MLX_BUN_GRAMMAR_BATCH=0 forcing the B0 serial
|
|
134
|
-
* fallback as the A/B + kill switch. */
|
|
135
|
-
hasGrammar: boolean;
|
|
136
|
-
/** The request asked for logprobs/top_logprobs capture. Batch-lane logprobs
|
|
137
|
-
* are deferred (the scheduler's per-row sampler doesn't capture or read
|
|
138
|
-
* back logprob arrays yet), so these route to the serial lane like the
|
|
139
|
-
* other mlx-lm request extensions above. */
|
|
140
|
-
wantsLogprobs: boolean;
|
|
141
|
-
/** A draft model is configured (serve --draft-model). Server-level and
|
|
142
|
-
* upstream-parity: mlx_lm.server sets is_batchable = (draft is None), so
|
|
143
|
-
* every request routes serial while a draft is mounted — speculation is a
|
|
144
|
-
* B=1 latency optimization, batching a throughput one; they are different
|
|
145
|
-
* modes by design (grammar-spec-batching-integration.md). */
|
|
146
|
-
hasDraft: boolean;
|
|
136
|
+
export interface RequestShape extends ExecutionRequirements {}
|
|
137
|
+
|
|
138
|
+
export type GenerationMechanism = "serial" | "continuous";
|
|
139
|
+
|
|
140
|
+
/** One immutable scheduling declaration for one exact request shape.
|
|
141
|
+
* `continuous` means scheduler admission, not that another row is present. */
|
|
142
|
+
export interface GenerationPlacement {
|
|
143
|
+
readonly shape: RequestShape;
|
|
144
|
+
readonly mechanism: GenerationMechanism;
|
|
145
|
+
readonly execution?: ResolvedExecution;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function disposeUnstartedRequest(options: GenerateOptions, vision?: Vision): void {
|
|
149
|
+
disposeResources([options.grammar, vision?.embeddings, vision?.imageMask,
|
|
150
|
+
vision?.multimodalMask, options.visionPixels].filter((resource) => resource != null));
|
|
147
151
|
}
|
|
148
152
|
|
|
149
153
|
export class GenerationGateway {
|
|
154
|
+
#closed = false;
|
|
155
|
+
readonly #runtime: RuntimeConfig;
|
|
156
|
+
readonly #binding: MlxGatewayBinding;
|
|
157
|
+
readonly #requests: AdmissionPool;
|
|
150
158
|
readonly #mutex = new AsyncMutex();
|
|
151
159
|
readonly #batch: number;
|
|
152
|
-
#scheduler:
|
|
160
|
+
#scheduler: MlxBatchGroup | null = null;
|
|
153
161
|
#rowsSubmitted = 0;
|
|
154
162
|
/** Serial-lane requests waiting for / holding the mutex. While > 0 the
|
|
155
163
|
* scheduler pauses admission (drain) so they can't be starved. */
|
|
156
164
|
#serialWaiters = 0;
|
|
157
165
|
/** Lazy, memoized: can this model's caches do the dynamic-B ops? */
|
|
158
166
|
#cacheBatchable: boolean | null = null;
|
|
159
|
-
/** Lazy, memoized: can the
|
|
160
|
-
* (Phase 3.1: kvConfig whose every configured layerIdx is a plain
|
|
161
|
-
* full-attention KVCache — uniform kvBits and rotating-layer configs
|
|
162
|
-
* stay serial. The scheme is a server-lifetime constant, so one memo.) */
|
|
167
|
+
/** Lazy, memoized: can the configured KV scheme convert every named cache? */
|
|
163
168
|
#kvSchemeBatchable: boolean | null = null;
|
|
164
|
-
|
|
165
169
|
constructor(
|
|
166
|
-
|
|
170
|
+
model: RuntimeModel | MlxGatewayBinding,
|
|
167
171
|
batch: number,
|
|
168
172
|
private readonly serialRun: SerialRun,
|
|
169
173
|
private readonly opts: {
|
|
170
174
|
kvBudgetBytes?: number;
|
|
175
|
+
memoryBudget?: MlxMemoryBudget;
|
|
176
|
+
checkpoints?: boolean;
|
|
177
|
+
stateCodecs?: import("../kv-store").CacheCodecProvider;
|
|
171
178
|
/** The server-wide KV scheme (server.ts kvScheme) — threaded to the
|
|
172
179
|
* scheduler at construction when batchable (Phase 3.1). turboQuant is
|
|
173
|
-
* never threaded to the scheduler (always solo-only — see
|
|
180
|
+
* never threaded to the scheduler (always solo-only — see placement);
|
|
174
181
|
* it's here only so the gateway can see it's active. */
|
|
175
|
-
kvScheme?:
|
|
182
|
+
kvScheme?: KvScheme;
|
|
176
183
|
/** The server's prompt cache (Phase 3.2): batch-lane joiners take()
|
|
177
184
|
* the longest usable prefix at admission; never-merged rows put()
|
|
178
185
|
* back on finish. Safe to share with the serial lane — both use it
|
|
179
186
|
* only inside this gateway's mutual-exclusion domain. */
|
|
180
187
|
promptCache?: RowPromptCache;
|
|
188
|
+
/** Finish cache writes before taking the generation lock. Writers
|
|
189
|
+
* need that same lock, so this hook must never run inside it. */
|
|
190
|
+
beforeSerial?: () => Promise<void>;
|
|
181
191
|
} = {},
|
|
182
192
|
) {
|
|
193
|
+
this.#binding = "createBatchGroup" in model ? model : bindMlxGateway(model);
|
|
194
|
+
this.#runtime = this.#binding.runtime;
|
|
195
|
+
this.opts = Object.freeze({ ...opts });
|
|
183
196
|
this.#batch = Math.max(1, Math.floor(batch));
|
|
197
|
+
this.#requests = new AdmissionPool(this.#batch);
|
|
184
198
|
}
|
|
185
199
|
|
|
186
200
|
/** True if `--batch N` (N>1) is on (batchability is then per-request). */
|
|
@@ -210,7 +224,15 @@ export class GenerationGateway {
|
|
|
210
224
|
* running/pending. activeRows/pendingRows alone MISS the serial lane —
|
|
211
225
|
* a serial generation holds the mutex but shows zero rows. */
|
|
212
226
|
get busy(): boolean {
|
|
213
|
-
return this.#
|
|
227
|
+
return this.#requests.active > 0 || this.#requests.queued > 0 ||
|
|
228
|
+
this.#mutex.locked || this.activeRows > 0 || this.pendingRows > 0;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async close(): Promise<void> {
|
|
232
|
+
this.#closed = true;
|
|
233
|
+
this.#requests.close();
|
|
234
|
+
await this.#scheduler?.close();
|
|
235
|
+
await this.onIdle();
|
|
214
236
|
}
|
|
215
237
|
|
|
216
238
|
/** Resolves once the engine is idle (poll-based, ~`pollMs` granularity —
|
|
@@ -238,106 +260,22 @@ export class GenerationGateway {
|
|
|
238
260
|
};
|
|
239
261
|
}
|
|
240
262
|
|
|
241
|
-
/** Cache-capability gate (mirrors mlx-lm server.py's all-caches-have-merge
|
|
242
|
-
* check): the scheduler's dynamic-B ops (temporalView/merge/filter) exist
|
|
243
|
-
* on KVCache, RotatingKVCache, and SSMCache (hybrid gated-DeltaNet models
|
|
244
|
-
* — Qwen3.5 — batch via SSMCache.mergeRows/filter; MLX_BUN_BATCH_SSM=0 is
|
|
245
|
-
* the kill switch back to serial routing). */
|
|
246
263
|
#modelCachesBatchable(): boolean {
|
|
247
|
-
|
|
248
|
-
// Tier-0 universal models: batchable for PLAIN full-attention archs
|
|
249
|
-
// only. The 2026-07-03 uneven-row bug (scalar-offset RoPE decoded
|
|
250
|
-
// padded rows at wrong positions) is fixed via
|
|
251
|
-
// UniversalRope.applyDynamic + ropeOffsetArr and GATED token-exact vs
|
|
252
|
-
// mlx-lm B=2 on Llama-3.2-3B (static + dynamic join/leave,
|
|
253
|
-
// tests/batched-decode-parity.test.ts "Llama 3B Tier-0"). The
|
|
254
|
-
// maskArray archs (gemma2-family: forwardLayers builds a pad-blind
|
|
255
|
-
// causal mask) and sliding-window universal archs remain UNVALIDATED
|
|
256
|
-
// cells → serial, per the per-model-cell discipline.
|
|
257
|
-
if (this.model instanceof UniversalDenseModel) {
|
|
258
|
-
const a = this.model.args;
|
|
259
|
-
this.#cacheBatchable =
|
|
260
|
-
!a.maskArray && !a.layerTypes?.includes("sliding_attention");
|
|
261
|
-
return this.#cacheBatchable;
|
|
262
|
-
}
|
|
263
|
-
const ssmOk = process.env.MLX_BUN_BATCH_SSM !== "0";
|
|
264
|
-
const proto = this.model.makeCache(); // fresh caches hold no buffers
|
|
265
|
-
this.#cacheBatchable = proto.every(
|
|
266
|
-
(c) =>
|
|
267
|
-
c instanceof KVCache ||
|
|
268
|
-
c instanceof RotatingKVCache ||
|
|
269
|
-
isBatchableCache(c) ||
|
|
270
|
-
(ssmOk && c instanceof SSMCache),
|
|
271
|
-
);
|
|
272
|
-
for (const c of proto) c.dispose();
|
|
273
|
-
}
|
|
274
|
-
return this.#cacheBatchable;
|
|
264
|
+
return this.#cacheBatchable ??= this.#binding.cachesBatchable();
|
|
275
265
|
}
|
|
276
266
|
|
|
277
|
-
/** Phase 3.1 + milestone 2: the server's kv scheme batches iff it is a
|
|
278
|
-
* per-layer kvConfig (the L2 mixed scheme) whose every configured layer
|
|
279
|
-
* is a full-attention KVCache (BatchedQuantDecodeMaskCache + quantized
|
|
280
|
-
* merge/extend/filter) or a rotating RotatingKVCache
|
|
281
|
-
* (BatchedRotatingQuantCache — milestone 2, gemma's kv_config). Uniform
|
|
282
|
-
* kvBits (quantizedKvStart threshold semantics via the serial no-byLayer
|
|
283
|
-
* path) and configs naming SSM layers stay serial. */
|
|
284
267
|
#kvBatchable(): boolean {
|
|
285
|
-
|
|
286
|
-
const scheme = this.opts.kvScheme;
|
|
287
|
-
if (!scheme || (!scheme.kvBits && !scheme.kvConfig?.length)) {
|
|
288
|
-
// Only consulted when shape.kvQuant is SET: a kv-quant request with
|
|
289
|
-
// no scheme threaded to the gateway cannot be applied by the
|
|
290
|
-
// scheduler — batching it would SILENTLY DROP the quantization (the
|
|
291
|
-
// exact composition bug optiq serve ships). Route serial.
|
|
292
|
-
this.#kvSchemeBatchable = false;
|
|
293
|
-
} else if (scheme.kvBits || !scheme.kvConfig?.length) {
|
|
294
|
-
this.#kvSchemeBatchable = false; // uniform bits: serial
|
|
295
|
-
} else {
|
|
296
|
-
const proto = this.model.makeCache();
|
|
297
|
-
this.#kvSchemeBatchable = scheme.kvConfig.every(
|
|
298
|
-
(e) => proto[e.layerIdx] instanceof KVCache ||
|
|
299
|
-
proto[e.layerIdx] instanceof RotatingKVCache,
|
|
300
|
-
);
|
|
301
|
-
for (const c of proto) c.dispose();
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
return this.#kvSchemeBatchable;
|
|
268
|
+
return this.#kvSchemeBatchable ??= !!this.opts.kvScheme && this.#binding.kvBatchable(this.opts.kvScheme);
|
|
305
269
|
}
|
|
306
270
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
// presence/frequency penalties) batch: the per-row sampler folds in the
|
|
316
|
-
// same makeSampler/makeLogitsProcessors closures the serial lane runs,
|
|
317
|
-
// over a per-row device-side token history. (Load-bearing beyond opt-in
|
|
318
|
-
// knobs: some models — Qwen3.5 — ship a default repetition penalty in
|
|
319
|
-
// generation_config.json, which used to route EVERY request serial.)
|
|
320
|
-
return (
|
|
321
|
-
!shape.hasVision &&
|
|
322
|
-
!shape.hasAdapters &&
|
|
323
|
-
!shape.wantsLogprobs &&
|
|
324
|
-
!shape.userSeed &&
|
|
325
|
-
// Phase 3.1: a kv-quant request batches when the server's scheme is
|
|
326
|
-
// the batchable kvConfig composition (the scheduler applies it);
|
|
327
|
-
// otherwise it routes serial exactly as before.
|
|
328
|
-
!(shape.kvQuant && !this.#kvBatchable()) &&
|
|
329
|
-
// TurboQuant: solo-only in v1, unconditionally — never partially
|
|
330
|
-
// batchable like kvConfig. The automatic instanceof exclusion in
|
|
331
|
-
// #modelCachesBatchable() already covers this once a cache converts;
|
|
332
|
-
// this is the explicit belt-and-braces refusal at request-shape time
|
|
333
|
-
// (mirrors the kvQuant pattern above — both layers exist on purpose).
|
|
334
|
-
!shape.turboQuant &&
|
|
335
|
-
!shape.hasDraft &&
|
|
336
|
-
// Grammar: B1 makes it batchable (per-row matchers) unless the kill
|
|
337
|
-
// switch forces serial. MLX_BUN_GRAMMAR_BATCH=0 = B0 behavior (serial),
|
|
338
|
-
// the A/B + kill lever for the new code, house style.
|
|
339
|
-
!(shape.hasGrammar && process.env.MLX_BUN_GRAMMAR_BATCH === "0")
|
|
340
|
-
);
|
|
271
|
+
place(shape: RequestShape, options: GenerateOptions = {}): GenerationPlacement {
|
|
272
|
+
const frozenShape = Object.freeze(shape);
|
|
273
|
+
const execution = this.#binding.plan(frozenShape, options, {
|
|
274
|
+
continuous: this.batchingEnabled,
|
|
275
|
+
quantizedBatch: shape.kvQuant && this.#kvBatchable(),
|
|
276
|
+
checkpoints: this.opts.checkpoints === true,
|
|
277
|
+
});
|
|
278
|
+
return Object.freeze({ shape: frozenShape, mechanism: execution.mechanism, execution });
|
|
341
279
|
}
|
|
342
280
|
|
|
343
281
|
/** Run `fn` with exclusive ownership of the GPU + shared model state (the
|
|
@@ -346,19 +284,37 @@ export class GenerationGateway {
|
|
|
346
284
|
* mount/unmount all come through here, so nothing runs concurrently with
|
|
347
285
|
* batched decode steps (or with each other). Registers as a serial waiter
|
|
348
286
|
* so the scheduler drains (stops admitting) until `fn` has run. */
|
|
349
|
-
|
|
287
|
+
/** Shared by inference, preparation and managed GPU subprocesses. */
|
|
288
|
+
async acquireExecutionLease(
|
|
289
|
+
signal?: AbortSignal,
|
|
290
|
+
trace?: PromptResponseTrace,
|
|
291
|
+
): Promise<import("../contracts/resources").DisposableResource> {
|
|
292
|
+
if (this.#closed) throw new Error("generation gateway is closed");
|
|
350
293
|
this.#serialWaiters++;
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
try {
|
|
354
|
-
return await fn();
|
|
355
|
-
} finally {
|
|
356
|
-
release();
|
|
357
|
-
}
|
|
358
|
-
} finally {
|
|
294
|
+
const closeAdmission = trace?.begin("engine.admission_wait", { mechanism: "serial" });
|
|
295
|
+
const resume = () => {
|
|
359
296
|
this.#serialWaiters--;
|
|
360
|
-
if (this.#serialWaiters === 0) this.#scheduler?.kick();
|
|
361
|
-
}
|
|
297
|
+
if (this.#serialWaiters === 0) this.#scheduler?.kick();
|
|
298
|
+
};
|
|
299
|
+
try {
|
|
300
|
+
const release = await this.#mutex.acquire(signal);
|
|
301
|
+
let released = false;
|
|
302
|
+
return { dispose: () => {
|
|
303
|
+
if (released) return;
|
|
304
|
+
released = true;
|
|
305
|
+
release(); resume();
|
|
306
|
+
} };
|
|
307
|
+
} catch (error) { resume(); throw error; }
|
|
308
|
+
finally { closeAdmission?.(); }
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async runExclusive<T>(
|
|
312
|
+
fn: () => Promise<T>,
|
|
313
|
+
trace?: PromptResponseTrace,
|
|
314
|
+
signal?: AbortSignal,
|
|
315
|
+
): Promise<T> {
|
|
316
|
+
const lease = await this.acquireExecutionLease(signal, trace);
|
|
317
|
+
try { return await fn(); } finally { lease.dispose(); }
|
|
362
318
|
}
|
|
363
319
|
|
|
364
320
|
/** Run one generation on the appropriate lane. onToken is invoked per emitted
|
|
@@ -369,17 +325,41 @@ export class GenerationGateway {
|
|
|
369
325
|
onToken: OnToken,
|
|
370
326
|
vision: Vision | undefined,
|
|
371
327
|
shape: RequestShape,
|
|
328
|
+
placement: GenerationPlacement,
|
|
372
329
|
signal?: AbortSignal,
|
|
330
|
+
trace?: PromptResponseTrace,
|
|
373
331
|
): Promise<GenerateStats> {
|
|
332
|
+
let reservation;
|
|
333
|
+
try { reservation = await acquireReservation(this.#requests, signal); }
|
|
334
|
+
catch (error) { cleanupFailure(error, () => disposeUnstartedRequest(options, vision)); }
|
|
335
|
+
try { return await this.#run(promptIds, options, onToken, vision, shape, placement, signal, trace); }
|
|
336
|
+
finally { reservation.dispose(); }
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
async #run(
|
|
340
|
+
promptIds: number[],
|
|
341
|
+
options: GenerateOptions & { stopSequences?: string[] },
|
|
342
|
+
onToken: OnToken,
|
|
343
|
+
vision: Vision | undefined,
|
|
344
|
+
shape: RequestShape,
|
|
345
|
+
placement: GenerationPlacement,
|
|
346
|
+
signal?: AbortSignal,
|
|
347
|
+
trace?: PromptResponseTrace,
|
|
348
|
+
): Promise<GenerateStats> {
|
|
349
|
+
const disposeUnstarted = () => disposeUnstartedRequest(options, vision);
|
|
350
|
+
if (placement.shape !== shape) {
|
|
351
|
+
disposeUnstarted();
|
|
352
|
+
throw new Error("generation placement does not belong to this request shape");
|
|
353
|
+
}
|
|
374
354
|
try {
|
|
375
355
|
signal?.throwIfAborted();
|
|
376
356
|
} catch (e) {
|
|
377
357
|
// The gateway is the first component that accepts ownership of a
|
|
378
358
|
// compiled controller. An already-aborted request reaches neither lane.
|
|
379
|
-
|
|
359
|
+
disposeUnstarted();
|
|
380
360
|
throw e;
|
|
381
361
|
}
|
|
382
|
-
if (
|
|
362
|
+
if (placement.mechanism === "serial") {
|
|
383
363
|
// Serial decode is an unbroken microtask chain (FFI + generator
|
|
384
364
|
// resumes): without a periodic macrotask hop the event loop starves for
|
|
385
365
|
// the WHOLE generation — /stats, /health, and new-connection accepts
|
|
@@ -391,88 +371,69 @@ export class GenerationGateway {
|
|
|
391
371
|
let lastHop = performance.now();
|
|
392
372
|
const hop = (): Promise<void> => new Promise<void>((r) => setImmediate(r));
|
|
393
373
|
const hoppingOnToken: OnToken = (token, lp) => {
|
|
394
|
-
|
|
374
|
+
signal?.throwIfAborted();
|
|
395
375
|
const r = onToken(token, lp);
|
|
396
376
|
if (r === false) return false;
|
|
397
377
|
if (performance.now() - lastHop < 25) return r;
|
|
398
378
|
lastHop = performance.now();
|
|
399
379
|
if (r instanceof Promise)
|
|
400
|
-
return r.then((v) =>
|
|
401
|
-
v === false
|
|
402
|
-
|
|
403
|
-
|
|
380
|
+
return r.then(async (v) => {
|
|
381
|
+
if (v === false) return false;
|
|
382
|
+
signal?.throwIfAborted();
|
|
383
|
+
await hop();
|
|
384
|
+
signal?.throwIfAborted();
|
|
385
|
+
return v;
|
|
386
|
+
});
|
|
387
|
+
return hop().then(() => {
|
|
388
|
+
signal?.throwIfAborted();
|
|
389
|
+
return r;
|
|
390
|
+
});
|
|
404
391
|
};
|
|
405
|
-
|
|
392
|
+
let started = false;
|
|
393
|
+
return Promise.resolve().then(() => this.opts.beforeSerial?.()).then(() => this.runExclusive(async () => {
|
|
406
394
|
signal?.throwIfAborted();
|
|
407
|
-
|
|
395
|
+
started = true;
|
|
396
|
+
const stats = await this.serialRun(
|
|
397
|
+
promptIds, signal ? { ...options, signal } : options, hoppingOnToken, vision, trace, placement.execution,
|
|
398
|
+
);
|
|
408
399
|
signal?.throwIfAborted();
|
|
409
400
|
return stats;
|
|
410
|
-
})
|
|
401
|
+
}, trace, signal))
|
|
411
402
|
// Covers a serial waiter aborted before serialRun takes ownership.
|
|
412
403
|
// generate() also disposes defensively; the operation is idempotent.
|
|
413
|
-
.finally(() =>
|
|
404
|
+
.finally(() => {
|
|
405
|
+
if (started) options.grammar?.dispose();
|
|
406
|
+
else disposeUnstarted();
|
|
407
|
+
});
|
|
414
408
|
}
|
|
415
409
|
|
|
416
|
-
//
|
|
417
|
-
//
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
// inside the closure keeps history exact.
|
|
427
|
-
let history: MlxArray | null = null;
|
|
428
|
-
if (processors.length > 0) history = ops.fromInt32(promptIds, [promptIds.length]);
|
|
429
|
-
const sample = (logits1V: MlxArray, step: number): MlxArray => {
|
|
430
|
-
let cur = logits1V; // caller-owned; only intermediates are disposed here
|
|
431
|
-
for (const p of processors) {
|
|
432
|
-
const next = p(history, cur);
|
|
433
|
-
if (cur !== logits1V) cur.dispose();
|
|
434
|
-
cur = next;
|
|
435
|
-
}
|
|
436
|
-
// Grammar mask (B1): after the standard processors, before the sampler —
|
|
437
|
-
// same "grammar has the final say" ordering as serial sampleStep.
|
|
438
|
-
// Precondition: the scheduler has awaited ready() before invoking this
|
|
439
|
-
// closure (the mask for this step is materialized). applyMask is sync.
|
|
440
|
-
if (options.grammar) {
|
|
441
|
-
const masked = options.grammar.applyMask(cur);
|
|
442
|
-
if (cur !== logits1V) cur.dispose();
|
|
443
|
-
cur = masked;
|
|
444
|
-
}
|
|
445
|
-
const lp = toLogprobs(cur);
|
|
446
|
-
if (cur !== logits1V) cur.dispose();
|
|
447
|
-
const tok = sampler(lp, step);
|
|
448
|
-
lp.dispose();
|
|
449
|
-
if (history) {
|
|
450
|
-
const t1 = ops.reshape(tok, [1]);
|
|
451
|
-
const prev = history;
|
|
452
|
-
history = ops.concatAxis([prev, t1], 0);
|
|
453
|
-
prev.dispose();
|
|
454
|
-
t1.dispose();
|
|
455
|
-
}
|
|
456
|
-
return tok;
|
|
457
|
-
};
|
|
410
|
+
// The scheduler owns grammar ready/accept sequencing. StepSampler owns the
|
|
411
|
+
// shared processors -> mask -> logprobs -> sample -> history contract.
|
|
412
|
+
const stepSampler = makeStepSampler(options, {
|
|
413
|
+
tokenRepresentation: "device",
|
|
414
|
+
grammarWait: "external",
|
|
415
|
+
historyUpdate: "after-sample",
|
|
416
|
+
initialHistory: promptIds,
|
|
417
|
+
});
|
|
418
|
+
const sample = (logits1V: MlxArray, step: number): MlxArray =>
|
|
419
|
+
stepSampler.sample(logits1V, step).token;
|
|
458
420
|
|
|
459
421
|
let st;
|
|
460
422
|
this.#rowsSubmitted++;
|
|
423
|
+
const closeAdmission = trace?.begin("engine.admission_wait", {
|
|
424
|
+
mechanism: "continuous",
|
|
425
|
+
});
|
|
461
426
|
try {
|
|
462
427
|
st = await this.#ensureScheduler().submit({
|
|
463
428
|
promptIds,
|
|
429
|
+
compiledDecode: placement.execution?.compiledDecode,
|
|
464
430
|
maxTokens: options.maxTokens ?? 512,
|
|
465
|
-
eosTokenIds: options.eosTokenIds ?? this.
|
|
431
|
+
eosTokenIds: options.eosTokenIds ?? this.#binding.config.eosTokenIds,
|
|
466
432
|
sample,
|
|
467
|
-
|
|
468
|
-
// (temperature 0, no curve override) with nothing per-row in the way
|
|
469
|
-
// (no processors, no grammar mask).
|
|
470
|
-
plainGreedy:
|
|
471
|
-
(options.temperature ?? 0) === 0 &&
|
|
472
|
-
!options.curve &&
|
|
473
|
-
processors.length === 0 &&
|
|
474
|
-
!options.grammar,
|
|
433
|
+
plainGreedy: stepSampler.isPlainGreedy,
|
|
475
434
|
onToken,
|
|
435
|
+
onAdmitted: closeAdmission,
|
|
436
|
+
trace,
|
|
476
437
|
...(signal ? { signal } : {}),
|
|
477
438
|
// B1: pass the per-row grammar controller through. The scheduler drives
|
|
478
439
|
// accept/ready/terminate; this gateway OWNS disposal (finally below)
|
|
@@ -483,8 +444,8 @@ export class GenerationGateway {
|
|
|
483
444
|
...(options.snapshotAt !== undefined ? { snapshotAt: options.snapshotAt } : {}),
|
|
484
445
|
});
|
|
485
446
|
} finally {
|
|
486
|
-
|
|
487
|
-
|
|
447
|
+
closeAdmission?.();
|
|
448
|
+
stepSampler.dispose();
|
|
488
449
|
// The scheduler never owns per-row grammar state.
|
|
489
450
|
options.grammar?.dispose();
|
|
490
451
|
}
|
|
@@ -493,21 +454,28 @@ export class GenerationGateway {
|
|
|
493
454
|
promptTokens: st.promptTokens,
|
|
494
455
|
cachedTokens: st.cachedTokens,
|
|
495
456
|
generatedTokens: st.generatedTokens,
|
|
496
|
-
|
|
457
|
+
finishReason: st.finishReason,
|
|
458
|
+
prefillMs: st.prefillMs,
|
|
459
|
+
decodeMs: st.decodeMs,
|
|
460
|
+
prefillTps: st.prefillMs > 0 ? ((st.promptTokens - st.cachedTokens) / st.prefillMs) * 1000 : 0,
|
|
461
|
+
decodeTps: st.decodeMs > 0 && st.generatedTokens > 1 ? ((st.generatedTokens - 1) / st.decodeMs) * 1000 : 0,
|
|
497
462
|
cacheTokens: [],
|
|
498
463
|
};
|
|
499
464
|
}
|
|
500
465
|
|
|
501
|
-
#ensureScheduler():
|
|
466
|
+
#ensureScheduler(): MlxBatchGroup {
|
|
502
467
|
if (!this.#scheduler)
|
|
503
|
-
this.#scheduler =
|
|
468
|
+
this.#scheduler = this.#binding.createBatchGroup({
|
|
469
|
+
runtime: this.#runtime,
|
|
504
470
|
maxBatch: this.#batch,
|
|
471
|
+
stateCodecs: this.opts.stateCodecs,
|
|
505
472
|
kvBudgetBytes: this.opts.kvBudgetBytes,
|
|
473
|
+
memoryBudget: this.opts.memoryBudget,
|
|
506
474
|
// Phase 3.1: the batchable kvConfig composition is applied by the
|
|
507
475
|
// scheduler (solo rows convert at serial chunk boundaries, then
|
|
508
476
|
// merge as quantized triples). Only threaded when the scheme
|
|
509
477
|
// passed #kvBatchable — otherwise those requests never reach here.
|
|
510
|
-
|
|
478
|
+
kvScheme: this.#kvBatchable() ? this.opts.kvScheme : undefined,
|
|
511
479
|
promptCache: this.opts.promptCache,
|
|
512
480
|
lock: { acquire: () => this.#mutex.acquire() },
|
|
513
481
|
// Drain: pause admission while any serial-lane request waits; the
|