web: QKV dual-GEMM fusion (bit-identical, CUTLASS ex. 45)
Browse files- web/public/transformer.js +32 -3
web/public/transformer.js
CHANGED
|
@@ -116,6 +116,35 @@
|
|
| 116 |
// ctx.audit re-checks random cells of this LIVE GEMM against the units
|
| 117 |
return V.vgemmBlock(Xf, Wf, { m, k, n, batch: 1, relu: !!relu }, ctx.L, ctx.bgemm, ctx.audit);
|
| 118 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
|
| 120 |
// ---- layernorm (no affine) -------------------------------------------------
|
| 121 |
function lnFwd(x, rows, C) {
|
|
@@ -227,9 +256,9 @@
|
|
| 227 |
for (let l = 0; l < layers; l++) {
|
| 228 |
const bl = m.blocks[l], cb = { xin: x };
|
| 229 |
const l1 = lnFwd(x, BT, C); cb.ln1 = l1;
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
const v = await
|
| 233 |
cb.q = q; cb.k = k; cb.v = v;
|
| 234 |
const scale = 1 / Math.sqrt(hd);
|
| 235 |
// gather-FUSED attention (CUTLASS ex. 36/52): the kernels read q/k/v in
|
|
|
|
| 116 |
// ctx.audit re-checks random cells of this LIVE GEMM against the units
|
| 117 |
return V.vgemmBlock(Xf, Wf, { m, k, n, batch: 1, relu: !!relu }, ctx.L, ctx.bgemm, ctx.audit);
|
| 118 |
}
|
| 119 |
+
// CUTLASS ex. 45 (dual GEMM): sibling GEMMs that share the same LEFT operand
|
| 120 |
+
// run as ONE batched dispatch, and the shared operand is quantized ONCE
|
| 121 |
+
// instead of once per sibling. Used for the q/k/v projections — same X
|
| 122 |
+
// (ln1.y), three weights, identical shapes. Bit-identical to three separate
|
| 123 |
+
// vmm calls: quantizeRows is deterministic (same input -> same int8+scales),
|
| 124 |
+
// the tiled copies index exactly like separate batch elements, and block
|
| 125 |
+
// scales are per-row/per-column PER BATCH ELEMENT, so concatenation changes
|
| 126 |
+
// no scale and no product. The batched kernel is the same exact-gated bgemm
|
| 127 |
+
// that training already runs, and the live-shape audit still samples it.
|
| 128 |
+
async function vmmShared3(Xf, Wa, Wb, Wc, m, k, n, ctx) {
|
| 129 |
+
const x = V.quantizeRows(Xf, m, k);
|
| 130 |
+
const xq = new Int8Array(3 * m * k), xs = new Float32Array(3 * m);
|
| 131 |
+
for (let i = 0; i < 3; i++) { xq.set(x.q, i * m * k); xs.set(x.s, i * m); }
|
| 132 |
+
const wq = new Int8Array(3 * k * n), ws = new Float32Array(3 * n);
|
| 133 |
+
[Wa, Wb, Wc].forEach((W, i) => { const w = V.quantizeCols(W, k, n); wq.set(w.q, i * k * n); ws.set(w.s, i * n); });
|
| 134 |
+
const d = { m, k, n, batch: 3 };
|
| 135 |
+
let out;
|
| 136 |
+
if (ctx.bgemm) {
|
| 137 |
+
out = await ctx.bgemm(xq, wq, xs, ws, d);
|
| 138 |
+
if (ctx.audit && ctx.audit.due()) {
|
| 139 |
+
const bad = V.auditTile(xq, wq, xs, ws, d, out, ctx.L, ctx.audit.cells);
|
| 140 |
+
if (bad) ctx.audit.fail(bad);
|
| 141 |
+
}
|
| 142 |
+
} else {
|
| 143 |
+
out = V.bgemmJS(xq, wq, xs, ws, d, ctx.L);
|
| 144 |
+
}
|
| 145 |
+
const MN = m * n;
|
| 146 |
+
return [out.subarray(0, MN), out.subarray(MN, 2 * MN), out.subarray(2 * MN, 3 * MN)];
|
| 147 |
+
}
|
| 148 |
|
| 149 |
// ---- layernorm (no affine) -------------------------------------------------
|
| 150 |
function lnFwd(x, rows, C) {
|
|
|
|
| 256 |
for (let l = 0; l < layers; l++) {
|
| 257 |
const bl = m.blocks[l], cb = { xin: x };
|
| 258 |
const l1 = lnFwd(x, BT, C); cb.ln1 = l1;
|
| 259 |
+
// q/k/v share the same left operand — one batched dispatch, one quantize
|
| 260 |
+
// of ln1.y instead of three (CUTLASS ex. 45; see vmmShared3)
|
| 261 |
+
const [q, k, v] = await vmmShared3(l1.y, bl.Wq, bl.Wk, bl.Wv, BT, C, C, ctx);
|
| 262 |
cb.q = q; cb.k = k; cb.v = v;
|
| 263 |
const scale = 1 / Math.sqrt(hd);
|
| 264 |
// gather-FUSED attention (CUTLASS ex. 36/52): the kernels read q/k/v in
|