From 2c1d798eeb1d82cc5b3754afe049e67eb1923433 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Sat, 18 Apr 2026 20:32:48 +0300 Subject: [PATCH] =?UTF-8?q?Revert=20"docs(specs):=20=D0=B4=D0=B8=D0=B7?= =?UTF-8?q?=D0=B0=D0=B9=D0=BD-=D1=81=D0=BF=D0=B5=D0=BA=20Parakeet=20backen?= =?UTF-8?q?d"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit cb6c1d77285bf28e25aa5fb7e2061928a1c73dfa. --- .../2026-04-18-parakeet-backend-design.md | 277 ------------------ 1 file changed, 277 deletions(-) delete mode 100644 docs/superpowers/specs/2026-04-18-parakeet-backend-design.md diff --git a/docs/superpowers/specs/2026-04-18-parakeet-backend-design.md b/docs/superpowers/specs/2026-04-18-parakeet-backend-design.md deleted file mode 100644 index 6d19452..0000000 --- a/docs/superpowers/specs/2026-04-18-parakeet-backend-design.md +++ /dev/null @@ -1,277 +0,0 @@ -# Parakeet backend — design spec - -**Дата:** 2026-04-18 -**Статус:** Готов к планированию реализации -**Ветка реализации:** `feature/parakeet-backend` (в отдельном git worktree) - -## Контекст и гипотеза - -`local-transcriber` — CLI для локальной транскрипции аудио/видео (Whisper). Архитектура pluggable backends (ADR-003): `FasterWhisperBackend` (CTranslate2, CPU/CUDA), `OpenVINOBackend` (Intel CPU/iGPU/NPU). Дефолтный язык — `ru`. Целевая аудитория — ноутбуки с Intel и AMD встройками; NVIDIA — меньшинство. - -**Гипотеза.** NVIDIA Parakeet TDT 0.6B v3 (через ONNX Runtime на CPU) быстрее и/или качественнее Whisper-бэкендов на Intel/AMD встройках. Проверяется прогоном на реальных русских файлах с сравнением wall-clock и качества против OpenVINO-Whisper medium и faster-whisper CPU medium. - -## Выбор технологии (зафиксирован) - -- **Модель:** `nvidia/parakeet-tdt-0.6b-v3` — multilingual, 25 языков включая русский. -- **Runtime:** ONNX Runtime через Python-пакет `onnx-asr` ([`istupakov/onnx-asr`](https://github.com/istupakov/onnx-asr)). Отклонены: NeMo toolkit (~1-2GB зависимостей), OpenVINO Parakeet (AMD iGPU не поддерживается, Intel — экспериментальный порт без сообщества). -- **Execution Provider в MVP:** CPU. DirectML (Windows AMD/Intel iGPU), ORT-OpenVINO EP, `onnxruntime-gpu` (CUDA) — отдельные фазы после валидации гипотезы. -- **VAD:** Silero VAD через `asr.with_vad()` — обязательно (без VAD лимит 20-30 сек на файл; все реальные файлы длиннее). -- **Quantization:** `int8` по умолчанию (~670MB диск, ~2GB RAM при inference), `fp32` — опциональное значение `--compute-type float32`. - -## Архитектура - -### Инвариант - -`TranscribeFileResult`, `TranscribeResult`, `Segment`, `format_transcript()` — **не меняем**. Регрессионный риск для существующих Whisper/OpenVINO-путей минимизирован. - -Parakeet интегрируется через две минимальные точки: -1. `result.language = "multi"` внутри `ParakeetBackend.transcribe` — соответствует поведению Parakeet v3. -2. Атрибут backend-инстанса `effective_model_name` — аналог существующих `actual_compute_type`, `actual_ov_device`. - -CLI после `_transcribe_file` читает `getattr(tfr.backend, "effective_model_name", defaults["model"])` для rich-вывода и `format_transcript(..., model_name=...)`. Rich-строка `Модель: ...` **переносится** после `_transcribe_file` (точка, где уже доступен `tfr.backend`). До транскрипции выводится только `Файл: ...` и сообщения `on_status` от загрузки модели. - -**Шапка транскрипта для Parakeet:** `result.language = "multi"` + `language_mode = "detected"` (CLI форсит `"detected"` при `--device parakeet*`). Формат — `**Язык**: multi (detected)`. - -### Контракт `--language` - -- `result.language = "multi"` всегда. -- CLI при `--device parakeet*` форсит `language_mode = "detected"` (независимо от того, задавал ли пользователь `--language`). -- **Warning только при явном CLI-флаге `--language`.** Если `language` пришёл из `.transcriber.toml` или `HARDCODED_DEFAULTS` (без явного CLI) — warning подавлен. Источник явности: `cli_values["language"] is not None` (не `"language" in config`). -- Текст warning: `Parakeet игнорирует --language; язык определяется автоматически`. - -### Контракт `--compute-type` - -| CLI `--compute-type` | `onnx_asr.load_model(quantization=...)` | Примечание | -|----------------------|------------------------------------------|------------| -| `int8` | `"int8"` | Дефолт | -| `float32` | `None` | Без quantization | -| другое | — | `ValueError("Parakeet поддерживает только int8 или float32")` | - -### Контракт VAD — runtime-зависимость, без prefetch - -`ensure_model_available` отвечает только за Parakeet-модель. `create_model` вызывает `onnx_asr.load_vad(model="silero")`, который при первом запуске скачивает Silero VAD (~15MB). - -Одна точка правды — `onnx_asr.load_vad`. Двойной prefetch (наш `snapshot_download` + внутренний `onnx-asr`) без точного знания repo/layout создавал несостыковку: `ensure_model_available` рапортует «готово», но `create_model` всё равно тянет из сети. - -Документировано в ADR-005 и README: **первый запуск `--device parakeet` требует интернет для Silero VAD; повторные — оффлайн** (проверяется в long-audio gate). - -### Контракт fallback — **НЕ fallback'ить Parakeet на Whisper CPU** - -В `load_model` и `_transcribe_file` при `actual_device.startswith("parakeet")` — пропускаем fallback-ветку, исключение пробрасывается наружу. - -Обоснование: Parakeet — другое семейство моделей с другим качеством и семантикой языка (`multi` vs `ru`). Молчаливая подмена на Whisper medium ломает интерпретацию транскрипта. Пользователь видит понятную ошибку (`Ошибка загрузки Silero VAD — проверьте интернет` / `Parakeet inference crashed — см. --verbose`) и делает осознанный выбор. - -### `detect_device` (utils.py) - -`--device auto` **не переключается на Parakeet.** Порядок остаётся: CUDA → OpenVINO GPU → OpenVINO CPU → CPU. Parakeet — только через явный `--device parakeet` / `--device parakeet-cpu`. - -### `config.py` - -```python -_VALID_DEVICES = { - "auto", "cpu", "cuda", - "openvino", "openvino-gpu", "openvino-cpu", - "parakeet", "parakeet-cpu", -} - -DEVICE_DEFAULTS["parakeet"] = {"model": "parakeet-tdt-0.6b-v3", "compute_type": "int8"} -DEVICE_DEFAULTS["parakeet-cpu"] = {"model": "parakeet-tdt-0.6b-v3", "compute_type": "int8"} -``` - -**Config-conflict на `model`.** Если в глобальном `.transcriber.toml` стоит `model = "medium"` и пользователь запускает `--device parakeet` без `--model`, `apply_device_defaults` возьмёт `"medium"` из конфига → `ParakeetBackend.ensure_model_available` кидает **понятный `ValueError`**: - -> `Parakeet поддерживает только 'parakeet-tdt-0.6b-v3' или алиас 'parakeet'. Получено: 'medium'. Передайте --model parakeet или уберите 'model' из .transcriber.toml.` - -Принципиально **не делаем** скрытого override в `apply_device_defaults` — это ломает инвариант «CLI > config > defaults». Пользователь получает ясную ошибку и делает явный фикс. - -Алиас `"parakeet"` → `"parakeet-tdt-0.6b-v3"` допустим в `ensure_model_available` (UX-удобство). - -### Новый бэкенд — `backends/parakeet.py` - -```python -class ParakeetBackend: - SUPPORTED_MODEL_NAMES = {"parakeet-tdt-0.6b-v3", "parakeet"} - SUPPORTED_COMPUTE_TYPES = {"int8", "float32"} - - def __init__(self, compute_type_explicit: bool = True): - self._compute_type_explicit = compute_type_explicit - self.actual_compute_type: str | None = None - self.effective_model_name: str = "parakeet-tdt-0.6b-v3" - - def ensure_model_available(self, model_name, compute_type, on_status) -> str: - # 1. model_name ∈ SUPPORTED_MODEL_NAMES иначе ValueError (текст выше) - # 2. compute_type ∈ SUPPORTED_COMPUTE_TYPES иначе ValueError - # 3. snapshot_download("nvidia/parakeet-tdt-0.6b-v3") → локальный путь - # 4. Silero VAD здесь НЕ трогаем (runtime-зависимость) - # 5. self.actual_compute_type = compute_type - # 6. Возвращаем путь к каталогу - - def create_model(self, model_path, device, compute_type, cpu_threads=0) -> Any: - # 1. quantization = "int8" if compute_type == "int8" else None - # 2. asr = onnx_asr.load_model("nemo-parakeet-tdt-0.6b-v3", path=model_path, quantization=quantization) - # 3. vad = onnx_asr.load_vad(model="silero") # может качать VAD при первом запуске - # 4. asr_with_vad = asr.with_vad(vad=vad) - # 5. cpu_threads → sess_options через providers_options - # 6. Возвращаем asr_with_vad - - def transcribe(self, model, file_path, language, on_segment, on_status) -> TranscribeResult: - # 1. Если language задан явно (см. «Контракт --language») — warnings.warn - # 2. result = model.recognize(str(file_path)) - # 3. Поддержка iterator И list (детектим на лету) - # 4. Для каждого SegmentResult → Segment(start, end, text); on_segment; on_status c heartbeat - # 5. Возвращаем TranscribeResult(segments, language="multi", language_probability=0.0, duration, device_used="") -``` - -### Регистрация в `backends/__init__.py` - -```python -if device in ("parakeet", "parakeet-cpu"): - try: - from .parakeet import ParakeetBackend - except ImportError: - raise ValueError( - "Parakeet бэкенд недоступен. Установите: pip install onnx-asr[cpu,hub]" - ) from None - return ParakeetBackend(compute_type_explicit=compute_type_explicit) -``` - -### `transcriber.py` — no-fallback для Parakeet - -```python -def _is_parakeet_error(exc: BaseException) -> bool: - if isinstance(exc, RuntimeError): - return True - mod = type(exc).__module__ or "" - return "onnxruntime" in mod or "onnx_asr" in mod - -def _is_backend_error(exc, device): - if device.startswith("parakeet"): - return _is_parakeet_error(exc) - # ... existing branches -``` - -В `load_model` и `_transcribe_file` добавляется ранний выход из fallback-ветки при `actual_device.startswith("parakeet")` — исключение пробрасывается (как будто `strict_device=True`). - -### `cli.py` — точечные изменения - -1. **Rich-строка `Модель: ...` переносится после `_transcribe_file`.** Использует `getattr(tfr.backend, "effective_model_name", defaults["model"])`. -2. **`format_transcript(..., model_name=...)`** получает `getattr(tfr.backend, "effective_model_name", defaults["model"])`. -3. **`language_mode`** при Parakeet: `if actual_device.startswith("parakeet"): language_mode = "detected"`. Иначе — существующая логика. -4. **Warning на явный `--language`** при Parakeet: проверка `cli_values["language"] is not None` (не `"language" in config`). Срабатывает в `_run_single` / `_run_batch` до `_transcribe_file` — пользователь видит сразу. -5. **`_format_device_info`**: `if device_used == "parakeet-cpu": return "Parakeet (CPU via ONNX Runtime)"`. - -Для Whisper-бэкендов код не меняется. - -### Progress UX — heartbeat если блокирующий вызов - -Если `asr_with_vad.recognize()` возвращает iterator — итерируем с вызовами `on_status` per segment. Если блокирующий — обёртка в поток с heartbeat-таймером (паттерн `openvino.py::_generate_with_progress`). Тип возврата проверяется в long-audio gate (см. ниже); при блокирующем вызове heartbeat — **обязателен до merge**. - -## Зависимости - -```toml -# pyproject.toml -dependencies = [ - # ... existing - "onnx-asr[cpu,hub]>=0.7", # минимальная версия уточнить после uv lock -] -``` - -`uv lock` обязателен до любой имплементации — проверить, что `onnxruntime` не конфликтует с тем, что тянут `faster-whisper` / `openvino-genai`. Platform markers не нужны: `onnx-asr` — pure Python; `onnxruntime` CPU-wheels покрывают Linux/macOS/Windows × x86_64/arm64. - -## Тесты - -### `tests/test_parakeet_backend.py` — юнит с mock `onnx_asr` - -1. `test_ensure_model_available_accepts_alias` — `model_name="parakeet"` → скачивает `nvidia/parakeet-tdt-0.6b-v3`. -2. `test_ensure_model_available_rejects_unknown_model` — `model_name="medium"` → `ValueError` с текстом-подсказкой. -3. `test_ensure_model_available_rejects_unknown_compute_type` — `compute_type="float16"` → `ValueError`. -4. `test_create_model_maps_compute_type` — `int8` → `quantization="int8"`; `float32` → `quantization=None`. -5. `test_transcribe_produces_segments` — mock `recognize` возвращает 3 `SegmentResult` → 3 `Segment`. -6. `test_transcribe_warns_on_explicit_cli_language` — явный `--language` → `warnings.warn`. -7. `test_transcribe_does_not_warn_on_config_language` — `language` из конфига → warning НЕ срабатывает. -8. `test_transcribe_calls_on_segment` — callback на каждый сегмент. -9. `test_transcribe_returns_multi_language` — `result.language == "multi"`, `language_probability == 0.0`. -10. `test_backend_sets_effective_model_name` — атрибут проставлен. -11. `test_transcribe_accepts_list_and_iterator_results` — оба типа возврата обрабатываются. - -### `tests/test_transcriber.py` - -12. `test_load_model_parakeet_no_fallback` — при ошибке Parakeet исключение пробрасывается (без fallback на CPU Whisper). - -### `tests/test_cli.py` — regression + новые - -13. `test_cli_whisper_transcript_header_unchanged` — `--device cpu --language ru` → шапка содержит `**Язык**: ru (forced)` (регрессия отсутствует). -14. `test_cli_whisper_auto_language_header_unchanged` — `--device cpu --language auto` → шапка отражает detected-режим. -15. `test_cli_parakeet_transcript_header` — `--device parakeet` → шапка `**Язык**: multi (detected)` и `**Модель**: parakeet-tdt-0.6b-v3`. -16. `test_cli_parakeet_model_row_shows_effective` — config `model="medium"` + `--device parakeet --model parakeet` → rich-вывод и `format_transcript` получают `parakeet-tdt-0.6b-v3`. -17. `test_cli_errors_on_config_model_mismatch_without_override` — config `model="medium"` + `--device parakeet` без `--model` → ясная ошибка (не traceback). -18. `test_cli_parakeet_warns_explicit_language` — config без `language`, явный `--language en --device parakeet` → warning. - -Все юнит-тесты — чистые mocks, без реального скачивания. - -## Long-audio verification gate — exit criteria, blocker для merge - -**Метрики и нормы (единственный источник истины):** - -| Метрика | Норма | -|---------|-------| -| Длина тестового файла | ≥30 минут, русский | -| Peak RSS | ≤4 GB | -| Первый `on_status` update | ≤10 сек от старта `_transcribe_file` | -| Максимальный интервал между `on_status` update'ами | ≤30 сек | -| Повторный прогон с `HF_HUB_OFFLINE=1` | без сетевых запросов | - -**Методика измерения:** -- Peak RSS: `/usr/bin/time -v ...` (поле "Maximum resident set size") или `psutil.Process().memory_info().rss` из внешнего процесса раз в секунду. -- Интервалы `on_status`: специальный тестовый callback в CLI логирует `time.monotonic()` каждого вызова в файл; пост-анализ считает max interval. -- Offline: `HF_HUB_OFFLINE=1` через `env`; либо `strace -e trace=connect`, либо проверка отсутствия задержек на сетевых операциях в начале прогона. - -**Blocker-правила (план заморожен до устранения):** -- Peak RSS > 4 GB → нужен ручной chunking; MVP не готов. -- Первый update > 10 сек ИЛИ любой интервал > 30 сек → нужен heartbeat-thread (паттерн `openvino.py::_generate_with_progress`). -- Offline-прогон делает сетевые обращения → VAD-кэш не работает, разбираться до merge. - -Результаты (числа всех метрик) фиксируются в `docs/gpu.md`. - -## Документация - -1. **ADR-005** (`docs/adr/005-parakeet-backend.md`) — контекст, решение (ONNX Runtime + onnx-asr + Silero VAD), отклонённые альтернативы (NeMo toolkit, OpenVINO Parakeet, DirectML в MVP, VAD prefetch, скрытый override в `apply_device_defaults`, cross-backend fallback на Whisper), последствия (язык игнорируется, no fallback, `effective_model_name` через backend-атрибут, VAD runtime-зависимость с требованием интернета на первый запуск). -2. **README.md** — секция «Parakeet (экспериментально)»: установка, `--device parakeet`, ограничения (int8/fp32, язык auto, первый запуск — интернет для VAD ~15MB, память ~2GB, конфликт с `model` в конфиге). -3. **docs/gpu.md** — секция «Parakeet»: результаты long-audio gate + сравнение с OpenVINO medium / faster-whisper medium CPU на одном и том же RU-файле. - -## Порядок шагов (для writing-plans) - -Вся работа — в git worktree на ветке `feature/parakeet-backend` (через `superpowers:using-git-worktrees`). - -1. **Scaffold + зависимости:** worktree, ветка, `uv add "onnx-asr[cpu,hub]>=0.7"`, `uv lock` → нет конфликтов `onnxruntime` с существующими deps (**блокер**). `uv sync`. Существующие `uv run pytest` зелёные. -2. **config.py:** `_VALID_DEVICES` + `DEVICE_DEFAULTS` для parakeet/parakeet-cpu. Тест на валидацию `device = "parakeet"` в конфиге. -3. **ParakeetBackend (заглушка):** файл `backends/parakeet.py` со stub'ами (`raise NotImplementedError`). Регистрация в `backends/__init__.py`. Импорт работает без ошибок. -4. **`ensure_model_available`:** валидация model_name / compute_type, `snapshot_download`, валидация каталога. Тесты 1-3. -5. **`create_model`:** `onnx_asr.load_model + load_vad + with_vad`, маппинг compute_type, cpu_threads. Тест 4. -6. **`transcribe`:** итерация `SegmentResult`, warning на язык, `language="multi"`, duration. Тесты 5-11. -7. **`transcriber.py` — no-fallback:** `_is_parakeet_error`, ветка в `_is_backend_error`, пропуск fallback при `actual_device.startswith("parakeet")`. Тест 12. -8. **cli.py:** перенос строки `Модель:` после `_transcribe_file`, чтение `effective_model_name` через `getattr`, force `language_mode="detected"` для Parakeet, warning на явный CLI `--language`, `_format_device_info` для `parakeet-cpu`. Regression-тесты 13-14, Parakeet-тесты 15-18. -9. **Long-audio verification gate:** прогон ≥30-мин RU-файла, замеры всех метрик, оффлайн-повтор. Фиксация в `docs/gpu.md`. **Blocker для merge.** -10. **ADR-005 + README** с финальными числами из шага 9. -11. **Финал:** `uv run pytest -v` зелёное, `uv run transcribe sample.mp3 --device parakeet` работает end-to-end, PR. - -## Что НЕ делаем в MVP (отложено) - -- DirectML EP для AMD iGPU / Intel iGPU (Windows). -- ORT-OpenVINO EP для Intel iGPU/NPU. -- Parakeet на CUDA (`onnxruntime-gpu`). -- Ручной chunking длинных файлов (доверяем `with_vad` + gate). -- `--device auto` включает Parakeet. -- Alias `parakeet-tdt-0.6b-v3` для моделей других бэкендов. -- Canary (ещё одна NeMo-модель, поддерживается `onnx-asr`). -- Prefetch Silero VAD в `ensure_model_available`. -- Изменение сигнатур `TranscribeFileResult` / `TranscribeResult` / `format_transcript`. - -## Открытые риски и митигации - -1. **Память на длинных файлах** — gate шага 9; провал → блокер MVP до ручного chunking. -2. **Progress UX (блокирующий `recognize`)** — gate шага 9; провал → heartbeat-thread обязателен. -3. **Качество на русском** — основная гипотеза; результат фиксируется в `docs/gpu.md`; при ухудшении по сравнению с Whisper — бэкенд остаётся с пометкой «EN лучше, чем RU». -4. **Конфликт версий `onnxruntime`** — блокер шага 1; решается через `uv lock` до любой имплементации. -5. **VAD offline** — gate шага 9 (`HF_HUB_OFFLINE=1`); провал → разбираться, при необходимости возвращаться к prefetch с реальной проверкой repo/layout. -6. **Config-conflict на `model`** — ясная ошибка, не скрытый override.