ferric-web 0.0.1

Ferric browser bindings — the same cross-fabric pure-Rust kernels running in WASM on WebGPU.
Documentation
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Ferric — a pure-Rust transformer in your browser (WebGPU)</title>
<style>
  :root { --bg:#0b0e14; --panel:#111725; --line:#1e2842; --ink:#e6ebf5; --dim:#8ea0c0; --gold:#e8b24a; --ok:#4ade80; --accent:#6ea8fe; }
  * { box-sizing:border-box; }
  body { margin:0; background:var(--bg); color:var(--ink); font:15px/1.55 ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,sans-serif; }
  .wrap { max-width:820px; margin:0 auto; padding:48px 22px 72px; }
  h1 { font-size:34px; margin:0 0 6px; letter-spacing:-.02em; }
  h1 .phi { color:var(--gold); }
  .sub { color:var(--dim); font-size:16px; margin:0 0 28px; }
  .panel { background:var(--panel); border:1px solid var(--line); border-radius:14px; padding:20px 22px; margin:16px 0; }
  .row { display:flex; flex-wrap:wrap; gap:10px; align-items:center; }
  label { color:var(--dim); font-size:13px; }
  input { background:#0a0f1c; color:var(--ink); border:1px solid var(--line); border-radius:8px; padding:9px 11px; font:14px ui-monospace,monospace; }
  input#prompt { flex:1; min-width:180px; }
  input#steps { width:70px; }
  button { background:var(--gold); color:#0b0e14; border:0; border-radius:8px; padding:10px 18px; font-weight:650; font-size:14px; cursor:pointer; }
  button:disabled { opacity:.5; cursor:default; }
  .chips { display:flex; flex-wrap:wrap; gap:7px; margin-top:6px; }
  .chip { font:13px ui-monospace,monospace; padding:5px 9px; border-radius:7px; border:1px solid var(--line); background:#0a0f1c; color:var(--dim); }
  .chip.gen { background:rgba(232,178,74,.12); border-color:rgba(232,178,74,.5); color:var(--gold); }
  .kv { display:grid; grid-template-columns:auto 1fr; gap:6px 16px; font:13px ui-monospace,monospace; }
  .kv b { color:var(--dim); font-weight:500; }
  .ok { color:var(--ok); }
  .muted { color:var(--dim); font-size:13px; }
  a { color:var(--accent); text-decoration:none; }
  .banner { border-left:3px solid var(--gold); padding:10px 14px; background:rgba(232,178,74,.07); border-radius:0 8px 8px 0; color:var(--dim); font-size:13.5px; }
  code { color:var(--gold); font-family:ui-monospace,monospace; }
</style>
</head>
<body>
<div class="wrap">
  <h1><span class="phi">Ferric</span> — a transformer, in your browser</h1>
  <p class="sub">A complete Llama/SmolVLA-style language model — RoPE attention, RMSNorm, SwiGLU — running on <b>WebGPU</b>, compiled from <b>pure Rust</b> to WebAssembly. The exact same kernels run on a datacenter GPU.</p>

  <div class="panel" id="statusPanel">
    <div class="kv">
      <b>status</b><span id="status">initializing…</span>
      <b>backend</b><span id="backend"></span>
      <b>adapter</b><span id="adapter"></span>
    </div>
  </div>

  <div class="panel">
    <div class="row">
      <label>prompt (token ids)</label>
      <input id="prompt" value="3,14,1,15" spellcheck="false">
      <label>steps</label>
      <input id="steps" type="number" value="6" min="1" max="32">
      <button id="run" disabled>Generate ▸</button>
    </div>
    <div id="result" style="margin-top:16px; display:none">
      <div class="muted">prompt → generated</div>
      <div class="chips" id="chips"></div>
      <div class="kv" style="margin-top:16px">
        <b>correctness</b><span id="diff"></span>
        <b>latency</b><span id="ms"></span>
        <b>model</b><span id="model"></span>
      </div>
    </div>
  </div>

  <div class="banner" id="gpuNote" style="display:none">
    This page needs <b>WebGPU</b>. Open it in Chrome or Edge (recent), or Safari Technology Preview with WebGPU enabled.
  </div>

  <p class="muted" style="margin-top:24px">
    Weights here are fixed pseudo-random (an untrained demo net), so generation is deterministic, not meaningful text — the point is that a real transformer's <i>compute</i> runs correctly in a browser tab. The GPU logits are checked live against an in-page CPU reference. &nbsp;·&nbsp;
    <a href="https://github.com/dcharlot-physicalai-bmi/ferric" target="_blank">source on GitHub</a>
  </p>
</div>

<script type="module">
import init, { ferric_lm_demo } from './pkg/ferric_web.js';
const $ = id => document.getElementById(id);
const set = (id, t) => { $(id).textContent = t; };

async function boot() {
  if (!navigator.gpu) { set('status','WebGPU not available'); $('gpuNote').style.display='block'; return; }
  await init();
  const adapter = await navigator.gpu.requestAdapter();
  if (!adapter) { set('status','no WebGPU adapter'); $('gpuNote').style.display='block'; return; }
  set('status','ready'); $('status').className='ok';
  $('run').disabled = false;
  $('run').onclick = run;
  run();
}

async function run() {
  $('run').disabled = true; set('status','running on WebGPU…'); $('status').className='';
  try {
    const prompt = $('prompt').value.trim();
    const steps = Math.max(1, Math.min(32, parseInt($('steps').value) || 6));
    const res = JSON.parse(await ferric_lm_demo(prompt, steps));
    set('backend', res.backend);
    set('adapter', res.adapter);
    $('result').style.display = 'block';
    const chips = res.prompt.map(t => `<span class="chip">${t}</span>`)
      .concat(res.generated.map(t => `<span class="chip gen">${t}</span>`)).join('');
    $('chips').innerHTML = chips;
    $('diff').innerHTML = `GPU vs CPU reference &nbsp; max|Δ| = <b>${res.logit_diff}</b> ${res.logit_diff < 1e-3 ? '<span class="ok">✓ match</span>':''}`;
    set('ms', `${res.ms.toFixed(1)} ms  (${res.generated.length} tokens)`);
    set('model', `${res.layers} layers · vocab ${res.vocab} · RoPE + RMSNorm + SwiGLU`);
    set('status','ready'); $('status').className='ok';
  } catch(e) { set('status','error: ' + (e.message||e)); $('status').className=''; }
  $('run').disabled = false;
}
boot();
</script>
</body>
</html>