Deploy paradee-tts-api
OpenAI-compatible TTS: 8 MB CPU model, 4 voices, timestamps
paradee-tts-api
Just deployed
Paradee TTS API — an OpenAI-compatible /v1/audio/speech service on an 8 MB CPU text-to-speech model. One service, zero variables, no API keys, no GPU, no volume.
Paste the URL Railway gives you into any OpenAI SDK as base_url and call audio.speech.create. Four English voices, word-level timestamps, 24 kHz mono WAV, 8.5 MB of int8 ONNX baked into the image at build time with a sha256 check and a build-time gate that fails the build if the model cannot speak offline.
What you get
POST /v1/audio/speech—{input, voice, response_format, speed, model?}→ 16-bit PCM WAV, 24 kHz, mono.POST /v1/audio/speech/with-timestamps— same input → base64 WAV pluswords: [{word,start,end}].GET /voices,GET /v1/models(paradee-1.2),GET /health,GET /docs.- Voices:
af_heart,am_puck(American),bf_isabella,bm_fable(British, misaki's British lexicon).
from openai import OpenAI
client = OpenAI(base_url="https://your-service.up.railway.app/v1", api_key="not-needed")
resp = client.audio.speech.create(
model="paradee-1.2",
voice="af_heart",
input="Paradee is a small voice that runs anywhere.",
response_format="wav",
speed=1.0,
)
open("speech.wav", "wb").write(resp.content)
Measured on Railway (shared vCPU, single replica, 1 thread)
These numbers were measured against a deployed instance with tests/battery.py, not copied from the model's README. Railway's shared vCPU is much slower than a desktop, and request latency varies with host load — the ranges below are real measurements, spread over repeated runs.
| What | Measured on Railway |
|---|---|
| Short sentence (4.6 s of audio), 10 calls | p50 2.1–3.9 s, p95 2.2–7.1 s (implied 1.2–2.2× realtime) |
| 1,500-character paragraph (102.6 s of audio) | 6.7–12.7 s wall (8–15× realtime) |
| Speed scaling | speed=2.0 → 1.92× shorter; speed=0.5 → 2.02× longer |
| RSS after boot (American lexicon warm) | 332 MB |
| RSS after the first British request | ~505 MB |
| RSS after a maximum-length (1,500-char) request | ~766–795 MB plateau, stable across repeats (not unbounded) |
| Cold start | about a minute including the healthcheck window; a restart reaches /health in ~15–20 s |
| Image | 8.5 MB int8 ONNX, 4 voices, 1 CPU thread, no GPU |
Memory is the number to watch if you run on a small instance: the per-request working set for a 102-second utterance is larger than the boot footprint and the process does not give it back. Repeating the same long request does not raise it further. The British lexicon loads on the first British request (one-off ~2 s); /health reports which accents are resident.
Disclosures worth reading before you deploy
- Output is stochastic, not bit-exact. The exported graph contains
RandomNormalnoise nodes (the decoder's breathiness). Identical requests give the identical audio length — the predicted durations are deterministic — but not identical samples; the measured run-to-run difference is ~0.8% of full scale. Do not build byte-level caching on top of it; cache by text + voice + speed with your own TTL. - English only. Other languages are phonemized as if they were English.
- WAV only, 24 kHz mono. There is no mp3/opus/flac encoder in the image, and requesting one returns
400 unsupported_response_formatwith the reason spelled out rather than a WAV labelled mp3. - Limits.
inputis capped at 4,000 characters (400 input_too_long); sentences longer than 510 phonemes are split at the last space that fits, so very long unpunctuated text gains small pauses.speedis clamped to 0.5–2.0 and echoed inX-Paradee-Speed/X-Paradee-Clamped. - One request at a time. The ONNX session is shared and guarded by a lock inside a single worker; concurrency scales by replicas, not threads.
- Quality is below the teacher. The author's 200-sentence evaluation puts
af_heartat UTMOS 4.40 versus 4.52 for Kokoro-82M (bf_isabella4.06,am_puck4.32,bm_fable4.18), with word error rate within a few tenths of a point. This is a small, fast model, not a drop-in Kokoro replacement. - Early release. Paradee 1.2.x moves quickly (1.0 → 1.2.1 in a few weeks). The image pins the upstream git commit, the Hugging Face revision, and the weight sha256, so upgrades are deliberate.
- No server-side URL fetching. The API takes text only; there is no SSRF surface.
Deploy and Host
Click Deploy. Railway creates one service from this repository, builds the Docker image (multi-stage python:3.12-slim, non-root uid 10001), bakes the weights in, runs the build-time speech gate, and starts uvicorn on the injected PORT. A public domain is provisioned automatically and /health gates traffic: the endpoint only returns 200 once the model has loaded and can actually speak.
There is nothing to fill in — zero variables. PORT is injected by Railway and every other setting is baked into the image.
After the deployment is green:
curl -X POST https://your-service.up.railway.app/v1/audio/speech \
-H 'Content-Type: application/json' \
-d '{"input":"Paradee is a small voice that runs anywhere.","voice":"af_heart","response_format":"wav"}' \
--output speech.wav
About Hosting
The service is a single stateless container. The model files live inside the image (/app/models/paradee_int8.onnx plus config.json, both sha256-verified at build time), so there is no volume, no object storage, no database and no outbound network access at runtime (HF_HUB_OFFLINE=1, no Hugging Face call on boot). That also means the deploy is reproducible: the same image always speaks with the same model.
Because inference is CPU-bound and serialized, vertical scaling helps more than horizontal concurrency tricks: choose more vCPU for lower latency, or more replicas for more parallel callers. The container is sized to run on a small instance; give it at least 1 GB of memory if you expect maximum-length paragraphs.
Restart policy is ON_FAILURE with 10 retries, and the healthcheck is /health with a 300 s window — the first boot after a cold image pull is the slow part, not the model load.
Why Deploy
- No API keys, no per-character bill. The model is 8.5 MB of int8 ONNX; you pay for a small container instead of for tokens.
- OpenAI-compatible by design. Existing
openaiSDK code works by changingbase_url; errors use OpenAI's envelope so SDK clients raise realBadRequestErrors, and/v1/modelslistsparadee-1.2. - Timestamps included. The
/with-timestampsendpoint returns per-word start/end times from the model itself, which is what subtitling and word-highlighting need. - Verified, not just packaged. The build fails if the model cannot load and speak with the hub disabled, if British and American phonemization stop differing, or if the WAV/timestamp contracts break — so a green deploy is a working voice.
- No vendor benchmark inheritance. The latency and memory numbers in this listing were measured on Railway's shared vCPU; the model author's ~62× figure comes from a different CPU and is not repeated here.
Common Use Cases
- Narration and accessibility: read articles, dashboards or documents aloud on demand.
- Voice agents and IVR prototypes where a held-open connection to a paid TTS API is overkill.
- Subtitle and transcript timing: synthesize and get word-level timestamps in one call.
- Local-first and privacy-sensitive deployments: the text never leaves your Railway project and the process makes no outbound calls.
- Testing OpenAI-compatible clients without spending on a commercial TTS provider.
Dependencies for
One Railway service from one public GitHub repository. No database, no cache, no volume, no credentials, no environment variables. The image already contains everything the model needs.
Deployment Dependencies
- Paradee 1.2 — model and code by Sahil Mahendrakar, Apache-2.0, weights at sahilmahendrakar/Paradee-8M-v1.2, pinned to git commit
c2789ef9abf54a9384eef89cc7de3f1afe5ad01d(tag v1.2.1) and Hugging Face revisioned2934d4df8a4fd891e29584c5e1104649409765. - Kokoro-82M — the teacher model Paradee is distilled from, by hexgrad, Apache-2.0; Paradee reuses its vocabulary and voice names.
- Training sentences — WikiText-103, CC BY-SA 3.0.
- misaki — grapheme-to-phoneme (Kokoro's own G2P), by hexgrad;
en_core_web_sm3.8.0 (MIT) is installed at build time so no download happens at runtime. - eSpeak NG — bundled inside the
espeakng-loaderwheel (GPL-3.0 library loaded at runtime as a separate work); no system package is needed. - This wrapper — FastAPI + uvicorn on
python:3.12-slim(Python 3.12 is required: misaki supports versions below 3.13), Apache-2.0.
Template Content
paradee-tts-api
lNamelessl/paradee-tts-api
