Upload web/public/webgpu.js with huggingface_hub
Browse files- web/public/webgpu.js +54 -16
web/public/webgpu.js
CHANGED
|
@@ -510,7 +510,7 @@
|
|
| 510 |
enc.copyBufferToBuffer(bufC, 0, read, 0, bytesC);
|
| 511 |
device.queue.submit([enc.finish()]);
|
| 512 |
await read.mapAsync(GPUMapMode.READ);
|
| 513 |
-
const out = new Int32Array(read.getMappedRange().slice(0));
|
| 514 |
read.unmap();
|
| 515 |
return { out, bufC, read };
|
| 516 |
}
|
|
@@ -524,7 +524,7 @@
|
|
| 524 |
{ binding: 0, resource: { buffer: bufX } }, { binding: 1, resource: { buffer: bufW } },
|
| 525 |
{ binding: 2, resource: { buffer: lutBuf } }, { binding: 3, resource: { buffer: bufC } },
|
| 526 |
{ binding: 4, resource: { buffer: bufD } } ], m, n);
|
| 527 |
-
[bufX, bufW, bufD, r.bufC, r.read]
|
| 528 |
return r.out;
|
| 529 |
}
|
| 530 |
|
|
@@ -540,7 +540,7 @@
|
|
| 540 |
{ binding: 0, resource: { buffer: bufQ } }, { binding: 1, resource: { buffer: bufK } },
|
| 541 |
{ binding: 2, resource: { buffer: bufQs } }, { binding: 3, resource: { buffer: bufKs } },
|
| 542 |
{ binding: 4, resource: { buffer: bufO } }, { binding: 5, resource: { buffer: bufD } } ], T, T, B * heads, d.acc);
|
| 543 |
-
[bufQ, bufK, bufQs, bufKs, bufD, r.bufO, r.read]
|
| 544 |
return r.out;
|
| 545 |
}
|
| 546 |
async function gpuAttCtx(device, pipeline, aq, vq, as, vs, d) {
|
|
@@ -554,7 +554,7 @@
|
|
| 554 |
{ binding: 0, resource: { buffer: bufA } }, { binding: 1, resource: { buffer: bufV } },
|
| 555 |
{ binding: 2, resource: { buffer: bufAs } }, { binding: 3, resource: { buffer: bufVs } },
|
| 556 |
{ binding: 4, resource: { buffer: bufO } }, { binding: 5, resource: { buffer: bufD } } ], T, hd, B * heads, d.acc);
|
| 557 |
-
[bufA, bufV, bufAs, bufVs, bufD, r.bufO, r.read]
|
| 558 |
return r.out;
|
| 559 |
}
|
| 560 |
|
|
@@ -585,9 +585,9 @@
|
|
| 585 |
enc.copyBufferToBuffer(bufO, 0, read, 0, m * n * 4);
|
| 586 |
device.queue.submit([enc.finish()]);
|
| 587 |
await read.mapAsync(GPUMapMode.READ);
|
| 588 |
-
const out = new Float32Array(read.getMappedRange().slice(0));
|
| 589 |
read.unmap();
|
| 590 |
-
[bufA, bufB, bufP, bufD1, bufO, bufD2, read]
|
| 591 |
return out;
|
| 592 |
}
|
| 593 |
|
|
@@ -605,7 +605,7 @@
|
|
| 605 |
enc.copyBufferToBuffer(bufO, 0, read, 0, bytesO);
|
| 606 |
device.queue.submit([enc.finish()]);
|
| 607 |
await read.mapAsync(GPUMapMode.READ);
|
| 608 |
-
const buf = read.getMappedRange().slice(0);
|
| 609 |
const out = raw ? new Int32Array(buf) : new Float32Array(buf);
|
| 610 |
read.unmap();
|
| 611 |
return { out, bufO, read };
|
|
@@ -623,7 +623,7 @@
|
|
| 623 |
{ binding: 2, resource: { buffer: lutBuf } }, { binding: 3, resource: { buffer: bufR } },
|
| 624 |
{ binding: 4, resource: { buffer: bufS } }, { binding: 5, resource: { buffer: bufO } },
|
| 625 |
{ binding: 6, resource: { buffer: bufD } } ], m, n, batch, d.acc);
|
| 626 |
-
[bufX, bufW, bufR, bufS, bufD, r.bufO, r.read]
|
| 627 |
return r.out;
|
| 628 |
}
|
| 629 |
|
|
@@ -645,7 +645,7 @@
|
|
| 645 |
{ binding: 0, resource: { buffer: bufX } }, { binding: 1, resource: { buffer: bufW } },
|
| 646 |
{ binding: 2, resource: { buffer: bufR } }, { binding: 3, resource: { buffer: bufS } },
|
| 647 |
{ binding: 4, resource: { buffer: bufO } }, { binding: 5, resource: { buffer: bufD } } ], m, n, batch, d.acc);
|
| 648 |
-
[bufX, bufW, bufR, bufS, bufD, r.bufO, r.read]
|
| 649 |
return r.out;
|
| 650 |
}
|
| 651 |
|
|
@@ -670,7 +670,9 @@
|
|
| 670 |
}
|
| 671 |
const bufRs = up(device, xs, SU), bufCs1 = up(device, w1s, SU), bufCs2 = up(device, w2s, SU);
|
| 672 |
const bufH = mk(device, m * h * 4, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC);
|
| 673 |
-
|
|
|
|
|
|
|
| 674 |
const UU = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST;
|
| 675 |
const bufD1 = up(device, new Uint32Array([m, kw1, h, 1]), UU); // flags=1: fused ReLU
|
| 676 |
const bufDM = up(device, new Uint32Array([m, h, 0, 0]), UU);
|
|
@@ -680,6 +682,7 @@
|
|
| 680 |
: [bufA, bufB, env.lutBuf, bufR, bufC, bufO, bufD])
|
| 681 |
.map((b, i) => ({ binding: i, resource: { buffer: b } })) });
|
| 682 |
const enc1 = device.createCommandEncoder();
|
|
|
|
| 683 |
let pass = enc1.beginComputePass();
|
| 684 |
pass.setPipeline(env.gemm);
|
| 685 |
pass.setBindGroup(0, gemmBind(bufX, bufW1, bufRs, bufCs1, bufH, bufD1));
|
|
@@ -696,9 +699,9 @@
|
|
| 696 |
enc1.copyBufferToBuffer(bufMX, 0, readM, 0, m * 4);
|
| 697 |
device.queue.submit([enc1.finish()]);
|
| 698 |
await Promise.all([readH.mapAsync(GPUMapMode.READ), readM.mapAsync(GPUMapMode.READ)]);
|
| 699 |
-
const h1 = new Float32Array(readH.getMappedRange().slice(0)); readH.unmap();
|
| 700 |
// the atomicMax'ed u32 bit patterns ARE the f32 |max| values
|
| 701 |
-
const mx = new Float32Array(readM.getMappedRange().slice(0)); readM.unmap();
|
| 702 |
// scale derivation in JS f64 — exactly rounded, identical on every device
|
| 703 |
// (WGSL division is 2.5 ULP, which is why it never runs on the GPU)
|
| 704 |
const sc = root.Verified.scalesFromAbsMax(mx);
|
|
@@ -722,18 +725,53 @@
|
|
| 722 |
enc2.copyBufferToBuffer(bufO, 0, readO, 0, m * n * 4);
|
| 723 |
device.queue.submit([enc2.finish()]);
|
| 724 |
await readO.mapAsync(GPUMapMode.READ);
|
| 725 |
-
const out = new Float32Array(readO.getMappedRange().slice(0)); readO.unmap();
|
| 726 |
-
[bufX, bufW1, bufW2, bufRs, bufCs1, bufCs2, bufH, bufMX, bufD1, bufDM,
|
| 727 |
-
bufInv, bufHs, bufQ, bufDQ, bufD2, bufO, readH, readM, readO]
|
| 728 |
return { h1, out };
|
| 729 |
}
|
| 730 |
|
| 731 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 732 |
function up(device, arr, usage) {
|
| 733 |
const b = mk(device, Math.max(16, arr.byteLength), usage);
|
| 734 |
device.queue.writeBuffer(b, 0, arr);
|
| 735 |
return b;
|
| 736 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 737 |
|
| 738 |
// ---- canonical kernel probe -------------------------------------------------
|
| 739 |
// The weight hash CANNOT catch a device whose kernel is wrong: weights only
|
|
|
|
| 510 |
enc.copyBufferToBuffer(bufC, 0, read, 0, bytesC);
|
| 511 |
device.queue.submit([enc.finish()]);
|
| 512 |
await read.mapAsync(GPUMapMode.READ);
|
| 513 |
+
const out = new Int32Array(read.getMappedRange(0, bytesC).slice(0)); // pooled: map only the logical range
|
| 514 |
read.unmap();
|
| 515 |
return { out, bufC, read };
|
| 516 |
}
|
|
|
|
| 524 |
{ binding: 0, resource: { buffer: bufX } }, { binding: 1, resource: { buffer: bufW } },
|
| 525 |
{ binding: 2, resource: { buffer: lutBuf } }, { binding: 3, resource: { buffer: bufC } },
|
| 526 |
{ binding: 4, resource: { buffer: bufD } } ], m, n);
|
| 527 |
+
release(device, [bufX, bufW, bufD, r.bufC, r.read]);
|
| 528 |
return r.out;
|
| 529 |
}
|
| 530 |
|
|
|
|
| 540 |
{ binding: 0, resource: { buffer: bufQ } }, { binding: 1, resource: { buffer: bufK } },
|
| 541 |
{ binding: 2, resource: { buffer: bufQs } }, { binding: 3, resource: { buffer: bufKs } },
|
| 542 |
{ binding: 4, resource: { buffer: bufO } }, { binding: 5, resource: { buffer: bufD } } ], T, T, B * heads, d.acc);
|
| 543 |
+
release(device, [bufQ, bufK, bufQs, bufKs, bufD, r.bufO, r.read]);
|
| 544 |
return r.out;
|
| 545 |
}
|
| 546 |
async function gpuAttCtx(device, pipeline, aq, vq, as, vs, d) {
|
|
|
|
| 554 |
{ binding: 0, resource: { buffer: bufA } }, { binding: 1, resource: { buffer: bufV } },
|
| 555 |
{ binding: 2, resource: { buffer: bufAs } }, { binding: 3, resource: { buffer: bufVs } },
|
| 556 |
{ binding: 4, resource: { buffer: bufO } }, { binding: 5, resource: { buffer: bufD } } ], T, hd, B * heads, d.acc);
|
| 557 |
+
release(device, [bufA, bufV, bufAs, bufVs, bufD, r.bufO, r.read]);
|
| 558 |
return r.out;
|
| 559 |
}
|
| 560 |
|
|
|
|
| 585 |
enc.copyBufferToBuffer(bufO, 0, read, 0, m * n * 4);
|
| 586 |
device.queue.submit([enc.finish()]);
|
| 587 |
await read.mapAsync(GPUMapMode.READ);
|
| 588 |
+
const out = new Float32Array(read.getMappedRange(0, m * n * 4).slice(0)); // pooled: logical range only
|
| 589 |
read.unmap();
|
| 590 |
+
release(device, [bufA, bufB, bufP, bufD1, bufO, bufD2, read]);
|
| 591 |
return out;
|
| 592 |
}
|
| 593 |
|
|
|
|
| 605 |
enc.copyBufferToBuffer(bufO, 0, read, 0, bytesO);
|
| 606 |
device.queue.submit([enc.finish()]);
|
| 607 |
await read.mapAsync(GPUMapMode.READ);
|
| 608 |
+
const buf = read.getMappedRange(0, bytesO).slice(0); // pooled: logical range only
|
| 609 |
const out = raw ? new Int32Array(buf) : new Float32Array(buf);
|
| 610 |
read.unmap();
|
| 611 |
return { out, bufO, read };
|
|
|
|
| 623 |
{ binding: 2, resource: { buffer: lutBuf } }, { binding: 3, resource: { buffer: bufR } },
|
| 624 |
{ binding: 4, resource: { buffer: bufS } }, { binding: 5, resource: { buffer: bufO } },
|
| 625 |
{ binding: 6, resource: { buffer: bufD } } ], m, n, batch, d.acc);
|
| 626 |
+
release(device, [bufX, bufW, bufR, bufS, bufD, r.bufO, r.read]);
|
| 627 |
return r.out;
|
| 628 |
}
|
| 629 |
|
|
|
|
| 645 |
{ binding: 0, resource: { buffer: bufX } }, { binding: 1, resource: { buffer: bufW } },
|
| 646 |
{ binding: 2, resource: { buffer: bufR } }, { binding: 3, resource: { buffer: bufS } },
|
| 647 |
{ binding: 4, resource: { buffer: bufO } }, { binding: 5, resource: { buffer: bufD } } ], m, n, batch, d.acc);
|
| 648 |
+
release(device, [bufX, bufW, bufR, bufS, bufD, r.bufO, r.read]);
|
| 649 |
return r.out;
|
| 650 |
}
|
| 651 |
|
|
|
|
| 670 |
}
|
| 671 |
const bufRs = up(device, xs, SU), bufCs1 = up(device, w1s, SU), bufCs2 = up(device, w2s, SU);
|
| 672 |
const bufH = mk(device, m * h * 4, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC);
|
| 673 |
+
// rowmax accumulates with atomicMax and NEEDS zeros. A fresh buffer was
|
| 674 |
+
// zero-initialized; a POOLED buffer is not — cleared in the encoder below.
|
| 675 |
+
const bufMX = mk(device, m * 4, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST);
|
| 676 |
const UU = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST;
|
| 677 |
const bufD1 = up(device, new Uint32Array([m, kw1, h, 1]), UU); // flags=1: fused ReLU
|
| 678 |
const bufDM = up(device, new Uint32Array([m, h, 0, 0]), UU);
|
|
|
|
| 682 |
: [bufA, bufB, env.lutBuf, bufR, bufC, bufO, bufD])
|
| 683 |
.map((b, i) => ({ binding: i, resource: { buffer: b } })) });
|
| 684 |
const enc1 = device.createCommandEncoder();
|
| 685 |
+
enc1.clearBuffer(bufMX, 0, m * 4); // pooled buffer: zero the atomicMax accumulator
|
| 686 |
let pass = enc1.beginComputePass();
|
| 687 |
pass.setPipeline(env.gemm);
|
| 688 |
pass.setBindGroup(0, gemmBind(bufX, bufW1, bufRs, bufCs1, bufH, bufD1));
|
|
|
|
| 699 |
enc1.copyBufferToBuffer(bufMX, 0, readM, 0, m * 4);
|
| 700 |
device.queue.submit([enc1.finish()]);
|
| 701 |
await Promise.all([readH.mapAsync(GPUMapMode.READ), readM.mapAsync(GPUMapMode.READ)]);
|
| 702 |
+
const h1 = new Float32Array(readH.getMappedRange(0, m * h * 4).slice(0)); readH.unmap();
|
| 703 |
// the atomicMax'ed u32 bit patterns ARE the f32 |max| values
|
| 704 |
+
const mx = new Float32Array(readM.getMappedRange(0, m * 4).slice(0)); readM.unmap();
|
| 705 |
// scale derivation in JS f64 — exactly rounded, identical on every device
|
| 706 |
// (WGSL division is 2.5 ULP, which is why it never runs on the GPU)
|
| 707 |
const sc = root.Verified.scalesFromAbsMax(mx);
|
|
|
|
| 725 |
enc2.copyBufferToBuffer(bufO, 0, readO, 0, m * n * 4);
|
| 726 |
device.queue.submit([enc2.finish()]);
|
| 727 |
await readO.mapAsync(GPUMapMode.READ);
|
| 728 |
+
const out = new Float32Array(readO.getMappedRange(0, m * n * 4).slice(0)); readO.unmap();
|
| 729 |
+
release(device, [bufX, bufW1, bufW2, bufRs, bufCs1, bufCs2, bufH, bufMX, bufD1, bufDM,
|
| 730 |
+
bufInv, bufHs, bufQ, bufDQ, bufD2, bufO, readH, readM, readO]);
|
| 731 |
return { h1, out };
|
| 732 |
}
|
| 733 |
|
| 734 |
+
// ---- buffer pool ------------------------------------------------------------
|
| 735 |
+
// Every dispatch used to create and destroy its buffers — ~19 create/destroy
|
| 736 |
+
// pairs per MLP chain call, per layer, per step. Creation cost is pure driver
|
| 737 |
+
// overhead, and at these GEMM sizes it is a large share of the step. The pool
|
| 738 |
+
// recycles buffers by (usage, size-bucket): same bytes uploaded, same regions
|
| 739 |
+
// read, ZERO math change — the kernels only ever index inside the logical
|
| 740 |
+
// dims, so the stale tail of a bucketed buffer is never read. The one
|
| 741 |
+
// exception is a buffer a kernel expects ZEROED (the rowmax accumulator):
|
| 742 |
+
// pooled buffers are NOT fresh, so that one is cleared explicitly in the
|
| 743 |
+
// encoder (see gpuMlpChain). Correctness is enforced where it always was:
|
| 744 |
+
// the exact init gates, the live audit, and the per-step probe hash all run
|
| 745 |
+
// through these pooled paths.
|
| 746 |
+
const _pools = new WeakMap(); // device -> Map(key -> free list)
|
| 747 |
+
function _poolOf(device) {
|
| 748 |
+
let p = _pools.get(device);
|
| 749 |
+
if (!p) { p = new Map(); _pools.set(device, p); }
|
| 750 |
+
return p;
|
| 751 |
+
}
|
| 752 |
+
function mk(device, size, usage) {
|
| 753 |
+
let cap = 256; while (cap < size) cap *= 2;
|
| 754 |
+
const key = usage + ":" + cap;
|
| 755 |
+
const list = _poolOf(device).get(key);
|
| 756 |
+
if (list && list.length) return list.pop();
|
| 757 |
+
const b = device.createBuffer({ size: cap, usage });
|
| 758 |
+
b._poolKey = key;
|
| 759 |
+
return b;
|
| 760 |
+
}
|
| 761 |
function up(device, arr, usage) {
|
| 762 |
const b = mk(device, Math.max(16, arr.byteLength), usage);
|
| 763 |
device.queue.writeBuffer(b, 0, arr);
|
| 764 |
return b;
|
| 765 |
}
|
| 766 |
+
function release(device, bufs) {
|
| 767 |
+
const p = _poolOf(device);
|
| 768 |
+
for (const b of bufs) {
|
| 769 |
+
if (!b || !b._poolKey) { if (b) b.destroy(); continue; }
|
| 770 |
+
let list = p.get(b._poolKey);
|
| 771 |
+
if (!list) { list = []; p.set(b._poolKey, list); }
|
| 772 |
+
if (list.length < 64) list.push(b); else b.destroy();
|
| 773 |
+
}
|
| 774 |
+
}
|
| 775 |
|
| 776 |
// ---- canonical kernel probe -------------------------------------------------
|
| 777 |
// The weight hash CANNOT catch a device whose kernel is wrong: weights only
|