Skip to content
runonweb

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

Installbash
pnpm add runonweb
npm install runonweb
bun add runonweb
yarn add runonweb

Requires 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:

Patternts
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 memory

Each 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

runonweb/corets
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.

runonweb/ocrts
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. Pass tiny (~6 MB) or medium (~139 MB).
  • small and medium cover 50 languages (Chinese, English, Japanese, Latin). tiny is 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.

runonweb/remove-bgts
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.

runonweb/sttts
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) and language (es, ja…) for a multilingual Whisper variant. Without language, tiny often guesses English and the transcript comes out translated. The live demo always uses task: "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 onPartial to 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.

runonweb/captionts
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. onPartial streams 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.

runonweb/depthts
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.

runonweb/detectts
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 threshold to 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.

runonweb/embedts
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 model if 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.

runonweb/translatets
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, … }
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-memory when available (≈37 MB, on par with cloud translators within ~5% COMET), tiny for the rest (≈22 MB). PAIRS lists every pair with size and COMET score.
  • Pairs without a direct model pivot through English: es → fr loads es-en and en-fr.
  • Pass html: true to translate markup while keeping the tags in place.
  • Codes are BCP 47: zh is Simplified Chinese, zh-Hant Traditional. One Translator can 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.

runonweb/cleants
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) and context (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.

runonweb/emojits
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; maxEmojis caps the length (default 12).
  • Weights are self-hosted: pass modelPath: "/models/" to serve them from your own site, or model with 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.

runonweb/ttsts
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 small is Kokoro 82M (StyleTTS 2): American, British, Spanish and French. Runs on Transformers.js directly, no kokoro-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_f1st_m5). Pass language per 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 on small and multi; WASM works everywhere.
  • Pass voice to 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.

runonweb/imagets
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 binary is the smaller payload (~3.4 GB). Pass size: "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.