NeuTTS-2E β€” ONNX

Original model: neuphonic/neutts-2e by Neuphonic.

Community ONNX export (FP32 + dynamic-int8) of NeuTTS-2E, Neuphonic's on-device emotional English TTS model (~236M params, Qwen3-architecture backbone + NeuCodec).

  • Speakers: emily, paul, sophie, steven (fixed, pre-encoded references ship with the neutts package)
  • Emotions: angry, disgusted, fearful, happy, neutral, sad, surprised
  • Output: 24 kHz audio, watermarked by default with Perth

This is an unofficial conversion, not affiliated with Neuphonic. Official releases: torch (original), q8 gguf, q4 gguf.

Files

File Size Fidelity vs torch fp32 Notes
model.onnx ~1.4 GB max logits diff < 1e-4 (measured 3.8e-05), argmax-identical FP32 export with KV cache β€” recommended for quality
model_int8.onnx ~350 MB logits diverge measurably (per-step top-50 overlap β‰ˆ 50–80%) Dynamic int8 β€” ~1.7Γ— faster; transcription-accurate in testing but not bit-comparable to fp32; listen before production use
onnx_infer.py β€” β€” Self-contained inference example (generation loop + NeuCodec decode)

A 4-bit MatMulNBits variant was also built and tested but is not shipped: it decoded at the same speed as int8 (45.1 vs 45.3 tok/s) while being 1.7Γ— larger and less accurate (argmax agreement 14/21 on teacher-forced steps), with no configuration (fp32/int8 lm_head, block size 32) beating int8 on any axis.

Benchmarks

Measured with bench_onnx.py on an idle AMD Ryzen Threadripper 7970X (32C/64T), onnxruntime 1.27.0 (CPU EP), prompt 454 tokens, 250 decode steps. Decode timing includes top-k/top-p/min-p sampling over the 217k vocab; it excludes prefill (shown separately) and NeuCodec decode (~0.2 s per 5 s of audio, ~4%). NeuCodec produces 50 frames/s of audio, so Γ—RT = tok/s Γ· 50 (higher is better).

Model Threads Prefill Decode Speed vs real-time
model.onnx (fp32) 4 564 ms 27.5 tok/s 0.55Γ—
model.onnx (fp32) default (64) 480 ms 14.9 tok/s 0.30Γ—
model_int8.onnx 3 367 ms 45.3 tok/s 0.91Γ—
model_int8.onnx 4 357 ms 40.6 tok/s 0.81Γ—
model_int8.onnx default (64) 343 ms 18.6 tok/s 0.37Γ—

Set intra_op_num_threads to 3–4. Decode throughput peaks there and collapses at higher counts (the ONNX Runtime default β€” all cores β€” is 2–3Γ— slower than the peak for this small-hidden-size model). onnx_infer.py defaults to 4 threads (--threads).

Conversion details

Exported with optimum-cli export onnx --task text-generation-with-past (optimum 2.1.0 / optimum-onnx 0.1.0, fp32). Export validated against the torch model: fp32 prefill logits max abs diff 3.8e-05 with identical argmax, and 21 teacher-forced decode steps with 21/21 argmax agreement and 10/10 top-10 overlap. Note the neutts library runs the original backbone in bfloat16, which itself deviates from the fp32 reference by up to 0.136 in logit space β€” the FP32 ONNX model is numerically closer to the fp32 weights than the official pipeline. The int8 model is a dynamic-range quantization (onnxruntime.quantization.quantize_dynamic, QInt8 weights).

Because ONNX cannot tie weights, the shared embedding/LM-head matrix of the original model is materialized twice in the FP32 graph β€” that is why the FP32 file is larger than the original safetensors. One config change was made for export tooling compatibility: the transformers-5 rope_parameters block was flattened back to the legacy rope_theta: 10000.0 key.

The neutts library does not support ONNX backbones directly (only ONNX codec decoders), so this repo ships onnx_infer.py, which reproduces the library's prompt format exactly (verified token-for-token against neutts' _apply_chat_template, including the emotion-token path and quote normalization).

Generation defaults

onnx_infer.py samples with temperature 1.0, top-k 50, top-p 0.95, min-p 0.05. The official torch backend uses pure top-k 50; the official GGUF path additionally inherits llama.cpp's default top-p 0.95 / min-p 0.05, and in A/B testing that chain scored slightly better (mean +0.21 PESQ over 4 matched pairs), so it is the default here β€” pass --top-p 1.0 --min-p 0 for exact torch-backend behavior. min_new_tokens=50 forces β‰₯1 s of audio even for very short inputs (matching the library), and the 2048-token context bounds a single generation to roughly 30 s of audio. As with the original model, output is stochastic: occasional bad draws (a slurred word, a trailing artifact) happen at any precision β€” re-run with a different --seed if a take sounds off.

Usage

pip install neutts onnxruntime soundfile
hf download Danny-Dasilva/neutts-2e-onnx --local-dir neutts-2e-onnx
cd neutts-2e-onnx

python onnx_infer.py --model model.onnx --tokenizer . \
    --text "I can't believe it's finally here!" \
    --speaker emily --emotion happy --out test.wav

NeuCodec weights are fetched from the Hub on first run. Pre-encoded speaker references are loaded from the installed neutts package. Use --model model_int8.onnx for the faster int8 variant and --codec neuphonic/neucodec-onnx-decoder to run the codec decoder under ONNX Runtime as well (torch is still required to load the .pt speaker references and by the neucodec/neutts packages).

Voice samples

Generated with the FP32 model.onnx in this repo (default sampler above, seed 1234 β€” except paul/sophie neutral, re-rolled with seed 4321), decoded with the torch NeuCodec decoder, Perth-watermarked. All four voices are included, each with a neutral sample and one emotional sample, plus two samples from model_int8.onnx for quality comparison. Every sample below was verified to transcribe exactly back to its input text with Whisper (base.en).

Model Voice Emotion Text Audio
fp32 emily neutral The quick brown fox jumps over the lazy dog, and honestly, it never gets old.
fp32 emily happy I can't believe it, this is the best news I've heard all year!
fp32 paul neutral The quick brown fox jumps over the lazy dog, and honestly, it never gets old.
fp32 paul angry How many times do I have to say it? This is completely unacceptable!
fp32 sophie neutral The quick brown fox jumps over the lazy dog, and honestly, it never gets old.
fp32 sophie sad I really thought things would turn out differently this time.
fp32 steven neutral The quick brown fox jumps over the lazy dog, and honestly, it never gets old.
fp32 steven surprised Wait, what? I had absolutely no idea that was even possible!
int8 emily neutral The quick brown fox jumps over the lazy dog, and honestly, it never gets old.
int8 sophie sad I really thought things would turn out differently this time.

License

Distributed under the NeuTTS Open License v1.0 (free research use; commercial use limited to entities under $5M annual revenue β€” see LICENSE). Modification notice per the license: this repository contains an ONNX export (and an int8-quantized variant) of the original safetensors weights; conversion changes are described above.

Downloads last month
36
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Model tree for Danny-Dasilva/neutts-2e-onnx

Quantized
(2)
this model