Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b5703a0e3 | ||
|
|
2c1d798eeb | ||
|
|
cb6c1d7728 | ||
|
|
749ede090c | ||
|
|
59b74f166c | ||
|
|
857267763c | ||
|
|
8ae74d2748 | ||
|
|
248f260005 | ||
|
|
413cb3e6d8 | ||
|
|
9d41b893c3 |
@@ -147,6 +147,7 @@ transcribe *.mp4 --force
|
|||||||
| `--output` | `-o` | `<файл>-transcript.md` | Путь к выходному файлу |
|
| `--output` | `-o` | `<файл>-transcript.md` | Путь к выходному файлу |
|
||||||
| `--device` | `-d` | `auto` | Устройство (auto, cpu, cuda, openvino, openvino-gpu, openvino-cpu) |
|
| `--device` | `-d` | `auto` | Устройство (auto, cpu, cuda, openvino, openvino-gpu, openvino-cpu) |
|
||||||
| `--compute-type` | — | float16 (CUDA) / int8 (OpenVINO GPU/CPU) / float32 (CPU) | Тип вычислений |
|
| `--compute-type` | — | float16 (CUDA) / int8 (OpenVINO GPU/CPU) / float32 (CPU) | Тип вычислений |
|
||||||
|
| `--threads` | `-t` | 0 (авто) | Потоки CPU (рекомендуется = число физ. ядер) |
|
||||||
| `--force` | `-f` | — | Перезаписать существующие транскрипты |
|
| `--force` | `-f` | — | Перезаписать существующие транскрипты |
|
||||||
| `--verbose` | `-v` | — | Подробный вывод |
|
| `--verbose` | `-v` | — | Подробный вывод |
|
||||||
|
|
||||||
@@ -243,6 +244,7 @@ language = "en"
|
|||||||
|-----|--------|----------|----------|--------------------|
|
|-----|--------|----------|----------|--------------------|
|
||||||
| `float16` | CUDA | ~4.5-5 GB | Отлично | **По умолчанию для CUDA** |
|
| `float16` | CUDA | ~4.5-5 GB | Отлично | **По умолчанию для CUDA** |
|
||||||
| `int8_float16` | CUDA | ~4.7 GB | Отлично | GPU от 6 GB, альтернатива float16 |
|
| `int8_float16` | CUDA | ~4.7 GB | Отлично | GPU от 6 GB, альтернатива float16 |
|
||||||
|
| `int8_float32` | CPU | Среднее | Отлично | **Рекомендуется для CPU** — 1.5x быстрее float32 при том же качестве |
|
||||||
| `int8` | CUDA / OpenVINO | Низкое | Хорошо, но бывают галлюцинации | **По умолчанию для OpenVINO** |
|
| `int8` | CUDA / OpenVINO | Низкое | Хорошо, но бывают галлюцинации | **По умолчанию для OpenVINO** |
|
||||||
| `fp16` | OpenVINO | Низкое | Отлично | OpenVINO large-v3 (выбирается автоматически) |
|
| `fp16` | OpenVINO | Низкое | Отлично | OpenVINO large-v3 (выбирается автоматически) |
|
||||||
| `float32` | CPU | Среднее | Отлично | **По умолчанию для CPU** |
|
| `float32` | CPU | Среднее | Отлично | **По умолчанию для CPU** |
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
# ADR-005: NVIDIA Parakeet TDT — оценено, отклонено для дефолтного use case
|
||||||
|
|
||||||
|
**Статус**: Отклонено (оставлено в экспериментальной ветке)
|
||||||
|
**Дата**: 2026-04-19
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
|
||||||
|
ADR-003 и ADR-004 зафиксировали стратегию: CTranslate2 на CPU (качество) +
|
||||||
|
OpenVINO (скорость на x86). Оба бэкенда — Whisper. Возникла гипотеза, что
|
||||||
|
**NVIDIA Parakeet TDT 0.6B v3** может выйти за пределы этого trade-off: модель
|
||||||
|
заявлена multilingual (25 языков включая русский), имеет ONNX-порт
|
||||||
|
[istupakov/parakeet-tdt-0.6b-v3-onnx](https://huggingface.co/istupakov/parakeet-tdt-0.6b-v3-onnx),
|
||||||
|
работает через `onnx-asr` + Silero VAD. Есть позитивные отзывы (напр.
|
||||||
|
приложение Handy использует Parakeet для dictation в реальном времени).
|
||||||
|
|
||||||
|
Основное применение проекта — транскрипция русскоязычных ИТ-встреч
|
||||||
|
(ментор ↔ менти, ~15–60 мин, ноутбучный микрофон). Если Parakeet обойдёт
|
||||||
|
Whisper medium int8 по скорости при сравнимом качестве, или по качеству при
|
||||||
|
сравнимой скорости — это основание поменять дефолт.
|
||||||
|
|
||||||
|
## Эксперимент
|
||||||
|
|
||||||
|
Реализован полный бэкенд в ветке `feature/parakeet-backend` (ONNX Runtime CPU EP,
|
||||||
|
Silero VAD, cache-first model loading). Прогнан автоматический бенчмарк
|
||||||
|
на двух 15-минутных отрывках реальных установочных встреч
|
||||||
|
(`scripts/quality_bench.py` в ветке), CPU Intel i7-11800H.
|
||||||
|
|
||||||
|
Качество оценивал агент-судья по шкале 1–5 по критериям completeness /
|
||||||
|
term accuracy / fluency / summary utility — на пригодность транскрипта
|
||||||
|
для построения конспекта без возврата к аудио.
|
||||||
|
|
||||||
|
### Скорость и ресурсы
|
||||||
|
|
||||||
|
| Отрывок | Бэкенд | Wall | RTFx | Peak RSS |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| artur-intro (15 мин) | **parakeet-int8** | 129.5s | **6.92x** | 2.21 GB |
|
||||||
|
| artur-intro (15 мин) | openvino-medium-int8 | 354.2s | 2.54x | 3.50 GB |
|
||||||
|
| mar15-mid (15 мин) | **parakeet-int8** | 159.6s | **5.64x** | 2.65 GB |
|
||||||
|
| mar15-mid (15 мин) | openvino-medium-int8 | 428.4s | 2.10x | 3.52 GB |
|
||||||
|
|
||||||
|
**Parakeet в 2.7x быстрее и использует на ~35% меньше RAM.**
|
||||||
|
|
||||||
|
### Качество (Summary utility, 1–5)
|
||||||
|
|
||||||
|
| Отрывок | Parakeet int8 | Whisper medium int8 |
|
||||||
|
|---|---|---|
|
||||||
|
| artur-intro | **2** | **4** |
|
||||||
|
| mar15-mid | **3** | **4** |
|
||||||
|
|
||||||
|
Систематические проблемы Parakeet на русской речи с низкой громкостью:
|
||||||
|
|
||||||
|
1. **Редукция коротких реплик менти до `Mm-hmm`/`Yeah`** — теряется
|
||||||
|
значительная часть диалога (низкий уровень микрофона менти).
|
||||||
|
2. **Вставки кусков польского/испанского/немецкого** посреди русской речи
|
||||||
|
(`Miejsce w sześć zajmie`, `Que salviosa`, `Genial positions`).
|
||||||
|
3. **Сильное искажение ИТ-терминов и бытовых выражений** —
|
||||||
|
`Pretty subs` вместо «пройти собес», `Состем основ` вместо «система контроля
|
||||||
|
версий», `І на глуп Джой` вместо `inner loop join`.
|
||||||
|
|
||||||
|
Whisper medium int8 даёт связный русский текст с нормальной пунктуацией;
|
||||||
|
искажения присутствуют (`капка` вместо Kafka, одна галлюцинация
|
||||||
|
«Добро пожаловать в наш канал!» на паузе), но не критичны для конспекта.
|
||||||
|
|
||||||
|
### Попытка тюнинга — Silero VAD threshold
|
||||||
|
|
||||||
|
Гипотеза: дефолтный `threshold=0.5` обрезает тихие реплики менти. Проверено
|
||||||
|
на 0.5/0.3/0.2 — снижение порога **уменьшает** объём транскрипта
|
||||||
|
(Parakeet хуже декодирует длинные склеенные сегменты с разреженной речью)
|
||||||
|
и **не снижает** частоту `Mm-hmm`-редукций. Проблема inherent для модели
|
||||||
|
на тихом русском диалоге, не артефакт VAD.
|
||||||
|
|
||||||
|
## Решение
|
||||||
|
|
||||||
|
**Parakeet TDT 0.6B v3 не принимается как дефолтный или опциональный бэкенд
|
||||||
|
для целевого use case.** Whisper medium int8 остаётся предпочтительным —
|
||||||
|
несмотря на 2.7x проигрыш по скорости, он даёт транскрипт, из которого можно
|
||||||
|
сделать конспект без возврата к аудио. Для русской речи с варьирующейся
|
||||||
|
громкостью микрофона Parakeet v3 недостаточно устойчив.
|
||||||
|
|
||||||
|
Код бэкенда **не мержится в master**. Экспериментальная ветка
|
||||||
|
`feature/parakeet-backend` сохраняется как reference для будущих экспериментов.
|
||||||
|
|
||||||
|
## Последствия
|
||||||
|
|
||||||
|
- Архитектура pluggable backends (ADR-003) подтвердила ценность: интегрировать
|
||||||
|
и оценить новый бэкенд оказалось недорого.
|
||||||
|
- Surface area master не растёт: нет `--device parakeet-cpu`, нет зависимости
|
||||||
|
`onnx-asr`, нет новой ветки в CLI.
|
||||||
|
- Если в будущем появится интерес — ветка `feature/parakeet-backend` содержит
|
||||||
|
готовый бэкенд (13 задач, ~19 тестов), скрипты бенчмарка и VAD-тюнинга.
|
||||||
|
|
||||||
|
## Когда стоит пересмотреть решение
|
||||||
|
|
||||||
|
1. **Англоязычный контент** — Parakeet обучался преимущественно на английском,
|
||||||
|
для native English встреч выигрыш в скорости может окупить качество.
|
||||||
|
2. **Live-captioning / dictation** (как в Handy) — другой use case, близкий
|
||||||
|
микрофон + короткие фразы, проблемы редукции неактуальны.
|
||||||
|
3. **Русские specialized-модели через ту же обвязку** `onnx-asr`:
|
||||||
|
`gigaam-v3-rnnt` (SberDevices), `nemo-fastconformer-ru-rnnt` (NVIDIA),
|
||||||
|
`t-one-ctc` (T-Bank). Это отдельная ветка экспериментов, не Parakeet.
|
||||||
|
4. **Слабые машины с ограниченной RAM** — Parakeet 2.2 GB vs Whisper 3.5 GB
|
||||||
|
на 15-мин аудио. Если важна RAM-экономия и английский контент.
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
# Бенчмарк: качество и скорость транскрипции
|
|
||||||
|
|
||||||
**Дата**: 2026-03-23
|
|
||||||
|
|
||||||
## Цель
|
|
||||||
|
|
||||||
Сравнить качество распознавания нашего приложения (local-transcriber) с облачным
|
|
||||||
сервисом Whisper.ai, подобрать оптимальную конфигурацию бэкенд/модель/compute_type.
|
|
||||||
|
|
||||||
## Тестовый стенд
|
|
||||||
|
|
||||||
- **CPU**: AMD Ryzen 7 8845H w/ Radeon 780M (3.80 GHz), 8 ядер
|
|
||||||
- **GPU**: нет NVIDIA (нет CUDA), есть встроенный Radeon 780M iGPU + Ryzen AI NPU (XDNA)
|
|
||||||
- **ОС**: Windows 11 Enterprise
|
|
||||||
- **Запись**: совещание 3 участников, 14:41, русский язык, микрофон ноутбука
|
|
||||||
|
|
||||||
## Конфигурации
|
|
||||||
|
|
||||||
| # | Модель | Бэкенд | compute_type | Устройство |
|
|
||||||
|---|--------|--------|--------------|------------|
|
|
||||||
| 1 | medium | OpenVINO | fp16 | CPU |
|
|
||||||
| 2 | large-v3 | OpenVINO | fp16 | CPU |
|
|
||||||
| 3 | medium | CTranslate2 (faster-whisper) | float32 | CPU |
|
|
||||||
| 4 | medium | CTranslate2 (faster-whisper) | int8_float32 | CPU |
|
|
||||||
| 5 | — | Whisper.ai (облако) | — | — |
|
|
||||||
|
|
||||||
## Результаты: скорость
|
|
||||||
|
|
||||||
| # | Конфигурация | Время, с | Коэфф. к реалтайму (14:41 = 881с) |
|
|
||||||
|---|---|---|---|
|
|
||||||
| 1 | medium, OpenVINO fp16 | **~240** | 0.27x |
|
|
||||||
| 2 | large-v3, OpenVINO fp16 | ~503 | 0.57x |
|
|
||||||
| 4 | medium, CT2 int8_float32 | ~599 | 0.68x |
|
|
||||||
| 3 | medium, CT2 float32 | ~881 | 1.0x |
|
|
||||||
|
|
||||||
## Результаты: качество
|
|
||||||
|
|
||||||
Для оценки выбраны маркерные фразы — технические термины, сложная разговорная речь,
|
|
||||||
имена собственные. Каждый маркер проверен вручную по контексту записи.
|
|
||||||
|
|
||||||
### Техтермины (английские в русской речи)
|
|
||||||
|
|
||||||
| Термин | #1 OV fp16 | #2 large OV | #3 CT2 f32 | #4 CT2 int8f32 | #5 Whisper.ai |
|
|
||||||
|--------|-----------|-------------|------------|----------------|---------------|
|
|
||||||
| DWH | ✅ DWH | ❌ ДВХ | ⚠️ ДВХ | ✅ DWH | ⚠️ dvh |
|
|
||||||
| Teradata | ✅ TeraData | ⚠️ тирадат | ⚠️ Тирадату | ✅ Teradata | ⚠️ тирадата |
|
|
||||||
| NiFi | ✅ NiFi | ❌ "на файл" | ⚠️ Найфай | ✅ NiFi | ⚠️ фай |
|
|
||||||
| FineBI | ✅ fineBI | ❌ "фанбой" | ✅ FNBI | ✅ finebi/FNBI | ⚠️ FineBI/NBI |
|
|
||||||
| Alation | ❌ "лэйшене" | ❌ "валаши" | ✅ Allation | ✅ Allation | ✅ Allation |
|
|
||||||
| ETL | ❌ ETA | ❌ "и ты или" | ✅ ETL | ✅ ETL | ✅ ETL |
|
|
||||||
| DBC Tables V | ✅ | ❌ "деби си" | ✅ | ✅ | ✅ |
|
|
||||||
| OpenLineage | ⚠️ OpenLinage | ⚠️ "open lineage и о" | ✅ OpenLinage.io | ✅ OpenLinage.io | ✅ Open Lineage.io |
|
|
||||||
|
|
||||||
### Русская разговорная речь
|
|
||||||
|
|
||||||
| Фраза (правильно) | #1 OV fp16 | #2 large OV | #3 CT2 f32 | #4 CT2 int8f32 | #5 Whisper.ai |
|
|
||||||
|--------------------|-----------|-------------|------------|----------------|---------------|
|
|
||||||
| "на другую мониторочку" | ❌ "другом не торчка" | ❌ "другому и" | ✅ "мониторчику" | ⚠️ "мою точку" | ✅ "мониторочку" |
|
|
||||||
| "верхнеуровневое" | ⚠️ "верхверх верхне..." | ✅ | ✅ | ✅ | ✅ |
|
|
||||||
| "датарентген" | ❌ "госпиталин ген" | ❌ "господа тренды" | ✅ "даторентген" | ✅ "даторентген" | ✅ "датарентген" |
|
|
||||||
| "девовские схемы" | ✅ | ❌ "девушки схемы" | ✅ | ✅ | ✅ |
|
|
||||||
| "с накиданными правами" | ✅ | ❌ "накиданных бровами" | ✅ | ✅ | ✅ |
|
|
||||||
| "Вьюхи" | ❌ "в юхе" | — | ✅ "Вьюхи" | ⚠️ "Yuhi" | ❌ "в юхе" |
|
|
||||||
|
|
||||||
### Структура и пунктуация
|
|
||||||
|
|
||||||
| Аспект | #1 OV fp16 | #2 large OV | #3 CT2 f32 | #4 CT2 int8f32 | #5 Whisper.ai |
|
|
||||||
|--------|-----------|-------------|------------|----------------|---------------|
|
|
||||||
| Пунктуация | слабая | слабая | хорошая | хорошая | средняя |
|
|
||||||
| Длина сегментов | ~1 мин | ~1 мин | ~1 мин | ~1 мин | 2–30 сек |
|
|
||||||
| Диаризация | нет | нет | нет | нет | есть (условная) |
|
|
||||||
|
|
||||||
## Ключевые выводы
|
|
||||||
|
|
||||||
### 1. OpenVINO fp16 ухудшает качество medium
|
|
||||||
|
|
||||||
OpenVINO даёт **3.7x ускорение**, но fp16-квантизация на CPU вносит заметные ошибки
|
|
||||||
в распознавание нечёткой русской речи ("госпиталин ген", "ETA"). При этом английские
|
|
||||||
техтермины (DWH, NiFi, FineBI) распознаются хорошо — видимо, они более «чёткие»
|
|
||||||
акустически.
|
|
||||||
|
|
||||||
### 2. large-v3 через OpenVINO — хуже medium
|
|
||||||
|
|
||||||
Удивительный результат: large-v3 с OpenVINO fp16 деградировал по всем параметрам.
|
|
||||||
FineBI → "фанбой", NiFi → "на файл", девовские → "девушки". Гипотеза: fp16 на CPU
|
|
||||||
бьёт сильнее по большим моделям из-за накопления ошибок округления.
|
|
||||||
|
|
||||||
### 3. CTranslate2 float32 — лучшее качество
|
|
||||||
|
|
||||||
Сравнимо с облачным Whisper.ai, а на некоторых маркерах лучше (Вьюхи, DWH).
|
|
||||||
Но **3.7x медленнее** OpenVINO — неприемлемо для длинных записей.
|
|
||||||
|
|
||||||
### 4. int8_float32 — оптимальный баланс
|
|
||||||
|
|
||||||
Mixed precision (int8 матрицы, float32 аккумулятор) даёт:
|
|
||||||
- **1.5x ускорение** vs float32 (599с vs 881с)
|
|
||||||
- Качество практически идентичное float32
|
|
||||||
- Техтермины DWH/Teradata/NiFi даже лучше, чем у float32
|
|
||||||
|
|
||||||
### 5. Whisper.ai — диаризация как преимущество
|
|
||||||
|
|
||||||
Единственное значимое преимущество облачного сервиса — диаризация спикеров.
|
|
||||||
По качеству распознавания наше приложение с CT2 int8_float32 на уровне или лучше.
|
|
||||||
|
|
||||||
## Рекомендации
|
|
||||||
|
|
||||||
### Краткосрочно
|
|
||||||
|
|
||||||
- **Дефолт для CPU без CUDA**: рассмотреть `int8_float32` вместо `float32` — 1.5x быстрее
|
|
||||||
при сопоставимом качестве.
|
|
||||||
- **OpenVINO fp16 оставить** как быстрый вариант — для случаев, когда скорость важнее
|
|
||||||
точности (превью, черновые транскрипты).
|
|
||||||
|
|
||||||
### Среднесрочно
|
|
||||||
|
|
||||||
- **DirectML бэкенд** для AMD Radeon iGPU — через onnxruntime-directml можно задействовать
|
|
||||||
Radeon 780M. Float16 на GPU (в отличие от CPU-квантизации) не должен терять качество.
|
|
||||||
Потенциально 3–5x ускорение при сохранении качества float32.
|
|
||||||
|
|
||||||
### Долгосрочно
|
|
||||||
|
|
||||||
- **Ryzen AI NPU (XDNA)** — AMD развивает поддержку, но tooling пока сырой.
|
|
||||||
- **Диаризация** — добавить идентификацию спикеров (pyannote-audio или аналоги).
|
|
||||||
+181
-20
@@ -37,7 +37,7 @@ OpenVINO ускоряет inference на x86 процессорах (Intel и AM
|
|||||||
| tiny | OpenVINO/whisper-tiny-int8-ov | — |
|
| tiny | OpenVINO/whisper-tiny-int8-ov | — |
|
||||||
| base | — | OpenVINO/whisper-base-fp16-ov |
|
| base | — | OpenVINO/whisper-base-fp16-ov |
|
||||||
| small | OpenVINO/whisper-small-int8-ov | — |
|
| small | OpenVINO/whisper-small-int8-ov | — |
|
||||||
| medium | OpenVINO/whisper-medium-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 | OpenVINO/whisper-large-v3-int8-ov | OpenVINO/whisper-large-v3-fp16-ov |
|
||||||
|
|
||||||
### Результаты тестирования OpenVINO
|
### Результаты тестирования OpenVINO
|
||||||
@@ -50,7 +50,7 @@ OpenVINO ускоряет inference на x86 процессорах (Intel и AM
|
|||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Intel Ultra 7 255H | **122с** | **411с** | — |
|
| Intel Ultra 7 255H | **122с** | **411с** | — |
|
||||||
| AMD Ryzen 7 8845H | 185с | 416с | 734с |
|
| AMD Ryzen 7 8845H | 185с | 416с | 734с |
|
||||||
| Intel i7 (WSL2) | 171-205с | — | 658с |
|
| Intel i7-11800H (WSL2) | 171-205с | — | 658с |
|
||||||
|
|
||||||
**Ускорение vs CPU float32:** **3-6x** в зависимости от CPU.
|
**Ускорение vs CPU float32:** **3-6x** в зависимости от CPU.
|
||||||
|
|
||||||
@@ -77,6 +77,113 @@ OpenVINO ускоряет inference на x86 процессорах (Intel и AM
|
|||||||
- **medium** — для повседневного использования и обработки ИИ. Ключевые термины верные, единичные ляпы не влияют на смысл конспекта. Оптимальный баланс скорости и качества.
|
- **medium** — для повседневного использования и обработки ИИ. Ключевые термины верные, единичные ляпы не влияют на смысл конспекта. Оптимальный баланс скорости и качества.
|
||||||
- **large-v3** — для важных записей, где нужна дословная точность. Лучшая пунктуация и связность. На OpenVINO (416с) быстрее, чем medium на чистом CPU (734с) — лучшее качество при выше скорости.
|
- **large-v3** — для важных записей, где нужна дословная точность. Лучшая пунктуация и связность. На OpenVINO (416с) быстрее, чем medium на чистом CPU (734с) — лучшее качество при выше скорости.
|
||||||
|
|
||||||
|
## CPU бэкенд (CTranslate2 / faster-whisper)
|
||||||
|
|
||||||
|
При `--device cpu` используется faster-whisper на основе CTranslate2. Этот бэкенд медленнее
|
||||||
|
OpenVINO, но обеспечивает **лучшее качество** благодаря развитому pipeline декодирования.
|
||||||
|
|
||||||
|
### Рекомендуемый compute_type: `int8_float32`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
transcribe meeting.mp4 --device cpu --compute-type int8_float32
|
||||||
|
```
|
||||||
|
|
||||||
|
`int8_float32` — int8 квантизация весов с float32 аккумулятором. Даёт **1.5x ускорение**
|
||||||
|
vs float32 при сопоставимом качестве (протестировано на русском языке с техтерминами).
|
||||||
|
|
||||||
|
| compute_type | Скорость* | Качество | Когда использовать |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `int8_float32` | **~460с** | Отлично | **Рекомендуется** — лучший баланс |
|
||||||
|
| `float32` | ~880с | Отлично (эталон) | Если важна максимальная точность |
|
||||||
|
| `int8` | быстрее | Хорошо, но бывают галлюцинации | Не рекомендуется для длинных записей |
|
||||||
|
|
||||||
|
\* Замеры на AMD Ryzen 7 8845H, medium, запись 14:41, 8 потоков.
|
||||||
|
|
||||||
|
### Оптимизация потоков
|
||||||
|
|
||||||
|
CTranslate2 по умолчанию использует 4 потока. На многоядерных CPU рекомендуется
|
||||||
|
задать число потоков равным числу **физических ядер** (не виртуальных):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
transcribe meeting.mp4 --device cpu --compute-type int8_float32 --threads 8
|
||||||
|
```
|
||||||
|
|
||||||
|
**Замеры (Intel i7-11800H, 8 ядер / 16 потоков, medium int8_float32, 16 мин файл):**
|
||||||
|
|
||||||
|
| --threads | Время | Ускорение |
|
||||||
|
|---|---|---|
|
||||||
|
| 0 (дефолт = 4) | 320с | baseline |
|
||||||
|
| 8 (физ. ядра) | **277с** | **+13%** |
|
||||||
|
|
||||||
|
SMT/Hyper-Threading не помогает — 16 потоков на 8-ядерном CPU медленнее, чем 8.
|
||||||
|
|
||||||
|
### Почему CPU бэкенд качественнее OpenVINO
|
||||||
|
|
||||||
|
При одной и той же модели (medium) CTranslate2 даёт заметно лучше распознавание,
|
||||||
|
чем OpenVINO. Разница **не в квантизации**, а в pipeline декодирования:
|
||||||
|
|
||||||
|
| Механизм | CTranslate2 (faster-whisper) | OpenVINO GenAI |
|
||||||
|
|---|---|---|
|
||||||
|
| Temperature fallback | ✅ до 5 попыток с ростом temperature | ❌ один проход |
|
||||||
|
| Фильтр по compression_ratio | ✅ отсекает повторы | ❌ нет |
|
||||||
|
| Фильтр по log_prob | ✅ отсекает неуверенные сегменты | ❌ нет |
|
||||||
|
| no_speech_threshold | ✅ детекция тишины | ❌ нет |
|
||||||
|
| VAD (Silero) | ✅ опционально | ❌ нет |
|
||||||
|
| condition_on_previous_text | ✅ с умным сбросом | ⚠️ базовый |
|
||||||
|
|
||||||
|
Эти эвристики критичны для русской разговорной речи с паузами и перебивками.
|
||||||
|
|
||||||
|
### Выбор между OpenVINO и CPU
|
||||||
|
|
||||||
|
| Сценарий | Рекомендация |
|
||||||
|
|---|---|
|
||||||
|
| Быстрый черновой транскрипт | `--device openvino-cpu` (int8, ~240с) |
|
||||||
|
| Качественный транскрипт для суммаризации | `--device cpu --compute-type int8_float32` (~460с) |
|
||||||
|
| Максимальная точность | `--device cpu --compute-type float32` (~880с) |
|
||||||
|
|
||||||
|
## CUDA: compute_type и качество
|
||||||
|
|
||||||
|
На NVIDIA GPU дефолт — `float16`, и для большинства случаев это оптимальный выбор.
|
||||||
|
|
||||||
|
### Результаты тестирования compute_type на GPU (RTX 3060 Laptop, 6 GB)
|
||||||
|
|
||||||
|
**Скорость (файл 16 мин, русский язык):**
|
||||||
|
|
||||||
|
| Модель | float16 | int8_float32 | int8_float16 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| medium | **53с** (~18x) | 64с (~15x) | 54с (~18x) |
|
||||||
|
| large-v3 | **86с** (~11x) | 113с (~8.5x) | 125с (~7.7x) |
|
||||||
|
|
||||||
|
**Скорость (файл 46 мин, русский язык):**
|
||||||
|
|
||||||
|
| Модель | float16 |
|
||||||
|
|---|---|
|
||||||
|
| medium | **127с** (~22x реалтайм) |
|
||||||
|
| large-v3 | 271с (~10x реалтайм) |
|
||||||
|
|
||||||
|
**Качество (файл 16 мин):**
|
||||||
|
|
||||||
|
| Модель | float16 | int8_float32 | int8_float16 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| medium | **Отлично** | **Отлично** (≈float16) | Хорошо (1-2 ошибки) |
|
||||||
|
| large-v3 | Хорошо (ед. галлюцинации) | **Плохо** (повтор "Ага" ×18) | **Неприемлемо** (мусор, иероглифы) |
|
||||||
|
|
||||||
|
**Качество (файл 46 мин):**
|
||||||
|
|
||||||
|
| Модель | float16 | Проблемы |
|
||||||
|
|---|---|---|
|
||||||
|
| medium | **Отлично** | Единичные ляпы ("email" вместо "ML"), стабилен |
|
||||||
|
| large-v3 | **Плохо** | Галлюцинации на 3+ языках (китайский, арабский), повторы фраз, "Аминь" вместо "Угу" |
|
||||||
|
|
||||||
|
### Выводы по CUDA compute_type
|
||||||
|
|
||||||
|
- **`float16` — оптимальный дефолт**: самый быстрый и стабильный
|
||||||
|
- **`int8_float32` не дал выигрыша**: на 20% медленнее float16, качество для medium сопоставимо, для large-v3 — деградация
|
||||||
|
- **`int8_float16`**: по скорости ≈ float16 для medium, но large-v3 даёт мусор
|
||||||
|
- **`int8`**: рискует галлюцинациями на large-v3 и длинных записях (повтор фраз ×25)
|
||||||
|
- **medium устойчивее к квантизации**, чем large-v3 — все варианты compute_type дают приемлемый результат
|
||||||
|
- **large-v3 на длинных записях (>20 мин)**: галлюцинации даже с float16 — medium надёжнее
|
||||||
|
|
||||||
## Настройка по платформам
|
## Настройка по платформам
|
||||||
|
|
||||||
### Linux / WSL2 (x86_64)
|
### Linux / WSL2 (x86_64)
|
||||||
@@ -98,45 +205,99 @@ winget install -e --id Nvidia.CUDA --version 12.9 # требует запус
|
|||||||
|
|
||||||
## Совместимость GPU
|
## Совместимость GPU
|
||||||
|
|
||||||
| GPU | VRAM | medium float16 | large-v3 float16 | Рекомендация |
|
| GPU | VRAM | CC | medium float16 | large-v3 float16 | Рекомендация |
|
||||||
|-----|------|---------------|-----------------|--------------|
|
|-----|------|----|---------------|-----------------|--------------|
|
||||||
| RTX 3060 | 6 GB | ✅ | ✅ | medium float16 (дефолт) |
|
| RTX 3060 | 6 GB | 8.6 | ✅ | ✅ | medium float16 (дефолт) |
|
||||||
| RTX 4050 | 6 GB | ✅ | ✅ | medium float16 |
|
| RTX 4050 | 6 GB | 8.9 | ✅ | ✅ | medium float16 |
|
||||||
| Quadro M3000M | 4 GB | ✅ | ⚠️ tight | medium float16 или int8 |
|
| Quadro M3000M | 4 GB | 5.0 | ❌ | ❌ | `--device cpu` или `openvino` |
|
||||||
|
|
||||||
|
> **Quadro M3000M и другие GPU с CC < 7.0 (Maxwell, Pascal)**: CUDA не работает —
|
||||||
|
> float16 требует CC ≥ 7.0, float32 падает с ошибкой (CTranslate2 4.x не включает
|
||||||
|
> sm_50/sm_60 в пребилды). Используйте `--device cpu --compute-type int8_float32`
|
||||||
|
> или `--device openvino`.
|
||||||
|
|
||||||
## Ожидаемая скорость
|
## Ожидаемая скорость
|
||||||
|
|
||||||
Замеры на RTX 3060 Laptop (6 GB) и Intel CPU (WSL2):
|
Замеры на RTX 3060 Laptop (6 GB) и Intel CPU (WSL2):
|
||||||
|
|
||||||
| Конфигурация | 16 мин файл | 42 мин файл | Отн. скорость |
|
| Конфигурация | 16 мин файл | 46 мин файл | Отн. скорость |
|
||||||
|-------------|-------------|-------------|---------------|
|
|-------------|-------------|-------------|---------------|
|
||||||
| GPU + medium float16 | ~35с | ~133с | ~19x реалтайм |
|
| GPU + medium float16 | **53с** | **127с** | **~18-22x реалтайм** |
|
||||||
| GPU + large-v3 float16 | ~90с | ~350с | ~7x реалтайм |
|
| GPU + large-v3 float16 | 86с | 271с | ~10-11x реалтайм |
|
||||||
|
| GPU + medium int8_float32 | 64с | — | ~15x реалтайм |
|
||||||
|
| GPU + medium int8_float16 | 54с | — | ~18x реалтайм |
|
||||||
| **OpenVINO + small int8** | **93с** | **153с** | **~10-16x реалтайм** |
|
| **OpenVINO + small int8** | **93с** | **153с** | **~10-16x реалтайм** |
|
||||||
| **OpenVINO + medium int8** | **171-205с** | **413с** | **~4-6x реалтайм** |
|
| **OpenVINO + medium int8** | **171-205с** | **413с** | **~4-6x реалтайм** |
|
||||||
| **OpenVINO + large-v3 fp16** | **416с** | — | **~2.3x реалтайм** |
|
| **OpenVINO + large-v3 fp16** | **416с** | — | **~2.3x реалтайм** |
|
||||||
|
| **CPU + medium int8_float32** | **~460с*** | — | **~2x реалтайм** |
|
||||||
| CPU + medium float32 | 658с (11 мин) | ~26 мин | ~1.5x реалтайм |
|
| CPU + medium float32 | 658с (11 мин) | ~26 мин | ~1.5x реалтайм |
|
||||||
| CPU + large-v3 int8 | 839с (14 мин) | ~37 мин | ~1:1 реалтайм |
|
| CPU + large-v3 int8 | 839с (14 мин) | ~37 мин | ~1:1 реалтайм |
|
||||||
|
|
||||||
|
\* Замер CPU int8_float32 на AMD Ryzen 7 8845H (8 потоков), файл 14:41.
|
||||||
|
|
||||||
## Результаты тестирования качества
|
## Результаты тестирования качества
|
||||||
|
|
||||||
Тесты проведены на реальных записях рабочих созвонов (русский язык, технические термины:
|
Тесты проведены на реальных записях рабочих созвонов (русский язык, технические термины:
|
||||||
SQL, PostgreSQL, Greenplum, Airflow, ClickHouse, Docker, CDR, GTP, MAP).
|
SQL, PostgreSQL, Greenplum, Airflow, ClickHouse, Docker, CDR, GTP, MAP).
|
||||||
|
|
||||||
| Конфигурация | Качество (длинная запись, 42 мин) | Проблемы |
|
| Конфигурация | Качество (короткие, ≤16 мин) | Качество (длинные, >40 мин) | Проблемы |
|
||||||
|---|---|---|
|
|---|---|---|---|
|
||||||
| large-v3 int8 GPU | Плохо | Галлюцинации (фразы ×25), потеря контента |
|
| medium float16 GPU | **Отлично** | **Отлично** | Единичные ляпы в терминах ("email" вместо "ML") |
|
||||||
| large-v3 float16 GPU | Отлично | — |
|
| medium int8_float32 GPU | **Отлично** | — | Качество ≈ float16, но на 20% медленнее |
|
||||||
| medium float16 GPU | Хорошо | Редкие мелкие ляпы в терминах |
|
| medium int8_float16 GPU | Хорошо | — | 1-2 ошибки |
|
||||||
| medium float32 CPU | Хорошо | Сопоставимо с large-v3 int8, без галлюцинаций |
|
| large-v3 float16 GPU | Хорошо | **Плохо** | Галлюцинации на длинных записях (мусор на 3+ языках) |
|
||||||
| large-v3 int8 CPU | Хорошо | Без галлюцинаций (на коротких файлах) |
|
| large-v3 int8_float32 GPU | **Плохо** | — | Повтор фраз ("Ага" ×18), искажения |
|
||||||
|
| large-v3 int8_float16 GPU | **Неприемлемо** | — | Мусор, китайские/арабские символы, потеря текста |
|
||||||
|
| large-v3 int8 GPU | **Плохо** | **Плохо** | Галлюцинации (фразы ×25), потеря контента |
|
||||||
|
| medium int8_float32 CPU | **Отлично** | — | Качество ≈ float32, на уровне облачных сервисов |
|
||||||
|
| medium float32 CPU | **Отлично** | Хорошо | Эталон качества |
|
||||||
|
| large-v3 int8 CPU | Хорошо | — | Без галлюцинаций (на коротких файлах) |
|
||||||
|
|
||||||
### Ключевые выводы
|
### Ключевые выводы
|
||||||
|
|
||||||
1. **Указание языка (`--language ru`) критично** — auto-detect может ошибиться и выдать мусор
|
1. **Указание языка (`--language ru`) критично** — auto-detect может ошибиться и выдать мусор
|
||||||
2. **float16/float32 стабильнее int8** — особенно на записях >20 минут
|
2. **medium + float16 на GPU — лучший баланс** скорости и качества для любых записей
|
||||||
3. **medium + float16 на GPU — лучший баланс** скорости и качества для повседневного использования
|
3. **large-v3 ненадёжен на длинных записях (>20 мин)** — галлюцинации даже с float16;
|
||||||
4. **large-v3 + float16 на GPU** — для максимального качества важных записей
|
medium стабильнее на любой длине
|
||||||
|
4. **medium устойчив к квантизации** — все варианты compute_type дают приемлемый результат;
|
||||||
|
large-v3 деградирует катастрофически при любом int8
|
||||||
|
5. **CPU: int8_float32 — лучший баланс** — 1.5x быстрее float32 при том же качестве
|
||||||
|
6. **OpenVINO быстрее, но CPU бэкенд качественнее** — разница в pipeline декодирования
|
||||||
|
(temperature fallback, фильтры галлюцинаций), а не в квантизации
|
||||||
|
|
||||||
|
## Потенциальные направления развития
|
||||||
|
|
||||||
|
### Качественный pipeline для OpenVINO
|
||||||
|
|
||||||
|
Сейчас качественный pipeline декодирования (temperature fallback, фильтры галлюцинаций,
|
||||||
|
VAD) доступен только через CTranslate2, т.е. на CPU и NVIDIA CUDA. Пользователи Intel Arc
|
||||||
|
и AMD Radeon получают скорость OpenVINO, но без защиты от галлюцинаций.
|
||||||
|
|
||||||
|
**Решение**: реализовать temperature fallback, compression_ratio и log_prob фильтры
|
||||||
|
поверх OpenVINO GenAI WhisperPipeline. Эти эвристики — логика на Python (~50-100 строк),
|
||||||
|
не зависящая от inference engine. Это даст Intel Arc / AMD GPU то же качество,
|
||||||
|
что сейчас есть только у CUDA-пользователей.
|
||||||
|
|
||||||
|
### CUDA: int8_float32 / int8_float16 — протестировано
|
||||||
|
|
||||||
|
На CPU `int8_float32` показал качество на уровне float32 при 1.5x ускорении.
|
||||||
|
На GPU (RTX 3060) результат другой: для **medium** int8_float32 даёт сопоставимое
|
||||||
|
качество, но на 20% медленнее float16 — выигрыша нет. Для **large-v3** любой int8
|
||||||
|
вариант (включая int8_float32) вызывает галлюцинации — float32 аккумулятор не спасает
|
||||||
|
глубокую модель на GPU. Вывод: **float16 остаётся оптимальным дефолтом для CUDA**.
|
||||||
|
|
||||||
|
### NVIDIA Parakeet TDT — проверено, отклонено
|
||||||
|
|
||||||
|
Оценивалась гипотеза: multilingual Parakeet TDT 0.6B v3 через `onnx-asr` может обойти
|
||||||
|
Whisper medium int8 на x86 CPU. На двух 15-минутных отрывках реальных русских ИТ-встреч
|
||||||
|
(Intel i7-11800H) Parakeet показал **2.7x преимущество по скорости** и **-35% RAM**, но
|
||||||
|
**качество ниже** — систематическая редукция тихих реплик менти до `Mm-hmm`, вставки
|
||||||
|
кусков польского/испанского, сильное искажение ИТ-терминов. Попытка тюнинга Silero VAD
|
||||||
|
threshold не помогла — проблема inherent для модели на тихой русской речи.
|
||||||
|
|
||||||
|
Код бэкенда не вмержен в master; эксперимент сохранён в ветке
|
||||||
|
`feature/parakeet-backend`. Подробности и когда стоит пересмотреть —
|
||||||
|
[ADR-005](adr/005-parakeet-evaluation.md).
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
|
|||||||
@@ -1,351 +0,0 @@
|
|||||||
# Исследование: ускорение на AMD Radeon / NPU
|
|
||||||
|
|
||||||
**Дата**: 2026-03-23
|
|
||||||
**Железо**: AMD Ryzen 7 8845H, Radeon 780M iGPU (RDNA3, 12 CU), XDNA 1 NPU (16 TOPS)
|
|
||||||
|
|
||||||
## Мотивация
|
|
||||||
|
|
||||||
Бенчмарк ([benchmark-2026-03-23.md](benchmark-2026-03-23.md)) показал: лучшее качество
|
|
||||||
даёт CTranslate2 float32/int8_float32, но это 600–880с на 15-минутную запись.
|
|
||||||
OpenVINO fp16 быстрее (240с), но теряет качество на русской речи.
|
|
||||||
|
|
||||||
Нужен путь: качество float32 + скорость GPU.
|
|
||||||
|
|
||||||
## Вариант 1: DirectML (onnxruntime-directml)
|
|
||||||
|
|
||||||
### Вердикт: ❌ НЕ рекомендуется
|
|
||||||
|
|
||||||
**Блокирующая проблема**: оператор `DecoderMaskedMultiHeadAttention` не реализован
|
|
||||||
в DirectML ([Olive#1213](https://github.com/microsoft/Olive/issues/1213), открыт с июня 2024).
|
|
||||||
Whisper-декодер fallback-ится на CPU — GPU ускоряет только encoder, выигрыш минимален.
|
|
||||||
|
|
||||||
**DirectML в maintenance mode** — Microsoft переключился на Windows ML (WinML).
|
|
||||||
Команда DML расформирована, новых операторов не будет. Фикс маловероятен.
|
|
||||||
|
|
||||||
**WinML** (замена DirectML) — GA с сентября 2025, но Python-пакет `onnxruntime-winml`
|
|
||||||
ещё не доступен. Требует Windows 11 25H2+. Потенциально интересен в будущем.
|
|
||||||
|
|
||||||
## Вариант 2: whisper.cpp + Vulkan ✅
|
|
||||||
|
|
||||||
### Вердикт: рекомендуется как основной путь
|
|
||||||
|
|
||||||
**Почему:**
|
|
||||||
- Проверен на AMD Radeon 780M — баг softmax ([#2596](https://github.com/ggml-org/whisper.cpp/issues/2596))
|
|
||||||
исправлен в декабре 2024
|
|
||||||
- **3–4x ускорение** vs CPU на Radeon 680M (RDNA2), 780M (RDNA3) должен быть не хуже
|
|
||||||
- Весь pipeline внутри: аудио → mel → encoder → decoder → beam search
|
|
||||||
- GGUF-модели компактнее ONNX
|
|
||||||
- Активная разработка, Vulkan-бэкенд зрелый
|
|
||||||
|
|
||||||
**Бенчмарки (чужие, похожее железо):**
|
|
||||||
|
|
||||||
| GPU | Модель | Результат |
|
|
||||||
|-----|--------|-----------|
|
|
||||||
| Radeon 680M (RDNA2) | medium | ~3–4x быстрее CPU |
|
|
||||||
| Ryzen 5 4500U (Vega) | medium | 1ч аудио за ~25 мин |
|
|
||||||
| Ryzen AI Max+ 395 (Strix Halo) | large-v3-turbo | 1ч20м аудио за ~3 мин |
|
|
||||||
|
|
||||||
**Память:** Radeon 780M использует системную RAM. whisper-medium в GGUF ≈ 1.5 ГБ —
|
|
||||||
помещается с запасом.
|
|
||||||
|
|
||||||
### Интеграция в проект
|
|
||||||
|
|
||||||
Новый бэкенд `backends/whisper_cpp.py`, реализующий Backend Protocol:
|
|
||||||
|
|
||||||
```
|
|
||||||
uv add pywhispercpp # Python-биндинги к whisper.cpp
|
|
||||||
```
|
|
||||||
|
|
||||||
Либо Vulkan-сборка whisper.cpp как subprocess (более надёжно,
|
|
||||||
но менее удобно для пользователя).
|
|
||||||
|
|
||||||
**Зависимость в pyproject.toml:**
|
|
||||||
```toml
|
|
||||||
# Опциональная, т.к. требует Vulkan-совместимый GPU
|
|
||||||
"pywhispercpp>=0.4; sys_platform == 'win32'"
|
|
||||||
```
|
|
||||||
|
|
||||||
Либо через dependency group:
|
|
||||||
```toml
|
|
||||||
[dependency-groups]
|
|
||||||
vulkan = ["pywhispercpp>=0.4"]
|
|
||||||
```
|
|
||||||
|
|
||||||
Тогда установка: `uv sync --group vulkan`
|
|
||||||
|
|
||||||
**Открытые вопросы:**
|
|
||||||
- pywhispercpp собран с Vulkan или нужна своя сборка?
|
|
||||||
- Качество GGUF-квантизации vs CTranslate2 float32 — нужен бенчмарк
|
|
||||||
- Формат моделей другой (GGUF vs CTranslate2/OpenVINO) — ещё одно скачивание
|
|
||||||
|
|
||||||
## Вариант 3: AMD NPU (XDNA)
|
|
||||||
|
|
||||||
### Вердикт: ⏳ рано для XDNA 1, перспективно для XDNA 2
|
|
||||||
|
|
||||||
**Что есть:**
|
|
||||||
- Ryzen AI Software 1.7.0 (февраль 2026) поддерживает 8845H (Hawk Point)
|
|
||||||
- [AMD LIRA](https://github.com/amd/LIRA) — CLI для ASR на NPU, поддерживает whisper base/small/medium
|
|
||||||
- [AMD-форк whisper.cpp](https://github.com/amd/whisper.cpp) с NPU-бэкендом
|
|
||||||
- Pre-квантизированные модели на HuggingFace (`amd/NPU-Whisper-Base-Small`)
|
|
||||||
|
|
||||||
**Ограничения на XDNA 1 (8845H):**
|
|
||||||
- 16 TOPS — в 3 раза слабее XDNA 2 (50 TOPS)
|
|
||||||
- **Только encoder** на NPU, decoder на CPU — ускорение частичное
|
|
||||||
- whisper-large-v3 не помещается
|
|
||||||
- Установка через отдельный installer AMD, не pip/uv
|
|
||||||
- Форк whisper.cpp не в mainline — риск отставания
|
|
||||||
|
|
||||||
**Когда станет интересно:**
|
|
||||||
- На железе с XDNA 2 (Ryzen AI 300+, 50 TOPS)
|
|
||||||
- Когда AMD выпустит pip-пакет или upstreamит в whisper.cpp
|
|
||||||
- Для сценариев, где CPU/GPU заняты другой работой
|
|
||||||
|
|
||||||
## Сводка
|
|
||||||
|
|
||||||
| Подход | Применимость | Ускорение | Сложность | Стабильность |
|
|
||||||
|--------|-------------|-----------|-----------|--------------|
|
|
||||||
| **whisper.cpp + Vulkan** | ✅ высокая | 3–4x vs CPU | низкая | хорошая |
|
|
||||||
| DirectML (onnxruntime) | ❌ заблокирован | — | — | — |
|
|
||||||
| AMD NPU (XDNA 1) | ⚠️ ограничен | частичное | высокая | средняя |
|
|
||||||
| WinML (будущее) | ⏳ не готов | неизвестно | средняя | — |
|
|
||||||
|
|
||||||
## Детали по whisper.cpp + Vulkan
|
|
||||||
|
|
||||||
### Модели (GGML .bin формат, НЕ GGUF)
|
|
||||||
|
|
||||||
Скачивать с https://huggingface.co/ggerganov/whisper.cpp/tree/main
|
|
||||||
|
|
||||||
| Модель | Файл | Размер |
|
|
||||||
|--------|------|--------|
|
|
||||||
| medium f16 | `ggml-medium.bin` | 1.53 ГБ |
|
|
||||||
| medium q8_0 | `ggml-medium-q8_0.bin` | 823 МБ |
|
|
||||||
| medium q5_0 | `ggml-medium-q5_0.bin` | 539 МБ |
|
|
||||||
| large-v3 f16 | `ggml-large-v3.bin` | 3.1 ГБ |
|
|
||||||
| large-v3 q5_0 | `ggml-large-v3-q5_0.bin` | 1.08 ГБ |
|
|
||||||
| large-v3-turbo f16 | `ggml-large-v3-turbo.bin` | 1.62 ГБ |
|
|
||||||
| large-v3-turbo q8_0 | `ggml-large-v3-turbo-q8_0.bin` | 874 МБ |
|
|
||||||
|
|
||||||
### Python-биндинги
|
|
||||||
|
|
||||||
**pywhispercpp** (v1.4.1) — pip wheel CPU-only. Для Vulkan нужна сборка из исходников:
|
|
||||||
```bash
|
|
||||||
GGML_VULKAN=1 pip install git+https://github.com/absadiki/pywhispercpp
|
|
||||||
```
|
|
||||||
Требует: VulkanSDK, cmake, C++ компилятор.
|
|
||||||
|
|
||||||
### Subprocess-подход (альтернатива)
|
|
||||||
|
|
||||||
whisper-cli принимает WAV 16kHz mono, выдаёт JSON с таймкодами:
|
|
||||||
```bash
|
|
||||||
whisper-cli -m ggml-medium.bin -f audio.wav -l ru -oj -of output
|
|
||||||
```
|
|
||||||
|
|
||||||
Для видео/mp3 нужна предварительная конвертация через ffmpeg (PyAV уже есть в зависимостях).
|
|
||||||
|
|
||||||
### Сборка whisper.cpp с Vulkan
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Linux/WSL
|
|
||||||
sudo apt install cmake build-essential libvulkan-dev
|
|
||||||
git clone https://github.com/ggml-org/whisper.cpp
|
|
||||||
cd whisper.cpp
|
|
||||||
cmake -B build -DGGML_VULKAN=ON -DCMAKE_BUILD_TYPE=Release
|
|
||||||
cmake --build build --config Release -j$(nproc)
|
|
||||||
# -> build/bin/whisper-cli
|
|
||||||
```
|
|
||||||
|
|
||||||
## Прототип: whisper.cpp в WSL2 (2026-03-23)
|
|
||||||
|
|
||||||
### Сборка
|
|
||||||
|
|
||||||
whisper.cpp собран с `-DGGML_VULKAN=ON`, но **Vulkan GPU не обнаружен**:
|
|
||||||
|
|
||||||
```
|
|
||||||
ggml_vulkan: No devices found.
|
|
||||||
whisper_backend_init_gpu: no GPU found
|
|
||||||
```
|
|
||||||
|
|
||||||
**Причина**: WSL2 не предоставляет Vulkan-доступ к AMD GPU.
|
|
||||||
- `/dev/dxg` есть (DirectX 12 paravirtualization), но **dozen** (Vulkan-over-D3D12) драйвер
|
|
||||||
отсутствует в текущей mesa (25.2.8)
|
|
||||||
- RADV (нативный Vulkan) не работает — нет `/dev/dri/renderD128`
|
|
||||||
- Единственный Vulkan-девайс — `llvmpipe` (CPU-эмуляция), whisper.cpp его игнорирует
|
|
||||||
|
|
||||||
Whisper.cpp отработал **на CPU** (4 потока, AVX512).
|
|
||||||
|
|
||||||
### Скорость (CPU fallback)
|
|
||||||
|
|
||||||
| Параметр | Значение |
|
|
||||||
|----------|----------|
|
|
||||||
| Модель | ggml-medium.bin (f16, 1533 МБ) |
|
|
||||||
| Аудио | 14:41 (881с), русский, 3 участника |
|
|
||||||
| Общее время | **886с** |
|
|
||||||
| encode | 368с (32 прохода, 11.5с/проход) |
|
|
||||||
| batchd (decode) | 424с |
|
|
||||||
| Потоки | 4 / 16 |
|
|
||||||
| Beam search | 5 beams + best of 5 |
|
|
||||||
|
|
||||||
Сравнение с бенчмарком (тот же аудиофайл):
|
|
||||||
|
|
||||||
| Конфигурация | Время, с |
|
|
||||||
|---|---|
|
|
||||||
| OpenVINO fp16 (medium) | ~240 |
|
|
||||||
| CT2 int8_float32 (medium) | ~599 |
|
|
||||||
| CT2 float32 (medium) | ~881 |
|
|
||||||
| **whisper.cpp CPU (medium f16)** | **~886** |
|
|
||||||
|
|
||||||
На CPU whisper.cpp ≈ CTranslate2 float32. Без GPU ускорения нет.
|
|
||||||
|
|
||||||
### Качество (сравнение с бенчмарком)
|
|
||||||
|
|
||||||
#### Техтермины
|
|
||||||
|
|
||||||
| Термин | whisper.cpp (CPU) | CT2 f32 | CT2 int8f32 |
|
|
||||||
|--------|------------------|---------|-------------|
|
|
||||||
| DWH | ✅ DWH | ⚠️ ДВХ | ✅ DWH |
|
|
||||||
| Teradata | ✅ TeraData | ⚠️ Тирадату | ✅ Teradata |
|
|
||||||
| NiFi | ⚠️ Найфай/Найфаю | ⚠️ Найфай | ✅ NiFi |
|
|
||||||
| FineBI | ✅ fineBI/FNBI | ✅ FNBI | ✅ finebi/FNBI |
|
|
||||||
| Alation | ✅ Allation | ✅ Allation | ✅ Allation |
|
|
||||||
| ETL | ✅ ETL | ✅ ETL | ✅ ETL |
|
|
||||||
| DBC Tables V | ✅ DBC tables-V | ✅ | ✅ |
|
|
||||||
| OpenLineage | ✅ OpenLinage.io | ✅ OpenLinage.io | ✅ OpenLinage.io |
|
|
||||||
|
|
||||||
#### Русская разговорная речь
|
|
||||||
|
|
||||||
| Фраза | whisper.cpp (CPU) | CT2 f32 | CT2 int8f32 |
|
|
||||||
|-------|------------------|---------|-------------|
|
|
||||||
| "на другую мониторочку" | ✅ "на другом мониторчике" | ✅ "мониторчику" | ⚠️ "мою точку" |
|
|
||||||
| "верхнеуровневое" | ✅ | ✅ | ✅ |
|
|
||||||
| "датарентген" | ✅ "даторентген" | ✅ "даторентген" | ✅ "даторентген" |
|
|
||||||
| "девовские схемы" | ✅ | ✅ | ✅ |
|
|
||||||
| "с накиданными правами" | ✅ | ✅ | ✅ |
|
|
||||||
| "Вьюхи" | ⚠️ "Yuhi" | ✅ "Вьюхи" | ⚠️ "Yuhi" |
|
|
||||||
|
|
||||||
#### Структура и пунктуация
|
|
||||||
|
|
||||||
| Аспект | whisper.cpp (CPU) | CT2 f32 | CT2 int8f32 |
|
|
||||||
|--------|------------------|---------|-------------|
|
|
||||||
| Пунктуация | хорошая | хорошая | хорошая |
|
|
||||||
| Длина сегментов | 1–10 сек | ~1 мин | ~1 мин |
|
|
||||||
|
|
||||||
**Вывод по качеству**: whisper.cpp medium f16 на уровне CTranslate2 float32/int8_float32.
|
|
||||||
Качество сопоставимо, различия минимальны. Сегменты короче (1–10 сек vs ~1 мин) —
|
|
||||||
это даже лучше для навигации по транскрипту.
|
|
||||||
|
|
||||||
### Блокер: Vulkan в WSL2 для AMD
|
|
||||||
|
|
||||||
Для получения GPU-ускорения нужно одно из:
|
|
||||||
|
|
||||||
1. **Нативный Linux** (dual boot / bare metal) — RADV драйвер работает с Radeon 780M
|
|
||||||
2. **Windows-сборка** — нативный Vulkan через AMD Adrenalin драйвер
|
|
||||||
3. **WSL2 + dozen** — экспериментальный Vulkan-over-D3D12 драйвер mesa.
|
|
||||||
Статус: [в разработке](https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests?search=dozen),
|
|
||||||
не включён в стандартную mesa Ubuntu.
|
|
||||||
Потенциально можно собрать mesa из исходников с `-Dvulkan-drivers=microsoft-experimental`
|
|
||||||
|
|
||||||
**Рекомендация**: попробовать Windows-сборку whisper.cpp — это самый простой путь
|
|
||||||
к Vulkan + Radeon 780M. Либо собрать mesa с dozen для WSL2.
|
|
||||||
|
|
||||||
## Прототип: whisper.cpp + Vulkan на Windows (2026-03-23)
|
|
||||||
|
|
||||||
### Сборка
|
|
||||||
|
|
||||||
whisper.cpp собран на Windows с MinGW (MSYS2) + Vulkan:
|
|
||||||
```
|
|
||||||
cmake -B build-vulkan -G "MinGW Makefiles" -DGGML_VULKAN=ON -DCMAKE_BUILD_TYPE=Release
|
|
||||||
```
|
|
||||||
|
|
||||||
Vulkan GPU обнаружен:
|
|
||||||
```
|
|
||||||
ggml_vulkan: Found 1 Vulkan devices:
|
|
||||||
ggml_vulkan: 0 = AMD Radeon(TM) 780M (AMD proprietary driver) | uma: 1 | fp16: 1 |
|
|
||||||
warp size: 64 | shared memory: 32768 | int dot: 1 | matrix cores: KHR_coopmat
|
|
||||||
```
|
|
||||||
|
|
||||||
### Проблема: зацикливание (repetition loop)
|
|
||||||
|
|
||||||
Первый запуск (дефолтные параметры): **520с**, но с ~07:00 модель зациклилась
|
|
||||||
на фразе "Ну, опять же, они могут завести…" — повторяется ~170 раз до конца записи.
|
|
||||||
Потеряна половина транскрипта.
|
|
||||||
|
|
||||||
**Причина**: whisper.cpp передаёт контекст предыдущего сегмента в декодер (`-mc -1`),
|
|
||||||
что провоцирует repetition loop на длинных записях с паузами/перебивками.
|
|
||||||
|
|
||||||
**Решение**: `-mc 0` (не передавать контекст между сегментами).
|
|
||||||
|
|
||||||
### Скорость (Vulkan, -mc 0)
|
|
||||||
|
|
||||||
| Параметр | Значение |
|
|
||||||
|----------|----------|
|
|
||||||
| Модель | ggml-medium.bin (f16, 1533 МБ) на Vulkan GPU |
|
|
||||||
| Аудио | 14:41 (881с), русский, 3 участника |
|
|
||||||
| Общее время | **432с** |
|
|
||||||
| encode | 123.5с (35 проходов, 3.5с/проход) |
|
|
||||||
| batchd (decode) | 252.7с |
|
|
||||||
| Потоки | 4 / 16 |
|
|
||||||
|
|
||||||
Сравнение с бенчмарком (тот же аудиофайл):
|
|
||||||
|
|
||||||
| Конфигурация | Время, с | Ускорение vs CPU f32 |
|
|
||||||
|---|---|---|
|
|
||||||
| OpenVINO fp16 (medium) | ~240 | 3.7x |
|
|
||||||
| **whisper.cpp Vulkan + Radeon 780M** | **~432** | **2.0x** |
|
|
||||||
| **CT2 int8_float32, 8 threads** | **~458** | **1.9x** |
|
|
||||||
| CT2 int8_float32, 16 threads | ~498 | 1.8x |
|
|
||||||
| CT2 int8_float32, default (4 threads) | ~599 | 1.5x |
|
|
||||||
| CT2 float32 / whisper.cpp CPU | ~881-886 | 1.0x |
|
|
||||||
|
|
||||||
**Оптимизация потоков**: CTranslate2 по умолчанию использует 4 потока (`cpu_threads=0`).
|
|
||||||
На Ryzen 7 8845H (8 ядер / 16 потоков) оптимум — **8 потоков** (по числу физических ядер).
|
|
||||||
16 потоков (SMT/hyperthreading) не помогают — контекст-свитчинг нивелирует выигрыш.
|
|
||||||
Управление: `OMP_NUM_THREADS=8` или параметр `cpu_threads` в WhisperModel.
|
|
||||||
|
|
||||||
CT2 int8_float32 с 8 потоками (**458с**) почти догоняет whisper.cpp Vulkan (**432с**)
|
|
||||||
без дополнительных зависимостей.
|
|
||||||
|
|
||||||
### Качество (Vulkan, -mc 0)
|
|
||||||
|
|
||||||
#### Техтермины
|
|
||||||
|
|
||||||
| Термин | whisper.cpp Vulkan | CT2 int8f32 |
|
|
||||||
|--------|-------------------|-------------|
|
|
||||||
| DWH | ✅ DWH | ✅ DWH |
|
|
||||||
| Teradata | ✅ TeraData | ✅ Teradata |
|
|
||||||
| NiFi | ⚠️ IFI | ✅ NiFi |
|
|
||||||
| FineBI | ✅ FindBI/FineBI/FNBI | ✅ finebi/FNBI |
|
|
||||||
| Alation | ✅ Allation | ✅ Allation |
|
|
||||||
| ETL | ✅ ETL | ✅ ETL |
|
|
||||||
| DBC Tables V | ⚠️ dbc tables v (lowercase) | ✅ DBC Tables V |
|
|
||||||
| OpenLineage | ✅ OpenLinage.io | ✅ OpenLinage.io |
|
|
||||||
| Вьюхи | ✅ Вьюхи | ⚠️ Yuhi |
|
|
||||||
|
|
||||||
#### Русская разговорная речь и пунктуация
|
|
||||||
|
|
||||||
Качество сопоставимо с CT2 int8_float32. Пунктуация хорошая в обоих случаях.
|
|
||||||
Сегменты у whisper.cpp короче (2-10 сек vs ~1 мин) — удобнее для навигации.
|
|
||||||
|
|
||||||
### Вывод
|
|
||||||
|
|
||||||
whisper.cpp + Vulkan на Radeon 780M даёт **432с** — всего **28% быстрее** CT2 int8_float32
|
|
||||||
(599с). Разница недостаточна, чтобы оправдать сложность интеграции:
|
|
||||||
|
|
||||||
- Отдельная сборка whisper.cpp (cmake + MinGW + VulkanSDK)
|
|
||||||
- Другой формат моделей (GGML вместо CTranslate2)
|
|
||||||
- Workaround `-mc 0` от зацикливания
|
|
||||||
- Конвертация в WAV (whisper.cpp не принимает mp4 напрямую)
|
|
||||||
- Ещё одна зависимость для пользователя
|
|
||||||
|
|
||||||
**CT2 int8_float32 остаётся оптимальным выбором**: уже встроен в проект, zero-config,
|
|
||||||
хорошее качество, приемлемая скорость.
|
|
||||||
|
|
||||||
## Рекомендуемый план (обновлённый)
|
|
||||||
|
|
||||||
1. ~~**Прототип в WSL** — собрать whisper.cpp с Vulkan, прогнать тестовый файл~~ ✅ Сделано
|
|
||||||
2. ~~**Качество подтверждено** — whisper.cpp medium f16 ≈ CT2 float32/int8_float32~~ ✅
|
|
||||||
3. ~~**GPU-ускорение в WSL2** — Vulkan не видит AMD GPU~~ ✅ Подтверждено
|
|
||||||
4. ~~**Windows-сборка с Vulkan** — 432с, 2x vs CPU, но всего 28% vs CT2 int8_float32~~ ✅
|
|
||||||
5. **Решение**: whisper.cpp + Vulkan не оправдывает себя на Radeon 780M iGPU.
|
|
||||||
CT2 int8_float32 — оптимальный дефолт для CPU без CUDA.
|
|
||||||
6. NPU — отложить до XDNA 2 или до появления pip-пакета от AMD
|
|
||||||
7. Пересмотреть при появлении дискретного AMD GPU или WinML с Python-поддержкой
|
|
||||||
@@ -30,8 +30,12 @@ class Backend(Protocol):
|
|||||||
model_path: str,
|
model_path: str,
|
||||||
device: str,
|
device: str,
|
||||||
compute_type: str,
|
compute_type: str,
|
||||||
|
cpu_threads: int = 0,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
"""Создаёт модель. Возвращает backend-специфичный объект."""
|
"""Создаёт модель. Возвращает backend-специфичный объект.
|
||||||
|
|
||||||
|
cpu_threads: число потоков для CPU inference (0 = дефолт библиотеки).
|
||||||
|
"""
|
||||||
...
|
...
|
||||||
|
|
||||||
def transcribe(
|
def transcribe(
|
||||||
|
|||||||
@@ -84,10 +84,17 @@ class FasterWhisperBackend:
|
|||||||
model_path: str,
|
model_path: str,
|
||||||
device: str,
|
device: str,
|
||||||
compute_type: str,
|
compute_type: str,
|
||||||
|
cpu_threads: int = 0,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
"""Создаёт WhisperModel."""
|
"""Создаёт WhisperModel.
|
||||||
|
|
||||||
|
cpu_threads: число потоков для CPU inference (0 = дефолт библиотеки, обычно 4).
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
return WhisperModel(model_path, device=device, compute_type=compute_type)
|
return WhisperModel(
|
||||||
|
model_path, device=device, compute_type=compute_type,
|
||||||
|
cpu_threads=cpu_threads,
|
||||||
|
)
|
||||||
except ImportError as exc:
|
except ImportError as exc:
|
||||||
if _is_missing_socksio_error(exc):
|
if _is_missing_socksio_error(exc):
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
|
|||||||
@@ -106,8 +106,9 @@ class OpenVINOBackend:
|
|||||||
model_path: str,
|
model_path: str,
|
||||||
device: str,
|
device: str,
|
||||||
compute_type: str,
|
compute_type: str,
|
||||||
|
cpu_threads: int = 0,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
"""Создаёт WhisperPipeline."""
|
"""Создаёт WhisperPipeline. cpu_threads не используется (OpenVINO управляет сам)."""
|
||||||
import openvino_genai as ov_genai
|
import openvino_genai as ov_genai
|
||||||
|
|
||||||
ov_dev = self._resolve_ov_device()
|
ov_dev = self._resolve_ov_device()
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ def main(
|
|||||||
None, "--compute-type", show_default=False,
|
None, "--compute-type", show_default=False,
|
||||||
help="Тип вычислений [по умолч.: float16 (CUDA) / int8 (OpenVINO GPU/CPU) / float32 (CPU)]"
|
help="Тип вычислений [по умолч.: float16 (CUDA) / int8 (OpenVINO GPU/CPU) / float32 (CPU)]"
|
||||||
),
|
),
|
||||||
|
threads: int = typer.Option(
|
||||||
|
0, "--threads", "-t", show_default=False, min=0,
|
||||||
|
help="Потоки CPU (0 = дефолт библиотеки; рекомендуется = число физ. ядер)"
|
||||||
|
),
|
||||||
verbose: bool = typer.Option(False, "--verbose", "-v", help="Подробный вывод"),
|
verbose: bool = typer.Option(False, "--verbose", "-v", help="Подробный вывод"),
|
||||||
force: bool = typer.Option(False, "--force", "-f", help="Перезаписать существующие транскрипты"),
|
force: bool = typer.Option(False, "--force", "-f", help="Перезаписать существующие транскрипты"),
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -89,9 +93,9 @@ def main(
|
|||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
|
|
||||||
if is_batch:
|
if is_batch:
|
||||||
_run_batch(expanded, defaults, verbose, force, ct_explicit)
|
_run_batch(expanded, defaults, verbose, force, ct_explicit, cpu_threads=threads)
|
||||||
else:
|
else:
|
||||||
_run_single(expanded[0], defaults, output, verbose, ct_explicit)
|
_run_single(expanded[0], defaults, output, verbose, ct_explicit, cpu_threads=threads)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
console.print("\nПрервано пользователем.", style="yellow")
|
console.print("\nПрервано пользователем.", style="yellow")
|
||||||
raise SystemExit(130)
|
raise SystemExit(130)
|
||||||
@@ -129,6 +133,7 @@ def _run_single(
|
|||||||
output: Path | None,
|
output: Path | None,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
compute_type_explicit: bool = False,
|
compute_type_explicit: bool = False,
|
||||||
|
cpu_threads: int = 0,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Пайплайн одного файла: валидация → модель → транскрипция → запись."""
|
"""Пайплайн одного файла: валидация → модель → транскрипция → запись."""
|
||||||
start = time.monotonic()
|
start = time.monotonic()
|
||||||
@@ -148,6 +153,7 @@ def _run_single(
|
|||||||
defaults["model"], resolved_device, defaults["compute_type"],
|
defaults["model"], resolved_device, defaults["compute_type"],
|
||||||
on_status=lambda msg: console.print(msg), strict_device=strict,
|
on_status=lambda msg: console.print(msg), strict_device=strict,
|
||||||
compute_type_explicit=compute_type_explicit,
|
compute_type_explicit=compute_type_explicit,
|
||||||
|
cpu_threads=cpu_threads,
|
||||||
)
|
)
|
||||||
actual_ct = getattr(backend, "actual_compute_type", defaults["compute_type"]) or defaults["compute_type"]
|
actual_ct = getattr(backend, "actual_compute_type", defaults["compute_type"]) or defaults["compute_type"]
|
||||||
console.print(
|
console.print(
|
||||||
@@ -174,6 +180,7 @@ def _run_single(
|
|||||||
on_segment=on_segment if verbose else None,
|
on_segment=on_segment if verbose else None,
|
||||||
on_status=status.update,
|
on_status=status.update,
|
||||||
strict_device=strict,
|
strict_device=strict,
|
||||||
|
cpu_threads=cpu_threads,
|
||||||
)
|
)
|
||||||
|
|
||||||
result = tfr.result
|
result = tfr.result
|
||||||
@@ -219,6 +226,7 @@ def _run_batch(
|
|||||||
verbose: bool,
|
verbose: bool,
|
||||||
force: bool,
|
force: bool,
|
||||||
compute_type_explicit: bool = False,
|
compute_type_explicit: bool = False,
|
||||||
|
cpu_threads: int = 0,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Трёхфазный батч-пайплайн: prescan → загрузка модели → транскрипция."""
|
"""Трёхфазный батч-пайплайн: prescan → загрузка модели → транскрипция."""
|
||||||
# Phase 1: Prescan — fail-fast + skip до загрузки модели (экономим ~2-5 сек)
|
# Phase 1: Prescan — fail-fast + skip до загрузки модели (экономим ~2-5 сек)
|
||||||
@@ -256,6 +264,7 @@ def _run_batch(
|
|||||||
defaults["model"], resolved_device, defaults["compute_type"],
|
defaults["model"], resolved_device, defaults["compute_type"],
|
||||||
on_status=lambda msg: console.print(msg), strict_device=strict,
|
on_status=lambda msg: console.print(msg), strict_device=strict,
|
||||||
compute_type_explicit=compute_type_explicit,
|
compute_type_explicit=compute_type_explicit,
|
||||||
|
cpu_threads=cpu_threads,
|
||||||
)
|
)
|
||||||
|
|
||||||
if actual_device == "openvino-gpu" and defaults["model"] != "large-v3":
|
if actual_device == "openvino-gpu" and defaults["model"] != "large-v3":
|
||||||
@@ -306,6 +315,7 @@ def _run_batch(
|
|||||||
on_segment=on_segment if verbose else None,
|
on_segment=on_segment if verbose else None,
|
||||||
on_status=status.update if not verbose else lambda msg: console.print(msg),
|
on_status=status.update if not verbose else lambda msg: console.print(msg),
|
||||||
strict_device=strict,
|
strict_device=strict,
|
||||||
|
cpu_threads=cpu_threads,
|
||||||
)
|
)
|
||||||
|
|
||||||
if tfr.actual_device != actual_device:
|
if tfr.actual_device != actual_device:
|
||||||
|
|||||||
@@ -22,11 +22,13 @@ def load_model(
|
|||||||
on_status: Callable[[str], None] | None = None,
|
on_status: Callable[[str], None] | None = None,
|
||||||
strict_device: bool = False,
|
strict_device: bool = False,
|
||||||
compute_type_explicit: bool = False,
|
compute_type_explicit: bool = False,
|
||||||
|
cpu_threads: int = 0,
|
||||||
) -> tuple[Any, str, Any, str]:
|
) -> tuple[Any, str, Any, str]:
|
||||||
"""Загружает модель: ensure + create с fallback.
|
"""Загружает модель: ensure + create с fallback.
|
||||||
|
|
||||||
Возвращает (model, actual_device, backend, model_path).
|
Возвращает (model, actual_device, backend, model_path).
|
||||||
compute_type_explicit: True если пользователь явно указал --compute-type.
|
compute_type_explicit: True если пользователь явно указал --compute-type.
|
||||||
|
cpu_threads: число потоков для CPU inference (0 = дефолт библиотеки).
|
||||||
"""
|
"""
|
||||||
backend = get_backend(device, compute_type_explicit=compute_type_explicit)
|
backend = get_backend(device, compute_type_explicit=compute_type_explicit)
|
||||||
actual_device = device
|
actual_device = device
|
||||||
@@ -35,7 +37,7 @@ def load_model(
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
_notify_status(on_status, f"Инициализирую модель на {device}...")
|
_notify_status(on_status, f"Инициализирую модель на {device}...")
|
||||||
model = backend.create_model(model_path, device, compute_type)
|
model = backend.create_model(model_path, device, compute_type, cpu_threads=cpu_threads)
|
||||||
# Резолвим actual_device по реальному OpenVINO device
|
# Резолвим actual_device по реальному OpenVINO device
|
||||||
ov_dev = getattr(backend, "actual_ov_device", None)
|
ov_dev = getattr(backend, "actual_ov_device", None)
|
||||||
if ov_dev == "GPU" and actual_device != "openvino-gpu":
|
if ov_dev == "GPU" and actual_device != "openvino-gpu":
|
||||||
@@ -55,7 +57,7 @@ def load_model(
|
|||||||
backend = get_backend("cpu")
|
backend = get_backend("cpu")
|
||||||
model_path = backend.ensure_model_available(model_name, compute_type, on_status)
|
model_path = backend.ensure_model_available(model_name, compute_type, on_status)
|
||||||
_notify_status(on_status, "Инициализирую модель на cpu...")
|
_notify_status(on_status, "Инициализирую модель на cpu...")
|
||||||
model = backend.create_model(model_path, "cpu", compute_type)
|
model = backend.create_model(model_path, "cpu", compute_type, cpu_threads=cpu_threads)
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@@ -74,6 +76,7 @@ def _transcribe_file(
|
|||||||
on_segment: Callable[[Segment], None] | None = None,
|
on_segment: Callable[[Segment], None] | None = None,
|
||||||
on_status: Callable[[str], None] | None = None,
|
on_status: Callable[[str], None] | None = None,
|
||||||
strict_device: bool = False,
|
strict_device: bool = False,
|
||||||
|
cpu_threads: int = 0,
|
||||||
) -> TranscribeFileResult:
|
) -> TranscribeFileResult:
|
||||||
"""Транскрибирует один файл. При mid-stream fallback перезагружает модель."""
|
"""Транскрибирует один файл. При mid-stream fallback перезагружает модель."""
|
||||||
lang_arg = language if language and language != "auto" else None
|
lang_arg = language if language and language != "auto" else None
|
||||||
@@ -95,7 +98,7 @@ def _transcribe_file(
|
|||||||
backend = get_backend("cpu")
|
backend = get_backend("cpu")
|
||||||
model_path = backend.ensure_model_available(model_name, compute_type, on_status)
|
model_path = backend.ensure_model_available(model_name, compute_type, on_status)
|
||||||
_notify_status(on_status, "Инициализирую модель на cpu...")
|
_notify_status(on_status, "Инициализирую модель на cpu...")
|
||||||
model = backend.create_model(model_path, "cpu", compute_type)
|
model = backend.create_model(model_path, "cpu", compute_type, cpu_threads=cpu_threads)
|
||||||
_notify_status(on_status, "Транскрибирую...")
|
_notify_status(on_status, "Транскрибирую...")
|
||||||
result = backend.transcribe(model, file_path, lang_arg, on_segment, on_status)
|
result = backend.transcribe(model, file_path, lang_arg, on_segment, on_status)
|
||||||
result.device_used = actual_device
|
result.device_used = actual_device
|
||||||
@@ -120,16 +123,19 @@ def transcribe(
|
|||||||
on_segment: Callable[[Segment], None] | None = None,
|
on_segment: Callable[[Segment], None] | None = None,
|
||||||
on_status: Callable[[str], None] | None = None,
|
on_status: Callable[[str], None] | None = None,
|
||||||
strict_device: bool = False,
|
strict_device: bool = False,
|
||||||
|
cpu_threads: int = 0,
|
||||||
) -> TranscribeResult:
|
) -> TranscribeResult:
|
||||||
"""High-level API: загрузка модели + транскрипция за один вызов."""
|
"""High-level API: загрузка модели + транскрипция за один вызов."""
|
||||||
model, actual_device, backend, model_path = load_model(
|
model, actual_device, backend, model_path = load_model(
|
||||||
model_name, device, compute_type, on_status, strict_device,
|
model_name, device, compute_type, on_status, strict_device,
|
||||||
compute_type_explicit=True, # Python API — caller explicitly chose compute_type
|
compute_type_explicit=True, # Python API — caller explicitly chose compute_type
|
||||||
|
cpu_threads=cpu_threads,
|
||||||
)
|
)
|
||||||
tfr = _transcribe_file(
|
tfr = _transcribe_file(
|
||||||
model, actual_device, backend, model_path,
|
model, actual_device, backend, model_path,
|
||||||
file_path, model_name, compute_type,
|
file_path, model_name, compute_type,
|
||||||
language, on_segment, on_status, strict_device,
|
language, on_segment, on_status, strict_device,
|
||||||
|
cpu_threads=cpu_threads,
|
||||||
)
|
)
|
||||||
return tfr.result
|
return tfr.result
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ def test_resolve_repo_explicit_unsupported_pair_raises():
|
|||||||
"""Явный --compute-type с несуществующей парой → ошибка."""
|
"""Явный --compute-type с несуществующей парой → ошибка."""
|
||||||
backend = OpenVINOBackend(compute_type_explicit=True)
|
backend = OpenVINOBackend(compute_type_explicit=True)
|
||||||
with pytest.raises(ValueError, match="недоступна с compute_type='fp16'"):
|
with pytest.raises(ValueError, match="недоступна с compute_type='fp16'"):
|
||||||
backend._resolve_repo("medium", "fp16")
|
backend._resolve_repo("small", "fp16")
|
||||||
|
|
||||||
|
|
||||||
def test_resolve_repo_explicit_unknown_model_raises():
|
def test_resolve_repo_explicit_unknown_model_raises():
|
||||||
|
|||||||
@@ -862,3 +862,62 @@ def test_cli_openvino_alias_resolves_to_gpu(tmp_path):
|
|||||||
assert out.exit_code == 0
|
assert out.exit_code == 0
|
||||||
# detect_device("openvino") resolved to "openvino-gpu", load_model receives it
|
# detect_device("openvino") resolved to "openvino-gpu", load_model receives it
|
||||||
assert mock_load_model.call_args[0][1] == "openvino-gpu"
|
assert mock_load_model.call_args[0][1] == "openvino-gpu"
|
||||||
|
|
||||||
|
|
||||||
|
# === --threads ===
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_threads_passed_to_load_model(tmp_path):
|
||||||
|
"""--threads передаётся в load_model как cpu_threads."""
|
||||||
|
audio = tmp_path / "test.mp3"
|
||||||
|
audio.write_bytes(b"fake")
|
||||||
|
result = _make_result()
|
||||||
|
model = _make_model()
|
||||||
|
backend = _make_backend()
|
||||||
|
tfr = _make_tfr(result=result, model=model, backend=backend)
|
||||||
|
mock_load_model = MagicMock(return_value=(model, "cpu", backend, "/models/medium"))
|
||||||
|
|
||||||
|
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", mock_load_model),
|
||||||
|
patch("local_transcriber.cli._transcribe_file", return_value=tfr),
|
||||||
|
patch("local_transcriber.cli.write_transcript"),
|
||||||
|
):
|
||||||
|
out = runner.invoke(app, [str(audio), "--threads", "8"])
|
||||||
|
|
||||||
|
assert out.exit_code == 0
|
||||||
|
assert mock_load_model.call_args.kwargs["cpu_threads"] == 8
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_threads_default_zero(tmp_path):
|
||||||
|
"""Без --threads load_model получает cpu_threads=0."""
|
||||||
|
audio = tmp_path / "test.mp3"
|
||||||
|
audio.write_bytes(b"fake")
|
||||||
|
result = _make_result()
|
||||||
|
model = _make_model()
|
||||||
|
backend = _make_backend()
|
||||||
|
tfr = _make_tfr(result=result, model=model, backend=backend)
|
||||||
|
mock_load_model = MagicMock(return_value=(model, "cpu", backend, "/models/medium"))
|
||||||
|
|
||||||
|
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", 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_load_model.call_args.kwargs["cpu_threads"] == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_threads_negative_rejected(tmp_path):
|
||||||
|
"""--threads с отрицательным значением отклоняется typer (min=0)."""
|
||||||
|
audio = tmp_path / "test.mp3"
|
||||||
|
audio.write_bytes(b"fake")
|
||||||
|
out = runner.invoke(app, [str(audio), "--threads", "-1"])
|
||||||
|
assert out.exit_code != 0
|
||||||
|
|||||||
@@ -321,6 +321,34 @@ def test_load_model_returns_backend_and_path(mock_get_backend):
|
|||||||
assert actual_device == "cpu"
|
assert actual_device == "cpu"
|
||||||
|
|
||||||
|
|
||||||
|
@patch("local_transcriber.transcriber.get_backend")
|
||||||
|
def test_load_model_passes_cpu_threads_to_backend(mock_get_backend):
|
||||||
|
backend = _make_backend(model_path="/mock/model/path")
|
||||||
|
mock_get_backend.return_value = backend
|
||||||
|
|
||||||
|
load_model("tiny", "cpu", "int8", cpu_threads=8)
|
||||||
|
|
||||||
|
assert backend.create_model.call_args.kwargs["cpu_threads"] == 8
|
||||||
|
|
||||||
|
|
||||||
|
@patch("local_transcriber.transcriber.get_backend")
|
||||||
|
def test_load_model_fallback_preserves_cpu_threads(mock_get_backend):
|
||||||
|
cuda_backend = _make_backend(create_model_error=RuntimeError("CUDA out of memory"))
|
||||||
|
cpu_model = MagicMock()
|
||||||
|
cpu_backend = _make_backend(model=cpu_model, model_path="/mock/cpu/model")
|
||||||
|
|
||||||
|
def backend_for_device(device, **kwargs):
|
||||||
|
return cuda_backend if device == "cuda" else cpu_backend
|
||||||
|
|
||||||
|
mock_get_backend.side_effect = backend_for_device
|
||||||
|
|
||||||
|
with pytest.warns(UserWarning, match="Переключение на CPU"):
|
||||||
|
load_model("tiny", "cuda", "int8", cpu_threads=6)
|
||||||
|
|
||||||
|
assert cuda_backend.create_model.call_args.kwargs["cpu_threads"] == 6
|
||||||
|
assert cpu_backend.create_model.call_args.kwargs["cpu_threads"] == 6
|
||||||
|
|
||||||
|
|
||||||
# === _transcribe_file() tests ===
|
# === _transcribe_file() tests ===
|
||||||
|
|
||||||
|
|
||||||
@@ -527,6 +555,36 @@ def test_transcribe_file_openvino_gpu_midstream_fallback(mock_get_backend):
|
|||||||
assert tfr.model_path == "/mock/cpu/model"
|
assert tfr.model_path == "/mock/cpu/model"
|
||||||
|
|
||||||
|
|
||||||
|
@patch("local_transcriber.transcriber.get_backend")
|
||||||
|
def test_transcribe_file_midstream_fallback_preserves_cpu_threads(mock_get_backend):
|
||||||
|
ov_backend = _make_backend(
|
||||||
|
transcribe_error=RuntimeError("OpenVINO inference error"),
|
||||||
|
)
|
||||||
|
cpu_backend = _make_backend(
|
||||||
|
transcribe_result=_make_result(count=2, device_used="cpu"),
|
||||||
|
model_path="/mock/cpu/model",
|
||||||
|
)
|
||||||
|
|
||||||
|
def backend_for_device(device, **kwargs):
|
||||||
|
return ov_backend if device.startswith("openvino") else cpu_backend
|
||||||
|
|
||||||
|
mock_get_backend.side_effect = backend_for_device
|
||||||
|
|
||||||
|
with pytest.warns(UserWarning, match="Переключение на CPU"):
|
||||||
|
_transcribe_file(
|
||||||
|
model=MagicMock(),
|
||||||
|
actual_device="openvino-gpu",
|
||||||
|
backend=ov_backend,
|
||||||
|
model_path="/mock/ov/model",
|
||||||
|
file_path=Path("test.mp3"),
|
||||||
|
model_name="medium",
|
||||||
|
compute_type="fp16",
|
||||||
|
cpu_threads=6,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert cpu_backend.create_model.call_args.kwargs["cpu_threads"] == 6
|
||||||
|
|
||||||
|
|
||||||
@patch("local_transcriber.transcriber.get_backend")
|
@patch("local_transcriber.transcriber.get_backend")
|
||||||
def test_openvino_gpu_strict_device_no_fallback(mock_get_backend):
|
def test_openvino_gpu_strict_device_no_fallback(mock_get_backend):
|
||||||
"""strict_device=True + OpenVINO GPU ошибка → raise."""
|
"""strict_device=True + OpenVINO GPU ошибка → raise."""
|
||||||
|
|||||||
Reference in New Issue
Block a user