mlx-bun 0.0.4
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/LICENSE +21 -0
- package/README.md +362 -0
- package/THIRD_PARTY_LICENSES.md +41 -0
- package/bin/mlx-bun.mjs +32 -0
- package/docs/curve-designer.html +303 -0
- package/docs/design/adapters-end-to-end.md +201 -0
- package/docs/design/compat-cli-surface-design.md +227 -0
- package/docs/design/docs-reorg-plan.md +152 -0
- package/docs/design/hlg-sampling.md +340 -0
- package/docs/design/memory-system.md +339 -0
- package/docs/design/optimization_plan.md +344 -0
- package/docs/design/parallel-slots.md +515 -0
- package/docs/design/runtime-isolation.md +247 -0
- package/docs/design/segmented-backward-training.md +508 -0
- package/docs/investigations/chunk-finetune-experiment.md +84 -0
- package/docs/investigations/curve-bisector-routing.md +123 -0
- package/docs/investigations/curve-runs/bisector-route.json +98 -0
- package/docs/investigations/curve-runs/bisector-trace.json +1393 -0
- package/docs/investigations/curve-runs/curve-terrain.json +1179 -0
- package/docs/investigations/curve-runs/curve-terrain.svg +42 -0
- package/docs/investigations/curve-terrain.html +105 -0
- package/docs/investigations/expert-offload-single-user-moe.md +292 -0
- package/docs/investigations/hlg-ab.html +34 -0
- package/docs/investigations/hlg-explorer.html +238 -0
- package/docs/investigations/hlg-figs/cliff.svg +1 -0
- package/docs/investigations/hlg-figs/cross.svg +1 -0
- package/docs/investigations/hlg-figs/edges.svg +1 -0
- package/docs/investigations/hlg-figs/k20-grids.svg +1 -0
- package/docs/investigations/hlg-figs/k20-peaks.svg +1 -0
- package/docs/investigations/hlg-figs/lw-curve.svg +1 -0
- package/docs/investigations/hlg-figs/terrain.svg +1 -0
- package/docs/investigations/hlg-report.html +262 -0
- package/docs/investigations/hlg-runs/hlg-ab.json +149 -0
- package/docs/investigations/hlg-runs/hlg-blind-prompts.md +87 -0
- package/docs/investigations/hlg-runs/hlg-blind.json +107 -0
- package/docs/investigations/hlg-runs/hlg-cliff.json +197 -0
- package/docs/investigations/hlg-runs/hlg-cross.json +505 -0
- package/docs/investigations/hlg-runs/hlg-edge.json +388 -0
- package/docs/investigations/hlg-runs/hlg-eval1.json +25 -0
- package/docs/investigations/hlg-runs/hlg-frontier.json +86 -0
- package/docs/investigations/hlg-runs/hlg-grid.json +242 -0
- package/docs/investigations/hlg-runs/hlg-grid2.json +343 -0
- package/docs/investigations/hlg-runs/hlg-lw-coarse.json +110 -0
- package/docs/investigations/hlg-runs/hlg-map.json +383 -0
- package/docs/investigations/hlg-runs/run0-original.log +159 -0
- package/docs/investigations/hlg-runs/runA-recommended.log +131 -0
- package/docs/investigations/hlg-runs/runAA-cliff-cpm5.log +26 -0
- package/docs/investigations/hlg-runs/runAB-grid2-e4b.log +18 -0
- package/docs/investigations/hlg-runs/runAC-peak-e4b-partial.log +25 -0
- package/docs/investigations/hlg-runs/runAD-ab-samples.log +8 -0
- package/docs/investigations/hlg-runs/runAE-edge-e4b.log +51 -0
- package/docs/investigations/hlg-runs/runAF-logit-calibration.log +10 -0
- package/docs/investigations/hlg-runs/runAG-eval-config2.log +16 -0
- package/docs/investigations/hlg-runs/runB-gain-variants.log +192 -0
- package/docs/investigations/hlg-runs/runC-pivots.log +162 -0
- package/docs/investigations/hlg-runs/runD-oetf.log +132 -0
- package/docs/investigations/hlg-runs/runE-eotf.log +162 -0
- package/docs/investigations/hlg-runs/runF-shaper-working.log +86 -0
- package/docs/investigations/hlg-runs/runG-shaper-sweep.log +101 -0
- package/docs/investigations/hlg-runs/runH-wsweep.log +176 -0
- package/docs/investigations/hlg-runs/runI-asweep.log +176 -0
- package/docs/investigations/hlg-runs/runJ-knobmap.log +49 -0
- package/docs/investigations/hlg-runs/runK-frontier.log +19 -0
- package/docs/investigations/hlg-runs/runL-widemap.log +65 -0
- package/docs/investigations/hlg-runs/runM-frontier-emb.log +15 -0
- package/docs/investigations/hlg-runs/runN-verify.log +116 -0
- package/docs/investigations/hlg-runs/runO-frontier-seed5000.log +15 -0
- package/docs/investigations/hlg-runs/runP-grid-AxSm.log +15 -0
- package/docs/investigations/hlg-runs/runQ-grid-tgW.log +15 -0
- package/docs/investigations/hlg-runs/runR-blind-transcript.log +79 -0
- package/docs/investigations/hlg-runs/runS-cross-coherence.log +33 -0
- package/docs/investigations/hlg-runs/runT-cross-cpm5.log +33 -0
- package/docs/investigations/hlg-runs/runU-cross-12b.log +33 -0
- package/docs/investigations/hlg-runs/runV-lw-e4b.log +22 -0
- package/docs/investigations/hlg-runs/runW-lw-12b.log +22 -0
- package/docs/investigations/hlg-runs/runX-lw-cpm5.log +22 -0
- package/docs/investigations/hlg-runs/runY-cliff-e4b.log +26 -0
- package/docs/investigations/hlg-runs/runZ-cliff-12b.log +26 -0
- package/docs/investigations/hlg-sampling-investigation.md +970 -0
- package/docs/investigations/lab-build-journal.md +157 -0
- package/docs/investigations/pi-builtin-investigation.md +308 -0
- package/docs/investigations/starter-model-port-handoff.md +323 -0
- package/docs/planning/IDEAS.md +26 -0
- package/docs/planning/PRODUCT_ROADMAP.md +426 -0
- package/docs/planning/ResearchTopics.md +119 -0
- package/docs/planning/chunk-task-roadmap.md +77 -0
- package/docs/planning/journal.md +155 -0
- package/docs/reference/distribution.md +121 -0
- package/docs/reference/embedding.md +168 -0
- package/docs/reference/library-api.md +153 -0
- package/docs/reference/server-api.md +224 -0
- package/docs/reference/server-config.md +243 -0
- package/docs/segmented-backward-handoff.md +148 -0
- package/package.json +68 -0
- package/src/anthropic.ts +491 -0
- package/src/chat-template.ts +186 -0
- package/src/cli.ts +1075 -0
- package/src/config.ts +229 -0
- package/src/curve-sampler.ts +122 -0
- package/src/dataset/generators.ts +1087 -0
- package/src/dataset/index.ts +12 -0
- package/src/dataset/job.ts +81 -0
- package/src/dataset/llm.ts +71 -0
- package/src/dataset/registry.ts +528 -0
- package/src/download.ts +283 -0
- package/src/eval/README.md +86 -0
- package/src/eval/kl-prompts.ts +29 -0
- package/src/eval/kl.ts +294 -0
- package/src/eval/quality-db.ts +92 -0
- package/src/eval/runner.ts +102 -0
- package/src/eval/score.ts +49 -0
- package/src/eval/tasks/bfcl.ts +500 -0
- package/src/eval/tasks/gsm8k.ts +85 -0
- package/src/eval/tasks/hashhop.ts +109 -0
- package/src/eval/tasks/humaneval.ts +252 -0
- package/src/eval/tasks/ifeval.ts +354 -0
- package/src/eval/tasks/mmlu.ts +145 -0
- package/src/evaldb.ts +199 -0
- package/src/expert-offload-build.ts +77 -0
- package/src/expert-offload.ts +61 -0
- package/src/expert-trace.ts +67 -0
- package/src/fit.ts +254 -0
- package/src/generate.ts +451 -0
- package/src/harness-pi.ts +177 -0
- package/src/hf-push.ts +628 -0
- package/src/index.ts +22 -0
- package/src/jobs/db.ts +139 -0
- package/src/jobs/index.ts +29 -0
- package/src/jobs/job-entry.ts +106 -0
- package/src/jobs/runner.ts +304 -0
- package/src/jobs/sse.ts +114 -0
- package/src/jobs/types.ts +58 -0
- package/src/kv-store.ts +185 -0
- package/src/lora.ts +314 -0
- package/src/md-modules.d.ts +7 -0
- package/src/mlx/array.ts +237 -0
- package/src/mlx/autograd.ts +267 -0
- package/src/mlx/checkpoint.ts +116 -0
- package/src/mlx/compile.ts +158 -0
- package/src/mlx/custom-vjp.ts +140 -0
- package/src/mlx/ffi.ts +433 -0
- package/src/mlx/metal-kernel.ts +130 -0
- package/src/mlx/ops.ts +707 -0
- package/src/mmap.ts +90 -0
- package/src/model/batched-mask.ts +204 -0
- package/src/model/batched-rotating.ts +300 -0
- package/src/model/compiled-decode.ts +681 -0
- package/src/model/factory.ts +27 -0
- package/src/model/fingerprint.ts +33 -0
- package/src/model/flash-attention.ts +674 -0
- package/src/model/fused-decode-kernel.ts +305 -0
- package/src/model/fused-geglu-kernel.ts +87 -0
- package/src/model/gemma4-base.ts +1616 -0
- package/src/model/gemma4.ts +1012 -0
- package/src/model/generated/gemma4-12b.ts +526 -0
- package/src/model/generated/gemma4-26b.ts +500 -0
- package/src/model/generated/gemma4-e4b.ts +757 -0
- package/src/model/generated/index.ts +18 -0
- package/src/model/minicpm5.ts +278 -0
- package/src/model/qwen3-delta.ts +224 -0
- package/src/model/qwen3_5.ts +429 -0
- package/src/model/support.ts +46 -0
- package/src/native-pack.ts +152 -0
- package/src/pi-provider.ts +95 -0
- package/src/pi-terminal.ts +264 -0
- package/src/pi-web.ts +924 -0
- package/src/preflight.ts +123 -0
- package/src/prompt-cache.ts +113 -0
- package/src/quantize/allocator.ts +627 -0
- package/src/quantize/calibration.ts +180 -0
- package/src/quantize/config-writer.ts +132 -0
- package/src/quantize/index.ts +57 -0
- package/src/quantize/job.ts +136 -0
- package/src/quantize/quantizer.ts +462 -0
- package/src/quantize/safetensors-writer.ts +171 -0
- package/src/quantize/sensitivity.ts +234 -0
- package/src/registry.ts +336 -0
- package/src/responses.ts +774 -0
- package/src/safetensors.ts +158 -0
- package/src/sampler.ts +517 -0
- package/src/serve/batch-scheduler.ts +362 -0
- package/src/serve/generation-gateway.ts +160 -0
- package/src/server.ts +1891 -0
- package/src/spec/drafter.ts +261 -0
- package/src/spec/generate.ts +221 -0
- package/src/status-page.html +497 -0
- package/src/tokenizer.ts +38 -0
- package/src/tool-call.ts +242 -0
- package/src/train/dataset.ts +461 -0
- package/src/train/export.ts +32 -0
- package/src/train/forward.ts +229 -0
- package/src/train/index.ts +43 -0
- package/src/train/job.ts +148 -0
- package/src/train/lora-params.ts +194 -0
- package/src/train/loss.ts +342 -0
- package/src/train/merge.ts +195 -0
- package/src/train/optimizer.ts +176 -0
- package/src/train/rank.ts +101 -0
- package/src/train/segmented.ts +520 -0
- package/src/train/trainer.ts +600 -0
- package/src/tui.ts +161 -0
- package/src/vision/embedder.ts +168 -0
- package/src/vision/preprocess.ts +247 -0
- package/src/vision/prompt.ts +158 -0
- package/src/vision/siglip.ts +599 -0
- package/src/web/app.html +2471 -0
- package/src/web/skills/web-research/SKILL.md +44 -0
- package/src/web/skills.ts +59 -0
- package/src/web-tools.ts +426 -0
- package/src/weights.ts +91 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// mx.compile from Bun: wrap a JS graph-builder in an mlx_closure, hand it
|
|
2
|
+
// to mlx_compile, and replay the compiled graph in C++ thereafter.
|
|
3
|
+
//
|
|
4
|
+
// The trace function runs EXACTLY ONCE per (ndim, dtype) input signature
|
|
5
|
+
// (shapeless=true): mlx invokes our JSCallback with tracer arrays during
|
|
6
|
+
// the first mlx_closure_apply, we build the graph with the ordinary ops
|
|
7
|
+
// layer, and every later apply replays it natively — zero per-op FFI.
|
|
8
|
+
//
|
|
9
|
+
// Contract for trace functions:
|
|
10
|
+
// - receive positional MlxArray inputs, return positional MlxArray outputs
|
|
11
|
+
// - must NOT dispose the inputs (the trampoline owns them) and must NOT
|
|
12
|
+
// eval anything (tracer arrays have no data — eval inside a trace is an
|
|
13
|
+
// mlx error by design)
|
|
14
|
+
// - arrays created inside the trace (weights, scalarLike constants) are
|
|
15
|
+
// captured as graph constants — correct for weights, which never change
|
|
16
|
+
// - with shapeless=true the graph must stay valid as dim SIZES change
|
|
17
|
+
// (ndim/dtype changes retrace automatically): no reshape/slice constants
|
|
18
|
+
// derived from a varying dimension
|
|
19
|
+
|
|
20
|
+
import { JSCallback, ptr, read } from "bun:ffi";
|
|
21
|
+
import { C, type MlxHandle, takeMlxError } from "./ffi";
|
|
22
|
+
import { MlxArray } from "./array";
|
|
23
|
+
|
|
24
|
+
export type TraceFn = (inputs: MlxArray[]) => MlxArray[];
|
|
25
|
+
|
|
26
|
+
const traceRegistry = new Map<number, { fn: TraceFn; count: number }>();
|
|
27
|
+
let nextTraceId = 1;
|
|
28
|
+
/** Error thrown by a trace fn, carried across the C boundary (the
|
|
29
|
+
* trampoline must not throw into mlx; it returns non-zero instead). */
|
|
30
|
+
let traceError: unknown = null;
|
|
31
|
+
|
|
32
|
+
/** Count of trace invocations, exposed for tests (a working shapeless
|
|
33
|
+
* compile traces once and replays thereafter). */
|
|
34
|
+
export let traceCalls = 0;
|
|
35
|
+
|
|
36
|
+
// int (*fun)(mlx_vector_array* res, const mlx_vector_array args, void* payload)
|
|
37
|
+
// mlx_vector_array is the usual one-pointer struct, by value → u64.
|
|
38
|
+
// Tracing happens synchronously inside mlx_closure_apply on the calling
|
|
39
|
+
// thread, so a non-threadsafe JSCallback is safe (same argument as the
|
|
40
|
+
// error handler in ffi.ts).
|
|
41
|
+
const trampoline = new JSCallback(
|
|
42
|
+
(resPtr: number, argsVec: bigint, payload: number): number => {
|
|
43
|
+
try {
|
|
44
|
+
const entry = traceRegistry.get(payload);
|
|
45
|
+
if (!entry) throw new Error(`compile trampoline: unknown trace id ${payload}`);
|
|
46
|
+
const fn = entry.fn;
|
|
47
|
+
entry.count++;
|
|
48
|
+
traceCalls++;
|
|
49
|
+
const n = Number(C.mlx_vector_array_size(argsVec));
|
|
50
|
+
const inputs: MlxArray[] = [];
|
|
51
|
+
const slot = new BigUint64Array(1);
|
|
52
|
+
const slotPtr = ptr(slot);
|
|
53
|
+
for (let i = 0; i < n; i++) {
|
|
54
|
+
slot[0] = C.mlx_array_new();
|
|
55
|
+
if (C.mlx_vector_array_get(slotPtr, argsVec, BigInt(i)) !== 0)
|
|
56
|
+
throw new Error(`vector_array_get(${i}) failed: ${takeMlxError() ?? ""}`);
|
|
57
|
+
inputs.push(new MlxArray(read.u64(slotPtr, 0)));
|
|
58
|
+
}
|
|
59
|
+
const outputs = fn(inputs);
|
|
60
|
+
const handles = new BigUint64Array(outputs.map((o) => o.handle));
|
|
61
|
+
if (C.mlx_vector_array_set_data(resPtr, ptr(handles), BigInt(outputs.length)) !== 0)
|
|
62
|
+
throw new Error(`vector_array_set_data failed: ${takeMlxError() ?? ""}`);
|
|
63
|
+
for (const o of outputs) o.dispose();
|
|
64
|
+
for (const a of inputs) a.dispose();
|
|
65
|
+
return 0;
|
|
66
|
+
} catch (e) {
|
|
67
|
+
traceError = e;
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
{ args: ["ptr", "u64", "ptr"], returns: "i32" },
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
/** MLX_COMPILE_MODE_* (compile.h). NO_FUSE keeps the host-side replay win
|
|
75
|
+
* while disabling kernel fusion — the escape hatch if fusion ever breaks
|
|
76
|
+
* the bit-exact gate. */
|
|
77
|
+
export function setCompileMode(mode: "disabled" | "no_simplify" | "no_fuse" | "enabled"): void {
|
|
78
|
+
const v = { disabled: 0, no_simplify: 1, no_fuse: 2, enabled: 3 }[mode];
|
|
79
|
+
if (C.mlx_set_compile_mode(v) !== 0)
|
|
80
|
+
throw new Error(`mlx_set_compile_mode failed: ${takeMlxError() ?? ""}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export class CompiledFunction {
|
|
84
|
+
#compiled: MlxHandle;
|
|
85
|
+
#traceId: number;
|
|
86
|
+
#disposed = false;
|
|
87
|
+
|
|
88
|
+
/** Times this function's trace ran. A shapeless decode closure must
|
|
89
|
+
* trace exactly once — its input ndims/dtypes never change — so any
|
|
90
|
+
* count > 1 is a silent retrace (per-step JS graph builds sneaking
|
|
91
|
+
* back in) and gets flagged by compiled-decode. */
|
|
92
|
+
get traceCount(): number {
|
|
93
|
+
return traceRegistry.get(this.#traceId)?.count ?? 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
constructor(fn: TraceFn, shapeless = true) {
|
|
97
|
+
this.#traceId = nextTraceId++;
|
|
98
|
+
traceRegistry.set(this.#traceId, { fn, count: 0 });
|
|
99
|
+
// payload is the trace id smuggled through the void* (never deref'd);
|
|
100
|
+
// dtor is null — nothing to free on the C side.
|
|
101
|
+
const raw = C.mlx_closure_new_func_payload(trampoline.ptr, this.#traceId as never, null);
|
|
102
|
+
const slot = new BigUint64Array([C.mlx_closure_new()]);
|
|
103
|
+
const slotPtr = ptr(slot);
|
|
104
|
+
const status = C.mlx_compile(slotPtr, raw, shapeless);
|
|
105
|
+
const compiled = read.u64(slotPtr, 0);
|
|
106
|
+
C.mlx_closure_free(raw);
|
|
107
|
+
if (status !== 0) {
|
|
108
|
+
C.mlx_closure_free(compiled);
|
|
109
|
+
traceRegistry.delete(this.#traceId);
|
|
110
|
+
throw new Error(`mlx_compile failed: ${takeMlxError() ?? ""}`);
|
|
111
|
+
}
|
|
112
|
+
this.#compiled = compiled;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Run the compiled graph. First call (per ndim/dtype signature) traces
|
|
116
|
+
* through the JS fn; later calls replay natively. Returned arrays are
|
|
117
|
+
* owned by the caller. */
|
|
118
|
+
apply(inputs: MlxArray[]): MlxArray[] {
|
|
119
|
+
if (this.#disposed) throw new Error("CompiledFunction used after dispose");
|
|
120
|
+
const handles = new BigUint64Array(inputs.length);
|
|
121
|
+
for (let i = 0; i < inputs.length; i++) handles[i] = inputs[i]!.handle;
|
|
122
|
+
const inVec = C.mlx_vector_array_new_data(ptr(handles), BigInt(inputs.length));
|
|
123
|
+
const slot = new BigUint64Array([C.mlx_vector_array_new()]);
|
|
124
|
+
const slotPtr = ptr(slot);
|
|
125
|
+
const status = C.mlx_closure_apply(slotPtr, this.#compiled, inVec);
|
|
126
|
+
const outVec = read.u64(slotPtr, 0);
|
|
127
|
+
C.mlx_vector_array_free(inVec);
|
|
128
|
+
if (status !== 0) {
|
|
129
|
+
C.mlx_vector_array_free(outVec);
|
|
130
|
+
const e = traceError;
|
|
131
|
+
traceError = null;
|
|
132
|
+
throw e instanceof Error
|
|
133
|
+
? e
|
|
134
|
+
: new Error(`compiled apply failed: ${takeMlxError() ?? "(no mlx message)"}`);
|
|
135
|
+
}
|
|
136
|
+
const n = Number(C.mlx_vector_array_size(outVec));
|
|
137
|
+
const outputs: MlxArray[] = [];
|
|
138
|
+
const aSlot = new BigUint64Array(1);
|
|
139
|
+
const aPtr = ptr(aSlot);
|
|
140
|
+
for (let i = 0; i < n; i++) {
|
|
141
|
+
aSlot[0] = C.mlx_array_new();
|
|
142
|
+
if (C.mlx_vector_array_get(aPtr, outVec, BigInt(i)) !== 0) {
|
|
143
|
+
C.mlx_vector_array_free(outVec);
|
|
144
|
+
throw new Error(`vector_array_get(out ${i}) failed: ${takeMlxError() ?? ""}`);
|
|
145
|
+
}
|
|
146
|
+
outputs.push(new MlxArray(read.u64(aPtr, 0)));
|
|
147
|
+
}
|
|
148
|
+
C.mlx_vector_array_free(outVec);
|
|
149
|
+
return outputs;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
dispose(): void {
|
|
153
|
+
if (this.#disposed) return;
|
|
154
|
+
this.#disposed = true;
|
|
155
|
+
C.mlx_closure_free(this.#compiled);
|
|
156
|
+
traceRegistry.delete(this.#traceId);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// Custom vjp via mlx-c mlx_custom_vjp: attach a hand-written backward to a
|
|
2
|
+
// forward whose primitives have no usable vjp (a fused mx.fast.metal_kernel
|
|
3
|
+
// is a CustomKernel with no gradient). This is the mechanism the L2 flash-
|
|
4
|
+
// attention training op uses — the forward is a Metal kernel, the backward is
|
|
5
|
+
// three more Metal kernels, wired together here so the enclosing
|
|
6
|
+
// value_and_grad differentiates through them.
|
|
7
|
+
//
|
|
8
|
+
// Same FFI lifecycle as ValueAndGrad/Checkpoint: a JSCallback per closure →
|
|
9
|
+
// mlx_closure_new_func_payload (forward) + mlx_closure_custom_new_func_payload
|
|
10
|
+
// (vjp) → mlx_custom_vjp → applied with mlx_closure_apply. Closures must not
|
|
11
|
+
// throw across the FFI boundary; a JS throw is captured and surfaced from
|
|
12
|
+
// apply().
|
|
13
|
+
|
|
14
|
+
import { JSCallback, ptr, read } from "bun:ffi";
|
|
15
|
+
import { C, takeMlxError } from "./ffi";
|
|
16
|
+
import { MlxArray } from "./array";
|
|
17
|
+
|
|
18
|
+
function readVec(vec: bigint): MlxArray[] {
|
|
19
|
+
const n = Number(C.mlx_vector_array_size(vec));
|
|
20
|
+
const out: MlxArray[] = [];
|
|
21
|
+
for (let i = 0; i < n; i++) {
|
|
22
|
+
const slot = new BigUint64Array([C.mlx_array_new()]);
|
|
23
|
+
const sp = ptr(slot);
|
|
24
|
+
if (C.mlx_vector_array_get(sp, vec, BigInt(i)) !== 0)
|
|
25
|
+
throw new Error(`vector_array_get(${i}) failed`);
|
|
26
|
+
out.push(new MlxArray(read.u64(sp, 0)));
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function setVec(outPtr: number, arrays: MlxArray[]): void {
|
|
32
|
+
const handles = new BigUint64Array(arrays.map((a) => a.handle));
|
|
33
|
+
if (C.mlx_vector_array_set_data(outPtr as never, ptr(handles), BigInt(arrays.length)) !== 0)
|
|
34
|
+
throw new Error("vector_array_set_data failed");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A reusable forward op with a hand-written backward.
|
|
38
|
+
*
|
|
39
|
+
* `fwd(inputs)` returns the forward outputs. `vjp(primals, cotangents,
|
|
40
|
+
* outputs)` returns one gradient per primal (in primal order). Both must be
|
|
41
|
+
* pure functions of their arguments — the vjp re-reads everything from the
|
|
42
|
+
* graph during the enclosing backward. */
|
|
43
|
+
export class CustomVjp {
|
|
44
|
+
readonly #fwdCb: JSCallback;
|
|
45
|
+
readonly #vjpCb: JSCallback;
|
|
46
|
+
readonly #fwdClosure: bigint;
|
|
47
|
+
readonly #vjpClosure: bigint;
|
|
48
|
+
readonly #combined: bigint;
|
|
49
|
+
#error: string | null = null;
|
|
50
|
+
#disposed = false;
|
|
51
|
+
|
|
52
|
+
constructor(
|
|
53
|
+
fwd: (inputs: MlxArray[]) => MlxArray[],
|
|
54
|
+
vjp: (primals: MlxArray[], cotangents: MlxArray[], outputs: MlxArray[]) => MlxArray[],
|
|
55
|
+
) {
|
|
56
|
+
this.#fwdCb = new JSCallback(
|
|
57
|
+
(outPtr: number, inVec: bigint, _payload: number): number => {
|
|
58
|
+
try {
|
|
59
|
+
const inputs = readVec(inVec);
|
|
60
|
+
const outs = fwd(inputs);
|
|
61
|
+
setVec(outPtr, outs);
|
|
62
|
+
for (const p of inputs) p.dispose();
|
|
63
|
+
for (const o of outs) o.dispose();
|
|
64
|
+
return 0;
|
|
65
|
+
} catch (e) {
|
|
66
|
+
this.#error = e instanceof Error ? e.message : String(e);
|
|
67
|
+
return 1;
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
{ args: ["ptr", "u64", "ptr"], returns: "i32" },
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
this.#vjpCb = new JSCallback(
|
|
74
|
+
(resPtr: number, primalsVec: bigint, cotsVec: bigint, outsVec: bigint, _payload: number): number => {
|
|
75
|
+
try {
|
|
76
|
+
const primals = readVec(primalsVec);
|
|
77
|
+
const cots = readVec(cotsVec);
|
|
78
|
+
const outs = readVec(outsVec);
|
|
79
|
+
const grads = vjp(primals, cots, outs);
|
|
80
|
+
setVec(resPtr, grads);
|
|
81
|
+
for (const a of [...primals, ...cots, ...outs]) a.dispose();
|
|
82
|
+
for (const g of grads) g.dispose();
|
|
83
|
+
return 0;
|
|
84
|
+
} catch (e) {
|
|
85
|
+
this.#error = e instanceof Error ? e.message : String(e);
|
|
86
|
+
return 1;
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
{ args: ["ptr", "u64", "u64", "u64", "ptr"], returns: "i32" },
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
this.#fwdClosure = C.mlx_closure_new_func_payload(this.#fwdCb.ptr as never, null, null);
|
|
93
|
+
this.#vjpClosure = C.mlx_closure_custom_new_func_payload(this.#vjpCb.ptr as never, null, null);
|
|
94
|
+
|
|
95
|
+
const slot = new BigUint64Array([C.mlx_closure_new()]);
|
|
96
|
+
if (C.mlx_custom_vjp(ptr(slot), this.#fwdClosure, this.#vjpClosure) !== 0) {
|
|
97
|
+
C.mlx_closure_free(this.#fwdClosure);
|
|
98
|
+
C.mlx_closure_custom_free(this.#vjpClosure);
|
|
99
|
+
this.#fwdCb.close();
|
|
100
|
+
this.#vjpCb.close();
|
|
101
|
+
throw new Error(`mlx_custom_vjp failed: ${takeMlxError() ?? ""}`);
|
|
102
|
+
}
|
|
103
|
+
this.#combined = read.u64(ptr(slot), 0);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
apply(inputs: MlxArray[]): MlxArray[] {
|
|
107
|
+
if (this.#disposed) throw new Error("CustomVjp used after dispose");
|
|
108
|
+
this.#error = null;
|
|
109
|
+
|
|
110
|
+
const handles = new BigUint64Array(inputs.map((p) => p.handle));
|
|
111
|
+
const inVec = C.mlx_vector_array_new_data(ptr(handles), BigInt(inputs.length));
|
|
112
|
+
const outSlot = new BigUint64Array([C.mlx_vector_array_new()]);
|
|
113
|
+
const st = C.mlx_closure_apply(ptr(outSlot), this.#combined, inVec);
|
|
114
|
+
const outVec = read.u64(ptr(outSlot), 0);
|
|
115
|
+
if (st !== 0) {
|
|
116
|
+
C.mlx_vector_array_free(outVec);
|
|
117
|
+
C.mlx_vector_array_free(inVec);
|
|
118
|
+
const detail = this.#error ?? takeMlxError() ?? "";
|
|
119
|
+
throw new Error(`custom_vjp apply failed${detail ? `: ${detail}` : ""}`);
|
|
120
|
+
}
|
|
121
|
+
const outs = readVec(outVec);
|
|
122
|
+
C.mlx_vector_array_free(outVec);
|
|
123
|
+
C.mlx_vector_array_free(inVec);
|
|
124
|
+
return outs;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
dispose(): void {
|
|
128
|
+
if (this.#disposed) return;
|
|
129
|
+
this.#disposed = true;
|
|
130
|
+
C.mlx_closure_free(this.#combined);
|
|
131
|
+
C.mlx_closure_free(this.#fwdClosure);
|
|
132
|
+
C.mlx_closure_custom_free(this.#vjpClosure);
|
|
133
|
+
this.#fwdCb.close();
|
|
134
|
+
this.#vjpCb.close();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
[Symbol.dispose](): void {
|
|
138
|
+
this.dispose();
|
|
139
|
+
}
|
|
140
|
+
}
|