From fae25a7fb37be75e0bee8f780dd0e3a09febf4ab Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Sat, 21 Mar 2026 23:22:13 +0300 Subject: [PATCH] =?UTF-8?q?feat(config):=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80?= =?UTF-8?q?=D0=B6=D0=BA=D0=B0=20=D1=83=D1=81=D1=82=D1=80=D0=BE=D0=B9=D1=81?= =?UTF-8?q?=D1=82=D0=B2=D0=B0=20openvino=20=D0=B2=20=D0=BA=D0=BE=D0=BD?= =?UTF-8?q?=D1=84=D0=B8=D0=B3=D0=B5=20=D0=B8=20auto-detect?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Зачем: - подготовка к OpenVINO бэкенду: config и utils должны знать о новом устройстве. - Что: - config.py: openvino добавлен в _VALID_DEVICES и DEVICE_DEFAULTS (model=medium, compute_type=int8). - utils.py: detect_device() расширен цепочкой CUDA → OpenVINO → CPU; _is_openvino_available() проверяет архитектуру (x86_64/AMD64) и наличие openvino_genai. - cli.py: --device help text обновлён (auto|cpu|cuda|openvino). - добавлены тесты: config с openvino device, device defaults, auto-detect приоритет. - Проверка: - uv run pytest -v — 103 passed. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/local_transcriber/cli.py | 2 +- src/local_transcriber/config.py | 3 ++- src/local_transcriber/utils.py | 19 +++++++++++++++++-- tests/test_config.py | 15 +++++++++++++++ tests/test_utils.py | 28 ++++++++++++++++++++++++++++ 5 files changed, 63 insertions(+), 4 deletions(-) diff --git a/src/local_transcriber/cli.py b/src/local_transcriber/cli.py index a62215e..84edf27 100644 --- a/src/local_transcriber/cli.py +++ b/src/local_transcriber/cli.py @@ -51,7 +51,7 @@ def main( output: Path | None = typer.Option(None, "--output", "-o", help="Путь к выходному файлу"), device: str | None = typer.Option( None, "--device", "-d", show_default=False, - help="Устройство (auto|cpu|cuda) [по умолч.: auto]" + help="Устройство (auto|cpu|cuda|openvino) [по умолч.: auto]" ), compute_type: str | None = typer.Option( None, "--compute-type", show_default=False, diff --git a/src/local_transcriber/config.py b/src/local_transcriber/config.py index 9ba8cdf..f58b57b 100644 --- a/src/local_transcriber/config.py +++ b/src/local_transcriber/config.py @@ -19,11 +19,12 @@ HARDCODED_DEFAULTS: dict[str, str] = { DEVICE_DEFAULTS: dict[str, dict[str, str]] = { "cuda": {"model": "medium", "compute_type": "float16"}, "cpu": {"model": "medium", "compute_type": "float32"}, + "openvino": {"model": "medium", "compute_type": "int8"}, } # Одно место правды для допустимых ключей конфига _VALID_KEYS = set(HARDCODED_DEFAULTS) -_VALID_DEVICES = {"auto", "cpu", "cuda"} +_VALID_DEVICES = {"auto", "cpu", "cuda", "openvino"} def find_config_file() -> Path | None: diff --git a/src/local_transcriber/utils.py b/src/local_transcriber/utils.py index bb22fa4..8a7fcfd 100644 --- a/src/local_transcriber/utils.py +++ b/src/local_transcriber/utils.py @@ -1,6 +1,7 @@ """Утилиты для валидации входных файлов, определения устройства и работы с путями.""" import glob +import platform import shutil import subprocess import warnings @@ -15,16 +16,30 @@ SUPPORTED_EXTENSIONS = { def detect_device(requested: str = "auto") -> str: """Определяет устройство для вычислений. - При ``requested="auto"`` проверяет наличие ``nvidia-smi`` в PATH - и возвращает ``"cuda"`` или ``"cpu"``. Явное значение возвращается как есть. + При ``requested="auto"`` проверяет: CUDA → OpenVINO → CPU. + Явное значение возвращается как есть. """ if requested != "auto": return requested if shutil.which("nvidia-smi") is not None: return "cuda" + if _is_openvino_available(): + return "openvino" return "cpu" +def _is_openvino_available() -> bool: + """Проверяет доступность OpenVINO: x86/AMD64 архитектура + пакет установлен.""" + if platform.machine().lower() not in {"x86_64", "amd64"}: + return False + try: + import openvino_genai # noqa: F401 + + return True + except ImportError: + return False + + def get_gpu_name() -> str | None: """Возвращает название GPU через ``nvidia-smi`` (для метаданных транскрипта).""" try: diff --git a/tests/test_config.py b/tests/test_config.py index 0fdd84e..77888e1 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -128,3 +128,18 @@ def test_apply_device_defaults_config_overrides(): result = apply_device_defaults(defaults, "cuda", cli, config) assert result["model"] == "small" assert result["compute_type"] == "int8" + + +def test_load_config_openvino_device(tmp_path): + config = tmp_path / "config.toml" + config.write_text('device = "openvino"\n') + result = load_config(config) + assert result == {"device": "openvino"} + + +def test_apply_device_defaults_openvino(): + defaults = {"model": "medium", "language": "ru", "device": "auto", "compute_type": "float32"} + cli = {"model": None, "language": None, "device": None, "compute_type": None} + result = apply_device_defaults(defaults, "openvino", cli, {}) + assert result["model"] == "medium" + assert result["compute_type"] == "int8" diff --git a/tests/test_utils.py b/tests/test_utils.py index d49cd2b..8ebb49d 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -58,6 +58,34 @@ def test_build_output_path_custom(): def test_detect_device_explicit(): assert detect_device("cpu") == "cpu" assert detect_device("cuda") == "cuda" + assert detect_device("openvino") == "openvino" + + +def test_detect_device_auto_openvino(): + """Нет nvidia-smi, есть openvino_genai, x86_64 → openvino.""" + with ( + patch("local_transcriber.utils.shutil.which", return_value=None), + patch("local_transcriber.utils._is_openvino_available", return_value=True), + ): + assert detect_device("auto") == "openvino" + + +def test_detect_device_cuda_over_openvino(): + """nvidia-smi доступен и openvino тоже → cuda побеждает.""" + with ( + patch("local_transcriber.utils.shutil.which", return_value="/usr/bin/nvidia-smi"), + patch("local_transcriber.utils._is_openvino_available", return_value=True), + ): + assert detect_device("auto") == "cuda" + + +def test_detect_device_auto_cpu_fallback(): + """Ни nvidia-smi, ни openvino → cpu.""" + with ( + patch("local_transcriber.utils.shutil.which", return_value=None), + patch("local_transcriber.utils._is_openvino_available", return_value=False), + ): + assert detect_device("auto") == "cpu" def test_get_gpu_name_no_nvidia_smi():