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/model/qwen3-delta.ts
CHANGED
|
@@ -194,10 +194,37 @@ export function gatedDeltaUpdate(
|
|
|
194
194
|
* and writes these slots directly; `advance(N)` only tracks token count for
|
|
195
195
|
* B=1 single-stream (lengths / left_padding are batched-decode concerns).
|
|
196
196
|
* Not a KVCache, so maybeQuantizeKv skips it. */
|
|
197
|
+
/** One armed speculative verify round on an SSMCache: the pre-round state
|
|
198
|
+
* snapshot (retained refs — MLX arrays are immutable, so this is free) plus
|
|
199
|
+
* the layer's recorded position-local kernel inputs, which make a partial
|
|
200
|
+
* reject bit-exactly replayable: state after the first `keep` window tokens
|
|
201
|
+
* is the same arithmetic prefix whether or not the rejected tail was ever
|
|
202
|
+
* processed. The replay is installed by the OWNING layer (it needs the conv
|
|
203
|
+
* weight / A_log / dt_bias); the cache only stores and frees. */
|
|
204
|
+
export interface SsmSpecRound {
|
|
205
|
+
/** True from specRoundBegin() until the layer's forward records. */
|
|
206
|
+
armed: boolean;
|
|
207
|
+
prevConv: MlxArray | null;
|
|
208
|
+
prevRecurrent: MlxArray | null;
|
|
209
|
+
prevOffset: number;
|
|
210
|
+
/** Pre-conv in_proj output [B,S,convDim] — position-local. */
|
|
211
|
+
qkv: MlxArray | null;
|
|
212
|
+
/** Gate/beta projections [B,S,Hv] — position-local. */
|
|
213
|
+
a: MlxArray | null;
|
|
214
|
+
b: MlxArray | null;
|
|
215
|
+
/** Window length S of the recorded verify forward. */
|
|
216
|
+
S: number;
|
|
217
|
+
/** Layer-bound prefix replay: called AFTER the snapshot is restored onto
|
|
218
|
+
* the cache; advances conv/recurrent/offset by `keep` tokens. */
|
|
219
|
+
replay: ((cache: SSMCache, keep: number) => void) | null;
|
|
220
|
+
}
|
|
221
|
+
|
|
197
222
|
export class SSMCache implements Cache {
|
|
198
223
|
conv: MlxArray | null = null;
|
|
199
224
|
recurrent: MlxArray | null = null;
|
|
200
225
|
offset = 0;
|
|
226
|
+
/** Live speculative verify round (serial spec lane only; null otherwise). */
|
|
227
|
+
specRound: SsmSpecRound | null = null;
|
|
201
228
|
/** Per-row token coverage (batch lane only; null on serial B=1 caches,
|
|
202
229
|
* where `offset` IS the row's count). mlx-lm's ArraysCache tracks no
|
|
203
230
|
* offset at all (cache.py:594-722) — per-sequence coverage is the
|
|
@@ -207,6 +234,10 @@ export class SSMCache implements Cache {
|
|
|
207
234
|
* lockstep by advance(), row-filtered by filter(). */
|
|
208
235
|
offsets: number[] | null = null;
|
|
209
236
|
|
|
237
|
+
signature(): string { return "ssm"; }
|
|
238
|
+
bytesPerToken(): number { return 0; }
|
|
239
|
+
get batchSize(): number { return this.offsets?.length ?? 1; }
|
|
240
|
+
|
|
210
241
|
/** Row `i`'s own token coverage (per-row when batched, `offset` serial). */
|
|
211
242
|
rowOffset(i: number): number {
|
|
212
243
|
return this.offsets ? this.offsets[i]! : this.offset;
|
|
@@ -241,14 +272,77 @@ export class SSMCache implements Cache {
|
|
|
241
272
|
}
|
|
242
273
|
|
|
243
274
|
isTrimmable(): boolean {
|
|
244
|
-
return false; // recurrent state can't be
|
|
275
|
+
return false; // recurrent state can't be trimmed per token — see specRound*
|
|
245
276
|
}
|
|
246
277
|
|
|
247
278
|
trim(_n: number): void {
|
|
248
279
|
throw new Error("SSMCache is not trimmable");
|
|
249
280
|
}
|
|
250
281
|
|
|
282
|
+
// --- speculative verify-round support (serial spec lane, B=1) ------------
|
|
283
|
+
// The serve loop arms a round before the verify forward and resolves it
|
|
284
|
+
// after the accept walk. The layer's forward, seeing an armed round, hands
|
|
285
|
+
// its replaced state slots to the round instead of disposing them, records
|
|
286
|
+
// qkv/a/b, and installs the replay. Cache/interface contract:
|
|
287
|
+
// rollback(keep) restores the snapshot then replays `keep` window tokens
|
|
288
|
+
// bit-exactly; commit frees the snapshot + recordings.
|
|
289
|
+
|
|
290
|
+
specRoundBegin(): void {
|
|
291
|
+
this.#dropSpecRound(); // defensive: a mid-round throw left a stale round
|
|
292
|
+
this.specRound = {
|
|
293
|
+
armed: true,
|
|
294
|
+
prevConv: null,
|
|
295
|
+
prevRecurrent: null,
|
|
296
|
+
prevOffset: this.offset,
|
|
297
|
+
qkv: null,
|
|
298
|
+
a: null,
|
|
299
|
+
b: null,
|
|
300
|
+
S: 0,
|
|
301
|
+
replay: null,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
specRoundCommit(): void {
|
|
306
|
+
this.#dropSpecRound();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
specRoundRollback(keep: number): void {
|
|
310
|
+
const r = this.specRound;
|
|
311
|
+
if (!r) throw new Error("SSMCache.specRoundRollback without an armed round");
|
|
312
|
+
if (r.armed)
|
|
313
|
+
throw new Error("SSMCache.specRoundRollback before the verify forward recorded");
|
|
314
|
+
if (keep < 0 || keep > r.S)
|
|
315
|
+
throw new Error(`SSMCache.specRoundRollback keep=${keep} outside window S=${r.S}`);
|
|
316
|
+
// Restore the pre-round snapshot (ownership moves back to the cache).
|
|
317
|
+
this.conv?.dispose();
|
|
318
|
+
this.recurrent?.dispose();
|
|
319
|
+
this.conv = r.prevConv;
|
|
320
|
+
this.recurrent = r.prevRecurrent;
|
|
321
|
+
r.prevConv = null;
|
|
322
|
+
r.prevRecurrent = null;
|
|
323
|
+
this.offset = r.prevOffset;
|
|
324
|
+
if (this.offsets)
|
|
325
|
+
throw new Error("SSMCache.specRoundRollback on a batched cache (serial lane only)");
|
|
326
|
+
if (keep > 0) {
|
|
327
|
+
if (!r.replay) throw new Error("SSMCache.specRoundRollback with no recorded replay");
|
|
328
|
+
r.replay(this, keep); // advances conv/recurrent/offset by `keep`
|
|
329
|
+
}
|
|
330
|
+
this.#dropSpecRound();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
#dropSpecRound(): void {
|
|
334
|
+
const r = this.specRound;
|
|
335
|
+
if (!r) return;
|
|
336
|
+
r.prevConv?.dispose();
|
|
337
|
+
r.prevRecurrent?.dispose();
|
|
338
|
+
r.qkv?.dispose();
|
|
339
|
+
r.a?.dispose();
|
|
340
|
+
r.b?.dispose();
|
|
341
|
+
this.specRound = null;
|
|
342
|
+
}
|
|
343
|
+
|
|
251
344
|
dispose(): void {
|
|
345
|
+
this.#dropSpecRound();
|
|
252
346
|
this.conv?.dispose();
|
|
253
347
|
this.recurrent?.dispose();
|
|
254
348
|
this.conv = null;
|
|
@@ -304,6 +398,8 @@ export class SSMCache implements Cache {
|
|
|
304
398
|
if (this.offsets) this.offsets = keep.map((i) => this.offsets![i]!);
|
|
305
399
|
}
|
|
306
400
|
|
|
401
|
+
filterRows(keep: readonly number[]): void { this.filter([...keep]); }
|
|
402
|
+
|
|
307
403
|
/** Row `i` as a fresh SERIAL cache — port of mlx-lm ArraysCache.extract
|
|
308
404
|
* (cache.py:673-676: `cache.cache = [c[idx : idx + 1] for c in self.cache]`,
|
|
309
405
|
* a B-axis slice of every state slot). Ours are OWNED contiguous copies
|
|
@@ -319,7 +415,7 @@ export class SSMCache implements Cache {
|
|
|
319
415
|
const hi = [...a.shape];
|
|
320
416
|
hi[0] = i + 1;
|
|
321
417
|
const view = a.slice(lo, hi);
|
|
322
|
-
const own = ops.contiguous(view)
|
|
418
|
+
const own = ops.copyOf(view); // TRUE copy: contiguous(view) is a no-op VIEW when already contiguous — pins the source buffer (2026-08-20 DeltaNet conv leak class)
|
|
323
419
|
view.dispose();
|
|
324
420
|
return own;
|
|
325
421
|
};
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
// Qwen3.5/3.8 multimodal RoPE (mRoPE) — the language-side position machinery
|
|
2
|
+
// for vision requests (PLAN 14v), port of mlx-vlm's qwen3_5 language rope:
|
|
3
|
+
// MRoPERotaryEmbedding (style "interleaved", mrope_section [11,11,10]) +
|
|
4
|
+
// get_rope_index + the rope_deltas decode continuation.
|
|
5
|
+
//
|
|
6
|
+
// TEXT-ONLY EQUIVALENCE: with all three position streams equal, interleaved
|
|
7
|
+
// selection collapses to plain partial RoPE — which is why the text path's
|
|
8
|
+
// ops.rope stays bit-exact untouched. This module only activates for
|
|
9
|
+
// requests whose prompt contains vision spans; while active, EVERY
|
|
10
|
+
// full-attention rope in the request (prefill AND decode) goes through the
|
|
11
|
+
// manual interleaved path, mirroring the reference (which never uses the
|
|
12
|
+
// fused fast-rope kernel when position_ids are supplied).
|
|
13
|
+
//
|
|
14
|
+
// Position semantics (get_rope_index, B=1 serial): text tokens advance all
|
|
15
|
+
// three streams together; an image span of grid (t, h/merge, w/merge) gets
|
|
16
|
+
// t/h/w grid positions offset by the current base; text after an image
|
|
17
|
+
// resumes at max+1. delta = (max position + 1) - sequence length; decode
|
|
18
|
+
// positions are offset + delta on all three streams.
|
|
19
|
+
|
|
20
|
+
import { MlxArray } from "../mlx/array";
|
|
21
|
+
import { Dtype } from "../mlx/ffi";
|
|
22
|
+
import * as ops from "../mlx/ops";
|
|
23
|
+
import { MetalKernel } from "../mlx/metal-kernel";
|
|
24
|
+
|
|
25
|
+
export interface MropeRequestState {
|
|
26
|
+
/** Full-grid positions for the prompt, one Int32Array per t/h/w stream. */
|
|
27
|
+
positions: [Int32Array, Int32Array, Int32Array];
|
|
28
|
+
/** (max position + 1) - promptLength; decode positions = offset + delta. */
|
|
29
|
+
delta: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface MropeForwardState {
|
|
33
|
+
/** Effective positions [3, 1, L] int32 for the CURRENT forward window. */
|
|
34
|
+
posIds: MlxArray;
|
|
35
|
+
/** Borrowed model-owned f32 inv_freq (mropeInvFreq) — not disposed here. */
|
|
36
|
+
invFreq: MlxArray;
|
|
37
|
+
rotaryDims: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Active per-forward mRoPE cos/sin, read by Qwen3Attention.forward. Null on
|
|
41
|
+
* the text-only path (serial lane only — vision requests never batch). */
|
|
42
|
+
export let activeMrope: MropeForwardState | null = null;
|
|
43
|
+
export function setActiveMrope(s: MropeForwardState | null): void {
|
|
44
|
+
activeMrope = s;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const MROPE_SECTION = [11, 11, 10] as const;
|
|
48
|
+
|
|
49
|
+
/** Interleaved frequency→stream selector (rope_utils
|
|
50
|
+
* _interleaved_position_selector): default t(0); indices 1,4,7,… take
|
|
51
|
+
* h(1) while < section[1]*3; indices 2,5,8,… take w(2) while < section[2]*3. */
|
|
52
|
+
export function interleavedSelector(freqDim: number): Int32Array {
|
|
53
|
+
const sel = new Int32Array(freqDim);
|
|
54
|
+
for (let dim = 1; dim <= 2; dim++) {
|
|
55
|
+
const offset = dim; // h starts at 1, w at 2
|
|
56
|
+
for (let i = offset; i < Math.min(MROPE_SECTION[dim]! * 3, freqDim); i += 3)
|
|
57
|
+
sel[i] = dim;
|
|
58
|
+
}
|
|
59
|
+
return sel;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** inv_freq for the language rope — computed on-device exactly like
|
|
63
|
+
* compute_inv_freq (Metal powf is not correctly rounded; a JS emulation
|
|
64
|
+
* drifts by 1 ulp on some entries). Returned as a live f32 device array
|
|
65
|
+
* (the apply kernel reads it directly). */
|
|
66
|
+
export function mropeInvFreq(rotaryDims: number, base: number): MlxArray {
|
|
67
|
+
const ar = ops.arange(0, rotaryDims, 2, Dtype.float32);
|
|
68
|
+
const ex = ops.mulScalar(ar, 1 / rotaryDims);
|
|
69
|
+
ar.dispose();
|
|
70
|
+
const theta = MlxArray.fromFloat32(Float32Array.from([base]), [1]);
|
|
71
|
+
const p = ops.pow(theta, ex);
|
|
72
|
+
theta.dispose();
|
|
73
|
+
ex.dispose();
|
|
74
|
+
const one = MlxArray.fromFloat32(Float32Array.from([1]), [1]);
|
|
75
|
+
const invA = ops.div(one, p);
|
|
76
|
+
one.dispose();
|
|
77
|
+
p.dispose();
|
|
78
|
+
return invA;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Effective positions [3,1,L] int32 for window [offset, offset+L) —
|
|
82
|
+
* prompt-grid positions while inside the prompt, offset+delta beyond it
|
|
83
|
+
* (the reference's rope_deltas continuation). */
|
|
84
|
+
export function buildMropePositions(
|
|
85
|
+
state: MropeRequestState, offset: number, L: number,
|
|
86
|
+
invFreq: MlxArray, rotaryDims: number,
|
|
87
|
+
): MropeForwardState {
|
|
88
|
+
const P = state.positions;
|
|
89
|
+
const promptLen = P[0].length;
|
|
90
|
+
const joint = new Int32Array(3 * L);
|
|
91
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
92
|
+
for (let l = 0; l < L; l++) {
|
|
93
|
+
const idx = offset + l;
|
|
94
|
+
joint[axis * L + l] = idx < promptLen ? P[axis]![idx]! : idx + state.delta;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return { posIds: MlxArray.fromInt32(joint, [3, 1, L]), invFreq, rotaryDims };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// The reference language-side apply is a CUSTOM METAL KERNEL (rope_utils
|
|
101
|
+
// _mrope_apply_kernel, pairing "half_split", position_ndim 3): per rotated
|
|
102
|
+
// pair it computes angle = pos * inv_freq in f32, cos/sin in-kernel, and
|
|
103
|
+
// writes x*c − x_pair*s / x_pair*c + x*s with ONE rounding to the tensor
|
|
104
|
+
// dtype. A bf16 cos/sin manual path is ~0.4% off per rotation and moved
|
|
105
|
+
// step-0 logits by ~1.0 — this verbatim port is the oracle's arithmetic.
|
|
106
|
+
const kernelCache = new Map<number, MetalKernel>();
|
|
107
|
+
function mropeKernel(rotaryDims: number): MetalKernel {
|
|
108
|
+
let k = kernelCache.get(rotaryDims);
|
|
109
|
+
if (k) return k;
|
|
110
|
+
const half = rotaryDims / 2;
|
|
111
|
+
const source = `
|
|
112
|
+
uint elem = thread_position_in_grid.x;
|
|
113
|
+
|
|
114
|
+
const int half_dim = ${half};
|
|
115
|
+
const int q_bsz = x_shape[0];
|
|
116
|
+
const int q_heads = x_shape[1];
|
|
117
|
+
const int q_len = x_shape[2];
|
|
118
|
+
const int q_dim = x_shape[3];
|
|
119
|
+
const int slots = half_dim + q_dim - ${rotaryDims};
|
|
120
|
+
const int work_size = q_bsz * q_heads * q_len * slots;
|
|
121
|
+
|
|
122
|
+
if (elem >= uint(work_size)) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
int local = int(elem);
|
|
127
|
+
int slot = local % slots;
|
|
128
|
+
int tmp = local / slots;
|
|
129
|
+
int t = tmp % q_len;
|
|
130
|
+
tmp = tmp / q_len;
|
|
131
|
+
int h = tmp % q_heads;
|
|
132
|
+
int b = tmp / q_heads;
|
|
133
|
+
int base = ((b * q_heads + h) * q_len + t) * q_dim;
|
|
134
|
+
|
|
135
|
+
if (slot >= half_dim) {
|
|
136
|
+
int pass_d = ${rotaryDims} + slot - half_dim;
|
|
137
|
+
int pass_idx = base + pass_d;
|
|
138
|
+
x_out[pass_idx] = x[pass_idx];
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
int freq_idx = slot;
|
|
143
|
+
int d = freq_idx;
|
|
144
|
+
int pair_d = d + half_dim;
|
|
145
|
+
int axis = int(position_selector[freq_idx]);
|
|
146
|
+
float pos = static_cast<float>(position_ids[(axis * q_bsz + b) * q_len + t]);
|
|
147
|
+
float angle = pos * static_cast<float>(inv_freq[freq_idx]);
|
|
148
|
+
float c = metal::cos(angle);
|
|
149
|
+
float s = metal::sin(angle);
|
|
150
|
+
|
|
151
|
+
int idx = base + d;
|
|
152
|
+
float xv = static_cast<float>(x[idx]);
|
|
153
|
+
float xp = static_cast<float>(x[base + pair_d]);
|
|
154
|
+
x_out[idx] = static_cast<T>(xv * c - xp * s);
|
|
155
|
+
x_out[base + pair_d] = static_cast<T>(xp * c + xv * s);
|
|
156
|
+
`;
|
|
157
|
+
k = new MetalKernel({
|
|
158
|
+
name: `mrope_apply_half_split_${rotaryDims}_3d`,
|
|
159
|
+
inputNames: ["x", "position_ids", "inv_freq", "position_selector"],
|
|
160
|
+
outputNames: ["x_out"],
|
|
161
|
+
source,
|
|
162
|
+
ensureRowContiguous: true,
|
|
163
|
+
});
|
|
164
|
+
kernelCache.set(rotaryDims, k);
|
|
165
|
+
return k;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const selectorCache = new Map<number, MlxArray>();
|
|
169
|
+
function selectorArr(half: number): MlxArray {
|
|
170
|
+
let s = selectorCache.get(half);
|
|
171
|
+
if (!s) {
|
|
172
|
+
s = MlxArray.fromInt32(interleavedSelector(half), [half]);
|
|
173
|
+
selectorCache.set(half, s);
|
|
174
|
+
}
|
|
175
|
+
return s;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Apply interleaved mRoPE to one [B, heads, L, headDim] tensor via the
|
|
179
|
+
* reference's exact Metal kernel. `invFreq` is the f32 device array from
|
|
180
|
+
* mropeInvFreq. */
|
|
181
|
+
export function applyInterleavedRope(
|
|
182
|
+
x: MlxArray, fwd: MropeForwardState,
|
|
183
|
+
): MlxArray {
|
|
184
|
+
const rotaryDims = fwd.rotaryDims;
|
|
185
|
+
const [B, H, L, D] = x.shape as [number, number, number, number];
|
|
186
|
+
const half = rotaryDims / 2;
|
|
187
|
+
const slots = half + D - rotaryDims;
|
|
188
|
+
const work = B * H * L * slots;
|
|
189
|
+
const [out] = mropeKernel(rotaryDims).apply(
|
|
190
|
+
[x, fwd.posIds, fwd.invFreq, selectorArr(half)],
|
|
191
|
+
{
|
|
192
|
+
outputs: [{ shape: [B, H, L, D], dtype: x.dtype }],
|
|
193
|
+
grid: [work, 1, 1],
|
|
194
|
+
threadGroup: [256, 1, 1],
|
|
195
|
+
templateDtypes: { T: x.dtype },
|
|
196
|
+
},
|
|
197
|
+
);
|
|
198
|
+
return out!;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** get_rope_index port (B=1, no attention mask): positions per stream + the
|
|
202
|
+
* decode delta for a prompt with vision spans. `grids` are the per-image
|
|
203
|
+
* (t, h, w) grids in PATCH units and must appear in prompt order. */
|
|
204
|
+
export function qwenRopeIndex(
|
|
205
|
+
ids: readonly number[],
|
|
206
|
+
grids: readonly [number, number, number][],
|
|
207
|
+
mergeSize: number,
|
|
208
|
+
imageTokenId: number,
|
|
209
|
+
videoTokenId: number,
|
|
210
|
+
): MropeRequestState {
|
|
211
|
+
const L = ids.length;
|
|
212
|
+
const P: [Int32Array, Int32Array, Int32Array] =
|
|
213
|
+
[new Int32Array(L), new Int32Array(L), new Int32Array(L)];
|
|
214
|
+
let write = 0; // next output index (== st in the reference walk)
|
|
215
|
+
let base = 0; // st_idx: next position after everything placed so far
|
|
216
|
+
let gi = 0;
|
|
217
|
+
let i = 0;
|
|
218
|
+
while (i < L) {
|
|
219
|
+
const tok = ids[i]!;
|
|
220
|
+
if ((tok === imageTokenId || tok === videoTokenId) && gi < grids.length) {
|
|
221
|
+
const [t, h, w] = grids[gi]!;
|
|
222
|
+
gi++;
|
|
223
|
+
const gh = Math.trunc(h / mergeSize);
|
|
224
|
+
const gw = Math.trunc(w / mergeSize);
|
|
225
|
+
const span = t * gh * gw;
|
|
226
|
+
for (let k = 0; k < span; k++) {
|
|
227
|
+
const tt = Math.trunc(k / (gh * gw));
|
|
228
|
+
const rem = k % (gh * gw);
|
|
229
|
+
const hh = Math.trunc(rem / gw);
|
|
230
|
+
const ww = rem % gw;
|
|
231
|
+
P[0][write + k] = base + tt;
|
|
232
|
+
P[1][write + k] = base + hh;
|
|
233
|
+
P[2][write + k] = base + ww;
|
|
234
|
+
}
|
|
235
|
+
// Reference: next base = max position in the span + 1 = base +
|
|
236
|
+
// max(t, gh, gw) … computed as llm_pos_ids.max()+1.
|
|
237
|
+
base = base + Math.max(t, gh, gw);
|
|
238
|
+
write += span;
|
|
239
|
+
i += span;
|
|
240
|
+
} else {
|
|
241
|
+
P[0][write] = base;
|
|
242
|
+
P[1][write] = base;
|
|
243
|
+
P[2][write] = base;
|
|
244
|
+
base++;
|
|
245
|
+
write++;
|
|
246
|
+
i++;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (write !== L)
|
|
250
|
+
throw new Error(`qwenRopeIndex covered ${write} of ${L} tokens (grids misaligned?)`);
|
|
251
|
+
return { positions: P, delta: base - L };
|
|
252
|
+
}
|