Detection is two-phase by construction: the registry only calls probe_toolchain once probe_hardware confirms the vendor, which is what structurally keeps torch from being imported on an Intel-only box. A test asserts exactly that. Hardware probing reads each render node's bound driver rather than loaded kernel modules: /sys/module/xe exists here with zero bound devices while i915 owns the card, so a module-presence check false-positives. Fallback is runtime, not detection-time -- construction and the first generate() sit in the same try, because the render-node permission failure and the OpenCL JIT failure both surface there rather than at device enumeration. A failure demotes the backend process-wide so a 50-job batch does not retry it 50 times, and an explicit --device never falls back silently. NVIDIA and AMD are interface-only: detection is real and the error names the module to implement and the model format required. The cached model is OpenVINO IR and cannot load on CUDA or ROCm, and CTranslate2 has no ROCm support, so those are two separate paths rather than one parameterized one. Model resolution is offline-first, and CACHE_DIR is anchored under XDG rather than the working directory, which the benchmark scripts depend on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
51 lines
2.0 KiB
Python
51 lines
2.0 KiB
Python
from __future__ import annotations
|
|
|
|
import numpy as np
|
|
import pytest
|
|
|
|
from ccn_transcribe import errors
|
|
from ccn_transcribe.backends.amd_backend import AmdBackend
|
|
from ccn_transcribe.backends.base import TranscribeRequest
|
|
from ccn_transcribe.backends.nvidia_backend import NvidiaBackend
|
|
|
|
STUBS = [NvidiaBackend, AmdBackend]
|
|
|
|
|
|
@pytest.mark.parametrize("backend", STUBS)
|
|
class TestStubs:
|
|
def test_hardware_probe_is_real(self, backend: type[NvidiaBackend]) -> None:
|
|
# No NVIDIA or AMD on this machine, so detection must say so honestly.
|
|
assert backend.probe_hardware() is False
|
|
|
|
def test_toolchain_reports_not_implemented(self, backend: type[NvidiaBackend]) -> None:
|
|
status = backend.probe_toolchain()
|
|
assert status.ok is False
|
|
assert "not implemented" in status.reason
|
|
|
|
def test_load_raises_with_an_actionable_hint(self, backend: type[NvidiaBackend]) -> None:
|
|
with pytest.raises(errors.BackendNotImplementedError) as caught:
|
|
backend().load()
|
|
hint = caught.value.hint or ""
|
|
assert "backends/" in hint
|
|
|
|
def test_transcribe_raises_rather_than_returning_nonsense(
|
|
self, backend: type[NvidiaBackend]
|
|
) -> None:
|
|
with pytest.raises(errors.BackendNotImplementedError):
|
|
backend().transcribe(np.zeros(16, np.float32), TranscribeRequest())
|
|
|
|
def test_close_is_safe(self, backend: type[NvidiaBackend]) -> None:
|
|
backend().close()
|
|
|
|
def test_default_model_is_not_the_openvino_ir(self, backend: type[NvidiaBackend]) -> None:
|
|
# OpenVINO IR cannot load on CUDA or ROCm, so each backend owns its default.
|
|
assert "-ov" not in backend.default_model
|
|
|
|
def test_a_custom_model_id_is_kept(self, backend: type[NvidiaBackend]) -> None:
|
|
assert backend("custom/model").model_id == "custom/model"
|
|
|
|
|
|
def test_the_two_stubs_use_different_runtimes() -> None:
|
|
# CTranslate2 has no ROCm support, so these cannot share one implementation.
|
|
assert NvidiaBackend.device != AmdBackend.device
|