Spaces:
Running
Running
Download worker.js from tanushlukha/tinygpt: direct link, hf CLI and curl.
- Browser
- Download file 4.5 kB
-
https://huggingface.co/spaces/tanushlukha/tinygpt/resolve/main/worker.js
- Command line
-
hf download hf://spaces/tanushlukha/tinygpt/worker.js
-
curl -L -o worker.js https://huggingface.co/spaces/tanushlukha/tinygpt/resolve/main/worker.js
4.5 kB
| // The page's second thread: downloads the models once, then runs them (web/tinygpt.js) without | |
| // freezing the page. Messages in: load, generate, stop, parse. Messages out: progress, ready, | |
| // piece, done, parsed, error. | |
| import { MODEL_BASE } from "./config.js"; | |
| import { GPT, Parser, Tokenizer, readSafetensors, stream } from "./tinygpt.js"; | |
| const CACHE = "tinygpt-models-v1"; | |
| const loaded = {}; // "lm" | "parser" -> Promise of {config, tok, model} | |
| let stopRequested = false; | |
| const post = (message) => self.postMessage(message); | |
| async function openCache() { // Cache Storage: kept between visits (absent in some private windows) | |
| try { return await caches.open(CACHE); } catch { return null; } | |
| } | |
| async function fetchOk(url) { | |
| let res; | |
| try { res = await fetch(url); } catch { throw new Error("the network request failed"); } | |
| if (!res.ok) throw new Error(`the server answered HTTP ${res.status}`); | |
| return res; | |
| } | |
| /** The file's bytes, reporting progress as they arrive. */ | |
| async function download(url, onProgress) { | |
| const res = await fetchOk(url); | |
| const total = Number(res.headers.get("content-length")) || 0; | |
| const reader = res.body.getReader(), parts = []; | |
| let got = 0; | |
| for (;;) { | |
| const { done, value } = await reader.read(); | |
| if (done) break; | |
| parts.push(value); | |
| got += value.length; | |
| onProgress(got, total); | |
| } | |
| const bytes = new Uint8Array(got); | |
| let at = 0; | |
| for (const p of parts) { bytes.set(p, at); at += p.length; } | |
| return bytes.buffer; | |
| } | |
| async function sha256(buffer) { | |
| const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", buffer)); | |
| return Array.from(digest, (b) => b.toString(16).padStart(2, "0")).join(""); | |
| } | |
| /** A bundle as bundle.load_bundle reads it: config, tokenizer, then weights checked against their | |
| * SHA-256. The weights are cached only once they pass, and checked again when read from the cache. */ | |
| async function loadBundle(name) { | |
| const base = new URL(`${name}/`, new URL(MODEL_BASE, self.location.href)).href; | |
| const config = await (await fetchOk(base + "config.json")).json(); | |
| const tok = new Tokenizer(await (await fetchOk(base + config.tokenizer)).json()); | |
| const url = base + "model.safetensors", want = config.sha256["model.safetensors"]; | |
| const cache = await openCache(); | |
| let buffer = null; | |
| const hit = cache && await cache.match(url); | |
| if (hit) { | |
| buffer = await hit.arrayBuffer(); | |
| if (await sha256(buffer) !== want) { buffer = null; await cache.delete(url); } // damaged: download again | |
| } | |
| if (!buffer) { | |
| buffer = await download(url, (got, total) => post({ type: "progress", name, got, total })); | |
| if (await sha256(buffer) !== want) throw new Error("the downloaded weights don't match their checksum"); | |
| cache?.put(url, new Response(buffer)).catch(() => {}); // the Response keeps its own copy | |
| } | |
| const model = new GPT(config.model_config, readSafetensors(buffer)); | |
| post({ type: "ready", name }); | |
| return { config, tok, model }; | |
| } | |
| function load(name) { // one download per model; after a failure, the next request tries again | |
| loaded[name] ??= loadBundle(name).catch((e) => { delete loaded[name]; throw e; }); | |
| return loaded[name]; | |
| } | |
| // a zero-delay pause, so a "stop" message can arrive between two tokens | |
| const channel = new MessageChannel(); | |
| const tick = () => new Promise((resolve) => { channel.port1.onmessage = () => resolve(); channel.port2.postMessage(0); }); | |
| async function generate({ id, prompt, params }) { | |
| const { model, tok } = await load("lm"); | |
| stopRequested = false; | |
| for (const event of stream(model, tok, prompt, params, () => stopRequested)) { | |
| post(event.done ? { type: "done", id, stats: event.done } : { type: "piece", id, text: event.text }); | |
| await tick(); | |
| } | |
| } | |
| async function parse({ id, text }) { | |
| const { model, tok, config } = await load("parser"); | |
| const t0 = performance.now(); | |
| const result = new Parser(model, tok, config.task).parse(text); | |
| post({ type: "parsed", id, result, seconds: (performance.now() - t0) / 1000 }); | |
| } | |
| self.onmessage = async ({ data }) => { | |
| try { | |
| if (data.type === "load") await load(data.name); | |
| else if (data.type === "stop") stopRequested = true; | |
| else if (data.type === "generate") await generate(data); | |
| else if (data.type === "parse") await parse(data); | |
| } catch (e) { | |
| post({ type: "error", id: data.id, name: data.name ?? (data.type === "parse" ? "parser" : "lm"), message: e.message }); | |
| } | |
| }; | |