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,305 @@
|
|
|
1
|
+
// Fused decode-SDPA Metal kernel, v2: flash-decoding split-N
|
|
2
|
+
// (docs/design/optimization_plan.md Phase E). L=1 attention over a QUANTIZED KV
|
|
3
|
+
// cache with the dequant inlined — neither the score row nor the
|
|
4
|
+
// dequantized cache is ever materialized. Dispatched from Bun via
|
|
5
|
+
// mx.fast.metal_kernel (no MLX fork), always OUTSIDE compiled-decode
|
|
6
|
+
// closures (CustomKernel has no output_shapes; compiled segments
|
|
7
|
+
// reclassify perf-kernel layers as JS layers instead).
|
|
8
|
+
//
|
|
9
|
+
// v1 post-mortem (git 48c9b10): numerics landed at the accepted tier-b
|
|
10
|
+
// envelope but ONE THREADGROUP PER QUERY HEAD (16 total on the 12B)
|
|
11
|
+
// could not occupy the GPU — 0.72× @8k. v2 splits the KV rows into
|
|
12
|
+
// G = ceil(N/BLOCK) blocks (grid [128, H, G]):
|
|
13
|
+
// kernel A: per-block (max, sumexp) over T-rounded scores
|
|
14
|
+
// kernel C: per-block Σ T(exp(s−M)/denom)·dequant(V) partials, with
|
|
15
|
+
// the full-row (M, denom) derived inline from A's stats —
|
|
16
|
+
// deterministic (no atomics: atomic add order would make
|
|
17
|
+
// rounding nondeterministic)
|
|
18
|
+
// merge: one mlx sum over G + cast to T (deterministic order)
|
|
19
|
+
// Rounding points still MATCH the compat path where it matters (scores
|
|
20
|
+
// and normalized probs round to T; f32 accumulation) — per-layer
|
|
21
|
+
// divergence stays at the stray-ulp scale the teacher-forced gate is
|
|
22
|
+
// calibrated for (tests/perf-kernel-oracle.test.ts).
|
|
23
|
+
|
|
24
|
+
import { MlxArray } from "../mlx/array";
|
|
25
|
+
import { Dtype } from "../mlx/ffi";
|
|
26
|
+
import { MetalKernel } from "../mlx/metal-kernel";
|
|
27
|
+
import * as ops from "../mlx/ops";
|
|
28
|
+
|
|
29
|
+
/** Perf-mode lever: DEFAULT ON (2026-06-11 cleared-machine pass, ~1.02x @8k).
|
|
30
|
+
* It matches optiq's fused-KV decode — which the quantized-KV parity goldens
|
|
31
|
+
* track — so it's the production default. It is NOT bit-exact vs the -O0
|
|
32
|
+
* reference (online softmax, tier-b), so it's the PERF side of the
|
|
33
|
+
* compat-vs-perf A/B: MLX_BUN_PERF_KERNEL=0 selects the compat path we bench
|
|
34
|
+
* against the python libs. (B/C-style bit-exact optimizations stay on either
|
|
35
|
+
* way — this lever only toggles the parity-breaking decode kernel.) */
|
|
36
|
+
export function perfKernelEnabled(): boolean {
|
|
37
|
+
return process.env.MLX_BUN_PERF_KERNEL !== "0";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Dispatch counter (the oracle gate asserts the kernel actually ran). */
|
|
41
|
+
export let fusedKernelCalls = 0;
|
|
42
|
+
|
|
43
|
+
const SIMD_GROUPS = 4;
|
|
44
|
+
const LANES = 32;
|
|
45
|
+
const TG_THREADS = SIMD_GROUPS * LANES;
|
|
46
|
+
|
|
47
|
+
// Shared per-row score computation: lane-sliced dot product with the
|
|
48
|
+
// dequant FACTORED out of the inner loop (the mlx qdot pattern,
|
|
49
|
+
// quantized.h, Apache-2.0 — translated into this kernel's dialect):
|
|
50
|
+
// Σ (s·w_e + b)·q_e = s·Σ(w_e·q_e) + b·Σ q_e
|
|
51
|
+
// and for 4-bit the nibbles multiply MASKED-BUT-UNSHIFTED against a
|
|
52
|
+
// query pre-divided by 16^k — the inner loop is mask+madd only, via
|
|
53
|
+
// 16-bit reads. Lane slices are group-aligned for D ∈ {256, 512},
|
|
54
|
+
// GS ∈ {32, 64, 128}: one scale/bias per lane per row.
|
|
55
|
+
const SCORE_PREAMBLE = String.raw`
|
|
56
|
+
constexpr int SG = ${SIMD_GROUPS};
|
|
57
|
+
constexpr int ELEMS = D / ${LANES};
|
|
58
|
+
constexpr int PER_WORD = 32 / BITS;
|
|
59
|
+
constexpr int WORDS = ELEMS / PER_WORD;
|
|
60
|
+
|
|
61
|
+
const uint lane = thread_index_in_simdgroup;
|
|
62
|
+
const uint sg = simdgroup_index_in_threadgroup;
|
|
63
|
+
const uint h = thread_position_in_grid.y;
|
|
64
|
+
const uint g = thread_position_in_grid.z;
|
|
65
|
+
const uint kv = h / NREP;
|
|
66
|
+
const int N = kp_shape[2];
|
|
67
|
+
const int G = (N + BLOCK - 1) / BLOCK;
|
|
68
|
+
const int nStart = (int)g * BLOCK;
|
|
69
|
+
const int nEnd = metal::min(nStart + BLOCK, N);
|
|
70
|
+
const int kRowWords = D / PER_WORD;
|
|
71
|
+
const int gRow = D / GS;
|
|
72
|
+
|
|
73
|
+
// lane's q slice: raw sum for the bias term, and (4-bit) nibble-place
|
|
74
|
+
// pre-scaled copies for the no-shift masked dot
|
|
75
|
+
float qv[ELEMS];
|
|
76
|
+
float qSum = 0.0f;
|
|
77
|
+
const uint qBase = h * D + lane * ELEMS;
|
|
78
|
+
for (int e = 0; e < ELEMS; e++) {
|
|
79
|
+
const float v = float(q[qBase + e]);
|
|
80
|
+
qSum += v;
|
|
81
|
+
qv[e] = BITS == 4
|
|
82
|
+
? v * ((e & 3) == 0 ? 1.0f : (e & 3) == 1 ? 0.0625f : (e & 3) == 2 ? 0.00390625f : 0.000244140625f)
|
|
83
|
+
: v;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const int grpIdx = (lane * ELEMS) / GS;
|
|
87
|
+
`;
|
|
88
|
+
|
|
89
|
+
const SCORE_ROW = String.raw`
|
|
90
|
+
const ulong rowOff = ((ulong)kv * (ulong)N + (ulong)n);
|
|
91
|
+
const float kScale = float(ks[rowOff * gRow + grpIdx]);
|
|
92
|
+
const float kBias = float(kb[rowOff * gRow + grpIdx]);
|
|
93
|
+
float accum = 0.0f;
|
|
94
|
+
if (BITS == 4) {
|
|
95
|
+
const device uint16_t* kw =
|
|
96
|
+
((const device uint16_t*)(kp + rowOff * kRowWords)) + lane * (ELEMS / 4);
|
|
97
|
+
for (int i = 0; i < ELEMS / 4; i++) {
|
|
98
|
+
const uint16_t wv = kw[i];
|
|
99
|
+
accum += qv[4 * i] * (float)(wv & 0x000f)
|
|
100
|
+
+ qv[4 * i + 1] * (float)(wv & 0x00f0)
|
|
101
|
+
+ qv[4 * i + 2] * (float)(wv & 0x0f00)
|
|
102
|
+
+ qv[4 * i + 3] * (float)(wv & 0xf000);
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
const device uint8_t* kw8 =
|
|
106
|
+
((const device uint8_t*)(kp + rowOff * kRowWords)) + lane * ELEMS;
|
|
107
|
+
for (int e = 0; e < ELEMS; e++) accum += qv[e] * (float)kw8[e];
|
|
108
|
+
}
|
|
109
|
+
const float partial = kScale * accum + qSum * kBias;
|
|
110
|
+
const float s = float(T(simd_sum(partial)));
|
|
111
|
+
`;
|
|
112
|
+
|
|
113
|
+
// kernel: single-pass per-block ONLINE softmax — K and V each read
|
|
114
|
+
// exactly once (the two-pass v2.0 read K twice and measured 0.945);
|
|
115
|
+
// per-block (max, sumexp) and unnormalized o land in stats/opart for
|
|
116
|
+
// the merge kernel.
|
|
117
|
+
const BLOCK_SOURCE = SCORE_PREAMBLE + String.raw`
|
|
118
|
+
float mx = -INFINITY;
|
|
119
|
+
float sumexp = 0.0f;
|
|
120
|
+
// V accumulates RAW masked nibbles per slot (factor 16^k folds once at
|
|
121
|
+
// the end) with the bias term collapsed to one scalar: Σ p·b — the
|
|
122
|
+
// qdot factoring applied to the value side.
|
|
123
|
+
float oacc[ELEMS];
|
|
124
|
+
for (int e = 0; e < ELEMS; e++) oacc[e] = 0.0f;
|
|
125
|
+
float pbSum = 0.0f;
|
|
126
|
+
|
|
127
|
+
for (int n = nStart + (int)sg; n < nEnd; n += SG) {
|
|
128
|
+
${SCORE_ROW}
|
|
129
|
+
const float newmax = metal::max(mx, s);
|
|
130
|
+
const float factor = metal::exp(mx - newmax);
|
|
131
|
+
const float p = metal::exp(s - newmax);
|
|
132
|
+
sumexp = sumexp * factor + p;
|
|
133
|
+
mx = newmax;
|
|
134
|
+
|
|
135
|
+
const float vScale = float(vs[rowOff * gRow + grpIdx]);
|
|
136
|
+
const float vBias = float(vb[rowOff * gRow + grpIdx]);
|
|
137
|
+
const float ps = p * vScale;
|
|
138
|
+
pbSum = pbSum * factor + p * vBias;
|
|
139
|
+
if (BITS == 4) {
|
|
140
|
+
const device uint16_t* vw =
|
|
141
|
+
((const device uint16_t*)(vp + rowOff * kRowWords)) + lane * (ELEMS / 4);
|
|
142
|
+
for (int i = 0; i < ELEMS / 4; i++) {
|
|
143
|
+
const uint16_t wv = vw[i];
|
|
144
|
+
oacc[4 * i] = oacc[4 * i] * factor + ps * (float)(wv & 0x000f);
|
|
145
|
+
oacc[4 * i + 1] = oacc[4 * i + 1] * factor + ps * (float)(wv & 0x00f0);
|
|
146
|
+
oacc[4 * i + 2] = oacc[4 * i + 2] * factor + ps * (float)(wv & 0x0f00);
|
|
147
|
+
oacc[4 * i + 3] = oacc[4 * i + 3] * factor + ps * (float)(wv & 0xf000);
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
const device uint8_t* vw8 =
|
|
151
|
+
((const device uint8_t*)(vp + rowOff * kRowWords)) + lane * ELEMS;
|
|
152
|
+
for (int e = 0; e < ELEMS; e++)
|
|
153
|
+
oacc[e] = oacc[e] * factor + ps * (float)vw8[e];
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// fold the nibble-place factors and the bias sum (once per block)
|
|
157
|
+
for (int e = 0; e < ELEMS; e++) {
|
|
158
|
+
const float inv = BITS == 4
|
|
159
|
+
? ((e & 3) == 0 ? 1.0f : (e & 3) == 1 ? 0.0625f : (e & 3) == 2 ? 0.00390625f : 0.000244140625f)
|
|
160
|
+
: 1.0f;
|
|
161
|
+
oacc[e] = oacc[e] * inv + pbSum;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// cross-simdgroup online merge within the block
|
|
165
|
+
threadgroup float tgMax[SG];
|
|
166
|
+
threadgroup float tgSum[SG];
|
|
167
|
+
threadgroup float tgO[SG * D];
|
|
168
|
+
if (lane == 0) {
|
|
169
|
+
tgMax[sg] = mx;
|
|
170
|
+
tgSum[sg] = sumexp;
|
|
171
|
+
}
|
|
172
|
+
for (int e = 0; e < ELEMS; e++) tgO[sg * D + lane * ELEMS + e] = oacc[e];
|
|
173
|
+
threadgroup_barrier(mem_flags::mem_threadgroup);
|
|
174
|
+
if (sg == 0) {
|
|
175
|
+
float M = -INFINITY;
|
|
176
|
+
for (int gg = 0; gg < SG; gg++) M = metal::max(M, tgMax[gg]);
|
|
177
|
+
float S = 0.0f;
|
|
178
|
+
float wgt[SG];
|
|
179
|
+
for (int gg = 0; gg < SG; gg++) {
|
|
180
|
+
wgt[gg] = metal::exp(tgMax[gg] - M);
|
|
181
|
+
S += tgSum[gg] * wgt[gg];
|
|
182
|
+
}
|
|
183
|
+
for (int e = 0; e < ELEMS; e++) {
|
|
184
|
+
const int d = lane * ELEMS + e;
|
|
185
|
+
float o = 0.0f;
|
|
186
|
+
for (int gg = 0; gg < SG; gg++) o += tgO[gg * D + d] * wgt[gg];
|
|
187
|
+
opart[((ulong)h * (ulong)G + (ulong)g) * (ulong)D + (ulong)d] = o;
|
|
188
|
+
}
|
|
189
|
+
if (lane == 0) {
|
|
190
|
+
stats[(h * (uint)G + g) * 2 + 0] = M;
|
|
191
|
+
stats[(h * (uint)G + g) * 2 + 1] = S;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
`;
|
|
195
|
+
|
|
196
|
+
// merge kernel: out[h,d] = Σ_g opart[h,g,d]·exp(m_g−M) / denom, cast T.
|
|
197
|
+
// Deterministic (fixed g order), one tiny dispatch.
|
|
198
|
+
const MERGE_SOURCE = String.raw`
|
|
199
|
+
const uint d = thread_position_in_grid.x;
|
|
200
|
+
const uint h = thread_position_in_grid.y;
|
|
201
|
+
const int G = stats_shape[2];
|
|
202
|
+
if ((int)d >= D) return;
|
|
203
|
+
float M = -INFINITY;
|
|
204
|
+
for (int g = 0; g < G; g++)
|
|
205
|
+
M = metal::max(M, stats[(h * (uint)G + (uint)g) * 2 + 0]);
|
|
206
|
+
float denom = 0.0f;
|
|
207
|
+
for (int g = 0; g < G; g++)
|
|
208
|
+
denom += stats[(h * (uint)G + (uint)g) * 2 + 1] *
|
|
209
|
+
metal::exp(stats[(h * (uint)G + (uint)g) * 2 + 0] - M);
|
|
210
|
+
float o = 0.0f;
|
|
211
|
+
for (int g = 0; g < G; g++)
|
|
212
|
+
o += opart[((ulong)h * (ulong)G + (ulong)g) * (ulong)D + (ulong)d] *
|
|
213
|
+
metal::exp(stats[(h * (uint)G + (uint)g) * 2 + 0] - M);
|
|
214
|
+
out[h * (uint)D + d] = T(o / denom);
|
|
215
|
+
`;
|
|
216
|
+
|
|
217
|
+
let blockKernel: MetalKernel | null = null;
|
|
218
|
+
let mergeKernel: MetalKernel | null = null;
|
|
219
|
+
|
|
220
|
+
function kernels(): { block: MetalKernel; merge: MetalKernel } {
|
|
221
|
+
if (!blockKernel) {
|
|
222
|
+
blockKernel = new MetalKernel({
|
|
223
|
+
name: "mlx_bun_fqsdpa_block",
|
|
224
|
+
inputNames: ["q", "kp", "ks", "kb", "vp", "vs", "vb"],
|
|
225
|
+
outputNames: ["opart", "stats"],
|
|
226
|
+
source: BLOCK_SOURCE,
|
|
227
|
+
ensureRowContiguous: true,
|
|
228
|
+
});
|
|
229
|
+
mergeKernel = new MetalKernel({
|
|
230
|
+
name: "mlx_bun_fqsdpa_merge",
|
|
231
|
+
inputNames: ["opart", "stats"],
|
|
232
|
+
outputNames: ["out"],
|
|
233
|
+
source: MERGE_SOURCE,
|
|
234
|
+
ensureRowContiguous: true,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
return { block: blockKernel, merge: mergeKernel! };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Split width: enough blocks to occupy the GPU at small N without
|
|
241
|
+
* shrinking blocks into per-dispatch overhead at large N. */
|
|
242
|
+
function blockSize(n: number): number {
|
|
243
|
+
return n <= 2048 ? 128 : 512;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Is this dispatch site servable by the fused kernel? (Phase D bakes
|
|
247
|
+
* bits/gs at generation time; shape checks guard runtime drift.) */
|
|
248
|
+
export function fusedDecodeKernelSupported(
|
|
249
|
+
q: MlxArray, bits: number, groupSize: number,
|
|
250
|
+
): boolean {
|
|
251
|
+
if (bits !== 4 && bits !== 8) return false;
|
|
252
|
+
if (groupSize !== 32 && groupSize !== 64 && groupSize !== 128) return false;
|
|
253
|
+
if (q.dtype !== Dtype.bfloat16) return false;
|
|
254
|
+
const [B, , L, D] = q.shape as [number, number, number, number];
|
|
255
|
+
if (B !== 1 || L !== 1) return false;
|
|
256
|
+
if (D % LANES !== 0) return false;
|
|
257
|
+
const elems = D / LANES;
|
|
258
|
+
if ((elems * bits) % 32 !== 0) return false; // whole u32 words per lane
|
|
259
|
+
// lane slice must sit inside one quantization group
|
|
260
|
+
if (elems > groupSize || groupSize % elems !== 0) return false;
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Fused decode attention over quantized KV (flash-decoding split-N).
|
|
265
|
+
* Caller guarantees fusedDecodeKernelSupported and mask mode "" (decode
|
|
266
|
+
* attends the whole fetched window). q [1,H,1,D] bf16. */
|
|
267
|
+
export function fusedDecodeSdpa(
|
|
268
|
+
q: MlxArray, kq: ops.QuantizedTensor, vq: ops.QuantizedTensor,
|
|
269
|
+
groupSize: number, bits: number,
|
|
270
|
+
): MlxArray {
|
|
271
|
+
const [, H, , D] = q.shape as [number, number, number, number];
|
|
272
|
+
const KV = kq.packed.shape[1]!;
|
|
273
|
+
const N = kq.packed.shape[2]!;
|
|
274
|
+
const nRep = H / KV;
|
|
275
|
+
const BLOCK = blockSize(N);
|
|
276
|
+
const G = Math.ceil(N / BLOCK);
|
|
277
|
+
fusedKernelCalls++;
|
|
278
|
+
|
|
279
|
+
const { block, merge } = kernels();
|
|
280
|
+
const tmpl = { D, GS: groupSize, BITS: bits, NREP: nRep, BLOCK };
|
|
281
|
+
|
|
282
|
+
const [opart, statsArr] = block.apply(
|
|
283
|
+
[q, kq.packed, kq.scales, kq.biases, vq.packed, vq.scales, vq.biases],
|
|
284
|
+
{
|
|
285
|
+
outputs: [
|
|
286
|
+
{ shape: [1, H, G, D], dtype: Dtype.float32 },
|
|
287
|
+
{ shape: [1, H, G, 2], dtype: Dtype.float32 },
|
|
288
|
+
],
|
|
289
|
+
grid: [TG_THREADS, H, G],
|
|
290
|
+
threadGroup: [TG_THREADS, 1, 1],
|
|
291
|
+
templateInts: tmpl,
|
|
292
|
+
templateDtypes: { T: q.dtype },
|
|
293
|
+
},
|
|
294
|
+
);
|
|
295
|
+
const [out] = merge.apply([opart!, statsArr!], {
|
|
296
|
+
outputs: [{ shape: [1, H, 1, D], dtype: q.dtype }],
|
|
297
|
+
grid: [D, H, 1],
|
|
298
|
+
threadGroup: [Math.min(D, 256), 1, 1],
|
|
299
|
+
templateInts: { D },
|
|
300
|
+
templateDtypes: { T: q.dtype },
|
|
301
|
+
});
|
|
302
|
+
opart!.dispose();
|
|
303
|
+
statsArr!.dispose();
|
|
304
|
+
return out!;
|
|
305
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Fused GeGLU Metal kernel (optimization_plan Phase E, opportunity B/C):
|
|
2
|
+
// gelu_approx(a)·b in ONE pass, replacing the ~9 element-wise kernels the
|
|
3
|
+
// spelled-out path emits (power, ×3, +, ×, tanh, +, ×, × + a final mul).
|
|
4
|
+
// Both GeGLU sites are this shape — MLP (gelu(gate)·up) and the e4b
|
|
5
|
+
// per-layer gate (gelu(gate)·pli).
|
|
6
|
+
//
|
|
7
|
+
// MATCH-COMPAT precision: bf16 in/out, each intermediate rounded to bf16
|
|
8
|
+
// op-for-op with the spelled-out path (the mlx-lm parity constraint). A
|
|
9
|
+
// first full-f32 version drifted 0.22 KL on e4b (small per-element error
|
|
10
|
+
// compounding over 42 MLPs) — match-compat keeps the distribution on the
|
|
11
|
+
// trained path; the only residual is the pow/tanh math-lib difference, which
|
|
12
|
+
// the KL gate quantifies. Still a perf kernel (not bit-exact) and never
|
|
13
|
+
// inside a compiled-decode trace: CustomKernel has no output_shapes (same
|
|
14
|
+
// rule as fusedDecodeSdpa) — the caller guards on !isCompiledTrace().
|
|
15
|
+
|
|
16
|
+
import { MlxArray } from "../mlx/array";
|
|
17
|
+
import { Dtype } from "../mlx/ffi";
|
|
18
|
+
import { MetalKernel } from "../mlx/metal-kernel";
|
|
19
|
+
|
|
20
|
+
/** ON by default: the match-compat kernel is BIT-EXACT with the spelled-out
|
|
21
|
+
* path (kl --decode = 0 on e4b), so per the bit-parity-floor principle it
|
|
22
|
+
* ships on — every optimization we can keep while staying bit-exact.
|
|
23
|
+
* MLX_BUN_FUSED_GELU=0 opts out (e.g. the compile arm of the A/B). */
|
|
24
|
+
export function fusedGeluEnabled(): boolean {
|
|
25
|
+
return process.env.MLX_BUN_FUSED_GELU !== "0";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Dispatch counter (a gate test asserts the kernel actually ran). */
|
|
29
|
+
export let fusedGeluCalls = 0;
|
|
30
|
+
|
|
31
|
+
// Pointwise: one thread per element. gelu_approx =
|
|
32
|
+
// 0.5 x (1 + tanh(√(2/π)·(x + 0.044715 x³))) — composed in f32.
|
|
33
|
+
const SOURCE = String.raw`
|
|
34
|
+
const uint i = thread_position_in_grid.x;
|
|
35
|
+
if (i >= SIZE) return;
|
|
36
|
+
// Match-compat: round each intermediate (and the weak scalars) to T, op
|
|
37
|
+
// for op with the spelled-out bf16 path, so the fused distribution tracks
|
|
38
|
+
// the TRAINED path rather than drifting to "more accurate but different".
|
|
39
|
+
// x^3 via pow (the reference uses mx.power, NOT x*x*x — they round
|
|
40
|
+
// differently in bf16). The single remaining residual vs compat is the
|
|
41
|
+
// pow/tanh math-lib difference, which the KL gate quantifies.
|
|
42
|
+
const float C_CUBE = float(T(0.044715f));
|
|
43
|
+
const float C_SQRT = float(T(0.7978845608028654f)); // √(2/π)
|
|
44
|
+
const float C_HALF = float(T(0.5f));
|
|
45
|
+
const float x = float(a[i]);
|
|
46
|
+
const float x3 = float(T(metal::pow(x, 3.0f)));
|
|
47
|
+
const float cx3 = float(T(x3 * C_CUBE));
|
|
48
|
+
const float inn = float(T(x + cx3));
|
|
49
|
+
const float scl = float(T(inn * C_SQRT));
|
|
50
|
+
const float tnh = float(T(metal::precise::tanh(scl)));
|
|
51
|
+
const float t1 = float(T(1.0f + tnh));
|
|
52
|
+
const float hx = float(T(x * C_HALF));
|
|
53
|
+
const float g = float(T(hx * t1));
|
|
54
|
+
out[i] = T(g * float(b[i]));
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
let kernel: MetalKernel | null = null;
|
|
58
|
+
function getKernel(): MetalKernel {
|
|
59
|
+
if (!kernel)
|
|
60
|
+
kernel = new MetalKernel({
|
|
61
|
+
name: "mlx_bun_fused_geglu",
|
|
62
|
+
inputNames: ["a", "b"],
|
|
63
|
+
outputNames: ["out"],
|
|
64
|
+
source: SOURCE,
|
|
65
|
+
ensureRowContiguous: true,
|
|
66
|
+
});
|
|
67
|
+
return kernel;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** bf16/f16 only (the activation dtype the kernel reads/writes). */
|
|
71
|
+
export function fusedGeluSupported(a: MlxArray): boolean {
|
|
72
|
+
return a.dtype === Dtype.bfloat16 || a.dtype === Dtype.float16;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** gelu_approx(a) · b, fused. a and b must share shape/dtype; out is a.dtype. */
|
|
76
|
+
export function fusedGeglu(a: MlxArray, b: MlxArray): MlxArray {
|
|
77
|
+
fusedGeluCalls++;
|
|
78
|
+
const size = a.shape.reduce((x, y) => x * y, 1);
|
|
79
|
+
const [out] = getKernel().apply([a, b], {
|
|
80
|
+
outputs: [{ shape: a.shape, dtype: a.dtype }],
|
|
81
|
+
grid: [size, 1, 1],
|
|
82
|
+
threadGroup: [Math.min(size, 256), 1, 1],
|
|
83
|
+
templateInts: { SIZE: size },
|
|
84
|
+
templateDtypes: { T: a.dtype },
|
|
85
|
+
});
|
|
86
|
+
return out!;
|
|
87
|
+
}
|