Humuhumu33 commited on
Commit
770541b
·
verified ·
1 Parent(s): 31a05bf

per-pass GPU trace ?bench=trace: name the non-weight overhead

Browse files
Files changed (1) hide show
  1. index.html +31 -0
index.html CHANGED
@@ -220,6 +220,36 @@ async function runPerfBench() {
220
  log.innerHTML = tbl; st.textContent = "live decode profile · done"; console.log("[perfbench]", runs);
221
  }
222
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  try {
224
  if (!navigator.gpu) throw new Error("This browser has no WebGPU — open in Chrome, Edge, or a recent mobile browser.");
225
  st.textContent = `loading ${m.name} (${m.size})…`;
@@ -234,6 +264,7 @@ try {
234
  input.placeholder = "Message Q…";
235
  if (params.get("bench") === "spec") { globalThis.__spec = false; await runSpecBench(); }
236
  else if (params.get("bench") === "perf") { await runPerfBench(); }
 
237
  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); }
238
  else await proactiveGreeting();
239
  } 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
+ // ── 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.
226
+ async function runTraceBench() {
227
+ log.innerHTML = ""; input.disabled = send.disabled = true;
228
+ const rep = m.rep ?? 1.3, gpu = engine._gpu;
229
+ if (!gpu || !gpu.generate) { st.textContent = "trace unavailable (no raw handle)"; bubble("a", "engine._gpu.generate missing"); return; }
230
+ const ids = engine.tokenize(engine.frameTurn("Write a detailed paragraph about how mountains form over geological time.", false));
231
+ globalThis.__spec = false;
232
+ st.textContent = "warming up (boosting clock)…";
233
+ gpu.reset(); await gpu.generate(ids.slice(), 40, rep); // warm → boost clock; __profileData will hold the LAST token's passes
234
+ st.textContent = "tracing a token…";
235
+ window.__profile = 1; gpu.reset(); await gpu.generate(ids.slice(), 24, rep); window.__profile = 0;
236
+ const pd = window.__profileData;
237
+ if (!pd || !pd.passes) { st.textContent = "no profile data (timestamp-query unsupported?)"; bubble("a", "window.__profileData empty — this GPU/browser may lack the timestamp-query feature."); return; }
238
+ const items = Object.entries(pd.passes).map(([tag, v]) => ({ tag, ms: v.ms, n: v.n })).sort((a, b) => b.ms - a.ms);
239
+ const tot = pd.passSumMs || items.reduce((s, x) => s + x.ms, 0);
240
+ const bar = (x) => Math.round(280 * x / (items[0].ms || 1));
241
+ const tbl = `<div style="font-family:ui-monospace,monospace;font-size:13px;max-width:820px;margin:0 auto;padding:8px">
242
+ <div style="font-size:18px;font-weight:700;margin-bottom:4px">Per-pass GPU trace — one token</div>
243
+ <div style="color:var(--dim);margin-bottom:12px">BitNet-2B · warmed · ${pd.nPasses} dispatches · GPU pass-sum ${tot.toFixed(1)} ms · wall-span ${(pd.gpuSpanMs||0).toFixed(1)} ms</div>
244
+ <table style="width:100%;border-collapse:collapse">
245
+ <tr style="color:var(--dim);text-align:left"><th style="padding:5px 8px">pass</th><th style="padding:5px 8px;text-align:right">GPU ms</th><th style="padding:5px 8px;text-align:right">% tok</th><th style="padding:5px 8px;text-align:right">count</th><th></th></tr>
246
+ ${items.map(x => `<tr style="border-top:1px solid var(--line)"><td style="padding:5px 8px">${x.tag}</td><td style="padding:5px 8px;text-align:right">${x.ms.toFixed(2)}</td><td style="padding:5px 8px;text-align:right">${(100*x.ms/tot).toFixed(0)}%</td><td style="padding:5px 8px;text-align:right">${x.n}</td><td style="padding:5px 8px"><span style="display:inline-block;height:9px;border-radius:2px;background:var(--q);width:${bar(x.ms)}px"></span></td></tr>`).join("")}
247
+ </table>
248
+ <div style="margin-top:12px;color:var(--dim)">The top rows are the lever. Weight-matmul passes that dominate ⇒ we're near bandwidth (little to win). Attention / argmax / lm_head / norms dominating ⇒ that's the non-weight overhead to cut (fuse passes, cheaper argmax, fewer dispatches).</div>
249
+ </div>`;
250
+ log.innerHTML = tbl; st.textContent = "per-pass trace · done"; console.log("[trace]", pd);
251
+ }
252
+
253
  try {
254
  if (!navigator.gpu) throw new Error("This browser has no WebGPU — open in Chrome, Edge, or a recent mobile browser.");
255
  st.textContent = `loading ${m.name} (${m.size})…`;
 
264
  input.placeholder = "Message Q…";
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); }