aboutsummaryrefslogtreecommitdiff
path: root/app/backends
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-09-02 19:13:24 -0400
committerhistoria <historiavg@proton.me>2026-09-02 19:13:24 -0400
commit6804c785c6b506c47b45264398728d0a609310be (patch)
tree0b95361d84a32d5b26e94f54530437a48f34d1c8 /app/backends
parent268b6734b22cc251bf340882ea9debbd079b9a48 (diff)
downloadtts-audiobook-generator-6804c785c6b506c47b45264398728d0a609310be.tar.gz
feat: sglang-omni model picker on manual server launch
Diffstat (limited to 'app/backends')
-rw-r--r--app/backends/servers.py8
-rw-r--r--app/backends/sglomni/__init__.py11
-rw-r--r--app/backends/sglomni/catalog.py20
-rw-r--r--app/backends/sglomni/models.py41
4 files changed, 75 insertions, 5 deletions
diff --git a/app/backends/servers.py b/app/backends/servers.py
index 7815f30..d352717 100644
--- a/app/backends/servers.py
+++ b/app/backends/servers.py
@@ -54,6 +54,14 @@ _BOOT_HINTS = (
("fp8e4nv not supported",
"the server crashed compiling an FP8 MoE kernel: FP8 needs compute "
"capability 8.9+ (RTX 4090/5090, Hopper) and cannot run on this GPU"),
+ # A model companion package missing from the backend venv (e.g. the
+ # sglang-omni Qwen3-TTS models need qwen-tts; a shared-cache weight
+ # install or a failed pip run leaves it absent).
+ ("ModuleNotFoundError",
+ "the backend venv is missing a Python module this model needs — "
+ "re-install the model via Configure Backends (checking the model "
+ "installs its companion packages), or pip-install it into the "
+ "backend's venv manually"),
)
# Progress callback: called with an event dict. KIND is one of:
diff --git a/app/backends/sglomni/__init__.py b/app/backends/sglomni/__init__.py
index 688e488..be94a0e 100644
--- a/app/backends/sglomni/__init__.py
+++ b/app/backends/sglomni/__init__.py
@@ -38,6 +38,7 @@ from .catalog import (
entries_by_keys,
entry_by_key,
entry_by_repo,
+ extra_import_name,
install_tree_families,
)
from .pythonenv import (
@@ -47,9 +48,11 @@ from .pythonenv import (
)
from .models import (
delete_model_weights,
+ install_companions,
install_model,
installed_entries,
installed_keys,
+ missing_companions,
model_installed,
preset_voices,
repo_dir,
@@ -82,12 +85,14 @@ __all__ = [
# catalog
"CAPABILITY_CLONE", "CAPABILITY_DESIGN", "CAPABILITY_SPEAKER",
"ENTRIES", "ModelEntry", "config_path", "entries_by_keys",
- "entry_by_key", "entry_by_repo", "install_tree_families",
+ "entry_by_key", "entry_by_repo", "extra_import_name",
+ "install_tree_families",
# pythonenv
"env_compatible", "env_version", "prepare_env",
# models
- "delete_model_weights", "install_model", "installed_entries",
- "installed_keys", "model_installed", "preset_voices", "repo_dir",
+ "delete_model_weights", "install_companions", "install_model",
+ "installed_entries", "installed_keys", "missing_companions",
+ "model_installed", "preset_voices", "repo_dir",
"resolve_model", "uninstall_model",
# status
"build_spec", "detect", "gpu_fallback_note", "launch_config_path",
diff --git a/app/backends/sglomni/catalog.py b/app/backends/sglomni/catalog.py
index 5ff0c81..61cb11c 100644
--- a/app/backends/sglomni/catalog.py
+++ b/app/backends/sglomni/catalog.py
@@ -96,6 +96,26 @@ _DAC_EXTRAS: Tuple[Extra, ...] = (
("descript-audiotools==0.7.2", False),
("descript-audio-codec==1.0.0", False))
+# Companion distributions whose top-level import name differs from the pip
+# name's plain dash-to-underscore normalization (verified against their
+# top_level.txt). Anything absent here normalizes: qwen-tts -> qwen_tts.
+_EXTRA_IMPORT_OVERRIDES = {
+ "descript-audiotools": "audiotools",
+ "descript-audio-codec": "dac",
+}
+
+
+def extra_import_name(spec: str) -> str:
+ """The Python module an extras requirement SPEC provides.
+
+ Takes the distribution name portion of the pip requirement (so
+ ``qwen-tts==0.1.1`` -> ``qwen_tts``) — the name a venv probe must
+ import to prove the companion is installed."""
+ base = spec.split("=")[0].split("<")[0].split(">")[0].strip()
+ if base in _EXTRA_IMPORT_OVERRIDES:
+ return _EXTRA_IMPORT_OVERRIDES[base]
+ return base.replace("-", "_")
+
ENTRIES: Tuple[ModelEntry, ...] = (
ModelEntry(
key="qwen3_tts_0_6b_customvoice",
diff --git a/app/backends/sglomni/models.py b/app/backends/sglomni/models.py
index a5211f6..607e36a 100644
--- a/app/backends/sglomni/models.py
+++ b/app/backends/sglomni/models.py
@@ -22,7 +22,8 @@ from pathlib import Path
from typing import List, Optional
from backends import common, envs
-from backends.sglomni.catalog import ModelEntry, entry_by_key, entry_by_repo
+from backends.sglomni.catalog import ModelEntry, entry_by_key, \
+ entry_by_repo, extra_import_name
from backends.sglomni.constants import SERVER_NAME, SGLOMNI_PIP_PKG
from backends.sglomni.pythonenv import SGLOMNI_ENV, prepare_env
@@ -129,6 +130,42 @@ def system_dep_missing(entry: ModelEntry) -> Optional[str]:
return None
+def missing_companions(entry: ModelEntry) -> List[Extra]:
+ """ENTRY's companion packages absent from the sglang-omni venv.
+
+ One import probe per extra (its top-level module, see
+ ``extra_import_name``) against the venv's interpreter, so a model whose
+ weights landed in the shared HF cache by another route — or whose
+ companions failed to pip-install during setup, which install_model only
+ warns about — is detected before the server dies on the import. A venv
+ that does not exist at all yields no verdict: the start flow fails on
+ the missing ``sgl-omni`` executable anyway."""
+ if not entry.extras or not envs.env_exists(SGLOMNI_ENV):
+ return []
+ return [extra for extra in entry.extras
+ if not envs.module_available(extra_import_name(extra[0]),
+ SGLOMNI_ENV)]
+
+
+def install_companions(entry: ModelEntry, *, emit=None, cancel=None) -> int:
+ """pip-install ENTRY's missing companion packages into the venv.
+
+ The same recipe ``install_model`` runs (the catalog's ``--no-deps``
+ flags preserved — the Qwen3-TTS companions must not replace the pinned
+ Transformers 5 stack), limited to what the import probe found absent,
+ so a start-time heal touches as little of the pinned environment as
+ possible. Returns the first failing exit code, 0 when all present."""
+ for spec, no_deps in missing_companions(entry):
+ args = ["--no-deps"] if no_deps else None
+ rc = common.pip_install([spec], emit=emit, cancel=cancel,
+ env_dir=SGLOMNI_ENV, extra_args=args)
+ if rc != 0:
+ print(f"[ERROR] pip install {spec} failed (exit {rc}); "
+ f"install it into {SGLOMNI_ENV} manually")
+ return rc
+ return 0
+
+
def install_model(key: str, *, emit=None, cancel=None) -> int:
"""Install a catalog model: companion packages, then its weights.
@@ -157,7 +194,7 @@ def install_model(key: str, *, emit=None, cancel=None) -> int:
note = sg_status.gpu_fallback_note(entry)
if note:
print(f"[WARNING] {note}")
- for spec, no_deps in entry.extras:
+ for spec, no_deps in missing_companions(entry):
args = ["--no-deps"] if no_deps else None
rc = common.pip_install([spec], emit=emit, cancel=cancel,
env_dir=SGLOMNI_ENV, extra_args=args)