diskformer 0.5.2 → 0.5.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.
@@ -66,10 +66,13 @@ export function configFromGguf(kv) {
66
66
  }
67
67
 
68
68
  // The engine's OPFS layout, derived from the header alone (ingest and load agree on it).
69
+ // Q8_0 experts: an int8 plane and an f16 scale plane per projection. Q4_0 experts (the qwen35moe Q4_0 files): a
70
+ // nibble plane (16 B per 32 values) and an f16 scale plane, as Gemma 4's. Dense tensors keep their GGUF type.
69
71
  export function planLayout(gguf) {
70
72
  const cfg = configFromGguf(gguf.kv);
71
73
  const H = cfg.hidden, F = cfg.expertFf;
72
- const qBytes = F * H; // one projection's int8 plane
74
+ const q4 = gguf.tensors.find((t) => /_exps\.weight$/.test(t.name))?.type === GGML.Q4_0;
75
+ const qBytes = q4 ? F * H / 2 : F * H; // one projection's int8 or nibble plane
73
76
  const sBytes = (F * H / 32) * 2; // its f16 scale plane
74
77
  const parts = {
75
78
  guQ: { off: 0, bytes: 2 * qBytes }, // gate rows 0..F-1, up rows F..2F-1
@@ -86,6 +89,10 @@ export function planLayout(gguf) {
86
89
  const n = t.dims.reduce((a, b) => a * b, 1);
87
90
  if (t.type === GGML.Q8_0) {
88
91
  dense[t.name] = { type: 'q8', dims: t.dims, q: { off: take(n), bytes: n }, s: { off: take(n / 16), bytes: n / 16 } };
92
+ } else if (t.type === GGML.Q4_0) {
93
+ dense[t.name] = { type: 'q4', dims: t.dims, q: { off: take(n / 2), bytes: n / 2 }, s: { off: take(n / 16), bytes: n / 16 } };
94
+ } else if (t.type === GGML.Q6_K) {
95
+ dense[t.name] = { type: 'q6k', dims: t.dims, raw: { off: take(tensorBytes(t)), bytes: tensorBytes(t) } };
89
96
  } else if (t.type === GGML.F32) {
90
97
  dense[t.name] = { type: 'f32', dims: t.dims, raw: { off: take(n * 4), bytes: n * 4 } };
91
98
  } else throw new Error(`dense tensor ${t.name}: unsupported type ${t.type}`);
@@ -100,7 +107,7 @@ export function planLayout(gguf) {
100
107
  // ── Ingest: GGUF (over HTTP Range) → OPFS, in the engine layout ─────────────
101
108
  // Every byte range of the GGUF the engine keeps, as units small enough to transform in
102
109
  // memory: one unit per expert slice, row bands for dense Q8_0, whole F32 tensors.
103
- function planUnits(gguf, layout) {
110
+ export function planUnits(gguf, layout) {
104
111
  const units = [];
105
112
  const { record, parts, perLayer } = layout.experts;
106
113
  const qBytes = parts.dQ.bytes, sBytes = parts.dS.bytes;
@@ -109,23 +116,25 @@ function planUnits(gguf, layout) {
109
116
  const m = /^blk\.(\d+)\.ffn_(gate|up|down)_exps\.weight$/.exec(t.name);
110
117
  if (m) {
111
118
  const layer = Number(m[1]), which = m[2];
112
- if (t.type !== GGML.Q8_0) throw new Error(`${t.name}: experts must be Q8_0`);
119
+ if (t.type !== GGML.Q8_0 && t.type !== GGML.Q4_0) throw new Error(`${t.name}: experts must be Q8_0 or Q4_0`);
120
+ const kind = t.type === GGML.Q4_0 ? { kind: 'q4' } : {};
113
121
  const slice = tensorBytes(t) / perLayer;
114
122
  const qOff = which === 'gate' ? parts.guQ.off : which === 'up' ? parts.guQ.off + qBytes : parts.dQ.off;
115
123
  const sOff = which === 'gate' ? parts.guS.off : which === 'up' ? parts.guS.off + sBytes : parts.dS.off;
116
124
  for (let e = 0; e < perLayer; e++) {
117
125
  const rec = (layer * perLayer + e) * record;
118
- units.push({ src: abs + e * slice, len: slice, file: 'experts', q: rec + qOff, s: rec + sOff });
126
+ units.push({ ...kind, src: abs + e * slice, len: slice, file: 'experts', q: rec + qOff, s: rec + sOff });
119
127
  }
120
128
  continue;
121
129
  }
122
130
  const d = layout.dense.tensors[t.name];
123
- if (d.type === 'f32') { units.push({ src: abs, len: d.raw.bytes, file: 'dense', raw: d.raw.off }); continue; }
124
- const cols = t.dims[0], rows = d.q.bytes / cols, rowBytes = (cols / 32) * Q8_BLOCK;
131
+ if (d.raw) { units.push({ src: abs, len: d.raw.bytes, file: 'dense', raw: d.raw.off }); continue; }
132
+ const q4 = d.type === 'q4', cols = t.dims[0], rows = (q4 ? 2 * d.q.bytes : d.q.bytes) / cols;
133
+ const rowBytes = (cols / 32) * (q4 ? Q4_BLOCK : Q8_BLOCK), qRow = q4 ? cols / 2 : cols;
125
134
  const band = Math.max(1, Math.floor((8 << 20) / rowBytes));
126
135
  for (let r = 0; r < rows; r += band) {
127
136
  const nr = Math.min(band, rows - r);
128
- units.push({ src: abs + r * rowBytes, len: nr * rowBytes, file: 'dense', q: d.q.off + r * cols, s: d.s.off + r * cols / 16 });
137
+ units.push({ ...(q4 ? { kind: 'q4' } : {}), src: abs + r * rowBytes, len: nr * rowBytes, file: 'dense', q: d.q.off + r * qRow, s: d.s.off + r * cols / 16 });
129
138
  }
130
139
  }
131
140
  units.sort((a, b) => a.src - b.src);
@@ -533,6 +542,295 @@ var<workgroup> bi: array<u32, 256>;
533
542
  }`,
534
543
  };
535
544
 
545
+ // ── Q4_0 / Q6_K kernels (Gemma 4 26B-A4B's, shared: Gemma 4, LFM2.5 24B-A2B and the qwen35moe Q4_0 files) ──
546
+ // Q4_0 as two planes (ingest's splitQ4): nibbles, 16 bytes per 32 values (byte j holds values j and j + 16),
547
+ // and one f16 scale per 32. Value = (nibble − 8) · scale, as ggml dequantizes it. Each thread reads one u32 of
548
+ // nibbles: values 4m..4m+3 (low nibbles) and 16+4m..16+4m+3 (high nibbles) of block b.
549
+ export const Q4DOT = `
550
+ fn q4dot(wd: u32, d: f32, xb: u32) -> f32 {
551
+ let lo = (f32(wd & 15u) - 8.0) * xin[xb] + (f32((wd >> 8u) & 15u) - 8.0) * xin[xb + 1u]
552
+ + (f32((wd >> 16u) & 15u) - 8.0) * xin[xb + 2u] + (f32((wd >> 24u) & 15u) - 8.0) * xin[xb + 3u];
553
+ let hi = (f32((wd >> 4u) & 15u) - 8.0) * xin[xb + 16u] + (f32((wd >> 12u) & 15u) - 8.0) * xin[xb + 17u]
554
+ + (f32((wd >> 20u) & 15u) - 8.0) * xin[xb + 18u] + (f32((wd >> 28u) & 15u) - 8.0) * xin[xb + 19u];
555
+ return d * (lo + hi);
556
+ }`;
557
+ // Q6_K (ggml block_q6_K: ql[128], qh[64], int8 scales[16], f16 d per 256 values). The GGUF's 210-byte blocks
558
+ // sit at 2-byte alignment, so at load q6kPlanes splits the table on the GPU into aligned planes: ql (32 words
559
+ // per block), qh (16), scales (4) and d as f32. q6v reads value t of block bi exactly as dequantize_row_q6_K
560
+ // forms it: (d · scale) · (q − 32).
561
+ export const Q6P = `
562
+ @group(0) @binding(0) var<storage, read> ql: array<u32>;
563
+ @group(0) @binding(1) var<storage, read> qh: array<u32>;
564
+ @group(0) @binding(2) var<storage, read> sc: array<u32>;
565
+ @group(0) @binding(3) var<storage, read> dd: array<f32>;
566
+ fn sc8(bi: u32, si: u32) -> f32 { let s8 = (sc[bi * 4u + (si >> 2u)] >> ((si & 3u) * 8u)) & 0xffu; return f32(select(i32(s8), i32(s8) - 256, s8 >= 128u)); }
567
+ fn q6v(bi: u32, t: u32) -> f32 {
568
+ let n = t >> 7u; let r = t & 127u; let qd = r >> 5u; let l = r & 31u;
569
+ let qb = n * 64u + l + (qd & 1u) * 32u;
570
+ let lo = (ql[bi * 32u + (qb >> 2u)] >> ((qb & 3u) * 8u)) & 0xffu;
571
+ let q4 = select(lo & 15u, lo >> 4u, qd >= 2u);
572
+ let hb = n * 32u + l;
573
+ let h = (qh[bi * 16u + (hb >> 2u)] >> ((hb & 3u) * 8u + qd * 2u)) & 3u;
574
+ return dd[bi] * sc8(bi, n * 8u + (l >> 4u) + qd * 2u) * f32(i32(q4 | (h << 4u)) - 32);
575
+ }`;
576
+ // ── Subgroup Q4_0 GEMVs (decode) ─────────────────────────────────────────────────
577
+ // One 32-lane subgroup per output row, 4 rows per 128-thread workgroup (the scalar kernels' dispatch
578
+ // geometry). A lane takes whole blocks: one vec4<u32> of nibbles, unpack4xU8 of the low and high nibble
579
+ // masks, vec4 dot products against the activations, the block scale once; subgroupAdd reduces the row.
580
+ // Used only when the adapter's subgroups are exactly 32 wide.
581
+ const Q4BLK = `
582
+ fn q4blk(w: vec4<u32>, xb: u32) -> f32 {
583
+ let m = 0x0f0f0f0fu;
584
+ return dot(vec4<f32>(unpack4xU8(w.x & m)) - 8.0, x[xb]) + dot(vec4<f32>(unpack4xU8((w.x >> 4u) & m)) - 8.0, x[xb + 4u])
585
+ + dot(vec4<f32>(unpack4xU8(w.y & m)) - 8.0, x[xb + 1u]) + dot(vec4<f32>(unpack4xU8((w.y >> 4u) & m)) - 8.0, x[xb + 5u])
586
+ + dot(vec4<f32>(unpack4xU8(w.z & m)) - 8.0, x[xb + 2u]) + dot(vec4<f32>(unpack4xU8((w.z >> 4u) & m)) - 8.0, x[xb + 6u])
587
+ + dot(vec4<f32>(unpack4xU8(w.w & m)) - 8.0, x[xb + 3u]) + dot(vec4<f32>(unpack4xU8((w.w >> 4u) & m)) - 8.0, x[xb + 7u]);
588
+ }`;
589
+ const QB4 = 'struct Q { T: u32, pos0: u32, S: u32, pad: u32 }';
590
+ export const Q4_KERNELS = {
591
+ // The GGUF's Q6_K bytes (raw) → aligned planes; one thread per block.
592
+ q6kPlanes: `
593
+ struct P { blocks: u32 }
594
+ @group(0) @binding(0) var<storage, read> raw: array<u32>;
595
+ @group(0) @binding(1) var<storage, read_write> ql: array<u32>;
596
+ @group(0) @binding(2) var<storage, read_write> qh: array<u32>;
597
+ @group(0) @binding(3) var<storage, read_write> sc: array<u32>;
598
+ @group(0) @binding(4) var<storage, read_write> dd: array<f32>;
599
+ @group(0) @binding(5) var<uniform> p: P;
600
+ fn byteAt(i: u32) -> u32 { return (raw[i >> 2u] >> ((i & 3u) * 8u)) & 0xffu; }
601
+ fn wordAt(i: u32) -> u32 { return byteAt(i) | (byteAt(i + 1u) << 8u) | (byteAt(i + 2u) << 16u) | (byteAt(i + 3u) << 24u); }
602
+ @compute @workgroup_size(256) fn main(@builtin(global_invocation_id) g: vec3<u32>, @builtin(num_workgroups) ng: vec3<u32>) {
603
+ let b = g.y * ng.x * 256u + g.x; if (b >= p.blocks) { return; }
604
+ let o = b * 210u;
605
+ for (var i = 0u; i < 32u; i++) { ql[b * 32u + i] = wordAt(o + 4u * i); }
606
+ for (var i = 0u; i < 16u; i++) { qh[b * 16u + i] = wordAt(o + 128u + 4u * i); }
607
+ for (var i = 0u; i < 4u; i++) { sc[b * 4u + i] = wordAt(o + 192u + 4u * i); }
608
+ dd[b] = unpack2x16float(byteAt(o + 208u) | (byteAt(o + 209u) << 8u)).x;
609
+ }`,
610
+ embedQ6K: `
611
+ ${TOK}
612
+ struct P { n: u32, nb: u32, scale: f32 }
613
+ ${Q6P}
614
+ @group(0) @binding(4) var<storage, read_write> y: array<f32>;
615
+ @group(0) @binding(5) var<uniform> tok: Tok;
616
+ @group(0) @binding(6) var<uniform> p: P;
617
+ @compute @workgroup_size(256) fn main(@builtin(global_invocation_id) g: vec3<u32>) {
618
+ let i = g.x; if (i >= p.n) { return; }
619
+ y[i] = q6v(tok.token * p.nb + (i >> 8u), i & 255u) * p.scale;
620
+ }`,
621
+ // Logits over the Q6_K planes: 4 rows per 256-thread workgroup, 64 threads per row; thread j takes quad j
622
+ // (4 consecutive values: one ql word, one qh word, one scale) of every block of the row.
623
+ matmulQ6K: `
624
+ struct P { M: u32, N: u32 }
625
+ ${Q6P}
626
+ @group(0) @binding(4) var<storage, read> x: array<vec4<f32>>;
627
+ @group(0) @binding(5) var<storage, read_write> y: array<f32>;
628
+ @group(0) @binding(6) var<uniform> p: P;
629
+ var<workgroup> part: array<f32, 256>;
630
+ @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>) {
631
+ let j = l.x & 63u; let m = (wg.y * ng.x + wg.x) * 4u + (l.x >> 6u); let mr = min(m, p.M - 1u);
632
+ let nb = p.N / 256u; let n = j >> 5u; let w = j & 31u; let qd = w >> 3u; let k = w & 7u;
633
+ var acc = 0.0;
634
+ for (var bb = 0u; bb < nb; bb++) {
635
+ let bi = mr * nb + bb;
636
+ let lo = unpack4xU8(ql[bi * 32u + n * 16u + (qd & 1u) * 8u + k]);
637
+ let q4 = select(lo & vec4<u32>(15u), lo >> vec4<u32>(4u), qd >= 2u);
638
+ let h = (unpack4xU8(qh[bi * 16u + n * 8u + k]) >> vec4<u32>(qd * 2u)) & vec4<u32>(3u);
639
+ let q = vec4<f32>(vec4<i32>(q4 | (h << vec4<u32>(4u))) - vec4<i32>(32));
640
+ acc += (dd[bi] * sc8(bi, n * 8u + (k >> 2u) + qd * 2u)) * dot(q, x[bb * 64u + j]);
641
+ }
642
+ part[l.x] = acc; workgroupBarrier();
643
+ for (var s = 32u; s > 0u; s >>= 1u) { if (j < s) { part[l.x] += part[l.x + s]; } workgroupBarrier(); }
644
+ if (j == 0u && m < p.M) { y[m] = part[l.x]; }
645
+ }`,
646
+ // Dense Q4_0 GEMV, 4 output rows per workgroup (2-D dispatch ok).
647
+ matmulQ4: `enable f16;
648
+ struct P { M: u32, N: u32 }
649
+ @group(0) @binding(0) var<storage, read> xin: array<f32>;
650
+ @group(0) @binding(1) var<storage, read> qw: array<u32>;
651
+ @group(0) @binding(2) var<storage, read> qs: array<f16>;
652
+ @group(0) @binding(3) var<storage, read_write> y: array<f32>;
653
+ @group(0) @binding(4) var<uniform> p: P;
654
+ ${Q4DOT}
655
+ var<workgroup> part: array<f32, 1024>;
656
+ @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>) {
657
+ 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;
658
+ if (m0 >= M) { return; }
659
+ let r0 = min(m0, M - 1u); let r1 = min(m0 + 1u, M - 1u); let r2 = min(m0 + 2u, M - 1u); let r3 = min(m0 + 3u, M - 1u);
660
+ var a0 = 0.0; var a1 = 0.0; var a2 = 0.0; var a3 = 0.0;
661
+ for (var w = t; w < nw; w += 256u) {
662
+ let b = w >> 2u; let xb = b * 32u + (w & 3u) * 4u;
663
+ a0 += q4dot(qw[r0 * nw + w], f32(qs[r0 * nb + b]), xb);
664
+ a1 += q4dot(qw[r1 * nw + w], f32(qs[r1 * nb + b]), xb);
665
+ a2 += q4dot(qw[r2 * nw + w], f32(qs[r2 * nb + b]), xb);
666
+ a3 += q4dot(qw[r3 * nw + w], f32(qs[r3 * nb + b]), xb);
667
+ }
668
+ part[t] = a0; part[256u + t] = a1; part[512u + t] = a2; part[768u + t] = a3;
669
+ workgroupBarrier();
670
+ for (var s = 128u; s > 0u; s >>= 1u) {
671
+ 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]; }
672
+ workgroupBarrier();
673
+ }
674
+ if (t < 4u && m0 + t < M) { y[m0 + t] = part[t * 256u]; }
675
+ }`,
676
+ // Routed experts, all k at once (workgroup z = top-k position); slots[z] picks the expert's rows in the pool.
677
+ expertQ4: `enable f16;
678
+ struct P { M: u32, N: u32, rowsPerExpert: u32, inputPerSlot: u32 }
679
+ @group(0) @binding(0) var<storage, read> xin: array<f32>;
680
+ @group(0) @binding(1) var<storage, read> qw: array<u32>;
681
+ @group(0) @binding(2) var<storage, read> qs: array<f16>;
682
+ @group(0) @binding(3) var<storage, read_write> y: array<f32>;
683
+ @group(0) @binding(4) var<uniform> p: P;
684
+ @group(0) @binding(5) var<storage, read> slots: array<u32>;
685
+ ${Q4DOT}
686
+ var<workgroup> part: array<f32, 1024>;
687
+ @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>) {
688
+ 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;
689
+ if (m0 >= M) { return; }
690
+ let rb = slots[z] * p.rowsPerExpert;
691
+ let xo = select(0u, z * p.N, p.inputPerSlot == 1u);
692
+ let r0 = rb + min(m0, M - 1u); let r1 = rb + min(m0 + 1u, M - 1u); let r2 = rb + min(m0 + 2u, M - 1u); let r3 = rb + min(m0 + 3u, M - 1u);
693
+ var a0 = 0.0; var a1 = 0.0; var a2 = 0.0; var a3 = 0.0;
694
+ for (var w = t; w < nw; w += 256u) {
695
+ let b = w >> 2u; let xb = xo + b * 32u + (w & 3u) * 4u;
696
+ a0 += q4dot(qw[r0 * nw + w], f32(qs[r0 * nb + b]), xb);
697
+ a1 += q4dot(qw[r1 * nw + w], f32(qs[r1 * nb + b]), xb);
698
+ a2 += q4dot(qw[r2 * nw + w], f32(qs[r2 * nb + b]), xb);
699
+ a3 += q4dot(qw[r3 * nw + w], f32(qs[r3 * nb + b]), xb);
700
+ }
701
+ part[t] = a0; part[256u + t] = a1; part[512u + t] = a2; part[768u + t] = a3;
702
+ workgroupBarrier();
703
+ for (var s = 128u; s > 0u; s >>= 1u) {
704
+ 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]; }
705
+ workgroupBarrier();
706
+ }
707
+ if (t < 4u && m0 + t < M) { y[z * M + m0 + t] = part[t * 256u]; }
708
+ }`,
709
+ };
710
+ export const Q4_SG_KERNELS = {
711
+ matmulQ4: `enable f16;
712
+ enable subgroups;
713
+ struct P { M: u32, N: u32 }
714
+ @group(0) @binding(0) var<storage, read> x: array<vec4<f32>>;
715
+ @group(0) @binding(1) var<storage, read> qw: array<vec4<u32>>;
716
+ @group(0) @binding(2) var<storage, read> qs: array<f16>;
717
+ @group(0) @binding(3) var<storage, read_write> y: array<f32>;
718
+ @group(0) @binding(4) var<uniform> p: P;
719
+ ${Q4BLK}
720
+ @compute @workgroup_size(128) fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(num_workgroups) ng: vec3<u32>, @builtin(local_invocation_id) l: vec3<u32>) {
721
+ let m = (wg.y * ng.x + wg.x) * 4u + l.x / 32u; let lane = l.x % 32u;
722
+ let nb = p.N / 32u; let rb = min(m, p.M - 1u) * nb;
723
+ var acc = 0.0;
724
+ for (var b = lane; b < nb; b += 32u) { acc += q4blk(qw[rb + b], b * 8u) * f32(qs[rb + b]); }
725
+ let total = subgroupAdd(acc);
726
+ if (lane == 0u && m < p.M) { y[m] = total; }
727
+ }`,
728
+ expertQ4: `enable f16;
729
+ enable subgroups;
730
+ struct P { M: u32, N: u32, rowsPerExpert: u32, inputPerSlot: u32 }
731
+ @group(0) @binding(0) var<storage, read> x: array<vec4<f32>>;
732
+ @group(0) @binding(1) var<storage, read> qw: array<vec4<u32>>;
733
+ @group(0) @binding(2) var<storage, read> qs: array<f16>;
734
+ @group(0) @binding(3) var<storage, read_write> y: array<f32>;
735
+ @group(0) @binding(4) var<uniform> p: P;
736
+ @group(0) @binding(5) var<storage, read> slots: array<u32>;
737
+ ${Q4BLK}
738
+ @compute @workgroup_size(128) fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(num_workgroups) ng: vec3<u32>, @builtin(local_invocation_id) l: vec3<u32>) {
739
+ let z = wg.z; let m = (wg.y * ng.x + wg.x) * 4u + l.x / 32u; let lane = l.x % 32u;
740
+ let nb = p.N / 32u; let rb = (slots[z] * p.rowsPerExpert + min(m, p.M - 1u)) * nb;
741
+ let xo = select(0u, z * p.N / 4u, p.inputPerSlot == 1u);
742
+ var acc = 0.0;
743
+ for (var b = lane; b < nb; b += 32u) { acc += q4blk(qw[rb + b], xo + b * 8u) * f32(qs[rb + b]); }
744
+ let total = subgroupAdd(acc);
745
+ if (lane == 0u && m < p.M) { y[z * p.M + m] = total; }
746
+ }`,
747
+ };
748
+ export const Q4_BATCH_KERNELS = {
749
+ embedQ6KB: `
750
+ struct P { n: u32, nb: u32, scale: f32 }
751
+ ${QB4}
752
+ ${Q6P}
753
+ @group(0) @binding(4) var<storage, read> ids: array<u32>;
754
+ @group(0) @binding(5) var<storage, read_write> y: array<f32>;
755
+ @group(0) @binding(6) var<uniform> p: P;
756
+ @group(0) @binding(7) var<uniform> qd: Q;
757
+ @compute @workgroup_size(256) fn main(@builtin(global_invocation_id) g: vec3<u32>) {
758
+ let i = g.x; let t = g.y; if (i >= p.n || t >= qd.T) { return; }
759
+ y[t * p.n + i] = q6v(ids[t] * p.nb + (i >> 8u), i & 255u) * p.scale;
760
+ }`,
761
+ // Tiled Q4_0 GEMM: y[t·M + m] = Σ_k x[t·N + k]·W[m][k]. 16 rows × 16 tokens per workgroup; each 32-wide
762
+ // block of W (decoded from its nibbles) and of x goes through shared memory, so a weight serves 16 tokens.
763
+ // Dispatch (ceil(M/16), ceil(T/16)).
764
+ matmulQ4T: `enable f16;
765
+ struct P { M: u32, N: u32 }
766
+ ${QB4}
767
+ @group(0) @binding(0) var<storage, read> x: array<f32>;
768
+ @group(0) @binding(1) var<storage, read> qw: array<u32>;
769
+ @group(0) @binding(2) var<storage, read> qs: array<f16>;
770
+ @group(0) @binding(3) var<storage, read_write> y: array<f32>;
771
+ @group(0) @binding(4) var<uniform> p: P;
772
+ @group(0) @binding(5) var<uniform> qd: Q;
773
+ var<workgroup> ws: array<f32, 512>;
774
+ var<workgroup> xs: array<f32, 512>;
775
+ @compute @workgroup_size(256) fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) l: vec3<u32>) {
776
+ let m0 = wg.x * 16u; let t0 = wg.y * 16u; let r = l.x / 16u; let c = l.x % 16u;
777
+ let nb = p.N / 32u; let nw = p.N / 8u; var acc = 0.0;
778
+ for (var b = 0u; b < nb; b++) {
779
+ for (var i = l.x; i < 512u; i += 256u) {
780
+ let rr = i / 32u; let kk = i % 32u;
781
+ let m = m0 + rr; var wv = 0.0;
782
+ if (m < p.M) {
783
+ let wd = qw[m * nw + b * 4u + ((kk & 15u) >> 2u)];
784
+ wv = (f32((wd >> ((kk & 3u) * 8u + (kk >> 4u) * 4u)) & 15u) - 8.0) * f32(qs[m * nb + b]);
785
+ }
786
+ ws[i] = wv;
787
+ let t = t0 + rr; var xv = 0.0;
788
+ if (t < qd.T) { xv = x[t * p.N + b * 32u + kk]; }
789
+ xs[i] = xv;
790
+ }
791
+ workgroupBarrier();
792
+ for (var kk = 0u; kk < 32u; kk++) { acc += ws[r * 32u + kk] * xs[c * 32u + kk]; }
793
+ workgroupBarrier();
794
+ }
795
+ let m = m0 + r; let t = t0 + c;
796
+ if (m < p.M && t < qd.T) { y[t * p.M + m] = acc; }
797
+ }`,
798
+ // Routed experts for every (token, pick) pair: z = t·K + j, slots[z] its pool slot. Input: the token's row
799
+ // (gate/up, inputMode 0) or the pair's own row (down, inputMode 1).
800
+ expertQ4B: `enable f16;
801
+ struct P { M: u32, N: u32, rowsPerExpert: u32, inputMode: u32, K: u32 }
802
+ @group(0) @binding(0) var<storage, read> xin: array<f32>;
803
+ @group(0) @binding(1) var<storage, read> qw: array<u32>;
804
+ @group(0) @binding(2) var<storage, read> qs: array<f16>;
805
+ @group(0) @binding(3) var<storage, read_write> y: array<f32>;
806
+ @group(0) @binding(4) var<uniform> p: P;
807
+ @group(0) @binding(5) var<storage, read> slots: array<u32>;
808
+ ${Q4DOT}
809
+ var<workgroup> part: array<f32, 1024>;
810
+ @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>) {
811
+ 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;
812
+ if (m0 >= M) { return; }
813
+ let rb = slots[z] * p.rowsPerExpert;
814
+ let xo = select((z / p.K) * p.N, z * p.N, p.inputMode == 1u);
815
+ let r0 = rb + min(m0, M - 1u); let r1 = rb + min(m0 + 1u, M - 1u); let r2 = rb + min(m0 + 2u, M - 1u); let r3 = rb + min(m0 + 3u, M - 1u);
816
+ var a0 = 0.0; var a1 = 0.0; var a2 = 0.0; var a3 = 0.0;
817
+ for (var w = t; w < nw; w += 256u) {
818
+ let b = w >> 2u; let xb = xo + b * 32u + (w & 3u) * 4u;
819
+ a0 += q4dot(qw[r0 * nw + w], f32(qs[r0 * nb + b]), xb);
820
+ a1 += q4dot(qw[r1 * nw + w], f32(qs[r1 * nb + b]), xb);
821
+ a2 += q4dot(qw[r2 * nw + w], f32(qs[r2 * nb + b]), xb);
822
+ a3 += q4dot(qw[r3 * nw + w], f32(qs[r3 * nb + b]), xb);
823
+ }
824
+ part[t] = a0; part[256u + t] = a1; part[512u + t] = a2; part[768u + t] = a3;
825
+ workgroupBarrier();
826
+ for (var s = 128u; s > 0u; s >>= 1u) {
827
+ 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]; }
828
+ workgroupBarrier();
829
+ }
830
+ if (t < 4u && m0 + t < M) { y[z * M + m0 + t] = part[t * 256u]; }
831
+ }`,
832
+ };
833
+
536
834
  export const STORAGE = 0x80, COPY_SRC = 0x04, COPY_DST = 0x08, UNIFORM = 0x40, MAP_READ = 0x01;
537
835
 
538
836
  // ── Engine ──────────────────────────────────────────────────────────────────
@@ -839,10 +1137,10 @@ export class Qwen3MoeSsd {
839
1137
  }
840
1138
  // The token's embedding into a.x, and the head (final norm, logits, argmax when wanted): a subclass
841
1139
  // with another embedding or head format overrides these two.
842
- encodeEmbed(pass) { this.dispatch(pass, 'embedQ8', this.g.embed, Math.ceil(this.cfg.hidden / 256)); }
1140
+ encodeEmbed(pass) { this.dispatch(pass, this.qk ? this.qk.emb : 'embedQ8', this.g.embed, Math.ceil(this.cfg.hidden / 256)); }
843
1141
  encodeHead(pass, want) {
844
1142
  this.dispatch(pass, 'rmsnorm', this.g.rmsOut, 1);
845
- this.dispatch(pass, 'matmulQ8', this.g.lm, Math.ceil(this.cfg.vocab / 4));
1143
+ this.dispatch(pass, this.qk ? this.qk.lm : 'matmulQ8', this.g.lm, Math.ceil(this.cfg.vocab / 4));
846
1144
  if (want === 'argmax') this.dispatch(pass, 'argmax', this.g.am, 1);
847
1145
  }
848
1146
  encodeExperts(pass, l) {
@@ -4,7 +4,7 @@
4
4
  because diskformer.naklitechie.com and the HF Space serve them and their CI has no Emscripten; the npm package ships them
5
5
  as `diskformer/llama`.
6
6
 
7
- Built from the fork `NakliTechie/llama.cpp` branch `webgpu-diskformer` at `377491df7` (upstream master of 2026-10-14 merged in), Emscripten 6.0.1 (Homebrew),
7
+ Built from the fork `NakliTechie/llama.cpp` branch `webgpu-diskformer` at `ecaa47b29` (upstream master of 2026-10-22 merged in, which adds Flint), Emscripten 6.0.13 (Homebrew),
8
8
  emdawnwebgpu from the fork's `emdawnwebgpu_pkg`, all C++ with `-fwasm-exceptions`. Rebuild (copies here as a
9
9
  post-build step):
10
10
 
@@ -12,7 +12,7 @@ post-build step):
12
12
 
13
13
  sha256 (check a copy with `shasum -a 256`):
14
14
 
15
- 701e17b9dca2fa6c86853daf724c08a217f16ad91ce10b3bce711ccf63f9172c df-chat.wasm
16
- 2267afb7b9ee76149748956cc2418202526e4f2144784268cf7bdabf5127e062 df-chat.mjs
15
+ cc2d43b6fbf5f9fe0848ebc42deb79365b5765f99a973eeadeb11fbbc6b0b4d8 df-chat.wasm
16
+ 43dea00df2a5345efc65c6521b825032670447bac854d2d346aa82132b3b37f9 df-chat.mjs
17
17
 
18
18
  Update the fork commit and the checksums whenever the build changes.