---
title: PiVo-1.4M
canonical_url: "https://www.modelscope.ai/models/empero-ai/PiVo-1.4M"
md_url: "https://www.modelscope.ai/models/empero-ai/PiVo-1.4M.md"
repository: empero-ai/PiVo-1.4M
last_updated: 2026-11-02
license: apache-2.0
pipeline_tag: text-to-speech
tasks:
  - text-to-speech
library_name:
  - onnx
  - pytorch
frameworks:
  - pytorch
language:
  - en
downloads: 1
stars: 0
tags:
  - onnx
  - pivo
  - fargan
  - cpu
  - english
  - text-to-speech
  - small-model
---

# PiVo-1.4M

> PiVo-1.4M - An open-source model by empero-ai on ModelScope. How many parameters does a voice need? PiVo is our attempt at an answer: two English voices, Pi and Vo, from a complete text-to-speech stack with 1,401,562 learned parameters, vocoder included. A…

empero-ai/PiVo-1.4M is a text-to-speech model on ModelScope. licensed under apache-2.0.

- **Repository**: empero-ai/PiVo-1.4M
- **License**: apache-2.0
- **Tasks**: text-to-speech
- **Tags**: onnx, pivo, fargan, cpu, english, text-to-speech, small-model
- **Downloads**: 1
- **Stars**: 0
- **Last updated**: 2026-11-02

Source: https://www.modelscope.ai/models/empero-ai/PiVo-1.4M

---

# PiVo-1.4M

How many parameters does a voice need? PiVo is our attempt at an answer:
two English voices, **Pi** and **Vo**, from a complete text-to-speech stack
with **1,401,562 learned parameters**, vocoder included. A 584,965-parameter
acoustic model predicts vocoder features, and an 816,597-parameter
FARGAN-derived vocoder turns them into **16 kHz mono audio**.

Everything runs locally on a CPU with NumPy and ONNX Runtime. There is no
PyTorch, teacher model, speaker encoder or network connection involved at
inference time.

PiVo is a research model. Its speech is very intelligible, but some
sentences still have a metallic or robotic texture. The
[technical report](technical_report.pdf) describes what we measured and
what we tried.

**Author:** Leon Lehmann (lehmann@empero.org) · **Empero Research** (research@empero.org)

## Listen

Both samples read "NASA is ready for the next mission."

| Voice | Sample |
| --- | --- |
| Pi | [pi.wav](samples/pi.wav) |
| Vo | [vo.wav](samples/vo.wav) |

## Quick start

With Python 3.11 or newer, from a clone of this repository:

```bash
python -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
python inference.py synthesize --model-dir . --voice Pi \
  --text "NASA is ready for the next mission." --output pi.wav
python inference.py synthesize --model-dir . --voice Vo \
  --text "Hello, my name is Vo." --output vo.wav
```

Or from Python:

```python
from pivo import PiVo

model = PiVo(".", threads=1)
model.save("pi.wav", "NASA is ready for the next mission.", voice="Pi")

audio = model.synthesize("Hello, my name is Vo.", voice="Vo", speed=1.0)
# float32 mono PCM at model.sample_rate (16000 Hz)
```

PiVo uses its own small ONNX runtime, so it does not work with
Transformers `AutoModel` or the hosted Inference API. Our companion code
repository adds an installable command-line tool, an HTTP server, a
Dockerfile and tests.

## Text handling

PiVo speaks English only, in the voices `Pi` and `Vo`. Its frontend looks
words up in the CMU pronouncing dictionary and expands numbers, currency
amounts, ordinals and ISO dates. When a word is not in the dictionary, it
raises an error instead of guessing. You can supply the pronunciation in
ARPAbet:

```python
model.save("name.wav", "Hello, Zzvo.", voice="Vo",
           overrides={"zzvo": ["Z", "IY1", "V", "OW0"]})
```

Short all-caps words such as "CPU" are spelled out letter by letter; "NASA"
is read as a word. Ambiguous input such as slash dates and fractions, or
unsupported characters, also raise a clear error.

The whole input is encoded before audio generation starts. After that,
`iter_audio` yields audio in 10 ms frames, so playback can begin while the
rest of the sentence is generated.

By default, input is limited to 2,000 characters and 512 frontend tokens.
One utterance can be at most 30 seconds long (3,000 acoustic frames), and
speed can be set from 0.5 to 2.0. Split longer text into sentences or
clauses.

