Eigentlicher Fehler zuerst: llm_provision_model/_result und llm_remove_model
standen nie in der RVS-Allow-List. Der RVS verwirft unbekannte Typen mit einem
stillen return — jeder Klick auf "Laden" im Modell-Katalog ist seit Stage D
(19.09.) an dieser Liste gestorben, die Box hat nie davon erfahren. Laden
liessen sich nur die Modelle aus der Basis-Config der Box.
Gefunden, weil grep rvs/server.js fuer eine Binaerdatei hielt und still
uebersprang: in Zeile 164 stand seit a7998bcd ein ECHTES Nullbyte im
Template-String statt eines Escapes. Node stoert das nicht, aber git zeigte
fuer jede Aenderung am RVS nur noch "Binary files differ". Jetzt \u0000 —
zur Laufzeit derselbe String.
Fortschritt: llama.cpp zieht das GGUF per -hf selbst und meldet nichts. Der
llm-adapter fragt vorab bei Hugging Face, welche Dateien (Groesse, sha256)
kommen, und misst genau diese im HF-Cache-Layout, das llama.cpp verwendet:
/models/models--org--repo/blobs/<sha256>.downloadInProgress (waechst)
/models/models--org--repo/blobs/<sha256> (fertig)
Layout gegen den echten llama.cpp-Server ausprobiert, nicht angenommen. Geht
auch bei gesplitteten Modellen und zwei Downloads gleichzeitig; Fallback fuer
aeltere Layouts ueber alle *.downloadInProgress. Phasen: queued → start →
download (%, MB/s, Restzeit) → load ("laedt in die Karte") → ready ("in Karte
N geladen", Name + VRAM aus nvidia-smi) bzw. error.
Mitgefixt: der Warmup wartete LLM_TIMEOUT_SEC (Default 60 s). Ein Modell mit
mehreren GB meldete deshalb "Fehler", waehrend llama-swap weiterlud. Jetzt
LLM_WARMUP_TIMEOUT_S (1900 s, ueber llama-swaps healthCheckTimeout).
Diagnostic: Knopf "⬇ Downloads" neben "Von HuggingFace aktualisieren" mit
Zaehler fuer laufende; "Laden" oeffnet das Fenster direkt. Der Server merkt
sich den Stand pro (Box, Modell), ein spaeter geoeffneter Tab sieht laufende
Downloads. Antwortet die Box nicht, steht nach 30 s eine Warnung da statt
nichts.
Compose (KI-Box): llm-adapter bekommt LLM_GPU (Karte fuer die Meldung) und
HF_TOKEN (Dateiliste gesperrter Repos).
Test: Fortschritt gegen einen echten llama.cpp-Download (Qwen2.5-0.5B Q8,
676 MB): Prozent steigend 0 → 91.6, dann load; zweiter Lauf aus dem Cache
direkt load mit cached=true.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
217 lines
11 KiB
YAML
217 lines
11 KiB
YAML
# ════════════════════════════════════════════════
|
|
# ARIA Compute-Node — GPU-Dienste (STT / TTS / LLM)
|
|
#
|
|
# KEINE feste "AI-Box" mehr: dieser Stack laeuft auf beliebig vielen
|
|
# Worker-Nodes. Jeder Node startet ueber COMPOSE_PROFILES nur die Dienste,
|
|
# die er anbieten soll, und pinnt sie per *_GPU auf bestimmte Grafikkarten.
|
|
# Alles verbindet sich ueber RVS mit der ARIA-Infrastruktur — kein VPN.
|
|
#
|
|
# Beispiele (in der jeweiligen .env):
|
|
# STT-Box → COMPOSE_PROFILES=voxtral
|
|
# TTS-Box → COMPOSE_PROFILES=f5tts
|
|
# LLM-Box → COMPOSE_PROFILES=llm
|
|
# All-in-One → COMPOSE_PROFILES=voxtral,f5tts,llm (die alte AI-Box)
|
|
#
|
|
# FLUX-Bildgenerierung liegt im /flux Verzeichnis — eigener Stack.
|
|
# ════════════════════════════════════════════════
|
|
#
|
|
# Voraussetzungen:
|
|
# - Docker + NVIDIA Container Toolkit (registriert die `nvidia`-Runtime)
|
|
# - .env mit RVS-Verbindungsdaten, COMPOSE_PROFILES, NODE_NAME, *_GPU
|
|
#
|
|
# Start: docker compose up -d (liest COMPOSE_PROFILES aus der .env)
|
|
# ════════════════════════════════════════════════
|
|
|
|
services:
|
|
|
|
# ─── F5-TTS Bridge (GPU) ──────────────────────
|
|
# Empfaengt xtts_request via RVS, rendert via F5-TTS mit Voice-Cloning,
|
|
# streamt PCM an die App. Voice-Upload: speichert WAV und laesst eine
|
|
# STT-Bridge den Referenztext transkribieren — der User tippt nichts.
|
|
f5tts-bridge:
|
|
build: ./f5tts
|
|
container_name: aria-f5tts-bridge
|
|
profiles: ["f5tts"] # startet nur mit COMPOSE_PROFILES=…f5tts…
|
|
runtime: nvidia
|
|
volumes:
|
|
- ./voices:/voices # WAV + TXT Referenz
|
|
- ./hf-cache:/root/.cache/huggingface # HF-Cache als Bind-Mount.
|
|
# Direkt sichtbar im xtts/hf-cache/,
|
|
# einfach manuell zu loeschen, kein
|
|
# Docker-Desktop .vhdx Bloat.
|
|
# Wird mit STT-Bridges geteilt.
|
|
environment:
|
|
# GPU-Wahl: NVIDIA_VISIBLE_DEVICES (mehrere via "0,1"). Default GPU 0.
|
|
- NVIDIA_VISIBLE_DEVICES=${F5TTS_GPU:-0}
|
|
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
|
|
- NODE_NAME=${NODE_NAME:-node} # erscheint in Diagnostic + Logs
|
|
# Bootstrap-only — alle anderen F5-TTS-Settings (Modell, cfg_strength,
|
|
# nfe_step, Custom-Checkpoint) kommen ueber Diagnostic via RVS-config.
|
|
- RVS_HOST=${RVS_HOST}
|
|
- RVS_PORT=${RVS_PORT:-443}
|
|
- RVS_TLS=${RVS_TLS:-true}
|
|
- RVS_TLS_FALLBACK=${RVS_TLS_FALLBACK:-true}
|
|
- RVS_TOKEN=${RVS_TOKEN}
|
|
- F5TTS_DEVICE=${F5TTS_DEVICE:-cuda}
|
|
- VOICES_DIR=/voices
|
|
restart: unless-stopped
|
|
|
|
# ─── Whisper STT (GPU) — opt-in Fallback-STT ──
|
|
# Faster-Whisper auf CUDA. Verbindet sich selbst per WebSocket an den RVS
|
|
# und nimmt stt_request / stt_stream_* Nachrichten entgegen. Zusaetzlich
|
|
# nutzt die f5tts-bridge Whisper intern fuer die Referenz-Transkription bei
|
|
# Voice-Uploads. Modell-Hot-Swap via Diagnostic (config-Broadcast).
|
|
# Nur EINEN STT-Provider pro Node laufen lassen (voxtral ODER whisper) —
|
|
# sonst beantworten beide dieselbe Anfrage doppelt.
|
|
whisper-bridge:
|
|
build: ./whisper
|
|
container_name: aria-whisper-bridge
|
|
profiles: ["whisper"] # startet nur mit COMPOSE_PROFILES=…whisper…
|
|
runtime: nvidia
|
|
environment:
|
|
- NVIDIA_VISIBLE_DEVICES=${WHISPER_GPU:-1}
|
|
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
|
|
- NODE_NAME=${NODE_NAME:-node}
|
|
- RVS_HOST=${RVS_HOST}
|
|
- RVS_PORT=${RVS_PORT:-443}
|
|
- RVS_TLS=${RVS_TLS:-true}
|
|
- RVS_TLS_FALLBACK=${RVS_TLS_FALLBACK:-true}
|
|
- RVS_TOKEN=${RVS_TOKEN}
|
|
- WHISPER_MODEL=${WHISPER_MODEL:-small}
|
|
- WHISPER_DEVICE=${WHISPER_DEVICE:-cuda}
|
|
- WHISPER_COMPUTE_TYPE=${WHISPER_COMPUTE_TYPE:-float16}
|
|
- WHISPER_LANGUAGE=${WHISPER_LANGUAGE:-de}
|
|
volumes:
|
|
- ./hf-cache:/root/.cache/huggingface # gleicher Cache wie f5tts-bridge —
|
|
# ein Modell muss nur einmal pro
|
|
# Maschine geladen werden, kein
|
|
# Re-Download bei Container-Restart.
|
|
- ./voice-id:/voice-id # Speaker-ID-Fingerprint (Stefans
|
|
# Stimm-Embedding) persistent zwischen
|
|
# Container-Restarts.
|
|
restart: unless-stopped
|
|
|
|
# ─── Voxtral STT-3B (Transformers, GPU) — DEFAULT-STT ─────────
|
|
# Voxtral-Mini-3B-2507 (~9 GB bf16). Laeuft auf Treiber 550/CUDA 12.4
|
|
# (torch cu124, KEIN Treiber-Upgrade noetig). Whisper ist der opt-in
|
|
# Fallback — immer nur EINEN STT-Provider pro Node aktiv haben.
|
|
voxtral-bridge:
|
|
build: ./voxtral
|
|
container_name: aria-voxtral-bridge
|
|
profiles: ["voxtral"] # startet nur mit COMPOSE_PROFILES=…voxtral…
|
|
runtime: nvidia
|
|
volumes:
|
|
- ./hf-cache:/root/.cache/huggingface # gleicher Modell-Cache wie whisper/f5
|
|
- ./voice-id:/voice-id # Speaker-Fingerprint (wie whisper)
|
|
environment:
|
|
- NVIDIA_VISIBLE_DEVICES=${VOXTRAL_GPU:-1} # STT-3B ~9 GB → 12-GB-Karte
|
|
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
|
|
- NODE_NAME=${NODE_NAME:-node}
|
|
- RVS_HOST=${RVS_HOST}
|
|
- RVS_PORT=${RVS_PORT:-443}
|
|
- RVS_TLS=${RVS_TLS:-true}
|
|
- RVS_TLS_FALLBACK=${RVS_TLS_FALLBACK:-true}
|
|
- RVS_TOKEN=${RVS_TOKEN}
|
|
- VOXTRAL_MODEL=mistralai/Voxtral-Mini-3B-2507
|
|
- VOXTRAL_LANGUAGE=${WHISPER_LANGUAGE:-de}
|
|
- HUGGING_FACE_HUB_TOKEN=${HF_TOKEN:-} # falls das Modell HF-gated ist
|
|
- PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True # weniger VRAM-Fragmentierung
|
|
restart: unless-stopped
|
|
|
|
# ─── Lokales LLM — llama-swap (GPU) ───────────
|
|
# llama-swap laedt/swappt mehrere Modelle on-demand. Welches geladen wird,
|
|
# bestimmt das `model`-Feld im Request (Brain schickt es aus local_llm.json).
|
|
# Erster Load zieht das GGUF via -hf von HF (Cache unter /models, persistent).
|
|
# OpenAI-kompatibel auf :8080, nur im Compose-Netz; die Bruecke macht der
|
|
# llm-adapter. Die Modell-Liste erzeugt der llm-adapter dynamisch aus
|
|
# ./llama-swap/config.yaml (Basis) + Registry → /models/llama-swap.config.yaml.
|
|
llama-swap:
|
|
image: ghcr.io/mostlygeek/llama-swap:unified-cuda
|
|
container_name: aria-llama-swap
|
|
profiles: ["llm"] # startet nur mit COMPOSE_PROFILES=…llm…
|
|
runtime: nvidia
|
|
volumes:
|
|
- ./models:/models # HF-Cache + generierte Config
|
|
environment:
|
|
- NVIDIA_VISIBLE_DEVICES=${LLM_GPU:-0}
|
|
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
|
|
- LLAMA_CACHE=/models # llama-server legt -hf-Downloads hier ab
|
|
# Liest die vom llm-adapter generierte Config. Beim allerersten Boot faengt
|
|
# restart: unless-stopped die Reihenfolge ab, bis der Adapter sie geschrieben hat.
|
|
command: ["--config", "/models/llama-swap.config.yaml", "--listen", "0.0.0.0:8080"]
|
|
# OpenAI-kompatibler Endpunkt (/v1/chat/completions, /v1/models) — damit
|
|
# koennen Fremd-Tools wie OpenHands, Continue oder Open WebUI direkt auf das
|
|
# lokale LLM. Standardmaessig NUR auf localhost gebunden (sicher); fuer
|
|
# Zugriff aus dem LAN LLM_OPENAI_BIND=0.0.0.0 setzen — dann aber unbedingt
|
|
# LLM_OPENAI_KEY vergeben, sonst kann jeder im Netz das Modell nutzen.
|
|
ports:
|
|
- "${LLM_OPENAI_BIND:-127.0.0.1}:${LLM_OPENAI_PORT:-8081}:8080"
|
|
restart: unless-stopped
|
|
|
|
# ─── Local-LLM-Adapter — RVS <-> llama.cpp ────
|
|
# Verbindet sich per Token an den RVS (wie f5tts/whisper), nimmt llm_request
|
|
# entgegen, ruft llama.cpp lokal, antwortet llm_response. Verwaltet ausserdem
|
|
# llama-swaps Config (Modelle hinzufuegen/entfernen via llm_provision_model).
|
|
llm-adapter:
|
|
build: ./llm-adapter
|
|
container_name: aria-llm-adapter
|
|
profiles: ["llm"]
|
|
runtime: nvidia # nur fuer nvidia-smi (Auslastungs-Monitor) — kein Compute
|
|
depends_on:
|
|
- llama-swap
|
|
volumes:
|
|
- ./models:/models # generierte Config + Registry + Cache
|
|
- ./llama-swap:/llamaswap:ro # Basis-Template (config.yaml)
|
|
environment:
|
|
- NVIDIA_VISIBLE_DEVICES=all # alle Karten sichtbar (nur nvidia-smi)
|
|
- NVIDIA_DRIVER_CAPABILITIES=utility # utility = nvidia-smi, KEIN VRAM/Compute
|
|
- NODE_NAME=${NODE_NAME:-node}
|
|
- RVS_HOST=${RVS_HOST}
|
|
- RVS_PORT=${RVS_PORT:-443}
|
|
- RVS_TLS=${RVS_TLS:-true}
|
|
- RVS_TLS_FALLBACK=${RVS_TLS_FALLBACK:-true}
|
|
- RVS_TOKEN=${RVS_TOKEN}
|
|
- LLAMA_URL=http://llama-swap:8080
|
|
- LLM_MODEL=${LLM_MODEL:-qwen3-8b}
|
|
- LLAMA_BASE_CONFIG=/llamaswap/config.yaml
|
|
- LLAMA_GEN_CONFIG=/models/llama-swap.config.yaml
|
|
- LLM_REGISTRY=/models/aria_models.json
|
|
# Erster Load eines Modells kann ein GGUF ziehen (mehrere GB) — grosszuegig.
|
|
- LLM_TIMEOUT_SEC=${LLM_TIMEOUT_SEC:-600}
|
|
# Auf welche Karte llama-swap gepinnt ist — fuer die Meldung „in Karte N
|
|
# geladen" samt VRAM nach dem Laden. Dieselbe Variable wie bei llama-swap.
|
|
- LLM_GPU=${LLM_GPU:-0}
|
|
# Fuer gesperrte (gated) HF-Repos: die Dateiliste fuer den Fortschritt.
|
|
- HF_TOKEN=${HF_TOKEN:-}
|
|
restart: unless-stopped
|
|
|
|
# ─── OpenHands (optional, Standalone) ────────────────
|
|
# Eigenstaendige Entwickler-Agenten-Oberflaeche, die das LOKALE LLM ueber den
|
|
# OpenAI-Endpunkt von llama-swap nutzt. Bewusst ein eigenes Profil: wer nur
|
|
# ARIAs Box fahren will, startet es gar nicht erst.
|
|
# COMPOSE_PROFILES=llm,openhands docker compose up -d
|
|
# Danach http://<box>:${OPENHANDS_PORT:-3000} und dort als Provider
|
|
# "OpenAI-kompatibel" mit Base-URL http://llama-swap:8080/v1 eintragen
|
|
# (aus dem Container heraus erreichbar), Modell z.B. qwen3-8b.
|
|
openhands:
|
|
image: docker.all-hands.dev/all-hands-ai/openhands:latest
|
|
container_name: aria-openhands
|
|
profiles: ["openhands"]
|
|
environment:
|
|
- SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:latest
|
|
- LOG_ALL_EVENTS=true
|
|
# Vorbelegung fuer das lokale Modell — in der Oberflaeche aenderbar.
|
|
- LLM_BASE_URL=${OPENHANDS_LLM_BASE_URL:-http://llama-swap:8080/v1}
|
|
- LLM_API_KEY=${LLM_OPENAI_KEY:-local}
|
|
- LLM_MODEL=${OPENHANDS_LLM_MODEL:-openai/qwen3-8b}
|
|
volumes:
|
|
# OpenHands startet seine Arbeits-Container selbst -> braucht den Socket.
|
|
# ACHTUNG: das gibt dem Container Kontrolle ueber den Docker-Host.
|
|
- /var/run/docker.sock:/var/run/docker.sock
|
|
- ./openhands-state:/.openhands
|
|
ports:
|
|
- "${OPENHANDS_BIND:-127.0.0.1}:${OPENHANDS_PORT:-3000}:3000"
|
|
extra_hosts:
|
|
- "host.docker.internal:host-gateway"
|
|
restart: unless-stopped
|