Skip to content

Releases: uttera/uttera-tts-vllm

v1.5.1 — public-repo cleanup + CI fix

Choose a tag to compare

@fakehec fakehec released this 25 Sep 11:37

Public-repo hygiene, no functional change to the engine:

  • Removed copyrighted/celebrity voice references and pre-rebrand (J.A.R.V.I.S./Stark) branding from docs and examples; git history rewritten to purge them.
  • Fixed the CI lint job (ruff): moved error-handler imports to the top, dropped redundant aliases, split multi-import/statement lines.

Same standalone engine as the previous release.

v1.5.0 — Standalone engine-hardening; drop Redis self-registration

Choose a tag to compare

@fakehec fakehec released this 25 Sep 07:19

Engine-hardening sweep. The server is now standalone — one process, one
VoxCPM2 model, an OpenAI-compatible API and a /health. Run one, or several
behind any load balancer. Same engine (nano-vllm-voxcpm) and model
(openbmb/VoxCPM2) as 1.4.x.

Added

  • Engine circuit breaker (ENGINE_FAIL_THRESHOLD): consecutive 5xx mark the
    node not-ready so /health returns 503 instead of serving from a dead engine;
    4xx never trip it; a later success closes it.
  • In-process recovery self-probe (ENGINE_PROBE_SECONDS) so a transient
    failure self-heals without a manual restart. Zero cost while healthy.
  • Correct HTTP status codes: oversized text → 413, GPU OOM → 503 (busy, not
    broken — and it does not trip the breaker), malformed JSON → 400. Tracebacks
    stripped from error bodies.
  • Voice preload + lazy load + LRU cap (VOICE_PRELOAD, VOICE_CACHE_MAX).
  • Reference-sample trimming (REF_MAX_SECONDS) so a long cloning upload
    can't OOM the node.
  • Text normalization before synthesis (line breaks, unterminated sentences).
  • On-disk cache sweep (CACHE_SWEEP_SECONDS) so CACHE_TTL_MINUTES is a
    real retention bound.
  • Response integrity headers: X-Audio-Duration, X-Audio-SHA256,
    Content-Digest (RFC 9530).
  • Optional offline mode (UTTERA_OFFLINE=1), OFF by default.

Changed

  • Cache key MD5 → SHA-256.
  • CACHE_TTL_MINUTES default 10080 (7 days) → 60 (1 hour): synthesized
    audio may contain personal data; short retention is the privacy-friendly
    default.

Removed

  • The optional Redis self-registration loop and its env vars. Use a reverse
    proxy or load balancer for discovery.

Validated end-to-end on an RTX 5090 (Blackwell): synthesis, cache
MISS/HIT/BYPASS, lazy voice load, adhoc cloning, streaming, 413/422/400,
circuit breaker + recovery probe.

v1.3.0 — default port 5100 → 9004 (canonical Uttera-stack scheme)

Choose a tag to compare

@fakehec fakehec released this 18 Apr 21:02

Adopts the canonical Uttera-stack port scheme: TTS services
on 9004 (all backends — both hotcold and vllm siblings),
paired with the opposite service family on the adjacent port. The
Gatekeeper and clients route by service family — hotcold ↔ vllm
becomes a backend swap, not a port swap.

Port scheme

Port Service family Repos
9004 TTS uttera-tts-hotcold, uttera-tts-vllm
9005 STT uttera-stt-hotcold, uttera-stt-vllm
8880 Gatekeeper echo-gatekeeper (OpenResty)

Why move off 5100

While 5100 itself had no mainstream collisions, pairing TTS=5100
with STT=9005 (STT had to move off 5000 due to macOS AirPlay +
Docker Registry v2 collisions) was asymmetric. Both service families
now live in the clean 9000-9099 range (IANA "User Ports", no
canonical assignment).

Migration

Deployments that already override PORT via env var: no change required.

