alias3-web / index.html
vmayoral's picture
alias3-web: 4-bit WebGPU graph, readout files, library and demo
2ab2e60 verified
Raw History Blame Contribute Delete
10.4 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>alias3-web · a cybersecurity transformer in your browser</title>
<style>
:root { --ink:#16202A; --muted:#5B6875; --rule:#D6DCE2; --bg:#F4F6F8; --card:#FFF;
--accent:#1F4E8C; --accent-soft:#E3EBF6; --good:#0ca30c; --mono:ui-monospace,Menlo,Consolas,monospace; }
* { box-sizing:border-box }
body { margin:0; background:var(--bg); color:var(--ink);
font:15px/1.5 -apple-system,"Segoe UI",Roboto,sans-serif; }
main { max-width:900px; margin:0 auto; padding:28px 18px 80px }
h1 { font-size:1.5rem; margin:.2em 0 .3em }
h1 small { color:var(--muted); font-weight:400; font-size:.95rem }
.sub { color:var(--muted); margin:0 0 18px }
.card { background:var(--card); border:1px solid var(--rule); border-radius:12px; padding:16px 18px; margin:14px 0 }
label { font-size:.85rem; color:var(--muted); display:block; margin-bottom:4px }
textarea, input[type=text] { width:100%; border:1px solid var(--rule); border-radius:8px; padding:8px 10px;
font:12.5px/1.45 var(--mono); background:#fbfcfd; color:var(--ink) }
textarea { min-height:120px; resize:vertical }
.row { display:flex; gap:10px; flex-wrap:wrap; align-items:flex-end }
.row > div { flex:1; min-width:180px }
button { background:var(--accent); color:#fff; border:0; border-radius:8px; padding:10px 18px;
font-size:.95rem; cursor:pointer; margin:12px 8px 0 0 }
button.secondary { background:var(--accent-soft); color:var(--accent) }
button:disabled { opacity:.45; cursor:wait }
#status { font-size:.9rem; color:var(--muted); margin-top:10px }
.result { margin-top:14px }
.bar-row { display:flex; align-items:center; gap:10px; margin:5px 0 }
.bar-label { width:190px; font-size:.85rem; text-align:right; overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
.bar-track { flex:1; background:#eef1f4; border-radius:6px; height:20px; position:relative }
.bar-fill { height:100%; border-radius:6px; background:var(--accent); transition:width .25s }
.bar-fill.win { background:var(--good) }
.bar-val { width:64px; font-size:.85rem; font-family:var(--mono) }
.kpis { display:flex; gap:12px; flex-wrap:wrap; margin-top:12px }
.kpi { background:var(--accent-soft); border-radius:10px; padding:10px 14px; min-width:120px }
.kpi b { display:block; font-size:1.5rem; color:var(--accent) }
.kpi span { font-size:.8rem; color:var(--muted) }
.pill { display:inline-block; background:var(--accent-soft); color:var(--accent); border-radius:20px;
padding:2px 10px; font-size:.78rem; margin:2px 4px 2px 0; font-family:var(--mono) }
details { margin-top:10px } summary { cursor:pointer; color:var(--muted); font-size:.85rem }
pre { background:#eef1f4; border-radius:8px; padding:10px; font-size:.75rem; overflow:auto }
.err { color:#b00020; font-family:var(--mono); font-size:.8rem; margin-top:8px; white-space:pre-wrap }
</style>
</head>
<body>
<main>
<h1>alias3-web <small>· typed security decisions and explanations, on your GPU (WebGPU)</small></h1>
<p class="sub">A 4B cybersecurity transformer quantised to 4 bits (ONNX Runtime GenAI, 2.44 GB) with the
<b>complete trained head</b> and the <b>model's own marker tokens</b>. Nothing leaves this page.
Prefill engine: <a href="https://github.com/eduardoworrel/ct0-edge">ct0-edge</a> by Eduardo Worrel (MIT), vendored.</p>
<div class="card">
<div class="row">
<div><label>State (the material to judge: code, a log, the source of a page…)</label>
<textarea id="state">const q = new URLSearchParams(location.search).get("q");
document.getElementById("out").innerHTML = "result: " + q;</textarea></div>
</div>
<div class="row" style="margin-top:10px">
<div><label>Question (yes/no and choice)</label>
<input type="text" id="q" value="Is user input written into the DOM without escaping?"></div>
<div><label>Options (choice, separated by ;)</label>
<input type="text" id="opts" value="dom-xss; sql-injection; ssrf; path-traversal"></div>
</div>
<button id="btn-load" class="secondary">1 · Load model (2.4 GB)</button>
<button id="btn-noul" disabled>Yes / No</button>
<button id="btn-choice" disabled>Choice</button>
<button id="btn-score" disabled>Severity 1-5</button>
<button id="btn-s2" disabled>Explanation (System 2)</button>
<div id="status">Press “Load model”. Needs Chrome or Edge with WebGPU.</div>
<div id="err" class="err"></div>
<details>
<summary>the marker tokens and the readout</summary>
<span class="pill">&lt;state&gt;→&lt;|fim_prefix|&gt;</span><span class="pill">&lt;q&gt;→&lt;|fim_middle|&gt;</span>
<span class="pill">&lt;opt&gt;→&lt;|box_start|&gt;</span><span class="pill">&lt;/opt&gt;→&lt;|box_end|&gt;</span>
<span class="pill">&lt;decide&gt;→&lt;|fim_suffix|&gt;</span>
<p style="font-size:.82rem;color:var(--muted)">readout: q(h&lt;decide&gt;)·k(h&lt;/opt&gt;ᵢ)·√256⁻¹/T, softmax over the options.
A yes/no question is a choice over (Yes, No) and severity a choice over (1..5): one mechanism, as trained.</p>
</details>
</div>
<div class="card result" id="result" style="display:none">
<div class="kpis">
<div class="kpi"><b id="k-top">·</b><span id="k-top-lbl">answer</span></div>
<div class="kpi"><b id="k-ms">·</b><span>total latency</span></div>
<div class="kpi"><b id="k-pref">·</b><span>prefill</span></div>
<div class="kpi"><b id="k-tok">·</b><span>tokens</span></div>
</div>
<div id="bars"></div>
<details><summary>raw</summary><pre id="raw"></pre></details>
</div>
</main>
<script type="importmap">
{ "imports": {
"ct0-edge": "./vendor/ct0.js",
"@huggingface/transformers": "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.7.5",
"onnxruntime-web/webgpu": "https://cdn.jsdelivr.net/npm/onnxruntime-web@1.30.0/dist/ort.webgpu.bundle.min.mjs",
"onnxruntime-web": "https://cdn.jsdelivr.net/npm/onnxruntime-web@1.30.0/dist/ort.webgpu.bundle.min.mjs"
} }
</script>
<script type="module">
import { AliasWeb } from "./aliasweb.mjs";
const $ = (id) => document.getElementById(id);
let model = null;
function bars(entries) {
$("bars").innerHTML = "";
const max = Math.max(...entries.map(([, v]) => v));
for (const [label, v] of entries) {
const row = document.createElement("div"); row.className = "bar-row";
const l = document.createElement("div"); l.className = "bar-label"; l.textContent = label; l.title = label;
const t = document.createElement("div"); t.className = "bar-track";
const f = document.createElement("div"); f.className = "bar-fill" + (v === max ? " win" : "");
f.style.width = (v * 100).toFixed(1) + "%";
const val = document.createElement("div"); val.className = "bar-val"; val.textContent = (v * 100).toFixed(1) + "%";
t.appendChild(f); row.append(l, t, val); $("bars").appendChild(row);
}
}
function show(r, topLabel, tok) {
$("result").style.display = "";
$("k-top").textContent = topLabel;
$("k-top-lbl").textContent = r.kind === "noul" ? "P(yes)" : r.kind === "score" ? "level" : "top";
$("k-ms").textContent = r.ms.toFixed(0) + " ms";
$("k-pref").textContent = r.prefillMs.toFixed(0) + " ms";
$("k-tok").textContent = tok;
const entries = r.kind === "noul" ? [["Yes", r.p], ["No", 1 - r.p]]
: r.kind === "score" ? Object.entries(r.per).map(([l, p]) => ["level " + l, p])
: Object.entries(r.choice);
bars(entries);
$("raw").textContent = JSON.stringify({ ...r, lastLogits: undefined }, null, 1).slice(0, 2000);
}
async function guard(fn) {
$("err").textContent = "";
if (!model) { $("err").textContent = "load the model first"; return; }
try { await fn(); } catch (e) { $("err").textContent = String(e?.stack || e); }
}
$("btn-load").onclick = async () => {
$("btn-load").disabled = true; $("err").textContent = "";
try {
model = await AliasWeb.load({ base: "./", status: (s) => $("status").textContent = s });
window.aliasweb = model;
for (const b of ["btn-noul", "btn-choice", "btn-score", "btn-s2"]) $(b).disabled = false;
$("status").textContent = "model ready · WebGPU active";
} catch (e) { $("err").textContent = String(e?.stack || e); $("btn-load").disabled = false; }
};
$("btn-noul").onclick = () => guard(async () => {
const state = $("state").value, q = $("q").value;
const r = await model.ask({ kind: "noul", state, q });
const enc = model.encode(state, q, ["Yes", "No"]);
show(r, (r.p * 100).toFixed(1) + "%", enc.ids.length);
});
$("btn-choice").onclick = () => guard(async () => {
const state = $("state").value;
const options = $("opts").value.split(";").map((s) => s.trim()).filter(Boolean);
const q = "Which vulnerability class does this code contain?";
const r = await model.ask({ kind: "choice", state, q, options });
const enc = model.encode(state, q, options);
show(r, r.top, enc.ids.length);
});
$("btn-s2").onclick = () => guard(async () => {
const state = $("state").value;
$("status").textContent = "System 2 is writing (about 10 tokens/s)…";
const r = await model.generate({
state,
prompt: "You are a security reviewer. Name the vulnerability in this code and the fix, in 2-3 sentences.",
maxNewTokens: 90, temperature: 0.7,
});
$("status").textContent = "model ready · WebGPU active";
$("result").style.display = "";
$("k-top").textContent = r.tokens + " tok";
$("k-top-lbl").textContent = "S2 · " + r.tokPerS + " tok/s";
$("k-ms").textContent = (r.ms / 1000).toFixed(1) + " s";
$("k-pref").textContent = "≈" + (r.ms / r.tokens).toFixed(0) + " ms/tok";
$("k-tok").textContent = "vs 0.15 s S1";
$("bars").innerHTML = "";
const p = document.createElement("p");
p.style.cssText = "font-size:.95rem;line-height:1.6";
p.textContent = r.text;
$("bars").appendChild(p);
$("raw").textContent = JSON.stringify({ tokens: r.tokens, tokPerS: r.tokPerS, ms: Math.round(r.ms) }, null, 1);
});
$("btn-score").onclick = () => guard(async () => {
const state = $("state").value;
const r = await model.ask({ kind: "score", state });
const enc = model.encode(state, "", ["1", "2", "3", "4", "5"]);
show(r, r.level + " / 5", enc.ids.length);
});
</script>
</body>
</html>