mlx-bun 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -588
- package/docs/README.md +91 -0
- package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
- package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
- package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
- package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
- package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
- package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
- package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
- package/docs/design/batching.md +569 -0
- package/docs/design/decode-speed-program.md +3178 -169
- package/docs/design/dreaming-nightly-pipeline.md +675 -282
- package/docs/design/generic-model-support.md +579 -334
- package/docs/design/kv-cache.md +680 -0
- package/docs/design/orpo-training.md +673 -1239
- package/docs/design/speculative-decoding.md +1627 -0
- package/docs/design/turboquant.md +2102 -0
- package/docs/design/unified-engine-frontier-plan.md +1831 -727
- package/docs/design/web-chat-redesign.md +666 -1083
- package/docs/planning/PRODUCT_ROADMAP.md +34 -1
- package/docs/planning/release-notes-v0.3.0.md +35 -0
- package/docs/planning/video-series.md +2 -2
- package/docs/reference/benchmarks.md +1162 -0
- package/docs/reference/cli.md +394 -159
- package/docs/reference/distribution.md +321 -182
- package/docs/reference/environment.md +348 -0
- package/docs/reference/glossary.md +39 -0
- package/docs/reference/library-api.md +566 -168
- package/docs/reference/memory.md +227 -64
- package/docs/reference/models.md +104 -6
- package/docs/reference/server-api.md +886 -425
- package/docs/reference/server-config.md +638 -330
- package/docs/reference/training.md +393 -254
- package/docs/reference/troubleshooting.md +166 -0
- package/package.json +9 -4
- package/src/audio/decode.ts +2 -2
- package/src/audio/features.ts +2 -2
- package/src/backends/mlx/autoregressive.ts +108 -0
- package/src/backends/mlx/batch-group.ts +1646 -0
- package/src/backends/mlx/diffusion.ts +39 -0
- package/src/backends/mlx/draft-target.ts +47 -0
- package/src/backends/mlx/evaluation.ts +105 -0
- package/src/backends/mlx/fill-append.ts +45 -0
- package/src/backends/mlx/gateway-binding.ts +71 -0
- package/src/backends/mlx/graph.ts +38 -0
- package/src/backends/mlx/memory-client.ts +189 -0
- package/src/backends/mlx/memory-guard.ts +40 -0
- package/src/backends/mlx/methods.ts +90 -0
- package/src/backends/mlx/model-prompt.ts +164 -0
- package/src/backends/mlx/model-serving.ts +79 -0
- package/src/backends/mlx/prefill.ts +33 -0
- package/src/backends/mlx/request-policy.ts +20 -0
- package/src/backends/mlx/rollback.ts +51 -0
- package/src/backends/mlx/serial-executor.ts +273 -0
- package/src/backends/mlx/speculative.ts +114 -0
- package/src/backends/mlx/state-views.ts +26 -0
- package/src/backends/mlx/text-engine.ts +73 -0
- package/src/cli.ts +58 -25
- package/src/client.ts +66 -0
- package/src/config.ts +21 -4
- package/src/contracts/completion.ts +16 -0
- package/src/contracts/execution.ts +49 -0
- package/src/contracts/generation.ts +103 -0
- package/src/contracts/host.ts +7 -0
- package/src/contracts/jobs.ts +66 -0
- package/src/contracts/pi-provider.ts +30 -0
- package/src/contracts/pi.ts +259 -0
- package/src/contracts/resources.ts +18 -0
- package/src/contracts/scheduling.ts +27 -0
- package/src/contracts/tsconfig.json +14 -0
- package/src/diffusion/diffusion-generate.ts +218 -153
- package/src/embed.ts +1 -1
- package/src/engine/admission.ts +59 -0
- package/src/engine/cancellation.ts +38 -0
- package/src/engine/engine.ts +56 -0
- package/src/engine/execution-coordinator.ts +70 -0
- package/src/engine/execution-plan.ts +51 -0
- package/src/engine/resources.ts +42 -0
- package/src/engine/scheduler.ts +67 -0
- package/src/engine/session.ts +247 -0
- package/src/eval/README.md +44 -1
- package/src/eval/kl.ts +236 -3
- package/src/eval/reference-logits.ts +207 -0
- package/src/eval/runner.ts +8 -96
- package/src/fill/echo-index.ts +188 -0
- package/src/fill/fill-session.ts +452 -0
- package/src/fill/proposal.ts +68 -0
- package/src/fill/schema-rows.ts +506 -0
- package/src/fill/tool-boundary.ts +103 -0
- package/src/fit.ts +1 -1
- package/src/generate.ts +656 -268
- package/src/grammar.ts +12 -7
- package/src/harness-pi.ts +14 -33
- package/src/hub-rest.ts +4 -4
- package/src/index.ts +7 -0
- package/src/inference/denoising.ts +17 -0
- package/src/inference/graph.ts +33 -0
- package/src/inference/prefill.ts +39 -0
- package/src/inference/rollback.ts +7 -0
- package/src/jobs/db.ts +4 -0
- package/src/jobs/index.ts +2 -0
- package/src/jobs/job-entry.ts +12 -12
- package/src/jobs/runner.ts +98 -13
- package/src/jobs/task-client.ts +19 -0
- package/src/jobs/types.ts +2 -58
- package/src/kv-store.ts +109 -49
- package/src/lab/README.md +18 -0
- package/src/{assets → lab/curve}/curve-designer.html +2 -2
- package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
- package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +5 -5
- package/src/{model → lab/paged-kv}/paged-kv.ts +9 -6
- package/src/library.ts +40 -0
- package/src/lora.ts +18 -0
- package/src/media-fetch.ts +1 -1
- package/src/memory/article.ts +1 -1
- package/src/memory/chunk.ts +1 -1
- package/src/memory/model.ts +30 -169
- package/src/memory/prompts.ts +1 -1
- package/src/memory/rest.ts +1 -1
- package/src/memory/schedule.ts +1 -1
- package/src/memory/stages.ts +4 -1
- package/src/memory/vault.ts +1 -3
- package/src/mlx/array.ts +9 -10
- package/src/mlx/compile.ts +12 -2
- package/src/mlx/ffi.ts +58 -11
- package/src/mlx/materialize.ts +23 -0
- package/src/mlx/metal-kernel.ts +10 -11
- package/src/mlx/ops.ts +33 -13
- package/src/mlx/quantized-rows.ts +36 -0
- package/src/mlx/token-bitmask.ts +52 -0
- package/src/mlx/turboquant-kv-decode.ts +82 -0
- package/src/mlx/turboquant-kv-inverse.ts +122 -0
- package/src/mlx/turboquant-ops.ts +33 -3
- package/src/mlx/turboquant-tables.ts +2 -2
- package/src/model/batched-mask.ts +8 -2
- package/src/model/batched-quant.ts +1 -1
- package/src/model/batched-rotating-quant.ts +2 -2
- package/src/model/batched-rotating.ts +1 -1
- package/src/model/compiled-decode.ts +1 -1
- package/src/model/diffusion-gemma.ts +8 -8
- package/src/model/factory.ts +76 -33
- package/src/model/fingerprint.ts +1 -1
- package/src/model/flash-attention.ts +3 -3
- package/src/model/gemma4-base.ts +26 -9
- package/src/model/gemma4.ts +6 -6
- package/src/model/generated/gemma4-12b.ts +2 -2
- package/src/model/generated/gemma4-26b.ts +2 -2
- package/src/model/generated/gemma4-e4b.ts +2 -2
- package/src/model/glm52-dsa.ts +13 -9
- package/src/model/glm52-memory.ts +1 -1
- package/src/model/implementation.ts +69 -0
- package/src/model/minicpm5.ts +6 -8
- package/src/model/profile.ts +12 -2
- package/src/model/qwen-append.ts +12 -0
- package/src/model/qwen-conv.ts +52 -0
- package/src/model/qwen3_5-checkpoint.ts +164 -0
- package/src/model/qwen3_5.ts +140 -56
- package/src/model/support.ts +1 -1
- package/src/model/trellis-balanced-scatter.ts +51 -0
- package/src/model/trellis-linear.ts +672 -0
- package/src/model/trellis-shared-m.ts +151 -0
- package/src/model/trellis-shared-scatter.ts +144 -0
- package/src/model/trellis-splitk-prefill.ts +158 -0
- package/src/model/trellis-tiled-prefill.ts +136 -0
- package/src/model/trellis-vector-expand.ts +62 -0
- package/src/model/trellis-wide-prefill.ts +94 -0
- package/src/model/universal/dense.ts +1 -1
- package/src/model/universal/modules.ts +8 -1
- package/src/native-pack.ts +6 -5
- package/src/pi-provider.ts +6 -64
- package/src/pi-web.ts +8 -315
- package/src/prompt-cache.ts +96 -53
- package/src/quantize/allocator.ts +1 -1
- package/src/quantize/config-writer.ts +10 -3
- package/src/quantize/rotate.ts +4 -4
- package/src/quantize/trellis.ts +586 -0
- package/src/responses.ts +28 -1
- package/src/runtime-config.ts +18 -4
- package/src/sampler.ts +4 -4
- package/src/serve/batch-scheduler.ts +8 -1671
- package/src/serve/chat-request.ts +391 -0
- package/src/serve/chat-stage.ts +195 -0
- package/src/serve/checkpoint-identity.ts +64 -0
- package/src/serve/completion-executor.ts +15 -7
- package/src/serve/discovery-routes.ts +12 -13
- package/src/serve/generation-gateway.ts +154 -193
- package/src/serve/http.ts +159 -0
- package/src/serve/inference-request.ts +79 -0
- package/src/serve/isolate.ts +297 -105
- package/src/serve/lab-routes.ts +3 -0
- package/src/serve/lane-registry.ts +2 -1
- package/src/serve/model-admin-routes.ts +7 -5
- package/src/serve/model-binding.ts +43 -0
- package/src/serve/model-host.ts +512 -0
- package/src/serve/openai-wire.ts +103 -0
- package/src/serve/parent-application.ts +56 -0
- package/src/serve/pipeline.ts +33 -0
- package/src/serve/preparation.ts +33 -0
- package/src/serve/request-plan.ts +19 -15
- package/src/serve/request-prep.ts +346 -0
- package/src/serve/response-lease.ts +24 -0
- package/src/serve/responses-client.ts +69 -0
- package/src/serve/session-completion-engine.ts +110 -0
- package/src/serve/session-search.ts +1 -1
- package/src/serve/static-routes.ts +5 -5
- package/src/serve/text-completion-stage.ts +104 -0
- package/src/serve/token-streams.ts +519 -0
- package/src/server.ts +317 -2649
- package/src/spec/assistant-source.ts +10 -44
- package/src/spec/deepspec-source.ts +6 -6
- package/src/spec/dflash-source.ts +8 -8
- package/src/spec/dspark/ab-stats.ts +3 -3
- package/src/spec/dspark/calibration.ts +2 -2
- package/src/spec/dspark/deepspec-module.ts +2 -2
- package/src/spec/dspark/generate-dflash.ts +2 -2
- package/src/spec/dspark/generate.ts +2 -2
- package/src/spec/dspark/loader.ts +1 -1
- package/src/spec/dspark/module-dflash.ts +4 -3
- package/src/spec/dspark/module.ts +3 -3
- package/src/spec/dspark/quantize-drafter.ts +1 -1
- package/src/spec/generate.ts +1 -1
- package/src/spec/glm52-mtp-source.ts +1 -1
- package/src/spec/ngram-source.ts +1 -1
- package/src/spec/prefix-state.ts +45 -0
- package/src/spec/qwen-mtp-source.ts +166 -69
- package/src/spec/serve-loop.ts +154 -156
- package/src/spec/source.ts +42 -10
- package/src/spec/two-model.ts +1 -1
- package/src/ssd-cache.ts +80 -12
- package/src/ssd-durability.ts +25 -13
- package/src/tool-call.ts +51 -14
- package/src/train/flash-cce.ts +2 -2
- package/src/train/forward.ts +6 -0
- package/src/train/loss.ts +1 -1
- package/src/train/prefix-shared.ts +5 -1
- package/src/train/segmented.ts +5 -3
- package/src/train/trainer.ts +6 -6
- package/src/vision/prompt.ts +1 -1
- package/src/vision/qwen3vl-tower.ts +52 -10
- package/src/web/app.html +1 -1
- package/src/web/app.js +5 -3
- package/src/web/src/adapters-panel.ts +3 -3
- package/src/web/src/assistant.ts +2 -36
- package/src/web/src/chat.ts +4 -4
- package/src/web/src/composer.ts +4 -4
- package/src/web/src/hub.ts +1 -1
- package/src/web/src/markdown.ts +0 -0
- package/src/web/src/memory-panel.ts +1 -1
- package/src/web/src/model-picker.ts +1 -1
- package/src/web/src/palette.ts +1 -1
- package/src/web/src/protocol.ts +3 -39
- package/src/web/src/sessions.ts +1 -1
- package/src/web/src/ui-catalog.ts +4 -18
- package/src/web-assets.ts +47 -0
- package/src/weights.ts +55 -9
- package/docs/archive/investigations/curve-runs/bisector-route.json +0 -98
- package/docs/archive/investigations/curve-runs/bisector-trace.json +0 -1393
- package/docs/archive/investigations/curve-runs/curve-terrain.json +0 -1179
- package/docs/archive/investigations/curve-runs/curve-terrain.svg +0 -42
- package/docs/archive/investigations/hlg-ab.html +0 -34
- package/docs/archive/investigations/hlg-explorer.html +0 -238
- package/docs/archive/investigations/hlg-figs/cliff.svg +0 -1
- package/docs/archive/investigations/hlg-figs/cross.svg +0 -1
- package/docs/archive/investigations/hlg-figs/edges.svg +0 -1
- package/docs/archive/investigations/hlg-figs/k20-grids.svg +0 -1
- package/docs/archive/investigations/hlg-figs/k20-peaks.svg +0 -1
- package/docs/archive/investigations/hlg-figs/lw-curve.svg +0 -1
- package/docs/archive/investigations/hlg-figs/terrain.svg +0 -1
- package/docs/archive/investigations/hlg-report.html +0 -262
- package/docs/archive/investigations/hlg-runs/hlg-ab.json +0 -149
- package/docs/archive/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
- package/docs/archive/investigations/hlg-runs/hlg-blind.json +0 -107
- package/docs/archive/investigations/hlg-runs/hlg-cliff.json +0 -197
- package/docs/archive/investigations/hlg-runs/hlg-cross.json +0 -505
- package/docs/archive/investigations/hlg-runs/hlg-edge.json +0 -388
- package/docs/archive/investigations/hlg-runs/hlg-eval1.json +0 -25
- package/docs/archive/investigations/hlg-runs/hlg-frontier.json +0 -86
- package/docs/archive/investigations/hlg-runs/hlg-grid.json +0 -242
- package/docs/archive/investigations/hlg-runs/hlg-grid2.json +0 -343
- package/docs/archive/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
- package/docs/archive/investigations/hlg-runs/hlg-map.json +0 -383
- package/docs/archive/investigations/hlg-runs/run0-original.log +0 -159
- package/docs/archive/investigations/hlg-runs/runA-recommended.log +0 -131
- package/docs/archive/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
- package/docs/archive/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
- package/docs/archive/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
- package/docs/archive/investigations/hlg-runs/runAD-ab-samples.log +0 -8
- package/docs/archive/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
- package/docs/archive/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
- package/docs/archive/investigations/hlg-runs/runAG-eval-config2.log +0 -16
- package/docs/archive/investigations/hlg-runs/runB-gain-variants.log +0 -192
- package/docs/archive/investigations/hlg-runs/runC-pivots.log +0 -162
- package/docs/archive/investigations/hlg-runs/runD-oetf.log +0 -132
- package/docs/archive/investigations/hlg-runs/runE-eotf.log +0 -162
- package/docs/archive/investigations/hlg-runs/runF-shaper-working.log +0 -86
- package/docs/archive/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
- package/docs/archive/investigations/hlg-runs/runH-wsweep.log +0 -176
- package/docs/archive/investigations/hlg-runs/runI-asweep.log +0 -176
- package/docs/archive/investigations/hlg-runs/runJ-knobmap.log +0 -49
- package/docs/archive/investigations/hlg-runs/runK-frontier.log +0 -19
- package/docs/archive/investigations/hlg-runs/runL-widemap.log +0 -65
- package/docs/archive/investigations/hlg-runs/runM-frontier-emb.log +0 -15
- package/docs/archive/investigations/hlg-runs/runN-verify.log +0 -116
- package/docs/archive/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
- package/docs/archive/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
- package/docs/archive/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
- package/docs/archive/investigations/hlg-runs/runR-blind-transcript.log +0 -79
- package/docs/archive/investigations/hlg-runs/runS-cross-coherence.log +0 -33
- package/docs/archive/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
- package/docs/archive/investigations/hlg-runs/runU-cross-12b.log +0 -33
- package/docs/archive/investigations/hlg-runs/runV-lw-e4b.log +0 -22
- package/docs/archive/investigations/hlg-runs/runW-lw-12b.log +0 -22
- package/docs/archive/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
- package/docs/archive/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
- package/docs/archive/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
- package/docs/archive/mlx-bun-lab-report.html +0 -275
- package/docs/archive/wwdc-mlx-bun.html +0 -848
- package/docs/design/adapters-end-to-end.md +0 -205
- package/docs/design/batching-perf-path.md +0 -255
- package/docs/design/batching-v2-plan.md +0 -529
- package/docs/design/bucketing-stage.md +0 -197
- package/docs/design/compat-cli-surface-design.md +0 -230
- package/docs/design/docs-reorg-plan.md +0 -165
- package/docs/design/dspark-serving-program.md +0 -371
- package/docs/design/dspark-speculative-decoding.md +0 -318
- package/docs/design/grammar-spec-batching-integration.md +0 -301
- package/docs/design/memory-inference-path.md +0 -422
- package/docs/design/memory-synthesis.md +0 -287
- package/docs/design/memory-system.md +0 -237
- package/docs/design/mlx-lm-serving-execution-seam.md +0 -81
- package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
- package/docs/design/omlx-adoption-map.md +0 -98
- package/docs/design/orpo-dynamic-lambda.md +0 -220
- package/docs/design/orpo-future-enhancements.md +0 -340
- package/docs/design/paged-kv-cache.md +0 -214
- package/docs/design/parallel-slots.md +0 -567
- package/docs/design/parity-tier-dag.md +0 -161
- package/docs/design/repo-cleanup-plan.md +0 -231
- package/docs/design/runtime-isolation.md +0 -263
- package/docs/design/segmented-backward-training.md +0 -543
- package/docs/design/spec-decode-larger-targets.md +0 -229
- package/docs/design/ssd-kv-cold-tier.md +0 -415
- package/docs/design/structured-output.md +0 -484
- package/docs/design/tauri-desktop-app.md +0 -127
- package/docs/design/the-dreaming-master-plan.md +0 -445
- package/docs/design/turboquant-kv.md +0 -235
- package/docs/design/turboquant-weights.md +0 -467
- package/docs/design/w4a16-compute-precision-spike.md +0 -232
- package/docs/design/web-chat-beat-matrix.md +0 -315
- package/docs/design/write-pipeline-entity.md +0 -468
- package/docs/planning/IDEAS.md +0 -69
- package/docs/reference/embedding.md +0 -197
- package/docs/reference/features-matrix.md +0 -101
- package/docs/reference/orpo-quickstart.md +0 -135
- package/src/flags.ts +0 -11
- /package/docs/{design → archive/investigations}/colibri-glm52-port.md +0 -0
- /package/docs/{planning → archive/planning}/chunk-task-roadmap.md +0 -0
- /package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +0 -0
- /package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +0 -0
- /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.2.0.md +0 -0
- /package/src/{memory-plan.ts → admission-plan.ts} +0 -0
- /package/{docs/archive/investigations → src/serve/assets}/curve-terrain.html +0 -0
- /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
package/src/ssd-cache.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// SSD cold tier for the prompt/KV cache (docs/design/
|
|
1
|
+
// SSD cold tier for the prompt/KV cache (docs/design/kv-cache.md).
|
|
2
2
|
//
|
|
3
3
|
// Files ARE the database: no sidecar index. Layout
|
|
4
4
|
// <dir>/<configFingerprint>/<nsHash>/<uuid>.mlxkv
|
|
@@ -22,7 +22,7 @@ import { randomUUID } from "node:crypto";
|
|
|
22
22
|
import { commonPrefixLength } from "./prompt-cache";
|
|
23
23
|
import {
|
|
24
24
|
saveKvCache, saveKvCacheAsync, loadKvCache, readKvHeader,
|
|
25
|
-
cacheHeadersTrimmable,
|
|
25
|
+
cacheHeadersTrimmable, legacyCacheCodecs, type CacheCodecProvider,
|
|
26
26
|
type KvSaveMeta, type KvLoadExpect, type LoadedKvCache,
|
|
27
27
|
} from "./kv-store";
|
|
28
28
|
import type { Cache } from "./model/gemma4-base";
|
|
@@ -39,9 +39,11 @@ export interface SsdIndexEntry {
|
|
|
39
39
|
* over a shorter usable one (the 2026-07-06 restart-0 defect: the
|
|
40
40
|
* [prompt+gen] file always won, always got rejected after restore). */
|
|
41
41
|
trimmable: boolean;
|
|
42
|
+
generationCheckpoint?: NonNullable<KvSaveMeta["generationCheckpoint"]>;
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
export interface SsdStoreOptions {
|
|
46
|
+
codecs?: CacheCodecProvider;
|
|
45
47
|
dir: string;
|
|
46
48
|
maxBytes: number;
|
|
47
49
|
/** Server identity — written into every file, enforced on scan/load. */
|
|
@@ -57,6 +59,7 @@ const nsHash = (ns: string): string =>
|
|
|
57
59
|
|
|
58
60
|
export class SsdCacheStore {
|
|
59
61
|
readonly #opts: SsdStoreOptions;
|
|
62
|
+
readonly #codecs: CacheCodecProvider;
|
|
60
63
|
readonly #root: string; // <dir>/<configFingerprint>
|
|
61
64
|
#index: SsdIndexEntry[] = [];
|
|
62
65
|
#warnedWriteFailure = false;
|
|
@@ -64,6 +67,7 @@ export class SsdCacheStore {
|
|
|
64
67
|
|
|
65
68
|
constructor(opts: SsdStoreOptions) {
|
|
66
69
|
this.#opts = opts;
|
|
70
|
+
this.#codecs = opts.codecs ?? legacyCacheCodecs;
|
|
67
71
|
this.#root = join(opts.dir, opts.configFingerprint);
|
|
68
72
|
}
|
|
69
73
|
|
|
@@ -121,10 +125,14 @@ export class SsdCacheStore {
|
|
|
121
125
|
h.tokenizerHash !== this.#opts.tokenizerHash
|
|
122
126
|
)
|
|
123
127
|
throw new Error("metadata mismatch");
|
|
128
|
+
if ((h.codecProvider ?? legacyCacheCodecs.id) !== this.#codecs.id) continue;
|
|
124
129
|
const st = statSync(path);
|
|
125
130
|
this.#index.push({
|
|
126
131
|
path, ns: h.ns ?? "", tokens: h.tokens, bytes: st.size, mtimeMs: st.mtimeMs,
|
|
127
|
-
trimmable: cacheHeadersTrimmable(h.caches),
|
|
132
|
+
trimmable: cacheHeadersTrimmable(h.caches, this.#codecs),
|
|
133
|
+
...(h.generationCheckpoint
|
|
134
|
+
? { generationCheckpoint: h.generationCheckpoint }
|
|
135
|
+
: {}),
|
|
128
136
|
});
|
|
129
137
|
} catch {
|
|
130
138
|
try { rmSync(path, { force: true }); } catch {}
|
|
@@ -171,7 +179,7 @@ export class SsdCacheStore {
|
|
|
171
179
|
ns: entry.ns,
|
|
172
180
|
verify: this.#opts.verify,
|
|
173
181
|
};
|
|
174
|
-
const loaded = loadKvCache(entry.path, model, expect);
|
|
182
|
+
const loaded = loadKvCache(entry.path, model, expect, this.#codecs);
|
|
175
183
|
const now = new Date();
|
|
176
184
|
try { utimesSync(entry.path, now, now); entry.mtimeMs = now.getTime(); } catch {}
|
|
177
185
|
this.stats.restores++;
|
|
@@ -198,7 +206,7 @@ export class SsdCacheStore {
|
|
|
198
206
|
const path = join(dir, `${randomUUID()}.mlxkv`);
|
|
199
207
|
try {
|
|
200
208
|
mkdirSync(dir, { recursive: true });
|
|
201
|
-
saveKvCache(path, tokens, caches, this.#meta(ns));
|
|
209
|
+
saveKvCache(path, tokens, caches, this.#meta(ns), this.#codecs);
|
|
202
210
|
return this.#indexStored(path, tokens, caches, ns);
|
|
203
211
|
} catch (err) {
|
|
204
212
|
return this.#storeFailed(path, err);
|
|
@@ -210,21 +218,75 @@ export class SsdCacheStore {
|
|
|
210
218
|
* yields the event loop between tensors so serving interleaves.
|
|
211
219
|
* Caller passes zero-copy CLONES it owns (a consistent snapshot no
|
|
212
220
|
* matter what the live entry does meanwhile) and disposes them after.
|
|
213
|
-
* `
|
|
214
|
-
* server passes the gateway's
|
|
215
|
-
*
|
|
216
|
-
async storeAsync(
|
|
221
|
+
* `runStep` owns every per-tensor step (see saveKvCacheAsync) — the
|
|
222
|
+
* server passes the gateway's exclusive runner so a request cannot start
|
|
223
|
+
* between an idle check and the blocking MLX readback. */
|
|
224
|
+
async storeAsync(
|
|
225
|
+
tokens: number[], caches: Cache[], ns = "",
|
|
226
|
+
runStep?: <T>(step: () => T) => Promise<T>,
|
|
227
|
+
): Promise<boolean> {
|
|
217
228
|
const dir = join(this.#root, nsHash(ns));
|
|
218
229
|
const path = join(dir, `${randomUUID()}.mlxkv`);
|
|
219
230
|
try {
|
|
220
231
|
mkdirSync(dir, { recursive: true });
|
|
221
|
-
await saveKvCacheAsync(path, tokens, caches, this.#meta(ns),
|
|
232
|
+
await saveKvCacheAsync(path, tokens, caches, this.#meta(ns), runStep, this.#codecs);
|
|
222
233
|
return this.#indexStored(path, tokens, caches, ns);
|
|
223
234
|
} catch (err) {
|
|
224
235
|
return this.#storeFailed(path, err);
|
|
225
236
|
}
|
|
226
237
|
}
|
|
227
238
|
|
|
239
|
+
/** Longest durable in-flight generation for an identical request. The
|
|
240
|
+
* checkpoint namespace is separate from the ordinary prompt-cache tier,
|
|
241
|
+
* so incomplete assistant output can never seed an unrelated prefill. */
|
|
242
|
+
findGenerationCheckpoint(
|
|
243
|
+
prompt: number[], key: string, cacheNs = "",
|
|
244
|
+
): SsdIndexEntry | null {
|
|
245
|
+
let best: SsdIndexEntry | null = null;
|
|
246
|
+
for (const entry of this.#index) {
|
|
247
|
+
const checkpoint = entry.generationCheckpoint;
|
|
248
|
+
if (!checkpoint || checkpoint.key !== key || checkpoint.cacheNs !== cacheNs)
|
|
249
|
+
continue;
|
|
250
|
+
if (checkpoint.originalPromptTokens !== prompt.length) continue;
|
|
251
|
+
if (entry.tokens.length <= prompt.length) continue;
|
|
252
|
+
if (commonPrefixLength(entry.tokens, prompt) !== prompt.length) continue;
|
|
253
|
+
if (!best || entry.tokens.length > best.tokens.length) best = entry;
|
|
254
|
+
}
|
|
255
|
+
return best;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** Persist one in-flight generation atomically. A completed newer
|
|
259
|
+
* checkpoint supersedes older checkpoints for the same request only after
|
|
260
|
+
* its rename succeeds, so a crash during a write leaves the prior point. */
|
|
261
|
+
async storeGenerationCheckpoint(
|
|
262
|
+
tokens: number[], caches: Cache[], checkpoint: NonNullable<KvSaveMeta["generationCheckpoint"]>,
|
|
263
|
+
): Promise<boolean> {
|
|
264
|
+
const ns = `__generation_checkpoint__:${checkpoint.key}`;
|
|
265
|
+
const dir = join(this.#root, nsHash(ns));
|
|
266
|
+
const path = join(dir, `${randomUUID()}.mlxkv`);
|
|
267
|
+
try {
|
|
268
|
+
mkdirSync(dir, { recursive: true });
|
|
269
|
+
await saveKvCacheAsync(path, tokens, caches, {
|
|
270
|
+
...this.#meta(ns), generationCheckpoint: checkpoint,
|
|
271
|
+
}, undefined, this.#codecs);
|
|
272
|
+
const stored = this.#indexStored(path, tokens, caches, ns, checkpoint);
|
|
273
|
+
if (stored) {
|
|
274
|
+
for (const entry of [...this.#index]) {
|
|
275
|
+
if (entry.path === path) continue;
|
|
276
|
+
if (entry.generationCheckpoint?.key === checkpoint.key) this.remove(entry.path);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return stored;
|
|
280
|
+
} catch (err) {
|
|
281
|
+
return this.#storeFailed(path, err);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
removeGenerationCheckpoints(key: string): void {
|
|
286
|
+
for (const entry of [...this.#index])
|
|
287
|
+
if (entry.generationCheckpoint?.key === key) this.remove(entry.path);
|
|
288
|
+
}
|
|
289
|
+
|
|
228
290
|
#meta(ns: string): KvSaveMeta {
|
|
229
291
|
return {
|
|
230
292
|
modelId: this.#opts.modelId,
|
|
@@ -234,7 +296,10 @@ export class SsdCacheStore {
|
|
|
234
296
|
};
|
|
235
297
|
}
|
|
236
298
|
|
|
237
|
-
#indexStored(
|
|
299
|
+
#indexStored(
|
|
300
|
+
path: string, tokens: number[], caches: Cache[], ns: string,
|
|
301
|
+
generationCheckpoint?: NonNullable<KvSaveMeta["generationCheckpoint"]>,
|
|
302
|
+
): boolean {
|
|
238
303
|
const st = statSync(path);
|
|
239
304
|
if (st.size > this.#opts.maxBytes) {
|
|
240
305
|
rmSync(path, { force: true });
|
|
@@ -260,7 +325,10 @@ export class SsdCacheStore {
|
|
|
260
325
|
if (commonPrefixLength(e.tokens, tokens) === e.tokens.length) this.remove(e.path);
|
|
261
326
|
}
|
|
262
327
|
}
|
|
263
|
-
this.#index.push({
|
|
328
|
+
this.#index.push({
|
|
329
|
+
path, ns, tokens, bytes: st.size, mtimeMs: Date.now(), trimmable,
|
|
330
|
+
...(generationCheckpoint ? { generationCheckpoint } : {}),
|
|
331
|
+
});
|
|
264
332
|
this.stats.spills++;
|
|
265
333
|
this.evictToCap();
|
|
266
334
|
return true;
|
package/src/ssd-durability.ts
CHANGED
|
@@ -115,9 +115,9 @@ export class SsdDurabilityCoordinator {
|
|
|
115
115
|
this.#attempts.set(key, task);
|
|
116
116
|
try {
|
|
117
117
|
const outcome = await task;
|
|
118
|
-
if (outcome
|
|
118
|
+
if (outcome === "stored" && this.#dirty.get(key) === rec)
|
|
119
119
|
this.#dirty.delete(key);
|
|
120
|
-
if (outcome
|
|
120
|
+
if (outcome !== "stored" && !force && this.#dirty.get(key) === rec)
|
|
121
121
|
this.#arm(key, this.busyRetryMs);
|
|
122
122
|
return outcome;
|
|
123
123
|
} finally {
|
|
@@ -129,6 +129,7 @@ export class SsdDurabilityCoordinator {
|
|
|
129
129
|
let snap: SpillItem | null = null;
|
|
130
130
|
try {
|
|
131
131
|
snap = await this.gateway.runExclusive(async () => {
|
|
132
|
+
if (this.isAlreadyDurable(rec.tokens, rec.ns)) return null;
|
|
132
133
|
const entry = this.promptCache.findExact(rec.tokens, rec.ns);
|
|
133
134
|
if (!entry) return null;
|
|
134
135
|
return {
|
|
@@ -150,7 +151,7 @@ export class SsdDurabilityCoordinator {
|
|
|
150
151
|
this.#timers.clear();
|
|
151
152
|
|
|
152
153
|
let flushedSnapshots = 0;
|
|
153
|
-
|
|
154
|
+
const missing = new Map<string, DirtySnapshot>();
|
|
154
155
|
const droppedBefore = this.spillQueue.droppedCount;
|
|
155
156
|
const failedBefore = this.spillQueue.failedCount;
|
|
156
157
|
|
|
@@ -160,22 +161,33 @@ export class SsdDurabilityCoordinator {
|
|
|
160
161
|
|
|
161
162
|
// Flush one entry at a time. This prevents the queue cap from dropping a
|
|
162
163
|
// boundary snapshot while a large final snapshot is already in flight.
|
|
164
|
+
// Attempt each record version once. Missing state stays dirty, so a
|
|
165
|
+
// second flush cannot report success without a real durable snapshot.
|
|
166
|
+
const attempted = new Set<DirtySnapshot>();
|
|
163
167
|
while (this.#dirty.size > 0) {
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
for (const key of
|
|
167
|
-
if (
|
|
168
|
+
const records = [...this.#dirty.entries()].filter(([, rec]) => !attempted.has(rec));
|
|
169
|
+
if (records.length === 0) break;
|
|
170
|
+
for (const [key, rec] of records) {
|
|
171
|
+
if (this.#dirty.get(key) !== rec) continue;
|
|
172
|
+
attempted.add(rec);
|
|
168
173
|
const outcome = await this.#attempt(key, true);
|
|
169
174
|
await this.spillQueue.drain();
|
|
170
175
|
if (outcome === "stored") {
|
|
171
176
|
flushedSnapshots++;
|
|
172
|
-
progressed = true;
|
|
173
177
|
} else if (outcome === "missing") {
|
|
174
|
-
|
|
175
|
-
progressed = true;
|
|
178
|
+
missing.set(key, rec);
|
|
176
179
|
}
|
|
177
180
|
}
|
|
178
|
-
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// A later trimmable snapshot may now cover a superseded RAM ancestor.
|
|
184
|
+
// Reconcile against committed SSD coverage after all writes settle;
|
|
185
|
+
// never clear a missing prefix merely because another write succeeded.
|
|
186
|
+
for (const [key, rec] of missing) {
|
|
187
|
+
if (!this.isAlreadyDurable(rec.tokens, rec.ns)) continue;
|
|
188
|
+
if (this.#dirty.get(key) === rec) this.#dirty.delete(key);
|
|
189
|
+
missing.delete(key);
|
|
190
|
+
flushedSnapshots++;
|
|
179
191
|
}
|
|
180
192
|
|
|
181
193
|
const stats = this.stats;
|
|
@@ -186,9 +198,9 @@ export class SsdDurabilityCoordinator {
|
|
|
186
198
|
stats.pendingSpills === 0 &&
|
|
187
199
|
stats.droppedSpills === droppedBefore &&
|
|
188
200
|
stats.failedSpills === failedBefore &&
|
|
189
|
-
|
|
201
|
+
missing.size === 0,
|
|
190
202
|
flushedSnapshots,
|
|
191
|
-
missingSnapshots,
|
|
203
|
+
missingSnapshots: missing.size,
|
|
192
204
|
elapsedMs: performance.now() - started,
|
|
193
205
|
};
|
|
194
206
|
}
|
package/src/tool-call.ts
CHANGED
|
@@ -161,7 +161,7 @@ export function parseToolCalls(text: string): ParsedToolCall[] {
|
|
|
161
161
|
|
|
162
162
|
const TOOL_CALL_BLOCK_RE = /<tool_call>\s*([\s\S]*?)\s*<\/tool_call>/gi;
|
|
163
163
|
const XML_FUNCTION_EQUALS_RE = /^\s*<function=([^>\s]+)>\s*([\s\S]*?)\s*<\/function>\s*$/i;
|
|
164
|
-
const XML_PARAMETER_EQUALS_RE = /<parameter=([^>\s]+)
|
|
164
|
+
const XML_PARAMETER_EQUALS_RE = /<parameter=([^>\s]+)>([\s\S]*?)<\/parameter>/gi;
|
|
165
165
|
const GLM52_ARG_KEY_RE = /<arg_key>/i;
|
|
166
166
|
const GLM52_ARG_PAIR_RE = /<arg_key>\s*([\s\S]*?)\s*<\/arg_key>\s*<arg_value>\s*([\s\S]*?)\s*<\/arg_value>/gi;
|
|
167
167
|
const XML_FUNCTION_ATTR_RE = /<function\s+name=["']([^"']+)["']\s*>\s*([\s\S]*?)\s*<\/function>/gi;
|
|
@@ -208,11 +208,9 @@ function toolParameterSchema(
|
|
|
208
208
|
return null;
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
-
/**
|
|
212
|
-
*
|
|
213
|
-
|
|
214
|
-
function decodeToolValue(value: string, schema: Record<string, unknown> | null): unknown {
|
|
215
|
-
const text = decodeXml(value);
|
|
211
|
+
/** Decode a value after its caller removes protocol-specific delimiters.
|
|
212
|
+
* String-typed params stay raw; everything else is JSON-decoded when possible. */
|
|
213
|
+
function decodeToolValue(text: string, schema: Record<string, unknown> | null): unknown {
|
|
216
214
|
if (!text) return "";
|
|
217
215
|
const types = schemaTypeNames(schema);
|
|
218
216
|
if (types.size && [...types].every((t) => t === "string" || t === "null")) return text;
|
|
@@ -546,23 +544,59 @@ function parseJsonToolCall(block: string): ParsedToolCall | null {
|
|
|
546
544
|
return repairs.length ? { ...call, repaired: true, repairs } : call;
|
|
547
545
|
}
|
|
548
546
|
|
|
549
|
-
function parseXmlEqualsToolCall(block: string, tools: ToolSpec[]): ParsedToolCall | null {
|
|
547
|
+
function parseXmlEqualsToolCall(block: string, tools: ToolSpec[], strict = false): ParsedToolCall | null {
|
|
550
548
|
const match = XML_FUNCTION_EQUALS_RE.exec(block);
|
|
551
549
|
XML_FUNCTION_EQUALS_RE.lastIndex = 0;
|
|
552
550
|
if (!match) return null;
|
|
551
|
+
if (strict && (match.index !== 0 || match[0].length !== block.length)) return null;
|
|
553
552
|
const name = match[1]!.trim();
|
|
554
553
|
const body = match[2]!;
|
|
555
554
|
const args: Record<string, unknown> = {};
|
|
555
|
+
let cursor = 0;
|
|
556
556
|
for (const param of body.matchAll(XML_PARAMETER_EQUALS_RE)) {
|
|
557
|
+
if (strict && body.slice(cursor, param.index).trim()) return null;
|
|
557
558
|
const key = param[1]!.trim();
|
|
558
559
|
if (!key) throw new Error(`tool '${name}' contains an empty parameter name`);
|
|
559
|
-
|
|
560
|
+
if (strict && Object.hasOwn(args, key)) return null;
|
|
561
|
+
// Qwen's format carries raw text, not XML entity-encoded text. Match
|
|
562
|
+
// mlx_lm.tool_parsers.qwen3_coder: remove one framing newline at each
|
|
563
|
+
// end, preserving indentation, literal entities and JSON string escapes.
|
|
564
|
+
const value = param[2]!.replace(/^\n|\n$/g, "");
|
|
565
|
+
args[key] = decodeToolValue(value, toolParameterSchema(tools, name, key));
|
|
566
|
+
cursor = param.index + param[0].length;
|
|
560
567
|
}
|
|
561
568
|
XML_PARAMETER_EQUALS_RE.lastIndex = 0;
|
|
569
|
+
if (strict && body.slice(cursor).trim()) return null;
|
|
562
570
|
return { name, arguments: args };
|
|
563
571
|
}
|
|
564
572
|
|
|
565
|
-
|
|
573
|
+
/** One complete envelope, with full consumption and no syntax repair. Used
|
|
574
|
+
* to establish structural fill boundaries, never to verify model predictions. */
|
|
575
|
+
export function parseStrictToolCall(text: string, tools: ToolSpec[]): ParsedToolCall | null {
|
|
576
|
+
const open = "<tool_call>", close = "</tool_call>";
|
|
577
|
+
if (!text.startsWith(open) || !text.endsWith(close)) return null;
|
|
578
|
+
const body = text.slice(open.length, -close.length).trim();
|
|
579
|
+
if (body.includes(open) || body.includes(close)) return null;
|
|
580
|
+
try {
|
|
581
|
+
let call: ParsedToolCall | null;
|
|
582
|
+
if (body.startsWith("{")) {
|
|
583
|
+
// JSON.parse must succeed before the existing payload interpretation.
|
|
584
|
+
JSON.parse(body);
|
|
585
|
+
call = parseJsonToolCall(body);
|
|
586
|
+
} else {
|
|
587
|
+
call = parseXmlEqualsToolCall(body, tools, true) ?? parseGlm52ToolCall(body, tools, true);
|
|
588
|
+
}
|
|
589
|
+
if (!call || call.repaired || !tools.some(tool => toolSpecName(tool) === call.name)) return null;
|
|
590
|
+
return call;
|
|
591
|
+
} catch { return null; }
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/** A textual argument delimiter is complete only after its closing bracket. */
|
|
595
|
+
export function hasCompleteToolValueDelimiter(text: string): boolean {
|
|
596
|
+
return /^(?:<\/parameter>|<\/arg_value>)/.test(text.trimStart());
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function parseGlm52ToolCall(block: string, tools: ToolSpec[], strict = false): ParsedToolCall | null {
|
|
566
600
|
const firstArg = block.search(GLM52_ARG_KEY_RE);
|
|
567
601
|
const name = (firstArg < 0 ? block : block.slice(0, firstArg)).trim();
|
|
568
602
|
if (!name || !/^[^\s<>]+$/.test(name)) return null;
|
|
@@ -576,7 +610,8 @@ function parseGlm52ToolCall(block: string, tools: ToolSpec[]): ParsedToolCall |
|
|
|
576
610
|
if (body.slice(cursor, pair.index).trim()) return null;
|
|
577
611
|
const key = pair[1]!.trim();
|
|
578
612
|
if (!key) throw new Error(`tool '${name}' contains an empty argument name`);
|
|
579
|
-
|
|
613
|
+
if (strict && Object.hasOwn(args, key)) return null;
|
|
614
|
+
args[key] = decodeToolValue(decodeXml(pair[2]!), toolParameterSchema(tools, name, key));
|
|
580
615
|
cursor = pair.index + pair[0].length;
|
|
581
616
|
matched = true;
|
|
582
617
|
}
|
|
@@ -594,7 +629,7 @@ function parseXmlAttrToolCalls(text: string, tools: ToolSpec[]): ParsedToolCall[
|
|
|
594
629
|
for (const param of body.matchAll(XML_PARAM_ATTR_RE)) {
|
|
595
630
|
const key = param[1]!.trim();
|
|
596
631
|
if (!key) throw new Error(`tool '${name}' contains an empty parameter name`);
|
|
597
|
-
args[key] = decodeToolValue(param[2]
|
|
632
|
+
args[key] = decodeToolValue(decodeXml(param[2]!), toolParameterSchema(tools, name, key));
|
|
598
633
|
}
|
|
599
634
|
XML_PARAM_ATTR_RE.lastIndex = 0;
|
|
600
635
|
calls.push({ name, arguments: args });
|
|
@@ -616,9 +651,11 @@ export function parseGeneratedToolCalls(text: string, tools: ToolSpec[]): Parsed
|
|
|
616
651
|
const calls: ParsedToolCall[] = [];
|
|
617
652
|
for (const block of text.matchAll(TOOL_CALL_BLOCK_RE)) {
|
|
618
653
|
const body = block[1]!.trim();
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
654
|
+
// A valid Qwen parameter may itself contain JSON. Dispatch by the outer
|
|
655
|
+
// format before JSON repair can mistake that value for the tool envelope.
|
|
656
|
+
const parsed = /^<function=/i.test(body)
|
|
657
|
+
? parseXmlEqualsToolCall(body, tools)
|
|
658
|
+
: parseJsonToolCall(body) ?? parseGlm52ToolCall(body, tools);
|
|
622
659
|
if (!parsed) throw new Error("unsupported tool_call payload format");
|
|
623
660
|
calls.push(parsed);
|
|
624
661
|
}
|
package/src/train/flash-cce.ts
CHANGED
|
@@ -728,7 +728,7 @@ const BWD_SG_SOURCE = String.raw`
|
|
|
728
728
|
// (flipped 2026-07-02 — the kernel-perf-review backlog #1 flip). The old
|
|
729
729
|
// 0.66%→2.7% dh-accuracy scare was on RANDOM targets (flat softmax, the filter's
|
|
730
730
|
// worst case — see PLAN.md "filter-on-real-data"); the real-data gate
|
|
731
|
-
// (
|
|
731
|
+
// (flash-cce-filter-realdata.ts (deleted 2026-08-23; git history)) measured, on REAL chunk-ORPO
|
|
732
732
|
// data (M1 Max, 2026-07-02): CPM5 eps 1e-5 = 0.343% dh for 1.41×; e4b = 0.158% dh
|
|
733
733
|
// for 1.70× — and the teacher-forced full-logits fidelity shows the filter's added
|
|
734
734
|
// error is far below the flash-vs-full fp-reassociation scatter that already
|
|
@@ -781,7 +781,7 @@ function bwdSgKernel(dimTiles: number, filterEps: string, blockEps: string): Met
|
|
|
781
781
|
// accumulated into D's frags LANE-LOCALLY (frag += frag — no get_coord math, the
|
|
782
782
|
// per-lane element mapping is identical between temp and D). dh atomic-added
|
|
783
783
|
// across the NBLK vocab-block programs. Validated dh ~1e-4 rel vs autograd.
|
|
784
|
-
// (Design + parity:
|
|
784
|
+
// (Design + parity: steel-bwd-htile-test.ts (deleted 2026-08-23; git history).)
|
|
785
785
|
// PHASE-2 DEQUANT: the Wd[32,HTw] staging uses MLX's vectorized+fused
|
|
786
786
|
// QuantizedBlockLoader (the SAME loader the forward uses), NOT a manual scalar
|
|
787
787
|
// dequant. The wrinkle: phase-2's GEMM contracts over vocab (K=32) and outputs H
|
package/src/train/forward.ts
CHANGED
|
@@ -31,6 +31,8 @@ import type { RuntimeModel } from "../model/factory";
|
|
|
31
31
|
* backward recompute, and a stateful (appending) cache would corrupt on the
|
|
32
32
|
* second run. */
|
|
33
33
|
export class TrainingCache implements Cache {
|
|
34
|
+
/** Training-only adapter; never admitted, merged, or persisted. */
|
|
35
|
+
signature(): string { return "train:training"; }
|
|
34
36
|
offset = 0;
|
|
35
37
|
updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
|
|
36
38
|
// Return fresh views: the caller disposes its input k/v right after (its
|
|
@@ -67,6 +69,8 @@ export class TrainingCache implements Cache {
|
|
|
67
69
|
* threw on the missing conv/recurrent/advance surface (the recorded
|
|
68
70
|
* "mlx-bun perplexity cannot score qwen3_5" gap). */
|
|
69
71
|
export class TrainingSSMCache implements Cache {
|
|
72
|
+
/** Training-only adapter; never admitted, merged, or persisted. */
|
|
73
|
+
signature(): string { return "train:training-ssm"; }
|
|
70
74
|
offset = 0;
|
|
71
75
|
specRound: null = null;
|
|
72
76
|
get conv(): MlxArray | null { return null; }
|
|
@@ -192,6 +196,8 @@ export function buildBatchedPadMask(
|
|
|
192
196
|
* forwardLayers only disposes the mask it pulled from makeMask, so makeMask
|
|
193
197
|
* hands back a fresh view each call and keeps the canonical copy itself. */
|
|
194
198
|
class BatchedMaskCache implements Cache {
|
|
199
|
+
/** Training-only adapter; never admitted, merged, or persisted. */
|
|
200
|
+
signature(): string { return "train:batched-mask"; }
|
|
195
201
|
constructor(
|
|
196
202
|
private readonly inner: Cache,
|
|
197
203
|
private readonly window: number | null,
|
package/src/train/loss.ts
CHANGED
|
@@ -1190,7 +1190,7 @@ export function fusedRespLogpMean(
|
|
|
1190
1190
|
// [chunk,vocabBlock]. Otherwise the whole-vocab analytic head below.
|
|
1191
1191
|
const vb = vocabBlock > 0 && vocabBlock < V ? vocabBlock : 0;
|
|
1192
1192
|
// Auto-dispatch by M (kernel backlog #3; measured 2026-07-02 on the M1 Max,
|
|
1193
|
-
//
|
|
1193
|
+
// head-dispatch-sweep.ts (deleted 2026-08-23; git history)): the fused QM head is FASTER
|
|
1194
1194
|
// at short M (e4b M=64 1.6×, M=512 1.13×; CPM5 faster at EVERY M) — the
|
|
1195
1195
|
// flash head's win is residency, plus time at long M on e4b (M≥2048 after
|
|
1196
1196
|
// the 2026-07-02 filter/blockMax flip; 8K: 10.7 vs 13.2 s). So a
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// prompt dominates (our chunk/document fine-tunes), ~0 when the response does.
|
|
12
12
|
//
|
|
13
13
|
// Bit-exact with the two-forward path (validated in
|
|
14
|
-
//
|
|
14
|
+
// prefix-shared-parity.ts (deleted 2026-08-23; git history)): each predicted token uses a hidden
|
|
15
15
|
// conditioned on exactly the same context + RoPE positions —
|
|
16
16
|
// chosen[k] from H[P-1+k] (k=0..Rc-1; H[P-1] = prompt's last)
|
|
17
17
|
// rejected[k] from H[P-1] (k=0) / H[P+Rc+k-1] (k≥1)
|
|
@@ -62,6 +62,8 @@ export function blockSparseMask(P: number, Rc: number, Rr: number): MlxArray {
|
|
|
62
62
|
* TrainingCache) but `makeMask` returns the block-sparse mask. One per layer;
|
|
63
63
|
* MiniCPM5's runLayerRange calls makeMask once and disposes the returned arr. */
|
|
64
64
|
export class PrefixSharedCache implements Cache {
|
|
65
|
+
/** Training-only adapter; never admitted, merged, or persisted. */
|
|
66
|
+
signature(): string { return "train:prefix-shared"; }
|
|
65
67
|
offset = 0;
|
|
66
68
|
constructor(
|
|
67
69
|
private readonly P: number, private readonly Rc: number, private readonly Rr: number,
|
|
@@ -371,6 +373,8 @@ export function blockSparsePrefixMaskGemma(P: number, Rc: number, Rr: number, wi
|
|
|
371
373
|
* type serves both (the window arg selects the sliding term). One per DONOR
|
|
372
374
|
* layer (sharers reuse donors' fetched KV). */
|
|
373
375
|
class Gemma4PrefixSharedCache implements Cache {
|
|
376
|
+
/** Training-only adapter; never admitted, merged, or persisted. */
|
|
377
|
+
signature(): string { return "train:gemma4prefix-shared"; }
|
|
374
378
|
offset = 0;
|
|
375
379
|
constructor(private readonly P: number, private readonly Rc: number, private readonly Rr: number) {}
|
|
376
380
|
updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
|
package/src/train/segmented.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// segment-by-segment so only ONE segment's activations are ever live, instead
|
|
3
3
|
// of holding every layer's recompute activations at once (the spike that makes
|
|
4
4
|
// naive gradient checkpointing crash at long context). Design + proof:
|
|
5
|
-
// docs/design/
|
|
5
|
+
// docs/design/orpo-training.md (mechanism validated on a toy in
|
|
6
6
|
// scripts/ckpt-mem-test.ts, SEG mode: 0.000% grad error, peak below per-layer
|
|
7
7
|
// checkpointing; bit-exact on MiniCPM5 under flash attention).
|
|
8
8
|
//
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
// `value_and_grad(sum(stop_grad(dh) (.) segment_forward(...)))` is numerically
|
|
25
25
|
// identical but LEAKS ~one activation buffer per segment per step at the mlx
|
|
26
26
|
// level (measured; not GC/cache/synchronize/reuse reclaimable — see
|
|
27
|
-
// docs/design/
|
|
27
|
+
// docs/design/orpo-training.md §9.4). mlx_vjp takes the cotangent
|
|
28
28
|
// directly, needs no surrogate, and does not leak.
|
|
29
29
|
//
|
|
30
30
|
// IMPORTANT lifetime fact: mlx `eval` does NOT detach — an eval'd array retains
|
|
@@ -130,7 +130,7 @@ export function planSegmentsBySize(nLayers: number, segSize: number): [number, n
|
|
|
130
130
|
// segment_size 1 measures 14.59 GB @8K (+3% step time) vs 17.5 @seg2. Below
|
|
131
131
|
// that needs the [M,V]-bounded SFT head (backlog #8, ~3 GB head-vjp spike)
|
|
132
132
|
// and/or an O(L)-memory attention backward. Evidence:
|
|
133
|
-
//
|
|
133
|
+
// seg-isolation-smoke.ts (deleted 2026-08-23; git history) + MLX_BUN_SEG_MEM_LOG=1 probes.
|
|
134
134
|
|
|
135
135
|
/** Detach `a` into a graph-free LEAF holding its exact (evaluated) bytes, then
|
|
136
136
|
* dispose `a`. mlx retains the upstream graph after `eval`, so a segment
|
|
@@ -1517,6 +1517,8 @@ export class SegmentedBackwardOrpoGemma4 {
|
|
|
1517
1517
|
* and null for full layers; the window arg selects the sliding term. One per DONOR
|
|
1518
1518
|
* layer (sharers reuse donors' fetched KV inside runLayerRange). */
|
|
1519
1519
|
class Gemma4PrefixSharedSegCache implements Cache {
|
|
1520
|
+
/** Training-only adapter; never admitted, merged, or persisted. */
|
|
1521
|
+
signature(): string { return "train:gemma4-prefix-shared-seg"; }
|
|
1520
1522
|
offset = 0;
|
|
1521
1523
|
constructor(private readonly P: number, private readonly Rc: number, private readonly Rr: number) {}
|
|
1522
1524
|
updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
|
package/src/train/trainer.ts
CHANGED
|
@@ -82,7 +82,7 @@ export interface TrainConfig {
|
|
|
82
82
|
* beats per-layer checkpointing at long context, where naive checkpointing
|
|
83
83
|
* holds every layer's recompute activations at once and crashes. B=1 SFT
|
|
84
84
|
* only (the responseOnlyCe path); mutually exclusive with gradCheckpoint.
|
|
85
|
-
* Numerically identical to off. See docs/design/
|
|
85
|
+
* Numerically identical to off. See docs/design/orpo-training.md. */
|
|
86
86
|
segmentSize: number;
|
|
87
87
|
/** Keep ALL eval-step checkpoints (full mountable adapters under
|
|
88
88
|
* checkpoints/step-<NNNNN>-val<loss>/) + write metrics.json. Off by default
|
|
@@ -440,12 +440,12 @@ export async function trainLora(
|
|
|
440
440
|
|
|
441
441
|
// ---------------------------------------------------------------------------
|
|
442
442
|
// Segment planning: uniform planSegmentsBySize only. A full-attention
|
|
443
|
-
// ISOLATION planner (kernel-review backlog #2 /
|
|
443
|
+
// ISOLATION planner (kernel-review backlog #2 / docs/design/orpo-training.md
|
|
444
444
|
// §5) was built and A/B-measured 2026-07-02: ZERO peak win — mlx's sdpa
|
|
445
445
|
// backward is O(L²) for EVERY layer (~3.5 GB/layer @8K e4b), sliding included,
|
|
446
446
|
// so the worst segment is set by layer count alone and segment_size is the
|
|
447
447
|
// whole knob (seg1 = 14.59 GB @8K, +3% step time — fits the 24 GB M4 Pro).
|
|
448
|
-
// Evidence:
|
|
448
|
+
// Evidence: seg-isolation-smoke.ts (deleted 2026-08-23; git history); note in segmented.ts.
|
|
449
449
|
// ---------------------------------------------------------------------------
|
|
450
450
|
|
|
451
451
|
// ---------------------------------------------------------------------------
|
|
@@ -473,7 +473,7 @@ async function sftLoop(
|
|
|
473
473
|
// Segmented backward: stream the SFT backward segment-by-segment (only one
|
|
474
474
|
// segment's activations live at a time). Phase A is MiniCPM5 SFT B=1 only;
|
|
475
475
|
// it replaces the single value_and_grad below and is mutually exclusive with
|
|
476
|
-
// gradient checkpointing (see docs/design/
|
|
476
|
+
// gradient checkpointing (see docs/design/orpo-training.md).
|
|
477
477
|
const useSegmented = cfg.segmentSize > 0;
|
|
478
478
|
let segmented: SegmentedBackward | SegmentedBackwardGemma4 | null = null;
|
|
479
479
|
if (useSegmented) {
|
|
@@ -532,13 +532,13 @@ async function sftLoop(
|
|
|
532
532
|
// hand-rolled flash kernel is an opt-in (MLX_BUN_TRAIN_ATTN=flash) O(L)-memory
|
|
533
533
|
// path for memory-bound long context — now correct (its dK-transpose and
|
|
534
534
|
// dQ-causal-barrier bugs were fixed; FD-validated in
|
|
535
|
-
//
|
|
535
|
+
// flash-fd-check.ts (deleted 2026-08-23; git history)), though ~30× slower than ops.sdpa.
|
|
536
536
|
// Cleared in finally.
|
|
537
537
|
//
|
|
538
538
|
// GEMMA GUARD (same convention as the launcher's env sanitization in
|
|
539
539
|
// cli.ts / job.ts, but enforced at the trainer): e4b on this path SIGTRAPed
|
|
540
540
|
// (uncatchable native crash) at multi-K sequence lengths (>=2K, reproduced in
|
|
541
|
-
//
|
|
541
|
+
// segmented-grad-test-e4b.ts (deleted 2026-08-23; git history); docs/reference/training.md)
|
|
542
542
|
// and has NOT been re-validated at that scale since the two kernel fixes —
|
|
543
543
|
// the regression tests stop at T<=256. cli.ts even defaults e4b seq to 8192,
|
|
544
544
|
// so a stale `export MLX_BUN_TRAIN_ATTN=flash` from a MiniCPM5 experiment
|
package/src/vision/prompt.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// n = min(ceil(duration_ms/40), 750) (processing_gemma4.py) — embed the full
|
|
9
9
|
// sequence, then overwrite the soft-token rows with the towers' features.
|
|
10
10
|
//
|
|
11
|
-
// Attention semantics (
|
|
11
|
+
// Attention semantics (docs/design/generic-model-support.md §3.3, resolved vs the oracle):
|
|
12
12
|
// - image runs attend bidirectionally among themselves (bidirMask), BUT
|
|
13
13
|
// any audio token in the prompt disables the vision overlay entirely —
|
|
14
14
|
// mixed image+audio prompts run fully causal (bidirMask = null);
|