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.
- package/CHANGELOG.md +25 -0
- package/README.md +3 -3
- package/engines/gemma4_moe_ssd.js +5 -288
- package/engines/minicpmv_vision.js +506 -0
- package/engines/qwen35_moe_ssd.js +478 -66
- package/engines/qwen3_moe_ssd.js +307 -9
- package/ggml/dist/BUILD.md +3 -3
- package/ggml/dist/df-chat.mjs +1 -1
- package/ggml/dist/df-chat.wasm +0 -0
- package/package.json +1 -1
package/engines/qwen3_moe_ssd.js
CHANGED
|
@@ -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
|
|
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.
|
|
124
|
-
const cols = t.dims[0], rows = d.q.bytes
|
|
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 *
|
|
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) {
|
package/ggml/dist/BUILD.md
CHANGED
|
@@ -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 `
|
|
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
|
-
|
|
16
|
-
|
|
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.
|