unslothai/unsloth · error · RuntimeError
sd-server failed to become ready. Last output: {tail[:2000]}
Error message
sd-server failed to become ready. Last output:
{tail[:2000]} What it means
The sd-server process spawned but did not answer /v1/models with HTTP 200 within startup_timeout. Upstream binds the port only after the model finishes loading, so this almost always means model load is slow (large model, cold page cache, slow disk) or the server crashed during load. The wrapper attaches the last 30 log lines (truncated to 2000 chars), kills the process, and disposes it; if the abort event was set it raises SdCppCancelled instead.
Source
Thrown at studio/backend/core/inference/sd_cpp_server.py:284
except Exception: # noqa: BLE001
pass
self._stdout_thread = threading.Thread(
target = _own_process, daemon = True, name = "sd-server-owner"
)
self._stdout_thread.start()
spawned.wait()
if self._spawn_error is not None:
self._dispose()
raise RuntimeError(f"failed to spawn sd-server: {self._spawn_error}")
if not self._wait_ready(startup_timeout):
tail = _diagnostic_tail(self._tail, keep = 30)
aborted = self._abort.is_set()
self._kill_locked()
self._dispose()
if aborted:
raise SdCppCancelled("sd-server startup was cancelled.")
raise RuntimeError("sd-server failed to become ready. Last output:\n" + tail[:2000])
def _wait_ready(
self,
timeout: float,
interval: float = 0.5,
) -> bool:
"""Poll ``/v1/models`` until 200; bail early if the process exits.
Upstream binds the port only AFTER the model is loaded, so a 200 here is a true
ready signal (no half-loaded race)."""
deadline = time.monotonic() + timeout
url = f"{self.base_url}{_READY_PATH}"
while time.monotonic() < deadline:
# A concurrent stop() sets _abort so this wait bails without holding the model load hostage for the full startup_timeout.
if self._abort.is_set():
logger.info("sd-server startup aborted before ready")
return False
if not self.is_alive():View on GitHub (pinned to 203007d190)
Solutions
- Pass a larger startup_timeout proportional to model size and disk speed.
- Read the embedded tail: load-stage crashes (OOM, corrupt file) show up there — fix those rather than raising the timeout.
- Prewarm the page cache (one manual load) or move the model to faster storage to cut load time.
- Re-download a model whose load consistently dies with checksum/parse errors in the tail.
Defensive patterns
Strategy: retry
Validate before calling
size_gb = model_path.stat().st_size / 2**30 timeout = max(startup_timeout, 60 + size_gb * 8) # scale with model size
Try / catch
try:
server.start(startup_timeout=t)
except RuntimeError as e:
if "failed to become ready" in str(e):
triage_tail(e); server.start(startup_timeout=t * 3)
raise Prevention
- Scale startup_timeout with model size and disk speed.
- Read the embedded tail before retrying — load crashes need a fix, not more time.
- Keep models on fast local storage and prewarm the cache for huge models.
When it happens
Trigger: Server.start() with a startup_timeout shorter than the model load time; model file corruption making load fail after bind-less startup; OOM while loading weights.
Common situations: First load of a 70B-class or heavily quantized diffusion model on a slow disk; timeout left at a small default after switching to a much bigger model; cold cache after reboot.
Related errors
- sd-cli timed out after {timeout}s
- failed to spawn sd-server: {self._spawn_error}
- sd-server is not running.
- sd-server connection lost during img_gen submit
- sd-server job queue is full (HTTP 429).
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/fbc200d279f9d3e0.
Report an issue: GitHub.