Remove Beta 1 and Piper fallback

This commit is contained in:
Mikei386 committed 2026-09-10 13:17:57 +02:00
1 parent 52482627be
commit 08ff3d7c4e
38 files changed
+94 -484

No files matched your search

-8
View File
@@ -6,8 +6,6 @@ CONTROLLER_TOKEN=GENERATED_BY_INSTALLER
FLUX_COMPONENT_DIR=/data/models/FLUX.2-klein-9B-components FLUX_COMPONENT_DIR=/data/models/FLUX.2-klein-9B-components
FLUX_TRANSFORMER_DIR=/data/models/FLUX.2-klein-9B-fp8 FLUX_TRANSFORMER_DIR=/data/models/FLUX.2-klein-9B-fp8
IMAGE_GPU_DEVICES=GPU-8ad38c6c-5a01-9d8e-1dfa-ed662ad78fbe IMAGE_GPU_DEVICES=GPU-8ad38c6c-5a01-9d8e-1dfa-ed662ad78fbe
PIPER_TTS_VERSION=1.6.0
PIPER_VOICE=de_DE-thorsten-high
QWEN3_TTS_IMAGE=ghcr.io/malaiwah/qwen3-tts-server:latest@sha256:b363a01d08b1bbecbfc3ca6f585368fae2cfdc591f9ecca6643738369f9a9d98 QWEN3_TTS_IMAGE=ghcr.io/malaiwah/qwen3-tts-server:latest@sha256:b363a01d08b1bbecbfc3ca6f585368fae2cfdc591f9ecca6643738369f9a9d98
QWEN3_TTS_CACHE_DIR=/data/models/qwen3-tts-cache QWEN3_TTS_CACHE_DIR=/data/models/qwen3-tts-cache
QWEN3_TTS_VOICES_DIR=/data/models/qwen3-tts-voices QWEN3_TTS_VOICES_DIR=/data/models/qwen3-tts-voices
@@ -17,7 +15,6 @@ DEFAULT_REASONING_EFFORT=off
FAST_MODEL_FILE=qwen-mix/Qwen3.8-27B-IQ4-MIX.gguf FAST_MODEL_FILE=qwen-mix/Qwen3.8-27B-IQ4-MIX.gguf
MEDIUM_MODEL_FILE=qwen-pure/qwen3.8-27b-IQ4_XS-pure.gguf MEDIUM_MODEL_FILE=qwen-pure/qwen3.8-27b-IQ4_XS-pure.gguf
BETA1_MODEL_FILE=qwen3.8-27b-gsq-rco-iq3s/Qwen3.8-27B-GSQ-RCO-IQ3_S-mtp.gguf
LARGE_MODEL_FILE=qwen-pure/qwen3.8-27b-IQ4_XS-pure.gguf LARGE_MODEL_FILE=qwen-pure/qwen3.8-27b-IQ4_XS-pure.gguf
ULTRA_MODEL_FILE=qwen-pure/qwen3.8-27b-IQ4_XS-pure.gguf ULTRA_MODEL_FILE=qwen-pure/qwen3.8-27b-IQ4_XS-pure.gguf
UNCENSORED_MODEL_FILE=qwen3.8-27b-abliterated/Qwen3.8-27B-ABLITERATED-Q4_K_M.gguf UNCENSORED_MODEL_FILE=qwen3.8-27b-abliterated/Qwen3.8-27B-ABLITERATED-Q4_K_M.gguf
@@ -30,9 +27,6 @@ FAST_UBATCH_SIZE=32
MEDIUM_CONTEXT=160000 MEDIUM_CONTEXT=160000
MEDIUM_BATCH_SIZE=2048 MEDIUM_BATCH_SIZE=2048
MEDIUM_UBATCH_SIZE=128 MEDIUM_UBATCH_SIZE=128
BETA1_CONTEXT=112000
BETA1_BATCH_SIZE=2048
BETA1_UBATCH_SIZE=128
LARGE_CONTEXT=192000 LARGE_CONTEXT=192000
LARGE_BATCH_SIZE=2048 LARGE_BATCH_SIZE=2048
LARGE_UBATCH_SIZE=128 LARGE_UBATCH_SIZE=128
@@ -45,7 +39,6 @@ UNCENSORED_UBATCH_SIZE=128
FAST_GPU_DEVICES=GPU-8ad38c6c-5a01-9d8e-1dfa-ed662ad78fbe,GPU-4834d9d7-5b61-3004-1fb3-4ae49d482d4b FAST_GPU_DEVICES=GPU-8ad38c6c-5a01-9d8e-1dfa-ed662ad78fbe,GPU-4834d9d7-5b61-3004-1fb3-4ae49d482d4b
MEDIUM_GPU_DEVICES=GPU-8ad38c6c-5a01-9d8e-1dfa-ed662ad78fbe,GPU-4834d9d7-5b61-3004-1fb3-4ae49d482d4b MEDIUM_GPU_DEVICES=GPU-8ad38c6c-5a01-9d8e-1dfa-ed662ad78fbe,GPU-4834d9d7-5b61-3004-1fb3-4ae49d482d4b
MEDIUM_TENSOR_SPLIT=85,15 MEDIUM_TENSOR_SPLIT=85,15
BETA1_GPU_DEVICES=GPU-8ad38c6c-5a01-9d8e-1dfa-ed662ad78fbe,GPU-4834d9d7-5b61-3004-1fb3-4ae49d482d4b
LARGE_GPU_DEVICES=GPU-8ad38c6c-5a01-9d8e-1dfa-ed662ad78fbe,GPU-4834d9d7-5b61-3004-1fb3-4ae49d482d4b LARGE_GPU_DEVICES=GPU-8ad38c6c-5a01-9d8e-1dfa-ed662ad78fbe,GPU-4834d9d7-5b61-3004-1fb3-4ae49d482d4b
LARGE_TENSOR_SPLIT=86,14 LARGE_TENSOR_SPLIT=86,14
ULTRA_GPU_DEVICES=GPU-8ad38c6c-5a01-9d8e-1dfa-ed662ad78fbe,GPU-4834d9d7-5b61-3004-1fb3-4ae49d482d4b ULTRA_GPU_DEVICES=GPU-8ad38c6c-5a01-9d8e-1dfa-ed662ad78fbe,GPU-4834d9d7-5b61-3004-1fb3-4ae49d482d4b
@@ -58,7 +51,6 @@ LLAMA_THREADS_BATCH=6
FAST_PARALLEL_SLOTS=1 FAST_PARALLEL_SLOTS=1
LLAMA_CACHE_RAM_MIB=32768 LLAMA_CACHE_RAM_MIB=32768
MEDIUM_PARALLEL_SLOTS=1 MEDIUM_PARALLEL_SLOTS=1
BETA1_PARALLEL_SLOTS=1
LARGE_PARALLEL_SLOTS=1 LARGE_PARALLEL_SLOTS=1
ULTRA_PARALLEL_SLOTS=1 ULTRA_PARALLEL_SLOTS=1
UNCENSORED_PARALLEL_SLOTS=1 UNCENSORED_PARALLEL_SLOTS=1
+2 -2
View File
@@ -11,7 +11,7 @@ Sie betreibt:
- llama.cpp mit genau einem aktiven Qwen-Profil, - llama.cpp mit genau einem aktiven Qwen-Profil,
- den OpenAI-kompatiblen Profile Router, - den OpenAI-kompatiblen Profile Router,
- FLUX.2 Klein 9B FP8 Beta für Textbilder und Referenzbild-Bearbeitung, - FLUX.2 Klein 9B FP8 Beta für Textbilder und Referenzbild-Bearbeitung,
- Qwen3-TTS und Piper für Sprache, - Qwen3-TTS für Sprache,
- ACE-Step 1.5 XL-SFT als exklusiven Musikstudio-Modus, - ACE-Step 1.5 XL-SFT als exklusiven Musikstudio-Modus,
- das Athena-Dashboard, - das Athena-Dashboard,
- Portainer CE als optionale Ansicht auf die laufenden Docker-Container, - Portainer CE als optionale Ansicht auf die laufenden Docker-Container,
@@ -66,7 +66,7 @@ Qwen-Profil wird vom Profile Controller verwaltet.
llama.cpp-Profil und Qwen3-TTS werden dafür gestoppt und danach automatisch llama.cpp-Profil und Qwen3-TTS werden dafür gestoppt und danach automatisch
wiederhergestellt. Die Beta arbeitet mit 1024 × 1024 Pixeln, vier Schritten wiederhergestellt. Die Beta arbeitet mit 1024 × 1024 Pixeln, vier Schritten
und Guidance 1,0. und Guidance 1,0.
- Qwen3-TTS 1.7B: RTX 3060; Piper bleibt CPU-Fallback. Der Router reicht - Qwen3-TTS 1.7B: RTX 3060. Der Router reicht
zusätzlich natives 24-kHz-PCM für den optionalen Hermes-Streaming-Adapter zusätzlich natives 24-kHz-PCM für den optionalen Hermes-Streaming-Adapter
unter `integrations/hermes-qwen3-stream` durch. unter `integrations/hermes-qwen3-stream` durch.
- ACE-Step 1.5 XL-SFT: exklusiver Musikmodus auf der RTX 5080. Dashboard und - ACE-Step 1.5 XL-SFT: exklusiver Musikmodus auf der RTX 5080. Dashboard und
+3 -3
View File
@@ -12,7 +12,7 @@ Bild- und Sprachausgabe. **Hermes und die Fach-MCPs laufen auf Unraid.**
- Profile Router auf Port 8081 - Profile Router auf Port 8081
- FLUX.2 Klein 9B FP8 Beta für Textbilder und Referenzbild-Bearbeitung: - FLUX.2 Klein 9B FP8 Beta für Textbilder und Referenzbild-Bearbeitung:
Transformer auf RTX 5080, Qwen3-8B-NF4-Textencoder auf RTX 3060 Transformer auf RTX 5080, Qwen3-8B-NF4-Textencoder auf RTX 3060
- Qwen3-TTS 1.7B auf der RTX 3060 mit Piper als CPU-Fallback - Qwen3-TTS 1.7B auf der RTX 3060 hinter einem Normalisierungs- und Streaming-Gateway
- Whisper.cpp `ggml-small` auf der CPU für lokale deutsche Spracherkennung - Whisper.cpp `ggml-small` auf der CPU für lokale deutsche Spracherkennung
- Live-Dashboard mit 21 Tagen Detailhistorie auf Port 8099 - Live-Dashboard mit 21 Tagen Detailhistorie auf Port 8099
- Dashboard-Umschaltung zwischen LLM-Betrieb, ACE-Step-Musikstudio, - Dashboard-Umschaltung zwischen LLM-Betrieb, ACE-Step-Musikstudio,
@@ -110,8 +110,8 @@ zuerst das aktive llama.cpp-Profil und Qwen3-TTS. Anschließend läuft der
FP8-Transformer auf der RTX 5080 und der in NF4 geladene Qwen3-8B-Textencoder FP8-Transformer auf der RTX 5080 und der in NF4 geladene Qwen3-8B-Textencoder
auf der RTX 3060. Vor dem VAE-Decoding werden Transformer und Textencoder auf der RTX 3060. Vor dem VAE-Decoding werden Transformer und Textencoder
freigegeben. Nach dem Bildauftrag stoppt der Router den Bild-Worker und stellt freigegeben. Nach dem Bildauftrag stoppt der Router den Bild-Worker und stellt
Qwen3-TTS sowie das zuvor aktive Textprofil automatisch wieder her. Piper Qwen3-TTS sowie das zuvor aktive Textprofil automatisch wieder her. Während
bleibt währenddessen als CPU-Fallback verfügbar. der exklusiven Nutzung der RTX 3060 ist TTS vorübergehend nicht verfügbar.
Die Beta ist derzeit bewusst auf `1024x1024`, vier Schritte, Guidance `1.0`, Die Beta ist derzeit bewusst auf `1024x1024`, vier Schritte, Guidance `1.0`,
einen parallelen Auftrag und maximal vier lokale Referenzbilder begrenzt. einen parallelen Auftrag und maximal vier lokale Referenzbilder begrenzt.
+4 -128
View File
@@ -230,89 +230,6 @@ services:
- --spec-draft-p-min - --spec-draft-p-min
- "0.05" - "0.05"
# Beta 1 keeps the complete GSQ-RCO IQ3_S text model and KV cache on the RTX
# 5080. Only the multimodal projector runs on the RTX 3060. The larger IQ3_S
# build starts conservatively at 112K until its hard context boundary has
# been measured; the former IQ3_XXS result does not transfer to this file.
llama-beta1:
<<: *llama-common
container_name: mike-ai-llama-beta1
labels:
com.mike-ai.llama-profile: beta1
environment:
NVIDIA_VISIBLE_DEVICES: ${BETA1_GPU_DEVICES:-0,1}
NVIDIA_DRIVER_CAPABILITIES: compute,utility
MTMD_BACKEND_DEVICE: CUDA1
command:
- --model
- "/models/${BETA1_MODEL_FILE:?BETA1_MODEL_FILE is required}"
- --mmproj
- "/models/${VISION_PROJECTOR_FILE:?VISION_PROJECTOR_FILE is required}"
- --mmproj-offload
- --mmproj-device
- CUDA1
- --alias
- qwen-beta-1
- --ctx-size
- "${BETA1_CONTEXT:-112000}"
- --flash-attn
- "on"
- --cache-type-k
- q4_0
- --cache-type-v
- q4_0
- --cache-prompt
- --cache-ram
- "${LLAMA_CACHE_RAM_MIB:-32768}"
- --threads
- "${LLAMA_THREADS:-6}"
- --threads-batch
- "${LLAMA_THREADS_BATCH:-6}"
- --batch-size
- "${BETA1_BATCH_SIZE:-2048}"
- --ubatch-size
- "${BETA1_UBATCH_SIZE:-128}"
- --parallel
- "${BETA1_PARALLEL_SLOTS:-1}"
- --kv-unified
- --jinja
- --reasoning
- auto
- --reasoning-preserve
- --host
- 0.0.0.0
- --port
- "8080"
- --metrics
- --fit
- "off"
- --n-gpu-layers
- all
- --no-mmap
- --no-ui
- --temperature
- "1.0"
- --top-p
- "0.95"
- --top-k
- "20"
- --device
- CUDA0
- --main-gpu
- "0"
- --split-mode
- none
- --spec-type
- draft-mtp
- --spec-draft-n-max
- "3"
- --spec-draft-type-k
- f16
- --spec-draft-type-v
- f16
- --spec-draft-p-min
- "0.05"
llama-large: llama-large:
<<: *llama-common <<: *llama-common
container_name: mike-ai-llama-large container_name: mike-ai-llama-large
@@ -565,7 +482,7 @@ services:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
environment: environment:
CONTROLLER_TOKEN: "${CONTROLLER_TOKEN:?CONTROLLER_TOKEN is required}" CONTROLLER_TOKEN: "${CONTROLLER_TOKEN:?CONTROLLER_TOKEN is required}"
ALLOWED_PROFILES: fast,medium,beta1,large,ultra,uncensored ALLOWED_PROFILES: fast,medium,large,ultra,uncensored
IMAGE_WORKER: image IMAGE_WORKER: image
RESTORE_WORKER: restore RESTORE_WORKER: restore
TTS_WORKER: qwen3 TTS_WORKER: qwen3
@@ -622,11 +539,10 @@ services:
CHAT_IMAGE_ALLOW_REMOTE_URLS: "false" CHAT_IMAGE_ALLOW_REMOTE_URLS: "false"
ENABLE_IMAGE_GENERATION: "true" ENABLE_IMAGE_GENERATION: "true"
ENABLE_TTS: "true" ENABLE_TTS: "true"
# Stable OpenAI compatibility names remain piper/alloy for existing # The gateway keeps text normalization, output conversion and native
# clients. The gateway maps them to Qwen3-TTS and falls back to Piper # PCM streaming in one stable API in front of Qwen3-TTS.
# while Qwen3-TTS is unavailable, busy or paused for image generation.
TTS_WORKER_URL: http://tts-gateway:8085 TTS_WORKER_URL: http://tts-gateway:8085
TTS_MODEL: piper TTS_MODEL: qwen3-tts
TTS_VOICES: alloy TTS_VOICES: alloy
TTS_DEFAULT_VOICE: alloy TTS_DEFAULT_VOICE: alloy
ENABLE_STT: "true" ENABLE_STT: "true"
@@ -655,8 +571,6 @@ services:
condition: service_healthy condition: service_healthy
profile-controller: profile-controller:
condition: service_healthy condition: service_healthy
piper:
condition: service_healthy
tts-gateway: tts-gateway:
condition: service_healthy condition: service_healthy
whisper: whisper:
@@ -699,37 +613,6 @@ services:
timeout: 3s timeout: 3s
retries: 12 retries: 12
piper:
build:
context: platform/docker/piper
args:
PIPER_TTS_VERSION: ${PIPER_TTS_VERSION:-1.6.0}
image: mike-ai/piper:local
container_name: mike-ai-piper
restart: unless-stopped
read_only: true
tmpfs:
- /tmp:size=256m,mode=1777
volumes:
- piper-data:/data
environment:
PIPER_DATA_DIR: /data
PIPER_VOICE: ${PIPER_VOICE:-de_DE-thorsten-high}
PIPER_VOICE_ALIAS: alloy
PIPER_HOST: 0.0.0.0
PIPER_PORT: "8085"
PIPER_MAX_TEXT_CHARS: "8000"
networks: [frontend]
security_opt: ["no-new-privileges:true"]
cap_drop: [ALL]
cap_add: [CHOWN, SETUID, SETGID]
healthcheck:
test: [CMD, curl, -fsS, "http://127.0.0.1:8085/status"]
interval: 10s
timeout: 5s
retries: 30
start_period: 120s
qwen3-tts: qwen3-tts:
image: ${QWEN3_TTS_IMAGE:-ghcr.io/malaiwah/qwen3-tts-server:latest@sha256:b363a01d08b1bbecbfc3ca6f585368fae2cfdc591f9ecca6643738369f9a9d98} image: ${QWEN3_TTS_IMAGE:-ghcr.io/malaiwah/qwen3-tts-server:latest@sha256:b363a01d08b1bbecbfc3ca6f585368fae2cfdc591f9ecca6643738369f9a9d98}
container_name: mike-ai-qwen3-tts container_name: mike-ai-qwen3-tts
@@ -787,17 +670,12 @@ services:
QWEN_TTS_VOICE: serena QWEN_TTS_VOICE: serena
QWEN_TTS_LANGUAGE: German QWEN_TTS_LANGUAGE: German
QWEN_TTS_TIMEOUT: "120" QWEN_TTS_TIMEOUT: "120"
PIPER_URL: http://piper:8085
TTS_VOICE_ALIAS: alloy TTS_VOICE_ALIAS: alloy
TTS_DEFAULT_LANGUAGE: de TTS_DEFAULT_LANGUAGE: de
# Mixed-language clip stitching caused long pauses and unintelligible # Mixed-language clip stitching caused long pauses and unintelligible
# transitions. Keep full sentences in one stable German voice. # transitions. Keep full sentences in one stable German voice.
TTS_CODE_SWITCH_ENABLED: "false" TTS_CODE_SWITCH_ENABLED: "false"
PIPER_TIMEOUT: "120"
networks: [frontend] networks: [frontend]
depends_on:
piper:
condition: service_healthy
security_opt: ["no-new-privileges:true"] security_opt: ["no-new-privileges:true"]
cap_drop: [ALL] cap_drop: [ALL]
healthcheck: healthcheck:
@@ -920,7 +798,6 @@ services:
- /data/docker-backups:/archive - /data/docker-backups:/archive
- /etc/mike-ai:/backup/etc-mike-ai:ro - /etc/mike-ai:/backup/etc-mike-ai:ro
- /opt/mike-ai/stack:/backup/stack:ro - /opt/mike-ai/stack:/backup/stack:ro
- piper-data:/backup/volumes/piper-data:ro
- router-state:/backup/volumes/router-state:ro - router-state:/backup/volumes/router-state:ro
- router-images:/backup/volumes/router-images:ro - router-images:/backup/volumes/router-images:ro
- portainer-data:/backup/volumes/portainer-data:ro - portainer-data:/backup/volumes/portainer-data:ro
@@ -947,7 +824,6 @@ networks:
name: mike-ai-tools-egress name: mike-ai-tools-egress
volumes: volumes:
piper-data:
whisper-data: whisper-data:
router-state: router-state:
router-images: router-images:
-10
View File
@@ -60,9 +60,6 @@ FAST_MODEL_SHA256=54879ae8738d5938f46cb3b8cbf16bf42b8c85b7d68d7c73f062b612ec183e
MEDIUM_MODEL_FILE=qwen-pure/qwen3.8-27b-IQ4_XS-pure.gguf MEDIUM_MODEL_FILE=qwen-pure/qwen3.8-27b-IQ4_XS-pure.gguf
MEDIUM_MODEL_URL=https://huggingface.co/jpetrina/Qwen3.8-27B-IQ4_XS-pure-GGUF/resolve/main/qwen3.8-27b-IQ4_XS-pure.gguf MEDIUM_MODEL_URL=https://huggingface.co/jpetrina/Qwen3.8-27B-IQ4_XS-pure-GGUF/resolve/main/qwen3.8-27b-IQ4_XS-pure.gguf
MEDIUM_MODEL_SHA256=ea5a3c45d407f9b9e5d2c0d647f0ea600f486f6b86b92b56d0823ba073dae675 MEDIUM_MODEL_SHA256=ea5a3c45d407f9b9e5d2c0d647f0ea600f486f6b86b92b56d0823ba073dae675
BETA1_MODEL_FILE=qwen3.8-27b-gsq-rco-iq3s/Qwen3.8-27B-GSQ-RCO-IQ3_S-mtp.gguf
BETA1_MODEL_URL=https://huggingface.co/ISTA-DASLab/Qwen3.8-27B-GSQ-RCO-GGUF/resolve/main/Qwen3.8-27B-GSQ-RCO-IQ3_S-mtp.gguf
BETA1_MODEL_SHA256=58fd826723939933dc86f45b7fe04545cbc2de1c70f6fe2cdd3858c87a98c12f
LARGE_MODEL_FILE=qwen-pure/qwen3.8-27b-IQ4_XS-pure.gguf LARGE_MODEL_FILE=qwen-pure/qwen3.8-27b-IQ4_XS-pure.gguf
LARGE_MODEL_URL=https://huggingface.co/jpetrina/Qwen3.8-27B-IQ4_XS-pure-GGUF/resolve/main/qwen3.8-27b-IQ4_XS-pure.gguf LARGE_MODEL_URL=https://huggingface.co/jpetrina/Qwen3.8-27B-IQ4_XS-pure-GGUF/resolve/main/qwen3.8-27b-IQ4_XS-pure.gguf
LARGE_MODEL_SHA256=ea5a3c45d407f9b9e5d2c0d647f0ea600f486f6b86b92b56d0823ba073dae675 LARGE_MODEL_SHA256=ea5a3c45d407f9b9e5d2c0d647f0ea600f486f6b86b92b56d0823ba073dae675
@@ -89,11 +86,6 @@ MEDIUM_CONTEXT=160000
MEDIUM_BATCH_SIZE=2048 MEDIUM_BATCH_SIZE=2048
MEDIUM_UBATCH_SIZE=128 MEDIUM_UBATCH_SIZE=128
MEDIUM_TENSOR_SPLIT=85,15 MEDIUM_TENSOR_SPLIT=85,15
BETA1_CONTEXT=112000
BETA1_BATCH_SIZE=2048
BETA1_UBATCH_SIZE=128
BETA1_GPU_DEVICES=GPU-8ad38c6c-5a01-9d8e-1dfa-ed662ad78fbe,GPU-4834d9d7-5b61-3004-1fb3-4ae49d482d4b
BETA1_PARALLEL_SLOTS=1
LARGE_CONTEXT=192000 LARGE_CONTEXT=192000
LARGE_BATCH_SIZE=2048 LARGE_BATCH_SIZE=2048
LARGE_UBATCH_SIZE=128 LARGE_UBATCH_SIZE=128
@@ -115,8 +107,6 @@ MEDIUM_PARALLEL_SLOTS=1
LARGE_PARALLEL_SLOTS=1 LARGE_PARALLEL_SLOTS=1
ULTRA_PARALLEL_SLOTS=1 ULTRA_PARALLEL_SLOTS=1
UNCENSORED_PARALLEL_SLOTS=1 UNCENSORED_PARALLEL_SLOTS=1
PIPER_TTS_VERSION=1.6.0
PIPER_VOICE=de_DE-thorsten-high
QWEN3_TTS_IMAGE=ghcr.io/malaiwah/qwen3-tts-server:latest@sha256:b363a01d08b1bbecbfc3ca6f585368fae2cfdc591f9ecca6643738369f9a9d98 QWEN3_TTS_IMAGE=ghcr.io/malaiwah/qwen3-tts-server:latest@sha256:b363a01d08b1bbecbfc3ca6f585368fae2cfdc591f9ecca6643738369f9a9d98
QWEN3_TTS_CACHE_DIR=/data/models/qwen3-tts-cache QWEN3_TTS_CACHE_DIR=/data/models/qwen3-tts-cache
QWEN3_TTS_VOICES_DIR=/data/models/qwen3-tts-voices QWEN3_TTS_VOICES_DIR=/data/models/qwen3-tts-voices
-12
View File
@@ -27,18 +27,6 @@
"mtp": 3, "mtp": 3,
"description": "Ausgewogenes Standardprofil für Alltag und lange agentische Aufgaben." "description": "Ausgewogenes Standardprofil für Alltag und lange agentische Aufgaben."
}, },
{
"id": "beta1",
"alias": "qwen-beta-1",
"context": 112000,
"parallel_slots": 1,
"model_env": "BETA1_MODEL_FILE",
"model_family": "Qwen3.8-27B GSQ-RCO IQ3_S MTP",
"gpu_split": "5080 model / 3060 vision",
"vision": true,
"mtp": 3,
"description": "Beta 1: qualitaetsoptimiertes GSQ-RCO-IQ3_S-Testprofil; 112K Startkontext bis zur erneuten Grenzmessung."
},
{ {
"id": "large", "id": "large",
"alias": "qwen-large", "alias": "qwen-large",
+2 -2
View File
@@ -71,8 +71,8 @@ class Handler(BaseHTTPRequestHandler):
self._send_json(200, { self._send_json(200, {
"status": "ok", "status": "ok",
"ready": True, "ready": True,
"voices": ["claribel"], "voices": ["alloy"],
"default_voice": "claribel", "default_voice": "alloy",
"load_errors": [], "load_errors": [],
"sample_rate": SAMPLE_RATE, "sample_rate": SAMPLE_RATE,
"uptime_seconds": 1.0, "uptime_seconds": 1.0,
+13 -13
View File
@@ -561,14 +561,14 @@ d=json.load(sys.stdin)
tts=d["tts"] tts=d["tts"]
assert tts["reachable"] is True, tts assert tts["reachable"] is True, tts
assert tts["ready"] is True, tts assert tts["ready"] is True, tts
assert set(tts["voices"])=={"claribel"}, tts assert set(tts["voices"])=={"alloy"}, tts
' && ok "Status: TTS erreichbar, bereit, 2 Stimmen" || bad "Status tts-Section" ' && ok "Status: TTS erreichbar und bereit" || bad "Status tts-Section"
# --- 28. TTS: POST /v1/audio/speech (wav) --------------------------------------------------------------- # --- 28. TTS: POST /v1/audio/speech (wav) ---------------------------------------------------------------
echo "== Test 28: POST /v1/audio/speech (wav)" echo "== Test 28: POST /v1/audio/speech (wav)"
CODE=$(curl -s -o /tmp/tts28.wav -w "%{http_code}" -D /tmp/hdr28.txt \ CODE=$(curl -s -o /tmp/tts28.wav -w "%{http_code}" -D /tmp/hdr28.txt \
"$BASE/v1/audio/speech" -H "Content-Type: application/json" \ "$BASE/v1/audio/speech" -H "Content-Type: application/json" \
-d '{"model":"xtts-v2","input":"Hallo Welt","voice":"claribel","response_format":"wav"}') -d '{"model":"qwen3-tts","input":"Hallo Welt","voice":"alloy","response_format":"wav"}')
CTYPE=$(grep -i content-type /tmp/hdr28.txt | tr -d "\r") CTYPE=$(grep -i content-type /tmp/hdr28.txt | tr -d "\r")
[ "$CODE" = "200" ] && [ -s /tmp/tts28.wav ] && echo "$CTYPE" | grep -qi "audio/wav" \ [ "$CODE" = "200" ] && [ -s /tmp/tts28.wav ] && echo "$CTYPE" | grep -qi "audio/wav" \
&& ok "TTS wav (200, $CTYPE, $(stat -f%z /tmp/tts28.wav 2>/dev/null || stat -c%s /tmp/tts28.wav) Bytes)" \ && ok "TTS wav (200, $CTYPE, $(stat -f%z /tmp/tts28.wav 2>/dev/null || stat -c%s /tmp/tts28.wav) Bytes)" \
@@ -578,7 +578,7 @@ CTYPE=$(grep -i content-type /tmp/hdr28.txt | tr -d "\r")
echo "== Test 29: POST /v1/audio/speech (mp3, Default)" echo "== Test 29: POST /v1/audio/speech (mp3, Default)"
CODE=$(curl -s -o /tmp/tts29.mp3 -w "%{http_code}" -D /tmp/hdr29.txt \ CODE=$(curl -s -o /tmp/tts29.mp3 -w "%{http_code}" -D /tmp/hdr29.txt \
"$BASE/v1/audio/speech" -H "Content-Type: application/json" \ "$BASE/v1/audio/speech" -H "Content-Type: application/json" \
-d '{"input":"Guten Tag","voice":"claribel"}') -d '{"input":"Guten Tag","voice":"alloy"}')
CTYPE=$(grep -i content-type /tmp/hdr29.txt | tr -d "\r") CTYPE=$(grep -i content-type /tmp/hdr29.txt | tr -d "\r")
[ "$CODE" = "200" ] && [ -s /tmp/tts29.mp3 ] && echo "$CTYPE" | grep -qi "audio/mpeg" \ [ "$CODE" = "200" ] && [ -s /tmp/tts29.mp3 ] && echo "$CTYPE" | grep -qi "audio/mpeg" \
&& ok "TTS mp3 (200, $CTYPE)" || bad "TTS mp3 (Code $CODE, $CTYPE)" && ok "TTS mp3 (200, $CTYPE)" || bad "TTS mp3 (Code $CODE, $CTYPE)"
@@ -586,7 +586,7 @@ CTYPE=$(grep -i content-type /tmp/hdr29.txt | tr -d "\r")
# --- 30. TTS: Validierung -------------------------------------------------------------------------------- # --- 30. TTS: Validierung --------------------------------------------------------------------------------
echo "== Test 30: TTS-Validierung" echo "== Test 30: TTS-Validierung"
CODE=$(curl -s -o /tmp/err30a.json -w "%{http_code}" "$BASE/v1/audio/speech" \ CODE=$(curl -s -o /tmp/err30a.json -w "%{http_code}" "$BASE/v1/audio/speech" \
-H "Content-Type: application/json" -d '{"voice":"claribel"}') -H "Content-Type: application/json" -d '{"voice":"alloy"}')
cat /tmp/err30a.json; echo cat /tmp/err30a.json; echo
[ "$CODE" = "400" ] && ok "400 bei fehlendem input" || bad "erwartet 400, bekam $CODE" [ "$CODE" = "400" ] && ok "400 bei fehlendem input" || bad "erwartet 400, bekam $CODE"
@@ -608,7 +608,7 @@ cat /tmp/err30d.json; echo
# --- 31. TTS: Worker-Fehler → 503 ------------------------------------------------------------------------ # --- 31. TTS: Worker-Fehler → 503 ------------------------------------------------------------------------
echo "== Test 31: TTS-Worker-Fehler → 503" echo "== Test 31: TTS-Worker-Fehler → 503"
CODE=$(curl -s -o /tmp/err31.json -w "%{http_code}" "$BASE/v1/audio/speech" \ CODE=$(curl -s -o /tmp/err31.json -w "%{http_code}" "$BASE/v1/audio/speech" \
-H "Content-Type: application/json" -d '{"input":"FAIL","voice":"claribel"}') -H "Content-Type: application/json" -d '{"input":"FAIL","voice":"alloy"}')
cat /tmp/err31.json; echo cat /tmp/err31.json; echo
[ "$CODE" = "503" ] && ok "503 bei TTS-Worker-Fehler" || bad "erwartet 503, bekam $CODE" [ "$CODE" = "503" ] && ok "503 bei TTS-Worker-Fehler" || bad "erwartet 503, bekam $CODE"
@@ -617,7 +617,7 @@ echo "== Test 32: TTS-Worker down → 503"
kill "$TTS_PID" 2>/dev/null || true kill "$TTS_PID" 2>/dev/null || true
sleep 0.5 sleep 0.5
CODE=$(curl -s -o /tmp/err32.json -w "%{http_code}" "$BASE/v1/audio/speech" \ CODE=$(curl -s -o /tmp/err32.json -w "%{http_code}" "$BASE/v1/audio/speech" \
-H "Content-Type: application/json" -d '{"input":"Hallo","voice":"claribel"}') -H "Content-Type: application/json" -d '{"input":"Hallo","voice":"alloy"}')
cat /tmp/err32.json; echo cat /tmp/err32.json; echo
[ "$CODE" = "503" ] && ok "503 bei downem TTS-Worker" || bad "erwartet 503, bekam $CODE" [ "$CODE" = "503" ] && ok "503 bei downem TTS-Worker" || bad "erwartet 503, bekam $CODE"
RESP=$(curl -sf "$BASE/status") RESP=$(curl -sf "$BASE/status")
@@ -634,7 +634,7 @@ MOCK_TTS_PORT="$TTS_PORT" MOCK_TTS_DELAY=0.1 \
TTS_PID=$! TTS_PID=$!
sleep 0.5 sleep 0.5
CODE=$(curl -s -o /tmp/tts33.wav -w "%{http_code}" "$BASE/v1/audio/speech" \ CODE=$(curl -s -o /tmp/tts33.wav -w "%{http_code}" "$BASE/v1/audio/speech" \
-H "Content-Type: application/json" -d '{"input":"Wieder da","voice":"claribel","response_format":"wav"}') -H "Content-Type: application/json" -d '{"input":"Wieder da","voice":"alloy","response_format":"wav"}')
[ "$CODE" = "200" ] && [ -s /tmp/tts33.wav ] \ [ "$CODE" = "200" ] && [ -s /tmp/tts33.wav ] \
&& ok "TTS nach Neustart wieder verfügbar" || bad "TTS-Recovery (Code $CODE)" && ok "TTS nach Neustart wieder verfügbar" || bad "TTS-Recovery (Code $CODE)"
@@ -727,8 +727,8 @@ import json,sys
d=json.load(sys.stdin) d=json.load(sys.stdin)
ids={m["id"] for m in d["data"]} ids={m["id"] for m in d["data"]}
assert "whisper-1" in ids, ids assert "whisper-1" in ids, ids
assert "xtts-v2" in ids, ids assert "qwen3-tts" in ids, ids
' && ok "Audio-Modelle: whisper-1 + xtts-v2" || bad "Audio-Modelle" ' && ok "Audio-Modelle: whisper-1 + qwen3-tts" || bad "Audio-Modelle"
# --- 41. /v1/audio/voices ------------------------------------------------------------------------------------------ # --- 41. /v1/audio/voices ------------------------------------------------------------------------------------------
echo "== Test 41: GET /v1/audio/voices" echo "== Test 41: GET /v1/audio/voices"
@@ -738,8 +738,8 @@ echo "$RESP" | python3 -c '
import json,sys import json,sys
d=json.load(sys.stdin) d=json.load(sys.stdin)
ids={v["id"] for v in d["data"]} ids={v["id"] for v in d["data"]}
assert "claribel" in ids, ids assert "alloy" in ids, ids
' && ok "Audio-Voices: claribel" || bad "Audio-Voices" ' && ok "Audio-Voices: alloy" || bad "Audio-Voices"
# --- 42. STT + Qwen parallel ---------------------------------------------------------------------------------------- # --- 42. STT + Qwen parallel ----------------------------------------------------------------------------------------
echo "== Test 42: STT + Qwen parallel" echo "== Test 42: STT + Qwen parallel"
@@ -770,7 +770,7 @@ sleep 0.2
# TTS-Request # TTS-Request
CODE=$(curl -s -o /tmp/tts43.mp3 -w "%{http_code}" \ CODE=$(curl -s -o /tmp/tts43.mp3 -w "%{http_code}" \
"$BASE/v1/audio/speech" -H "Content-Type: application/json" \ "$BASE/v1/audio/speech" -H "Content-Type: application/json" \
-d '{"input":"Hallo","voice":"claribel"}') -d '{"input":"Hallo","voice":"alloy"}')
wait $STT_PID43 wait $STT_PID43
[ "$CODE" = "200" ] && [ -s /tmp/tts43.mp3 ] \ [ "$CODE" = "200" ] && [ -s /tmp/tts43.mp3 ] \
&& ok "STT + TTS parallel (beide 200)" || bad "STT + TTS parallel (TTS Code $CODE)" && ok "STT + TTS parallel (beide 200)" || bad "STT + TTS parallel (TTS Code $CODE)"
+2 -3
View File
@@ -8,7 +8,7 @@ flowchart LR
P --> Q[genau ein llama.cpp-Profil<br/>Qwen Fast / Medium / Large / Ultra / Uncensored] P --> Q[genau ein llama.cpp-Profil<br/>Qwen Fast / Medium / Large / Ultra / Uncensored]
R --> I[FLUX.2 Klein 9B FP8 Beta<br/>RTX 5080 Transformer] R --> I[FLUX.2 Klein 9B FP8 Beta<br/>RTX 5080 Transformer]
I --> E[Qwen3-8B NF4 Textencoder<br/>RTX 3060 während Bildauftrag] I --> E[Qwen3-8B NF4 Textencoder<br/>RTX 3060 während Bildauftrag]
R --> T[Qwen3-TTS RTX 3060<br/>Piper CPU-Fallback] R --> T[Qwen3-TTS RTX 3060<br/>Normalisierungs- und Streaming-Gateway]
R --> STT[Whisper.cpp ggml-small<br/>CPU, lokale Spracherkennung] R --> STT[Whisper.cpp ggml-small<br/>CPU, lokale Spracherkennung]
H --> U[MUA / Unraid MCP] H --> U[MUA / Unraid MCP]
@@ -50,7 +50,6 @@ Kontextgröße:
| Medium | 160.000 Token | | Medium | 160.000 Token |
| Large | 192.000 Token | | Large | 192.000 Token |
| Ultra | 262.144 Token | | Ultra | 262.144 Token |
| Beta 1 | 112.000 Token |
| Uncensored | 80.000 Token | | Uncensored | 80.000 Token |
## Exklusiver Bildmodus ## Exklusiver Bildmodus
@@ -65,7 +64,7 @@ gleichzeitig. Der Wechsel ist transaktional:
4. Nach dem Prompt-Encoding werden die Embeddings zur RTX 5080 übertragen. 4. Nach dem Prompt-Encoding werden die Embeddings zur RTX 5080 übertragen.
5. Vor dem VAE-Decoding werden Textencoder und Transformer freigegeben. 5. Vor dem VAE-Decoding werden Textencoder und Transformer freigegeben.
6. Der Worker wird gestoppt; anschließend starten Qwen3-TTS und das vorherige 6. Der Worker wird gestoppt; anschließend starten Qwen3-TTS und das vorherige
Textprofil wieder. Piper bleibt als CPU-Fallback verfügbar. Textprofil wieder. Während der exklusiven Bildphase steht kein TTS bereit.
Der Bild-Worker ist lazy und besitzt `restart: "no"`; im normalen Textbetrieb Der Bild-Worker ist lazy und besitzt `restart: "no"`; im normalen Textbetrieb
belegt er daher keinen VRAM. Container werden über eindeutige Docker-Labels belegt er daher keinen VRAM. Container werden über eindeutige Docker-Labels
+3 -5
View File
@@ -1,8 +1,8 @@
# Container-Inventar auf Athena # Container-Inventar auf Athena
Stand: 9. September 2026 Stand: 10. September 2026
Athena besteht derzeit aus 25 Docker-Containern. Nicht jeder Container enthält Athena besteht nach der Bereinigung aus 23 Docker-Containern. Nicht jeder Container enthält
ein KI-Modell: Router, Oberflächen, Netzwerk, Steuerung und Sicherung sind ein KI-Modell: Router, Oberflächen, Netzwerk, Steuerung und Sicherung sind
gewöhnliche Dienste. Die rechenintensiven GPU-Worker werden absichtlich nur bei gewöhnliche Dienste. Die rechenintensiven GPU-Worker werden absichtlich nur bei
Bedarf gestartet. Ein Container im Zustand `Created` oder `Exited (0)` ist daher Bedarf gestartet. Ein Container im Zustand `Created` oder `Exited (0)` ist daher
@@ -13,7 +13,6 @@ nicht automatisch ein ungenutzter Rest.
| `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 | 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-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-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-beta1` | Qwen3.8-27B GSQ-RCO `IQ3_S-MTP`, Qwen-MMProj BF16 | Experimentelles Beta-Profil mit 112.000 Token Kontext; wird nur auf Anforderung geladen. |
| `mike-ai-llama-dashboard` | kein Modell | Zeigt Telemetrie, Profile, GPU-Nutzung und Betriebsarten an und bietet die Modusumschaltung. | | `mike-ai-llama-dashboard` | kein Modell | Zeigt Telemetrie, Profile, GPU-Nutzung und Betriebsarten an und bietet die Modusumschaltung. |
| `mike-ai-llama-fast` | Qwen3.8-27B `IQ4-MIX`, Qwen-MMProj BF16 | Schnelles Q4-Text-/Vision-Profil mit 76.800 Token Kontext. | | `mike-ai-llama-fast` | Qwen3.8-27B `IQ4-MIX`, Qwen-MMProj BF16 | Schnelles Q4-Text-/Vision-Profil mit 76.800 Token Kontext. |
| `mike-ai-llama-large` | Qwen3.8-27B `IQ4_XS-pure`, Qwen-MMProj BF16 | Q4-Text-/Vision-Profil mit 192.000 Token Kontext und Verteilung auf beide GPUs. | | `mike-ai-llama-large` | Qwen3.8-27B `IQ4_XS-pure`, Qwen-MMProj BF16 | Q4-Text-/Vision-Profil mit 192.000 Token Kontext und Verteilung auf beide GPUs. |
@@ -23,13 +22,12 @@ nicht automatisch ein ungenutzter Rest.
| `mike-ai-mcp-athena-operator` | kein Modell | Stellt Hermes begrenzte Werkzeuge zum Prüfen, Ändern, Testen, Sichern und Versionieren von Athena bereit. | | `mike-ai-mcp-athena-operator` | kein Modell | Stellt Hermes begrenzte Werkzeuge zum Prüfen, Ändern, Testen, Sichern und Versionieren von Athena bereit. |
| `mike-ai-music-acestep-test` | ACE-Step 1.5 XL-SFT und `acestep-5Hz-lm-1.7B` | Generiert Musik im exklusiven Musikmodus auf der RTX 5080. | | `mike-ai-music-acestep-test` | ACE-Step 1.5 XL-SFT und `acestep-5Hz-lm-1.7B` | Generiert Musik im exklusiven Musikmodus auf der RTX 5080. |
| `mike-ai-music-ui` | kein Modell; `fspecii/ace-step-ui` | Community-Oberfläche für ACE-Step; bleibt als leichte UI verfügbar, während der GPU-Worker bedarfsgesteuert läuft. | | `mike-ai-music-ui` | kein Modell; `fspecii/ace-step-ui` | Community-Oberfläche für ACE-Step; bleibt als leichte UI verfügbar, während der GPU-Worker bedarfsgesteuert läuft. |
| `mike-ai-piper` | `de_DE-thorsten-high` | CPU-basierte deutsche TTS-Rückfallebene, die auch während GPU-Umschaltungen verfügbar bleibt. |
| `mike-ai-portainer` | kein Modell; Portainer CE | Optionale Docker-Verwaltungsoberfläche. | | `mike-ai-portainer` | kein Modell; Portainer CE | Optionale Docker-Verwaltungsoberfläche. |
| `mike-ai-profile-controller` | kein Modell | Startet und stoppt ausschließlich freigegebene Modellprofile und Spezialworker in einer sicheren Reihenfolge. | | `mike-ai-profile-controller` | kein Modell | Startet und stoppt ausschließlich freigegebene Modellprofile und Spezialworker in einer sicheren Reihenfolge. |
| `mike-ai-qwen3-tts` | `Qwen/Qwen3-TTS-12Hz-1.7B-Base`, Stimme Serena | Hochwertige deutsche Sprachausgabe auf der RTX 3060 im LLM-Betrieb. | | `mike-ai-qwen3-tts` | `Qwen/Qwen3-TTS-12Hz-1.7B-Base`, Stimme Serena | Hochwertige deutsche Sprachausgabe auf der RTX 3060 im LLM-Betrieb. |
| `mike-ai-router` | kein eigenes Modell | Einzige OpenAI-kompatible Modelladresse; koordiniert Profile, Bildaufträge, Sprache und Betriebsarten. | | `mike-ai-router` | kein eigenes Modell | Einzige OpenAI-kompatible Modelladresse; koordiniert Profile, Bildaufträge, Sprache und Betriebsarten. |
| `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-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, leitet TTS an Qwen3-TTS weiter und fällt bei Bedarf auf Piper zurück. | | `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-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 `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-wireguard-gateway` | kein Modell | Veröffentlicht Dashboard und Fachoberflächen ausschließlich über den privaten WireGuard-Pfad. |
+2 -2
View File
@@ -24,7 +24,7 @@ bereit:
- RTX 5080: 9B-FP8-Diffusionstransformer und VAE-Decoding - RTX 5080: 9B-FP8-Diffusionstransformer und VAE-Decoding
- RTX 3060: Qwen3-8B-Textencoder in NF4 - RTX 3060: Qwen3-8B-Textencoder in NF4
- Qwen3-TTS und aktives llama.cpp-Profil werden für den Bildauftrag pausiert - Qwen3-TTS und aktives llama.cpp-Profil werden für den Bildauftrag pausiert
- Piper bleibt währenddessen als CPU-TTS verfügbar - TTS ist währenddessen vorübergehend nicht verfügbar
- nach Abschluss werden TTS und das vorherige Textprofil wiederhergestellt - nach Abschluss werden TTS und das vorherige Textprofil wiederhergestellt
Ein vollständiger Aufruf über Athenas OpenAI-kompatiblen Router wurde mit Ein vollständiger Aufruf über Athenas OpenAI-kompatiblen Router wurde mit
@@ -61,7 +61,7 @@ Build 10718, Commit `41ef91f7c8046087cdfbb276b79bff311ecf1c6d`. Dessen lokales
Fallback-Image wurde am 8. September 2026 beim gezielten Aufräumen entfernt; Fallback-Image wurde am 8. September 2026 beim gezielten Aufräumen entfernt;
ein Rückfall erfordert daher einen Neubau dieses Commits. ein Rückfall erfordert daher einen Neubau dieses Commits.
Build 10781 wurde nach dem Bau produktiv verifiziert. Alle sechs Build 10781 wurde nach dem Bau produktiv verifiziert. Alle fünf
Profildefinitionen verwenden dasselbe neue Image. Am 8. September lief Ultra Profildefinitionen verwenden dasselbe neue Image. Am 8. September lief Ultra
mit 262.144 Tokens Kontext gesund; MTP und eine lokale Textprobe wurden mit 262.144 Tokens Kontext gesund; MTP und eine lokale Textprobe wurden
erfolgreich geprüft. erfolgreich geprüft.
+1 -1
View File
@@ -44,7 +44,7 @@ Der Profile Controller stoppt vor dem Start des Bild-Workers alle
llama.cpp-Profile und den mit `com.mike-ai.tts-worker=qwen3` markierten llama.cpp-Profile und den mit `com.mike-ai.tts-worker=qwen3` markierten
Qwen3-TTS-Container. Dadurch bleibt genügend VRAM für beide Bildkomponenten. Qwen3-TTS-Container. Dadurch bleibt genügend VRAM für beide Bildkomponenten.
Nach dem Bildauftrag startet er Qwen3-TTS und das zuvor aktive Textprofil Nach dem Bildauftrag startet er Qwen3-TTS und das zuvor aktive Textprofil
wieder. Der TTS-Gateway kann während des Wechsels auf Piper ausweichen. wieder. Während des exklusiven GPU-Wechsels ist TTS vorübergehend nicht verfügbar.
## Aktuelle Grenzen ## Aktuelle Grenzen
+7 -2
View File
@@ -1,7 +1,12 @@
# Qwen Beta 1 – GSQ-RCO # Qwen Beta 1 – GSQ-RCO
`qwen-beta-1` ist ein zusätzliches, nicht standardmäßig aktives Router-Profil. > Historischer Testbericht. Das Beta-1-Profil wurde am 10. September 2026
Die bestehenden Profile und das Standardprofil `qwen-medium` bleiben unverändert. > vollständig aus dem produktiven Router entfernt, weil die kleinere
> Quantisierung gegenüber den Q4-Profilen keinen belastbaren Vorteil brachte.
`qwen-beta-1` war ein zusätzliches, nicht standardmäßig aktives Router-Profil.
Der Bericht bleibt erhalten, damit diese Quantisierung nicht versehentlich
erneut getestet wird.
## Laufzeitkonfiguration ## Laufzeitkonfiguration
-1
View File
@@ -7,7 +7,6 @@
- `/etc/mike-ai` mit lokaler Konfiguration, - `/etc/mike-ai` mit lokaler Konfiguration,
- Router-Zustand und erzeugte Bilder, - Router-Zustand und erzeugte Bilder,
- Piper-Daten,
- der kanonische Stack als zusätzlicher Snapshot. - der kanonische Stack als zusätzlicher Snapshot.
Nicht in das Archiv gehören die großen Modellgewichte unter `/data/models`. Nicht in das Archiv gehören die großen Modellgewichte unter `/data/models`.
-2
View File
@@ -8,7 +8,6 @@ Standardprofil: **medium** · globales Ausgabelimit: **8192 Token**
|---|---|---:|---:|---|---|---|---:| |---|---|---:|---:|---|---|---|---:|
| fast | `qwen-fast` | 76,800 | 1 | Qwen3.8-27B IQ4 Mix | 5080 only | ja | 2 | | fast | `qwen-fast` | 76,800 | 1 | Qwen3.8-27B IQ4 Mix | 5080 only | ja | 2 |
| medium | `qwen-medium` | 160,000 | 1 | Qwen3.8-27B IQ4 XS Pure | 85:15 | ja | 3 | | medium | `qwen-medium` | 160,000 | 1 | Qwen3.8-27B IQ4 XS Pure | 85:15 | ja | 3 |
| beta1 | `qwen-beta-1` | 112,000 | 1 | Qwen3.8-27B GSQ-RCO IQ3_S MTP | 5080 model / 3060 vision | ja | 3 |
| large | `qwen-large` | 192,000 | 1 | Qwen3.8-27B IQ4 XS Pure | 86:14 | ja | 3 | | large | `qwen-large` | 192,000 | 1 | Qwen3.8-27B IQ4 XS Pure | 86:14 | ja | 3 |
| ultra | `qwen-ultra` | 262,144 | 1 | Qwen3.8-27B IQ4 XS Pure | 80:20 | nein | 2 | | ultra | `qwen-ultra` | 262,144 | 1 | Qwen3.8-27B IQ4 XS Pure | 80:20 | nein | 2 |
| uncensored | `qwen-uncensored` | 80,000 | 1 | Qwen3.8-27B Abliterated Q4_K_M | 90:10 | ja | 2 | | uncensored | `qwen-uncensored` | 80,000 | 1 | Qwen3.8-27B Abliterated Q4_K_M | 90:10 | ja | 2 |
@@ -17,7 +16,6 @@ Standardprofil: **medium** · globales Ausgabelimit: **8192 Token**
- **fast**: Schnelles Profil für kurze Chats und zügige Werkzeugaufgaben. - **fast**: Schnelles Profil für kurze Chats und zügige Werkzeugaufgaben.
- **medium**: Ausgewogenes Standardprofil für Alltag und lange agentische Aufgaben. - **medium**: Ausgewogenes Standardprofil für Alltag und lange agentische Aufgaben.
- **beta1**: Beta 1: qualitaetsoptimiertes GSQ-RCO-IQ3_S-Testprofil; 112K Startkontext bis zur erneuten Grenzmessung.
- **large**: Großes Profil für umfangreiche Dokumente und lange technische Arbeiten. - **large**: Großes Profil für umfangreiche Dokumente und lange technische Arbeiten.
- **ultra**: Maximaler Textkontext; bewusst ohne Vision-Projektor. - **ultra**: Maximaler Textkontext; bewusst ohne Vision-Projektor.
- **uncensored**: Weniger restriktives Spezialprofil; Werkzeugrechte bleiben unverändert. - **uncensored**: Weniger restriktives Spezialprofil; Werkzeugrechte bleiben unverändert.
+2 -2
View File
@@ -27,7 +27,7 @@ Statuswerte:
| 04.09.2026 | ISTA-DASLab Qwen3.8-27B GSQ-RCO `IQ3_XXS-MTP` | bis 196.608 | sehr platzsparend und bis 196.608 technisch lauffähig; später durch IQ3_S ersetzt | **ersetzt** | [GSQ_RCO_BETA1_20260904.md](GSQ_RCO_BETA1_20260904.md) | | 04.09.2026 | ISTA-DASLab Qwen3.8-27B GSQ-RCO `IQ3_XXS-MTP` | bis 196.608 | sehr platzsparend und bis 196.608 technisch lauffähig; später durch IQ3_S ersetzt | **ersetzt** | [GSQ_RCO_BETA1_20260904.md](GSQ_RCO_BETA1_20260904.md) |
| 07.09.2026 | `Jackrong/Qwopus3.8-27B-Flash-GGUF` / `Qwopus3.8-27B-Flash-MTP-IQ4_XS.gguf` | 160K | Recall 3/3; Decode 87,2 statt 105,3 Token/s, Lang-Decode 51,0 statt 56,7 Token/s; kein Gesamtvorteil | **verworfen** | Athena: `/data/benchmarks/qwen38-ab-20260907/` | | 07.09.2026 | `Jackrong/Qwopus3.8-27B-Flash-GGUF` / `Qwopus3.8-27B-Flash-MTP-IQ4_XS.gguf` | 160K | Recall 3/3; Decode 87,2 statt 105,3 Token/s, Lang-Decode 51,0 statt 56,7 Token/s; kein Gesamtvorteil | **verworfen** | Athena: `/data/benchmarks/qwen38-ab-20260907/` |
| 07.09.2026 | `bartowski/Qwen3.8-27B-GGUF` / `Qwen3.8-27B-IQ4_XS.gguf` | 160K | Recall 3/3; Decode 90,4 statt 105,3 Token/s, Lang-Decode 51,9 statt 56,7 Token/s; kein Gesamtvorteil | **verworfen** | Athena: `/data/benchmarks/qwen38-ab-20260907/` | | 07.09.2026 | `bartowski/Qwen3.8-27B-GGUF` / `Qwen3.8-27B-IQ4_XS.gguf` | 160K | Recall 3/3; Decode 90,4 statt 105,3 Token/s, Lang-Decode 51,9 statt 56,7 Token/s; kein Gesamtvorteil | **verworfen** | Athena: `/data/benchmarks/qwen38-ab-20260907/` |
| 08.09.2026 | `ISTA-DASLab/Qwen3.8-27B-GSQ-RCO-GGUF` / `IQ3_S-MTP` | 76,8K–262K | Qualität im lokalen Test praktisch gleich, trotz optimierter GPU-Splits überwiegend langsamer als Q4 | **Beta** mit 112K; kein Ersatz für Q4 | [GSQ_RCO_BETA1_20260904.md](GSQ_RCO_BETA1_20260904.md) | | 08.09.2026 | `ISTA-DASLab/Qwen3.8-27B-GSQ-RCO-GGUF` / `IQ3_S-MTP` | 76,8K–262K | Qualität im lokalen Test praktisch gleich, trotz optimierter GPU-Splits überwiegend langsamer als Q4 | **entfernt**; kein Ersatz für Q4 | [GSQ_RCO_BETA1_20260904.md](GSQ_RCO_BETA1_20260904.md) |
| 08.09.2026 | `Tiel-Coder-35B-A3B-UD-IQ4_XS.gguf` | 160K vorgesehen | Testcontainer und Gewichte vorhanden gewesen, aber kein versionierter, belastbarer Abnahmebericht | **unvollständig**; nicht als getesteter Sieger behandeln | kein Ergebnisartefakt vorhanden | | 08.09.2026 | `Tiel-Coder-35B-A3B-UD-IQ4_XS.gguf` | 160K vorgesehen | Testcontainer und Gewichte vorhanden gewesen, aber kein versionierter, belastbarer Abnahmebericht | **unvollständig**; nicht als getesteter Sieger behandeln | kein Ergebnisartefakt vorhanden |
## Externe CPU-Helfermodelle ## Externe CPU-Helfermodelle
@@ -54,7 +54,7 @@ Titelgenerierung und Kontextkompression in Hermes.
| Datum | Modell | Ergebnis | Status / Entscheidung | | Datum | Modell | Ergebnis | Status / Entscheidung |
|---|---|---|---| |---|---|---|---|
| 05.09.2026 | Coqui XTTS v2 | deutsche Satzzeichen, Enden und Streaming-Chunks erzeugten Halluzinationen und unnatürliche Prosodie | **verworfen und entfernt** | | 05.09.2026 | Coqui XTTS v2 | deutsche Satzzeichen, Enden und Streaming-Chunks erzeugten Halluzinationen und unnatürliche Prosodie | **verworfen und entfernt** |
| 05.09.2026 | `Qwen/Qwen3-TTS-12Hz-1.7B-Base` | deutlich natürlichere deutsche Ausgabe ohne die XTTS-Endhalluzinationen | **produktiv** mit Piper-Fallback | | 05.09.2026 | `Qwen/Qwen3-TTS-12Hz-1.7B-Base` | deutlich natürlichere deutsche Ausgabe ohne die XTTS-Endhalluzinationen | **produktiv** als einziges TTS-Backend |
| 06.–08.09.2026 | Whisper.cpp `large-v3-turbo` | lokaler TTS→STT-Rundlauf und OpenClaw-Transkription erfolgreich; später zugunsten des kleineren Laufzeitmodells entfernt | **ersetzt** | | 06.–08.09.2026 | Whisper.cpp `large-v3-turbo` | lokaler TTS→STT-Rundlauf und OpenClaw-Transkription erfolgreich; später zugunsten des kleineren Laufzeitmodells entfernt | **ersetzt** |
| seit 03.09.2026 | Whisper.cpp `ggml-small` | tatsächlich im Compose-Stack und im laufenden Container verwendetes CPU-STT-Modell | **produktiv** | | seit 03.09.2026 | Whisper.cpp `ggml-small` | tatsächlich im Compose-Stack und im laufenden Container verwendetes CPU-STT-Modell | **produktiv** |
| 09.09.2026 | `RMSnow/Vevo2`, Amphion `26f6883110181f1dbfe95c70a7c7dbaf4de5f42a` | Technik und Geschwindigkeit funktionierten, reale deutsche Sprachwandlung mit kurzer und langer Referenz war jedoch unverständlich, halluzinierend oder musikalisch | **qualitativ verworfen und entfernt**; Ergebnis bleibt hier dokumentiert, Images, Daten und altes Projekt wurden am 09.09. bereinigt | | 09.09.2026 | `RMSnow/Vevo2`, Amphion `26f6883110181f1dbfe95c70a7c7dbaf4de5f42a` | Technik und Geschwindigkeit funktionierten, reale deutsche Sprachwandlung mit kurzer und langer Referenz war jedoch unverständlich, halluzinierend oder musikalisch | **qualitativ verworfen und entfernt**; Ergebnis bleibt hier dokumentiert, Images, Daten und altes Projekt wurden am 09.09. bereinigt |
+2 -12
View File
@@ -41,7 +41,6 @@ source "$CONFIG"
required=(AI_HOSTNAME ADMIN_USER MODEL_DIR FAST_MODEL_FILE required=(AI_HOSTNAME ADMIN_USER MODEL_DIR FAST_MODEL_FILE
FAST_MODEL_URL FAST_MODEL_SHA256 MEDIUM_MODEL_FILE MEDIUM_MODEL_URL FAST_MODEL_URL FAST_MODEL_SHA256 MEDIUM_MODEL_FILE MEDIUM_MODEL_URL
MEDIUM_MODEL_SHA256 LARGE_MODEL_FILE LARGE_MODEL_URL LARGE_MODEL_SHA256 MEDIUM_MODEL_SHA256 LARGE_MODEL_FILE LARGE_MODEL_URL LARGE_MODEL_SHA256
BETA1_MODEL_FILE BETA1_MODEL_URL BETA1_MODEL_SHA256
ULTRA_MODEL_FILE ULTRA_MODEL_URL ULTRA_MODEL_SHA256 ULTRA_MODEL_FILE ULTRA_MODEL_URL ULTRA_MODEL_SHA256
UNCENSORED_MODEL_FILE UNCENSORED_MODEL_URL UNCENSORED_MODEL_SHA256 UNCENSORED_MODEL_FILE UNCENSORED_MODEL_URL UNCENSORED_MODEL_SHA256
UNCENSORED_PROJECTOR_FILE UNCENSORED_PROJECTOR_URL UNCENSORED_PROJECTOR_FILE UNCENSORED_PROJECTOR_URL
@@ -314,8 +313,6 @@ MODEL_DIR=$MODEL_DIR
WIREGUARD_CONFIG_FILE=${WIREGUARD_CONFIG_FILE:-/etc/mike-ai/wireguard/fritz-athena.conf} WIREGUARD_CONFIG_FILE=${WIREGUARD_CONFIG_FILE:-/etc/mike-ai/wireguard/fritz-athena.conf}
ROUTER_API_KEY=$(<$SECRETS_DIR/router-api-key) ROUTER_API_KEY=$(<$SECRETS_DIR/router-api-key)
CONTROLLER_TOKEN=$(<$SECRETS_DIR/controller-token) CONTROLLER_TOKEN=$(<$SECRETS_DIR/controller-token)
PIPER_TTS_VERSION=${PIPER_TTS_VERSION:-1.6.0}
PIPER_VOICE=${PIPER_VOICE:-de_DE-thorsten-high}
QWEN3_TTS_IMAGE=${QWEN3_TTS_IMAGE:-ghcr.io/malaiwah/qwen3-tts-server:latest@sha256:b363a01d08b1bbecbfc3ca6f585368fae2cfdc591f9ecca6643738369f9a9d98} QWEN3_TTS_IMAGE=${QWEN3_TTS_IMAGE:-ghcr.io/malaiwah/qwen3-tts-server:latest@sha256:b363a01d08b1bbecbfc3ca6f585368fae2cfdc591f9ecca6643738369f9a9d98}
QWEN3_TTS_CACHE_DIR=$QWEN3_TTS_CACHE_DIR QWEN3_TTS_CACHE_DIR=$QWEN3_TTS_CACHE_DIR
QWEN3_TTS_VOICES_DIR=$QWEN3_TTS_VOICES_DIR QWEN3_TTS_VOICES_DIR=$QWEN3_TTS_VOICES_DIR
@@ -323,7 +320,6 @@ QWEN3_TTS_GPU_DEVICE=${QWEN3_TTS_GPU_DEVICE:-GPU-4834d9d7-5b61-3004-1fb3-4ae49d4
AI_DNS=${WG_DNS:-1.1.1.1} AI_DNS=${WG_DNS:-1.1.1.1}
FAST_MODEL_FILE=$FAST_MODEL_FILE FAST_MODEL_FILE=$FAST_MODEL_FILE
MEDIUM_MODEL_FILE=$MEDIUM_MODEL_FILE MEDIUM_MODEL_FILE=$MEDIUM_MODEL_FILE
BETA1_MODEL_FILE=$BETA1_MODEL_FILE
LARGE_MODEL_FILE=$LARGE_MODEL_FILE LARGE_MODEL_FILE=$LARGE_MODEL_FILE
ULTRA_MODEL_FILE=$ULTRA_MODEL_FILE ULTRA_MODEL_FILE=$ULTRA_MODEL_FILE
UNCENSORED_MODEL_FILE=$UNCENSORED_MODEL_FILE UNCENSORED_MODEL_FILE=$UNCENSORED_MODEL_FILE
@@ -337,10 +333,6 @@ MEDIUM_CONTEXT=${MEDIUM_CONTEXT:-160000}
MEDIUM_BATCH_SIZE=${MEDIUM_BATCH_SIZE:-2048} MEDIUM_BATCH_SIZE=${MEDIUM_BATCH_SIZE:-2048}
MEDIUM_UBATCH_SIZE=${MEDIUM_UBATCH_SIZE:-128} MEDIUM_UBATCH_SIZE=${MEDIUM_UBATCH_SIZE:-128}
MEDIUM_PARALLEL_SLOTS=${MEDIUM_PARALLEL_SLOTS:-1} MEDIUM_PARALLEL_SLOTS=${MEDIUM_PARALLEL_SLOTS:-1}
BETA1_CONTEXT=${BETA1_CONTEXT:-112000}
BETA1_BATCH_SIZE=${BETA1_BATCH_SIZE:-2048}
BETA1_UBATCH_SIZE=${BETA1_UBATCH_SIZE:-128}
BETA1_PARALLEL_SLOTS=${BETA1_PARALLEL_SLOTS:-1}
LARGE_CONTEXT=${LARGE_CONTEXT:-192000} LARGE_CONTEXT=${LARGE_CONTEXT:-192000}
LARGE_BATCH_SIZE=${LARGE_BATCH_SIZE:-2048} LARGE_BATCH_SIZE=${LARGE_BATCH_SIZE:-2048}
LARGE_UBATCH_SIZE=${LARGE_UBATCH_SIZE:-128} LARGE_UBATCH_SIZE=${LARGE_UBATCH_SIZE:-128}
@@ -356,7 +348,6 @@ UNCENSORED_PARALLEL_SLOTS=${UNCENSORED_PARALLEL_SLOTS:-1}
FAST_GPU_DEVICES=${TEXT_GPU_DEVICES:-0}${SECONDARY_GPU_DEVICES:+,$SECONDARY_GPU_DEVICES} FAST_GPU_DEVICES=${TEXT_GPU_DEVICES:-0}${SECONDARY_GPU_DEVICES:+,$SECONDARY_GPU_DEVICES}
MEDIUM_GPU_DEVICES=${TEXT_GPU_DEVICES:-0}${SECONDARY_GPU_DEVICES:+,$SECONDARY_GPU_DEVICES} MEDIUM_GPU_DEVICES=${TEXT_GPU_DEVICES:-0}${SECONDARY_GPU_DEVICES:+,$SECONDARY_GPU_DEVICES}
MEDIUM_TENSOR_SPLIT=${MEDIUM_TENSOR_SPLIT:-90,10} MEDIUM_TENSOR_SPLIT=${MEDIUM_TENSOR_SPLIT:-90,10}
BETA1_GPU_DEVICES=${TEXT_GPU_DEVICES:-0}${SECONDARY_GPU_DEVICES:+,$SECONDARY_GPU_DEVICES}
LARGE_GPU_DEVICES=${TEXT_GPU_DEVICES:-0}${SECONDARY_GPU_DEVICES:+,$SECONDARY_GPU_DEVICES} LARGE_GPU_DEVICES=${TEXT_GPU_DEVICES:-0}${SECONDARY_GPU_DEVICES:+,$SECONDARY_GPU_DEVICES}
LARGE_TENSOR_SPLIT=${LARGE_TENSOR_SPLIT:-86,14} LARGE_TENSOR_SPLIT=${LARGE_TENSOR_SPLIT:-86,14}
ULTRA_GPU_DEVICES=${TEXT_GPU_DEVICES:-0}${SECONDARY_GPU_DEVICES:+,$SECONDARY_GPU_DEVICES} ULTRA_GPU_DEVICES=${TEXT_GPU_DEVICES:-0}${SECONDARY_GPU_DEVICES:+,$SECONDARY_GPU_DEVICES}
@@ -405,7 +396,6 @@ download_models() {
done <<EOF done <<EOF
$FAST_MODEL_FILE|$FAST_MODEL_URL|$FAST_MODEL_SHA256 $FAST_MODEL_FILE|$FAST_MODEL_URL|$FAST_MODEL_SHA256
$MEDIUM_MODEL_FILE|$MEDIUM_MODEL_URL|$MEDIUM_MODEL_SHA256 $MEDIUM_MODEL_FILE|$MEDIUM_MODEL_URL|$MEDIUM_MODEL_SHA256
$BETA1_MODEL_FILE|$BETA1_MODEL_URL|$BETA1_MODEL_SHA256
$LARGE_MODEL_FILE|$LARGE_MODEL_URL|$LARGE_MODEL_SHA256 $LARGE_MODEL_FILE|$LARGE_MODEL_URL|$LARGE_MODEL_SHA256
$ULTRA_MODEL_FILE|$ULTRA_MODEL_URL|$ULTRA_MODEL_SHA256 $ULTRA_MODEL_FILE|$ULTRA_MODEL_URL|$ULTRA_MODEL_SHA256
$UNCENSORED_MODEL_FILE|$UNCENSORED_MODEL_URL|$UNCENSORED_MODEL_SHA256 $UNCENSORED_MODEL_FILE|$UNCENSORED_MODEL_URL|$UNCENSORED_MODEL_SHA256
@@ -514,11 +504,11 @@ build_and_start() {
# Portable MCPs and Hermes live on Unraid and are restored through Appdata. # Portable MCPs and Hermes live on Unraid and are restored through Appdata.
"$STACK_DIR/platform/mcp/install-tools.sh" "$STACK_DIR/platform/mcp/install-tools.sh"
docker compose --env-file "$SECRETS_DIR/stack.env" --profile inference create \ docker compose --env-file "$SECRETS_DIR/stack.env" --profile inference create \
llama-fast llama-medium llama-beta1 llama-large llama-ultra llama-uncensored llama-fast llama-medium llama-large llama-ultra llama-uncensored
docker compose --env-file "$SECRETS_DIR/stack.env" --profile image create image-worker docker compose --env-file "$SECRETS_DIR/stack.env" --profile image create image-worker
docker volume inspect portainer_data >/dev/null 2>&1 || docker volume create portainer_data >/dev/null docker volume inspect portainer_data >/dev/null 2>&1 || docker volume create portainer_data >/dev/null
docker compose --env-file "$SECRETS_DIR/stack.env" up -d --build \ docker compose --env-file "$SECRETS_DIR/stack.env" up -d --build \
wireguard-gateway qwen3-tts piper tts-gateway profile-controller router llama-dashboard portainer backup wireguard-gateway qwen3-tts tts-gateway profile-controller router llama-dashboard portainer backup
if [[ ${WIREGUARD_MODE:-container} == container ]]; then if [[ ${WIREGUARD_MODE:-container} == container ]]; then
systemctl restart mike-ai-container-vpn-guard.service systemctl restart mike-ai-container-vpn-guard.service
fi fi
+1 -1
View File
@@ -7,7 +7,7 @@ Athena speech stack:
2. Athena Whisper transcribes it, 2. Athena Whisper transcribes it,
3. OpenClaw's normal agent-consult path answers with its configured model and 3. OpenClaw's normal agent-consult path answers with its configured model and
tools, tools,
4. Athena XTTS/Piper returns PCM audio to the Talk client. 4. Athena Qwen3-TTS returns PCM audio to the Talk client.
Long replies are synthesized incrementally. The first short phrase starts Long replies are synthesized incrementally. The first short phrase starts
playing as soon as it is ready while the next phrase is generated in parallel. playing as soon as it is ready while the next phrase is generated in parallel.
+1 -1
View File
@@ -324,7 +324,7 @@ export default definePluginEntry({
label: "Athena Local Talk", label: "Athena Local Talk",
aliases: ["athena", "local-athena"], aliases: ["athena", "local-athena"],
defaultModel: "athena-local", defaultModel: "athena-local",
voices: ["alloy", "claribel"], voices: ["alloy"],
autoSelectOrder: 1, autoSelectOrder: 1,
capabilities: { capabilities: {
transports: ["gateway-relay"], transports: ["gateway-relay"],
+2 -2
View File
@@ -307,7 +307,7 @@ class AthenaTalkBridge {
const response = await fetch(`${this.cfg.baseUrl}/audio/speech`, { const response = await fetch(`${this.cfg.baseUrl}/audio/speech`, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json", ...this.authHeaders() }, headers: { "Content-Type": "application/json", ...this.authHeaders() },
// Let Athena select its currently active local backend (XTTS or Piper). // Athena normalizes the request and serves it through Qwen3-TTS.
body: JSON.stringify({ voice: this.cfg.voice, input: text, response_format: "wav" }), body: JSON.stringify({ voice: this.cfg.voice, input: text, response_format: "wav" }),
signal, signal,
}); });
@@ -330,7 +330,7 @@ export default definePluginEntry({
label: "Athena Local Talk", label: "Athena Local Talk",
aliases: ["athena", "local-athena"], aliases: ["athena", "local-athena"],
defaultModel: "athena-local", defaultModel: "athena-local",
voices: ["alloy", "claribel"], voices: ["alloy"],
autoSelectOrder: 1, autoSelectOrder: 1,
capabilities: { capabilities: {
transports: ["gateway-relay"], transports: ["gateway-relay"],
+1 -1
View File
@@ -52,7 +52,7 @@ case "$command" in
[[ $# -eq 0 ]] || { echo "core akzeptiert keine weiteren Services" >&2; exit 2; } [[ $# -eq 0 ]] || { echo "core akzeptiert keine weiteren Services" >&2; exit 2; }
run "$ROOT_DIR/platform/mcp/install-tools.sh" run "$ROOT_DIR/platform/mcp/install-tools.sh"
run "${compose[@]}" up -d --build \ run "${compose[@]}" up -d --build \
wireguard-gateway piper xtts tts-gateway profile-controller router llama-dashboard portainer backup wireguard-gateway qwen3-tts tts-gateway profile-controller router llama-dashboard portainer backup
else else
run "${compose[@]}" up -d --build --no-deps "$@" run "${compose[@]}" up -d --build --no-deps "$@"
fi fi
-24
View File
@@ -1,24 +0,0 @@
FROM python:3.12-slim-bookworm
ARG PIPER_TTS_VERSION=1.6.0
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates curl ffmpeg gosu \
&& python -m pip install --no-cache-dir "piper-tts==${PIPER_TTS_VERSION}" \
&& useradd --system --uid 10003 --home-dir /nonexistent --shell /usr/sbin/nologin piper \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY piper_worker.py /app/piper_worker.py
COPY entrypoint.sh /usr/local/bin/mike-ai-piper-entrypoint
RUN chmod 0755 /usr/local/bin/mike-ai-piper-entrypoint
ENV PIPER_DATA_DIR=/data \
PIPER_VOICE=de_DE-thorsten-high \
PIPER_VOICE_ALIAS=alloy \
PIPER_HOST=0.0.0.0 \
PIPER_PORT=8085
VOLUME ["/data"]
EXPOSE 8085
ENTRYPOINT ["/usr/local/bin/mike-ai-piper-entrypoint"]
-15
View File
@@ -1,15 +0,0 @@
#!/bin/sh
set -eu
data_dir=${PIPER_DATA_DIR:-/data}
voice=${PIPER_VOICE:-de_DE-thorsten-high}
mkdir -p "$data_dir"
chown 10003:10003 "$data_dir"
if [ ! -s "$data_dir/$voice.onnx" ] || [ ! -s "$data_dir/$voice.onnx.json" ]; then
echo "Downloading Piper voice: $voice"
gosu piper python -m piper.download_voices --data-dir "$data_dir" "$voice"
fi
exec gosu piper python /app/piper_worker.py
-153
View File
@@ -1,153 +0,0 @@
#!/usr/bin/env python3
"""Small, private Piper worker for the Mike AI profile router.
The public OpenAI-compatible endpoint remains in the router. This worker only
accepts the narrow internal /status and /tts protocol and never logs input text.
"""
from __future__ import annotations
import io
import json
import os
import subprocess
import threading
import wave
from http import HTTPStatus
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from piper import PiperVoice, SynthesisConfig
DATA_DIR = Path(os.getenv("PIPER_DATA_DIR", "/data"))
VOICE_NAME = os.getenv("PIPER_VOICE", "de_DE-thorsten-high")
VOICE_ALIAS = os.getenv("PIPER_VOICE_ALIAS", "alloy")
HOST = os.getenv("PIPER_HOST", "0.0.0.0")
PORT = int(os.getenv("PIPER_PORT", "8085"))
MAX_TEXT_CHARS = int(os.getenv("PIPER_MAX_TEXT_CHARS", "8000"))
MAX_REQUEST_BYTES = int(os.getenv("PIPER_MAX_REQUEST_BYTES", "65536"))
VOICE_PATH = DATA_DIR / f"{VOICE_NAME}.onnx"
VOICE = PiperVoice.load(str(VOICE_PATH))
SYNTHESIS_LOCK = threading.Lock()
def synthesize_wav(text: str, speed: float) -> bytes:
"""Synthesize a complete WAV in memory without retaining the text."""
output = io.BytesIO()
config = SynthesisConfig(length_scale=1.0 / speed)
with SYNTHESIS_LOCK, wave.open(output, "wb") as wav_file:
VOICE.synthesize_wav(text, wav_file, syn_config=config)
return output.getvalue()
def wav_to_mp3(wav_bytes: bytes) -> bytes:
"""Convert Piper's WAV to the MP3 format Open WebUI requests by default."""
result = subprocess.run(
[
"ffmpeg", "-hide_banner", "-loglevel", "error",
"-f", "wav", "-i", "pipe:0",
"-codec:a", "libmp3lame", "-b:a", "96k",
"-f", "mp3", "pipe:1",
],
input=wav_bytes,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
timeout=120,
)
if result.returncode != 0:
raise RuntimeError("ffmpeg conversion failed")
return result.stdout
class Handler(BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1"
def log_message(self, fmt: str, *args: object) -> None:
# Deliberately omit URLs and request bodies from the log.
print(f"piper-worker: {self.command} -> {args[1] if len(args) > 1 else '-'}")
def send_bytes(self, status: int, body: bytes, content_type: str) -> None:
self.send_response(status)
self.send_header("Content-Type", content_type)
self.send_header("Content-Length", str(len(body)))
self.send_header("Cache-Control", "no-store")
self.end_headers()
self.wfile.write(body)
def send_json(self, status: int, payload: dict) -> None:
self.send_bytes(
status,
json.dumps(payload, separators=(",", ":")).encode(),
"application/json",
)
def do_GET(self) -> None: # noqa: N802
if self.path != "/status":
self.send_json(HTTPStatus.NOT_FOUND, {"error": "not found"})
return
self.send_json(
HTTPStatus.OK,
{
"ready": True,
"engine": "piper",
"model": VOICE_NAME,
"voices": [VOICE_ALIAS],
},
)
def do_POST(self) -> None: # noqa: N802
if self.path != "/tts":
self.send_json(HTTPStatus.NOT_FOUND, {"error": "not found"})
return
try:
content_length = int(self.headers.get("Content-Length", "0"))
except ValueError:
content_length = 0
if content_length <= 0 or content_length > MAX_REQUEST_BYTES:
self.send_json(HTTPStatus.REQUEST_ENTITY_TOO_LARGE, {"error": "invalid request size"})
return
try:
request = json.loads(self.rfile.read(content_length))
text = request.get("text", "")
voice = request.get("voice", VOICE_ALIAS)
output_format = request.get("format", "mp3")
speed = float(request.get("speed", 1.0))
except (json.JSONDecodeError, TypeError, ValueError):
self.send_json(HTTPStatus.BAD_REQUEST, {"error": "invalid JSON request"})
return
if not isinstance(text, str) or not text.strip() or len(text) > MAX_TEXT_CHARS:
self.send_json(HTTPStatus.BAD_REQUEST, {"error": "invalid text"})
return
if voice != VOICE_ALIAS:
self.send_json(HTTPStatus.BAD_REQUEST, {"error": "unknown voice"})
return
if output_format not in {"wav", "mp3"}:
self.send_json(HTTPStatus.BAD_REQUEST, {"error": "unsupported format"})
return
if not 0.5 <= speed <= 2.0:
self.send_json(HTTPStatus.BAD_REQUEST, {"error": "invalid speed"})
return
try:
audio = synthesize_wav(text.strip(), speed)
if output_format == "mp3":
audio = wav_to_mp3(audio)
content_type = "audio/mpeg"
else:
content_type = "audio/wav"
except (OSError, RuntimeError, subprocess.SubprocessError):
self.send_json(HTTPStatus.INTERNAL_SERVER_ERROR, {"error": "synthesis failed"})
return
self.send_bytes(HTTPStatus.OK, audio, content_type)
if __name__ == "__main__":
print(f"Piper worker ready: {VOICE_NAME} as {VOICE_ALIAS} on {HOST}:{PORT}")
ThreadingHTTPServer((HOST, PORT), Handler).serve_forever()
@@ -230,7 +230,7 @@ def set_image_worker(running: bool, kind: str = IMAGE_WORKER) -> dict:
for profile_item in containers().values(): for profile_item in containers().values():
stop_container(profile_item) stop_container(profile_item)
# The 9B beta text encoder temporarily borrows the RTX 3060 from # The 9B beta text encoder temporarily borrows the RTX 3060 from
# Qwen3-TTS. The gateway retains Piper as a fallback meanwhile. # Qwen3-TTS. TTS is unavailable during this exclusive GPU phase.
stop_container(tts_container(), timeout=30) stop_container(tts_container(), timeout=30)
stop_music_if_configured() stop_music_if_configured()
stop_separator_if_configured() stop_separator_if_configured()
@@ -244,25 +244,20 @@ class LanguageSegmentationTests(unittest.TestCase):
self.assertTrue(all(len(part.split()) > 4 for _, part in segments)) self.assertTrue(all(len(part.split()) > 4 for _, part in segments))
class FallbackTests(unittest.TestCase): class BackendFailureTests(unittest.TestCase):
def setUp(self): def setUp(self):
self.original_xtts = gateway.synthesize_xtts self.original_qwen = gateway.synthesize_qwen
self.original_piper = gateway.synthesize_piper
def tearDown(self): def tearDown(self):
gateway.synthesize_xtts = self.original_xtts gateway.synthesize_qwen = self.original_qwen
gateway.synthesize_piper = self.original_piper
def test_piper_is_used_when_xtts_fails(self): def test_qwen_failure_is_reported_without_fallback(self):
def fail(*_args): def fail(*_args):
raise RuntimeError("synthetic XTTS failure") raise RuntimeError("synthetic Qwen failure")
gateway.synthesize_xtts = fail gateway.synthesize_qwen = fail
gateway.synthesize_piper = lambda *_args: (b"piper", "audio/wav") with self.assertRaisesRegex(RuntimeError, "synthetic Qwen failure"):
self.assertEqual( gateway.synthesize("synthetic test", "wav", 1.0)
gateway.synthesize("synthetic test", "wav", 1.0),
(b"piper", "audio/wav"),
)
class AudioJoinTests(unittest.TestCase): class AudioJoinTests(unittest.TestCase):
+15 -33
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Private Qwen3-TTS-first gateway with a Piper fallback. """Private Qwen3-TTS gateway.
The gateway implements the narrow /status and /tts protocol already consumed The gateway implements the narrow /status and /tts protocol already consumed
by the profile router. Request text is never logged or persisted. by the profile router. Request text is never logged or persisted.
@@ -33,7 +33,6 @@ QWEN_TTS_VOICE = os.getenv("QWEN_TTS_VOICE", "serena")
QWEN_TTS_LANGUAGE = os.getenv("QWEN_TTS_LANGUAGE", "German") QWEN_TTS_LANGUAGE = os.getenv("QWEN_TTS_LANGUAGE", "German")
QWEN_TTS_TIMEOUT = float(os.getenv("QWEN_TTS_TIMEOUT", "120")) QWEN_TTS_TIMEOUT = float(os.getenv("QWEN_TTS_TIMEOUT", "120"))
XTTS_URL = os.getenv("XTTS_URL", "http://xtts:80").rstrip("/") XTTS_URL = os.getenv("XTTS_URL", "http://xtts:80").rstrip("/")
PIPER_URL = os.getenv("PIPER_URL", "http://piper:8085").rstrip("/")
VOICE_ALIAS = os.getenv("TTS_VOICE_ALIAS", "alloy") VOICE_ALIAS = os.getenv("TTS_VOICE_ALIAS", "alloy")
XTTS_SPEAKER = os.getenv("XTTS_SPEAKER", "Annmarie Nele") XTTS_SPEAKER = os.getenv("XTTS_SPEAKER", "Annmarie Nele")
DEFAULT_LANGUAGE = os.getenv("TTS_DEFAULT_LANGUAGE", "de") DEFAULT_LANGUAGE = os.getenv("TTS_DEFAULT_LANGUAGE", "de")
@@ -43,7 +42,6 @@ MAX_TEXT_CHARS = int(os.getenv("TTS_MAX_TEXT_CHARS", "8000"))
MAX_REQUEST_BYTES = int(os.getenv("TTS_MAX_REQUEST_BYTES", "65536")) MAX_REQUEST_BYTES = int(os.getenv("TTS_MAX_REQUEST_BYTES", "65536"))
MAX_AUDIO_BYTES = int(os.getenv("TTS_MAX_AUDIO_BYTES", str(64 * 1024 * 1024))) MAX_AUDIO_BYTES = int(os.getenv("TTS_MAX_AUDIO_BYTES", str(64 * 1024 * 1024)))
XTTS_TIMEOUT = float(os.getenv("XTTS_TIMEOUT", "120")) XTTS_TIMEOUT = float(os.getenv("XTTS_TIMEOUT", "120"))
PIPER_TIMEOUT = float(os.getenv("PIPER_TIMEOUT", "120"))
QUEUE_TIMEOUT = float(os.getenv("XTTS_QUEUE_TIMEOUT", "15")) QUEUE_TIMEOUT = float(os.getenv("XTTS_QUEUE_TIMEOUT", "15"))
# XTTS loses natural prosody when a sentence is synthesized as many tiny # XTTS loses natural prosody when a sentence is synthesized as many tiny
# requests: every request starts a fresh utterance. Keep complete sentences # requests: every request starts a fresh utterance. Keep complete sentences
@@ -63,8 +61,7 @@ SPEAKER_LOCK = threading.Lock()
SPEAKER_CONDITIONING: dict | None = None SPEAKER_CONDITIONING: dict | None = None
STATE = { STATE = {
"last_backend": None, "last_backend": None,
"xtts_failures": 0, "qwen_failures": 0,
"piper_fallbacks": 0,
"last_error": None, "last_error": None,
} }
@@ -738,18 +735,6 @@ def synthesize_xtts(text: str, output_format: str,
return _convert(_wav(_join_pcm(pcm_parts)), output_format, speed) return _convert(_wav(_join_pcm(pcm_parts)), output_format, speed)
def synthesize_piper(text: str, output_format: str,
speed: float) -> tuple[bytes, str]:
upstream_format = "wav" if output_format == "pcm" else output_format
audio, content_type = _request(
f"{PIPER_URL}/tts",
payload={"text": text, "voice": "alloy", "speed": speed,
"format": upstream_format},
timeout=PIPER_TIMEOUT,
)
return _convert(audio, "pcm", 1.0) if output_format == "pcm" else (audio, content_type)
def synthesize_qwen(text: str, output_format: str, def synthesize_qwen(text: str, output_format: str,
speed: float) -> tuple[bytes, str]: speed: float) -> tuple[bytes, str]:
text = prepare_for_qwen_speech(text) text = prepare_for_qwen_speech(text)
@@ -814,22 +799,18 @@ def synthesize(text: str, output_format: str, speed: float) -> tuple[bytes, str]
STATE["last_backend"] = "qwen3-tts-1.7b" STATE["last_backend"] = "qwen3-tts-1.7b"
STATE["last_error"] = None STATE["last_error"] = None
return audio return audio
except Exception as exc: # fallback must cover all Qwen failures except Exception as exc:
with STATE_LOCK: with STATE_LOCK:
STATE["xtts_failures"] += 1 STATE["qwen_failures"] += 1
STATE["last_error"] = type(exc).__name__ STATE["last_error"] = type(exc).__name__
raise
finally: finally:
SYNTHESIS_LOCK.release() SYNTHESIS_LOCK.release()
else: else:
with STATE_LOCK: with STATE_LOCK:
STATE["xtts_failures"] += 1 STATE["qwen_failures"] += 1
STATE["last_error"] = "queue-timeout" STATE["last_error"] = "queue-timeout"
raise RuntimeError("speech queue timeout")
audio = synthesize_piper(text, output_format, speed)
with STATE_LOCK:
STATE["last_backend"] = "piper"
STATE["piper_fallbacks"] += 1
return audio
class Handler(BaseHTTPRequestHandler): class Handler(BaseHTTPRequestHandler):
@@ -856,19 +837,20 @@ class Handler(BaseHTTPRequestHandler):
self.send_json(HTTPStatus.NOT_FOUND, {"error": "not found"}) self.send_json(HTTPStatus.NOT_FOUND, {"error": "not found"})
return return
primary_ready = _reachable(QWEN_TTS_URL, "/health") primary_ready = _reachable(QWEN_TTS_URL, "/health")
fallback_ready = _reachable(PIPER_URL, "/status")
with STATE_LOCK: with STATE_LOCK:
state = dict(STATE) state = dict(STATE)
# This endpoint is also the container liveness check. Qwen3-TTS is
# deliberately stopped in exclusive GPU modes such as Applio, so the
# gateway itself must stay healthy while reporting ready=false.
self.send_json( self.send_json(
HTTPStatus.OK if fallback_ready else HTTPStatus.SERVICE_UNAVAILABLE, HTTPStatus.OK,
{ {
"ready": fallback_ready, "ready": primary_ready,
"engine": "qwen3-tts-with-piper-fallback", "engine": "qwen3-tts",
"model": "Qwen3-TTS-12Hz-1.7B-Base", "model": "Qwen3-TTS-12Hz-1.7B-Base",
"voices": [VOICE_ALIAS], "voices": [VOICE_ALIAS],
"speaker": QWEN_TTS_VOICE, "speaker": QWEN_TTS_VOICE,
"primary_ready": primary_ready, "primary_ready": primary_ready,
"fallback_ready": fallback_ready,
**state, **state,
}, },
) )
@@ -916,7 +898,7 @@ class Handler(BaseHTTPRequestHandler):
with STATE_LOCK: with STATE_LOCK:
STATE["last_error"] = type(exc).__name__ STATE["last_error"] = type(exc).__name__
self.send_json(HTTPStatus.SERVICE_UNAVAILABLE, self.send_json(HTTPStatus.SERVICE_UNAVAILABLE,
{"error": "all local speech backends failed"}) {"error": "local Qwen3-TTS backend failed"})
return return
print(f"tts-gateway: synthesized via {STATE['last_backend']} in " print(f"tts-gateway: synthesized via {STATE['last_backend']} in "
f"{time.monotonic() - started:.2f}s") f"{time.monotonic() - started:.2f}s")
@@ -992,5 +974,5 @@ class Handler(BaseHTTPRequestHandler):
if __name__ == "__main__": if __name__ == "__main__":
print(f"TTS gateway ready on {HOST}:{PORT}; primary={QWEN_TTS_VOICE}; fallback=Piper") print(f"TTS gateway ready on {HOST}:{PORT}; backend=Qwen3-TTS; voice={QWEN_TTS_VOICE}")
ThreadingHTTPServer((HOST, PORT), Handler).serve_forever() ThreadingHTTPServer((HOST, PORT), Handler).serve_forever()
@@ -76,7 +76,6 @@ start_proxy() {
start_proxy 22 172.30.10.1:22 start_proxy 22 172.30.10.1:22
start_proxy 8081 router:8081 start_proxy 8081 router:8081
start_proxy 8085 tts-gateway:8085 start_proxy 8085 tts-gateway:8085
start_proxy 8091 piper:8085
start_proxy 8099 llama-dashboard:8099 start_proxy 8099 llama-dashboard:8099
start_proxy 7861 music-ui:3000 start_proxy 7861 music-ui:3000
start_proxy 7862 music-worker:7860 start_proxy 7862 music-worker:7860
+2 -3
View File
@@ -144,13 +144,12 @@ stt:
model: "base" model: "base"
language: "de" language: "de"
# Reuse Athena's OpenAI-compatible TTS route. It currently serves XTTS v2 with # Reuse Athena's OpenAI-compatible Qwen3-TTS route.
# Annmarie Nele and transparently falls back to Piper when XTTS is unavailable.
tts: tts:
provider: "openai" provider: "openai"
speed: 1.0 speed: 1.0
openai: openai:
model: "piper" model: "qwen3-tts"
voice: "alloy" voice: "alloy"
speed: 1.0 speed: 1.0
base_url: "http://router:8081/v1" base_url: "http://router:8081/v1"
@@ -25,7 +25,7 @@ image worker, then restores the previous LLM state.
Only one heavy GPU path may be active. Do not manually start a second GPU Only one heavy GPU path may be active. Do not manually start a second GPU
worker around the controller. The lightweight dashboard, router, controller, worker around the controller. The lightweight dashboard, router, controller,
gateway, UI, CPU-STT, Piper, backup and operator containers may remain active. gateway, UI, CPU-STT, backup and operator containers may remain active.
## Model profiles ## Model profiles
@@ -33,7 +33,6 @@ gateway, UI, CPU-STT, Piper, backup and operator containers may remain active.
- Medium: Qwen3.8-27B IQ4_XS-pure, 160,000 tokens, vision. - Medium: Qwen3.8-27B IQ4_XS-pure, 160,000 tokens, vision.
- Large: the same Q4 model, 192,000 tokens, vision. - Large: the same Q4 model, 192,000 tokens, vision.
- Ultra: the same Q4 model, 262,144 tokens, no vision projector. - Ultra: the same Q4 model, 262,144 tokens, no vision projector.
- Beta 1: Qwen3.8-27B GSQ-RCO IQ3_S-MTP, 112,000 tokens; experimental only.
- Uncensored: Abliterated Q4_K_M, 80,000 tokens, vision. - Uncensored: Abliterated Q4_K_M, 80,000 tokens, vision.
Medium, Large, Ultra and Beta distribute their runtime across both GPUs. Do not Medium, Large, Ultra and Beta distribute their runtime across both GPUs. Do not
@@ -163,10 +163,10 @@ log "Tool-Container mit der neuen Stackdefinition aktivieren"
log "Router und OpenWebUI mit den wiederhergestellten Schlüsseln neu erstellen" log "Router und OpenWebUI mit den wiederhergestellten Schlüsseln neu erstellen"
cd "$STACK_DIR" cd "$STACK_DIR"
docker compose --env-file "$SECRETS_DIR/stack.env" up -d --force-recreate \ docker compose --env-file "$SECRETS_DIR/stack.env" up -d --force-recreate \
qwen3-tts piper tts-gateway router open-webui qwen3-tts tts-gateway router open-webui
deadline=$((SECONDS + 180)) deadline=$((SECONDS + 180))
for container in mike-ai-qwen3-tts mike-ai-piper mike-ai-tts-gateway \ for container in mike-ai-qwen3-tts mike-ai-tts-gateway \
mike-ai-router mike-ai-open-webui; do mike-ai-router mike-ai-open-webui; do
until [[ $(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' \ until [[ $(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' \
"$container" 2>/dev/null || true) == healthy ]]; do "$container" 2>/dev/null || true) == healthy ]]; do
+1 -1
View File
@@ -83,7 +83,7 @@ with con:
for key, value in ( for key, value in (
("task.follow_up.enable", False), ("task.follow_up.enable", False),
("audio.tts.engine", "openai"), ("audio.tts.engine", "openai"),
("audio.tts.model", "piper"), ("audio.tts.model", "qwen3-tts"),
("audio.tts.voice", "alloy"), ("audio.tts.voice", "alloy"),
("audio.tts.openai.api_base_url", "http://router:8081/v1"), ("audio.tts.openai.api_base_url", "http://router:8081/v1"),
("web.search.enable", True), ("web.search.enable", True),
+2 -2
View File
@@ -7,9 +7,9 @@ SERVICE="${LLAMA_SERVICE:-mike-ai-llama-ui.service}"
PROFILE="${1:-}" PROFILE="${1:-}"
case "$PROFILE" in case "$PROFILE" in
fast|medium|beta1|large|ultra|uncensored) ;; fast|medium|large|ultra|uncensored) ;;
*) *)
echo "Usage: llama-profile {fast|medium|beta1|large|ultra|uncensored}" >&2 echo "Usage: llama-profile {fast|medium|large|ultra|uncensored}" >&2
exit 2 exit 2
;; ;;
esac esac
+1 -2
View File
@@ -60,7 +60,7 @@ backup_root=$work/backup
[[ -d $backup_root/volumes ]] || die "Backup enthält keine Docker-Volumes." [[ -d $backup_root/volumes ]] || die "Backup enthält keine Docker-Volumes."
# Stop only users of the restored volumes. WireGuard, SSH and networking stay up. # Stop only users of the restored volumes. WireGuard, SSH and networking stay up.
for container in mike-ai-router mike-ai-profile-controller mike-ai-piper mike-ai-portainer; do for container in mike-ai-router mike-ai-profile-controller mike-ai-portainer; do
if [[ $(docker inspect -f '{{.State.Running}}' "$container" 2>/dev/null || true) == true ]]; then if [[ $(docker inspect -f '{{.State.Running}}' "$container" 2>/dev/null || true) == true ]]; then
docker stop "$container" >/dev/null docker stop "$container" >/dev/null
fi fi
@@ -78,7 +78,6 @@ restore_volume() {
rsync -a --delete "$source/" "$mountpoint/" rsync -a --delete "$source/" "$mountpoint/"
} }
restore_volume mike-ai_piper-data "$backup_root/volumes/piper-data"
restore_volume mike-ai_router-state "$backup_root/volumes/router-state" restore_volume mike-ai_router-state "$backup_root/volumes/router-state"
restore_volume mike-ai_router-images "$backup_root/volumes/router-images" restore_volume mike-ai_router-images "$backup_root/volumes/router-images"
restore_volume portainer_data "$backup_root/volumes/portainer-data" restore_volume portainer_data "$backup_root/volumes/portainer-data"
+11 -12
View File
@@ -2,21 +2,20 @@
"""AI Profile Router – OpenAI-kompatibler Proxy vor llama.cpp. """AI Profile Router – OpenAI-kompatibler Proxy vor llama.cpp.
Leitet OpenAI-kompatible Requests transparent an den lokalen llama.cpp-Server Leitet OpenAI-kompatible Requests transparent an den lokalen llama.cpp-Server
weiter (Streaming, Tool Calls, JSON) und schaltet zwischen sechs festen weiter (Streaming, Tool Calls, JSON) und schaltet zwischen fünf festen
Profilen um: Profilen um:
Profil Kontext Profil Kontext
------ -------- ------ --------
fast 76800 fast 76800
medium 160000 medium 160000
beta1 192000
large 192000 large 192000
ultra 262144 ultra 262144
uncensored 80000 uncensored 80000
Virtuelle Modelle: qwen-fast, qwen-medium, qwen-beta-1, qwen-large, Virtuelle Modelle: qwen-fast, qwen-medium, qwen-large,
qwen-ultra, qwen-uncensored qwen-ultra, qwen-uncensored
Kommandos: POST /fast, /medium, /beta1, /large, /ultra, Kommandos: POST /fast, /medium, /large, /ultra,
/uncensored /uncensored
GET /status (Zustand) GET /status (Zustand)
@@ -26,7 +25,7 @@ Bildgenerierung und Editing (FLUX.2 Klein 9B FP8 beta):
GET /images (Liste) GET /images (Liste)
GET /images/<datei> (PNG-Download) GET /images/<datei> (PNG-Download)
Sprachausgabe (Qwen3-TTS auf RTX 3060, Piper als CPU-Fallback): Sprachausgabe (Qwen3-TTS auf RTX 3060):
POST /v1/audio/speech (OpenAI-kompatibel) POST /v1/audio/speech (OpenAI-kompatibel)
GET /v1/audio/voices (verfügbare Stimmen) GET /v1/audio/voices (verfügbare Stimmen)
@@ -177,15 +176,15 @@ IMAGE_QUALITY = {"standard": 4, "high": 4}
IMAGE_DEFAULT_QUALITY = "standard" IMAGE_DEFAULT_QUALITY = "standard"
IMAGE_MAX_N = 4 IMAGE_MAX_N = 4
# --- Sprachausgabe (austauschbarer interner TTS-Worker, CPU-only) --- # --- Sprachausgabe (Qwen3-TTS über das interne Normalisierungs-Gateway) ---
TTS_WORKER_URL = os.environ.get("TTS_WORKER_URL", "http://127.0.0.1:8085") TTS_WORKER_URL = os.environ.get("TTS_WORKER_URL", "http://127.0.0.1:8085")
TTS_TIMEOUT = float(os.environ.get("TTS_TIMEOUT", "300")) # s, pro Synthese TTS_TIMEOUT = float(os.environ.get("TTS_TIMEOUT", "300")) # s, pro Synthese
TTS_CONNECT_TIMEOUT = float(os.environ.get("TTS_CONNECT_TIMEOUT", "5")) TTS_CONNECT_TIMEOUT = float(os.environ.get("TTS_CONNECT_TIMEOUT", "5"))
TTS_MODEL = os.environ.get("TTS_MODEL", "xtts-v2") TTS_MODEL = os.environ.get("TTS_MODEL", "qwen3-tts")
TTS_VOICES = tuple(v.strip() for v in os.environ.get( TTS_VOICES = tuple(v.strip() for v in os.environ.get(
"TTS_VOICES", "claribel").split(",") if v.strip()) "TTS_VOICES", "alloy").split(",") if v.strip())
TTS_DEFAULT_VOICE = os.environ.get( TTS_DEFAULT_VOICE = os.environ.get(
"TTS_DEFAULT_VOICE", TTS_VOICES[0] if TTS_VOICES else "claribel") "TTS_DEFAULT_VOICE", TTS_VOICES[0] if TTS_VOICES else "alloy")
TTS_FORMATS = ("mp3", "wav", "pcm") TTS_FORMATS = ("mp3", "wav", "pcm")
TTS_DEFAULT_FORMAT = "mp3" TTS_DEFAULT_FORMAT = "mp3"
@@ -2363,7 +2362,7 @@ class Handler(BaseHTTPRequestHandler):
self.end_headers() self.end_headers()
self.wfile.write(data) self.wfile.write(data)
# ---------- Sprachausgabe (Qwen3-TTS mit Piper-Fallback) ---------- # ---------- Sprachausgabe (Qwen3-TTS) ----------
def _speech(self) -> None: def _speech(self) -> None:
try: try:
@@ -2422,7 +2421,7 @@ class Handler(BaseHTTPRequestHandler):
"invalid_request_error", "invalid_speed") "invalid_request_error", "invalid_speed")
return return
# Modell-Name optional; falls angegeben, muss es xtts-v2 sein. # Modell-Name optional; falls angegeben, muss es Qwen3-TTS sein.
model = data.get("model") model = data.get("model")
if model is not None and model != TTS_MODEL: if model is not None and model != TTS_MODEL:
self._send_error(400, f"unbekanntes Modell: {model!r} " self._send_error(400, f"unbekanntes Modell: {model!r} "
@@ -2532,7 +2531,7 @@ class Handler(BaseHTTPRequestHandler):
models.append({ models.append({
"id": TTS_MODEL, "id": TTS_MODEL,
"object": "model", "object": "model",
"owned_by": "coqui-xtts", "owned_by": "qwen",
"type": "speech", "type": "speech",
}) })
return {"object": "list", "data": models} return {"object": "list", "data": models}
-4
View File
@@ -8,10 +8,6 @@
"context": 160000, "context": 160000,
"model_alias": "qwen-medium" "model_alias": "qwen-medium"
}, },
"beta1": {
"context": 112000,
"model_alias": "qwen-beta-1"
},
"large": { "large": {
"context": 192000, "context": 192000,
"model_alias": "qwen-large" "model_alias": "qwen-large"
+1 -1
View File
@@ -35,7 +35,7 @@ def main() -> int:
speech = request( speech = request(
"/audio/speech", "/audio/speech",
json.dumps({ json.dumps({
"model": "piper", "model": "qwen3-tts",
"voice": "alloy", "voice": "alloy",
"response_format": "wav", "response_format": "wav",
"input": TEST_TEXT, "input": TEST_TEXT,
+1 -2
View File
@@ -27,7 +27,6 @@ for name in \
mike-ai-wireguard-gateway \ mike-ai-wireguard-gateway \
mike-ai-profile-controller \ mike-ai-profile-controller \
mike-ai-router \ mike-ai-router \
mike-ai-piper \
mike-ai-qwen3-tts \ mike-ai-qwen3-tts \
mike-ai-tts-gateway \ mike-ai-tts-gateway \
mike-ai-llama-dashboard \ mike-ai-llama-dashboard \
@@ -45,7 +44,7 @@ done
pass "Dashboard und Portainer besitzen stabile Netzwerk-Namespaces" pass "Dashboard und Portainer besitzen stabile Netzwerk-Namespaces"
active_llama=$(docker ps --format '{{.Names}}' | \ active_llama=$(docker ps --format '{{.Names}}' | \
grep -Ec '^mike-ai-llama-(fast|medium|beta1|large|ultra|uncensored)$' || true) grep -Ec '^mike-ai-llama-(fast|medium|large|ultra|uncensored)$' || true)
[[ $active_llama -eq 1 ]] || fail "$active_llama aktive llama-Profile (erwartet: 1)" [[ $active_llama -eq 1 ]] || fail "$active_llama aktive llama-Profile (erwartet: 1)"
pass "Genau ein llama.cpp-Profil ist aktiv" pass "Genau ein llama.cpp-Profil ist aktiv"