mGPUmanager
GPU-Inference-Control-Plane für mRock — Scheduler vor TTS/STT/LLM/Image-Gen mit globalem GPU-Lock + LRU-Eviction + einheitlicher /v1-Fassade. Konsumenten: mVoice, whisper-server, Ollama, ComfyUI/FLUX, später Furbotto. Go.
Full design: docs/design.md — Bestandsaufnahme, 10-Alternativen-Survey, Eviction-Algorithmus, Migrationspfad.
Was es macht
Auf mrock:8770 sitzt ein Go-Daemon, der:
/v1/tts,/v1/stt,/v1/llm,/v1/imageals einheitliche Konsumenten-Fassade exponiert,- jede Anfrage durch einen globalen GPU-Scheduler schleust (seriell, Queue),
- bei VRAM-Druck LRU-Eviction über die deklarierten Coexistenz-Gruppen aus
config/consumers.yamlfährt, - in
/v1/statusLive-GPU-Belegung + Consumer-Health + Scheduler-Statistiken zeigt, - niemals stille Fallbacks zurückgibt — Fehler kommen als strukturiertes
{error,message,consumer,retryable}.
Konsumenten-Registry
config/consumers.yaml deklariert pro Consumer:
url,health.{method,path}für Liveness-Probingpaths.<kind>.{method,path}— wie der Broker zu seinem TTS/STT/LLM/Image-Endpoint kommtvram_resident_mib— für die Scheduler-Mathe (Schritt 5)unload.{method,path,body}und optionalload.{method,path}— wie der Broker den Consumer aus dem VRAM räumt / wieder hochfährtcan_coexist_with: [..]— wer parallel resident sein darfpriority(0=low, 4=urgent),max_concurrency
Build + Deploy
make build # ./bin/mgpumanager
make test # go test ./...
make run # lokal gegen ./config/consumers.yaml
make deploy HOST=mrock # rsync + systemd reload + restart
Auf mRock läuft der Daemon als System-Unit (/etc/systemd/system/mgpumanager.service).
Endpoints
| Verb | Pfad | Verhalten |
|---|---|---|
| POST | /v1/tts |
Proxy zu routing.tts-Consumer (default: mvoice /api/synthesize) |
| POST | /v1/stt |
Proxy zu routing.stt-Consumer (default: mvoice /api/transcribe) |
| POST | /v1/llm |
Proxy zu routing.llm-Consumer (default: ollama /api/generate) |
| POST | /v1/image |
Proxy zu routing.image-Consumer (default: comfyui /prompt) |
| POST | /v1/lease |
GPU-Lease anfordern ({kind|consumer, ttl_seconds, wait_seconds}) → hält den globalen GPU-Lock über den ganzen Async-Zyklus, liefert {token, consumer, granted_at, expires_at, ttl_seconds} |
| POST | /v1/lease/{token}/renew |
Heartbeat: Safety-Expiry auf now+ttl zurücksetzen → 200 oder 404 lease_unknown |
| DELETE | /v1/lease/{token} |
Idempotenter Release → {released} (reason: unknown_or_expired wenn unbekannt/abgelaufen) |
| GET | /audio/* |
Proxy zu audio_proxy-Consumer (wa.sh fetcht generiertes Audio so) |
| GET | /v1/status |
Live-Snapshot: GPU + Consumer-Health + Scheduler-Stats + aktive Lease-Holder |
| GET | /healthz |
Broker-Liveness (200 OK) |
GPU-Lease (Issue #2, für ImaGen #15)
Lang laufende Async-Consumer (ComfyUI/FLUX) führen einen mehrstufigen Zyklus
(/upload → /prompt → poll /history → /view) selbst gegen ihr Backend aus. Der
synchrone Proxy hält den Lock nur für den einen /prompt-Call — zu kurz. Das
Lease hält Eviction + Lock über den gesamten Zyklus: acquire (evict +
ensureLoaded + Lock) → Consumer rendert direkt am Backend → release. Ein
abgestürzter Holder hört auf zu renewen, der Lock wird innerhalb einer TTL frei.
Auf dem Lease-Pfad schlägt das acquire mit 503 insufficient_vram
(retryable:false) fehl, wenn auch nach maximaler Eviction kein Platz für den
Ziel-Consumer ist (statt einen garantierten OOM zu gewähren) — der synchrone
Proxy bleibt optimistisch.
Fehler-Schema
Jeder Broker-eigene Fehler hat die Form:
{
"error": "consumer_unreachable",
"message": "upstream mvoice last probe failed: connection refused",
"consumer": "mvoice",
"retryable": true
}
Codes: consumer_unreachable, no_consumer, scheduler_error, bad_consumer_url, bad_request, insufficient_vram, scheduler_timeout, lease_unknown. Pass-through-4xx/5xx vom Consumer landet unverändert beim Client.
Phase 1 Status (Issue #1)
- ✅ Schritt 0 — ComfyUI persistent (
systemd: comfyui.service) - ✅ Schritt 1 —
mvoice /api/admin/{load,unload}(mai/knuth/admin-load-unload @ mVoice) - ✅ Schritt 2 — Routing-Façade +
/v1/status - ✅ Schritt 3 — wa.sh auf Broker umgestellt (m/mAi
mai/knuth/wa-tts-broker) - ✅ Schritt 4 — Queue + globaler GPU-Lock
- ✅ Schritt 5 — Coexistenz-Gruppen + LRU-Eviction
- ✅ Issue #2 — Generisches GPU-Lease (
/v1/leaseacquire/renew/release) für lang laufende Async-Consumer (ImaGen #15)