feat(compute): Worker-Selbstanmeldung ueber RVS + Flotten-Anzeige (Stage 2)

Jeder GPU-Dienst meldet sich beim Connect mit worker_hello {instanceId,
service, node, gpus, model} und haelt die Registry per periodischem
worker_ping {instanceId, busy} (~10s) frisch. So weiss ARIA, was wo laeuft.

- xtts/{voxtral,whisper,f5tts}/bridge.py + llm-adapter/adapter.py:
  INSTANCE_ID=service@NODE_NAME, _worker_register()-Coroutine (hello + ping),
  busy-Quelle je Worker (aktive STT-Sessions / TTS-Render / in-flight LLM);
  Task sauber gecancelt bei Reconnect.
- bridge/aria_bridge.py: self._workers-Registry + Handler worker_hello/
  worker_ping (spiegelt sat_hello), _worker_list() (35s-Offline-TTL),
  _pick_worker() (Round-Robin freie Instanz, fuer Stage-3-Routing),
  /internal/worker-list-Endpoint.
- diagnostic/server.js: workers-Map, worker_hello/worker_ping-Tracking,
  worker_update-Broadcast + worker_list-Action + on-connect-Snapshot.
- diagnostic/index.html: "Compute-Flotte"-Panel im Satelliten-Tab — pro Node
  gruppiert, mit Dienst/Modell/GPU und frei/beschaeftigt/offline-Status.

Stage 2 von 3. Reine Sichtbarkeit, kein Routing-Verhalten geaendert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-09-18 12:22:47 +02:00
co-authored by Claude Opus 4.8
parent e75f1eeb6a
commit f2ead1242f
7 changed files with 330 additions and 0 deletions
+34
View File
@@ -60,6 +60,15 @@ RVS_TOKEN = os.getenv("RVS_TOKEN", "").strip()
# f5ttsCkptFile, f5ttsVocabFile, f5ttsCfgStrength, f5ttsNfeStep).
F5TTS_DEVICE = os.getenv("F5TTS_DEVICE", "cuda") # nur Bootstrap
# ── Compute-Fleet: Worker-Identitaet & Registrierung ──────────────
# Meldet sich bei der aria-bridge (worker_hello) + periodischer worker_ping.
NODE_NAME = os.getenv("NODE_NAME", "node").strip() or "node"
GPU_IDS = os.getenv("NVIDIA_VISIBLE_DEVICES", "").strip()
WORKER_SERVICE = "f5tts"
INSTANCE_ID = f"{WORKER_SERVICE}@{NODE_NAME}"
WORKER_PING_INTERVAL_S = int(os.getenv("WORKER_PING_INTERVAL_S", "10"))
_tts_busy = False # True waehrend eine Synthese laeuft (busy-Report im ping)
DEFAULT_F5TTS_MODEL = "F5TTS_v1_Base"
DEFAULT_F5TTS_CKPT_FILE = "" # leer = Default-Checkpoint von HF
DEFAULT_F5TTS_VOCAB_FILE = "" # leer = Default-Vocab vom Modell
@@ -460,13 +469,16 @@ _tts_queue: asyncio.Queue[tuple] = asyncio.Queue()
async def _tts_worker(ws, runner: F5Runner) -> None:
"""Serialisiert Synthesen — GPU kann sonst OOM gehen."""
global _tts_busy
while True:
text, voice, request_id, message_id, language, speed = await _tts_queue.get()
_tts_busy = True
try:
await _do_tts(ws, runner, text, voice, request_id, message_id, language, speed)
except Exception:
logger.exception("TTS-Worker Fehler")
finally:
_tts_busy = False
_tts_queue.task_done()
@@ -808,6 +820,24 @@ async def _broadcast_status(ws, state: str, **extra) -> None:
await _send(ws, "service_status", payload)
async def _worker_register(ws, *, model: str = "", busy_fn=None) -> None:
"""Meldet diesen Worker bei der aria-bridge an (worker_hello) und haelt die
Flotten-Registry per periodischem worker_ping (mit busy-Status) frisch."""
try:
await _send(ws, "worker_hello", {
"instanceId": INSTANCE_ID, "service": WORKER_SERVICE,
"node": NODE_NAME, "gpus": GPU_IDS, "model": model,
})
while True:
await asyncio.sleep(WORKER_PING_INTERVAL_S)
busy = bool(busy_fn()) if busy_fn else False
await _send(ws, "worker_ping", {"instanceId": INSTANCE_ID, "busy": busy})
except asyncio.CancelledError:
raise
except Exception:
return # Socket tot → still beenden; run_loop reconnectet + startet neu
async def run_loop(runner: F5Runner) -> None:
use_tls = RVS_TLS
retry_s = 2
@@ -855,6 +885,9 @@ async def run_loop(runner: F5Runner) -> None:
# TTS-Worker fuer diese Verbindung starten
worker = asyncio.create_task(_tts_worker(ws, runner))
ping_task = asyncio.create_task(_worker_register(
ws, model=runner.model_id,
busy_fn=lambda: _tts_busy or not _tts_queue.empty()))
try:
async for raw in ws:
@@ -958,6 +991,7 @@ async def run_loop(runner: F5Runner) -> None:
_last_diag_voice = ""
finally:
worker.cancel()
ping_task.cancel()
try:
await worker
except asyncio.CancelledError: