| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- """Tests for the ROCm accelerator helpers."""
- import logging
- import os
- import sys
- import types
- import pytest
- from stt_daemon import accel
- from stt_daemon import transcriber as transcriber_mod
- from stt_daemon.transcriber import Transcriber
- def _fake_torch(*, hip=None, cuda_available=False):
- return types.SimpleNamespace(
- version=types.SimpleNamespace(hip=hip),
- cuda=types.SimpleNamespace(is_available=lambda: cuda_available),
- )
- class _RecordingWhisperModel:
- """Records the allocator and arguments visible at construction time."""
- last: "_RecordingWhisperModel | None" = None
- def __init__(self, model, device, compute_type, cpu_threads=0):
- self.model = None # no CTranslate2 inner model in the fake
- self.allocator_at_creation = os.environ.get(accel.CT2_ALLOCATOR_ENV)
- self.device_arg = device
- self.compute_type_arg = compute_type
- _RecordingWhisperModel.last = self
- def test_is_rocm_host_true_when_torch_reports_hip(monkeypatch):
- monkeypatch.setitem(sys.modules, "torch", _fake_torch(hip="7.2.0"))
- assert accel.is_rocm_host() is True
- def test_is_rocm_host_false_when_torch_reports_no_hip(monkeypatch):
- monkeypatch.setitem(sys.modules, "torch", _fake_torch(hip=None))
- assert accel.is_rocm_host() is False
- def test_is_rocm_host_false_without_torch_env_or_marker(monkeypatch):
- monkeypatch.delitem(sys.modules, "torch", raising=False)
- monkeypatch.delenv("ROCM_PATH", raising=False)
- monkeypatch.delenv("ROCM_HOME", raising=False)
- monkeypatch.setattr(accel, "_ROCM_MARKER_PATHS", ())
- assert accel.is_rocm_host() is False
- def test_is_rocm_host_true_when_marker_path_is_a_directory(monkeypatch, tmp_path):
- monkeypatch.delitem(sys.modules, "torch", raising=False)
- monkeypatch.delenv("ROCM_PATH", raising=False)
- monkeypatch.delenv("ROCM_HOME", raising=False)
- monkeypatch.setattr(accel, "_ROCM_MARKER_PATHS", (str(tmp_path),))
- assert accel.is_rocm_host() is True
- def test_configure_ct2_allocator_is_noop_on_non_rocm_host(monkeypatch):
- monkeypatch.setattr(accel, "is_rocm_host", lambda: False)
- monkeypatch.delenv(accel.CT2_ALLOCATOR_ENV, raising=False)
- assert accel.configure_ct2_allocator() is None
- assert accel.CT2_ALLOCATOR_ENV not in os.environ
- def test_configure_ct2_allocator_sets_cub_caching_on_rocm_host(monkeypatch):
- monkeypatch.setattr(accel, "is_rocm_host", lambda: True)
- monkeypatch.delenv(accel.CT2_ALLOCATOR_ENV, raising=False)
- assert accel.configure_ct2_allocator() == accel.ROCM_CT2_ALLOCATOR
- assert os.environ[accel.CT2_ALLOCATOR_ENV] == "cub_caching"
- def test_configure_ct2_allocator_preserves_explicit_value(monkeypatch):
- monkeypatch.setattr(accel, "is_rocm_host", lambda: True)
- monkeypatch.setenv(accel.CT2_ALLOCATOR_ENV, "default")
- assert accel.configure_ct2_allocator() == "default"
- assert os.environ[accel.CT2_ALLOCATOR_ENV] == "default"
- @pytest.mark.parametrize(
- ("configured", "resolved", "count"),
- [
- ("cpu", "cpu", 0),
- ("auto", "cuda", 1),
- ],
- )
- def test_acceleration_warning_returns_none_when_healthy(configured, resolved, count):
- assert accel.acceleration_warning(configured, resolved, count) is None
- def test_acceleration_warning_cuda_states_request_not_honored(monkeypatch):
- message = accel.acceleration_warning("cuda", "cpu", 0)
- assert message is not None
- assert "device=cuda requested" in message
- assert "no CUDA/HIP device" in message
- assert "install-rocm.sh" in message
- assert "\n" not in message
- def test_acceleration_warning_auto_names_missing_device(monkeypatch):
- message = accel.acceleration_warning("auto", "cpu", 0)
- assert message is not None
- assert "device=auto resolved to CPU" in message
- assert "no CUDA/HIP" in message
- assert "install-rocm.sh" in message
- assert "\n" not in message
- def test_transcriber_sets_allocator_before_model_creation(monkeypatch, caplog):
- monkeypatch.delenv(accel.CT2_ALLOCATOR_ENV, raising=False)
- monkeypatch.setattr(accel, "is_rocm_host", lambda: True)
- monkeypatch.setattr(transcriber_mod, "ct2_cuda_device_count", lambda: 1)
- monkeypatch.setattr(transcriber_mod, "WhisperModel", _RecordingWhisperModel)
- with caplog.at_level(logging.INFO, logger="stt_daemon.transcriber"):
- transcriber = Transcriber(
- "small", device="cpu", compute_type="int8", language=None
- )
- assert _RecordingWhisperModel.last is not None
- assert _RecordingWhisperModel.last.allocator_at_creation == "cub_caching"
- assert transcriber.device == "cpu"
- assert "device=cpu" in caplog.text
- assert "ct2_allocator=cub_caching" in caplog.text
- def test_transcriber_cuda_fallback_logs_without_stdout(monkeypatch, caplog, capsys):
- monkeypatch.delenv(accel.CT2_ALLOCATOR_ENV, raising=False)
- monkeypatch.setitem(sys.modules, "torch", _fake_torch(hip=None))
- monkeypatch.setattr(transcriber_mod, "ct2_cuda_device_count", lambda: 0)
- monkeypatch.setattr(transcriber_mod, "WhisperModel", _RecordingWhisperModel)
- with caplog.at_level(logging.WARNING, logger="stt_daemon.transcriber"):
- transcriber = Transcriber(
- "small", device="cuda", compute_type="float16", language=None
- )
- assert capsys.readouterr().out == ""
- assert transcriber.device == "cpu"
- assert transcriber.compute_type == "int8"
- assert "CUDA requested but not available" in caplog.text
- @pytest.mark.parametrize(
- ("compute_type", "expected"),
- [
- ("float16", "int8"),
- ("int8_float16", "int8"),
- ("bfloat16", "int8"),
- ("int8_bfloat16", "int8"),
- ("int8", "int8"),
- ("int8_float32", "int8_float32"),
- ("float32", "float32"),
- ],
- )
- def test_cpu_safe_compute_type(compute_type, expected):
- assert accel.cpu_safe_compute_type(compute_type) == expected
- def test_transcriber_downgrades_gpu_compute_type_when_no_device(monkeypatch, caplog):
- monkeypatch.delenv(accel.CT2_ALLOCATOR_ENV, raising=False)
- monkeypatch.setattr(accel, "is_rocm_host", lambda: True)
- monkeypatch.setattr(transcriber_mod, "ct2_cuda_device_count", lambda: 0)
- monkeypatch.setattr(transcriber_mod, "WhisperModel", _RecordingWhisperModel)
- with caplog.at_level(logging.WARNING, logger="stt_daemon.transcriber"):
- transcriber = Transcriber(
- "small", device="auto", compute_type="float16", language=None
- )
- assert _RecordingWhisperModel.last is not None
- assert _RecordingWhisperModel.last.device_arg == "cpu"
- assert _RecordingWhisperModel.last.compute_type_arg == "int8"
- assert transcriber.device == "cpu"
- assert transcriber.compute_type == "int8"
- assert "device=auto resolved to CPU" in caplog.text
- assert "needs a GPU" in caplog.text
- def test_transcriber_downgrades_gpu_compute_type_on_explicit_cpu(monkeypatch, caplog):
- monkeypatch.delenv(accel.CT2_ALLOCATOR_ENV, raising=False)
- monkeypatch.setattr(accel, "is_rocm_host", lambda: False)
- monkeypatch.setattr(transcriber_mod, "ct2_cuda_device_count", lambda: 1)
- monkeypatch.setattr(transcriber_mod, "WhisperModel", _RecordingWhisperModel)
- with caplog.at_level(logging.WARNING, logger="stt_daemon.transcriber"):
- Transcriber("small", device="cpu", compute_type="float16", language=None)
- assert _RecordingWhisperModel.last is not None
- assert _RecordingWhisperModel.last.compute_type_arg == "int8"
- assert "needs a GPU" in caplog.text
|