fix(auto)!: учтены ограничения ONNX-профиля
- Зачем: - после смены auto-профиля пользователю нужны явные предупреждения о языковых ограничениях и рабочие пути выбора Whisper-бэкенда. - Что: - в MODEL_CATALOG добавлены поддерживаемые языки ONNX-моделей и предупреждение о несовместимом языке. - для Whisper-имён добавлены подсказки с совместимыми парами backend/model. - вывод языка различает детекцию, язык моноязычной модели и отсутствие детекции. - усилены тесты implicit compute type и turbo-каталога, удалены дубли auto-тестов. - Проверка: - uv run pytest -q: 241 passed, 1 skipped. - uv lock --check, compileall и git diff --check. BREAKING CHANGE: --device auto без NVIDIA теперь выбирает ONNX GigaAM RNN-T; прежний профиль возвращается явным --device или конфигурацией.
This commit is contained in:
@@ -17,15 +17,10 @@ from local_transcriber.types import Segment
|
||||
# === _resolve_repo ===
|
||||
|
||||
|
||||
def test_model_catalog_contains_supported_profiles():
|
||||
assert MODEL_REPOS == {
|
||||
("tiny", "int8"): "OpenVINO/whisper-tiny-int8-ov",
|
||||
("base", "fp16"): "OpenVINO/whisper-base-fp16-ov",
|
||||
("small", "int8"): "OpenVINO/whisper-small-int8-ov",
|
||||
("medium", "int8"): "OpenVINO/whisper-medium-int8-ov",
|
||||
("medium", "fp16"): "OpenVINO/whisper-medium-fp16-ov",
|
||||
("large-v3", "int8"): "OpenVINO/whisper-large-v3-int8-ov",
|
||||
("large-v3", "fp16"): "OpenVINO/whisper-large-v3-fp16-ov",
|
||||
def test_model_catalog_contains_large_v3_turbo_profiles():
|
||||
assert {
|
||||
pair: repo for pair, repo in MODEL_REPOS.items() if pair[0] == "large-v3-turbo"
|
||||
} == {
|
||||
("large-v3-turbo", "int8"): "OpenVINO/whisper-large-v3-turbo-int8-ov",
|
||||
("large-v3-turbo", "fp16"): "OpenVINO/whisper-large-v3-turbo-fp16-ov",
|
||||
}
|
||||
@@ -61,11 +56,20 @@ def test_resolve_repo_implicit_fallback():
|
||||
assert backend._resolve_repo("base", "int8") == ("OpenVINO/whisper-base-fp16-ov", "fp16")
|
||||
|
||||
|
||||
def test_resolve_repo_implicit_large_v3_prefers_fp16():
|
||||
"""Неявный compute_type: large-v3 автоматически получает fp16."""
|
||||
@pytest.mark.parametrize(
|
||||
("model_name", "expected_compute_type"),
|
||||
[("large-v3", "fp16"), ("large-v3-turbo", "int8")],
|
||||
)
|
||||
def test_resolve_repo_implicit_large_v3_profiles(
|
||||
model_name, expected_compute_type
|
||||
):
|
||||
"""Неявный compute_type различает обычную и turbo-модель."""
|
||||
backend = OpenVINOBackend(compute_type_explicit=False)
|
||||
# Дефолт int8, но для large-v3 override на fp16
|
||||
assert backend._resolve_repo("large-v3", "int8") == ("OpenVINO/whisper-large-v3-fp16-ov", "fp16")
|
||||
|
||||
assert backend._resolve_repo(model_name, "int8") == (
|
||||
f"OpenVINO/whisper-{model_name}-{expected_compute_type}-ov",
|
||||
expected_compute_type,
|
||||
)
|
||||
|
||||
|
||||
def test_resolve_repo_explicit_large_v3_int8_respected():
|
||||
|
||||
+19
-1
@@ -5,7 +5,7 @@ import pytest
|
||||
from rich.console import Console
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from local_transcriber.cli import _format_device_info, app
|
||||
from local_transcriber.cli import _format_device_info, _format_language_mode, app
|
||||
from local_transcriber.transcriber import Segment, TranscribeFileResult, TranscribeResult
|
||||
|
||||
runner = CliRunner()
|
||||
@@ -42,6 +42,24 @@ def _make_tfr(result=None, model=None, actual_device="cpu", backend=None, model_
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("requested_language", "language", "probability", "expected"),
|
||||
[
|
||||
("ru", "ru", 1.0, "forced"),
|
||||
("auto", "ru", 0.95, "detected"),
|
||||
("auto", "ru", 0.0, "из профиля модели"),
|
||||
("auto", "unknown", 0.0, "не определён"),
|
||||
],
|
||||
)
|
||||
def test_format_language_mode(
|
||||
requested_language, language, probability, expected
|
||||
):
|
||||
result = _make_result(language=language)
|
||||
result.language_probability = probability
|
||||
|
||||
assert _format_language_mode(requested_language, result) == expected
|
||||
|
||||
|
||||
def _single_patches(result=None, tmp_file=None, actual_device="cpu"):
|
||||
"""Patches for a standard single-file CLI happy path."""
|
||||
if result is None:
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Tests for onnx-asr backend."""
|
||||
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
from local_transcriber.backends.onnx_asr import OnnxAsrBackend
|
||||
@@ -224,6 +226,68 @@ class TestCreateModel:
|
||||
|
||||
|
||||
class TestTranscribe:
|
||||
@pytest.mark.parametrize(
|
||||
("model_name", "language", "expects_warning"),
|
||||
[
|
||||
("gigaam-v3-e2e-rnnt", "en", True),
|
||||
("gigaam-v3-e2e-rnnt", "ru", False),
|
||||
("gigaam-multilingual-ctc", "en", False),
|
||||
],
|
||||
)
|
||||
def test_warns_when_language_is_not_supported(
|
||||
self, monkeypatch, tmp_path, model_name, language, expects_warning
|
||||
):
|
||||
wav_file = tmp_path / "test.wav"
|
||||
wav_file.write_bytes(b"fake audio")
|
||||
|
||||
monkeypatch.setattr(
|
||||
"faster_whisper.decode_audio",
|
||||
lambda path, sampling_rate=16000: [0.0] * 16000,
|
||||
)
|
||||
|
||||
class FakeModel:
|
||||
def recognize(self, waveform, sample_rate, language=None):
|
||||
return iter(())
|
||||
|
||||
backend = OnnxAsrBackend()
|
||||
backend.ensure_model_available(model_name, "int8")
|
||||
|
||||
if expects_warning:
|
||||
with pytest.warns(
|
||||
UserWarning,
|
||||
match=(
|
||||
r"Язык 'en'.*--device openvino-cpu --model medium.*"
|
||||
r"--device cuda --model medium"
|
||||
),
|
||||
):
|
||||
backend.transcribe(FakeModel(), wav_file, language=language)
|
||||
else:
|
||||
with warnings.catch_warnings(record=True) as caught:
|
||||
backend.transcribe(FakeModel(), wav_file, language=language)
|
||||
assert caught == []
|
||||
|
||||
def test_auto_language_uses_single_supported_model_language(
|
||||
self, monkeypatch, tmp_path
|
||||
):
|
||||
wav_file = tmp_path / "test.wav"
|
||||
wav_file.write_bytes(b"fake audio")
|
||||
monkeypatch.setattr(
|
||||
"faster_whisper.decode_audio",
|
||||
lambda path, sampling_rate=16000: [0.0] * 16000,
|
||||
)
|
||||
|
||||
class FakeModel:
|
||||
def recognize(self, waveform, sample_rate, language=None):
|
||||
return iter(())
|
||||
|
||||
backend = OnnxAsrBackend()
|
||||
backend.ensure_model_available("gigaam-v3-e2e-rnnt", "int8")
|
||||
|
||||
result = backend.transcribe(FakeModel(), wav_file, language=None)
|
||||
|
||||
assert result.language == "ru"
|
||||
assert result.language_probability == 0.0
|
||||
|
||||
def test_transcribe_collects_segments(self, monkeypatch, tmp_path):
|
||||
"""Verify transcribe maps VAD segments to project Segments."""
|
||||
wav_file = tmp_path / "test.wav"
|
||||
@@ -386,3 +450,23 @@ class TestModelAliases:
|
||||
backend = OnnxAsrBackend()
|
||||
with pytest.raises(ValueError, match="Неподдерживаемая модель"):
|
||||
backend._resolve_model("nonexistent-model")
|
||||
|
||||
def test_whisper_alias_error_suggests_explicit_backend(self):
|
||||
backend = OnnxAsrBackend()
|
||||
|
||||
with pytest.raises(
|
||||
ValueError,
|
||||
match=r"Whisper.*--device openvino-cpu.*--device cuda",
|
||||
):
|
||||
backend._resolve_model("medium")
|
||||
|
||||
def test_turbo_whisper_error_suggests_models_supported_by_backends(self):
|
||||
backend = OnnxAsrBackend()
|
||||
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
backend._resolve_model("large-v3-turbo")
|
||||
|
||||
message = str(exc_info.value)
|
||||
assert "--device openvino-cpu --model large-v3-turbo" in message
|
||||
assert "--device cuda --model medium" in message
|
||||
assert "--device cuda --model large-v3-turbo" not in message
|
||||
|
||||
+6
-30
@@ -66,41 +66,17 @@ def test_detect_device_explicit_passthrough():
|
||||
assert detect_device("openvino-cpu") == "openvino-cpu"
|
||||
|
||||
|
||||
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") == "onnx"
|
||||
|
||||
|
||||
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") == "onnx"
|
||||
|
||||
|
||||
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_gpu_available", return_value=True),
|
||||
def test_detect_device_auto_cuda_when_nvidia_smi_available():
|
||||
"""При доступном nvidia-smi auto выбирает CUDA."""
|
||||
with patch(
|
||||
"local_transcriber.utils.shutil.which", return_value="/usr/bin/nvidia-smi"
|
||||
):
|
||||
assert detect_device("auto") == "cuda"
|
||||
|
||||
|
||||
def test_detect_device_auto_onnx_without_accelerators():
|
||||
def test_detect_device_auto_onnx_without_cuda():
|
||||
"""Без 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),
|
||||
):
|
||||
with patch("local_transcriber.utils.shutil.which", return_value=None):
|
||||
assert detect_device("auto") == "onnx"
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user