diff --git a/README.md b/README.md index 8c589d1..93b373b 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ # Athena AI -Stand: **12. September 2026**, auf Athena geprüft. Produktiv läuft -**llama.cpp b10930** (`56381e407c0ccfb3a6f71e668a27a901001d22ce`). +Stand: **15. September 2026**, auf Athena geprüft. Produktiv läuft +**llama.cpp 0.4.1** (`b29c606`). Der [geprüfte Live-Stand](docs/LIVE_STATE.md) beschreibt Profile, GPUs und die Abweichung zwischen dem bereitgestellten Stack und dem Git-Checkout. -Der [Updatebericht](docs/LLAMA_B10930_UPDATE_20260912.md) enthält Tests und Rückfallstand. +Der [Update- und Aufräumbericht vom 15. September](docs/UPDATE_AUDIT_20260915.md) +enthält Versionsvergleich, Tests und Rückfallstand. Athena ist die lokale Inferenzmaschine. Der Docker-Stack stellt @@ -49,10 +50,10 @@ Schätzungen stehen in [Auflösungstest vom 12. September](docs/FLUX_RESOLUTION_ ## Installation des Repository-Stands -Der Live-Stack enthält zusätzliche, noch nicht vollständig in Git übernommene -Anpassungen. Ein frischer Clone bildet daher nicht automatisch den kompletten -Live-Stand einschließlich Spezialdiensten ab. Vor Wiederherstellung den -gesicherten bereitgestellten Quellstand und [LIVE_STATE.md](docs/LIVE_STATE.md) berücksichtigen. +Der produktive Quellstand ist mit diesem Repository abgeglichen. Ein frischer +Clone enthält den Athena-Kern, die Spezialprofile sowie die LTX-Erweiterungen. +Modelle, Laufzeitdaten und geheime Konfiguration bleiben außerhalb von Git und +werden über die dokumentierten Sicherungen wiederhergestellt. ```bash cp config/install.env.example /root/mike-ai-install.env @@ -150,8 +151,7 @@ Unraid-DockerMan-Templates. Details stehen in ## Wiederherstellung Der Sicherungsumfang und die Grenzen eines Neuaufbaus aus dem Repository stehen -in [docs/RECOVERY.md](docs/RECOVERY.md). Der gesicherte Live-Quellstand enthält -zusätzliche Anpassungen und muss erhalten bleiben. +in [docs/RECOVERY.md](docs/RECOVERY.md). Der vor dem Update gesicherte Live-Quellstand dient als Rückfallstand. ## Sicherheitsregeln diff --git a/compose.yaml b/compose.yaml index 273243b..5cf5ec3 100644 --- a/compose.yaml +++ b/compose.yaml @@ -530,8 +530,6 @@ services: ROUTER_STATE_FILE: /var/lib/mike-ai-profile-router/state.json ROUTER_MAX_CONCURRENT_REQUESTS: "16" UPSTREAM_URL: http://llama-upstream:8080 - PROFILE_CONTROL_URL: http://profile-controller:8090 - PROFILE_CONTROL_TOKEN: "${CONTROLLER_TOKEN:?CONTROLLER_TOKEN is required}" SWITCH_TIMEOUT: "600" REQUEST_TIMEOUT: "600" YUE2_START_TIMEOUT: "600" @@ -736,7 +734,7 @@ services: image: mike-ai/llama-dashboard:local container_name: mike-ai-llama-dashboard restart: unless-stopped - networks: [frontend] + networks: [frontend, control] gpus: all read_only: true tmpfs: @@ -752,6 +750,8 @@ services: DASHBOARD_PORT: "8099" ROUTER_URL: http://router:8081 ROUTER_API_KEY: "${ROUTER_API_KEY:?ROUTER_API_KEY is required}" + PROFILE_CONTROL_URL: http://profile-controller:8090 + PROFILE_CONTROL_TOKEN: "${CONTROLLER_TOKEN:?CONTROLLER_TOKEN is required}" MUSIC_COMMUNITY_UI_URL: "${MUSIC_COMMUNITY_UI_URL:-http://192.168.1.212:7861/}" MUSIC_ORIGINAL_UI_URL: "${MUSIC_ORIGINAL_UI_URL:-http://192.168.1.212:7862/}" SEPARATOR_UI_URL: "${SEPARATOR_UI_URL:-http://192.168.1.212:8007/}" @@ -800,7 +800,7 @@ services: security_opt: ["no-new-privileges:true"] backup: - image: ${BACKUP_IMAGE:-offen/docker-volume-backup@sha256:19102d8e59eb1d598cf8c647c2b21100abaadc5a1c808ac643fa612e323c3013} + image: ${BACKUP_IMAGE:-offen/docker-volume-backup@sha256:ca882e494b409297885a8429af2c311e627d69dc8897857159a84ef5efc1a05b} container_name: mike-ai-backup restart: unless-stopped environment: diff --git a/docs/CONTAINER_INVENTORY.md b/docs/CONTAINER_INVENTORY.md index 0628a1d..d939afb 100644 --- a/docs/CONTAINER_INVENTORY.md +++ b/docs/CONTAINER_INVENTORY.md @@ -1,6 +1,6 @@ # Container-Inventar auf Athena -Stand: 12. September 2026 +Stand: 15. September 2026 Athena besteht beim lesenden Abgleich aus 25 Docker-Containern. Nicht jeder Container enthält ein KI-Modell: Router, Oberflächen, Netzwerk, Steuerung und Sicherung sind @@ -10,7 +10,7 @@ nicht automatisch ein ungenutzter Rest. | Container | Modell oder wesentliche Komponente | Aufgabe | |---|---|---| -| `mike-ai-backup` | kein Modell; Offen Docker Volume Backup | Sichert `/data`, `/etc/mike-ai`, den Stack und die persistenten Docker-Volumes im Fünf-Stunden-Takt. | +| `mike-ai-backup` | kein Modell; Offen Docker Volume Backup `v2` (Digest vom 15. September 2026) | Sichert `/data`, `/etc/mike-ai`, den Stack und die persistenten Docker-Volumes im Fünf-Stunden-Takt. | | `mike-ai-applio-studio` | Applio/RVC; Stimmenmodelle werden nutzerseitig ergänzt | Vollständige RVC-Oberfläche für Inferenz, Modellverwaltung und Training auf der RTX 5080. Für eine Konvertierung ist ein importiertes oder trainiertes `.pth`-Modell nötig; eine Referenzaufnahme allein reicht nicht. | | `mike-ai-image-worker` | FLUX.2 Klein 9B FP8, Qwen3-8B NF4 Textencoder und VAE | Erzeugt und bearbeitet Bilder transaktional; nutzt während eines Auftrags RTX 5080 und RTX 3060. | | `mike-ai-llama-dashboard` | kein Modell | Zeigt Telemetrie, Profile, GPU-Nutzung und Betriebsarten an und bietet die Modusumschaltung. | @@ -29,14 +29,14 @@ nicht automatisch ein ungenutzter Rest. | `mike-ai-stem-separator` | BS-RoFormer Viperx 1297, `htdemucs_ft`, `htdemucs_6s`, `MossFormer2_SE_48K` | Trennt Gesang, Instrumente oder Sprache/Hintergrundgeräusche im exklusiven Separationsmodus. | | `mike-ai-tts-gateway` | kein eigenes Modell | Normalisiert Text, konvertiert Ausgabeformate und stellt Qwen3-TTS sowie natives PCM-Streaming über eine stabile interne API bereit. | | `mike-ai-voice-studio` | `k2-fsa/OmniVoice` 0.2.1 mit Whisper-ASR | Erzeugt Text-to-Speech mit einer Referenzstimme; kein Audio-to-Audio-Voice-Changer. | -| `mike-ai-whisper` | Whisper.cpp `ggml-small` | Lokale deutsche Spracherkennung auf der CPU über `/v1/audio/transcriptions`. | +| `mike-ai-whisper` | Whisper.cpp 1.9.4, `ggml-small` | Lokale deutsche Spracherkennung auf der CPU über `/v1/audio/transcriptions`. | | `mike-ai-wireguard-gateway` | kein Modell | Veröffentlicht Dashboard und Fachoberflächen ausschließlich über den privaten WireGuard-Pfad. | | `mike-ai-xvc-studio` | `chenxie95/X-VC`, GLM-4-Voice-Tokenizer und optional Resemble Enhance | Wandelt eine vorhandene Sprachaufnahme anhand einer Referenzstimme in Audio zu Audio um; gibt das native 16-kHz-Ergebnis und optional eine neural restaurierte 44,1-kHz-Fassung aus. | | `mike-ai-yue2-playground` | Image `mike-ai/yue2:3b-0.1.6` | Vorhandener, gestoppter Playground; in diesem Abgleich nicht funktional getestet. | | `mike-ai-trellis-studio` | Image `mike-ai/trellis-studio:0.6.0-q8` | Vorhandener, gestoppter 3D-Worker; in diesem Abgleich nicht funktional getestet. | | `mike-ai-mikes-applio-ui` | Image `mike-ai/mikes-applio-ui:latest` | Laufende zusätzliche Applio-Oberfläche. | -Alle fünf llama-Container verwenden b10930. Beim Abgleich lief Medium; +Alle fünf llama-Container verwenden llama.cpp 0.4.1 (`b29c606`). Beim Abgleich lief Medium; die anderen vier waren gestoppt. Der Image-Worker stand auf Created. Die übrigen GPU-Spezialworker waren gestoppt, die Infrastruktur und die Musik-/Applio-Oberflächen liefen. Diese Zustände ändern sich mit der Nutzung. diff --git a/docs/CURRENT_RUNTIME_NOTES.md b/docs/CURRENT_RUNTIME_NOTES.md index c64fc76..c7c4b39 100644 --- a/docs/CURRENT_RUNTIME_NOTES.md +++ b/docs/CURRENT_RUNTIME_NOTES.md @@ -1,19 +1,23 @@ # Aktuelle Laufzeitnotizen -Stand: 12. September 2026. +Stand: 15. September 2026. Der verbindliche Abgleich steht im [geprüften Live-Stand](LIVE_STATE.md). -Produktiv läuft **llama.cpp b10930**, zuvor b10872. Die frühere Angabe b10781 -war veraltet. Alle fünf installierten Profile wurden aktualisiert; die -Startoption wurde auf `--load-mode none` migriert. +Produktiv läuft **llama.cpp 0.4.1** auf Commit `b29c606`, zuvor b10930. +Alle fünf installierten Profile verwenden dasselbe CUDA-Image. Die Startoption +bleibt `--load-mode none`. [B10930-Updatebericht](LLAMA_B10930_UPDATE_20260912.md): Version, Image-ID, Funktionsproben, Vergleichsmessungen und gesicherter Rückfallstand. +[Update-Audit vom 15. September](UPDATE_AUDIT_20260915.md): aktualisierte +Komponenten, unveränderte aktuelle Komponenten, Aufräumarbeiten und Rollback. + FLUX verwendet **9B FP8 Beta** mit Textencoder auf der RTX 3060. [Auflösungstest](FLUX_RESOLUTION_TEST_20260912.md): 1024 erfolgreich, 1280 CUDA-OOM; produktive Begrenzung weiterhin 1024. -Sprachausgabe: Qwen3-TTS 1.7B ohne Piper. Spracherkennung: Whisper.cpp small auf CPU. +Sprachausgabe: Qwen3-TTS 1.7B ohne Piper. Spracherkennung: Whisper.cpp 1.9.4 +mit dem Modell `small` auf der CPU. Applio basiert auf dem stabilen Release 3.6.4. Datierte ältere Testberichte beschreiben ihre damaligen Versuchsbedingungen, keine automatische Freigabe für den heutigen Betrieb. Die Repository-Matrix diff --git a/docs/UPDATE_AUDIT_20260915.md b/docs/UPDATE_AUDIT_20260915.md new file mode 100644 index 0000000..ad1dc23 --- /dev/null +++ b/docs/UPDATE_AUDIT_20260915.md @@ -0,0 +1,48 @@ +# Update-Audit und Aufräumarbeiten vom 15. September 2026 + +Der Abgleich wurde zunächst ohne Zustandsänderung gegen die offiziellen Git-Repositories +und Container-Registries durchgeführt. Danach wurden ausschließlich bestätigte veraltete +Komponenten aktualisiert. Athena wurde weder neu gestartet noch heruntergefahren. + +## Aktualisiert + +| Komponente | Vorher | Nachher | +|---|---|---| +| llama.cpp | b10930 / `56381e4` | Release 0.4.1 / `b29c606` | +| whisper.cpp | 1.9.1 | 1.9.4 | +| Applio | Commit `7fa68ec` | stabiles Release 3.6.4 / `45f2dcb` | +| Offen Docker Volume Backup | Image-Digest `19102d…` | aktueller `v2`-Digest `ca882e…` | + +Das llama.cpp-Update enthält unter anderem Korrekturen für Qwen-Modelle, +Chat- und Tool-Parsing, multimodale Eingaben sowie den Serverbetrieb. + +## Bereits aktuell + +LTX Desktop 1.2.7, ACE-Step 1.5 0.1.8, Portainer CE 2.45.0 LTS, +Qwen3-TTS-Server, TRELLIS.cpp 0.6.0, X-VC und YuE2 0.1.6 waren beim +Abgleich bereits auf dem aktuellen stabilen beziehungsweise festgelegten Stand. + +## Konsolidierter Quellstand + +Die produktiven Erweiterungen für LTX, den lokalen Prompt Enhancer, das rollierende +Video-Extend sowie das Schlafen und Aufwecken der VNC-Oberfläche wurden aus dem +Live-Stack in Git übernommen. AppleDouble-Dateien (`._*`), Python-Caches, +temporäre Sicherungskopien und überholte Zweit-Checkouts gehören nicht zum +Produktionsquellstand. + +Vor der Bereinigung wurde auf Athena ein vollständiges Archiv der Konfiguration und +Arbeitskopien unter `/root/athena-pre-update-20260915-073740.tar.zst` abgelegt. +SHA-256: `c15aa831908bed36ba9de2c7f80ae4aa7b3a9bb49d0d4ec579737e506f0aa4cc`. + +## Update-Regel + +Ein Update-Audit vergleicht getrennt: + +1. den in Dockerfile oder Compose festgelegten Upstream-Stand, +2. den Git-Stand des Repositories, +3. das gebaute lokale Image und +4. das Image des tatsächlich vorhandenen Containers. + +Floating Tags allein gelten nicht als Nachweis eines Updates. Produktionsstände +werden auf Commit, Release oder Registry-Digest festgelegt und erst nach Build, +Healthcheck und Funktionsprobe dokumentiert. diff --git a/experiments/applio-rvc/Dockerfile b/experiments/applio-rvc/Dockerfile index d090116..3b0cbd0 100644 --- a/experiments/applio-rvc/Dockerfile +++ b/experiments/applio-rvc/Dockerfile @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 FROM python:3.12-trixie -ARG APPLIO_COMMIT=7fa68ec2166ab1331c539704159fa14901e94e5a +ARG APPLIO_COMMIT=45f2dcbb9c6dfaa188c5428f4d56297e1e30a63c ENV PATH=/app/.venv/bin:$PATH \ HF_HOME=/models/huggingface \ PIP_DISABLE_PIP_VERSION_CHECK=1 diff --git a/experiments/applio-rvc/compose.yaml b/experiments/applio-rvc/compose.yaml index 85efd90..48980af 100644 --- a/experiments/applio-rvc/compose.yaml +++ b/experiments/applio-rvc/compose.yaml @@ -1,7 +1,7 @@ services: applio-studio: build: . - image: mike-ai/applio-studio:7fa68ec + image: mike-ai/applio-studio:3.6.4 container_name: mike-ai-applio-studio restart: "no" # PyTorch DataLoader workers exchange training batches through /dev/shm. diff --git a/platform/docker/profile-controller/profile_controller.py b/platform/docker/profile-controller/profile_controller.py index f0f1a3b..0fa9c33 100644 --- a/platform/docker/profile-controller/profile_controller.py +++ b/platform/docker/profile-controller/profile_controller.py @@ -54,10 +54,12 @@ class UnixConnection(http.client.HTTPConnection): self.sock.connect(SOCKET_PATH) -def docker_request(method: str, path: str) -> tuple[int, bytes]: +def docker_request(method: str, path: str, payload: dict | None = None) -> tuple[int, bytes]: conn = UnixConnection("localhost", timeout=30) try: - conn.request(method, path, headers={"Content-Type": "application/json"}) + body = json.dumps(payload).encode() if payload is not None else None + conn.request(method, path, body=body, + headers={"Content-Type": "application/json"}) response = conn.getresponse() return response.status, response.read() finally: @@ -200,6 +202,69 @@ def video_container() -> dict: return matches[0] +def docker_exec(item: dict, command: str) -> str: + status, body = docker_request("POST", f"/containers/{item['Id']}/exec", { + "AttachStdout": True, "AttachStderr": True, "Tty": True, + "Cmd": ["/bin/sh", "-c", command], + }) + if status != 201: + raise RuntimeError(f"failed to create container exec: HTTP {status}") + exec_id = json.loads(body)["Id"] + status, body = docker_request("POST", f"/exec/{exec_id}/start", + {"Detach": False, "Tty": True}) + if status != 200: + raise RuntimeError(f"failed to start container exec: HTTP {status}") + return body.decode(errors="replace").strip() + + +VIDEO_UI_PROCESSES = r'''for p in /proc/[0-9]*; do + [ -r "$p/cmdline" ] || continue + exe=$(readlink "$p/exe" 2>/dev/null) || continue + c=$(tr '\000' ' ' < "$p/cmdline") + case "${exe##*/}:$c" in + ltx-desktop:*--type=*) + printf '%s\n' "${p##*/}";; + esac +done''' + +VIDEO_UI_STATE: str | None = None + + +def video_ui_state(item: dict | None = None) -> str: + item = item or video_container() + if item.get("State") != "running": + return "unavailable" + output = docker_exec(item, VIDEO_UI_PROCESSES + r''' | while read p; do + state=$(sed -n 's/^State:[[:space:]]*\([A-Z]\).*/\1/p' "/proc/$p/status") + printf '%s\n' "$state" +done''') + states = [line.strip() for line in output.splitlines() if line.strip()] + if not states: + return "unavailable" + return "sleeping" if all(state == "T" for state in states) else "awake" + + +def cached_video_ui_state(item: dict | None = None) -> str: + global VIDEO_UI_STATE + if VIDEO_UI_STATE is None: + VIDEO_UI_STATE = video_ui_state(item) + return VIDEO_UI_STATE + + +def set_video_ui(awake: bool) -> dict: + global VIDEO_UI_STATE + with LOCK: + item = video_container() + if item.get("State") != "running": + raise RuntimeError("LTX-2 is not running") + signal = "CONT" if awake else "STOP" + docker_exec(item, VIDEO_UI_PROCESSES + + f''' | while read p; do kill -{signal} "$p"; done''') + time.sleep(0.25) + VIDEO_UI_STATE = video_ui_state(item) + return {"video_ui": VIDEO_UI_STATE} + + def stop_video_if_configured() -> None: if VIDEO_WORKER: stop_container(video_container(), timeout=30) @@ -482,6 +547,7 @@ def set_trellis_worker(running: bool) -> dict: def set_video_worker(running: bool) -> dict: """Start LTX-2 exclusively, or stop it before another mode is loaded.""" + global VIDEO_UI_STATE with LOCK: item = video_container() if running: @@ -496,8 +562,10 @@ def set_video_worker(running: bool) -> dict: stop_voice_tools() stop_trellis_if_configured() start_container(item) + VIDEO_UI_STATE = "awake" else: stop_container(item, timeout=30) + VIDEO_UI_STATE = "unavailable" return {"video_worker": VIDEO_WORKER, "state": "running" if running else "stopped"} @@ -660,6 +728,7 @@ class Handler(BaseHTTPRequestHandler): "trellis_health": trellis_health, "video_worker": video.get("State", "disabled"), "video_health": video_health, + "video_ui": cached_video_ui_state(video) if video else "unavailable", "profiles": {name: items.get(name, {}).get( "State", "missing") for name in ALLOWED}}) except Exception as exc: @@ -733,6 +802,13 @@ class Handler(BaseHTTPRequestHandler): log.exception("video worker transition failed") self.reply(503, {"error": str(exc)}) return + if self.path in {"/workers/video/ui/sleep", "/workers/video/ui/wake"}: + try: + self.reply(200, set_video_ui(self.path.endswith("/wake"))) + except Exception as exc: + log.exception("video UI transition failed") + self.reply(503, {"error": str(exc)}) + return worker_paths = { "/workers/image/start": (IMAGE_WORKER, True), "/workers/image/stop": (IMAGE_WORKER, False), diff --git a/platform/docker/whisper/Dockerfile b/platform/docker/whisper/Dockerfile index 9268e8c..8dcfb30 100644 --- a/platform/docker/whisper/Dockerfile +++ b/platform/docker/whisper/Dockerfile @@ -1,6 +1,6 @@ FROM python:3.13.7-slim-bookworm -ARG WHISPER_CPP_VERSION=v1.9.1 +ARG WHISPER_CPP_VERSION=v1.9.4 RUN apt-get update \ && apt-get install -y --no-install-recommends \ diff --git a/platform/llama-dashboard/app.py b/platform/llama-dashboard/app.py index 6f8f37d..da4d4b3 100644 --- a/platform/llama-dashboard/app.py +++ b/platform/llama-dashboard/app.py @@ -20,6 +20,8 @@ HOST = os.getenv("DASHBOARD_HOST", "0.0.0.0") PORT = int(os.getenv("DASHBOARD_PORT", "8099")) ROUTER_URL = os.getenv("ROUTER_URL", "http://router:8081").rstrip("/") ROUTER_API_KEY = os.getenv("ROUTER_API_KEY", "") +PROFILE_CONTROL_URL = os.getenv("PROFILE_CONTROL_URL", "http://profile-controller:8090").rstrip("/") +PROFILE_CONTROL_TOKEN = os.getenv("PROFILE_CONTROL_TOKEN", "") MUSIC_COMMUNITY_UI_URL = os.getenv( "MUSIC_COMMUNITY_UI_URL", os.getenv("MUSIC_UI_URL", "http://192.168.1.212:7861/"), @@ -335,6 +337,25 @@ def change_mode(mode: str) -> tuple[int, dict[str, Any]]: return 503, {"error": str(exc)} +def controller_request(path: str, method: str = "GET") -> tuple[int, dict[str, Any]]: + headers = {"Accept": "application/json", "Content-Type": "application/json"} + if PROFILE_CONTROL_TOKEN: + headers["Authorization"] = f"Bearer {PROFILE_CONTROL_TOKEN}" + request = urllib.request.Request( + f"{PROFILE_CONTROL_URL}{path}", data=b"{}" if method == "POST" else None, + headers=headers, method=method) + try: + with urllib.request.urlopen(request, timeout=10) as response: + return response.status, json.load(response) + except urllib.error.HTTPError as exc: + try: + return exc.code, json.loads(exc.read()) + except (ValueError, json.JSONDecodeError): + return exc.code, {"error": str(exc)} + except (OSError, urllib.error.URLError) as exc: + return 503, {"error": str(exc)} + + _MODEL_LOCK = threading.Lock() _MODEL_AT = 0.0 _MODEL_CACHE: tuple[list[dict[str, Any]], dict[str, Any]] = ([], {"count": 0, "total_size": 0}) @@ -429,6 +450,8 @@ def collect() -> dict[str, Any]: return _COLLECT_CACHE gpus, gpu_error = gpu_status() router, router_error = router_status() + controller_code, controller = controller_request("/status") + controller_error = None if controller_code == 200 else controller.get("error", f"HTTP {controller_code}") models, model_summary = model_inventory() result = { "timestamp": time.time(), @@ -438,10 +461,11 @@ def collect() -> dict[str, Any]: "gpu_processes": gpu_processes(), "llama_runtime": llama_runtime(), "router": router, + "controller": controller if controller_code == 200 else {}, "models": models, "model_summary": model_summary, "events": EVENTS.update(router, router_error), - "errors": {"gpu": gpu_error, "router": router_error}, + "errors": {"gpu": gpu_error, "router": router_error, "controller": controller_error}, } with _COLLECT_LOCK: _COLLECT_CACHE = result @@ -674,7 +698,7 @@ HTML = r'''