Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
119 lines
7.6 KiB
Markdown
119 lines
7.6 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 сохраняется как вход: `get_backend` выбирает
|
||
по нему adapter, adapter называет свой движок атрибутом `engine`, а
|
||
аппаратную часть (CPU, CUDA, Intel GPU, список providers) трактует сам.
|
||
Новое исполнение — новое значение `device` в `get_backend` и его понимание
|
||
в 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 уже однозначно определяет бэкенд |
|