Humuhumu33 commited on
Commit
dabcb0d
·
verified ·
1 Parent(s): d2afec3

discrete-GPU validation ?bench=discrete: bandwidth + live + spec-flip in one page

Browse files
Files changed (1) hide show
  1. index.html +74 -0
index.html CHANGED
@@ -220,6 +220,79 @@ async function runPerfBench() {
220
  log.innerHTML = tbl; st.textContent = "live decode profile · done"; console.log("[perfbench]", runs);
221
  }
222
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  // ── PER-PASS GPU TRACE (?bench=trace) ── where does a token's ~40ms go? Runs a profiled forward through the
224
  // step() path (window.__profile → timestamp-query'd ns per pass) and dumps the breakdown sorted by cost, so the
225
  // 83% non-weight overhead is named exactly (attention / argmax / lm_head / norms / dispatch count) — no guessing.
@@ -265,6 +338,7 @@ try {
265
  if (params.get("bench") === "spec") { globalThis.__spec = false; await runSpecBench(); }
266
  else if (params.get("bench") === "perf") { await runPerfBench(); }
267
  else if (params.get("bench") === "trace") { await runTraceBench(); }
 
268
  else if (pending) { const w = [...log.querySelectorAll(".a")].reverse().find((x) => x.dataset.pending); if (w) w.remove(); const p = pending; pending = null; generate(p, true); }
269
  else await proactiveGreeting();
270
  } catch (e) { st.textContent = "⚠ " + e.message; bubble("a", "Could not start: " + e.message); }
 
220
  log.innerHTML = tbl; st.textContent = "live decode profile · done"; console.log("[perfbench]", runs);
221
  }
222
 
