Docs
runonweb is a set of ES modules. Each one picks WebGPU or WASM, downloads the right weights, and decodes inputs for you.
Install runonweb
pnpm add runonweb
npm install runonweb
bun add runonweb
yarn add runonwebRequires a bundler that understands package exports (Vite, Next, Astro, Bun…). Weights are fetched from Hugging Face at runtime and cached by the browser.
The pattern: load, run, dispose
Every module follows the same shape:
const m = new Module({ onProgress })
await m.load() // downloads + caches weights, safe to call twice
await m.<task>(input) // the actual work
m.dispose() // free memoryEach module also exports a one-shot function (transcribe, removeBackground,caption, read…) that loads, runs and disposes in one call. Use the class when you will run more than once.
Core: WebGPU detection
import { isWebGPUAvailable, resolveDevice } from 'runonweb/core'
const ok = await isWebGPUAvailable()
const device = await resolveDevice() // 'webgpu' | 'wasm'Scan : OCR
Live demo →Extract printed and scene text in the browser. Three sizes (tiny, small, medium) so you can trade a few megabytes for accuracy. Nothing is uploaded.
import { OCR } from 'runonweb/ocr'
const ocr = new OCR({
size: 'small', // tiny | small | medium
onProgress: (info) => console.log(info.status, info.progress),
})
await ocr.load()
const { text, lines } = await ocr.read(imageFile)
console.log(text)
// lines: [{ text, score, box: { xmin, ymin, xmax, ymax } }]
ocr.dispose()- Base ·
- PP-OCRv6 by PaddlePaddle
- License ·
- Apache-2.0
- Download ·
- ~31 MB (small) · tiny ~6 MB · medium ~139 MB
- Runs on ·
- fp32 on WebGPU · fp32 on WASM
- Default size is
small(~31 MB): the best size/quality trade-off. Passtiny(~6 MB) ormedium(~139 MB). smallandmediumcover 50 languages (Chinese, English, Japanese, Latin).tinyis Chinese + English.- Each line comes back with a box and a confidence score.
Remover : Background removal
Live demo →Remove image backgrounds client-side. Drop a photo and get a PNG with an alpha channel. Private by default, and offline-capable once the model is cached.
import { RemoveBackground } from 'runonweb/remove-bg'
const remover = new RemoveBackground({
onProgress: (info) => console.log(info.status, info.progress),
})
await remover.load()
// Blob, File, URL, HTMLImageElement, ImageData, or canvas
const png = await remover.remove(imageFile)
// png is a PNG Blob with alpha channel
document.querySelector('img').src = URL.createObjectURL(png)
remover.dispose()- Base ·
- BEN2 by Prama LLC
- License ·
- MIT
- Download ·
- ~219 MB
- Runs on ·
- fp16 on WASM
- BEN2 (2025) is a general background eraser: hair, fur, products and hard edges, not just portraits.
- ~219 MB of fp16 weights, cached after the first download.
- Output is a PNG Blob with alpha, same size as the input.
- Runs on WASM. WebGPU dies in BEN2’s LayerNorm shader (fp16 activation, fp32 scale and bias) on current onnxruntime-web. The fix is upstream (onnxruntime#32629, 2026-09-22) and not published yet.
- Sample photos: Messi by Bryan Berlin (CC BY-SA 4.0), alfajores by V!NZ (CC BY-SA 3.0). Both resized.
Scribe : Speech-to-Text
Live demo →Transcribe audio to text entirely in the browser. Record from the mic or drop a file. Nothing is uploaded, nothing is billed per minute.
import { SpeechToText } from 'runonweb/stt'
const stt = new SpeechToText({
onProgress: (info) => console.log(info.status, info.progress),
})
await stt.load()
// Blob, File, URL string, or Float32Array (16 kHz mono)
const { text } = await stt.transcribe(audioBlob, {
onPartial: (partial) => console.log(partial),
})
console.log(text)
stt.dispose()- Base ·
- Whisper tiny.en by OpenAI
- License ·
- Apache-2.0
- Download ·
- ~150 MB / ~40 MB
- Runs on ·
- fp32 on WebGPU · q8 on WASM
- English-only by default. Pass
model(e.g.onnx-community/whisper-tiny) andlanguage(es,ja…) for a multilingual Whisper variant. Withoutlanguage, tiny often guesses English and the transcript comes out translated. The live demo always usestask: "transcribe"and lets you pick the language. - WebGPU is several times faster; WASM works everywhere else.
- Audio is decoded and resampled to 16 kHz mono in the browser.
- Pass
onPartialto receive words as they are generated. Results include word timestamps when the model supports them.
Alt : Image captioning
Live demo →Describe any image in one sentence, in English, Spanish, Portuguese, French, German, Arabic, Chinese, Japanese or Korean. Useful for alt text, search indexing and accessibility. Generated on-device, no image ever leaves the page.
import { ImageCaptioner } from 'runonweb/caption'
const captioner = new ImageCaptioner({ language: 'es', detail: 'short' })
await captioner.load()
const { text } = await captioner.caption(imageFile)
// "Un gato sentado sobre una mesa de madera."
// Per call overrides, streamed
await captioner.caption(imageFile, {
language: 'en',
detail: 'detailed',
onPartial: (partial) => console.log(partial),
})
captioner.dispose()- Base ·
- LFM2.5-VL-450M by Liquid AI
- License ·
- LFM Open License v1.0 (free < USD 10M revenue)
- Download ·
- ~316 MB / ~505 MB
- Runs on ·
- fp16+q4f16 on WebGPU · fp16+q8+q4 on WASM
- LFM2.5-VL-450M (Liquid AI, Nov 2025). A small vision-language model, prompted for captions, not a dedicated captioner.
- Pass
language(en,es,pt,fr,de,ar,zh,ja,ko) to caption in that language. Other languages are best-effort. - Pass
detail: "detailed"or"more"for longer captions. Default is short alt text.onPartialstreams the text as it is generated. - License: LFM Open License v1.0. Free for individuals and companies under USD 10M annual revenue. Above that, Liquid AI requires a commercial license. Not OSI-approved; the rest of the catalog is Apache-2.0 or MIT.
- WebGPU is the fast path (fp16 vision + q4f16 decoder, about a second per caption). WASM works but takes ~30 s per caption.
- It is a generative model: captions can hallucinate details. Review before publishing.
Depth : Depth estimation
Live demo →Turn a single photo into a depth map. Great for parallax effects, portrait blur or 3D-ish previews without any server round-trip.
import { DepthEstimator } from 'runonweb/depth'
const estimator = new DepthEstimator()
await estimator.load()
const { depth, width, height } = await estimator.estimate(imageFile)
// depth is a grayscale PNG Blob
document.querySelector('img').src = URL.createObjectURL(depth)
estimator.dispose()- Base ·
- Depth Anything V2 Small by DepthAnything (HKU / TikTok)
- License ·
- Apache-2.0
- Download ·
- ~50 MB / ~27 MB
- Runs on ·
- fp16 on WebGPU · q8 on WASM
- Output is a grayscale PNG: brighter means closer.
- The Small variant is Apache-2.0; larger Depth Anything models are not.
Spot : Object detection
Live demo →Find and label objects in an image with bounding boxes. People, cars, cats, cups, laptops: 80 COCO classes detected locally with RF-DETR Nano.
import { ObjectDetector } from 'runonweb/detect'
const detector = new ObjectDetector({
threshold: 0.5, // min confidence
})
await detector.load()
const objects = await detector.detect(imageFile)
// [{ label: 'cat', score: 0.98, box: { xmin, ymin, xmax, ymax } }]
detector.dispose()- Base ·
- RF-DETR Nano by Roboflow
- License ·
- Apache-2.0
- Download ·
- ~29 MB
- Runs on ·
- q8 on WASM
- RF-DETR Nano (2025). 80 COCO classes: people, cars, animals, furniture and more.
- WASM only for now. WebGPU is skipped: this ONNX export collapses confidence scores.
- Boxes are returned in pixel coordinates of the original image.
- Tune
thresholdto trade recall for precision.
Vector : Text embeddings
Live demo →Turn sentences into 384-dimensional vectors for semantic search, clustering or deduplication. Compare meaning, not keywords, in the browser, in milliseconds.
import { TextEmbedder, cosineSimilarity } from 'runonweb/embed'
const embedder = new TextEmbedder()
await embedder.load()
const { embeddings } = await embedder.embed([
'How do I reset my password?',
'I forgot my login credentials',
'What is the weather today?',
])
cosineSimilarity(embeddings[0], embeddings[1]) // ~0.7
cosineSimilarity(embeddings[0], embeddings[2]) // ~0.0
embedder.dispose()- Base ·
- all-MiniLM-L6-v2 by sentence-transformers
- License ·
- Apache-2.0
- Download ·
- ~45 MB / ~23 MB
- Runs on ·
- fp16 on WebGPU · q8 on WASM
- Vectors are L2-normalized by default, so cosine similarity is a dot product.
- Works best on English; pass a multilingual model via
modelif needed.
Lingo : Translation
Live demo →Translate text entirely in the browser. 17–44 MB per language pair, 58 languages to and from English. Private, offline after the first load, and free of per-character pricing.
import { Translator, PAIRS } from 'runonweb/translate'
// en → es: base-memory, ~37 MB
const translator = new Translator({ from: 'en', to: 'es' })
await translator.load()
const { text } = await translator.translate('Hello world')
// "Hola mundo"
// same instance, another pair (downloaded on demand)
await translator.translate('Bonjour', { from: 'fr', to: 'en' })
// keep the markup, translate the text nodes
await translator.translate('<b>Hello</b> world', { html: true })
translator.dispose()
PAIRS['en-ja'] // { architecture: 'base-memory', bytes: 43849787, comet: 0.90, … }- Base ·
- Firefox Translations (Marian NMT) by Mozilla
- License ·
- MPL-2.0
- Download ·
- ~22 MB (tiny) · ~37 MB (base-memory) · ~49 MB (ja, ko, zh)
- Runs on ·
- int8 on WASM (Bergamot)
- Marian NMT students distilled from a larger teacher, run through the Bergamot WASM runtime (int8 GEMM, single worker, no COOP/COEP headers needed).
base-memorywhen available (≈37 MB, on par with cloud translators within ~5% COMET),tinyfor the rest (≈22 MB).PAIRSlists every pair with size and COMET score.- Pairs without a direct model pivot through English:
es → frloadses-enanden-fr. - Pass
html: trueto translate markup while keeping the tags in place. - Codes are BCP 47:
zhis Simplified Chinese,zh-HantTraditional. OneTranslatorcan serve many pairs; new pairs download on demand. - Prefer one model for 100 languages? Pass
model: "Xenova/m2m100_418M"(MIT, ~630 MB, Transformers.js, slower).
Polish : Transcript cleanup
Live demo →Clean a speech-to-text transcript in the tab: drop fillers, resolve self-corrections, write numbers and emails, and apply punctuation. Pair it with Scribe, or paste any rough transcript.
import { TranscriptCleaner } from 'runonweb/clean'
const cleaner = new TranscriptCleaner({
styling: 'semi-formal',
onProgress: (info) => console.log(info.status, info.progress),
})
await cleaner.load()
const { text } = await cleaner.clean(
'so um i need to like send the the report by uh friday no wait make that thursday',
{ onPartial: (partial) => console.log(partial) },
)
// "I need to send the report by Thursday."
cleaner.dispose()- Base ·
- S1-mini by Superwhisper
- License ·
- Apache-2.0
- Download ·
- ~339 MB / ~385 MB
- Runs on ·
- q4f16 on WebGPU · q4 on WASM
- S1-mini by Superwhisper (keep that exact name). Fine-tuned from Qwen3-0.6B to do one job: normalize English ASR output. It is not a chat model.
- Pass
styling(casual·semi-casual·semi-formal·formal),structure(prose·lists) andcontext(general·email). Defaults are semi-formal prose. - English only. Keep a single pass under ~1,000 tokens; chunk longer transcripts at sentence boundaries.
- Filler-only input (
um,uh) returns an empty string. That is a valid result, not a failure. - WebGPU prefers q4f16 (~339 MB) and falls back to q4 (~385 MB) if the session fails to start. WASM uses q4.
Emojify : Text-to-Emoji
Live demo →Translate any English sentence into a short emoji sequence with a 2.4M-parameter model trained by runonweb. Loads in under a second, runs in a few milliseconds and never leaves the tab.
import { Emojifier } from 'runonweb/emoji'
// 3.9 MB, served from your own /models/ folder
const emojifier = new Emojifier({ modelPath: '/models/' })
await emojifier.load()
const { text, emojis } = await emojifier.emojify('I love pizza and my dog')
// text "🍕❤️🐶"
// emojis ["🍕", "❤️", "🐶"]
emojifier.dispose()- Base ·
- text2emoji-tiny (T5, 2.4M params, from scratch) by runonweb · data: Text2Emoji (KomeijiForce)
- License ·
- MIT
- Download ·
- ~4 MB
- Runs on ·
- q8 on WASM
- Trained from scratch: a 3-layer T5 (d_model 128, 8k shared vocab) on ~490k text/emoji pairs from the Text2Emoji dataset. Recipe in
training/text2emoji. - English input. Output is a short sequence of distinct emojis;
maxEmojiscaps the length (default 12). - Weights are self-hosted: pass
modelPath: "/models/"to serve them from your own site, ormodelwith a Hugging Face repo id.
Voice : Text-to-Speech
Live demo →Synthesize speech from text on-device. Three sizes: Kokoro 82M for English, Spanish and French, Supertonic 2 for English, Korean, Spanish, Portuguese and French at 44.1 kHz, or KittenTTS nano at ~28 MB. Stream PCM as it is generated, or download a WAV.
import { TextToSpeech } from 'runonweb/tts'
const tts = new TextToSpeech({ size: 'small', voice: 'af_heart' })
await tts.load()
for await (const chunk of tts.speakStream('Hello from the browser')) {
// chunk.audio is 24 kHz PCM. Play as it arrives
}
const wav = await tts.speakToBlob('Hola mundo', { voice: 'ef_dora' })
new Audio(URL.createObjectURL(wav)).play()
// Supertonic 2: one model, five languages, 44.1 kHz
const multi = new TextToSpeech({ size: 'multi', voice: 'st_f1' })
const pt = await multi.speakToBlob('Olá do navegador', { language: 'pt' })
tts.dispose()- Base ·
- Kokoro 82M by hexgrad
- License ·
- Apache-2.0
- Download ·
- ~326 MB / ~92 MB (small) · multi ~262 MB · tiny ~28 MB
- Runs on ·
- fp32 on WebGPU · q8 on WASM (multi is fp32, tiny is WASM)
- Default
smallis Kokoro 82M (StyleTTS 2): American, British, Spanish and French. Runs on Transformers.js directly, nokokoro-js. fp32 on WebGPU (fp16 gives NaN on the v4 runtime), q8 on WASM. - Pass
size: "multi"for Supertonic 2 (Supertone, 2026): one ~262 MB model for English, Korean, Spanish, Portuguese and French at 44.1 kHz, 10 preset voices (st_f1…st_m5). Passlanguageper call. - Supertonic 2 is OpenRAIL-M: open weights with use restrictions (no impersonation, no deception, no illegal use). The rest of the TTS sizes are Apache-2.0.
- Pass
size: "tiny"for KittenTTS nano (~28 MB, 8 English voices, WASM). - Audio streams sentence by sentence via
speakStream. WebGPU is several times faster onsmallandmulti; WASM works everywhere. - Pass
voiceto pick a speaker (af_heart/bella/st_f1). Kokoro Spanish and French download a local eSpeak-NG WASM (~18 MB) on first use. Nothing is uploaded.
Imagine : Image generation
Live demo →Generate images from a text prompt entirely in the tab. Bonsai Image 4B runs on WebGPU. No API key, no upload, nothing leaves the device after the first download.
import { ImageGenerator } from 'runonweb/image'
const gen = new ImageGenerator({ size: 'binary' })
await gen.load()
const { image, seed } = await gen.generate(
'A bonsai tree in a quiet ceramic studio, soft morning light',
)
document.querySelector('img').src = URL.createObjectURL(image)
gen.dispose()- Base ·
- Bonsai Image 4B (FLUX.2 Klein) by Prism ML
- License ·
- Apache-2.0
- Download ·
- ~3.4 GB (binary) · ternary ~3.9 GB
- Runs on ·
- 1-bit / 1.58-bit on WebGPU
- Bonsai Image 4B (Prism ML, 2026) is a 1-bit / 1.58-bit FLUX.2 Klein deployment. WebGPU only. There is no WASM path.
- Default
binaryis the smaller payload (~3.4 GB). Passsize: "ternary"(~3.9 GB) for the quality-oriented weights. - Tuned for 4 FlowMatch-Euler steps at guidance 1.0. More steps rarely help and can add artifacts. Negative prompts are not used.
- Default output is 512×512. Native training resolution is 1024×1024; sides must be multiples of 16 (32 recommended).
- First download is several gigabytes and is cached in IndexedDB. Chromium + a recent GPU is required.