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
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// Qwen3.5 checkpoint-generation normalization — the load-seam port of
|
|
2
|
+
// mlx_lm.models.qwen3_5's `Model.sanitize` + `TextModel.sanitize`.
|
|
3
|
+
//
|
|
4
|
+
// Two checkpoint generations ship the same graph under different conventions:
|
|
5
|
+
//
|
|
6
|
+
// pre-5.8 (mlx-lm-converted) 5.8-family (transformers 5.8.0 export)
|
|
7
|
+
// ────────────────────────── ───────────────────────────────────────
|
|
8
|
+
// language_model.model.* model.language_model.*
|
|
9
|
+
// language_model.lm_head.* lm_head.* (top level)
|
|
10
|
+
// vision_tower.* model.visual.*
|
|
11
|
+
// (no mtp tensors in-repo) mtp.* (in-repo)
|
|
12
|
+
// conv1d.weight [C, K, 1] conv1d.weight [C, 1, K] (HF layout)
|
|
13
|
+
// RMSNorm gains stored as γ RMSNorm gains stored as γ−1
|
|
14
|
+
//
|
|
15
|
+
// Our graph speaks the FIRST column (`PREFIX = "language_model"` in
|
|
16
|
+
// qwen3_5.ts) — which is exactly mlx-lm's post-sanitize name space — so this
|
|
17
|
+
// module maps the artifact onto it instead of teaching every consumer two
|
|
18
|
+
// namings. The rules below are a line-for-line copy of the oracle; see
|
|
19
|
+
// mlx_lm/models/qwen3_5.py `Model.sanitize` (naming/drop) and
|
|
20
|
+
// `TextModel.sanitize` (γ shift, conv layout).
|
|
21
|
+
//
|
|
22
|
+
// The γ shift is the dangerous one: a spurious +1.0 silently corrupts an old
|
|
23
|
+
// artifact. The oracle's discriminator is NOT the naming — it is
|
|
24
|
+
// should_shift = any("mtp." in k) or any conv1d.weight with shape[-1] != 1
|
|
25
|
+
// evaluated over the whole (post-rename, pre-drop) weight map. Reproduced
|
|
26
|
+
// verbatim. Measured on the two artifacts in play (2026-09-01, M1 Max):
|
|
27
|
+
// • ~/.cache/…/models--mjriii--Qwen3.8-27B/snapshots/staged — no mtp.*,
|
|
28
|
+
// conv1d [10240,4,1] → no shift; layer-0 input_layernorm ≡ 1.0.
|
|
29
|
+
// • /Volumes/MLX-Models/models/mjriii/Qwen3.8-27B-{tqalloc-norot,rtn4-g64}
|
|
30
|
+
// — 31 mtp.* tensors, conv1d [10240,1,4] → shift; layer-0
|
|
31
|
+
// input_layernorm mean −0.0334 (= γ−1), byte-identical to the raw
|
|
32
|
+
// Qwen/Qwen3.8-27B 5.8 checkpoint (the quantizer copies norms verbatim).
|
|
33
|
+
|
|
34
|
+
import type { MlxArray } from "../mlx/array";
|
|
35
|
+
import * as ops from "../mlx/ops";
|
|
36
|
+
import type { Weights, WeightsView } from "../weights";
|
|
37
|
+
|
|
38
|
+
/** Header-level facts about one stored tensor (no bytes are read). */
|
|
39
|
+
export interface CheckpointTensor {
|
|
40
|
+
readonly name: string;
|
|
41
|
+
readonly shape: readonly number[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface Qwen35Sanitized {
|
|
45
|
+
/** canonical (graph) name → the name the artifact actually stores. */
|
|
46
|
+
readonly names: Map<string, string>;
|
|
47
|
+
/** Canonical names stored as γ−1: a +1.0 is due at load. */
|
|
48
|
+
readonly normShift: Set<string>;
|
|
49
|
+
/** Canonical names in HF conv layout: a moveaxis(2, 1) is due at load. */
|
|
50
|
+
readonly convMoveaxis: Set<string>;
|
|
51
|
+
/** True when the artifact already IS our canonical layout — no view needed
|
|
52
|
+
* and the pre-existing load path runs untouched. */
|
|
53
|
+
readonly identity: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** mlx-lm `TextModel.sanitize` norm_keys, verbatim and in order. */
|
|
57
|
+
const NORM_KEYS = [
|
|
58
|
+
".input_layernorm.weight",
|
|
59
|
+
".post_attention_layernorm.weight",
|
|
60
|
+
"model.norm.weight",
|
|
61
|
+
".q_norm.weight",
|
|
62
|
+
".k_norm.weight",
|
|
63
|
+
] as const;
|
|
64
|
+
|
|
65
|
+
/** mlx-lm `Model.sanitize`: drop vision, fold every naming onto
|
|
66
|
+
* `language_model.…`. Returns null for a dropped tensor. */
|
|
67
|
+
function canonicalName(key: string): string | null {
|
|
68
|
+
if (key.startsWith("vision_tower") || key.startsWith("model.visual")) return null;
|
|
69
|
+
if (key.startsWith("model.language_model"))
|
|
70
|
+
return "language_model.model" + key.slice("model.language_model".length);
|
|
71
|
+
if (key.startsWith("language_model.")) return key;
|
|
72
|
+
return "language_model." + key;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Port of mlx_lm.models.qwen3_5 `Model.sanitize` ∘ `TextModel.sanitize`,
|
|
77
|
+
* name/shape half. Pure: takes header facts, returns the load plan.
|
|
78
|
+
*/
|
|
79
|
+
export function sanitizeQwen35Checkpoint(
|
|
80
|
+
tensors: Iterable<CheckpointTensor>,
|
|
81
|
+
): Qwen35Sanitized {
|
|
82
|
+
// Pass 1 — rename (vision dropped), exactly as Model.sanitize.
|
|
83
|
+
const renamed: { canonical: string; source: string; shape: readonly number[] }[] = [];
|
|
84
|
+
let sawForeignName = false;
|
|
85
|
+
for (const t of tensors) {
|
|
86
|
+
const canonical = canonicalName(t.name);
|
|
87
|
+
if (canonical === null) {
|
|
88
|
+
sawForeignName ||= t.name.startsWith("model.visual");
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (canonical !== t.name) sawForeignName = true;
|
|
92
|
+
renamed.push({ canonical, source: t.name, shape: t.shape });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Pass 2 — TextModel.sanitize's two flags, over the renamed map BEFORE the
|
|
96
|
+
// mtp drop (the oracle computes them first, then filters).
|
|
97
|
+
const hasMtpWeights = renamed.some((t) => t.canonical.includes("mtp."));
|
|
98
|
+
const hasUnsanitizedConv1d = renamed.some(
|
|
99
|
+
(t) => t.canonical.includes("conv1d.weight") && t.shape[t.shape.length - 1] !== 1,
|
|
100
|
+
);
|
|
101
|
+
const shouldShiftNormWeights = hasMtpWeights || hasUnsanitizedConv1d;
|
|
102
|
+
|
|
103
|
+
const names = new Map<string, string>();
|
|
104
|
+
const normShift = new Set<string>();
|
|
105
|
+
const convMoveaxis = new Set<string>();
|
|
106
|
+
for (const t of renamed) {
|
|
107
|
+
if (t.canonical.includes("mtp.")) continue; // oracle drops these outright
|
|
108
|
+
names.set(t.canonical, t.source);
|
|
109
|
+
if (t.canonical.includes("conv1d.weight") && t.shape[t.shape.length - 1] !== 1)
|
|
110
|
+
convMoveaxis.add(t.canonical);
|
|
111
|
+
if (
|
|
112
|
+
shouldShiftNormWeights &&
|
|
113
|
+
NORM_KEYS.some((sfx) => t.canonical.endsWith(sfx)) &&
|
|
114
|
+
t.shape.length === 1
|
|
115
|
+
)
|
|
116
|
+
normShift.add(t.canonical);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
names,
|
|
121
|
+
normShift,
|
|
122
|
+
convMoveaxis,
|
|
123
|
+
identity: !sawForeignName && !hasMtpWeights && !shouldShiftNormWeights &&
|
|
124
|
+
convMoveaxis.size === 0,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The {@link WeightsView} for a qwen3.5 artifact, or null when the artifact is
|
|
130
|
+
* already canonical (then nothing is installed and the load path is bit-for-bit
|
|
131
|
+
* what it was before this seam existed).
|
|
132
|
+
*/
|
|
133
|
+
export function qwen35WeightsView(weights: Weights): WeightsView | null {
|
|
134
|
+
const plan = sanitizeQwen35Checkpoint(
|
|
135
|
+
weights.tensorNames.map((name) => ({ name, shape: weights.info(name).shape })),
|
|
136
|
+
);
|
|
137
|
+
if (plan.identity) return null;
|
|
138
|
+
return {
|
|
139
|
+
names: plan.names,
|
|
140
|
+
fixup(canonical: string, arr: MlxArray): MlxArray | null {
|
|
141
|
+
// Oracle order: moveaxis first, then the γ shift (disjoint sets — a
|
|
142
|
+
// conv1d weight is 3-D and never a norm gain).
|
|
143
|
+
if (plan.convMoveaxis.has(canonical)) {
|
|
144
|
+
// `v.moveaxis(2, 1)` on [C, 1, K] → [C, K, 1]. Materialized: the
|
|
145
|
+
// conv1d kernel consumes it every forward, and the transposed view
|
|
146
|
+
// would otherwise pin the source shard buffer.
|
|
147
|
+
const moved = ops.transposeAxes(arr, [0, 2, 1]);
|
|
148
|
+
const out = ops.contiguous(moved);
|
|
149
|
+
moved.dispose();
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
if (plan.normShift.has(canonical)) {
|
|
153
|
+
// `v + 1.0` — a weak python scalar, so the add happens at the gain's
|
|
154
|
+
// own dtype (bf16 here); scalarLike builds the constant host-side at
|
|
155
|
+
// that dtype for the same single-kernel add the reference dispatches.
|
|
156
|
+
const one = ops.scalarLike(1, arr);
|
|
157
|
+
const out = ops.add(arr, one);
|
|
158
|
+
one.dispose();
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
package/src/model/qwen3_5.ts
CHANGED
|
@@ -10,18 +10,20 @@
|
|
|
10
10
|
// shared maybeQuantizeKv path).
|
|
11
11
|
|
|
12
12
|
import type { ModelConfig } from "../config";
|
|
13
|
+
import { gatedDeltaState } from "./qwen3-delta-state";
|
|
13
14
|
import type { Weights } from "../weights";
|
|
14
15
|
import { MlxArray } from "../mlx/array";
|
|
15
|
-
import { Dtype } from "../mlx/ffi";
|
|
16
|
+
import { Dtype, deviceArchitecture } from "../mlx/ffi";
|
|
16
17
|
import * as ops from "../mlx/ops";
|
|
17
18
|
import { CompiledFunction } from "../mlx/compile";
|
|
19
|
+
import { qwenAppendChunkSize } from "./qwen-append";
|
|
20
|
+
import { TrellisLinear, fusedGateUpEligible, fusedGateUpSwiglu, TRELLIS_MATVEC_MAX_M } from "./trellis-linear";
|
|
18
21
|
import {
|
|
19
22
|
argmaxLastPosition,
|
|
20
23
|
disposeTriple,
|
|
21
24
|
disposing,
|
|
22
25
|
KVCache,
|
|
23
26
|
LoraState,
|
|
24
|
-
QuantizedKVCache,
|
|
25
27
|
QuantizedEmbedding,
|
|
26
28
|
QuantizedLinear,
|
|
27
29
|
quantizedSdpa,
|
|
@@ -29,7 +31,10 @@ import {
|
|
|
29
31
|
type Cache,
|
|
30
32
|
type Mask,
|
|
31
33
|
} from "./gemma4-base";
|
|
34
|
+
import { qwen35WeightsView } from "./qwen3_5-checkpoint";
|
|
35
|
+
import { materializeCopy } from "../mlx/materialize";
|
|
32
36
|
import { gatedDeltaUpdate, SSMCache } from "./qwen3-delta";
|
|
37
|
+
import type { QwenConvolution } from "./qwen-conv";
|
|
33
38
|
import {
|
|
34
39
|
activeMrope, applyInterleavedRope, buildMropePositions, mropeInvFreq,
|
|
35
40
|
setActiveMrope, type MropeRequestState,
|
|
@@ -107,6 +112,10 @@ export function compiledSilu(x: MlxArray): MlxArray {
|
|
|
107
112
|
|
|
108
113
|
/** Gated-DeltaNet linear-attention layer (mlx-lm GatedDeltaNet). */
|
|
109
114
|
export class GatedDeltaNet {
|
|
115
|
+
/** Per-model experiment seam; null retains the oracle graph. The same
|
|
116
|
+
* implementation advances speculative rollback prefixes. Borrowed inputs,
|
|
117
|
+
* owned activation and independent state tail; no global runtime mutation. */
|
|
118
|
+
convolution: QwenConvolution | null = null;
|
|
110
119
|
readonly inProjQkv: QuantizedLinear;
|
|
111
120
|
readonly inProjZ: QuantizedLinear;
|
|
112
121
|
readonly inProjB: QuantizedLinear;
|
|
@@ -146,7 +155,40 @@ export class GatedDeltaNet {
|
|
|
146
155
|
this.normWeight = weights.tensor(`${prefix}.norm.weight`);
|
|
147
156
|
}
|
|
148
157
|
|
|
149
|
-
|
|
158
|
+
#convolve(qkv: MlxArray, state: MlxArray, rowLengths?: readonly number[]): [MlxArray, MlxArray] {
|
|
159
|
+
if (this.convolution) {
|
|
160
|
+
const result = this.convolution(qkv, state, this.convWeight);
|
|
161
|
+
if (!rowLengths) return result;
|
|
162
|
+
using input = ops.concatAxis([state, qkv], 1);
|
|
163
|
+
const tail = this.#convTail(input, qkv.shape[1]!, rowLengths);
|
|
164
|
+
result[1].dispose();
|
|
165
|
+
return [result[0], tail];
|
|
166
|
+
}
|
|
167
|
+
const [, S, D] = qkv.shape as [number, number, number];
|
|
168
|
+
const input = ops.concatAxis([state, qkv], 1);
|
|
169
|
+
// MLX copy and contiguous can both alias the whole prefill buffer.
|
|
170
|
+
// Materialize only the tail so cache residency follows its logical size.
|
|
171
|
+
const tail = this.#convTail(input, S, rowLengths);
|
|
172
|
+
const conv = ops.conv1d(input, this.convWeight, 1, 0, 1, D);
|
|
173
|
+
input.dispose();
|
|
174
|
+
const out = compiledSilu(conv);
|
|
175
|
+
conv.dispose();
|
|
176
|
+
return [out, tail];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
#convTail(input: MlxArray, processed: number, rowLengths?: readonly number[]): MlxArray {
|
|
180
|
+
const [B, , D] = input.shape as [number, number, number];
|
|
181
|
+
const nKeep = this.convKernel - 1;
|
|
182
|
+
if (rowLengths) {
|
|
183
|
+
using indices = ops.fromInt32(rowLengths.flatMap(length =>
|
|
184
|
+
Array.from({ length: nKeep }, (_, position) => length + position)), [B, nKeep, 1]);
|
|
185
|
+
return ops.takeAlongAxis(input, indices, 1);
|
|
186
|
+
}
|
|
187
|
+
using view = input.slice([0, processed, 0], [B, processed + nKeep, D]);
|
|
188
|
+
return materializeCopy(view);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
forward(x: MlxArray, cache: SSMCache, independentRows = false, mask?: MlxArray | null): MlxArray {
|
|
150
192
|
const [B, S] = x.shape as [number, number, number];
|
|
151
193
|
const convDim = this.keyDim * 2 + this.valueDim;
|
|
152
194
|
const nKeep = this.convKernel - 1;
|
|
@@ -168,38 +210,34 @@ export class GatedDeltaNet {
|
|
|
168
210
|
}
|
|
169
211
|
|
|
170
212
|
const t0 = prof ? performance.now() : 0;
|
|
171
|
-
|
|
172
|
-
let z = this.inProjZ.forward(x);
|
|
213
|
+
let qkv = this.inProjQkv.forward(x, independentRows); // [B,S,convDim]
|
|
214
|
+
let z = this.inProjZ.forward(x, independentRows);
|
|
173
215
|
z = disposing(z, ops.reshape(z, [B, S, this.numVHeads, this.headVDim]));
|
|
174
|
-
const b = this.inProjB.forward(x); // [B,S,numVHeads]
|
|
175
|
-
const a = this.inProjA.forward(x);
|
|
216
|
+
const b = this.inProjB.forward(x, independentRows); // [B,S,numVHeads]
|
|
217
|
+
const a = this.inProjA.forward(x, independentRows);
|
|
176
218
|
if (prof) { ops.evalAll([qkv, z, b, a]); prof.proj = (prof.proj ?? 0) + performance.now() - t0; }
|
|
177
219
|
const tc = prof ? performance.now() : 0; // [B,S,numVHeads]
|
|
178
220
|
|
|
179
|
-
//
|
|
221
|
+
// The model shares one padding mask across recurrent layers. Direct layer
|
|
222
|
+
// callers can request the same cache-owned mask without a model wrapper.
|
|
223
|
+
using localMask = mask === undefined ? cache.prefillPadding?.makeMask(S) ?? null : null;
|
|
224
|
+
const ssmMask = mask ?? localMask;
|
|
225
|
+
if (ssmMask) {
|
|
226
|
+
using expanded = ops.reshape(ssmMask, [B, S, 1]);
|
|
227
|
+
using zero = MlxArray.fromBytesCopy(new Uint8Array(qkv.dtype === Dtype.float32 ? 4 : 2), [], qkv.dtype);
|
|
228
|
+
qkv = disposing(qkv, ops.where(expanded, qkv, zero));
|
|
229
|
+
}
|
|
230
|
+
// Convolution keeps each row's last real-token tail, including empty rows.
|
|
180
231
|
const convState =
|
|
181
232
|
cache.conv ?? ops.zeros([B, nKeep, convDim], x.dtype);
|
|
182
|
-
const
|
|
233
|
+
const [convOut, newConv] = this.#convolve(qkv, convState, cache.prefillPadding?.convolutionLengths(S));
|
|
183
234
|
if (!cache.conv) convState.dispose();
|
|
184
235
|
if (spec) spec.qkv = qkv;
|
|
185
236
|
else qkv.dispose();
|
|
186
|
-
// New conv state = last nKeep rows. MUST be a TRUE copy (mx.copy):
|
|
187
|
-
// this tail slice is already row-contiguous at B=1, so ops.contiguous
|
|
188
|
-
// is a no-op VIEW sharing convInput's buffer — caching it pinned the
|
|
189
|
-
// whole [B, S+nKeep, convDim] chunk buffer (~42 MB/layer/chunk, 48
|
|
190
|
-
// layers = the measured 2.0 GB-per-2048-chunk prefill leak; 46 GB
|
|
191
|
-
// active at 32k, async-GPU-OOM on 24 GB — M4 2026-08-20).
|
|
192
|
-
const newConvView = convInput.slice([0, S, 0], [B, S + nKeep, convDim]);
|
|
193
|
-
const newConv = ops.copyOf(newConvView);
|
|
194
|
-
newConvView.dispose();
|
|
195
237
|
if (spec) spec.prevConv = cache.conv;
|
|
196
238
|
else cache.conv?.dispose();
|
|
197
239
|
cache.conv = newConv;
|
|
198
240
|
|
|
199
|
-
const conv = ops.conv1d(convInput, this.convWeight, 1, 0, 1, convDim);
|
|
200
|
-
convInput.dispose();
|
|
201
|
-
const convOut = compiledSilu(conv); // nn.silu (mx.compile), matches mlx-lm
|
|
202
|
-
conv.dispose();
|
|
203
241
|
if (prof) { ops.evalAll([convOut]); prof.conv = (prof.conv ?? 0) + performance.now() - tc; }
|
|
204
242
|
const tn = prof ? performance.now() : 0;
|
|
205
243
|
|
|
@@ -223,7 +261,7 @@ export class GatedDeltaNet {
|
|
|
223
261
|
const tk = prof ? performance.now() : 0;
|
|
224
262
|
|
|
225
263
|
const [out, newState] = gatedDeltaUpdate(
|
|
226
|
-
q, k, v, a, b, this.aLog, this.dtBias, cache.recurrent,
|
|
264
|
+
q, k, v, a, b, this.aLog, this.dtBias, cache.recurrent, ssmMask,
|
|
227
265
|
);
|
|
228
266
|
if (prof) { ops.evalAll([out]); prof.kernel = (prof.kernel ?? 0) + performance.now() - tk; }
|
|
229
267
|
const to = prof ? performance.now() : 0;
|
|
@@ -248,7 +286,7 @@ export class GatedDeltaNet {
|
|
|
248
286
|
z.dispose();
|
|
249
287
|
const merged = ops.reshape(gated, [B, S, this.valueDim]);
|
|
250
288
|
gated.dispose();
|
|
251
|
-
const result = this.outProj.forward(merged);
|
|
289
|
+
const result = this.outProj.forward(merged, independentRows);
|
|
252
290
|
merged.dispose();
|
|
253
291
|
if (prof) { ops.evalAll([result]); prof.out = (prof.out ?? 0) + performance.now() - to; }
|
|
254
292
|
return result;
|
|
@@ -262,66 +300,69 @@ export class GatedDeltaNet {
|
|
|
262
300
|
* recurrent state is BIT-EXACTLY what a forward over only the accepted
|
|
263
301
|
* prefix would have produced. Output projections (z / out_proj) are state-
|
|
264
302
|
* free and skipped: only the states matter here. */
|
|
265
|
-
#replaySpecPrefix(cache: SSMCache, keep: number): void {
|
|
303
|
+
#replaySpecPrefix(cache: SSMCache, keep: number | readonly number[]): void {
|
|
266
304
|
const r = cache.specRound;
|
|
267
|
-
if (!r || !r.qkv || !r.a || !r.b)
|
|
305
|
+
if (!r || !r.qkv || !r.a || !r.b)
|
|
306
|
+
throw new Error("GatedDeltaNet replay without recorded round inputs");
|
|
268
307
|
const B = r.qkv.shape[0]!;
|
|
308
|
+
const count = typeof keep === "number" ? keep : Math.max(...keep);
|
|
309
|
+
const rowLengths = typeof keep === "number" ? undefined : keep;
|
|
269
310
|
const convDim = this.keyDim * 2 + this.valueDim;
|
|
270
311
|
const nKeep = this.convKernel - 1;
|
|
271
312
|
|
|
272
|
-
const qkvPfxView = r.qkv.slice([0, 0, 0], [B,
|
|
313
|
+
const qkvPfxView = r.qkv.slice([0, 0, 0], [B, count, convDim]);
|
|
273
314
|
const qkvPfx = ops.contiguous(qkvPfxView);
|
|
274
315
|
qkvPfxView.dispose();
|
|
275
316
|
const convState = cache.conv ?? ops.zeros([B, nKeep, convDim], qkvPfx.dtype);
|
|
276
|
-
const
|
|
317
|
+
const [convOut, newConv] = this.#convolve(qkvPfx, convState, rowLengths);
|
|
277
318
|
if (!cache.conv) convState.dispose();
|
|
278
319
|
qkvPfx.dispose();
|
|
279
|
-
// TRUE copy, same reason as forward(): the contiguous tail slice would
|
|
280
|
-
// otherwise be a view pinning the whole replay convInput buffer.
|
|
281
|
-
const rConvView = convInput.slice([0, keep, 0], [B, keep + nKeep, convDim]);
|
|
282
|
-
const newConv = ops.copyOf(rConvView);
|
|
283
|
-
rConvView.dispose();
|
|
284
320
|
cache.conv?.dispose();
|
|
285
321
|
cache.conv = newConv;
|
|
286
322
|
|
|
287
|
-
const conv = ops.conv1d(convInput, this.convWeight, 1, 0, 1, convDim);
|
|
288
|
-
convInput.dispose();
|
|
289
|
-
const convOut = compiledSilu(conv);
|
|
290
|
-
conv.dispose();
|
|
291
323
|
const [qFlat, kFlat, vFlat] = ops.split(
|
|
292
324
|
convOut, [this.keyDim, 2 * this.keyDim], -1,
|
|
293
325
|
) as [MlxArray, MlxArray, MlxArray];
|
|
294
326
|
convOut.dispose();
|
|
295
|
-
let q = ops.reshape(qFlat, [B,
|
|
327
|
+
let q = rowLengths ? null : ops.reshape(qFlat, [B, count, this.numKHeads, this.headKDim]);
|
|
296
328
|
qFlat.dispose();
|
|
297
|
-
let k = ops.reshape(kFlat, [B,
|
|
329
|
+
let k = ops.reshape(kFlat, [B, count, this.numKHeads, this.headKDim]);
|
|
298
330
|
kFlat.dispose();
|
|
299
|
-
const v = disposing(vFlat, ops.reshape(vFlat, [B,
|
|
331
|
+
const v = disposing(vFlat, ops.reshape(vFlat, [B, count, this.numVHeads, this.headVDim]));
|
|
300
332
|
const invScale = Math.pow(this.headKDim, -0.5);
|
|
301
|
-
|
|
302
|
-
|
|
333
|
+
if (q) {
|
|
334
|
+
q = disposing(q, ops.rmsNorm(q, null, 1e-6));
|
|
335
|
+
q = disposing(q, ops.mulScalar(q, invScale * invScale));
|
|
336
|
+
}
|
|
303
337
|
k = disposing(k, ops.rmsNorm(k, null, 1e-6));
|
|
304
338
|
k = disposing(k, ops.mulScalar(k, invScale));
|
|
305
339
|
|
|
306
|
-
const aPfxView = r.a.slice([0, 0, 0], [B,
|
|
340
|
+
const aPfxView = r.a.slice([0, 0, 0], [B, count, this.numVHeads]);
|
|
307
341
|
const aPfx = ops.contiguous(aPfxView);
|
|
308
342
|
aPfxView.dispose();
|
|
309
|
-
const bPfxView = r.b.slice([0, 0, 0], [B,
|
|
343
|
+
const bPfxView = r.b.slice([0, 0, 0], [B, count, this.numVHeads]);
|
|
310
344
|
const bPfx = ops.contiguous(bPfxView);
|
|
311
345
|
bPfxView.dispose();
|
|
312
346
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
347
|
+
let newState: MlxArray;
|
|
348
|
+
if (rowLengths) {
|
|
349
|
+
// Unequal prefixes need a row-length-aware recurrence. The state-only
|
|
350
|
+
// kernel omits query/output work. Uniform replay retains its measured
|
|
351
|
+
// kernel until the separate optimization demonstrates a serving win.
|
|
352
|
+
newState = gatedDeltaState(k, v, aPfx, bPfx, this.aLog, this.dtBias, cache.recurrent, rowLengths);
|
|
353
|
+
} else {
|
|
354
|
+
const [output, state] = gatedDeltaUpdate(q!, k, v, aPfx, bPfx, this.aLog, this.dtBias, cache.recurrent);
|
|
355
|
+
output.dispose(); newState = state;
|
|
356
|
+
}
|
|
357
|
+
q?.dispose();
|
|
317
358
|
k.dispose();
|
|
318
359
|
v.dispose();
|
|
319
360
|
aPfx.dispose();
|
|
320
361
|
bPfx.dispose();
|
|
321
|
-
y.dispose(); // only the state advance matters on the rollback path
|
|
322
362
|
cache.recurrent?.dispose();
|
|
323
363
|
cache.recurrent = newState;
|
|
324
|
-
cache.advance(keep);
|
|
364
|
+
if (typeof keep === "number") cache.advance(keep);
|
|
365
|
+
else cache.advanceRows(keep);
|
|
325
366
|
}
|
|
326
367
|
|
|
327
368
|
private rmsNormGated(hidden: MlxArray, gate: MlxArray): MlxArray {
|
|
@@ -365,18 +406,18 @@ export class Qwen3Attention {
|
|
|
365
406
|
this.kNorm = new RMSNorm(weights.tensor(`${prefix}.k_norm.weight`), t.rmsNormEps);
|
|
366
407
|
}
|
|
367
408
|
|
|
368
|
-
forward(x: MlxArray, mask: Mask, cache: Cache): MlxArray {
|
|
409
|
+
forward(x: MlxArray, mask: Mask, cache: Cache, independentRows = false): MlxArray {
|
|
369
410
|
const [B, L] = x.shape as [number, number, number];
|
|
370
411
|
|
|
371
412
|
// q_proj emits 2× head_dim per head → split into queries + gate.
|
|
372
|
-
const qp = this.qProj.forward(x);
|
|
413
|
+
const qp = this.qProj.forward(x, independentRows);
|
|
373
414
|
const qpr = disposing(qp, ops.reshape(qp, [B, L, this.nHeads, this.headDim * 2]));
|
|
374
415
|
const [qHeads, gateHeads] = ops.split(qpr, [this.headDim], -1) as [MlxArray, MlxArray];
|
|
375
416
|
qpr.dispose();
|
|
376
417
|
const gate = disposing(gateHeads, ops.reshape(gateHeads, [B, L, this.nHeads * this.headDim]));
|
|
377
418
|
|
|
378
|
-
let k = this.kProj.forward(x);
|
|
379
|
-
let v = this.vProj.forward(x);
|
|
419
|
+
let k = this.kProj.forward(x, independentRows);
|
|
420
|
+
let v = this.vProj.forward(x, independentRows);
|
|
380
421
|
|
|
381
422
|
// q/k norm over head_dim BEFORE transpose (reference order).
|
|
382
423
|
let q = this.qNorm.forward(qHeads);
|
|
@@ -391,7 +432,7 @@ export class Qwen3Attention {
|
|
|
391
432
|
// Batched decode: the scheduler's mask wrapper exposes each row's REAL
|
|
392
433
|
// position as ropeOffsetArr (rows have different prompt lengths); the
|
|
393
434
|
// dynamic-offset kernel is the same fast::rope, bit-exact vs the static
|
|
394
|
-
// form (tests/compile.test.ts). Serial lane: scalar offset, unchanged.
|
|
435
|
+
// form (tests/unit/compile.test.ts). Serial lane: scalar offset, unchanged.
|
|
395
436
|
// Vision requests (serial lane only) install activeMrope for the current
|
|
396
437
|
// forward: 3D interleaved positions via the manual apply — the reference
|
|
397
438
|
// never uses the fused fast-rope kernel when position_ids are supplied,
|
|
@@ -411,11 +452,16 @@ export class Qwen3Attention {
|
|
|
411
452
|
}
|
|
412
453
|
|
|
413
454
|
let attn: MlxArray;
|
|
414
|
-
|
|
415
|
-
|
|
455
|
+
const quantized = cache.quantizedAttention;
|
|
456
|
+
if (cache.attentionState) {
|
|
457
|
+
const view = cache.attentionState.appendAndFetch(k, v);
|
|
458
|
+
k.dispose(); v.dispose();
|
|
459
|
+
try { attn = view.attend(q, this.scale, mask); } finally { view.dispose(); }
|
|
460
|
+
} else if (quantized) {
|
|
461
|
+
const [keys, values] = quantized.updateAndFetchQuantized(k, v);
|
|
416
462
|
k.dispose();
|
|
417
463
|
v.dispose();
|
|
418
|
-
attn = quantizedSdpa(q, keys, values, this.scale, mask,
|
|
464
|
+
attn = quantizedSdpa(q, keys, values, this.scale, mask, quantized.groupSize, quantized.bits);
|
|
419
465
|
disposeTriple(keys);
|
|
420
466
|
disposeTriple(values);
|
|
421
467
|
} else {
|
|
@@ -439,32 +485,56 @@ export class Qwen3Attention {
|
|
|
439
485
|
const gated = ops.mul(merged, sig);
|
|
440
486
|
merged.dispose();
|
|
441
487
|
sig.dispose();
|
|
442
|
-
const out = this.oProj.forward(gated);
|
|
488
|
+
const out = this.oProj.forward(gated, independentRows);
|
|
443
489
|
gated.dispose();
|
|
444
490
|
return out;
|
|
445
491
|
}
|
|
446
492
|
}
|
|
447
493
|
|
|
494
|
+
/** MLP projections are affine-quantized (QuantizedLinear) or, in a Q2b
|
|
495
|
+
* packed-trellis artifact, TrellisLinear — chosen per module by the config's
|
|
496
|
+
* quantization entry (`mode: "trellis"`). */
|
|
497
|
+
export type MlpLinear = QuantizedLinear | TrellisLinear;
|
|
498
|
+
function loadMlpLinear(weights: Weights, path: string, config: ModelConfig): MlpLinear {
|
|
499
|
+
if (!TrellisLinear.isTrellis(config, path)) return QuantizedLinear.load(weights, path, config);
|
|
500
|
+
// M4 Pro measurements support the 27B down projection at M3/4. The
|
|
501
|
+
// operation also checks variant, dtype and packed-code layout eligibility.
|
|
502
|
+
const useSharedScatterCodebook = config.text.hiddenSize === 5120 &&
|
|
503
|
+
config.text.intermediateSize === 17408 && path.endsWith(".down_proj");
|
|
504
|
+
return TrellisLinear.load(weights, path, config, useSharedScatterCodebook);
|
|
505
|
+
}
|
|
506
|
+
|
|
448
507
|
export class Qwen3MLP {
|
|
449
|
-
readonly gate:
|
|
450
|
-
readonly up:
|
|
451
|
-
readonly down:
|
|
508
|
+
readonly gate: MlpLinear;
|
|
509
|
+
readonly up: MlpLinear;
|
|
510
|
+
readonly down: MlpLinear;
|
|
452
511
|
|
|
453
512
|
constructor(weights: Weights, config: ModelConfig, prefix: string) {
|
|
454
|
-
this.gate =
|
|
455
|
-
this.up =
|
|
456
|
-
this.down =
|
|
513
|
+
this.gate = loadMlpLinear(weights, `${prefix}.gate_proj`, config);
|
|
514
|
+
this.up = loadMlpLinear(weights, `${prefix}.up_proj`, config);
|
|
515
|
+
this.down = loadMlpLinear(weights, `${prefix}.down_proj`, config);
|
|
457
516
|
}
|
|
458
517
|
|
|
459
|
-
forward(x: MlxArray): MlxArray {
|
|
460
|
-
|
|
461
|
-
|
|
518
|
+
forward(x: MlxArray, inputRowContiguous = false, independentRows = false): MlxArray {
|
|
519
|
+
// Packed-trellis decode (M ≤ 4): gate, up and the swiglu in ONE kernel —
|
|
520
|
+
// x read once, no gate/up vectors materialized. This Lab path retains
|
|
521
|
+
// the packed activation arithmetic documented in turboquant.md.
|
|
522
|
+
if (this.gate instanceof TrellisLinear && this.up instanceof TrellisLinear &&
|
|
523
|
+
fusedGateUpEligible(this.gate, this.up) &&
|
|
524
|
+
x.shape.slice(0, -1).reduce((a, b) => a * b, 1) <= TRELLIS_MATVEC_MAX_M) {
|
|
525
|
+
const hidden = fusedGateUpSwiglu(x, this.gate, this.up);
|
|
526
|
+
const out = this.down instanceof QuantizedLinear ? this.down.forward(hidden, independentRows) : this.down.forward(hidden);
|
|
527
|
+
hidden.dispose();
|
|
528
|
+
return out;
|
|
529
|
+
}
|
|
530
|
+
const g = this.gate instanceof TrellisLinear ? this.gate.forward(x, inputRowContiguous) : this.gate.forward(x, independentRows);
|
|
531
|
+
const u = this.up instanceof TrellisLinear ? this.up.forward(x, inputRowContiguous) : this.up.forward(x, independentRows);
|
|
462
532
|
// Oracle: down_proj(swiglu(gate_proj(x), up_proj(x))) — swiglu ALWAYS compiled
|
|
463
533
|
// (mlx-lm's @mx.compile swiglu; no unfused silu+mul, matches its kernel set).
|
|
464
534
|
const hidden = compiledSwiglu(g, u);
|
|
465
535
|
g.dispose();
|
|
466
536
|
u.dispose();
|
|
467
|
-
const out = this.down.forward(hidden);
|
|
537
|
+
const out = this.down instanceof QuantizedLinear ? this.down.forward(hidden, independentRows) : this.down.forward(hidden);
|
|
468
538
|
hidden.dispose();
|
|
469
539
|
return out;
|
|
470
540
|
}
|
|
@@ -489,16 +559,18 @@ export class Qwen3Layer {
|
|
|
489
559
|
this.postAttnNorm = new RMSNorm(weights.tensor(`${prefix}.post_attention_layernorm.weight`), config.text.rmsNormEps);
|
|
490
560
|
}
|
|
491
561
|
|
|
492
|
-
forward(x: MlxArray, faMask: Mask, cache: Cache): MlxArray {
|
|
562
|
+
forward(x: MlxArray, faMask: Mask, cache: Cache, independentRows = false, ssmMask?: MlxArray | null): MlxArray {
|
|
493
563
|
const xn = this.inputNorm.forward(x);
|
|
494
564
|
const r = this.isLinear
|
|
495
|
-
? this.linearAttn!.forward(xn, cache as SSMCache)
|
|
496
|
-
: this.selfAttn!.forward(xn, faMask, cache);
|
|
565
|
+
? this.linearAttn!.forward(xn, cache as SSMCache, independentRows, ssmMask)
|
|
566
|
+
: this.selfAttn!.forward(xn, faMask, cache, independentRows);
|
|
497
567
|
xn.dispose();
|
|
498
568
|
const h = ops.add(x, r);
|
|
499
569
|
r.dispose();
|
|
500
570
|
const hn = this.postAttnNorm.forward(h);
|
|
501
|
-
|
|
571
|
+
// RMSNorm allocates an aligned row-contiguous output. Pass that layout
|
|
572
|
+
// proof so Trellis can match native small-prefill matmul without an eval.
|
|
573
|
+
const m = this.mlp.forward(hn, true, independentRows);
|
|
502
574
|
hn.dispose();
|
|
503
575
|
const out = ops.add(h, m);
|
|
504
576
|
h.dispose();
|
|
@@ -522,6 +594,14 @@ export class Qwen35Model {
|
|
|
522
594
|
readonly faIdx: number;
|
|
523
595
|
|
|
524
596
|
constructor(weights: Weights, config: ModelConfig) {
|
|
597
|
+
// Checkpoint-generation normalization BEFORE the first tensor() call: the
|
|
598
|
+
// 5.8-family export names the trunk `model.language_model.*` with a
|
|
599
|
+
// top-level `lm_head`, ships `mtp.*` in-repo, and stores RMSNorm gains as
|
|
600
|
+
// γ−1 with HF-layout conv1d. The view maps all of that onto the names and
|
|
601
|
+
// values this graph reads (mlx-lm's post-sanitize space). No-op — nothing
|
|
602
|
+
// installed at all — for artifacts already in that space.
|
|
603
|
+
const view = qwen35WeightsView(weights);
|
|
604
|
+
if (view) weights.setView(view);
|
|
525
605
|
this.config = config;
|
|
526
606
|
this.tied = config.text.tieWordEmbeddings;
|
|
527
607
|
this.weightsBytes = [...weights.shards.files.values()]
|
|
@@ -555,9 +635,10 @@ export class Qwen35Model {
|
|
|
555
635
|
out.set(`${p}.linear_attn.in_proj_a`, l.linearAttn.inProjA);
|
|
556
636
|
out.set(`${p}.linear_attn.out_proj`, l.linearAttn.outProj);
|
|
557
637
|
}
|
|
558
|
-
|
|
559
|
-
out.set(`${p}.mlp.
|
|
560
|
-
out.set(`${p}.mlp.
|
|
638
|
+
// Trellis-coded MLP tensors are not LoRA targets (no adapter seam yet).
|
|
639
|
+
if (l.mlp.gate instanceof QuantizedLinear) out.set(`${p}.mlp.gate_proj`, l.mlp.gate);
|
|
640
|
+
if (l.mlp.up instanceof QuantizedLinear) out.set(`${p}.mlp.up_proj`, l.mlp.up);
|
|
641
|
+
if (l.mlp.down instanceof QuantizedLinear) out.set(`${p}.mlp.down_proj`, l.mlp.down);
|
|
561
642
|
}
|
|
562
643
|
return out;
|
|
563
644
|
}
|
|
@@ -566,6 +647,38 @@ export class Qwen35Model {
|
|
|
566
647
|
return this.layers.map((l) => (l.isLinear ? new SSMCache() : new KVCache()));
|
|
567
648
|
}
|
|
568
649
|
|
|
650
|
+
/** The 27B append path keeps the native M1 affine arithmetic while Trellis
|
|
651
|
+
* shares up to four rows. Other shapes retain single-token execution. */
|
|
652
|
+
createAppend(policy: { hasAdapters: boolean; pagedKv: boolean }) {
|
|
653
|
+
const t = this.config.text;
|
|
654
|
+
if (policy.hasAdapters || policy.pagedKv || this.loraState.active.length || this.mrope ||
|
|
655
|
+
t.hiddenSize !== 5120 || t.intermediateSize !== 17408 || this.layers.length !== 64 ||
|
|
656
|
+
t.headDim !== 256 || t.numAttentionHeads !== 24 || t.numKeyValueHeads !== 4)
|
|
657
|
+
return null;
|
|
658
|
+
if (deviceArchitecture() !== "applegpu_g16s") return null;
|
|
659
|
+
const qualified = (linear: QuantizedLinear | TrellisLinear) => {
|
|
660
|
+
if (linear instanceof TrellisLinear) return true;
|
|
661
|
+
const { mode, bits, groupSize } = linear.spec;
|
|
662
|
+
return mode === "affine" && [2, 3, 4, 6, 8].includes(bits) && [32, 64, 128].includes(groupSize);
|
|
663
|
+
};
|
|
664
|
+
for (const layer of this.layers) {
|
|
665
|
+
const a = layer.linearAttn, s = layer.selfAttn;
|
|
666
|
+
const attention = a ? [a.inProjQkv, a.inProjZ, a.inProjB, a.inProjA, a.outProj]
|
|
667
|
+
: [s!.qProj, s!.kProj, s!.vProj, s!.oProj];
|
|
668
|
+
if (!attention.every(qualified) || ![layer.mlp.gate, layer.mlp.up, layer.mlp.down].every(qualified))
|
|
669
|
+
return null;
|
|
670
|
+
}
|
|
671
|
+
return {
|
|
672
|
+
maxChunkSize: (state: readonly Cache[]) => qwenAppendChunkSize(state[0]!.offset),
|
|
673
|
+
forwardHidden: (ids: MlxArray, cache: Cache[]): MlxArray => {
|
|
674
|
+
if (ids.shape.length !== 2 || ids.shape[0] !== 1 || ids.shape[1]! > 4)
|
|
675
|
+
throw new Error("Qwen committed-token append supports one row and at most four positions");
|
|
676
|
+
const hidden = this.embed.encode(ids);
|
|
677
|
+
return this.forwardLayers(hidden, cache, true);
|
|
678
|
+
},
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
|
|
569
682
|
forwardHidden(ids: MlxArray, cache: Cache[]): MlxArray {
|
|
570
683
|
const h = this.embed.encode(ids);
|
|
571
684
|
return this.forwardLayers(h, cache);
|
|
@@ -609,11 +722,12 @@ export class Qwen35Model {
|
|
|
609
722
|
tap.captured.set(i, copy);
|
|
610
723
|
}
|
|
611
724
|
|
|
612
|
-
protected forwardLayers(h0: MlxArray, cache: Cache[]): MlxArray {
|
|
725
|
+
protected forwardLayers(h0: MlxArray, cache: Cache[], independentRows = false): MlxArray {
|
|
613
726
|
const L = h0.shape[1]!;
|
|
614
727
|
// One full-attention mask shared by all full layers (same offset); linear
|
|
615
728
|
// layers see no ssm mask at B=1.
|
|
616
729
|
const faMask = cache[this.faIdx]!.makeMask(L, null);
|
|
730
|
+
using ssmMask = (cache[0] as SSMCache).prefillPadding?.makeMask(L) ?? null;
|
|
617
731
|
// Vision requests: one interleaved-mRoPE cos/sin table per forward,
|
|
618
732
|
// shared by all 12 full-attention layers (positions are layer-invariant).
|
|
619
733
|
let mropeFwd: ReturnType<typeof buildMropePositions> | null = null;
|
|
@@ -634,9 +748,19 @@ export class Qwen35Model {
|
|
|
634
748
|
try {
|
|
635
749
|
for (let i = 0; i < this.layers.length; i++) {
|
|
636
750
|
const tl = prof ? performance.now() : 0;
|
|
637
|
-
const next = this.layers[i]!.forward(h, faMask, cache[i]
|
|
751
|
+
const next = this.layers[i]!.forward(h, faMask, cache[i]!, independentRows, ssmMask);
|
|
638
752
|
h.dispose();
|
|
639
753
|
h = next;
|
|
754
|
+
if (L > TRELLIS_MATVEC_MAX_M) {
|
|
755
|
+
// Affine prefills also need bounded evaluation: a retained prefix
|
|
756
|
+
// history can leave too little room for the entire deferred graph.
|
|
757
|
+
// Materialize the cache outputs too. In particular, the tiny
|
|
758
|
+
// copied conv tail is not a dependency of `h`; leaving it lazy
|
|
759
|
+
// pins the whole prefill conv buffer until the chunk ends.
|
|
760
|
+
const state = cache[i]!.state();
|
|
761
|
+
try { ops.evalAll([h, ...state]); }
|
|
762
|
+
finally { if (cache[i]!.stateNeedsDispose) for (const a of state) a.dispose(); }
|
|
763
|
+
}
|
|
640
764
|
if (prof) {
|
|
641
765
|
ops.evalAll([h]);
|
|
642
766
|
const key = this.layers[i]!.isLinear ? "layerLin" : "layerFull";
|