Spaces:
Running
Running
spec-decode for BitNet: subNorm+f32-KV batched verify, ?spec + ?bench=spec
Browse files- index.html +51 -1
index.html
CHANGED
|
@@ -50,6 +50,9 @@ let m = pick ? (MODELS.find((x) => new RegExp(pick, "i").test(x.name)) || MODELS
|
|
| 50 |
}
|
| 51 |
// ?verify=gpu → re-derive each BLAKE3 weight-block κ ENTIRELY on the GPU (2.74 GB/s) instead of pure-JS BLAKE3.
|
| 52 |
if (params.get("verify") === "gpu") globalThis.__gpuVerify = true;
|
|
|
|
|
|
|
|
|
|
| 53 |
|
| 54 |
// GROUND the model as on-device Q (a base/instruct model has NO self-knowledge — without this it confabulates
|
| 55 |
// a generic "I run on OpenAI/AWS cloud servers" identity, which is false). Injected as the SYSTEM turn.
|
|
@@ -129,6 +132,52 @@ send.onclick = onSend;
|
|
| 129 |
input.onkeydown = (e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); onSend(); } };
|
| 130 |
input.oninput = () => { input.style.height = "auto"; input.style.height = Math.min(140, input.scrollHeight) + "px"; };
|
| 131 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
try {
|
| 133 |
if (!navigator.gpu) throw new Error("This browser has no WebGPU — open in Chrome, Edge, or a recent mobile browser.");
|
| 134 |
st.textContent = `loading ${m.name} (${m.size})…`;
|
|
@@ -141,7 +190,8 @@ try {
|
|
| 141 |
armed = true;
|
| 142 |
st.textContent = `${m.name} · ${m.size} · in your browser · ready`;
|
| 143 |
input.placeholder = "Message Q…";
|
| 144 |
-
if (
|
|
|
|
| 145 |
else await proactiveGreeting();
|
| 146 |
} catch (e) { st.textContent = "⚠ " + e.message; bubble("a", "Could not start: " + e.message); }
|
| 147 |
</script></body></html>
|
|
|
|
| 50 |
}
|
| 51 |
// ?verify=gpu → re-derive each BLAKE3 weight-block κ ENTIRELY on the GPU (2.74 GB/s) instead of pure-JS BLAKE3.
|
| 52 |
if (params.get("verify") === "gpu") globalThis.__gpuVerify = true;
|
| 53 |
+
// ?spec → speculative decode (n-gram draft + batched-K verify). Byte-identical to greedy; big wins on echo-heavy
|
| 54 |
+
// text (code/quote/retrieval), no gain on free-form chat. ?bench=spec runs the A/B measurement harness after load.
|
| 55 |
+
if (params.get("spec") || params.get("bench") === "spec") globalThis.__spec = true;
|
| 56 |
|
| 57 |
// GROUND the model as on-device Q (a base/instruct model has NO self-knowledge — without this it confabulates
|
| 58 |
// a generic "I run on OpenAI/AWS cloud servers" identity, which is false). Injected as the SYSTEM turn.
|
|
|
|
| 132 |
input.onkeydown = (e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); onSend(); } };
|
| 133 |
input.oninput = () => { input.style.height = "auto"; input.style.height = Math.min(140, input.scrollHeight) + "px"; };
|
| 134 |
|
| 135 |
+
// ── SPEC-DECODE A/B HARNESS (?bench=spec) ── measures baseline greedy vs speculative on the operator's real
|
| 136 |
+
// GPU across echo-heavy and free-form prompts: byte-identical check (G1), mean accepted tokens/verify (G2),
|
| 137 |
+
// and decode tok/s for both. One load, one table — the honest verdict on whether spec-decode earns its place.
|
| 138 |
+
const SPEC_BENCH = [
|
| 139 |
+
{ tag: "code / edit (echo-heavy)", text: "Here is a function:\n\nfunction add(a, b) {\n return a + b;\n}\n\nRewrite it exactly the same but rename add to sum." },
|
| 140 |
+
{ tag: "retrieval / quote", text: "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." },
|
| 141 |
+
{ tag: "free-form chat", text: "In one short sentence, why is the sky blue?" },
|
| 142 |
+
];
|
| 143 |
+
const eqArr = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);
|
| 144 |
+
async function runSpecBench() {
|
| 145 |
+
log.innerHTML = ""; input.disabled = send.disabled = true;
|
| 146 |
+
if (!engine.specAvailable) { st.textContent = "spec-decode unavailable for this model"; bubble("a", "This model can't use the batched-verify head (specAvailable=false)."); return; }
|
| 147 |
+
const N = 96, rep = m.rep ?? 1.3, rows = [];
|
| 148 |
+
let prev = { windows: 0, drafted: 0, accepted: 0 };
|
| 149 |
+
for (const b of SPEC_BENCH) {
|
| 150 |
+
st.textContent = `bench: ${b.tag}…`;
|
| 151 |
+
const ids = engine.tokenize(frameSystem() + engine.frameTurn(b.text, false));
|
| 152 |
+
globalThis.__spec = false; engine.reset();
|
| 153 |
+
const t0 = performance.now(); const r0 = await engine.generate(ids.slice(), { maxNew: N, repPenalty: rep }); const w0 = performance.now() - t0;
|
| 154 |
+
globalThis.__spec = true; engine.reset();
|
| 155 |
+
const t1 = performance.now(); const r1 = await engine.generate(ids.slice(), { maxNew: N, repPenalty: rep }); const w1 = performance.now() - t1;
|
| 156 |
+
globalThis.__spec = false;
|
| 157 |
+
const cur = (r1.stats && r1.stats.spec) || prev;
|
| 158 |
+
const dd = { windows: cur.windows - prev.windows, drafted: cur.drafted - prev.drafted, accepted: cur.accepted - prev.accepted }; prev = { windows: cur.windows, drafted: cur.drafted, accepted: cur.accepted };
|
| 159 |
+
rows.push({
|
| 160 |
+
tag: b.tag, same: eqArr(r0.outIds, r1.outIds), nB: r0.outIds.length, nS: r1.outIds.length,
|
| 161 |
+
baseTok: r0.outIds.length / (w0 / 1000), specTok: r1.outIds.length / (w1 / 1000),
|
| 162 |
+
perVerify: dd.windows ? 1 + dd.accepted / dd.windows : 0, accept: dd.drafted ? dd.accepted / dd.drafted : 0, windows: dd.windows,
|
| 163 |
+
});
|
| 164 |
+
}
|
| 165 |
+
const allSame = rows.every((r) => r.same);
|
| 166 |
+
const fmt = (x) => x.toFixed(x < 10 ? 1 : 0);
|
| 167 |
+
const tbl = `<div style="font-family:ui-monospace,monospace;font-size:13px;max-width:900px;margin:0 auto;padding:8px">
|
| 168 |
+
<div style="font-size:18px;font-weight:700;margin-bottom:4px">Speculative decode — measured on your GPU</div>
|
| 169 |
+
<div style="color:var(--dim);margin-bottom:12px">BitNet-2B · n-gram draft + batched-K verify · greedy, byte-exact by construction</div>
|
| 170 |
+
<table style="width:100%;border-collapse:collapse">
|
| 171 |
+
<tr style="color:var(--dim);text-align:left"><th style="padding:6px 8px">workload</th><th style="padding:6px 8px;text-align:right">baseline</th><th style="padding:6px 8px;text-align:right">spec</th><th style="padding:6px 8px;text-align:right">speedup</th><th style="padding:6px 8px;text-align:right">tok/verify</th><th style="padding:6px 8px;text-align:right">accept</th><th style="padding:6px 8px;text-align:right">byte-exact</th></tr>
|
| 172 |
+
${rows.map((r) => `<tr style="border-top:1px solid var(--line)"><td style="padding:6px 8px">${r.tag}</td><td style="padding:6px 8px;text-align:right">${fmt(r.baseTok)} tok/s</td><td style="padding:6px 8px;text-align:right">${fmt(r.specTok)} tok/s</td><td style="padding:6px 8px;text-align:right;color:${r.specTok > r.baseTok * 1.05 ? "#48c26c" : r.specTok < r.baseTok * 0.95 ? "#f0616d" : "var(--dim)"}">${(r.specTok / r.baseTok).toFixed(2)}×</td><td style="padding:6px 8px;text-align:right">${r.perVerify.toFixed(2)}</td><td style="padding:6px 8px;text-align:right">${(r.accept * 100).toFixed(0)}%</td><td style="padding:6px 8px;text-align:right;color:${r.same ? "#48c26c" : "#f0616d"}">${r.same ? "✓ identical" : "✗ DIVERGED"}</td></tr>`).join("")}
|
| 173 |
+
</table>
|
| 174 |
+
<div style="margin-top:14px;font-weight:600;color:${allSame ? "#48c26c" : "#f0616d"}">${allSame ? "✓ G1 PASS — spec output is byte-identical to greedy on every prompt." : "✗ G1 FAIL — spec diverged from greedy; not shippable until fixed (see console)."}</div>
|
| 175 |
+
</div>`;
|
| 176 |
+
log.innerHTML = tbl;
|
| 177 |
+
st.textContent = "spec-decode bench · done";
|
| 178 |
+
console.log("[specbench]", rows);
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
try {
|
| 182 |
if (!navigator.gpu) throw new Error("This browser has no WebGPU — open in Chrome, Edge, or a recent mobile browser.");
|
| 183 |
st.textContent = `loading ${m.name} (${m.size})…`;
|
|
|
|
| 190 |
armed = true;
|
| 191 |
st.textContent = `${m.name} · ${m.size} · in your browser · ready`;
|
| 192 |
input.placeholder = "Message Q…";
|
| 193 |
+
if (params.get("bench") === "spec") { globalThis.__spec = false; await runSpecBench(); }
|
| 194 |
+
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); }
|
| 195 |
else await proactiveGreeting();
|
| 196 |
} catch (e) { st.textContent = "⚠ " + e.message; bubble("a", "Could not start: " + e.message); }
|
| 197 |
</script></body></html>
|