diskformer 0.5.4 → 0.6.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.
@@ -0,0 +1,487 @@
1
+ /* flint_moe_ssd.js — Recursive Flint (GGUF arch `flint`, 39.8B total, ~2.5B active) with its routed experts on disk:
2
+ * rung 2g of the SSD-streaming ladder. Flint is a coding-agent model (released 2026-10-21, Apache-2.0). As an engine it
3
+ * is the Hy-Memory graph (hy_v3_moe_ssd.js, on the LFM2.5 rung) with attention sinks, a sliding window and no RoPE on
4
+ * its full-attention layers, read from Q4_1 weights.
5
+ *
6
+ * The math follows llama.cpp src/models/flint.cpp (a37cc461). 49 layers, each:
7
+ * x += attn(rms_norm(x) · attn_norm): Q/K/V (32 / 4 / 4 heads × 128; Q is 4096 wide), per-head weighted RMS norm on
8
+ * Q and K, then NeoX RoPE (base 10000, all 128 dims) on the 35 sliding layers and none on the 14 full layers
9
+ * (rope_pattern). Scores QKᵀ/√128; the sliding layers see the current token and the 512 before it (window 513,
10
+ * llama.cpp LLAMA_SWA_TYPE_STANDARD); softmax with one learned sink logit per head in the denominator
11
+ * (attn_sinks); output projection 4096 → 2048.
12
+ * x += ffn(rms_norm(x) · ffn_norm): a dense SwiGLU (6144) on layers 0–1; on layers 2–48 a MoE of 256 experts, top 6
13
+ * by sigmoid(router) (no selection bias in this file), weights = the picked sigmoids over their sum (scale 1),
14
+ * SwiGLU experts (512), plus one always-on shared SwiGLU expert (1024, no gate).
15
+ * Embedding: F32 rows (no scale); head: rms_norm(x) · output_norm, then output.weight (Q4_1, untied).
16
+ *
17
+ * File: Recursive's own Q4_1 GGUF (RecursiveSI/flint-int4-gguf: the MLX export's trained INT4 codes with f16 scales and
18
+ * offsets): every matrix and expert Q4_1; norms, sinks, router and the token table F32. Q4_1 blocks are 20 bytes (f16
19
+ * d, f16 m, 16 bytes of nibbles), a whole number of u32 words, so the store keeps them as they are in the GGUF and the
20
+ * kernels read value = d·q + m straight from the blocks. The store's scale planes (the `s` binding and the pool's
21
+ * guS / dS parts) are unused here.
22
+ */
23
+
24
+ import { Qwen3MoeSsd, GGML, TOK, STORAGE } from './qwen3_moe_ssd.js';
25
+ import { Gemma4MoeSsd } from './gemma4_moe_ssd.js';
26
+ import { Lfm2MoeSsd } from './lfm2_moe_ssd.js';
27
+ import { HyV3Tokenizer, isolate } from './hy_v3_moe_ssd.js';
28
+
29
+ const Q41_BLOCK = 20; // f16 d + f16 m + 32 × 4-bit
30
+ const GGML_Q4_1 = 3;
31
+
32
+ export function configFromGgufFlint(kv) {
33
+ const a = kv['general.architecture'];
34
+ if (a !== 'flint') throw new Error(`expected a flint GGUF, got ${a}`);
35
+ const g = (k) => kv[`${a}.${k}`];
36
+ if (g('expert_gating_func') !== 2) throw new Error(`flint expert_gating_func ${g('expert_gating_func')}: this engine implements sigmoid (2)`);
37
+ if (g('expert_weights_norm') === false) throw new Error('flint without expert_weights_norm: not implemented');
38
+ const modelLayers = g('block_count'), lead = g('leading_dense_block_count'), layers = modelLayers - lead;
39
+ const H = g('embedding_length'), heads = g('attention.head_count'), kvH = g('attention.head_count_kv');
40
+ const hd = g('attention.key_length') || H / heads, theta = g('rope.freq_base');
41
+ const swaPat = g('attention.sliding_window_pattern'), ropePat = g('attention.rope_pattern');
42
+ if (swaPat.length !== modelLayers || ropePat.length !== modelLayers) throw new Error('flint: per-layer patterns do not match block_count');
43
+ return {
44
+ arch: a, modelLayers, lead, layers,
45
+ attn: Array(modelLayers).fill(true),
46
+ hidden: H, denseFf: g('feed_forward_length'), heads, kvHeadsAttn: kvH, attnHeadDim: hd,
47
+ experts: g('expert_count'), topK: g('expert_used_count'), expertFf: g('expert_feed_forward_length'),
48
+ shexpFf: g('expert_shared_feed_forward_length'), expScale: g('expert_weights_scale') ?? 1,
49
+ eps: g('attention.layer_norm_rms_epsilon'), contextLength: g('context_length'),
50
+ vocab: kv['tokenizer.ggml.tokens'].length,
51
+ bos: kv['tokenizer.ggml.bos_token_id'], eos: kv['tokenizer.ggml.eos_token_id'],
52
+ // Per model layer: the attention window (0 = all positions) and whether Q and K take RoPE.
53
+ win: swaPat.map((s) => (s ? g('attention.sliding_window') : 0)), rope: ropePat.map(Boolean),
54
+ // The shape Gemma4MoeSsd's RoPE tables read, per MoE layer: one RoPE table (the NoPE layers bind an identity one).
55
+ swa: Array(layers).fill(false), headDim: Array(layers).fill(hd), ropeTheta: Array(layers).fill(theta), softcap: 0,
56
+ kvHeads: Array(layers).fill(kvH),
57
+ };
58
+ }
59
+
60
+ // The OPFS layout from the header alone: the LFM2.5 store shape with Q4_1 blocks kept whole. One expert record = the
61
+ // unused guS and dS (256 bytes each, first, so every record ends in written bytes and the file reaches its full
62
+ // length), gate rows then up rows (guQ), down rows (dQ); the store holds the 47 MoE layers, indexed from 0. Dense
63
+ // tensors are raw copies (Q4_1 blocks or F32).
64
+ export function planLayoutFlint(gguf) {
65
+ const cfg = configFromGgufFlint(gguf.kv), H = cfg.hidden, F = cfg.expertFf;
66
+ const names = new Set(gguf.tensors.map((t) => t.name));
67
+ let lead = 0;
68
+ while (names.has(`blk.${lead}.ffn_gate.weight`)) lead++;
69
+ if (lead !== cfg.lead || !names.has(`blk.${lead}.ffn_gate_inp.weight`)) throw new Error(`flint: ${lead} dense leading layers, expected ${cfg.lead}`);
70
+ const blk = (rows, cols) => rows * (cols / 32) * Q41_BLOCK, gu = blk(2 * F, H), dn = blk(H, F);
71
+ const parts = {
72
+ guS: { off: 0, bytes: 256 }, dS: { off: 256, bytes: 256 },
73
+ guQ: { off: 512, bytes: gu }, dQ: { off: 512 + gu, bytes: dn },
74
+ };
75
+ const record = parts.dQ.off + parts.dQ.bytes;
76
+ const dense = {};
77
+ let off = 0;
78
+ const take = (n) => { const o = off; off += Math.ceil(n / 256) * 256; return o; };
79
+ for (const t of gguf.tensors) {
80
+ if (/_exps\.weight$/.test(t.name)) continue;
81
+ const n = t.dims.reduce((x, y) => x * y, 1);
82
+ if (t.type === GGML_Q4_1) dense[t.name] = { type: 'q41', dims: t.dims, raw: { off: take(n / 32 * Q41_BLOCK), bytes: n / 32 * Q41_BLOCK } };
83
+ else if (t.type === GGML.F32) dense[t.name] = { type: 'f32', dims: t.dims, raw: { off: take(n * 4), bytes: n * 4 } };
84
+ else throw new Error(`dense tensor ${t.name}: unsupported type ${t.type} (this engine reads Recursive's Q4_1 file)`);
85
+ }
86
+ return {
87
+ config: cfg,
88
+ experts: { file: 'experts.bin', record, parts, layers: cfg.layers, perLayer: cfg.experts, bytes: record * cfg.layers * cfg.experts },
89
+ dense: { file: 'dense.bin', bytes: off, tensors: dense },
90
+ };
91
+ }
92
+
93
+ // Every unit is a raw copy. Expert slices go to their record; dense tensors in bands of at most 8 MB (the F32 token
94
+ // table is 1.23 GB, larger than one range request).
95
+ export function planUnitsFlint(gguf, layout) {
96
+ const units = [];
97
+ const { record, parts, perLayer } = layout.experts, lead = layout.config.lead, F = layout.config.expertFf, H = layout.config.hidden;
98
+ const gateBytes = F * (H / 32) * Q41_BLOCK, band = 8 << 20;
99
+ for (const t of gguf.tensors) {
100
+ const abs = gguf.dataStart + t.offset;
101
+ const m = /^blk\.(\d+)\.ffn_(gate|up|down)_exps\.weight$/.exec(t.name);
102
+ if (m) {
103
+ if (t.type !== GGML_Q4_1) throw new Error(`${t.name}: experts must be Q4_1`);
104
+ const layer = Number(m[1]) - lead, which = m[2];
105
+ const slice = t.dims.reduce((x, y) => x * y, 1) / 32 * Q41_BLOCK / perLayer;
106
+ const dst = which === 'down' ? parts.dQ.off : parts.guQ.off + (which === 'up' ? gateBytes : 0);
107
+ for (let e = 0; e < perLayer; e++) units.push({ src: abs + e * slice, len: slice, file: 'experts', raw: (layer * perLayer + e) * record + dst });
108
+ continue;
109
+ }
110
+ const d = layout.dense.tensors[t.name];
111
+ for (let o = 0; o < d.raw.bytes; o += band) units.push({ src: abs + o, len: Math.min(band, d.raw.bytes - o), file: 'dense', raw: d.raw.off + o });
112
+ }
113
+ units.sort((a, b) => a.src - b.src);
114
+ return units;
115
+ }
116
+
117
+ export const FLINT_INGEST_PLAN = { layout: planLayoutFlint, units: planUnitsFlint };
118
+
119
+ // Recursive's own Q4_1 GGUF (rev 9c47151, 2026-10-21); sha256 = the LFS object id.
120
+ export const FLINT_40B_A2B = {
121
+ repo: 'RecursiveSI/flint-int4-gguf',
122
+ file: 'flint-int4-q4_1.gguf',
123
+ revision: '9c47151036bc2c34331142dda04488bceab2bc9f',
124
+ sha256: '43c07baccdba42eb7528a9630ee15699b1942c297291dc1b39a061f508ee1a4b',
125
+ size: 25992966976,
126
+ };
127
+
128
+ // ── Tokenizer: byte-level BPE; tokenizer.json's pre-tokenizer is a Sequence of three Isolated splits (llama.cpp
129
+ // LLAMA_VOCAB_PRE_TYPE_FLINT has the same three regexes; `(?i:'s|…)` spelled out case by case): digit runs of up to
130
+ // 3, CJK / kana runs, then the word split. Same mechanics as Hy-Memory's (HyV3Tokenizer), other word split.
131
+ const CONTR = "(?:'[sS]|'[tT]|'[rR][eE]|'[vV][eE]|'[mM]|'[lL][lL]|'[dD])?";
132
+ const FLINT_SPLITS = [
133
+ /\p{N}{1,3}/gu,
134
+ /[一-龥぀-ゟ゠-ヿ]+/gu,
135
+ new RegExp(`[^\\r\\n\\p{L}\\p{N}]?[\\p{Lu}\\p{Lt}\\p{Lm}\\p{Lo}\\p{M}]*[\\p{Ll}\\p{Lm}\\p{Lo}\\p{M}]+${CONTR}|[^\\r\\n\\p{L}\\p{N}]?[\\p{Lu}\\p{Lt}\\p{Lm}\\p{Lo}\\p{M}]+[\\p{Ll}\\p{Lm}\\p{Lo}\\p{M}]*${CONTR}| ?[\\p{P}\\p{S}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+`, 'gu'),
136
+ ];
137
+ export class FlintTokenizer extends HyV3Tokenizer {
138
+ constructor(kv) {
139
+ super(kv);
140
+ this.preRe = { [Symbol.matchAll]: (s) => FLINT_SPLITS.reduce(isolate, [s]).map((p) => [p])[Symbol.iterator]() };
141
+ }
142
+ }
143
+
144
+ // The chat template (chat_template.jinja), no native tools: <|system|>…, <|user|>…, <|assistant|><think>reasoning
145
+ // </think>…, <|observation|><tool_response>…</tool_response>, with no separators; the generation prompt is
146
+ // <|assistant|>. User, system and tool text loses NUL, <think> and </think>, and its role markers are escaped as the
147
+ // template does. Earlier replies keep their reasoning, as the model card asks (LocalMind's <|think|> … <|/think|>
148
+ // markers or the model's own </think>).
149
+ const FLINT_MARKERS = ['<|begin_of_text|>', '<|end_of_text|>', '<|system|>', '<|developer|>', '<|user|>', '<|assistant|>', '<|observation|>'];
150
+ function flintText(t) {
151
+ let s = String(t ?? '').replace(/\x00/g, '').replace(/<think>/g, '').replace(/<\/think>/g, '');
152
+ for (const m of FLINT_MARKERS) s = s.split(m).join('&lt;' + m.slice(1, -1) + '&gt;');
153
+ return s;
154
+ }
155
+ export function flintChatPrompt(messages) {
156
+ let s = '';
157
+ for (const m of messages) {
158
+ if (m.role === 'system') s += '<|system|>' + flintText(m.content);
159
+ else if (m.role === 'developer') s += '<|developer|>' + flintText(m.content);
160
+ else if (m.role === 'user') s += '<|user|>' + flintText(m.content);
161
+ else if (m.role === 'tool') s += '<|observation|><tool_response>' + flintText(m.content) + '</tool_response>';
162
+ else if (m.role === 'assistant') {
163
+ let c = String(m.content ?? ''), r = String(m.reasoning_content ?? '');
164
+ c = c.replace(/<\|think\|>([\s\S]*?)<\|\/think\|>/g, (_, x) => { r += x; return ''; });
165
+ if (c.includes('</think>')) { const i = c.lastIndexOf('</think>'); r += c.slice(0, i).replace(/<think>/g, ''); c = c.slice(i + 8); }
166
+ r = r.trim(); c = c.trim();
167
+ s += '<|assistant|>' + (r ? `<think>${r}</think>` : '') + c;
168
+ }
169
+ }
170
+ const last = messages[messages.length - 1];
171
+ return last && last.role === 'assistant' ? s : s + '<|assistant|>';
172
+ }
173
+
174
+ // ── WGSL ────────────────────────────────────────────────────────────────────
175
+ // Q4_1 straight from the GGUF blocks: block (row r, block b) is words (r·nb + b)·5 …+4 of `qw`; word 0 = f16 d | f16 m
176
+ // << 16, words 1–4 the 16 nibble bytes in ggml's order (low nibbles = values 0..15, high = 16..31). Value = d·q + m,
177
+ // so a block's dot is d·Σ q·x + m·Σ x. `aux` (binding 2) keeps the Q4_0 kernels' bindings; it is read once and unused.
178
+ const Q41DOT = `
179
+ fn q41dot(wd: u32, dm: vec2<f32>, xb: u32) -> f32 {
180
+ let lo = f32(wd & 15u) * xin[xb] + f32((wd >> 8u) & 15u) * xin[xb + 1u]
181
+ + f32((wd >> 16u) & 15u) * xin[xb + 2u] + f32((wd >> 24u) & 15u) * xin[xb + 3u];
182
+ let hi = f32((wd >> 4u) & 15u) * xin[xb + 16u] + f32((wd >> 12u) & 15u) * xin[xb + 17u]
183
+ + f32((wd >> 20u) & 15u) * xin[xb + 18u] + f32((wd >> 28u) & 15u) * xin[xb + 19u];
184
+ let sx = xin[xb] + xin[xb + 1u] + xin[xb + 2u] + xin[xb + 3u] + xin[xb + 16u] + xin[xb + 17u] + xin[xb + 18u] + xin[xb + 19u];
185
+ return dm.x * (lo + hi) + dm.y * sx;
186
+ }`;
187
+ const Q41_BINDINGS = `@group(0) @binding(0) var<storage, read> xin: array<f32>;
188
+ @group(0) @binding(1) var<storage, read> qw: array<u32>;
189
+ @group(0) @binding(2) var<storage, read> aux: array<u32>;
190
+ @group(0) @binding(3) var<storage, read_write> y: array<f32>;`;
191
+ // Four rows per workgroup; rows past M repeat the last one and are not written.
192
+ const Q41_ROWS = (rowBase, xoff) => `
193
+ var a0 = 0.0; var a1 = 0.0; var a2 = 0.0; var a3 = 0.0;
194
+ let r0 = ${rowBase} + min(m0, M - 1u); let r1 = ${rowBase} + min(m0 + 1u, M - 1u); let r2 = ${rowBase} + min(m0 + 2u, M - 1u); let r3 = ${rowBase} + min(m0 + 3u, M - 1u);
195
+ for (var w = t; w < nw; w += 256u) {
196
+ let b = w >> 2u; let i = 1u + (w & 3u); let xb = ${xoff} + b * 32u + (w & 3u) * 4u;
197
+ let k0 = (r0 * nb + b) * 5u; let k1 = (r1 * nb + b) * 5u; let k2 = (r2 * nb + b) * 5u; let k3 = (r3 * nb + b) * 5u;
198
+ a0 += q41dot(qw[k0 + i], unpack2x16float(qw[k0]), xb);
199
+ a1 += q41dot(qw[k1 + i], unpack2x16float(qw[k1]), xb);
200
+ a2 += q41dot(qw[k2 + i], unpack2x16float(qw[k2]), xb);
201
+ a3 += q41dot(qw[k3 + i], unpack2x16float(qw[k3]), xb);
202
+ }
203
+ part[t] = a0; part[256u + t] = a1; part[512u + t] = a2; part[768u + t] = a3;
204
+ workgroupBarrier();
205
+ for (var s = 128u; s > 0u; s >>= 1u) {
206
+ if (t < s) { part[t] += part[t + s]; part[256u + t] += part[256u + t + s]; part[512u + t] += part[512u + t + s]; part[768u + t] += part[768u + t + s]; }
207
+ workgroupBarrier();
208
+ }`;
209
+ const SOFTMAX_SINK = (rowExpr, headExpr, nExpr) => `
210
+ let n = ${nExpr}; let base = ${rowExpr} * n; let t = l.x; let sink = sinks[${headExpr}];
211
+ var mx = -3.4e38;
212
+ for (var i = t; i < n; i += 256u) { mx = max(mx, sc[base + i]); }
213
+ red[t] = mx; workgroupBarrier();
214
+ for (var s = 128u; s > 0u; s >>= 1u) { if (t < s) { red[t] = max(red[t], red[t + s]); } workgroupBarrier(); }
215
+ let m = max(red[0], sink); workgroupBarrier();
216
+ var sum = 0.0;
217
+ for (var i = t; i < n; i += 256u) { let e = exp(sc[base + i] - m); sc[base + i] = e; sum += e; }
218
+ red[t] = sum; workgroupBarrier();
219
+ for (var s = 128u; s > 0u; s >>= 1u) { if (t < s) { red[t] += red[t + s]; } workgroupBarrier(); }
220
+ let inv = 1.0 / (red[0] + exp(sink - m));
221
+ for (var i = t; i < n; i += 256u) { sc[base + i] = sc[base + i] * inv; }
222
+ }`;
223
+ const WIN = 'fn winStart(n: u32, win: u32) -> u32 { return select(0u, n - win, win > 0u && n > win); }';
224
+ const QB = 'struct Q { T: u32, pos0: u32, S: u32, pad: u32 }';
225
+
226
+ const FL = {
227
+ matmulQ41: `
228
+ struct P { M: u32, N: u32 }
229
+ ${Q41_BINDINGS}
230
+ @group(0) @binding(4) var<uniform> p: P;
231
+ ${Q41DOT}
232
+ var<workgroup> part: array<f32, 1024>;
233
+ @compute @workgroup_size(256) fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(num_workgroups) ng: vec3<u32>, @builtin(local_invocation_id) l: vec3<u32>) {
234
+ _ = aux[0];
235
+ let m0 = (wg.y * ng.x + wg.x) * 4u; let t = l.x; let M = p.M; let nw = p.N / 8u; let nb = p.N / 32u;
236
+ if (m0 >= M) { return; }${Q41_ROWS('0u', '0u')}
237
+ if (t < 4u && m0 + t < M) { y[m0 + t] = part[t * 256u]; }
238
+ }`,
239
+ // Routed experts, all k at once (workgroup z = top-k position); slots[z] picks the expert's rows in the pool.
240
+ expertQ41: `
241
+ struct P { M: u32, N: u32, rowsPerExpert: u32, inputPerSlot: u32 }
242
+ ${Q41_BINDINGS}
243
+ @group(0) @binding(4) var<uniform> p: P;
244
+ @group(0) @binding(5) var<storage, read> slots: array<u32>;
245
+ ${Q41DOT}
246
+ var<workgroup> part: array<f32, 1024>;
247
+ @compute @workgroup_size(256) fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(num_workgroups) ng: vec3<u32>, @builtin(local_invocation_id) l: vec3<u32>) {
248
+ _ = aux[0];
249
+ let z = wg.z; let m0 = (wg.y * ng.x + wg.x) * 4u; let t = l.x; let M = p.M; let nw = p.N / 8u; let nb = p.N / 32u;
250
+ if (m0 >= M) { return; }
251
+ let rb = slots[z] * p.rowsPerExpert; let xo = select(0u, z * p.N, p.inputPerSlot == 1u);${Q41_ROWS('rb', 'xo')}
252
+ if (t < 4u && m0 + t < M) { y[z * M + m0 + t] = part[t * 256u]; }
253
+ }`,
254
+ // The F32 token table: x = row tok.token.
255
+ embedF32: `
256
+ ${TOK}
257
+ struct P { n: u32 }
258
+ @group(0) @binding(0) var<storage, read> emb: array<f32>;
259
+ @group(0) @binding(1) var<storage, read_write> y: array<f32>;
260
+ @group(0) @binding(2) var<uniform> tok: Tok;
261
+ @group(0) @binding(3) var<uniform> p: P;
262
+ @compute @workgroup_size(256) fn main(@builtin(global_invocation_id) g: vec3<u32>) {
263
+ let i = g.x; if (i < p.n) { y[i] = emb[tok.token * p.n + i]; }
264
+ }`,
265
+ // Windowed scores (positions [start, seqLen)) times the 1/√hd scale.
266
+ attnScoreSW: `enable f16;
267
+ ${TOK}
268
+ struct P { heads: u32, kvHeads: u32, hd: u32, win: u32, scale: f32, p0: u32, p1: u32, p2: u32 }
269
+ @group(0) @binding(0) var<storage, read> q: array<f32>;
270
+ @group(0) @binding(1) var<storage, read> kc: array<f16>;
271
+ @group(0) @binding(2) var<storage, read_write> sc: array<f32>;
272
+ @group(0) @binding(3) var<uniform> tok: Tok;
273
+ @group(0) @binding(4) var<uniform> p: P;
274
+ ${WIN}
275
+ @compute @workgroup_size(256) fn main(@builtin(global_invocation_id) g: vec3<u32>) {
276
+ let st = winStart(tok.seqLen, p.win); let n = tok.seqLen - st; let idx = g.x;
277
+ if (idx >= p.heads * n) { return; }
278
+ let h = idx / n; let j = idx % n; let kvh = h / (p.heads / p.kvHeads);
279
+ let qo = h * p.hd; let ko = (st + j) * p.kvHeads * p.hd + kvh * p.hd;
280
+ var acc = 0.0;
281
+ for (var d = 0u; d < p.hd; d++) { acc += q[qo + d] * f32(kc[ko + d]); }
282
+ sc[h * n + j] = acc * p.scale;
283
+ }`,
284
+ // Softmax over the window with the head's sink logit in the denominator (ggml_soft_max_ext with sinks).
285
+ softmaxSinkW: `
286
+ ${TOK}
287
+ struct P { heads: u32, kvHeads: u32, hd: u32, win: u32 }
288
+ @group(0) @binding(0) var<storage, read_write> sc: array<f32>;
289
+ @group(0) @binding(1) var<storage, read> sinks: array<f32>;
290
+ @group(0) @binding(2) var<uniform> tok: Tok;
291
+ @group(0) @binding(3) var<uniform> p: P;
292
+ ${WIN}
293
+ var<workgroup> red: array<f32, 256>;
294
+ @compute @workgroup_size(256) fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) l: vec3<u32>) {${SOFTMAX_SINK('wg.x', 'wg.x', 'tok.seqLen - winStart(tok.seqLen, p.win)')}`,
295
+ };
296
+
297
+ const FLB = {
298
+ // Tiled Q4_1 GEMM (matmulQ4T's tiling): y[t·M + m] = Σ_k x[t·N + k]·W[m][k], 16 rows × 16 tokens per workgroup.
299
+ matmulQ41T: `
300
+ struct P { M: u32, N: u32 }
301
+ ${QB}
302
+ @group(0) @binding(0) var<storage, read> x: array<f32>;
303
+ @group(0) @binding(1) var<storage, read> qw: array<u32>;
304
+ @group(0) @binding(2) var<storage, read> aux: array<u32>;
305
+ @group(0) @binding(3) var<storage, read_write> y: array<f32>;
306
+ @group(0) @binding(4) var<uniform> p: P;
307
+ @group(0) @binding(5) var<uniform> qd: Q;
308
+ var<workgroup> ws: array<f32, 512>;
309
+ var<workgroup> xs: array<f32, 512>;
310
+ @compute @workgroup_size(256) fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) l: vec3<u32>) {
311
+ _ = aux[0];
312
+ let m0 = wg.x * 16u; let t0 = wg.y * 16u; let r = l.x / 16u; let c = l.x % 16u;
313
+ let nb = p.N / 32u; var acc = 0.0;
314
+ for (var b = 0u; b < nb; b++) {
315
+ for (var i = l.x; i < 512u; i += 256u) {
316
+ let rr = i / 32u; let kk = i % 32u;
317
+ let m = m0 + rr; var wv = 0.0;
318
+ if (m < p.M) {
319
+ let k = (m * nb + b) * 5u; let dm = unpack2x16float(qw[k]);
320
+ let wd = qw[k + 1u + ((kk & 15u) >> 2u)];
321
+ wv = f32((wd >> ((kk & 3u) * 8u + (kk >> 4u) * 4u)) & 15u) * dm.x + dm.y;
322
+ }
323
+ ws[i] = wv;
324
+ let t = t0 + rr; var xv = 0.0;
325
+ if (t < qd.T) { xv = x[t * p.N + b * 32u + kk]; }
326
+ xs[i] = xv;
327
+ }
328
+ workgroupBarrier();
329
+ for (var kk = 0u; kk < 32u; kk++) { acc += ws[r * 32u + kk] * xs[c * 32u + kk]; }
330
+ workgroupBarrier();
331
+ }
332
+ let m = m0 + r; let t = t0 + c;
333
+ if (m < p.M && t < qd.T) { y[t * p.M + m] = acc; }
334
+ }`,
335
+ // Routed experts for every (token, pick) pair: z = t·K + j. Input: the token's row (gate/up, inputMode 0) or the
336
+ // pair's own row (down, inputMode 1).
337
+ expertQ41B: `
338
+ struct P { M: u32, N: u32, rowsPerExpert: u32, inputMode: u32, K: u32 }
339
+ ${Q41_BINDINGS}
340
+ @group(0) @binding(4) var<uniform> p: P;
341
+ @group(0) @binding(5) var<storage, read> slots: array<u32>;
342
+ ${Q41DOT}
343
+ var<workgroup> part: array<f32, 1024>;
344
+ @compute @workgroup_size(256) fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(num_workgroups) ng: vec3<u32>, @builtin(local_invocation_id) l: vec3<u32>) {
345
+ _ = aux[0];
346
+ let z = wg.z; let m0 = (wg.y * ng.x + wg.x) * 4u; let t = l.x; let M = p.M; let nw = p.N / 8u; let nb = p.N / 32u;
347
+ if (m0 >= M) { return; }
348
+ let rb = slots[z] * p.rowsPerExpert; let xo = select((z / p.K) * p.N, z * p.N, p.inputMode == 1u);${Q41_ROWS('rb', 'xo')}
349
+ if (t < 4u && m0 + t < M) { y[z * M + m0 + t] = part[t * 256u]; }
350
+ }`,
351
+ embedF32B: `
352
+ struct P { n: u32 }
353
+ ${QB}
354
+ @group(0) @binding(0) var<storage, read> emb: array<f32>;
355
+ @group(0) @binding(1) var<storage, read> ids: array<u32>;
356
+ @group(0) @binding(2) var<storage, read_write> y: array<f32>;
357
+ @group(0) @binding(3) var<uniform> p: P;
358
+ @group(0) @binding(4) var<uniform> qd: Q;
359
+ @compute @workgroup_size(256) fn main(@builtin(global_invocation_id) g: vec3<u32>) {
360
+ let i = g.x; let t = g.y; if (i >= p.n || t >= qd.T) { return; }
361
+ y[t * p.n + i] = emb[ids[t] * p.n + i];
362
+ }`,
363
+ // Causal (and, on sliding layers, windowed) scores for T queries over S = pos0 + T keys, times the scale.
364
+ attnScoreSWB: `enable f16;
365
+ struct P { heads: u32, kvHeads: u32, hd: u32, win: u32, scale: f32, p0: u32, p1: u32, p2: u32 }
366
+ ${QB}
367
+ @group(0) @binding(0) var<storage, read> q: array<f32>;
368
+ @group(0) @binding(1) var<storage, read> kc: array<f16>;
369
+ @group(0) @binding(2) var<storage, read_write> sc: array<f32>;
370
+ @group(0) @binding(3) var<uniform> p: P;
371
+ @group(0) @binding(4) var<uniform> qd: Q;
372
+ @compute @workgroup_size(256) fn main(@builtin(global_invocation_id) g: vec3<u32>, @builtin(num_workgroups) ng: vec3<u32>) {
373
+ let idx = g.y * ng.x * 256u + g.x; let S = qd.S; let T = qd.T;
374
+ if (idx >= p.heads * T * S) { return; }
375
+ let h = idx / (T * S); let rem = idx % (T * S); let t = rem / S; let pos = rem % S; let qp = qd.pos0 + t;
376
+ if (pos > qp || (p.win > 0u && qp - pos >= p.win)) { sc[idx] = -3.4e38; return; }
377
+ let kvh = h / (p.heads / p.kvHeads);
378
+ let qo = t * p.heads * p.hd + h * p.hd; let ko = pos * p.kvHeads * p.hd + kvh * p.hd;
379
+ var acc = 0.0;
380
+ for (var d = 0u; d < p.hd; d++) { acc += q[qo + d] * f32(kc[ko + d]); }
381
+ sc[idx] = acc * p.scale;
382
+ }`,
383
+ // Row wg.x = h·T + t over S keys (masked ones are -3.4e38 and come out 0), with the head's sink.
384
+ softmaxSinkB: `
385
+ ${QB}
386
+ @group(0) @binding(0) var<storage, read_write> sc: array<f32>;
387
+ @group(0) @binding(1) var<storage, read> sinks: array<f32>;
388
+ @group(0) @binding(2) var<uniform> qd: Q;
389
+ var<workgroup> red: array<f32, 256>;
390
+ @compute @workgroup_size(256) fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) l: vec3<u32>) {${SOFTMAX_SINK('wg.x', 'wg.x / qd.T', 'qd.S')}`,
391
+ };
392
+
393
+ // ── Engine ──────────────────────────────────────────────────────────────────
394
+ export class FlintMoeSsd extends Lfm2MoeSsd {
395
+ static configFrom(kv) { return configFromGgufFlint(kv); }
396
+ static get ingestPlan() { return FLINT_INGEST_PLAN; }
397
+ static tokenizerFrom(kv) { return new FlintTokenizer(kv); }
398
+ static load(modelId, opts = {}) { return Qwen3MoeSsd.load.call(this, modelId, { ...opts, source: opts.source || FLINT_40B_A2B }); }
399
+
400
+ chatPrompt(messages) { return flintChatPrompt(messages); }
401
+ get stopTokenIds() {
402
+ const t = this.tokenizer.ids;
403
+ return [this.cfg.eos, t.get('<|user|>'), t.get('<|observation|>'), t.get('<|developer|>')].filter((x) => x !== undefined);
404
+ }
405
+ get thinkOpenTokenId() { return this.tokenizer.ids.get('<think>') ?? null; }
406
+ get thinkCloseTokenId() { return this.tokenizer.ids.get('</think>') ?? null; }
407
+ get thinkInPrompt() { return false; }
408
+ get alwaysThinks() { return false; }
409
+ get thinkPreamble() { return ''; }
410
+
411
+ get kq() { return { mm: 'matmulQ41', mmT: 'matmulQ41T', ex: 'expertQ41', exB: 'expertQ41B' }; }
412
+ get attnK() { return { score: 'attnScoreSW', soft: 'softmaxSinkW', out: 'attnOutW', scoreB: 'attnScoreSWB', softB: 'softmaxSinkB', outB: 'attnOutB' }; }
413
+ get embedKernel() { return 'embedF32'; }
414
+ get embedKernelB() { return 'embedF32B'; }
415
+ get lmKernel() { return 'matmulQ41'; }
416
+ get kernels() {
417
+ return (this._flKernels ||= { ...super.kernels, ...FL, ...(this.batchPrefill ? FLB : {}) });
418
+ }
419
+
420
+ // Q4_1 and F32 tensors arrive raw. A Q4_1 matrix binds its blocks as `q` and a 16-byte unused buffer as `s`; each MoE
421
+ // layer gets a zero router bias (this file has none; topkSig adds one before picking).
422
+ async uploadDense(onProgress) {
423
+ const dt = this.manifest.dense.tensors;
424
+ for (const [name, d] of Object.entries(dt)) if (d.type !== 'f32' && d.type !== 'q41') throw new Error(`${name}: ${d.type} in a Q4_1 flint file`);
425
+ await Qwen3MoeSsd.prototype.uploadDense.call(this, onProgress);
426
+ this.ropeFreqs = null;
427
+ this.aux = this.buffer(16, STORAGE, 'dense');
428
+ for (const [name, d] of Object.entries(dt)) if (d.type === 'q41') Object.assign(this.w[name], { q: this.w[name].raw, s: this.aux });
429
+ const c = this.cfg, zero = this.buffer(c.experts * 4, STORAGE, 'dense');
430
+ for (let L = c.lead; L < c.modelLayers; L++) this.w[`blk.${L}.exp_probs_b`] = { raw: zero };
431
+ }
432
+
433
+ // The base binds, then Flint's own: the token table and head, an identity RoPE table for the NoPE layers, and per
434
+ // layer the windowed, scaled scores and the softmax with sinks.
435
+ initBuffers() {
436
+ const c = this.cfg, H = c.hidden, hd = c.attnHeadDim, W = this.w;
437
+ this.ropeId = this.buffer(hd * 4, STORAGE | 0x8 /* COPY_DST */);
438
+ this.device.queue.writeBuffer(this.ropeId, 0, identityRope(hd, 1));
439
+ super.initBuffers();
440
+ const a = this.a, qk = this.uniform([hd, { f: c.eps }]), scale = { f: Math.fround(1 / Math.sqrt(hd)) };
441
+ this.g.embed = this.bind('embedF32', [W['token_embd.weight'].raw, a.x, this.tok, this.uniform([H])]);
442
+ this.g.lm = this.bind('matmulQ41', [a.xn, W['output.weight'].q, W['output.weight'].s, a.logits, this.uniform([c.vocab, H])]);
443
+ for (let L = 0; L < c.modelLayers; L++) {
444
+ const ly = this.mlayers[L], p = (n) => W[`blk.${L}.${n}`];
445
+ if (!c.rope[L]) {
446
+ ly.ropeQ = this.bind('qkNormRope', [a.q, p('attn_q_norm.weight').raw, this.ropeId, qk]);
447
+ ly.ropeK = this.bind('qkNormRope', [a.k, p('attn_k_norm.weight').raw, this.ropeId, qk]);
448
+ }
449
+ const win = this.uniform([c.heads, c.kvHeadsAttn, hd, c.win[L]]);
450
+ ly.score = this.bind('attnScoreSW', [a.q, this.kc[L], a.sc, this.tok, this.uniform([c.heads, c.kvHeadsAttn, hd, c.win[L], scale, 0, 0, 0])]);
451
+ ly.soft = this.bind('softmaxSinkW', [a.sc, p('attn_sinks.weight').raw, this.tok, win]);
452
+ ly.attOut = this.bind('attnOutW', [a.sc, this.vc[L], a.att, this.tok, win]);
453
+ }
454
+ }
455
+ initBatch() {
456
+ super.initBatch();
457
+ const c = this.cfg, B = this.chunkTokens, H = c.hidden, hd = c.attnHeadDim, W = this.w, b = this.b, qb = this.qb;
458
+ const QN = c.heads * hd, KVN = c.kvHeadsAttn * hd, eps = { f: c.eps }, scale = { f: Math.fround(1 / Math.sqrt(hd)) };
459
+ this.ropeIdB = this.buffer(B * hd * 4, STORAGE | 0x8, 'batch');
460
+ this.device.queue.writeBuffer(this.ropeIdB, 0, identityRope(hd, B));
461
+ this.gB.embed = this.bind('embedF32B', [W['token_embd.weight'].raw, b.ids, b.x, this.uniform([H]), qb]);
462
+ const rq = this.uniform([hd, QN, eps]), rk = this.uniform([hd, KVN, eps]), attOut = this.uniform([c.heads, c.kvHeadsAttn, hd, 0]);
463
+ for (let L = 0; L < c.modelLayers; L++) {
464
+ const B_ = this.mlayers[L].B, p = (n) => W[`blk.${L}.${n}`];
465
+ if (!c.rope[L]) {
466
+ B_.ropeQ = this.bind('qkNormRopeB', [b.q, p('attn_q_norm.weight').raw, this.ropeIdB, rq]);
467
+ B_.ropeK = this.bind('qkNormRopeB', [b.k, p('attn_k_norm.weight').raw, this.ropeIdB, rk]);
468
+ }
469
+ B_.score = this.bind('attnScoreSWB', [b.q, this.kc[L], b.sc, this.uniform([c.heads, c.kvHeadsAttn, hd, c.win[L], scale, 0, 0, 0]), qb]);
470
+ B_.soft = this.bind('softmaxSinkB', [b.sc, p('attn_sinks.weight').raw, qb]);
471
+ B_.attOut = this.bind('attnOutB', [b.sc, this.vc[L], b.att, attOut, qb]);
472
+ }
473
+ }
474
+
475
+ // No recurrent state: thinking follows the model (LFM2.5 forces it on).
476
+ async *generateTurn(messages, opts = {}) { yield* Gemma4MoeSsd.prototype.generateTurn.call(this, messages, opts); }
477
+ }
478
+
479
+ // n rows of [cos | sin] = [1 … 1 | 0 … 0]: qkNormRope with this table only normalises.
480
+ function identityRope(hd, n) {
481
+ const r = new Float32Array(n * hd);
482
+ for (let t = 0; t < n; t++) r.fill(1, t * hd, t * hd + hd / 2);
483
+ return r;
484
+ }
485
+
486
+ // For kernel tests (scripts): the WGSL this module adds.
487
+ export { FL as FLINT_KERNELS, FLB as FLINT_BATCH_KERNELS };