mlx-bun 0.0.12 → 0.2.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 +40 -24
- package/bin/mlx-bun.mjs +1 -1
- package/docs/{design → archive}/hlg-sampling.md +3 -3
- package/docs/{investigations → archive/investigations}/hlg-sampling-investigation.md +1 -1
- package/docs/archive/investigations/pareto-specialized-runtime-findings.md +92 -0
- package/docs/{planning → archive/planning}/chat-ui-vision.md +1 -1
- package/docs/archive/planning/release-notes-v0.0.13.md +77 -0
- package/docs/{planning → archive/planning}/release-notes-v0.0.9.md +1 -1
- package/docs/{planning → archive/planning}/website-readme-pass-plan.md +1 -1
- package/docs/design/colibri-glm52-port.md +8 -1
- package/docs/design/compat-cli-surface-design.md +1 -1
- package/docs/design/decode-speed-program.md +1 -1
- package/docs/design/docs-reorg-plan.md +1 -1
- package/docs/design/dspark-serving-program.md +1 -1
- package/docs/design/dspark-speculative-decoding.md +53 -3
- package/docs/design/grammar-spec-batching-integration.md +1 -1
- package/docs/design/mlx-lm-serving-execution-seam.md +81 -0
- package/docs/design/orpo-dynamic-lambda.md +1 -1
- package/docs/design/orpo-future-enhancements.md +1 -1
- package/docs/design/orpo-training.md +2 -2
- package/docs/design/parity-tier-dag.md +1 -1
- package/docs/design/repo-cleanup-plan.md +46 -4
- package/docs/design/segmented-backward-training.md +2 -2
- package/docs/design/ssd-kv-cold-tier.md +127 -0
- package/docs/design/turboquant-weights.md +467 -0
- package/docs/design/w4a16-compute-precision-spike.md +1 -1
- package/docs/design/web-chat-redesign.md +5 -5
- package/docs/planning/chunk-task-roadmap.md +1 -1
- package/docs/planning/curve-sampler-research-plan.md +4 -4
- package/docs/planning/memory-docs-and-dag-plan.md +1 -1
- package/docs/planning/release-notes-v0.2.0.md +49 -0
- package/docs/planning/video-series.md +421 -0
- package/docs/reference/cli.md +15 -4
- package/docs/reference/distribution.md +1 -0
- package/docs/reference/features-matrix.md +3 -1
- package/docs/reference/library-api.md +41 -1
- package/docs/reference/models.md +33 -0
- package/docs/reference/server-api.md +86 -19
- package/docs/reference/server-config.md +57 -33
- package/package.json +2 -2
- package/src/anthropic.ts +92 -3
- package/src/chat-template.ts +34 -1
- package/src/cli.ts +131 -36
- package/src/config.ts +10 -0
- package/src/eval/kl.ts +27 -20
- package/src/eval/runner.ts +23 -4
- package/src/eval/tasks/bfcl.ts +2 -1
- package/src/eval/tasks/gsm8k.ts +2 -1
- package/src/eval/tasks/hashhop.ts +2 -1
- package/src/eval/tasks/humaneval.ts +5 -4
- package/src/eval/tasks/ifeval.ts +2 -1
- package/src/eval/tasks/mmlu.ts +2 -1
- package/src/expert-io.ts +3 -1
- package/src/expert-offload.ts +3 -1
- package/src/expert-trace.ts +3 -1
- package/src/fit.ts +24 -43
- package/src/flags.ts +7 -9
- package/src/generate.ts +107 -92
- package/src/grammar.ts +2 -1
- package/src/harness-pi.ts +1 -1
- package/src/index.ts +20 -0
- package/src/jobs/job-entry.ts +3 -2
- package/src/kv-scheme.ts +199 -0
- package/src/kv-store.ts +370 -205
- package/src/media-fetch.ts +19 -3
- package/src/memory/model.ts +2 -1
- package/src/memory/vault.ts +2 -1
- package/src/memory-plan.ts +18 -0
- package/src/mlx/ffi.ts +8 -11
- package/src/mlx/ops.ts +31 -6
- package/src/model/batched-mask.ts +1 -1
- package/src/model/batched-quant.ts +1 -1
- package/src/model/batched-rotating-quant.ts +43 -97
- package/src/model/batched-rotating-state.ts +73 -0
- package/src/model/batched-rotating.ts +56 -105
- package/src/model/batched-row-storage.ts +127 -0
- package/src/model/compiled-decode.ts +3 -2
- package/src/model/factory.ts +44 -49
- package/src/model/gemma4-base.ts +163 -10
- package/src/model/generated/gemma4-12b.ts +50 -146
- package/src/model/generated/gemma4-26b.ts +32 -92
- package/src/model/generated/gemma4-e4b.ts +26 -74
- package/src/model/glm52-cache.ts +19 -9
- package/src/model/glm52-memory.ts +28 -1
- package/src/model/minicpm5.ts +2 -1
- package/src/model/profile.ts +433 -0
- package/src/model/qwen3-delta.ts +98 -2
- package/src/model/qwen3-mrope.ts +252 -0
- package/src/model/qwen3_5.ts +217 -25
- package/src/native/frame_extract.swift +140 -0
- package/src/native-pack.ts +13 -6
- package/src/pi-web.ts +6 -5
- package/src/prompt-cache.ts +2 -2
- package/src/quantize/atomic-output.ts +38 -0
- package/src/quantize/calibration.ts +2 -1
- package/src/quantize/config-writer.ts +6 -0
- package/src/quantize/index.ts +28 -1
- package/src/quantize/job.ts +11 -0
- package/src/quantize/quantizer.ts +265 -100
- package/src/quantize/rotate.ts +603 -0
- package/src/quantize/safetensors-writer.ts +79 -1
- package/src/quantize/sensitivity.ts +41 -59
- package/src/quantize/weight-transform.ts +292 -0
- package/src/responses.ts +102 -2
- package/src/runtime-config.ts +67 -0
- package/src/sampler.ts +272 -0
- package/src/serve/admin-routes.ts +184 -0
- package/src/serve/aux-routes.ts +132 -0
- package/src/serve/batch-scheduler.ts +200 -87
- package/src/serve/completion-executor.ts +346 -0
- package/src/serve/completion-sink.ts +157 -0
- package/src/serve/discovery-routes.ts +205 -0
- package/src/serve/generation-gateway.ts +116 -109
- package/src/serve/kv-budget.ts +14 -0
- package/src/serve/lab-routes.ts +338 -0
- package/src/serve/model-admin-routes.ts +153 -0
- package/src/serve/prompt-response-trace.ts +147 -0
- package/src/serve/request-plan.ts +158 -0
- package/src/serve/static-routes.ts +101 -0
- package/src/server.ts +768 -1367
- package/src/spec/dflash-source.ts +2 -1
- package/src/spec/dspark/module-dflash.ts +1 -1
- package/src/spec/glm52-mtp-source.ts +8 -0
- package/src/spec/qwen-mtp-source.ts +431 -0
- package/src/spec/serve-loop.ts +36 -68
- package/src/ssd-cache.ts +17 -14
- package/src/ssd-durability.ts +195 -0
- package/src/train/flash-cce.ts +12 -11
- package/src/train/forward.ts +32 -1
- package/src/train/loss.ts +2 -1
- package/src/train/segmented.ts +4 -3
- package/src/train/trainer.ts +3 -2
- package/src/vision/preprocess.ts +1 -1
- package/src/vision/prompt.ts +47 -1
- package/src/vision/qwen3vl-preprocess.ts +409 -0
- package/src/vision/qwen3vl-prompt.ts +196 -0
- package/src/vision/qwen3vl-tower.ts +513 -0
- package/src/vision/video-frames.ts +148 -0
- package/src/web/app.js +10 -3
- package/src/web/src/chat.ts +12 -5
- package/src/weights.ts +81 -26
- /package/docs/{investigations → archive/investigations}/chunk-finetune-experiment.md +0 -0
- /package/docs/{investigations → archive/investigations}/colibri-oracle-pin.md +0 -0
- /package/docs/{investigations → archive/investigations}/curve-bisector-routing.md +0 -0
- /package/docs/{investigations → archive/investigations}/curve-runs/bisector-route.json +0 -0
- /package/docs/{investigations → archive/investigations}/curve-runs/bisector-trace.json +0 -0
- /package/docs/{investigations → archive/investigations}/curve-runs/curve-terrain.json +0 -0
- /package/docs/{investigations → archive/investigations}/curve-runs/curve-terrain.svg +0 -0
- /package/docs/{investigations → archive/investigations}/curve-terrain.html +0 -0
- /package/docs/{investigations → archive/investigations}/decode-roofline-lookagain.md +0 -0
- /package/docs/{investigations → archive/investigations}/dspark-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/expert-offload-single-user-moe.md +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-ab.html +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-explorer.html +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-figs/cliff.svg +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-figs/cross.svg +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-figs/edges.svg +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-figs/k20-grids.svg +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-figs/k20-peaks.svg +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-figs/lw-curve.svg +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-figs/terrain.svg +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-report.html +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-ab.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-blind-prompts.md +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-blind.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-cliff.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-cross.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-edge.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-eval1.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-frontier.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-grid.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-grid2.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-lw-coarse.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-map.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/run0-original.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runA-recommended.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runAA-cliff-cpm5.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runAB-grid2-e4b.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runAC-peak-e4b-partial.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runAD-ab-samples.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runAE-edge-e4b.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runAF-logit-calibration.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runAG-eval-config2.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runB-gain-variants.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runC-pivots.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runD-oetf.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runE-eotf.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runF-shaper-working.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runG-shaper-sweep.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runH-wsweep.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runI-asweep.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runJ-knobmap.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runK-frontier.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runL-widemap.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runM-frontier-emb.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runN-verify.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runO-frontier-seed5000.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runP-grid-AxSm.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runQ-grid-tgW.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runR-blind-transcript.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runS-cross-coherence.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runT-cross-cpm5.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runU-cross-12b.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runV-lw-e4b.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runW-lw-12b.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runX-lw-cpm5.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runY-cliff-e4b.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runZ-cliff-12b.log +0 -0
- /package/docs/{investigations → archive/investigations}/indexshare-performance-spike.md +0 -0
- /package/docs/{investigations → archive/investigations}/kernel-perf-review-2026-07.md +0 -0
- /package/docs/{investigations → archive/investigations}/lab-build-journal.md +0 -0
- /package/docs/{investigations → archive/investigations}/openwiki-evaluation.md +0 -0
- /package/docs/{investigations → archive/investigations}/orpo-base-uf-experiment-and-directions.md +0 -0
- /package/docs/{investigations → archive/investigations}/orpo-flash-cce-pin-leak.md +0 -0
- /package/docs/{investigations → archive/investigations}/orpo-uf-testing-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/pi-builtin-investigation.md +0 -0
- /package/docs/{investigations → archive/investigations}/segmented-backward-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/starter-model-port-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/steel-flash-cce-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/trainer-validation-experiment.md +0 -0
- /package/docs/{planning → archive/planning}/chat-ui-vision-memory-first.md +0 -0
- /package/docs/{planning → archive/planning}/journal.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.0.10.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.0.11.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.0.12.md +0 -0
- /package/docs/{planning → archive/planning}/web-ui-pass-plan.md +0 -0
package/src/kv-store.ts
CHANGED
|
@@ -46,7 +46,7 @@ import { MlxArray } from "./mlx/array";
|
|
|
46
46
|
import type { Dtype } from "./mlx/ffi";
|
|
47
47
|
import * as ops from "./mlx/ops";
|
|
48
48
|
import {
|
|
49
|
-
type Cache, KVCache, RotatingKVCache,
|
|
49
|
+
type Cache, cacheSignature, KVCache, RotatingKVCache,
|
|
50
50
|
QuantizedKVCache, RotatingQuantizedKVCache,
|
|
51
51
|
TurboQuantKVCache, type TurboQuantTensor,
|
|
52
52
|
} from "./model/gemma4-base";
|
|
@@ -142,6 +142,331 @@ const alignUp = (n: number) => Math.ceil(n / ALIGN) * ALIGN;
|
|
|
142
142
|
* as-laid-out with ringIdx. Slice handles are lazy graph nodes (no GPU
|
|
143
143
|
* materialization until rawBytes at write time). */
|
|
144
144
|
interface TensorSource { arr: MlxArray; disposeAfter: boolean }
|
|
145
|
+
|
|
146
|
+
interface SnapshotContext {
|
|
147
|
+
slots: TensorSlot[];
|
|
148
|
+
push(a: MlxArray, disposeAfter: boolean): void;
|
|
149
|
+
liveSlice(a: MlxArray, upTo: number): MlxArray;
|
|
150
|
+
liveMlaSlice(a: MlxArray, upTo: number): MlxArray;
|
|
151
|
+
pushTriple(t: ops.QuantizedTensor, upTo: number | null): void;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
interface CloneContext {
|
|
155
|
+
view(a: MlxArray): MlxArray;
|
|
156
|
+
liveView(a: MlxArray, upTo: number): MlxArray;
|
|
157
|
+
mlaView(a: MlxArray, upTo: number): MlxArray;
|
|
158
|
+
tripleView(t: ops.QuantizedTensor, upTo: number | null): ops.QuantizedTensor;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
interface LoadContext {
|
|
162
|
+
path: string;
|
|
163
|
+
arr(slot: TensorSlot): MlxArray;
|
|
164
|
+
grownArr(slot: TensorSlot, offset: number): MlxArray;
|
|
165
|
+
triple(slots: TensorSlot[], at: number): ops.QuantizedTensor;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
interface CacheCodec {
|
|
169
|
+
matches(cache: Cache): boolean;
|
|
170
|
+
snapshot(cache: Cache, context: SnapshotContext): CacheHeaderEntry;
|
|
171
|
+
clone(cache: Cache, context: CloneContext): Cache;
|
|
172
|
+
load(entry: CacheHeaderEntry, context: LoadContext): Cache;
|
|
173
|
+
headerTrimmable(entry: CacheHeaderEntry): boolean;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const requireMlaState = (cache: Glm52Cache, operation: string): void => {
|
|
177
|
+
if (!cache.latent || !cache.rope || cache.batchSize === null || cache.offset === 0)
|
|
178
|
+
throw new Error(`cannot ${operation} an empty GLM compressed cache`);
|
|
179
|
+
if (cache.dsa && (!cache.dsa.data || cache.dsa.offset !== cache.offset))
|
|
180
|
+
throw new Error(`cannot ${operation} misaligned GLM DSA state`);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const snapshotMla = (
|
|
184
|
+
kind: Extract<CacheKind, "mla" | "mla-dsa" | "mtp-mla">,
|
|
185
|
+
cache: Cache,
|
|
186
|
+
context: SnapshotContext,
|
|
187
|
+
): CacheHeaderEntry => {
|
|
188
|
+
const c = cache as Glm52Cache;
|
|
189
|
+
requireMlaState(c, "persist");
|
|
190
|
+
context.push(context.liveMlaSlice(c.latent!, c.offset), true);
|
|
191
|
+
context.push(context.liveMlaSlice(c.rope!, c.offset), true);
|
|
192
|
+
if (c.dsa) context.push(context.liveMlaSlice(c.dsa.data!, c.offset), true);
|
|
193
|
+
return {
|
|
194
|
+
kind,
|
|
195
|
+
offset: c.offset,
|
|
196
|
+
kvLoraRank: c.kvLoraRank,
|
|
197
|
+
ropeHeadDim: c.ropeHeadDim,
|
|
198
|
+
...(c.dsa ? { dsaHeadDim: c.dsa.headDim } : {}),
|
|
199
|
+
maxTokens: c.maxTokens,
|
|
200
|
+
tensors: context.slots,
|
|
201
|
+
};
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const cloneMla = (cache: Cache, context: CloneContext): Cache => {
|
|
205
|
+
const c = cache as Glm52Cache;
|
|
206
|
+
requireMlaState(c, "clone");
|
|
207
|
+
const clone = new MLACache({
|
|
208
|
+
kvLoraRank: c.kvLoraRank,
|
|
209
|
+
ropeHeadDim: c.ropeHeadDim,
|
|
210
|
+
...(c.dsa ? { dsa: { headDim: c.dsa.headDim } } : {}),
|
|
211
|
+
maxTokens: c.maxTokens,
|
|
212
|
+
role: c.role,
|
|
213
|
+
});
|
|
214
|
+
clone.restoreCompressedState(
|
|
215
|
+
context.mlaView(c.latent!, c.offset),
|
|
216
|
+
context.mlaView(c.rope!, c.offset),
|
|
217
|
+
c.dsa ? context.mlaView(c.dsa.data!, c.offset) : null,
|
|
218
|
+
c.offset,
|
|
219
|
+
);
|
|
220
|
+
return clone;
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const loadMla = (entry: CacheHeaderEntry, context: LoadContext): Cache => {
|
|
224
|
+
const hasDsa = entry.kind === "mla-dsa";
|
|
225
|
+
if (entry.kind === "mtp-mla" && entry.dsaHeadDim !== undefined)
|
|
226
|
+
throw new Error(`${context.path}: native MTP cache cannot contain DSA state`);
|
|
227
|
+
const cache = new MLACache({
|
|
228
|
+
kvLoraRank: entry.kvLoraRank!,
|
|
229
|
+
ropeHeadDim: entry.ropeHeadDim!,
|
|
230
|
+
...(hasDsa ? { dsa: { headDim: entry.dsaHeadDim! } } : {}),
|
|
231
|
+
maxTokens: entry.maxTokens,
|
|
232
|
+
role: entry.kind === "mtp-mla" ? "mtp" : "target",
|
|
233
|
+
});
|
|
234
|
+
cache.restoreCompressedState(
|
|
235
|
+
context.arr(entry.tensors[0]!),
|
|
236
|
+
context.arr(entry.tensors[1]!),
|
|
237
|
+
hasDsa ? context.arr(entry.tensors[2]!) : null,
|
|
238
|
+
entry.offset,
|
|
239
|
+
);
|
|
240
|
+
return cache;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
/** One authoritative persistence algebra. Adding a cache storage kind must
|
|
244
|
+
* define all four operations here; save/clone/load and SSD eligibility
|
|
245
|
+
* therefore cannot grow independent instanceof/switch chains. */
|
|
246
|
+
const CACHE_CODECS = {
|
|
247
|
+
kv: {
|
|
248
|
+
matches: (cache) => cacheSignature(cache) === "kv:plain",
|
|
249
|
+
snapshot: (cache, context) => {
|
|
250
|
+
const c = cache as KVCache;
|
|
251
|
+
if (!c.keys || !c.values) throw new Error("cannot persist an empty cache");
|
|
252
|
+
context.push(context.liveSlice(c.keys, c.offset), true);
|
|
253
|
+
context.push(context.liveSlice(c.values, c.offset), true);
|
|
254
|
+
return { kind: "kv", offset: c.offset, tensors: context.slots };
|
|
255
|
+
},
|
|
256
|
+
clone: (cache, context) => {
|
|
257
|
+
const c = cache as KVCache;
|
|
258
|
+
if (!c.keys || !c.values) throw new Error("cannot clone an empty cache");
|
|
259
|
+
const clone = new KVCache();
|
|
260
|
+
clone.restoreState(
|
|
261
|
+
context.liveView(c.keys, c.offset),
|
|
262
|
+
context.liveView(c.values, c.offset),
|
|
263
|
+
c.offset,
|
|
264
|
+
);
|
|
265
|
+
return clone;
|
|
266
|
+
},
|
|
267
|
+
load: (entry, context) => {
|
|
268
|
+
const cache = new KVCache();
|
|
269
|
+
cache.restoreState(
|
|
270
|
+
context.grownArr(entry.tensors[0]!, entry.offset),
|
|
271
|
+
context.grownArr(entry.tensors[1]!, entry.offset),
|
|
272
|
+
entry.offset,
|
|
273
|
+
);
|
|
274
|
+
return cache;
|
|
275
|
+
},
|
|
276
|
+
headerTrimmable: () => true,
|
|
277
|
+
},
|
|
278
|
+
rotating: {
|
|
279
|
+
matches: (cache) => cacheSignature(cache) === "kv:rotating-plain",
|
|
280
|
+
snapshot: (cache, context) => {
|
|
281
|
+
const c = cache as RotatingKVCache;
|
|
282
|
+
if (!c.keys || !c.values) throw new Error("cannot persist an empty cache");
|
|
283
|
+
context.push(c.keys, false);
|
|
284
|
+
context.push(c.values, false);
|
|
285
|
+
return { kind: "rotating", offset: c.offset, idx: c.ringIdx,
|
|
286
|
+
maxSize: c.maxSize, tensors: context.slots };
|
|
287
|
+
},
|
|
288
|
+
clone: (cache, context) => {
|
|
289
|
+
const c = cache as RotatingKVCache;
|
|
290
|
+
if (!c.keys || !c.values) throw new Error("cannot clone an empty cache");
|
|
291
|
+
const clone = new RotatingKVCache(c.maxSize);
|
|
292
|
+
clone.restoreState(context.view(c.keys), context.view(c.values), c.offset, c.ringIdx);
|
|
293
|
+
return clone;
|
|
294
|
+
},
|
|
295
|
+
load: (entry, context) => {
|
|
296
|
+
const cache = new RotatingKVCache(entry.maxSize!);
|
|
297
|
+
cache.restoreState(
|
|
298
|
+
context.arr(entry.tensors[0]!), context.arr(entry.tensors[1]!),
|
|
299
|
+
entry.offset, entry.idx!,
|
|
300
|
+
);
|
|
301
|
+
return cache;
|
|
302
|
+
},
|
|
303
|
+
headerTrimmable: (entry) => entry.offset < (entry.maxSize ?? 0),
|
|
304
|
+
},
|
|
305
|
+
qkv: {
|
|
306
|
+
matches: (cache) => cacheSignature(cache).startsWith("kv:quant:"),
|
|
307
|
+
snapshot: (cache, context) => {
|
|
308
|
+
const c = cache as QuantizedKVCache;
|
|
309
|
+
if (!c.keys || !c.values) throw new Error("cannot persist an empty cache");
|
|
310
|
+
context.pushTriple(c.keys, c.offset);
|
|
311
|
+
context.pushTriple(c.values, c.offset);
|
|
312
|
+
return { kind: "qkv", offset: c.offset, groupSize: c.groupSize,
|
|
313
|
+
bits: c.bits, tensors: context.slots };
|
|
314
|
+
},
|
|
315
|
+
clone: (cache, context) => {
|
|
316
|
+
const c = cache as QuantizedKVCache;
|
|
317
|
+
if (!c.keys || !c.values) throw new Error("cannot clone an empty cache");
|
|
318
|
+
const clone = new QuantizedKVCache(c.groupSize, c.bits);
|
|
319
|
+
clone.restoreState(
|
|
320
|
+
context.tripleView(c.keys, c.offset),
|
|
321
|
+
context.tripleView(c.values, c.offset),
|
|
322
|
+
c.offset,
|
|
323
|
+
);
|
|
324
|
+
return clone;
|
|
325
|
+
},
|
|
326
|
+
load: (entry, context) => {
|
|
327
|
+
const cache = new QuantizedKVCache(entry.groupSize!, entry.bits!);
|
|
328
|
+
cache.restoreState(
|
|
329
|
+
context.triple(entry.tensors, 0), context.triple(entry.tensors, 3), entry.offset,
|
|
330
|
+
);
|
|
331
|
+
return cache;
|
|
332
|
+
},
|
|
333
|
+
headerTrimmable: () => true,
|
|
334
|
+
},
|
|
335
|
+
"rotating-qkv": {
|
|
336
|
+
matches: (cache) => cacheSignature(cache).startsWith("kv:rotating-quant:"),
|
|
337
|
+
snapshot: (cache, context) => {
|
|
338
|
+
const c = cache as RotatingQuantizedKVCache;
|
|
339
|
+
if (!c.keys || !c.values) throw new Error("cannot persist an empty cache");
|
|
340
|
+
context.pushTriple(c.keys, null);
|
|
341
|
+
context.pushTriple(c.values, null);
|
|
342
|
+
return { kind: "rotating-qkv", offset: c.offset, idx: c.ringIdx,
|
|
343
|
+
maxSize: c.maxSize, groupSize: c.groupSize, bits: c.bits,
|
|
344
|
+
tensors: context.slots };
|
|
345
|
+
},
|
|
346
|
+
clone: (cache, context) => {
|
|
347
|
+
const c = cache as RotatingQuantizedKVCache;
|
|
348
|
+
if (!c.keys || !c.values) throw new Error("cannot clone an empty cache");
|
|
349
|
+
const clone = new RotatingQuantizedKVCache(c.maxSize, c.groupSize, c.bits);
|
|
350
|
+
clone.restoreState(
|
|
351
|
+
context.tripleView(c.keys, null), context.tripleView(c.values, null),
|
|
352
|
+
c.offset, c.ringIdx,
|
|
353
|
+
);
|
|
354
|
+
return clone;
|
|
355
|
+
},
|
|
356
|
+
load: (entry, context) => {
|
|
357
|
+
const cache = new RotatingQuantizedKVCache(
|
|
358
|
+
entry.maxSize!, entry.groupSize!, entry.bits!,
|
|
359
|
+
);
|
|
360
|
+
cache.restoreState(
|
|
361
|
+
context.triple(entry.tensors, 0), context.triple(entry.tensors, 3),
|
|
362
|
+
entry.offset, entry.idx!,
|
|
363
|
+
);
|
|
364
|
+
return cache;
|
|
365
|
+
},
|
|
366
|
+
headerTrimmable: (entry) => entry.offset < (entry.maxSize ?? 0),
|
|
367
|
+
},
|
|
368
|
+
ssm: {
|
|
369
|
+
matches: (cache) => cacheSignature(cache) === "ssm",
|
|
370
|
+
snapshot: (cache, context) => {
|
|
371
|
+
const c = cache as SSMCache;
|
|
372
|
+
if (!c.conv || !c.recurrent) throw new Error("cannot persist an empty cache");
|
|
373
|
+
context.push(c.conv, false);
|
|
374
|
+
context.push(c.recurrent, false);
|
|
375
|
+
return { kind: "ssm", offset: c.offset, tensors: context.slots };
|
|
376
|
+
},
|
|
377
|
+
clone: (cache, context) => {
|
|
378
|
+
const c = cache as SSMCache;
|
|
379
|
+
if (!c.conv || !c.recurrent) throw new Error("cannot clone an empty cache");
|
|
380
|
+
const clone = new SSMCache();
|
|
381
|
+
clone.conv = context.view(c.conv);
|
|
382
|
+
clone.recurrent = context.view(c.recurrent);
|
|
383
|
+
clone.offset = c.offset;
|
|
384
|
+
return clone;
|
|
385
|
+
},
|
|
386
|
+
load: (entry, context) => {
|
|
387
|
+
const cache = new SSMCache();
|
|
388
|
+
cache.conv = context.arr(entry.tensors[0]!);
|
|
389
|
+
cache.recurrent = context.arr(entry.tensors[1]!);
|
|
390
|
+
cache.offset = entry.offset;
|
|
391
|
+
return cache;
|
|
392
|
+
},
|
|
393
|
+
headerTrimmable: () => false,
|
|
394
|
+
},
|
|
395
|
+
turboquant: {
|
|
396
|
+
matches: (cache) => cacheSignature(cache).startsWith("kv:turboquant:"),
|
|
397
|
+
snapshot: (cache, context) => {
|
|
398
|
+
const c = cache as TurboQuantKVCache;
|
|
399
|
+
const state = c.state();
|
|
400
|
+
if (state.length === 0) throw new Error("cannot persist an empty cache");
|
|
401
|
+
for (const array of state) context.push(array, true);
|
|
402
|
+
return { kind: "turboquant", offset: c.offset, kBits: c.kBits,
|
|
403
|
+
vBits: c.vBits, headDim: c.headDim ?? 0, tensors: context.slots };
|
|
404
|
+
},
|
|
405
|
+
clone: (cache, context) => {
|
|
406
|
+
const c = cache as TurboQuantKVCache;
|
|
407
|
+
const state = c.state();
|
|
408
|
+
if (state.length === 0) throw new Error("cannot clone an empty cache");
|
|
409
|
+
try {
|
|
410
|
+
const [kIdx, kScales, kZeros, vPacked, vScales] = state;
|
|
411
|
+
const clone = new TurboQuantKVCache(c.kBits, c.vBits);
|
|
412
|
+
clone.restoreState({
|
|
413
|
+
kIdx: context.view(kIdx!),
|
|
414
|
+
kScales: context.view(kScales!),
|
|
415
|
+
kZeros: context.view(kZeros!),
|
|
416
|
+
vPacked: context.view(vPacked!),
|
|
417
|
+
vScales: context.view(vScales!),
|
|
418
|
+
}, c.offset, c.headDim!);
|
|
419
|
+
return clone;
|
|
420
|
+
} finally {
|
|
421
|
+
for (const array of state) array.dispose();
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
load: (entry, context) => {
|
|
425
|
+
const cache = new TurboQuantKVCache(entry.kBits!, entry.vBits!);
|
|
426
|
+
const tensors: TurboQuantTensor = {
|
|
427
|
+
kIdx: context.arr(entry.tensors[0]!),
|
|
428
|
+
kScales: context.arr(entry.tensors[1]!),
|
|
429
|
+
kZeros: context.arr(entry.tensors[2]!),
|
|
430
|
+
vPacked: context.arr(entry.tensors[3]!),
|
|
431
|
+
vScales: context.arr(entry.tensors[4]!),
|
|
432
|
+
};
|
|
433
|
+
cache.restoreState(tensors, entry.offset, entry.headDim!);
|
|
434
|
+
return cache;
|
|
435
|
+
},
|
|
436
|
+
headerTrimmable: () => true,
|
|
437
|
+
},
|
|
438
|
+
mla: {
|
|
439
|
+
matches: (cache) => cacheSignature(cache) === "kv:mla:target",
|
|
440
|
+
snapshot: (cache, context) => snapshotMla("mla", cache, context),
|
|
441
|
+
clone: cloneMla,
|
|
442
|
+
load: loadMla,
|
|
443
|
+
headerTrimmable: () => true,
|
|
444
|
+
},
|
|
445
|
+
"mla-dsa": {
|
|
446
|
+
matches: (cache) => cacheSignature(cache) === "kv:mla:target:dsa",
|
|
447
|
+
snapshot: (cache, context) => snapshotMla("mla-dsa", cache, context),
|
|
448
|
+
clone: cloneMla,
|
|
449
|
+
load: loadMla,
|
|
450
|
+
headerTrimmable: () => true,
|
|
451
|
+
},
|
|
452
|
+
"mtp-mla": {
|
|
453
|
+
matches: (cache) => cacheSignature(cache) === "kv:mla:mtp",
|
|
454
|
+
snapshot: (cache, context) => snapshotMla("mtp-mla", cache, context),
|
|
455
|
+
clone: cloneMla,
|
|
456
|
+
load: loadMla,
|
|
457
|
+
headerTrimmable: () => true,
|
|
458
|
+
},
|
|
459
|
+
} satisfies Record<CacheKind, CacheCodec>;
|
|
460
|
+
|
|
461
|
+
const cacheCodec = (cache: Cache): CacheCodec => {
|
|
462
|
+
const codec = Object.values(CACHE_CODECS).find((candidate) => candidate.matches(cache));
|
|
463
|
+
if (!codec) throw new Error(`unsupported cache signature ${cacheSignature(cache)}`);
|
|
464
|
+
return codec;
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
export const cacheHeadersTrimmable = (caches: readonly CacheHeaderEntry[]): boolean =>
|
|
468
|
+
caches.every((entry) => CACHE_CODECS[entry.kind].headerTrimmable(entry));
|
|
469
|
+
|
|
145
470
|
function snapshotCache(c: Cache, dataOffset: number): { entry: CacheHeaderEntry; sources: TensorSource[]; next: number } {
|
|
146
471
|
const slots: TensorSlot[] = [];
|
|
147
472
|
const sources: TensorSource[] = [];
|
|
@@ -168,64 +493,9 @@ function snapshotCache(c: Cache, dataOffset: number): { entry: CacheHeaderEntry;
|
|
|
168
493
|
}
|
|
169
494
|
};
|
|
170
495
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
throw new Error("cannot persist an empty GLM compressed cache");
|
|
175
|
-
push(liveMlaSlice(c.latent, c.offset), true);
|
|
176
|
-
push(liveMlaSlice(c.rope, c.offset), true);
|
|
177
|
-
if (c.dsa) {
|
|
178
|
-
if (!c.dsa.data || c.dsa.offset !== c.offset)
|
|
179
|
-
throw new Error("cannot persist misaligned GLM DSA state");
|
|
180
|
-
push(liveMlaSlice(c.dsa.data, c.offset), true);
|
|
181
|
-
}
|
|
182
|
-
entry = {
|
|
183
|
-
kind: c.role === "mtp" ? "mtp-mla" : c.dsa ? "mla-dsa" : "mla",
|
|
184
|
-
offset: c.offset,
|
|
185
|
-
kvLoraRank: c.kvLoraRank,
|
|
186
|
-
ropeHeadDim: c.ropeHeadDim,
|
|
187
|
-
...(c.dsa ? { dsaHeadDim: c.dsa.headDim } : {}),
|
|
188
|
-
maxTokens: c.maxTokens,
|
|
189
|
-
tensors: slots,
|
|
190
|
-
};
|
|
191
|
-
} else if (c instanceof TurboQuantKVCache) {
|
|
192
|
-
const t = c.state();
|
|
193
|
-
if (t.length === 0) throw new Error("cannot persist an empty cache");
|
|
194
|
-
const [kIdx, kScales, kZeros, vPacked, vScales] = t as [MlxArray, MlxArray, MlxArray, MlxArray, MlxArray];
|
|
195
|
-
// state() already trims to offset — dispose these lazy slice views
|
|
196
|
-
// once written (same disposeAfter contract as liveSlice elsewhere).
|
|
197
|
-
for (const a of [kIdx, kScales, kZeros, vPacked, vScales]) push(a, true);
|
|
198
|
-
entry = { kind: "turboquant", offset: c.offset, kBits: c.kBits, vBits: c.vBits,
|
|
199
|
-
headDim: c.headDim ?? 0, tensors: slots };
|
|
200
|
-
} else if (c instanceof RotatingQuantizedKVCache) {
|
|
201
|
-
if (!c.keys || !c.values) throw new Error("cannot persist an empty cache");
|
|
202
|
-
pushTriple(c.keys, null);
|
|
203
|
-
pushTriple(c.values, null);
|
|
204
|
-
entry = { kind: "rotating-qkv", offset: c.offset, idx: c.ringIdx, maxSize: c.maxSize,
|
|
205
|
-
groupSize: c.groupSize, bits: c.bits, tensors: slots };
|
|
206
|
-
} else if (c instanceof QuantizedKVCache) {
|
|
207
|
-
if (!c.keys || !c.values) throw new Error("cannot persist an empty cache");
|
|
208
|
-
pushTriple(c.keys, c.offset);
|
|
209
|
-
pushTriple(c.values, c.offset);
|
|
210
|
-
entry = { kind: "qkv", offset: c.offset, groupSize: c.groupSize, bits: c.bits, tensors: slots };
|
|
211
|
-
} else if (c instanceof RotatingKVCache) {
|
|
212
|
-
if (!c.keys || !c.values) throw new Error("cannot persist an empty cache");
|
|
213
|
-
push(c.keys, false);
|
|
214
|
-
push(c.values, false);
|
|
215
|
-
entry = { kind: "rotating", offset: c.offset, idx: c.ringIdx, maxSize: c.maxSize, tensors: slots };
|
|
216
|
-
} else if (c instanceof SSMCache) {
|
|
217
|
-
if (!c.conv || !c.recurrent) throw new Error("cannot persist an empty cache");
|
|
218
|
-
push(c.conv, false);
|
|
219
|
-
push(c.recurrent, false);
|
|
220
|
-
entry = { kind: "ssm", offset: c.offset, tensors: slots };
|
|
221
|
-
} else if (c instanceof KVCache) {
|
|
222
|
-
if (!c.keys || !c.values) throw new Error("cannot persist an empty cache");
|
|
223
|
-
push(liveSlice(c.keys, c.offset), true);
|
|
224
|
-
push(liveSlice(c.values, c.offset), true);
|
|
225
|
-
entry = { kind: "kv", offset: c.offset, tensors: slots };
|
|
226
|
-
} else {
|
|
227
|
-
throw new Error("unknown cache type");
|
|
228
|
-
}
|
|
496
|
+
const entry = cacheCodec(c).snapshot(c, {
|
|
497
|
+
slots, push, liveSlice, liveMlaSlice, pushTriple,
|
|
498
|
+
});
|
|
229
499
|
return { entry, sources, next: cursor };
|
|
230
500
|
}
|
|
231
501
|
|
|
@@ -261,65 +531,7 @@ export function cloneKvCaches(caches: Cache[]): Cache[] {
|
|
|
261
531
|
const out: Cache[] = [];
|
|
262
532
|
try {
|
|
263
533
|
for (const c of caches) {
|
|
264
|
-
|
|
265
|
-
if (!c.latent || !c.rope || c.batchSize === null || c.offset === 0)
|
|
266
|
-
throw new Error("cannot clone an empty GLM compressed cache");
|
|
267
|
-
const n = new MLACache({
|
|
268
|
-
kvLoraRank: c.kvLoraRank,
|
|
269
|
-
ropeHeadDim: c.ropeHeadDim,
|
|
270
|
-
...(c.dsa ? { dsa: { headDim: c.dsa.headDim } } : {}),
|
|
271
|
-
maxTokens: c.maxTokens,
|
|
272
|
-
role: c.role,
|
|
273
|
-
});
|
|
274
|
-
n.restoreCompressedState(
|
|
275
|
-
mlaView(c.latent, c.offset),
|
|
276
|
-
mlaView(c.rope, c.offset),
|
|
277
|
-
c.dsa ? mlaView(c.dsa.data!, c.offset) : null,
|
|
278
|
-
c.offset,
|
|
279
|
-
);
|
|
280
|
-
out.push(n);
|
|
281
|
-
} else if (c instanceof TurboQuantKVCache) {
|
|
282
|
-
const state = c.state();
|
|
283
|
-
if (state.length === 0) throw new Error("cannot clone an empty cache");
|
|
284
|
-
const [kIdx, kScales, kZeros, vPacked, vScales] = state;
|
|
285
|
-
const n = new TurboQuantKVCache(c.kBits, c.vBits);
|
|
286
|
-
n.restoreState(
|
|
287
|
-
{ kIdx: view(kIdx!), kScales: view(kScales!), kZeros: view(kZeros!),
|
|
288
|
-
vPacked: view(vPacked!), vScales: view(vScales!) },
|
|
289
|
-
c.offset, c.headDim!,
|
|
290
|
-
);
|
|
291
|
-
for (const a of state) a.dispose();
|
|
292
|
-
out.push(n);
|
|
293
|
-
} else if (c instanceof RotatingQuantizedKVCache) {
|
|
294
|
-
if (!c.keys || !c.values) throw new Error("cannot clone an empty cache");
|
|
295
|
-
const n = new RotatingQuantizedKVCache(c.maxSize, c.groupSize, c.bits);
|
|
296
|
-
n.restoreState(tripleView(c.keys, null), tripleView(c.values, null), c.offset, c.ringIdx);
|
|
297
|
-
out.push(n);
|
|
298
|
-
} else if (c instanceof QuantizedKVCache) {
|
|
299
|
-
if (!c.keys || !c.values) throw new Error("cannot clone an empty cache");
|
|
300
|
-
const n = new QuantizedKVCache(c.groupSize, c.bits);
|
|
301
|
-
n.restoreState(tripleView(c.keys, c.offset), tripleView(c.values, c.offset), c.offset);
|
|
302
|
-
out.push(n);
|
|
303
|
-
} else if (c instanceof RotatingKVCache) {
|
|
304
|
-
if (!c.keys || !c.values) throw new Error("cannot clone an empty cache");
|
|
305
|
-
const n = new RotatingKVCache(c.maxSize);
|
|
306
|
-
n.restoreState(view(c.keys), view(c.values), c.offset, c.ringIdx);
|
|
307
|
-
out.push(n);
|
|
308
|
-
} else if (c instanceof SSMCache) {
|
|
309
|
-
if (!c.conv || !c.recurrent) throw new Error("cannot clone an empty cache");
|
|
310
|
-
const n = new SSMCache();
|
|
311
|
-
n.conv = view(c.conv);
|
|
312
|
-
n.recurrent = view(c.recurrent);
|
|
313
|
-
n.offset = c.offset;
|
|
314
|
-
out.push(n);
|
|
315
|
-
} else if (c instanceof KVCache) {
|
|
316
|
-
if (!c.keys || !c.values) throw new Error("cannot clone an empty cache");
|
|
317
|
-
const n = new KVCache();
|
|
318
|
-
n.restoreState(liveView(c.keys, c.offset), liveView(c.values, c.offset), c.offset);
|
|
319
|
-
out.push(n);
|
|
320
|
-
} else {
|
|
321
|
-
throw new Error("cloneKvCaches: unknown cache type");
|
|
322
|
-
}
|
|
534
|
+
out.push(cacheCodec(c).clone(c, { view, liveView, mlaView, tripleView }));
|
|
323
535
|
}
|
|
324
536
|
} catch (err) {
|
|
325
537
|
for (const c of out) c.dispose();
|
|
@@ -468,12 +680,17 @@ export interface SpillItem {
|
|
|
468
680
|
* Items run strictly serially in enqueue order via an internal chain;
|
|
469
681
|
* store failures are swallowed (cold tier is best-effort) but clones
|
|
470
682
|
* are disposed on every settle path. */
|
|
471
|
-
type SpillRec = SpillItem & {
|
|
683
|
+
type SpillRec = SpillItem & {
|
|
684
|
+
bytes: number;
|
|
685
|
+
dropped: boolean;
|
|
686
|
+
settle: (stored: boolean) => void;
|
|
687
|
+
};
|
|
472
688
|
|
|
473
689
|
export class SpillQueue {
|
|
474
690
|
#queue: SpillRec[] = [];
|
|
475
691
|
#bytes = 0;
|
|
476
692
|
#dropped = 0;
|
|
693
|
+
#failed = 0;
|
|
477
694
|
#inFlight: object | null = null;
|
|
478
695
|
#chain: Promise<void> = Promise.resolve();
|
|
479
696
|
|
|
@@ -492,9 +709,18 @@ export class SpillQueue {
|
|
|
492
709
|
get pendingCount(): number { return this.#queue.length; }
|
|
493
710
|
/** Spills dropped by the cap since start (each = one future cache miss). */
|
|
494
711
|
get droppedCount(): number { return this.#dropped; }
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
712
|
+
/** Store calls that threw or returned false. */
|
|
713
|
+
get failedCount(): number { return this.#failed; }
|
|
714
|
+
|
|
715
|
+
enqueue(item: SpillItem): Promise<boolean> {
|
|
716
|
+
let settle!: (stored: boolean) => void;
|
|
717
|
+
const result = new Promise<boolean>((resolve) => { settle = resolve; });
|
|
718
|
+
const rec: SpillRec = {
|
|
719
|
+
...item,
|
|
720
|
+
bytes: this.bytesOf(item.caches),
|
|
721
|
+
dropped: false,
|
|
722
|
+
settle,
|
|
723
|
+
};
|
|
498
724
|
this.#queue.push(rec);
|
|
499
725
|
this.#bytes += rec.bytes;
|
|
500
726
|
while (this.#bytes > this.capBytes) {
|
|
@@ -508,24 +734,30 @@ export class SpillQueue {
|
|
|
508
734
|
if (rec.dropped) return; // clones already disposed at drop time
|
|
509
735
|
this.#inFlight = rec;
|
|
510
736
|
try {
|
|
511
|
-
await this.store(rec);
|
|
737
|
+
const stored = await this.store(rec);
|
|
738
|
+
if (stored === false) this.#failed++;
|
|
739
|
+
rec.settle(stored !== false);
|
|
512
740
|
} catch {
|
|
513
741
|
// best-effort tier — the entry simply won't restore
|
|
742
|
+
this.#failed++;
|
|
743
|
+
rec.settle(false);
|
|
514
744
|
} finally {
|
|
515
745
|
this.#inFlight = null;
|
|
516
746
|
this.#remove(rec);
|
|
517
747
|
this.disposeClones(rec.caches);
|
|
518
748
|
}
|
|
519
749
|
});
|
|
750
|
+
return result;
|
|
520
751
|
}
|
|
521
752
|
|
|
522
753
|
/** Settles when everything enqueued so far has flushed or dropped
|
|
523
|
-
* (tests
|
|
754
|
+
* (tests and graceful shutdown). */
|
|
524
755
|
drain(): Promise<void> { return this.#chain; }
|
|
525
756
|
|
|
526
757
|
#drop(rec: SpillRec): void {
|
|
527
758
|
rec.dropped = true;
|
|
528
759
|
this.#dropped++;
|
|
760
|
+
rec.settle(false);
|
|
529
761
|
this.#remove(rec);
|
|
530
762
|
this.disposeClones(rec.caches);
|
|
531
763
|
}
|
|
@@ -592,24 +824,26 @@ function validateGlm52Prototype(
|
|
|
592
824
|
): void {
|
|
593
825
|
const glmKind = entry.kind === "mla" || entry.kind === "mla-dsa" ||
|
|
594
826
|
entry.kind === "mtp-mla";
|
|
595
|
-
|
|
827
|
+
const prototypeSignature = cacheSignature(prototype);
|
|
828
|
+
if (!prototypeSignature.startsWith("kv:mla:")) {
|
|
596
829
|
if (glmKind)
|
|
597
830
|
throw new Error(`${path}: GLM cache kind ${entry.kind} does not match model cache`);
|
|
598
831
|
return;
|
|
599
832
|
}
|
|
600
|
-
const
|
|
833
|
+
const glmPrototype = prototype as Glm52Cache;
|
|
834
|
+
const expectedKind: CacheKind = glmPrototype.role === "mtp"
|
|
601
835
|
? "mtp-mla"
|
|
602
|
-
:
|
|
836
|
+
: glmPrototype.dsa ? "mla-dsa" : "mla";
|
|
603
837
|
if (entry.kind !== expectedKind) {
|
|
604
838
|
throw new Error(
|
|
605
839
|
`${path}: cache kind ${entry.kind} != model cache kind ${expectedKind}`,
|
|
606
840
|
);
|
|
607
841
|
}
|
|
608
842
|
for (const [field, actual, expected] of [
|
|
609
|
-
["kvLoraRank", entry.kvLoraRank,
|
|
610
|
-
["ropeHeadDim", entry.ropeHeadDim,
|
|
611
|
-
["dsaHeadDim", entry.dsaHeadDim,
|
|
612
|
-
["maxTokens", entry.maxTokens,
|
|
843
|
+
["kvLoraRank", entry.kvLoraRank, glmPrototype.kvLoraRank],
|
|
844
|
+
["ropeHeadDim", entry.ropeHeadDim, glmPrototype.ropeHeadDim],
|
|
845
|
+
["dsaHeadDim", entry.dsaHeadDim, glmPrototype.dsa?.headDim],
|
|
846
|
+
["maxTokens", entry.maxTokens, glmPrototype.maxTokens],
|
|
613
847
|
] as const) {
|
|
614
848
|
if (actual !== expected) {
|
|
615
849
|
throw new Error(
|
|
@@ -617,7 +851,7 @@ function validateGlm52Prototype(
|
|
|
617
851
|
);
|
|
618
852
|
}
|
|
619
853
|
}
|
|
620
|
-
const expectedTensors =
|
|
854
|
+
const expectedTensors = glmPrototype.dsa ? 3 : 2;
|
|
621
855
|
if (entry.tensors.length !== expectedTensors) {
|
|
622
856
|
throw new Error(
|
|
623
857
|
`${path}: ${entry.kind} has ${entry.tensors.length} tensors, ` +
|
|
@@ -732,79 +966,10 @@ export function loadKvCache(
|
|
|
732
966
|
const caches: Cache[] = [];
|
|
733
967
|
try {
|
|
734
968
|
for (const e of header.caches) {
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
c.restoreState(
|
|
740
|
-
grownArr(t[0]!, e.offset),
|
|
741
|
-
grownArr(t[1]!, e.offset),
|
|
742
|
-
e.offset,
|
|
743
|
-
);
|
|
744
|
-
caches.push(c);
|
|
745
|
-
break;
|
|
746
|
-
}
|
|
747
|
-
case "rotating": {
|
|
748
|
-
const c = new RotatingKVCache(e.maxSize!);
|
|
749
|
-
c.restoreState(arr(t[0]!), arr(t[1]!), e.offset, e.idx!);
|
|
750
|
-
caches.push(c);
|
|
751
|
-
break;
|
|
752
|
-
}
|
|
753
|
-
case "qkv": {
|
|
754
|
-
const c = new QuantizedKVCache(e.groupSize!, e.bits!);
|
|
755
|
-
c.restoreState(triple(t, 0), triple(t, 3), e.offset);
|
|
756
|
-
caches.push(c);
|
|
757
|
-
break;
|
|
758
|
-
}
|
|
759
|
-
case "rotating-qkv": {
|
|
760
|
-
const c = new RotatingQuantizedKVCache(e.maxSize!, e.groupSize!, e.bits!);
|
|
761
|
-
c.restoreState(triple(t, 0), triple(t, 3), e.offset, e.idx!);
|
|
762
|
-
caches.push(c);
|
|
763
|
-
break;
|
|
764
|
-
}
|
|
765
|
-
case "ssm": {
|
|
766
|
-
const c = new SSMCache();
|
|
767
|
-
c.conv = arr(t[0]!);
|
|
768
|
-
c.recurrent = arr(t[1]!);
|
|
769
|
-
c.offset = e.offset;
|
|
770
|
-
caches.push(c);
|
|
771
|
-
break;
|
|
772
|
-
}
|
|
773
|
-
case "turboquant": {
|
|
774
|
-
const c = new TurboQuantKVCache(e.kBits!, e.vBits!);
|
|
775
|
-
const kv: TurboQuantTensor = {
|
|
776
|
-
kIdx: arr(t[0]!), kScales: arr(t[1]!), kZeros: arr(t[2]!),
|
|
777
|
-
vPacked: arr(t[3]!), vScales: arr(t[4]!),
|
|
778
|
-
};
|
|
779
|
-
c.restoreState(kv, e.offset, e.headDim!);
|
|
780
|
-
caches.push(c);
|
|
781
|
-
break;
|
|
782
|
-
}
|
|
783
|
-
case "mla":
|
|
784
|
-
case "mla-dsa":
|
|
785
|
-
case "mtp-mla": {
|
|
786
|
-
const hasDsa = e.kind === "mla-dsa";
|
|
787
|
-
if (e.kind === "mtp-mla" && e.dsaHeadDim !== undefined)
|
|
788
|
-
throw new Error(`${path}: native MTP cache cannot contain DSA state`);
|
|
789
|
-
const c = new MLACache({
|
|
790
|
-
kvLoraRank: e.kvLoraRank!,
|
|
791
|
-
ropeHeadDim: e.ropeHeadDim!,
|
|
792
|
-
...(hasDsa ? { dsa: { headDim: e.dsaHeadDim! } } : {}),
|
|
793
|
-
maxTokens: e.maxTokens,
|
|
794
|
-
role: e.kind === "mtp-mla" ? "mtp" : "target",
|
|
795
|
-
});
|
|
796
|
-
c.restoreCompressedState(
|
|
797
|
-
arr(t[0]!),
|
|
798
|
-
arr(t[1]!),
|
|
799
|
-
hasDsa ? arr(t[2]!) : null,
|
|
800
|
-
e.offset,
|
|
801
|
-
);
|
|
802
|
-
caches.push(c);
|
|
803
|
-
break;
|
|
804
|
-
}
|
|
805
|
-
default:
|
|
806
|
-
throw new Error(`${path}: unknown cache kind ${(e as { kind: string }).kind}`);
|
|
807
|
-
}
|
|
969
|
+
const codec = CACHE_CODECS[e.kind];
|
|
970
|
+
if (!codec)
|
|
971
|
+
throw new Error(`${path}: unknown cache kind ${(e as { kind: string }).kind}`);
|
|
972
|
+
caches.push(codec.load(e, { path, arr, grownArr, triple }));
|
|
808
973
|
// This entry's tensors are now owned by its cache — stop tracking them
|
|
809
974
|
// (the catch must not double-free through both pending AND caches).
|
|
810
975
|
pending.length = 0;
|