Join the conversation

Join the community of Machine Learners and AI enthusiasts.

Sign Up
maratosΒ 
posted an update 9 days ago
Post
82
LinnetTTS: a 41 M-parameter English text-to-speech model with voice prompts that streams from the first 80 ms and runs in real time on one phone CPU core. Code Apache-2.0; weights under the Linnet Weights License (free to use in your own products, commercial included).

Same machine, same three texts, each model through its own Python package. Speed only, quality not measured:
- Linnet 41 M: RTF 10.6 % on one core (3.4 % on four), first audio 87 ms, 187 MB RSS, voice prompts, streams every 80 ms
- pocket-tts 100 M: 23.8 % on two cores, 968 MB, voice prompts, streams every 80 ms
- Kokoro-82M: 51.7 %, 1.2 GB, whole utterance per call, 8 languages
- Piper medium 16 M: 5.8 %, 503 MB, no voice prompts, per-sentence chunks
On phones: iPhone 13 Pro Max 5.5 ms per 80 ms step, iPhone 11 7 ms, Snapdragon 662 14 ms on four cores.

🎧 Hear the same sentence from all four, side by side: the "Compare" tab of the demo Space VoxRT/linnet-tts (type your own text for Linnet there too)
πŸ“¦ Weights and model card: VoxRT/linnet-en-35m
πŸ¦€ Code, Rust runtime, bindings for Linux C, Python (pip install linnet-tts), Android and iOS, and the benchmark script: https://github.com/VoxRT/LinnetTTS
πŸ“Š Method: https://github.com/VoxRT/LinnetTTS/blob/main/docs/benchmarks.md
πŸ“ Write-up with the trade-offs and the samples: https://huggingface.co/blog/VoxRT/small-cpu-text-to-speech-models-compared-piper-kok
πŸ“š Collection of small CPU TTS models and their demos: VoxRT/on-device-tts-small-models-that-run-on-a-cpu-6aec390f2f84a4407d227cf4

English only for now; no text normalisation, so write numbers as words. If it is useful to you, a like on the model or the Space helps other people find it. Questions welcome.

The first-audio definition in the benchmark is helpful: it stops at the API returning a chunk. For the phone results, do you also have request-to-first-playback latency, with the audio output already open versus opened for that request? That would show how much of the 80 ms chunking benefit survives the playback buffer in a voice UI.

Β·

Good question, and no, we do not have that number yet. What the phone figures measure: the timer starts after the player object exists and stops when the first 80 ms chunk comes back from the runtime (prime pass + one step). On iOS the sample creates LinnetPlayer before the timer, and its AVAudioEngine is started in the constructor, so the output is already open. On Android the AudioTrack is built before the timer but play() is called on the first write, so that one is "opened for this request" minus the construction cost. Nothing after the first write is measured: no output latency of the audio stack, no "cold open" of the route.

Two things I can say without measuring. The 8 s figure in the player helpers is a cap on how much audio may be queued ahead, not a pre-fill: playback starts on the first chunk, so the chunking benefit is not lost in our own buffer. What sits on top is the platform's output latency, which we have not characterised per device and will not guess at here.

It is easy to add: the iOS player already schedules buffers with a .dataPlayedBack completion callback, so timestamping the first one gives request-to-first-played-back directly, and AudioTrack.getTimestamp does the same on Android. I will run both variants (route already open vs opened for the request) on the A15 and the Snapdragon 662 and post the numbers here and in the README.

In this post