Repository navigation
fix(watchdog): keep tracking a model that an eviction could not stop - #12859
hcharbonnier wants to merge 3 commits into
Conversation
An eviction untracks the backend before it stops it. When the stop failed,
the error was only logged: the backend kept running and kept its VRAM, but
the watchdog had already forgotten it. Group and LRU enforcement then saw no
conflict, and the next load started next to it and ran out of memory.
Stopping a model takes its exclusive lifecycle lock, which a model that is
still loading holds for the whole load. The stop then times out ("waiting to
shut down model: context deadline exceeded"), and the watchdog no longer
knows the model is there.
The tracking state is now captured before the backend is untracked, and put
back when the stop fails. This applies to every eviction path that untracks
first: group, LRU, idle timeout and busy timeout. A model that is already
gone (ErrModelNotFound) still counts as stopped, and a replacement
registered on the same address in the meantime is left alone. EvictedCount
only counts backends that really stopped, and NeedMore is set when one did
not, so the loader keeps retrying instead of treating the room as made. The
idle and busy checks retry the stop on their next tick.
The state is only put back while the loader still holds the model at that
address (ModelLoader.HasModelAtAddress). deleteProcess drops a remote-only
model, or the local half of a local+remote placement, before it reports a
failed remote unload; tracking that model again would leave a phantom entry
counted as loaded and as a group conflict. The memory reclaimer applies the
same rule and untracks a model the loader already dropped.
The untrack-before-stop ordering dates from mudler#7535, so this is not a
regression of 4.12.0.
Assisted-by: Claude:claude-sonnet-5.5 go vet
Signed-off-by: Hugues CHARBONNIER <hugues.charbonnier@gmail.com>
|
@mudler review of head P1: restoring the snapshot loses request lifecycle events during shutdown (
I reproduced both deterministically with a fake process manager that invokes the completion callback or starts a request inside Validation used the unmodified watchdog source and options with isolated Go/Ginkgo tests. The PR merges cleanly and passes whitespace checks. Full package tests remain unverified because generated protobuf bindings are absent and fetching protoc returned HTTP 403. This requires a concurrency-state change beyond a safe minor patch, so I have not pushed a speculative fix. |
Restoring a snapshot after a failed stop lost the request lifecycle events that happened while the stop was in progress. A request completing during the stop found no tracked request, and the restore then resurrected it, leaving the backend marked busy. A request starting during the stop was overwritten by the restore, so completing an older request marked the backend idle while the new one still ran, allowing an unsafe eviction. An eviction now detaches only the backend identity (model, process, size estimate) from the registry and marks the address as pending. Request state stays live, so callbacks keep updating it during the stop. Idle and busy checks skip pending addresses so they neither re-evict nor wipe that state. When the stop settles, a backend that still runs is registered again; otherwise its leftover request state is cleared. A replacement registered on the address meanwhile is left alone. Regression tests cover a request completing and a request starting inside a failing force stop. The idle and busy failure tests now prove re-tracking through a second stop attempt instead of sampling the registry, which raced with the pending window. Assisted-by: Claude:claude-opus-5.5 go vet golangci-lint Signed-off-by: Hugues CHARBONNIER <hugues.charbonnier@gmail.com>
|
Thanks for the reproduction, you are right: replaying the snapshot is unsound. Fixed in c69e4fb0d (follow-up commit, no force-push). Change
Regression coverage (
I also made the idle and busy failure tests prove re-tracking through a second stop attempt instead of sampling the registry, which could race with the pending window. Validation
|
|
@mudler source-review sign-off on revised head The revision keeps request lifecycle state live while eviction is pending, so completed requests are not resurrected and newly started requests are not overwritten. Both original reproduction cases now pass. No new blocking source findings. Validation: 72 isolated watchdog specs pass, including the two original regressions; focused go vet and whitespace checks pass, and the head integrates cleanly with master |
Description
An eviction untracks the backend before it stops it. When the stop failed, the error was only logged: the backend kept running and kept its VRAM, but the watchdog had already forgotten it. Group and LRU enforcement then saw no conflict, and the next load started next to it and ran out of memory.
Stopping a model takes its exclusive lifecycle lock, which a model that is still loading holds for the whole load. The stop then times out ("waiting to shut down model: context deadline exceeded"), and the watchdog no longer knows the model is there.
The tracking state is now captured before the backend is untracked, and put back when the stop fails. This applies to every eviction path that untracks first: group, LRU, idle timeout and busy timeout. A model that is already gone (ErrModelNotFound) still counts as stopped, and a replacement registered on the same address in the meantime is left alone. EvictedCount only counts backends that really stopped, and NeedMore is set when one did not, so the loader keeps retrying instead of treating the room as made. The idle and busy checks retry the stop on their next tick.
The state is only put back while the loader still holds the model at that address (ModelLoader.HasModelAtAddress). deleteProcess drops a remote-only model, or the local half of a local+remote placement, before it reports a failed remote unload; tracking that model again would leave a phantom entry counted as loaded and as a group conflict. The memory reclaimer applies the same rule and untracks a model the loader already dropped.
Assisted-by: Claude:claude-sonnet-5.5
Notes for Reviewers
Signed commits