tsm-webgpu-test / static /webgpu_component.html
juanbascur's picture
Upload static/webgpu_component.html with huggingface_hub
ab2314f verified
Raw
History Blame Contribute Delete
4.5 kB
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body { font-family: sans-serif; margin: 0; padding: 6px; font-size: 14px; color: #333; }
#status { color: #666; margin-bottom: 4px; }
#progress { width: 100%; height: 6px; display: none; margin-bottom: 4px; }
#error { color: #c00; line-height: 1.5; }
</style>
</head>
<body>
<div id="status"></div>
<progress id="progress" max="100" value="0"></progress>
<div id="error"></div>
<script type="module">
const MODEL_REPO = "juanbascur/specter2-proximity-onnx";
const BATCH_SIZE = 64;
// ── Streamlit component API ──────────────────────────────────────────────
function send(type, extra = {}) {
window.parent.postMessage({ isStreamlitMessage: true, type, ...extra }, "*");
}
const setValue = v => send("streamlit:setComponentValue", { value: v, dataType: "json" });
const setHeight = h => send("streamlit:setFrameHeight", { height: h });
send("streamlit:componentReady", { apiVersion: 1 });
setHeight(20);
window.addEventListener("message", async (event) => {
if (event.data.type !== "streamlit:render") return;
const { papers, run } = event.data.args;
if (run) await runEmbeddings(papers);
});
// ── Main ─────────────────────────────────────────────────────────────────
async function runEmbeddings(papers) {
const statusEl = document.getElementById("status");
const progressEl = document.getElementById("progress");
const errorEl = document.getElementById("error");
errorEl.textContent = "";
if (!navigator.gpu) {
errorEl.innerHTML =
"It seems your browser does not support WebGPU. " +
"Chrome, Edge and Opera support it by default. " +
"Firefox and Safari need additional configuration. " +
"Alternatively, run the embeddings on the web server " +
"by checking <b>Use fallback resources</b>, but this takes much longer.";
setHeight(90);
setValue({ error: "webgpu_not_supported" });
return;
}
try {
progressEl.style.display = "block";
setHeight(50);
statusEl.textContent = "Loading model… (first run may take a minute)";
const { AutoTokenizer, AutoModel } = await import(
"https://cdn.jsdelivr.net/npm/@huggingface/transformers@3"
);
const tokenizer = await AutoTokenizer.from_pretrained(MODEL_REPO);
statusEl.textContent = "Tokenizer ready, loading model weights…";
const model = await AutoModel.from_pretrained(MODEL_REPO, {
device: "webgpu",
dtype: "fp16",
});
statusEl.textContent = "Model ready, starting encoding…";
const total = papers.length;
const allCLS = new Float32Array(total * 768);
for (let start = 0; start < total; start += BATCH_SIZE) {
const batch = papers.slice(start, start + BATCH_SIZE);
const texts = batch.map(p => p.title + " [SEP] " + (p.abstract || ""));
const inputs = tokenizer(texts, { padding: true, truncation: true, max_length: 512 });
const output = await model(inputs);
const hs = output.last_hidden_state;
const [B, L, H] = hs.dims;
const data = hs.data;
for (let b = 0; b < B; b++)
allCLS.set(data.slice(b * L * H, b * L * H + H), (start + b) * 768);
const done = Math.min(start + BATCH_SIZE, total);
progressEl.value = (done / total) * 100;
statusEl.textContent = `Encoding ${done} / ${total}…`;
}
progressEl.value = 100;
statusEl.textContent = "Done.";
// Encode as base64 in chunks to avoid call stack overflow
const bytes = new Uint8Array(allCLS.buffer);
let binary = "";
for (let i = 0; i < bytes.length; i += 8192)
binary += String.fromCharCode(...bytes.subarray(i, i + 8192));
setValue({ embeddings_b64: btoa(binary), n_papers: total, n_dims: 768 });
} catch (err) {
errorEl.textContent = "Error: " + err.message;
setHeight(60);
setValue({ error: err.message });
}
}
</script>
</body>
</html>