Обновление OpenVINO до 2026.3, large-v3-turbo и ONNX по умолчанию #6

Merged
ddmitry merged 9 commits from feature/4-openvino-2026-3 into master 2026-08-12 12:43:46 +03:00
21 changed files with 985 additions and 180 deletions
+63 -19
View File
@@ -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,24 @@ 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.
Эта модель понимает только русскую речь
- **Другие языки без NVIDIA**: выбирайте Whisper явно —
`--device openvino-cpu --model medium` на x86 или `--device cpu --model medium`
на любой платформе
- **OpenVINO** для Intel GPU или x86 CPU остаётся доступен через явный
`--device openvino`, `--device openvino-gpu` или `--device openvino-cpu`
**4. Готово:**
```bash
transcribe meeting.mp4
```
Модели скачиваются автоматически при первом запуске (~1.5 GB для medium), нужен доступ в интернет.
Модели скачиваются автоматически при первом запуске; размер зависит от выбранного
профиля, нужен доступ в интернет.
<details>
<summary><code>transcribe: command not found</code></summary>
@@ -103,7 +109,7 @@ HuggingFace Hub использует симлинки для экономии м
## Использование
```bash
# Простой запуск (medium, русский, автодетект устройства)
# Простой запуск (CUDA medium или ONNX GigaAM RNN-T, язык ru)
transcribe meeting.mp4
# Указать язык
@@ -180,11 +186,11 @@ transcribe --uninstall-menu
| Опция | Сокращение | По умолчанию | Описание |
|-------|-----------|-------------|----------|
| `--model` | `-m` | `medium` | Модель Whisper |
| `--language` | `-l` | `ru` | Язык (ru, en, auto и др.) |
| `--model` | `-m` | medium (CUDA) / gigaam-v3-e2e-rnnt (ONNX) | Модель распознавания |
| `--language` | `-l` | `ru` | Язык (ru, en, auto и др.); автоматический профиль без NVIDIA понимает только русский |
| `--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,12 +199,14 @@ 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) |
Данные по macOS основаны на доступности пакетов onnxruntime: прогонов на этой
платформе не было.
<details>
<summary>Linux / WSL2</summary>
@@ -237,8 +245,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 +257,13 @@ language = "en"
Приоритет: **CLI-аргумент > конфиг > device-aware дефолт > встроенный дефолт**.
При `device = "auto"` выбирается CUDA, если доступен `nvidia-smi`, иначе ONNX.
Явный `device` из CLI или конфига отключает этот автоматический выбор.
Каталоги моделей различаются между бэкендами, поэтому при закреплении `model`
в конфиге рекомендуется явно закрепить и совместимый `device`. То же с языком:
автоматический ONNX-профиль рассчитан на русскую речь, а для остальных языков
нужен Whisper — например, `device = "openvino-cpu"` и `model = "medium"`.
Дефолты зависят от устройства:
| Параметр | CUDA | OpenVINO (GPU) | OpenVINO (CPU) | ONNX | CPU |
@@ -258,17 +275,20 @@ 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`
- **Макс. скорость CPU (русский):** `--device onnx --model gigaam-v3` (17-29× RTF, без пунктуации; рекомендуется LLM-нормализация терминов после)
- **CPU с пунктуацией (русский):** `--device openvino-cpu --model medium` (5-6× RTF; для встреч ≤30 мин с равномерной громкостью — на длинных файлах с тихими фрагментами возможны галлюцинации)
- **Быстрый OpenVINO с низким WER:** `--device openvino-cpu --model large-v3-turbo --compute-type int8` (7,2× RTFx на контрольном Intel CPU; пунктуация может быть слабой)
- **OpenVINO для чтения и конспекта:** `--device openvino-cpu --model medium` (около 6× RTFx; независимая оценка показала лучшую сохранность содержания, чем turbo)
- **Быстрый тест:** `tiny` — для проверки пайплайна
<details>
<summary>Таблица моделей</summary>
#### Whisper через faster-whisper (`--device cuda`, `--device cpu`)
| Модель | Размер на диске | VRAM (int8) | Скорость (GPU) | Качество |
|--------|----------------|-------------|----------------|----------|
| `tiny` | ~75 MB | ~1 GB | ★★★★★ | ★ |
@@ -277,6 +297,15 @@ language = "en"
| `medium` | ~1.5 GB | ~2.5 GB | ★★ | ★★★★ |
| `large-v3` | ~3 GB | ~2.5 GB | ★ | ★★★★★ |
#### Whisper через OpenVINO (`--device openvino-cpu`, `--device openvino-gpu`)
Здесь те же модели Whisper, но предквантизированные, поэтому на диске они
занимают меньше места: `medium` int8 — 748 MB, `large-v3-turbo` int8 — 790 MB,
`large-v3-turbo` fp16 — 1552 MB. Модель `large-v3-turbo` доступна только здесь:
faster-whisper её не поддерживает, и запуск с `--device cuda` завершится
ошибкой. Полный список репозиториев —
[docs/gpu.md](docs/gpu.md#доступные-openvino-модели).
#### ONNX-модели (`--device onnx`)
Другие архитектуры, не Whisper. Работают через onnxruntime на CPU:
@@ -308,6 +337,9 @@ language = "en"
кыргызский и узбекский внутри одной записи. `onnx-asr` не передаёт этим моделям
подсказку языка, поэтому `--language` не управляет выбором языка.
Если модель не понимает запрошенный язык, CLI предупреждает об этом до начала
распознавания и подсказывает совместимый профиль, но работу не прерывает.
Для моделей из таблицы опубликованы `int8` и `float32`. Если неявный
device-aware дефолт недоступен для выбранной модели, CLI сообщит о подстановке
доступного варианта. Явное значение из `--compute-type` или
@@ -331,11 +363,15 @@ device-aware дефолт недоступен для выбранной мод
`float16`/`fp16` и `float32` значительно стабильнее на записях >20 минут.
> Для OpenVINO `--compute-type` выбирает предквантизированную модель (int8 или fp16),
> а не runtime-параметр. Для `large-v3` по умолчанию выбирается `fp16`.
> а не параметр времени выполнения. Для `large-v3` по умолчанию выбирается
> `fp16`; для `large-v3-turbo` доступны явные варианты `int8` и `fp16`, а
> неявный профиль OpenVINO использует `int8`.
</details>
Подробнее: бенчмарки, OpenVINO, совместимость GPU, результаты тестирования — [docs/gpu.md](docs/gpu.md).
Подробнее: бенчмарки, OpenVINO, совместимость GPU, результаты тестирования —
[docs/gpu.md](docs/gpu.md). Сравнение `large-v3-turbo` с CPU-профилями:
[OpenVINO 2026.3 и large-v3-turbo](docs/benchmarks/2026-08-12-openvino-large-v3-turbo-comparison.md).
<details>
<summary>Формат вывода</summary>
@@ -345,7 +381,7 @@ device-aware дефолт недоступен для выбранной мод
- **Дата транскрипции**: 2026-03-17 14:30:05
- **Модель**: large-v3
- **Язык**: ru (detected)
- **Язык**: ru (задан явно)
- **Длительность**: 01:23:45
- **Устройство**: CUDA (NVIDIA GeForce RTX 3060)
@@ -360,6 +396,14 @@ device-aware дефолт недоступен для выбранной мод
Близкие по времени сегменты автоматически объединяются в абзацы (пауза > 2 сек или длительность > 60 сек разделяет абзацы).
Таймкоды: `MM:SS.ss`, для записей длиннее 1 часа — `HH:MM:SS.ss`.
В скобках после языка указан его источник:
- `задан явно` — язык взят из `--language` или конфига;
- `определён автоматически` — распознан моделью при `--language auto`;
- `из профиля модели` — у модели всего один язык, как у GigaAM.
Если язык определить не удалось, строка выглядит так: `- **Язык**: не определён`.
</details>
## Поддерживаемые форматы
+34 -32
View File
@@ -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 Путь к выходному файлу
По умолчанию: <input_stem>-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 Подробный вывод (прогресс сегментов)
```
@@ -63,7 +64,7 @@ transcribe <путь_к_файлу> [опции]
- **Дата транскрипции**: 2026-03-17 14:30:05
- **Модель**: large-v3
- **Язык**: ru (detected) / ru (forced)
- **Язык**: ru (задан явно) / ru (определён автоматически)
- **Длительность**: 01:23:45
- **Устройство**: CUDA (NVIDIA GeForce RTX 3060)
@@ -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
+9 -6
View File
@@ -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
## Отклонённые альтернативы
+17 -8
View File
@@ -2,7 +2,7 @@
**Статус**: Принято
**Дата**: 2026-04-25
**Обновлено**: 2026-08-11
**Обновлено**: 2026-08-12
## Контекст
@@ -101,10 +101,16 @@ 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 понимает только русскую речь, поэтому для остальных языков автоматический
профиль не годится — нужен явный Whisper. Несовместимый язык работу не
останавливает: CLI предупреждает о нём до начала распознавания и называет
подходящий профиль.
Модели:
- **`gigaam-v3-e2e-rnnt`** — модель по умолчанию: практически равна обычному
@@ -122,16 +128,20 @@ 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-этапом нормализации (восстановление латинских терминов и имён компаний) — рекомендуемая практика для финального конспекта.
## Открытые вопросы / следующие шаги
- **Galлюцинации Whisper medium на длинных файлах** — отдельный продуктовый риск, требующий собственного исследования. Возможно, имеет смысл ограничить максимальную длину чанка для openvino-medium, или дать предупреждение пользователю.
- **Галлюцинации Whisper medium на длинных файлах** — отдельный продуктовый риск, требующий собственного исследования. Возможно, имеет смысл ограничить максимальную длину чанка для openvino-medium, или дать предупреждение пользователю.
- **Canary** (`nemo-canary-1b-v2`) — тяжелее, но multilingual + пунктуация. Кандидат на «лучшее качество за разумную скорость» для тех, кому важна пунктуация.
- **Auto-detect onnx**: после стабилизации в production-использовании (несколько недель) — рассмотреть включение в auto-detect как первый CPU-бэкенд (ниже CUDA, выше OpenVINO).
- Проверить качество на других языках и при необходимости дополнить
многоязычные рекомендации. Сама автоматическая политика от языка не зависит:
она предупреждает о несовместимости, но профиль за пользователя не меняет.
## Отклонённые альтернативы
@@ -139,5 +149,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-редукция, иноязычные вставки) делают его непригодным для русского; для других языков не валидировано в этом эксперименте |
+10 -4
View File
@@ -252,13 +252,19 @@ SQL`), словарь пользовательский в `.transcriber.toml`.
---
### Включение `onnx` в `--device auto`
### `large-v3-turbo` для faster-whisper
**Что:** После периода стабилизации `--device onnx` (несколько недель production-использования без жалоб) — рассмотреть включение в auto-detect chain.
**Что:** Добавить `large-v3-turbo` в каталог faster-whisper, чтобы модель была
доступна с `--device cuda` и `--device cpu`, а не только через OpenVINO.
**Порядок в chain (предложение):** CUDA → onnx (если CPU x86_64) → OpenVINO → CPU.
**Почему:** На CPU turbo INT8 оказался быстрее `medium` и точнее по WER
([сравнение от 2026-08-12](benchmarks/2026-08-12-openvino-large-v3-turbo-comparison.md)),
и владельцам NVIDIA этот профиль сейчас недоступен без ручного указания
репозитория HuggingFace.
**Почему откладывается:** политика experimental backend — не сюрпризить существующих пользователей до накопления опыта. Источник: [ADR-006](adr/006-onnx-asr-backend.md#решение).
**Почему откладывается:** каталог faster-whisper в проекте собран из
репозиториев `Systran`, и для turbo нужно сначала выяснить, какая CT2-сборка
годится, проверить её происхождение и качество, и только потом вносить в код.
---
@@ -0,0 +1,348 @@
# Сравнение OpenVINO large-v3-turbo с CPU-профилями
**Дата:** 2026-08-12
**Статус:** завершённое сравнение на трёх русскоязычных встречах и двух CPU
## Цель
Проверить обновление OpenVINO с линии 2026.0 до 2026.3 и определить, даёт ли
`large-v3-turbo` практическое преимущество перед `medium` и быстрым профилем
ONNX на CPU. Модель по умолчанию и порядок выбора `--device auto` в рамках
сравнения не менялись.
## Оборудование и условия
- ноутбук Lenovo 82JD;
- Intel Core i7-11800H, 8 ядер / 16 логических процессоров, 2,30 ГГц;
- 64 ГиБ DDR4-3200, два модуля Kingston по 32 ГиБ;
- Windows 11 Корпоративная, сборка 26200;
- схема питания «Сбалансированная»;
- OpenVINO 2026.3.0, OpenVINO GenAI и Tokenizers 2026.3.0.0;
- Python 3.13.13;
- язык во всех командах задан явно: `--language ru`;
- модели запускались последовательно, без конкурирующих прогонов.
Кэш каждой модели предварительно прогревался. Время измерялось от запуска
команды до её завершения и включает чтение и декодирование локального файла,
но не скачивание модели. Загрузка процессора и свободная память опрашивались
раз в две секунды. Минимум свободной памяти характеризует всю систему, а не
только процесс распознавания.
Во время прогонов система оставалась пригодной для обычной интерактивной
работы. На OpenVINO средняя загрузка составляла 54–62%. ONNX на двух записях
кратковременно занимал процессор полностью, но наблюдаемого зависания системы
не было. Это субъективное наблюдение, а не числовой порог приёмки.
## Контрольный набор
Использованы те же три записи общей длительностью 43:37 и те же внешние
транскрипты, что в [сравнении GigaAM и Whisper](2026-08-11-gigaam-model-comparison.md).
Размеры и SHA-256 всех шести локальных файлов совпали с опубликованным там
манифестом.
Внешние транскрипты сами содержат ошибки, редактируют разговорную речь и
добавляют разделение по говорящим. Поэтому WER служит сравнительным сигналом
внутри одной записи, а не абсолютной оценкой качества.
## Методика
Команды OpenVINO имели следующий вид:
```powershell
uv run transcribe <recording.mp4> `
--device openvino-cpu `
--model <medium|large-v3-turbo> `
--compute-type <int8|fp16> `
--language ru `
--force
```
Для ONNX использовались `--device onnx`, `--model gigaam-v3-e2e-rnnt` и
`--compute-type int8`. WER считался после удаления метаданных, таймкодов и
обозначений говорящих, приведения к нижнему регистру, замены `ё` на `е` и
удаления пунктуации. Итоговый WER — микроусреднение: сумма замен, удалений и
вставок делится на общее число слов во внешних текстах. Плотность пунктуации —
число знаков `.,!?…:;` на 1000 распознанных слов.
## Проверка обновления OpenVINO
До изменения зависимостей OpenVINO 2026.0 `medium` INT8 был повторно запущен
на всём наборе с прогретым кэшем. После обновления тот же профиль успешно
прошёл автоматические проверки и реальный прогон.
| Стек и профиль | Суммарное время | RTFx | WER | Пунктуация / 1000 слов | Предупреждения |
|---|---:|---:|---:|---:|---:|
| OpenVINO 2026.0 medium INT8 | 417,0 с | 6,28× | 28,9% | 191 | 1 |
| OpenVINO 2026.3 medium INT8 | 436,7 с | 5,99× | 25,3% | 185 | 0 |
На линии 2026.0 RSQM содержал 11 одинаковых последовательных сегментов и
получил предупреждение о возможной галлюцинации. После обновления повтор не
воспроизвёлся, а WER этой записи снизился с 34,2% до 23,3%. Суммарное время
выросло на 4,7%; из-за естественного разброса единичных запусков это не следует
считать устойчивой регрессией без серии повторов.
## Результаты после обновления
### Сводка по трём записям
| Профиль | Время | RTFx | WER | Пунктуация / 1000 слов | Средний / пиковый CPU | Минимум свободной памяти |
|---|---:|---:|---:|---:|---:|---:|
| OpenVINO medium INT8 | 436,7 с | 5,99× | 25,3% | 185 | 54,3% / 85% | 36,25 ГиБ |
| **OpenVINO large-v3-turbo INT8** | **365,5 с** | **7,16×** | **24,0%** | 96 | 62,0% / 86% | 35,04 ГиБ |
| OpenVINO large-v3-turbo FP16 | 545,0 с | 4,80× | 24,8% | 103 | 57,1% / 87% | 32,96 ГиБ |
| ONNX GigaAM v3 E2E RNN-T INT8 | **265,7 с** | **9,85×** | 29,1% | **290** | 70,7% / 100% | 35,52 ГиБ |
### По отдельным записям
| Запись | Профиль | Время | RTFx | WER | Пунктуация / 1000 слов |
|---|---|---:|---:|---:|---:|
| BDMA | medium INT8 | 137,0 с | 6,50× | **23,0%** | 194 |
| BDMA | turbo INT8 | **118,7 с** | **7,51×** | 24,4% | 79 |
| BDMA | turbo FP16 | 180,0 с | 4,95× | 26,2% | 60 |
| BDMA | GigaAM RNN-T INT8 | **87,6 с** | **10,17×** | 29,0% | **326** |
| RSQM | medium INT8 | 125,7 с | 6,72× | 23,3% | 171 |
| RSQM | turbo INT8 | 116,9 с | 7,23× | **22,4%** | 4 |
| RSQM | turbo FP16 | 170,2 с | 4,96× | 23,3% | 31 |
| RSQM | GigaAM RNN-T INT8 | **92,9 с** | **9,09×** | 27,4% | **286** |
| DataCat | medium INT8 | 174,0 с | 5,07× | 28,1% | 188 |
| DataCat | turbo INT8 | **129,9 с** | **6,79×** | **24,7%** | 169 |
| DataCat | turbo FP16 | 194,7 с | 4,53× | **24,7%** | 181 |
| DataCat | GigaAM RNN-T INT8 | **85,2 с** | **10,36×** | 30,4% | **265** |
## Качественная оценка
Два независимых субагента читали полные транскрипты без таблицы численных
метрик. Один оценивал читаемость и пригодность для конспекта и протокола,
второй — сохранность содержания и терминов, пропуски, повторы и галлюцинации.
### Читаемость и пригодность результата
| Профиль | Читаемость | Для конспекта | Для протокола |
|---|---:|---:|---:|
| ONNX GigaAM RNN-T INT8 | **4/5** | **4/5** | **3/5** |
| OpenVINO medium INT8 | 3/5 | 3/5 | **3/5** |
| OpenVINO turbo INT8 | 2/5 | 3/5 | 2/5 |
| OpenVINO turbo FP16 | 2/5 | 3/5 | 2/5 |
Проверяющий поставил GigaAM на первое место благодаря пунктуации, отделению
реплик и естественному ритму. Из текста без труда извлекаются решения: собрать
вопросы для архитектуры и сайзинга, не связываться с «дата-рентгеном» в пилоте,
уточнить способ доступа к FineBI. При этом протокол требует сверки терминов:
`XML` превращается в «Максмельная», `Spark` — в «парк», `MPP` — в «MP-шный».
Medium в основном сохраняет ход мысли, но нестабилен в названиях: «бутылочка в
FineBI» вместо учётной записи, `NiFi` как `I5`, `edge cases` как `HKEYS`.
Оба turbo-профиля иногда лучше узнают отдельные имена и термины, включая
«Рома Иваницкий», `Open Lineage` и `DBC tables V`, однако длинные блоки почти
без пунктуации приходится разбирать вручную. Практического преимущества FP16
над INT8 по читаемости не найдено.
### Сохранность содержания
| Профиль | Смысл | Термины | Пропуски | Повторы и выдумки |
|---|---:|---:|---:|---:|
| ONNX GigaAM RNN-T INT8 | **5/5** | **4/5** | **4/5** | **4/5** |
| OpenVINO medium INT8 | 4/5 | 3/5 | **4/5** | **4/5** |
| OpenVINO turbo FP16 | 4/5 | 3/5 | 3/5 | **4/5** |
| OpenVINO turbo INT8 | 3/5 | 3/5 | 3/5 | **4/5** |
Второй проверяющий также поставил GigaAM первым: он лучше удерживает ход
рассуждения, отрицания и поручения. Среди OpenVINO medium оказался надёжнее
обоих turbo-профилей по сохранности содержания, несмотря на худший WER.
Наиболее опасные искажения:
- turbo FP16 в DataCat пропустил оговорку о том, что API-контракт извлечён из
JAR-файлов, а не документации; предположение выглядит установленным фактом;
- turbo INT8 почти потерял обсуждение доступа к DBC, риска перегрузить FineBI
через API и необходимости ограничить частоту запросов;
- medium превратил поручение посчитать объём и квоту Teradata в плохо читаемое
«нам требование это продать… конкретный объём в продате квоту»;
- GigaAM один раз заменил «МТС хочет продать дата-рентген» на «ты хочешь
продать дата-рентген», хотя соседняя фраза восстанавливает инициатора;
- ключ поиска `IMSI плюс время` не сохранился точно ни в одном профиле;
- у medium после фактического окончания RSQM появился одиночный сегмент «В».
Длинных повторяющихся или полностью выдуманных фрагментов после обновления
проверяющие не нашли. Общий недостаток всех профилей — отсутствие надёжного
разделения по участникам, что ограничивает пригодность для формального протокола.
## Повторный прогон на AMD Ryzen 7 8845H
Повторный прогон выполнен на втором ноутбуке тем же коммитом, на тех же шести
файлах и с теми же параметрами. Размеры и SHA-256 всех файлов совпали с
манифестом. Результаты Intel выше не перезаписывались.
### Оборудование и условия
- ноутбук Lenovo 83D5;
- AMD Ryzen 7 8845H, 8 ядер / 16 логических процессоров;
- 32 ГиБ LPDDR5X, четыре чипа Micron, фактическая скорость 6400 MT/s;
- Windows 11 Корпоративная, сборка 26200;
- схема питания «Сбалансированная»;
- OpenVINO 2026.3.0, OpenVINO GenAI и Tokenizers 2026.3.0.0;
- Python 3.13.13;
- язык во всех командах задан явно: `--language ru`;
- кэш каждой модели предварительно прогрет, скачивание не включено во время;
- загрузка CPU и свободная память опрашивались раз в две секунды.
Во время всех прогонов ноутбук оставался пригодным для интерактивной работы.
Минимум свободной памяти ниже, чем на Intel, из-за 32 ГиБ физической памяти и
фоновой нагрузки; это показатель всей системы, а не потребления одной модели.
### Сводка по трём записям
| Профиль | Время | RTFx | WER | Пунктуация / 1000 слов | Средний / пиковый CPU | Минимум свободной памяти |
|---|---:|---:|---:|---:|---:|---:|
| OpenVINO medium INT8 | 438,5 с | 5,97× | 22,9% | 193 | 59,2% / 89% | 6,65 ГиБ |
| **OpenVINO large-v3-turbo INT8** | **267,4 с** | **9,79×** | **19,6%** | 108 | 57,4% / 72% | 8,93 ГиБ |
| OpenVINO large-v3-turbo FP16 | 333,6 с | 7,85× | 20,1% | 114 | 59,0% / 78% | 8,39 ГиБ |
| **ONNX GigaAM v3 E2E RNN-T INT8** | **194,8 с** | **13,44×** | 26,9% | **290** | 56,2% / 82% | 11,71 ГиБ |
Для `medium` на RSQM получились три измеряемых времени: 158,8, 118,3 и
129,3 секунды. Все три транскрипта и отдельный прогревочный проход содержательно
совпали. В таблице использована медиана 129,3 секунды, чтобы не выбирать
произвольно единичный фоновый выброс.
### По отдельным записям
| Запись | Профиль | Время | RTFx | WER | Пунктуация / 1000 слов |
|---|---|---:|---:|---:|---:|
| BDMA | medium INT8 | 140,3 с | 6,35× | 25,0% | 202 |
| BDMA | turbo INT8 | **88,8 с** | **10,03×** | 24,5% | 55 |
| BDMA | turbo FP16 | 109,1 с | 8,16× | **24,4%** | 61 |
| BDMA | GigaAM RNN-T INT8 | **67,5 с** | **13,21×** | 29,0% | **326** |
| RSQM | medium INT8 | 129,3 с | 6,53× | 20,2% | **175** |
| RSQM | turbo INT8 | **82,1 с** | **10,28×** | **16,7%** | 61 |
| RSQM | turbo FP16 | 104,6 с | 8,07× | 20,4% | 48 |
| RSQM | GigaAM RNN-T INT8 | **58,6 с** | **14,41×** | 25,4% | **286** |
| DataCat | medium INT8 | 168,9 с | 5,22× | 23,2% | **199** |
| DataCat | turbo INT8 | **96,5 с** | **9,14×** | 18,0% | 178 |
| DataCat | turbo FP16 | 119,8 с | 7,36× | **16,8%** | 197 |
| DataCat | GigaAM RNN-T INT8 | **68,7 с** | **12,83×** | 26,4% | **265** |
### Повторы `medium` на паузах
На линии 2026.0 один контрольный прогон RSQM на Intel содержал 11 одинаковых
последовательных сегментов. После обновления на Ryzen выполнены четыре прохода
того же файла: один прогревочный и три измеряемых. Во всех четырёх получены
одинаковые 152 сегмента, объединённые форматтером в 17 абзацев. Детектор серий
из четырёх и более одинаковых сегментов не сработал, различий в содержательной
части файлов нет, ложного хвоста также нет.
На этом контрольном файле повтор после OpenVINO 2026.3 устойчиво не
воспроизводится на двух машинах. Это сильнее единичного успешного запуска, но
не доказывает устранение всего класса Whisper-галлюцинаций на тишине: набор
содержит только одну ранее проблемную запись.
### Отличия Ryzen от Intel
- `medium` показал практически одинаковое суммарное время: 438,5 против
436,7 секунды.
- Turbo INT8 на Ryzen быстрее на 27%: 267,4 против 365,5 секунды; FP16 быстрее
на 39%: 333,6 против 545,0 секунды.
- GigaAM на Ryzen быстрее на 27%: 194,8 против 265,7 секунды.
- На Ryzen Turbo INT8 ускоряет обработку относительно `medium` на 39%, а
GigaAM — на 56%.
- Численные WER на Ryzen ниже у всех четырёх профилей. Поскольку файлы,
версии библиотек и заявленная методика совпадают, но сами выходы различаются.
Сравнение характеризует всю среду выполнения; приписывать разницу только
архитектуре CPU без отдельного исследования нельзя.
- Пунктуация Turbo остаётся нестабильной: на Ryzen она лучше результата Intel
для RSQM, но всё ещё значительно реже, чем у `medium` и GigaAM.
### Независимая оценка качества на Ryzen
Как и на первом ноутбуке, два субагента читали полные транскрипты вслепую, без
названий профилей и таблицы численных метрик. Их оценки разошлись по ожидаемой
причине: один ставил выше готовность текста к чтению, второй — точность
технических деталей.
| Профиль | Читаемость | Для конспекта | Для протокола |
|---|---:|---:|---:|
| ONNX GigaAM RNN-T INT8 | **3/5** | **4/5** | **3/5** |
| OpenVINO medium INT8 | **3/5** | 3/5 | 2/5 |
| OpenVINO turbo FP16 | 2/5 | 3/5 | 2/5 |
| OpenVINO turbo INT8 | 2/5 | 2/5 | 2/5 |
По читаемости GigaAM снова занял первое место: он лучше размечает смысловые
границы, сохраняет бытовые и организационные реплики и требует меньше ручной
чистки для чернового конспекта. `medium` оказался вторым. У обоих Turbo начало
нескольких записей превращается в длинные блоки нижнего регистра почти без
пунктуации. Ни один вариант не признан готовым формальным протоколом без сверки.
| Профиль | Смысл | Термины | Полнота | Нет повторов и выдумок |
|---|---:|---:|---:|---:|
| OpenVINO turbo FP16 | **4/5** | **4/5** | 4/5 | **4/5** |
| OpenVINO turbo INT8 | 4/5 | **4/5** | 4/5 | **4/5** |
| OpenVINO medium INT8 | 4/5 | **4/5** | 4/5 | 3/5 |
| ONNX GigaAM RNN-T INT8 | 4/5 | 3/5 | **5/5** | 3/5 |
По технической сохранности первым стал Turbo FP16, особенно на DataCat; Turbo
INT8 почти равен ему. Они лучше удерживают `FineBI`, `OpenLineage`, `Spark`,
`XML`, `DBC TablesV/ColumnsV`, `Open Platform` и архитектурные связи. GigaAM
самый полный, но чаще заменяет неразобранное гладко звучащими ложными терминами.
Наиболее опасные места:
- GigaAM потерял `IMSI` в требуемом ключе `IMSI плюс время`, исказил основание
оставить MPP-вычисления в Teradata и не сохранил Alation как технический
ориентир интеграции с FineBI;
- `medium` заменил Tele2 на `TeraData 2` и «админскую учётку» на «учётку от
Минска», то есть правдоподобно изменил адресата и источник доступа;
- Turbo INT8 почти потерял риск DDoS FineBI через API и необходимость
ограничивать частоту запросов; в другом месте ответ о production-базах
локально перевёрнут;
- Turbo FP16 хуже остальных передал `edge cases` на RSQM и потерял поручение
оценить требования и квоту Teradata, но лучше всего сохранил технически
насыщенный DataCat.
Ни один проверяющий не нашёл длинных повторяющихся или автономно выдуманных
блоков. Основной остаточный риск всех профилей — локальная правдоподобная
подстановка термина, числа или адресата.
### Что это означает для профиля по умолчанию
Результаты поддерживают ONNX GigaAM RNN-T как основной CPU-профиль: он быстрее
всех на обеих машинах, дважды занял первое место по читаемости и пригодности
для конспекта и использует VAD. После обсуждения принята простая политика:
`--device auto` выбирает CUDA при наличии `nvidia-smi`, иначе ONNX.
Ограничения выбора сохраняются:
- GigaAM ориентирован на русский язык и хуже сохраняет латиницу, аббревиатуры,
названия систем и компаний;
- OpenVINO Whisper остаётся многоязычным путём и умеет использовать Intel GPU;
- Turbo лучше GigaAM по WER и техническим терминам, хотя хуже подготовлен для
непосредственного чтения;
- изменение существующего auto-пути является несовместимым изменением поведения;
явные значения из CLI и конфигурации остаются способом сохранить прежний
OpenVINO/Whisper-профиль или выбрать Turbo для технически насыщенных встреч.
## Вывод
- `large-v3-turbo` реально работает с OpenVINO 2026.3 в вариантах INT8 и FP16.
- Turbo INT8 оказался на 16% быстрее medium INT8 и снизил суммарный WER с
25,3% до 24,0%. Это лучший OpenVINO-профиль по численным показателям на
данном CPU, но не по независимой оценке сохранности содержания и читаемости.
- Turbo FP16 на этом CPU на 49% медленнее turbo INT8 и не дал выигрыша по WER.
Его стоит оставлять доступным для явного выбора и других устройств, но не
рекомендовать как основной CPU-профиль.
- GigaAM RNN-T остаётся самым быстрым и лучше всех расставляет пунктуацию. Он
уступает turbo по WER и сильнее загружает CPU, но оба независимых проверяющих
признали его лучшим для чтения и сохранения делового содержания.
- У turbo INT8 пунктуация нестабильна: на RSQM её почти нет. Для готового
протокола может потребоваться последующая расстановка пунктуации.
- Автоматических предупреждений о повторах или потере хвоста после обновления
не было; ручная проверка нашла у medium одиночный ложный сегмент в конце RSQM.
- В самом benchmark-коммите модель по умолчанию и порядок `--device auto` не
менялись; по итогам анализа отдельно принято решение использовать ONNX на
машинах без CUDA.
- Повторный Ryzen-прогон подтвердил, что прежний блок из 11 повторов `medium`
не воспроизводится: четыре последовательных прохода дали одинаковый текст без
повторов и ложного хвоста. Это подтверждение для контрольного файла, а не
гарантия устранения всех Whisper-галлюцинаций на тишине.
- Для русской речи на CPU ONNX GigaAM RNN-T выглядит лучшим пользовательским
дефолтом по скорости и готовности текста к чтению. OpenVINO сохраняет сильные
аргументы для Intel GPU, других языков и технически насыщенных записей.
+32 -7
View File
@@ -2,11 +2,11 @@
## Режимы `--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
- `openvino-cpu` — строго CPU через OpenVINO (ускорение 2-4x на x86)
- `openvino-cpu` — строго CPU через OpenVINO (3-10x на x86, зависит от модели)
- `cpu` — строго CPU (faster-whisper/CTranslate2)
## Какой бэкенд на каком оборудовании
@@ -14,12 +14,17 @@
| Оборудование | Рекомендуемый `--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, модели и записи.
Автоматический профиль без NVIDIA рассчитан на русскую речь: GigaAM других
языков не понимает. Для них берите Whisper — `openvino-cpu` на x86 или `cpu`
на любой платформе.
## OpenVINO
@@ -28,6 +33,8 @@ OpenVINO ускоряет inference на x86 процессорах (Intel и AM
- **Модели**: предконвертированные из [HuggingFace](https://huggingface.co/OpenVINO) (int8/fp16)
- **Дефолт**: `medium` + `int8` (для `large-v3` автоматически выбирается `fp16`)
- **Быстрый профиль с низким WER**: явный `large-v3-turbo` + `int8`; для
читаемости и сохранности содержания `medium` остаётся предпочтительнее
- **Аудиодекодирование**: через PyAV (бандлит FFmpeg), системный ffmpeg не нужен
### Доступные OpenVINO модели
@@ -39,9 +46,23 @@ OpenVINO ускоряет inference на x86 процессорах (Intel и AM
| small | OpenVINO/whisper-small-int8-ov | — |
| medium | OpenVINO/whisper-medium-int8-ov | OpenVINO/whisper-medium-fp16-ov |
| large-v3 | OpenVINO/whisper-large-v3-int8-ov | OpenVINO/whisper-large-v3-fp16-ov |
| large-v3-turbo | OpenVINO/whisper-large-v3-turbo-int8-ov | OpenVINO/whisper-large-v3-turbo-fp16-ov |
Размер в кеше HuggingFace: `medium` int8 — 748 MB, `large-v3-turbo` int8 —
790 MB, `large-v3-turbo` fp16 — 1552 MB. Это меньше, чем у тех же моделей для
faster-whisper, потому что веса уже квантизированы.
Модель `large-v3-turbo` доступна только в OpenVINO: для `--device cuda` и
`--device cpu` каталог faster-whisper заканчивается на `large-v3`.
### Результаты тестирования OpenVINO
Актуальное сравнение OpenVINO 2026.3 на трёх русскоязычных встречах:
[medium, large-v3-turbo и GigaAM](benchmarks/2026-08-12-openvino-large-v3-turbo-comparison.md).
На Intel Core i7-11800H `large-v3-turbo` INT8 обработал 43:37 аудио за
365,5 секунды (7,16× RTFx) при WER 24,0%. FP16 занял 545,0 секунды и получил
WER 24,8%, поэтому для CPU рекомендуется INT8.
Реальные записи рабочих созвонов (русский, техтермины: SQL, PostgreSQL, LDAP, DLP и др.).
**Скорость (эталонный файл 16 мин, OpenVINO, medium int8):**
@@ -76,6 +97,10 @@ OpenVINO ускоряет inference на x86 процессорах (Intel и AM
- **small** — для быстрого сканирования большого объёма видео по маске (`*.mp4`). Ошибки в отдельных словах; для обработки ИИ (МОМ, конспект) рискованно — "рецензия" вместо "лицензия" может исказить смысл.
- **medium** — для повседневного использования и обработки ИИ. Ключевые термины верные, единичные ляпы не влияют на смысл конспекта. Оптимальный баланс скорости и качества.
- **large-v3** — для важных записей, где нужна дословная точность. Лучшая пунктуация и связность. На OpenVINO (416с) быстрее, чем medium на чистом CPU (734с) — лучшее качество при выше скорости.
- **large-v3-turbo** — явный профиль для CPU с приоритетом скорости и низкого
WER. В проверенном наборе INT8 быстрее и численно точнее medium, но хуже по
независимой оценке читаемости и сохранности содержания; FP16 на CPU пользы
не показал.
## CPU бэкенд (CTranslate2 / faster-whisper)
+1 -1
View File
@@ -10,7 +10,7 @@ dependencies = [
"faster-whisper>=1.2.1,<2",
"socksio>=1.0.0,<2",
"nvidia-cublas-cu12>=12.4,<13; sys_platform == 'linux' and platform_machine == 'x86_64'",
"openvino-genai>=2026.0.0.0,<2026.1; sys_platform != 'darwin' and (platform_machine == 'x86_64' or platform_machine == 'AMD64')",
"openvino-genai>=2026.3.0.0,<2026.4; sys_platform != 'darwin' and (platform_machine == 'x86_64' or platform_machine == 'AMD64')",
"onnx-asr[cpu,hub]>=0.12,<0.13",
"onnxruntime>=1.28,<2",
]
+100 -5
View File
@@ -2,45 +2,91 @@
from __future__ import annotations
import warnings
from collections.abc import Callable
from dataclasses import dataclass
from pathlib import Path
from typing import Any
from local_transcriber.types import Segment, TranscribeResult
from local_transcriber.types import UNKNOWN_LANGUAGE, Segment, TranscribeResult
@dataclass(frozen=True)
class OnnxModelSpec:
"""Имя onnx-asr и опубликованные варианты квантизации модели."""
"""Имя onnx-asr, варианты квантизации и поддерживаемые языки."""
model_id: str
quantizations: frozenset[str | None]
supported_languages: frozenset[str]
_INT8_AND_FLOAT32 = frozenset({"int8", None})
_RUSSIAN_ONLY = frozenset({"ru"})
_GIGAAM_MULTILINGUAL_LANGUAGES = frozenset({"ru", "en", "kk", "ky", "uz"})
_PARAKEET_V3_LANGUAGES = frozenset(
{
"bg",
"hr",
"cs",
"da",
"nl",
"en",
"et",
"fi",
"fr",
"de",
"el",
"hu",
"it",
"lv",
"lt",
"mt",
"pl",
"pt",
"ro",
"sk",
"sl",
"es",
"sv",
"ru",
"uk",
}
)
_WHISPER_MODEL_NAMES = frozenset(
{"tiny", "base", "small", "medium", "large-v3", "large-v3-turbo"}
)
# faster-whisper не знает turbo, поэтому для cpu и cuda подсказываем medium.
# Источник правды — MODEL_REPOS в backends/faster_whisper.py и backends/openvino.py
_OPENVINO_ONLY_WHISPER_MODELS = frozenset({"large-v3-turbo"})
MODEL_CATALOG: dict[str, OnnxModelSpec] = {
"gigaam-v3": OnnxModelSpec("gigaam-v3-ctc", _INT8_AND_FLOAT32),
"gigaam-v3": OnnxModelSpec(
"gigaam-v3-ctc", _INT8_AND_FLOAT32, _RUSSIAN_ONLY
),
"parakeet-v3": OnnxModelSpec(
"nemo-parakeet-tdt-0.6b-v3",
_INT8_AND_FLOAT32,
_PARAKEET_V3_LANGUAGES,
),
"gigaam-multilingual-ctc": OnnxModelSpec(
"gigaam-multilingual-ctc",
_INT8_AND_FLOAT32,
_GIGAAM_MULTILINGUAL_LANGUAGES,
),
"gigaam-multilingual-large-ctc": OnnxModelSpec(
"gigaam-multilingual-large-ctc",
_INT8_AND_FLOAT32,
_GIGAAM_MULTILINGUAL_LANGUAGES,
),
"gigaam-v3-e2e-ctc": OnnxModelSpec(
"gigaam-v3-e2e-ctc",
_INT8_AND_FLOAT32,
_RUSSIAN_ONLY,
),
"gigaam-v3-e2e-rnnt": OnnxModelSpec(
"gigaam-v3-e2e-rnnt",
_INT8_AND_FLOAT32,
_RUSSIAN_ONLY,
),
}
@@ -85,6 +131,8 @@ class OnnxAsrBackend:
self._compute_type_explicit = compute_type_explicit
self.actual_compute_type: str | None = None
self._resolved_model_id: str | None = None
self._model_name: str | None = None
self._model_spec: OnnxModelSpec | None = None
self._vad: Any = None
def ensure_model_available(
@@ -119,6 +167,8 @@ class OnnxAsrBackend:
self.actual_compute_type = resolved_compute_type
self._resolved_model_id = self._resolve_model(model_name)
self._model_name = model_name
self._model_spec = spec
return self._resolved_model_id
def create_model(
@@ -162,13 +212,16 @@ class OnnxAsrBackend:
"""
from faster_whisper import decode_audio
self._warn_if_language_unsupported(language)
_notify(on_status, "Загружаю аудио...")
audio_array = decode_audio(str(file_path), sampling_rate=16000)
duration = len(audio_array) / 16000.0
_notify(on_status, "Транскрибирую (onnx-asr)...")
segments: list[Segment] = []
detected_language = language or "unknown"
result_language = (
language or _model_language(self._model_spec) or UNKNOWN_LANGUAGE
)
for vad_seg in model.recognize(
audio_array, sample_rate=16000, language=language
@@ -192,7 +245,7 @@ class OnnxAsrBackend:
return TranscribeResult(
segments=segments,
language=detected_language,
language=result_language,
language_probability=1.0 if language else 0.0,
duration=duration,
device_used="", # оркестратор проставит
@@ -202,6 +255,15 @@ class OnnxAsrBackend:
"""Resolve alias to onnx-asr model name. Raw names pass through."""
if model_name in MODEL_ALIASES:
return MODEL_ALIASES[model_name]
if model_name in _WHISPER_MODEL_NAMES:
fallback = _whisper_fallback_model(model_name)
raise ValueError(
f"Модель '{model_name}' относится к Whisper и не поддерживается "
"ONNX-бэкендом. Без CUDA --device auto выбирает ONNX; "
f"укажите --device openvino-cpu --model {model_name} на x86, "
f"--device cpu --model {fallback} на любой платформе "
f"или --device cuda --model {fallback} при NVIDIA GPU."
)
if "/" in model_name or model_name.count("-") >= 2:
# Looks like a raw onnx-asr name — allow passthrough
return model_name
@@ -211,6 +273,26 @@ class OnnxAsrBackend:
f"Либо укажите полное имя модели onnx-asr."
)
def _warn_if_language_unsupported(self, language: str | None) -> None:
if (
language is None
or self._model_spec is None
or language in self._model_spec.supported_languages
):
return
supported = ", ".join(sorted(self._model_spec.supported_languages))
warnings.warn(
f"Язык '{language}' не поддерживается моделью '{self._model_name}' "
f"(поддерживаются: {supported}). Результат может быть некорректным. "
"Для других языков возьмите Whisper: "
"--device openvino-cpu --model medium на x86, "
"--device cpu --model medium на любой платформе "
"или --device cuda --model medium при NVIDIA GPU.",
UserWarning,
stacklevel=2,
)
def _format_compute_types(quantizations: frozenset[str | None]) -> str:
values = [_compute_type_for_quantization(value) for value in quantizations]
@@ -228,6 +310,19 @@ def _preferred_compute_type(quantizations: frozenset[str | None]) -> str:
raise ValueError("Для ONNX-модели не указаны доступные квантизации")
def _whisper_fallback_model(model_name: str) -> str:
"""Модель для подсказки про faster-whisper: turbo там недоступен."""
if model_name in _OPENVINO_ONLY_WHISPER_MODELS:
return "medium"
return model_name
def _model_language(spec: OnnxModelSpec | None) -> str | None:
if spec is not None and len(spec.supported_languages) == 1:
return next(iter(spec.supported_languages))
return None
def _notify(on_status: Callable[[str], None] | None, message: str) -> None:
if on_status is not None:
on_status(message)
+4 -2
View File
@@ -12,7 +12,7 @@ from typing import Any
from huggingface_hub import snapshot_download
from huggingface_hub.errors import LocalEntryNotFoundError
from local_transcriber.types import Segment, TranscribeResult
from local_transcriber.types import UNKNOWN_LANGUAGE, Segment, TranscribeResult
# (model_alias, compute_type) → HF repo
MODEL_REPOS: dict[tuple[str, str], str] = {
@@ -23,6 +23,8 @@ MODEL_REPOS: dict[tuple[str, str], str] = {
("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",
("large-v3-turbo", "int8"): "OpenVINO/whisper-large-v3-turbo-int8-ov",
("large-v3-turbo", "fp16"): "OpenVINO/whisper-large-v3-turbo-fp16-ov",
}
# Fallback: если точная пара не найдена, пробуем альтернативный compute_type
@@ -157,7 +159,7 @@ class OpenVINOBackend:
f"Транскрибирую (OpenVINO)... [{len(segments)} сегм.]",
)
detected_language = language or "auto"
detected_language = language or UNKNOWN_LANGUAGE
language_probability = 1.0 if language else 0.0
return TranscribeResult(
+29 -5
View File
@@ -12,6 +12,10 @@ from .config import apply_device_defaults, load_config, resolve_defaults
from .context_menu import install_menu as install_context_menu
from .context_menu import uninstall_menu as uninstall_context_menu
from .formatter import (
LANGUAGE_DETECTED,
LANGUAGE_FORCED,
LANGUAGE_FROM_MODEL,
LANGUAGE_UNKNOWN,
format_duration,
format_timestamp,
format_transcript,
@@ -30,6 +34,7 @@ from .transcriber import (
_transcribe_file,
load_model,
)
from .types import UNKNOWN_LANGUAGE
from .utils import (
build_output_path,
detect_device,
@@ -57,6 +62,19 @@ def _format_device_info(device_used: str) -> str:
return "CPU"
def _format_language_mode(
requested_language: str, result: TranscribeResult
) -> str:
"""Описывает источник языка, не выдавая профиль модели за детектор."""
if requested_language != "auto":
return LANGUAGE_FORCED
if result.language_probability > 0:
return LANGUAGE_DETECTED
if result.language not in {"", UNKNOWN_LANGUAGE}:
return LANGUAGE_FROM_MODEL
return LANGUAGE_UNKNOWN
def _format_repetition_blocks(
blocks: list[RepetitionBlock],
use_hours: bool,
@@ -114,7 +132,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 +148,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,
@@ -308,7 +333,7 @@ def _run_single(
)
device_info = _format_device_info(result.device_used)
language_mode = "detected" if defaults["language"] == "auto" else "forced"
language_mode = _format_language_mode(defaults["language"], result)
content = format_transcript(
result=result,
@@ -394,8 +419,6 @@ def _run_batch(
# Phase 3: Transcribe
processed = 0
failed = 0
language_mode = "detected" if defaults["language"] == "auto" else "forced"
batch_start = time.monotonic()
for i, file in enumerate(to_process, 1):
@@ -435,6 +458,7 @@ def _run_batch(
model_path = tfr.model_path
result = tfr.result
language_mode = _format_language_mode(defaults["language"], result)
if len(result.segments) == 0:
console.print(
+17 -1
View File
@@ -10,6 +10,19 @@ from .types import Segment, TranscribeResult
_PAUSE_THRESHOLD_S = 2.0 # пауза между сегментами для разбиения на абзацы
_MAX_PARAGRAPH_S = 60.0 # максимальная длительность абзаца
# Источник языка в шапке транскрипта
LANGUAGE_FORCED = "задан явно"
LANGUAGE_DETECTED = "определён автоматически"
LANGUAGE_FROM_MODEL = "из профиля модели"
LANGUAGE_UNKNOWN = "не определён"
LANGUAGE_MODES = (
LANGUAGE_FORCED,
LANGUAGE_DETECTED,
LANGUAGE_FROM_MODEL,
LANGUAGE_UNKNOWN,
)
@dataclass
class _Paragraph:
@@ -80,7 +93,7 @@ def format_transcript(
source_filename: str,
model_name: str,
device_info: str,
language_mode: str, # "detected" | "forced"
language_mode: str, # см. LANGUAGE_MODES
transcription_date: datetime | None = None, # None -> datetime.now()
) -> str:
"""Собирает markdown-транскрипт: шапка с метаданными + абзацы с таймкодами."""
@@ -92,6 +105,9 @@ def format_transcript(
lines.append("")
lines.append(f"- **Дата транскрипции**: {date.strftime('%Y-%m-%d %H:%M:%S')}")
lines.append(f"- **Модель**: {model_name}")
if language_mode == LANGUAGE_UNKNOWN:
lines.append(f"- **Язык**: {LANGUAGE_UNKNOWN}")
else:
lines.append(f"- **Язык**: {result.language} ({language_mode})")
lines.append(f"- **Длительность**: {format_duration(result.duration)}")
if tail_gap(result) > TAIL_GAP_WARN_S:
+5 -2
View File
@@ -4,6 +4,9 @@ from collections.abc import Callable
from dataclasses import dataclass
from typing import Any
# Единый признак «язык неизвестен» для всех бэкендов
UNKNOWN_LANGUAGE = "unknown"
@dataclass
class Segment:
@@ -15,10 +18,10 @@ class Segment:
@dataclass
class TranscribeResult:
segments: list[Segment]
language: str
language: str # код языка или UNKNOWN_LANGUAGE, если он неизвестен
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
+5 -7
View File
@@ -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"
+65 -6
View File
@@ -11,12 +11,21 @@ from local_transcriber.backends.openvino import (
OpenVINOBackend,
_validate_model_dir,
)
from local_transcriber.types import Segment
from local_transcriber.types import UNKNOWN_LANGUAGE, Segment
# === _resolve_repo ===
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",
}
def test_resolve_repo_exact_match():
backend = OpenVINOBackend(compute_type_explicit=True)
assert backend._resolve_repo("medium", "int8") == ("OpenVINO/whisper-medium-int8-ov", "int8")
@@ -47,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():
@@ -60,6 +78,26 @@ def test_resolve_repo_explicit_large_v3_int8_respected():
assert backend._resolve_repo("large-v3", "int8") == ("OpenVINO/whisper-large-v3-int8-ov", "int8")
@pytest.mark.parametrize("compute_type", ["int8", "fp16"])
def test_resolve_repo_large_v3_turbo_quantization(compute_type):
backend = OpenVINOBackend(compute_type_explicit=True)
assert backend._resolve_repo("large-v3-turbo", compute_type) == (
f"OpenVINO/whisper-large-v3-turbo-{compute_type}-ov",
compute_type,
)
def test_resolve_repo_large_v3_turbo_unsupported_quantization_raises():
backend = OpenVINOBackend(compute_type_explicit=True)
with pytest.raises(
ValueError,
match="Доступные варианты: fp16, int8",
):
backend._resolve_repo("large-v3-turbo", "float32")
# === ensure_model_available ===
@@ -101,6 +139,27 @@ def test_ensure_model_available_downloads(mock_download, tmp_path):
assert any("Скачиваю" in s for s in statuses)
@patch("local_transcriber.backends.openvino.snapshot_download")
def test_large_v3_turbo_model_is_resolved_and_created(mock_download, tmp_path):
model_dir = tmp_path / "large-v3-turbo"
model_dir.mkdir()
(model_dir / "openvino_encoder_model.xml").write_text("<xml/>")
(model_dir / "openvino_decoder_model.xml").write_text("<xml/>")
mock_download.return_value = str(model_dir)
mock_ov = MagicMock()
backend = OpenVINOBackend(ov_device="openvino-cpu", compute_type_explicit=True)
model_path = backend.ensure_model_available("large-v3-turbo", "int8")
with patch.dict("sys.modules", {"openvino_genai": mock_ov}):
backend.create_model(model_path, "openvino-cpu", "int8")
mock_download.assert_called_once_with(
"OpenVINO/whisper-large-v3-turbo-int8-ov",
local_files_only=True,
)
mock_ov.WhisperPipeline.assert_called_once_with(str(model_dir), "CPU")
# === create_model ===
@@ -242,7 +301,7 @@ def test_transcribe_no_language_auto():
call_kwargs = mock_model.generate.call_args.kwargs
assert "language" not in call_kwargs
assert result.language == "auto"
assert result.language == UNKNOWN_LANGUAGE
assert result.language_probability == 0.0
+77 -8
View File
@@ -5,8 +5,15 @@ 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.formatter import (
LANGUAGE_DETECTED,
LANGUAGE_FORCED,
LANGUAGE_FROM_MODEL,
LANGUAGE_UNKNOWN,
)
from local_transcriber.transcriber import Segment, TranscribeFileResult, TranscribeResult
from local_transcriber.types import UNKNOWN_LANGUAGE
runner = CliRunner()
@@ -42,6 +49,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, LANGUAGE_FORCED),
("auto", "ru", 0.95, LANGUAGE_DETECTED),
("auto", "ru", 0.0, LANGUAGE_FROM_MODEL),
("auto", UNKNOWN_LANGUAGE, 0.0, LANGUAGE_UNKNOWN),
],
)
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:
@@ -73,27 +98,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 +690,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")
+8 -2
View File
@@ -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():
+37 -12
View File
@@ -2,12 +2,16 @@ from datetime import datetime
from pathlib import Path
from local_transcriber.formatter import (
LANGUAGE_DETECTED,
LANGUAGE_FORCED,
LANGUAGE_UNKNOWN,
_group_segments,
format_timestamp,
format_transcript,
write_transcript,
)
from local_transcriber.transcriber import Segment, TranscribeResult
from local_transcriber.types import UNKNOWN_LANGUAGE
def test_format_timestamp_minutes():
@@ -40,14 +44,14 @@ def test_format_transcript_basic():
source_filename="meeting.mp4",
model_name="large-v3",
device_info="CUDA (NVIDIA GeForce RTX 3060)",
language_mode="detected",
language_mode=LANGUAGE_DETECTED,
transcription_date=datetime(2026, 3, 17, 14, 30, 5),
)
assert "# Транскрипт: meeting.mp4" in content
assert "**Дата транскрипции**: 2026-03-17 14:30:05" in content
assert "**Модель**: large-v3" in content
assert "**Язык**: ru (detected)" in content
assert "**Язык**: ru (определён автоматически)" in content
assert "**Длительность**: 02:00" in content
assert "**Устройство**: CUDA (NVIDIA GeForce RTX 3060)" in content
assert "---" in content
@@ -55,6 +59,27 @@ def test_format_transcript_basic():
assert "[00:00.00 - 00:09.15] Добрый день, коллеги. Первый вопрос." in content
def test_format_transcript_unknown_language_without_placeholder():
"""Неизвестный язык печатается одной строкой, без служебного значения."""
result = TranscribeResult(
segments=[Segment(start=0.0, end=4.0, text=" Добрый день.")],
language=UNKNOWN_LANGUAGE,
language_probability=0.0,
duration=120.0,
device_used="openvino-cpu",
)
content = format_transcript(
result,
source_filename="meeting.mp4",
model_name="medium",
device_info="OpenVINO (CPU)",
language_mode=LANGUAGE_UNKNOWN,
)
assert "**Язык**: не определён" in content
assert UNKNOWN_LANGUAGE not in content
def test_format_transcript_segment_no_leading_space():
"""Сегменты без ведущего пробела должны форматироваться корректно."""
result = TranscribeResult(
@@ -69,7 +94,7 @@ def test_format_transcript_segment_no_leading_space():
source_filename="f.mp3",
model_name="tiny",
device_info="CPU",
language_mode="detected",
language_mode=LANGUAGE_DETECTED,
transcription_date=datetime(2026, 1, 1, 0, 0, 0),
)
assert "[00:00.00 - 00:02.00] Hello" in content
@@ -88,7 +113,7 @@ def test_format_transcript_empty():
source_filename="silence.wav",
model_name="tiny",
device_info="CPU",
language_mode="detected",
language_mode=LANGUAGE_DETECTED,
transcription_date=datetime(2026, 1, 1, 0, 0, 0),
)
@@ -113,12 +138,12 @@ def test_format_transcript_long():
source_filename="long.mp4",
model_name="large-v3",
device_info="CUDA",
language_mode="forced",
language_mode=LANGUAGE_FORCED,
transcription_date=datetime(2026, 3, 17, 10, 0, 0),
)
assert "**Длительность**: 01:03:20" in content
assert "**Язык**: en (forced)" in content
assert "**Язык**: en (задан явно)" in content
# Timestamps should use hours format
assert "[00:00:00.00 - 00:00:10.50] Начало." in content
assert "[01:01:40.00 - 01:01:50.25] Конец." in content
@@ -188,7 +213,7 @@ def test_format_transcript_tail_gap_warning():
source_filename="tail.mp3",
model_name="medium",
device_info="CPU",
language_mode="forced",
language_mode=LANGUAGE_FORCED,
transcription_date=datetime(2026, 1, 1, 0, 0, 0),
)
@@ -210,7 +235,7 @@ def test_format_transcript_no_tail_gap_warning_for_small_gap():
source_filename="ok.mp3",
model_name="medium",
device_info="CPU",
language_mode="forced",
language_mode=LANGUAGE_FORCED,
transcription_date=datetime(2026, 1, 1, 0, 0, 0),
)
@@ -231,7 +256,7 @@ def test_format_transcript_no_tail_gap_warning_for_exact_threshold():
source_filename="ok.mp3",
model_name="medium",
device_info="CPU",
language_mode="forced",
language_mode=LANGUAGE_FORCED,
transcription_date=datetime(2026, 1, 1, 0, 0, 0),
)
@@ -257,7 +282,7 @@ def test_format_transcript_repetition_warning():
source_filename="repeat.mp3",
model_name="medium",
device_info="CPU",
language_mode="forced",
language_mode=LANGUAGE_FORCED,
transcription_date=datetime(2026, 1, 1, 0, 0, 0),
)
@@ -284,7 +309,7 @@ def test_format_transcript_repetition_warning_uses_hours():
source_filename="long-repeat.mp3",
model_name="medium",
device_info="CPU",
language_mode="forced",
language_mode=LANGUAGE_FORCED,
transcription_date=datetime(2026, 1, 1, 0, 0, 0),
)
@@ -305,7 +330,7 @@ def test_format_transcript_without_anomalies_has_no_warning_lines():
source_filename="ok.mp3",
model_name="medium",
device_info="CPU",
language_mode="forced",
language_mode=LANGUAGE_FORCED,
transcription_date=datetime(2026, 1, 1, 0, 0, 0),
)
+96 -2
View File
@@ -1,9 +1,11 @@
"""Tests for onnx-asr backend."""
import warnings
import pytest
from local_transcriber.backends.onnx_asr import OnnxAsrBackend
from local_transcriber.types import Segment, TranscribeResult
from local_transcriber.types import UNKNOWN_LANGUAGE, Segment, TranscribeResult
class FakeVadSegment:
@@ -224,6 +226,69 @@ 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 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"
@@ -326,7 +391,7 @@ class TestTranscribe:
result = backend.transcribe(FakeModel(), wav_file, language=None)
assert len(result.segments) == 0
assert result.language == "unknown"
assert result.language == UNKNOWN_LANGUAGE
assert result.duration == 1.0
def test_transcribe_skips_zero_length_vad_segments(self, monkeypatch, tmp_path):
@@ -386,3 +451,32 @@ 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_whisper_error_offers_platform_independent_backend(self):
"""На macOS и ARM нет ни OpenVINO, ни CUDA — нужен путь через cpu."""
backend = OnnxAsrBackend()
with pytest.raises(ValueError, match=r"--device cpu --model medium"):
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 cpu --model medium" in message
assert "--device cuda --model large-v3-turbo" not in message
assert "--device cpu --model large-v3-turbo" not in message
+9 -32
View File
@@ -61,46 +61,23 @@ 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."""
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"
def test_detect_device_auto_openvino_cpu():
"""auto + нет nvidia-smi + есть OpenVINO, нет GPU → 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"
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_cpu_fallback():
"""Ни nvidia-smi, ни 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=False),
):
assert detect_device("auto") == "cpu"
def test_detect_device_auto_onnx_without_cuda():
"""Без CUDA auto выбирает ONNX CPU."""
with patch("local_transcriber.utils.shutil.which", return_value=None):
assert detect_device("auto") == "onnx"
def test_detect_device_openvino_resolves_to_gpu():
Generated
+18 -18
View File
@@ -279,7 +279,7 @@ requires-dist = [
{ name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'", specifier = ">=12.4,<13" },
{ name = "onnx-asr", extras = ["cpu", "hub"], specifier = ">=0.12,<0.13" },
{ name = "onnxruntime", specifier = ">=1.28,<2" },
{ name = "openvino-genai", marker = "(platform_machine == 'AMD64' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and sys_platform != 'darwin')", specifier = ">=2026.0.0.0,<2026.1" },
{ name = "openvino-genai", marker = "(platform_machine == 'AMD64' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and sys_platform != 'darwin')", specifier = ">=2026.3.0.0,<2026.4" },
{ name = "rich", specifier = ">=14.3.3,<15" },
{ name = "socksio", specifier = ">=1.0.0,<2" },
{ name = "typer", specifier = ">=0.24.1,<1" },
@@ -416,35 +416,35 @@ wheels = [
[[package]]
name = "openvino"
version = "2026.0.0"
version = "2026.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
{ name = "openvino-telemetry" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/71/3f/95b15760d7ae4b7dfefdbadeccae9604985d4335b221350e1bb04701a158/openvino-2026.0.0-20965-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:452902e4b8fba526e8740622cd5638c457d3ae44da7b39e6d4ef7919be0e95d4", size = 53444542, upload-time = "2026-02-23T16:10:46.343Z" },
{ url = "https://files.pythonhosted.org/packages/d4/26/82d326f3769c9e5c17d34ef10dd0aff4a94a3b550e1d2efe977e5754b84e/openvino-2026.0.0-20965-cp313-cp313-win_amd64.whl", hash = "sha256:14069e5af2ac8a77f6ea55d7020d34cc7d3538d49ebda91a18c00d4da830ab58", size = 69160606, upload-time = "2026-02-23T16:10:52.9Z" },
{ url = "https://files.pythonhosted.org/packages/fe/8e/52df01e8687f4711259729433226219c6839a0495988ddeb7e27af59aba8/openvino-2026.0.0-20965-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:79ce4b5d7f3a7f84de878752d97620b84e0c4a8ee550fb3bca9fc36fe5669450", size = 53449059, upload-time = "2026-02-23T16:10:59.54Z" },
{ url = "https://files.pythonhosted.org/packages/f7/a8/bc8e75d3f75ee2529a12be9522f52a8d0e5614f24c00f95a20b69f587d3c/openvino-2026.0.0-20965-cp314-cp314-win_amd64.whl", hash = "sha256:6d33440d290e67836825418134ecf9e17f150d50d3d9c07cf481997f5b1f0e6d", size = 69165824, upload-time = "2026-02-23T16:11:03.127Z" },
{ url = "https://files.pythonhosted.org/packages/a8/06/1a2caa07bfcb4e057048b8d5515a7aff35a2aa53ab5379762c1685cbeacc/openvino-2026.0.0-20965-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:8cbe36e0dacd8676eb69c9a4b564fa430d784f6e2b0e18e7ebc363599256c184", size = 53502042, upload-time = "2026-02-23T16:11:09.744Z" },
{ url = "https://files.pythonhosted.org/packages/2c/dd/6a05a399d90ad4e8b15e0d5a4dd7de90c10484cb1585bd65e7e69e779762/openvino-2026.0.0-20965-cp314-cp314t-win_amd64.whl", hash = "sha256:a9e5524322c42f6a1283148fb70085aba8640a7d3067ede896085abbff5e33fe", size = 69325734, upload-time = "2026-02-23T16:11:13.283Z" },
{ url = "https://files.pythonhosted.org/packages/8c/d3/69e7083339f32621359f0bce2be3a7454db3c9a71fa7492f0a0941c00037/openvino-2026.3.0-22451-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:b7d09f20f009b9167a863f615a2b27400ce025bda8086cc72a2eb6ac3bf1d2af", size = 57480757, upload-time = "2026-08-04T09:06:44.336Z" },
{ url = "https://files.pythonhosted.org/packages/48/f8/f71508784562a3d427f52f59d74a6364d559e6e82bb112cab5d6a6a677bb/openvino-2026.3.0-22451-cp313-cp313-win_amd64.whl", hash = "sha256:1c41f2d1072d600c260741b350aaf4a09d53f821a9a8fc8989bdc79a46b50a2c", size = 75797507, upload-time = "2026-08-04T09:06:51.858Z" },
{ url = "https://files.pythonhosted.org/packages/d7/97/fdace942843da232ea06a5a67cc3a70d292873657dc933408fdb9bb796a8/openvino-2026.3.0-22451-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:81a64aebd1a80da93bc9413b3ba9c93846c7cac57161cd4d7b287b354d77ad19", size = 57482441, upload-time = "2026-08-04T09:06:59.277Z" },
{ url = "https://files.pythonhosted.org/packages/60/cb/ed637225c7373d9a4267e7fa7252c1f3767fbc9853a906d78068a279b73a/openvino-2026.3.0-22451-cp314-cp314-win_amd64.whl", hash = "sha256:07a8c1cb32e3f7942aab4a0c48404b591e63c89813906c7bc5b001f94bed447c", size = 75798958, upload-time = "2026-08-04T09:07:07.28Z" },
{ url = "https://files.pythonhosted.org/packages/f0/3c/40c0bbd5c57fc0b5c0c41f9e6f0ec722f231ff25c37d20240d2baf446409/openvino-2026.3.0-22451-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:65f34103c28107e830e1fb70cb9c62afdb31cfde4f8b6056c942761796b1d4fe", size = 57526976, upload-time = "2026-08-04T09:07:15.131Z" },
{ url = "https://files.pythonhosted.org/packages/dc/ca/927354fa957dd0e8c8f53401dcd1239c4cd50d95a26ff5da3bc4b502f4dc/openvino-2026.3.0-22451-cp314-cp314t-win_amd64.whl", hash = "sha256:17581c5acbdaf9bf503cd21d5ad852df7e547073ad7a529661b19f40ab3c8db6", size = 75963071, upload-time = "2026-08-04T09:07:24.796Z" },
]
[[package]]
name = "openvino-genai"
version = "2026.0.0.0"
version = "2026.3.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "openvino-tokenizers" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/d3/98/fe5716b9dc2f29286b68b98a4c67bc33003e3b02cf4d92236345a2d7280e/openvino_genai-2026.0.0.0-2050-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:08f73852a455d63453e7f74f22b0aebd0c3579f8cb89133e66e438c09df26b71", size = 4930444, upload-time = "2026-02-23T16:17:33.953Z" },
{ url = "https://files.pythonhosted.org/packages/02/80/bc87545dfbbd7f4a3bd1667a4c9b5f1d8e1b476fec34225692bca6a86590/openvino_genai-2026.0.0.0-2050-cp313-cp313-win_amd64.whl", hash = "sha256:ada030b34a9e2fe0c6c406c2af2ce7b6e8cb16654329a469055fdac8a3eba19d", size = 2900293, upload-time = "2026-02-23T16:17:37.673Z" },
{ url = "https://files.pythonhosted.org/packages/af/09/18e678f2d0ac4ee993244d3cf0e6a80ec7013add2972153ad355d889d8cc/openvino_genai-2026.0.0.0-2050-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:77b73d1947d7f62a672857ef35d187074c6918de1db1d3f981452c2d67511c13", size = 4931196, upload-time = "2026-02-23T16:17:40.423Z" },
{ url = "https://files.pythonhosted.org/packages/a4/be/3f02a70a16147b08b933908ef1c8af930ecd5c65ba78e14f988112d3d691/openvino_genai-2026.0.0.0-2050-cp314-cp314-win_amd64.whl", hash = "sha256:c59aeab5993c78194dc552cc9249bab509f7e44b1fb2f0d5a90c5e61869f06b8", size = 2900527, upload-time = "2026-02-23T16:17:43.244Z" },
{ url = "https://files.pythonhosted.org/packages/6d/e8/7da6a1b86e3178e825560442ea4460ba4c92bb417fb8edda9e2210febec6/openvino_genai-2026.0.0.0-2050-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:d807c5cabdea20bab2199ffa736831f57baaf0571e80d19cf4b65004f6b00747", size = 4943971, upload-time = "2026-02-23T16:17:46.021Z" },
{ url = "https://files.pythonhosted.org/packages/e3/19/221256ce3b0276bb3ba3ce7e8fc57b2968a662c2acde7427c342d917867f/openvino_genai-2026.0.0.0-2050-cp314-cp314t-win_amd64.whl", hash = "sha256:bb537f5a65203eee12326e6bb9578e834d96d94ab3fa48c806990ef04a455935", size = 2900592, upload-time = "2026-02-23T16:17:47.452Z" },
{ url = "https://files.pythonhosted.org/packages/0f/94/6968a1b95f6b9931741ef1a302c2e7ab2d3193f76224fead0ed736506db3/openvino_genai-2026.3.0.0-2495-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:cd9f7bc035d2a23f01617e64f8963477335f15e048c6c9fcf1cce6c925fedf3e", size = 6211947, upload-time = "2026-08-04T09:34:59.252Z" },
{ url = "https://files.pythonhosted.org/packages/50/74/cec114195adf6237c000fabd6c5da61f04edf3b1d719db7601e452977e4f/openvino_genai-2026.3.0.0-2495-cp313-cp313-win_amd64.whl", hash = "sha256:08411eb51bd0bddc717e2d97c541cc5cc946bde70e74da6dd7428149e5e0cdbb", size = 3669271, upload-time = "2026-08-04T09:35:02.699Z" },
{ url = "https://files.pythonhosted.org/packages/ed/02/ed9f6773a40cc8b0fc166979abf6b56aed3a9f5840f9f0bf76fbf82cc349/openvino_genai-2026.3.0.0-2495-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:2df610ec970b66b95cc4987d888c543e979bda515aed0c8ffda99954d10b4133", size = 6213490, upload-time = "2026-08-04T09:35:05.799Z" },
{ url = "https://files.pythonhosted.org/packages/0d/9d/9e877a92ff22e9cec0cb930bcccd58013050726af91a9c1b8ff15264ed27/openvino_genai-2026.3.0.0-2495-cp314-cp314-win_amd64.whl", hash = "sha256:9331df790dcf57d796b6486ce9d9219ad4172c851cd657fafb9c5c0c82177c43", size = 3670597, upload-time = "2026-08-04T09:35:09.064Z" },
{ url = "https://files.pythonhosted.org/packages/72/98/a4747cc685a5d2cc252ccb337c022e455f5761041e0d19980d0884ecadd9/openvino_genai-2026.3.0.0-2495-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:58a9e7493391ce8478440fbc14610968255a06a2717068f476b1ba519fe488a1", size = 6222203, upload-time = "2026-08-04T09:35:11.83Z" },
{ url = "https://files.pythonhosted.org/packages/33/27/af5aee755635dc1cd19bb7406410a099364205c3a35e49766e54ca174c15/openvino_genai-2026.3.0.0-2495-cp314-cp314t-win_amd64.whl", hash = "sha256:0d3770d721f336549747b3ce054ad6bc726609e1fc3f5ce90c4954c63f23e4b9", size = 3670880, upload-time = "2026-08-04T09:35:14.895Z" },
]
[[package]]
@@ -458,14 +458,14 @@ wheels = [
[[package]]
name = "openvino-tokenizers"
version = "2026.0.0.0"
version = "2026.3.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "openvino" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/45/22/2b976c129d43b214034a16a999d54eb659d5b2f15c12b2be52ab44e3235d/openvino_tokenizers-2026.0.0.0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:84ee496d9fe4ff7866ed9a1370cb71bf3af9526601a701c59d269854a2b0dfc7", size = 13693316, upload-time = "2026-02-23T16:12:13.41Z" },
{ url = "https://files.pythonhosted.org/packages/24/86/4e7428a2fb350311a7f616c897ee1427d70cd814a2d64b2af9efb81fb881/openvino_tokenizers-2026.0.0.0-py3-none-win_amd64.whl", hash = "sha256:90f77c203c40623733e867754b952e8f0b86d9c86782abdbe9cf4bf2c82ce693", size = 13988492, upload-time = "2026-02-23T16:12:19.586Z" },
{ url = "https://files.pythonhosted.org/packages/0b/68/c1ba2177f4ce1f455aae858548aece8b6491b862a6458b03c5d5dbf356ef/openvino_tokenizers-2026.3.0.0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:9d35bb52d353a30d1194cd21c43d3949d0fac853b5bd3721994f70acfea051e4", size = 1829895, upload-time = "2026-08-04T09:28:12.083Z" },
{ url = "https://files.pythonhosted.org/packages/c9/65/707874d377a245fbc5fbeaeac77e4aaf878f4e0938ab9d1203f8b01e5aab/openvino_tokenizers-2026.3.0.0-py3-none-win_amd64.whl", hash = "sha256:7c6fb2f1e9b6c3c4b2bdb992e69ef97869787d8486b193d155d24aaa9cd5fed0", size = 1541664, upload-time = "2026-08-04T09:28:15.307Z" },
]
[[package]]