Phase 1: GPU-Manager — Schritte 0–5 (ComfyUI systemd → mVoice load/unload → Broker MVP → wa.sh migrate → Queue → LRU-Eviction) #1

Open
opened 2026-05-11 11:18:04 +00:00 by mAi · 4 comments

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)

# Entscheidung
Name mGPUmanager (NICHT mForge — das ist schon eine Proxmox-Maschine)
Sprache Go (konsistent mit mAi/ImaGen)
Repo Eigenes Repo m/mGPUmanager (dieser hier)
Phase 1 Scope Schritte 0–5 voller Manager inkl. Queue + LRU-Eviction
mai-mesh Später — erst bauen, dann mesh-anbinden

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/tts am 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

  • Schritt 6 — ImaGen auf Broker umstellen
  • Schritt 7 — m-CLI Konsumenten umstellen
  • Schritt 8 — Furbotto-Voice-Pipeline anbinden
  • mai-mesh Health-Integration

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.md als Briefing.

Refs

  • mAi#220 — wa.sh stale-OGG-Fix (Quick-Win parallel, könnte vor Schritt 3 mergen)
  • mVoice#18 — Original-Issue, redirected hierhin
  • otto-wa-public-Delegation #1662 — Incident-Details + Schadensbericht
  • docs/design.md — vollständige Research-Doc von hermes (mvoice inventor)
## 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) | # | Entscheidung | |---|---| | Name | **mGPUmanager** (NICHT mForge — das ist schon eine Proxmox-Maschine) | | Sprache | **Go** (konsistent mit mAi/ImaGen) | | Repo | **Eigenes Repo `m/mGPUmanager`** (dieser hier) | | Phase 1 Scope | **Schritte 0–5** voller Manager inkl. Queue + LRU-Eviction | | mai-mesh | **Später** — erst bauen, dann mesh-anbinden | ## 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/tts` am 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 - Schritt 6 — ImaGen auf Broker umstellen - Schritt 7 — m-CLI Konsumenten umstellen - Schritt 8 — Furbotto-Voice-Pipeline anbinden - mai-mesh Health-Integration 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.md` als Briefing. ## Refs - mAi#220 — wa.sh stale-OGG-Fix (Quick-Win parallel, könnte vor Schritt 3 mergen) - mVoice#18 — Original-Issue, redirected hierhin - otto-wa-public-Delegation #1662 — Incident-Details + Schadensbericht - `docs/design.md` — vollständige Research-Doc von hermes (mvoice inventor)
Author

Shift 1 — Phase 1 abgeschlossen (Schritte 0–5)

Drei Repos berührt, alles lokal auf Feature-Branches, nicht deployed:

m/mGPUmanager — branch mai/knuth/issue-1-phase-1-schritte

  • c81c145 feat: Schritt 2 — mGPUmanager MVP routing + /v1/status
  • 3b3d828 feat: Schritt 4 — Locked scheduler (global GPU lock, queue, stats)
  • ca9bb17 feat: Schritt 5 — VRAM-pressure eviction + coexistence groups

Go-Daemon auf :8770, ~1.7k Zeilen Code + Tests, kompiliert sauber unter go 1.25.5.

Modul-Layout:

cmd/mgpumanager/main.go    # entry point, signal handling, slog JSON
internal/config/           # consumers.yaml loader + validation + tests
internal/registry/         # consumer health probing (5s cadence)
internal/gpu/              # nvidia-smi sample loop (2s cadence)
internal/scheduler/        # Passthrough → Locked → Evicting, jeweils mit Tests
internal/server/           # /v1 façade, audio proxy, /v1/status, structured errors
config/consumers.yaml      # default consumer registry (mvoice/whisper/ollama/comfyui)
systemd/mgpumanager.service
Makefile                   # build / test / run / deploy

