mlx-bun 0.0.7 → 0.0.9
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 +112 -20
- package/docs/design/batching-v2-plan.md +515 -0
- package/docs/design/bucketing-stage.md +196 -0
- package/docs/design/compat-cli-surface-design.md +11 -11
- package/docs/design/diffusion-gemma-port.md +602 -0
- package/docs/design/docs-reorg-plan.md +1 -1
- package/docs/design/dreaming-nightly-pipeline.md +291 -0
- package/docs/design/dspark-speculative-decoding.md +106 -0
- package/docs/design/generic-model-support.md +381 -0
- package/docs/design/memory-inference-path.md +422 -0
- package/docs/design/memory-synthesis.md +287 -0
- package/docs/design/minicpm5-decode-megakernel.md +484 -0
- package/docs/design/mlx-lm-tool-parity-plan.md +535 -0
- package/docs/design/orpo-dynamic-lambda.md +41 -0
- package/docs/design/orpo-training.md +35 -5
- package/docs/design/parallel-slots.md +12 -1
- package/docs/design/parity-tier-dag.md +13 -5
- package/docs/design/spec-decode-larger-targets.md +229 -0
- package/docs/design/tauri-desktop-app.md +2 -2
- package/docs/design/the-dreaming-handoff.md +141 -0
- package/docs/design/the-dreaming-master-plan.md +446 -0
- package/docs/design/write-pipeline-entity.md +468 -0
- package/docs/investigations/curve-bisector-routing.md +1 -1
- package/docs/investigations/decode-roofline-lookagain.md +262 -0
- package/docs/investigations/dspark-handoff.md +58 -0
- package/docs/investigations/kernel-perf-review-2026-07.md +92 -0
- package/docs/planning/PRODUCT_ROADMAP.md +2 -2
- package/docs/planning/curve-sampler-research-plan.md +331 -0
- package/docs/planning/memory-docs-and-dag-plan.md +185 -0
- package/docs/planning/pi-integration-review.md +178 -0
- package/docs/planning/release-notes-v0.0.9.md +122 -0
- package/docs/planning/web-ui-pass-plan.md +213 -0
- package/docs/planning/website-readme-pass-plan.md +224 -0
- package/docs/reference/cli.md +255 -0
- package/docs/reference/embedding.md +7 -2
- package/docs/reference/library-api.md +22 -3
- package/docs/reference/memory.md +5 -5
- package/docs/reference/models.md +137 -0
- package/docs/reference/orpo-quickstart.md +6 -0
- package/docs/reference/server-api.md +187 -9
- package/docs/reference/server-config.md +40 -9
- package/docs/reference/training.md +25 -11
- package/package.json +1 -1
- package/src/anthropic.ts +22 -0
- package/src/assets/curve-designer.html +1 -1
- package/src/chat-template.ts +25 -7
- package/src/cli.ts +1029 -86
- package/src/config.ts +54 -3
- package/src/diffusion/diffusion-generate.ts +436 -0
- package/src/download.ts +99 -8
- package/src/embed.ts +77 -0
- package/src/eval/perplexity.ts +164 -0
- package/src/eval/runner.ts +19 -7
- package/src/eval/tasks/bfcl.ts +1 -1
- package/src/generate.ts +196 -19
- package/src/harness-pi.ts +43 -17
- package/src/index.ts +2 -0
- package/src/lora.ts +2 -2
- package/src/memory/article.ts +558 -0
- package/src/memory/chunk-validation.ts +150 -0
- package/src/memory/chunk.ts +305 -12
- package/src/memory/cluster.ts +344 -17
- package/src/memory/coverage.ts +134 -0
- package/src/memory/crosslink.ts +0 -0
- package/src/memory/db.ts +184 -45
- package/src/memory/entity.ts +203 -0
- package/src/memory/gate.ts +187 -0
- package/src/memory/history.ts +152 -0
- package/src/memory/ingest.ts +324 -11
- package/src/memory/model.ts +321 -0
- package/src/memory/normalize.ts +671 -0
- package/src/memory/parse.ts +22 -0
- package/src/memory/pipeline.ts +242 -30
- package/src/memory/prompts.ts +42 -0
- package/src/memory/query.ts +334 -0
- package/src/memory/reconcile.ts +734 -0
- package/src/memory/redirect.ts +108 -0
- package/src/memory/reindex.ts +325 -0
- package/src/memory/resolve.ts +307 -0
- package/src/memory/route.ts +272 -0
- package/src/memory/schedule.ts +6 -4
- package/src/memory/stages.ts +774 -0
- package/src/memory/synthesize.ts +1629 -20
- package/src/memory/tools.ts +446 -96
- package/src/memory/vault.ts +462 -35
- package/src/memory/wikify.ts +1057 -16
- package/src/mlx/ffi.ts +11 -0
- package/src/mlx/ops.ts +113 -0
- package/src/model/compiled-decode.ts +43 -8
- package/src/model/diffusion-gemma.ts +1026 -0
- package/src/model/factory.ts +35 -8
- package/src/model/flash-attention.ts +4 -2
- package/src/model/fused-decode-kernel.ts +14 -7
- package/src/model/fused-mlp-kernel.ts +169 -0
- package/src/model/fused-swiglu-kernel.ts +240 -0
- package/src/model/gemma4-base.ts +62 -6
- package/src/model/gemma4.ts +33 -2
- package/src/model/minicpm5.ts +106 -8
- package/src/model/qwen3.ts +276 -0
- package/src/model/steel-linear-kernel.ts +96 -0
- package/src/model/support.ts +45 -7
- package/src/model/universal/archs.ts +332 -0
- package/src/model/universal/dense.ts +576 -0
- package/src/model/universal/modules.ts +163 -0
- package/src/model/universal/rope.ts +366 -0
- package/src/pi-provider.ts +1 -1
- package/src/pi-terminal.ts +29 -8
- package/src/pi-web.ts +19 -2
- package/src/quantize/config-writer.ts +1 -1
- package/src/registry.ts +215 -7
- package/src/responses.ts +23 -0
- package/src/sampler.ts +220 -9
- package/src/serve/batch-scheduler.ts +272 -80
- package/src/serve/generation-gateway.ts +90 -18
- package/src/server.ts +643 -50
- package/src/spec/drafter.ts +81 -49
- package/src/spec/dspark/data-dflash.ts +156 -0
- package/src/spec/dspark/data.ts +201 -0
- package/src/spec/dspark/generate-dflash.ts +193 -0
- package/src/spec/dspark/generate.ts +290 -0
- package/src/spec/dspark/loss.ts +213 -0
- package/src/spec/dspark/module-dflash.ts +361 -0
- package/src/spec/dspark/module.ts +540 -0
- package/src/spec/dspark/sample.ts +112 -0
- package/src/spec/generate.ts +40 -19
- package/src/status-page.html +1 -1
- package/src/tokenizer.ts +7 -0
- package/src/train/diffusion-lora.ts +216 -0
- package/src/train/flash-cce.ts +70 -15
- package/src/train/fuse.ts +243 -0
- package/src/train/index.ts +3 -0
- package/src/train/job.ts +8 -0
- package/src/train/loss.ts +289 -30
- package/src/train/prefix-shared.ts +33 -5
- package/src/train/segmented.ts +67 -11
- package/src/train/trainer.ts +92 -28
- package/src/vision/diffusion-vision.ts +620 -0
- package/src/web/skills/memory/SKILL.md +40 -17
package/src/model/gemma4.ts
CHANGED
|
@@ -295,7 +295,14 @@ class Attention {
|
|
|
295
295
|
} else if (
|
|
296
296
|
ta === "flash" && flashSupported(q) && (mask.mode === "causal" || mask.mode === "array")
|
|
297
297
|
) {
|
|
298
|
-
// Training
|
|
298
|
+
// Training, OPT-IN flash path (MLX_BUN_TRAIN_ATTN=flash) for O(L)-memory
|
|
299
|
+
// long context. NOTE the default (ops.sdpa, below) is the CORRECT and
|
|
300
|
+
// verified route: its dQ/dK/dV vjps are exact (finite-difference checked;
|
|
301
|
+
// it's what mlx-lm's tuner differentiates). The two historical bugs were
|
|
302
|
+
// in THIS flash kernel (spurious dK transpose, divergent dQ barrier),
|
|
303
|
+
// both fixed and gated by tests/flash-attention.test.ts vs the ops.sdpa
|
|
304
|
+
// oracle — see flash-attention.ts's routing comment. Flash also remains
|
|
305
|
+
// Gemma-guarded in the trainer (e4b >=2K SIGTRAP not yet re-validated).
|
|
299
306
|
// Full layers → window 0 (pure causal); sliding layers → their window.
|
|
300
307
|
const window = this.isSliding ? this.windowSize : 0;
|
|
301
308
|
attn = flashAttention(q, shared.keys, shared.values, 1.0, true, window);
|
|
@@ -663,6 +670,13 @@ export class Gemma4Model {
|
|
|
663
670
|
/** Set by the trainer to enable gradient checkpointing in forwardLayers
|
|
664
671
|
* (null = off; the non-checkpointed path is untouched). */
|
|
665
672
|
gradCkpt: GradCheckpointCtx | null = null;
|
|
673
|
+
/** Optional per-forward hidden-state tap for DSpark's DFlash H_ctx (paper
|
|
674
|
+
* Eq 2). null = off → byte-identical forward. `layers`: residual-output
|
|
675
|
+
* indices to capture (0..nLayers-1 = post-layer residual; nLayers =
|
|
676
|
+
* post-finalNorm). `pos`: if set, capture only [1,1,H] at that seq position
|
|
677
|
+
* (live decode); else the full [1,L,H] (offline regen). The caller owns and
|
|
678
|
+
* disposes the captured arrays. Additive/read-only → parity untouched. */
|
|
679
|
+
hiddenTap: { layers: Set<number>; pos?: number; captured: Map<number, MlxArray> } | null = null;
|
|
666
680
|
// e2b/e4b per-layer input machinery
|
|
667
681
|
readonly perLayerEmbed: QuantizedEmbedding | null;
|
|
668
682
|
readonly perLayerModelProjection: QuantizedLinear | null;
|
|
@@ -820,6 +834,20 @@ export class Gemma4Model {
|
|
|
820
834
|
return out;
|
|
821
835
|
}
|
|
822
836
|
|
|
837
|
+
/** Store layer `i`'s residual stream `h` ([1,L,H]) if the hidden tap requests
|
|
838
|
+
* it. Copies (astype bf16, optional slice) so the loop's `h.dispose()` can't
|
|
839
|
+
* free the capture. No-op and no graph ops when the tap is null → the forward
|
|
840
|
+
* is byte-identical to an untapped run. (DSpark DFlash H_ctx.) */
|
|
841
|
+
protected captureLayer(i: number, h: MlxArray): void {
|
|
842
|
+
const tap = this.hiddenTap;
|
|
843
|
+
if (!tap || !tap.layers.has(i)) return;
|
|
844
|
+
const H = h.shape[2]!;
|
|
845
|
+
const src = tap.pos !== undefined ? h.slice([0, tap.pos, 0], [1, tap.pos + 1, H]) : h;
|
|
846
|
+
const copy = src.astype(this.embed.scales.dtype); // bf16, matches regen dump dtype
|
|
847
|
+
if (src !== h) src.dispose();
|
|
848
|
+
tap.captured.set(i, copy);
|
|
849
|
+
}
|
|
850
|
+
|
|
823
851
|
/** Consumes h. */
|
|
824
852
|
protected forwardLayers(
|
|
825
853
|
h0: MlxArray, cache: Cache[], bidir: MlxArray | null, ids: MlxArray | null,
|
|
@@ -878,6 +906,7 @@ export class Gemma4Model {
|
|
|
878
906
|
pls?.dispose();
|
|
879
907
|
h.dispose();
|
|
880
908
|
h = next;
|
|
909
|
+
this.captureLayer(i, h); // DFlash H_ctx tap (no-op unless hiddenTap set)
|
|
881
910
|
intermediates[i] = shared;
|
|
882
911
|
}
|
|
883
912
|
perLayer?.dispose();
|
|
@@ -895,7 +924,9 @@ export class Gemma4Model {
|
|
|
895
924
|
}
|
|
896
925
|
for (const m of masks.values()) m.arr?.dispose();
|
|
897
926
|
|
|
898
|
-
|
|
927
|
+
h = disposing(h, this.finalNorm.forward(h));
|
|
928
|
+
this.captureLayer(this.layers.length, h); // post-finalNorm sentinel (index = nLayers)
|
|
929
|
+
return h;
|
|
899
930
|
}
|
|
900
931
|
|
|
901
932
|
// --- Segmented-backward support (docs/design/segmented-backward-training.md
|
package/src/model/minicpm5.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import type { ModelConfig } from "../config";
|
|
5
5
|
import type { Weights } from "../weights";
|
|
6
6
|
import { MlxArray } from "../mlx/array";
|
|
7
|
+
import { Dtype } from "../mlx/ffi";
|
|
7
8
|
import * as ops from "../mlx/ops";
|
|
8
9
|
import {
|
|
9
10
|
argmaxLastPosition,
|
|
@@ -16,9 +17,56 @@ import {
|
|
|
16
17
|
QuantizedLinear,
|
|
17
18
|
quantizedSdpa,
|
|
18
19
|
RMSNorm,
|
|
20
|
+
isCompiledTrace,
|
|
19
21
|
type Cache,
|
|
20
22
|
type Mask,
|
|
21
23
|
} from "./gemma4-base";
|
|
24
|
+
import {
|
|
25
|
+
fusedSwiglu,
|
|
26
|
+
fusedSwigluEnabled,
|
|
27
|
+
fusedSwigluSupported,
|
|
28
|
+
swigluMMin,
|
|
29
|
+
swigluUnderCap,
|
|
30
|
+
} from "./fused-swiglu-kernel";
|
|
31
|
+
import { steelLinear } from "./steel-linear-kernel";
|
|
32
|
+
import { fusedMlp } from "./fused-mlp-kernel";
|
|
33
|
+
|
|
34
|
+
/** Break the graph at a boundary: contiguous → eval → fresh graph-free leaf — the
|
|
35
|
+
* exact primitive the segmented TRAINER uses so MLX never plans across boundaries
|
|
36
|
+
* and prior-segment activations are freed (copy of train/segmented.ts detachLeaf). */
|
|
37
|
+
function detachLeaf(a: MlxArray): MlxArray {
|
|
38
|
+
const c = ops.contiguous(a);
|
|
39
|
+
c.eval();
|
|
40
|
+
const leaf = MlxArray.fromBytesCopy(c.rawBytes(), c.shape, c.dtype);
|
|
41
|
+
c.dispose();
|
|
42
|
+
a.dispose();
|
|
43
|
+
return leaf;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** A quantized projection as OUR steel dispatch when the controlled path is live
|
|
47
|
+
* (no adapter, affine, clean tiling, M>1 so it's not a decode GEMV); otherwise
|
|
48
|
+
* the normal linear. x is [B,L,H] or [M,H]. */
|
|
49
|
+
function ourLinear(lin: QuantizedLinear, x: MlxArray): MlxArray {
|
|
50
|
+
const H = x.shape[x.shape.length - 1]!;
|
|
51
|
+
const M = x.shape.reduce((a, b) => a * b, 1) / H;
|
|
52
|
+
if (
|
|
53
|
+
!fusedSwigluEnabled() || isCompiledTrace() || M < swigluMMin() ||
|
|
54
|
+
lin.adapters != null || lin.biases == null ||
|
|
55
|
+
(x.dtype !== Dtype.bfloat16 && x.dtype !== Dtype.float16) ||
|
|
56
|
+
H % 32 !== 0 || lin.spec.groupSize % 32 !== 0
|
|
57
|
+
) {
|
|
58
|
+
return lin.forward(x);
|
|
59
|
+
}
|
|
60
|
+
const x2 = x.shape.length === 2 ? x : ops.reshape(x, [M, H]);
|
|
61
|
+
const y = steelLinear(x2, lin.w, lin.scales, lin.biases, lin.spec);
|
|
62
|
+
if (x2 !== x) x2.dispose();
|
|
63
|
+
return x.shape.length === 2
|
|
64
|
+
? y
|
|
65
|
+
: disposing(y, ops.reshape(y, [...x.shape.slice(0, -1), lin.w.shape[0]!]));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** How often the inference fused path materializes the residual stream (layers). */
|
|
69
|
+
const EVAL_EVERY = Math.max(1, Number(process.env.MLX_BUN_SWIGLU_EVAL_EVERY) || 1);
|
|
22
70
|
import { flashAttention, getTrainingAttn, flashSupported } from "./flash-attention";
|
|
23
71
|
|
|
24
72
|
// Shared prompt-prefix plan (lever 7). When set, the attention ropes the
|
|
@@ -156,7 +204,40 @@ class LlamaMLP {
|
|
|
156
204
|
this.down = QuantizedLinear.load(weights, `${prefix}.down_proj`, config);
|
|
157
205
|
}
|
|
158
206
|
|
|
159
|
-
|
|
207
|
+
/** MLP. When `residual` (the layer input h) is given AND the fused path applies,
|
|
208
|
+
* the kernel computes h + mlp(x) directly — the residual is ABSORBED, so nothing
|
|
209
|
+
* stays live across the kernel for MLX's planner to alias (it just runs our one
|
|
210
|
+
* dispatch). Otherwise returns mlp(x) and the caller adds the residual. */
|
|
211
|
+
forward(x: MlxArray, residual?: MlxArray): MlxArray {
|
|
212
|
+
const H = x.shape[x.shape.length - 1]!;
|
|
213
|
+
const M = x.shape.reduce((a, b) => a * b, 1) / H;
|
|
214
|
+
if (
|
|
215
|
+
residual != null &&
|
|
216
|
+
fusedSwigluEnabled() && !isCompiledTrace() && M >= swigluMMin() && swigluUnderCap() &&
|
|
217
|
+
this.gate.adapters == null && this.up.adapters == null && this.down.adapters == null &&
|
|
218
|
+
this.down.biases != null && this.down.spec.groupSize % 32 === 0 &&
|
|
219
|
+
fusedSwigluSupported(x, this.gate.spec, this.up.spec, this.gate.biases, this.up.biases)
|
|
220
|
+
) {
|
|
221
|
+
// The WHOLE MLP + residual in ONE kernel — gate+up+silu+down then +h, `hidden`
|
|
222
|
+
// never materialized, residual absorbed. MLX runs one dispatch; nothing for
|
|
223
|
+
// its planner to alias → chains across layers with no boundary.
|
|
224
|
+
const Hin = this.gate.scales.shape[1]! * this.gate.spec.groupSize;
|
|
225
|
+
const Ho = this.down.w.shape[0]!;
|
|
226
|
+
const x2 = x.shape.length === 2 ? x : ops.reshape(x, [M, Hin]);
|
|
227
|
+
const h2 = residual.shape.length === 2 ? residual : ops.reshape(residual, [M, Ho]);
|
|
228
|
+
const out2 = fusedMlp(
|
|
229
|
+
x2,
|
|
230
|
+
this.gate.w, this.gate.scales, this.gate.biases!, this.gate.spec,
|
|
231
|
+
this.up.w, this.up.scales, this.up.biases!, this.up.spec,
|
|
232
|
+
this.down.w, this.down.scales, this.down.biases!, this.down.spec,
|
|
233
|
+
h2,
|
|
234
|
+
);
|
|
235
|
+
if (x2 !== x) x2.dispose();
|
|
236
|
+
if (h2 !== residual) h2.dispose();
|
|
237
|
+
return x.shape.length === 2
|
|
238
|
+
? out2
|
|
239
|
+
: disposing(out2, ops.reshape(out2, [...x.shape.slice(0, -1), Ho]));
|
|
240
|
+
}
|
|
160
241
|
const gate = this.gate.forward(x);
|
|
161
242
|
const up = this.up.forward(x);
|
|
162
243
|
const sig = ops.sigmoid(gate);
|
|
@@ -166,9 +247,10 @@ class LlamaMLP {
|
|
|
166
247
|
const hidden = ops.mul(silu, up);
|
|
167
248
|
silu.dispose();
|
|
168
249
|
up.dispose();
|
|
169
|
-
const
|
|
250
|
+
const m = this.down.forward(hidden);
|
|
170
251
|
hidden.dispose();
|
|
171
|
-
return
|
|
252
|
+
if (residual == null) return m;
|
|
253
|
+
return disposing(m, ops.add(residual, m));
|
|
172
254
|
}
|
|
173
255
|
}
|
|
174
256
|
|
|
@@ -192,11 +274,10 @@ class LlamaLayer {
|
|
|
192
274
|
const h = ops.add(x, a);
|
|
193
275
|
a.dispose();
|
|
194
276
|
const hn = this.postAttnNorm.forward(h);
|
|
195
|
-
|
|
277
|
+
// Pass the residual h INTO the MLP so the fused kernel absorbs it (out = h + mlp).
|
|
278
|
+
const out = this.mlp.forward(hn, h);
|
|
196
279
|
hn.dispose();
|
|
197
|
-
const out = ops.add(h, m);
|
|
198
280
|
h.dispose();
|
|
199
|
-
m.dispose();
|
|
200
281
|
return out;
|
|
201
282
|
}
|
|
202
283
|
}
|
|
@@ -256,7 +337,15 @@ export class MiniCPM5Model {
|
|
|
256
337
|
}
|
|
257
338
|
|
|
258
339
|
protected forwardLayers(h0: MlxArray, cache: Cache[]): MlxArray {
|
|
259
|
-
|
|
340
|
+
// When the fused SwiGLU path is live, WE own the eval schedule: materialize
|
|
341
|
+
// each layer's output so the custom kernel sits in a shallow per-layer graph
|
|
342
|
+
// rather than a 24-deep lazy graph whose buffer planner aliases the kernel's
|
|
343
|
+
// output (the deep-graph corruption). Training/segmented-backward call
|
|
344
|
+
// runLayerRange directly and never set this, so autograd is untouched.
|
|
345
|
+
const h = this.runLayerRange(
|
|
346
|
+
h0, 0, this.layers.length, cache,
|
|
347
|
+
fusedSwigluEnabled() && process.env.MLX_BUN_SWIGLU_NOEVAL !== "1",
|
|
348
|
+
);
|
|
260
349
|
h0.dispose(); // runLayerRange keeps its input alive; we own h0 here
|
|
261
350
|
return disposing(h, this.finalNorm.forward(h));
|
|
262
351
|
}
|
|
@@ -269,7 +358,7 @@ export class MiniCPM5Model {
|
|
|
269
358
|
* segmented-backward training path (docs/design/segmented-backward-training.md).
|
|
270
359
|
* MiniCPM5 is plain full-attention with no KV-sharing, so each layer reads
|
|
271
360
|
* its own stateless `cache[i]` and a single causal mask. */
|
|
272
|
-
runLayerRange(h: MlxArray, aIdx: number, bIdx: number, cache: Cache[]): MlxArray {
|
|
361
|
+
runLayerRange(h: MlxArray, aIdx: number, bIdx: number, cache: Cache[], evalPerLayer = false): MlxArray {
|
|
273
362
|
if (aIdx >= bIdx) return h;
|
|
274
363
|
const L = h.shape[1]!;
|
|
275
364
|
const mask = cache[aIdx]!.makeMask(L, null);
|
|
@@ -278,6 +367,15 @@ export class MiniCPM5Model {
|
|
|
278
367
|
const next = this.layers[i]!.forward(cur, mask, cache[i]!);
|
|
279
368
|
if (i > aIdx) cur.dispose(); // keep the input boundary leaf; drop interiors
|
|
280
369
|
cur = next;
|
|
370
|
+
// Per-layer boundary (segmented-trainer pattern): materialize each layer's
|
|
371
|
+
// output so MLX never plans across layers — our single-dispatch fused MLP is
|
|
372
|
+
// the only custom kernel it runs per layer. evalAll (GPU sync, no host copy)
|
|
373
|
+
// suffices for one kernel/boundary; MLX_BUN_SWIGLU_DETACH=1 forces the
|
|
374
|
+
// stronger graph-breaking detachLeaf copy.
|
|
375
|
+
if (evalPerLayer) {
|
|
376
|
+
if (process.env.MLX_BUN_SWIGLU_DETACH === "1") cur = detachLeaf(cur);
|
|
377
|
+
else ops.evalAll([cur]);
|
|
378
|
+
}
|
|
281
379
|
}
|
|
282
380
|
mask.arr?.dispose();
|
|
283
381
|
return cur;
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
// Concrete model graph for plain Qwen3 (Qwen3ForCausalLM).
|
|
2
|
+
// Port target: mlx_lm.models.qwen3. A standard dense decoder — GQA softmax
|
|
3
|
+
// attention with per-head q/k RMSNorm (applied over head_dim BEFORE RoPE),
|
|
4
|
+
// full-head RoPE (theta 1e6), SwiGLU MLP, RMSNorm, tied embeddings.
|
|
5
|
+
//
|
|
6
|
+
// Primary use here is as a TEXT-EMBEDDING backbone (mlx-community Qwen3-Embedding-*):
|
|
7
|
+
// `embedPooled` returns the last-token hidden, L2-normalized — the pooling
|
|
8
|
+
// Qwen3-Embedding was trained with (an <|endoftext|> token is appended by the
|
|
9
|
+
// caller; its hidden is the sentence vector). The full causal-LM surface
|
|
10
|
+
// (forward/generate via the tied head) is wired too so it conforms to RuntimeModel.
|
|
11
|
+
|
|
12
|
+
import type { ModelConfig } from "../config";
|
|
13
|
+
import type { Weights } from "../weights";
|
|
14
|
+
import { MlxArray } from "../mlx/array";
|
|
15
|
+
import * as ops from "../mlx/ops";
|
|
16
|
+
import {
|
|
17
|
+
argmaxLastPosition,
|
|
18
|
+
disposing,
|
|
19
|
+
KVCache,
|
|
20
|
+
LoraState,
|
|
21
|
+
QuantizedEmbedding,
|
|
22
|
+
QuantizedLinear,
|
|
23
|
+
RMSNorm,
|
|
24
|
+
type Cache,
|
|
25
|
+
type Mask,
|
|
26
|
+
} from "./gemma4-base";
|
|
27
|
+
|
|
28
|
+
class Qwen3Attention {
|
|
29
|
+
readonly qProj: QuantizedLinear;
|
|
30
|
+
readonly kProj: QuantizedLinear;
|
|
31
|
+
readonly vProj: QuantizedLinear;
|
|
32
|
+
readonly oProj: QuantizedLinear;
|
|
33
|
+
readonly qNorm: RMSNorm;
|
|
34
|
+
readonly kNorm: RMSNorm;
|
|
35
|
+
readonly nHeads: number;
|
|
36
|
+
readonly nKvHeads: number;
|
|
37
|
+
readonly headDim: number;
|
|
38
|
+
readonly scale: number;
|
|
39
|
+
readonly ropeBase: number;
|
|
40
|
+
|
|
41
|
+
constructor(weights: Weights, config: ModelConfig, prefix: string) {
|
|
42
|
+
const t = config.text;
|
|
43
|
+
this.nHeads = t.numAttentionHeads;
|
|
44
|
+
this.nKvHeads = t.numKeyValueHeads;
|
|
45
|
+
this.headDim = t.headDim;
|
|
46
|
+
this.scale = Math.pow(this.headDim, -0.5);
|
|
47
|
+
this.ropeBase = t.ropeParameters.full_attention?.ropeTheta ?? 1000000;
|
|
48
|
+
this.qProj = QuantizedLinear.load(weights, `${prefix}.q_proj`, config);
|
|
49
|
+
this.kProj = QuantizedLinear.load(weights, `${prefix}.k_proj`, config);
|
|
50
|
+
this.vProj = QuantizedLinear.load(weights, `${prefix}.v_proj`, config);
|
|
51
|
+
this.oProj = QuantizedLinear.load(weights, `${prefix}.o_proj`, config);
|
|
52
|
+
this.qNorm = new RMSNorm(weights.tensor(`${prefix}.q_norm.weight`), t.rmsNormEps);
|
|
53
|
+
this.kNorm = new RMSNorm(weights.tensor(`${prefix}.k_norm.weight`), t.rmsNormEps);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
forward(x: MlxArray, mask: Mask, cache: Cache): MlxArray {
|
|
57
|
+
const [B, L] = x.shape as [number, number, number];
|
|
58
|
+
let q = this.qProj.forward(x);
|
|
59
|
+
let k = this.kProj.forward(x);
|
|
60
|
+
let v = this.vProj.forward(x);
|
|
61
|
+
|
|
62
|
+
// Reshape to heads, then q/k RMSNorm over head_dim BEFORE transpose+RoPE
|
|
63
|
+
// (mlx-lm qwen3 reference order).
|
|
64
|
+
q = disposing(q, ops.reshape(q, [B, L, this.nHeads, this.headDim]));
|
|
65
|
+
k = disposing(k, ops.reshape(k, [B, L, this.nKvHeads, this.headDim]));
|
|
66
|
+
v = disposing(v, ops.reshape(v, [B, L, this.nKvHeads, this.headDim]));
|
|
67
|
+
q = disposing(q, this.qNorm.forward(q));
|
|
68
|
+
k = disposing(k, this.kNorm.forward(k));
|
|
69
|
+
|
|
70
|
+
q = disposing(q, ops.transposeAxes(q, [0, 2, 1, 3]));
|
|
71
|
+
k = disposing(k, ops.transposeAxes(k, [0, 2, 1, 3]));
|
|
72
|
+
v = disposing(v, ops.transposeAxes(v, [0, 2, 1, 3]));
|
|
73
|
+
|
|
74
|
+
q = disposing(q, ops.rope(q, this.headDim, this.ropeBase, cache.offset, null));
|
|
75
|
+
k = disposing(k, ops.rope(k, this.headDim, this.ropeBase, cache.offset, null));
|
|
76
|
+
|
|
77
|
+
const [keys, values] = cache.updateAndFetch(k, v);
|
|
78
|
+
k.dispose();
|
|
79
|
+
v.dispose();
|
|
80
|
+
const attn = ops.sdpa(q, keys, values, this.scale, mask.mode, mask.arr);
|
|
81
|
+
keys.dispose();
|
|
82
|
+
values.dispose();
|
|
83
|
+
q.dispose();
|
|
84
|
+
|
|
85
|
+
const attnT = ops.transposeAxes(attn, [0, 2, 1, 3]);
|
|
86
|
+
attn.dispose();
|
|
87
|
+
const merged = ops.reshape(attnT, [B, L, -1]);
|
|
88
|
+
attnT.dispose();
|
|
89
|
+
const out = this.oProj.forward(merged);
|
|
90
|
+
merged.dispose();
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
class Qwen3MLP {
|
|
96
|
+
readonly gate: QuantizedLinear;
|
|
97
|
+
readonly up: QuantizedLinear;
|
|
98
|
+
readonly down: QuantizedLinear;
|
|
99
|
+
|
|
100
|
+
constructor(weights: Weights, config: ModelConfig, prefix: string) {
|
|
101
|
+
this.gate = QuantizedLinear.load(weights, `${prefix}.gate_proj`, config);
|
|
102
|
+
this.up = QuantizedLinear.load(weights, `${prefix}.up_proj`, config);
|
|
103
|
+
this.down = QuantizedLinear.load(weights, `${prefix}.down_proj`, config);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
forward(x: MlxArray): MlxArray {
|
|
107
|
+
const gate = this.gate.forward(x);
|
|
108
|
+
const up = this.up.forward(x);
|
|
109
|
+
const sig = ops.sigmoid(gate);
|
|
110
|
+
const silu = ops.mul(gate, sig);
|
|
111
|
+
gate.dispose();
|
|
112
|
+
sig.dispose();
|
|
113
|
+
const hidden = ops.mul(silu, up);
|
|
114
|
+
silu.dispose();
|
|
115
|
+
up.dispose();
|
|
116
|
+
const out = this.down.forward(hidden);
|
|
117
|
+
hidden.dispose();
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
class Qwen3Layer {
|
|
123
|
+
readonly attn: Qwen3Attention;
|
|
124
|
+
readonly mlp: Qwen3MLP;
|
|
125
|
+
readonly inputNorm: RMSNorm;
|
|
126
|
+
readonly postAttnNorm: RMSNorm;
|
|
127
|
+
|
|
128
|
+
constructor(weights: Weights, config: ModelConfig, prefix: string) {
|
|
129
|
+
this.attn = new Qwen3Attention(weights, config, `${prefix}.self_attn`);
|
|
130
|
+
this.mlp = new Qwen3MLP(weights, config, `${prefix}.mlp`);
|
|
131
|
+
this.inputNorm = new RMSNorm(weights.tensor(`${prefix}.input_layernorm.weight`), config.text.rmsNormEps);
|
|
132
|
+
this.postAttnNorm = new RMSNorm(weights.tensor(`${prefix}.post_attention_layernorm.weight`), config.text.rmsNormEps);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
forward(x: MlxArray, mask: Mask, cache: Cache): MlxArray {
|
|
136
|
+
const xn = this.inputNorm.forward(x);
|
|
137
|
+
const a = this.attn.forward(xn, mask, cache);
|
|
138
|
+
xn.dispose();
|
|
139
|
+
const h = ops.add(x, a);
|
|
140
|
+
a.dispose();
|
|
141
|
+
const hn = this.postAttnNorm.forward(h);
|
|
142
|
+
const m = this.mlp.forward(hn);
|
|
143
|
+
hn.dispose();
|
|
144
|
+
const out = ops.add(h, m);
|
|
145
|
+
h.dispose();
|
|
146
|
+
m.dispose();
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export class Qwen3Model {
|
|
152
|
+
readonly config: ModelConfig;
|
|
153
|
+
readonly weightsBytes: number;
|
|
154
|
+
readonly prefixBase = "model";
|
|
155
|
+
readonly loraState = new LoraState();
|
|
156
|
+
readonly embed: QuantizedEmbedding;
|
|
157
|
+
readonly layers: Qwen3Layer[];
|
|
158
|
+
readonly finalNorm: RMSNorm;
|
|
159
|
+
/** Separate lm_head when NOT tied; otherwise the tied embedding's asLinear. */
|
|
160
|
+
readonly lmHead: QuantizedLinear | null;
|
|
161
|
+
|
|
162
|
+
constructor(weights: Weights, config: ModelConfig) {
|
|
163
|
+
this.config = config;
|
|
164
|
+
this.weightsBytes = [...weights.shards.files.values()]
|
|
165
|
+
.reduce((a, f) => a + f.mmap.size, 0);
|
|
166
|
+
this.embed = QuantizedEmbedding.load(weights, "model.embed_tokens", config);
|
|
167
|
+
this.layers = Array.from(
|
|
168
|
+
{ length: config.text.numHiddenLayers },
|
|
169
|
+
(_, i) => new Qwen3Layer(weights, config, `model.layers.${i}`),
|
|
170
|
+
);
|
|
171
|
+
this.finalNorm = new RMSNorm(weights.tensor("model.norm.weight"), config.text.rmsNormEps);
|
|
172
|
+
this.lmHead = config.text.tieWordEmbeddings
|
|
173
|
+
? null
|
|
174
|
+
: QuantizedLinear.load(weights, "lm_head", config);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
loraTargets(): Map<string, QuantizedLinear> {
|
|
178
|
+
const out = new Map<string, QuantizedLinear>();
|
|
179
|
+
for (let i = 0; i < this.layers.length; i++) {
|
|
180
|
+
const l = this.layers[i]!;
|
|
181
|
+
const p = `model.layers.${i}`;
|
|
182
|
+
out.set(`${p}.self_attn.q_proj`, l.attn.qProj);
|
|
183
|
+
out.set(`${p}.self_attn.k_proj`, l.attn.kProj);
|
|
184
|
+
out.set(`${p}.self_attn.v_proj`, l.attn.vProj);
|
|
185
|
+
out.set(`${p}.self_attn.o_proj`, l.attn.oProj);
|
|
186
|
+
out.set(`${p}.mlp.gate_proj`, l.mlp.gate);
|
|
187
|
+
out.set(`${p}.mlp.up_proj`, l.mlp.up);
|
|
188
|
+
out.set(`${p}.mlp.down_proj`, l.mlp.down);
|
|
189
|
+
}
|
|
190
|
+
return out;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
makeCache(): Cache[] {
|
|
194
|
+
return this.layers.map(() => new KVCache());
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
forwardHidden(ids: MlxArray, cache: Cache[]): MlxArray {
|
|
198
|
+
const h = this.embed.encode(ids);
|
|
199
|
+
return this.forwardLayers(h, cache);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
forwardEmbeddings(_embeds: MlxArray, _cache: Cache[], _bidir: MlxArray | null): MlxArray {
|
|
203
|
+
throw new Error("qwen3 input-embedding path is not supported");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
protected forwardLayers(h0: MlxArray, cache: Cache[]): MlxArray {
|
|
207
|
+
const L = h0.shape[1]!;
|
|
208
|
+
const mask = cache[0]!.makeMask(L, null);
|
|
209
|
+
let cur = h0;
|
|
210
|
+
for (let i = 0; i < this.layers.length; i++) {
|
|
211
|
+
const next = this.layers[i]!.forward(cur, mask, cache[i]!);
|
|
212
|
+
cur.dispose();
|
|
213
|
+
cur = next;
|
|
214
|
+
}
|
|
215
|
+
mask.arr?.dispose();
|
|
216
|
+
return disposing(cur, this.finalNorm.forward(cur));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Last-token pooled, L2-normalized sentence embedding for one sequence.
|
|
220
|
+
* ids [1, L] → vector [1, hidden]. The caller appends the EOS/eod token
|
|
221
|
+
* (Qwen3-Embedding's pooling token) to `ids`. */
|
|
222
|
+
embedPooled(ids: MlxArray): MlxArray {
|
|
223
|
+
const cache = this.makeCache();
|
|
224
|
+
try {
|
|
225
|
+
const h = this.forwardHidden(ids, cache); // [1, L, hidden]
|
|
226
|
+
const [B, L, H] = h.shape as [number, number, number];
|
|
227
|
+
const last = h.slice([0, L - 1, 0], [B, L, H]); // [1, 1, hidden]
|
|
228
|
+
h.dispose();
|
|
229
|
+
const vec = disposing(last, ops.reshape(last, [B, H])); // [1, hidden]
|
|
230
|
+
// L2 normalize: x / max(||x||_2, eps)
|
|
231
|
+
const sq = ops.square(vec);
|
|
232
|
+
const ss = disposing(sq, ops.sumAxis(sq, -1, true));
|
|
233
|
+
const norm = disposing(ss, ops.sqrt(ss));
|
|
234
|
+
const out = ops.div(vec, norm);
|
|
235
|
+
vec.dispose();
|
|
236
|
+
norm.dispose();
|
|
237
|
+
return out;
|
|
238
|
+
} finally {
|
|
239
|
+
for (const c of cache) c.dispose();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
logitsFromHidden(h: MlxArray): MlxArray {
|
|
244
|
+
return this.lmHead ? this.lmHead.forward(h) : this.embed.asLinear(h);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
forward(tokens: number[] | MlxArray, cache: Cache[]): MlxArray {
|
|
248
|
+
const ids = Array.isArray(tokens)
|
|
249
|
+
? ops.fromInt32(tokens, [1, tokens.length])
|
|
250
|
+
: tokens;
|
|
251
|
+
const h = this.forwardHidden(ids, cache);
|
|
252
|
+
if (Array.isArray(tokens)) ids.dispose();
|
|
253
|
+
const logits = this.logitsFromHidden(h);
|
|
254
|
+
h.dispose();
|
|
255
|
+
return logits;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
generate(promptTokens: number[], maxTokens: number, eosIds: number[] = []): number[] {
|
|
259
|
+
const cache = this.makeCache();
|
|
260
|
+
const out: number[] = [];
|
|
261
|
+
try {
|
|
262
|
+
let tokens = promptTokens;
|
|
263
|
+
for (let step = 0; step < maxTokens; step++) {
|
|
264
|
+
const logits = this.forward(tokens, cache);
|
|
265
|
+
const next = argmaxLastPosition(logits);
|
|
266
|
+
logits.dispose();
|
|
267
|
+
if (eosIds.includes(next)) break;
|
|
268
|
+
out.push(next);
|
|
269
|
+
tokens = [next];
|
|
270
|
+
}
|
|
271
|
+
} finally {
|
|
272
|
+
for (const c of cache) c.dispose();
|
|
273
|
+
}
|
|
274
|
+
return out;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// Plain quantized linear y = x @ Wᵀ as OUR dispatch, composed from the steel
|
|
2
|
+
// building blocks already in src/train/steel-qmm-header.ts (BlockMMA + Block/
|
|
3
|
+
// QuantizedBlockLoader — MLX's verbatim GEMM, the same code qmm_t_impl wraps).
|
|
4
|
+
// This is the projection primitive for the controlled forward (q/k/v/o/down):
|
|
5
|
+
// we own the dispatch and the eval boundary instead of emitting an ops.* node
|
|
6
|
+
// into MLX's lazy graph. N (out) and H (in/K) are baked as template literals;
|
|
7
|
+
// M comes from the x_shape helper. Output bit-matches the BlockMMA store epilogue
|
|
8
|
+
// (TransformNone → cast to T), the same store qmm_t_impl uses.
|
|
9
|
+
|
|
10
|
+
import { MlxArray } from "../mlx/array";
|
|
11
|
+
import { MetalKernel } from "../mlx/metal-kernel";
|
|
12
|
+
import { STEEL_QMM_HEADER } from "../train/steel-qmm-header";
|
|
13
|
+
import * as ops from "../mlx/ops";
|
|
14
|
+
import type { QuantSpec } from "../mlx/ops";
|
|
15
|
+
|
|
16
|
+
const BM = 32, BN = 32, BK = 32, WM = 2, WN = 2;
|
|
17
|
+
const TGN = WM * WN * 32;
|
|
18
|
+
|
|
19
|
+
// One BM×BN output tile per threadgroup (grid.y = N-tiles, grid.z = M-tiles), the
|
|
20
|
+
// K=H reduction looped inside — the qmm_t_impl body, dims via templateInts.
|
|
21
|
+
const SOURCE = String.raw`
|
|
22
|
+
const uint M=x_shape[0]; constexpr int N=N_T, H=H_T;
|
|
23
|
+
constexpr int gs=GS_T, bts=BITS_T;
|
|
24
|
+
constexpr int pf=get_pack_factor<bts,8>(), bpp=get_bytes_per_pack<bts>();
|
|
25
|
+
constexpr int BKp=BK_T+16/sizeof(T);
|
|
26
|
+
threadgroup T Xs[BM_T*BKp];
|
|
27
|
+
threadgroup T Ws[BN_T*BKp];
|
|
28
|
+
using mma_t=mlx::steel::BlockMMA<T,T,BM_T,BN_T,BK_T,WM_T,WN_T,false,true,BKp,BKp>;
|
|
29
|
+
using lx_t=mlx::steel::BlockLoader<T,BM_T,BK_T,BKp,1,WM_T*WN_T*SIMD_SIZE>;
|
|
30
|
+
using lw_t=QuantizedBlockLoader<T,BN_T,BK_T,BKp,1,WM_T*WN_T*SIMD_SIZE,gs,bts>;
|
|
31
|
+
const uint sg=simdgroup_index_in_threadgroup, sl=thread_index_in_simdgroup;
|
|
32
|
+
const int Hh=(int)H, K_w=Hh*bpp/pf, K_g=Hh/gs;
|
|
33
|
+
const int m0=(int)threadgroup_position_in_grid.z*BM_T;
|
|
34
|
+
const int n0=(int)threadgroup_position_in_grid.y*BN_T;
|
|
35
|
+
const short num_els=(short)min(BM_T,(int)M-m0);
|
|
36
|
+
const short num_outs=(short)min(BN_T,(int)N-n0);
|
|
37
|
+
|
|
38
|
+
mma_t mma_op(sg,sl);
|
|
39
|
+
lx_t lx(x + (int64_t)m0*Hh, Hh, Xs, sg, sl);
|
|
40
|
+
lw_t lw((const device uint8_t*)w + (int64_t)n0*K_w, scales + (int64_t)n0*K_g, biases + (int64_t)n0*K_g, Hh, Ws, sg, sl);
|
|
41
|
+
|
|
42
|
+
for (int k=0; k<Hh; k+=BK_T){
|
|
43
|
+
threadgroup_barrier(mem_flags::mem_threadgroup);
|
|
44
|
+
if (num_els<BM_T) lx.load_safe(short2(BK_T,num_els)); else lx.load_unsafe();
|
|
45
|
+
if (num_outs<BN_T) lw.load_safe(short2(BK_T,num_outs)); else lw.load_unsafe();
|
|
46
|
+
threadgroup_barrier(mem_flags::mem_threadgroup);
|
|
47
|
+
mma_op.mma(Xs,Ws);
|
|
48
|
+
lx.next(); lw.next();
|
|
49
|
+
}
|
|
50
|
+
threadgroup_barrier(mem_flags::mem_threadgroup);
|
|
51
|
+
// Store straight to device y[m0.., n0..] via the BlockMMA epilogue (float accum
|
|
52
|
+
// → T, the SAME rounding MLX's quantized_matmul applies).
|
|
53
|
+
device T* yp = y + (int64_t)m0*(int64_t)N + n0;
|
|
54
|
+
if (num_els<BM_T || num_outs<BN_T) mma_op.store_result_safe(yp, N, short2(num_outs,num_els));
|
|
55
|
+
else mma_op.store_result(yp, N);
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
const _kernels = new Map<string, MetalKernel>();
|
|
59
|
+
function getKernel(gs: number, bits: number): MetalKernel {
|
|
60
|
+
const key = `${gs}:${bits}`;
|
|
61
|
+
let k = _kernels.get(key);
|
|
62
|
+
if (!k) {
|
|
63
|
+
k = new MetalKernel({
|
|
64
|
+
name: "mlx_bun_steel_linear",
|
|
65
|
+
inputNames: ["x", "w", "scales", "biases"],
|
|
66
|
+
outputNames: ["y"],
|
|
67
|
+
source: SOURCE,
|
|
68
|
+
header:
|
|
69
|
+
`${STEEL_QMM_HEADER}\n` +
|
|
70
|
+
`#define GS_T ${gs}\n#define BITS_T ${bits}\n` +
|
|
71
|
+
`#define BM_T ${BM}\n#define BN_T ${BN}\n#define BK_T ${BK}\n` +
|
|
72
|
+
`#define WM_T ${WM}\n#define WN_T ${WN}\n#define TGN_T ${TGN}\n`,
|
|
73
|
+
ensureRowContiguous: true,
|
|
74
|
+
});
|
|
75
|
+
_kernels.set(key, k);
|
|
76
|
+
}
|
|
77
|
+
return k;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** y = x @ Wᵀ, quantized, as our own dispatch. `x` is [M, H] (bf16/f16); `w` is
|
|
81
|
+
* the packed weight [N, H*bits/32] with per-group scales/biases [N, H/groupSize].
|
|
82
|
+
* Returns [M, N] in x.dtype. H % BK == 0 and groupSize % BK == 0 required. */
|
|
83
|
+
export function steelLinear(
|
|
84
|
+
x: MlxArray, w: MlxArray, scales: MlxArray, biases: MlxArray, spec: QuantSpec,
|
|
85
|
+
): MlxArray {
|
|
86
|
+
const M = x.shape[0]!, H = x.shape[1]!, N = w.shape[0]!;
|
|
87
|
+
const [y] = getKernel(spec.groupSize, spec.bits).apply([x, w, scales, biases], {
|
|
88
|
+
outputShapeFn: (ins) => [{ shape: [ins[0]!.shape[0]!, N], dtype: x.dtype }],
|
|
89
|
+
grid: (ins) => [TGN, Math.ceil(N / BN), Math.ceil(ins[0]!.shape[0]! / BM)],
|
|
90
|
+
threadGroup: [TGN, 1, 1],
|
|
91
|
+
templateInts: { N_T: N, H_T: H },
|
|
92
|
+
templateDtypes: { T: x.dtype },
|
|
93
|
+
});
|
|
94
|
+
void M;
|
|
95
|
+
return y!;
|
|
96
|
+
}
|