ADR-002 и ADR-003 отражают Transcriber как единственного владельца загрузки и fallback; README получил раздел «Диагностика исполнения» и уточнение --threads для ONNX; AGENTS.md — новые тестовые паттерны. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
118 lines
7.5 KiB
Markdown
118 lines
7.5 KiB
Markdown
# ADR-003: Pluggable backends и OpenVINO
|
|
|
|
**Статус**: Принято
|
|
**Дата**: 2026-03-21
|
|
**Обновлено**: 2026-09-21
|
|
|
|
## Контекст
|
|
|
|
На CPU (faster-whisper/CTranslate2) транскрипция работает медленно (~1.5x реалтайм для medium).
|
|
CUDA доступна на малом проценте машин (ноутбуки с NVIDIA GPU), на офисных ПК её нет.
|
|
|
|
OpenVINO ускоряет inference на x86 CPU (Intel и AMD) в 2-4 раза. Для его поддержки
|
|
нужен второй движок транскрипции, а архитектура должна позволять добавлять новые
|
|
бэкенды (CoreML для Mac, AMD XDNA NPU) без переписывания существующего кода.
|
|
|
|
## Решение
|
|
|
|
### Backend Protocol (structural typing)
|
|
|
|
Минимальный интерфейс в `backends/base.py`:
|
|
|
|
```python
|
|
class Backend(Protocol):
|
|
def ensure_model_available(self, model_name, compute_type, on_status) -> str: ...
|
|
def create_model(self, model_path, device, compute_type) -> Any: ...
|
|
def transcribe(self, model, file_path, language, on_segment, on_status) -> TranscribeResult: ...
|
|
```
|
|
|
|
Protocol вместо ABC — бэкенды не наследуются, достаточно реализовать методы.
|
|
Соответствует стилю проекта (наследование нигде не используется).
|
|
|
|
### Ленивые импорты
|
|
|
|
Бэкенды импортируются только при выборе — `get_backend(device)` делает import внутри.
|
|
CUDA bootstrap вызывается только при создании CUDA-модели (см. ADR-001).
|
|
Импорт openvino-genai загружает ~50MB shared libraries и не должен происходить,
|
|
если бэкенд не выбран.
|
|
|
|
### Device как селектор бэкенда
|
|
|
|
Вместо отдельного `--backend` флага устройство само определяет бэкенд:
|
|
- `cuda`, `cpu` → FasterWhisperBackend
|
|
- `openvino` → OpenVINOBackend
|
|
- `onnx` → OnnxAsrBackend
|
|
- `auto` → ONNX на CPU независимо от `nvidia-smi` и установки extra
|
|
|
|
### Transcriber — единственный владелец загрузки и исполнения
|
|
|
|
Обновлено 2026-09-21. `Transcriber(ExecutionRequest)` в `transcriber.py`
|
|
разрешает `auto` и device-aware умолчания, выполняет ensure_model_available +
|
|
create_model в одном месте и держит модель, adapter и фактическое исполнение
|
|
на весь запуск. CLI не вызывает `get_backend` и не хранит handles.
|
|
|
|
Внутри module различаются три вещи: поддерживаемая модель, движок
|
|
распознавания (`faster-whisper`, `openvino`, `onnx-asr`) и способ исполнения.
|
|
Значение `device` из CLI/TOML сохраняется как вход: таблица `_ENGINES`
|
|
сопоставляет ему движок, а аппаратную часть (CPU, CUDA, Intel GPU, список
|
|
providers) трактует adapter. Новое исполнение — строка таблицы и его
|
|
понимание в adapter'е, не новая иерархия adapters.
|
|
|
|
### Cross-backend fallback
|
|
|
|
Fallback живёт внутри `Transcriber`, не в бэкендах и не в CLI:
|
|
- CUDA ошибка → CPU (FasterWhisper)
|
|
- OpenVINO ошибка → CPU (FasterWhisper)
|
|
- strict запуск (явный device из CLI или TOML) → ошибка без fallback
|
|
|
|
После перехода module сам держит новое состояние; следующий файл батча
|
|
использует его без участия caller. `WordTimestampsUnavailableError`
|
|
и ошибки пользовательских данных переходом не считаются.
|
|
|
|
### Диагностика исполнения
|
|
|
|
Каждый adapter отдаёт `runtime_info()` — версии runtime и фактическую
|
|
конфигурацию (для ONNX — доступные providers и те, что заданы сессиям ASR
|
|
и VAD, квантизация, бюджет потоков). `Transcriber` включает их в
|
|
`ExecutionInfo`; CLI печатает их в `--verbose`. Наличие provider в wheel не
|
|
считается доказательством выполнения графа на GPU/NPU.
|
|
|
|
### Аудио для OpenVINO
|
|
|
|
OpenVINO GenAI WhisperPipeline принимает raw PCM float массив, не путь к файлу.
|
|
Используем `faster_whisper.decode_audio()` (PyAV) → `.tolist()` → `pipe.generate()`.
|
|
Системный ffmpeg не требуется — PyAV бандлит FFmpeg внутри wheel.
|
|
|
|
### compute_type для OpenVINO
|
|
|
|
OpenVINO модели предквантизированы (int8/fp16), compute_type определяет какую модель
|
|
скачать. Контракт:
|
|
- Явный `--compute-type` или значение из конфига — уважается всегда
|
|
- Из дефолтов: для large-v3 автоматически выбирается fp16 (стабильнее по качеству)
|
|
- Несуществующая пара (model + compute_type) при явном выборе → ошибка
|
|
|
|
### Зависимости бэкендов по умолчанию
|
|
|
|
faster-whisper, onnx-asr/onnxruntime и openvino-genai ставятся вместе. Модели
|
|
скачиваются только для активного бэкенда. cuBLAS подключается через extra `cuda`
|
|
для Linux/WSL x86_64 и Windows x64 (см. ADR-001). OpenVINO — conditional для x86_64/AMD64, кроме
|
|
macOS; ONNX обеспечивает автоматический CPU-путь на остальных платформах.
|
|
|
|
## Последствия
|
|
|
|
- Обратная совместимость: `transcribe()` сохранён; `load_model()` и `_transcribe_file()` удалены (2026-09-21)
|
|
- Новый бэкенд добавляется одним файлом в `backends/` + регистрацией в `__init__.py`
|
|
- Модели скачиваются по запросу — CUDA пользователь не качает OpenVINO модели, и наоборот
|
|
- ARM и macOS: OpenVINO не ставится (platform markers), auto использует ONNX
|
|
|
|
## Отклонённые альтернативы
|
|
|
|
| Альтернатива | Почему отклонена |
|
|
|---|---|
|
|
| OpenVINO как optional extra (`pip install .[openvino]`) | Теряется zero-config UX; пользователь должен знать про extras |
|
|
| whisper.cpp (pywhispercpp) | Другой движок, больший объём интеграции; OpenVINO GenAI проще |
|
|
| Единый бэкенд с OpenVINO для всего | CTranslate2 лучше оптимизирован для CUDA; OpenVINO — для CPU |
|
|
| ABC вместо Protocol | Наследование не используется в проекте; Protocol проще |
|
|
| librosa для загрузки аудио в OpenVINO | Лишняя зависимость; для видеоконтейнеров ненадёжна без системного ffmpeg |
|
|
| `--backend` как отдельный флаг | Усложняет CLI; device уже однозначно определяет бэкенд |
|