Free · runs on your device · no sign-up
Type anything and hear it right away. The voice runs in this tab: a 3.5 MB model on a 136 kB WebAssembly engine I wrote for it, so your text never leaves your device. I started from Paradee v1.1, a small open model distilled from Kokoro-82M, and made it 12.9× faster in the browser while it scored slightly better on quality. Below you can race the old and new versions on your own hardware, see what each speed-up was, and take the files home.
Demo: the first pages of Alice's Adventures in Wonderland (Lewis Carroll, 1865, public domain), about 5 minutes of speech. Press Speak and watch how fast it renders.5,040 / 12,000
–
seconds of speech per second of compute
pdn
3.5 MB model, loads on first use
On device
your text never leaves this tab
How this text to speech works
Text becomes phonemes
A JavaScript port of misaki, the phonemizer Kokoro was trained on, looks words up in a lexicon it fetches in small pieces: about 90 kB before the first sentence.
A tiny model makes the sound
The phonemes go to a 2-4 MB model running on pdn, a WebAssembly engine built for this one model, in a background worker so the page stays smooth.
Audio streams to you
Sound starts after the first slice of a sentence is ready, a few tens of milliseconds, and plays while the rest is made. Download the whole thing as a WAV.
Live, on your hardware
Race them on your device
Same passage, same phonemes, one CPU thread each. These numbers come from your browser, not my benchmark box.
The passage every engine reads
“I started with a voice model that ran about fifteen times faster than real time in a browser. Then I rebuilt its sound engine, slimmed down every layer, and wrote a tiny WebAssembly runtime just for it. Now the same voice starts talking before you can blink.”
CPU: 1 thread per engine
SIMD:
checking…
· WebGPU:
no
v2fmine
onnxruntime-web, WASM
Paradee v1.1
onnxruntime-web, WASM
First run downloads about 22 MB; after that it comes from your browser cache.
Each engine runs in its own fresh Web Worker, one after another, on one CPU thread, with a short warm-up first. Speed is seconds of speech per second of compute. First sound is how long the first sentence takes before it can play: pdn streams, so it's the first chunk; onnxruntime-web can't, so it's the whole sentence. onnxruntime-web is 1.22.0-dev (the build Chickadee ships), loaded from jsDelivr; v1.1 is the published file from Hugging Face. onnxruntime-web needs several hundred MB of memory, so on a phone the tab may reload.
The speed-up, step by step
How I made it 12.9× faster
Every step below is a real model file, measured in the same run as v1.1 on one desktop core (i7-14700K) in Node, which runs WebAssembly on the same V8 engine as Chrome. Every step that changed the weights had to keep quality: 400 dev sentences × 3 seeds, 1,600 more held-out sentences, and a second speech recogniser as a guard. The test set got one look per release.
onnxruntime-web 1.22, the runtime I started onpdn, my engine× realtime, one CPU thread, the same 200 test sentences
Paradee-8M v1.1: Kokoro-82M's af_heart voice distilled into 7.6M parameters. It sounds great, but in onnxruntime-web it ran at 15× realtime and took 3.6 s from a cold start to its first sound.
1
Faster DSP, same weights
A closed-form sine bank instead of a running-sum oscillator, radix-4 block STFTs, and DSP tables rebuilt when the session starts instead of stored in the file.
2
Slimmer text side, packed weights
A smaller text model, and every layer stored at the fewest bits it can take (4, 6 or 8), unpacked at load. The file went from 8.46 MB to 4.60 MB.
3
Copy-free decoder head
I laid out the head's tensors so the STFT stages never copy data between them. Same function, less memory traffic.
4
GAN-trained decoder head
v2d
A new head with a 300-point noise path, trained adversarially. It sounds better than v1.1's and does less work.
5
A distilled, slimmer trunk
The decoder trunk went from 256 to 192 channels, distilled from the wider one.
6
Quantisation-aware training, int8 math
v2e
Fine-tuned for its own 4-bit weights and 8-bit compute, so the biggest matrix multiplies run in int8 without costing quality.
7
Slimmer again: 128 channels
A second distillation round. The trunk is now half as wide as v1.1's.
Harmonics computed on the noise path's grid so one inverse STFT makes both, noise drawn by rows from a small table, and only ops a GPU backend supports.
9
A smaller text model
lite
A narrower text encoder: 0.8 MB smaller and about 15% faster, for a small word-error cost.
10
No LSTM, fully streamable
v2g
A convolutional text side with a sentence vector, and a decoder with fixed per-channel norms. Both run in chunks, so audio can start after about 10 ms.
A C to WebAssembly engine written for exactly this model: 136 kB instead of onnxruntime-web's 21.6 MB, SIMD128 and relaxed-SIMD builds with int8 dot products, FFT-based DSP, its own noise generator, and streaming. Same v2g weights, 2.1× faster again. (v2f on pdn: 149×, 9.6× v1.1.)
v2f on pdn is already in production as the read-aloud voice of a web-novel reading site. The browser numbers above are on one thread, because a normal web page can't count on more: the 4-thread onnxruntime-web build needs cross-origin isolation, and pdn doesn't need any of it.
Phone speed, emulated
Who speaks first on a phone?
The same files on a desktop core slowed down to phone speed with Chrome's CPU throttling. The lanes start talking at their measured time.
Who speaks first?
A median 7.4 s sentence, played back at real speed
Paradee v1.1
onnxruntime-web
1.8 s
onnxruntime-web can't stream: its first sound waits for the whole sentence.
Speed on the same throttled CPU
× realtime, one thread, 30 test sentences
Paradee v1.1
onnxruntime-web
These are pdn's relaxed-SIMD numbers (Chrome on Android has it). With plain SIMD128, as on Safari and Firefox, v2g gets 35× and 55 ms to first sound.
This is an emulation, not a real phone. I don't have a phone lab, so I used Chrome's standard CPU throttling (4× is Lighthouse's “mid-tier mobile”, 6× is low end) with Chrome pinned to one desktop core. It doesn't model ARM chips, heat or memory bandwidth, so trust the ratios more than the absolute numbers. The race above runs on your actual device. First visit on slow 4G at mid-tier speed, from page load to the first sound: v2g 11.6 s, v2f 16.5 s. Nearly all of that is downloading 2.1 MB or 3.1 MB; after that it's cached.
Download, memory, start-up
Smaller everything
The squares are drawn to scale by area. Cold start means a fresh process to the first sound of a short sentence: 3.6 s with v1.1 on onnxruntime-web, most of it compiling a 21.6 MB WebAssembly runtime.
Runtime download
Compressed: 3.0 MB vs 38 kB
Model file
7.57M → 2.91M parameters. v2f is 3.42 MB.
Memory at start
Peak, fresh process to the first sound of a short sentence
Cold start to first sound
Runtime compile, model load and a short sentence, local files
WebGPU
What about the GPU?
I measured it once, on a desktop graphics card in Chrome.
v2f on onnxruntime-web
WebGPU, RTX 4070 Ti
113×
v2f on onnxruntime-web
WASM, one CPU core
89×
One core of a desktop CPU running pdn beat a desktop RTX 4070 Ti running onnxruntime-web's WebGPU backend, in the same Chrome session. The GPU also needed 1.75 s to start (session plus shader compilation) and can't stream, and a laptop's integrated GPU or a phone would be slower still. So the CPU is the right default for a model this small. v1.1 can't take the GPU route at all. This is one measured run, so read it as a direction, not a decimal.
Quality
Did the voice get worse?
No. Faster and smaller didn't cost quality: the naturalness score went up and word errors stayed within noise. Listen for yourself.
Listen side by side
“White dwarf stars, if they have a near companion, may then become Type Ia supernovae.”
Held-out test sentences, rendered with the released files. 96 kbps MP3.
Scores on 200 held-out sentences
| Model | UTMOS (higher) | WER (lower) |
|---|---|---|
| Kokoro-82M | 4.520 | 5.67% |
| Paradee v1.1 | 4.391 | 5.85% |
| v2f | 4.462 | 5.65% |
| v2g | 4.453 | 5.48% |
UTMOS predicts how natural speech sounds; v2f's gain over v1.1 is statistically significant. Word error rate is what Whisper mishears, and those differences are within noise. A stronger recogniser, Whisper large-v3-turbo, shows no regression against v1.1 on two larger sets, and on 1,600 more held-out sentences v2f's error rate there came out lower than Kokoro's own.
Do it yourself
Run it on your own site
It's all static files: no server, no API key, no GPU. These are the release files byte for byte, with checksums.
Licences. The models are Apache-2.0. They're derived from Paradee-8M v1.1 by Sahil Mahendrakar (Apache-2.0), which is distilled from Kokoro-82M by hexgrad (Apache-2.0); please keep both credits. The engine and JavaScript are Apache-2.0, and misaki's lexicons are Apache-2.0.
eSpeak NG is not in the bundle. It's GPL-3.0-or-later. Without it, the phonemizer reads every word in its lexicon (96.5% of words in my test set) and throws on the rest, mostly rare names. To add it, pass espeakUrl as above (it loads from jsDelivr only when needed) or npm i phonemizer with a bundler or Node. Check the GPL fits your project first. This page uses it for unknown words.
Credits
The voice is Paradee: Paradee-8M v1.1 by Sahil Mahendrakar (Apache-2.0) is the model I started from, and it was distilled from Kokoro-82M by hexgrad (Apache-2.0), whose af_heart voice it speaks. Text goes through a JavaScript port of hexgrad's misaki phonemizer and its lexicons (Apache-2.0). Words outside the lexicon go through eSpeak NG (GPL-3.0-or-later), which your browser loads from jsDelivr only when it's needed. The comparisons use onnxruntime-web (MIT) from jsDelivr and the Kokoro-82M ONNX export by onnx-community. The v2 models, the pdn engine and this page are mine.