From ad6ec3c6ef3d2fcc436c416b57ff0d9d20b6a52f Mon Sep 17 00:00:00 2001 From: jamiepine <32987599+jamiepine@users.noreply.github.com> Date: Sat, 3 Oct 2026 20:49:49 +0000 Subject: [PATCH] fix(backend): count the S3TokenizerV2 repo in the Chatterbox MLX cache check; register the backend with PyInstaller Review follow-ups: mlx-audio's Model.from_pretrained fetches the S3 speech tokenizer from mlx-community/S3TokenizerV2 (~470 MB) separately from the chatterbox checkout, so _is_model_cached now requires both repos (same shape as the Hume backend's codec check) and the config's size_mb reflects the real footprint. backend.backends.chatterbox_mlx_backend is a function-level import that PyInstaller's graph will not see, so it is added to the Apple Silicon hidden-import list in build_binary.py and voicebox-server.spec next to mlx_backend. --- backend/backends/__init__.py | 3 ++- backend/backends/chatterbox_mlx_backend.py | 10 ++++++++-- backend/build_binary.py | 2 ++ backend/voicebox-server.spec | 2 +- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/backend/backends/__init__.py b/backend/backends/__init__.py index fd8759cf..5d006d59 100644 --- a/backend/backends/__init__.py +++ b/backend/backends/__init__.py @@ -297,7 +297,8 @@ def _get_non_qwen_tts_configs() -> list[ModelConfig]: on_mlx = get_backend_type() == "mlx" if on_mlx: chatterbox_repo = "mlx-community/chatterbox-multilingual-v3" - chatterbox_size_mb = 2600 + # 2.5 GB of weights plus the separately fetched S3TokenizerV2 (~470 MB) + chatterbox_size_mb = 3000 else: chatterbox_repo = "ResembleAI/chatterbox" chatterbox_size_mb = 3200 diff --git a/backend/backends/chatterbox_mlx_backend.py b/backend/backends/chatterbox_mlx_backend.py index d4629426..ca26c75a 100644 --- a/backend/backends/chatterbox_mlx_backend.py +++ b/backend/backends/chatterbox_mlx_backend.py @@ -30,9 +30,13 @@ from .mlx_backend import _run_on_mlx_thread logger = logging.getLogger(__name__) CHATTERBOX_MLX_HF_REPO = "mlx-community/chatterbox-multilingual-v3" +# mlx-audio's Model.from_pretrained fetches the S3 speech tokenizer from this +# second repo (~470 MB), so the engine is only "downloaded" once both are cached. +S3_TOKENIZER_HF_REPO = "mlx-community/S3TokenizerV2" # Files that must be present for the MLX multilingual model _MLX_WEIGHT_FILES = ["model.safetensors", "config.json", "tokenizer.json"] +_S3_TOKENIZER_FILES = ["model.safetensors", "config.json"] class ChatterboxMLXTTSBackend: @@ -51,7 +55,9 @@ class ChatterboxMLXTTSBackend: return CHATTERBOX_MLX_HF_REPO def _is_model_cached(self, model_size: str = "default") -> bool: - return is_model_cached(CHATTERBOX_MLX_HF_REPO, required_files=_MLX_WEIGHT_FILES) + model_cached = is_model_cached(CHATTERBOX_MLX_HF_REPO, required_files=_MLX_WEIGHT_FILES) + tokenizer_cached = is_model_cached(S3_TOKENIZER_HF_REPO, required_files=_S3_TOKENIZER_FILES) + return model_cached and tokenizer_cached async def load_model(self, model_size: str = "default") -> None: """Load the Chatterbox multilingual MLX model.""" @@ -75,7 +81,7 @@ class ChatterboxMLXTTSBackend: logger.info("Loading Chatterbox Multilingual TTS on MLX (Metal)...") ckpt_dir = snapshot_download(CHATTERBOX_MLX_HF_REPO) - self.model = Model.from_pretrained(ckpt_dir) + self.model = Model.from_pretrained(ckpt_dir, s3_tokenizer_repo=S3_TOKENIZER_HF_REPO) logger.info("Chatterbox Multilingual TTS (MLX) loaded successfully") diff --git a/backend/build_binary.py b/backend/build_binary.py index 691c775e..0a7cf879 100644 --- a/backend/build_binary.py +++ b/backend/build_binary.py @@ -442,6 +442,8 @@ def build_server(cuda=False, rocm=False): "mlx_lm", "--hidden-import", "backend.backends.qwen_llm_backend", + "--hidden-import", + "backend.backends.chatterbox_mlx_backend", "--collect-submodules", "mlx", "--collect-submodules", diff --git a/backend/voicebox-server.spec b/backend/voicebox-server.spec index d0e8d978..a40d99cb 100644 --- a/backend/voicebox-server.spec +++ b/backend/voicebox-server.spec @@ -5,7 +5,7 @@ from PyInstaller.utils.hooks import copy_metadata datas = [] binaries = [] -hiddenimports = ['backend', 'backend.main', 'backend.config', 'backend.database', 'backend.models', 'backend.services.profiles', 'backend.services.history', 'backend.services.tts', 'backend.services.transcribe', 'backend.utils.platform_detect', 'backend.backends', 'backend.backends.pytorch_backend', 'backend.backends.qwen_custom_voice_backend', 'backend.utils.audio', 'backend.utils.cache', 'backend.utils.progress', 'backend.utils.hf_progress', 'backend.services.cuda', 'backend.services.effects', 'backend.utils.effects', 'backend.services.versions', 'pedalboard', 'chatterbox', 'chatterbox.tts_turbo', 'chatterbox.mtl_tts', 'backend.backends.chatterbox_backend', 'backend.backends.chatterbox_turbo_backend', 'backend.backends.luxtts_backend', 'zipvoice', 'zipvoice.luxvoice', 'torch', 'transformers', 'fastapi', 'uvicorn', 'sqlalchemy', 'soundfile', 'qwen_tts', 'qwen_tts.inference', 'qwen_tts.inference.qwen3_tts_model', 'qwen_tts.inference.qwen3_tts_tokenizer', 'qwen_tts.core', 'qwen_tts.cli', 'requests', 'pkg_resources.extern', 'backend.backends.hume_backend', 'tada', 'tada.modules', 'tada.modules.tada', 'tada.modules.encoder', 'tada.modules.decoder', 'tada.modules.aligner', 'tada.modules.acoustic_spkr_verf', 'tada.nn', 'tada.nn.vibevoice', 'tada.utils', 'tada.utils.gray_code', 'tada.utils.text', 'backend.utils.dac_shim', 'torchaudio', 'backend.backends.kokoro_backend', 'en_core_web_sm', 'loguru', 'backend.mcp_server', 'backend.mcp_server.server', 'backend.mcp_server.tools', 'backend.mcp_server.context', 'backend.mcp_server.resolve', 'backend.mcp_server.events', 'sse_starlette', 'backend.backends.mlx_backend', 'mlx', 'mlx.core', 'mlx.nn', 'mlx_audio', 'mlx_audio.tts', 'mlx_audio.stt', 'mlx_lm', 'backend.backends.qwen_llm_backend'] +hiddenimports = ['backend', 'backend.main', 'backend.config', 'backend.database', 'backend.models', 'backend.services.profiles', 'backend.services.history', 'backend.services.tts', 'backend.services.transcribe', 'backend.utils.platform_detect', 'backend.backends', 'backend.backends.pytorch_backend', 'backend.backends.qwen_custom_voice_backend', 'backend.utils.audio', 'backend.utils.cache', 'backend.utils.progress', 'backend.utils.hf_progress', 'backend.services.cuda', 'backend.services.effects', 'backend.utils.effects', 'backend.services.versions', 'pedalboard', 'chatterbox', 'chatterbox.tts_turbo', 'chatterbox.mtl_tts', 'backend.backends.chatterbox_backend', 'backend.backends.chatterbox_turbo_backend', 'backend.backends.luxtts_backend', 'zipvoice', 'zipvoice.luxvoice', 'torch', 'transformers', 'fastapi', 'uvicorn', 'sqlalchemy', 'soundfile', 'qwen_tts', 'qwen_tts.inference', 'qwen_tts.inference.qwen3_tts_model', 'qwen_tts.inference.qwen3_tts_tokenizer', 'qwen_tts.core', 'qwen_tts.cli', 'requests', 'pkg_resources.extern', 'backend.backends.hume_backend', 'tada', 'tada.modules', 'tada.modules.tada', 'tada.modules.encoder', 'tada.modules.decoder', 'tada.modules.aligner', 'tada.modules.acoustic_spkr_verf', 'tada.nn', 'tada.nn.vibevoice', 'tada.utils', 'tada.utils.gray_code', 'tada.utils.text', 'backend.utils.dac_shim', 'torchaudio', 'backend.backends.kokoro_backend', 'en_core_web_sm', 'loguru', 'backend.mcp_server', 'backend.mcp_server.server', 'backend.mcp_server.tools', 'backend.mcp_server.context', 'backend.mcp_server.resolve', 'backend.mcp_server.events', 'sse_starlette', 'backend.backends.mlx_backend', 'mlx', 'mlx.core', 'mlx.nn', 'mlx_audio', 'mlx_audio.tts', 'mlx_audio.stt', 'mlx_lm', 'backend.backends.qwen_llm_backend', 'backend.backends.chatterbox_mlx_backend'] datas += copy_metadata('qwen-tts') datas += copy_metadata('requests') datas += copy_metadata('transformers')