Spaces:
Running
Running
per-pass GPU trace ?bench=trace: name the non-weight overhead
Browse files- 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); }
|