mlx-bun 0.2.0 → 0.4.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 +94 -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/archive/investigations/openjarvis-fit.md +198 -0
- package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
- package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
- package/docs/archive/investigations/qwen38-27b-campaign-log.md +1092 -0
- package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
- package/docs/archive/planning/release-notes-v0.3.0.md +35 -0
- package/docs/design/batching.md +1841 -0
- package/docs/design/decode-speed-program.md +2346 -169
- package/docs/design/dreaming-nightly-pipeline.md +675 -282
- package/docs/design/generic-model-support.md +655 -334
- package/docs/design/kv-cache.md +824 -0
- package/docs/design/orpo-training.md +673 -1239
- package/docs/design/speculative-decoding.md +1990 -0
- package/docs/design/turboquant.md +2212 -0
- package/docs/design/unified-engine-frontier-plan.md +1833 -727
- package/docs/design/web-chat-redesign.md +666 -1083
- package/docs/planning/PRODUCT_ROADMAP.md +50 -1
- package/docs/planning/release-notes-v0.4.0.md +45 -0
- package/docs/planning/video-series.md +2 -2
- package/docs/reference/benchmarks.md +3613 -0
- package/docs/reference/cli.md +434 -160
- package/docs/reference/distribution.md +328 -182
- package/docs/reference/environment.md +398 -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 +113 -6
- package/docs/reference/server-api.md +944 -429
- package/docs/reference/server-config.md +740 -338
- package/docs/reference/training.md +393 -254
- package/docs/reference/troubleshooting.md +158 -0
- package/package.json +9 -4
- package/src/audio/decode.ts +2 -2
- package/src/audio/features.ts +2 -2
- package/src/backends/mlx/assistant-target.ts +46 -0
- package/src/backends/mlx/autoregressive-draft-rows.ts +120 -0
- package/src/backends/mlx/autoregressive.ts +108 -0
- package/src/backends/mlx/batch-group.ts +1790 -0
- package/src/backends/mlx/cache-attachment.ts +67 -0
- package/src/backends/mlx/cache-layout.ts +31 -0
- package/src/backends/mlx/checkpoint-state.ts +53 -0
- package/src/backends/mlx/continuation-persistence.ts +106 -0
- package/src/backends/mlx/continuation-request.ts +43 -0
- package/src/backends/mlx/continuation.ts +77 -0
- package/src/backends/mlx/diffusion.ts +39 -0
- package/src/backends/mlx/draft-target.ts +32 -0
- package/src/backends/mlx/evaluation.ts +105 -0
- package/src/backends/mlx/fill-append.ts +45 -0
- package/src/backends/mlx/full-kv-row-append.ts +32 -0
- package/src/backends/mlx/full-kv-row-donor.ts +54 -0
- package/src/backends/mlx/full-transitioning-kv-rows.ts +65 -0
- package/src/backends/mlx/gateway-binding.ts +130 -0
- package/src/backends/mlx/graph.ts +38 -0
- package/src/backends/mlx/kv-codec.ts +9 -0
- package/src/backends/mlx/kv-maintenance.ts +133 -0
- package/src/backends/mlx/kv-tensor-rows.ts +212 -0
- package/src/backends/mlx/memory-client.ts +189 -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-cohort.ts +89 -0
- package/src/backends/mlx/prefill-rows.ts +200 -0
- package/src/backends/mlx/prefill.ts +33 -0
- package/src/backends/mlx/projected-context-rows.ts +72 -0
- package/src/backends/mlx/request-policy.ts +20 -0
- package/src/backends/mlx/request-state-policy.ts +41 -0
- package/src/backends/mlx/rollback.ts +89 -0
- package/src/backends/mlx/row-sampling.ts +49 -0
- package/src/backends/mlx/serial-executor.ts +242 -0
- package/src/backends/mlx/speculative-group.ts +359 -0
- package/src/backends/mlx/speculative-round.ts +161 -0
- package/src/backends/mlx/speculative.ts +114 -0
- package/src/backends/mlx/state-rows.ts +104 -0
- package/src/backends/mlx/state-views.ts +46 -0
- package/src/backends/mlx/target-layout-capability.ts +35 -0
- package/src/backends/mlx/text-engine.ts +73 -0
- package/src/backends/mlx/transitioning-kv-rows.ts +165 -0
- package/src/cli.ts +66 -58
- package/src/client.ts +66 -0
- package/src/config.ts +21 -4
- package/src/contracts/completion.ts +16 -0
- package/src/contracts/execution.ts +60 -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/prefix-cache.ts +23 -0
- package/src/contracts/resources.ts +25 -0
- package/src/contracts/sampling.ts +23 -0
- package/src/contracts/scheduling.ts +45 -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 +53 -0
- package/src/engine/resources.ts +53 -0
- package/src/engine/scheduler.ts +72 -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 +30 -16
- package/src/generate.ts +709 -417
- 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/draft-acceptance.ts +41 -0
- package/src/inference/draft-output.ts +49 -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-scheme.ts +70 -12
- package/src/kv-store.ts +177 -63
- 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/lab/paged-kv/paged-kv-rows.ts +83 -0
- package/src/{model → lab/paged-kv}/paged-kv.ts +32 -15
- 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 +11 -11
- 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/artifact-identity.ts +73 -0
- package/src/model/batched-kv.ts +235 -0
- package/src/model/batched-mask.ts +20 -6
- package/src/model/batched-quant.ts +9 -3
- package/src/model/batched-quantized-kv.ts +144 -0
- package/src/model/batched-rotating-quant.ts +78 -17
- package/src/model/batched-rotating-state.ts +76 -0
- package/src/model/batched-rotating.ts +65 -23
- package/src/model/batched-row-storage.ts +52 -0
- package/src/model/batched-ssm.ts +52 -0
- package/src/model/batched-turboquant-kv.ts +93 -0
- package/src/model/compiled-decode.ts +8 -2
- package/src/model/decoded-kv-donor.ts +12 -0
- package/src/model/delayed-quantized-kv.ts +72 -0
- package/src/model/delayed-rotating-quantized-kv.ts +93 -0
- package/src/model/delayed-turboquant-kv.ts +55 -0
- 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/full-prefill-padding.ts +56 -0
- package/src/model/full-prefill-row.ts +105 -0
- package/src/model/gemma4-base.ts +179 -80
- package/src/model/gemma4.ts +40 -32
- package/src/model/generated/gemma4-12b.ts +6 -3
- package/src/model/generated/gemma4-26b.ts +6 -3
- package/src/model/generated/gemma4-e4b.ts +6 -3
- package/src/model/glm52-cache.ts +80 -4
- package/src/model/glm52-dsa.ts +13 -9
- package/src/model/glm52-memory.ts +1 -1
- package/src/model/glm52-mla.ts +12 -6
- package/src/model/glm52.ts +19 -40
- package/src/model/implementation.ts +69 -0
- package/src/model/kv-attention-view.ts +67 -0
- package/src/model/minicpm5.ts +17 -15
- 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-delta-state.ts +74 -0
- package/src/model/qwen3-delta.ts +59 -36
- package/src/model/qwen3_5-checkpoint.ts +164 -0
- package/src/model/qwen3_5.ts +202 -78
- package/src/model/rotating-kv-donor.ts +35 -0
- package/src/model/rotating-kv-layout.ts +262 -0
- package/src/model/rotating-row-transaction.ts +35 -0
- package/src/model/speculative-rotating-kv.ts +101 -0
- package/src/model/ssm-prefill-padding.ts +39 -0
- 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/turboquant-codec.ts +83 -0
- package/src/model/universal/dense.ts +15 -14
- 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/preflight.ts +31 -15
- package/src/prompt-cache.ts +110 -69
- 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 +40 -29
- 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 +183 -213
- 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 +511 -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 +23 -18
- package/src/serve/request-prep.ts +344 -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 +335 -2711
- package/src/spec/assistant-rows.ts +92 -0
- package/src/spec/assistant-source.ts +31 -55
- package/src/spec/cache-identity.ts +22 -0
- package/src/spec/deepspec-rows.ts +29 -0
- package/src/spec/deepspec-source.ts +16 -14
- package/src/spec/dflash-source.ts +36 -12
- package/src/spec/drafter.ts +153 -142
- package/src/spec/dspark/ab-stats.ts +3 -3
- package/src/spec/dspark/calibration.ts +2 -2
- package/src/spec/dspark/deepspec-module.ts +97 -122
- 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 +129 -172
- 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-graph.ts +29 -0
- package/src/spec/glm52-mtp-rows.ts +132 -0
- package/src/spec/glm52-mtp-source.ts +54 -137
- package/src/spec/glm52-mtp-state.ts +33 -0
- package/src/spec/ngram-source.ts +93 -21
- package/src/spec/projected-draft-rows.ts +73 -0
- package/src/spec/qwen-mtp-module.ts +171 -0
- package/src/spec/qwen-mtp-rows.ts +219 -0
- package/src/spec/qwen-mtp-source.ts +147 -195
- package/src/spec/qwen-mtp-state.ts +30 -0
- package/src/spec/serve-loop.ts +201 -201
- package/src/spec/source.ts +110 -13
- package/src/spec/two-model-rows.ts +61 -0
- package/src/spec/two-model.ts +44 -102
- package/src/ssd-cache.ts +99 -19
- package/src/ssd-durability.ts +36 -20
- 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 +62 -60
- 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
|
@@ -20,20 +20,23 @@
|
|
|
20
20
|
// untouched. PERSISTENT (like BatchedRotatingCache, unlike the ephemeral
|
|
21
21
|
// per-step BatchedQuantDecodeMaskCache): the scheduler passes it through
|
|
22
22
|
// every step; it self-tracks per-row offset/leftPad and the scalar ring
|
|
23
|
-
// state.
|
|
23
|
+
// state. Multi-token blocks use the same chronological storage operation as
|
|
24
|
+
// the bf16 layout; one-token updates keep the existing in-place ring kernel.
|
|
24
25
|
//
|
|
25
|
-
// Gates: tests/batched-rotating-quant.test.ts (model-free byte-identity vs
|
|
26
|
+
// Gates: tests/unit/batched-rotating-quant.test.ts (model-free byte-identity vs
|
|
26
27
|
// the serial oracle per row, through ring wrap, B=1 and B=2) + the gemma
|
|
27
|
-
// scheduler gate in tests/batched-kv-quant-parity.test.ts.
|
|
28
|
+
// scheduler gate in tests/parity/batched-kv-quant-parity.test.ts.
|
|
28
29
|
|
|
29
30
|
import { MlxArray } from "../mlx/array";
|
|
30
31
|
import * as ops from "../mlx/ops";
|
|
31
32
|
import { Dtype } from "../mlx/ffi";
|
|
32
|
-
import { RotatingQuantizedKVCache, type Mask } from "./gemma4-base";
|
|
33
|
+
import { RotatingQuantizedKVCache, type Mask, type PaddedPrefillCache, type PrefillPadding } from "./gemma4-base";
|
|
33
34
|
import { buildBatchedRotatingMask } from "./batched-rotating";
|
|
34
35
|
import type { QuantRow } from "./batched-quant";
|
|
35
|
-
import { BatchedRotatingState } from "./batched-rotating-state";
|
|
36
|
+
import { BatchedRotatingState, type RotatingPositionSnapshot } from "./batched-rotating-state";
|
|
36
37
|
import {
|
|
38
|
+
appendRotatingStorage,
|
|
39
|
+
rowRollIndices,
|
|
37
40
|
mergeStorageRows,
|
|
38
41
|
quantizedRowStorage,
|
|
39
42
|
temporalStorageView,
|
|
@@ -54,7 +57,7 @@ const mapTriple = (
|
|
|
54
57
|
* mechanics (see batched-rotating.ts) with RotatingQuantizedKVCache
|
|
55
58
|
* storage. Scalar ring state (`ringIdx`/`offset` reuse the base fields);
|
|
56
59
|
* per-row `offsetArr` (absolute positions → RoPE) and `leftPad`. */
|
|
57
|
-
export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
|
|
60
|
+
export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache implements PaddedPrefillCache {
|
|
58
61
|
readonly #rows: BatchedRotatingState;
|
|
59
62
|
/** Per-row RoPE positions. STABLE ACROSS A STEP — refreshed only at
|
|
60
63
|
* releaseRopeArr() (the scheduler's post-dispatch hook), never inside
|
|
@@ -72,7 +75,34 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
|
|
|
72
75
|
) {
|
|
73
76
|
super(maxSize, groupSize, bits);
|
|
74
77
|
this.#rows = new BatchedRotatingState(maxSize, leftPad, offsets);
|
|
75
|
-
this.ropeOffsetArr = MlxArray.fromInt32(Int32Array.from(offsets), [offsets.length]);
|
|
78
|
+
this.ropeOffsetArr = offsets.length ? MlxArray.fromInt32(Int32Array.from(offsets), [offsets.length]) : ops.zeros([0], Dtype.int32);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static empty(maxSize: number, groupSize: number, bits: number, leftPad: readonly number[]): BatchedRotatingQuantCache {
|
|
82
|
+
return new BatchedRotatingQuantCache(maxSize, groupSize, bits, [...leftPad], leftPad.map(pad => -pad));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
get positionSnapshot(): RotatingPositionSnapshot { return this.#rows.snapshot(); }
|
|
86
|
+
validOffset(row: number): number { return this.#rows.validOffset(row); }
|
|
87
|
+
preparePrefill(padding: PrefillPadding, rightPaddedBatch?: boolean): void { this.#rows.preparePrefill(padding, rightPaddedBatch); this.releaseRopeArr(); }
|
|
88
|
+
finalizePrefill(): void {
|
|
89
|
+
const shifts = this.#rows.prefillRoll();
|
|
90
|
+
if (shifts?.some(shift => shift !== 0) && this.keys && this.values) {
|
|
91
|
+
using indices = rowRollIndices(this.keys.packed.shape[2]!, shifts);
|
|
92
|
+
const keys = quantizedRowStorage.rollRows(this.keys, indices);
|
|
93
|
+
let values: ops.QuantizedTensor;
|
|
94
|
+
try { values = quantizedRowStorage.rollRows(this.values, indices); }
|
|
95
|
+
catch (error) { disposeTriple(keys); throw error; }
|
|
96
|
+
disposeTriple(this.keys); disposeTriple(this.values); this.keys = keys; this.values = values;
|
|
97
|
+
}
|
|
98
|
+
this.#rows.finalizePrefill(); this.releaseRopeArr();
|
|
99
|
+
}
|
|
100
|
+
/** Adopt owned tensors without changing their physical ring columns. */
|
|
101
|
+
static adoptPhysical(keys: ops.QuantizedTensor | null, values: ops.QuantizedTensor | null, groupSize: number, bits: number, position: RotatingPositionSnapshot): BatchedRotatingQuantCache {
|
|
102
|
+
const cache = new BatchedRotatingQuantCache(position.maxSize, groupSize, bits, [...position.leftPad], [...position.offsets]);
|
|
103
|
+
cache.#rows.restore(position); cache.keys = keys; cache.values = values;
|
|
104
|
+
cache.offset = position.totalOffset; cache.ringIdx = position.ringIndex;
|
|
105
|
+
return cache;
|
|
76
106
|
}
|
|
77
107
|
|
|
78
108
|
get offsetArr(): number[] { return this.#rows.offsets; }
|
|
@@ -93,7 +123,7 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
|
|
|
93
123
|
* old handle after this). */
|
|
94
124
|
releaseRopeArr(): void {
|
|
95
125
|
this.ropeOffsetArr.dispose();
|
|
96
|
-
this.ropeOffsetArr = MlxArray.fromInt32(Int32Array.from(this.offsetArr), [this.#B]);
|
|
126
|
+
this.ropeOffsetArr = this.#B ? MlxArray.fromInt32(Int32Array.from(this.offsetArr), [this.#B]) : ops.zeros([0], Dtype.int32);
|
|
97
127
|
}
|
|
98
128
|
|
|
99
129
|
#allocTriple(B: number, H: number, T: number, dim: number, dtype: Dtype): ops.QuantizedTensor {
|
|
@@ -111,7 +141,7 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
|
|
|
111
141
|
mode: "array",
|
|
112
142
|
arr: buildBatchedRotatingMask(
|
|
113
143
|
this.#B, N, this.leftPad, this.maxSize, window,
|
|
114
|
-
this.#rows.ringIndex, this.#rows.totalOffset, this.#rows.rotated,
|
|
144
|
+
this.#rows.ringIndex, this.#rows.totalOffset, this.#rows.rotated, N > 1 || this.#rows.hasPendingPadding,
|
|
115
145
|
),
|
|
116
146
|
};
|
|
117
147
|
}
|
|
@@ -122,8 +152,7 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
|
|
|
122
152
|
override updateAndFetchQuantized(k: MlxArray, v: MlxArray): [ops.QuantizedTensor, ops.QuantizedTensor] {
|
|
123
153
|
const [B, H, S, D] = k.shape as [number, number, number, number];
|
|
124
154
|
const vD = v.shape[3]!;
|
|
125
|
-
if (S !== 1)
|
|
126
|
-
throw new Error("BatchedRotatingQuantCache supports N=1 decode updates only (solo-prefill then merge)");
|
|
155
|
+
if (S !== 1 || this.#rows.hasPendingPadding) return this.#updateConcat(k, v);
|
|
127
156
|
const prev = this.#rows.totalOffset;
|
|
128
157
|
|
|
129
158
|
// Grow the buffer (in STEP chunks) until it reaches maxSize.
|
|
@@ -202,6 +231,29 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
|
|
|
202
231
|
return [cut(this.keys!), cut(this.values!)];
|
|
203
232
|
}
|
|
204
233
|
|
|
234
|
+
#updateConcat(k: MlxArray, v: MlxArray): [ops.QuantizedTensor, ops.QuantizedTensor] {
|
|
235
|
+
const kq = ops.quantize(k, this.groupSize, this.bits);
|
|
236
|
+
const vq = ops.quantize(v, this.groupSize, this.bits);
|
|
237
|
+
let keys: ops.QuantizedTensor | undefined;
|
|
238
|
+
let values: ops.QuantizedTensor;
|
|
239
|
+
try {
|
|
240
|
+
keys = appendRotatingStorage(quantizedRowStorage, this.keys, kq, this.#rows);
|
|
241
|
+
values = appendRotatingStorage(quantizedRowStorage, this.values, vq, this.#rows);
|
|
242
|
+
} catch (error) { if (keys) disposeTriple(keys); throw error; }
|
|
243
|
+
finally { disposeTriple(kq); disposeTriple(vq); }
|
|
244
|
+
const history = this.keys ? this.#rows.activeLength : 0;
|
|
245
|
+
if (this.keys) disposeTriple(this.keys);
|
|
246
|
+
if (this.values) disposeTriple(this.values);
|
|
247
|
+
this.keys = keys; this.values = values;
|
|
248
|
+
this.#rows.commitConcat(k.shape[2]!, history);
|
|
249
|
+
this.offset = this.#rows.totalOffset;
|
|
250
|
+
this.ringIdx = this.#rows.ringIndex;
|
|
251
|
+
return [
|
|
252
|
+
quantizedRowStorage.slice(keys, 0, this.#B, 0, this.ringIdx),
|
|
253
|
+
quantizedRowStorage.slice(values, 0, this.#B, 0, this.ringIdx),
|
|
254
|
+
];
|
|
255
|
+
}
|
|
256
|
+
|
|
205
257
|
#assign2(dst: MlxArray, src: MlxArray, S: number): MlxArray {
|
|
206
258
|
const [B, H, , D] = dst.shape as [number, number, number, number];
|
|
207
259
|
const out = ops.sliceUpdate(dst, src, [0, 0, this.ringIdx, 0], [B, H, this.ringIdx + S, D]);
|
|
@@ -214,8 +266,12 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
|
|
|
214
266
|
override temporalView(): [ops.QuantizedTensor, ops.QuantizedTensor] {
|
|
215
267
|
if (!this.keys || !this.values) throw new Error("cache is empty");
|
|
216
268
|
return [
|
|
217
|
-
temporalStorageView(quantizedRowStorage, this.keys, this.#rows
|
|
218
|
-
|
|
269
|
+
temporalStorageView(quantizedRowStorage, this.keys, this.#rows, {
|
|
270
|
+
from: Math.max(0, this.#rows.activeLength - this.maxSize), to: this.#rows.activeLength,
|
|
271
|
+
}),
|
|
272
|
+
temporalStorageView(quantizedRowStorage, this.values, this.#rows, {
|
|
273
|
+
from: Math.max(0, this.#rows.activeLength - this.maxSize), to: this.#rows.activeLength,
|
|
274
|
+
}),
|
|
219
275
|
];
|
|
220
276
|
}
|
|
221
277
|
|
|
@@ -228,15 +284,15 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
|
|
|
228
284
|
* is byte-safe (packing along HEAD_DIM — file header); per-row byte
|
|
229
285
|
* identity vs the serial oracle is the class invariant
|
|
230
286
|
* (tests/batched-rotating-quant), extraction is a pure slice+copy. */
|
|
231
|
-
extractRow(i: number): RotatingQuantizedKVCache | null {
|
|
287
|
+
extractRow(i: number, limit = Infinity): RotatingQuantizedKVCache | null {
|
|
232
288
|
if (!this.keys || !this.values) return null;
|
|
233
|
-
const pad = Math.max(0, this.leftPad[i]
|
|
289
|
+
const pad = Math.max(0, this.leftPad[i]!, this.#rows.activeLength - limit);
|
|
234
290
|
const c = new RotatingQuantizedKVCache(this.maxSize, this.groupSize, this.bits);
|
|
235
291
|
const k = temporalStorageView(quantizedRowStorage, this.keys, this.#rows, {
|
|
236
|
-
row: i, from: pad, copy: true,
|
|
292
|
+
row: i, from: pad, to: this.#rows.activeLength, copy: true,
|
|
237
293
|
});
|
|
238
294
|
const v = temporalStorageView(quantizedRowStorage, this.values, this.#rows, {
|
|
239
|
-
row: i, from: pad, copy: true,
|
|
295
|
+
row: i, from: pad, to: this.#rows.activeLength, copy: true,
|
|
240
296
|
});
|
|
241
297
|
c.restoreState(k, v, this.offsetArr[i]!, k.packed.shape[2]!);
|
|
242
298
|
return c;
|
|
@@ -258,6 +314,11 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
|
|
|
258
314
|
|
|
259
315
|
filterRows(keep: readonly number[]): void { this.filter([...keep]); }
|
|
260
316
|
|
|
317
|
+
override trim(count: number): void {
|
|
318
|
+
this.#rows.trim(count); this.offset = this.#rows.totalOffset; this.ringIdx = this.#rows.ringIndex;
|
|
319
|
+
this.releaseRopeArr();
|
|
320
|
+
}
|
|
321
|
+
|
|
261
322
|
override isTrimmable(): boolean {
|
|
262
323
|
return false; // batched rows never re-enter the prompt cache directly
|
|
263
324
|
}
|
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
import type { PrefillPadding } from "./gemma4-base";
|
|
2
|
+
|
|
3
|
+
export interface RotatingPositionSnapshot {
|
|
4
|
+
readonly maxSize: number;
|
|
5
|
+
readonly offsets: readonly number[];
|
|
6
|
+
readonly leftPad: readonly number[];
|
|
7
|
+
readonly ringIndex: number;
|
|
8
|
+
readonly totalOffset: number;
|
|
9
|
+
readonly rotated: boolean;
|
|
10
|
+
readonly prefillEnds?: readonly number[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Acceptance changes row positions, independently of plain or encoded planes. */
|
|
14
|
+
export function rotatingRollbackPlan(snapshot: RotatingPositionSnapshot, before: readonly number[],
|
|
15
|
+
keep: number | readonly number[], preserveColumns = false): { trim: number } | { shifts: number[]; position: RotatingPositionSnapshot } {
|
|
16
|
+
const counts = before.map((_, row) => typeof keep === "number" ? keep : keep[row]!);
|
|
17
|
+
if (!preserveColumns && before.length === 1 && snapshot.totalOffset < snapshot.maxSize)
|
|
18
|
+
return { trim: snapshot.offsets[0]! - before[0]! - counts[0]! };
|
|
19
|
+
const offsets = before.map((offset, row) => offset + counts[row]!);
|
|
20
|
+
const shifts = snapshot.offsets.map((offset, row) => offset - offsets[row]!);
|
|
21
|
+
return { shifts, position: { ...snapshot, offsets,
|
|
22
|
+
leftPad: snapshot.leftPad.map((pad, row) => pad + shifts[row]!) } };
|
|
23
|
+
}
|
|
24
|
+
|
|
1
25
|
/** Host-side row and ring bookkeeping shared by plain and quantized storage. */
|
|
2
26
|
export class BatchedRotatingState {
|
|
3
27
|
readonly maxSize: number;
|
|
@@ -6,6 +30,7 @@ export class BatchedRotatingState {
|
|
|
6
30
|
ringIndex = 0;
|
|
7
31
|
totalOffset = 0;
|
|
8
32
|
rotated = false;
|
|
33
|
+
#prefillEnds?: number[];
|
|
9
34
|
|
|
10
35
|
constructor(maxSize: number, leftPad: readonly number[], offsets?: readonly number[]) {
|
|
11
36
|
this.maxSize = maxSize;
|
|
@@ -13,8 +38,45 @@ export class BatchedRotatingState {
|
|
|
13
38
|
this.offsets = offsets ? [...offsets] : leftPad.map((pad) => -pad);
|
|
14
39
|
}
|
|
15
40
|
|
|
41
|
+
snapshot(): RotatingPositionSnapshot {
|
|
42
|
+
return { maxSize: this.maxSize, offsets: [...this.offsets], leftPad: [...this.leftPad],
|
|
43
|
+
ringIndex: this.ringIndex, totalOffset: this.totalOffset, rotated: this.rotated,
|
|
44
|
+
...(this.#prefillEnds ? { prefillEnds: [...this.#prefillEnds] } : {}) };
|
|
45
|
+
}
|
|
46
|
+
restore(snapshot: RotatingPositionSnapshot): void {
|
|
47
|
+
this.offsets = [...snapshot.offsets]; this.leftPad = [...snapshot.leftPad];
|
|
48
|
+
this.ringIndex = snapshot.ringIndex; this.totalOffset = snapshot.totalOffset; this.rotated = snapshot.rotated;
|
|
49
|
+
this.#prefillEnds = snapshot.prefillEnds ? [...snapshot.prefillEnds] : undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
preparePrefill(padding: PrefillPadding, rightPaddedBatch = padding.rightPadding?.some(pad => pad > 0)): void {
|
|
53
|
+
if (!this.batchSize) { this.leftPad = padding.lengths.map(() => 0); this.offsets = [...this.leftPad]; }
|
|
54
|
+
if (padding.leftPadding) {
|
|
55
|
+
this.leftPad = this.leftPad.map((pad, row) => pad + padding.leftPadding![row]!);
|
|
56
|
+
this.offsets = this.offsets.map((offset, row) => offset - padding.leftPadding![row]!);
|
|
57
|
+
}
|
|
58
|
+
this.#prefillEnds = rightPaddedBatch
|
|
59
|
+
? padding.lengths.map((length, row) => length + this.offsets[row]!) : undefined;
|
|
60
|
+
}
|
|
61
|
+
get hasPendingPadding(): boolean { return this.#prefillEnds !== undefined; }
|
|
62
|
+
validOffset(row: number): number {
|
|
63
|
+
return Math.min(this.offsets[row]!, this.#prefillEnds?.[row] ?? this.offsets[row]!);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
prefillRoll(): number[] | undefined {
|
|
67
|
+
return this.#prefillEnds?.map((end, row) => Math.max(0, this.offsets[row]! - end));
|
|
68
|
+
}
|
|
69
|
+
#rollPadding(): void {
|
|
70
|
+
const shifts = this.prefillRoll();
|
|
71
|
+
if (!shifts) return;
|
|
72
|
+
this.leftPad = this.leftPad.map((pad, row) => pad + shifts[row]!);
|
|
73
|
+
this.offsets = this.offsets.map((offset, row) => offset - shifts[row]!);
|
|
74
|
+
}
|
|
75
|
+
finalizePrefill(): void { this.#rollPadding(); this.#prefillEnds = undefined; }
|
|
76
|
+
|
|
16
77
|
get batchSize(): number { return this.leftPad.length; }
|
|
17
78
|
get validLength(): number { return Math.min(this.totalOffset, this.maxSize); }
|
|
79
|
+
get activeLength(): number { return this.rotated ? this.maxSize : this.ringIndex; }
|
|
18
80
|
get trimmable(): boolean { return this.totalOffset < this.maxSize; }
|
|
19
81
|
|
|
20
82
|
markGrown(previousOffset: number): void {
|
|
@@ -43,9 +105,22 @@ export class BatchedRotatingState {
|
|
|
43
105
|
this.ringIndex += tokens;
|
|
44
106
|
}
|
|
45
107
|
|
|
108
|
+
/** mlx-lm BatchRotatingKVCache._update_concat: the block keeps up to
|
|
109
|
+
* maxSize - 1 historical columns so each query has a complete window. */
|
|
110
|
+
commitConcat(tokens: number, historicalLength: number): void {
|
|
111
|
+
if (historicalLength) this.#rollPadding();
|
|
112
|
+
const trim = Math.max(0, historicalLength - this.maxSize + 1);
|
|
113
|
+
this.leftPad = this.leftPad.map(pad => pad - trim);
|
|
114
|
+
this.totalOffset += tokens;
|
|
115
|
+
this.offsets = this.offsets.map(offset => offset + tokens);
|
|
116
|
+
this.ringIndex = historicalLength - trim + tokens;
|
|
117
|
+
this.rotated = false;
|
|
118
|
+
}
|
|
119
|
+
|
|
46
120
|
filter(keep: readonly number[]): void {
|
|
47
121
|
this.offsets = keep.map((row) => this.offsets[row]!);
|
|
48
122
|
this.leftPad = keep.map((row) => this.leftPad[row]!);
|
|
123
|
+
if (this.#prefillEnds) this.#prefillEnds = keep.map(row => this.#prefillEnds![row]!);
|
|
49
124
|
}
|
|
50
125
|
|
|
51
126
|
trim(tokens: number): number {
|
|
@@ -61,6 +136,7 @@ export class BatchedRotatingState {
|
|
|
61
136
|
this.ringIndex = width;
|
|
62
137
|
this.offsets = [...offsets];
|
|
63
138
|
this.rotated = false;
|
|
139
|
+
this.#prefillEnds = undefined;
|
|
64
140
|
}
|
|
65
141
|
|
|
66
142
|
/** Physical ranges that expose a ring buffer in temporal order. */
|
|
@@ -6,12 +6,10 @@
|
|
|
6
6
|
// column is the same for every row), with PER-ROW `offset` (absolute position,
|
|
7
7
|
// drives RoPE) and `leftPad` (padding columns in the buffer, masked out).
|
|
8
8
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// (
|
|
13
|
-
// solo-prefills each request (single-stream RotatingKVCache) then merges, so
|
|
14
|
-
// this cache only ever sees N=1 updates. Positions (offset/leftPad/_idx/_offset/
|
|
9
|
+
// Includes merge, one-token writes, block prefill, padding finalization, masks
|
|
10
|
+
// and row extraction. Prefill preparation is a storage capability; the serving
|
|
11
|
+
// scheduler's use of whole prompt cohorts is a separate concern.
|
|
12
|
+
// Positions (offset/leftPad/_idx/_offset/
|
|
15
13
|
// rotated) are tracked on the HOST (small deterministic ints) so make_mask is
|
|
16
14
|
// built in JS like buildBatchedDecodeMask; only K/V live on device.
|
|
17
15
|
//
|
|
@@ -20,16 +18,18 @@
|
|
|
20
18
|
// temporal coordinates then ROLLED to the ring's physical layout — see
|
|
21
19
|
// buildBatchedRotatingMask (a column-by-column port of mlx-lm make_mask,
|
|
22
20
|
// including the `roll(shift=idx+1)`). Gated model-free against mlx-lm
|
|
23
|
-
// (tests/batched-rotating.test.ts) + end-to-end vs a long-context Gemma oracle.
|
|
21
|
+
// (tests/unit/batched-rotating.test.ts) + end-to-end vs a long-context Gemma oracle.
|
|
24
22
|
|
|
25
23
|
import { MlxArray } from "../mlx/array";
|
|
26
24
|
import * as ops from "../mlx/ops";
|
|
27
|
-
import { RotatingKVCache, type Cache, type Mask } from "./gemma4-base";
|
|
28
|
-
import { BatchedRotatingState } from "./batched-rotating-state";
|
|
25
|
+
import { RotatingKVCache, type Cache, type Mask, type PaddedPrefillCache, type PrefillPadding } from "./gemma4-base";
|
|
26
|
+
import { BatchedRotatingState, type RotatingPositionSnapshot } from "./batched-rotating-state";
|
|
29
27
|
import {
|
|
28
|
+
appendRotatingStorage,
|
|
30
29
|
mergeStorageRows,
|
|
31
30
|
plainRowStorage,
|
|
32
31
|
temporalStorageView,
|
|
32
|
+
rowRollIndices,
|
|
33
33
|
} from "./batched-row-storage";
|
|
34
34
|
|
|
35
35
|
const STEP = 256;
|
|
@@ -45,14 +45,14 @@ const STEP = 256;
|
|
|
45
45
|
* update's decrement is the persistent one, applied separately by the cache). */
|
|
46
46
|
export function buildBatchedRotatingMask(
|
|
47
47
|
B: number, N: number, leftPad: number[],
|
|
48
|
-
maxSize: number, window: number, idx: number, offsetScalar: number, rotated: boolean,
|
|
48
|
+
maxSize: number, window: number, idx: number, offsetScalar: number, rotated: boolean, block = N > 1,
|
|
49
49
|
): MlxArray {
|
|
50
50
|
const off = Math.min(maxSize - 1, offsetScalar);
|
|
51
51
|
const S = off + N;
|
|
52
52
|
|
|
53
53
|
// Local (non-persisted) leftPad for the mask: trim + rotation shrink it.
|
|
54
|
-
const trimSize = idx - maxSize + (
|
|
55
|
-
const isRot =
|
|
54
|
+
const trimSize = idx - maxSize + (block ? 1 : 0);
|
|
55
|
+
const isRot = !block && (rotated || idx >= maxSize);
|
|
56
56
|
const lp = leftPad.map((x) => x - (trimSize > 0 ? trimSize : 0) - (isRot ? 1 : 0));
|
|
57
57
|
|
|
58
58
|
// roll(shift): physical column = (temporal column + shift) mod S.
|
|
@@ -80,8 +80,9 @@ export function buildBatchedRotatingMask(
|
|
|
80
80
|
return mask;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
/**
|
|
84
|
-
|
|
83
|
+
/** Port of mlx-lm BatchRotatingKVCache; padded singleton chunks use its
|
|
84
|
+
* concat operation with the matching block mask until finalization. */
|
|
85
|
+
export class BatchedRotatingCache implements Cache, PaddedPrefillCache {
|
|
85
86
|
keys: MlxArray | null = null;
|
|
86
87
|
values: MlxArray | null = null;
|
|
87
88
|
readonly #rows: BatchedRotatingState;
|
|
@@ -103,6 +104,29 @@ export class BatchedRotatingCache implements Cache {
|
|
|
103
104
|
* misroutes a batched cache into a serial-only path. */
|
|
104
105
|
signature(): string { return "kv:rotating-plain"; }
|
|
105
106
|
|
|
107
|
+
get positionSnapshot(): RotatingPositionSnapshot { return this.#rows.snapshot(); }
|
|
108
|
+
validOffset(row: number): number { return this.#rows.validOffset(row); }
|
|
109
|
+
preparePrefill(padding: PrefillPadding, rightPaddedBatch?: boolean): void { this.#rows.preparePrefill(padding, rightPaddedBatch); this.releaseRopeArr(); }
|
|
110
|
+
finalizePrefill(): void {
|
|
111
|
+
const shifts = this.#rows.prefillRoll();
|
|
112
|
+
if (shifts?.some(shift => shift !== 0) && this.keys && this.values) {
|
|
113
|
+
using indices = rowRollIndices(this.keys.shape[2]!, shifts);
|
|
114
|
+
const keys = plainRowStorage.rollRows(this.keys, indices);
|
|
115
|
+
let values: MlxArray;
|
|
116
|
+
try { values = plainRowStorage.rollRows(this.values, indices); }
|
|
117
|
+
catch (error) { keys.dispose(); throw error; }
|
|
118
|
+
this.keys.dispose(); this.values.dispose(); this.keys = keys; this.values = values;
|
|
119
|
+
}
|
|
120
|
+
this.#rows.finalizePrefill(); this.releaseRopeArr();
|
|
121
|
+
}
|
|
122
|
+
/** Adopt owned tensors without changing their physical ring columns. */
|
|
123
|
+
static adoptPhysical(keys: MlxArray | null, values: MlxArray | null, position: RotatingPositionSnapshot): BatchedRotatingCache {
|
|
124
|
+
const cache = new BatchedRotatingCache(position.maxSize, [...position.leftPad]);
|
|
125
|
+
cache.#rows.restore(position); cache.keys = keys; cache.values = values;
|
|
126
|
+
|
|
127
|
+
return cache;
|
|
128
|
+
}
|
|
129
|
+
|
|
106
130
|
get offsetArr(): number[] { return this.#rows.offsets; }
|
|
107
131
|
get leftPad(): number[] { return this.#rows.leftPad; }
|
|
108
132
|
get batchSize(): number { return this.#rows.batchSize; }
|
|
@@ -133,7 +157,7 @@ export class BatchedRotatingCache implements Cache {
|
|
|
133
157
|
mode: "array",
|
|
134
158
|
arr: buildBatchedRotatingMask(
|
|
135
159
|
this.#B, N, this.leftPad, this.maxSize, window,
|
|
136
|
-
this.#rows.ringIndex, this.offset, this.#rows.rotated,
|
|
160
|
+
this.#rows.ringIndex, this.offset, this.#rows.rotated, N > 1 || this.#rows.hasPendingPadding,
|
|
137
161
|
),
|
|
138
162
|
};
|
|
139
163
|
}
|
|
@@ -142,8 +166,7 @@ export class BatchedRotatingCache implements Cache {
|
|
|
142
166
|
updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
|
|
143
167
|
const [B, H, S, D] = k.shape as [number, number, number, number];
|
|
144
168
|
const vD = v.shape[3]!;
|
|
145
|
-
if (S !== 1)
|
|
146
|
-
throw new Error("BatchedRotatingCache supports N=1 decode updates only (solo-prefill then merge)");
|
|
169
|
+
if (S !== 1 || this.#rows.hasPendingPadding) return this.#updateConcat(k, v);
|
|
147
170
|
const prev = this.offset;
|
|
148
171
|
|
|
149
172
|
// Grow the buffer (in STEP chunks) until it reaches maxSize.
|
|
@@ -202,6 +225,21 @@ export class BatchedRotatingCache implements Cache {
|
|
|
202
225
|
];
|
|
203
226
|
}
|
|
204
227
|
|
|
228
|
+
#updateConcat(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
|
|
229
|
+
const keys = appendRotatingStorage(plainRowStorage, this.keys, k, this.#rows);
|
|
230
|
+
let values: MlxArray;
|
|
231
|
+
try { values = appendRotatingStorage(plainRowStorage, this.values, v, this.#rows); }
|
|
232
|
+
catch (error) { keys.dispose(); throw error; }
|
|
233
|
+
const history = this.keys ? this.#rows.activeLength : 0;
|
|
234
|
+
this.keys?.dispose(); this.values?.dispose();
|
|
235
|
+
this.keys = keys; this.values = values;
|
|
236
|
+
this.#rows.commitConcat(k.shape[2]!, history);
|
|
237
|
+
return [
|
|
238
|
+
plainRowStorage.slice(keys, 0, this.#B, 0, this.#rows.ringIndex),
|
|
239
|
+
plainRowStorage.slice(values, 0, this.#B, 0, this.#rows.ringIndex),
|
|
240
|
+
];
|
|
241
|
+
}
|
|
242
|
+
|
|
205
243
|
/** Free the per-step RoPE array without disposing KV (wrapper-rebuild path). */
|
|
206
244
|
releaseRopeArr(): void {
|
|
207
245
|
this.#ropeArr?.dispose();
|
|
@@ -213,8 +251,12 @@ export class BatchedRotatingCache implements Cache {
|
|
|
213
251
|
temporalView(): [MlxArray, MlxArray] {
|
|
214
252
|
if (!this.keys || !this.values) throw new Error("cache is empty");
|
|
215
253
|
return [
|
|
216
|
-
temporalStorageView(plainRowStorage, this.keys, this.#rows
|
|
217
|
-
|
|
254
|
+
temporalStorageView(plainRowStorage, this.keys, this.#rows, {
|
|
255
|
+
from: Math.max(0, this.#rows.activeLength - this.maxSize), to: this.#rows.activeLength,
|
|
256
|
+
}),
|
|
257
|
+
temporalStorageView(plainRowStorage, this.values, this.#rows, {
|
|
258
|
+
from: Math.max(0, this.#rows.activeLength - this.maxSize), to: this.#rows.activeLength,
|
|
259
|
+
}),
|
|
218
260
|
];
|
|
219
261
|
}
|
|
220
262
|
|
|
@@ -228,15 +270,15 @@ export class BatchedRotatingCache implements Cache {
|
|
|
228
270
|
* oracle's `cache._idx = cache.keys.shape[2]`. Bit-exact vs a solo run:
|
|
229
271
|
* merge/decode/filter keep each row's ring bytes identical to the serial
|
|
230
272
|
* cache's (tests/batched-rotating) and this is a pure slice+copy. */
|
|
231
|
-
extractRow(i: number): RotatingKVCache | null {
|
|
273
|
+
extractRow(i: number, limit?: number): RotatingKVCache | null {
|
|
232
274
|
if (!this.keys || !this.values) return null;
|
|
233
|
-
const pad = Math.max(0, this.leftPad[i]
|
|
275
|
+
const pad = Math.max(0, this.leftPad[i]!, limit === undefined ? 0 : this.#rows.activeLength - limit);
|
|
234
276
|
const c = new RotatingKVCache(this.maxSize);
|
|
235
277
|
const k = temporalStorageView(plainRowStorage, this.keys, this.#rows, {
|
|
236
|
-
row: i, from: pad, copy: true,
|
|
278
|
+
row: i, from: pad, to: this.#rows.activeLength, copy: true,
|
|
237
279
|
});
|
|
238
280
|
const v = temporalStorageView(plainRowStorage, this.values, this.#rows, {
|
|
239
|
-
row: i, from: pad, copy: true,
|
|
281
|
+
row: i, from: pad, to: this.#rows.activeLength, copy: true,
|
|
240
282
|
});
|
|
241
283
|
c.restoreState(k, v, this.offsetArr[i]!, k.shape[2]!);
|
|
242
284
|
return c;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { MlxArray } from "../mlx/array";
|
|
2
2
|
import * as ops from "../mlx/ops";
|
|
3
3
|
import { BatchedRotatingState } from "./batched-rotating-state";
|
|
4
|
+
import { Dtype } from "../mlx/ffi";
|
|
4
5
|
|
|
5
6
|
export interface RowStorage<T> {
|
|
6
7
|
shape(value: T): readonly [number, number, number, number];
|
|
@@ -10,6 +11,7 @@ export interface RowStorage<T> {
|
|
|
10
11
|
padLeft(value: T, tokens: number): T;
|
|
11
12
|
takeRows(value: T, keep: readonly number[]): T;
|
|
12
13
|
copy(value: T): T;
|
|
14
|
+
rollRows(value: T, indices: MlxArray): T;
|
|
13
15
|
dispose(value: T): void;
|
|
14
16
|
}
|
|
15
17
|
|
|
@@ -36,6 +38,7 @@ export const plainRowStorage: RowStorage<MlxArray> = {
|
|
|
36
38
|
return out;
|
|
37
39
|
},
|
|
38
40
|
copy: (value) => ops.copyOf(value),
|
|
41
|
+
rollRows: (value, indices) => ops.takeAlongAxis(value, indices, 2),
|
|
39
42
|
dispose: (value) => value.dispose(),
|
|
40
43
|
};
|
|
41
44
|
|
|
@@ -77,6 +80,14 @@ export const quantizedRowStorage: RowStorage<ops.QuantizedTensor> = {
|
|
|
77
80
|
biases: ops.copyOf(value.biases),
|
|
78
81
|
};
|
|
79
82
|
},
|
|
83
|
+
rollRows(value, indices) {
|
|
84
|
+
const packed = ops.takeAlongAxis(value.packed, indices, 2);
|
|
85
|
+
let scales: MlxArray | undefined;
|
|
86
|
+
try {
|
|
87
|
+
scales = ops.takeAlongAxis(value.scales, indices, 2);
|
|
88
|
+
return { packed, scales, biases: ops.takeAlongAxis(value.biases, indices, 2) };
|
|
89
|
+
} catch (error) { packed.dispose(); scales?.dispose(); throw error; }
|
|
90
|
+
},
|
|
80
91
|
dispose(value) {
|
|
81
92
|
value.packed.dispose();
|
|
82
93
|
value.scales.dispose();
|
|
@@ -84,6 +95,19 @@ export const quantizedRowStorage: RowStorage<ops.QuantizedTensor> = {
|
|
|
84
95
|
},
|
|
85
96
|
};
|
|
86
97
|
|
|
98
|
+
/** Per-row cyclic shift, shared by plain and encoded storage planes.
|
|
99
|
+
* This is mlx-lm's dynamic_roll indexing; only positions cross the FFI. */
|
|
100
|
+
export function rowRollIndices(length: number, shifts: readonly number[]): MlxArray {
|
|
101
|
+
using columns = ops.arange(0, length, 1, Dtype.int32);
|
|
102
|
+
using positions = ops.reshape(columns, [1, 1, length, 1]);
|
|
103
|
+
using starts = ops.fromInt32(shifts.map(shift => ((-shift % length) + length) % length), [shifts.length, 1, 1, 1]);
|
|
104
|
+
using raw = ops.add(positions, starts);
|
|
105
|
+
using width = ops.fromInt32([length], []);
|
|
106
|
+
using wrapped = ops.sub(raw, width);
|
|
107
|
+
using over = ops.greaterEqual(raw, width);
|
|
108
|
+
return ops.where(over, wrapped, raw);
|
|
109
|
+
}
|
|
110
|
+
|
|
87
111
|
export function mergeStorageRows<T>(
|
|
88
112
|
storage: RowStorage<T>,
|
|
89
113
|
rows: readonly T[],
|
|
@@ -125,3 +149,31 @@ export function temporalStorageView<T>(
|
|
|
125
149
|
storage.dispose(cut);
|
|
126
150
|
return owned;
|
|
127
151
|
}
|
|
152
|
+
|
|
153
|
+
/** Borrow storage, return an owned chronological block without decoding the
|
|
154
|
+
* representation. Port of mlx-lm's BatchRotatingKVCache._update_concat
|
|
155
|
+
* (MIT): discard spare capacity, retain the full window for the first query. */
|
|
156
|
+
export function appendRotatingStorage<T>(
|
|
157
|
+
storage: RowStorage<T>, previous: T | null, incoming: T, state: BatchedRotatingState,
|
|
158
|
+
): T {
|
|
159
|
+
if (previous === null) return storage.slice(incoming, 0, storage.shape(incoming)[0], 0, storage.shape(incoming)[2]);
|
|
160
|
+
const active = state.activeLength;
|
|
161
|
+
const shifts = state.prefillRoll();
|
|
162
|
+
if (shifts?.some(shift => shift !== 0)) {
|
|
163
|
+
const chronological = temporalStorageView(storage, previous, state, { to: active });
|
|
164
|
+
let rolled: T | undefined, history: T | undefined;
|
|
165
|
+
try {
|
|
166
|
+
using indices = rowRollIndices(active, shifts);
|
|
167
|
+
rolled = storage.rollRows(chronological, indices);
|
|
168
|
+
history = storage.slice(rolled, 0, state.batchSize, Math.max(0, active - state.maxSize + 1), active);
|
|
169
|
+
return storage.concatTokens([history, incoming]);
|
|
170
|
+
} finally {
|
|
171
|
+
storage.dispose(chronological); if (rolled) storage.dispose(rolled); if (history) storage.dispose(history);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const history = temporalStorageView(storage, previous, state, {
|
|
175
|
+
from: Math.max(0, active - state.maxSize + 1), to: active,
|
|
176
|
+
});
|
|
177
|
+
try { return storage.concatTokens([history, incoming]); }
|
|
178
|
+
finally { storage.dispose(history); }
|
|
179
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { SsmPrefillPadding } from "./ssm-prefill-padding";
|
|
2
|
+
import type { PaddedPrefillCache, PrefillPadding } from "./gemma4-base";
|
|
3
|
+
import * as ops from "../mlx/ops";
|
|
4
|
+
import type { MlxArray } from "../mlx/array";
|
|
5
|
+
import type { BatchableCache, Cache } from "./gemma4-base";
|
|
6
|
+
import { SSMCache } from "./qwen3-delta";
|
|
7
|
+
|
|
8
|
+
/** Recurrent storage behind the same row-layout port as attention KV.
|
|
9
|
+
* Inputs to merge are borrowed; request membership is owned by the caller. */
|
|
10
|
+
export class BatchedSSMCache extends SSMCache implements BatchableCache, PaddedPrefillCache {
|
|
11
|
+
constructor() { super(); this.offsets = []; }
|
|
12
|
+
preparePrefill(padding: PrefillPadding): void {
|
|
13
|
+
if (!this.offsets!.length) this.offsets = padding.lengths.map(() => 0);
|
|
14
|
+
const padded = padding.leftPadding?.some(n => n > 0) || padding.rightPadding?.some(n => n > 0);
|
|
15
|
+
this.prefillPadding = padded ? new SsmPrefillPadding(padding) : null;
|
|
16
|
+
}
|
|
17
|
+
finalizePrefill(): void { this.prefillPadding = null; }
|
|
18
|
+
get rowOffsets(): readonly number[] { return this.offsets!; }
|
|
19
|
+
get leftPad(): readonly number[] { return this.rowOffsets.map(() => 0); }
|
|
20
|
+
makeEmptyBatch(): BatchedSSMCache { return new BatchedSSMCache(); }
|
|
21
|
+
projectedBytes(_tokens: number): number {
|
|
22
|
+
return this.batchSize ? this.state().reduce((bytes, array) => bytes + array.nbytes, 0) / this.batchSize : 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
mergeRows(rows: readonly Cache[]): void {
|
|
26
|
+
if (!rows.length) { this.dispose(); return; }
|
|
27
|
+
const sources = rows as readonly SSMCache[];
|
|
28
|
+
const offsets = sources.flatMap(source => source.offsets ?? [source.offset]);
|
|
29
|
+
const merge = (field: "conv" | "recurrent"): MlxArray | null => {
|
|
30
|
+
const prototype = sources.find(source => source[field])?.[field];
|
|
31
|
+
if (!prototype) return null;
|
|
32
|
+
if (sources.length === 1) return ops.copyOf(prototype);
|
|
33
|
+
const empty: MlxArray[] = [];
|
|
34
|
+
try {
|
|
35
|
+
return ops.concatAxis(sources.map(source => {
|
|
36
|
+
if (source[field]) return source[field]!;
|
|
37
|
+
const zeros = ops.zeros([source.offsets?.length ?? 1, ...prototype.shape.slice(1)], prototype.dtype);
|
|
38
|
+
empty.push(zeros); return zeros;
|
|
39
|
+
}), 0);
|
|
40
|
+
} finally { for (const array of empty) array.dispose(); }
|
|
41
|
+
};
|
|
42
|
+
const conv = merge("conv");
|
|
43
|
+
let recurrent;
|
|
44
|
+
try { recurrent = merge("recurrent"); }
|
|
45
|
+
catch (error) { conv?.dispose(); throw error; }
|
|
46
|
+
this.dispose();
|
|
47
|
+
this.conv = conv; this.recurrent = recurrent;
|
|
48
|
+
this.offsets = offsets; this.offset = Math.max(0, ...offsets);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
override dispose(): void { super.dispose(); this.offset = 0; this.offsets = []; }
|
|
52
|
+
}
|