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
package/src/eval/kl.ts
CHANGED
|
@@ -22,14 +22,15 @@
|
|
|
22
22
|
|
|
23
23
|
import { existsSync } from "node:fs";
|
|
24
24
|
import { MlxArray } from "../mlx/array";
|
|
25
|
-
import { Dtype } from "../mlx/ffi";
|
|
25
|
+
import { clearCache, Dtype } from "../mlx/ffi";
|
|
26
26
|
import * as ops from "../mlx/ops";
|
|
27
27
|
import { Registry } from "../registry";
|
|
28
28
|
import { Weights } from "../weights";
|
|
29
29
|
import { loadModelConfig, type ModelConfig, type TurboQuantScheme } from "../config";
|
|
30
30
|
import { createModel, type RuntimeModel } from "../model/factory";
|
|
31
31
|
import { loadTokenizer, type LoadedTokenizer } from "../tokenizer";
|
|
32
|
-
import { maybeQuantizeKv } from "../generate";
|
|
32
|
+
import { evalCacheState, maybeQuantizeKv } from "../generate";
|
|
33
|
+
import { ReferenceLogitsDump, type ReferenceRecords } from "./reference-logits";
|
|
33
34
|
import {
|
|
34
35
|
configureRuntime,
|
|
35
36
|
runtimeKey,
|
|
@@ -310,7 +311,7 @@ export async function evaluateKlServingDecode(opts: {
|
|
|
310
311
|
/** KV-scheme A/B on the teacher-forced serving-decode path: bf16 baseline
|
|
311
312
|
* (no kvOverride) vs an explicit KV scheme (e.g. TurboQuant), same model /
|
|
312
313
|
* same prompts / same weight load — the quality-vs-bpw curve gate
|
|
313
|
-
* (scripts/eval-turboquant-curve.ts). Unlike evaluateKlServingDecode (env-flag
|
|
314
|
+
* (scripts/turboquant/eval-turboquant-curve.ts). Unlike evaluateKlServingDecode (env-flag
|
|
314
315
|
* self-flip), the two arms differ by an actual GenerateOptions kv scheme
|
|
315
316
|
* object, so this is the seam for schemes that aren't env-lever-shaped. */
|
|
316
317
|
export async function evaluateKlKvArm(opts: {
|
|
@@ -355,3 +356,235 @@ export async function evaluateKlKvArm(opts: {
|
|
|
355
356
|
: `bf16 vs kv-scheme (${decodeSteps} steps)`,
|
|
356
357
|
};
|
|
357
358
|
}
|
|
359
|
+
|
|
360
|
+
// --- saved-teacher KL (--reference-logits) --------------------------------
|
|
361
|
+
// The two-model mode above needs both arms resident; a bf16 teacher never
|
|
362
|
+
// fits beside a candidate on 32 GB. So the teacher is dumped ONCE from
|
|
363
|
+
// Python (top-k logits + full-vocab logsumexp per position; format and
|
|
364
|
+
// rationale in reference-logits.ts) and every candidate is scored against
|
|
365
|
+
// the dump through OUR engine.
|
|
366
|
+
//
|
|
367
|
+
// Per position, with the teacher's top-k slice j ∈ K:
|
|
368
|
+
// p_j = exp(ref_logit_j − ref_logsumexp) (full-vocab softmax)
|
|
369
|
+
// log q_j = cand_logit_j − logsumexp(cand, FULL vocab)
|
|
370
|
+
// KL = Σ_j p_j · (log p_j − log q_j)
|
|
371
|
+
// The sum is truncated to K, so it under-counts by the tail p carries
|
|
372
|
+
// outside the top-k — `meanCapturedMass` (Σ_j p_j) reports exactly how much
|
|
373
|
+
// of p the number covers, and a run with low captured mass is not a
|
|
374
|
+
// comparable KL. Everything is float32; the candidate's logsumexp is over
|
|
375
|
+
// its FULL vocab (never the top-k slice), so log q is a real log-probability.
|
|
376
|
+
//
|
|
377
|
+
// The forward is the SERVING path (model.makeCache + chunked forwardHidden +
|
|
378
|
+
// logitsFromHidden), NOT trainForward: trainForward's cache stub has no
|
|
379
|
+
// DeltaNet SSMCache.advance, which is why `mlx-bun perplexity` cannot score
|
|
380
|
+
// qwen3_5 at all (docs/design/turboquant.md, "Known engine gaps"). KV stays
|
|
381
|
+
// bf16 — this is a weights-only instrument.
|
|
382
|
+
|
|
383
|
+
export interface RefLogitsKLResult extends KLResult {
|
|
384
|
+
/** Positions actually scored (Σ over sequences). */
|
|
385
|
+
nPositions: number;
|
|
386
|
+
/** Fraction of positions where argmax(ref) == argmax(cand) over full vocab. */
|
|
387
|
+
top1Agreement: number;
|
|
388
|
+
/** Mean Σ_{j∈topK} p_j — how much of the teacher's mass the KL sum covers. */
|
|
389
|
+
meanCapturedMass: number;
|
|
390
|
+
topK: number;
|
|
391
|
+
/** manifest.model — the teacher these numbers are against. */
|
|
392
|
+
refModel: string;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** Teacher-forced candidate logits for one sequence, emitted in
|
|
396
|
+
* position-ordered chunks: `onChunk(startPos, logits[1, C, V])`. The callee
|
|
397
|
+
* owns the array and disposes it after `onChunk` returns. This is the seam
|
|
398
|
+
* the model-free test replaces (no weights, no registry). */
|
|
399
|
+
export type TeacherForcedLogits = (
|
|
400
|
+
tokens: number[],
|
|
401
|
+
onChunk: (startPos: number, logits: MlxArray) => void,
|
|
402
|
+
) => void;
|
|
403
|
+
|
|
404
|
+
/** The real engine forward: one cache for the whole sequence, prefilled in
|
|
405
|
+
* chunks, lm_head applied per chunk so full-vocab logits for a 4k context
|
|
406
|
+
* never exist at once (4096 × 151k × 4 B ≈ 2.5 GB). */
|
|
407
|
+
export function engineTeacherForcedLogits(
|
|
408
|
+
model: RuntimeModel,
|
|
409
|
+
chunkSize: number,
|
|
410
|
+
): TeacherForcedLogits {
|
|
411
|
+
return (tokens, onChunk) => {
|
|
412
|
+
const cache = model.makeCache();
|
|
413
|
+
try {
|
|
414
|
+
for (let pos = 0; pos < tokens.length; pos += chunkSize) {
|
|
415
|
+
const end = Math.min(pos + chunkSize, tokens.length);
|
|
416
|
+
const ids = ops.fromInt32(tokens.slice(pos, end), [1, end - pos]);
|
|
417
|
+
let h: MlxArray;
|
|
418
|
+
try {
|
|
419
|
+
h = model.forwardHidden(ids, cache);
|
|
420
|
+
} finally {
|
|
421
|
+
ids.dispose();
|
|
422
|
+
}
|
|
423
|
+
let logits: MlxArray;
|
|
424
|
+
try {
|
|
425
|
+
logits = model.logitsFromHidden(h);
|
|
426
|
+
} finally {
|
|
427
|
+
h.dispose();
|
|
428
|
+
}
|
|
429
|
+
try {
|
|
430
|
+
onChunk(pos, logits);
|
|
431
|
+
} finally {
|
|
432
|
+
logits.dispose();
|
|
433
|
+
}
|
|
434
|
+
evalCacheState(cache);
|
|
435
|
+
clearCache(); // mlx-lm _prefill cadence: don't let chunk transients pile up
|
|
436
|
+
}
|
|
437
|
+
} finally {
|
|
438
|
+
for (const c of cache) c.dispose();
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
interface ChunkScore {
|
|
444
|
+
kl: Float32Array; // [rows]
|
|
445
|
+
mass: Float32Array; // [rows]
|
|
446
|
+
top1: Uint8Array; // [rows] 1 = candidate argmax == teacher argmax
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** Score `rows` candidate positions against the matching teacher records.
|
|
450
|
+
* `logits` is [1, C, V]; rows [rowLo, rowHi) of it line up with
|
|
451
|
+
* `records` rows [0, count). */
|
|
452
|
+
function scoreChunkAgainstRecords(
|
|
453
|
+
logits: MlxArray, rowLo: number, rowHi: number, records: ReferenceRecords,
|
|
454
|
+
): ChunkScore {
|
|
455
|
+
const rows = rowHi - rowLo;
|
|
456
|
+
const K = records.topK;
|
|
457
|
+
const V = logits.shape[2]!;
|
|
458
|
+
if (records.count !== rows)
|
|
459
|
+
throw new Error(`reference-logits: ${records.count} records for ${rows} candidate rows`);
|
|
460
|
+
|
|
461
|
+
// Teacher indices must exist in the candidate's vocabulary.
|
|
462
|
+
let maxIdx = -1;
|
|
463
|
+
for (const v of records.indices) if (v > maxIdx) maxIdx = v;
|
|
464
|
+
if (maxIdx >= V || records.indices.some((v) => v < 0))
|
|
465
|
+
throw new Error(
|
|
466
|
+
`reference-logits: teacher index ${maxIdx} out of range for candidate vocab ${V} ` +
|
|
467
|
+
`— the dump was made with a different tokenizer`,
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
const owned: MlxArray[] = [];
|
|
471
|
+
try {
|
|
472
|
+
const sliced = logits.slice([0, rowLo, 0], [1, rowHi, V]);
|
|
473
|
+
owned.push(sliced);
|
|
474
|
+
const f32 = sliced.dtype === Dtype.float32 ? sliced : sliced.astype(Dtype.float32);
|
|
475
|
+
if (f32 !== sliced) owned.push(f32);
|
|
476
|
+
|
|
477
|
+
const lse = ops.logsumexpAxis(f32, -1, false); // [1, rows], full vocab
|
|
478
|
+
owned.push(lse);
|
|
479
|
+
const argmax = ops.argmaxAxis(f32, -1); // [1, rows], full vocab
|
|
480
|
+
owned.push(argmax);
|
|
481
|
+
const idx = MlxArray.fromInt32(records.indices, [1, rows, K]);
|
|
482
|
+
owned.push(idx);
|
|
483
|
+
const gathered = ops.takeAlongAxis(f32, idx, 2); // [1, rows, K]
|
|
484
|
+
owned.push(gathered);
|
|
485
|
+
|
|
486
|
+
const candTop = gathered.toFloat32();
|
|
487
|
+
const candLse = lse.toFloat32();
|
|
488
|
+
const candArgmax = argmax.toIntTokens();
|
|
489
|
+
|
|
490
|
+
const kl = new Float32Array(rows);
|
|
491
|
+
const mass = new Float32Array(rows);
|
|
492
|
+
const top1 = new Uint8Array(rows);
|
|
493
|
+
for (let r = 0; r < rows; r++) {
|
|
494
|
+
const refLse = records.logsumexp[r]!;
|
|
495
|
+
const qLse = candLse[r]!;
|
|
496
|
+
const base = r * K;
|
|
497
|
+
let acc = 0;
|
|
498
|
+
let m = 0;
|
|
499
|
+
for (let j = 0; j < K; j++) {
|
|
500
|
+
const logP = records.logits[base + j]! - refLse;
|
|
501
|
+
const p = Math.exp(logP);
|
|
502
|
+
if (p === 0) continue;
|
|
503
|
+
m += p;
|
|
504
|
+
acc += p * (logP - (candTop[base + j]! - qLse));
|
|
505
|
+
}
|
|
506
|
+
kl[r] = acc;
|
|
507
|
+
mass[r] = m;
|
|
508
|
+
top1[r] = candArgmax[r] === records.indices[base]! ? 1 : 0;
|
|
509
|
+
}
|
|
510
|
+
return { kl, mass, top1 };
|
|
511
|
+
} finally {
|
|
512
|
+
for (const a of owned) a.dispose();
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** KL(saved bf16 teacher ‖ candidate) at EVERY teacher-forced position. */
|
|
517
|
+
export async function evaluateKlVsReferenceLogits(opts: {
|
|
518
|
+
candidate: string;
|
|
519
|
+
/** Dump directory (runs/kl-teacher/<tag>). */
|
|
520
|
+
referenceDir: string;
|
|
521
|
+
/** Cap on sequences scored (default: all in the manifest). */
|
|
522
|
+
nSeqs?: number;
|
|
523
|
+
/** Positions per candidate forward (default 512 — see the memory note). */
|
|
524
|
+
chunkSize?: number;
|
|
525
|
+
/** Test seam: use this forward instead of loading `candidate`. */
|
|
526
|
+
forward?: TeacherForcedLogits;
|
|
527
|
+
}): Promise<RefLogitsKLResult> {
|
|
528
|
+
const t0 = Date.now();
|
|
529
|
+
const dump = ReferenceLogitsDump.open(opts.referenceDir);
|
|
530
|
+
const chunkSize = Math.max(1, opts.chunkSize ?? 512);
|
|
531
|
+
const nSeqs = Math.max(1, Math.min(opts.nSeqs ?? dump.nSeqs, dump.nSeqs));
|
|
532
|
+
|
|
533
|
+
const forward = opts.forward
|
|
534
|
+
?? engineTeacherForcedLogits((await loadRunnable(opts.candidate)).model, chunkSize);
|
|
535
|
+
|
|
536
|
+
try {
|
|
537
|
+
const perSeq: Float32Array[] = [];
|
|
538
|
+
let nPositions = 0;
|
|
539
|
+
let top1Hits = 0;
|
|
540
|
+
let massSum = 0;
|
|
541
|
+
|
|
542
|
+
for (let s = 0; s < nSeqs; s++) {
|
|
543
|
+
const tokens = dump.tokens(s);
|
|
544
|
+
const scored = Math.min(dump.positionsPerSeq, tokens.length - 1);
|
|
545
|
+
const kls = new Float32Array(scored);
|
|
546
|
+
let filled = 0;
|
|
547
|
+
|
|
548
|
+
forward(tokens, (startPos, logits) => {
|
|
549
|
+
const chunkLen = logits.shape[1]!;
|
|
550
|
+
const lo = startPos;
|
|
551
|
+
const hi = Math.min(startPos + chunkLen, scored);
|
|
552
|
+
if (hi <= lo) return; // trailing positions have no teacher record
|
|
553
|
+
const records = dump.readRecords(s, lo, hi);
|
|
554
|
+
const sc = scoreChunkAgainstRecords(logits, lo - startPos, hi - startPos, records);
|
|
555
|
+
for (let r = 0; r < sc.kl.length; r++) {
|
|
556
|
+
kls[lo + r] = sc.kl[r]!;
|
|
557
|
+
massSum += sc.mass[r]!;
|
|
558
|
+
top1Hits += sc.top1[r]!;
|
|
559
|
+
}
|
|
560
|
+
filled += hi - lo;
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
if (filled !== scored)
|
|
564
|
+
throw new Error(
|
|
565
|
+
`reference-logits: candidate forward covered ${filled} of ${scored} positions ` +
|
|
566
|
+
`on sequence ${s}`,
|
|
567
|
+
);
|
|
568
|
+
perSeq.push(kls);
|
|
569
|
+
nPositions += scored;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const agg = aggregate(perSeq);
|
|
573
|
+
return {
|
|
574
|
+
nPrompts: perSeq.length,
|
|
575
|
+
seqLen: dump.ctxLen,
|
|
576
|
+
meanKl: agg.mean,
|
|
577
|
+
medianKl: agg.median,
|
|
578
|
+
p95Kl: agg.p95,
|
|
579
|
+
elapsedSec: (Date.now() - t0) / 1000,
|
|
580
|
+
refLabel: `ref-logits:${dump.manifest.model} top${dump.topK} (${opts.referenceDir})`,
|
|
581
|
+
nPositions,
|
|
582
|
+
top1Agreement: nPositions ? top1Hits / nPositions : 0,
|
|
583
|
+
meanCapturedMass: nPositions ? massSum / nPositions : 0,
|
|
584
|
+
topK: dump.topK,
|
|
585
|
+
refModel: dump.manifest.model,
|
|
586
|
+
};
|
|
587
|
+
} finally {
|
|
588
|
+
dump.close();
|
|
589
|
+
}
|
|
590
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// Reader for a saved bf16 TEACHER-LOGITS dump (format_version 1).
|
|
2
|
+
//
|
|
3
|
+
// Why the format exists: a bf16 teacher can never be resident on a 32 GB box
|
|
4
|
+
// alongside the candidate, so the teacher's distributions are computed ONCE
|
|
5
|
+
// (Python side, one model at a time) and written to disk as a top-k slice
|
|
6
|
+
// plus the full-vocab logsumexp. Any candidate loaded through OUR engine can
|
|
7
|
+
// then be scored against it (src/eval/kl.ts `evaluateKlVsReferenceLogits`)
|
|
8
|
+
// without the teacher ever being co-resident.
|
|
9
|
+
//
|
|
10
|
+
// On-disk contract (fixed — this module reads it, it does not negotiate it):
|
|
11
|
+
//
|
|
12
|
+
// <dir>/manifest.json { format_version: 1, model, corpus_sha256, ctx_len,
|
|
13
|
+
// n_seqs, top_k, positions_per_seq, created, notes }
|
|
14
|
+
// <dir>/tokens.bin int32 LE [n_seqs][ctx_len] — teacher-forced ids
|
|
15
|
+
// <dir>/seq-<i>.bin one record per r = 0..ctx_len-2; record r is the
|
|
16
|
+
// distribution over the token at index r+1, i.e. the
|
|
17
|
+
// distribution predicted from tokens[0..r] — so record
|
|
18
|
+
// r lines up with candidate logits at POSITION r:
|
|
19
|
+
// int32[top_k] indices, descending by logit
|
|
20
|
+
// float16[top_k] reference logits at those indices
|
|
21
|
+
// float32 logsumexp over the FULL vocab
|
|
22
|
+
//
|
|
23
|
+
// Records are fixed-width (6·top_k + 4 bytes), so a range is one pread — the
|
|
24
|
+
// scorer streams a chunk of records at a time and never holds a whole
|
|
25
|
+
// sequence file (top_k 2048 × ctx 4096 ≈ 50 MB) in the JS heap.
|
|
26
|
+
|
|
27
|
+
import { closeSync, existsSync, openSync, readFileSync, readSync, statSync } from "node:fs";
|
|
28
|
+
|
|
29
|
+
export interface ReferenceLogitsManifest {
|
|
30
|
+
format_version: number;
|
|
31
|
+
model: string;
|
|
32
|
+
corpus_sha256: string;
|
|
33
|
+
ctx_len: number;
|
|
34
|
+
n_seqs: number;
|
|
35
|
+
top_k: number;
|
|
36
|
+
positions_per_seq: number;
|
|
37
|
+
created?: string;
|
|
38
|
+
notes?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A contiguous run of records, flattened: row i of `indices`/`logits` is
|
|
42
|
+
* `[i*topK, (i+1)*topK)`. Logits are decoded from float16 to float32. */
|
|
43
|
+
export interface ReferenceRecords {
|
|
44
|
+
/** Absolute record index of row 0. */
|
|
45
|
+
start: number;
|
|
46
|
+
count: number;
|
|
47
|
+
topK: number;
|
|
48
|
+
indices: Int32Array; // [count · topK]
|
|
49
|
+
logits: Float32Array; // [count · topK]
|
|
50
|
+
logsumexp: Float32Array; // [count] — over the FULL teacher vocab
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const SUPPORTED_FORMAT_VERSION = 1;
|
|
54
|
+
|
|
55
|
+
/** float16 bits → float32 (host-side; same expansion as MlxArray.toFloat32Host,
|
|
56
|
+
* kept local so reading a dump never touches the GPU). */
|
|
57
|
+
function decodeFloat16(bits: number): number {
|
|
58
|
+
const sign = bits & 0x8000;
|
|
59
|
+
const exponent = (bits >>> 10) & 0x1f;
|
|
60
|
+
const fraction = bits & 0x03ff;
|
|
61
|
+
let value: number;
|
|
62
|
+
if (exponent === 0) value = fraction === 0 ? 0 : fraction * 2 ** -24;
|
|
63
|
+
else if (exponent === 0x1f) value = fraction === 0 ? Infinity : NaN;
|
|
64
|
+
else value = (1 + fraction / 1024) * 2 ** (exponent - 15);
|
|
65
|
+
return sign ? -value : value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class ReferenceLogitsDump {
|
|
69
|
+
readonly dir: string;
|
|
70
|
+
readonly manifest: ReferenceLogitsManifest;
|
|
71
|
+
readonly topK: number;
|
|
72
|
+
readonly ctxLen: number;
|
|
73
|
+
readonly nSeqs: number;
|
|
74
|
+
/** Records per sequence file — derived from the file, cross-checked
|
|
75
|
+
* against the manifest. */
|
|
76
|
+
readonly positionsPerSeq: number;
|
|
77
|
+
readonly recordBytes: number;
|
|
78
|
+
|
|
79
|
+
#tokens: Int32Array;
|
|
80
|
+
#fd: number | null = null;
|
|
81
|
+
#fdSeq = -1;
|
|
82
|
+
|
|
83
|
+
private constructor(dir: string, manifest: ReferenceLogitsManifest, tokens: Int32Array, positionsPerSeq: number) {
|
|
84
|
+
this.dir = dir;
|
|
85
|
+
this.manifest = manifest;
|
|
86
|
+
this.topK = manifest.top_k;
|
|
87
|
+
this.ctxLen = manifest.ctx_len;
|
|
88
|
+
this.nSeqs = manifest.n_seqs;
|
|
89
|
+
this.positionsPerSeq = positionsPerSeq;
|
|
90
|
+
this.recordBytes = manifest.top_k * 6 + 4;
|
|
91
|
+
this.#tokens = tokens;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
static open(dir: string): ReferenceLogitsDump {
|
|
95
|
+
const manifestPath = `${dir}/manifest.json`;
|
|
96
|
+
if (!existsSync(manifestPath))
|
|
97
|
+
throw new Error(`reference-logits: no manifest.json in ${dir}`);
|
|
98
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8")) as ReferenceLogitsManifest;
|
|
99
|
+
if (manifest.format_version !== SUPPORTED_FORMAT_VERSION)
|
|
100
|
+
throw new Error(
|
|
101
|
+
`reference-logits: format_version ${manifest.format_version} unsupported ` +
|
|
102
|
+
`(this reader implements ${SUPPORTED_FORMAT_VERSION})`,
|
|
103
|
+
);
|
|
104
|
+
for (const k of ["ctx_len", "n_seqs", "top_k"] as const)
|
|
105
|
+
if (!Number.isInteger(manifest[k]) || manifest[k] <= 0)
|
|
106
|
+
throw new Error(`reference-logits: manifest.${k} must be a positive integer (got ${manifest[k]})`);
|
|
107
|
+
|
|
108
|
+
const tokensBytes = readFileSync(`${dir}/tokens.bin`);
|
|
109
|
+
const wantTokenBytes = manifest.n_seqs * manifest.ctx_len * 4;
|
|
110
|
+
if (tokensBytes.byteLength !== wantTokenBytes)
|
|
111
|
+
throw new Error(
|
|
112
|
+
`reference-logits: tokens.bin is ${tokensBytes.byteLength} B, expected ` +
|
|
113
|
+
`${wantTokenBytes} B (n_seqs ${manifest.n_seqs} × ctx_len ${manifest.ctx_len} × int32)`,
|
|
114
|
+
);
|
|
115
|
+
// Copy: the Buffer's byteOffset need not be int32-aligned for a view.
|
|
116
|
+
const tokens = new Int32Array(
|
|
117
|
+
tokensBytes.buffer.slice(tokensBytes.byteOffset, tokensBytes.byteOffset + tokensBytes.byteLength),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const recordBytes = manifest.top_k * 6 + 4;
|
|
121
|
+
const seq0 = `${dir}/seq-0.bin`;
|
|
122
|
+
if (!existsSync(seq0)) throw new Error(`reference-logits: missing ${seq0}`);
|
|
123
|
+
const size = statSync(seq0).size;
|
|
124
|
+
if (size % recordBytes !== 0)
|
|
125
|
+
throw new Error(
|
|
126
|
+
`reference-logits: seq-0.bin is ${size} B, not a multiple of the ` +
|
|
127
|
+
`${recordBytes} B record (top_k ${manifest.top_k})`,
|
|
128
|
+
);
|
|
129
|
+
const positions = size / recordBytes;
|
|
130
|
+
if (positions > manifest.ctx_len - 1)
|
|
131
|
+
throw new Error(
|
|
132
|
+
`reference-logits: seq-0.bin holds ${positions} records but ctx_len ` +
|
|
133
|
+
`${manifest.ctx_len} allows at most ${manifest.ctx_len - 1}`,
|
|
134
|
+
);
|
|
135
|
+
if (Number.isInteger(manifest.positions_per_seq) && manifest.positions_per_seq !== positions)
|
|
136
|
+
console.warn(
|
|
137
|
+
`[reference-logits] manifest.positions_per_seq=${manifest.positions_per_seq} but ` +
|
|
138
|
+
`seq-0.bin holds ${positions} records — using the file`,
|
|
139
|
+
);
|
|
140
|
+
return new ReferenceLogitsDump(dir, manifest, tokens, positions);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Teacher-forced token ids of sequence `seq` (length ctx_len). */
|
|
144
|
+
tokens(seq: number): number[] {
|
|
145
|
+
this.#assertSeq(seq);
|
|
146
|
+
const off = seq * this.ctxLen;
|
|
147
|
+
return Array.from(this.#tokens.subarray(off, off + this.ctxLen));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Records [lo, hi) of sequence `seq` — one pread, decoded to float32. */
|
|
151
|
+
readRecords(seq: number, lo: number, hi: number): ReferenceRecords {
|
|
152
|
+
this.#assertSeq(seq);
|
|
153
|
+
if (lo < 0 || hi > this.positionsPerSeq || hi <= lo)
|
|
154
|
+
throw new Error(
|
|
155
|
+
`reference-logits: record range [${lo},${hi}) out of bounds for ` +
|
|
156
|
+
`${this.positionsPerSeq} records`,
|
|
157
|
+
);
|
|
158
|
+
const count = hi - lo;
|
|
159
|
+
const K = this.topK;
|
|
160
|
+
const buf = Buffer.allocUnsafe(count * this.recordBytes);
|
|
161
|
+
const fd = this.#open(seq);
|
|
162
|
+
const read = readSync(fd, buf, 0, buf.byteLength, lo * this.recordBytes);
|
|
163
|
+
if (read !== buf.byteLength)
|
|
164
|
+
throw new Error(`reference-logits: short read on seq-${seq}.bin (${read}/${buf.byteLength} B)`);
|
|
165
|
+
|
|
166
|
+
const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
167
|
+
const indices = new Int32Array(count * K);
|
|
168
|
+
const logits = new Float32Array(count * K);
|
|
169
|
+
const logsumexp = new Float32Array(count);
|
|
170
|
+
for (let r = 0; r < count; r++) {
|
|
171
|
+
const base = r * this.recordBytes;
|
|
172
|
+
const row = r * K;
|
|
173
|
+
for (let j = 0; j < K; j++) indices[row + j] = dv.getInt32(base + j * 4, true);
|
|
174
|
+
const lbase = base + K * 4;
|
|
175
|
+
for (let j = 0; j < K; j++) logits[row + j] = decodeFloat16(dv.getUint16(lbase + j * 2, true));
|
|
176
|
+
logsumexp[r] = dv.getFloat32(lbase + K * 2, true);
|
|
177
|
+
}
|
|
178
|
+
return { start: lo, count, topK: K, indices, logits, logsumexp };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
close(): void {
|
|
182
|
+
if (this.#fd !== null) closeSync(this.#fd);
|
|
183
|
+
this.#fd = null;
|
|
184
|
+
this.#fdSeq = -1;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
#open(seq: number): number {
|
|
188
|
+
if (this.#fdSeq === seq && this.#fd !== null) return this.#fd;
|
|
189
|
+
if (this.#fd !== null) closeSync(this.#fd);
|
|
190
|
+
const path = `${this.dir}/seq-${seq}.bin`;
|
|
191
|
+
if (!existsSync(path)) throw new Error(`reference-logits: missing ${path}`);
|
|
192
|
+
const size = statSync(path).size;
|
|
193
|
+
if (size < this.positionsPerSeq * this.recordBytes)
|
|
194
|
+
throw new Error(
|
|
195
|
+
`reference-logits: seq-${seq}.bin is ${size} B, short of the ` +
|
|
196
|
+
`${this.positionsPerSeq * this.recordBytes} B seq-0.bin defines`,
|
|
197
|
+
);
|
|
198
|
+
this.#fd = openSync(path, "r");
|
|
199
|
+
this.#fdSeq = seq;
|
|
200
|
+
return this.#fd;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
#assertSeq(seq: number): void {
|
|
204
|
+
if (!Number.isInteger(seq) || seq < 0 || seq >= this.nSeqs)
|
|
205
|
+
throw new Error(`reference-logits: sequence ${seq} out of range (n_seqs ${this.nSeqs})`);
|
|
206
|
+
}
|
|
207
|
+
}
|
package/src/eval/runner.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { GenerateOptions } from "../generate";
|
|
1
2
|
// Task-eval runner: load a model and greedily complete prompts through the
|
|
2
3
|
// REAL serving path. Unlike the M0 KL (compat forward() + plain caches),
|
|
3
4
|
// this drives generate() with the model's kv_config, so generation runs the
|
|
@@ -5,9 +6,6 @@
|
|
|
5
6
|
|
|
6
7
|
import { homedir } from "node:os";
|
|
7
8
|
import { readFileSync } from "node:fs";
|
|
8
|
-
import { generate, type GenerateOptions } from "../generate";
|
|
9
|
-
import { clearCache } from "../mlx/ffi";
|
|
10
|
-
import * as ops from "../mlx/ops";
|
|
11
9
|
import { ChatTemplate } from "../chat-template";
|
|
12
10
|
import { loadModelConfig, type ModelConfig } from "../config";
|
|
13
11
|
import { runtimeValue } from "../runtime-config";
|
|
@@ -17,13 +15,12 @@ import {
|
|
|
17
15
|
type RuntimeModel,
|
|
18
16
|
} from "../model/factory";
|
|
19
17
|
import { isGlm52Config } from "../model/support";
|
|
20
|
-
import { DiffusionGemmaModel } from "../model/diffusion-gemma";
|
|
21
18
|
import { loadTokenizer, type LoadedTokenizer } from "../tokenizer";
|
|
22
19
|
import { resolveModelDir } from "./kl";
|
|
23
20
|
|
|
24
21
|
export const EVAL_DATA_DIR = runtimeValue("MLX_BUN_EVAL_DATA") ?? `${homedir()}/.cache/mlx-bun/eval-data`;
|
|
25
22
|
|
|
26
|
-
/** Read a jsonl dataset exported by scripts/
|
|
23
|
+
/** Read a jsonl dataset exported by scripts/oracle/export-datasets.py. */
|
|
27
24
|
export function loadJsonl<T = Record<string, unknown>>(name: string): T[] {
|
|
28
25
|
const path = `${EVAL_DATA_DIR}/${name}.jsonl`;
|
|
29
26
|
const out: T[] = [];
|
|
@@ -55,6 +52,7 @@ export function sampleIndices(total: number, n: number, seed = 42): number[] {
|
|
|
55
52
|
type SamplerArm = Partial<Pick<GenerateOptions, "temperature" | "topP" | "topK" | "seed" | "hlg" | "curve">>;
|
|
56
53
|
|
|
57
54
|
export interface TaskModel {
|
|
55
|
+
completionClient?: import("../contracts/completion").CompletionClient<{ body: string; options?: GenOpts }, string>;
|
|
58
56
|
model: RuntimeModel;
|
|
59
57
|
tokenizer: LoadedTokenizer;
|
|
60
58
|
template: ChatTemplate | null;
|
|
@@ -117,96 +115,10 @@ export interface GenOpts {
|
|
|
117
115
|
kvScheme?: Pick<GenerateOptions, "kvBits" | "kvConfig" | "quantizedKvStart" | "turboQuant">;
|
|
118
116
|
}
|
|
119
117
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
* (forwardHidden/logitsFromHidden + pipelined sampler) diverges from the raw forward
|
|
123
|
-
* on near-ties past ~32 tokens, so the EVAL decodes bit-exactly itself rather than
|
|
124
|
-
* alter the serving path. Greedy + full-precision KV only. */
|
|
125
|
-
export function greedyDecodeBitExact(tm: TaskModel, ids: number[], maxTokens: number): string {
|
|
126
|
-
if (tm.activeAdapters) tm.model.loraState.active = tm.activeAdapters; // apply the mounted adapter
|
|
127
|
-
const cache = tm.model.makeCache();
|
|
128
|
-
try {
|
|
129
|
-
// Chunked, LM-head-free prefill. The old single-shot `forward(prompt)`
|
|
130
|
-
// materialized [1, L, vocab] logits just to discard them — at a 248k
|
|
131
|
-
// vocab that's ~1 GB per 1k prompt tokens, and with no allocator-cache
|
|
132
|
-
// clears the Metal buffer cache retained every giant freed tensor
|
|
133
|
-
// (the 27B capability-suite 73 GB swap-thrash). Prefill only needs the
|
|
134
|
-
// cache state, which forwardHidden advances identically (the LM head
|
|
135
|
-
// never touches the cache) — bit-exact by construction. Chunk size and
|
|
136
|
-
// clear cadence mirror mlx-lm generate_step / our generate().
|
|
137
|
-
const PREFILL_CHUNK = 2048;
|
|
138
|
-
const prompt = ids.slice(0, -1);
|
|
139
|
-
for (let s = 0; s < prompt.length; s += PREFILL_CHUNK) {
|
|
140
|
-
const chunk = prompt.slice(s, s + PREFILL_CHUNK);
|
|
141
|
-
const cIds = ops.fromInt32(chunk, [1, chunk.length]);
|
|
142
|
-
tm.model.forwardHidden(cIds, cache).dispose();
|
|
143
|
-
cIds.dispose();
|
|
144
|
-
clearCache();
|
|
145
|
-
}
|
|
146
|
-
let last = ids[ids.length - 1]!;
|
|
147
|
-
const eos = new Set(tm.config.eosTokenIds);
|
|
148
|
-
const out: number[] = [];
|
|
149
|
-
for (let i = 0; i < maxTokens; i++) {
|
|
150
|
-
const lg = tm.model.forward([last], cache);
|
|
151
|
-
const am = ops.argmaxAxis(lg, -1); // GPU argmax (mlx-lm greedy: lowest-index tie-break)
|
|
152
|
-
lg.dispose();
|
|
153
|
-
const best = ops.itemUint32(am);
|
|
154
|
-
am.dispose();
|
|
155
|
-
if (i % 256 === 0) clearCache(); // mlx-lm decode cadence
|
|
156
|
-
if (eos.has(best)) break; // mlx-lm halts on EOS and excludes it from the output
|
|
157
|
-
out.push(best);
|
|
158
|
-
last = best;
|
|
159
|
-
}
|
|
160
|
-
return tm.tokenizer.decode(out, true);
|
|
161
|
-
} finally {
|
|
162
|
-
for (const c of cache) c.dispose();
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/** Complete `body` via the model's real quantized-KV path. Greedy by default;
|
|
167
|
-
* pass `opts.sampler` to drive temperature/HLG/etc. */
|
|
168
|
-
export async function generateText(tm: TaskModel, body: string, opts: GenOpts = {}): Promise<string> {
|
|
169
|
-
const maxTokens = opts.maxTokens ?? 256;
|
|
170
|
-
const enableThinking = opts.enableThinking ?? runtimeValue("MLX_BUN_EVAL_THINK") === "1";
|
|
171
|
-
const templated = opts.useChat !== false && tm.template !== null;
|
|
172
|
-
const text = templated
|
|
173
|
-
? tm.template!.render([{ role: "user", content: body }], { addGenerationPrompt: true, enableThinking })
|
|
174
|
-
: body;
|
|
175
|
-
// The chat template ALREADY emits the BOS; encoding it with add_special_tokens on
|
|
176
|
-
// would prepend a SECOND BOS (<s><s>…), corrupting generation. Raw bodies (no
|
|
177
|
-
// template) get the BOS added as normal.
|
|
178
|
-
const ids = tm.tokenizer.encode(text, /* addSpecialTokens */ !templated);
|
|
179
|
-
|
|
180
|
-
// optiq's PUBLISHED eval generates with a FULL-PRECISION KV cache — its eval calls
|
|
181
|
-
// plain mlx_lm.generate (kv-quant lives in the serving runtime, not the eval). So
|
|
182
|
-
// for like-for-like parity we default the eval to UNQUANTIZED KV; MMLU's argmax
|
|
183
|
-
// path was already unquantized, which is why it matched while generation didn't.
|
|
184
|
-
// MLX_BUN_EVAL_KV_QUANT=1 generates through the model's quantized KV (serving
|
|
185
|
-
// config); an explicit opts.kvScheme (cli generate's resolved tier route) wins.
|
|
186
|
-
const envKv: GenOpts["kvScheme"] | undefined =
|
|
187
|
-
(runtimeValue("MLX_BUN_EVAL_KV_QUANT") === "1" && tm.config.kvQuant?.length)
|
|
188
|
-
? { kvConfig: tm.config.kvQuant, quantizedKvStart: 0 } : undefined;
|
|
189
|
-
const kvScheme = opts.kvScheme ?? envKv;
|
|
190
|
-
const kvActive = !!(kvScheme && (kvScheme.kvBits || kvScheme.kvConfig?.length || kvScheme.turboQuant));
|
|
191
|
-
|
|
192
|
-
// Parity default — greedy + full-precision KV + no sampler arm: decode bit-exactly
|
|
193
|
-
// via the raw forward (matches mlx-lm token-for-token). Sampler arms / kv-quant fall
|
|
194
|
-
// through to the product generate(). DiffusionGemma is non-autoregressive (no AR
|
|
195
|
-
// forward) — always go through generate(), which routes it to the denoising engine.
|
|
196
|
-
const isDiffusion = tm.model instanceof DiffusionGemmaModel;
|
|
197
|
-
if (!isDiffusion && !kvActive && !opts.sampler && !tm.samplerOverride) {
|
|
198
|
-
return greedyDecodeBitExact(tm, ids, maxTokens);
|
|
199
|
-
}
|
|
200
|
-
const gen = generate(tm.model, ids, {
|
|
201
|
-
maxTokens,
|
|
202
|
-
temperature: 0, // greedy default — deterministic head-to-head arms
|
|
203
|
-
adapters: tm.activeAdapters ?? [], // per-request activation of the mounted eval-adapter
|
|
204
|
-
...(kvActive ? kvScheme : {}),
|
|
205
|
-
...(opts.sampler ?? {}), // overrides temperature when provided
|
|
206
|
-
...(tm.samplerOverride ?? {}), // arm override wins over the task's own sampler
|
|
207
|
-
});
|
|
118
|
+
export { greedyDecodeBitExact } from "../backends/mlx/evaluation";
|
|
119
|
+
import { createEvalCompletionClient } from "../backends/mlx/evaluation";
|
|
208
120
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
return tm.
|
|
121
|
+
/** Application entry point; selection and model math belong to its client. */
|
|
122
|
+
export function generateText(tm: TaskModel, body: string, opts: GenOpts = {}): Promise<string> {
|
|
123
|
+
return (tm.completionClient ?? createEvalCompletionClient(tm)).complete({ body, options: opts });
|
|
212
124
|
}
|