Skip to content

Commit 9d0b139

Browse files
authored
Add Strix Halo (ROCm) support + bf16 + voice conditioning cache
Adds a separate Strix Halo Docker stack (Dockerfile.strixhalo, docker-compose-strixhalo.yml, requirements-strixhalo*.txt) and two engine-level optimizations: - TTS_BF16 env var to convert T3 to bfloat16 + run generate under torch.autocast (S3Gen stays in float32 for stability) - Voice conditioning cache keyed by (path, mtime, exaggeration), cleared on reload_model() A follow-up commit changes the TTS_BF16 default from 'auto' to 'off' so existing users see no behavior change on upgrade — opt-in via TTS_BF16=on (or TTS_BF16=auto).
1 parent 87da40c commit 9d0b139

5 files changed

Lines changed: 252 additions & 22 deletions

File tree

‎Dockerfile.strixhalo‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
FROM rocm/dev-ubuntu-22.04:latest
2+
3+
# Set environment variables
4+
ENV PYTHONDONTWRITEBYTECODE=1
5+
ENV PYTHONUNBUFFERED=1
6+
ENV DEBIAN_FRONTEND=noninteractive
7+
# Set the Hugging Face home directory for better model caching
8+
ENV HF_HOME=/app/hf_cache
9+
10+
# Install system dependencies
11+
RUN apt-get update && apt-get install -y --no-install-recommends \
12+
build-essential \
13+
libsndfile1 \
14+
ffmpeg \
15+
python3 \
16+
python3-pip \
17+
python3-dev \
18+
python3-venv \
19+
git \
20+
rocm-ml-libraries \
21+
&& apt-get clean \
22+
&& rm -rf /var/lib/apt/lists/*
23+
24+
# Create a symlink for python3 to be python for convenience
25+
RUN ln -s /usr/bin/python3 /usr/bin/python
26+
27+
# Set up working directory
28+
WORKDIR /app
29+
30+
# Copy requirements first to leverage Docker cache
31+
COPY requirements-strixhalo-init.txt ./requirements-strixhalo-init.txt
32+
COPY requirements-strixhalo.txt ./requirements-strixhalo.txt
33+
34+
# Upgrade pip and install Python dependencies
35+
RUN python3 -m pip install --no-cache-dir --upgrade pip && \
36+
pip3 install --no-cache-dir -r requirements-strixhalo.txt && \
37+
python3 -m pip install --no-cache-dir --force-reinstall -r requirements-strixhalo-init.txt
38+
39+
# Patch s3tokenizer dtype issues for torch 2.9+ compatibility:
40+
# 1. Cast numpy wav to float32 in _prepare_audio (numpy defaults to float64)
41+
# This fixes the cascade: float64 wav → float64 STFT → float64 mel → assertion failure
42+
# 2. Cast _mel_filters to match magnitudes dtype (guards against float64 checkpoint weights)
43+
RUN sed -i \
44+
's/wav = torch\.from_numpy(wav)$/wav = torch.from_numpy(wav).float()/' \
45+
/usr/local/lib/python3.10/dist-packages/chatterbox/models/s3tokenizer/s3tokenizer.py && \
46+
sed -i \
47+
's/mel_spec = self\._mel_filters\.to(self\.device) @ magnitudes/mel_spec = self._mel_filters.to(self.device).to(magnitudes.dtype) @ magnitudes/' \
48+
/usr/local/lib/python3.10/dist-packages/chatterbox/models/s3tokenizer/s3tokenizer.py
49+
50+
# Patch voice_encoder dtype issue: melspectrogram() returns numpy float64 → LSTM requires float32
51+
RUN sed -i \
52+
's/utt_embeds = self\.inference(mels\.to(self\.device),/utt_embeds = self.inference(mels.to(self.device).float(),/' \
53+
/usr/local/lib/python3.10/dist-packages/chatterbox/models/voice_encoder/voice_encoder.py
54+
55+
# Copy the rest of the application code
56+
COPY . .
57+
58+
# Create required directories for the application (fixed syntax error)
59+
RUN mkdir -p model_cache reference_audio outputs voices logs hf_cache
60+
61+
# Expose the port the application will run on
62+
EXPOSE 8004
63+
64+
# Command to run the application
65+
CMD ["python3", "server.py"]

‎docker-compose-strixhalo.yml‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
2+
services:
3+
chatterbox-tts-server:
4+
build:
5+
context: .
6+
dockerfile: Dockerfile.strixhalo
7+
ports:
8+
- "${PORT:-8004}:8004"
9+
volumes:
10+
# Mount local config file for persistence
11+
- ./config.yaml:/app/config.yaml
12+
# Mount local directories for persistent app data
13+
- ./voices:/app/voices
14+
- ./reference_audio:/app/reference_audio
15+
- ./outputs:/app/outputs
16+
- ./logs:/app/logs
17+
- hf_cache:/app/hf_cache
18+
19+
# --- ROCm GPU Access ---
20+
# Standard ROCm device access - required for AMD GPU acceleration
21+
devices:
22+
- /dev/kfd
23+
- /dev/dri
24+
group_add:
25+
- video
26+
- render
27+
ipc: host
28+
shm_size: 8g
29+
security_opt:
30+
- seccomp=unconfined
31+
32+
# --- Optional: Enhanced ROCm Access ---
33+
# Uncomment the lines below if you experience GPU access issues
34+
# privileged: true
35+
# cap_add:
36+
# - SYS_PTRACE
37+
# devices:
38+
# - /dev/mem
39+
40+
restart: unless-stopped
41+
environment:
42+
# Enable faster Hugging Face downloads
43+
- HF_HUB_ENABLE_HF_TRANSFER=1
44+
- HF_TOKEN=YOUR_TOKEN_HERE
45+
- TTS_ENGINE_DEVICE=cuda
46+
# Enable bfloat16 for T3/S3Gen — halves memory bandwidth on token generation
47+
# Set TTS_BF16=off if you experience precision issues
48+
- TTS_BF16=on
49+
- TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1
50+
- MIOPEN_FIND_MODE=FAST
51+
- HSA_OVERRIDE_GFX_VERSION=11.0.0
52+
# Critical for Strix Halo unified memory (gfx1151): enables page-fault retry
53+
- HSA_XNACK=1
54+
# Reduces memory fragmentation on large unified memory pool
55+
- PYTORCH_ALLOC_CONF=expandable_segments:True
56+
# NOTE: HSA_OVERRIDE_GFX_VERSION should only be set by users with unsupported GPUs
57+
# Example usage: HSA_OVERRIDE_GFX_VERSION=10.3.0 docker compose up
58+
# Common values: 10.3.0 (RX 5000/6000), 11.0.0 (RX 7000), 9.0.6 (Vega)
59+
60+
volumes:
61+
hf_cache:

‎engine.py‎

Lines changed: 92 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import gc
55
import logging
6+
import os
67
import random
78
import numpy as np
89
import torch
@@ -38,7 +39,8 @@
3839

3940
logger = logging.getLogger(__name__)
4041

41-
# Log Turbo availability status at module load time
42+
# Log BF16 setting at module load so it's visible in startup logs
43+
# (BF16_ENABLED is resolved after logger is set up — logged in initialize_tts_model)
4244
if TURBO_AVAILABLE:
4345
logger.info("ChatterboxTurboTTS is available in the installed chatterbox package.")
4446
else:
@@ -79,6 +81,25 @@
7981
"shush",
8082
]
8183

84+
# --- BF16 optimization flag ---
85+
# TTS_BF16: controls whether T3/S3Gen are converted to bfloat16 and whether
86+
# autocast is used during inference.
87+
# auto (default) — enable only if torch.cuda.is_bf16_supported()
88+
# on / 1 / true — force-enable (assumes hardware supports it)
89+
# off / 0 / false — disable even if hardware supports it
90+
def _resolve_bf16_setting() -> bool:
91+
val = os.environ.get("TTS_BF16", "auto").strip().lower()
92+
if val in ("off", "0", "false"):
93+
return False
94+
if val in ("on", "1", "true"):
95+
return True
96+
# auto: detect at runtime
97+
if torch.cuda.is_available():
98+
return torch.cuda.is_bf16_supported()
99+
return False
100+
101+
BF16_ENABLED: bool = _resolve_bf16_setting()
102+
82103
# --- Global Module Variables ---
83104
chatterbox_model: Optional[ChatterboxTTS] = None
84105
MODEL_LOADED: bool = False
@@ -90,6 +111,18 @@
90111
loaded_model_type: Optional[str] = None # "original" or "turbo"
91112
loaded_model_class_name: Optional[str] = None # "ChatterboxTTS" or "ChatterboxTurboTTS"
92113

114+
# Voice conditioning cache: avoids re-encoding the same voice file on every request.
115+
# Key: (resolved_path, file_mtime, exaggeration) — mtime invalidates if file changes.
116+
_conds_cache: dict = {}
117+
118+
119+
def _conds_cache_key(path: str, exaggeration: float) -> tuple:
120+
try:
121+
mtime = os.path.getmtime(path)
122+
except OSError:
123+
mtime = 0.0
124+
return (path, mtime, exaggeration)
125+
93126

94127
def set_seed(seed_value: int):
95128
"""
@@ -307,6 +340,10 @@ def load_model() -> bool:
307340

308341
model_device = resolved_device_str
309342
logger.info(f"Final device selection: {model_device}")
343+
logger.info(
344+
f"BF16 optimization: {'enabled' if BF16_ENABLED else 'disabled'} "
345+
f"(TTS_BF16={os.environ.get('TTS_BF16', 'auto')})"
346+
)
310347

311348
# Get the model selector from config
312349
model_selector = config_manager.get_string("model.repo_id", "chatterbox-turbo")
@@ -329,6 +366,17 @@ def load_model() -> bool:
329366
# Load the model using from_pretrained - handles HuggingFace downloads automatically
330367
chatterbox_model = model_class.from_pretrained(device=model_device)
331368

369+
# Convert T3 to bfloat16 if enabled.
370+
# Token generation is memory-bandwidth bound; bf16 halves bytes read per
371+
# forward pass. S3Gen is intentionally kept in float32 — it runs only
372+
# 2 CFM timesteps and bf16 causes token/mask size mismatches.
373+
if BF16_ENABLED:
374+
if hasattr(chatterbox_model, "t3"):
375+
chatterbox_model.t3 = chatterbox_model.t3.bfloat16()
376+
logger.info("T3 model converted to bfloat16 for faster token generation.")
377+
else:
378+
logger.info("BF16 optimization disabled (TTS_BF16=off or hardware unsupported).")
379+
332380
# Store model metadata
333381
loaded_model_type = model_type
334382
loaded_model_class_name = model_class.__name__
@@ -423,25 +471,45 @@ def synthesize(
423471
f"language={language}"
424472
)
425473

426-
# Call the core model's generate method
427-
# Multilingual model requires language_id parameter
428-
if loaded_model_type == "multilingual":
429-
wav_tensor = chatterbox_model.generate(
430-
text=text,
431-
language_id=language,
432-
audio_prompt_path=audio_prompt_path,
433-
temperature=temperature,
434-
exaggeration=exaggeration,
435-
cfg_weight=cfg_weight,
436-
)
437-
else:
438-
wav_tensor = chatterbox_model.generate(
439-
text=text,
440-
audio_prompt_path=audio_prompt_path,
441-
temperature=temperature,
442-
exaggeration=exaggeration,
443-
cfg_weight=cfg_weight,
444-
)
474+
# Voice conditioning cache: skip re-encoding the same voice file.
475+
# Turbo ignores exaggeration in conds; others include it in the key.
476+
effective_prompt = audio_prompt_path
477+
conds_key = None
478+
if audio_prompt_path and hasattr(chatterbox_model, "conds"):
479+
ex_for_key = 0.0 if loaded_model_type == "turbo" else exaggeration
480+
conds_key = _conds_cache_key(audio_prompt_path, ex_for_key)
481+
if conds_key in _conds_cache:
482+
chatterbox_model.conds = _conds_cache[conds_key]
483+
effective_prompt = None # conds already set, skip prepare_conditionals
484+
logger.debug(f"Voice cache hit: {audio_prompt_path}")
485+
486+
# Call the core model's generate method.
487+
# autocast promotes float32 inputs to bfloat16 to match T3/S3Gen weights,
488+
# keeping numerically sensitive ops (softmax, norms) in float32 automatically.
489+
with torch.autocast("cuda", dtype=torch.bfloat16, enabled=BF16_ENABLED):
490+
if loaded_model_type == "multilingual":
491+
wav_tensor = chatterbox_model.generate(
492+
text=text,
493+
language_id=language,
494+
audio_prompt_path=effective_prompt,
495+
temperature=temperature,
496+
exaggeration=exaggeration,
497+
cfg_weight=cfg_weight,
498+
)
499+
else:
500+
wav_tensor = chatterbox_model.generate(
501+
text=text,
502+
audio_prompt_path=effective_prompt,
503+
temperature=temperature,
504+
exaggeration=exaggeration,
505+
cfg_weight=cfg_weight,
506+
)
507+
508+
# Store conds in cache after first compute for this voice.
509+
if conds_key is not None and effective_prompt is not None:
510+
if chatterbox_model.conds is not None:
511+
_conds_cache[conds_key] = chatterbox_model.conds
512+
logger.debug(f"Cached voice conditionals for: {audio_prompt_path}")
445513

446514
# The ChatterboxTTS.generate method already returns a CPU tensor.
447515
return wav_tensor, chatterbox_model.sr
@@ -507,7 +575,7 @@ def reload_model() -> bool:
507575
Returns:
508576
bool: True if the new model loaded successfully, False otherwise.
509577
"""
510-
global chatterbox_model, MODEL_LOADED, model_device, loaded_model_type, loaded_model_class_name
578+
global chatterbox_model, MODEL_LOADED, model_device, loaded_model_type, loaded_model_class_name, _conds_cache
511579

512580
logger.info("Initiating model hot-swap/reload sequence...")
513581

@@ -517,10 +585,12 @@ def reload_model() -> bool:
517585
del chatterbox_model
518586
chatterbox_model = None
519587

520-
# 2. Reset state flags
588+
# 2. Reset state flags and clear voice cache (conds are model-specific)
521589
MODEL_LOADED = False
522590
loaded_model_type = None
523591
loaded_model_class_name = None
592+
_conds_cache.clear()
593+
logger.info("Voice conditioning cache cleared.")
524594

525595
# 3. Force Python Garbage Collection
526596
gc.collect()

‎requirements-strixhalo-init.txt‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/triton-3.5.1%2Brocm7.2.0.gita272dfa8-cp310-cp310-linux_x86_64.whl
2+
https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torch-2.9.1%2Brocm7.2.0.lw.git7e1940d4-cp310-cp310-linux_x86_64.whl
3+
https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torchvision-0.24.0%2Brocm7.2.0.gitb919bd0c-cp310-cp310-linux_x86_64.whl
4+
https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torchaudio-2.9.0%2Brocm7.2.0.gite3c6ee2b-cp310-cp310-linux_x86_64.whl

‎requirements-strixhalo.txt‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# requirements.txt
2+
3+
# Chatterbox TTS engine - Install from chatterbox-v2 fork
4+
chatterbox-tts @ git+https://github.com/devnen/chatterbox-v2.git@master
5+
6+
# Core Web Framework
7+
fastapi
8+
uvicorn[standard]
9+
10+
# Machine Learning & Audio
11+
numpy>=1.24.0,<1.26.0 # Match chatterbox-v2 requirements
12+
soundfile # Requires libsndfile system library (e.g., sudo apt-get install libsndfile1 on Debian/Ubuntu)
13+
huggingface_hub
14+
descript-audio-codec
15+
safetensors
16+
17+
# Configuration & Utilities
18+
pydantic
19+
python-dotenv # Used ONLY for initial config seeding if config.yaml missing
20+
Jinja2
21+
python-multipart # For file uploads
22+
requests # For health checks or other potential uses
23+
PyYAML # For parsing presets.yaml AND primary config.yaml
24+
tqdm
25+
26+
# Audio Post-processing
27+
pydub
28+
praat-parselmouth # For unvoiced segment removal
29+
librosa # for changes to sampling
30+
hf-transfer

0 commit comments

Comments
 (0)