Instructions to use litert-community/PiVo-1.4M with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- LiteRT
How to use litert-community/PiVo-1.4M with LiteRT:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- Notebooks
- Google Colab
- Kaggle
PiVo-1.4M for LiteRT
This repository is a conversion of empero-ai/PiVo-1.4M to LiteRT, made with litert-torch 0.10.0 for LiteRT 2.3.0: on one CPU thread of a Galaxy S26, it makes one second of speech in 27.5 ms (2026-11-08). Speed is still being tuned (updates follow).
empero-ai/PiVo-1.4M is an English text-to-speech model with two voices, Pi and Vo, in 1,401,562 parameters, vocoder included. Its acoustic model predicts 18 cepstral coefficients, the pitch and the pitch correlation every 10 ms, and a FARGAN-derived vocoder turns them into 16 kHz mono audio. Its card says speed and memory had not yet been measured on ARM or mobile devices. This repository measured the speed on a Galaxy S26 and a Pixel 11 Pro.
The source model ships ONNX graphs and no PyTorch code, and LiteRT's converters do not take ONNX. So the four graphs (encoder, decoder, conditioning and vocoder) were written again in PyTorch, given the weights of the source model's ONNX files, and converted at float32. The graphs have fixed shapes: the encoder comes in five sizes, for up to 32, 64, 128, 256 and 512 tokens (phonemes, spaces, punctuation and two sentence markers); the decoder makes 16 frames (160 ms) per call; the vocoder makes one 10 ms frame per call. The Python host in host/ runs them with the source model's own text frontend, dictionary and loop: the loop rounds the durations and feeds the decoder and the vocoder, so audio comes out 10 ms at a time. By default the host uses a copy of the loop that calls the vocoder once per frame. runner/ is the C++ program the phones ran (LiteRT C API; runner/README.md has the build). There is no Android app yet.
Listen
Both voices read "NASA is ready for the next mission.", the sentence of the source model's samples. This repository's audio was made on a Galaxy S26's CPU with the files here; the reference audio by the source model's own code on ONNX Runtime.
| Voice | This repository, Galaxy S26 CPU | Source model, ONNX Runtime |
|---|---|---|
| Pi | pi_litert.wav | pi_reference.wav |
| Vo | vo_litert.wav | vo_reference.wav |
Each pair has the same number of samples (29,920 for Pi, 28,960 for Vo), and their log-mel spectrograms correlate at 0.9999993 and 0.9999997.
Run it
With the hf CLI (pip install huggingface_hub) and Python 3.12, the version the pinned packages were checked with:
hf download litert-community/PiVo-1.4M --local-dir PiVo-1.4M
cd PiVo-1.4M
python3.12 -m venv .venv
.venv/bin/pip install -r host/requirements.txt
.venv/bin/python examples/say.py "NASA is ready for the next mission." pi.wav
.venv/bin/python examples/say.py "Hello, my name is Vo." vo.wav --voice Vo --check
--check also downloads the source model's ONNX graphs (about 6 MB), runs the same text through the source model's own code on ONNX Runtime, and compares the two audios against the bars below: length, harmonics-to-noise ratio, 2β8 kHz band share and log-mel correlation. It exits with 1 when a bar is missed.
In your own code, the host's methods take the same arguments as the source model's PiVo:
import sys
sys.path.insert(0, "host")
from pivo_litert import PiVoLiteRT
model = PiVoLiteRT(".") # the CPU, one thread
model.save("pi.wav", "NASA is ready for the next mission.", voice="Pi")
audio = model.synthesize("Hello, my name is Vo.", voice="Vo") # float32 at 16 kHz
for frame in model.iter_audio("Good morning.", voice="Pi"):
pass # 160 samples (10 ms) at a time
The host reads text with the source model's frontend: English only, words from the CMU pronouncing dictionary, plus numbers, currency amounts, ordinals and ISO dates. A word that is not in the dictionary raises an error; overrides={"zzvo": ["Z", "IY1", "V", "OW0"]} (--override "zzvo=Z IY1 V OW0" in say.py) gives its pronunciation in ARPAbet. The limits are the source model's: 2,000 characters, 512 tokens, 30 seconds per utterance, and a speed from 0.5 to 2.0. The checks below ran at speed 1.0.
How close it is to the source model
The reference is the source model's own code on its own ONNX files: ONNX Runtime on the CPU, one thread. The check runs 54 utterances: 27 sentences in both voices, 3 from the source model's card and 24 written for this check, from 2 to 88 words. The bar was set before any LiteRT output was compared. The word error rate is over all 54 utterances; every other measure has to hold on each utterance:
- Whisper large-v3-turbo transcribes both audios (greedy). The word error rate may rise by at most 1 point, and at most 3 of the 54 transcripts may differ from the reference's.
- The speaker embedding (ECAPA,
speechbrain/spkrec-ecapa-voxceleb) has a cosine of at least 0.95 to the reference's. - The harmonics-to-noise ratio moves by at most 1 dB, and the share of energy between 2 and 8 kHz by at most 1.5 dB.
- The log-mel spectrograms correlate at 0.99 or more.
- At float32, each token's length in frames and each frame's pitch period come out as in the reference, except where the reference's own value lies within 0.0001 of a rounding boundary.
| Where | Runtime | Word error rate (transcripts that differ) | Lowest speaker cosine | Lowest log-mel correlation | Utterances with the reference's length | Within the bar |
|---|---|---|---|---|---|---|
| Apple M4 Max | Source model, the reference | 0.73 % | ||||
| Apple M4 Max | CPU, 1 thread | 0.73 % (0) | 0.99983 | 0.99982 | 54 of 54 | yes |
| Apple M4 Max | GPU (Metal), FP32 | 0.73 % (0) | 0.999996 | 0.999999 | 54 of 54 | yes |
| Galaxy S26 | CPU, 1 thread | 0.73 % (0) | 0.99999 | 0.999997 | 54 of 54 | yes |
| Pixel 11 Pro | CPU, 1 thread | 0.73 % (0) | 0.999998 | 0.9999995 | 54 of 54 | yes |
| Apple M4 Max | GPU (Metal), default precision (FP16) | 0.73 % (0) | 0.922 | 0.984 | 25 of 54 | no |
| Galaxy S26 | NPU (Qualcomm), every graph | 0.73 % (0) | 0.850 | 0.942 | 18 of 54 | no |
| Apple M4 Max | CPU, int8 weights | 0.73 % (0) | 0.897 | 0.922 | 13 of 54 | no |
The Apple M4 Max CPU row is one pitch period away from the reference: one frame of one utterance, where the reference's own value lies 1e-7 from the rounding boundary. The last two rows ran forms that are not in this repository: graphs rewritten for the NPU, and int8 weights. None of these runs moved a transcript or missed the bars for the harmonics-to-noise ratio and the 2β8 kHz band. All the tables are in REPRODUCE.md.
Run every graph at FP32. At FP16 the words stay the same, but rounding moves token lengths and pitch periods across their rounding boundaries, so the timing and the voice change. On Apple Metal at its default precision, 62 lengths and 590 pitch periods came out different and 29 of the 54 utterances changed length; on the Galaxy S26's GPU at FP16, 76 lengths, 628 pitch periods and also 29 utterances. On both, FP16 in the encoder and the decoder alone, with the vocoder at FP32, gave the same changes. On a GPU, set FP32 precision: CompiledModel.GpuOptions.Precision.FP32 in Kotlin, kLiteRtDelegatePrecisionFp32 in the C API; the host's backend="gpu" sets it. The Galaxy S26's NPU computes in FP16 and missed the bar, even with only the vocoder on it (lowest speaker cosine 0.937).
Speed
Time per second of speech is the runner's time for all 54 utterances (325 seconds of speech), after one warm-up utterance, divided by their length: 27.5 ms is a real-time factor of 0.0275. First audio is the time from the start of an utterance, with the graphs loaded and the tokens ready, to its first 10 ms of audio, the median over the 54 utterances. The phones ran runner/ on LiteRT 2.3.0, with the tokens that the host computed on a Mac; the text frontend is not in these times.
| Where | Runtime | Time per second of speech | First audio |
|---|---|---|---|
| Galaxy S26 | CPU, 1 thread | 27.5 ms | 1.11 ms |
| Galaxy S26 | CPU, 1 thread, vocoder_step |
29.8 ms | 1.12 ms |
| Galaxy S26 | CPU, 4 threads | 35.7 ms | 1.53 ms |
| Galaxy S26 | GPU, FP32 | 211 ms | 6.22 ms |
| Pixel 11 Pro | CPU, 4 threads | 33.2 ms | 2.92 ms |
| Pixel 11 Pro | CPU, 1 thread | 39.5 ms | 2.20 ms |
On the Galaxy S26, use one thread: four threads took 30 % longer than one, and 21 % longer in a second pair of runs in the opposite order, with an earlier version of the decoder file. On the Pixel 11 Pro, four threads were faster than one (33.2 and 35.6 ms in two runs). On the Galaxy S26's GPU, one vocoder call took 2.1 ms against 0.25 ms on one CPU thread, and the vocoder is called 100 times per second of speech. vocoder_step is the source model's split of the vocoder: the conditioning once per frame, then four calls of 40 samples. On the Galaxy S26 it took 8 % longer than vocoder_frame, and 5 % longer in a second pair.
Files
| File | Size | What it is |
|---|---|---|
encoder_n{32,64,128,256,512}_fp32.tflite |
1.7 to 1.8 MB | The text encoder, one file per input length in tokens |
decoder_fp32.tflite |
0.7 MB | The acoustic decoder, 16 frames per call |
vocoder_frame_fp32.tflite |
3.3 MB | The vocoder with its conditioning, one 10 ms frame (160 samples) per call. The host's default |
conditioning_fp32.tflite, vocoder_step_fp32.tflite |
0.29 MB, 3.0 MB | The source model's split of the vocoder (vocoder="step" in the host). On one CPU thread it gives the same audio as vocoder_frame, bit for bit |
host/, examples/ |
The Python host and the example. host/pivo/ is the source model's package (frontend, dictionary, loop), unchanged |
|
runner/ |
The C++ runner the phones ran (LiteRT C API) and its build script | |
samples/ |
The audio and the figure above | |
conversion/, REPRODUCE.md, SHA256SUMS |
The scripts that wrote the graphs from the source model's ONNX files, every check, and the checksums |
Run again in a new environment on the Mac, the scripts wrote all nine graphs byte for byte. If you change them, keep the decoder's feature head at rank 3: written as a rank-2 FULLY_CONNECTED followed by a broadcast multiply, it came out wrong on the Mac's Metal GPU.
License
The source model's terms, unchanged. The encoder and the decoder hold the PiVo acoustic model, under the Apache License 2.0. The conditioning and vocoder graphs hold the FARGAN-derived vocoder, whose code and converted weights keep the Xiph/Opus BSD license and its patent notices. The pronunciation dictionary in host/ keeps its CMU notice. LICENSE, NOTICE, LICENSES/ and THIRD_PARTY.md are the source model's files, unchanged.
- Downloads last month
- -
Model tree for litert-community/PiVo-1.4M
Base model
empero-ai/PiVo-1.4M