Deployments running on the old default (:5100):

  • Repoint your Gatekeeper / reverse proxy at :9004.
  • Or preserve the old endpoint by setting PORT=5100 in your env.
  • Docker users: update your -p flag or docker-compose.yml.

Related releases

  • uttera-tts-hotcold adopts the same 9004 port.
  • The other family (STT) moves to 9005 in the same round.

See CHANGELOG.md.

v1.2.0 — OpenAI-compat polish sweep (8 fixes, 1 CRITICAL)

Choose a tag to compare

@fakehec fakehec released this 18 Apr 17:25

⚠️ CRITICAL: if you are running v1.1.0 and depend on adhoc voice
cloning, upgrade. v1.1.0 silently ignored uploaded custom_voice_file
/ speaker_wav bodies and returned default-voice audio. v1.2.0 is
the first release where adhoc cloning actually works end-to-end.

Fixed

  1. [CRITICAL] Adhoc voice cloning was silently disabled. The
    isinstance(spec, UploadFile) check used
    fastapi.datastructures.UploadFile, but Starlette's form parser
    returns starlette.datastructures.UploadFile — distinct classes
    in FastAPI 0.136+ / Starlette 1.0+. The check always returned
    False, speaker_wav never latched, the handler silently fell
    through to the default voice, and the response was cached as a
    regular request (X-Route: HOT, X-Cache: MISS | HIT instead of
    the documented ADHOC / ADHOC). Fixed by matching either class
    plus a duck-type fallback.
  2. Bogus custom_voice_file bodies (non-audio, empty) were
    silently accepted — same root cause. Now rejected with HTTP 400
    and a trimmed decode error.
  3. JSON body without input raised pydantic.ValidationError
    that escaped as HTTP 500 with no body. Now caught → HTTP 422.
  4. speed range validation. Values outside [0.25, 4.0]
    (OpenAI spec) were silently accepted. Now HTTP 422.
  5. speed is now actually applied. The VoxCPM2 engine doesn't
    support variable-rate synthesis natively; speed != 1.0 was
    silently ignored. _encode_audio() now routes every output
    format through an ffmpeg atempo filter chain (chained for
    values < 0.5 or > 2.0).
  6. cfg_value range validation in [0.5, 5.0] (VoxCPM2 safe
    range). Previously any float was accepted, allowing NaN / garbage
    output from the diffusion solver. Now HTTP 422 outside the range.
  7. HEAD /health now accepted (@app.api_route(methods=["GET", "HEAD"])).
    Load-balancer uptime probes no longer get 405s.
  8. Opt-in CORSMiddleware gated on CORS_ALLOW_ORIGINS env var
    (comma-separated list or "*"). Disabled by default — API-first
    deployments don't need it.

Verified

  • 128-concurrent regression burst: 128/128 OK, ~12 rps.
  • Adhoc voice cloning via both custom_voice_file and speaker_wav
    alias emit X-Route: ADHOC and generate audio in the cloned voice.
  • speed=2.0 → ~half-duration audio; speed=0.5 → ~double-duration
    (verified with ffprobe).
  • CORS preflight + actual POST emit the expected headers when
    CORS_ALLOW_ORIGINS is set.
  • Zero HTTP 500s in the sweep log.

Full diff

See CHANGELOG.md.

v1.1.0 — custom_voice_file canonical

Choose a tag to compare

@fakehec fakehec released this 17 Apr 23:14

Additive change (v1.x compatible with v1.0.0 — no breaking rename).

What changed

Adhoc voice cloning now uses custom_voice_file as the canonical multipart field name, matching uttera-tts-hotcold. The v1.0.0 name speaker_wav remains accepted as a backward-compatible alias.

# Canonical (new):
curl -F custom_voice_file=@sample.wav ...

# Still works (alias):
curl -F speaker_wav=@sample.wav ...

The server accepts any libsndfile-readable format (wav, flac, mp3, ogg, m4a) regardless of which field name you use. The speaker_wav name was a Coqui carry-over that misleadingly implied a WAV-only upload.

