mlx-bun 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -588
- package/docs/README.md +91 -0
- package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
- package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
- package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
- package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
- package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
- package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
- package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
- package/docs/design/batching.md +569 -0
- package/docs/design/decode-speed-program.md +3178 -169
- package/docs/design/dreaming-nightly-pipeline.md +675 -282
- package/docs/design/generic-model-support.md +579 -334
- package/docs/design/kv-cache.md +680 -0
- package/docs/design/orpo-training.md +673 -1239
- package/docs/design/speculative-decoding.md +1627 -0
- package/docs/design/turboquant.md +2102 -0
- package/docs/design/unified-engine-frontier-plan.md +1831 -727
- package/docs/design/web-chat-redesign.md +666 -1083
- package/docs/planning/PRODUCT_ROADMAP.md +34 -1
- package/docs/planning/release-notes-v0.3.0.md +35 -0
- package/docs/planning/video-series.md +2 -2
- package/docs/reference/benchmarks.md +1162 -0
- package/docs/reference/cli.md +394 -159
- package/docs/reference/distribution.md +321 -182
- package/docs/reference/environment.md +348 -0
- package/docs/reference/glossary.md +39 -0
- package/docs/reference/library-api.md +566 -168
- package/docs/reference/memory.md +227 -64
- package/docs/reference/models.md +104 -6
- package/docs/reference/server-api.md +886 -425
- package/docs/reference/server-config.md +638 -330
- package/docs/reference/training.md +393 -254
- package/docs/reference/troubleshooting.md +166 -0
- package/package.json +9 -4
- package/src/audio/decode.ts +2 -2
- package/src/audio/features.ts +2 -2
- package/src/backends/mlx/autoregressive.ts +108 -0
- package/src/backends/mlx/batch-group.ts +1646 -0
- package/src/backends/mlx/diffusion.ts +39 -0
- package/src/backends/mlx/draft-target.ts +47 -0
- package/src/backends/mlx/evaluation.ts +105 -0
- package/src/backends/mlx/fill-append.ts +45 -0
- package/src/backends/mlx/gateway-binding.ts +71 -0
- package/src/backends/mlx/graph.ts +38 -0
- package/src/backends/mlx/memory-client.ts +189 -0
- package/src/backends/mlx/memory-guard.ts +40 -0
- package/src/backends/mlx/methods.ts +90 -0
- package/src/backends/mlx/model-prompt.ts +164 -0
- package/src/backends/mlx/model-serving.ts +79 -0
- package/src/backends/mlx/prefill.ts +33 -0
- package/src/backends/mlx/request-policy.ts +20 -0
- package/src/backends/mlx/rollback.ts +51 -0
- package/src/backends/mlx/serial-executor.ts +273 -0
- package/src/backends/mlx/speculative.ts +114 -0
- package/src/backends/mlx/state-views.ts +26 -0
- package/src/backends/mlx/text-engine.ts +73 -0
- package/src/cli.ts +58 -25
- package/src/client.ts +66 -0
- package/src/config.ts +21 -4
- package/src/contracts/completion.ts +16 -0
- package/src/contracts/execution.ts +49 -0
- package/src/contracts/generation.ts +103 -0
- package/src/contracts/host.ts +7 -0
- package/src/contracts/jobs.ts +66 -0
- package/src/contracts/pi-provider.ts +30 -0
- package/src/contracts/pi.ts +259 -0
- package/src/contracts/resources.ts +18 -0
- package/src/contracts/scheduling.ts +27 -0
- package/src/contracts/tsconfig.json +14 -0
- package/src/diffusion/diffusion-generate.ts +218 -153
- package/src/embed.ts +1 -1
- package/src/engine/admission.ts +59 -0
- package/src/engine/cancellation.ts +38 -0
- package/src/engine/engine.ts +56 -0
- package/src/engine/execution-coordinator.ts +70 -0
- package/src/engine/execution-plan.ts +51 -0
- package/src/engine/resources.ts +42 -0
- package/src/engine/scheduler.ts +67 -0
- package/src/engine/session.ts +247 -0
- package/src/eval/README.md +44 -1
- package/src/eval/kl.ts +236 -3
- package/src/eval/reference-logits.ts +207 -0
- package/src/eval/runner.ts +8 -96
- package/src/fill/echo-index.ts +188 -0
- package/src/fill/fill-session.ts +452 -0
- package/src/fill/proposal.ts +68 -0
- package/src/fill/schema-rows.ts +506 -0
- package/src/fill/tool-boundary.ts +103 -0
- package/src/fit.ts +1 -1
- package/src/generate.ts +656 -268
- package/src/grammar.ts +12 -7
- package/src/harness-pi.ts +14 -33
- package/src/hub-rest.ts +4 -4
- package/src/index.ts +7 -0
- package/src/inference/denoising.ts +17 -0
- package/src/inference/graph.ts +33 -0
- package/src/inference/prefill.ts +39 -0
- package/src/inference/rollback.ts +7 -0
- package/src/jobs/db.ts +4 -0
- package/src/jobs/index.ts +2 -0
- package/src/jobs/job-entry.ts +12 -12
- package/src/jobs/runner.ts +98 -13
- package/src/jobs/task-client.ts +19 -0
- package/src/jobs/types.ts +2 -58
- package/src/kv-store.ts +109 -49
- package/src/lab/README.md +18 -0
- package/src/{assets → lab/curve}/curve-designer.html +2 -2
- package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
- package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +5 -5
- package/src/{model → lab/paged-kv}/paged-kv.ts +9 -6
- package/src/library.ts +40 -0
- package/src/lora.ts +18 -0
- package/src/media-fetch.ts +1 -1
- package/src/memory/article.ts +1 -1
- package/src/memory/chunk.ts +1 -1
- package/src/memory/model.ts +30 -169
- package/src/memory/prompts.ts +1 -1
- package/src/memory/rest.ts +1 -1
- package/src/memory/schedule.ts +1 -1
- package/src/memory/stages.ts +4 -1
- package/src/memory/vault.ts +1 -3
- package/src/mlx/array.ts +9 -10
- package/src/mlx/compile.ts +12 -2
- package/src/mlx/ffi.ts +58 -11
- package/src/mlx/materialize.ts +23 -0
- package/src/mlx/metal-kernel.ts +10 -11
- package/src/mlx/ops.ts +33 -13
- package/src/mlx/quantized-rows.ts +36 -0
- package/src/mlx/token-bitmask.ts +52 -0
- package/src/mlx/turboquant-kv-decode.ts +82 -0
- package/src/mlx/turboquant-kv-inverse.ts +122 -0
- package/src/mlx/turboquant-ops.ts +33 -3
- package/src/mlx/turboquant-tables.ts +2 -2
- package/src/model/batched-mask.ts +8 -2
- package/src/model/batched-quant.ts +1 -1
- package/src/model/batched-rotating-quant.ts +2 -2
- package/src/model/batched-rotating.ts +1 -1
- package/src/model/compiled-decode.ts +1 -1
- package/src/model/diffusion-gemma.ts +8 -8
- package/src/model/factory.ts +76 -33
- package/src/model/fingerprint.ts +1 -1
- package/src/model/flash-attention.ts +3 -3
- package/src/model/gemma4-base.ts +26 -9
- package/src/model/gemma4.ts +6 -6
- package/src/model/generated/gemma4-12b.ts +2 -2
- package/src/model/generated/gemma4-26b.ts +2 -2
- package/src/model/generated/gemma4-e4b.ts +2 -2
- package/src/model/glm52-dsa.ts +13 -9
- package/src/model/glm52-memory.ts +1 -1
- package/src/model/implementation.ts +69 -0
- package/src/model/minicpm5.ts +6 -8
- package/src/model/profile.ts +12 -2
- package/src/model/qwen-append.ts +12 -0
- package/src/model/qwen-conv.ts +52 -0
- package/src/model/qwen3_5-checkpoint.ts +164 -0
- package/src/model/qwen3_5.ts +140 -56
- package/src/model/support.ts +1 -1
- package/src/model/trellis-balanced-scatter.ts +51 -0
- package/src/model/trellis-linear.ts +672 -0
- package/src/model/trellis-shared-m.ts +151 -0
- package/src/model/trellis-shared-scatter.ts +144 -0
- package/src/model/trellis-splitk-prefill.ts +158 -0
- package/src/model/trellis-tiled-prefill.ts +136 -0
- package/src/model/trellis-vector-expand.ts +62 -0
- package/src/model/trellis-wide-prefill.ts +94 -0
- package/src/model/universal/dense.ts +1 -1
- package/src/model/universal/modules.ts +8 -1
- package/src/native-pack.ts +6 -5
- package/src/pi-provider.ts +6 -64
- package/src/pi-web.ts +8 -315
- package/src/prompt-cache.ts +96 -53
- package/src/quantize/allocator.ts +1 -1
- package/src/quantize/config-writer.ts +10 -3
- package/src/quantize/rotate.ts +4 -4
- package/src/quantize/trellis.ts +586 -0
- package/src/responses.ts +28 -1
- package/src/runtime-config.ts +18 -4
- package/src/sampler.ts +4 -4
- package/src/serve/batch-scheduler.ts +8 -1671
- package/src/serve/chat-request.ts +391 -0
- package/src/serve/chat-stage.ts +195 -0
- package/src/serve/checkpoint-identity.ts +64 -0
- package/src/serve/completion-executor.ts +15 -7
- package/src/serve/discovery-routes.ts +12 -13
- package/src/serve/generation-gateway.ts +154 -193
- package/src/serve/http.ts +159 -0
- package/src/serve/inference-request.ts +79 -0
- package/src/serve/isolate.ts +297 -105
- package/src/serve/lab-routes.ts +3 -0
- package/src/serve/lane-registry.ts +2 -1
- package/src/serve/model-admin-routes.ts +7 -5
- package/src/serve/model-binding.ts +43 -0
- package/src/serve/model-host.ts +512 -0
- package/src/serve/openai-wire.ts +103 -0
- package/src/serve/parent-application.ts +56 -0
- package/src/serve/pipeline.ts +33 -0
- package/src/serve/preparation.ts +33 -0
- package/src/serve/request-plan.ts +19 -15
- package/src/serve/request-prep.ts +346 -0
- package/src/serve/response-lease.ts +24 -0
- package/src/serve/responses-client.ts +69 -0
- package/src/serve/session-completion-engine.ts +110 -0
- package/src/serve/session-search.ts +1 -1
- package/src/serve/static-routes.ts +5 -5
- package/src/serve/text-completion-stage.ts +104 -0
- package/src/serve/token-streams.ts +519 -0
- package/src/server.ts +317 -2649
- package/src/spec/assistant-source.ts +10 -44
- package/src/spec/deepspec-source.ts +6 -6
- package/src/spec/dflash-source.ts +8 -8
- package/src/spec/dspark/ab-stats.ts +3 -3
- package/src/spec/dspark/calibration.ts +2 -2
- package/src/spec/dspark/deepspec-module.ts +2 -2
- package/src/spec/dspark/generate-dflash.ts +2 -2
- package/src/spec/dspark/generate.ts +2 -2
- package/src/spec/dspark/loader.ts +1 -1
- package/src/spec/dspark/module-dflash.ts +4 -3
- package/src/spec/dspark/module.ts +3 -3
- package/src/spec/dspark/quantize-drafter.ts +1 -1
- package/src/spec/generate.ts +1 -1
- package/src/spec/glm52-mtp-source.ts +1 -1
- package/src/spec/ngram-source.ts +1 -1
- package/src/spec/prefix-state.ts +45 -0
- package/src/spec/qwen-mtp-source.ts +166 -69
- package/src/spec/serve-loop.ts +154 -156
- package/src/spec/source.ts +42 -10
- package/src/spec/two-model.ts +1 -1
- package/src/ssd-cache.ts +80 -12
- package/src/ssd-durability.ts +25 -13
- package/src/tool-call.ts +51 -14
- package/src/train/flash-cce.ts +2 -2
- package/src/train/forward.ts +6 -0
- package/src/train/loss.ts +1 -1
- package/src/train/prefix-shared.ts +5 -1
- package/src/train/segmented.ts +5 -3
- package/src/train/trainer.ts +6 -6
- package/src/vision/prompt.ts +1 -1
- package/src/vision/qwen3vl-tower.ts +52 -10
- package/src/web/app.html +1 -1
- package/src/web/app.js +5 -3
- package/src/web/src/adapters-panel.ts +3 -3
- package/src/web/src/assistant.ts +2 -36
- package/src/web/src/chat.ts +4 -4
- package/src/web/src/composer.ts +4 -4
- package/src/web/src/hub.ts +1 -1
- package/src/web/src/markdown.ts +0 -0
- package/src/web/src/memory-panel.ts +1 -1
- package/src/web/src/model-picker.ts +1 -1
- package/src/web/src/palette.ts +1 -1
- package/src/web/src/protocol.ts +3 -39
- package/src/web/src/sessions.ts +1 -1
- package/src/web/src/ui-catalog.ts +4 -18
- package/src/web-assets.ts +47 -0
- package/src/weights.ts +55 -9
- package/docs/archive/investigations/curve-runs/bisector-route.json +0 -98
- package/docs/archive/investigations/curve-runs/bisector-trace.json +0 -1393
- package/docs/archive/investigations/curve-runs/curve-terrain.json +0 -1179
- package/docs/archive/investigations/curve-runs/curve-terrain.svg +0 -42
- package/docs/archive/investigations/hlg-ab.html +0 -34
- package/docs/archive/investigations/hlg-explorer.html +0 -238
- package/docs/archive/investigations/hlg-figs/cliff.svg +0 -1
- package/docs/archive/investigations/hlg-figs/cross.svg +0 -1
- package/docs/archive/investigations/hlg-figs/edges.svg +0 -1
- package/docs/archive/investigations/hlg-figs/k20-grids.svg +0 -1
- package/docs/archive/investigations/hlg-figs/k20-peaks.svg +0 -1
- package/docs/archive/investigations/hlg-figs/lw-curve.svg +0 -1
- package/docs/archive/investigations/hlg-figs/terrain.svg +0 -1
- package/docs/archive/investigations/hlg-report.html +0 -262
- package/docs/archive/investigations/hlg-runs/hlg-ab.json +0 -149
- package/docs/archive/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
- package/docs/archive/investigations/hlg-runs/hlg-blind.json +0 -107
- package/docs/archive/investigations/hlg-runs/hlg-cliff.json +0 -197
- package/docs/archive/investigations/hlg-runs/hlg-cross.json +0 -505
- package/docs/archive/investigations/hlg-runs/hlg-edge.json +0 -388
- package/docs/archive/investigations/hlg-runs/hlg-eval1.json +0 -25
- package/docs/archive/investigations/hlg-runs/hlg-frontier.json +0 -86
- package/docs/archive/investigations/hlg-runs/hlg-grid.json +0 -242
- package/docs/archive/investigations/hlg-runs/hlg-grid2.json +0 -343
- package/docs/archive/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
- package/docs/archive/investigations/hlg-runs/hlg-map.json +0 -383
- package/docs/archive/investigations/hlg-runs/run0-original.log +0 -159
- package/docs/archive/investigations/hlg-runs/runA-recommended.log +0 -131
- package/docs/archive/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
- package/docs/archive/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
- package/docs/archive/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
- package/docs/archive/investigations/hlg-runs/runAD-ab-samples.log +0 -8
- package/docs/archive/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
- package/docs/archive/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
- package/docs/archive/investigations/hlg-runs/runAG-eval-config2.log +0 -16
- package/docs/archive/investigations/hlg-runs/runB-gain-variants.log +0 -192
- package/docs/archive/investigations/hlg-runs/runC-pivots.log +0 -162
- package/docs/archive/investigations/hlg-runs/runD-oetf.log +0 -132
- package/docs/archive/investigations/hlg-runs/runE-eotf.log +0 -162
- package/docs/archive/investigations/hlg-runs/runF-shaper-working.log +0 -86
- package/docs/archive/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
- package/docs/archive/investigations/hlg-runs/runH-wsweep.log +0 -176
- package/docs/archive/investigations/hlg-runs/runI-asweep.log +0 -176
- package/docs/archive/investigations/hlg-runs/runJ-knobmap.log +0 -49
- package/docs/archive/investigations/hlg-runs/runK-frontier.log +0 -19
- package/docs/archive/investigations/hlg-runs/runL-widemap.log +0 -65
- package/docs/archive/investigations/hlg-runs/runM-frontier-emb.log +0 -15
- package/docs/archive/investigations/hlg-runs/runN-verify.log +0 -116
- package/docs/archive/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
- package/docs/archive/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
- package/docs/archive/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
- package/docs/archive/investigations/hlg-runs/runR-blind-transcript.log +0 -79
- package/docs/archive/investigations/hlg-runs/runS-cross-coherence.log +0 -33
- package/docs/archive/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
- package/docs/archive/investigations/hlg-runs/runU-cross-12b.log +0 -33
- package/docs/archive/investigations/hlg-runs/runV-lw-e4b.log +0 -22
- package/docs/archive/investigations/hlg-runs/runW-lw-12b.log +0 -22
- package/docs/archive/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
- package/docs/archive/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
- package/docs/archive/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
- package/docs/archive/mlx-bun-lab-report.html +0 -275
- package/docs/archive/wwdc-mlx-bun.html +0 -848
- package/docs/design/adapters-end-to-end.md +0 -205
- package/docs/design/batching-perf-path.md +0 -255
- package/docs/design/batching-v2-plan.md +0 -529
- package/docs/design/bucketing-stage.md +0 -197
- package/docs/design/compat-cli-surface-design.md +0 -230
- package/docs/design/docs-reorg-plan.md +0 -165
- package/docs/design/dspark-serving-program.md +0 -371
- package/docs/design/dspark-speculative-decoding.md +0 -318
- package/docs/design/grammar-spec-batching-integration.md +0 -301
- package/docs/design/memory-inference-path.md +0 -422
- package/docs/design/memory-synthesis.md +0 -287
- package/docs/design/memory-system.md +0 -237
- package/docs/design/mlx-lm-serving-execution-seam.md +0 -81
- package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
- package/docs/design/omlx-adoption-map.md +0 -98
- package/docs/design/orpo-dynamic-lambda.md +0 -220
- package/docs/design/orpo-future-enhancements.md +0 -340
- package/docs/design/paged-kv-cache.md +0 -214
- package/docs/design/parallel-slots.md +0 -567
- package/docs/design/parity-tier-dag.md +0 -161
- package/docs/design/repo-cleanup-plan.md +0 -231
- package/docs/design/runtime-isolation.md +0 -263
- package/docs/design/segmented-backward-training.md +0 -543
- package/docs/design/spec-decode-larger-targets.md +0 -229
- package/docs/design/ssd-kv-cold-tier.md +0 -415
- package/docs/design/structured-output.md +0 -484
- package/docs/design/tauri-desktop-app.md +0 -127
- package/docs/design/the-dreaming-master-plan.md +0 -445
- package/docs/design/turboquant-kv.md +0 -235
- package/docs/design/turboquant-weights.md +0 -467
- package/docs/design/w4a16-compute-precision-spike.md +0 -232
- package/docs/design/web-chat-beat-matrix.md +0 -315
- package/docs/design/write-pipeline-entity.md +0 -468
- package/docs/planning/IDEAS.md +0 -69
- package/docs/reference/embedding.md +0 -197
- package/docs/reference/features-matrix.md +0 -101
- package/docs/reference/orpo-quickstart.md +0 -135
- package/src/flags.ts +0 -11
- /package/docs/{design → archive/investigations}/colibri-glm52-port.md +0 -0
- /package/docs/{planning → archive/planning}/chunk-task-roadmap.md +0 -0
- /package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +0 -0
- /package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +0 -0
- /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.2.0.md +0 -0
- /package/src/{memory-plan.ts → admission-plan.ts} +0 -0
- /package/{docs/archive/investigations → src/serve/assets}/curve-terrain.html +0 -0
- /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
|
@@ -27,9 +27,10 @@ import {
|
|
|
27
27
|
type RequestRejection,
|
|
28
28
|
} from "./request-plan";
|
|
29
29
|
import type { PromptResponseTrace } from "./prompt-response-trace";
|
|
30
|
+
import { cleanupFailure } from "../engine/resources";
|
|
30
31
|
|
|
31
32
|
export interface CompletionEngine {
|
|
32
|
-
place(shape: RequestShape): GenerationPlacement;
|
|
33
|
+
place(shape: RequestShape, options?: GenerateOptions): GenerationPlacement;
|
|
33
34
|
run(
|
|
34
35
|
promptIds: number[],
|
|
35
36
|
options: GenerateOptions & { stopSequences?: string[] },
|
|
@@ -118,6 +119,8 @@ export interface CompletionUsage {
|
|
|
118
119
|
completionTokens: number;
|
|
119
120
|
totalTokens: number;
|
|
120
121
|
speculation?: GenerateStats["spec"];
|
|
122
|
+
/** Token fast-forwarding telemetry (serial lane, MLX_BUN_FILL=strict). */
|
|
123
|
+
fill?: GenerateStats["fill"];
|
|
121
124
|
}
|
|
122
125
|
|
|
123
126
|
export interface CompletionSummary {
|
|
@@ -199,7 +202,7 @@ function finalLane(
|
|
|
199
202
|
stats: GenerateStats,
|
|
200
203
|
): Lane {
|
|
201
204
|
if (mechanism === "continuous") return "batched";
|
|
202
|
-
return stats.spec ? "serial+spec" :
|
|
205
|
+
return stats.spec ? "serial+spec" : "serial";
|
|
203
206
|
}
|
|
204
207
|
|
|
205
208
|
function finishReason(
|
|
@@ -209,7 +212,7 @@ function finishReason(
|
|
|
209
212
|
): CompletionFinishReason {
|
|
210
213
|
if (result.toolCalls.length) return "tool_calls";
|
|
211
214
|
if (result.stopped) return "stop";
|
|
212
|
-
return stats.generatedTokens >= maxTokens ? "length" : "stop";
|
|
215
|
+
return stats.finishReason ?? (stats.generatedTokens >= maxTokens ? "length" : "stop");
|
|
213
216
|
}
|
|
214
217
|
|
|
215
218
|
function combineControl(
|
|
@@ -251,12 +254,14 @@ export class CompletionExecutor {
|
|
|
251
254
|
try {
|
|
252
255
|
if (usageProgress) control.onUsageProgress!(usageProgress);
|
|
253
256
|
const closePlacement = control.trace?.begin("completion.placement");
|
|
254
|
-
const enginePlacement = this.engine.place(planned.shape);
|
|
257
|
+
const enginePlacement = this.engine.place(planned.shape, planned.options);
|
|
255
258
|
closePlacement?.();
|
|
256
259
|
if (enginePlacement.shape !== planned.shape)
|
|
257
260
|
throw new Error("generation placement does not belong to this request shape");
|
|
258
261
|
const mechanism = enginePlacement.mechanism;
|
|
259
|
-
const lane =
|
|
262
|
+
const lane = enginePlacement.execution
|
|
263
|
+
? (mechanism === "continuous" ? "batched" : enginePlacement.execution.method === "speculative" ? "serial+spec" : "serial")
|
|
264
|
+
: initialLane(mechanism, planned.shape);
|
|
260
265
|
recordLane(input.requestId, lane);
|
|
261
266
|
const placement = { mechanism, lane, shape: planned.shape };
|
|
262
267
|
input.onPlacement?.(placement);
|
|
@@ -314,6 +319,8 @@ export class CompletionExecutor {
|
|
|
314
319
|
usageProgress.totalTokens = stats.promptTokens + stats.generatedTokens;
|
|
315
320
|
if (stats.spec) usageProgress.speculation = stats.spec;
|
|
316
321
|
else delete usageProgress.speculation;
|
|
322
|
+
if (stats.fill) usageProgress.fill = stats.fill;
|
|
323
|
+
else delete usageProgress.fill;
|
|
317
324
|
}
|
|
318
325
|
const summary = {
|
|
319
326
|
content: result.content,
|
|
@@ -332,15 +339,16 @@ export class CompletionExecutor {
|
|
|
332
339
|
completionTokens: stats.generatedTokens,
|
|
333
340
|
totalTokens: stats.promptTokens + stats.generatedTokens,
|
|
334
341
|
...(stats.spec ? { speculation: stats.spec } : {}),
|
|
342
|
+
...(stats.fill ? { fill: stats.fill } : {}),
|
|
335
343
|
},
|
|
336
344
|
logprobs: logprobs?.payload() ?? null,
|
|
337
345
|
};
|
|
346
|
+
planned.dispose(); // retained preparation reservations outlive the native run
|
|
338
347
|
closeCompletion?.();
|
|
339
348
|
return summary;
|
|
340
349
|
} catch (error) {
|
|
341
350
|
closeCompletion?.();
|
|
342
|
-
planned.dispose();
|
|
343
|
-
throw error;
|
|
351
|
+
cleanupFailure(error, () => planned.dispose());
|
|
344
352
|
}
|
|
345
353
|
}
|
|
346
354
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
+
import { modelServingBinding } from "../backends/mlx/model-serving";
|
|
1
2
|
import pkgJson from "../../package.json" with { type: "json" };
|
|
2
|
-
import type {
|
|
3
|
+
import type { ServingContext } from "./model-host";
|
|
3
4
|
import type { GenerationGateway } from "./generation-gateway";
|
|
4
5
|
import { fit } from "../fit";
|
|
5
|
-
import { isEmbeddingModel } from "../embed";
|
|
6
|
-
import { Glm52Model } from "../model/glm52";
|
|
7
6
|
|
|
8
7
|
const pkgVersion = (pkgJson as { version: string }).version;
|
|
9
8
|
|
|
@@ -32,7 +31,7 @@ export interface DiscoveryRoutes {
|
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
export function createDiscoveryRoutes(
|
|
35
|
-
ctx:
|
|
34
|
+
ctx: ServingContext,
|
|
36
35
|
gateway: Pick<GenerationGateway, "batchMode">,
|
|
37
36
|
startedAt: number,
|
|
38
37
|
): DiscoveryRoutes {
|
|
@@ -46,7 +45,7 @@ export function createDiscoveryRoutes(
|
|
|
46
45
|
async handle(url, request) {
|
|
47
46
|
switch (matchDiscoveryRoute(request.method, url.pathname)) {
|
|
48
47
|
case "library": {
|
|
49
|
-
if (!libraryCache || Date.now() - libraryCache.at > 30_000) {
|
|
48
|
+
if (url.searchParams.get("refresh") === "1" || !libraryCache || Date.now() - libraryCache.at > 30_000) {
|
|
50
49
|
const { Registry, visionCapable, audioCapable } = await import("../registry");
|
|
51
50
|
const { loadModelConfig } = await import("../config");
|
|
52
51
|
const { supportTier } = await import("../model/support");
|
|
@@ -132,7 +131,7 @@ export function createDiscoveryRoutes(
|
|
|
132
131
|
top_p: ctx.genDefaults.topP ?? null,
|
|
133
132
|
top_k: ctx.genDefaults.topK ?? null,
|
|
134
133
|
};
|
|
135
|
-
const
|
|
134
|
+
const capabilities = modelServingBinding(ctx).discovery;
|
|
136
135
|
const data: Array<Record<string, unknown>> = [{
|
|
137
136
|
id: ctx.modelId,
|
|
138
137
|
object: "model",
|
|
@@ -147,10 +146,10 @@ export function createDiscoveryRoutes(
|
|
|
147
146
|
batch_mode: gateway.batchMode,
|
|
148
147
|
tools: true,
|
|
149
148
|
structured_output: true,
|
|
150
|
-
embeddings:
|
|
151
|
-
adapters:
|
|
152
|
-
training:
|
|
153
|
-
dsa:
|
|
149
|
+
embeddings: capabilities.embeddings,
|
|
150
|
+
adapters: capabilities.adapters,
|
|
151
|
+
training: capabilities.training,
|
|
152
|
+
dsa: capabilities.dsa,
|
|
154
153
|
mtp: ctx.draft?.provider.id === "glm52-native-mtp",
|
|
155
154
|
capabilities: {
|
|
156
155
|
chat_completions: true,
|
|
@@ -161,11 +160,11 @@ export function createDiscoveryRoutes(
|
|
|
161
160
|
tools: true,
|
|
162
161
|
structured_output: true,
|
|
163
162
|
logprobs: true,
|
|
164
|
-
embeddings:
|
|
163
|
+
embeddings: capabilities.embeddings,
|
|
165
164
|
vision: !!(ctx.vision || ctx.loadVision),
|
|
166
165
|
audio: !!(ctx.audio || ctx.loadAudio),
|
|
167
|
-
adapters:
|
|
168
|
-
training:
|
|
166
|
+
adapters: capabilities.adapters,
|
|
167
|
+
training: capabilities.training,
|
|
169
168
|
},
|
|
170
169
|
gen_defaults: genDefaults,
|
|
171
170
|
}];
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { AdmissionPool } from "../engine/admission";
|
|
2
|
+
import { acquireReservation } from "./preparation";
|
|
3
|
+
import { cleanupFailure, disposeResources } from "../engine/resources";
|
|
1
4
|
// GenerationGateway is the scheduling seam between CompletionExecutor and
|
|
2
5
|
// the inference runtime. It declares one mechanism per request: the preserved
|
|
3
6
|
// strict/dedicated serial executor, or the continuous scheduler (`--batch N`).
|
|
@@ -32,25 +35,19 @@
|
|
|
32
35
|
// AND sliding-window (Gemma) models both batch — the scheduler assembles each
|
|
33
36
|
// layer's cache by type. Prompt-cache prefix reuse works on the batch lane
|
|
34
37
|
// too (Phase 3.2): joiners take() at admission; never-merged lone rows put()
|
|
35
|
-
// 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).
|
|
36
40
|
|
|
37
41
|
import { MlxArray } from "../mlx/array";
|
|
38
42
|
import type { RuntimeModel } from "../model/factory";
|
|
39
|
-
import { DiffusionGemmaModel } from "../model/diffusion-gemma";
|
|
40
|
-
import {
|
|
41
|
-
KVCache,
|
|
42
|
-
RotatingKVCache,
|
|
43
|
-
isBatchableCache,
|
|
44
|
-
isPlainKvCache,
|
|
45
|
-
isRotatingPlainCache,
|
|
46
|
-
} from "../model/gemma4-base";
|
|
47
|
-
import { SSMCache } from "../model/qwen3-delta";
|
|
48
|
-
import { UniversalDenseModel } from "../model/universal/dense";
|
|
49
43
|
import type { GenerateOptions, GenerateStats, TokenLogprobs } from "../generate";
|
|
50
44
|
import type { KvScheme } from "../kv-scheme";
|
|
51
45
|
import { makeStepSampler } from "../sampler";
|
|
52
|
-
import {
|
|
53
|
-
import {
|
|
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";
|
|
54
51
|
import type { PromptResponseTrace } from "./prompt-response-trace";
|
|
55
52
|
|
|
56
53
|
/** Async mutex: acquire() resolves to a release fn; releases run FIFO. */
|
|
@@ -61,18 +58,38 @@ class AsyncMutex {
|
|
|
61
58
|
get locked(): boolean {
|
|
62
59
|
return this.#pending > 0;
|
|
63
60
|
}
|
|
64
|
-
acquire(): Promise<() => void> {
|
|
61
|
+
acquire(signal?: AbortSignal): Promise<() => void> {
|
|
62
|
+
if (signal?.aborted) return Promise.reject(signal.reason);
|
|
65
63
|
this.#pending++;
|
|
66
64
|
let release!: () => void;
|
|
67
65
|
const gate = new Promise<void>((r) => { release = r; });
|
|
68
66
|
const wait = this.#tail;
|
|
69
67
|
this.#tail = this.#tail.then(() => gate);
|
|
70
68
|
let released = false;
|
|
71
|
-
|
|
69
|
+
const unlock = () => {
|
|
72
70
|
if (released) return; // idempotent: a double release must not skew #pending
|
|
73
71
|
released = true;
|
|
74
72
|
this.#pending--;
|
|
75
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
|
+
});
|
|
76
93
|
});
|
|
77
94
|
}
|
|
78
95
|
}
|
|
@@ -85,7 +102,7 @@ export type Vision = {
|
|
|
85
102
|
embeddings: MlxArray;
|
|
86
103
|
/** bool [L] image-token mask for the bidirectional attention overlay.
|
|
87
104
|
* Absent when the prompt carries ANY audio — audio(-containing) prompts
|
|
88
|
-
* run fully causal (
|
|
105
|
+
* run fully causal (docs/design/generic-model-support.md §3.3 Q1). */
|
|
89
106
|
imageMask?: MlxArray;
|
|
90
107
|
/** bool [L] union multimodal soft-token mask (image | audio) for
|
|
91
108
|
* per-layer-input id zeroing. Absent on the legacy vision-only shape,
|
|
@@ -112,53 +129,11 @@ export type SerialRun = (
|
|
|
112
129
|
onToken: OnToken,
|
|
113
130
|
vision?: Vision,
|
|
114
131
|
trace?: PromptResponseTrace,
|
|
132
|
+
execution?: ResolvedExecution,
|
|
115
133
|
) => Promise<GenerateStats>;
|
|
116
134
|
|
|
117
135
|
/** What the batchable decision needs from a request (cheap to compute). */
|
|
118
|
-
export interface RequestShape {
|
|
119
|
-
readonly hasVision: boolean;
|
|
120
|
-
readonly hasAdapters: boolean;
|
|
121
|
-
readonly hasRepetitionPenalty: boolean;
|
|
122
|
-
/** The user explicitly set `seed` (reproducibility) — not the random default. */
|
|
123
|
-
readonly userSeed: boolean;
|
|
124
|
-
/** KV quantization is active. Per-layer configs may use the continuous
|
|
125
|
-
* scheduler; uniform schemes use the serial mechanism. */
|
|
126
|
-
readonly kvQuant: boolean;
|
|
127
|
-
/** TurboQuant is active (docs/design/turboquant-kv.md). Solo-only in v1:
|
|
128
|
-
* TurboQuantKVCache is a novel Cache implementation (not a KVCache/
|
|
129
|
-
* RotatingKVCache subclass), so #modelCachesBatchable() already excludes
|
|
130
|
-
* it automatically once a request's cache is converted — this flag is the
|
|
131
|
-
* BELT on top of that automatic BRACES, an explicit refusal at the
|
|
132
|
-
* request-shape level so a turbo request never reaches the scheduler even
|
|
133
|
-
* before any cache conversion has happened. Both layers exist on purpose. */
|
|
134
|
-
readonly turboQuant: boolean;
|
|
135
|
-
/** Any of the mlx-lm sampler/processor extensions is active: min_p, XTC,
|
|
136
|
-
* logit_bias, presence/frequency penalty. INFORMATIONAL ONLY: since the
|
|
137
|
-
* continuous scheduler grew per-row samplers/logits processors, these all
|
|
138
|
-
* compose. Placement deliberately does not gate on this field (see the
|
|
139
|
-
* repetition-penalty note in #supportsContinuous). Kept so /stats and
|
|
140
|
-
* scheduling traces can show what
|
|
141
|
-
* a request carries. */
|
|
142
|
-
readonly hasLogitsExtras: boolean;
|
|
143
|
-
/** A grammar controller compiled for this request (response_format /
|
|
144
|
-
* guided_*). Degrade-path requests (compile failed → prompt injection)
|
|
145
|
-
* have NO controller and stay batchable — the injection already happened
|
|
146
|
-
* at the prompt level. B0 routes grammar to serial; B1 makes it batchable
|
|
147
|
-
* (per-row matchers), with MLX_BUN_GRAMMAR_BATCH=0 forcing the B0 serial
|
|
148
|
-
* fallback as the A/B + kill switch. */
|
|
149
|
-
readonly hasGrammar: boolean;
|
|
150
|
-
/** The request asked for logprobs/top_logprobs capture. Batch-lane logprobs
|
|
151
|
-
* are deferred (the scheduler's per-row sampler doesn't capture or read
|
|
152
|
-
* back logprob arrays yet), so these route to the serial lane like the
|
|
153
|
-
* other mlx-lm request extensions above. */
|
|
154
|
-
readonly wantsLogprobs: boolean;
|
|
155
|
-
/** A draft model is configured (serve --draft-model). Server-level and
|
|
156
|
-
* upstream-parity: mlx_lm.server sets is_batchable = (draft is None), so
|
|
157
|
-
* every request routes serial while a draft is mounted — speculation is a
|
|
158
|
-
* B=1 latency optimization, batching a throughput one; they are different
|
|
159
|
-
* mechanisms by design (grammar-spec-batching-integration.md). */
|
|
160
|
-
readonly hasDraft: boolean;
|
|
161
|
-
}
|
|
136
|
+
export interface RequestShape extends ExecutionRequirements {}
|
|
162
137
|
|
|
163
138
|
export type GenerationMechanism = "serial" | "continuous";
|
|
164
139
|
|
|
@@ -167,12 +142,22 @@ export type GenerationMechanism = "serial" | "continuous";
|
|
|
167
142
|
export interface GenerationPlacement {
|
|
168
143
|
readonly shape: RequestShape;
|
|
169
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));
|
|
170
151
|
}
|
|
171
152
|
|
|
172
153
|
export class GenerationGateway {
|
|
154
|
+
#closed = false;
|
|
155
|
+
readonly #runtime: RuntimeConfig;
|
|
156
|
+
readonly #binding: MlxGatewayBinding;
|
|
157
|
+
readonly #requests: AdmissionPool;
|
|
173
158
|
readonly #mutex = new AsyncMutex();
|
|
174
159
|
readonly #batch: number;
|
|
175
|
-
#scheduler:
|
|
160
|
+
#scheduler: MlxBatchGroup | null = null;
|
|
176
161
|
#rowsSubmitted = 0;
|
|
177
162
|
/** Serial-lane requests waiting for / holding the mutex. While > 0 the
|
|
178
163
|
* scheduler pauses admission (drain) so they can't be starved. */
|
|
@@ -182,11 +167,14 @@ export class GenerationGateway {
|
|
|
182
167
|
/** Lazy, memoized: can the configured KV scheme convert every named cache? */
|
|
183
168
|
#kvSchemeBatchable: boolean | null = null;
|
|
184
169
|
constructor(
|
|
185
|
-
|
|
170
|
+
model: RuntimeModel | MlxGatewayBinding,
|
|
186
171
|
batch: number,
|
|
187
172
|
private readonly serialRun: SerialRun,
|
|
188
173
|
private readonly opts: {
|
|
189
174
|
kvBudgetBytes?: number;
|
|
175
|
+
memoryBudget?: MlxMemoryBudget;
|
|
176
|
+
checkpoints?: boolean;
|
|
177
|
+
stateCodecs?: import("../kv-store").CacheCodecProvider;
|
|
190
178
|
/** The server-wide KV scheme (server.ts kvScheme) — threaded to the
|
|
191
179
|
* scheduler at construction when batchable (Phase 3.1). turboQuant is
|
|
192
180
|
* never threaded to the scheduler (always solo-only — see placement);
|
|
@@ -197,9 +185,16 @@ export class GenerationGateway {
|
|
|
197
185
|
* back on finish. Safe to share with the serial lane — both use it
|
|
198
186
|
* only inside this gateway's mutual-exclusion domain. */
|
|
199
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>;
|
|
200
191
|
} = {},
|
|
201
192
|
) {
|
|
193
|
+
this.#binding = "createBatchGroup" in model ? model : bindMlxGateway(model);
|
|
194
|
+
this.#runtime = this.#binding.runtime;
|
|
195
|
+
this.opts = Object.freeze({ ...opts });
|
|
202
196
|
this.#batch = Math.max(1, Math.floor(batch));
|
|
197
|
+
this.#requests = new AdmissionPool(this.#batch);
|
|
203
198
|
}
|
|
204
199
|
|
|
205
200
|
/** True if `--batch N` (N>1) is on (batchability is then per-request). */
|
|
@@ -229,7 +224,15 @@ export class GenerationGateway {
|
|
|
229
224
|
* running/pending. activeRows/pendingRows alone MISS the serial lane —
|
|
230
225
|
* a serial generation holds the mutex but shows zero rows. */
|
|
231
226
|
get busy(): boolean {
|
|
232
|
-
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();
|
|
233
236
|
}
|
|
234
237
|
|
|
235
238
|
/** Resolves once the engine is idle (poll-based, ~`pollMs` granularity —
|
|
@@ -257,113 +260,22 @@ export class GenerationGateway {
|
|
|
257
260
|
};
|
|
258
261
|
}
|
|
259
262
|
|
|
260
|
-
/** Cache-capability gate (mirrors mlx-lm server.py's all-caches-have-merge
|
|
261
|
-
* check): the scheduler's dynamic-B ops (temporalView/merge/filter) exist
|
|
262
|
-
* on KVCache, RotatingKVCache, and SSMCache (hybrid gated-DeltaNet models
|
|
263
|
-
* — Qwen3.5 — batch via SSMCache.mergeRows/filter; MLX_BUN_BATCH_SSM=0 is
|
|
264
|
-
* the kill switch back to serial routing). */
|
|
265
263
|
#modelCachesBatchable(): boolean {
|
|
266
|
-
|
|
267
|
-
// Tier-0 universal models: batchable for PLAIN full-attention archs
|
|
268
|
-
// only. The 2026-07-03 uneven-row bug (scalar-offset RoPE decoded
|
|
269
|
-
// padded rows at wrong positions) is fixed via
|
|
270
|
-
// UniversalRope.applyDynamic + ropeOffsetArr and GATED token-exact vs
|
|
271
|
-
// mlx-lm B=2 on Llama-3.2-3B (static + dynamic join/leave,
|
|
272
|
-
// tests/batched-decode-parity.test.ts "Llama 3B Tier-0"). The
|
|
273
|
-
// maskArray archs (gemma2-family: forwardLayers builds a pad-blind
|
|
274
|
-
// causal mask) and sliding-window universal archs remain UNVALIDATED
|
|
275
|
-
// cells → serial, per the per-model-cell discipline.
|
|
276
|
-
if (this.model instanceof UniversalDenseModel) {
|
|
277
|
-
const a = this.model.args;
|
|
278
|
-
this.#cacheBatchable =
|
|
279
|
-
!a.maskArray && !a.layerTypes?.includes("sliding_attention");
|
|
280
|
-
return this.#cacheBatchable;
|
|
281
|
-
}
|
|
282
|
-
const ssmOk = runtimeValue("MLX_BUN_BATCH_SSM") !== "0";
|
|
283
|
-
const proto = this.model.makeCache(); // fresh caches hold no buffers
|
|
284
|
-
this.#cacheBatchable = proto.every(
|
|
285
|
-
(c) =>
|
|
286
|
-
c instanceof KVCache ||
|
|
287
|
-
c instanceof RotatingKVCache ||
|
|
288
|
-
isBatchableCache(c) ||
|
|
289
|
-
(ssmOk && c instanceof SSMCache),
|
|
290
|
-
);
|
|
291
|
-
for (const c of proto) c.dispose();
|
|
292
|
-
}
|
|
293
|
-
return this.#cacheBatchable;
|
|
264
|
+
return this.#cacheBatchable ??= this.#binding.cachesBatchable();
|
|
294
265
|
}
|
|
295
266
|
|
|
296
|
-
/** Phase 3.1 + milestone 2: the server's kv scheme batches iff it is a
|
|
297
|
-
* per-layer kvConfig (the L2 mixed scheme) whose every configured layer
|
|
298
|
-
* is a full-attention KVCache (BatchedQuantDecodeMaskCache + quantized
|
|
299
|
-
* merge/extend/filter) or a rotating RotatingKVCache
|
|
300
|
-
* (BatchedRotatingQuantCache — milestone 2, gemma's kv_config). Uniform
|
|
301
|
-
* kvBits (quantizedKvStart threshold semantics via the serial no-byLayer
|
|
302
|
-
* path) and configs naming SSM layers stay serial. */
|
|
303
267
|
#kvBatchable(): boolean {
|
|
304
|
-
|
|
305
|
-
const scheme = this.opts.kvScheme;
|
|
306
|
-
if (scheme?.kind !== "affine-config") {
|
|
307
|
-
this.#kvSchemeBatchable = false;
|
|
308
|
-
} else {
|
|
309
|
-
const proto = this.model.makeCache();
|
|
310
|
-
try {
|
|
311
|
-
this.#kvSchemeBatchable = scheme.batchable(
|
|
312
|
-
this.model.config,
|
|
313
|
-
(layerIdx) =>
|
|
314
|
-
isPlainKvCache(proto[layerIdx]) || isRotatingPlainCache(proto[layerIdx]),
|
|
315
|
-
);
|
|
316
|
-
} finally {
|
|
317
|
-
for (const cache of proto) cache.dispose();
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
return this.#kvSchemeBatchable;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/** Whether the declared execution composition is implemented by the
|
|
325
|
-
* continuous scheduler. This is a support check only: it never rewrites
|
|
326
|
-
* MTP/drafting, KV, TurboQuant, grammar, adapters, or sampling. */
|
|
327
|
-
#supportsContinuous(shape: RequestShape): boolean {
|
|
328
|
-
// DiffusionGemma is non-autoregressive — the batch scheduler assumes the AR
|
|
329
|
-
// KV-cache decode path, so it always runs serially through generate().
|
|
330
|
-
if (this.model instanceof DiffusionGemmaModel) return false;
|
|
331
|
-
if (!this.batchingEnabled) return false;
|
|
332
|
-
if (!this.#modelCachesBatchable()) return false;
|
|
333
|
-
// repetitionPenalty / logits extras (min_p, XTC, logit_bias,
|
|
334
|
-
// presence/frequency penalties) batch through the same StepSampler and
|
|
335
|
-
// per-row device history as the serial lane. (Load-bearing beyond opt-in
|
|
336
|
-
// knobs: some models — Qwen3.5 — ship a default repetition penalty in
|
|
337
|
-
// generation_config.json, which used to route EVERY request serial.)
|
|
338
|
-
return (
|
|
339
|
-
!shape.hasVision &&
|
|
340
|
-
!shape.hasAdapters &&
|
|
341
|
-
!shape.wantsLogprobs &&
|
|
342
|
-
!shape.userSeed &&
|
|
343
|
-
// Phase 3.1: a kv-quant request batches when the server's scheme is
|
|
344
|
-
// the batchable kvConfig composition (the scheduler applies it);
|
|
345
|
-
// otherwise it routes serial exactly as before.
|
|
346
|
-
!(shape.kvQuant && !this.#kvBatchable()) &&
|
|
347
|
-
// TurboQuant: solo-only in v1, unconditionally — never partially
|
|
348
|
-
// batchable like kvConfig. The automatic instanceof exclusion in
|
|
349
|
-
// #modelCachesBatchable() already covers this once a cache converts;
|
|
350
|
-
// this is the explicit belt-and-braces refusal at request-shape time
|
|
351
|
-
// (mirrors the kvQuant pattern above — both layers exist on purpose).
|
|
352
|
-
!shape.turboQuant &&
|
|
353
|
-
!shape.hasDraft &&
|
|
354
|
-
// Grammar: B1 makes it batchable (per-row matchers) unless the kill
|
|
355
|
-
// switch forces serial. MLX_BUN_GRAMMAR_BATCH=0 = B0 behavior (serial),
|
|
356
|
-
// the A/B + kill lever for the new code, house style.
|
|
357
|
-
!(shape.hasGrammar && runtimeValue("MLX_BUN_GRAMMAR_BATCH") === "0")
|
|
358
|
-
);
|
|
268
|
+
return this.#kvSchemeBatchable ??= !!this.opts.kvScheme && this.#binding.kvBatchable(this.opts.kvScheme);
|
|
359
269
|
}
|
|
360
270
|
|
|
361
|
-
place(shape: RequestShape): GenerationPlacement {
|
|
271
|
+
place(shape: RequestShape, options: GenerateOptions = {}): GenerationPlacement {
|
|
362
272
|
const frozenShape = Object.freeze(shape);
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
273
|
+
const execution = this.#binding.plan(frozenShape, options, {
|
|
274
|
+
continuous: this.batchingEnabled,
|
|
275
|
+
quantizedBatch: shape.kvQuant && this.#kvBatchable(),
|
|
276
|
+
checkpoints: this.opts.checkpoints === true,
|
|
366
277
|
});
|
|
278
|
+
return Object.freeze({ shape: frozenShape, mechanism: execution.mechanism, execution });
|
|
367
279
|
}
|
|
368
280
|
|
|
369
281
|
/** Run `fn` with exclusive ownership of the GPU + shared model state (the
|
|
@@ -372,26 +284,37 @@ export class GenerationGateway {
|
|
|
372
284
|
* mount/unmount all come through here, so nothing runs concurrently with
|
|
373
285
|
* batched decode steps (or with each other). Registers as a serial waiter
|
|
374
286
|
* so the scheduler drains (stops admitting) until `fn` has run. */
|
|
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");
|
|
293
|
+
this.#serialWaiters++;
|
|
294
|
+
const closeAdmission = trace?.begin("engine.admission_wait", { mechanism: "serial" });
|
|
295
|
+
const resume = () => {
|
|
296
|
+
this.#serialWaiters--;
|
|
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
|
+
|
|
375
311
|
async runExclusive<T>(
|
|
376
312
|
fn: () => Promise<T>,
|
|
377
313
|
trace?: PromptResponseTrace,
|
|
314
|
+
signal?: AbortSignal,
|
|
378
315
|
): Promise<T> {
|
|
379
|
-
this
|
|
380
|
-
|
|
381
|
-
mechanism: "serial",
|
|
382
|
-
});
|
|
383
|
-
try {
|
|
384
|
-
const release = await this.#mutex.acquire();
|
|
385
|
-
closeAdmission?.();
|
|
386
|
-
try {
|
|
387
|
-
return await fn();
|
|
388
|
-
} finally {
|
|
389
|
-
release();
|
|
390
|
-
}
|
|
391
|
-
} finally {
|
|
392
|
-
this.#serialWaiters--;
|
|
393
|
-
if (this.#serialWaiters === 0) this.#scheduler?.kick(); // resume admission
|
|
394
|
-
}
|
|
316
|
+
const lease = await this.acquireExecutionLease(signal, trace);
|
|
317
|
+
try { return await fn(); } finally { lease.dispose(); }
|
|
395
318
|
}
|
|
396
319
|
|
|
397
320
|
/** Run one generation on the appropriate lane. onToken is invoked per emitted
|
|
@@ -406,8 +329,26 @@ export class GenerationGateway {
|
|
|
406
329
|
signal?: AbortSignal,
|
|
407
330
|
trace?: PromptResponseTrace,
|
|
408
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);
|
|
409
350
|
if (placement.shape !== shape) {
|
|
410
|
-
|
|
351
|
+
disposeUnstarted();
|
|
411
352
|
throw new Error("generation placement does not belong to this request shape");
|
|
412
353
|
}
|
|
413
354
|
try {
|
|
@@ -415,7 +356,7 @@ export class GenerationGateway {
|
|
|
415
356
|
} catch (e) {
|
|
416
357
|
// The gateway is the first component that accepts ownership of a
|
|
417
358
|
// compiled controller. An already-aborted request reaches neither lane.
|
|
418
|
-
|
|
359
|
+
disposeUnstarted();
|
|
419
360
|
throw e;
|
|
420
361
|
}
|
|
421
362
|
if (placement.mechanism === "serial") {
|
|
@@ -430,28 +371,40 @@ export class GenerationGateway {
|
|
|
430
371
|
let lastHop = performance.now();
|
|
431
372
|
const hop = (): Promise<void> => new Promise<void>((r) => setImmediate(r));
|
|
432
373
|
const hoppingOnToken: OnToken = (token, lp) => {
|
|
433
|
-
|
|
374
|
+
signal?.throwIfAborted();
|
|
434
375
|
const r = onToken(token, lp);
|
|
435
376
|
if (r === false) return false;
|
|
436
377
|
if (performance.now() - lastHop < 25) return r;
|
|
437
378
|
lastHop = performance.now();
|
|
438
379
|
if (r instanceof Promise)
|
|
439
|
-
return r.then((v) =>
|
|
440
|
-
v === false
|
|
441
|
-
|
|
442
|
-
|
|
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
|
+
});
|
|
443
391
|
};
|
|
444
|
-
|
|
392
|
+
let started = false;
|
|
393
|
+
return Promise.resolve().then(() => this.opts.beforeSerial?.()).then(() => this.runExclusive(async () => {
|
|
445
394
|
signal?.throwIfAborted();
|
|
395
|
+
started = true;
|
|
446
396
|
const stats = await this.serialRun(
|
|
447
|
-
promptIds, options, hoppingOnToken, vision, trace,
|
|
397
|
+
promptIds, signal ? { ...options, signal } : options, hoppingOnToken, vision, trace, placement.execution,
|
|
448
398
|
);
|
|
449
399
|
signal?.throwIfAborted();
|
|
450
400
|
return stats;
|
|
451
|
-
}, trace)
|
|
401
|
+
}, trace, signal))
|
|
452
402
|
// Covers a serial waiter aborted before serialRun takes ownership.
|
|
453
403
|
// generate() also disposes defensively; the operation is idempotent.
|
|
454
|
-
.finally(() =>
|
|
404
|
+
.finally(() => {
|
|
405
|
+
if (started) options.grammar?.dispose();
|
|
406
|
+
else disposeUnstarted();
|
|
407
|
+
});
|
|
455
408
|
}
|
|
456
409
|
|
|
457
410
|
// The scheduler owns grammar ready/accept sequencing. StepSampler owns the
|
|
@@ -473,8 +426,9 @@ export class GenerationGateway {
|
|
|
473
426
|
try {
|
|
474
427
|
st = await this.#ensureScheduler().submit({
|
|
475
428
|
promptIds,
|
|
429
|
+
compiledDecode: placement.execution?.compiledDecode,
|
|
476
430
|
maxTokens: options.maxTokens ?? 512,
|
|
477
|
-
eosTokenIds: options.eosTokenIds ?? this.
|
|
431
|
+
eosTokenIds: options.eosTokenIds ?? this.#binding.config.eosTokenIds,
|
|
478
432
|
sample,
|
|
479
433
|
plainGreedy: stepSampler.isPlainGreedy,
|
|
480
434
|
onToken,
|
|
@@ -500,16 +454,23 @@ export class GenerationGateway {
|
|
|
500
454
|
promptTokens: st.promptTokens,
|
|
501
455
|
cachedTokens: st.cachedTokens,
|
|
502
456
|
generatedTokens: st.generatedTokens,
|
|
503
|
-
|
|
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,
|
|
504
462
|
cacheTokens: [],
|
|
505
463
|
};
|
|
506
464
|
}
|
|
507
465
|
|
|
508
|
-
#ensureScheduler():
|
|
466
|
+
#ensureScheduler(): MlxBatchGroup {
|
|
509
467
|
if (!this.#scheduler)
|
|
510
|
-
this.#scheduler =
|
|
468
|
+
this.#scheduler = this.#binding.createBatchGroup({
|
|
469
|
+
runtime: this.#runtime,
|
|
511
470
|
maxBatch: this.#batch,
|
|
471
|
+
stateCodecs: this.opts.stateCodecs,
|
|
512
472
|
kvBudgetBytes: this.opts.kvBudgetBytes,
|
|
473
|
+
memoryBudget: this.opts.memoryBudget,
|
|
513
474
|
// Phase 3.1: the batchable kvConfig composition is applied by the
|
|
514
475
|
// scheduler (solo rows convert at serial chunk boundaries, then
|
|
515
476
|
// merge as quantized triples). Only threaded when the scheme
|