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/mlx/autograd.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Native autograd via mlx-c value_and_grad over a JS loss closure.
|
|
2
2
|
//
|
|
3
|
-
// Encapsulates the exact FFI lifecycle proven in spikes/phase-train-vag.ts:
|
|
3
|
+
// Encapsulates the exact FFI lifecycle proven in lab/spikes/phase-train-vag.ts:
|
|
4
4
|
// JS loss closure (JSCallback) → mlx_closure_new_func_payload →
|
|
5
5
|
// mlx_value_and_grad → mlx_closure_value_and_grad_apply → read grads.
|
|
6
6
|
//
|
|
@@ -15,6 +15,23 @@ import { JSCallback, ptr, read } from "bun:ffi";
|
|
|
15
15
|
import { C, takeMlxError } from "./ffi";
|
|
16
16
|
import { MlxArray } from "./array";
|
|
17
17
|
|
|
18
|
+
type CallbackHandle = Pick<JSCallback, "ptr" | "close">;
|
|
19
|
+
type NativeCall<F extends (...args: any[]) => any> =
|
|
20
|
+
(...args: Parameters<F>) => ReturnType<F>;
|
|
21
|
+
|
|
22
|
+
/** Constructor-only dependency seam for deterministic native failure tests. */
|
|
23
|
+
export interface ValueAndGradConstructorHooks {
|
|
24
|
+
callback?: (
|
|
25
|
+
callback: ConstructorParameters<typeof JSCallback>[0],
|
|
26
|
+
definition: ConstructorParameters<typeof JSCallback>[1],
|
|
27
|
+
) => CallbackHandle;
|
|
28
|
+
closureNew?: NativeCall<typeof C.mlx_closure_new_func_payload>;
|
|
29
|
+
transformedNew?: NativeCall<typeof C.mlx_closure_value_and_grad_new>;
|
|
30
|
+
transform?: NativeCall<typeof C.mlx_value_and_grad>;
|
|
31
|
+
transformedFree?: NativeCall<typeof C.mlx_closure_value_and_grad_free>;
|
|
32
|
+
closureFree?: NativeCall<typeof C.mlx_closure_free>;
|
|
33
|
+
}
|
|
34
|
+
|
|
18
35
|
/** A reusable value-and-grad over a JS-built loss graph.
|
|
19
36
|
*
|
|
20
37
|
* `loss(primals)` receives the input arrays in argument order and must
|
|
@@ -28,24 +45,36 @@ import { MlxArray } from "./array";
|
|
|
28
45
|
* are owned by mlx — do NOT dispose them; the returned loss IS disposed
|
|
29
46
|
* of by mlx's graph. */
|
|
30
47
|
export class ValueAndGrad {
|
|
31
|
-
readonly #cb:
|
|
48
|
+
readonly #cb: CallbackHandle;
|
|
32
49
|
readonly #closure: bigint;
|
|
33
50
|
readonly #vag: bigint;
|
|
34
51
|
readonly #argIdx: Int32Array;
|
|
52
|
+
readonly #freeClosure: NativeCall<typeof C.mlx_closure_free>;
|
|
53
|
+
readonly #freeVag: NativeCall<typeof C.mlx_closure_value_and_grad_free>;
|
|
35
54
|
#closureError: string | null = null;
|
|
36
55
|
#disposed = false;
|
|
37
56
|
|
|
38
|
-
constructor(
|
|
57
|
+
constructor(
|
|
58
|
+
loss: (primals: MlxArray[]) => MlxArray,
|
|
59
|
+
argIdx: number[],
|
|
60
|
+
hooks: ValueAndGradConstructorHooks = {},
|
|
61
|
+
) {
|
|
39
62
|
if (argIdx.length === 0) throw new Error("ValueAndGrad: argIdx must be non-empty");
|
|
40
63
|
this.#argIdx = new Int32Array(argIdx);
|
|
64
|
+
const makeCallback = hooks.callback ?? ((callback, definition) => new JSCallback(callback, definition));
|
|
65
|
+
const closureNew = hooks.closureNew ?? C.mlx_closure_new_func_payload;
|
|
66
|
+
const transformedNew = hooks.transformedNew ?? C.mlx_closure_value_and_grad_new;
|
|
67
|
+
const transform = hooks.transform ?? C.mlx_value_and_grad;
|
|
68
|
+
this.#freeClosure = hooks.closureFree ?? C.mlx_closure_free;
|
|
69
|
+
this.#freeVag = hooks.transformedFree ?? C.mlx_closure_value_and_grad_free;
|
|
41
70
|
|
|
42
|
-
|
|
71
|
+
const cb = makeCallback(
|
|
43
72
|
(outPtr: number, inVec: bigint, _payload: number): number => {
|
|
44
73
|
try {
|
|
45
74
|
// Read the primals out of the input vector_array. mlx_vector_array_get
|
|
46
75
|
// returns a fresh (ref-counted) handle, so disposing the wrapper
|
|
47
76
|
// afterward is correct — it drops our reference, not mlx's graph
|
|
48
|
-
// reference (proven in spikes/phase-train-vag.ts).
|
|
77
|
+
// reference (proven in lab/spikes/phase-train-vag.ts).
|
|
49
78
|
const n = Number(C.mlx_vector_array_size(inVec));
|
|
50
79
|
const primals: MlxArray[] = [];
|
|
51
80
|
for (let i = 0; i < n; i++) {
|
|
@@ -73,16 +102,24 @@ export class ValueAndGrad {
|
|
|
73
102
|
{ args: ["ptr", "u64", "ptr"], returns: "i32" },
|
|
74
103
|
);
|
|
75
104
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
105
|
+
let closure: bigint | null = null;
|
|
106
|
+
let vagSlot: BigUint64Array | null = null;
|
|
107
|
+
try {
|
|
108
|
+
closure = closureNew(cb.ptr as never, null, null);
|
|
109
|
+
vagSlot = new BigUint64Array([transformedNew()]);
|
|
110
|
+
if (transform(ptr(vagSlot), closure, ptr(this.#argIdx), BigInt(this.#argIdx.length)) !== 0)
|
|
111
|
+
throw new Error("mlx_value_and_grad failed");
|
|
112
|
+
this.#cb = cb;
|
|
113
|
+
this.#closure = closure;
|
|
114
|
+
this.#vag = read.u64(ptr(vagSlot), 0);
|
|
115
|
+
} catch (e) {
|
|
116
|
+
// mlx-c out-params are initialized live handles. On failure the slot
|
|
117
|
+
// still owns whichever handle it contains and must be freed explicitly.
|
|
118
|
+
if (vagSlot) this.#freeVag(read.u64(ptr(vagSlot), 0));
|
|
119
|
+
if (closure !== null) this.#freeClosure(closure);
|
|
120
|
+
cb.close();
|
|
121
|
+
throw e;
|
|
84
122
|
}
|
|
85
|
-
this.#vag = read.u64(ptr(vagSlot), 0);
|
|
86
123
|
}
|
|
87
124
|
|
|
88
125
|
/** Run the loss + backward on `primals` (in argument order). Returns the
|
|
@@ -136,8 +173,8 @@ export class ValueAndGrad {
|
|
|
136
173
|
dispose(): void {
|
|
137
174
|
if (this.#disposed) return;
|
|
138
175
|
this.#disposed = true;
|
|
139
|
-
|
|
140
|
-
|
|
176
|
+
this.#freeVag(this.#vag);
|
|
177
|
+
this.#freeClosure(this.#closure);
|
|
141
178
|
this.#cb.close();
|
|
142
179
|
}
|
|
143
180
|
|
package/src/mlx/checkpoint.ts
CHANGED
|
@@ -15,6 +15,22 @@ import { JSCallback, ptr, read } from "bun:ffi";
|
|
|
15
15
|
import { C, takeMlxError } from "./ffi";
|
|
16
16
|
import { MlxArray } from "./array";
|
|
17
17
|
|
|
18
|
+
type CallbackHandle = Pick<JSCallback, "ptr" | "close">;
|
|
19
|
+
type NativeCall<F extends (...args: any[]) => any> =
|
|
20
|
+
(...args: Parameters<F>) => ReturnType<F>;
|
|
21
|
+
|
|
22
|
+
/** Constructor-only dependency seam for deterministic native failure tests. */
|
|
23
|
+
export interface CheckpointConstructorHooks {
|
|
24
|
+
callback?: (
|
|
25
|
+
callback: ConstructorParameters<typeof JSCallback>[0],
|
|
26
|
+
definition: ConstructorParameters<typeof JSCallback>[1],
|
|
27
|
+
) => CallbackHandle;
|
|
28
|
+
closureNew?: NativeCall<typeof C.mlx_closure_new_func_payload>;
|
|
29
|
+
transformedNew?: NativeCall<typeof C.mlx_closure_new>;
|
|
30
|
+
transform?: NativeCall<typeof C.mlx_checkpoint>;
|
|
31
|
+
closureFree?: NativeCall<typeof C.mlx_closure_free>;
|
|
32
|
+
}
|
|
33
|
+
|
|
18
34
|
/** A reusable checkpointed wrapper over a JS-built subgraph.
|
|
19
35
|
*
|
|
20
36
|
* `fn(inputs)` receives the input arrays (in order) and returns one or more
|
|
@@ -24,14 +40,24 @@ import { MlxArray } from "./array";
|
|
|
24
40
|
* AND to any outer-trace leaves captured by `fn` (e.g. swapped-in LoRA
|
|
25
41
|
* primals), exactly as if `fn` had been inlined. */
|
|
26
42
|
export class Checkpoint {
|
|
27
|
-
readonly #cb:
|
|
43
|
+
readonly #cb: CallbackHandle;
|
|
28
44
|
readonly #closure: bigint;
|
|
29
45
|
readonly #ckpt: bigint;
|
|
46
|
+
readonly #freeClosure: NativeCall<typeof C.mlx_closure_free>;
|
|
30
47
|
#closureError: string | null = null;
|
|
31
48
|
#disposed = false;
|
|
32
49
|
|
|
33
|
-
constructor(
|
|
34
|
-
|
|
50
|
+
constructor(
|
|
51
|
+
fn: (inputs: MlxArray[]) => MlxArray[],
|
|
52
|
+
hooks: CheckpointConstructorHooks = {},
|
|
53
|
+
) {
|
|
54
|
+
const makeCallback = hooks.callback ?? ((callback, definition) => new JSCallback(callback, definition));
|
|
55
|
+
const closureNew = hooks.closureNew ?? C.mlx_closure_new_func_payload;
|
|
56
|
+
const transformedNew = hooks.transformedNew ?? C.mlx_closure_new;
|
|
57
|
+
const transform = hooks.transform ?? C.mlx_checkpoint;
|
|
58
|
+
this.#freeClosure = hooks.closureFree ?? C.mlx_closure_free;
|
|
59
|
+
|
|
60
|
+
const cb = makeCallback(
|
|
35
61
|
(outPtr: number, inVec: bigint, _payload: number): number => {
|
|
36
62
|
try {
|
|
37
63
|
const n = Number(C.mlx_vector_array_size(inVec));
|
|
@@ -58,15 +84,22 @@ export class Checkpoint {
|
|
|
58
84
|
{ args: ["ptr", "u64", "ptr"], returns: "i32" },
|
|
59
85
|
);
|
|
60
86
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
87
|
+
let closure: bigint | null = null;
|
|
88
|
+
let slot: BigUint64Array | null = null;
|
|
89
|
+
try {
|
|
90
|
+
closure = closureNew(cb.ptr as never, null, null);
|
|
91
|
+
slot = new BigUint64Array([transformedNew()]);
|
|
92
|
+
if (transform(ptr(slot), closure) !== 0)
|
|
93
|
+
throw new Error(`mlx_checkpoint failed: ${takeMlxError() ?? ""}`);
|
|
94
|
+
this.#cb = cb;
|
|
95
|
+
this.#closure = closure;
|
|
96
|
+
this.#ckpt = read.u64(ptr(slot), 0);
|
|
97
|
+
} catch (e) {
|
|
98
|
+
if (slot) this.#freeClosure(read.u64(ptr(slot), 0));
|
|
99
|
+
if (closure !== null) this.#freeClosure(closure);
|
|
100
|
+
cb.close();
|
|
101
|
+
throw e;
|
|
68
102
|
}
|
|
69
|
-
this.#ckpt = read.u64(ptr(slot), 0);
|
|
70
103
|
}
|
|
71
104
|
|
|
72
105
|
/** Apply the checkpointed fn. Returns its outputs as owned MlxArrays. */
|
|
@@ -105,8 +138,8 @@ export class Checkpoint {
|
|
|
105
138
|
dispose(): void {
|
|
106
139
|
if (this.#disposed) return;
|
|
107
140
|
this.#disposed = true;
|
|
108
|
-
|
|
109
|
-
|
|
141
|
+
this.#freeClosure(this.#ckpt);
|
|
142
|
+
this.#freeClosure(this.#closure);
|
|
110
143
|
this.#cb.close();
|
|
111
144
|
}
|
|
112
145
|
|
package/src/mlx/custom-vjp.ts
CHANGED
|
@@ -15,6 +15,24 @@ import { JSCallback, ptr, read } from "bun:ffi";
|
|
|
15
15
|
import { C, takeMlxError } from "./ffi";
|
|
16
16
|
import { MlxArray } from "./array";
|
|
17
17
|
|
|
18
|
+
type CallbackHandle = Pick<JSCallback, "ptr" | "close">;
|
|
19
|
+
type NativeCall<F extends (...args: any[]) => any> =
|
|
20
|
+
(...args: Parameters<F>) => ReturnType<F>;
|
|
21
|
+
|
|
22
|
+
/** Constructor-only dependency seam for deterministic native failure tests. */
|
|
23
|
+
export interface CustomVjpConstructorHooks {
|
|
24
|
+
callback?: (
|
|
25
|
+
callback: ConstructorParameters<typeof JSCallback>[0],
|
|
26
|
+
definition: ConstructorParameters<typeof JSCallback>[1],
|
|
27
|
+
) => CallbackHandle;
|
|
28
|
+
forwardClosureNew?: NativeCall<typeof C.mlx_closure_new_func_payload>;
|
|
29
|
+
vjpClosureNew?: NativeCall<typeof C.mlx_closure_custom_new_func_payload>;
|
|
30
|
+
transformedNew?: NativeCall<typeof C.mlx_closure_new>;
|
|
31
|
+
transform?: NativeCall<typeof C.mlx_custom_vjp>;
|
|
32
|
+
closureFree?: NativeCall<typeof C.mlx_closure_free>;
|
|
33
|
+
customClosureFree?: NativeCall<typeof C.mlx_closure_custom_free>;
|
|
34
|
+
}
|
|
35
|
+
|
|
18
36
|
function readVec(vec: bigint): MlxArray[] {
|
|
19
37
|
const n = Number(C.mlx_vector_array_size(vec));
|
|
20
38
|
const out: MlxArray[] = [];
|
|
@@ -41,19 +59,30 @@ function setVec(outPtr: number, arrays: MlxArray[]): void {
|
|
|
41
59
|
* pure functions of their arguments — the vjp re-reads everything from the
|
|
42
60
|
* graph during the enclosing backward. */
|
|
43
61
|
export class CustomVjp {
|
|
44
|
-
readonly #fwdCb:
|
|
45
|
-
readonly #vjpCb:
|
|
62
|
+
readonly #fwdCb: CallbackHandle;
|
|
63
|
+
readonly #vjpCb: CallbackHandle;
|
|
46
64
|
readonly #fwdClosure: bigint;
|
|
47
65
|
readonly #vjpClosure: bigint;
|
|
48
66
|
readonly #combined: bigint;
|
|
67
|
+
readonly #freeClosure: NativeCall<typeof C.mlx_closure_free>;
|
|
68
|
+
readonly #freeCustomClosure: NativeCall<typeof C.mlx_closure_custom_free>;
|
|
49
69
|
#error: string | null = null;
|
|
50
70
|
#disposed = false;
|
|
51
71
|
|
|
52
72
|
constructor(
|
|
53
73
|
fwd: (inputs: MlxArray[]) => MlxArray[],
|
|
54
74
|
vjp: (primals: MlxArray[], cotangents: MlxArray[], outputs: MlxArray[]) => MlxArray[],
|
|
75
|
+
hooks: CustomVjpConstructorHooks = {},
|
|
55
76
|
) {
|
|
56
|
-
|
|
77
|
+
const makeCallback = hooks.callback ?? ((callback, definition) => new JSCallback(callback, definition));
|
|
78
|
+
const forwardClosureNew = hooks.forwardClosureNew ?? C.mlx_closure_new_func_payload;
|
|
79
|
+
const vjpClosureNew = hooks.vjpClosureNew ?? C.mlx_closure_custom_new_func_payload;
|
|
80
|
+
const transformedNew = hooks.transformedNew ?? C.mlx_closure_new;
|
|
81
|
+
const transform = hooks.transform ?? C.mlx_custom_vjp;
|
|
82
|
+
this.#freeClosure = hooks.closureFree ?? C.mlx_closure_free;
|
|
83
|
+
this.#freeCustomClosure = hooks.customClosureFree ?? C.mlx_closure_custom_free;
|
|
84
|
+
|
|
85
|
+
const fwdCb = makeCallback(
|
|
57
86
|
(outPtr: number, inVec: bigint, _payload: number): number => {
|
|
58
87
|
try {
|
|
59
88
|
const inputs = readVec(inVec);
|
|
@@ -70,37 +99,56 @@ export class CustomVjp {
|
|
|
70
99
|
{ args: ["ptr", "u64", "ptr"], returns: "i32" },
|
|
71
100
|
);
|
|
72
101
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
102
|
+
let vjpCb: CallbackHandle;
|
|
103
|
+
try {
|
|
104
|
+
vjpCb = makeCallback(
|
|
105
|
+
(resPtr: number, primalsVec: bigint, cotsVec: bigint, outsVec: bigint, _payload: number): number => {
|
|
106
|
+
try {
|
|
107
|
+
const primals = readVec(primalsVec);
|
|
108
|
+
const cots = readVec(cotsVec);
|
|
109
|
+
const outs = readVec(outsVec);
|
|
110
|
+
const grads = vjp(primals, cots, outs);
|
|
111
|
+
setVec(resPtr, grads);
|
|
112
|
+
for (const a of [...primals, ...cots, ...outs]) a.dispose();
|
|
113
|
+
for (const g of grads) g.dispose();
|
|
114
|
+
return 0;
|
|
115
|
+
} catch (e) {
|
|
116
|
+
this.#error = e instanceof Error ? e.message : String(e);
|
|
117
|
+
return 1;
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
{ args: ["ptr", "u64", "u64", "u64", "ptr"], returns: "i32" },
|
|
121
|
+
);
|
|
122
|
+
} catch (e) {
|
|
123
|
+
// Callback construction is itself fallible. The native-closure guard
|
|
124
|
+
// below starts only after both callbacks exist, so this partial state
|
|
125
|
+
// must release the already-created forward callback here.
|
|
126
|
+
fwdCb.close();
|
|
127
|
+
throw e;
|
|
128
|
+
}
|
|
94
129
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
130
|
+
let fwdClosure: bigint | null = null;
|
|
131
|
+
let vjpClosure: bigint | null = null;
|
|
132
|
+
let slot: BigUint64Array | null = null;
|
|
133
|
+
try {
|
|
134
|
+
fwdClosure = forwardClosureNew(fwdCb.ptr as never, null, null);
|
|
135
|
+
vjpClosure = vjpClosureNew(vjpCb.ptr as never, null, null);
|
|
136
|
+
slot = new BigUint64Array([transformedNew()]);
|
|
137
|
+
if (transform(ptr(slot), fwdClosure, vjpClosure) !== 0)
|
|
138
|
+
throw new Error(`mlx_custom_vjp failed: ${takeMlxError() ?? ""}`);
|
|
139
|
+
this.#fwdCb = fwdCb;
|
|
140
|
+
this.#vjpCb = vjpCb;
|
|
141
|
+
this.#fwdClosure = fwdClosure;
|
|
142
|
+
this.#vjpClosure = vjpClosure;
|
|
143
|
+
this.#combined = read.u64(ptr(slot), 0);
|
|
144
|
+
} catch (e) {
|
|
145
|
+
if (slot) this.#freeClosure(read.u64(ptr(slot), 0));
|
|
146
|
+
if (fwdClosure !== null) this.#freeClosure(fwdClosure);
|
|
147
|
+
if (vjpClosure !== null) this.#freeCustomClosure(vjpClosure);
|
|
148
|
+
fwdCb.close();
|
|
149
|
+
vjpCb.close();
|
|
150
|
+
throw e;
|
|
102
151
|
}
|
|
103
|
-
this.#combined = read.u64(ptr(slot), 0);
|
|
104
152
|
}
|
|
105
153
|
|
|
106
154
|
apply(inputs: MlxArray[]): MlxArray[] {
|
|
@@ -127,9 +175,9 @@ export class CustomVjp {
|
|
|
127
175
|
dispose(): void {
|
|
128
176
|
if (this.#disposed) return;
|
|
129
177
|
this.#disposed = true;
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
178
|
+
this.#freeClosure(this.#combined);
|
|
179
|
+
this.#freeClosure(this.#fwdClosure);
|
|
180
|
+
this.#freeCustomClosure(this.#vjpClosure);
|
|
133
181
|
this.#fwdCb.close();
|
|
134
182
|
this.#vjpCb.close();
|
|
135
183
|
}
|
package/src/mlx/ffi.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { existsSync } from "node:fs";
|
|
|
15
15
|
import { dirname, join } from "node:path";
|
|
16
16
|
import { nativePackDir } from "../native-pack";
|
|
17
17
|
|
|
18
|
-
const { ptr: P, i32, u64, f32, cstring } = FFIType;
|
|
18
|
+
const { ptr: P, i32, u64, f32, f64, cstring } = FFIType;
|
|
19
19
|
|
|
20
20
|
/** libmlxc resolution, in order: explicit env override → next to the
|
|
21
21
|
* executable (the `bun build --compile` sidecar layout, where
|
|
@@ -57,6 +57,9 @@ export const C = dlopen(LIBMLXC_PATH, {
|
|
|
57
57
|
mlx_array_data_float16: { args: [u64], returns: P },
|
|
58
58
|
mlx_array_data_bfloat16: { args: [u64], returns: P },
|
|
59
59
|
mlx_array_data_uint32: { args: [u64], returns: P },
|
|
60
|
+
mlx_array_data_int32: { args: [u64], returns: P },
|
|
61
|
+
mlx_array_data_uint8: { args: [u64], returns: P },
|
|
62
|
+
mlx_array_data_int8: { args: [u64], returns: P },
|
|
60
63
|
mlx_array_item_float32: { args: [P, u64], returns: i32 },
|
|
61
64
|
// streams
|
|
62
65
|
mlx_default_gpu_stream_new: { args: [], returns: u64 },
|
|
@@ -142,6 +145,7 @@ export const C = dlopen(LIBMLXC_PATH, {
|
|
|
142
145
|
mlx_negative: { args: [P, u64, u64], returns: i32 },
|
|
143
146
|
mlx_argsort_axis: { args: [P, u64, i32, u64], returns: i32 },
|
|
144
147
|
mlx_argpartition_axis: { args: [P, u64, i32, i32, u64], returns: i32 },
|
|
148
|
+
mlx_arange: { args: [P, f64, f64, f64, i32, u64], returns: i32 },
|
|
145
149
|
mlx_take_along_axis: { args: [P, u64, u64, i32, u64], returns: i32 },
|
|
146
150
|
mlx_put_along_axis: { args: [P, u64, u64, u64, i32, u64], returns: i32 },
|
|
147
151
|
mlx_cumsum: { args: [P, u64, i32, FFIType.bool, FFIType.bool, u64], returns: i32 },
|
|
@@ -169,7 +173,17 @@ export const C = dlopen(LIBMLXC_PATH, {
|
|
|
169
173
|
// (res, input, weight, stride, padding, dilation, groups, stream) — depthwise
|
|
170
174
|
// causal conv1d for Qwen3.5 gated-DeltaNet (groups == channels, padding 0).
|
|
171
175
|
mlx_conv1d: { args: [P, u64, u64, i32, i32, i32, i32, u64], returns: i32 },
|
|
172
|
-
//
|
|
176
|
+
// C signature: (res, input, weight, stride_0, stride_1, padding_0,
|
|
177
|
+
// padding_1, dilation_0, dilation_1, groups, stream) — NHWC conv2d for the
|
|
178
|
+
// gemma-4 audio SSCP subsampler.
|
|
179
|
+
// HAZARD (lab/repro/bun-ffi-stack-args): Apple arm64 packs sub-8-byte STACK
|
|
180
|
+
// args at natural size (dilation_1@sp+0, groups@sp+4, stream@sp+8) but
|
|
181
|
+
// bun:ffi 1.3.14 writes one 8-byte slot per arg — the callee reads shifted
|
|
182
|
+
// garbage and mlx segfaults on a bogus stream. Workaround: declare the two
|
|
183
|
+
// stack ints as ONE u64 (dilation_1 | groups<<32) so Bun's slot layout
|
|
184
|
+
// byte-matches the Apple ABI. ops.conv2d does the packing.
|
|
185
|
+
mlx_conv2d: { args: [P, u64, u64, i32, i32, i32, i32, i32, u64, u64], returns: i32 },
|
|
186
|
+
// --- training: autograd (value_and_grad) — proven in lab/spikes/phase-train-vag.ts.
|
|
173
187
|
// mlx_value_and_grad(res: mlx_closure_value_and_grad*, fun: mlx_closure,
|
|
174
188
|
// argnums: int*, n) → builds a value+grad closure differentiating the
|
|
175
189
|
// selected inputs of `fun` (a mlx_closure_new_func_payload over a JS loss).
|
|
@@ -260,6 +274,16 @@ export const C = dlopen(LIBMLXC_PATH, {
|
|
|
260
274
|
// GPU trace capture (Phase E step 2: size the prize before the kernel)
|
|
261
275
|
mlx_metal_start_capture: { args: [cstring], returns: i32 },
|
|
262
276
|
mlx_metal_stop_capture: { args: [], returns: i32 },
|
|
277
|
+
// TurboQuant KV codec (Phase 13): FWHT rotation + per-group affine/Lloyd-Max
|
|
278
|
+
// (res, a, opt scale, stream) — 1/sqrt(dim) normalization when scale is null.
|
|
279
|
+
mlx_hadamard_transform: { args: [P, u64, u64, u64], returns: i32 },
|
|
280
|
+
mlx_min_axis: { args: [P, u64, i32, FFIType.bool, u64], returns: i32 },
|
|
281
|
+
mlx_round: { args: [P, u64, i32, u64], returns: i32 },
|
|
282
|
+
mlx_greater: { args: [P, u64, u64, u64], returns: i32 },
|
|
283
|
+
mlx_bitwise_and: { args: [P, u64, u64, u64], returns: i32 },
|
|
284
|
+
mlx_bitwise_or: { args: [P, u64, u64, u64], returns: i32 },
|
|
285
|
+
mlx_left_shift: { args: [P, u64, u64, u64], returns: i32 },
|
|
286
|
+
mlx_right_shift: { args: [P, u64, u64, u64], returns: i32 },
|
|
263
287
|
}).symbols;
|
|
264
288
|
|
|
265
289
|
export type MlxHandle = bigint;
|
|
@@ -329,7 +353,7 @@ export const NULL_HANDLE = 0n;
|
|
|
329
353
|
// RULE: never read a typed array that a bun:ffi call wrote through a
|
|
330
354
|
// pointer — once the calling function is DFG-compiled, the JIT eliminates
|
|
331
355
|
// the load across the native call and `buf[i]` returns stale values
|
|
332
|
-
// (repro/bun-ffi-f64/ISSUE.md; PLAN.md Phase 4 findings). Out-param slots
|
|
356
|
+
// (lab/repro/bun-ffi-f64/ISSUE.md; PLAN.md Phase 4 findings). Out-param slots
|
|
333
357
|
// must be read back with bun:ffi `read.*` (verified safe). Initializing the
|
|
334
358
|
// slot via the typed-array *constructor* is fine: that store happens in
|
|
335
359
|
// host code the JIT can't elide.
|
package/src/mlx/metal-kernel.ts
CHANGED
|
@@ -89,15 +89,18 @@ export class MetalKernel {
|
|
|
89
89
|
C.mlx_fast_metal_kernel_config_set_thread_group(cfg, call.threadGroup[0], call.threadGroup[1], call.threadGroup[2]);
|
|
90
90
|
if (call.initValue !== undefined)
|
|
91
91
|
C.mlx_fast_metal_kernel_config_set_init_value(cfg, call.initValue);
|
|
92
|
-
|
|
92
|
+
// Dtypes-first, then ints, to MATCH mlx's own specialization-name order
|
|
93
|
+
// (mlx-lm's gated_delta_step passes [("InT",…),("StT",…),("Dk",…),…]).
|
|
94
|
+
// Order affects only the generated name/cache-key, not binding (named args).
|
|
95
|
+
for (const [k, v] of Object.entries(call.templateDtypes ?? {})) {
|
|
93
96
|
const kb = Buffer.from(k + "\0", "utf8");
|
|
94
97
|
keepAlive.push(kb);
|
|
95
|
-
C.
|
|
98
|
+
C.mlx_fast_metal_kernel_config_add_template_arg_dtype(cfg, ptr(kb), v);
|
|
96
99
|
}
|
|
97
|
-
for (const [k, v] of Object.entries(call.
|
|
100
|
+
for (const [k, v] of Object.entries(call.templateInts ?? {})) {
|
|
98
101
|
const kb = Buffer.from(k + "\0", "utf8");
|
|
99
102
|
keepAlive.push(kb);
|
|
100
|
-
C.
|
|
103
|
+
C.mlx_fast_metal_kernel_config_add_template_arg_int(cfg, ptr(kb), v);
|
|
101
104
|
}
|
|
102
105
|
|
|
103
106
|
const handles = new BigUint64Array(inputs.map((a) => a.handle));
|