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

Log-mel spectrograms of the four samples

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
-
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Model tree for litert-community/PiVo-1.4M

Quantized
(1)
this model