Remove Beta 1 and Piper fallback
This commit is contained in:
1 parent
52482627be
commit
08ff3d7c4e
38 files changed
+94
-484
No files matched your search
@@ -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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
@@ -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)"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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. |
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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`.
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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"],
|
||||||
|
|||||||
@@ -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"],
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"]
|
|
||||||
@@ -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
|
|
||||||
@@ -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):
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
@@ -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"
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user