## How it works

The acoustic design is inspired by Mariusz Kurman's
[Neuroblast](https://github.com/mkurman/neuroblast-v3); our implementation
is original. A 96-wide text encoder mixes attention and dilated
convolution, predicts a duration for each phone, and adds a few simple
timing signals that tell the decoder where it is within each phone. Two
causal decoder blocks then predict 18 cepstral coefficients, pitch and
pitch correlation every 10 ms. The FARGAN-derived vocoder generates four
40-sample subframes per frame. The discriminators used during training are
not part of the released model.

## Training data

Pi and Vo are synthetic voices. We designed them with Qwen3-TTS
VoiceDesign and used Qwen3-TTS Base to record the training data. After
filtering, the corpus contains 8,628 utterances (4,314 scripts read by both
voices), about 12.29 hours of audio, or 10.77 hours after trimming silence.
We split it into 8,362 training, 114 validation and 152 test utterances.
The exact teacher models and revisions are listed in
`evidence/teachers.json` and `evidence/teacher-model-card-provenance.json`.

## Evaluation

We evaluated PiVo and 12 other candidates on the same 266 held-out
utterances, transcribing the output with Whisper small.en:

| Split | Utterances | Reference words | Word errors | Word error rate |
| --- | ---: | ---: | ---: | ---: |
| Validation | 114 | 1,738 | 15 | 0.863% |
| Test | 152 | 2,254 | 12 | 0.532% |

These numbers say the words come through clearly. They do not measure
naturalness, and because the held-out text comes from the same sources and
teacher as the training data, they are not a general English benchmark.
Test scores played no part in choosing the model.

We also tried adapting the vocoder to reduce the robotic texture. In a
blind comparison of 20 sentence pairs, one listener rated the original and
adapted vocoders 6.10 and 6.15 out of 10 on average, with 18 ties and one
win each. Since it made no consistent difference, this release keeps the
original vocoder.

The ONNX graphs match the PyTorch model closely: the largest differences
are 1.23e-6 (encoder), 6.68e-6 (decoder), 3.87e-7 (conditioning) and
2.98e-7 (vocoder step). Full results are in [evaluation.json](evaluation.json).

## Limitations

- Some text sounds metallic or robotic, especially around sibilants and pauses.
- Pronunciation is limited to the CMU dictionary plus your overrides.
- Listening results come from a single listener and are not a MOS study.
- We have not yet measured speed or memory on ARM or mobile devices, and
  have not tested quantized versions.

## Files

| Path | Contents |
| --- | --- |
| `onnx/` | The four inference graphs and their metadata |
| `checkpoints/acoustic.pt` | Acoustic model checkpoint (SHA256 `0b4a2dc993e7e500aac1825465886ff23c11f2e1a2662577f2f5cd27f4efb4eb`) |
| `checkpoints/vocoder.pt` | Converted FARGAN vocoder (SHA256 `355d470826817afaa4e729d1b19c8383e985f365af5d2b913f1b9acdf8f7eca4`) |
| `model_manifest.json` | Sizes and hashes checked when the model loads |
| `PROVENANCE.json`, `evidence/` | Model lineage and teacher details |
| `evaluation.json` | Full evaluation results |
| `pivo/`, `inference.py` | Reference inference code |
| `samples/` | Demonstration audio |
| `technical_report.pdf`, `CITATION.cff` | Technical report and citation |

The PyTorch checkpoints are included for reference; inference only loads
the ONNX graphs, and their hashes are checked against `model_manifest.json`
at startup.

## License

Our code and the acoustic model weights are released under Apache-2.0. The
FARGAN-derived code and converted vocoder weights keep the Xiph/Opus BSD
license, and the pronunciation dictionary keeps its CMU license. Neither
the teacher weights nor the training recordings are included. Please keep
`LICENSE`, `NOTICE`, `LICENSES/` and `THIRD_PARTY.md` with any
redistribution.

## Citation

```bibtex
@techreport{lehmann2026pivo,
  title = {PiVo: two English voices in 1.4 million inference parameters},
  author = {Lehmann, Leon},
  institution = {Empero Research},
  year = {2026},
  month = {November},
  note = {Technical report, version 1.0}
}
```
