fix: tolerate Confucius and DOTS accelerator probe failures
This commit is contained in:
+1
-1
@@ -20,7 +20,7 @@ the frozen-backend fallback mirror it for their toolchains.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Confucius accelerator routing matches device selection, and dots.tts keeps safe CPU precision on non-CUDA accelerators (#1831) — thanks @li-lizhe!
|
||||
- Confucius accelerator routing tolerates failed device probes, and dots.tts keeps safe default precision on non-CUDA hosts (#1831) — thanks @li-lizhe!
|
||||
|
||||
|
||||
## [0.5.2] — 2026-09-02
|
||||
|
||||
@@ -115,6 +115,7 @@ def _load_model(stdout):
|
||||
import torch
|
||||
from confuciustts.cli.inference import ConfuciusTTS # type: ignore[import-not-found]
|
||||
|
||||
try:
|
||||
# Existing manually provisioned venvs may predate torch.accelerator.
|
||||
current_accelerator = getattr(getattr(torch, "accelerator", None), "current_accelerator", None)
|
||||
if current_accelerator is None:
|
||||
@@ -122,6 +123,8 @@ def _load_model(stdout):
|
||||
else:
|
||||
device = current_accelerator(check_available=True)
|
||||
device = device.type if device is not None else "cpu" # 'cuda', 'npu', 'mps', 'xpu', 'cpu'
|
||||
except Exception:
|
||||
device = "cpu" # Broken accelerator drivers must not block CPU loading.
|
||||
if device == "mps":
|
||||
device = "cpu" # MPS was slower than CPU in the existing validation run
|
||||
_send(stdout, {"op": "progress", "stage": "loading_model", "percent": 50})
|
||||
|
||||
@@ -117,7 +117,10 @@ def _load_runtime(stdout):
|
||||
repo = os.environ.get("OMNIVOICE_DOTS_TTS_MODEL", _DEFAULT_REPO)
|
||||
# Match DotsTtsRuntime's own CUDA/CPU selection. Its _check_torch_env
|
||||
# rejects half precision without CUDA, even when an XPU/NPU is available.
|
||||
try:
|
||||
default_precision = "bfloat16" if torch.cuda.is_available() else "float32"
|
||||
except Exception:
|
||||
default_precision = "float32" # Probe failure must not force half precision.
|
||||
precision = os.environ.get("OMNIVOICE_DOTS_TTS_PRECISION", default_precision)
|
||||
optimize = os.environ.get("OMNIVOICE_DOTS_TTS_OPTIMIZE", "0") == "1"
|
||||
|
||||
|
||||
@@ -96,3 +96,5 @@ through upstream's device-aware model loading. The engine venv needs a matching
|
||||
PyTorch/vendor runtime. XPU/NPU selection is covered by mocked loader and routing
|
||||
tests; this change does not certify synthesis on physical XPU/NPU hardware.
|
||||
MPS keeps the existing CPU fallback described in the validation record above.
|
||||
If modern accelerator detection or the legacy CUDA probe raises, loading falls
|
||||
back to CPU instead of aborting before model construction.
|
||||
|
||||
@@ -119,4 +119,5 @@ disk and how uv keeps the cost down, see
|
||||
The upstream runtime selects CUDA or CPU internally. Automatic precision follows
|
||||
that selection: bfloat16 on CUDA, float32 otherwise, including XPU/NPU/MPS hosts
|
||||
where this runtime executes on CPU. `OMNIVOICE_DOTS_TTS_PRECISION` remains an
|
||||
explicit override.
|
||||
explicit override. If the CUDA availability probe raises, the automatic precision
|
||||
default stays float32; upstream remains responsible for its device selection.
|
||||
|
||||
@@ -210,3 +210,24 @@ def test_load_model_matches_routing(sc, monkeypatch, family, legacy):
|
||||
accelerator.assert_called_once_with(check_available=True)
|
||||
caps = HostCaps(family=family or 'cpu', available_families=(family, 'cpu') if family else ('cpu',))
|
||||
assert resolve_routing(Confucius4Backend.gpu_compat, caps)['effective_device'] == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("legacy", [False, True])
|
||||
def test_load_model_falls_back_when_accelerator_probe_raises(sc, monkeypatch, legacy):
|
||||
import sys
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import Mock
|
||||
|
||||
probe = Mock(side_effect=RuntimeError("accelerator driver unavailable"))
|
||||
monkeypatch.setitem(sys.modules, "torch", SimpleNamespace(
|
||||
accelerator=SimpleNamespace() if legacy else SimpleNamespace(current_accelerator=probe),
|
||||
cuda=SimpleNamespace(is_available=probe),
|
||||
))
|
||||
constructor = Mock()
|
||||
monkeypatch.setitem(sys.modules, "confuciustts.cli.inference", SimpleNamespace(ConfuciusTTS=constructor))
|
||||
monkeypatch.setattr(sc, "_model", None)
|
||||
monkeypatch.setattr(sc, "_config_path", lambda: "fixture.yaml")
|
||||
monkeypatch.setattr(sc, "_ensure_clone_on_sys_path", lambda: None)
|
||||
sc._load_model(io.BytesIO())
|
||||
constructor.assert_called_once_with(config_path="fixture.yaml", device="cpu")
|
||||
assert probe.call_count == 1
|
||||
|
||||
@@ -22,3 +22,20 @@ def test_precision_matches_runtime_device(monkeypatch, family):
|
||||
monkeypatch.delenv('OMNIVOICE_DOTS_TTS_PRECISION', raising=False)
|
||||
main._load_runtime(io.BytesIO())
|
||||
assert loader.from_pretrained.call_args.kwargs['precision'] == ('bfloat16' if family == 'cuda' else 'float32')
|
||||
|
||||
|
||||
@pytest.mark.parametrize("override", [None, "float16"])
|
||||
def test_precision_probe_failure_uses_safe_default_or_explicit_override(monkeypatch, override):
|
||||
from engines.dots_tts import main
|
||||
|
||||
probe = Mock(side_effect=RuntimeError("CUDA driver unavailable"))
|
||||
monkeypatch.setitem(sys.modules, "torch", SimpleNamespace(cuda=SimpleNamespace(is_available=probe)))
|
||||
loader = Mock()
|
||||
monkeypatch.setitem(sys.modules, "dots_tts.runtime", SimpleNamespace(DotsTtsRuntime=loader))
|
||||
monkeypatch.setattr(main, "_runtime", None)
|
||||
if override is None:
|
||||
monkeypatch.delenv("OMNIVOICE_DOTS_TTS_PRECISION", raising=False)
|
||||
else:
|
||||
monkeypatch.setenv("OMNIVOICE_DOTS_TTS_PRECISION", override)
|
||||
main._load_runtime(io.BytesIO())
|
||||
assert loader.from_pretrained.call_args.kwargs["precision"] == (override or "float32")
|
||||
|
||||
Reference in New Issue
Block a user