feat(omnivoice): tune streaming defaults (16-step + aggressive packing)
Empirical follow-up to the streaming /tts smoke test on the 3090. OmniVoice is diffusion: a ~fixed per-call overhead (~1.5s at 32 steps, ~0.7s at 16) dominates regardless of chunk length, so the upstream-claimed 40x RTF does NOT hold here (measured ~2.8x/32-step, ~5.6x/16-step) and the chatterbox- tuned scheduler over-chunks and starves. - Streaming /tts defaults to num_step=16 (TTFA ~1.5s -> ~0.7s); batch /v1/audio/speech stays num_step=32 for quality. Per-request override intact. - Scheduler prior raised to rtf_prior=20 (env OMNIVOICE_STREAM_RTF_PRIOR, wired through compose + .env.example) so it packs whole-text-minus-first- sentence into a few chunks: validated ~3 chunks, no starvation, total wall ~= one-shot, less per-chunk silence padding. - Docs corrected: the "sub-second / 40x" claims were wrong; streaming has a diffusion TTFA floor (~0.7s) and wins mainly on long replies. chatterbox- fast (autoregressive, ~0.5s TTFA) stays the lowest-latency front-end; OmniVoice is the multilingual / voice-design complement.
This commit is contained in:
+19
-4
@@ -10,9 +10,11 @@ Two consumption modes:
|
||||
- BATCH (asset-engine / OpenAI-compat): POST /v1/audio/speech -> one WAV blob.
|
||||
- STREAM (live speech-to-speech chat engines): POST /tts -> chunked PCM, driven
|
||||
by the vendored adaptive buffer-ratchet scheduler (scheduler.py, from
|
||||
chatterbox-fast). Emits the first sentence immediately for sub-second
|
||||
time-to-first-audio, then ratchets chunk size up on OmniVoice's ~40x realtime
|
||||
headroom. Wire-compatible with chatterbox-fast's /tts (both 24 kHz mono s16le).
|
||||
chatterbox-fast). Emits the first sentence immediately so first-audio comes
|
||||
sooner than one-shot, then packs the rest into a few chunks. NB: OmniVoice is
|
||||
diffusion, so a ~fixed per-call overhead sets a TTFA floor (~0.7s at 16 steps
|
||||
on the 3090, NOT sub-second); the win grows with utterance length. Wire-
|
||||
compatible with chatterbox-fast's /tts (both 24 kHz mono s16le).
|
||||
|
||||
All text is run through the language-safe sanitizer (sanitize.py) before synthesis
|
||||
on BOTH endpoints — strips markdown / LLM artifacts / control tokens without the
|
||||
@@ -76,6 +78,14 @@ CKPT = os.environ.get("OMNIVOICE_CKPT", "k2-fsa/OmniVoice")
|
||||
VOICES_DIR = os.environ.get("OMNIVOICE_VOICES_DIR", "/app/voices")
|
||||
ASR_MODEL = os.environ.get("OMNIVOICE_ASR_MODEL", "openai/whisper-large-v3-turbo")
|
||||
|
||||
# Streaming scheduler prior. OmniVoice is diffusion: a ~fixed per-call overhead
|
||||
# dominates (short and long chunks cost ~the same), so the chatterbox default
|
||||
# (rtf_prior=3.4) over-chunks and STARVES — each extra chunk re-pays the fixed
|
||||
# cost and adds boundary silence. A high prior packs whole-text-minus-first-
|
||||
# sentence into a few chunks (validated on the 3090: ~3 chunks, no starvation,
|
||||
# total ≈ one-shot). Per-request `rtf_prior` still overrides this.
|
||||
OMNIVOICE_STREAM_RTF_PRIOR = float(os.environ.get("OMNIVOICE_STREAM_RTF_PRIOR", "20"))
|
||||
|
||||
app = FastAPI(title="OmniVoice TTS (asset-engine + streaming wrapper)")
|
||||
|
||||
MODEL: Optional[OmniVoice] = None
|
||||
@@ -99,7 +109,7 @@ class GenParams(BaseModel):
|
||||
language: Optional[str] = "Auto" # "Auto" -> auto-detect
|
||||
speed: Optional[float] = None # 0.5–1.5; ignored if duration set
|
||||
duration: Optional[float] = None # fixed seconds; overrides speed
|
||||
num_step: int = 32 # 4–64 diffusion steps
|
||||
num_step: int = 32 # 4–64 diffusion steps (batch=32; /tts overrides to 16)
|
||||
guidance_scale: float = 2.0 # 0.0–4.0 CFG
|
||||
denoise: bool = True
|
||||
preprocess_prompt: bool = True
|
||||
@@ -120,6 +130,10 @@ class SpeechRequest(GenParams):
|
||||
class TTSStreamRequest(GenParams):
|
||||
"""Streaming /tts request — chatterbox-fast-compatible wire protocol."""
|
||||
|
||||
# Streaming defaults to FEWER diffusion steps than batch (32): halves the
|
||||
# ~per-call diffusion overhead (server-side TTFA ~1.5s -> ~0.7s on the 3090)
|
||||
# at some quality cost. Override per-request for the quality/latency trade.
|
||||
num_step: int = 16
|
||||
format: Literal["pcm", "wav"] = "pcm" # raw s16le PCM (default) or open-ended WAV
|
||||
stream: bool = True # False -> whole-text one-shot (A/B vs stream)
|
||||
# Scheduler overrides (None -> ChunkConfig defaults; see scheduler.py).
|
||||
@@ -224,6 +238,7 @@ def _wav_header(sr: int, data_len: Optional[int] = None) -> bytes:
|
||||
|
||||
def _chunk_config(req: TTSStreamRequest) -> ChunkConfig:
|
||||
cfg = ChunkConfig()
|
||||
cfg.rtf_prior = OMNIVOICE_STREAM_RTF_PRIOR # diffusion-aware default (pack aggressively)
|
||||
if req.margin is not None:
|
||||
cfg.margin = req.margin
|
||||
if req.margin_first is not None:
|
||||
|
||||
Reference in New Issue
Block a user