Manage Docker containers as engines — Berth-owned start/stop + keep-durably-out when the card is claimed
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Idoneità per principianti
- 25/100
Direzione di ricerca
Start by reading issue #96 and the existing engine configuration and lease drain/grant flow; the payload does not name source files or tests. Decide how container lifecycle commands, Compose services, restart policies, and unload-only behavior fit the engine model, then implement and test the chosen design. Done means a container can be kept stopped while another tenant holds the card and is accounted for through its engine entry.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
The problem
Berth's value comes from owning the card's admission: a grant, a drain, a claim.
But it can only govern workloads it can control — either a child process it launched
(berth run) or, since the design being queued for #96, an engine that exposes its own
load_url/unload_url.
A Docker container that allocates VRAM on its own schedule is in the blind spot. Concretely,
on my box the immich-pet-tagger container (a scheduled image-embedding service,
restart: unless-stopped) loads ~2 GB of CUDA models on every startup and on schedule (in example every ~20 min) and unloads them when the cycle ends. Berth sees that VRAM — it shows up in the stowaway list — but has no control channel to it. Two consequences, both measured:
-
The card is not gated. An unleased container will load its models the moment its own
scheduler fires, regardless of what else is using the card. With the 27 B model fully
resident (15,330 MiB used / 664 MiB free — the hardest state on this 16,303 MiB card), a
live manual trigger produced this (2 s cadence):t VRAM event 11:13:12 15,330 trigger; 27 B serving an active 58,907-token prompt 11:13:22–11:14:14 15,483 → 15,567 peak tagger's YOLO (2 s) + CLIP (45 s) load; 27 B yields ~0.75 GB 11:14:15 — STATSclean, no OOM, no errors11:14:18 11,043 (4,953 free) tagger models released; 27 B still displaced (~4.3 GB in RAM) 11:14:55 15,301, util 90–92 % 27 B re-paginated in <2 s It's self-healing — but "coexists by displacement" is not the same as "the card is
protected." For a gaming session or a long LLM batch job (OCR, transcribe, tag-a-disk)
the operator would want the opposite: *while the card is held by another tenant, this workload
stays out of VRAM. That would be the value of Berth in this scenario. * There is no configuration
that gives that today; the stowaway list is report-only.
While self-healing it still causes effectively system lock-up for a while (unresponsive desktop, stuttering,
possible timeouts of certain jobs and Berth would be here valuable solution to remove manual lifecycle
management. -
Manual start/stop is still needed. Killing the
berth runwrapper releases only the
lease; it does not stop the container (daemon-owned,unless-stopped,compose up
without-donly attaches logs). So the operator is back to hand-runningdocker stop/
docker startto keep a stowaway workload out — exactly the manual step this feature removes.
The precedent this builds on (llama-swap + #96)
The llama-swap wiring works because the drain is the engine's own exit: Berth holds the
lease and, on a competing grant, calls llama-swap's own unload endpoint
(/api/models/unload/<model>) — it never signals the process. The 27 B unloads, VRAM frees,
the next engine is admitted.
Issue #96 generalizes this and the maintainer has confirmed the shape: Berth never signals a
process it did not start; it uses the engine's own way out (a load_url/unload_url on the
[[engine]] entry, plus a match list so the engine's VRAM is counted as the engine's, not a
stowaway).
A Docker container fits that design exactly — its "own way out" is the container runtime's
documented lifecycle:
- unload =
docker stop <name>(ordocker compose -f <file> stop <svc>) — sends
SIGTERM, the container's graceful path, releases the CUDA context and its VRAM. - load =
docker start <name>(ordocker compose -f <file> up -d <svc>) — brings it back.
docker stop is a graceful stop, not a kill -9; the container can trap it and clean up
(unload models), so it is the container's own exit. Berth invokes the runtime's lifecycle API —
it does not signal an arbitrary PID. This is the same principle as the llama-swap endpoint, with
a shell command standing in for the HTTP URL.
One load-bearing Docker behavior that makes this a stable knob (per the documented restart
policies): with restart: unless-stopped, a container that is explicitly stopped via the
CLI/API is not auto-restarted. So when Berth runs docker stop, the container stays stopped
until Berth (or the operator) starts it again. That is what lets Berth durably keep a workload
out of the card while a competing tenant holds it — instead of a transient stop that the
daemon immediately reverts. (Contrast restart: always, which would revert an explicit stop;
see "Open questions".)
How this could work
Extend the [[engine]] entry so an engine's load/unload can be commands, not only HTTP
URLs — with a kind = "docker" / kind = "compose" preset that fills them in. Then a container
is governed like any other engine.
Sketch (there may be better ways to do it via docker desktop API?):
[[engine]]
name = "pet-tagger"
kind = "docker" # or "compose"; sets load/unload to the lifecycle below
# --- docker lifecycle (the "own way out") ---
name_docker = "immich-pet-tagger"
# load -> docker start immich-pet-tagger (or: docker compose -f F:/AI/immich-pet-tagger/docker-compose.yml up -d immich-pet-tagger)
# unload -> docker stop immich-pet-tagger (or: docker compose ... stop immich-pet-tagger)
# --- accounting (the #96 "match") ---
match = ["immich-pet-tagger"] # process / container identity whose VRAM is the engine's, not a stowaway
needs_mib = 2560 # measured peak (see evidence); drives admission math
# --- optional ---
# ready = "..." # readiness probe after load (e.g. the app's HTTP port)
Behaviour that follows:
- Per-container load/unload controlled by Berth. On a grant, Berth runs
load(start the
container); on a drain (a higher-priority grant, or a whole-card claim), Berth runsunload
(docker stop), waits for the driver to show the memory free, and only then releases the
lease / admits the next tenant. Symmetric to llama-swap. - Admission gating (the "keep out while busy" the operator actually wants). While a
whole-cardclaim/hold(e.g. a game) or a higher-priority lease is active, a drained Docker
engine stays stopped — Berth does not restart it on its own schedule, because the card is
held. On release, it reloads only if its own lease/claim is granted. This is what makes
"card busy with gaming/OCR → tagger stays out of VRAM" actually hold, rather than
"coexists by displacement." matchaccounting. The container's VRAM is attributed to its engine entry (counted as the
engine's, withneeds_mib), not buried in the stowaway list — so the stowaway list stays
honest and the admission math sees the real number.
The design deliberately reuses #96's invariants:
-
Berth never signals a process it didn't start — it runs the container runtime's own lifecycle.
-
If
unloadis refused or the memory doesn't come down, Berth waits for the next boundary and
reports the lease stuck; it never grants the card on top of a loaded model.I am not yet sure how it would ideally work in practice, but due to different nature of ML containers
- some of them load models on startup some only lazy-load - it would be useful to have "unload-only" option
- where Berth evicts a certain container, but does not restart it when memory is free.
Evidence
- Displacement coexistence, hardest state (unleased): peak 15,567 MiB < 16,303 card, no OOM,
no lockup, no stuck state; the 27 B kept serving through the whole scan window; ~4.3 GB of its
weights went to RAM for the ~65 s scan; re-pagination <2 s. So the coexistence is safe — the
missing piece is the gate, which this feature adds. - Leased run works end-to-end:
berth run --as pet-tagger --priority foreground --needs-mib ...drained the 27 B lease (<5 s), granted the tagger, ran a clean poll cycle (VRAM peak
2,098 MiB), and released on wrapper exit — no stuck leases. So the broker path that this
feature reuses (drain → grant → release) is already proven; we're extending what can be an
engine (a container) and how it's drained (its lifecycle). needs_mibmeasured: peak 2,098 MiB during a clean cycle → configure2560.- Manual trigger exists:
POST http://<container>:8000/api/scan(port not host-mapped; call
viadocker exec) — used to force a cycle for the measurements above.
Environment
Windows 11, Docker Desktop (WSL2 backend), RTX 5070 Ti 16,303 MiB (governed), berth 0.3.x.
Containers run inside the WSL2 VM; the GPU is passed through via the NVIDIA runtime, so a stopped
container's CUDA context and VRAM are released back to the driver. (Other backends should be
equivalent for the lifecycle semantics.)
Open questions
- Restart policy.
unless-stopped(explicit stop holds) is the policy under which Berth's
docker stopis a stable "keep out" knob. Should the entry require/verify a non-always
policy, or should Berth treat "container came back without myload" as a stowaway and
re-drain it? - Lease semantics for a service. The tagger is a long-running scheduled service, not a
run-to-completion job. Is the intended model "lease = container running (Berth keeps it up
while granted, stops it on drain)" rather than "lease = one scan"? - Command vs. Docker preset. Do you want
load_cmd/unload_cmd(generic, any engine with a
start/stop) plus akind="docker"sugar, or a dedicated container entry? Either reuses #96's
match+ stuck-drain logic. - Compose granularity.
docker compose stop <svc>preserves the container (and state/volumes);
downremoves it. Presumingstop/start(notdown) is the right option. - is docker CLI even the best lever here - maybe there is a batter one I don't know in docker ecosystem? Especially one that would interact with app INSIDE the container.
Note on scope / relation to #96
Not a duplicate of #96. #96 is about adopting an engine Berth did not start (Strata) via its
HTTP API. This is the Docker-container instantiation of the same "engine's own way out"
design, where the "way out" is the container runtime's lifecycle instead of an HTTP endpoint. If
load_url/unload_url ships first, this is just the command-based sibling of it.
- Lingua principale
- Go
- Stelle
- 226
- Fork
- 20
- Merge medio
- 51m
- PR unite (30g)
- 225
Preparare l'ambiente
Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di T-Crypt/berth
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 42/100
T-Crypt/berth#204 · 11 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
berthd --gpu-backend amd exits at startup on a desktop session: "process VRAM exceeds card usage"Aperta
Difficoltà 4/5 1-2 giorni Idoneità per principianti 48/100
T-Crypt/berth#196 · 8 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
Lend part of a card from a running engine and get it back, without a park or a reloadForse già presa @T-Crypt l’ha presa 7 giorni fa. Aperta
T-Crypt/berth#189 · 27 commenti · 1 reazione · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
Idea: Proxmox support in every layout (testers wanted)Forse già presa @sharmis-tp l’ha presa 3 giorni fa. Apertaenhancement help wanted
Difficoltà 5/5 Più di una settimana Idoneità per principianti 25/100
T-Crypt/berth#128 · 2 commenti · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
enhancement
Difficoltà 5/5 Più di una settimana Idoneità per principianti 25/100
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di T-Crypt/berth
Issue simili
-
docs(prompts): IF BLOCKED workflow-scope branch says to comment on the issue on jira/local trackersApertaagent-butler-finding bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 77/100
jordansmall/spindrift#6238 ·
I maintainer di solito rispondono entro 1 giorno
-
bug: microcks test get swallows test failure details and operation results in text output modeAperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 85/100
microcks/microcks-cli#608 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
bug from-studio
Difficoltà 2/5 1-3 ore Idoneità per principianti 65/100
esengine/DeepSeek-Reasonix#14868 ·
I maintainer di solito rispondono entro 1 giorno
-
Install notes print "Camunda REST API: https://" without a host when global.host is setForse già presa @irisfield l’ha presa oggi. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
camunda/camunda-platform-helm#7790 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
wso2/agent-manager#2220 ·
I maintainer di solito rispondono entro 1 giorno