aboutsummaryrefslogtreecommitdiff
path: root/app/backends/servers.py
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-09-02 01:26:09 -0400
committerhistoria <historiavg@proton.me>2026-09-02 01:26:09 -0400
commit8579517a35ef1865fc9b428899d73d52dcb27a14 (patch)
treedba52f8d99cfe4014e0b787367de99f238e5a0db /app/backends/servers.py
parent391f50da7a085bec75155c0eb9b47910266058cc (diff)
downloadtts-audiobook-generator-8579517a35ef1865fc9b428899d73d52dcb27a14.tar.gz
feat: sglang backend support
Diffstat (limited to 'app/backends/servers.py')
-rw-r--r--app/backends/servers.py57
1 files changed, 45 insertions, 12 deletions
diff --git a/app/backends/servers.py b/app/backends/servers.py
index 25ab472..7815f30 100644
--- a/app/backends/servers.py
+++ b/app/backends/servers.py
@@ -45,12 +45,23 @@ STOP_GRACE_SECONDS = 10
# How often the start poll re-checks readiness (seconds).
POLL_INTERVAL = 1
+# Known crash signatures in a failed boot's log tail, each with a
+# plain-language hint appended to the failure report (the raw tail alone
+# is often a wall of framework traceback).
+_BOOT_HINTS = (
+ # sglang fused-MoE fp8e4nv kernel on pre-sm_89 GPUs (e.g. an FP8
+ # checkpoint or a default FP8 pipeline on Ampere).
+ ("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"),
+)
+
# Progress callback: called with an event dict. KIND is one of:
# "starting" {name, argv, cwd, log_path, pid} spawned, waiting for boot
# "elapsed" {name, seconds} heartbeat while waiting
# "ready" {name, url} server is up and answering
-# "exited" {name, returncode, log_tail} process exited while booting
-# "timeout" {name, seconds, log_tail} readiness deadline elapsed
+# "exited" {name, returncode, log_tail, hint} process died while booting
+# "timeout" {name, seconds, log_tail, hint} readiness deadline elapsed
# "running" {name, url} already up (no spawn)
# "cancelled" {name} boot aborted via cancel
# "error" {message} could not spawn the executable
@@ -77,10 +88,14 @@ def _console_progress(event: dict) -> None:
print(f"[ERROR] {event['name']} server exited with code "
f"{event['returncode']}")
_print_tail(event.get("log_tail"))
+ if event.get("hint"):
+ print(f"[WARNING] hint: {event['hint']}")
elif kind == "timeout":
print(f"[ERROR] {event['name']} server did not start within "
f"{int(event['seconds'])}s")
_print_tail(event.get("log_tail"))
+ if event.get("hint"):
+ print(f"[WARNING] hint: {event['hint']}")
elif kind == "error":
print(f"[ERROR] {event['message']}")
@@ -114,6 +129,15 @@ def _read_log_tail(name: str, lines: int = 20) -> List[str]:
return text.splitlines()[-lines:]
+def _boot_hint(log_tail: List[str]) -> Optional[str]:
+ """A plain-language hint for a known crash signature in LOG_TAIL."""
+ text = "\n".join(log_tail)
+ for signature, hint in _BOOT_HINTS:
+ if signature in text:
+ return hint
+ return None
+
+
def _print_tail(tail: List[str]) -> None:
"""Print a log-tail event payload (used by the console callback)."""
if tail:
@@ -282,14 +306,16 @@ def start(spec, progress: ProgressCallback = None,
spec's CWD when it has one (audio.cpp discovers model_specs/ from its
process working directory), records the pid, and polls readiness —
``_server_ready``, so an IDENTITY spec must actually answer HTTP — until
- it is up or ``SERVER_START_TIMEOUT`` elapses. Returns True when the
- server is up; on timeout or early exit reports the log tail and returns
- False. A no-op (True) when the server is already running.
+ it is up or the spec's start timeout elapses (``ServerSpec.start_timeout``
+ overrides SERVER_START_TIMEOUT; the sglang-omni pipeline needs the
+ longer budget). Returns True when the server is up; on timeout or early
+ exit reports the log tail and returns False. A no-op (True) when the
+ server is already running.
PROGRESS, when given, receives each boot event (see ProgressCallback);
- the default ``_console_progress`` prints them, preserving the old
- console output. CANCEL (a threading.Event) aborts the boot: the spawned
- process is terminated and False is reported (event kind "cancelled").
+ the default ``_console_progress`` prints them, preserving the old console
+ output. CANCEL (a threading.Event) aborts the boot: the spawned process
+ is terminated and False is reported (event kind "cancelled").
"""
report = progress if progress is not None else _console_progress
argv: List[str] = list(spec.argv)
@@ -303,6 +329,9 @@ def start(spec, progress: ProgressCallback = None,
report({"kind": "running", "name": spec.name, "url": spec.url})
return True
+ start_timeout = getattr(spec, "start_timeout", None) \
+ or SERVER_START_TIMEOUT
+
LOG_DIR.mkdir(parents=True, exist_ok=True)
# Refuse to double-start: a live pid file means a previous start is
# still booting (or its process is wedged). Spawning a second server
@@ -381,7 +410,7 @@ def start(spec, progress: ProgressCallback = None,
started = time.time()
next_heartbeat = started + 15
- deadline = started + SERVER_START_TIMEOUT
+ deadline = started + start_timeout
while time.time() < deadline:
if cancel is not None and cancel.is_set():
# User cancelled while booting: kill what we spawned (the
@@ -394,9 +423,11 @@ def start(spec, progress: ProgressCallback = None,
report({"kind": "cancelled", "name": spec.name})
return False
if proc.poll() is not None:
+ tail = _read_log_tail(spec.name)
report({"kind": "exited", "name": spec.name,
"returncode": proc.returncode,
- "log_tail": _read_log_tail(spec.name)})
+ "log_tail": tail,
+ "hint": _boot_hint(tail)})
try:
pid_file.unlink()
except OSError:
@@ -410,9 +441,11 @@ def start(spec, progress: ProgressCallback = None,
"seconds": time.time() - started})
next_heartbeat += 15
time.sleep(POLL_INTERVAL)
+ tail = _read_log_tail(spec.name)
report({"kind": "timeout", "name": spec.name,
- "seconds": SERVER_START_TIMEOUT,
- "log_tail": _read_log_tail(spec.name)})
+ "seconds": start_timeout,
+ "log_tail": tail,
+ "hint": _boot_hint(tail)})
# Leave the pid file in place so stop() can kill it (it may still load).
return False