Fix cosyvoice models not showing on model page and runtime import errors

- Model filter in ModelManagement was an allowlist missing cosyvoice prefix,
  replaced with inverse filter (everything except whisper = voice model)
- Shim training-only modules (cosyvoice.dataset.processor, matcha.utils.*)
  to prevent hyperpyyaml from importing pyarrow, pyworld, lightning etc.
- Patch torchaudio.load for 2.9+ compat (torchcodec now required by default)
- Add matplotlib to requirements (matcha hifigan imports it at module level)
This commit is contained in:
Jamie Pine
2026-03-18 04:40:21 -07:00
parent f77dd621e2
commit 8302c14e24
3 changed files with 88 additions and 19 deletions
@@ -394,14 +394,7 @@ export function ModelManagement() {
setDetailOpen(true);
};
const voiceModels =
modelStatus?.models.filter(
(m) =>
m.model_name.startsWith('qwen-tts') ||
m.model_name.startsWith('luxtts') ||
m.model_name.startsWith('chatterbox') ||
m.model_name.startsWith('tada'),
) ?? [];
const voiceModels = modelStatus?.models.filter((m) => !m.model_name.startsWith('whisper')) ?? [];
const whisperModels = modelStatus?.models.filter((m) => m.model_name.startsWith('whisper')) ?? [];
// Build sections
+86 -11
View File
@@ -77,27 +77,72 @@ def _ensure_cosyvoice_on_path() -> None:
sys.path.insert(0, matcha_str)
def _shim_matcha_pylogger() -> None:
def _shim_training_only_modules() -> None:
"""
Replace Matcha-TTS's ``pylogger`` module with a lightweight shim.
Pre-populate ``sys.modules`` with lightweight stubs for modules that
the CosyVoice YAML configs reference but are only needed for training.
The original ``matcha.utils.pylogger`` imports ``lightning.pytorch``
(pytorch-lightning) at module level just to get ``rank_zero_only``.
We don't need multi-GPU logging, so we inject a plain-logging
replacement to avoid pulling in the entire lightning dependency.
``hyperpyyaml`` resolves every ``!name:`` / ``!new:`` tag via
``pydoc.locate`` which eagerly imports the target module. The YAML
references ``cosyvoice.dataset.processor`` (12 times) which pulls in
``pyarrow``, ``pyworld``, etc. at module level.
Several ``matcha.utils.*`` submodules also import
``lightning.pytorch`` at module level. We stub those so the real
``matcha.utils`` package can still expose ``audio.py`` and ``model.py``
for inference.
"""
import types
import logging as _logging
_noop = lambda *a, **kw: None
def get_pylogger(name: str = __name__) -> _logging.Logger:
return _logging.getLogger(name)
# Pre-populate the module in sys.modules before CosyVoice imports it
fake_utils = types.ModuleType("matcha.utils")
# ── matcha.utils submodules that import lightning ──────────────
fake_pylogger = types.ModuleType("matcha.utils.pylogger")
fake_pylogger.get_pylogger = get_pylogger # type: ignore[attr-defined]
sys.modules.setdefault("matcha.utils", fake_utils)
sys.modules.setdefault("matcha.utils.pylogger", fake_pylogger)
fake_logging_utils = types.ModuleType("matcha.utils.logging_utils")
fake_logging_utils.log_hyperparameters = _noop # type: ignore[attr-defined]
fake_rich_utils = types.ModuleType("matcha.utils.rich_utils")
fake_rich_utils.enforce_tags = _noop # type: ignore[attr-defined]
fake_rich_utils.print_config_tree = _noop # type: ignore[attr-defined]
fake_instantiators = types.ModuleType("matcha.utils.instantiators")
fake_instantiators.instantiate_callbacks = lambda *a, **kw: [] # type: ignore[attr-defined]
fake_instantiators.instantiate_loggers = lambda *a, **kw: [] # type: ignore[attr-defined]
fake_utils_utils = types.ModuleType("matcha.utils.utils")
fake_utils_utils.extras = _noop # type: ignore[attr-defined]
fake_utils_utils.get_metric_value = _noop # type: ignore[attr-defined]
fake_utils_utils.task_wrapper = lambda fn: fn # type: ignore[attr-defined]
sys.modules["matcha.utils.pylogger"] = fake_pylogger
sys.modules["matcha.utils.logging_utils"] = fake_logging_utils
sys.modules["matcha.utils.rich_utils"] = fake_rich_utils
sys.modules["matcha.utils.instantiators"] = fake_instantiators
sys.modules["matcha.utils.utils"] = fake_utils_utils
# ── cosyvoice.dataset.processor (training data pipeline) ──────
# Referenced 12 times in cosyvoice2.yaml / cosyvoice3.yaml via
# !name: tags. Imports pyarrow, pyworld, whisper at module level.
fake_dataset = types.ModuleType("cosyvoice.dataset")
fake_dataset.__path__ = [] # type: ignore[attr-defined]
fake_processor = types.ModuleType("cosyvoice.dataset.processor")
for _fn in (
"parquet_opener", "tokenize", "filter", "resample", "truncate",
"compute_fbank", "compute_whisper_fbank", "compute_f0",
"parse_embedding", "shuffle", "sort", "batch", "padding",
):
setattr(fake_processor, _fn, _noop)
sys.modules.setdefault("cosyvoice.dataset", fake_dataset)
sys.modules["cosyvoice.dataset.processor"] = fake_processor
def _patch_modelscope_to_hf() -> None:
@@ -121,6 +166,35 @@ def _patch_modelscope_to_hf() -> None:
sys.modules["modelscope"] = fake_ms
def _patch_torchaudio_load() -> None:
"""
Replace ``torchaudio.load`` with a soundfile-backed implementation.
torchaudio >= 2.9 unconditionally delegates to TorchCodec and ignores
the ``backend`` parameter. CosyVoice calls ``torchaudio.load(wav,
backend='soundfile')`` which now fails unless ``torchcodec`` is
installed. We swap in a lightweight wrapper that reads via soundfile
and returns the same ``(Tensor, sample_rate)`` tuple.
"""
import torch
import torchaudio
import soundfile as sf
def _sf_load(uri, frame_offset=0, num_frames=-1, normalize=True,
channels_first=True, format=None, buffer_size=4096,
backend=None):
data, sr = sf.read(uri, start=frame_offset,
stop=None if num_frames < 0 else frame_offset + num_frames,
dtype="float32", always_2d=True)
# data shape: (frames, channels) → tensor
tensor = torch.from_numpy(data)
if channels_first:
tensor = tensor.T # (channels, frames)
return tensor, sr
torchaudio.load = _sf_load
class CosyVoiceTTSBackend:
"""CosyVoice2 / CosyVoice3 TTS backend for voice cloning with instruct support."""
@@ -192,8 +266,9 @@ class CosyVoiceTTSBackend:
# 2. Patch imports (thread-safe, once)
with CosyVoiceTTSBackend._import_lock:
if not CosyVoiceTTSBackend._patched:
_shim_matcha_pylogger()
_shim_training_only_modules()
_patch_modelscope_to_hf()
_patch_torchaudio_load()
CosyVoiceTTSBackend._patched = True
# 3. Patch torch.load to force map_location on CPU
+1
View File
@@ -48,6 +48,7 @@ openai-whisper>=20231117
tiktoken
einops
inflect
matplotlib
# Audio processing
librosa>=0.10.0