diff --git a/README.md b/README.md index e555a7f..54ce456 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ transcribe meeting.mp4 ``` - **Полностью локально** — данные не покидают машину -- **Авто-ускорение** — NVIDIA CUDA, Intel GPU (OpenVINO), ONNX (CPU), OpenVINO CPU или CPU fallback +- **Авто-ускорение** — NVIDIA CUDA при наличии GPU, иначе ONNX на CPU - **Батч-режим** — обработка нескольких файлов за один вызов - **Из проводника Windows** — пункт Transcribe в меню «Отправить» ([установка](#контекстное-меню-проводника-windows)) - **Markdown с таймкодами** — удобен для суммаризации ИИ @@ -33,18 +33,20 @@ uv tool install git+https://github.com/dementev-dev/local-transcriber **3. Ускорение (ставится автоматически):** -- **Intel GPU** (Arc, встроенная графика): работает через OpenVINO — ускорение в ~2x vs CPU -- **OpenVINO** (Intel/AMD x86 CPU): ставится автоматически на Linux и Windows — ускорение в 2-4x - **NVIDIA CUDA** (GPU): если есть GPU — транскрипция в 5-10× быстрее - **Windows**: `winget install -e --id Nvidia.CUDA --version 12.9` (от администратора), перезапустить терминал - **Linux / WSL2**: работает из коробки (нужен только драйвер: `nvidia-smi`) +- **Без NVIDIA GPU**: автоматически используется ONNX с GigaAM RNN-T на CPU +- **OpenVINO** для Intel GPU или x86 CPU остаётся доступен через явный + `--device openvino`, `--device openvino-gpu` или `--device openvino-cpu` **4. Готово:** ```bash transcribe meeting.mp4 ``` -Модели скачиваются автоматически при первом запуске (~1.5 GB для medium), нужен доступ в интернет. +Модели скачиваются автоматически при первом запуске; размер зависит от выбранного +профиля, нужен доступ в интернет.
transcribe: command not found @@ -103,7 +105,7 @@ HuggingFace Hub использует симлинки для экономии м ## Использование ```bash -# Простой запуск (medium, русский, автодетект устройства) +# Простой запуск (CUDA medium или ONNX GigaAM RNN-T, язык ru) transcribe meeting.mp4 # Указать язык @@ -180,11 +182,11 @@ transcribe --uninstall-menu | Опция | Сокращение | По умолчанию | Описание | |-------|-----------|-------------|----------| -| `--model` | `-m` | `medium` | Модель Whisper | +| `--model` | `-m` | medium (CUDA) / gigaam-v3-e2e-rnnt (ONNX) | Модель распознавания | | `--language` | `-l` | `ru` | Язык (ru, en, auto и др.) | | `--output` | `-o` | `<файл>-transcript.md` | Путь к выходному файлу | | `--device` | `-d` | `auto` | Устройство (auto, cpu, cuda, openvino, openvino-gpu, openvino-cpu, onnx) | -| `--compute-type` | — | float16 (CUDA) / int8 (OpenVINO/ONNX) / float32 (CPU) | Тип вычислений | +| `--compute-type` | — | float16 (CUDA) / int8 (ONNX/OpenVINO) / float32 (CPU) | Тип вычислений | | `--threads` | `-t` | 0 (авто) | Потоки CPU (рекомендуется = число физ. ядер) | | `--force` | `-f` | — | Перезаписать существующие транскрипты | | `--verbose` | `-v` | — | Подробный вывод | @@ -193,10 +195,9 @@ transcribe --uninstall-menu | | Linux / WSL2 | macOS | Windows | |---|---|---|---| -| CPU | ✅ | ✅ | ✅ | -| OpenVINO (x86 CPU) | ✅ авто | — | ✅ авто | -| OpenVINO (Intel GPU) | ✅ авто | — | ✅ авто | -| ONNX (CPU) | ✅ явно | ✅ явно | ✅ явно | +| CPU через ONNX | ✅ авто | ✅ авто | ✅ авто | +| OpenVINO (x86 CPU) | ✅ явно | — | ✅ явно | +| OpenVINO (Intel GPU) | ✅ явно | — | ✅ явно | | GPU (NVIDIA) | ✅ авто | — | ✅ (нужен CUDA 12) |
@@ -237,8 +238,10 @@ transcribe --uninstall-menu Дефолтные параметры можно задать в `.transcriber.toml`: ```toml -model = "large-v3" -language = "en" +device = "openvino-cpu" +model = "large-v3-turbo" +compute_type = "int8" +language = "ru" ``` Порядок поиска: @@ -247,6 +250,11 @@ language = "en" Приоритет: **CLI-аргумент > конфиг > device-aware дефолт > встроенный дефолт**. +При `device = "auto"` выбирается CUDA, если доступен `nvidia-smi`, иначе ONNX. +Явный `device` из CLI или конфига отключает этот автоматический выбор. +Каталоги моделей различаются между бэкендами, поэтому при закреплении `model` +в конфиге рекомендуется явно закрепить и совместимый `device`. + Дефолты зависят от устройства: | Параметр | CUDA | OpenVINO (GPU) | OpenVINO (CPU) | ONNX | CPU | @@ -258,7 +266,7 @@ language = "en" ## Модели и GPU Рекомендации: -- **По умолчанию для ONNX:** `gigaam-v3-e2e-rnnt` — читаемый русский текст с +- **По умолчанию без CUDA:** ONNX `gigaam-v3-e2e-rnnt` — читаемый русский текст с пунктуацией почти без потери скорости относительно сырого `gigaam-v3` - **Макс. качество (NVIDIA):** `large-v3` + `--compute-type float16` - **Макс. качество (Intel GPU):** `large-v3` + `--device openvino-gpu` diff --git a/docs/PRD.md b/docs/PRD.md index f83bbc0..30e137a 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -24,8 +24,9 @@ transcribe meeting-2026-03-17.mp4 ### 3.1. Основной flow 1. Пользователь вызывает CLI, передаёт путь к файлу (или glob-маску, post-MVP) -2. Проверка: ffmpeg доступен в PATH -3. Файл передаётся в faster-whisper (он сам обрабатывает и аудио, и видео через libav/ffmpeg — отдельное извлечение аудиодорожки не нужно) +2. Файл валидируется по пути, размеру и расширению +3. По `device` выбирается backend; аудио декодируется через PyAV без отдельного + извлечения дорожки 4. Результат форматируется в markdown с таймкодами 5. Файл `<имя>-transcript.md` сохраняется рядом с исходным (кодировка: UTF-8) @@ -41,16 +42,16 @@ transcribe meeting-2026-03-17.mp4 transcribe <путь_к_файлу> [опции] Опции: - --model, -m Модель Whisper (tiny|base|small|medium|large-v3) - По умолчанию: large-v3 + --model, -m Модель распознавания + По умолчанию: medium (CUDA) / gigaam-v3-e2e-rnnt (ONNX) --language, -l Язык (ru|en|auto) - По умолчанию: auto (автодетект) + По умолчанию: ru --output, -o Путь к выходному файлу По умолчанию: -transcript.md - --device, -d Устройство (auto|cpu|cuda|openvino|openvino-gpu|openvino-cpu) - По умолчанию: auto (CUDA → OpenVINO GPU → OpenVINO CPU → CPU) - --compute-type Тип вычислений (float16|int8|int8_float16|float32) - По умолчанию: int8 (универсален для GPU 4-8 GB и CPU) + --device, -d Устройство (auto|cpu|cuda|onnx|openvino|openvino-gpu|openvino-cpu) + По умолчанию: auto (CUDA при наличии, иначе ONNX CPU) + --compute-type Тип вычислений + По умолчанию: float16 (CUDA) / int8 (ONNX) --verbose, -v Подробный вывод (прогресс сегментов) ``` @@ -82,16 +83,17 @@ transcribe <путь_к_файлу> [опции] **Правила форматирования**: - Таймкоды в формате `[MM:SS.ss - MM:SS.ss]` (минуты:секунды.сотые) - Для записей длиннее 1 часа — `[HH:MM:SS.ss - HH:MM:SS.ss]` -- Каждый сегмент — отдельный абзац +- Соседние сегменты объединяются в абзац до паузы 2 секунды или длительности 60 секунд - Метаданные в шапке файла -- Пустая строка между сегментами для читаемости +- Пустая строка между абзацами для читаемости ### 3.4. Поддерживаемые форматы **Аудио**: mp3, wav, flac, ogg, m4a, wma, aac **Видео**: mp4, mkv, avi, mov, webm, ts -Определение типа — по расширению. Фактическое декодирование выполняет ffmpeg внутри faster-whisper; если формат не поддерживается, ошибка будет от ffmpeg. +Определение типа — по расширению. Фактическое декодирование выполняет PyAV с +встроенными библиотеками FFmpeg; системная установка `ffmpeg` не требуется. ## 4. Нефункциональные требования @@ -102,6 +104,8 @@ transcribe <путь_к_файлу> [опции] | RTX 3060 + large-v3 | ~10-15x (1 час аудио ≈ 4-6 мин) | | RTX 4050 + large-v3 | ~12-18x (1 час аудио ≈ 3-5 мин) | | Quadro M3000M + large-v3 | ~3-5x (1 час аудио ≈ 12-20 мин) | +| CPU + ONNX GigaAM RNN-T | ~10-14x (1 час аудио ≈ 4-6 мин) | +| CPU + OpenVINO Turbo INT8 | ~7-10x (1 час аудио ≈ 6-9 мин) | | CPU (modern) + large-v3 | ~0.5-1x (1 час аудио ≈ 60-120 мин) | | CPU + small | ~3-5x (1 час аудио ≈ 12-20 мин) | @@ -112,23 +116,22 @@ transcribe <путь_к_файлу> [опции] | RTX 3060 | 6 GB | ✅ | ✅ (впритык) | int8 — безопасный выбор | | RTX 4050 | 6 GB | ✅ | ✅ (впритык) | int8 — безопасный выбор | | Quadro M3000M | 4 GB | ✅ | ⚠️ может OOM | int8 обязательно | -| Без GPU | — | CPU int8 | — | int8 на CPU | +| Без GPU | — | ONNX GigaAM INT8 | — | auto выбирает ONNX | -Дефолт `int8` выбран как универсальный: работает на всех GPU от 4 GB и на CPU, при минимальной потере качества относительно float16. +Device-aware дефолты выбирают `float16` для CUDA и `int8` для ONNX/OpenVINO. ### 4.2. Требования к окружению - Python ≥ 3.13 -- ffmpeg в PATH (используется faster-whisper внутри для декодирования любых медиаформатов) - Для GPU: Linux/WSL2 — cuBLAS из nvidia-cublas-cu12 (ставится автоматически через `uv sync`); Windows — системный CUDA toolkit (см. ADR-001) -- Дисковое пространство для моделей: ~3 GB (large-v3) +- Дисковое пространство для моделей: зависит от выбранного backend и модели - Выходные файлы: UTF-8 (явная кодировка при записи) ### 4.3. Кроссплатформенность - Linux: нативный запуск - Windows: нативный Python или WSL2 -- macOS: не приоритет, но faster-whisper поддерживает CPU-режим +- macOS: ONNX CPU в auto-режиме; FasterWhisper CPU доступен явно ## 5. Технический стек @@ -136,19 +139,18 @@ transcribe <путь_к_файлу> [опции] |---------------------|-------------------------------------------------| | Язык | Python 3.13+ | | Управление проектом | uv (pyproject.toml) | -| Распознавание речи | faster-whisper (CTranslate2 backend) | -| Медиа-декодирование | ffmpeg (системная зависимость, используется faster-whisper внутри) | +| Распознавание речи | faster-whisper, ONNX Runtime, OpenVINO GenAI | +| Медиа-декодирование | PyAV со встроенными библиотеками FFmpeg | | CLI-фреймворк | typer | | Прогресс | rich (progress bar + статус) | -### 5.1. Почему faster-whisper +### 5.1. Почему несколько backend -- В 4× быстрее оригинального OpenAI Whisper при том же качестве -- Меньше потребление VRAM (large-v3 влезает в 6 GB с float16/int8) -- Нативный Python API, без Docker -- Поддержка CPU fallback из коробки -- Активное сообщество, регулярные обновления -- Автоматическая загрузка моделей из Hugging Face Hub +- faster-whisper оптимизирован для NVIDIA CUDA и поддерживает много языков +- ONNX GigaAM RNN-T даёт быстрый читаемый результат на CPU +- OpenVINO предоставляет явные профили для Intel GPU и x86 CPU +- Все backend работают локально через Python API, без Docker и облачных ключей +- Модели загружаются автоматически и кешируются локально ### 5.2. Структура проекта @@ -160,9 +162,10 @@ local-transcriber/ │ └── local_transcriber/ │ ├── __init__.py │ ├── cli.py # CLI entry point (typer) -│ ├── transcriber.py # Обёртка над faster-whisper +│ ├── transcriber.py # Оркестрация backend и fallback +│ ├── backends/ # Адаптеры FasterWhisper, ONNX и OpenVINO │ ├── formatter.py # Форматирование в markdown -│ └── utils.py # Проверки (ffmpeg), определение device и т.д. +│ └── utils.py # Проверки файлов и определение device └── tests/ └── ... ``` @@ -173,11 +176,10 @@ local-transcriber/ |------|---------|-----------| | Качество распознавания русского текста | Среднее | large-v3 хорошо справляется с ru; при проблемах — попробовать `--language ru` вместо auto | | Нет разделения по спикерам | Низкое | Осознанно выведено за скоуп MVP; добавление diarization (pyannote.audio) — возможное расширение | -| ffmpeg отсутствует в системе | Высокое | Проверка при старте + понятное сообщение об ошибке с инструкцией по установке | | Первый запуск: долгая загрузка модели | Низкое | Прогресс-бар при скачивании; модели кешируются в `~/.cache/huggingface/` | | CUDA несовместимость на Windows | Среднее | Автоматический fallback на CPU + предупреждение; в README — инструкция по CUDA | -| Большие файлы (>2 часов) | Низкое | faster-whisper работает потоково, не грузит всё в память | -| OOM на GPU с 4 GB VRAM | Среднее | Дефолт int8 (~2.5 GB); при OOM — fallback на CPU с предупреждением | +| Большие файлы (>2 часов) | Среднее | Учитывать память выбранного backend; чанкование рассматривается отдельно | +| OOM на GPU с 4 GB VRAM | Среднее | CUDA использует float16; при OOM — fallback на CPU с предупреждением или явный более лёгкий профиль | | Файл без речи (тишина, музыка, шум) | Низкое | Создаётся транскрипт с шапкой метаданных и `*Речь не обнаружена.*` в теле + предупреждение в stderr | ## 7. Вне скоупа MVP diff --git a/docs/adr/003-pluggable-backends.md b/docs/adr/003-pluggable-backends.md index ba812d4..7c99330 100644 --- a/docs/adr/003-pluggable-backends.md +++ b/docs/adr/003-pluggable-backends.md @@ -2,6 +2,7 @@ **Статус**: Принято **Дата**: 2026-03-21 +**Обновлено**: 2026-08-12 ## Контекст @@ -39,7 +40,8 @@ shared libraries. Ни то, ни другое не должно происхо Вместо отдельного `--backend` флага устройство само определяет бэкенд: - `cuda`, `cpu` → FasterWhisperBackend - `openvino` → OpenVINOBackend -- `auto` → CUDA (nvidia-smi) → OpenVINO (import check + x86) → CPU +- `onnx` → OnnxAsrBackend +- `auto` → CUDA при наличии `nvidia-smi`, иначе ONNX на CPU ### load_model() — единственный владелец pipeline @@ -71,18 +73,19 @@ OpenVINO модели предквантизированы (int8/fp16), compute_ - Из дефолтов: для large-v3 автоматически выбирается fp16 (стабильнее по качеству) - Несуществующая пара (model + compute_type) при явном выборе → ошибка -### Обе зависимости по умолчанию +### Зависимости бэкендов по умолчанию -faster-whisper (~37MB) и openvino-genai (~69MB) ставятся вместе — суммарно ~106MB, -приемлемо. Модели скачиваются только для активного бэкенда. CUDA (nvidia-cublas-cu12, -~554MB) остаётся conditional (Linux x86_64). OpenVINO — conditional (x86_64/AMD64, не macOS). +faster-whisper, onnx-asr/onnxruntime и openvino-genai ставятся вместе. Модели +скачиваются только для активного бэкенда. CUDA (`nvidia-cublas-cu12`) остаётся +conditional для Linux x86_64. OpenVINO — conditional для x86_64/AMD64, кроме +macOS; ONNX обеспечивает автоматический CPU-путь на остальных платформах. ## Последствия - Обратная совместимость: `transcribe()` сохранён; `load_model()` изменил сигнатуру (возвращает 4-tuple вместо 2-tuple, добавлен `compute_type_explicit`) - Новый бэкенд добавляется одним файлом в `backends/` + регистрацией в `__init__.py` - Модели скачиваются по запросу — CUDA пользователь не качает OpenVINO модели, и наоборот -- ARM и macOS: OpenVINO не ставится (platform markers), работает CPU через faster-whisper +- ARM и macOS: OpenVINO не ставится (platform markers), auto использует ONNX ## Отклонённые альтернативы diff --git a/docs/adr/006-onnx-asr-backend.md b/docs/adr/006-onnx-asr-backend.md index bec38b3..c52d1cd 100644 --- a/docs/adr/006-onnx-asr-backend.md +++ b/docs/adr/006-onnx-asr-backend.md @@ -2,7 +2,7 @@ **Статус**: Принято **Дата**: 2026-04-25 -**Обновлено**: 2026-08-11 +**Обновлено**: 2026-08-12 ## Контекст @@ -101,10 +101,11 @@ Mm-hmm-редукция и иностранные вставки **не обна ## Решение -**Принять onnx-asr как экспериментальный бэкенд с явным `--device onnx`. -GigaAM v3 E2E RNN-T — модель по умолчанию для русских встреч на CPU.** +**Принять onnx-asr как CPU-бэкенд автоматического профиля. +GigaAM v3 E2E RNN-T — модель по умолчанию на машинах без CUDA.** -Бэкенд **не в auto-detect** — только при явном указании пользователем (политика experimental backend, как для openvino). +Порядок `--device auto`: CUDA при наличии `nvidia-smi`, иначе ONNX. OpenVINO и +FasterWhisper CPU остаются доступными через явный CLI-аргумент или конфиг. Модели: - **`gigaam-v3-e2e-rnnt`** — модель по умолчанию: практически равна обычному @@ -122,7 +123,9 @@ GigaAM v3 E2E RNN-T — модель по умолчанию для русски - Пользователи CPU-only с русскоязычным контентом получают 3-5× ускорение по сравнению с OpenVINO medium **при превосходящем качестве** (4/5 vs 1-2/5 summary utility на длинных файлах). - Пользователи ONNX без явного `--model` получают пунктуацию и нормализацию RNN-T; более точный сырой CTC остаётся доступен как `--model gigaam-v3`. -- Whisper medium (`--device openvino-cpu`) **остаётся допустимым** для коротких (≤30 мин) встреч с равномерной громкостью; на длинных файлах с тихими участками он галлюцинирует целыми блоками — этот риск зафиксирован, но решение не выводит OpenVINO из списка дефолтов (часть пользователей всё ещё нуждается в пунктуации, и для коротких файлов галлюцинации не воспроизводятся). +- Whisper medium (`--device openvino-cpu`) **остаётся допустимым явным профилем** + для коротких (≤30 мин) встреч с равномерной громкостью; на длинных файлах с + тихими участками он может галлюцинировать целыми блоками. - Parakeet-v3 формально доступен, но в README рекомендуется только для англоязычного контента — для русского явно не годится. - GPU faster-whisper large-v3 остаётся эталоном по качеству (для пользователей с NVIDIA GPU). - Пост-процессинг GigaAM-транскрипта LLM-этапом нормализации (восстановление латинских терминов и имён компаний) — рекомендуемая практика для финального конспекта. @@ -131,7 +134,8 @@ GigaAM v3 E2E RNN-T — модель по умолчанию для русски - **Galлюцинации Whisper medium на длинных файлах** — отдельный продуктовый риск, требующий собственного исследования. Возможно, имеет смысл ограничить максимальную длину чанка для openvino-medium, или дать предупреждение пользователю. - **Canary** (`nemo-canary-1b-v2`) — тяжелее, но multilingual + пунктуация. Кандидат на «лучшее качество за разумную скорость» для тех, кому важна пунктуация. -- **Auto-detect onnx**: после стабилизации в production-использовании (несколько недель) — рассмотреть включение в auto-detect как первый CPU-бэкенд (ниже CUDA, выше OpenVINO). +- Проверить профиль на других языках и при необходимости добавить явные + многоязычные рекомендации; автоматическая политика намеренно не зависит от языка. ## Отклонённые альтернативы @@ -139,5 +143,4 @@ GigaAM v3 E2E RNN-T — модель по умолчанию для русски |---|---| | NeMo Parakeet напрямую (без onnx-asr) | Требует PyTorch + CUDA, Python ≥ 3.12, ~2 GB зависимостей — слишком тяжело для CLI | | Замена faster-whisper на onnx-asr | faster-whisper поддерживает 99+ языков и пунктуацию, остаётся лучшим GPU-бэкендом | -| GigaAM как auto-detect default | Экспериментальный бэкенд, политика — не сюрпризить существующих пользователей; включение в auto-detect — после периода стабилизации | | Parakeet-v3 как multilingual default | Воспроизведённые проблемы из ADR-005 (Mm-hmm-редукция, иноязычные вставки) делают его непригодным для русского; для других языков не валидировано в этом эксперименте | diff --git a/docs/backlog.md b/docs/backlog.md index 35b9146..11090e9 100644 --- a/docs/backlog.md +++ b/docs/backlog.md @@ -252,16 +252,6 @@ SQL`), словарь пользовательский в `.transcriber.toml`. --- -### Включение `onnx` в `--device auto` - -**Что:** После периода стабилизации `--device onnx` (несколько недель production-использования без жалоб) — рассмотреть включение в auto-detect chain. - -**Порядок в chain (предложение):** CUDA → onnx (если CPU x86_64) → OpenVINO → CPU. - -**Почему откладывается:** политика experimental backend — не сюрпризить существующих пользователей до накопления опыта. Источник: [ADR-006](adr/006-onnx-asr-backend.md#решение). - ---- - ## Отклонённые направления *(пока пусто — добавлять сюда то, что попробовали и решили не делать, с причиной)* diff --git a/docs/benchmarks/2026-08-12-openvino-large-v3-turbo-comparison.md b/docs/benchmarks/2026-08-12-openvino-large-v3-turbo-comparison.md index 6ce8b86..b25cf5b 100644 --- a/docs/benchmarks/2026-08-12-openvino-large-v3-turbo-comparison.md +++ b/docs/benchmarks/2026-08-12-openvino-large-v3-turbo-comparison.md @@ -304,23 +304,21 @@ INT8 почти равен ему. Они лучше удерживают `FineB ### Что это означает для профиля по умолчанию -Результаты поддерживают ONNX GigaAM RNN-T как основной кандидат для русской -речи на CPU: он быстрее всех на обеих машинах, дважды занял первое место по -читаемости и пригодности для конспекта и использует VAD. Но делать его -безусловным глобальным `--device auto` преждевременно: +Результаты поддерживают ONNX GigaAM RNN-T как основной CPU-профиль: он быстрее +всех на обеих машинах, дважды занял первое место по читаемости и пригодности +для конспекта и использует VAD. После обсуждения принята простая политика: +`--device auto` выбирает CUDA при наличии `nvidia-smi`, иначе ONNX. + +Ограничения выбора сохраняются: - GigaAM ориентирован на русский язык и хуже сохраняет латиницу, аббревиатуры, названия систем и компаний; - OpenVINO Whisper остаётся многоязычным путём и умеет использовать Intel GPU; - Turbo лучше GigaAM по WER и техническим терминам, хотя хуже подготовлен для непосредственного чтения; -- изменение существующего auto-пути будет несовместимым изменением поведения и - требует отдельной продуктовой задачи с language-aware выбором. - -Практичный кандидат на будущую политику: `CUDA` для NVIDIA, OpenVINO для Intel -GPU, ONNX GigaAM RNN-T для явно русской речи на CPU, OpenVINO/Whisper для других -языков и явный выбор Turbo для технически насыщенных встреч. В рамках текущей -задачи эта политика не реализуется. +- изменение существующего auto-пути является несовместимым изменением поведения; + явные значения из CLI и конфигурации остаются способом сохранить прежний + OpenVINO/Whisper-профиль или выбрать Turbo для технически насыщенных встреч. ## Вывод @@ -338,8 +336,9 @@ GPU, ONNX GigaAM RNN-T для явно русской речи на CPU, OpenVIN протокола может потребоваться последующая расстановка пунктуации. - Автоматических предупреждений о повторах или потере хвоста после обновления не было; ручная проверка нашла у medium одиночный ложный сегмент в конце RSQM. -- Модель по умолчанию и порядок `--device auto` оставлены без изменений. Решение - об их изменении требует отдельной продуктовой задачи. +- В самом benchmark-коммите модель по умолчанию и порядок `--device auto` не + менялись; по итогам анализа отдельно принято решение использовать ONNX на + машинах без CUDA. - Повторный Ryzen-прогон подтвердил, что прежний блок из 11 повторов `medium` не воспроизводится: четыре последовательных прохода дали одинаковый текст без повторов и ложного хвоста. Это подтверждение для контрольного файла, а не diff --git a/docs/gpu.md b/docs/gpu.md index 05810a7..1fb94c0 100644 --- a/docs/gpu.md +++ b/docs/gpu.md @@ -2,7 +2,7 @@ ## Режимы `--device` -- `auto` (по умолчанию) — CUDA → OpenVINO GPU → OpenVINO CPU → CPU (первый доступный) +- `auto` (по умолчанию) — CUDA при наличии `nvidia-smi`, иначе ONNX на CPU - `cuda` — строго NVIDIA GPU, ошибка если недоступен - `openvino` — авто-выбор OpenVINO GPU или CPU - `openvino-gpu` — строго Intel GPU через OpenVINO @@ -14,12 +14,13 @@ | Оборудование | Рекомендуемый `--device` | Бэкенд | Ожидаемая скорость | |---|---|---|---| | NVIDIA GPU (6+ GB VRAM) | `auto` / `cuda` | faster-whisper (CTranslate2) | 7-19x реалтайм | -| Intel Arc iGPU / dGPU | `auto` / `openvino-gpu` | OpenVINO GenAI (GPU) | TBD | -| Intel/AMD x86 CPU | `auto` / `openvino-cpu` | OpenVINO GenAI (CPU) | 3-6x реалтайм* | -| Любой CPU (fallback) | `cpu` | faster-whisper (CTranslate2) | ~1.5x реалтайм | -| Apple Silicon (macOS) | `cpu` | faster-whisper (CTranslate2) | ~2x реалтайм | +| Любой CPU без NVIDIA | `auto` / `onnx` | ONNX GigaAM RNN-T | 10-14x реалтайм* | +| Intel Arc iGPU / dGPU | `openvino-gpu` | OpenVINO GenAI (GPU) | TBD | +| Intel/AMD x86 CPU | `openvino-cpu` | OpenVINO GenAI (CPU) | 3-10x реалтайм* | +| Любой CPU, FasterWhisper | `cpu` | faster-whisper (CTranslate2) | ~1.5x реалтайм | -\* По результатам тестирования на Intel и AMD CPU. Реальная скорость зависит от CPU и модели. +\* По результатам контрольных прогонов на Intel и AMD CPU. Реальная скорость +зависит от CPU, модели и записи. ## OpenVINO diff --git a/src/local_transcriber/cli.py b/src/local_transcriber/cli.py index 62bc80e..65c041a 100644 --- a/src/local_transcriber/cli.py +++ b/src/local_transcriber/cli.py @@ -114,7 +114,11 @@ def _print_quality_warnings(result: TranscribeResult, file_name: str | None = No def main( files: list[Path] | None = typer.Argument(None, help="Пути к аудио/видеофайлам"), model: str | None = typer.Option( - None, "--model", "-m", show_default=False, help="Модель Whisper [по умолч.: medium]" + None, + "--model", + "-m", + show_default=False, + help="Модель [по умолч.: medium (CUDA) / gigaam-v3-e2e-rnnt (ONNX)]", ), language: str | None = typer.Option( None, "--language", "-l", show_default=False, help="Язык [по умолч.: ru]" @@ -126,7 +130,10 @@ def main( ), compute_type: str | None = typer.Option( None, "--compute-type", show_default=False, - help="Тип вычислений [по умолч.: float16 (CUDA) / int8 (OpenVINO GPU/CPU) / float32 (CPU)]" + help=( + "Тип вычислений [по умолч.: float16 (CUDA) / " + "int8 (ONNX/OpenVINO) / float32 (CPU)]" + ), ), threads: int = typer.Option( 0, "--threads", "-t", show_default=False, min=0, diff --git a/src/local_transcriber/types.py b/src/local_transcriber/types.py index 295f7ba..f625dfb 100644 --- a/src/local_transcriber/types.py +++ b/src/local_transcriber/types.py @@ -18,7 +18,7 @@ class TranscribeResult: language: str language_probability: float duration: float # seconds - device_used: str # "cpu" / "cuda" / "openvino-gpu" / "openvino-cpu" + device_used: str # "cpu" / "cuda" / "onnx" / "openvino-gpu" / "openvino-cpu" @dataclass diff --git a/src/local_transcriber/utils.py b/src/local_transcriber/utils.py index d8d3360..8cfafe0 100644 --- a/src/local_transcriber/utils.py +++ b/src/local_transcriber/utils.py @@ -16,18 +16,16 @@ SUPPORTED_EXTENSIONS = { def detect_device(requested: str = "auto") -> str: """Определяет устройство для вычислений. - При ``requested="auto"`` проверяет: CUDA → OpenVINO GPU → OpenVINO CPU → CPU. - ``"openvino"`` — алиас для авто-детекта внутри OpenVINO (GPU если доступен, иначе CPU). + При ``requested="auto"`` выбирает CUDA при наличии ``nvidia-smi``, + иначе ONNX на CPU. + ``"openvino"`` — алиас для авто-детекта внутри OpenVINO + (GPU если доступен, иначе CPU). Возвращает всегда конкретное значение (не абстрактный ``"openvino"``). """ if requested == "auto": if shutil.which("nvidia-smi") is not None: return "cuda" - if _is_openvino_gpu_available(): - return "openvino-gpu" - if _is_openvino_available(): - return "openvino-cpu" - return "cpu" + return "onnx" if requested == "openvino": if _is_openvino_gpu_available(): return "openvino-gpu" diff --git a/tests/test_cli.py b/tests/test_cli.py index 4fae93a..040c06c 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -73,27 +73,32 @@ def test_cli_happy_path_exit_code_zero(tmp_path): def test_cli_default_options_passed_to_transcribe(tmp_path): audio = tmp_path / "test.mp3" audio.write_bytes(b"fake") - result = _make_result() + result = _make_result(device_used="onnx") model = _make_model() backend = _make_backend() - tfr = _make_tfr(result=result, model=model, backend=backend) + tfr = _make_tfr(result=result, model=model, actual_device="onnx", backend=backend) mock_transcribe_file = MagicMock(return_value=tfr) with ( patch("local_transcriber.cli.load_config", return_value={}), patch("local_transcriber.cli.validate_input_file", return_value=audio), - patch("local_transcriber.cli.detect_device", return_value="cpu"), - patch("local_transcriber.cli.load_model", return_value=(model, "cpu", backend, "/models/medium")), + patch("local_transcriber.cli.detect_device", return_value="onnx"), + patch( + "local_transcriber.cli.load_model", + return_value=(model, "onnx", backend, "/models/gigaam-v3-e2e-rnnt"), + ), patch("local_transcriber.cli._transcribe_file", mock_transcribe_file), patch("local_transcriber.cli.write_transcript"), ): - runner.invoke(app, [str(audio)]) + out = runner.invoke(app, [str(audio)]) call_kwargs = mock_transcribe_file.call_args[1] - assert call_kwargs["model_name"] == "medium" - assert call_kwargs["compute_type"] == "float32" + assert call_kwargs["model_name"] == "gigaam-v3-e2e-rnnt" + assert call_kwargs["compute_type"] == "int8" assert call_kwargs["language"] == "ru" assert call_kwargs["on_segment"] is None # verbose=False + assert "Модель: gigaam-v3-e2e-rnnt" in out.output + assert "Устройство: onnx" in out.output def test_cli_custom_options(tmp_path): @@ -660,6 +665,45 @@ def test_cli_config_applied(tmp_path): assert mock_load_model.call_args[0][0] == "tiny" +def test_cli_config_overrides_auto_device(tmp_path): + audio = tmp_path / "test.mp3" + audio.write_bytes(b"fake") + model = _make_model() + backend = _make_backend() + result = _make_result(device_used="openvino-cpu") + tfr = _make_tfr( + result=result, + model=model, + actual_device="openvino-cpu", + backend=backend, + ) + mock_detect_device = MagicMock(return_value="openvino-cpu") + mock_load_model = MagicMock( + return_value=(model, "openvino-cpu", backend, "/models/medium") + ) + + with ( + patch( + "local_transcriber.cli.load_config", + return_value={ + "device": "openvino-cpu", + "model": "medium", + "compute_type": "int8", + }, + ), + patch("local_transcriber.cli.validate_input_file", return_value=audio), + patch("local_transcriber.cli.detect_device", mock_detect_device), + patch("local_transcriber.cli.load_model", mock_load_model), + patch("local_transcriber.cli._transcribe_file", return_value=tfr), + patch("local_transcriber.cli.write_transcript"), + ): + out = runner.invoke(app, [str(audio)]) + + assert out.exit_code == 0 + assert mock_detect_device.call_args_list[0].args == ("openvino-cpu",) + assert mock_load_model.call_args.args[:3] == ("medium", "openvino-cpu", "int8") + + def test_cli_cli_overrides_config(tmp_path): audio = tmp_path / "test.mp3" audio.write_bytes(b"fake") diff --git a/tests/test_config.py b/tests/test_config.py index 00e834b..6e2162b 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -71,11 +71,17 @@ def test_load_config_invalid_device(tmp_path): def test_resolve_defaults_cli_wins(): - config = {"model": "tiny", "language": "en"} - cli = {"model": "small", "language": None, "device": None, "compute_type": None} + config = {"model": "tiny", "language": "en", "device": "openvino-cpu"} + cli = { + "model": "small", + "language": None, + "device": "onnx", + "compute_type": None, + } result = resolve_defaults(cli, config) assert result["model"] == "small" assert result["language"] == "en" + assert result["device"] == "onnx" def test_resolve_defaults_config_wins(): diff --git a/tests/test_utils.py b/tests/test_utils.py index 8e2cb94..f95f089 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -61,27 +61,28 @@ def test_detect_device_explicit_passthrough(): """Явные device strings проходят без изменений.""" assert detect_device("cpu") == "cpu" assert detect_device("cuda") == "cuda" + assert detect_device("onnx") == "onnx" assert detect_device("openvino-gpu") == "openvino-gpu" assert detect_device("openvino-cpu") == "openvino-cpu" -def test_detect_device_auto_openvino_gpu(): - """auto + нет nvidia-smi + есть OpenVINO GPU → openvino-gpu.""" +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") == "openvino-gpu" + assert detect_device("auto") == "onnx" -def test_detect_device_auto_openvino_cpu(): - """auto + нет nvidia-smi + есть OpenVINO, нет GPU → openvino-cpu.""" +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") == "openvino-cpu" + assert detect_device("auto") == "onnx" def test_detect_device_cuda_over_openvino(): @@ -93,14 +94,14 @@ def test_detect_device_cuda_over_openvino(): assert detect_device("auto") == "cuda" -def test_detect_device_auto_cpu_fallback(): - """Ни nvidia-smi, ни openvino → cpu.""" +def test_detect_device_auto_onnx_without_accelerators(): + """Без 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), ): - assert detect_device("auto") == "cpu" + assert detect_device("auto") == "onnx" def test_detect_device_openvino_resolves_to_gpu():