diskformer 0.5.3 → 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,903 @@
1
+ /* ling_moe_ssd.js — inclusionAI Ling 3.0 Flash (GGUF arch `bailingmoe3`, 124B total, 5.1B active) with its routed
2
+ * experts on disk: rung 2h of the SSD-streaming ladder. Released 2026-08-02, MIT. The file is inclusionAI's own Q4_K_M
3
+ * GGUF (inclusionAI/Ling-3.0-flash-GGUF, two parts, 77.0 GB): Q4_K matrices with Q6_K on some, Q5_0 on attn_k_b, F32
4
+ * norms, routers and small vectors. The engine reads every block type straight from the GGUF bytes (iq_quants.js).
5
+ *
6
+ * The math follows llama.cpp src/models/bailingmoe3.cpp and its CPU ops (a37cc461). 42 layers (a 43rd, the MTP head,
7
+ * is not stored); layers 5, 11, …, 41 (head_count_kv = 1) are MLA attention, the other 35 are KDA linear attention:
8
+ * KDA (Kimi Delta Attention; 32 heads × 128): q, k, v projections, each through a 4-tap causal conv and SiLU; q, k
9
+ * L2-normalised per head (x / √(Σx² + eps)); per-channel decay g = -5·sigmoid(a_h·(x·f_a + dt_b)); β = sigmoid(x·w_β)
10
+ * per head; the delta rule on a 128×128 state per head, S = diag(e^g)·S, S += k ⊗ β(v − Sᵀk), o = Sᵀq / √128
11
+ * (ggml_gated_delta_net, KDA branch); then RMSNorm(o)·w per head times sigmoid(x·g_a), and the output projection.
12
+ * MLA (multi-head latent attention, 32 heads, absorbed): q = x·W_q (per head 128 no-RoPE + 64 RoPE dims);
13
+ * [c | k_pe] = x·W_kv_a (512 + 64); c = RMSNorm(c)·w; RoPE (GGML NORM: adjacent pairs, base 6e6) on q_pe and k_pe;
14
+ * the cache keeps [c | k_pe] per token (576 f32). Scores (W_k_b,h·q_nope,h)·c + q_pe·k_pe, over √192; softmax;
15
+ * o_h = W_v_b,h·Σ p·c; o_h scaled by sigmoid(x·w_gate)_h; output projection 4096 → 2560.
16
+ * FFN: layers 0–1 a dense SwiGLU (6144). Layers 2–41: 512 experts in 8 groups of 64; p = sigmoid(router);
17
+ * selection score p + bias; the 4 groups with the highest sum of their top-2 scores; top 8 experts of those by
18
+ * score; weights = their p over the sum (≥ 6.1e-5), × 2.5; SwiGLU experts (768) plus one shared expert (768).
19
+ * Some layers clamp the SwiGLU (llama.cpp's non-fused form): up to [-L, L], silu(gate) to ≤ L.
20
+ * Embedding: Q4_K rows; head: RMSNorm · output_norm, then output.weight (Q6_K, untied).
21
+ *
22
+ * Decode is one token at a time and prefill feeds tokens through the same step. The recurrent KDA state only moves
23
+ * forward, so a prompt that does not extend the cached tokens is fed again from position 0.
24
+ *
25
+ * Two-part GGUF: the engine reads both part headers first and gives the store one virtual file, part 1's bytes then
26
+ * part 2's (splitFetch answers range requests across the boundary); tensors of part 2 get offsets in that space.
27
+ */
28
+
29
+ import { Qwen3MoeSsd, BpeTokenizer, GGML, TOK, STORAGE, COPY_SRC, COPY_DST, MAP_READ, parseGguf, STORE_FORMAT, STORE_ROOT } from './qwen3_moe_ssd.js';
30
+ import { readStore } from '../src/ingest.js';
31
+ import { QTYPES, QTYPE_BY_ID, rowBytes, mvKernel, embedKernel, deqFn, gpuBlock, gridData } from './iq_quants.js';
32
+
33
+ // inclusionAI's own Q4_K_M GGUF (rev 29edce5, 2026-08-31); sha256 = the LFS object ids of the two parts.
34
+ export const LING_FLASH = {
35
+ repo: 'inclusionAI/Ling-3.0-flash-GGUF',
36
+ revision: '29edce5130491e638f3e5ebec91fa0a5ed4f780e',
37
+ file: 'Q4_K_M/Ling-3.0-flash-Q4_K_M-00001-of-00002.gguf',
38
+ parts: [
39
+ { file: 'Q4_K_M/Ling-3.0-flash-Q4_K_M-00001-of-00002.gguf', size: 49995534208, sha256: '24ff06f1ab74546a91f7c07bd725b3015896bbe9dd82ab0e5838be9ed06a6dcf' },
40
+ { file: 'Q4_K_M/Ling-3.0-flash-Q4_K_M-00002-of-00002.gguf', size: 27014610464, sha256: '1a6a8c91562b817fd8ec5b250453be696b0fc209ed8f2acdb6c13f4e17f7e188' },
41
+ ],
42
+ size: 49995534208 + 27014610464,
43
+ };
44
+
45
+ // ── Two-part GGUF as one file ──
46
+ // A fetch-shaped function over the parts laid end to end: a Range request is split at the part boundaries and the
47
+ // bodies are streamed back in order. content-range reports the total size.
48
+ export function splitFetch(urls, sizes, fetchFn = (u, i) => fetch(u, i)) {
49
+ const starts = sizes.map((_, i) => sizes.slice(0, i).reduce((a, b) => a + b, 0)), total = sizes.reduce((a, b) => a + b, 0);
50
+ return async (_url, init = {}) => {
51
+ const m = /^bytes=(\d+)-(\d+)$/.exec((init.headers && init.headers.Range) || '');
52
+ if (!m) throw new Error('splitFetch: only Range requests (bytes=a-b) are supported');
53
+ const a = Number(m[1]), b = Math.min(Number(m[2]), total - 1);
54
+ const pieces = [];
55
+ for (let i = 0; i < urls.length; i++) {
56
+ const lo = Math.max(a, starts[i]), hi = Math.min(b, starts[i] + sizes[i] - 1);
57
+ if (lo <= hi) pieces.push([i, lo - starts[i], hi - starts[i]]);
58
+ }
59
+ const responses = [];
60
+ for (const [i, lo, hi] of pieces) {
61
+ const r = await fetchFn(urls[i], { ...init, headers: { ...init.headers, Range: `bytes=${lo}-${hi}` } });
62
+ if (r.status !== 206) throw new Error(`part ${i + 1} range ${lo}-${hi}: HTTP ${r.status}`);
63
+ responses.push(r);
64
+ }
65
+ if (responses.length === 1) {
66
+ return new Response(responses[0].body, { status: 206, headers: { 'content-range': `bytes ${a}-${b}/${total}`, ...(responses[0].headers.get('etag') ? { etag: responses[0].headers.get('etag') } : {}) } });
67
+ }
68
+ let k = 0, reader = null;
69
+ const body = new ReadableStream({
70
+ async pull(ctl) {
71
+ for (;;) {
72
+ if (!reader) { if (k >= responses.length) { ctl.close(); return; } reader = responses[k++].body.getReader(); }
73
+ const { done, value } = await reader.read();
74
+ if (done) { reader = null; continue; }
75
+ ctl.enqueue(value); return;
76
+ }
77
+ },
78
+ cancel() { for (const r of responses) try { r.body.cancel(); } catch (_) {} },
79
+ });
80
+ return new Response(body, { status: 206, headers: { 'content-range': `bytes ${a}-${b}/${total}` } });
81
+ };
82
+ }
83
+
84
+ // Reads part 2's header (the parts after the first carry only tensor infos) → its tensors with offsets made relative
85
+ // to part 1's data start in the virtual file.
86
+ export async function readPartTensors(url, partStart, dataStart1, fetchFn) {
87
+ let n = 4 << 20;
88
+ for (;;) {
89
+ const r = await fetchFn(url, { headers: { Range: `bytes=0-${n - 1}` }, cache: 'no-store' });
90
+ if (!r.ok) throw new Error(`GGUF part header fetch: HTTP ${r.status}`);
91
+ const u8 = new Uint8Array(await r.arrayBuffer());
92
+ try {
93
+ const g = parseGguf(u8);
94
+ return g.tensors.map((t) => ({ ...t, offset: partStart + g.dataStart + t.offset - dataStart1 }));
95
+ } catch (e) { if (!e.needBytes) throw e; n = e.needBytes; }
96
+ }
97
+ }
98
+
99
+ // ── Config ──
100
+ export function configFromGgufLing(kv) {
101
+ const a = kv['general.architecture'];
102
+ if (a !== 'bailingmoe3') throw new Error(`expected a bailingmoe3 GGUF, got ${a}`);
103
+ const g = (k) => kv[`${a}.${k}`];
104
+ if (g('expert_gating_func') !== 2) throw new Error(`bailingmoe3 expert_gating_func ${g('expert_gating_func')}: this engine implements sigmoid (2)`);
105
+ if (!g('expert_weights_norm')) throw new Error('bailingmoe3 without expert_weights_norm: not implemented');
106
+ if (g('attention.q_lora_rank')) throw new Error('bailingmoe3 with a q LoRA (Ling 3.1): not implemented');
107
+ if (g('rope.dimension_sections')) throw new Error('bailingmoe3 with mrope sections (the VL file): not implemented');
108
+ const allLayers = g('block_count'), nextn = g('nextn_predict_layers') || 0, modelLayers = allLayers - nextn;
109
+ const lead = g('leading_dense_block_count'), layers = modelLayers - lead;
110
+ const kvH = g('attention.head_count_kv');
111
+ return {
112
+ arch: a, modelLayers, lead, layers,
113
+ mla: Array.from({ length: modelLayers }, (_, l) => (Array.isArray(kvH) ? kvH[l] : kvH) > 0),
114
+ hidden: g('embedding_length'), denseFf: g('feed_forward_length'), heads: g('attention.head_count'),
115
+ kdaHd: g('kda.head_dim'), conv: g('ssm.conv_kernel'), gateLow: g('kda.gate_lower_bound'),
116
+ kvRank: g('attention.kv_lora_rank'), qkHd: g('attention.key_length_mla'), vHd: g('attention.value_length_mla'),
117
+ nRot: g('rope.dimension_count'), ropeTheta: g('rope.freq_base'),
118
+ experts: g('expert_count'), topK: g('expert_used_count'), groups: g('expert_group_count'), groupsUsed: g('expert_group_used_count'),
119
+ expertFf: g('expert_feed_forward_length'), shexpFf: g('expert_shared_feed_forward_length'), expScale: g('expert_weights_scale') ?? 1,
120
+ clampExp: g('swiglu_clamp_exp') || [], clampShexp: g('swiglu_clamp_shexp') || [],
121
+ eps: g('attention.layer_norm_rms_epsilon'), contextLength: g('context_length'),
122
+ vocab: kv['tokenizer.ggml.tokens'].length, bos: kv['tokenizer.ggml.bos_token_id'], eos: kv['tokenizer.ggml.eos_token_id'],
123
+ // Base-class fields the pool and readback size from.
124
+ kvHeads: 1, headDim: g('attention.key_length_mla'),
125
+ };
126
+ }
127
+
128
+ // ── Store layout ──
129
+ // One expert record = 256-byte guS and dS (unused), down rows (dQ), then gate rows and up rows (Q4_K, guQ). Down is
130
+ // Q6_K on 17 layers and Q4_K on 23, so dQ is sized for Q6_K and a Q4_K layer leaves a gap after its down rows; guQ
131
+ // last means every record ends in written bytes, so the file reaches its full length. Dense tensors are raw copies;
132
+ // the MTP layer is skipped.
133
+ const KEEP_TYPES = new Set([GGML.F32, QTYPES.Q4_K.id, QTYPES.Q6_K.id, QTYPES.Q5_0.id]);
134
+ const typeName = (id) => (id === GGML.F32 ? 'f32' : QTYPE_BY_ID[id]);
135
+ const nBytes = (t) => { const n = t.dims.reduce((x, y) => x * y, 1); return t.type === GGML.F32 ? n * 4 : (n / QTYPES[QTYPE_BY_ID[t.type]].qk) * QTYPES[QTYPE_BY_ID[t.type]].block; };
136
+
137
+ export function planLayoutLing(gguf) {
138
+ const cfg = configFromGgufLing(gguf.kv), H = cfg.hidden, F = cfg.expertFf;
139
+ const gu = 2 * F * rowBytes('Q4_K', H), dn = Math.max(H * rowBytes('Q6_K', F), H * rowBytes('Q4_K', F));
140
+ const parts = { guS: { off: 0, bytes: 256 }, dS: { off: 256, bytes: 256 }, dQ: { off: 512, bytes: dn }, guQ: { off: 512 + dn, bytes: gu } };
141
+ const record = parts.guQ.off + parts.guQ.bytes;
142
+ const dense = {}, downType = [];
143
+ let off = 0;
144
+ const take = (n) => { const o = off; off += Math.ceil(n / 256) * 256; return o; };
145
+ for (const t of gguf.tensors) {
146
+ const layer = /^blk\.(\d+)\./.exec(t.name);
147
+ if (layer && Number(layer[1]) >= cfg.modelLayers) continue; // the MTP head
148
+ if (!KEEP_TYPES.has(t.type)) throw new Error(`${t.name}: unsupported type ${t.type} (this engine reads inclusionAI's Q4_K_M file)`);
149
+ const m = /^blk\.(\d+)\.ffn_(gate|up|down)_exps\.weight$/.exec(t.name);
150
+ if (m) {
151
+ const want = m[2] === 'down' ? null : QTYPES.Q4_K.id;
152
+ if (want !== null && t.type !== want) throw new Error(`${t.name}: experts gate/up must be Q4_K`);
153
+ if (m[2] === 'down') downType[Number(m[1]) - cfg.lead] = typeName(t.type);
154
+ continue;
155
+ }
156
+ const n = nBytes(t);
157
+ dense[t.name] = { type: typeName(t.type), dims: t.dims, raw: { off: take(n), bytes: n } };
158
+ }
159
+ for (let l = 0; l < cfg.layers; l++) if (!downType[l]) throw new Error(`bailingmoe3: no ffn_down_exps for layer ${l + cfg.lead}`);
160
+ return {
161
+ config: cfg, downType,
162
+ experts: { file: 'experts.bin', record, parts, layers: cfg.layers, perLayer: cfg.experts, bytes: record * cfg.layers * cfg.experts },
163
+ dense: { file: 'dense.bin', bytes: off, tensors: dense },
164
+ };
165
+ }
166
+
167
+ // Every unit is a raw copy: expert slices to their record, dense tensors in bands of at most 8 MB.
168
+ export function planUnitsLing(gguf, layout) {
169
+ const units = [];
170
+ const { record, parts, perLayer } = layout.experts, { lead, modelLayers, hidden: H, expertFf: F } = layout.config;
171
+ const gateBytes = F * rowBytes('Q4_K', H), band = 8 << 20;
172
+ for (const t of gguf.tensors) {
173
+ const abs = gguf.dataStart + t.offset;
174
+ const layer = /^blk\.(\d+)\./.exec(t.name);
175
+ if (layer && Number(layer[1]) >= modelLayers) continue;
176
+ const m = /^blk\.(\d+)\.ffn_(gate|up|down)_exps\.weight$/.exec(t.name);
177
+ if (m) {
178
+ const l = Number(m[1]) - lead, slice = nBytes(t) / perLayer;
179
+ const dst = m[2] === 'down' ? parts.dQ.off : parts.guQ.off + (m[2] === 'up' ? gateBytes : 0);
180
+ for (let e = 0; e < perLayer; e++) units.push({ src: abs + e * slice, len: slice, file: 'experts', raw: (l * perLayer + e) * record + dst });
181
+ continue;
182
+ }
183
+ const d = layout.dense.tensors[t.name];
184
+ 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 });
185
+ }
186
+ units.sort((a, b) => a.src - b.src);
187
+ return units;
188
+ }
189
+
190
+ // ── Tokenizer: byte-level BPE, llama.cpp LLAMA_VOCAB_PRE_TYPE_BAILINGMOE (tokenizer.ggml.pre = bailingmoe2): the Qwen2
191
+ // split with its newline piece written \s*[\r\n] (Qwen2: \s*[\r\n]+). Possessive quantifiers spelled greedy, as llama.cpp.
192
+ const BAILING_PRE = /(?:'[sSdDmMtT]|'[lL][lL]|'[vV][eE]|'[rR][eE])|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]|\s+(?!\S)|\s+/gu;
193
+ export class LingTokenizer extends BpeTokenizer {
194
+ constructor(kv) { super(kv); this.preRe = BAILING_PRE; }
195
+ }
196
+
197
+ // The GGUF's Bailing V3 template (no tools): '<role>SYSTEM</role>' + system text + 'detailed thinking on|off' +
198
+ // '<|role_end|>', then '<role>HUMAN</role>…<|role_end|>' and '<role>ASSISTANT</role>\n<think>…</think>…<|role_end|>',
199
+ // ending in '<role>ASSISTANT</role>\n<think>' (thinking on) or '…\n<think></think>' (off). Earlier replies keep their
200
+ // reasoning (preserved_thinking).
201
+ export function lingChatPrompt(messages, { enableThinking = true } = {}) {
202
+ const opt = enableThinking ? 'on' : 'off';
203
+ const first = messages[0] && messages[0].role === 'system' ? messages[0] : null;
204
+ const text = (c) => (typeof c === 'string' ? c : '');
205
+ let s = '<role>SYSTEM</role>';
206
+ if (first && /detailed thinking (on|off)/.test(text(first.content))) s += text(first.content) + '<|role_end|>';
207
+ else s += (first ? text(first.content) + '\n' : '') + `detailed thinking ${opt}<|role_end|>`;
208
+ for (let i = first ? 1 : 0; i < messages.length; i++) {
209
+ const m = messages[i], c = text(m.content);
210
+ if (m.role === 'user') s += '<role>HUMAN</role>' + c + '<|role_end|>';
211
+ else if (m.role === 'system') s += '<role>SYSTEM</role>' + c + '<|role_end|>';
212
+ else if (m.role === 'assistant') {
213
+ let reasoning = typeof m.reasoning_content === 'string' ? m.reasoning_content : '', content = c;
214
+ if (!reasoning && content.includes('</think>')) {
215
+ reasoning = content.split('</think>')[0].replace(/\n+$/, '').split('<think>').pop().replace(/^\n+/, '');
216
+ content = content.split('</think>').pop().replace(/^\n+/, '');
217
+ }
218
+ s += reasoning ? '<role>ASSISTANT</role>\n<think>' + reasoning.replace(/^\n+|\n+$/g, '') + '</think>' + content.replace(/^\n+/, '')
219
+ : '<role>ASSISTANT</role>\n<think></think>' + content;
220
+ s += '<|role_end|>';
221
+ } else if (m.role === 'tool') {
222
+ if (i === 0 || messages[i - 1].role !== 'tool') s += '<role>OBSERVATION</role>';
223
+ s += '\n<tool_response>\n' + c + '\n</tool_response>';
224
+ if (i === messages.length - 1 || messages[i + 1].role !== 'tool') s += '<|role_end|>';
225
+ }
226
+ }
227
+ return s + '<role>ASSISTANT</role>' + (enableThinking ? '\n<think>' : '\n<think></think>');
228
+ }
229
+
230
+ // ── Kernels ──
231
+ const SIGM = 'fn sigm(x: f32) -> f32 { return 1.0 / (1.0 + exp(-x)); }';
232
+ const LK = {
233
+ // Debug capture (opts.captureX): dst[off·n + i] = x[i].
234
+ copyAt: `
235
+ struct P { n: u32, off: u32 }
236
+ @group(0) @binding(0) var<storage, read> x: array<f32>;
237
+ @group(0) @binding(1) var<storage, read_write> dst: array<f32>;
238
+ @group(0) @binding(2) var<uniform> p: P;
239
+ @compute @workgroup_size(256) fn main(@builtin(global_invocation_id) g: vec3<u32>) { if (g.x < p.n) { dst[p.off * p.n + g.x] = x[g.x]; } }`,
240
+ // x += a (n values); also used for the shared expert's output.
241
+ addInPlace: `
242
+ struct P { n: u32 }
243
+ @group(0) @binding(0) var<storage, read_write> x: array<f32>;
244
+ @group(0) @binding(1) var<storage, read> a: array<f32>;
245
+ @group(0) @binding(2) var<uniform> p: P;
246
+ @compute @workgroup_size(256) fn main(@builtin(global_invocation_id) g: vec3<u32>) { if (g.x < p.n) { x[g.x] = x[g.x] + a[g.x]; } }`,
247
+ // act[z·F + j] = min(silu(g), L)·clamp(u, -L, L) with g = gu[z·2F + j], u = gu[z·2F + F + j]; L = 0 means no clamp.
248
+ swiglu: `
249
+ struct P { F: u32, k: u32, L: f32, pad: u32 }
250
+ @group(0) @binding(0) var<storage, read> gu: array<f32>;
251
+ @group(0) @binding(1) var<storage, read_write> act: array<f32>;
252
+ @group(0) @binding(2) var<uniform> p: P;
253
+ @compute @workgroup_size(256) fn main(@builtin(global_invocation_id) g: vec3<u32>) {
254
+ let i = g.x; if (i >= p.k * p.F) { return; }
255
+ let z = i / p.F; let j = i % p.F;
256
+ let g0 = gu[z * 2u * p.F + j]; var u = gu[z * 2u * p.F + p.F + j];
257
+ var sg = g0 / (1.0 + exp(-g0));
258
+ if (p.L > 0.0) { u = clamp(u, -p.L, p.L); sg = min(sg, p.L); }
259
+ act[i] = sg * u;
260
+ }`,
261
+ // x[h] += Σ_j w_j · out[j·H + h] + shared[h]; weights are the selection's second half.
262
+ moeAccum: `
263
+ struct P { H: u32, k: u32 }
264
+ @group(0) @binding(0) var<storage, read> out: array<f32>;
265
+ @group(0) @binding(1) var<storage, read> sel: array<u32>;
266
+ @group(0) @binding(2) var<storage, read> sh: array<f32>;
267
+ @group(0) @binding(3) var<storage, read_write> x: array<f32>;
268
+ @group(0) @binding(4) var<uniform> p: P;
269
+ @compute @workgroup_size(256) fn main(@builtin(global_invocation_id) g: vec3<u32>) {
270
+ let h = g.x; if (h >= p.H) { return; }
271
+ var acc = 0.0;
272
+ for (var j = 0u; j < p.k; j++) { acc += bitcast<f32>(sel[p.k + j]) * out[j * p.H + h]; }
273
+ x[h] = x[h] + (acc + sh[h]);
274
+ }`,
275
+ // Group-limited sigmoid routing (build_moe_ffn with exp_probs_b and n_expert_groups). 512 experts, 2 per thread.
276
+ // sel[0..k) = expert ids, sel[k..2k) = weights (f32 bits).
277
+ routeLing: `
278
+ struct P { n: u32, k: u32, groups: u32, used: u32, scale: f32, pad0: u32, pad1: u32, pad2: u32 }
279
+ @group(0) @binding(0) var<storage, read> lg: array<f32>;
280
+ @group(0) @binding(1) var<storage, read> bias: array<f32>;
281
+ @group(0) @binding(2) var<storage, read_write> sel: array<u32>;
282
+ @group(0) @binding(3) var<uniform> p: P;
283
+ ${SIGM}
284
+ var<workgroup> sc: array<f32, 512>;
285
+ var<workgroup> gs: array<f32, 16>;
286
+ var<workgroup> keep: array<u32, 16>;
287
+ var<workgroup> red: array<f32, 256>;
288
+ var<workgroup> bi: array<u32, 256>;
289
+ var<workgroup> picks: array<u32, 32>;
290
+ @compute @workgroup_size(256) fn main(@builtin(local_invocation_id) l: vec3<u32>) {
291
+ let t = l.x; let per = p.n / p.groups;
292
+ for (var e = t; e < p.n; e += 256u) { sc[e] = sigm(lg[e]) + bias[e]; }
293
+ workgroupBarrier();
294
+ if (t < p.groups) {
295
+ var a = -3.4e38; var b = -3.4e38;
296
+ for (var e = t * per; e < (t + 1u) * per; e++) { let v = sc[e]; if (v > a) { b = a; a = v; } else if (v > b) { b = v; } }
297
+ gs[t] = a + b; keep[t] = 0u;
298
+ }
299
+ workgroupBarrier();
300
+ if (t == 0u) {
301
+ for (var j = 0u; j < p.used; j++) {
302
+ var best = 0u; var bv = -3.4e38;
303
+ for (var q = 0u; q < p.groups; q++) { if (keep[q] == 0u && gs[q] > bv) { bv = gs[q]; best = q; } }
304
+ keep[best] = 1u;
305
+ }
306
+ }
307
+ workgroupBarrier();
308
+ var m0 = select(-3.4e38, sc[t], keep[t / per] == 1u);
309
+ var m1 = select(-3.4e38, sc[t + 256u], keep[(t + 256u) / per] == 1u);
310
+ for (var j = 0u; j < p.k; j++) {
311
+ if (m0 >= m1) { red[t] = m0; bi[t] = t; } else { red[t] = m1; bi[t] = t + 256u; }
312
+ workgroupBarrier();
313
+ for (var s = 128u; s > 0u; s >>= 1u) {
314
+ if (t < s) { let o = red[t + s]; if (o > red[t] || (o == red[t] && bi[t + s] < bi[t])) { red[t] = o; bi[t] = bi[t + s]; } }
315
+ workgroupBarrier();
316
+ }
317
+ if (t == 0u) { picks[j] = bi[0]; }
318
+ workgroupBarrier();
319
+ if (picks[j] == t) { m0 = -3.4e38; }
320
+ if (picks[j] == t + 256u) { m1 = -3.4e38; }
321
+ workgroupBarrier();
322
+ }
323
+ if (t == 0u) {
324
+ var wsum = 0.0;
325
+ for (var j = 0u; j < p.k; j++) { wsum += sigm(lg[picks[j]]); }
326
+ wsum = max(wsum, 6.103515625e-5);
327
+ for (var j = 0u; j < p.k; j++) { sel[j] = picks[j]; sel[p.k + j] = bitcast<u32>((sigm(lg[picks[j]]) / wsum) * p.scale); }
328
+ }
329
+ }`,
330
+ // KDA short conv, one token: for channel c, y = silu(Σ_{i<3} w[4c+i]·st[3c+i] + w[4c+3]·x[c]); st shifts by one.
331
+ convStep: `
332
+ struct P { n: u32 }
333
+ @group(0) @binding(0) var<storage, read> x: array<f32>;
334
+ @group(0) @binding(1) var<storage, read> w: array<f32>;
335
+ @group(0) @binding(2) var<storage, read_write> st: array<f32>;
336
+ @group(0) @binding(3) var<storage, read_write> y: array<f32>;
337
+ @group(0) @binding(4) var<uniform> p: P;
338
+ @compute @workgroup_size(256) fn main(@builtin(global_invocation_id) g: vec3<u32>) {
339
+ let c = g.x; if (c >= p.n) { return; }
340
+ let s0 = st[3u * c]; let s1 = st[3u * c + 1u]; let s2 = st[3u * c + 2u]; let xc = x[c];
341
+ let a = w[4u * c] * s0 + w[4u * c + 1u] * s1 + w[4u * c + 2u] * s2 + w[4u * c + 3u] * xc;
342
+ st[3u * c] = s1; st[3u * c + 1u] = s2; st[3u * c + 2u] = xc;
343
+ y[c] = a / (1.0 + exp(-a));
344
+ }`,
345
+ // KDA gates: g[c] = lo·sigmoid(a[c / hd]·(fa[c] + dtb[c])) (c < H·hd), beta[h] = sigmoid(bl[h]) (stored after g).
346
+ kdaGates: `
347
+ struct P { n: u32, hd: u32, heads: u32, lo: f32 }
348
+ @group(0) @binding(0) var<storage, read> fa: array<f32>;
349
+ @group(0) @binding(1) var<storage, read> dtb: array<f32>;
350
+ @group(0) @binding(2) var<storage, read> A: array<f32>;
351
+ @group(0) @binding(3) var<storage, read> bl: array<f32>;
352
+ @group(0) @binding(4) var<storage, read_write> gb: array<f32>;
353
+ @group(0) @binding(5) var<uniform> p: P;
354
+ ${SIGM}
355
+ @compute @workgroup_size(256) fn main(@builtin(global_invocation_id) g: vec3<u32>) {
356
+ let c = g.x;
357
+ if (c < p.n) { gb[c] = p.lo * sigm(A[c / p.hd] * (fa[c] + dtb[c])); }
358
+ if (c < p.heads) { gb[p.n + c] = sigm(bl[c]); }
359
+ }`,
360
+ // KDA delta rule, one token; one workgroup of 128 per head, lane j owns column j of S (st[h][j][i] = S[i][j]).
361
+ // q and k are L2-normalised here (x / √(Σx² + eps)); gb holds g (H·hd) then β (H).
362
+ kdaStep: `
363
+ struct P { hd: u32, n: u32, eps: f32, scale: f32 }
364
+ @group(0) @binding(0) var<storage, read> q: array<f32>;
365
+ @group(0) @binding(1) var<storage, read> k: array<f32>;
366
+ @group(0) @binding(2) var<storage, read> v: array<f32>;
367
+ @group(0) @binding(3) var<storage, read> gb: array<f32>;
368
+ @group(0) @binding(4) var<storage, read_write> st: array<f32>;
369
+ @group(0) @binding(5) var<storage, read_write> o: array<f32>;
370
+ @group(0) @binding(6) var<uniform> p: P;
371
+ var<workgroup> qs: array<f32, 128>;
372
+ var<workgroup> ks: array<f32, 128>;
373
+ var<workgroup> eg: array<f32, 128>;
374
+ var<workgroup> red: array<f32, 128>;
375
+ var<workgroup> red2: array<f32, 128>;
376
+ @compute @workgroup_size(128) fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) l: vec3<u32>) {
377
+ let h = wg.x; let j = l.x; let b = h * 128u;
378
+ let qv = q[b + j]; let kv = k[b + j];
379
+ red[j] = qv * qv; red2[j] = kv * kv; workgroupBarrier();
380
+ for (var s = 64u; s > 0u; s >>= 1u) { if (j < s) { red[j] += red[j + s]; red2[j] += red2[j + s]; } workgroupBarrier(); }
381
+ qs[j] = qv / sqrt(red[0] + p.eps); ks[j] = kv / sqrt(red2[0] + p.eps); eg[j] = exp(gb[b + j]);
382
+ workgroupBarrier();
383
+ let beta = gb[p.n + h];
384
+ let row = (h * 128u + j) * 128u;
385
+ var sk = 0.0;
386
+ for (var i = 0u; i < 128u; i++) { let m = st[row + i] * eg[i]; st[row + i] = m; sk += m * ks[i]; }
387
+ let d = (v[b + j] - sk) * beta;
388
+ var acc = 0.0;
389
+ for (var i = 0u; i < 128u; i++) { let m = st[row + i] + ks[i] * d; st[row + i] = m; acc += m * qs[i]; }
390
+ o[b + j] = acc * p.scale;
391
+ }`,
392
+ // y = RMSNorm(o_h)·w · sigmoid(gz) per head of hd (one workgroup of 128 per head).
393
+ headNormGate: `
394
+ struct P { hd: u32, eps: f32 }
395
+ @group(0) @binding(0) var<storage, read> o: array<f32>;
396
+ @group(0) @binding(1) var<storage, read> w: array<f32>;
397
+ @group(0) @binding(2) var<storage, read> gz: array<f32>;
398
+ @group(0) @binding(3) var<storage, read_write> y: array<f32>;
399
+ @group(0) @binding(4) var<uniform> p: P;
400
+ ${SIGM}
401
+ var<workgroup> red: array<f32, 128>;
402
+ @compute @workgroup_size(128) fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) l: vec3<u32>) {
403
+ let i = wg.x * p.hd + l.x; let v = o[i];
404
+ red[l.x] = v * v; workgroupBarrier();
405
+ for (var s = 64u; s > 0u; s >>= 1u) { if (l.x < s) { red[l.x] += red[l.x + s]; } workgroupBarrier(); }
406
+ y[i] = (v / sqrt(red[0] / f32(p.hd) + p.eps)) * w[l.x] * sigm(gz[i]);
407
+ }`,
408
+ // MLA: the cache row for this token: c = RMSNorm(kva[0..R))·w, k_pe = RoPE(kva[R..R+nRot)); one workgroup.
409
+ mlaKv: `
410
+ ${TOK}
411
+ struct P { R: u32, nRot: u32, eps: f32, pad: u32 }
412
+ @group(0) @binding(0) var<storage, read> kva: array<f32>;
413
+ @group(0) @binding(1) var<storage, read> w: array<f32>;
414
+ @group(0) @binding(2) var<storage, read> rope: array<f32>;
415
+ @group(0) @binding(3) var<storage, read_write> cache: array<f32>;
416
+ @group(0) @binding(4) var<uniform> tok: Tok;
417
+ @group(0) @binding(5) var<uniform> p: P;
418
+ var<workgroup> red: array<f32, 256>;
419
+ @compute @workgroup_size(256) fn main(@builtin(local_invocation_id) l: vec3<u32>) {
420
+ let t = l.x; var acc = 0.0;
421
+ for (var i = t; i < p.R; i += 256u) { let v = kva[i]; acc += v * v; }
422
+ red[t] = acc; workgroupBarrier();
423
+ for (var s = 128u; s > 0u; s >>= 1u) { if (t < s) { red[t] += red[t + s]; } workgroupBarrier(); }
424
+ let scale = 1.0 / sqrt(red[0] / f32(p.R) + p.eps);
425
+ let base = tok.pos * (p.R + p.nRot);
426
+ for (var i = t; i < p.R; i += 256u) { cache[base + i] = (kva[i] * scale) * w[i]; }
427
+ if (t < p.nRot / 2u) {
428
+ let a = kva[p.R + 2u * t]; let b = kva[p.R + 2u * t + 1u]; let c = rope[t]; let sn = rope[p.nRot / 2u + t];
429
+ cache[base + p.R + 2u * t] = a * c - b * sn;
430
+ cache[base + p.R + 2u * t + 1u] = a * sn + b * c;
431
+ }
432
+ }`,
433
+ // MLA query: qa[h][r] = Σ_d wkb[h][r][d]·q[h·qk + d] (d < nope), and the RoPE'd q_pe[h] after it: qa row = R + nRot.
434
+ // One thread per (h, r) for r < R; threads r < nRot/2 of row R.. rotate the q_pe pairs.
435
+ mlaQ: `
436
+ struct P { heads: u32, R: u32, nope: u32, nRot: u32 }
437
+ @group(0) @binding(0) var<storage, read> q: array<f32>;
438
+ @group(0) @binding(1) var<storage, read> wkb: array<f32>;
439
+ @group(0) @binding(2) var<storage, read> rope: array<f32>;
440
+ @group(0) @binding(3) var<storage, read_write> qa: array<f32>;
441
+ @group(0) @binding(4) var<uniform> p: P;
442
+ @compute @workgroup_size(256) fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) l: vec3<u32>) {
443
+ let h = wg.y; let r = wg.x * 256u + l.x; let qk = p.nope + p.nRot; let qb = h * qk; let ob = h * (p.R + p.nRot);
444
+ if (r < p.R) {
445
+ var acc = 0.0; let wb = (h * p.R + r) * p.nope;
446
+ for (var d = 0u; d < p.nope; d++) { acc += wkb[wb + d] * q[qb + d]; }
447
+ qa[ob + r] = acc;
448
+ } else if (r < p.R + p.nRot / 2u) {
449
+ let i = r - p.R;
450
+ let a = q[qb + p.nope + 2u * i]; let b = q[qb + p.nope + 2u * i + 1u]; let c = rope[i]; let sn = rope[p.nRot / 2u + i];
451
+ qa[ob + p.R + 2u * i] = a * c - b * sn;
452
+ qa[ob + p.R + 2u * i + 1u] = a * sn + b * c;
453
+ }
454
+ }`,
455
+ // Scores sc[h·S + t] = qa[h]·cache[t] · scale over the R + nRot latent dims, t < seqLen. One thread per (h, t).
456
+ mlaScore: `
457
+ ${TOK}
458
+ struct P { heads: u32, D: u32, S: u32, scale: f32 }
459
+ @group(0) @binding(0) var<storage, read> qa: array<f32>;
460
+ @group(0) @binding(1) var<storage, read> cache: array<f32>;
461
+ @group(0) @binding(2) var<storage, read_write> sc: array<f32>;
462
+ @group(0) @binding(3) var<uniform> tok: Tok;
463
+ @group(0) @binding(4) var<uniform> p: P;
464
+ @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>) {
465
+ let i = (wg.y * ng.x + wg.x) * 256u + l.x; let h = i / tok.seqLen; let t = i % tok.seqLen;
466
+ if (h >= p.heads) { return; }
467
+ var acc = 0.0; let qb = h * p.D; let cb = t * p.D;
468
+ for (var d = 0u; d < p.D; d++) { acc += qa[qb + d] * cache[cb + d]; }
469
+ sc[h * p.S + t] = acc * p.scale;
470
+ }`,
471
+ // Softmax over sc[h·S .. h·S + seqLen) in place; one workgroup per head.
472
+ softmaxS: `
473
+ ${TOK}
474
+ struct P { S: u32 }
475
+ @group(0) @binding(0) var<storage, read_write> sc: array<f32>;
476
+ @group(0) @binding(1) var<uniform> tok: Tok;
477
+ @group(0) @binding(2) var<uniform> p: P;
478
+ var<workgroup> red: array<f32, 256>;
479
+ @compute @workgroup_size(256) fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) l: vec3<u32>) {
480
+ let b = wg.x * p.S; let n = tok.seqLen; let t = l.x;
481
+ var mx = -3.4e38; for (var i = t; i < n; i += 256u) { mx = max(mx, sc[b + i]); }
482
+ red[t] = mx; workgroupBarrier();
483
+ for (var s = 128u; s > 0u; s >>= 1u) { if (t < s) { red[t] = max(red[t], red[t + s]); } workgroupBarrier(); }
484
+ let m = red[0]; workgroupBarrier();
485
+ var sum = 0.0; for (var i = t; i < n; i += 256u) { let e = exp(sc[b + i] - m); sc[b + i] = e; sum += e; }
486
+ red[t] = sum; workgroupBarrier();
487
+ for (var s = 128u; s > 0u; s >>= 1u) { if (t < s) { red[t] += red[t + s]; } workgroupBarrier(); }
488
+ let inv = 1.0 / red[0];
489
+ for (var i = t; i < n; i += 256u) { sc[b + i] = sc[b + i] * inv; }
490
+ }`,
491
+ // ol[h][r] = Σ_t sc[h·S + t]·cache[t][r], r < R. One thread per (h, r).
492
+ mlaMix: `
493
+ ${TOK}
494
+ struct P { heads: u32, R: u32, D: u32, S: u32 }
495
+ @group(0) @binding(0) var<storage, read> sc: array<f32>;
496
+ @group(0) @binding(1) var<storage, read> cache: array<f32>;
497
+ @group(0) @binding(2) var<storage, read_write> ol: array<f32>;
498
+ @group(0) @binding(3) var<uniform> tok: Tok;
499
+ @group(0) @binding(4) var<uniform> p: P;
500
+ @compute @workgroup_size(256) fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) l: vec3<u32>) {
501
+ let h = wg.y; let r = wg.x * 256u + l.x; if (r >= p.R) { return; }
502
+ var acc = 0.0;
503
+ for (var t = 0u; t < tok.seqLen; t++) { acc += sc[h * p.S + t] * cache[t * p.D + r]; }
504
+ ol[h * p.R + r] = acc;
505
+ }`,
506
+ // o[h·vd + d] = sigmoid(gt[h]) · Σ_r wvb[h][d][r]·ol[h][r]. One thread per (h, d).
507
+ mlaOut: `
508
+ struct P { heads: u32, R: u32, vd: u32, pad: u32 }
509
+ @group(0) @binding(0) var<storage, read> ol: array<f32>;
510
+ @group(0) @binding(1) var<storage, read> wvb: array<f32>;
511
+ @group(0) @binding(2) var<storage, read> gt: array<f32>;
512
+ @group(0) @binding(3) var<storage, read_write> o: array<f32>;
513
+ @group(0) @binding(4) var<uniform> p: P;
514
+ ${SIGM}
515
+ @compute @workgroup_size(128) fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) l: vec3<u32>) {
516
+ let h = wg.x; let d = l.x; if (d >= p.vd) { return; }
517
+ var acc = 0.0; let wb = (h * p.vd + d) * p.R; let ob = h * p.R;
518
+ for (var r = 0u; r < p.R; r++) { acc += wvb[wb + r] * ol[ob + r]; }
519
+ o[h * p.vd + d] = acc * sigm(gt[h]);
520
+ }`,
521
+ };
522
+
523
+ // Expert matvec over the pool: z = workgroup_id.z picks the expert, slot = slots[z]; rows start at slot·slotBytes;
524
+ // x is shared (xStride 0, gate‖up) or per expert (xStride F, down). y[z·M + m]. Generated per block type.
525
+ function expertKernel(type) {
526
+ const CPB = QTYPES[type].qk / 32;
527
+ return `
528
+ struct P { M: u32, N: u32, rowBytes: u32, slotBytes: u32, xStride: u32, pad0: u32, pad1: u32, pad2: u32 }
529
+ @group(0) @binding(0) var<storage, read> xa: array<f32>;
530
+ @group(0) @binding(1) var<storage, read> wq: array<u32>;
531
+ @group(0) @binding(2) var<storage, read> grid: array<u32>;
532
+ @group(0) @binding(3) var<storage, read_write> y: array<f32>;
533
+ @group(0) @binding(4) var<uniform> p: P;
534
+ @group(0) @binding(5) var<storage, read> slots: array<u32>;
535
+ ${deqFn(type)}
536
+ const R = 16u;
537
+ var<workgroup> red: array<f32, 1024>;
538
+ @compute @workgroup_size(64) fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) l: vec3<u32>) {
539
+ _ = grid[0];
540
+ let z = wg.z; let m0 = wg.x * R; let t = l.x; let nc = p.N / 32u;
541
+ let base = slots[z] * p.slotBytes; let xo = z * p.xStride;
542
+ var acc: array<f32, 16>;
543
+ for (var c = t; c < nc; c += 64u) {
544
+ var xv: array<f32, 32>;
545
+ for (var i = 0u; i < 32u; i++) { xv[i] = xa[xo + c * 32u + i]; }
546
+ let bo = (c / ${CPB}u) * ${gpuBlock(type)}u; let s = c % ${CPB}u;
547
+ for (var r = 0u; r < R; r++) {
548
+ let B = base + min(m0 + r, p.M - 1u) * p.rowBytes + bo;
549
+ var a = 0.0;
550
+ for (var g = 0u; g < 4u; g++) {
551
+ let v = deq8(B, s, g);
552
+ for (var j = 0u; j < 8u; j++) { a += v[j] * xv[8u * g + j]; }
553
+ }
554
+ acc[r] += a;
555
+ }
556
+ }
557
+ for (var r = 0u; r < R; r++) { red[r * 64u + t] = acc[r]; }
558
+ workgroupBarrier();
559
+ for (var st = 32u; st > 0u; st >>= 1u) {
560
+ if (t < st) { for (var r = 0u; r < R; r++) { red[r * 64u + t] += red[r * 64u + t + st]; } }
561
+ workgroupBarrier();
562
+ }
563
+ if (t < R && m0 + t < p.M) { y[z * p.M + m0 + t] = red[t * 64u]; }
564
+ }`;
565
+ }
566
+
567
+ // Whole tensor → f32 (W_k_b, W_v_b at upload): thread i writes values 8i..8i+7 of the flat block sequence.
568
+ function deqAllKernel(type) {
569
+ const CPB = QTYPES[type].qk / 32;
570
+ return `
571
+ struct P { n8: u32 }
572
+ @group(0) @binding(0) var<storage, read> wq: array<u32>;
573
+ @group(0) @binding(1) var<storage, read> grid: array<u32>;
574
+ @group(0) @binding(2) var<storage, read_write> y: array<f32>;
575
+ @group(0) @binding(3) var<uniform> p: P;
576
+ ${deqFn(type)}
577
+ @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>) {
578
+ _ = grid[0];
579
+ let i = (wg.y * ng.x + wg.x) * 256u + l.x; if (i >= p.n8) { return; }
580
+ let c = i >> 2u; let g = i & 3u;
581
+ let v = deq8((c / ${CPB}u) * ${gpuBlock(type)}u, c % ${CPB}u, g);
582
+ for (var j = 0u; j < 8u; j++) { y[i * 8u + j] = v[j]; }
583
+ }`;
584
+ }
585
+
586
+ const TYPES = ['Q4_K', 'Q6_K', 'Q5_0'];
587
+
588
+ export class LingMoeSsd extends Qwen3MoeSsd {
589
+ static configFrom(kv) { return configFromGgufLing(kv); }
590
+ static tokenizerFrom(kv) { return new LingTokenizer(kv); }
591
+
592
+ // Reads part 2's tensor table first, then ingests both parts as one file (splitFetch) with a plan over all tensors.
593
+ // opts.url alone: one GGUF (a truncated test file), ingested as it is. opts.urls: the parts from elsewhere.
594
+ static async load(modelId = null, opts = {}) {
595
+ const src = opts.source || LING_FLASH;
596
+ const baseFetch = opts.fetch || ((u, i) => fetch(u, i));
597
+ if (opts.url && !opts.urls) {
598
+ const One = class extends this { static get ingestPlan() { return { layout: planLayoutLing, units: planUnitsLing }; } };
599
+ return Qwen3MoeSsd.load.call(One, modelId, { ...opts, source: { repo: src.repo, file: opts.url.split('/').pop(), revision: src.revision } });
600
+ }
601
+ const urls = opts.urls || src.parts.map((p) => `https://huggingface.co/${src.repo}/resolve/${src.revision}/${p.file}`);
602
+ const sizes = src.parts.map((p) => p.size);
603
+ const fetchFn = splitFetch(urls, sizes, baseFetch);
604
+ let extra = null, partErr = null;
605
+ const all = (gguf) => { if (!extra) throw partErr || new Error('Ling: part 2 header not read'); return { ...gguf, tensors: [...gguf.tensors, ...extra] }; };
606
+ const plan = {
607
+ layout: (gguf) => planLayoutLing(all(gguf)),
608
+ units: (gguf, layout) => planUnitsLing(all(gguf), layout),
609
+ };
610
+ const self = this;
611
+ const Sub = class extends self {
612
+ static get ingestPlan() { return plan; }
613
+ static async open(id, o) {
614
+ // Part 2's tensors are needed only to ingest, so a complete store loads without reading either part's header
615
+ // (20 MB from Hugging Face otherwise). Offline, a failed read throws only if an ingest is needed.
616
+ const complete = !o.reingest && (await readStore(o.key, { root: o.root || STORE_ROOT, format: STORE_FORMAT })).state === 'complete';
617
+ if (!extra && !complete) {
618
+ try {
619
+ const h = await baseFetch(urls[0], { headers: { Range: 'bytes=0-16777215' }, cache: 'no-store' });
620
+ const g1 = parseGguf(new Uint8Array(await h.arrayBuffer()));
621
+ extra = await readPartTensors(urls[1], sizes[0], g1.dataStart, baseFetch);
622
+ } catch (e) { partErr = e; }
623
+ }
624
+ return super.open(id, o);
625
+ }
626
+ };
627
+ return Qwen3MoeSsd.load.call(Sub, modelId, { ...opts, fetch: fetchFn, url: 'split:' + urls[0], key: opts.key || 'ling-3.0-flash-q4_k_m', source: { repo: src.repo, file: src.file, revision: src.revision, size: src.size } });
628
+ }
629
+
630
+ // Prefetch off by default: with 512 experts a guess from the layer before misses too often. Measured 2026-10-23 (M4 Pro,
631
+ // 4 GB pool, 371-token prompt, 128 tokens): off 5.53 tok/s, lookahead 1 4.49, lookahead 2 3.77.
632
+ constructor(device, manifest, gguf, opts) { super(device, manifest, gguf, opts); this.prefetch = opts.prefetch ?? false; }
633
+
634
+ chatPrompt(messages, opts) { return lingChatPrompt(messages, opts); }
635
+ get stopTokenIds() { return [this.cfg.eos, this.tokenizer.ids.get('<|endoftext|>')].filter((t) => t !== undefined); }
636
+ get thinkOpenTokenId() { return this.tokenizer.ids.get('<think>') ?? null; }
637
+ get thinkCloseTokenId() { return this.tokenizer.ids.get('</think>') ?? null; }
638
+ get thinkInPrompt() { return true; }
639
+ get alwaysThinks() { return false; }
640
+ get thinkPreamble() { return ''; }
641
+
642
+ get kernels() {
643
+ if (!this._kernels) {
644
+ const k = { rmsnorm: super.kernels.rmsnorm, matmulF32: super.kernels.matmulF32, argmax: super.kernels.argmax, ...LK };
645
+ for (const t of TYPES) { k[`mv_${t}`] = mvKernel(t); k[`ex_${t}`] = expertKernel(t); k[`deq_${t}`] = deqAllKernel(t); }
646
+ k.embed = embedKernel('Q4_K');
647
+ this._kernels = k;
648
+ }
649
+ return this._kernels;
650
+ }
651
+
652
+ // The MLA cache (f32 [c | k_pe] per token on 7 layers) and the KDA state (f32 128×128 per head, 35 layers).
653
+ kvBytes() {
654
+ const c = this.cfg, D = c.kvRank + c.nRot, nMla = c.mla.filter(Boolean).length, nKda = c.modelLayers - nMla;
655
+ return nMla * this.maxCtx * D * 4 + nKda * (c.heads * c.kdaHd * c.kdaHd + 3 * 3 * c.heads * c.kdaHd) * 4;
656
+ }
657
+
658
+ // Dense raw copies to the GPU, then W_k_b and W_v_b dequantized to f32 once (they serve per-head matvecs).
659
+ async uploadDense(onProgress) {
660
+ await super.uploadDense(onProgress);
661
+ this.grid = this.buffer(gridData().byteLength, STORAGE | COPY_DST, 'dense');
662
+ this.device.queue.writeBuffer(this.grid, 0, gridData());
663
+ const enc = this.device.createCommandEncoder(), pass = enc.beginComputePass();
664
+ this.f32 = {};
665
+ for (const [name, d] of Object.entries(this.manifest.dense.tensors)) {
666
+ if (!/attn_(k|v)_b\.weight$/.test(name)) continue;
667
+ const n = d.dims.reduce((a, b) => a * b, 1), out = this.buffer(n * 4, STORAGE, 'dense');
668
+ const t = d.type, gr = this.bind(`deq_${t}`, [this.w[name].raw, this.grid, out, this.uniform([n / 8])]);
669
+ this.dispatch(pass, `deq_${t}`, gr, Math.ceil(n / 8 / 256));
670
+ this.f32[name] = out;
671
+ }
672
+ pass.end(); this.device.queue.submit([enc.finish()]);
673
+ await this.device.queue.onSubmittedWorkDone();
674
+ }
675
+
676
+ // A matvec on a dense tensor of any kept type: { name, group, x } for encode.
677
+ mv(name, x, y) {
678
+ const d = this.manifest.dense.tensors[name];
679
+ if (!d) throw new Error(`no tensor ${name}`);
680
+ const [N, M] = d.dims;
681
+ if (d.type === 'f32') return { k: 'matmulF32', g: this.bind('matmulF32', [x, this.w[name].raw, y, this.uniform([M, N])]), n: M };
682
+ return { k: `mv_${d.type}`, g: this.bind(`mv_${d.type}`, [x, this.w[name].raw, this.grid, y, this.uniform([M, N, rowBytes(d.type, N), 0])]), n: Math.ceil(M / 16) };
683
+ }
684
+
685
+ initBuffers() {
686
+ const c = this.cfg, H = c.hidden, K = c.topK, F = c.expertFf, hd = c.kdaHd, NK = c.heads * hd;
687
+ const D = c.kvRank + c.nRot, R = c.kvRank, nope = c.qkHd - c.nRot;
688
+ if (c.experts > 512 || c.topK > 32) throw new Error(`${c.experts} experts / top-${c.topK}: the router handles at most 512 / 32`);
689
+ const A = (n) => this.buffer(n * 4, STORAGE | COPY_SRC | COPY_DST);
690
+ const a = this.a = {
691
+ x: A(H), xn: A(H), o: A(H), sh: A(H), pxn: A(H),
692
+ q: A(Math.max(NK, c.heads * c.qkHd)), k: A(NK), v: A(NK), qc: A(NK), kc: A(NK), vc: A(NK), fa: A(NK), gz: A(NK), gb: A(NK + c.heads),
693
+ bl: A(c.heads), att: A(Math.max(NK, c.heads * c.vHd)), att2: A(NK),
694
+ kva: A(D), qa: A(c.heads * D), sc: A(c.heads * this.maxCtx), ol: A(c.heads * R), gt: A(c.heads),
695
+ ffgu: A(2 * Math.max(c.denseFf, c.shexpFf)), ffact: A(Math.max(c.denseFf, c.shexpFf)),
696
+ rl: A(c.experts), sel: A(2 * K), prl: A(c.experts), psel: A(64 * this.lookahead), slots: A(K),
697
+ gu: A(K * 2 * F), act: A(K * F), dn: A(K * H), logits: A(c.vocab), am: A(4), rope: A(c.nRot),
698
+ };
699
+ this.tok = this.buffer(16, 0x40 | COPY_DST);
700
+ this.rbSel = this.buffer(256 * (1 + this.lookahead), MAP_READ | COPY_DST);
701
+ this.rbLogits = this.buffer(c.vocab * 4, MAP_READ | COPY_DST);
702
+ this.rbArg = this.buffer(16, MAP_READ | COPY_DST);
703
+ // Recurrent state and MLA caches, per model layer.
704
+ this.state = []; this.convSt = []; this.cache = [];
705
+ for (let l = 0; l < c.modelLayers; l++) {
706
+ if (c.mla[l]) { this.cache[l] = this.buffer(this.maxCtx * D * 4, STORAGE, 'kv'); continue; }
707
+ this.state[l] = this.buffer(c.heads * hd * hd * 4, STORAGE | COPY_DST, 'kv');
708
+ this.convSt[l] = ['q', 'k', 'v'].map(() => this.buffer(3 * NK * 4, STORAGE | COPY_DST, 'kv'));
709
+ }
710
+ const W = this.w, P = (l, n) => `blk.${l}.${n}`, raw = (n) => W[n].raw;
711
+ const u = {
712
+ rmsH: this.uniform([H, { f: c.eps }]), nH: this.uniform([H]), nNK: this.uniform([NK]),
713
+ gates: this.uniform([NK, hd, c.heads, { f: c.gateLow }]), kda: this.uniform([hd, NK, { f: c.eps }, { f: 1 / Math.sqrt(hd) }]),
714
+ hng: this.uniform([hd, { f: c.eps }]), kv: this.uniform([R, c.nRot, { f: c.eps }, 0]),
715
+ mq: this.uniform([c.heads, R, nope, c.nRot]), ms: this.uniform([c.heads, D, this.maxCtx, { f: 1 / Math.sqrt(c.qkHd) }]),
716
+ soft: this.uniform([this.maxCtx]), mix: this.uniform([c.heads, R, D, this.maxCtx]), mo: this.uniform([c.heads, R, c.vHd, 0]),
717
+ route: this.uniform([c.experts, K, c.groups, c.groupsUsed, { f: c.expScale }, 0, 0, 0]),
718
+ acc: this.uniform([H, K]), am: this.uniform([c.vocab]),
719
+ };
720
+ this.u = u;
721
+ const swi = (Fn, k, L) => this.uniform([Fn, k, { f: L || 0 }, 0]);
722
+ const embd = this.manifest.dense.tensors['token_embd.weight'];
723
+ this.g = { embed: this.bind('embed', [raw('token_embd.weight'), this.grid, a.x, this.tok, this.uniform([H, rowBytes(embd.type, H)])]) };
724
+ this.trunk = [];
725
+ for (let l = 0; l < c.modelLayers; l++) {
726
+ const t = { rmsA: this.bind('rmsnorm', [a.x, raw(P(l, 'attn_norm.weight')), a.xn, u.rmsH]), mla: c.mla[l] };
727
+ if (c.mla[l]) {
728
+ t.q = this.mv(P(l, 'attn_q.weight'), a.xn, a.q);
729
+ t.kva = this.mv(P(l, 'attn_kv_a_mqa.weight'), a.xn, a.kva);
730
+ t.gate = this.mv(P(l, 'attn_gate.weight'), a.xn, a.gt);
731
+ t.kv = this.bind('mlaKv', [a.kva, raw(P(l, 'attn_kv_a_norm.weight')), a.rope, this.cache[l], this.tok, u.kv]);
732
+ t.mq = this.bind('mlaQ', [a.q, this.f32[P(l, 'attn_k_b.weight')], a.rope, a.qa, u.mq]);
733
+ t.score = this.bind('mlaScore', [a.qa, this.cache[l], a.sc, this.tok, u.ms]);
734
+ t.soft = this.bind('softmaxS', [a.sc, this.tok, u.soft]);
735
+ t.mix = this.bind('mlaMix', [a.sc, this.cache[l], a.ol, this.tok, u.mix]);
736
+ t.mo = this.bind('mlaOut', [a.ol, this.f32[P(l, 'attn_v_b.weight')], a.gt, a.att, u.mo]);
737
+ } else {
738
+ t.q = this.mv(P(l, 'attn_q.weight'), a.xn, a.q); t.k = this.mv(P(l, 'attn_k.weight'), a.xn, a.k); t.v = this.mv(P(l, 'attn_v.weight'), a.xn, a.v);
739
+ t.fa = this.mv(P(l, 'ssm_f_a.weight'), a.xn, a.fa); t.gz = this.mv(P(l, 'ssm_g_a.weight'), a.xn, a.gz); t.bl = this.mv(P(l, 'ssm_beta.weight'), a.xn, a.bl);
740
+ t.conv = [['q', a.q, a.qc], ['k', a.k, a.kc], ['v', a.v, a.vc]].map(([n, x, y], i) =>
741
+ this.bind('convStep', [x, raw(P(l, `ssm_conv1d_${n}.weight`)), this.convSt[l][i], y, u.nNK]));
742
+ t.gates = this.bind('kdaGates', [a.fa, raw(P(l, 'ssm_dt.bias')), raw(P(l, 'ssm_a')), a.bl, a.gb, u.gates]);
743
+ t.delta = this.bind('kdaStep', [a.qc, a.kc, a.vc, a.gb, this.state[l], a.att2, u.kda]);
744
+ t.hng = this.bind('headNormGate', [a.att2, raw(P(l, 'ssm_norm.weight')), a.gz, a.att, u.hng]);
745
+ }
746
+ t.o = this.mv(P(l, 'attn_output.weight'), a.att, a.o);
747
+ t.addO = this.bind('addInPlace', [a.x, a.o, u.nH]);
748
+ t.rmsF = this.bind('rmsnorm', [a.x, raw(P(l, 'ffn_norm.weight')), a.xn, u.rmsH]);
749
+ const ffn = (gate, up, down, Fn, L) => ({
750
+ g: this.mv(P(l, gate), a.xn, a.ffgu), u: this.muOff(P(l, up), a.xn, a.ffgu, Fn),
751
+ act: this.bind('swiglu', [a.ffgu, a.ffact, swi(Fn, 1, L)]), d: this.mv(P(l, down), a.ffact, a.sh), F: Fn,
752
+ });
753
+ if (l < c.lead) {
754
+ t.ffn = ffn('ffn_gate.weight', 'ffn_up.weight', 'ffn_down.weight', c.denseFf, 0);
755
+ t.addF = this.bind('addInPlace', [a.x, a.sh, u.nH]);
756
+ } else {
757
+ t.shexp = ffn('ffn_gate_shexp.weight', 'ffn_up_shexp.weight', 'ffn_down_shexp.weight', c.shexpFf, c.clampShexp[l]);
758
+ t.router = this.mv(P(l, 'ffn_gate_inp.weight'), a.xn, a.rl);
759
+ t.route = this.bind('routeLing', [a.rl, raw(P(l, 'exp_probs_b.bias')), a.sel, u.route]);
760
+ }
761
+ this.trunk.push(t);
762
+ }
763
+ // MoE layer view for the base step loop: layer l = model layer lead + l.
764
+ const xp = this.manifest.experts, slotGu = xp.parts.guQ.bytes, slotDn = xp.parts.dQ.bytes, Hb = rowBytes('Q4_K', H);
765
+ this.layers = [];
766
+ for (let l = 0; l < c.layers; l++) {
767
+ const ml = c.lead + l, dt = this.manifest.downType[l];
768
+ this.layers.push({
769
+ pf: Array.from({ length: Math.min(this.lookahead, c.layers - 1 - l) }, (_, i) => {
770
+ const nl = ml + 1 + i;
771
+ return {
772
+ rms: this.bind('rmsnorm', [a.x, raw(P(nl, 'ffn_norm.weight')), a.pxn, u.rmsH]),
773
+ router: this.mv(P(nl, 'ffn_gate_inp.weight'), a.pxn, a.prl),
774
+ route: this.device.createBindGroup({ layout: this.pipeline('routeLing').getBindGroupLayout(0), entries: [
775
+ { binding: 0, resource: { buffer: a.prl } }, { binding: 1, resource: { buffer: raw(P(nl, 'exp_probs_b.bias')) } },
776
+ { binding: 2, resource: { buffer: a.psel, offset: 256 * i, size: 8 * K } }, { binding: 3, resource: { buffer: u.route } },
777
+ ] }),
778
+ };
779
+ }),
780
+ gu: this.bind('ex_Q4_K', [a.xn, this.pool.guQ, this.grid, a.gu, this.uniform([2 * F, H, Hb, slotGu, 0, 0, 0, 0]), a.slots]),
781
+ act: this.bind('swiglu', [a.gu, a.act, swi(F, K, c.clampExp[ml])]),
782
+ dn: this.bind(`ex_${dt}`, [a.act, this.pool.dQ, this.grid, a.dn, this.uniform([H, F, rowBytes(dt, F), slotDn, F, 0, 0, 0]), a.slots]),
783
+ dk: `ex_${dt}`,
784
+ acc: this.bind('moeAccum', [a.dn, a.sel, a.sh, a.x, u.acc]),
785
+ });
786
+ }
787
+ this.g.rmsOut = this.bind('rmsnorm', [a.x, raw('output_norm.weight'), a.xn, u.rmsH]);
788
+ this.g.lm = this.mv('output.weight', a.xn, a.logits);
789
+ this.g.am = this.bind('argmax', [a.logits, a.am, u.am]);
790
+ // Debug: every model layer's output x for the last step (opts.captureX), read by readCapture().
791
+ if (this.opts.captureX) {
792
+ this.dbg = this.buffer(c.modelLayers * H * 4, STORAGE | COPY_SRC);
793
+ this.dbgRead = this.buffer(c.modelLayers * H * 4, MAP_READ | COPY_DST);
794
+ this.g.cap = Array.from({ length: c.modelLayers }, (_, ml) => this.bind('copyAt', [a.x, this.dbg, this.uniform([H, ml])]));
795
+ }
796
+ this.clearState();
797
+ }
798
+
799
+ // The up half of a gate‖up buffer: the same matvec written Fn floats in (a bind group over y at an offset).
800
+ muOff(name, x, y, Fn) {
801
+ const d = this.manifest.dense.tensors[name], [N, M] = d.dims;
802
+ const g = this.device.createBindGroup({ layout: this.pipeline(`mv_${d.type}`).getBindGroupLayout(0), entries: [
803
+ { binding: 0, resource: { buffer: x } }, { binding: 1, resource: { buffer: this.w[name].raw } }, { binding: 2, resource: { buffer: this.grid } },
804
+ { binding: 3, resource: { buffer: y, offset: Fn * 4, size: M * 4 } }, { binding: 4, resource: { buffer: this.uniform([M, N, rowBytes(d.type, N), 0]) } },
805
+ ] });
806
+ return { k: `mv_${d.type}`, g, n: Math.ceil(M / 16) };
807
+ }
808
+
809
+ clearState() {
810
+ const enc = this.device.createCommandEncoder();
811
+ for (const s of this.state) if (s) enc.clearBuffer(s);
812
+ for (const cs of this.convSt) if (cs) for (const b of cs) enc.clearBuffer(b);
813
+ this.device.queue.submit([enc.finish()]);
814
+ }
815
+
816
+ // ── forward ──
817
+ run(pass, m) { this.dispatch(pass, m.k, m.g, m.n); }
818
+ encodeFfn(pass, f) {
819
+ const d = (n, g, x, y, z) => this.dispatch(pass, n, g, x, y, z);
820
+ this.run(pass, f.g); this.run(pass, f.u);
821
+ d('swiglu', f.act, Math.ceil(f.F / 256));
822
+ this.run(pass, f.d);
823
+ }
824
+ // Model layer ml's attention block, then (MoE layers) its FFN norm, shared expert, router and the prefetch guesses.
825
+ encodeTrunk(pass, ml, seqLen, pf) {
826
+ const c = this.cfg, t = this.trunk[ml], d = (n, g, x, y, z) => this.dispatch(pass, n, g, x, y, z), H = c.hidden, NK = c.heads * c.kdaHd;
827
+ d('rmsnorm', t.rmsA, 1);
828
+ if (t.mla) {
829
+ this.run(pass, t.q); this.run(pass, t.kva); this.run(pass, t.gate);
830
+ d('mlaKv', t.kv, 1);
831
+ d('mlaQ', t.mq, Math.ceil((c.kvRank + c.nRot / 2) / 256), c.heads);
832
+ d('mlaScore', t.score, Math.ceil(c.heads * seqLen / 256));
833
+ d('softmaxS', t.soft, c.heads);
834
+ d('mlaMix', t.mix, Math.ceil(c.kvRank / 256), c.heads);
835
+ d('mlaOut', t.mo, c.heads);
836
+ } else {
837
+ this.run(pass, t.q); this.run(pass, t.k); this.run(pass, t.v); this.run(pass, t.fa); this.run(pass, t.gz); this.run(pass, t.bl);
838
+ for (const g of t.conv) d('convStep', g, Math.ceil(NK / 256));
839
+ d('kdaGates', t.gates, Math.ceil(NK / 256));
840
+ d('kdaStep', t.delta, c.heads);
841
+ d('headNormGate', t.hng, c.heads);
842
+ }
843
+ this.run(pass, t.o);
844
+ d('addInPlace', t.addO, Math.ceil(H / 256));
845
+ d('rmsnorm', t.rmsF, 1);
846
+ if (t.ffn) { this.encodeFfn(pass, t.ffn); d('addInPlace', t.addF, Math.ceil(H / 256)); if (this.g.cap) d('copyAt', this.g.cap[ml], Math.ceil(H / 256)); return; }
847
+ if (this.prefetch && pf) for (const p of pf) { d('rmsnorm', p.rms, 1); this.run(pass, p.router); d('routeLing', p.route, 1); }
848
+ this.encodeFfn(pass, t.shexp);
849
+ this.run(pass, t.router);
850
+ d('routeLing', t.route, 1);
851
+ }
852
+ // The base loop's attention hook for MoE layer l: the dense lead layers go first with layer 0.
853
+ encodeAttention(pass, l, seqLen) {
854
+ if (l === 0) for (let ml = 0; ml < this.cfg.lead; ml++) this.encodeTrunk(pass, ml, seqLen, null);
855
+ this.encodeTrunk(pass, this.cfg.lead + l, seqLen, this.layers[l].pf);
856
+ }
857
+ encodeExperts(pass, l) {
858
+ const c = this.cfg, g = this.layers[l], d = (n, gr, x, y, z) => this.dispatch(pass, n, gr, x, y, z);
859
+ d('ex_Q4_K', g.gu, Math.ceil(2 * c.expertFf / 16), 1, c.topK);
860
+ d('swiglu', g.act, Math.ceil(c.topK * c.expertFf / 256));
861
+ d(g.dk, g.dn, Math.ceil(c.hidden / 16), 1, c.topK);
862
+ d('moeAccum', g.acc, Math.ceil(c.hidden / 256));
863
+ if (this.g.cap) d('copyAt', this.g.cap[c.lead + l], Math.ceil(c.hidden / 256));
864
+ }
865
+ async readCapture() {
866
+ const enc = this.device.createCommandEncoder();
867
+ enc.copyBufferToBuffer(this.dbg, 0, this.dbgRead, 0, this.dbg.size);
868
+ this.device.queue.submit([enc.finish()]);
869
+ await this.dbgRead.mapAsync(1);
870
+ const out = new Float32Array(this.dbgRead.getMappedRange().slice(0));
871
+ this.dbgRead.unmap();
872
+ return out;
873
+ }
874
+ encodeEmbed(pass) { this.dispatch(pass, 'embed', this.g.embed, Math.ceil(this.cfg.hidden / 8 / 256)); }
875
+ encodeHead(pass, want) {
876
+ this.dispatch(pass, 'rmsnorm', this.g.rmsOut, 1);
877
+ this.run(pass, this.g.lm);
878
+ if (want === 'argmax') this.dispatch(pass, 'argmax', this.g.am, 1);
879
+ }
880
+
881
+ writeTokenUniforms(token, pos) {
882
+ const c = this.cfg;
883
+ this.device.queue.writeBuffer(this.tok, 0, new Uint32Array([token, pos, pos + 1, 0]));
884
+ // GGML NORM RoPE angles: pair i turns by pos · base^(-2i/nRot); cos then sin.
885
+ const half = c.nRot / 2, r = new Float32Array(c.nRot), inv = Math.fround(-1 / c.nRot);
886
+ for (let i = 0; i < half; i++) {
887
+ const th = Math.fround(pos * Math.fround(Math.pow(c.ropeTheta, Math.fround(inv * 2 * i))));
888
+ r[i] = Math.cos(th); r[half + i] = Math.sin(th);
889
+ }
890
+ this.device.queue.writeBuffer(this.a.rope, 0, r);
891
+ }
892
+
893
+ reset() { super.reset(); if (this.state) this.clearState(); }
894
+ // The KDA state cannot step back: a prompt that extends the cached tokens continues, any other starts over.
895
+ async prefill(ids, want = 'argmax') {
896
+ let common = 0;
897
+ while (common < ids.length - 1 && common < this.cached.length && this.cached[common] === ids[common]) common++;
898
+ if (common < this.cached.length) { this.reset(); common = 0; }
899
+ let r = null;
900
+ for (let i = common; i < ids.length; i++) r = await this.step(ids[i], i === ids.length - 1 ? want : 'none');
901
+ return r;
902
+ }
903
+ }