If both fields are sent on the same request, custom_voice_file wins.

Why

Symmetry with uttera-tts-hotcold v2.1.0. The same client code works against either backend:

# Targets any Uttera TTS backend unchanged:
curl -X POST $UTTERA_TTS/v1/audio/speech \
  -F input="Hola mundo" \
  -F custom_voice_file=@my_voice.wav \
  -F response_format=mp3 \
  -o out.mp3

Compatibility

This is a v1.x minor release. Everything from v1.0.0 still works — the rename is additive. Clients using speaker_wav don't need to change anything.

v1.0.0 — First stable release

Choose a tag to compare

@fakehec fakehec released this 17 Apr 21:37

uttera-tts-vllm graduates from pre-alpha to its first stable release.

What changed

End-to-end validation on NVIDIA RTX 5090 / Blackwell against the 40-prompt Spanish corpus in uttera-benchmarks Run 6:

Profile OK / Total p50 p95
latency 20/20 1.8 s 2.5 s
burst@8 8/8 3.3 s 3.4 s
burst@64 64/64 11.7 s 15.2 s
burst@256 256/256 33.9 s 57.7 s
burst@512 512/512 64.2 s 115.0 s
burst@1024 1024/1024 123 s 223 s
sustained (2 rps × 5 min) 600/600 3.3 s 4.0 s

Zero failures across every published profile. Aggregate throughput plateaus near 4.3 rps from N = 256 upwards.

API surface — now frozen behind semver

  • POST /v1/audio/speech — OpenAI-compatible JSON body or multipart form. Adhoc voice cloning via speaker_wav file field.
  • POST /v1/audio/speech/stream — chunked audio/wav streaming.
  • GET /v1/voices, POST /admin/reload-voices, GET /v1/models, GET /health.
  • Per-request cache opt-out via {"cache": false} body field or Cache-Control: no-cache header.
  • Response headers X-Route: CACHE | HOT | ADHOC and X-Cache: HIT | MISS | BYPASS | ADHOC | DISABLED expose routing + cache decisions.

Compatibility contract going forward

  • Endpoint paths, request/response schemas, env var names and their defaults, and the X-Cache / X-Route header values are frozen — breaking changes require v2.0.0.
  • Additive extensions are v1.x minor releases.
  • Bug fixes are v1.0.x patch releases.

Roadmap

  • v1.1 — dynamic voice registry (POST/DELETE /v1/voices, Model B).
  • v1.2 — Prometheus /metrics, structured logging, streaming-with-adhoc-cloning, in-repo bench harness.
  • Full details in ROADMAP.md.

Install

git clone https://github.com/uttera/uttera-tts-vllm
cd uttera-tts-vllm
./setup.sh   # creates venv, pre-installs torch, installs nano-vllm-voxcpm
./setup_assets.sh   # pulls the VoxCPM2 model + 6 standard voices
python main_tts.py   # serves on :5100

For head-to-head numbers against uttera-tts-hotcold on the same GPU and corpus, see uttera-benchmarks.

v0.1.0 — initial scaffold (pre-alpha)

Pre-release

Choose a tag to compare

@fakehec fakehec released this 17 Apr 04:36

Initial scaffold release of uttera-tts-vllm — the high-throughput TTS sibling of uttera-tts-hotcold. Single Python process, nano-vllm-voxcpm's continuous batching as the concurrency primitive, OpenAI-compatible API, adhoc voice cloning from day one.

⚠️ Pre-alpha. Not live-tested yet. Syntax + lint + structure pass locally; the first live validation is planned for v0.2.0.

API surface (stable target from v0.1.0):

  • POST /v1/audio/speech — JSON body or multipart (enables adhoc cloning via speaker_wav).
  • POST /v1/audio/speech/stream — chunked audio/wav, registered voices only in v0.1.0.
  • GET /v1/voices, POST /admin/reload-voices, GET /v1/models, GET /health.

