feat(tts): default to offline GPU MeloTTS with brain-role worker
Switch the default TTS engine from online Edge TTS to offline GPU MeloTTS (privacy-first: reply text never leaves the host). Adds a supervisord melo-worker program that runs in full/bot/brain roles and synthesises Korean on the GPU (MELO_DEVICE=cuda, MELO_SPEED=1.5), a baked /opt/melo py3.11 venv layer in the Dockerfile, and compose/env defaults. Edge TTS stays available as an opt-in online fallback via TTS_ENGINE=edge. Verified on the RTX 5050 (Blackwell sm_120): melo-worker logs "ready (lang=KR speed=1.5 device=cuda)", /health returns ok, and a /synth round-trip returns a 177KB WAV. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
20
.env.example
20
.env.example
@@ -36,12 +36,20 @@ WHISPER_DEVICE=cuda
|
|||||||
WHISPER_COMPUTE_TYPE=float16
|
WHISPER_COMPUTE_TYPE=float16
|
||||||
# Optional explicit Piper voice model (.onnx). If empty, the jarvis default is used.
|
# Optional explicit Piper voice model (.onnx). If empty, the jarvis default is used.
|
||||||
TTS_PIPER_MODEL_PATH=
|
TTS_PIPER_MODEL_PATH=
|
||||||
# TTS engine: "edge" (default) uses Microsoft Edge TTS, a natural Korean neural
|
# TTS engine:
|
||||||
# voice. Set to "piper" for the offline English voice. NOTE: edge is ONLINE —
|
# melo (default) GPU MeloTTS — offline Korean voice, privacy-first (text never
|
||||||
# reply text is sent to Microsoft's servers and needs internet.
|
# leaves the host), synthesises on the GPU via the melo-worker. Needs a
|
||||||
TTS_ENGINE=edge
|
# GPU (MELO_DEVICE=cuda); falls back to CPU (slow) with MELO_DEVICE=cpu.
|
||||||
# Edge voice + speaking rate. Rate is a percentage (+45% ≈ 1.45×). Korean voices:
|
# edge Microsoft Edge TTS — natural Korean neural voice, but ONLINE: reply
|
||||||
# ko-KR-HyunsuMultilingualNeural (M), ko-KR-InJoonNeural (M), ko-KR-SunHiNeural (F).
|
# text is sent to Microsoft's servers and needs internet.
|
||||||
|
# piper offline English voice (fallback).
|
||||||
|
TTS_ENGINE=melo
|
||||||
|
# GPU MeloTTS knobs (used when TTS_ENGINE=melo).
|
||||||
|
MELO_DEVICE=cuda
|
||||||
|
MELO_SPEED=1.5
|
||||||
|
# Edge voice + speaking rate (used when TTS_ENGINE=edge). Rate is a percentage
|
||||||
|
# (+45% ≈ 1.45×). Korean voices: ko-KR-HyunsuMultilingualNeural (M),
|
||||||
|
# ko-KR-InJoonNeural (M), ko-KR-SunHiNeural (F).
|
||||||
EDGE_TTS_VOICE=ko-KR-HyunsuMultilingualNeural
|
EDGE_TTS_VOICE=ko-KR-HyunsuMultilingualNeural
|
||||||
EDGE_TTS_RATE=+45%
|
EDGE_TTS_RATE=+45%
|
||||||
# Neural-only by default: if synthesis fails the bridge returns no audio rather
|
# Neural-only by default: if synthesis fails the bridge returns no audio rather
|
||||||
|
|||||||
17
Dockerfile
17
Dockerfile
@@ -65,10 +65,19 @@ RUN ls -d /opt/venv/lib/python*/site-packages/nvidia/cublas/lib \
|
|||||||
> /etc/ld.so.conf.d/nvidia-cu12.conf 2>/dev/null \
|
> /etc/ld.so.conf.d/nvidia-cu12.conf 2>/dev/null \
|
||||||
&& /sbin/ldconfig || true
|
&& /sbin/ldconfig || true
|
||||||
|
|
||||||
# --- Korean voice: Microsoft Edge TTS (online neural). No model is baked — the
|
# --- Korean voice: GPU MeloTTS (offline, privacy-first) in a separate /opt/melo
|
||||||
# `edge-tts` pip package (in requirements-bridge.txt) calls the MS service at
|
# py3.11 venv; see docker/setup-melo.sh. Heavy layer (torch cu128 GPU +
|
||||||
# runtime and the bridge transcodes the MP3 to PCM16 with ffmpeg. No heavy
|
# transformers + MeCab + baked KR model), placed before the app COPY so it
|
||||||
# TTS build layer is needed. ---
|
# stays cached across source-only changes. Runtime device is MELO_DEVICE=cuda
|
||||||
|
# (compose), so TTS synthesises on the GPU next to Ollama + Whisper. Started
|
||||||
|
# only in full/bot/brain roles (see docker/supervisord.conf). The offline
|
||||||
|
# Edge TTS (`edge-tts`, in requirements-bridge.txt) remains available as an
|
||||||
|
# opt-in fallback via TTS_ENGINE=edge, but it is ONLINE (text leaves the
|
||||||
|
# host), so MeloTTS is the default. ---
|
||||||
|
COPY docker/setup-melo.sh /app/docker/setup-melo.sh
|
||||||
|
# Strip CR first: a Windows (autocrlf) checkout yields CRLF, which makes bash
|
||||||
|
# read `set -euxo pipefail\r` and abort. Idempotent on an LF tree.
|
||||||
|
RUN sed -i 's/\r$//' /app/docker/setup-melo.sh && bash /app/docker/setup-melo.sh
|
||||||
|
|
||||||
# --- Human input + window management for the on-screen Chrome control tool.
|
# --- Human input + window management for the on-screen Chrome control tool.
|
||||||
# xdotool injects real X pointer/keyboard events (visible cursor,
|
# xdotool injects real X pointer/keyboard events (visible cursor,
|
||||||
|
|||||||
@@ -87,11 +87,17 @@ services:
|
|||||||
WHISPER_DEVICE: ${WHISPER_DEVICE:-cuda}
|
WHISPER_DEVICE: ${WHISPER_DEVICE:-cuda}
|
||||||
WHISPER_COMPUTE_TYPE: ${WHISPER_COMPUTE_TYPE:-float16}
|
WHISPER_COMPUTE_TYPE: ${WHISPER_COMPUTE_TYPE:-float16}
|
||||||
# TTS engine. Rendered into /app/config/jarvis.json via envsubst (the
|
# TTS engine. Rendered into /app/config/jarvis.json via envsubst (the
|
||||||
# bridge reads that JSON BEFORE the env, so it must carry the real engine,
|
# bridge reads that JSON BEFORE the env, so it must carry the real engine).
|
||||||
# not a hardcoded one — otherwise Korean text is read by the English Piper
|
# Default melo: GPU MeloTTS (offline, privacy-first) via the melo-worker.
|
||||||
# voice). Default edge; .env can override (e.g. piper for offline).
|
# Override with TTS_ENGINE=edge for the online Microsoft neural voice (text
|
||||||
TTS_ENGINE: ${TTS_ENGINE:-edge}
|
# leaves the host) or piper for the offline English voice.
|
||||||
# Edge TTS voice + rate (the chosen natural Korean voice). NOTE: edge is an
|
TTS_ENGINE: ${TTS_ENGINE:-melo}
|
||||||
|
# GPU MeloTTS worker knobs. MELO_DEVICE=cuda runs synth on the GPU (next to
|
||||||
|
# Ollama + Whisper); cpu is the no-GPU fallback. MELO_SPEED must always be
|
||||||
|
# set — supervisord's melo-worker expands %(ENV_MELO_SPEED)s at startup.
|
||||||
|
MELO_DEVICE: ${MELO_DEVICE:-cuda}
|
||||||
|
MELO_SPEED: ${MELO_SPEED:-1.5}
|
||||||
|
# Edge TTS voice + rate (used only when TTS_ENGINE=edge). NOTE: edge is an
|
||||||
# ONLINE engine — reply text is sent to Microsoft and needs internet.
|
# ONLINE engine — reply text is sent to Microsoft and needs internet.
|
||||||
EDGE_TTS_VOICE: ${EDGE_TTS_VOICE:-ko-KR-HyunsuMultilingualNeural}
|
EDGE_TTS_VOICE: ${EDGE_TTS_VOICE:-ko-KR-HyunsuMultilingualNeural}
|
||||||
EDGE_TTS_RATE: ${EDGE_TTS_RATE:-+45%}
|
EDGE_TTS_RATE: ${EDGE_TTS_RATE:-+45%}
|
||||||
|
|||||||
@@ -49,8 +49,27 @@ stdout_logfile_maxbytes=0
|
|||||||
stderr_logfile=/dev/stderr
|
stderr_logfile=/dev/stderr
|
||||||
stderr_logfile_maxbytes=0
|
stderr_logfile_maxbytes=0
|
||||||
|
|
||||||
# (No TTS worker program: the default Edge TTS engine synthesises in-process in
|
[program:melo-worker]
|
||||||
# the bridge via the `edge-tts` package — no warm model/worker is needed.)
|
; Warm GPU MeloTTS Korean voice (offline) in its own py3.11 venv (/opt/melo). The
|
||||||
|
; bridge's synthesize() POSTs here when TTS_ENGINE=melo. Runs wherever the bridge
|
||||||
|
; runs (full/bot/brain) so a GPU brain host serves TTS on the GPU. If TTS_ENGINE
|
||||||
|
; =edge the bridge synthesises in-process and never calls this worker.
|
||||||
|
command=/app/docker/run-if-role.sh full,bot,brain /opt/melo/bin/python /app/bridge/melo_worker.py
|
||||||
|
directory=/app
|
||||||
|
; HF_HOME points at the dedicated, image-baked melo cache (warmed in
|
||||||
|
; setup-melo.sh). The whisper_cache volume is mounted over /root/.cache/huggingface,
|
||||||
|
; so without this the pre-cached BERT + KR checkpoint would be shadowed and
|
||||||
|
; re-downloaded (and would fail offline). HF_HUB_OFFLINE/TRANSFORMERS_OFFLINE
|
||||||
|
; force pure-cache reads. MELO_DEVICE/MELO_SPEED inherit from the container env
|
||||||
|
; (compose sets both with defaults cuda / 1.5), so MELO_SPEED must always be set
|
||||||
|
; in the env or this %(ENV_x)s expansion fails at startup (compose guarantees it).
|
||||||
|
environment=MELO_LANGUAGE="KR",MELO_SPEED="%(ENV_MELO_SPEED)s",MELO_DEVICE="%(ENV_MELO_DEVICE)s",MELO_WORKER_HOST="127.0.0.1",MELO_WORKER_PORT="8770",HF_HOME="/opt/melo-cache",HF_HUB_OFFLINE="1",TRANSFORMERS_OFFLINE="1"
|
||||||
|
priority=280
|
||||||
|
autorestart=true
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
|
||||||
[program:bridge]
|
[program:bridge]
|
||||||
command=/app/docker/run-if-role.sh full,bot,brain /opt/venv/bin/python -m bridge.server
|
command=/app/docker/run-if-role.sh full,bot,brain /opt/venv/bin/python -m bridge.server
|
||||||
|
|||||||
@@ -100,13 +100,16 @@ BRIDGE_HOST=0.0.0.0 # bridge listens on the LAN
|
|||||||
SETTINGS_PUBLISH_BIND=0.0.0.0 # publish bridge port 8765 to the LAN
|
SETTINGS_PUBLISH_BIND=0.0.0.0 # publish bridge port 8765 to the LAN
|
||||||
OLLAMA_BASE_URL=http://192.168.10.9:11434 # Ollama on this host (bind 0.0.0.0)
|
OLLAMA_BASE_URL=http://192.168.10.9:11434 # Ollama on this host (bind 0.0.0.0)
|
||||||
OLLAMA_CHAT_MODEL=qwen2.5:7b
|
OLLAMA_CHAT_MODEL=qwen2.5:7b
|
||||||
WHISPER_DEVICE=cuda
|
WHISPER_DEVICE=cuda # STT on the GPU
|
||||||
WHISPER_COMPUTE_TYPE=float16
|
WHISPER_COMPUTE_TYPE=float16
|
||||||
|
TTS_ENGINE=melo # GPU MeloTTS (offline, privacy-first)
|
||||||
|
MELO_DEVICE=cuda # TTS on the GPU (melo-worker)
|
||||||
BROWSER_CONTROL_URL=http://192.168.10.5:8777 # drive the app host's Chrome
|
BROWSER_CONTROL_URL=http://192.168.10.5:8777 # drive the app host's Chrome
|
||||||
COMPOSE_FILE=docker-compose.yml:docker-compose.gpu-linux.yml # GPU (":" on Linux)
|
COMPOSE_FILE=docker-compose.yml:docker-compose.gpu-linux.yml # GPU (":" on Linux)
|
||||||
|
|
||||||
# Ollama runs on this host (systemd or a container) listening on 0.0.0.0:11434.
|
# STT (Whisper), LLM (Ollama) and TTS (MeloTTS) all run on this host's GPU.
|
||||||
docker compose up -d javis # brain bridge only (no bot, no desktop)
|
# Ollama listens on 0.0.0.0:11434 (systemd or a container).
|
||||||
|
docker compose up -d javis # brain bridge + melo-worker only (no bot, no desktop)
|
||||||
```
|
```
|
||||||
|
|
||||||
### App host (.5 — bot + browser, no GPU)
|
### App host (.5 — bot + browser, no GPU)
|
||||||
|
|||||||
Reference in New Issue
Block a user