223
+ // ── DISCRETE-GPU VALIDATION (?bench=discrete) ── the whole thesis in one page, on whatever GPU opens it:
224
+ // (1) real VRAM bandwidth + decode roofline, (2) live BitNet tok/s warmed, (3) does spec-decode FLIP from the
225
+ // iGPU loss to a win once bandwidth-bound? Confirms the discrete GPU (bandwidth >350 GB/s) is actually in use.
226
+ const BW_FILL = `@group(0) @binding(0) var<storage,read_write> d: array<u32>;
227
+ @group(0) @binding(1) var<uniform> P: vec4<u32>;
228
+ @compute @workgroup_size(256) fn main(@builtin(global_invocation_id) g:vec3<u32>){ let n=P.x; var i=g.x; loop{ if(i>=n){break;} d[i]=(i*2654435761u+1u); i=i+P.y; } }`;
229
+ const BW_READ = `@group(0) @binding(0) var<storage,read> d: array<vec4<u32>>;
230
+ @group(0) @binding(1) var<storage,read_write> sink: array<u32>;
231
+ @group(0) @binding(2) var<uniform> P: vec4<u32>;
232
+ @compute @workgroup_size(256) fn main(@builtin(global_invocation_id) g:vec3<u32>){ let n=P.x; let stride=P.y; var acc=vec4<u32>(0u); var i=g.x; loop{ if(i>=n){break;} acc=acc^d[i]; i=i+stride; } sink[g.x]=acc.x^acc.y^acc.z^acc.w; }`;
233
+ async function measureVramBW(dev) {
234
+ const L = dev.limits;
235
+ const bytes = Math.floor(Math.min(L.maxStorageBufferBindingSize, L.maxBufferSize, 512 * 1024 * 1024) / 16) * 16, nVec = bytes / 16;
236
+ const buf = dev.createBuffer({ size: bytes, usage: GPUBufferUsage.STORAGE });
237
+ const wg = Math.min(L.maxComputeWorkgroupsPerDimension, 65535), TOTAL = wg * 256;
238
+ const sink = dev.createBuffer({ size: TOTAL * 4, usage: GPUBufferUsage.STORAGE });
239
+ const P = dev.createBuffer({ size: 16, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
240
+ dev.queue.writeBuffer(P, 0, new Uint32Array([bytes / 4, TOTAL, 0, 0]));
241
+ const fp = dev.createComputePipeline({ layout: "auto", compute: { module: dev.createShaderModule({ code: BW_FILL }), entryPoint: "main" } });
242
+ const fbg = dev.createBindGroup({ layout: fp.getBindGroupLayout(0), entries: [{ binding: 0, resource: { buffer: buf } }, { binding: 1, resource: { buffer: P } }] });
243
+ { const e = dev.createCommandEncoder(); const p = e.beginComputePass(); p.setPipeline(fp); p.setBindGroup(0, fbg); p.dispatchWorkgroups(wg); p.end(); dev.queue.submit([e.finish()]); await dev.queue.onSubmittedWorkDone(); }
244
+ dev.queue.writeBuffer(P, 0, new Uint32Array([nVec, TOTAL, 0, 0]));
245
+ const rp = dev.createComputePipeline({ layout: "auto", compute: { module: dev.createShaderModule({ code: BW_READ }), entryPoint: "main" } });
246
+ const rbg = dev.createBindGroup({ layout: rp.getBindGroupLayout(0), entries: [{ binding: 0, resource: { buffer: buf } }, { binding: 1, resource: { buffer: sink } }, { binding: 2, resource: { buffer: P } }] });
247
+ const run = async (passes) => { const e = dev.createCommandEncoder(); for (let k = 0; k < passes; k++) { const p = e.beginComputePass(); p.setPipeline(rp); p.setBindGroup(0, rbg); p.dispatchWorkgroups(wg); p.end(); } const t0 = performance.now(); dev.queue.submit([e.finish()]); await dev.queue.onSubmittedWorkDone(); return performance.now() - t0; };
248
+ await run(4); let best = 1e9; for (let k = 0; k < 5; k++) best = Math.min(best, await run(32));
249
+ buf.destroy(); sink.destroy();
250
+ return (bytes * 32 / 1073741824) / (best / 1000);
251
+ }
252
+ async function runDiscreteBench() {
253
+ log.innerHTML = ""; input.disabled = send.disabled = true;
254
+ const rep = m.rep ?? 1.3, gpu = engine._gpu, dev = gpu && gpu._dev && gpu._dev();
255
+ let adapterStr = "unknown";
256
+ try { const a = await navigator.gpu.requestAdapter({ powerPreference: "high-performance" }); const inf = (a && a.info) || {}; adapterStr = ((inf.vendor || "") + " " + (inf.architecture || "") + " " + (inf.device || "")).trim() || "unknown"; } catch {}
257
+ st.textContent = "measuring VRAM bandwidth…";
258
+ let gbps = 0; try { gbps = await measureVramBW(dev); } catch (e) { console.warn("bw", e); }
259
+ const roofTok = gbps / 0.69, discrete = gbps > 350;
260
+ st.textContent = "warming + measuring live decode…";
261
+ globalThis.__spec = false;
262
+ engine.reset(); await engine.generate(engine.tokenize(engine.frameTurn("Write one sentence about the sea.", false)), { maxNew: 48, repPenalty: rep });
263
+ let live = 0;
264
+ for (let i = 0; i < 3; i++) { engine.reset(); const r = await engine.generate(engine.tokenize(engine.frameTurn("Write a detailed paragraph about ocean currents.", false)), { maxNew: 128, repPenalty: rep }); live = Math.max(live, (r.stats && r.stats.tokps) || 0); }
265
+ st.textContent = "measuring spec-decode flip…";
266
+ const sIds = engine.tokenize(engine.frameTurn("Passage: \"The quick brown fox jumps over the lazy dog near the river bank at dawn.\" Repeat that passage back to me word for word.", false));
267
+ globalThis.__spec = false; engine.reset(); let t0 = performance.now(); const rb = await engine.generate(sIds.slice(), { maxNew: 192, repPenalty: rep }); const bTok = rb.outIds.length / ((performance.now() - t0) / 1000);
268
+ let sTok = 0, perVerify = 0, accept = 0, same = true, hasSpec = !!engine.specAvailable;
269
+ if (hasSpec) {
270
+ globalThis.__spec = true; engine.reset(); t0 = performance.now(); const rs = await engine.generate(sIds.slice(), { maxNew: 192, repPenalty: rep }); sTok = rs.outIds.length / ((performance.now() - t0) / 1000); globalThis.__spec = false;
271
+ const sp = (rs.stats && rs.stats.spec) || null;
272
+ perVerify = sp && sp.windows ? 1 + sp.accepted / sp.windows : 0; accept = sp && sp.drafted ? 100 * sp.accepted / sp.drafted : 0;
273
+ same = rb.outIds.length === rs.outIds.length && rb.outIds.every((x, i) => x === rs.outIds[i]);
274
+ }
275
+ const flip = hasSpec && sTok > bTok * 1.1, spRatio = bTok ? sTok / bTok : 0;
276
+ const row = (k, v) => `<tr style="border-top:1px solid var(--line)"><td style="padding:7px 8px;color:var(--dim)">${k}</td><td style="padding:7px 8px;font-weight:600">${v}</td></tr>`;
277
+ log.innerHTML = `<div style="font-family:ui-monospace,monospace;font-size:13px;max-width:820px;margin:0 auto;padding:8px">
278
+ <div style="font-size:18px;font-weight:700;margin-bottom:4px">Discrete-GPU validation</div>
279
+ <div style="color:var(--dim);margin-bottom:12px">adapter: ${adapterStr} · BitNet-2B</div>
280
+ <table style="width:100%;border-collapse:collapse">
281
+ ${row("GPU in use", `<span style="color:${discrete ? "#48c26c" : "#e0a94a"}">${discrete ? "DISCRETE ✓" : "integrated (bandwidth "+gbps.toFixed(0)+" GB/s — not a discrete GPU)"}</span>`)}
282
+ ${row("VRAM bandwidth", `${gbps.toFixed(0)} GB/s`)}
283
+ ${row("Decode roofline", `${roofTok.toFixed(0)} tok/s`)}
284
+ ${row("Live decode (warmed)", `${live.toFixed(0)} tok/s · ${(100*live/roofTok).toFixed(0)}% of roofline`)}
285
+ ${row("Spec-decode (retrieval)", hasSpec ? `${sTok.toFixed(0)} vs ${bTok.toFixed(0)} baseline · <b style="color:${flip ? "#48c26c" : "#e0a94a"}">${spRatio.toFixed(2)}×</b> · ${perVerify.toFixed(2)} tok/verify · ${accept.toFixed(0)}% accept · ${same ? "byte-exact ✓" : "DIVERGED ✗"}` : "unavailable")}
286
+ </table>
287
+ <div style="margin-top:14px;font-size:15px;font-weight:600;color:${flip ? "#48c26c" : "#e0a94a"}">${!discrete
288
+ ? "⚠ This is still an integrated GPU (bandwidth ≤350 GB/s). Open on a machine with a discrete GPU to validate the >1000 path — the browser may be picking the iGPU for power saving."
289
+ : flip
290
+ ? `✓ CONFIRMED: on discrete silicon spec-decode FLIPPED to a ${spRatio.toFixed(2)}× win (byte-exact), and the roofline is ${roofTok.toFixed(0)} tok/s. The code we built delivers the high-throughput path unchanged on this hardware.`
291
+ : `Spec-decode is ${spRatio.toFixed(2)}× here — not yet a clear win. Bandwidth ${gbps.toFixed(0)} GB/s (roofline ${roofTok.toFixed(0)}); the forward may still be compute-bound on this GPU. Send me the numbers.`}</div>
292
+ </div>`;
293
+ st.textContent = "discrete validation · done"; console.log("[discrete]", { adapterStr, gbps, roofTok, live, bTok, sTok, perVerify, accept, same });
294
+ }
295
+
296
  // ── PER-PASS GPU TRACE (?bench=trace) ── where does a token's ~40ms go? Runs a profiled forward through the
297
  // step() path (window.__profile → timestamp-query'd ns per pass) and dumps the breakdown sorted by cost, so the
298
  // 83% non-weight overhead is named exactly (attention / argmax / lm_head / norms / dispatch count) — no guessing.
 
338
  if (params.get("bench") === "spec") { globalThis.__spec = false; await runSpecBench(); }
339
  else if (params.get("bench") === "perf") { await runPerfBench(); }
340
  else if (params.get("bench") === "trace") { await runTraceBench(); }
341
+ else if (params.get("bench") === "discrete") { globalThis.__spec = false; await runDiscreteBench(); }
342
  else if (pending) { const w = [...log.querySelectorAll(".a")].reverse().find((x) => x.dataset.pending); if (w) w.remove(); const p = pending; pending = null; generate(p, true); }
343
  else await proactiveGreeting();
344
  } catch (e) { st.textContent = "⚠ " + e.message; bubble("a", "Could not start: " + e.message); }