Voice story (Models A + C of the pre-code design):

  • A — file-based registry (voices.json + assets/voices/{standard,elite}/), 6 standard voices precomputed at startup.
  • C — adhoc cloning via speaker_wav multipart field on /v1/audio/speech. Nothing persisted, cache bypassed, X-Route: ADHOC.

Model B (dynamic POST/DELETE /v1/voices) is scheduled for v0.2 — see ROADMAP.md.


First scaffold release. Pre-alpha — active development. API surface may
still change before v1.0.0.

Added

  • Single-process FastAPI server embedding nano-vllm-voxcpm's
    AsyncVoxCPM2ServerPool in-process. Concurrency handled entirely by
    the engine's internal continuous batching — no hot/cold worker pool.
  • OpenAI-compatible endpoints:
    • POST /v1/audio/speech — JSON body (OpenAI classic) or
      multipart/form-data (enables adhoc voice cloning via
      speaker_wav file upload). Response formats: mp3, wav, pcm,
      opus, flac. Cache-key identical to uttera-tts-hotcold: MD5 of
      (model, voice, speed, format, params, text). Cache is bypassed
      for adhoc requests.
    • POST /v1/audio/speech/stream — chunked audio/wav streaming.
      Uses AsyncVoxCPM2ServerPool.generate() directly; emits a
      0xFFFFFFFF-length WAV header immediately, then raw PCM chunks as
      the engine produces them.
    • GET /v1/voices — lists every voice whose latents are currently
      resident in memory, plus the configured default.
    • POST /admin/reload-voices — re-reads voices.json and recomputes
      latents for new entries without restarting the engine.
    • GET /v1/models, GET /health.
  • File-based voice registry (design Model A): voices.json at the
    repo root maps name → relative path inside
    assets/voices/{standard,elite}/. Latents are precomputed at startup
    and kept in memory.
  • Adhoc voice cloning (design Model C): POST /v1/audio/speech with
    a speaker_wav multipart file field clones the voice just for that
    request. No persistence. Cache is bypassed.
  • Audio cache with identical semantics to uttera-tts-hotcold:
    AUDIO_CACHE_DIR + CACHE_TTL_MINUTES. Set TTL to 0 to disable.
  • Optional Redis self-registration (parity with every other Uttera
    repo). When REDIS_URL is set, publishes {load_score, accepts_requests, host, port, version, engine="nano-vllm-voxcpm", model, ts} to tts:nodes:{NODE_ID}.
  • Engine tuning env vars: VLLM_GPU_MEM_UTIL,
    VLLM_MAX_NUM_SEQS, VLLM_MAX_NUM_BATCHED_TOKENS,
    VLLM_MAX_MODEL_LEN, VOXCPM_INFERENCE_TIMESTEPS. Defaults tuned
    for RTX 5090 (32 GB).
  • Asset pre-provisioning: setup_assets.sh pulls the VoxCPM2 model
    (~1.7 GB) and the 6 standard OpenAI reference voices
    (alloy/echo/fable/onyx/nova/shimmer) before the first request.
  • OSS scaffolding shared with the rest of the Uttera stack:
    LICENSE (Apache-2.0), NOTICE, AUTHORS.md, CODE_OF_CONDUCT.md,
    SECURITY.md, CONTRIBUTING.md, CODEOWNERS, .github/ templates,
    docs/img/ banner, Dockerfile + docker-compose.yml with NVIDIA GPU
    passthrough, systemd unit (uttera-tts-vllm.yml), CI workflow
    (lint + structure + optional GPU smoke).

Not implemented (yet)

  • tests/ with a benchmark harness. Will be ported from
    uttera-benchmarks in a later release.
  • Dynamic voice registry (design Model B: POST /v1/voices to upload +
    persist cloned voices, DELETE /v1/voices/{id} to remove). Documented
    in ROADMAP.
  • The GPU smoke CI job is defined but gated off (if: false) because
    no self-hosted GPU runner is configured yet.