diff options
| author | historia <historiavg@proton.me> | 2026-09-02 01:26:09 -0400 |
|---|---|---|
| committer | historia <historiavg@proton.me> | 2026-09-02 01:26:09 -0400 |
| commit | 8579517a35ef1865fc9b428899d73d52dcb27a14 (patch) | |
| tree | dba52f8d99cfe4014e0b787367de99f238e5a0db /app/backends/sglomni/catalog.py | |
| parent | 391f50da7a085bec75155c0eb9b47910266058cc (diff) | |
| download | tts-audiobook-generator-8579517a35ef1865fc9b428899d73d52dcb27a14.tar.gz | |
feat: sglang backend support
Diffstat (limited to 'app/backends/sglomni/catalog.py')
| -rw-r--r-- | app/backends/sglomni/catalog.py | 277 |
1 files changed, 277 insertions, 0 deletions
diff --git a/app/backends/sglomni/catalog.py b/app/backends/sglomni/catalog.py new file mode 100644 index 0000000..c89e865 --- /dev/null +++ b/app/backends/sglomni/catalog.py @@ -0,0 +1,277 @@ +"""The sglang-omni model catalog: what can be installed, hosted, and how. + +One server process hosts one model (`sgl-omni serve --model-path <hf-repo> +--config <yaml>`), so each entry is one launchable unit — unlike audio.cpp's +multi-entry server.json. Every entry states how its voice is supplied +(`capability`), whether a reference clip is mandatory for narration +(`requires_reference`), and which model-companion packages (`extras`) must +be pip-installed into the backend venv before its server will start. + +The catalog is static knowledge about sglang-omni's supported TTS models +(v0.1.4-era), not a live query: the server's /v1/models answers only which +model is currently hosted. Entries carry the HuggingFace repo id verbatim — +it is both the `--model-path` value and the model name /v1/models reports, +so a running server is matched back to its entry by that id. + +Voice capabilities (mirroring the audio.cpp client's vocabulary): + speaker the voice is a named preset shipped with the model (Qwen3-TTS + CustomVoice speaker table; Voxtral's preset voices) + clone the voice comes from a reference clip (per-request ref_audio + + ref_text, transcribed with Whisper when not provided) + design the voice is described by instructions (Qwen3-TTS VoiceDesign) +""" + +from dataclasses import dataclass, field +from typing import Dict, List, Optional, Tuple + +# Voice capabilities (same words the audio.cpp client uses). +CAPABILITY_SPEAKER = "speaker" +CAPABILITY_CLONE = "clone" +CAPABILITY_DESIGN = "design" + +# A model-companion pip package: (requirement spec, no_deps). The no-deps +# flag mirrors upstream install instructions that must not drag in their +# own (conflicting) transformer pins — the Qwen3-TTS companion pins +# Transformers 4 against sglang-omni's Transformers 5 stack. +Extra = Tuple[str, bool] + + +@dataclass(frozen=True) +class ModelEntry: + """One installable/hostable sglang-omni TTS model.""" + key: str # catalog id used by --model and the forms + label: str # human-readable name (menus, status lines) + repo: str # HuggingFace repo id == --model-path value + config: Optional[str] # vendored config yaml file name, None = none + capability: str # speaker / clone / design + requires_reference: bool # clone models: narration needs ref audio + extras: Tuple[Extra, ...] = field(default=()) + system_dep: Optional[str] = None # system binary the extras need + system_hint: Optional[str] = None # remediation when the binary is absent + speakers: Optional[Tuple[str, ...]] = None # preset voices (speaker) + supports_seed: bool = False # request-scoped seed accepted (Qwen3-TTS Base) + notes: str = "" # one-line description (documentation) + # The model's DEFAULT pipeline dynamically quantizes its MoE experts to + # FP8 at load time (sglang-omni's zonos2 config hardcodes it) — a Triton + # fp8e4nv kernel that only compiles on compute capability 8.9+ (RTX + # 4090/5090, Hopper). On older GPUs the server dies mid-boot; BF16_CONFIG + # is the vendored config that turns FP8 off so the model runs in bf16 + # (~2x the MoE VRAM) there instead. + fp8_moe: bool = False + fp8_min_compute_capability: Optional[Tuple[int, int]] = None + bf16_config: Optional[str] = None + + +# The Qwen3-TTS CustomVoice speaker table — the same built-in speakers the +# qwen-tts demo and audio.cpp's CustomVoice entry expose (kept as a local +# copy so this catalog stays importable without the converter package; the +# test suite asserts it matches converter.clients.speakers). +QWEN_CUSTOMVOICE_SPEAKERS: Tuple[str, ...] = ( + "Vivian", "Serena", "Uncle_Fu", "Dylan", "Eric", + "Ryan", "Aiden", "Ono_Anna", "Sohee") + +# The Qwen3-TTS companions follow the upstream TTS guide exactly: the +# qwen-tts demo package installs WITHOUT dependencies (its Transformers 4 +# pin would replace sglang-omni's pinned 5.12 stack; sglang-omni shims the +# API differences), as do sox/einops (a normal sox resolve pulls numpy past +# the ceiling numba imposes). The sox *binary* is a system package the +# wizard detects and guides (it is required at synthesis time). +_QWEN_EXTRAS: Tuple[Extra, ...] = ( + ("sox", True), ("einops", True), ("qwen-tts==0.1.1", True)) +_SOX_HINT = ("install the sox system package (e.g. sudo pacman -S sox, " + "sudo apt install sox, brew install sox)") +# The Fish Audio and ZONOS2 pipelines use the Descript DAC codec, which +# upstream installs WITH dependencies (nothing conflicts). +_DAC_EXTRAS: Tuple[Extra, ...] = ( + ("descript-audiotools==0.7.2", False), + ("descript-audio-codec==1.0.0", False)) + +ENTRIES: Tuple[ModelEntry, ...] = ( + ModelEntry( + key="qwen3_tts_0_6b_customvoice", + label="Qwen3-TTS 0.6B CustomVoice", + repo="Qwen/Qwen3-TTS-12Hz-0.6B-CustomVoice", + config="qwen3_tts_0_6b_customvoice.yaml", + capability=CAPABILITY_SPEAKER, + requires_reference=False, + extras=_QWEN_EXTRAS, system_dep="sox", system_hint=_SOX_HINT, + speakers=QWEN_CUSTOMVOICE_SPEAKERS, + notes="built-in speakers, lightest model", + ), + ModelEntry( + key="qwen3_tts_0_6b_base", + label="Qwen3-TTS 0.6B Base", + repo="Qwen/Qwen3-TTS-12Hz-0.6B-Base", + config="qwen3_tts_0_6b.yaml", + capability=CAPABILITY_CLONE, + requires_reference=True, + extras=_QWEN_EXTRAS, system_dep="sox", system_hint=_SOX_HINT, + supports_seed=True, + notes="voice cloning from a reference clip", + ), + ModelEntry( + key="qwen3_tts_1_7b_base", + label="Qwen3-TTS 1.7B Base", + repo="Qwen/Qwen3-TTS-12Hz-1.7B-Base", + config="qwen3_tts_1_7b.yaml", + capability=CAPABILITY_CLONE, + requires_reference=True, + extras=_QWEN_EXTRAS, system_dep="sox", system_hint=_SOX_HINT, + supports_seed=True, + notes="voice cloning, higher quality", + ), + ModelEntry( + key="qwen3_tts_1_7b_voicedesign", + label="Qwen3-TTS 1.7B VoiceDesign", + repo="Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign", + config="qwen3_tts_1_7b_voicedesign.yaml", + capability=CAPABILITY_DESIGN, + requires_reference=False, + extras=_QWEN_EXTRAS, system_dep="sox", system_hint=_SOX_HINT, + notes="voice described by instructions", + ), + ModelEntry( + key="higgs_audio_v3_tts", + label="Higgs Audio v3 TTS", + repo="bosonai/higgs-audio-v3-tts-4b", + config=None, + capability=CAPABILITY_CLONE, + requires_reference=False, + notes="zero-shot narration, cloning from a reference clip", + ), + ModelEntry( + key="moss_tts", + label="MOSS-TTS v1.5", + repo="OpenMOSS-Team/MOSS-TTS-v1.5", + config="moss_tts.yaml", + capability=CAPABILITY_CLONE, + requires_reference=True, + notes="voice cloning from a reference clip", + ), + ModelEntry( + key="moss_tts_local", + label="MOSS-TTS Local v1.5", + repo="OpenMOSS-Team/MOSS-TTS-Local-Transformer-v1.5", + config="moss_tts_local.yaml", + capability=CAPABILITY_CLONE, + requires_reference=False, + notes="48 kHz, narration without a reference or cloning", + ), + ModelEntry( + key="voxtral_tts", + label="Voxtral TTS 4B", + repo="mistralai/Voxtral-4B-TTS-2603", + config="voxtral_tts.yaml", + capability=CAPABILITY_SPEAKER, + requires_reference=False, + notes="preset voices, text-only requests", + ), + ModelEntry( + key="dots_tts_mf", + label="dots.tts (MeanFlow)", + repo="dots-studio/dots.tts-mf", + config="dots_tts.yaml", + capability=CAPABILITY_CLONE, + requires_reference=True, + notes="voice cloning, requires reference audio + transcript", + ), + ModelEntry( + key="fish_s2_pro", + label="Fish Speech S2-Pro", + repo="fishaudio/s2-pro", + config="s2pro_tts.yaml", + capability=CAPABILITY_CLONE, + requires_reference=False, + extras=_DAC_EXTRAS, + notes="zero-shot narration or cloning from a reference clip", + ), + ModelEntry( + key="zonos2", + label="ZONOS2", + repo="Zyphra/zonos2", + config=None, + capability=CAPABILITY_CLONE, + requires_reference=True, + extras=_DAC_EXTRAS, + notes="voice cloning, 44.1 kHz DAC vocoder", + fp8_moe=True, + fp8_min_compute_capability=(8, 9), + bf16_config="zonos2_bf16.yaml", + ), +) + +_BY_KEY: Dict[str, ModelEntry] = {entry.key: entry for entry in ENTRIES} +_BY_REPO: Dict[str, ModelEntry] = {entry.repo: entry for entry in ENTRIES} + + +def entry_by_key(key: str) -> Optional[ModelEntry]: + """The catalog entry for a catalog KEY, or None.""" + return _BY_KEY.get(key) + + +def entry_by_repo(repo: str) -> Optional[ModelEntry]: + """The catalog entry hosting REPO (a /v1/models id), or None.""" + return _BY_REPO.get(repo) + + +def entries_by_keys(keys) -> List[ModelEntry]: + """The ENTRIES for KEYS, in catalog order (unknown keys dropped).""" + wanted = set(keys) + return [entry for entry in ENTRIES if entry.key in wanted] + + +def config_path(entry: ModelEntry): + """The vendored config yaml path for ENTRY, or None when it runs on + --model-path alone (Higgs, ZONOS2). A declared-but-missing file means + a broken install — callers treat that like a missing entry.""" + return _config_file(entry.config) + + +def fallback_config_path(entry: ModelEntry): + """The vendored bf16 config yaml for ENTRY (None when it has none). + + The config a GPU below the entry's fp8_min_compute_capability launches + with instead of the model's default FP8-quantized pipeline.""" + return _config_file(entry.bf16_config) + + +def _config_file(name: Optional[str]): + """CONFIGS_DIR/NAME, or None when NAME is None (no config for ENTRY).""" + if name is None: + return None + from .constants import CONFIGS_DIR + return CONFIGS_DIR / name + + +def install_tree_families(available: List[ModelEntry]) -> List[dict]: + """The checkbox-tree shape for the model picker, grouped by upstream. + + One family per model origin with one option per model, so the picker + reads like the audio.cpp one (a collapsed tree of related packages). + No ``detail`` is set: the tree's status line under the buttons would + only repeat the catalog keys under the cursor. AVAILABLE filters what + is shown (already-installed models stay listed so they can be + re-checked or repaired). + """ + groups: List[Tuple[str, List[ModelEntry]]] = [ + ("Qwen3-TTS (Qwen)", [e for e in available if e.repo.startswith("Qwen/")]), + ("Boson AI", [e for e in available if e.repo.startswith("bosonai/")]), + ("OpenMOSS", [e for e in available if e.repo.startswith("OpenMOSS-Team/")]), + ("Mistral AI", [e for e in available if e.repo.startswith("mistralai/")]), + ("dots.studio", [e for e in available if e.repo.startswith("dots-studio/")]), + ("Fish Audio", [e for e in available if e.repo.startswith("fishaudio/")]), + ("Zyphra", [e for e in available if e.repo.startswith("Zyphra/")]), + ] + families = [] + for label, entries in groups: + if not entries: + continue + families.append({ + "label": label, + "options": [ + {"key": entry.key, "label": entry.label, + "recommended": False} + for entry in entries + ], + }) + return families |
