test_accel.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. """Tests for the ROCm accelerator helpers."""
  2. import logging
  3. import os
  4. import sys
  5. import types
  6. import pytest
  7. from stt_daemon import accel
  8. from stt_daemon import transcriber as transcriber_mod
  9. from stt_daemon.transcriber import Transcriber
  10. def _fake_torch(*, hip=None, cuda_available=False):
  11. return types.SimpleNamespace(
  12. version=types.SimpleNamespace(hip=hip),
  13. cuda=types.SimpleNamespace(is_available=lambda: cuda_available),
  14. )
  15. class _RecordingWhisperModel:
  16. """Records the allocator and arguments visible at construction time."""
  17. last: "_RecordingWhisperModel | None" = None
  18. def __init__(self, model, device, compute_type, cpu_threads=0):
  19. self.model = None # no CTranslate2 inner model in the fake
  20. self.allocator_at_creation = os.environ.get(accel.CT2_ALLOCATOR_ENV)
  21. self.device_arg = device
  22. self.compute_type_arg = compute_type
  23. _RecordingWhisperModel.last = self
  24. def test_is_rocm_host_true_when_torch_reports_hip(monkeypatch):
  25. monkeypatch.setitem(sys.modules, "torch", _fake_torch(hip="7.2.0"))
  26. assert accel.is_rocm_host() is True
  27. def test_is_rocm_host_false_when_torch_reports_no_hip(monkeypatch):
  28. monkeypatch.setitem(sys.modules, "torch", _fake_torch(hip=None))
  29. assert accel.is_rocm_host() is False
  30. def test_is_rocm_host_false_without_torch_env_or_marker(monkeypatch):
  31. monkeypatch.delitem(sys.modules, "torch", raising=False)
  32. monkeypatch.delenv("ROCM_PATH", raising=False)
  33. monkeypatch.delenv("ROCM_HOME", raising=False)
  34. monkeypatch.setattr(accel, "_ROCM_MARKER_PATHS", ())
  35. assert accel.is_rocm_host() is False
  36. def test_is_rocm_host_true_when_marker_path_is_a_directory(monkeypatch, tmp_path):
  37. monkeypatch.delitem(sys.modules, "torch", raising=False)
  38. monkeypatch.delenv("ROCM_PATH", raising=False)
  39. monkeypatch.delenv("ROCM_HOME", raising=False)
  40. monkeypatch.setattr(accel, "_ROCM_MARKER_PATHS", (str(tmp_path),))
  41. assert accel.is_rocm_host() is True
  42. def test_configure_ct2_allocator_is_noop_on_non_rocm_host(monkeypatch):
  43. monkeypatch.setattr(accel, "is_rocm_host", lambda: False)
  44. monkeypatch.delenv(accel.CT2_ALLOCATOR_ENV, raising=False)
  45. assert accel.configure_ct2_allocator() is None
  46. assert accel.CT2_ALLOCATOR_ENV not in os.environ
  47. def test_configure_ct2_allocator_sets_cub_caching_on_rocm_host(monkeypatch):
  48. monkeypatch.setattr(accel, "is_rocm_host", lambda: True)
  49. monkeypatch.delenv(accel.CT2_ALLOCATOR_ENV, raising=False)
  50. assert accel.configure_ct2_allocator() == accel.ROCM_CT2_ALLOCATOR
  51. assert os.environ[accel.CT2_ALLOCATOR_ENV] == "cub_caching"
  52. def test_configure_ct2_allocator_preserves_explicit_value(monkeypatch):
  53. monkeypatch.setattr(accel, "is_rocm_host", lambda: True)
  54. monkeypatch.setenv(accel.CT2_ALLOCATOR_ENV, "default")
  55. assert accel.configure_ct2_allocator() == "default"
  56. assert os.environ[accel.CT2_ALLOCATOR_ENV] == "default"
  57. @pytest.mark.parametrize(
  58. ("configured", "resolved", "count"),
  59. [
  60. ("cpu", "cpu", 0),
  61. ("auto", "cuda", 1),
  62. ],
  63. )
  64. def test_acceleration_warning_returns_none_when_healthy(configured, resolved, count):
  65. assert accel.acceleration_warning(configured, resolved, count) is None
  66. def test_acceleration_warning_cuda_states_request_not_honored(monkeypatch):
  67. message = accel.acceleration_warning("cuda", "cpu", 0)
  68. assert message is not None
  69. assert "device=cuda requested" in message
  70. assert "no CUDA/HIP device" in message
  71. assert "install-rocm.sh" in message
  72. assert "\n" not in message
  73. def test_acceleration_warning_auto_names_missing_device(monkeypatch):
  74. message = accel.acceleration_warning("auto", "cpu", 0)
  75. assert message is not None
  76. assert "device=auto resolved to CPU" in message
  77. assert "no CUDA/HIP" in message
  78. assert "install-rocm.sh" in message
  79. assert "\n" not in message
  80. def test_transcriber_sets_allocator_before_model_creation(monkeypatch, caplog):
  81. monkeypatch.delenv(accel.CT2_ALLOCATOR_ENV, raising=False)
  82. monkeypatch.setattr(accel, "is_rocm_host", lambda: True)
  83. monkeypatch.setattr(transcriber_mod, "ct2_cuda_device_count", lambda: 1)
  84. monkeypatch.setattr(transcriber_mod, "WhisperModel", _RecordingWhisperModel)
  85. with caplog.at_level(logging.INFO, logger="stt_daemon.transcriber"):
  86. transcriber = Transcriber(
  87. "small", device="cpu", compute_type="int8", language=None
  88. )
  89. assert _RecordingWhisperModel.last is not None
  90. assert _RecordingWhisperModel.last.allocator_at_creation == "cub_caching"
  91. assert transcriber.device == "cpu"
  92. assert "device=cpu" in caplog.text
  93. assert "ct2_allocator=cub_caching" in caplog.text
  94. def test_transcriber_cuda_fallback_logs_without_stdout(monkeypatch, caplog, capsys):
  95. monkeypatch.delenv(accel.CT2_ALLOCATOR_ENV, raising=False)
  96. monkeypatch.setitem(sys.modules, "torch", _fake_torch(hip=None))
  97. monkeypatch.setattr(transcriber_mod, "ct2_cuda_device_count", lambda: 0)
  98. monkeypatch.setattr(transcriber_mod, "WhisperModel", _RecordingWhisperModel)
  99. with caplog.at_level(logging.WARNING, logger="stt_daemon.transcriber"):
  100. transcriber = Transcriber(
  101. "small", device="cuda", compute_type="float16", language=None
  102. )
  103. assert capsys.readouterr().out == ""
  104. assert transcriber.device == "cpu"
  105. assert transcriber.compute_type == "int8"
  106. assert "CUDA requested but not available" in caplog.text
  107. @pytest.mark.parametrize(
  108. ("compute_type", "expected"),
  109. [
  110. ("float16", "int8"),
  111. ("int8_float16", "int8"),
  112. ("bfloat16", "int8"),
  113. ("int8_bfloat16", "int8"),
  114. ("int8", "int8"),
  115. ("int8_float32", "int8_float32"),
  116. ("float32", "float32"),
  117. ],
  118. )
  119. def test_cpu_safe_compute_type(compute_type, expected):
  120. assert accel.cpu_safe_compute_type(compute_type) == expected
  121. def test_transcriber_downgrades_gpu_compute_type_when_no_device(monkeypatch, caplog):
  122. monkeypatch.delenv(accel.CT2_ALLOCATOR_ENV, raising=False)
  123. monkeypatch.setattr(accel, "is_rocm_host", lambda: True)
  124. monkeypatch.setattr(transcriber_mod, "ct2_cuda_device_count", lambda: 0)
  125. monkeypatch.setattr(transcriber_mod, "WhisperModel", _RecordingWhisperModel)
  126. with caplog.at_level(logging.WARNING, logger="stt_daemon.transcriber"):
  127. transcriber = Transcriber(
  128. "small", device="auto", compute_type="float16", language=None
  129. )
  130. assert _RecordingWhisperModel.last is not None
  131. assert _RecordingWhisperModel.last.device_arg == "cpu"
  132. assert _RecordingWhisperModel.last.compute_type_arg == "int8"
  133. assert transcriber.device == "cpu"
  134. assert transcriber.compute_type == "int8"
  135. assert "device=auto resolved to CPU" in caplog.text
  136. assert "needs a GPU" in caplog.text
  137. def test_transcriber_downgrades_gpu_compute_type_on_explicit_cpu(monkeypatch, caplog):
  138. monkeypatch.delenv(accel.CT2_ALLOCATOR_ENV, raising=False)
  139. monkeypatch.setattr(accel, "is_rocm_host", lambda: False)
  140. monkeypatch.setattr(transcriber_mod, "ct2_cuda_device_count", lambda: 1)
  141. monkeypatch.setattr(transcriber_mod, "WhisperModel", _RecordingWhisperModel)
  142. with caplog.at_level(logging.WARNING, logger="stt_daemon.transcriber"):
  143. Transcriber("small", device="cpu", compute_type="float16", language=None)
  144. assert _RecordingWhisperModel.last is not None
  145. assert _RecordingWhisperModel.last.compute_type_arg == "int8"
  146. assert "needs a GPU" in caplog.text