From 927d59f8055b177d052871a37e5a210d0a3eedb2 Mon Sep 17 00:00:00 2001 From: javis-bot Date: Sun, 14 Jun 2026 02:22:36 +0900 Subject: [PATCH] perf: run MeloTTS on the GPU (cu128 torch) + warm CUDA at startup CPU MeloTTS serialised under concurrent load (whisper STT + bot) and blew voice-reply TTS to 7-8s. Install the Blackwell-verified cu128 torch in the melo venv, select the GPU via MELO_DEVICE=cuda, and do a throwaway synth at worker startup so the one-off CUDA kernel-init (~5s) doesn't land on the user's first reply. Measured: ~0.3s/sentence on GPU vs ~1.2-2.6s on CPU. Co-Authored-By: Claude Opus 4.7 --- bridge/melo_worker.py | 14 ++++++++++++++ docker-compose.yml | 3 +++ docker/setup-melo.sh | 17 ++++++++++------- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/bridge/melo_worker.py b/bridge/melo_worker.py index 073183e..4da17eb 100644 --- a/bridge/melo_worker.py +++ b/bridge/melo_worker.py @@ -66,6 +66,20 @@ def _ensure_model() -> None: speaker_id = spk_map[LANGUAGE] if LANGUAGE in spk_map else spk_map[keys[0]] _model = model _speaker_id = speaker_id + # Warm the GPU once at load: the first CUDA synth pays a one-off + # kernel-init cost (~5s) that would otherwise land on the user's + # first reply. A throwaway synth here moves it to startup. No-op + # cost on CPU. + try: + with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as _wt: + _wp = _wt.name + model.tts_to_file("워밍업", speaker_id, _wp, speed=SPEED) + try: + os.unlink(_wp) + except OSError: + pass + except Exception as _we: # pragma: no cover + print(f"[melo-worker] warmup synth skipped: {_we}", flush=True) print( f"[melo-worker] ready (lang={LANGUAGE} speed={SPEED} " f"device={DEVICE} speakers={list(spk_map.keys())})", diff --git a/docker-compose.yml b/docker-compose.yml index f7b0916..4be1b75 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -67,6 +67,9 @@ services: WHISPER_MODEL: ${WHISPER_MODEL:-medium} WHISPER_DEVICE: ${WHISPER_DEVICE:-cuda} WHISPER_COMPUTE_TYPE: ${WHISPER_COMPUTE_TYPE:-float16} + # MeloTTS on the GPU (cu128 torch baked by docker/setup-melo.sh). CPU synth + # serialised under load and pushed TTS to 7-8s; GPU does ~0.3s/sentence. + MELO_DEVICE: ${MELO_DEVICE:-cuda} # Optional single-language lock for replies (empty = user's own language). OUTPUT_LANGUAGE: ${OUTPUT_LANGUAGE:-} # Drop the pre-loop planner LLM call to cut voice-reply latency on small diff --git a/docker/setup-melo.sh b/docker/setup-melo.sh index 1a363d0..5202bd7 100755 --- a/docker/setup-melo.sh +++ b/docker/setup-melo.sh @@ -9,8 +9,11 @@ # - It isolates the heavy torch/transformers stack from the slim bridge env, # which pins numpy<2 for faster-whisper. # -# torch is pinned to the CPU build: TTS runs on CPU so the GPU stays reserved -# for Ollama + Whisper, and we avoid pulling multi-GB CUDA wheels. +# torch is the CUDA (cu128) build so MeloTTS runs on the GPU alongside Ollama + +# Whisper. CPU synth serialised under concurrent load (whisper STT + bot) and +# blew TTS up to 7-8s per reply; on the GPU a sentence synthesises in ~0.3s. +# cu128 is the Blackwell (sm_120) wheel verified on this host's RTX 5050. +# The worker selects the device via MELO_DEVICE=cuda (compose). # ============================================================================ set -euxo pipefail @@ -29,11 +32,11 @@ rm -rf /var/lib/apt/lists/* python3.11 -m venv /opt/melo /opt/melo/bin/pip install --no-cache-dir --upgrade pip wheel setuptools -# CPU-only torch first, so MeloTTS's unpinned `torch` dep is already satisfied -# and pip does not pull the CUDA build. Pinned for reproducible rebuilds (these -# are the versions the CPU index resolved when this layer was verified). -/opt/melo/bin/pip install --no-cache-dir torch==2.12.0 torchaudio==2.11.0 \ - --index-url https://download.pytorch.org/whl/cpu +# CUDA (cu128) torch first, so MeloTTS's unpinned `torch` dep is already +# satisfied with the GPU build. Pinned to the Blackwell-verified versions +# (2.11.0+cu128) for reproducible rebuilds. +/opt/melo/bin/pip install --no-cache-dir torch==2.11.0+cu128 torchaudio==2.11.0+cu128 \ + --index-url https://download.pytorch.org/whl/cu128 # MeloTTS from GitHub. The PyPI sdist is broken (its setup.py reads a # requirements.txt that is not shipped in the sdist), so install from the repo.