feat(model): debounce model loads after a failure to stop retry-storms#10728
Conversation
A client that keeps polling a model whose load fails (e.g. a backend that crashes deterministically on init) triggered a fresh backend start on every request: request -> load -> crash in ~10s -> 500, repeat on the next poll. Each attempt could leak GPU/CUDA state, and under LOCALAI_SINGLE_ACTIVE_BACKEND it kept stealing the active slot from healthy models. The existing loading-coalesce map only dedups *concurrent* loads, so sequential polls were never covered. Track load failures per modelID in ModelLoader. After a load fails, refuse fresh load triggers for that model until a cooldown elapses, returning a typed ModelLoadCooldownError that the HTTP layer maps to 503 with a Retry-After header. The cooldown grows exponentially per consecutive failure (base, doubling, capped at 5m) and resets on a successful load. The coalesced follower-retry of an in-flight burst bypasses the gate, so a genuinely concurrent burst still gets its one retry -- only new, independent triggers are refused, matching the report's "refuse new load-triggers" wording. Configurable via --model-load-failure-cooldown / LOCALAI_MODEL_LOAD_FAILURE_COOLDOWN (default 10s, 0 disables), plumbed through ApplicationConfig and applied unconditionally at startup. Closes #10719 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Assisted-by: Claude:claude-opus-4-8 [Claude Code]
|
Field feedback on how this landed — from a case tonight where the same model failed to load twice with a stale post-reboot driver state (SIGABRT within ~10 s each attempt, classic Pattern-A on this GB10 hardware). Observed cooldown values I saw returned in the retry-after messages, in order, from the same client:
My interpretation, with the caveat that this is 4 data points from one session and not a controlled repro: only actual load-attempts trigger a new/extended cooldown, while queries during a live cooldown just report the remaining time. That part is a nice property. But the length of each new cooldown appears to grow roughly geometrically per failed attempt (exponential-ish backoff), and a naïve retry client that just polls after the A few questions I would find useful to have documented, either in the PR description or the log line:
For context on this side: the underlying pattern that keeps producing the fails is a deterministic 10-14 s SIGABRT during vLLM engine init on Blackwell (GB10 / sm_121a) — persistent even after a host reboot (I did one tonight and got the same class of fail on both retries afterwards). Filed separately at vllm-project/vllm#48031 (default Happy to run a more controlled repro (defined number of forced fails, measure cooldown values, extract the growth factor) once the underlying init issue is resolved and I can trigger fails on demand — likely sometime later this week. |
Problem
A client that keeps polling a model whose load fails (e.g. a backend that crashes deterministically on init) triggers a fresh backend start on every request: request → load → crash in ~10s → 500, then the next poll repeats it. Each attempt can leak GPU/CUDA state, and under
LOCALAI_SINGLE_ACTIVE_BACKENDthe failing model keeps stealing the active slot from healthy ones. The reporter watched leaked contexts climb to ~58 GB before a host reboot was needed.The existing
loading-coalesce map only dedups concurrent loads, so sequential polls (60s apart) were never covered.Fix
ModelLoadernow tracks load failures permodelID. After a load fails, fresh load triggers for that model are refused until a cooldown elapses, returning a typedModelLoadCooldownErrorthat the HTTP layer maps to 503 +Retry-After. The cooldown grows exponentially per consecutive failure (base → ×2, capped at 5m) and resets on a successful load.The coalesced follower-retry of an in-flight burst bypasses the gate, so a genuinely concurrent request burst still gets its one retry — only new, independent triggers are refused, matching the report's "refuse new load-triggers" wording.
Configurable via
--model-load-failure-cooldown/LOCALAI_MODEL_LOAD_FAILURE_COOLDOWN(default10s,0disables), plumbed throughApplicationConfigand applied unconditionally at startup (independent of the watchdog/LRU limiter).Testing
pkg/modelsuite green under-race;corebuilds;golangci-lintclean (new-from-merge-base).Notes / scope
applyModelLoadCooldown) is left to review + CI: an internal unit test conflicts with the package's single-RunSpecsbootstrap, and the full-API()harness is heavyweight. The substantive logic (the debounce state machine) is unit-tested directly.Closes #10719
🤖 Generated with Claude Code