# ════════════════════════════════════════════════ # 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} - ARIA_GPU=${F5TTS_GPU:-0} # nur Anzeige (Toolkit macht aus NVIDIA_VISIBLE_DEVICES "void") - 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} - ARIA_GPU=${WHISPER_GPU:-1} # nur Anzeige (Toolkit macht aus NVIDIA_VISIBLE_DEVICES "void") - 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 - ARIA_GPU=${VOXTRAL_GPU:-1} # nur Anzeige (Toolkit macht aus NVIDIA_VISIBLE_DEVICES "void") - 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: # Alle Karten sichtbar — welches Modell auf welcher Karte laeuft, legt der # llm-adapter pro Modell fest (CUDA_VISIBLE_DEVICES= in der # generierten Config). Erlaubt sind nur die LLM-Karten aus LLM_GPUS. - NVIDIA_VISIBLE_DEVICES=all - NVIDIA_DRIVER_CAPABILITIES=compute,utility # CUDA zaehlt Karten sonst "schnellste zuerst" — dann waere CUDA-Karte 0 # nicht unbedingt nvidia-smi-Karte 0. PCI-Reihenfolge = wie nvidia-smi. - CUDA_DEVICE_ORDER=PCI_BUS_ID # Echten Treiber vom Host zuerst laden. Das Image traegt in seiner # ld.so.conf die eigene CUDA-Kompatibilitaets-libcuda (fuer Treiber 575) # VOR dem eingehaengten Host-Treiber ein. Die hilft nur Rechenzentrums- # karten; bei GeForce scheitert llama.cpp damit ("forward compatibility …" # bei aelterem, "unsupported display driver / cuda driver combination" bei # neuerem Treiber) und rechnet still auf der CPU. Auf der Box getestet: # so sieht llama.cpp beide Karten. - LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu:/usr/local/cuda/lib64 - LLAMA_CACHE=/models # llama-server legt -hf-Downloads hier ab # Liest die vom llm-adapter generierte Config. --watch-config: Aenderungen # (Modell in Karte geladen/entladen) greifen von selbst — einen Reload- # Endpunkt hat llama-swap nicht. Beim allerersten Boot faengt restart: # unless-stopped die Reihenfolge ab, bis der Adapter die Config geschrieben hat. command: ["--config", "/models/llama-swap.config.yaml", "--listen", "0.0.0.0:8080", "--watch-config"] # 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} # Welche Karten dem LLM gehoeren. Jede haelt genau ein Modell, geladen # wird nur ueber die Diagnostic. Mehrere: LLM_GPUS=1,2 — eine: LLM_GPU=1 # (alte Schreibweise, gilt weiter, wenn LLM_GPUS leer ist). - LLM_GPUS=${LLM_GPUS:-} - 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://:${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