feat(auto): выбран ONNX по умолчанию без CUDA

- Зачем:
  - пользователям без NVIDIA нужен самый быстрый и читаемый CPU-профиль без дополнительных параметров.
- Что:
  - auto-политика изменена на CUDA при наличии nvidia-smi, иначе ONNX GigaAM RNN-T int8.
  - сохранён приоритет явных значений CLI и конфигурации для OpenVINO и FasterWhisper CPU.
  - обновлены тесты, README, PRD, ADR, GPU-документация и вывод benchmark.
- Проверка:
  - uv run pytest -q: 232 passed, 1 skipped.
  - uv lock --check и git diff --cached --check.
This commit is contained in:
Dmitriy Dementiev
2026-08-12 10:45:05 +03:00
parent 12e17020bf
commit 136e93765c
13 changed files with 177 additions and 115 deletions
+51 -7
View File
@@ -73,27 +73,32 @@ def test_cli_happy_path_exit_code_zero(tmp_path):
def test_cli_default_options_passed_to_transcribe(tmp_path):
audio = tmp_path / "test.mp3"
audio.write_bytes(b"fake")
result = _make_result()
result = _make_result(device_used="onnx")
model = _make_model()
backend = _make_backend()
tfr = _make_tfr(result=result, model=model, backend=backend)
tfr = _make_tfr(result=result, model=model, actual_device="onnx", backend=backend)
mock_transcribe_file = MagicMock(return_value=tfr)
with (
patch("local_transcriber.cli.load_config", return_value={}),
patch("local_transcriber.cli.validate_input_file", return_value=audio),
patch("local_transcriber.cli.detect_device", return_value="cpu"),
patch("local_transcriber.cli.load_model", return_value=(model, "cpu", backend, "/models/medium")),
patch("local_transcriber.cli.detect_device", return_value="onnx"),
patch(
"local_transcriber.cli.load_model",
return_value=(model, "onnx", backend, "/models/gigaam-v3-e2e-rnnt"),
),
patch("local_transcriber.cli._transcribe_file", mock_transcribe_file),
patch("local_transcriber.cli.write_transcript"),
):
runner.invoke(app, [str(audio)])
out = runner.invoke(app, [str(audio)])
call_kwargs = mock_transcribe_file.call_args[1]
assert call_kwargs["model_name"] == "medium"
assert call_kwargs["compute_type"] == "float32"
assert call_kwargs["model_name"] == "gigaam-v3-e2e-rnnt"
assert call_kwargs["compute_type"] == "int8"
assert call_kwargs["language"] == "ru"
assert call_kwargs["on_segment"] is None # verbose=False
assert "Модель: gigaam-v3-e2e-rnnt" in out.output
assert "Устройство: onnx" in out.output
def test_cli_custom_options(tmp_path):
@@ -660,6 +665,45 @@ def test_cli_config_applied(tmp_path):
assert mock_load_model.call_args[0][0] == "tiny"
def test_cli_config_overrides_auto_device(tmp_path):
audio = tmp_path / "test.mp3"
audio.write_bytes(b"fake")
model = _make_model()
backend = _make_backend()
result = _make_result(device_used="openvino-cpu")
tfr = _make_tfr(
result=result,
model=model,
actual_device="openvino-cpu",
backend=backend,
)
mock_detect_device = MagicMock(return_value="openvino-cpu")
mock_load_model = MagicMock(
return_value=(model, "openvino-cpu", backend, "/models/medium")
)
with (
patch(
"local_transcriber.cli.load_config",
return_value={
"device": "openvino-cpu",
"model": "medium",
"compute_type": "int8",
},
),
patch("local_transcriber.cli.validate_input_file", return_value=audio),
patch("local_transcriber.cli.detect_device", mock_detect_device),
patch("local_transcriber.cli.load_model", mock_load_model),
patch("local_transcriber.cli._transcribe_file", return_value=tfr),
patch("local_transcriber.cli.write_transcript"),
):
out = runner.invoke(app, [str(audio)])
assert out.exit_code == 0
assert mock_detect_device.call_args_list[0].args == ("openvino-cpu",)
assert mock_load_model.call_args.args[:3] == ("medium", "openvino-cpu", "int8")
def test_cli_cli_overrides_config(tmp_path):
audio = tmp_path / "test.mp3"
audio.write_bytes(b"fake")
+8 -2
View File
@@ -71,11 +71,17 @@ def test_load_config_invalid_device(tmp_path):
def test_resolve_defaults_cli_wins():
config = {"model": "tiny", "language": "en"}
cli = {"model": "small", "language": None, "device": None, "compute_type": None}
config = {"model": "tiny", "language": "en", "device": "openvino-cpu"}
cli = {
"model": "small",
"language": None,
"device": "onnx",
"compute_type": None,
}
result = resolve_defaults(cli, config)
assert result["model"] == "small"
assert result["language"] == "en"
assert result["device"] == "onnx"
def test_resolve_defaults_config_wins():
+10 -9
View File
@@ -61,27 +61,28 @@ def test_detect_device_explicit_passthrough():
"""Явные device strings проходят без изменений."""
assert detect_device("cpu") == "cpu"
assert detect_device("cuda") == "cuda"
assert detect_device("onnx") == "onnx"
assert detect_device("openvino-gpu") == "openvino-gpu"
assert detect_device("openvino-cpu") == "openvino-cpu"
def test_detect_device_auto_openvino_gpu():
"""auto + нет nvidia-smi + есть OpenVINO GPU → openvino-gpu."""
def test_detect_device_auto_onnx_even_with_openvino_gpu():
"""auto + нет nvidia-smi → onnx, даже если доступен OpenVINO GPU."""
with (
patch("local_transcriber.utils.shutil.which", return_value=None),
patch("local_transcriber.utils._is_openvino_gpu_available", return_value=True),
):
assert detect_device("auto") == "openvino-gpu"
assert detect_device("auto") == "onnx"
def test_detect_device_auto_openvino_cpu():
"""auto + нет nvidia-smi + есть OpenVINO, нет GPU → openvino-cpu."""
def test_detect_device_auto_onnx_even_with_openvino_cpu():
"""auto + нет nvidia-smi → onnx, даже если доступен OpenVINO CPU."""
with (
patch("local_transcriber.utils.shutil.which", return_value=None),
patch("local_transcriber.utils._is_openvino_gpu_available", return_value=False),
patch("local_transcriber.utils._is_openvino_available", return_value=True),
):
assert detect_device("auto") == "openvino-cpu"
assert detect_device("auto") == "onnx"
def test_detect_device_cuda_over_openvino():
@@ -93,14 +94,14 @@ def test_detect_device_cuda_over_openvino():
assert detect_device("auto") == "cuda"
def test_detect_device_auto_cpu_fallback():
"""Ни nvidia-smi, ни openvino → cpu."""
def test_detect_device_auto_onnx_without_accelerators():
"""Без CUDA auto выбирает ONNX CPU."""
with (
patch("local_transcriber.utils.shutil.which", return_value=None),
patch("local_transcriber.utils._is_openvino_gpu_available", return_value=False),
patch("local_transcriber.utils._is_openvino_available", return_value=False),
):
assert detect_device("auto") == "cpu"
assert detect_device("auto") == "onnx"
def test_detect_device_openvino_resolves_to_gpu():