Phase 1: GPU-Manager — Schritte 0–5 (ComfyUI systemd → mVoice load/unload → Broker MVP → wa.sh migrate → Queue → LRU-Eviction) #1
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
m hat heute (2026-05-11) entschieden, einen GPU-Inference-Control-Plane für mRock zu bauen. Auslöser: CUDA-OOM zwischen mVoice/whisper/Ollama/ComfyUI + Felix-Banholzer-Stale-OGG-Incident.
Vorarbeit: Inventor-Research von hermes (mvoice/inventor) — 527-Zeilen-Survey von 10 Alternativen (Triton, vLLM, Ray Serve, TGI/TEI, LitServe, KServe, …), Scoring → custom Go-Daemon gewinnt Σ=45/45 für Single-User-Single-GPU. Doc in diesem Repo:
docs/design.md(kopiert aus mvoice-Worktree). Original-Issue: m/mVoice#18 (wird redirected).m's Decisions (2026-05-11 via PWA-AskUserQuestion)
m/mGPUmanager(dieser hier)Scope (Schritte 0–5)
Schritt 0 — ComfyUI als systemd-user-unit auf mRock
Entkoppelt ImaGen, ComfyUI startet sauber + lässt sich kontrolliert stoppen. Allein wertvoll, unabhängig vom Broker.
Schritt 1 — mVoice bekommt /api/admin/{load,unload}
Damit der Broker das Modell explizit aus dem VRAM ziehen / wieder laden kann. ~30 LoC in mVoice/server.py.
Schritt 2 — mGPUmanager MVP: Read-only Routing + /v1/status
Go-Daemon auf mRock:8770. Routet
/v1/tts→ mVoice:8766,/v1/stt→ whisper:8178,/v1/llm→ ollama:11434,/v1/image→ comfyui:8188. Kein Eviction noch, nur sichtbare Fassade + Status-Endpoint.Schritt 3 — wa.sh auf mGPUmanager umstellen
wa.sh ruft nicht mehr direkt qwen-tts, sondern
/v1/ttsam Broker. Damit ist der Felix-Banholzer-Stale-OGG-Code-Pfad strukturell tot (unique tmp-paths sind dann broker-seitig kein Issue mehr). Quick-Win.Schritt 4 — mGPUmanager Queue + globaler Lock
Jobs werden seriell auf GPU losgelassen. Concurrency-Limit pro Consumer aus
consumers.yaml. Kein Eviction noch — pessimistisch alle Modelle auf einmal im VRAM, aber Queue serialisiert.Schritt 5 — Coexistenz-Gruppen + LRU-Eviction
Deklarative Coexistenz-Regeln in
consumers.yaml: welche Modelle dürfen koexistieren, welche müssen evicted werden. LRU bei VRAM-Pressure. Damit kann der 16-GiB-GPU-Pool durchorchestriert werden.Out of Phase 1
Diese werden als separate Issues gefiled wenn Phase 1 live ist.
Workflow
Coder-Shift. hermes (mvoice) hat die Research-Phase gemacht — Same-Worker-No-Context-Loss-Pfad: hermes wird in dieses Repo umgesiedelt (mai-mGPUmanager session), kriegt diesen Issue als Task.
Falls Same-Worker-Migration zu komplex ist: fresh Coder-Worker in mai-mGPUmanager mit
docs/design.mdals Briefing.Refs
docs/design.md— vollständige Research-Doc von hermes (mvoice inventor)Shift 1 — Phase 1 abgeschlossen (Schritte 0–5)
Drei Repos berührt, alles lokal auf Feature-Branches, nicht deployed:
m/mGPUmanager— branchmai/knuth/issue-1-phase-1-schrittec81c145feat: Schritt 2 — mGPUmanager MVP routing +/v1/status3b3d828feat: Schritt 4 — Locked scheduler (global GPU lock, queue, stats)ca9bb17feat: Schritt 5 — VRAM-pressure eviction + coexistence groupsGo-Daemon auf
:8770, ~1.7k Zeilen Code + Tests, kompiliert sauber untergo 1.25.5.Modul-Layout:
m/mVoice— branchmai/knuth/admin-load-unload9f4d430feat(server):/api/admin/{load,unload}für mGPUmanager VRAM eviction_load_gpu_models()(idempotent) +_unload_gpu_models()(clear +gc.collect+torch.cuda.empty_cache+ipc_collect)./api/healthliefert jetztgpu_resident_mibund einloadedbool. Lifespan behält weiterhinaudio_dir+ Lexicon (CPU-Seite, von unload/load nicht angefasst).m/mAi— branchmai/knuth/wa-tts-brokera4a1b1cfeat(wa): route TTS via mGPUmanager broker (kills stale-OGG class)wa_tts()macht jetzt EINEN HTTP-Roundtrip statt ssh+qwen-tts:POST $WA_BROKER_URL/v1/tts→ JSONaudio_url→GET $WA_BROKER_URL$audio_url→ lokalesffmpegopus.WA_BROKER_URL=http://mrock:8770default. Per-Call mktemp dir +curl --fail-with-body+.error-Check killt jede Form von silent fallback. Felix-Banholzer-Stale-OGG-Bug-Class ist strukturell tot (kein fester remote /tmp Pfad mehr, mit dem zwei Calls kollidieren könnten).Schritt 0 — ComfyUI persistent
Bestehender System-Unit
/etc/systemd/system/comfyui.service(warenabled, inactiveweil heute morgen manuellsystemctl stop't) gestartet.curl http://localhost:8188/system_statsantwortet, ComfyUI lädt FLUX. Last uptime laut journal: 2d 19h. Design-Abweichung: m hatte user-unit angedacht, System-Unit war schon vorhanden + bewährt — Persistenz-Ziel ist erfüllt ohneloginctl enable-lingerund ohne Doppel-Unit-Risiko. Migration auf user-unit kann später, wenn ein konkreter Grund existiert.Test-Lage (alles grün, race detector inklusive)
Stats.Evictions=1), coexistent ollama nicht angetastet auch wenn LRUSmoke-Test (lokal auf knuth)
Was als nächstes mit head abzustimmen ist
main(nicht mehr Coder-Scope).make deploy HOST=mrock(rsync binary + config + systemd-unit,sudo systemctl daemon-reload && restart)./api/admin/*-Endpoints, einmalPOST /api/admin/unload//loaddurchspielen,gpu_resident_mibin health prüfen.mai/knuth/wa-tts-broker.m gpu-Wrapper, Furbotto-Voice-Pipeline).Phase 1 merged to main
cacba89—Merge Phase 1 — Schritte 0–5 (broker MVP, queue, LRU eviction)9b3282b—Merge admin-load-unload — /api/admin/{load,unload} for mGPUmanager VRAM evictionm/mAi
mai/knuth/wa-tts-brokerdeliberately not merged yet — kept on its branch until mGPUmanager is live on mrock:8770. Merging wa.sh before deploy breaks every otto WhatsApp voice reply.Next: live mRock deploy + smoke (
t-mgpumgr-i1-deploy).Shift 2 — Live mRock deploy verified (Schritte 0–5 acceptance evidence)
All five live-validation steps from atlas's instruction pass. WA roundtrip + 24 h soak (step 6) gated on m driving a real voice reply, queued separately.
Schritt 0 — ComfyUI als systemd-user-unit (DONE)
Migriert vom alten System-Unit zur User-Unit.
loginctl enable-linger m(UID 1000) erst aktiviert.FLUX-VRAM-Messung kommt erst wenn m oder atlas tatsächlich ein /v1/image mit echtem Workflow fährt — ein leerer prompt={} liefert 400 und lädt FLUX nicht.
Schritt 1b — mvoice rebooted on main 9b3282b (DONE)
/api/admin/{load,unload}Roundtrip verifiziert:Schritt 2 — mGPUmanager deployed (DONE)
Deploy switched to systemd --user (Konvention auf mRock: whisper-server, mvoice-launcher, comfyui sind alles user-units). Bind ist
0.0.0.0:8770damit mRiver / Tailscale-peers durchkommen. Commit167999c.systemctl --user is-active mgpumanager.service→active.Schritt 4 — /v1/tts roundtrip cross-host (DONE)
Von mRiver gegen
mrock:8770:ffprobe-Bestätigung: sample_rate=24000, channels=1, format_name=wav, bit_rate=384091.
Schritt 5 — Eviction smoke (DONE — incl. one bug fix during deploy)
Live-Bug entdeckt: comfyui wurde im Scheduler bei Startup als
loaded=truemarkiert, obwohl FLUX erst on-demand geladen wird.ensureFits()short-circuitete deshalb auf der allerersten /v1/image-Anfrage, und mvoice wurde nicht evicted. Fix in commit468317e:initialLoaded(cons)heuristik:Testabdeckung dafür:
TestInitialLoadedHeuristic(table-driven, pinnt alle vier Fälle).Nach Deploy des Fix:
Evictions=2 weil der eviction-loop nach mvoice (echte 2.4 GiB freed) noch nicht 13 GiB hatte und whisper-server als nächsten LRU-Kandidaten gewählt hat. Whisper hat
systemd_unitaber keinen HTTP-Unload — Code markiert es nur alsloaded=false, sudo-systemctl-stop ist in Phase 1 nicht implementiert (siehe ToDo unten). Mvoice wurde echt freed, daher gilt der Test.Final /v1/status (stable nach Test)
Was offen ist
Acceptance gate für wa.sh-Merge (atlas): Steps 1–5 sind verifiziert. atlas kann jetzt mai/knuth/wa-tts-broker mergen.
Step 6 — m drives: echte WA-Voice-Reply roundtrip (otto antwortet auf eine WhatsApp-Voice-Nachricht mit Stimme). Sobald wa.sh in main ist, kann m das durchspielen.
Schwacher Punkt: whisper-server-Eviction über systemd-unit-Stop ist noch ein Stub (markiert nur loaded=false, kein sudo-systemctl-stop). Heute kein realer Pain weil mvoice's 2.4 GiB plus die übrigen freien GiBs reichen, um FLUX-FP8 (~12 GiB) zu packen wenn mvoice raus ist. Für FLUX-dev FP16 oder grössere Workflows müsste das gefixt werden. Separates Phase-2-Issue wert.
Branch state:
mai/knuth/issue-1-phase-1-schrittepushed mit167999c(deploy-as-user-unit) +468317e(initialLoaded fix). Merge nach Acceptance.Commits seit shift-1:
167999cbuild: deploy as systemd --user unit on mRock468317efix(scheduler): mark lazy consumers as not-loaded at startupPhase 1 acceptance — live on mRock, wa.sh now on main
Knuth verified all five live-validation steps (see #issuecomment-7984 for full evidence). Acceptance gate cleared.
Merges (final)
d02c88b— deploy fixes (systemd --user unit + initialLoaded heuristic for lazy consumers like ComfyUI). Bug found and fixed live on mRock, withTestInitialLoadedHeuristicpinning all four cases.9b96ace— wa.sh routes WhatsApp TTS through mrock:8770 (kills the stale-OGG class structurally — per-call tmpdir, --fail-with-body curl, no silent fallback).State on mRock
comfyui.service→ systemd --user (linger enabled)mvoicerunning main9b3282b(with/api/admin/{load,unload})mgpumanager.service(systemd --user) listening on0.0.0.0:8770mvoice 2345 → 9 MiBon /v1/image, back to2917 MiBon next /v1/tts.Step 6 — m drives
Left for m to drive end-to-end since it needs his voice:
Phase 2 (followups — separate issues to file when this stays green)
sudo systemctl stop(today it only marksloaded=false; works because mvoice's 2.4 GiB headroom suffices for FLUX-FP8, breaks for FLUX-dev FP16)./v1/{kind}routing + per-consumer routes-map (today the four endpoint kinds are hard-coded).m gpuCLI, Furbotto pipeline.