m/mVoice — branch mai/knuth/admin-load-unload

  • 9f4d430 feat(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/health liefert jetzt gpu_resident_mib und ein loaded bool. Lifespan behält weiterhin audio_dir + Lexicon (CPU-Seite, von unload/load nicht angefasst).

m/mAi — branch mai/knuth/wa-tts-broker

  • a4a1b1c feat(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 → JSON audio_urlGET $WA_BROKER_URL$audio_url → lokales ffmpeg opus. WA_BROKER_URL=http://mrock:8770 default. 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 (war enabled, inactive weil heute morgen manuell systemctl stop't) gestartet. curl http://localhost:8188/system_stats antwortet, 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 ohne loginctl enable-linger und ohne Doppel-Unit-Risiko. Migration auf user-unit kann später, wenn ein konkreter Grund existiert.

Test-Lage (alles grün, race detector inklusive)

  • config: load valid → struct, reject unknown routing target, reject missing URL
  • server: proxy forwards body + Content-Type, audio proxy streams bytes, unhealthy consumer → strukturiertes 503, /v1/status JSON shape
  • scheduler/Locked: 5 concurrent goroutines → max in-flight = 1, ctx-cancel auf parkendem Job → ctx.Err(), in-flight/queue-Stats konsistent
  • scheduler/Evicting: skip-when-resident (kein unload), comfyui→mvoice eviction (Stats.Evictions=1), coexistent ollama nicht angetastet auch wenn LRU

Smoke-Test (lokal auf knuth)

GET /healthz       → {"status":"ok"}
GET /v1/status     → vollständiges JSON: GPU-Sample, Consumer-Health, Scheduler-Stats
POST /v1/tts       → korrektes Proxying an `routing.tts`-Consumer (nvidia-smi not on this host, daher GPU-err im status; das ist erwartet auf einem Nicht-Mrock-Host)

Was als nächstes mit head abzustimmen ist

  1. Merge der drei Feature-Branches auf jeweils main (nicht mehr Coder-Scope).
  2. Deploy auf mRock: make deploy HOST=mrock (rsync binary + config + systemd-unit, sudo systemctl daemon-reload && restart).
  3. mVoice restart mit den /api/admin/*-Endpoints, einmal POST /api/admin/unload//load durchspielen, gpu_resident_mib in health prüfen.
  4. Live-Test wa.sh mit echtem WhatsApp-Voice-Reply via mai/knuth/wa-tts-broker.
  5. Nach Live-Test Phase 2 Issues öffnen (ImaGen-Migration, m-CLI m gpu-Wrapper, Furbotto-Voice-Pipeline).
## Shift 1 — Phase 1 abgeschlossen (Schritte 0–5) Drei Repos berührt, alles lokal auf Feature-Branches, **nicht deployed**: ### `m/mGPUmanager` — branch `mai/knuth/issue-1-phase-1-schritte` - `c81c145` feat: Schritt 2 — mGPUmanager MVP routing + `/v1/status` - `3b3d828` feat: Schritt 4 — Locked scheduler (global GPU lock, queue, stats) - `ca9bb17` feat: Schritt 5 — VRAM-pressure eviction + coexistence groups Go-Daemon auf `:8770`, ~1.7k Zeilen Code + Tests, kompiliert sauber unter `go 1.25.5`. Modul-Layout: ``` cmd/mgpumanager/main.go # entry point, signal handling, slog JSON internal/config/ # consumers.yaml loader + validation + tests internal/registry/ # consumer health probing (5s cadence) internal/gpu/ # nvidia-smi sample loop (2s cadence) internal/scheduler/ # Passthrough → Locked → Evicting, jeweils mit Tests internal/server/ # /v1 façade, audio proxy, /v1/status, structured errors config/consumers.yaml # default consumer registry (mvoice/whisper/ollama/comfyui) systemd/mgpumanager.service Makefile # build / test / run / deploy ``` ### `m/mVoice` — branch `mai/knuth/admin-load-unload` - `9f4d430` feat(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/health` liefert jetzt `gpu_resident_mib` und ein `loaded` bool. Lifespan behält weiterhin `audio_dir` + Lexicon (CPU-Seite, von unload/load nicht angefasst). ### `m/mAi` — branch `mai/knuth/wa-tts-broker` - `a4a1b1c` feat(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` → JSON `audio_url` → `GET $WA_BROKER_URL$audio_url` → lokales `ffmpeg` opus. `WA_BROKER_URL=http://mrock:8770` default. 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` (war `enabled, inactive` weil heute morgen manuell `systemctl stop`'t) gestartet. `curl http://localhost:8188/system_stats` antwortet, 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 ohne `loginctl enable-linger` und ohne Doppel-Unit-Risiko. Migration auf user-unit kann später, wenn ein konkreter Grund existiert. ### Test-Lage (alles grün, race detector inklusive) - **config**: load valid → struct, reject unknown routing target, reject missing URL - **server**: proxy forwards body + Content-Type, audio proxy streams bytes, unhealthy consumer → strukturiertes 503, /v1/status JSON shape - **scheduler/Locked**: 5 concurrent goroutines → max in-flight = 1, ctx-cancel auf parkendem Job → ctx.Err(), in-flight/queue-Stats konsistent - **scheduler/Evicting**: skip-when-resident (kein unload), comfyui→mvoice eviction (`Stats.Evictions=1`), coexistent ollama nicht angetastet auch wenn LRU ### Smoke-Test (lokal auf knuth) ``` GET /healthz → {"status":"ok"} GET /v1/status → vollständiges JSON: GPU-Sample, Consumer-Health, Scheduler-Stats POST /v1/tts → korrektes Proxying an `routing.tts`-Consumer (nvidia-smi not on this host, daher GPU-err im status; das ist erwartet auf einem Nicht-Mrock-Host) ``` ### Was als nächstes mit head abzustimmen ist 1. **Merge** der drei Feature-Branches auf jeweils `main` (nicht mehr Coder-Scope). 2. **Deploy** auf mRock: `make deploy HOST=mrock` (rsync binary + config + systemd-unit, `sudo systemctl daemon-reload && restart`). 3. **mVoice restart** mit den `/api/admin/*`-Endpoints, einmal `POST /api/admin/unload`/`/load` durchspielen, `gpu_resident_mib` in health prüfen. 4. **Live-Test wa.sh** mit echtem WhatsApp-Voice-Reply via `mai/knuth/wa-tts-broker`. 5. Nach Live-Test Phase 2 Issues öffnen (ImaGen-Migration, m-CLI `m gpu`-Wrapper, Furbotto-Voice-Pipeline).
Author

Phase 1 merged to main

  • m/mGPUmanager: cacba89Merge Phase 1 — Schritte 0–5 (broker MVP, queue, LRU eviction)
  • m/mVoice: 9b3282bMerge admin-load-unload — /api/admin/{load,unload} for mGPUmanager VRAM eviction

m/mAi mai/knuth/wa-tts-broker deliberately 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).

## Phase 1 merged to main - m/mGPUmanager: [`cacba89`](https://mgit.msbls.de/m/mGPUmanager/commit/cacba89) — `Merge Phase 1 — Schritte 0–5 (broker MVP, queue, LRU eviction)` - m/mVoice: [`9b3282b`](https://mgit.msbls.de/m/mVoice/commit/9b3282b) — `Merge admin-load-unload — /api/admin/{load,unload} for mGPUmanager VRAM eviction` m/mAi `mai/knuth/wa-tts-broker` **deliberately 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`).
Author

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.

/etc/systemd/system/comfyui.service     → stopped, disabled
~/.config/systemd/user/comfyui.service  → active (running), enabled
GET http://mrock:8188/system_stats → {"system":{"os":"linux","comfyui_version":"0.20.1", ...}}

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)

cd ~/dev/mVoice && git pull --ff-only origin main   # 9a80264 → 9b3282b
just restart                                          # tmux session 'mvoice'

/api/admin/{load,unload} Roundtrip verifiziert:

GET  /api/health   → {"loaded": true,  "gpu_resident_mib": 2336}
POST /api/admin/unload → {"vram_before_mib": 2336, "vram_after_mib": 0}
  nvidia-smi         8871 MiB → 6499 MiB  (Δ -2.4 GiB freed)
GET  /api/health   → {"loaded": false, "gpu_resident_mib": 0}
POST /api/admin/load   → {"vram_before_mib": 0,    "vram_after_mib": 2336}
GET  /api/health   → {"loaded": true,  "gpu_resident_mib": 2336}
  nvidia-smi         6499 MiB → 8871 MiB

Schritt 2 — mGPUmanager deployed (DONE)

make deploy HOST=mrock
→ bin/mgpumanager           → mrock:/home/m/dev/mGPUmanager/bin/mgpumanager
→ config/consumers.yaml      → mrock:/home/m/dev/mGPUmanager/config/consumers.yaml
→ systemd/mgpumanager.service → mrock:/home/m/.config/systemd/user/mgpumanager.service
→ systemctl --user enable + restart

Deploy switched to systemd --user (Konvention auf mRock: whisper-server, mvoice-launcher, comfyui sind alles user-units). Bind ist 0.0.0.0:8770 damit mRiver / Tailscale-peers durchkommen. Commit 167999c.

systemctl --user is-active mgpumanager.serviceactive.

Schritt 4 — /v1/tts roundtrip cross-host (DONE)

Von mRiver gegen mrock:8770:

POST /v1/tts  (text="Hallo, das ist ein Live-Test des GPU-Brokers.", profile=otto)
→ 200 {
    "audio_url": "/api/audio/response_9cbe6e03.wav",
    "audio_duration": 3.9,
    "tts_ms": 1010
  }
GET  /api/audio/response_9cbe6e03.wav
→ 200 RIFF, WAVE audio, Microsoft PCM, 16 bit, mono 24000 Hz, 184876 B, duration 3.85 s

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=true markiert, obwohl FLUX erst on-demand geladen wird. ensureFits() short-circuitete deshalb auf der allerersten /v1/image-Anfrage, und mvoice wurde nicht evicted. Fix in commit 468317e: initialLoaded(cons) heuristik:

  • VRAMManaged (ollama) → true (nie evicten)
  • Load + Unload (mvoice) → true (eigene lifespan lädt)
  • Unload only, no Load (comfyui) → false (lazy, FLUX cold-loads on /prompt)
  • SystemdUnit only (whisper-server) → true (always-on)

Testabdeckung dafür: TestInitialLoadedHeuristic (table-driven, pinnt alle vier Fälle).

Nach Deploy des Fix:

--- pre /v1/image ---
nvidia-smi:                       8963 MiB used
/v1/status mvoice:                gpu_resident_mib=2345, loaded=true

POST /v1/image {"prompt":{}}     → 400 upstream rejected empty workflow,
                                    BUT broker ran ensureFits() first

--- post /v1/image ---
nvidia-smi:                       6547 MiB used   (Δ -2.4 GiB)
/v1/status mvoice:                gpu_resident_mib=9, loaded=false  ← evicted
/v1/status scheduler.evictions:   2

--- /v1/tts to reload ---
POST /v1/tts                      → 200 audio_url, tts_ms=670, audio 3.5 s
nvidia-smi:                       8943 MiB used   (Δ +2.4 GiB)
/v1/status mvoice:                gpu_resident_mib=2917, loaded=true ← back
/v1/status scheduler.total_jobs:  2

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_unit aber keinen HTTP-Unload — Code markiert es nur als loaded=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)

{
  "gpu": {"total_mib": 16376, "used_mib": 8943, "free_mib": 6969, "reserved_mib": 1024},
  "routing": {"tts":"mvoice","stt":"mvoice","llm":"ollama","image":"comfyui"},
  "consumers": [
    {"name":"comfyui",       "healthy":true, "gpu_resident_mib":   0, "vram_budget_mib":13000, "total_requests":1},
    {"name":"mvoice",        "healthy":true, "gpu_resident_mib":2343, "vram_budget_mib": 2800, "total_requests":1},
    {"name":"ollama",        "healthy":true, "gpu_resident_mib":   0, "vram_budget_mib":    0},
    {"name":"whisper-server","healthy":true, "gpu_resident_mib":   0, "vram_budget_mib": 2050}
  ],
  "scheduler": {"queue_depth":0, "in_flight":0, "total_jobs":2, "last_run_ms":671, "evictions":2}
}

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-schritte pushed mit 167999c (deploy-as-user-unit) + 468317e (initialLoaded fix). Merge nach Acceptance.

Commits seit shift-1:

  • 167999c build: deploy as systemd --user unit on mRock
  • 468317e fix(scheduler): mark lazy consumers as not-loaded at startup
## 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. ``` /etc/systemd/system/comfyui.service → stopped, disabled ~/.config/systemd/user/comfyui.service → active (running), enabled GET http://mrock:8188/system_stats → {"system":{"os":"linux","comfyui_version":"0.20.1", ...}} ``` 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) ``` cd ~/dev/mVoice && git pull --ff-only origin main # 9a80264 → 9b3282b just restart # tmux session 'mvoice' ``` `/api/admin/{load,unload}` Roundtrip verifiziert: ``` GET /api/health → {"loaded": true, "gpu_resident_mib": 2336} POST /api/admin/unload → {"vram_before_mib": 2336, "vram_after_mib": 0} nvidia-smi 8871 MiB → 6499 MiB (Δ -2.4 GiB freed) GET /api/health → {"loaded": false, "gpu_resident_mib": 0} POST /api/admin/load → {"vram_before_mib": 0, "vram_after_mib": 2336} GET /api/health → {"loaded": true, "gpu_resident_mib": 2336} nvidia-smi 6499 MiB → 8871 MiB ``` ### Schritt 2 — mGPUmanager deployed (DONE) ``` make deploy HOST=mrock → bin/mgpumanager → mrock:/home/m/dev/mGPUmanager/bin/mgpumanager → config/consumers.yaml → mrock:/home/m/dev/mGPUmanager/config/consumers.yaml → systemd/mgpumanager.service → mrock:/home/m/.config/systemd/user/mgpumanager.service → systemctl --user enable + restart ``` Deploy switched to systemd --user (Konvention auf mRock: whisper-server, mvoice-launcher, comfyui sind alles user-units). Bind ist `0.0.0.0:8770` damit mRiver / Tailscale-peers durchkommen. Commit `167999c`. `systemctl --user is-active mgpumanager.service` → `active`. ### Schritt 4 — /v1/tts roundtrip cross-host (DONE) Von mRiver gegen `mrock:8770`: ``` POST /v1/tts (text="Hallo, das ist ein Live-Test des GPU-Brokers.", profile=otto) → 200 { "audio_url": "/api/audio/response_9cbe6e03.wav", "audio_duration": 3.9, "tts_ms": 1010 } GET /api/audio/response_9cbe6e03.wav → 200 RIFF, WAVE audio, Microsoft PCM, 16 bit, mono 24000 Hz, 184876 B, duration 3.85 s ``` 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=true` markiert, obwohl FLUX erst on-demand geladen wird. `ensureFits()` short-circuitete deshalb auf der allerersten /v1/image-Anfrage, und mvoice wurde nicht evicted. Fix in commit `468317e`: `initialLoaded(cons)` heuristik: - VRAMManaged (ollama) → true (nie evicten) - Load + Unload (mvoice) → true (eigene lifespan lädt) - **Unload only, no Load (comfyui) → false (lazy, FLUX cold-loads on /prompt)** - SystemdUnit only (whisper-server) → true (always-on) Testabdeckung dafür: `TestInitialLoadedHeuristic` (table-driven, pinnt alle vier Fälle). Nach Deploy des Fix: ``` --- pre /v1/image --- nvidia-smi: 8963 MiB used /v1/status mvoice: gpu_resident_mib=2345, loaded=true POST /v1/image {"prompt":{}} → 400 upstream rejected empty workflow, BUT broker ran ensureFits() first --- post /v1/image --- nvidia-smi: 6547 MiB used (Δ -2.4 GiB) /v1/status mvoice: gpu_resident_mib=9, loaded=false ← evicted /v1/status scheduler.evictions: 2 --- /v1/tts to reload --- POST /v1/tts → 200 audio_url, tts_ms=670, audio 3.5 s nvidia-smi: 8943 MiB used (Δ +2.4 GiB) /v1/status mvoice: gpu_resident_mib=2917, loaded=true ← back /v1/status scheduler.total_jobs: 2 ``` 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_unit` aber keinen HTTP-Unload — Code markiert es nur als `loaded=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) ```json { "gpu": {"total_mib": 16376, "used_mib": 8943, "free_mib": 6969, "reserved_mib": 1024}, "routing": {"tts":"mvoice","stt":"mvoice","llm":"ollama","image":"comfyui"}, "consumers": [ {"name":"comfyui", "healthy":true, "gpu_resident_mib": 0, "vram_budget_mib":13000, "total_requests":1}, {"name":"mvoice", "healthy":true, "gpu_resident_mib":2343, "vram_budget_mib": 2800, "total_requests":1}, {"name":"ollama", "healthy":true, "gpu_resident_mib": 0, "vram_budget_mib": 0}, {"name":"whisper-server","healthy":true, "gpu_resident_mib": 0, "vram_budget_mib": 2050} ], "scheduler": {"queue_depth":0, "in_flight":0, "total_jobs":2, "last_run_ms":671, "evictions":2} } ``` ### 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-schritte` pushed mit `167999c` (deploy-as-user-unit) + `468317e` (initialLoaded fix). Merge nach Acceptance. Commits seit shift-1: - `167999c` build: deploy as systemd --user unit on mRock - `468317e` fix(scheduler): mark lazy consumers as not-loaded at startup
Author

Phase 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)

  • m/mGPUmanager d02c88b — deploy fixes (systemd --user unit + initialLoaded heuristic for lazy consumers like ComfyUI). Bug found and fixed live on mRock, with TestInitialLoadedHeuristic pinning all four cases.
  • m/mAi 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)
  • mvoice running main 9b3282b (with /api/admin/{load,unload})
  • mgpumanager.service (systemd --user) listening on 0.0.0.0:8770
  • Eviction verified live: mvoice 2345 → 9 MiB on /v1/image, back to 2917 MiB on next /v1/tts.

Step 6 — m drives

Left for m to drive end-to-end since it needs his voice:

  1. Real otto WA voice-reply roundtrip (m records a WhatsApp voice note → otto transcribes → replies via TTS through the new broker path).
  2. 24h soak — verify nothing flaps under daily voice/image load.

Phase 2 (followups — separate issues to file when this stays green)

  • whisper-server eviction via real sudo systemctl stop (today it only marks loaded=false; works because mvoice's 2.4 GiB headroom suffices for FLUX-FP8, breaks for FLUX-dev FP16).
  • Generic /v1/{kind} routing + per-consumer routes-map (today the four endpoint kinds are hard-coded).
  • ImaGen → broker, m gpu CLI, Furbotto pipeline.
## Phase 1 acceptance — live on mRock, wa.sh now on main Knuth verified all five live-validation steps (see [#issuecomment-7984](https://mgit.msbls.de/m/mGPUmanager/issues/1#issuecomment-7984) for full evidence). Acceptance gate cleared. ### Merges (final) - m/mGPUmanager [`d02c88b`](https://mgit.msbls.de/m/mGPUmanager/commit/d02c88b) — deploy fixes (systemd --user unit + initialLoaded heuristic for lazy consumers like ComfyUI). Bug found and fixed live on mRock, with `TestInitialLoadedHeuristic` pinning all four cases. - m/mAi [`9b96ace`](https://mgit.msbls.de/m/mAi/commit/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) - `mvoice` running main `9b3282b` (with `/api/admin/{load,unload}`) - `mgpumanager.service` (systemd --user) listening on `0.0.0.0:8770` - Eviction verified live: `mvoice 2345 → 9 MiB` on /v1/image, back to `2917 MiB` on next /v1/tts. ### Step 6 — m drives Left for m to drive end-to-end since it needs his voice: 1. Real otto WA voice-reply roundtrip (m records a WhatsApp voice note → otto transcribes → replies via TTS through the new broker path). 2. 24h soak — verify nothing flaps under daily voice/image load. ### Phase 2 (followups — separate issues to file when this stays green) - whisper-server eviction via real `sudo systemctl stop` (today it only marks `loaded=false`; works because mvoice's 2.4 GiB headroom suffices for FLUX-FP8, breaks for FLUX-dev FP16). - Generic `/v1/{kind}` routing + per-consumer routes-map (today the four endpoint kinds are hard-coded). - ImaGen → broker, `m gpu` CLI, Furbotto pipeline.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: m/mGPUmanager#1
No description provided.