mlx-bun 0.0.10 → 0.0.12
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 +107 -32
- package/THIRD_PARTY_LICENSES.md +10 -0
- package/docs/archive/mlx-bun-lab-report.html +275 -0
- package/docs/archive/wwdc-mlx-bun.html +848 -0
- package/docs/design/audio-input-plan.md +356 -0
- package/docs/design/batching-perf-path.md +58 -35
- package/docs/design/batching-v2-plan.md +23 -9
- package/docs/design/bucketing-stage.md +2 -1
- package/docs/design/colibri-glm52-port.md +1479 -0
- package/docs/design/compat-cli-surface-design.md +4 -3
- package/docs/design/decode-speed-program.md +172 -0
- package/docs/design/docs-reorg-plan.md +1 -1
- package/docs/design/dspark-serving-program.md +371 -0
- package/docs/design/dspark-speculative-decoding.md +167 -5
- package/docs/design/faithful-l1-consolidation.md +310 -0
- package/docs/design/generic-model-support.md +8 -5
- package/docs/design/grammar-spec-batching-integration.md +301 -0
- package/docs/design/minicpm5-decode-megakernel.md +16 -11
- package/docs/design/mlx-lm-tool-parity-plan.md +11 -1
- package/docs/design/omlx-adoption-map.md +14 -6
- package/docs/design/orpo-training.md +34 -30
- package/docs/design/paged-kv-cache.md +214 -0
- package/docs/design/parallel-slots.md +82 -49
- package/docs/design/parity-tier-dag.md +43 -33
- package/docs/design/pre-colibri-stabilization.md +329 -0
- package/docs/design/repo-cleanup-plan.md +189 -0
- package/docs/design/runtime-isolation.md +19 -3
- package/docs/design/segmented-backward-training.md +3 -2
- package/docs/design/ssd-kv-cold-tier.md +130 -0
- package/docs/design/structured-output.md +484 -0
- package/docs/design/the-dreaming-master-plan.md +2 -3
- package/docs/design/turboquant-kv.md +235 -0
- package/docs/design/unified-engine-frontier-plan.md +774 -0
- package/docs/design/web-chat-beat-matrix.md +315 -0
- package/docs/design/web-chat-redesign.md +1112 -0
- package/docs/investigations/colibri-oracle-pin.md +1284 -0
- package/docs/investigations/dspark-handoff.md +182 -16
- package/docs/investigations/indexshare-performance-spike.md +357 -0
- package/docs/investigations/lab-build-journal.md +2 -2
- package/docs/investigations/openwiki-evaluation.md +249 -0
- package/docs/planning/IDEAS.md +14 -0
- package/docs/planning/PRODUCT_ROADMAP.md +2 -3
- package/docs/planning/chat-ui-vision-memory-first.md +199 -0
- package/docs/planning/chat-ui-vision.md +50 -0
- package/docs/planning/memory-docs-and-dag-plan.md +15 -6
- package/docs/planning/release-notes-v0.0.11.md +239 -0
- package/docs/planning/release-notes-v0.0.12.md +80 -0
- package/docs/reference/cli.md +63 -18
- package/docs/reference/distribution.md +37 -6
- package/docs/reference/embedding.md +40 -16
- package/docs/reference/features-matrix.md +99 -0
- package/docs/reference/library-api.md +69 -20
- package/docs/reference/memory.md +26 -6
- package/docs/reference/models.md +60 -3
- package/docs/reference/orpo-quickstart.md +3 -4
- package/docs/reference/server-api.md +717 -24
- package/docs/reference/server-config.md +265 -129
- package/docs/reference/training.md +24 -16
- package/package.json +5 -3
- package/src/anthropic.ts +67 -44
- package/src/audio/conformer.ts +647 -0
- package/src/audio/decode.ts +133 -0
- package/src/audio/features.ts +202 -0
- package/src/audio/transcode.ts +67 -0
- package/src/chat-template.ts +155 -10
- package/src/cli.ts +483 -113
- package/src/config.ts +116 -24
- package/src/diffusion/diffusion-generate.ts +14 -8
- package/src/download.ts +143 -12
- package/src/eval/README.md +7 -6
- package/src/eval/ifeval.ts +63 -120
- package/src/eval/kl.ts +59 -3
- package/src/eval/quality-db.ts +2 -2
- package/src/eval/runner.ts +17 -8
- package/src/eval/tasks/ifeval.ts +167 -31
- package/src/expert-io.ts +391 -0
- package/src/expert-offload.ts +15 -2
- package/src/expert-residency.ts +1134 -0
- package/src/expert-usage.ts +415 -0
- package/src/flags.ts +13 -0
- package/src/generate.ts +438 -35
- package/src/grammar.ts +585 -0
- package/src/hf-push.ts +1 -1
- package/src/hub-rest.ts +242 -0
- package/src/index.ts +14 -2
- package/src/jobs/runner.ts +37 -18
- package/src/kv-store.ts +586 -93
- package/src/lora.ts +85 -14
- package/src/md-modules.d.ts +42 -0
- package/src/media-fetch.ts +426 -0
- package/src/memory/rest.ts +245 -0
- package/src/memory/vault.ts +74 -0
- package/src/mlx/array.ts +149 -19
- package/src/mlx/autograd.ts +53 -16
- package/src/mlx/checkpoint.ts +46 -13
- package/src/mlx/custom-vjp.ts +83 -35
- package/src/mlx/ffi.ts +27 -3
- package/src/mlx/metal-kernel.ts +7 -4
- package/src/mlx/ops.ts +153 -23
- package/src/mlx/turboquant-ops.ts +831 -0
- package/src/mlx/turboquant-tables.ts +237 -0
- package/src/model/batched-mask.ts +62 -1
- package/src/model/batched-quant.ts +206 -0
- package/src/model/batched-rotating-quant.ts +346 -0
- package/src/model/batched-rotating.ts +46 -1
- package/src/model/compiled-decode.ts +4 -15
- package/src/model/factory.ts +103 -6
- package/src/model/gemma4-base.ts +395 -39
- package/src/model/gemma4.ts +122 -33
- package/src/model/generated/gemma4-12b.ts +9 -13
- package/src/model/generated/gemma4-26b.ts +10 -5
- package/src/model/generated/gemma4-e4b.ts +14 -9
- package/src/model/glm52-atlas.ts +511 -0
- package/src/model/glm52-cache.ts +706 -0
- package/src/model/glm52-config.ts +262 -0
- package/src/model/glm52-container.ts +305 -0
- package/src/model/glm52-coupling.ts +388 -0
- package/src/model/glm52-dsa.ts +602 -0
- package/src/model/glm52-expert-layout.ts +253 -0
- package/src/model/glm52-layout.ts +152 -0
- package/src/model/glm52-memory.ts +473 -0
- package/src/model/glm52-mla.ts +786 -0
- package/src/model/glm52-moe.ts +223 -0
- package/src/model/glm52-pilot.ts +537 -0
- package/src/model/glm52-reference.ts +445 -0
- package/src/model/glm52-residency.ts +557 -0
- package/src/model/glm52-resident-weights.ts +135 -0
- package/src/model/glm52-streamed-experts.ts +453 -0
- package/src/model/glm52-streamed-metal.ts +610 -0
- package/src/model/glm52-weights.ts +503 -0
- package/src/model/glm52.ts +1153 -0
- package/src/model/minicpm5.ts +58 -102
- package/src/model/paged-kv.ts +253 -0
- package/src/model/qwen3-delta.ts +76 -15
- package/src/model/qwen3-moe.ts +404 -0
- package/src/model/qwen3.ts +9 -8
- package/src/model/qwen3_5.ts +88 -23
- package/src/model/support.ts +19 -1
- package/src/model/universal/dense.ts +40 -5
- package/src/model/universal/rope.ts +36 -21
- package/src/native/expert_io.c +523 -0
- package/src/native-pack.ts +6 -4
- package/src/pi-web.ts +1299 -82
- package/src/preflight.ts +27 -8
- package/src/prompt-cache.ts +293 -33
- package/src/quantize/quantizer.ts +16 -1
- package/src/registry.ts +57 -2
- package/src/responses.ts +62 -49
- package/src/serve/batch-scheduler.ts +1050 -78
- package/src/serve/generation-gateway.ts +252 -24
- package/src/serve/isolate.ts +417 -0
- package/src/serve/lane-registry.ts +44 -0
- package/src/serve/session-search.ts +326 -0
- package/src/server.ts +1701 -175
- package/src/spec/assistant-source.ts +150 -0
- package/src/spec/deepspec-source.ts +143 -0
- package/src/spec/dflash-source.ts +124 -0
- package/src/spec/dspark/ab-stats.ts +136 -0
- package/src/spec/dspark/calibration.ts +112 -0
- package/src/spec/dspark/deepspec-module.ts +801 -0
- package/src/spec/dspark/generate-dflash.ts +72 -20
- package/src/spec/dspark/loader.ts +23 -0
- package/src/spec/dspark/module-dflash.ts +278 -36
- package/src/spec/dspark/quantize-drafter.ts +91 -0
- package/src/spec/generate.ts +59 -25
- package/src/spec/glm52-mtp-source.ts +392 -0
- package/src/spec/ngram-source.ts +132 -0
- package/src/spec/serve-loop.ts +617 -0
- package/src/spec/source.ts +118 -0
- package/src/spec/two-model.ts +171 -0
- package/src/ssd-cache.ts +111 -35
- package/src/tokenizer.ts +288 -2
- package/src/tool-approvals.ts +131 -0
- package/src/tool-call.ts +438 -22
- package/src/train/flash-cce.ts +2 -2
- package/src/train/job.ts +0 -12
- package/src/train/lora-params.ts +17 -6
- package/src/train/trainer.ts +60 -30
- package/src/vision/prompt.ts +217 -48
- package/src/web/app.html +1197 -1791
- package/src/web/app.js +5694 -0
- package/src/web/icon.svg +13 -0
- package/src/web/manifest.webmanifest +24 -0
- package/src/web/src/adapters-panel.ts +288 -0
- package/src/web/src/api.ts +57 -0
- package/src/web/src/assistant.ts +485 -0
- package/src/web/src/chat.ts +1050 -0
- package/src/web/src/composer.ts +959 -0
- package/src/web/src/dataset.ts +132 -0
- package/src/web/src/finetune.ts +267 -0
- package/src/web/src/hub.ts +428 -0
- package/src/web/src/main.ts +98 -0
- package/src/web/src/markdown.ts +0 -0
- package/src/web/src/memory-panel.ts +572 -0
- package/src/web/src/model-picker.ts +159 -0
- package/src/web/src/palette.ts +346 -0
- package/src/web/src/protocol.ts +62 -0
- package/src/web/src/quantize.ts +167 -0
- package/src/web/src/rag.ts +274 -0
- package/src/web/src/sessions.ts +469 -0
- package/src/web/src/shell.ts +804 -0
- package/src/web/src/status.ts +300 -0
- package/src/web/src/ui-catalog.ts +92 -0
- package/src/web/sw.js +71 -0
- package/src/web/vendor/README.md +61 -0
- package/src/web/vendor/hljs-LICENSE +29 -0
- package/src/web/vendor/hljs-theme.css +59 -0
- package/src/web/vendor/hljs.js +26 -0
- package/src/web-tools.ts +54 -2
- package/src/weights.ts +82 -19
- package/src/model/fused-decode-kernel.ts +0 -312
- package/src/model/fused-geglu-kernel.ts +0 -198
- package/src/model/fused-mlp-kernel.ts +0 -169
- package/src/model/fused-swiglu-kernel.ts +0 -240
- package/src/model/steel-linear-kernel.ts +0 -96
- package/src/status-page.html +0 -497
package/src/model/gemma4.ts
CHANGED
|
@@ -45,15 +45,77 @@ import {
|
|
|
45
45
|
RMSNorm,
|
|
46
46
|
RotatingKVCache,
|
|
47
47
|
RotatingQuantizedKVCache,
|
|
48
|
+
TurboQuantKVCache,
|
|
48
49
|
quantizedSdpa,
|
|
49
50
|
type Cache,
|
|
50
51
|
type Mask,
|
|
51
52
|
type SharedKv,
|
|
52
53
|
type LoraWeights,
|
|
53
54
|
} from "./gemma4-base";
|
|
54
|
-
import { fusedGeglu, fusedGegluDifferentiable, fusedGeluEnabled, fusedGeluSupported, fusedGeluTraining } from "./fused-geglu-kernel";
|
|
55
55
|
import { Checkpoint } from "../mlx/checkpoint";
|
|
56
56
|
import { flashAttention, getTrainingAttn, flashSupported } from "./flash-attention";
|
|
57
|
+
import { unrotateValues as tqUnrotateValues } from "../mlx/turboquant-ops";
|
|
58
|
+
import { CompiledFunction } from "../mlx/compile";
|
|
59
|
+
import { flagOn } from "../flags";
|
|
60
|
+
|
|
61
|
+
/** Verbatim port of mlx_lm/models/gemma4_text.py:
|
|
62
|
+
* `@partial(mx.compile, shapeless=True) def geglu(gate, x): return nn.gelu_approx(gate) * x`
|
|
63
|
+
* The oracle @mx.compile's this and uses it for EVERY MLP shape (dense MLP
|
|
64
|
+
* __call__ line 114, and the SwitchGLU GeGLU activation line 149-150). Our
|
|
65
|
+
* default MLP runs the SAME math UNFUSED (standalone ops.geluApprox + ops.mul),
|
|
66
|
+
* a different dispatched kernel set + the per-op host tax the MiniCPM5 swiglu
|
|
67
|
+
* fix measured at ~5%. This closure fuses geluApprox's ~9 element-wise ops + the
|
|
68
|
+
* final mul into one traced-once/replayed graph, matching the oracle's kernel
|
|
69
|
+
* set. `geluApprox` is composed op-for-op from the same primitives as the
|
|
70
|
+
* oracle's nn.gelu_approx (0.5·x·(1+tanh(√(2/π)(x+0.044715·x³)))), so the
|
|
71
|
+
* compiled graph is the oracle's geglu graph. Autograd-safe (verified: the vjp
|
|
72
|
+
* through this closure is bit-identical to the plain composition). */
|
|
73
|
+
/** True when gemma's geglu activation runs through the compiled closure (matching
|
|
74
|
+
* the oracle's @mx.compile geglu) instead of the spelled-out ops.geluApprox+ops.mul.
|
|
75
|
+
* DEFAULT ON: compiled geglu is bit-exact to mlx-lm's @mx.compile geglu (same
|
|
76
|
+
* libmlx graph → same kernel) AND dispatches one fused kernel set instead of the
|
|
77
|
+
* ~9-op path. `MLX_BUN_COMPILED_GEGLU=0` (--compiled-activations off) selects the
|
|
78
|
+
* uncompiled composition — same L1 parity, slower (the A/B opt-out). */
|
|
79
|
+
export function compiledGegluActive(): boolean {
|
|
80
|
+
return flagOn("MLX_BUN_COMPILED_GEGLU", true);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let _gegluClosure: CompiledFunction | null = null;
|
|
84
|
+
export function compiledGeglu(gate: MlxArray, up: MlxArray): MlxArray {
|
|
85
|
+
if (!_gegluClosure) {
|
|
86
|
+
_gegluClosure = new CompiledFunction((inputs) => {
|
|
87
|
+
const g = inputs[0]!, u = inputs[1]!; // nn.gelu_approx(gate) * x
|
|
88
|
+
const act = ops.geluApprox(g);
|
|
89
|
+
const out = ops.mul(act, u); act.dispose();
|
|
90
|
+
return [out];
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return _gegluClosure.apply([gate, up])[0]!;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** gemma4_text.py `@partial(mx.compile) def logit_softcap(softcap, x):
|
|
97
|
+
* return mx.tanh(x / softcap) * softcap`
|
|
98
|
+
* The oracle fuses divide+tanh+multiply into ONE kernel; our default
|
|
99
|
+
* `logitSoftcap` runs them UNFUSED (standalone div + tanh + mul → a separate
|
|
100
|
+
* `vn_Tanh` + `vsn_Divide`). Keyed by cap so the softcap is baked as a graph
|
|
101
|
+
* constant (matching the oracle's weak-scalar → the `…Divide…Tanh…Multiply…_VC_`
|
|
102
|
+
* kernel). The default (matches mlx-lm's dispatched kernel set). */
|
|
103
|
+
const _softcapClosures = new Map<number, CompiledFunction>();
|
|
104
|
+
export function compiledLogitSoftcap(x: MlxArray, cap: number): MlxArray {
|
|
105
|
+
let fn = _softcapClosures.get(cap);
|
|
106
|
+
if (!fn) {
|
|
107
|
+
fn = new CompiledFunction((inputs) => {
|
|
108
|
+
const xx = inputs[0]!; // tanh(x / softcap) * softcap
|
|
109
|
+
const capArr = ops.scalarLike(cap, xx);
|
|
110
|
+
const scaled = ops.div(xx, capArr);
|
|
111
|
+
const t = ops.tanh(scaled); scaled.dispose();
|
|
112
|
+
const out = ops.mul(t, capArr); t.dispose(); capArr.dispose();
|
|
113
|
+
return [out];
|
|
114
|
+
});
|
|
115
|
+
_softcapClosures.set(cap, fn);
|
|
116
|
+
}
|
|
117
|
+
return fn.apply([x])[0]!;
|
|
118
|
+
}
|
|
57
119
|
|
|
58
120
|
/** Optional gradient-checkpointing context for the training forward. When set,
|
|
59
121
|
* forwardLayers wraps each layer in a Checkpoint so its interior activations
|
|
@@ -275,6 +337,14 @@ class Attention {
|
|
|
275
337
|
groupSize: cache.groupSize, bits: cache.bits,
|
|
276
338
|
offsetArr,
|
|
277
339
|
};
|
|
340
|
+
} else if (cache instanceof TurboQuantKVCache) {
|
|
341
|
+
// Deferred-V read: values stay rotated; the attention output is
|
|
342
|
+
// un-rotated below (shared.vRotated) — including by KV-shared
|
|
343
|
+
// consumer layers, which see the flag through sharedIn.
|
|
344
|
+
const [keys, values] = cache.updateAndFetchDeferredV(kRoped, vT);
|
|
345
|
+
kRoped.dispose();
|
|
346
|
+
vT.dispose();
|
|
347
|
+
shared = { kind: "plain", keys, values, offset, offsetArr, vRotated: true };
|
|
278
348
|
} else {
|
|
279
349
|
const [keys, values] = cache.updateAndFetch(kRoped, vT);
|
|
280
350
|
kRoped.dispose();
|
|
@@ -310,6 +380,11 @@ class Attention {
|
|
|
310
380
|
attn = ops.sdpa(q, shared.keys, shared.values, 1.0, mask.mode, mask.arr);
|
|
311
381
|
}
|
|
312
382
|
q.dispose();
|
|
383
|
+
if (shared.kind === "plain" && shared.vRotated) {
|
|
384
|
+
// Attention is linear in V: one InvFWHT on the [B,H,L,D] output
|
|
385
|
+
// replaces one per cached token (TurboQuant deferred-V).
|
|
386
|
+
attn = disposing(attn, tqUnrotateValues(attn));
|
|
387
|
+
}
|
|
313
388
|
const attnT = ops.transposeAxes(attn, [0, 2, 1, 3]);
|
|
314
389
|
attn.dispose();
|
|
315
390
|
const merged = ops.reshape(attnT, [B, L, -1]);
|
|
@@ -334,14 +409,11 @@ class MLP {
|
|
|
334
409
|
forward(x: MlxArray): MlxArray {
|
|
335
410
|
const g = this.gate.forward(x);
|
|
336
411
|
const u = this.up.forward(x);
|
|
337
|
-
// Fused GeGLU perf kernel (opt-in, not in a compiled trace): one pass
|
|
338
|
-
// instead of ~9 element-wise kernels. Off → the bit-exact spelled path.
|
|
339
|
-
// In training mode the differentiable wrapper (kernel forward + hand-derived
|
|
340
|
-
// vjp) is used so autograd can flow AND the backward recomputes the gelu
|
|
341
|
-
// from the primal instead of retaining the spelled-out intermediates.
|
|
342
412
|
let h: MlxArray;
|
|
343
|
-
if (
|
|
344
|
-
|
|
413
|
+
if (compiledGegluActive() && !isCompiledTrace()) {
|
|
414
|
+
// Oracle geglu(gate_proj(x), up_proj(x)): @mx.compile'd gelu_approx·mul in
|
|
415
|
+
// one kernel set (the default; = mlx-lm). Autograd-safe → covers training.
|
|
416
|
+
h = compiledGeglu(g, u);
|
|
345
417
|
g.dispose();
|
|
346
418
|
u.dispose();
|
|
347
419
|
} else {
|
|
@@ -464,12 +536,21 @@ class SwitchGLU {
|
|
|
464
536
|
const xUp = this.up.forward(h, idx, doSort);
|
|
465
537
|
const xGate = this.gate.forward(h, idx, doSort);
|
|
466
538
|
h.dispose();
|
|
467
|
-
// GeGLU activation: gelu_approx(gate) · up (same composition as MLP)
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
539
|
+
// GeGLU activation: gelu_approx(gate) · up (same composition as MLP). The
|
|
540
|
+
// oracle SwitchGLU uses the SAME @mx.compile'd geglu (activation=GeGLU(),
|
|
541
|
+
// switch_layers.py) → the compiled path routes through the shared closure.
|
|
542
|
+
let mid: MlxArray;
|
|
543
|
+
if (compiledGegluActive() && !isCompiledTrace()) {
|
|
544
|
+
mid = compiledGeglu(xGate, xUp);
|
|
545
|
+
xGate.dispose();
|
|
546
|
+
xUp.dispose();
|
|
547
|
+
} else {
|
|
548
|
+
const act = ops.geluApprox(xGate);
|
|
549
|
+
xGate.dispose();
|
|
550
|
+
mid = ops.mul(act, xUp);
|
|
551
|
+
act.dispose();
|
|
552
|
+
xUp.dispose();
|
|
553
|
+
}
|
|
473
554
|
let y = this.down.forward(mid, idx, doSort);
|
|
474
555
|
mid.dispose();
|
|
475
556
|
if (idx !== indices) idx.dispose();
|
|
@@ -625,17 +706,12 @@ export class DecoderLayer {
|
|
|
625
706
|
f.dispose();
|
|
626
707
|
|
|
627
708
|
// per-layer input gating (reference gemma4_text DecoderLayer). gelu(gate)·pli
|
|
628
|
-
// is the second GeGLU site —
|
|
629
|
-
// bit-exact spelled-out gelu+mul on the inference path (unchanged).
|
|
709
|
+
// is the second GeGLU site — the bit-exact spelled-out gelu+mul.
|
|
630
710
|
if (this.perLayerGate && this.perLayerProjection && this.postPerLayerNorm && perLayerInput) {
|
|
631
711
|
const res2 = h;
|
|
632
712
|
let gate = this.perLayerGate.forward(h);
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
} else {
|
|
636
|
-
gate = disposing(gate, ops.geluApprox(gate));
|
|
637
|
-
gate = disposing(gate, ops.mul(gate, perLayerInput));
|
|
638
|
-
}
|
|
713
|
+
gate = disposing(gate, ops.geluApprox(gate));
|
|
714
|
+
gate = disposing(gate, ops.mul(gate, perLayerInput));
|
|
639
715
|
gate = disposing(gate, this.perLayerProjection.forward(gate));
|
|
640
716
|
gate = disposing(gate, this.postPerLayerNorm.forward(gate));
|
|
641
717
|
h = ops.add(res2, gate);
|
|
@@ -805,25 +881,35 @@ export class Gemma4Model {
|
|
|
805
881
|
}
|
|
806
882
|
|
|
807
883
|
/** Pre-merged (unscaled) input embeddings → hidden states. Used by the
|
|
808
|
-
* vision path; `bidir` (bool [L]) marks image tokens,
|
|
809
|
-
* bidirectionally among themselves (use_bidirectional_attention:
|
|
810
|
-
* "vision" — text stays causal
|
|
811
|
-
*
|
|
812
|
-
*
|
|
884
|
+
* multimodal (vision/audio) path; `bidir` (bool [L]) marks image tokens,
|
|
885
|
+
* which attend bidirectionally among themselves (use_bidirectional_attention:
|
|
886
|
+
* "vision" — text stays causal; audio prompts pass NO bidir mask, and a
|
|
887
|
+
* mixed image+audio prompt drops the image overlay too, §3.3 Q1 of
|
|
888
|
+
* docs/design/audio-input-plan.md). `ids` ([1, L], the spliced token ids)
|
|
889
|
+
* is required for per-layer-input models (e2b/e4b): multimodal soft-token
|
|
890
|
+
* positions get token 0's per-layer embedding, and their tower content
|
|
813
891
|
* enters only through the projection term (the merged hidden). Matches
|
|
814
|
-
* optiq's frontend: `zeroed = where(text_mask, input_ids, 0)`.
|
|
892
|
+
* optiq's frontend: `zeroed = where(text_mask, input_ids, 0)`.
|
|
893
|
+
*
|
|
894
|
+
* `multimodal` (bool [L]) is the per-layer-input ZEROING mask — the UNION
|
|
895
|
+
* of multimodal soft tokens (image | audio, §3.3 Q2), decoupled from the
|
|
896
|
+
* attention overlay: an audio-only prompt has no bidir mask but its audio
|
|
897
|
+
* positions must still be zeroed. When null (the legacy vision call
|
|
898
|
+
* shape), the zeroing mask falls back to `bidir` — vision passes the same
|
|
899
|
+
* image mask for both roles, so that path is unchanged. */
|
|
815
900
|
forwardEmbeddings(
|
|
816
901
|
embeds: MlxArray, cache: Cache[], bidir: MlxArray | null,
|
|
817
|
-
ids: MlxArray | null = null,
|
|
902
|
+
ids: MlxArray | null = null, multimodal: MlxArray | null = null,
|
|
818
903
|
): MlxArray {
|
|
819
904
|
const h = ops.mulScalar(embeds, this.embedScale);
|
|
820
905
|
if (this.perLayerWidth === 0) return this.forwardLayers(h, cache, bidir, null);
|
|
821
906
|
|
|
822
907
|
if (!ids)
|
|
823
|
-
throw new Error("per-layer-input
|
|
908
|
+
throw new Error("per-layer-input multimodal models require token ids");
|
|
909
|
+
const zeroing = multimodal ?? bidir;
|
|
824
910
|
let plIds = ids;
|
|
825
|
-
if (
|
|
826
|
-
const cond = ops.reshape(
|
|
911
|
+
if (zeroing) {
|
|
912
|
+
const cond = ops.reshape(zeroing, ids.shape);
|
|
827
913
|
const zero = ops.zeros(ids.shape, ids.dtype);
|
|
828
914
|
plIds = ops.where(cond, zero, ids);
|
|
829
915
|
cond.dispose();
|
|
@@ -1180,7 +1266,10 @@ export class Gemma4Model {
|
|
|
1180
1266
|
logitsFromHidden(h: MlxArray): MlxArray {
|
|
1181
1267
|
let logits = this.embed.asLinear(h);
|
|
1182
1268
|
const softcap = this.config.text.finalLogitSoftcapping;
|
|
1183
|
-
if (softcap !== null)
|
|
1269
|
+
if (softcap !== null)
|
|
1270
|
+
logits = disposing(logits, compiledGegluActive() && !isCompiledTrace()
|
|
1271
|
+
? compiledLogitSoftcap(logits, softcap) // fused, matches @mx.compile logit_softcap
|
|
1272
|
+
: logitSoftcap(logits, softcap));
|
|
1184
1273
|
return logits;
|
|
1185
1274
|
}
|
|
1186
1275
|
|
|
@@ -24,9 +24,6 @@ import {
|
|
|
24
24
|
type Mask,
|
|
25
25
|
type SharedKv,
|
|
26
26
|
} from "../gemma4-base";
|
|
27
|
-
import {
|
|
28
|
-
fusedDecodeKernelSupported, fusedDecodeSdpa, perfKernelEnabled,
|
|
29
|
-
} from "../fused-decode-kernel";
|
|
30
27
|
import { Gemma4Model, type DecoderLayer } from "../gemma4";
|
|
31
28
|
|
|
32
29
|
export const FINGERPRINT = "9f812d2eb461fcbe";
|
|
@@ -73,9 +70,7 @@ function donorSlidQuantG64B4VScal(layer: DecoderLayer, x: MlxArray, mask: Mask,
|
|
|
73
70
|
q = disposing(q, ops.transposeAxes(q, [0, 2, 1, 3]));
|
|
74
71
|
q = disposing(q, a.rope(q, cache.ropeOffsetArr ?? offset));
|
|
75
72
|
// dispatch-site constants: bits=4 group_size=64 nRep=2 head_dim=256
|
|
76
|
-
const attn =
|
|
77
|
-
? fusedDecodeSdpa(q, kq, vq, 64, 4)
|
|
78
|
-
: (L > 1 || process.env.MLX_BUN_FUSED_DECODE === "1") && fusedSdpaRuntimeOk(q, mask)
|
|
73
|
+
const attn = L > 1 && fusedSdpaRuntimeOk(q, mask)
|
|
79
74
|
? quantizedSdpaTiled(q, kq, vq, 1.0, mask, 64, 4)
|
|
80
75
|
: quantizedSdpaUnfused(q, kq, vq, 1.0, mask, 64, 4);
|
|
81
76
|
q.dispose();
|
|
@@ -129,9 +124,7 @@ function donorSlidQuantG64B8VScal(layer: DecoderLayer, x: MlxArray, mask: Mask,
|
|
|
129
124
|
q = disposing(q, ops.transposeAxes(q, [0, 2, 1, 3]));
|
|
130
125
|
q = disposing(q, a.rope(q, cache.ropeOffsetArr ?? offset));
|
|
131
126
|
// dispatch-site constants: bits=8 group_size=64 nRep=2 head_dim=256
|
|
132
|
-
const attn =
|
|
133
|
-
? fusedDecodeSdpa(q, kq, vq, 64, 8)
|
|
134
|
-
: (L > 1 || process.env.MLX_BUN_FUSED_DECODE === "1") && fusedSdpaRuntimeOk(q, mask)
|
|
127
|
+
const attn = L > 1 && fusedSdpaRuntimeOk(q, mask)
|
|
135
128
|
? quantizedSdpaTiled(q, kq, vq, 1.0, mask, 64, 8)
|
|
136
129
|
: quantizedSdpaUnfused(q, kq, vq, 1.0, mask, 64, 8);
|
|
137
130
|
q.dispose();
|
|
@@ -182,9 +175,7 @@ function donorFullQuantG64B4KeqVScal(layer: DecoderLayer, x: MlxArray, mask: Mas
|
|
|
182
175
|
q = disposing(q, ops.transposeAxes(q, [0, 2, 1, 3]));
|
|
183
176
|
q = disposing(q, a.rope(q, cache.ropeOffsetArr ?? offset));
|
|
184
177
|
// dispatch-site constants: bits=4 group_size=64 nRep=16 head_dim=512
|
|
185
|
-
const attn =
|
|
186
|
-
? fusedDecodeSdpa(q, kq, vq, 64, 4)
|
|
187
|
-
: (L > 1 || process.env.MLX_BUN_FUSED_DECODE === "1") && fusedSdpaRuntimeOk(q, mask)
|
|
178
|
+
const attn = L > 1 && fusedSdpaRuntimeOk(q, mask)
|
|
188
179
|
? quantizedSdpaTiled(q, kq, vq, 1.0, mask, 64, 4)
|
|
189
180
|
: quantizedSdpaUnfused(q, kq, vq, 1.0, mask, 64, 4);
|
|
190
181
|
q.dispose();
|
|
@@ -366,7 +357,12 @@ export class GeneratedGemma4 extends Gemma4Model {
|
|
|
366
357
|
protected override forwardLayers(
|
|
367
358
|
h0: MlxArray, cache: Cache[], bidir: MlxArray | null, ids: MlxArray | null,
|
|
368
359
|
): MlxArray {
|
|
369
|
-
|
|
360
|
+
// hiddenTap (DSpark H_ctx capture) lives in the monolith's forwardLayers —
|
|
361
|
+
// this unrolled forward never calls captureLayer, so a tapped call MUST
|
|
362
|
+
// fall back or the spec path silently captures nothing and throws. Tapped
|
|
363
|
+
// forwards are only the spec path's prefill/verify; the monolith is the
|
|
364
|
+
// bit-exact reference, so this is correct-but-unspecialized.
|
|
365
|
+
if (bidir !== null || this.hiddenTap !== null || !this.#matches(cache))
|
|
370
366
|
return super.forwardLayers(h0, cache, bidir, ids);
|
|
371
367
|
generatedForwardUses++;
|
|
372
368
|
const L = h0.shape[1]!;
|
|
@@ -70,7 +70,7 @@ function donorSlidQuantG64B8VMoeScal(layer: DecoderLayer, x: MlxArray, mask: Mas
|
|
|
70
70
|
q = disposing(q, ops.transposeAxes(q, [0, 2, 1, 3]));
|
|
71
71
|
q = disposing(q, a.rope(q, cache.ropeOffsetArr ?? offset));
|
|
72
72
|
// dispatch-site constants: bits=8 group_size=64 nRep=2 head_dim=256
|
|
73
|
-
const attn =
|
|
73
|
+
const attn = L > 1 && fusedSdpaRuntimeOk(q, mask)
|
|
74
74
|
? quantizedSdpaTiled(q, kq, vq, 1.0, mask, 64, 8)
|
|
75
75
|
: quantizedSdpaUnfused(q, kq, vq, 1.0, mask, 64, 8);
|
|
76
76
|
q.dispose();
|
|
@@ -134,7 +134,7 @@ function donorSlidQuantG64B4VMoeScal(layer: DecoderLayer, x: MlxArray, mask: Mas
|
|
|
134
134
|
q = disposing(q, ops.transposeAxes(q, [0, 2, 1, 3]));
|
|
135
135
|
q = disposing(q, a.rope(q, cache.ropeOffsetArr ?? offset));
|
|
136
136
|
// dispatch-site constants: bits=4 group_size=64 nRep=2 head_dim=256
|
|
137
|
-
const attn =
|
|
137
|
+
const attn = L > 1 && fusedSdpaRuntimeOk(q, mask)
|
|
138
138
|
? quantizedSdpaTiled(q, kq, vq, 1.0, mask, 64, 4)
|
|
139
139
|
: quantizedSdpaUnfused(q, kq, vq, 1.0, mask, 64, 4);
|
|
140
140
|
q.dispose();
|
|
@@ -195,7 +195,7 @@ function donorFullQuantG64B8KeqVMoeScal(layer: DecoderLayer, x: MlxArray, mask:
|
|
|
195
195
|
q = disposing(q, ops.transposeAxes(q, [0, 2, 1, 3]));
|
|
196
196
|
q = disposing(q, a.rope(q, cache.ropeOffsetArr ?? offset));
|
|
197
197
|
// dispatch-site constants: bits=8 group_size=64 nRep=8 head_dim=512
|
|
198
|
-
const attn =
|
|
198
|
+
const attn = L > 1 && fusedSdpaRuntimeOk(q, mask)
|
|
199
199
|
? quantizedSdpaTiled(q, kq, vq, 1.0, mask, 64, 8)
|
|
200
200
|
: quantizedSdpaUnfused(q, kq, vq, 1.0, mask, 64, 8);
|
|
201
201
|
q.dispose();
|
|
@@ -256,7 +256,7 @@ function donorFullQuantG64B4KeqVMoeScal(layer: DecoderLayer, x: MlxArray, mask:
|
|
|
256
256
|
q = disposing(q, ops.transposeAxes(q, [0, 2, 1, 3]));
|
|
257
257
|
q = disposing(q, a.rope(q, cache.ropeOffsetArr ?? offset));
|
|
258
258
|
// dispatch-site constants: bits=4 group_size=64 nRep=8 head_dim=512
|
|
259
|
-
const attn =
|
|
259
|
+
const attn = L > 1 && fusedSdpaRuntimeOk(q, mask)
|
|
260
260
|
? quantizedSdpaTiled(q, kq, vq, 1.0, mask, 64, 4)
|
|
261
261
|
: quantizedSdpaUnfused(q, kq, vq, 1.0, mask, 64, 4);
|
|
262
262
|
q.dispose();
|
|
@@ -394,7 +394,12 @@ export class GeneratedGemma4 extends Gemma4Model {
|
|
|
394
394
|
protected override forwardLayers(
|
|
395
395
|
h0: MlxArray, cache: Cache[], bidir: MlxArray | null, ids: MlxArray | null,
|
|
396
396
|
): MlxArray {
|
|
397
|
-
|
|
397
|
+
// hiddenTap (DSpark H_ctx capture) lives in the monolith's forwardLayers —
|
|
398
|
+
// this unrolled forward never calls captureLayer, so a tapped call MUST
|
|
399
|
+
// fall back or the spec path silently captures nothing and throws. Tapped
|
|
400
|
+
// forwards are only the spec path's prefill/verify; the monolith is the
|
|
401
|
+
// bit-exact reference, so this is correct-but-unspecialized.
|
|
402
|
+
if (bidir !== null || this.hiddenTap !== null || !this.#matches(cache))
|
|
398
403
|
return super.forwardLayers(h0, cache, bidir, ids);
|
|
399
404
|
generatedForwardUses++;
|
|
400
405
|
const L = h0.shape[1]!;
|
|
@@ -88,7 +88,7 @@ function donorSlidQuantG64B4VPliScal(layer: DecoderLayer, x: MlxArray, mask: Mas
|
|
|
88
88
|
q = disposing(q, ops.transposeAxes(q, [0, 2, 1, 3]));
|
|
89
89
|
q = disposing(q, a.rope(q, cache.ropeOffsetArr ?? offset));
|
|
90
90
|
// dispatch-site constants: bits=4 group_size=64 nRep=4 head_dim=256
|
|
91
|
-
const attn =
|
|
91
|
+
const attn = L > 1 && fusedSdpaRuntimeOk(q, mask)
|
|
92
92
|
? quantizedSdpaTiled(q, kq, vq, 1.0, mask, 64, 4)
|
|
93
93
|
: quantizedSdpaUnfused(q, kq, vq, 1.0, mask, 64, 4);
|
|
94
94
|
q.dispose();
|
|
@@ -151,7 +151,7 @@ function donorSlidQuantG64B8VPliScal(layer: DecoderLayer, x: MlxArray, mask: Mas
|
|
|
151
151
|
q = disposing(q, ops.transposeAxes(q, [0, 2, 1, 3]));
|
|
152
152
|
q = disposing(q, a.rope(q, cache.ropeOffsetArr ?? offset));
|
|
153
153
|
// dispatch-site constants: bits=8 group_size=64 nRep=4 head_dim=256
|
|
154
|
-
const attn =
|
|
154
|
+
const attn = L > 1 && fusedSdpaRuntimeOk(q, mask)
|
|
155
155
|
? quantizedSdpaTiled(q, kq, vq, 1.0, mask, 64, 8)
|
|
156
156
|
: quantizedSdpaUnfused(q, kq, vq, 1.0, mask, 64, 8);
|
|
157
157
|
q.dispose();
|
|
@@ -214,7 +214,7 @@ function donorFullQuantG64B4VPliScal(layer: DecoderLayer, x: MlxArray, mask: Mas
|
|
|
214
214
|
q = disposing(q, ops.transposeAxes(q, [0, 2, 1, 3]));
|
|
215
215
|
q = disposing(q, a.rope(q, cache.ropeOffsetArr ?? offset));
|
|
216
216
|
// dispatch-site constants: bits=4 group_size=64 nRep=4 head_dim=512
|
|
217
|
-
const attn =
|
|
217
|
+
const attn = L > 1 && fusedSdpaRuntimeOk(q, mask)
|
|
218
218
|
? quantizedSdpaTiled(q, kq, vq, 1.0, mask, 64, 4)
|
|
219
219
|
: quantizedSdpaUnfused(q, kq, vq, 1.0, mask, 64, 4);
|
|
220
220
|
q.dispose();
|
|
@@ -277,7 +277,7 @@ function donorFullQuantG64B8VPliScal(layer: DecoderLayer, x: MlxArray, mask: Mas
|
|
|
277
277
|
q = disposing(q, ops.transposeAxes(q, [0, 2, 1, 3]));
|
|
278
278
|
q = disposing(q, a.rope(q, cache.ropeOffsetArr ?? offset));
|
|
279
279
|
// dispatch-site constants: bits=8 group_size=64 nRep=4 head_dim=512
|
|
280
|
-
const attn =
|
|
280
|
+
const attn = L > 1 && fusedSdpaRuntimeOk(q, mask)
|
|
281
281
|
? quantizedSdpaTiled(q, kq, vq, 1.0, mask, 64, 8)
|
|
282
282
|
: quantizedSdpaUnfused(q, kq, vq, 1.0, mask, 64, 8);
|
|
283
283
|
q.dispose();
|
|
@@ -340,7 +340,7 @@ function donorSlidQuantG64B4VPliScalExp(layer: DecoderLayer, x: MlxArray, mask:
|
|
|
340
340
|
q = disposing(q, ops.transposeAxes(q, [0, 2, 1, 3]));
|
|
341
341
|
q = disposing(q, a.rope(q, cache.ropeOffsetArr ?? offset));
|
|
342
342
|
// dispatch-site constants: bits=4 group_size=64 nRep=4 head_dim=256
|
|
343
|
-
const attn =
|
|
343
|
+
const attn = L > 1 && fusedSdpaRuntimeOk(q, mask)
|
|
344
344
|
? quantizedSdpaTiled(q, kq, vq, 1.0, mask, 64, 4)
|
|
345
345
|
: quantizedSdpaUnfused(q, kq, vq, 1.0, mask, 64, 4);
|
|
346
346
|
q.dispose();
|
|
@@ -403,7 +403,7 @@ function donorFullQuantG64B4VPliScalExp(layer: DecoderLayer, x: MlxArray, mask:
|
|
|
403
403
|
q = disposing(q, ops.transposeAxes(q, [0, 2, 1, 3]));
|
|
404
404
|
q = disposing(q, a.rope(q, cache.ropeOffsetArr ?? offset));
|
|
405
405
|
// dispatch-site constants: bits=4 group_size=64 nRep=4 head_dim=512
|
|
406
|
-
const attn =
|
|
406
|
+
const attn = L > 1 && fusedSdpaRuntimeOk(q, mask)
|
|
407
407
|
? quantizedSdpaTiled(q, kq, vq, 1.0, mask, 64, 4)
|
|
408
408
|
: quantizedSdpaUnfused(q, kq, vq, 1.0, mask, 64, 4);
|
|
409
409
|
q.dispose();
|
|
@@ -449,7 +449,7 @@ function sharerSlidQuantG64B4VPliScal(layer: DecoderLayer, x: MlxArray, mask: Ma
|
|
|
449
449
|
q = disposing(q, a.rope(q, shared.offsetArr ?? shared.offset));
|
|
450
450
|
if (shared.kind !== "quant") throw new Error("generated sharer expected quant shared KV");
|
|
451
451
|
// dispatch-site constants: bits=4 group_size=64 nRep=4 head_dim=256
|
|
452
|
-
const attn =
|
|
452
|
+
const attn = L > 1 && fusedSdpaRuntimeOk(q, mask)
|
|
453
453
|
? quantizedSdpaTiled(q, shared.keys, shared.values, 1.0, mask, 64, 4)
|
|
454
454
|
: quantizedSdpaUnfused(q, shared.keys, shared.values, 1.0, mask, 64, 4);
|
|
455
455
|
q.dispose();
|
|
@@ -494,7 +494,7 @@ function sharerFullQuantG64B4VPliScal(layer: DecoderLayer, x: MlxArray, mask: Ma
|
|
|
494
494
|
q = disposing(q, a.rope(q, shared.offsetArr ?? shared.offset));
|
|
495
495
|
if (shared.kind !== "quant") throw new Error("generated sharer expected quant shared KV");
|
|
496
496
|
// dispatch-site constants: bits=4 group_size=64 nRep=4 head_dim=512
|
|
497
|
-
const attn =
|
|
497
|
+
const attn = L > 1 && fusedSdpaRuntimeOk(q, mask)
|
|
498
498
|
? quantizedSdpaTiled(q, shared.keys, shared.values, 1.0, mask, 64, 4)
|
|
499
499
|
: quantizedSdpaUnfused(q, shared.keys, shared.values, 1.0, mask, 64, 4);
|
|
500
500
|
q.dispose();
|
|
@@ -612,7 +612,12 @@ export class GeneratedGemma4 extends Gemma4Model {
|
|
|
612
612
|
protected override forwardLayers(
|
|
613
613
|
h0: MlxArray, cache: Cache[], bidir: MlxArray | null, ids: MlxArray | null,
|
|
614
614
|
): MlxArray {
|
|
615
|
-
|
|
615
|
+
// hiddenTap (DSpark H_ctx capture) lives in the monolith's forwardLayers —
|
|
616
|
+
// this unrolled forward never calls captureLayer, so a tapped call MUST
|
|
617
|
+
// fall back or the spec path silently captures nothing and throws. Tapped
|
|
618
|
+
// forwards are only the spec path's prefill/verify; the monolith is the
|
|
619
|
+
// bit-exact reference, so this is correct-but-unspecialized.
|
|
620
|
+
if (bidir !== null || ids === null || this.hiddenTap !== null || !this.#matches(cache))
|
|
616
621
|
return super.forwardLayers(h0, cache, bidir, ids);
|
|
617
622
|
generatedForwardUses++;
|
|
618
623
|
const L = h0.shape[1]!;
|