MetaDiffusion-Demo / index.html
CodeSoft's picture
Update index.html
4e8b17d verified
Raw History Blame Contribute Delete
40.3 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>MetaDiffusion-150M - Live Denoising</title> <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='3' fill='%2313141a'/%3E%3Crect x='4' y='4' width='8' height='8' rx='1.5' fill='%236e9fd4'/%3E%3C/svg%3E"> <style> * { box-sizing: border-box; } body { font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; max-width: 980px; margin: 0 auto; padding: 24px 20px 40px; color: #c9d1d9; line-height: 1.55; background: #13141a; } .subtitle { color: #7d8590; font-size: 0.85em; margin-bottom: 14px; } .subtitle code { font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; font-size: 0.95em; color: #9cc4ee; background: #1c1d26; padding: 1px 5px; border-radius: 3px; } .status-line { font-size: 0.78em; color: #7d8590; margin-bottom: 18px; padding: 6px 10px; background: #1c1d26; border: 1px solid #2d2e3a; border-radius: 4px; display: inline-block; max-width: 100%; } .status-line span { margin-right: 16px; max-width: 280px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: bottom; } .status-line span:last-child { margin-right: 0; } .layout { display: flex; gap: 20px; flex-wrap: wrap; align-items: flex-start; } .col-left { width: 310px; flex-shrink: 0; } .col-right { flex: 1; min-width: 320px; } fieldset { border: 1px solid #2d2e3a; border-radius: 6px; padding: 16px; margin: 0 0 14px; background: #1a1b23; } legend { font-weight: 600; font-size: 0.82em; padding: 0 6px; color: #9ca3af; } label { display: block; font-size: 0.8em; margin: 12px 0 3px; color: #9ca3af; font-weight: 500; } label .val { float: right; font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; font-weight: 400; color: #e6edf3; } input[type=range] { width: 100%; margin: 2px 0; cursor: pointer; accent-color: #6e9fd4; } textarea { width: 100%; height: 72px; resize: vertical; font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; font-size: 13px; padding: 8px 10px; border: 1px solid #2d2e3a; border-radius: 4px; background: #111218; color: #c9d1d9; line-height: 1.5; } textarea:focus { outline: none; border-color: #6e9fd4; box-shadow: 0 0 0 2px rgba(110,159,212,0.15); } .seg { display: flex; border: 1px solid #2d2e3a; border-radius: 4px; overflow: hidden; margin-top: 4px; } .seg button { flex: 1; padding: 6px 0; font-size: 0.78em; font-weight: 600; background: #1e1f28; color: #9ca3af; border: 0; cursor: pointer; border-right: 1px solid #2d2e3a; transition: background 0.12s, color 0.12s; } .seg button:last-child { border-right: 0; } .seg button:hover { background: #282936; color: #c9d1d9; } .seg button.active { background: #2b3a52; color: #bcd4ee; } .seg button:disabled { opacity: 0.38; cursor: not-allowed; } .seg button:disabled:hover { background: #1e1f28; color: #9ca3af; } .btn-row { margin-top: 12px; display: flex; gap: 8px; flex-wrap: wrap; } .btn-row button { padding: 8px 16px; cursor: pointer; font-size: 0.83em; font-weight: 600; border: 1px solid #2d2e3a; border-radius: 4px; background: #22232e; color: #c9d1d9; transition: background 0.12s; } .btn-row button:hover { background: #2a2b38; } .btn-row button:disabled { opacity: 0.4; cursor: not-allowed; } #runBtn { background: #3b6ea5; color: #e6edf3; border-color: #3b6ea5; } #runBtn:hover { background: #4a7fb8; } #runBtn:disabled { background: #3b6ea5; } #stopBtn { color: #e57373; border-color: #5c3333; background: #1e1518; } #stopBtn:hover { background: #2a1c20; } #getWeightsBtn { background: #3b6ea5; color: #e6edf3; border-color: #3b6ea5; } #getWeightsBtn:hover { background: #4a7fb8; } #presets { margin-top: 12px; } #presets button { font-size: 0.75em; padding: 3px 9px; margin: 0 5px 5px 0; cursor: pointer; border: 1px solid #2d2e3a; border-radius: 3px; background: #1e1f28; color: #9ca3af; transition: background 0.12s, color 0.12s; } #presets button:hover { background: #282936; border-color: #4a4b5c; color: #c9d1d9; } #status { font-size: 0.78em; color: #7d8590; margin-top: 12px; font-family: Consolas, Menlo, monospace; } #status .ok { color: #7ee787; font-weight: 600; } #status .err { color: #f47067; font-weight: 600; } #dlbar { margin-top: 8px; height: 4px; background: #22232e; border-radius: 2px; overflow: hidden; } #dlbar > div { height: 100%; width: 0%; background: #6e9fd4; transition: width 0.2s; } #cacheInfo { font-size: 0.78em; color: #7d8590; font-family: Consolas, Menlo, monospace; margin-bottom: 4px; min-height: 2.6em; } #cacheInfo b { color: #e6edf3; font-weight: 600; } #probe { min-height: 140px; max-height: 300px; overflow-y: auto; border: 1px solid #2d2e3a; border-radius: 4px; padding: 12px 14px; margin-bottom: 10px; font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; font-size: 13px; line-height: 1.8; background: #111218; white-space: pre-wrap; word-wrap: break-word; overflow-wrap: break-word; transition: border-color 0.15s; } #probe:hover { border-color: #3a3c4d; } .tok { display: inline; white-space: pre-wrap; word-break: break-word; } .tok.prompt { color: #7ee787; } .tok.mask { color: #3d3f50; } .tok.fresh { background: #263352; border-radius: 2px; } #meta { font-size: 0.76em; color: #7d8590; font-family: Consolas, Menlo, monospace; margin: 8px 0; padding: 0 2px; } #meta b { color: #e6edf3; font-weight: 600; } #progressWrap { height: 5px; background: #22232e; border-radius: 3px; margin: 8px 0; overflow: hidden; } #progressFill { height: 100%; width: 0%; background: #6e9fd4; border-radius: 3px; transition: width 0.15s; } .sparkhead { display: flex; justify-content: space-between; font-size: 0.72em; color: #7d8590; font-family: Consolas, Menlo, monospace; margin-top: 10px; padding: 0 2px; } .sparkhead b { color: #e6edf3; font-weight: 600; } #spark { width: 100%; height: 40px; display: block; background: #111218; border: 1px solid #2d2e3a; border-radius: 4px; margin-top: 4px; } #finalText { display: none; border: 1px solid #2d2e3a; border-radius: 4px; padding: 12px 14px; margin-top: 12px; font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; font-size: 13px; line-height: 1.7; white-space: pre-wrap; word-wrap: break-word; overflow-wrap: break-word; max-height: 240px; overflow-y: auto; background: #111218; color: #c9d1d9; } #copyBtn { display: none; margin-top: 8px; font-size: 0.78em; padding: 5px 12px; cursor: pointer; border: 1px solid #2d2e3a; border-radius: 3px; background: #22232e; color: #9ca3af; transition: background 0.12s, color 0.12s; } #copyBtn:hover { background: #2a2b38; color: #c9d1d9; } footer { margin-top: 32px; padding-top: 14px; border-top: 1px solid #2d2e3a; font-size: 0.76em; color: #555b66; } footer a { color: #6e9fd4; text-decoration: none; } footer a:hover { text-decoration: underline; } @media (max-width: 700px) { .layout { flex-direction: column; } .col-left { width: 100%; } } </style> </head> <body> <div class="subtitle"><code>CodeSoft/MetaDiffusion-150M-exp</code> &mdash; masked diffusion language model</div> <div class="status-line"> <span id="badgeEngine">engine: &mdash;</span> <span id="badgeGpu">gpu: probing&hellip;</span> <span id="badgeWeights">tokenizer: &mdash;</span> </div> <div class="layout"> <div class="col-left"> <fieldset> <legend>Model weights</legend> <div id="cacheInfo">Checking local cache&hellip;</div> <div class="btn-row"> <button id="getWeightsBtn" style="display:none">Download weights</button> <button id="clearCacheBtn" style="display:none">Clear cache</button> </div> <div id="dlbar"><div id="dlfill"></div></div> </fieldset> <fieldset> <legend>Prompt &amp; Controls</legend> <label for="prompt">Prompt text</label> <textarea id="prompt" spellcheck="false">The cat sat on the</textarea> <label>Sequence length <span class="val" id="vSeq">96</span></label> <input type="range" id="seqLen" min="32" max="256" step="16" value="96"> <label>Denoising steps <span class="val" id="vSteps">64</span></label> <input type="range" id="numSteps" min="16" max="128" step="8" value="64"> <label>Temperature <span class="val" id="vTemp">0.60</span></label> <input type="range" id="temperature" min="0.3" max="1.5" step="0.05" value="0.6"> <label>Repetition penalty <span class="val" id="vRep">1.50</span></label> <input type="range" id="repPenalty" min="1.0" max="2.0" step="0.05" value="1.5"> <label>Backend</label> <div class="seg" id="backendSeg"> <button type="button" data-backend="auto" class="active">Auto</button> <button type="button" data-backend="webgpu">WebGPU</button> <button type="button" data-backend="wasm">WASM</button> </div> <div id="presets"> <button data-preset='{"prompt":"Once upon a time,","seqLen":96,"numSteps":64,"temperature":0.6,"repPenalty":1.5}'>Fairy tale</button> <button data-preset='{"prompt":"The science of","seqLen":128,"numSteps":96,"temperature":0.5,"repPenalty":1.6}'>Science</button> <button data-preset='{"prompt":"def fibonacci(n):","seqLen":96,"numSteps":64,"temperature":0.2,"repPenalty":1.8}'>Code</button> <button data-preset='{"prompt":"The cat sat on the","seqLen":160,"numSteps":112,"temperature":0.8,"repPenalty":1.3}'>Creative</button> </div> <div class="btn-row"> <button id="runBtn" disabled>&#9654; Denoise</button> <button id="stopBtn" disabled>&#9632; Stop</button> </div> <div id="status">Initializing&hellip;</div> </fieldset> </div> <div class="col-right"> <fieldset> <legend>Live denoising output</legend> <div id="probe" aria-live="polite"></div> <div id="meta"> step <b id="mStep">0</b>/<span id="mSteps">64</span> &nbsp;&middot;&nbsp; noise <b id="mT">1.00</b> &nbsp;&middot;&nbsp; masks <b id="mMasks">0</b> &nbsp;&middot;&nbsp; <b id="mTps">&ndash;</b> tok/s &nbsp; <span id="mEta"></span> </div> <div id="progressWrap"><div id="progressFill"></div></div> <div class="sparkhead"><span>step time</span><b id="mMs">&ndash;</b></div> <canvas id="spark"></canvas> <div id="finalText"></div> <button id="copyBtn">Copy output</button> </fieldset> </div> </div> <footer> <a href="https://huggingface.co/CodeSoft/MetaDiffusion-150M-exp" target="_blank" rel="noopener">CodeSoft/MetaDiffusion-150M-exp</a> &middot; Apache-2.0 &middot; int8 ONNX &middot; <a href="https://onnxruntime.ai" target="_blank" rel="noopener">onnxruntime-web</a> </footer> <!-- The ORT bundle is loaded dynamically in loadModel(): ort.webgpu.min.js (wasm + webgpu EP) when navigator.gpu exists, else the lean ort.min.js. The default ort.min.js does NOT contain the WebGPU EP. --> <script id="engine"> "use strict"; const MD_MASK_TOKEN = 32000; const MD_SPECIAL_IDS = new Set([0, 1, 2, 3]); function buildByteMap() { const map = new Array(256); const bs = []; for (let b = 33; b <= 126; b++) bs.push(b); for (let b = 161; b <= 172; b++) bs.push(b); for (let b = 174; b <= 255; b++) bs.push(b); const cs = bs.slice(); let n = 0; for (let b = 0; b < 256; b++) { if (!bs.includes(b)) { bs.push(b); cs.push(256 + n); n++; } } for (let i = 0; i < 256; i++) map[bs[i]] = String.fromCharCode(cs[i]); return map; } const BYTE_MAP = buildByteMap(); const CHAR_TO_BYTE = new Map(); for (let b = 0; b < 256; b++) CHAR_TO_BYTE.set(BYTE_MAP[b], b); function strToUtf8Bytes(s) { return Array.from(new TextEncoder().encode(s)); } function bytesToStr(bytes) { return new TextDecoder("utf-8", { fatal: false }).decode(new Uint8Array(bytes)); } class ByteLevelTokenizer { constructor(json) { const model = json.model; this.vocab = new Map(Object.entries(model.vocab)); this.idToToken = new Array(model.vocab ? Object.keys(model.vocab).length : 0); for (const [tok, id] of this.vocab) this.idToToken[id] = tok; this.unkId = model.unk_token ? this.vocab.get(model.unk_token) : 3; this.merges = new Map(); (model.merges || []).forEach((m, rank) => { const pair = Array.isArray(m) ? m[0] + m[1] : m.indexOf(" ") >= 0 ? m.replace(" ", "") : m; this.merges.set(pair, rank); }); const pt = (json.pre_tokenizer || {}); this.regex = pt.use_regex === false ? null : /'s|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+/gu; } byteEncode(str) { let out = ""; for (const b of strToUtf8Bytes(str)) out += BYTE_MAP[b]; return out; } bpe(word) { if (word.length <= 1) return [word]; let parts = Array.from(word); while (parts.length > 1) { let bestRank = Infinity, bestIdx = -1; for (let i = 0; i < parts.length - 1; i++) { const rank = this.merges.get(parts[i] + parts[i + 1]); if (rank !== undefined && rank < bestRank) { bestRank = rank; bestIdx = i; } } if (bestIdx === -1) break; parts.splice(bestIdx, 2, parts[bestIdx] + parts[bestIdx + 1]); } return parts; } encode(text) { const ids = []; if (this.regex) { const matches = text.match(this.regex); if (matches) { for (const piece of matches) { const encoded = this.byteEncode(piece); for (const sub of this.bpe(encoded)) { const id = this.vocab.get(sub); ids.push(id === undefined ? this.unkId : id); } } } } else { for (const sub of this.bpe(this.byteEncode(text))) { const id = this.vocab.get(sub); ids.push(id === undefined ? this.unkId : id); } } return ids; } decode(ids, skipSpecial = true) { const bytes = []; for (const id of ids) { if (skipSpecial && MD_SPECIAL_IDS.has(id)) continue; const tok = this.idToToken[id]; if (tok === undefined) continue; for (const ch of tok) { const b = CHAR_TO_BYTE.get(ch); if (b !== undefined) bytes.push(b); } } return bytesToStr(bytes); } } function cumulativeUnmaskFrac(i, n) { return 0.5 * (1 - Math.cos(Math.PI * i / n)); } async function* denoise(env, params) { const { prompt, seqLen, numSteps, temperature, repPenalty, runStep, greedy } = params; const tok = env.tokenizer; const promptIds = tok.encode(prompt); if (promptIds.length >= seqLen) { throw new Error("Prompt is " + promptIds.length + " tokens, must be < seq len " + seqLen + "."); } const total = seqLen; const genLen = total - promptIds.length; const vocab = env.vocabSize; const inputIds = new Int32Array(total).fill(MD_MASK_TOKEN); for (let i = 0; i < promptIds.length; i++) inputIds[i] = promptIds[i]; const positionIds = new Int32Array(total); for (let i = 0; i < total; i++) positionIds[i] = i; const logits = new Float32Array(total * vocab); const startTime = performance.now(); for (let step = 0; step < numSteps; step++) { if (env.cancelled) throw new Error("cancelled"); const fracNow = cumulativeUnmaskFrac(step, numSteps); const fracNext = cumulativeUnmaskFrac(step + 1, numSteps); let nMasked = 0; for (let i = promptIds.length; i < total; i++) if (inputIds[i] === MD_MASK_TOKEN) nMasked++; const nTotalToUnmask = Math.round((fracNext - fracNow) * genLen); let nUnmask; if (step === numSteps - 1) nUnmask = nMasked; else nUnmask = nMasked > 0 ? Math.max(nTotalToUnmask, 1) : 0; const t = 1 - fracNow; await runStep(inputIds, positionIds, t, logits); const maskPositions = []; const seenTokens = new Set(); for (let i = 0; i < total; i++) { const row = i * vocab; logits[row + MD_MASK_TOKEN] = -1e9; if (inputIds[i] === MD_MASK_TOKEN) maskPositions.push(i); else seenTokens.add(inputIds[i]); } if (repPenalty !== 1.0 && seenTokens.size > 0) { for (const tokId of seenTokens) { for (let i = 0; i < total; i++) { const v = logits[i * vocab + tokId]; logits[i * vocab + tokId] = v < 0 ? v * repPenalty : v / repPenalty; } } } const nMask = maskPositions.length; const rowMaxProb = new Float32Array(total); const invTemp = 1 / temperature; for (let i = 0; i < total; i++) { const row = i * vocab; let mx = -Infinity; for (let k = 0; k < vocab; k++) mx = Math.max(mx, logits[row + k]); let sum = 0; for (let k = 0; k < vocab; k++) { const v = Math.exp((logits[row + k] - mx) * invTemp); logits[row + k] = v; sum += v; } const invSum = 1 / sum; let pmax = 0; for (let k = 0; k < vocab; k++) { const p = logits[row + k] * invSum; logits[row + k] = p; if (p > pmax) pmax = p; } rowMaxProb[i] = pmax; } const sampled = new Int32Array(nMask); const confidence = new Float32Array(nMask); for (let j = 0; j < nMask; j++) { const row = maskPositions[j] * vocab; if (greedy) { let best = 0; for (let k = 1; k < vocab; k++) if (logits[row + k] > logits[row + best]) best = k; sampled[j] = best; } else { const u = Math.random(); let cdf = 0; let chosen = vocab - 1; for (let k = 0; k < vocab; k++) { cdf += logits[row + k]; if (cdf >= u) { chosen = k; break; } } sampled[j] = chosen; } confidence[j] = rowMaxProb[maskPositions[j]]; } const order = Array.from({ length: nMask }, (_, j) => j); order.sort((a, b) => confidence[b] - confidence[a]); const nFill = Math.min(nUnmask, nMask); for (let k = 0; k < nFill; k++) { inputIds[maskPositions[order[k]]] = sampled[order[k]]; } yield { step, numSteps, t, nMasked: nMask, inputIds: inputIds.slice(), promptLen: promptIds.length, elapsed: performance.now() - startTime, }; } } </script> <script> "use strict"; var $ = function(id) { return document.getElementById(id); }; var els = { prompt: $("prompt"), seqLen: $("seqLen"), numSteps: $("numSteps"), temperature: $("temperature"), repPenalty: $("repPenalty"), vSeq: $("vSeq"), vSteps: $("vSteps"), vTemp: $("vTemp"), vRep: $("vRep"), runBtn: $("runBtn"), stopBtn: $("stopBtn"), status: $("status"), dlfill: $("dlfill"), badgeEngine: $("badgeEngine"), badgeWeights: $("badgeWeights"), badgeGpu: $("badgeGpu"), cacheInfo: $("cacheInfo"), getWeightsBtn: $("getWeightsBtn"), clearCacheBtn: $("clearCacheBtn"), probe: $("probe"), mStep: $("mStep"), mSteps: $("mSteps"), mT: $("mT"), mMasks: $("mMasks"), mEta: $("mEta"), mTps: $("mTps"), mMs: $("mMs"), spark: $("spark"), progressFill: $("progressFill"), finalText: $("finalText"), copyBtn: $("copyBtn"), }; var ORT_CDN = "https://cdn.jsdelivr.net/npm/onnxruntime-web@1.27.0/dist/"; var env = { tokenizer: null, vocabSize: 32001, session: null, engine: "loading", cancelled: false, modelBuf: null, backend: "auto", engineName: "-", threads: 1, hasWebGpuBundle: false, idbOk: true, cacheState: "checking", cachedSize: 0, cachedSavedAt: 0, modelSize: 0, stepTimes: [], lastNumSteps: 64, }; function setStatus(html) { els.status.innerHTML = html; } function fmtMB(n) { return (n / 1e6).toFixed(0) + " MB"; } /* ================= IndexedDB weight cache ================= */ var IDB_NAME = "metadiffusion-cache", IDB_STORE = "weights", IDB_VER = 1; function openDB() { return new Promise(function(ok, no) { var rq = indexedDB.open(IDB_NAME, IDB_VER); rq.onupgradeneeded = function() { var db = rq.result; if (!db.objectStoreNames.contains(IDB_STORE)) db.createObjectStore(IDB_STORE); }; rq.onsuccess = function() { ok(rq.result); }; rq.onerror = function() { no(rq.error); }; }); } function idbGet(key) { return openDB().then(function(db) { return new Promise(function(ok, no) { var rq = db.transaction(IDB_STORE, "readonly").objectStore(IDB_STORE).get(key); rq.onsuccess = function() { ok(rq.result); }; rq.onerror = function() { no(rq.error); }; }); }); } function idbPut(key, val) { return openDB().then(function(db) { return new Promise(function(ok, no) { var tx = db.transaction(IDB_STORE, "readwrite"); tx.objectStore(IDB_STORE).put(val, key); tx.oncomplete = function() { ok(); }; tx.onerror = function() { no(tx.error); }; }); }); } function idbDel(key) { return openDB().then(function(db) { return new Promise(function(ok, no) { var tx = db.transaction(IDB_STORE, "readwrite"); tx.objectStore(IDB_STORE).delete(key); tx.oncomplete = function() { ok(); }; tx.onerror = function() { no(tx.error); }; }); }); } // 'meta' is a tiny record checked on every load; 'model' is the big ArrayBuffer, // only read when the user explicitly loads from cache. function getCachedMeta() { return idbGet("meta"); } function getWeightsBuffer() { return idbGet("model"); } function putCached(buf) { return idbPut("model", buf).then(function() { return idbPut("meta", { size: buf.byteLength, savedAt: Date.now() }); }); } function clearCached() { return idbDel("model").then(function() { return idbDel("meta"); }); } /* ================= cache UI state machine ================= */ function updateCacheUI() { var info = els.cacheInfo, btn = els.getWeightsBtn, clr = els.clearCacheBtn; var canClear = env.idbOk && (env.cacheState === "cached" || env.cacheState === "ready"); clr.style.display = canClear ? "" : "none"; switch (env.cacheState) { case "checking": info.textContent = "Checking local cache\u2026"; btn.style.display = "none"; break; case "absent": info.innerHTML = env.idbOk ? "Not cached." + (env.modelSize ? " ~" + fmtMB(env.modelSize) + " download." : "") : "Local cache unavailable here; weights re-download each visit."; btn.style.display = ""; btn.textContent = "Download weights" + (env.modelSize ? " (" + fmtMB(env.modelSize) + ")" : ""); break; case "cached": var when = env.cachedSavedAt ? " \u00b7 saved " + new Date(env.cachedSavedAt).toLocaleDateString() : ""; info.innerHTML = "Cached locally" + (env.cachedSize ? " (" + fmtMB(env.cachedSize) + ")" : "") + when + ". Not loaded yet."; btn.style.display = ""; btn.textContent = "Load from cache"; break; case "downloading": info.textContent = "Downloading\u2026"; btn.style.display = "none"; break; case "loading": info.textContent = "Building inference session\u2026"; btn.style.display = "none"; break; case "ready": info.innerHTML = "Loaded on <b>" + env.engineName + "</b>."; btn.style.display = "none"; break; } } /* ================= ORT helpers ================= */ function loadScript(src) { return new Promise(function(ok, no) { var s = document.createElement("script"); s.src = src; s.onload = function() { ok(); }; s.onerror = function() { no(new Error("failed to load " + src)); }; document.head.appendChild(s); }); } function engineLabel() { var base = env.engineName; if (base.indexOf("wasm") >= 0) base += " \u00b7 " + env.threads + "t"; return "engine: " + base; } async function detectGpu() { if (!navigator.gpu) { els.badgeGpu.textContent = "gpu: not available"; return; } try { var adapter = await navigator.gpu.requestAdapter(); if (!adapter) { els.badgeGpu.textContent = "gpu: no adapter"; return; } var info = adapter.info || (adapter.requestAdapterInfo ? await adapter.requestAdapterInfo() : null); var label = ""; if (info) label = info.description || [info.vendor, info.architecture].filter(Boolean).join(" "); els.badgeGpu.textContent = "gpu: " + (label || "available"); } catch (e) { els.badgeGpu.textContent = "gpu: available"; } } async function createSession(backend) { var buf = env.modelBuf; if (backend === "wasm") { var s = await ort.InferenceSession.create(buf, { executionProviders: ["wasm"] }); env.engineName = "wasm"; return s; } if (backend === "webgpu") { if (!env.hasWebGpuBundle) throw new Error("WebGPU backend not loaded (no navigator.gpu, or page not in a secure context)"); var s2 = await ort.InferenceSession.create(buf, { executionProviders: ["webgpu"] }); env.engineName = "webgpu"; return s2; } // auto if (env.hasWebGpuBundle) { try { var s3 = await ort.InferenceSession.create(buf, { executionProviders: ["webgpu", "wasm"] }); env.engineName = "webgpu+wasm"; return s3; } catch (e) { console.warn("webgpu session init failed, using wasm:", e); var s4 = await ort.InferenceSession.create(buf, { executionProviders: ["wasm"] }); env.engineName = "wasm"; return s4; } } var s5 = await ort.InferenceSession.create(buf, { executionProviders: ["wasm"] }); env.engineName = "wasm"; return s5; } async function headSize(url) { try { var r = await fetch(url, { method: "HEAD" }); var cl = r.headers.get("content-length"); return cl ? Number(cl) : 0; } catch (e) { return 0; } } async function fetchWithProgress(url) { var res = await fetch(url); if (!res.ok) throw new Error("HTTP " + res.status + " for " + url); var total = Number(res.headers.get("content-length")) || 0; if (!total || !res.body) return res.arrayBuffer(); var reader = res.body.getReader(); var chunks = []; var received = 0; while (true) { var r = await reader.read(); if (r.done) break; chunks.push(r.value); received += r.value.length; els.dlfill.style.width = Math.min(100, (received / total) * 100).toFixed(1) + "%"; setStatus("Downloading model weights\u2026 " + (received / 1e6).toFixed(0) + " / " + (total / 1e6).toFixed(0) + " MB"); } var buf = new Uint8Array(received); var off = 0; for (var i = 0; i < chunks.length; i++) { buf.set(chunks[i], off); off += chunks[i].length; } return buf.buffer; } /* ================= weight acquisition (user-initiated) ================= */ async function buildSessionFromBuffer() { env.cacheState = "loading"; updateCacheUI(); setStatus("Initializing runtime (" + env.backend + ")\u2026"); env.session = await createSession(env.backend); els.badgeEngine.textContent = engineLabel(); env.engine = "ready"; env.cacheState = "ready"; updateCacheUI(); els.runBtn.disabled = false; var note = ""; if (env.engineName.indexOf("webgpu") >= 0) note = " GPU active."; else if (navigator.gpu) note = " <span style='color:#7d8590'>(webgpu present but session fell back to wasm)</span>"; if (!window.crossOriginIsolated) note += " <span style='color:#555b66'>(serve with COOP/COEP for multi-threaded wasm)</span>"; setStatus('<span class="ok">Ready.</span> ' + env.engineName + '.' + note); } async function downloadWeights() { env.cacheState = "downloading"; updateCacheUI(); els.dlfill.style.width = "0%"; setStatus("Downloading model weights\u2026"); try { var buf = await fetchWithProgress("model.onnx"); env.modelBuf = buf; els.dlfill.style.width = "100%"; if (env.idbOk) { setStatus("Saving to local cache\u2026"); try { await putCached(buf); env.cachedSize = buf.byteLength; env.cachedSavedAt = Date.now(); } catch (e2) { console.warn("cache save failed:", e2); env.idbOk = false; } } await buildSessionFromBuffer(); } catch (e) { console.error(e); env.cacheState = "absent"; updateCacheUI(); setStatus('<span class="err">Download failed: ' + e.message + '</span>'); } } async function loadFromCache() { env.cacheState = "loading"; updateCacheUI(); setStatus("Reading weights from local cache\u2026"); try { var buf = await getWeightsBuffer(); if (!buf) { env.cacheState = "absent"; updateCacheUI(); setStatus("Cache entry missing. Download the weights instead."); return; } env.modelBuf = buf; await buildSessionFromBuffer(); } catch (e) { console.error(e); env.cacheState = "absent"; updateCacheUI(); setStatus('<span class="err">Cache load failed: ' + e.message + '</span>'); } } els.getWeightsBtn.addEventListener("click", function() { if (env.cacheState === "absent") downloadWeights(); else if (env.cacheState === "cached") loadFromCache(); }); els.clearCacheBtn.addEventListener("click", async function() { try { await clearCached(); } catch (e) { console.warn(e); } env.modelBuf = null; env.session = null; env.engine = "awaiting-weights"; env.cacheState = "absent"; env.cachedSize = 0; env.cachedSavedAt = 0; els.runBtn.disabled = true; els.badgeEngine.textContent = "engine: \u2014"; els.modelSize = await headSize("model.onnx"); updateCacheUI(); setStatus("Cache cleared. Download the weights to continue."); }); /* ================= init (no auto weight download) ================= */ async function loadModel() { detectGpu(); var gpuPresent = !!navigator.gpu; if (!gpuPresent) { var wg = document.querySelector('#backendSeg button[data-backend="webgpu"]'); if (wg) { wg.disabled = true; wg.title = "WebGPU unavailable (needs https or localhost)"; } } setStatus("Loading tokenizer\u2026"); var tokRes = await fetch("tokenizer.json"); env.tokenizer = new ByteLevelTokenizer(await tokRes.json()); env.vocabSize = env.tokenizer.idToToken.length + 1; els.badgeWeights.textContent = "tokenizer: ok"; env.hasWebGpuBundle = false; setStatus("Loading ONNX runtime (" + (gpuPresent ? "webgpu+wasm" : "wasm") + ")\u2026"); try { if (gpuPresent) { await loadScript(ORT_CDN + "ort.webgpu.min.js"); env.hasWebGpuBundle = true; } else { await loadScript(ORT_CDN + "ort.min.js"); } } catch (e1) { console.warn("primary ORT bundle failed, falling back to wasm build:", e1); await loadScript(ORT_CDN + "ort.min.js"); env.hasWebGpuBundle = false; } if (typeof ort === "undefined") throw new Error("onnxruntime-web failed to load from CDN"); ort.env.wasm.wasmPaths = ORT_CDN; if (window.crossOriginIsolated) { env.threads = Math.min(navigator.hardwareConcurrency || 4, 8); ort.env.wasm.numThreads = env.threads; } else { env.threads = 1; } // Check the cache (cheap meta read only). Do NOT download automatically. env.cacheState = "checking"; updateCacheUI(); try { await openDB(); env.idbOk = true; } catch (e) { env.idbOk = false; } els.runBtn.disabled = true; env.engine = "awaiting-weights"; if (env.idbOk) { var meta = await getCachedMeta(); if (meta && meta.size) { env.cachedSize = meta.size; env.cachedSavedAt = meta.savedAt || 0; env.cacheState = "cached"; updateCacheUI(); setStatus("Weights are cached. <b>Load from cache</b> to start (no download)."); return; } } env.cacheState = "absent"; env.modelSize = await headSize("model.onnx"); updateCacheUI(); setStatus(env.idbOk ? "Weights not cached. Download them once; they'll be stored locally for next time." : "Local cache unavailable here. Download the weights to begin."); } /* ================= backend switcher ================= */ document.querySelectorAll("#backendSeg button").forEach(function(btn) { btn.addEventListener("click", async function() { if (running || btn.disabled || btn.classList.contains("active")) return; var prev = document.querySelector("#backendSeg .active"); if (prev) prev.classList.remove("active"); btn.classList.add("active"); env.backend = btn.dataset.backend; if (!env.modelBuf) { setStatus("Backend set to <b>" + env.backend + "</b> \u2014 applies when weights are loaded."); return; } env.engine = "loading"; setStatus("Switching backend to <b>" + env.backend + "</b>\u2026"); try { env.session = await createSession(env.backend); els.badgeEngine.textContent = engineLabel(); env.engine = "ready"; env.cacheState = "ready"; updateCacheUI(); setStatus('<span class="ok">Ready.</span> Running on <b>' + env.engineName + '</b>.'); } catch (e) { console.warn(e); env.engine = "ready"; setStatus('<span class="err">' + env.backend + ' failed: ' + e.message + '</span> \u2014 still on ' + env.engineName + '.'); } }); }); /* ================= live view ================= */ function renderTokens(state) { var tok = env.tokenizer; var ids = state.inputIds; var frag = document.createDocumentFragment(); var fresh = new Set(); if (state.prevMasked) { for (var i = 0; i < ids.length; i++) { if (state.prevMasked[i] && ids[i] !== MD_MASK_TOKEN) fresh.add(i); } } var prevMasked = new Array(ids.length); for (var i = 0; i < ids.length; i++) { var id = ids[i]; var span = document.createElement("span"); if (id === MD_MASK_TOKEN) { span.className = "tok mask"; span.textContent = "\u25ae"; prevMasked[i] = true; } else { var isPrompt = i < state.promptLen; span.className = "tok" + (isPrompt ? " prompt" : "") + (fresh.has(i) ? " fresh" : ""); span.textContent = tok.idToToken[id] !== undefined ? tok.decode([id], true) : "\ufffd"; prevMasked[i] = false; } frag.appendChild(span); } els.probe.replaceChildren(frag); return prevMasked; } function updateMeta(state) { els.mStep.textContent = String(state.step + 1); els.mSteps.textContent = String(state.numSteps); els.mT.textContent = state.t.toFixed(3); els.mMasks.textContent = String(state.nMasked); els.progressFill.style.width = ((state.step + 1) / state.numSteps * 100).toFixed(1) + "%"; var perStep = state.elapsed / (state.step + 1); var left = (state.numSteps - state.step - 1) * perStep; els.mEta.textContent = left > 0 ? "~" + Math.round(left / 1000) + "s left" : ""; } function updatePerf(state) { var times = env.stepTimes; if (times.length) { var recent = times.slice(-5); var avg = 0; for (var i = 0; i < recent.length; i++) avg += recent[i]; avg /= recent.length; els.mMs.textContent = avg.toFixed(1) + " ms"; } var genLen = state.inputIds.length - state.promptLen; var revealed = genLen - state.nMasked; var tps = state.elapsed > 0 ? revealed / (state.elapsed / 1000) : 0; els.mTps.textContent = tps.toFixed(1); } function drawSparkline() { var c = els.spark; var dpr = window.devicePixelRatio || 1; var w = c.clientWidth || 300, h = c.clientHeight || 40; if (c.width !== Math.round(w * dpr) || c.height !== Math.round(h * dpr)) { c.width = Math.round(w * dpr); c.height = Math.round(h * dpr); } var ctx = c.getContext("2d"); ctx.setTransform(dpr, 0, 0, dpr, 0, 0); ctx.clearRect(0, 0, w, h); var times = env.stepTimes; if (!times.length) return; var slots = Math.max(times.length, env.lastNumSteps); var slot = (w - 8) / slots; var bw = Math.max(1.5, Math.min(slot - 1.5, 8)); var max = 0; for (var i = 0; i < times.length; i++) if (times[i] > max) max = times[i]; if (max <= 0) max = 1; for (var i = 0; i < times.length; i++) { var bh = Math.max(2, (times[i] / max) * (h - 8)); ctx.fillStyle = (i === times.length - 1) ? "#9cc4ee" : "#4f7bb0"; ctx.fillRect(4 + i * slot, h - 4 - bh, bw, bh); } } /* ================= run ================= */ var running = false; async function run() { if (running) return; if (env.engine !== "ready") { setStatus('Weights not loaded yet \u2014 download or load from cache first.'); return; } var params = { prompt: els.prompt.value, seqLen: parseInt(els.seqLen.value, 10), numSteps: parseInt(els.numSteps.value, 10), temperature: parseFloat(els.temperature.value), repPenalty: parseFloat(els.repPenalty.value), }; running = true; env.cancelled = false; env.stepTimes = []; env.lastNumSteps = params.numSteps; els.runBtn.disabled = true; els.stopBtn.disabled = false; els.finalText.style.display = "none"; els.copyBtn.style.display = "none"; els.probe.replaceChildren(); els.progressFill.style.width = "0%"; els.mTps.textContent = "\u2013"; els.mMs.textContent = "\u2013"; drawSparkline(); var session = env.session; var prevMasked = null; var lastElapsed = 0; var runStep = async function(inputIds, positionIds, t, logitsOut) { var seq = inputIds.length; var idsBig = new BigInt64Array(seq); for (var i = 0; i < seq; i++) idsBig[i] = BigInt(inputIds[i]); var posBig = new BigInt64Array(seq); for (var i = 0; i < seq; i++) posBig[i] = BigInt(positionIds[i]); var feeds = { input_ids: new ort.Tensor("int64", idsBig, [1, seq]), timesteps: new ort.Tensor("float32", new Float32Array([t]), [1]), position_ids: new ort.Tensor("int64", posBig, [1, seq]), }; var out = await session.run(feeds); logitsOut.set(out.logits.data); }; try { var state; var gen = denoise(env, Object.assign({}, params, { runStep: runStep })); var first = true; while (true) { var result = await gen.next(); if (result.done) break; state = result.value; if (first) { els.mSteps.textContent = String(state.numSteps); first = false; } state.prevMasked = prevMasked; prevMasked = renderTokens(state); updateMeta(state); var stepMs = state.elapsed - lastElapsed; lastElapsed = state.elapsed; env.stepTimes.push(stepMs); drawSparkline(); updatePerf(state); setStatus("Denoising on <b>" + env.engineName + "</b>\u2026 step <b>" + (state.step + 1) + "</b>/" + state.numSteps + " \u00b7 noise <b>" + state.t.toFixed(2) + "</b>"); await new Promise(function(r) { setTimeout(r, 0); }); } var finalText = env.tokenizer.decode(state.inputIds, true); els.finalText.textContent = finalText; els.finalText.style.display = "block"; els.copyBtn.style.display = "inline-block"; setStatus('<span class="ok">Done.</span> ' + env.engineName + ' \u00b7 ' + els.mMs.textContent + '/step avg \u00b7 denoised text below.'); } catch (e) { if (e && e.message === "cancelled") setStatus("Stopped."); else { console.error(e); setStatus('<span class="err">Error: ' + e.message + '</span>'); } } finally { running = false; els.runBtn.disabled = false; els.stopBtn.disabled = true; } } els.runBtn.addEventListener("click", run); els.stopBtn.addEventListener("click", function() { env.cancelled = true; }); function bindSlider(input, label, fmt) { var upd = function() { label.textContent = fmt ? fmt(parseFloat(input.value)) : input.value; }; input.addEventListener("input", upd); upd(); } bindSlider(els.seqLen, els.vSeq, function(v) { return String(v); }); bindSlider(els.numSteps, els.vSteps, function(v) { return String(v); }); bindSlider(els.temperature, els.vTemp, function(v) { return v.toFixed(2); }); bindSlider(els.repPenalty, els.vRep, function(v) { return v.toFixed(2); }); document.querySelectorAll("#presets button").forEach(function(btn) { btn.addEventListener("click", function() { var p = JSON.parse(btn.dataset.preset); els.prompt.value = p.prompt; els.seqLen.value = p.seqLen; els.vSeq.textContent = p.seqLen; els.numSteps.value = p.numSteps; els.vSteps.textContent = p.numSteps; els.temperature.value = p.temperature; els.vTemp.textContent = p.temperature.toFixed(2); els.repPenalty.value = p.repPenalty; els.vRep.textContent = p.repPenalty.toFixed(2); }); }); els.copyBtn.addEventListener("click", async function() { try { await navigator.clipboard.writeText(els.finalText.textContent); els.copyBtn.textContent = "Copied!"; setTimeout(function() { els.copyBtn.textContent = "Copy output"; }, 1500); } catch (e) {} }); loadModel().catch(function(e) { console.error(e); setStatus('<span class="err">Failed to initialize: ' + e.message + '</span>'); }); </script> </body> </html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>MetaDiffusion-150M - Live Denoising</title>
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='3' fill='%2313141a'/%3E%3Crect x='4' y='4' width='8' height='8' rx='1.5' fill='%236e9fd4'/%3E%3C/svg%3E">
<style>
* { box-sizing: border-box; }
body {
font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
max-width: 980px;
margin: 0 auto;
padding: 24px 20px 40px;
color: #c9d1d9;
line-height: 1.55;
background: #13141a;
}
.subtitle { color: #7d8590; font-size: 0.85em; margin-bottom: 14px; }
.subtitle code {
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 0.95em; color: #9cc4ee; background: #1c1d26;
padding: 1px 5px; border-radius: 3px;
}
.status-line {
font-size: 0.78em; color: #7d8590; margin-bottom: 18px;
padding: 6px 10px; background: #1c1d26; border: 1px solid #2d2e3a;
border-radius: 4px; display: inline-block; max-width: 100%;
}
.status-line span {
margin-right: 16px; max-width: 280px; overflow: hidden;
text-overflow: ellipsis; white-space: nowrap;
display: inline-block; vertical-align: bottom;
}
.status-line span:last-child { margin-right: 0; }
.layout { display: flex; gap: 20px; flex-wrap: wrap; align-items: flex-start; }
.col-left { width: 310px; flex-shrink: 0; }
.col-right { flex: 1; min-width: 320px; }
fieldset {
border: 1px solid #2d2e3a; border-radius: 6px;
padding: 16px; margin: 0 0 14px; background: #1a1b23;
}
legend { font-weight: 600; font-size: 0.82em; padding: 0 6px; color: #9ca3af; }
label { display: block; font-size: 0.8em; margin: 12px 0 3px; color: #9ca3af; font-weight: 500; }
label .val { float: right; font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; font-weight: 400; color: #e6edf3; }
input[type=range] { width: 100%; margin: 2px 0; cursor: pointer; accent-color: #6e9fd4; }
textarea {
width: 100%; height: 72px; resize: vertical;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 13px; padding: 8px 10px;
border: 1px solid #2d2e3a; border-radius: 4px;
background: #111218; color: #c9d1d9; line-height: 1.5;
}
textarea:focus { outline: none; border-color: #6e9fd4; box-shadow: 0 0 0 2px rgba(110,159,212,0.15); }
.seg { display: flex; border: 1px solid #2d2e3a; border-radius: 4px; overflow: hidden; margin-top: 4px; }
.seg button {
flex: 1; padding: 6px 0; font-size: 0.78em; font-weight: 600;
background: #1e1f28; color: #9ca3af; border: 0; cursor: pointer;
border-right: 1px solid #2d2e3a; transition: background 0.12s, color 0.12s;
}
.seg button:last-child { border-right: 0; }
.seg button:hover { background: #282936; color: #c9d1d9; }
.seg button.active { background: #2b3a52; color: #bcd4ee; }
.seg button:disabled { opacity: 0.38; cursor: not-allowed; }
.seg button:disabled:hover { background: #1e1f28; color: #9ca3af; }
.btn-row { margin-top: 12px; display: flex; gap: 8px; flex-wrap: wrap; }
.btn-row button {
padding: 8px 16px; cursor: pointer; font-size: 0.83em; font-weight: 600;
border: 1px solid #2d2e3a; border-radius: 4px; background: #22232e; color: #c9d1d9;
transition: background 0.12s;
}
.btn-row button:hover { background: #2a2b38; }
.btn-row button:disabled { opacity: 0.4; cursor: not-allowed; }
#runBtn { background: #3b6ea5; color: #e6edf3; border-color: #3b6ea5; }
#runBtn:hover { background: #4a7fb8; }
#runBtn:disabled { background: #3b6ea5; }
#stopBtn { color: #e57373; border-color: #5c3333; background: #1e1518; }
#stopBtn:hover { background: #2a1c20; }
#getWeightsBtn { background: #3b6ea5; color: #e6edf3; border-color: #3b6ea5; }
#getWeightsBtn:hover { background: #4a7fb8; }
#presets { margin-top: 12px; }
#presets button {
font-size: 0.75em; padding: 3px 9px; margin: 0 5px 5px 0;
cursor: pointer; border: 1px solid #2d2e3a; border-radius: 3px;
background: #1e1f28; color: #9ca3af; transition: background 0.12s, color 0.12s;
}
#presets button:hover { background: #282936; border-color: #4a4b5c; color: #c9d1d9; }
#status { font-size: 0.78em; color: #7d8590; margin-top: 12px; font-family: Consolas, Menlo, monospace; }
#status .ok { color: #7ee787; font-weight: 600; }
#status .err { color: #f47067; font-weight: 600; }
#dlbar { margin-top: 8px; height: 4px; background: #22232e; border-radius: 2px; overflow: hidden; }
#dlbar > div { height: 100%; width: 0%; background: #6e9fd4; transition: width 0.2s; }