ncm-chat-experimental / index.html
danielharkin21's picture
Upload folder using huggingface_hub
4c6f4df verified
Raw History Blame Contribute Delete
4.34 kB
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>NCM chat (experimental, rough)</title>
<style>
body{font-family:system-ui,sans-serif;margin:0;background:#111;color:#eee;display:flex;flex-direction:column;height:100dvh}
header{padding:10px 14px;background:#1b1b1b;font-size:13px;line-height:1.35}
header b{font-size:15px}
#log{flex:1;overflow:auto;padding:12px;display:flex;flex-direction:column;gap:8px}
.m{max-width:85%;padding:8px 12px;border-radius:14px;white-space:pre-wrap;word-wrap:break-word}
.u{align-self:flex-end;background:#2b5fd9}.b{align-self:flex-start;background:#2a2a2a}.s{align-self:center;color:#aaa;font-size:12px}
form{display:flex;gap:8px;padding:10px;background:#1b1b1b}
input{flex:1;font-size:16px;padding:10px;border-radius:10px;border:1px solid #444;background:#222;color:#eee}
button{font-size:16px;padding:10px 16px;border-radius:10px;border:0;background:#2b5fd9;color:#fff}button:disabled{opacity:.5}
</style></head><body>
<header><b>NCM chat</b> (experimental, rough) &middot; Next-Concept Modeling, Stanford Hazy Research (arXiv 2610.14162). Runs fully in your browser: the released XSum NCM generator, briefly fine-tuned on short dialogue, fp16-stored-weights ONNX, fp32 compute, verified identical to the PyTorch model on test prompts. Replies are short and often wrong or garbled. Not a capable assistant. First load downloads the model (<span id="mb">~375</span> MB, needs roughly 1 GB free RAM; desktop browser recommended).</header>
<div id="log"></div>
<form id="f"><input id="t" autocomplete="off" placeholder="Loading model..." disabled><button id="b" disabled>Send</button></form>
<script type="module">
import { AutoTokenizer } from "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.0.2";
import * as ort from "https://cdn.jsdelivr.net/npm/onnxruntime-web@1.20.1/dist/ort.min.mjs";
const MODEL="ncm_fp16w.onnx", P=128, EOS=50256, PAD=50257, PPAD=50257, TOTAL=200, V=4096, T=0.7;
const log=document.getElementById("log"), inp=document.getElementById("t"), btn=document.getElementById("b");
const add=(c,txt)=>{const d=document.createElement("div");d.className="m "+c;d.textContent=txt;log.appendChild(d);log.scrollTop=1e9;return d};
let tok,sess,hist=[];
async function load(){
const s=add("s","Loading tokenizer and model...");
tok=await AutoTokenizer.from_pretrained("Xenova/gpt2");
ort.env.wasm.wasmPaths="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.20.1/dist/";
ort.env.wasm.numThreads=crossOriginIsolated?Math.min(4,navigator.hardwareConcurrency||2):1;
const buf=await (await fetch(MODEL)).arrayBuffer();
document.getElementById("mb").textContent=Math.round(buf.byteLength/1e6);
sess=await ort.InferenceSession.create(buf,{executionProviders:["wasm"]});
s.textContent="Model ready. Say something."; inp.disabled=btn.disabled=false; inp.placeholder="Type a message"; inp.focus();
}
function gumbel(n){const a=new Float32Array(n);for(let i=0;i<n;i++){const u=Math.random()*(1-1e-7)+1e-7;a[i]=-Math.log(-Math.log(u));}return a}
async function reply(){
const turns=hist.slice(-4); let p=""; for(const [r,t] of turns) p+=(r==="u"?"User: ":"Assistant: ")+t+"\n"; p+="Assistant:";
let ids=Array.from(tok.encode(p)).slice(-P); while(ids.length<P) ids.push(PPAD);
const prefix=new ort.Tensor("int64",BigInt64Array.from(ids.map(BigInt)),[1,P]);
const noise=new ort.Tensor("float32",gumbel(TOTAL*V),[1,TOTAL,V]);
const temp=new ort.Tensor("float32",new Float32Array([T]),[]);
const out=await sess.run({prefix,noise,temp}); let o=Array.from(out.ids.data,Number);
const e=o.indexOf(EOS); if(e>=0) o=o.slice(0,e); o=o.filter(x=>x!==PAD&&x!==EOS);
return tok.decode(o,{skip_special_tokens:true}).trim()||"(empty reply)";
}
document.getElementById("f").addEventListener("submit",async ev=>{
ev.preventDefault(); const m=inp.value.trim(); if(!m) return; inp.value=""; add("u",m); hist.push(["u",m]);
inp.disabled=btn.disabled=true; const w=add("b","..."); const t0=performance.now();
try{const r=await reply(); w.textContent=r; hist.push(["a",r]); w.title=((performance.now()-t0)/1000).toFixed(1)+"s";}
catch(e){w.textContent="Error: "+e.message}
inp.disabled=btn.disabled=false; inp.focus();
});
load().catch(e=>add("s","Load failed: "+e.message));
</script></body></html>