fix: tolerate Confucius and DOTS accelerator probe failures

This commit is contained in:
Palash Debnath
2026-09-07 12:24:42 +05:30
parent d55838542e
commit df46b71be2
7 changed files with 57 additions and 10 deletions
+1 -1
View File
@@ -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
+10 -7
View File
@@ -115,13 +115,16 @@ def _load_model(stdout):
import torch
from confuciustts.cli.inference import ConfuciusTTS # type: ignore[import-not-found]
# Existing manually provisioned venvs may predate torch.accelerator.
current_accelerator = getattr(getattr(torch, "accelerator", None), "current_accelerator", None)
if current_accelerator is None:
device = torch.device("cuda") if torch.cuda.is_available() else None
else:
device = current_accelerator(check_available=True)
device = device.type if device is not None else "cpu" # 'cuda', 'npu', 'mps', 'xpu', 'cpu'
try:
# Existing manually provisioned venvs may predate torch.accelerator.
current_accelerator = getattr(getattr(torch, "accelerator", None), "current_accelerator", None)
if current_accelerator is None:
device = torch.device("cuda") if torch.cuda.is_available() else None
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})
+4 -1
View File
@@ -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.
default_precision = "bfloat16" if torch.cuda.is_available() else "float32"
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"
+2
View File
@@ -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.
+2 -1
View File
@@ -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.
+21
View File
@@ -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")