Text-to-Speech
ONNX
GGUF
Chinese
English
onnxruntime
tts
on-device
jetson
telephony
vits
mb-istft-vits
multi-speaker
mandarin
taiwanese-mandarin
imatrix
conversational
Instructions to use Luigi/PrimeTTS with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use Luigi/PrimeTTS with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf Luigi/PrimeTTS:F32 # Run inference directly in the terminal: llama cli -hf Luigi/PrimeTTS:F32
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf Luigi/PrimeTTS:F32 # Run inference directly in the terminal: llama cli -hf Luigi/PrimeTTS:F32
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf Luigi/PrimeTTS:F32 # Run inference directly in the terminal: ./llama-cli -hf Luigi/PrimeTTS:F32
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf Luigi/PrimeTTS:F32 # Run inference directly in the terminal: ./build/bin/llama-cli -hf Luigi/PrimeTTS:F32
Use Docker
docker model run hf.co/Luigi/PrimeTTS:F32
- LM Studio
- Jan
- Ollama
How to use Luigi/PrimeTTS with Ollama:
ollama run hf.co/Luigi/PrimeTTS:F32
- Unsloth Studio
How to use Luigi/PrimeTTS with Unsloth Studio:
Install Unsloth Studio (macOS, Linux, WSL)
curl -fsSL https://unsloth.ai/install.sh | sh # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for Luigi/PrimeTTS to start chatting
Install Unsloth Studio (Windows)
irm https://unsloth.ai/install.ps1 | iex # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for Luigi/PrimeTTS to start chatting
Using HuggingFace Spaces for Unsloth
# No setup required # Open https://huggingface.co/spaces/unsloth/studio in your browser # Search for Luigi/PrimeTTS to start chatting
- Docker Model Runner
How to use Luigi/PrimeTTS with Docker Model Runner:
docker model run hf.co/Luigi/PrimeTTS:F32
- Lemonade
How to use Luigi/PrimeTTS with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull Luigi/PrimeTTS:F32
Run and chat with the model
lemonade run user.PrimeTTS-F32
List all available models
lemonade list
- Atomic Chat
| #!/usr/bin/env python3 | |
| """Generate the BreezyVoice teacher corpus for distillation into Inflect-Nano. | |
| Clean short reference (no prompt-leak) + INLINE ASR GATE: each clip is transcribed | |
| (faster-whisper) and kept only if Han-CER vs intended text is below threshold; else | |
| retried, then skipped. Writes 22.05kHz wav + manifest {id,text,wav,dur,cer}. Resumable. | |
| Run in .venv-breezy with PYTHONPATH=BreezyVoice repo. | |
| """ | |
| from __future__ import annotations | |
| import argparse, json, os, sys, time | |
| import soundfile as sf, numpy as np | |
| BV = "/home/luigi/jetson-tts/third_party/BreezyVoice" | |
| ZT = "/home/luigi/jetson-tts/mossnano/zhtw8k" | |
| sys.path.insert(0, BV) | |
| from single_inference import CustomCosyVoice, get_bopomofo_rare | |
| from g2pw import G2PWConverter | |
| from cosyvoice.utils.file_utils import load_wav | |
| from faster_whisper import WhisperModel | |
| REF_AUDIO = f"{ZT}/ref/ref_clean.wav" | |
| REF_TEXT = open(f"{ZT}/ref/ref_clean.txt").read().strip() | |
| import re as _re | |
| import opencc as _opencc | |
| _T2S = _opencc.OpenCC('t2s') # normalize trad<->simp so the gate scores REAL pronunciation, not script | |
| def han(s): return "".join(c for c in s if "一" <= c <= "鿿") | |
| def is_zh(text): return bool(_re.search(r"[一-鿿]", text)) | |
| def _lev(r, h): | |
| if not r: return 0.0 | |
| d = list(range(len(h)+1)) | |
| for i in range(1, len(r)+1): | |
| prev = d[0]; d[0] = i | |
| for j in range(1, len(h)+1): | |
| cur = d[j]; d[j] = min(d[j]+1, d[j-1]+1, prev+(r[i-1] != h[j-1])); prev = cur | |
| return d[len(h)]/len(r) | |
| def _enwords(s): return _re.findall(r"[a-z']+", s.lower()) | |
| def score(ref, hyp): | |
| """Han-CER for zh/mix; word-error-rate for pure-English. Lower = better.""" | |
| if is_zh(ref): | |
| return _lev(han(_T2S.convert(ref)), han(_T2S.convert(hyp))) | |
| return _lev(_enwords(ref), _enwords(hyp)) # word-level Levenshtein ratio | |
| def read_tsv(path, limit, skip): | |
| rows = [] | |
| for line in open(path, encoding="utf-8"): | |
| line = line.rstrip("\n") | |
| if not line.strip(): continue | |
| parts = line.split("\t") | |
| rows.append((parts[0] if len(parts) > 1 else f"utt{len(rows):06d}", parts[-1].strip())) | |
| rows = rows[skip:] | |
| return rows[:limit] if limit else rows | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--model", default="/home/luigi/jetson-tts/models/BreezyVoice") | |
| ap.add_argument("--corpus", default="/home/luigi/jetson-tts/data/text/train.tsv") | |
| ap.add_argument("--out-dir", required=True) | |
| ap.add_argument("--limit", type=int, default=0) | |
| ap.add_argument("--skip", type=int, default=0) | |
| ap.add_argument("--min-sec", type=float, default=0.8) | |
| ap.add_argument("--max-sec", type=float, default=20.0) | |
| ap.add_argument("--cer-thresh", type=float, default=0.30) | |
| ap.add_argument("--retries", type=int, default=3) | |
| args = ap.parse_args() | |
| os.makedirs(args.out_dir, exist_ok=True) | |
| man_path = os.path.join(args.out_dir, "manifest.jsonl") | |
| done = set() | |
| if os.path.exists(man_path): | |
| for l in open(man_path): | |
| try: done.add(json.loads(l)["id"]) | |
| except Exception: pass | |
| print(f"resuming: {len(done)} done | ref: {REF_TEXT}") | |
| cv = CustomCosyVoice(args.model); conv = G2PWConverter() | |
| asr = WhisperModel("SoybeanMilk/faster-whisper-Breeze-ASR-25", device="cuda", compute_type="float16") # zh-TW gate (traditional output) | |
| ref_bopo = get_bopomofo_rare(cv.frontend.text_normalize_new(REF_TEXT, split=False), conv) | |
| ref_wav = load_wav(REF_AUDIO, 16000) | |
| rows = read_tsv(args.corpus, args.limit, args.skip) | |
| todo = [(u, t) for u, t in rows if u not in done] | |
| print(f"to synth: {len(todo)} / {len(rows)}") | |
| mf = open(man_path, "a", encoding="utf-8") | |
| t0 = time.time(); n_ok = 0; n_skip = 0; tot = 0.0 | |
| for i, (utt, text) in enumerate(todo): | |
| bopo = get_bopomofo_rare(cv.frontend.text_normalize_new(text, split=False), conv) | |
| best = None; best_cer = 9.9 | |
| for attempt in range(args.retries): | |
| try: | |
| out = cv.inference_zero_shot_no_normalize(bopo, ref_bopo, ref_wav) | |
| w = out["tts_speech"].squeeze().cpu().numpy().astype(np.float32) | |
| except Exception as e: | |
| print(f" [err {utt}] {e}"); continue | |
| dur = len(w) / 22050 | |
| if dur < args.min_sec or dur > args.max_sec: continue | |
| tmp = os.path.join(args.out_dir, f".{utt}.tmp.wav"); sf.write(tmp, w, 22050) | |
| segs, _ = asr.transcribe(tmp, language=("zh" if is_zh(text) else "en"), beam_size=5) | |
| c = score(text, "".join(s.text for s in segs)) | |
| if c < best_cer: best_cer = c; best = (w, dur, tmp) | |
| if c <= args.cer_thresh: break | |
| if best is None or best_cer > args.cer_thresh: | |
| n_skip += 1 | |
| if best and os.path.exists(best[2]): os.remove(best[2]) | |
| if n_skip <= 20 or n_skip % 50 == 0: | |
| print(f" [SKIP {utt}] best_cer={best_cer:.2f} | {text[:30]}") | |
| continue | |
| w, dur, tmp = best | |
| wp = os.path.join(args.out_dir, f"{utt}.wav"); os.replace(tmp, wp) | |
| mf.write(json.dumps({"id": utt, "text": text, "wav": wp, "dur": round(dur, 3), | |
| "cer": round(best_cer, 3)}, ensure_ascii=False) + "\n"); mf.flush() | |
| n_ok += 1; tot += dur | |
| if (i + 1) % 25 == 0: | |
| el = time.time() - t0 | |
| print(f" {i+1}/{len(todo)} ok={n_ok} skip={n_skip} audio={tot/60:.1f}min " | |
| f"{el/(i+1):.2f}s/clip eta={(len(todo)-i-1)*el/(i+1)/60:.0f}min") | |
| mf.close() | |
| print(f"DONE ok={n_ok} skip={n_skip} audio={tot/60:.1f}min in {(time.time()-t0)/60:.0f}min") | |
| if __name__ == "__main__": | |
| main() | |