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/servers.py | |
| parent | 391f50da7a085bec75155c0eb9b47910266058cc (diff) | |
| download | tts-audiobook-generator-8579517a35ef1865fc9b428899d73d52dcb27a14.tar.gz | |
feat: sglang backend support
Diffstat (limited to 'app/backends/servers.py')
| -rw-r--r-- | app/backends/servers.py | 57 |
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 |
