feat(cuda): добавлен необязательный runtime для Linux и Windows (#57)
Closes #2. Независимое ревью Opus: APPROVED; 524 теста прошли, 2 пропущены. Реальный Windows GPU-прогон без Toolkit остаётся невыполненным и зафиксирован в документации.
This commit was merged in pull request #57.
This commit is contained in:
commit
343a31b35b
21 files changed
+613
-184
No files matched your search
@@ -8,7 +8,7 @@ transcribe meeting.mp4
|
|||||||
```
|
```
|
||||||
|
|
||||||
- **Полностью локально** — данные не покидают машину
|
- **Полностью локально** — данные не покидают машину
|
||||||
- **Авто-ускорение** — NVIDIA CUDA при наличии GPU, иначе ONNX на CPU
|
- **ONNX на CPU по умолчанию** — CUDA и OpenVINO включаются явно
|
||||||
- **Батч-режим** — обработка нескольких файлов за один вызов
|
- **Батч-режим** — обработка нескольких файлов за один вызов
|
||||||
- **Разделение говорящих** — локальная диаризация по флагу `--diarize`
|
- **Разделение говорящих** — локальная диаризация по флагу `--diarize`
|
||||||
- **Из проводника Windows** — пункт Transcribe в меню «Отправить» ([установка](#контекстное-меню-проводника-windows))
|
- **Из проводника Windows** — пункт Transcribe в меню «Отправить» ([установка](#контекстное-меню-проводника-windows))
|
||||||
@@ -50,18 +50,16 @@ transcribe --install-menu
|
|||||||
|
|
||||||
Подробнее — в разделе [контекстное меню проводника](#контекстное-меню-проводника-windows).
|
Подробнее — в разделе [контекстное меню проводника](#контекстное-меню-проводника-windows).
|
||||||
|
|
||||||
**4. Ускорение (ставится автоматически):**
|
**4. Выбор исполнения:**
|
||||||
|
|
||||||
- **NVIDIA CUDA** (GPU): если есть GPU — транскрипция в 5-10× быстрее
|
- По умолчанию используется ONNX с GigaAM RNN-T на CPU, независимо от наличия
|
||||||
- **Windows**: `winget install -e --id Nvidia.CUDA --version 12.9` (от администратора), перезапустить терминал
|
NVIDIA. Эта модель понимает только русскую речь.
|
||||||
- **Linux / WSL2**: работает из коробки (нужен только драйвер: `nvidia-smi`)
|
- Для других языков выбирайте Whisper явно:
|
||||||
- **Без NVIDIA GPU**: автоматически используется ONNX с GigaAM RNN-T на CPU.
|
`--device openvino-cpu --model medium` на x86 или `--device cpu --model medium`.
|
||||||
Эта модель понимает только русскую речь
|
- OpenVINO для Intel GPU или x86 CPU доступен через `--device openvino`,
|
||||||
- **Другие языки без NVIDIA**: выбирайте Whisper явно —
|
`--device openvino-gpu` или `--device openvino-cpu`.
|
||||||
`--device openvino-cpu --model medium` на x86 или `--device cpu --model medium`
|
- Для NVIDIA установите дополнительные библиотеки и выберите CUDA явно:
|
||||||
на любой платформе
|
[подключение CUDA](#подключение-cuda).
|
||||||
- **OpenVINO** для Intel GPU или x86 CPU остаётся доступен через явный
|
|
||||||
`--device openvino`, `--device openvino-gpu` или `--device openvino-cpu`
|
|
||||||
|
|
||||||
**5. Готово:**
|
**5. Готово:**
|
||||||
|
|
||||||
@@ -132,17 +130,74 @@ HuggingFace Hub использует симлинки для экономии м
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
## Подключение CUDA
|
||||||
|
|
||||||
|
Обычная установка не требует NVIDIA-пакетов. Extra `cuda` добавляет cuBLAS
|
||||||
|
в окружение приложения на Linux/WSL x86_64 и Windows x64. Полный системный
|
||||||
|
CUDA Toolkit и ручная правка системного PATH для этого пути не нужны.
|
||||||
|
Совместимые GPU и драйвер NVIDIA необходимы отдельно: extra не устанавливает
|
||||||
|
драйвер и не исправляет несовместимость старой видеокарты.
|
||||||
|
|
||||||
|
Для установки из Git или подключения CUDA к уже установленной программе:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv tool install --python 3.13 --force "local-transcriber[cuda] @ git+https://git.dementev.space/ddmitry/local-transcriber.git"
|
||||||
|
```
|
||||||
|
|
||||||
|
Из клона репозитория:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv tool install --python 3.13 --force ".[cuda]"
|
||||||
|
```
|
||||||
|
|
||||||
|
Для локальной разработки:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv sync --extra cuda
|
||||||
|
uv run --extra cuda transcribe meeting.mp4 --device cuda
|
||||||
|
```
|
||||||
|
|
||||||
|
После установки extra выберите CUDA при запуске:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
transcribe meeting.mp4 --device cuda
|
||||||
|
```
|
||||||
|
|
||||||
|
Либо задайте в `.transcriber.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
device = "cuda"
|
||||||
|
```
|
||||||
|
|
||||||
|
Установка extra сама по себе не меняет `auto`: он всегда использует ONNX CPU.
|
||||||
|
Явная CUDA при ошибке завершает обработку с исходной причиной; для распознанных
|
||||||
|
причин выводится подсказка. Молчаливого перехода на CPU нет. В батче ошибка
|
||||||
|
учитывается для каждого файла.
|
||||||
|
|
||||||
|
На Windows нужен также [Visual C++ Runtime x64](https://aka.ms/vs/17/release/vc_redist.x64.exe).
|
||||||
|
Состав DLL проверен для закреплённых версий CTranslate2 и cuBLAS;
|
||||||
|
реальный прогон нового пути на Windows без Toolkit пока не выполнен.
|
||||||
|
Подробности и ограничения проверки — [ADR-001](docs/adr/001-cuda-bootstrap.md).
|
||||||
|
|
||||||
|
При обновлении из старой установки:
|
||||||
|
|
||||||
|
- Для CPU повторите обычную команду `uv tool install` с `--force` без `[cuda]`.
|
||||||
|
В окружении разработки выполните `uv sync` — прежняя обязательная cuBLAS будет удалена.
|
||||||
|
- Для CUDA повторите соответствующую команду выше. Старый автоматический выбор
|
||||||
|
по `nvidia-smi` больше не действует; сохраните `device = "cuda"` в конфиге
|
||||||
|
либо передавайте `--device cuda`.
|
||||||
|
|
||||||
## Использование
|
## Использование
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Простой запуск (CUDA medium или ONNX GigaAM RNN-T, язык ru)
|
# Простой запуск (ONNX GigaAM RNN-T на CPU, язык ru)
|
||||||
transcribe meeting.mp4
|
transcribe meeting.mp4
|
||||||
|
|
||||||
# Указать язык
|
# Указать язык
|
||||||
transcribe lecture.mp3 --language en
|
transcribe lecture.mp3 --device cpu --model medium --language en
|
||||||
|
|
||||||
# Максимальное качество на NVIDIA GPU
|
# Максимальное качество на NVIDIA GPU
|
||||||
transcribe podcast.wav --model large-v3 --compute-type float16
|
transcribe podcast.wav --device cuda --model large-v3 --compute-type float16
|
||||||
|
|
||||||
# Максимальное качество на Intel GPU
|
# Максимальное качество на Intel GPU
|
||||||
transcribe podcast.wav --model large-v3 --device openvino-gpu
|
transcribe podcast.wav --model large-v3 --device openvino-gpu
|
||||||
@@ -243,7 +298,7 @@ transcribe --uninstall-menu
|
|||||||
| Опция | Сокращение | По умолчанию | Описание |
|
| Опция | Сокращение | По умолчанию | Описание |
|
||||||
|-------|-----------|-------------|----------|
|
|-------|-----------|-------------|----------|
|
||||||
| `--model` | `-m` | medium (CUDA) / gigaam-v3-e2e-rnnt (ONNX) | Модель распознавания |
|
| `--model` | `-m` | medium (CUDA) / gigaam-v3-e2e-rnnt (ONNX) | Модель распознавания |
|
||||||
| `--language` | `-l` | `ru` | Язык (ru, en, auto и др.); автоматический профиль без NVIDIA понимает только русский |
|
| `--language` | `-l` | `ru` | Язык (ru, en, auto и др.); автоматический профиль понимает только русский |
|
||||||
| `--output` | `-o` | `<файл>-transcript.md` | Путь к выходному файлу |
|
| `--output` | `-o` | `<файл>-transcript.md` | Путь к выходному файлу |
|
||||||
| `--device` | `-d` | `auto` | Устройство (auto, cpu, cuda, openvino, openvino-gpu, openvino-cpu, onnx) |
|
| `--device` | `-d` | `auto` | Устройство (auto, cpu, cuda, openvino, openvino-gpu, openvino-cpu, onnx) |
|
||||||
| `--compute-type` | — | float16 (CUDA) / int8 (ONNX/OpenVINO) / float32 (CPU) | Тип вычислений |
|
| `--compute-type` | — | float16 (CUDA) / int8 (ONNX/OpenVINO) / float32 (CPU) | Тип вычислений |
|
||||||
@@ -260,7 +315,7 @@ transcribe --uninstall-menu
|
|||||||
| CPU через ONNX | ✅ авто | ✅ авто | ✅ авто |
|
| CPU через ONNX | ✅ авто | ✅ авто | ✅ авто |
|
||||||
| OpenVINO (x86 CPU) | ✅ явно | — | ✅ явно |
|
| OpenVINO (x86 CPU) | ✅ явно | — | ✅ явно |
|
||||||
| OpenVINO (Intel GPU) | ✅ явно | — | ✅ явно |
|
| OpenVINO (Intel GPU) | ✅ явно | — | ✅ явно |
|
||||||
| GPU (NVIDIA) | ✅ авто | — | ✅ (нужен CUDA 12) |
|
| GPU (NVIDIA) | extra + явно (x86_64) | — | extra + явно (x64; прогон без Toolkit ожидается) |
|
||||||
|
|
||||||
Данные по macOS основаны на доступности пакетов onnxruntime: прогонов на этой
|
Данные по macOS основаны на доступности пакетов onnxruntime: прогонов на этой
|
||||||
платформе не было.
|
платформе не было.
|
||||||
@@ -269,9 +324,9 @@ transcribe --uninstall-menu
|
|||||||
<summary>Linux / WSL2</summary>
|
<summary>Linux / WSL2</summary>
|
||||||
|
|
||||||
- **Intel GPU** (Arc, встроенная графика) работает из коробки через OpenVINO
|
- **Intel GPU** (Arc, встроенная графика) работает из коробки через OpenVINO
|
||||||
- **NVIDIA GPU** работает из коробки — cuBLAS ставится автоматически как зависимость
|
- **NVIDIA GPU**: [extra `cuda` и явный выбор](#подключение-cuda), отдельно совместимый драйвер
|
||||||
- Нужен только драйвер NVIDIA (проверка: `nvidia-smi`)
|
- `nvidia-smi` проверяет наличие драйвера, но не доказывает совместимость GPU с runtime
|
||||||
- На ARM (aarch64) cuBLAS через pip недоступен — нужен системный CUDA toolkit
|
- CUDA extra на ARM (aarch64) этим проектом не поддерживается
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@@ -288,13 +343,9 @@ transcribe --uninstall-menu
|
|||||||
|
|
||||||
- CPU работает из коробки
|
- CPU работает из коробки
|
||||||
- **Intel GPU** (Arc, встроенная графика) работает из коробки через OpenVINO
|
- **Intel GPU** (Arc, встроенная графика) работает из коробки через OpenVINO
|
||||||
- Для **NVIDIA GPU** нужен **CUDA 12** (ctranslate2 4.7 не совместим с CUDA 11 и 13):
|
- **NVIDIA GPU**: [extra `cuda` и явный выбор](#подключение-cuda), отдельно совместимый драйвер
|
||||||
```
|
- Требуется Visual C++ Runtime x64; системный CUDA Toolkit не нужен для extra
|
||||||
winget install -e --id Nvidia.CUDA --version 12.9
|
- Реальная транскрипция с extra без Toolkit пока не проверена на Windows
|
||||||
```
|
|
||||||
> `winget install` требует запуска от имени администратора (elevated terminal).
|
|
||||||
> `uv tool install` работает без админа (ставит в пользовательскую директорию).
|
|
||||||
- После установки CUDA перезапустите терминал
|
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@@ -318,8 +369,8 @@ diarize = true
|
|||||||
`--diarize` и `--no-diarize` позволяют переопределить `diarize` из конфига для
|
`--diarize` и `--no-diarize` позволяют переопределить `diarize` из конфига для
|
||||||
отдельного запуска.
|
отдельного запуска.
|
||||||
|
|
||||||
При `device = "auto"` выбирается CUDA, если доступен `nvidia-smi`, иначе ONNX.
|
При `device = "auto"` всегда выбирается ONNX CPU, даже если доступны
|
||||||
Явный `device` из CLI или конфига отключает этот автоматический выбор.
|
`nvidia-smi` и extra `cuda`. Другие варианты выбираются явно из CLI или конфига.
|
||||||
Каталоги моделей различаются между бэкендами, поэтому при закреплении `model`
|
Каталоги моделей различаются между бэкендами, поэтому при закреплении `model`
|
||||||
в конфиге рекомендуется явно закрепить и совместимый `device`. То же с языком:
|
в конфиге рекомендуется явно закрепить и совместимый `device`. То же с языком:
|
||||||
автоматический ONNX-профиль рассчитан на русскую речь, а для остальных языков
|
автоматический ONNX-профиль рассчитан на русскую речь, а для остальных языков
|
||||||
@@ -336,9 +387,9 @@ diarize = true
|
|||||||
## Модели и GPU
|
## Модели и GPU
|
||||||
|
|
||||||
Рекомендации:
|
Рекомендации:
|
||||||
- **По умолчанию без CUDA:** ONNX `gigaam-v3-e2e-rnnt` — читаемый русский текст с
|
- **По умолчанию:** ONNX `gigaam-v3-e2e-rnnt` — читаемый русский текст с
|
||||||
пунктуацией почти без потери скорости относительно сырого `gigaam-v3`
|
пунктуацией почти без потери скорости относительно сырого `gigaam-v3`
|
||||||
- **Макс. качество (NVIDIA):** `large-v3` + `--compute-type float16`
|
- **Макс. качество (NVIDIA):** `--device cuda --model large-v3 --compute-type float16`
|
||||||
- **Макс. качество (Intel GPU):** `large-v3` + `--device openvino-gpu`
|
- **Макс. качество (Intel GPU):** `large-v3` + `--device openvino-gpu`
|
||||||
- **Макс. скорость CPU (русский):** `--device onnx --model gigaam-v3` (17-29× RTF, без пунктуации; рекомендуется LLM-нормализация терминов после)
|
- **Макс. скорость CPU (русский):** `--device onnx --model gigaam-v3` (17-29× RTF, без пунктуации; рекомендуется LLM-нормализация терминов после)
|
||||||
- **Быстрый OpenVINO с низким WER:** `--device openvino-cpu --model large-v3-turbo --compute-type int8` (7,2× RTFx на контрольном Intel CPU; пунктуация может быть слабой)
|
- **Быстрый OpenVINO с низким WER:** `--device openvino-cpu --model large-v3-turbo --compute-type int8` (7,2× RTFx на контрольном Intel CPU; пунктуация может быть слабой)
|
||||||
|
|||||||
+6
-6
@@ -49,7 +49,7 @@ transcribe <путь_к_файлу> [опции]
|
|||||||
--output, -o Путь к выходному файлу
|
--output, -o Путь к выходному файлу
|
||||||
По умолчанию: <input_stem>-transcript.md
|
По умолчанию: <input_stem>-transcript.md
|
||||||
--device, -d Устройство (auto|cpu|cuda|onnx|openvino|openvino-gpu|openvino-cpu)
|
--device, -d Устройство (auto|cpu|cuda|onnx|openvino|openvino-gpu|openvino-cpu)
|
||||||
По умолчанию: auto (CUDA при наличии, иначе ONNX CPU)
|
По умолчанию: auto (всегда ONNX CPU)
|
||||||
--compute-type Тип вычислений
|
--compute-type Тип вычислений
|
||||||
По умолчанию: float16 (CUDA) / int8 (ONNX)
|
По умолчанию: float16 (CUDA) / int8 (ONNX)
|
||||||
--verbose, -v Подробный вывод (прогресс сегментов)
|
--verbose, -v Подробный вывод (прогресс сегментов)
|
||||||
@@ -103,7 +103,7 @@ transcribe <путь_к_файлу> [опции]
|
|||||||
|---------------------------|---------------------------------------|
|
|---------------------------|---------------------------------------|
|
||||||
| RTX 3060 + large-v3 | ~10-15x (1 час аудио ≈ 4-6 мин) |
|
| RTX 3060 + large-v3 | ~10-15x (1 час аудио ≈ 4-6 мин) |
|
||||||
| RTX 4050 + large-v3 | ~12-18x (1 час аудио ≈ 3-5 мин) |
|
| RTX 4050 + large-v3 | ~12-18x (1 час аудио ≈ 3-5 мин) |
|
||||||
| Quadro M3000M + large-v3 | ~3-5x (1 час аудио ≈ 12-20 мин) |
|
| Quadro M3000M + CUDA | Не поддерживается текущей сборкой; см. [совместимость](gpu.md#совместимость-gpu) |
|
||||||
| CPU + ONNX GigaAM RNN-T | ~10-14x (1 час аудио ≈ 4-6 мин) |
|
| CPU + ONNX GigaAM RNN-T | ~10-14x (1 час аудио ≈ 4-6 мин) |
|
||||||
| CPU + OpenVINO Turbo INT8 | ~7-10x (1 час аудио ≈ 6-9 мин) |
|
| CPU + OpenVINO Turbo INT8 | ~7-10x (1 час аудио ≈ 6-9 мин) |
|
||||||
| CPU (modern) + large-v3 | ~0.5-1x (1 час аудио ≈ 60-120 мин) |
|
| CPU (modern) + large-v3 | ~0.5-1x (1 час аудио ≈ 60-120 мин) |
|
||||||
@@ -115,7 +115,7 @@ transcribe <путь_к_файлу> [опции]
|
|||||||
|-------------------|-------|--------------------------|----------------------------|-------------------------|
|
|-------------------|-------|--------------------------|----------------------------|-------------------------|
|
||||||
| RTX 3060 | 6 GB | ✅ | ✅ (впритык) | int8 — безопасный выбор |
|
| RTX 3060 | 6 GB | ✅ | ✅ (впритык) | int8 — безопасный выбор |
|
||||||
| RTX 4050 | 6 GB | ✅ | ✅ (впритык) | int8 — безопасный выбор |
|
| RTX 4050 | 6 GB | ✅ | ✅ (впритык) | int8 — безопасный выбор |
|
||||||
| Quadro M3000M | 4 GB | ✅ | ⚠️ может OOM | int8 обязательно |
|
| Quadro M3000M | 4 GB | ❌ | ❌ | ONNX CPU или OpenVINO |
|
||||||
| Без GPU | — | ONNX GigaAM INT8 | — | auto выбирает ONNX |
|
| Без GPU | — | ONNX GigaAM INT8 | — | auto выбирает ONNX |
|
||||||
|
|
||||||
Device-aware дефолты выбирают `float16` для CUDA и `int8` для ONNX/OpenVINO.
|
Device-aware дефолты выбирают `float16` для CUDA и `int8` для ONNX/OpenVINO.
|
||||||
@@ -123,7 +123,7 @@ Device-aware дефолты выбирают `float16` для CUDA и `int8` д
|
|||||||
### 4.2. Требования к окружению
|
### 4.2. Требования к окружению
|
||||||
|
|
||||||
- Python ≥ 3.13
|
- Python ≥ 3.13
|
||||||
- Для GPU: Linux/WSL2 — cuBLAS из nvidia-cublas-cu12 (ставится автоматически через `uv sync`); Windows — системный CUDA toolkit (см. ADR-001)
|
- Для NVIDIA GPU на Linux/WSL x86_64 и Windows x64: совместимый драйвер, extra `cuda` и явный выбор CUDA (см. ADR-001); Windows также требует Visual C++ Runtime x64
|
||||||
- Дисковое пространство для моделей: зависит от выбранного backend и модели
|
- Дисковое пространство для моделей: зависит от выбранного backend и модели
|
||||||
- Выходные файлы: UTF-8 (явная кодировка при записи)
|
- Выходные файлы: UTF-8 (явная кодировка при записи)
|
||||||
|
|
||||||
@@ -177,9 +177,9 @@ local-transcriber/
|
|||||||
| Качество распознавания русского текста | Среднее | large-v3 хорошо справляется с ru; при проблемах — попробовать `--language ru` вместо auto |
|
| Качество распознавания русского текста | Среднее | large-v3 хорошо справляется с ru; при проблемах — попробовать `--language ru` вместо auto |
|
||||||
| Нет разделения по спикерам | Низкое | Осознанно выведено за скоуп MVP; добавление diarization (pyannote.audio) — возможное расширение |
|
| Нет разделения по спикерам | Низкое | Осознанно выведено за скоуп MVP; добавление diarization (pyannote.audio) — возможное расширение |
|
||||||
| Первый запуск: долгая загрузка модели | Низкое | Прогресс-бар при скачивании; модели кешируются в `~/.cache/huggingface/` |
|
| Первый запуск: долгая загрузка модели | Низкое | Прогресс-бар при скачивании; модели кешируются в `~/.cache/huggingface/` |
|
||||||
| CUDA несовместимость на Windows | Среднее | Автоматический fallback на CPU + предупреждение; в README — инструкция по CUDA |
|
| CUDA несовместимость на Windows | Среднее | Явная CUDA сохраняет причину ошибки; пользователь выбирает CPU или исправляет окружение по README |
|
||||||
| Большие файлы (>2 часов) | Среднее | Учитывать память выбранного backend; чанкование рассматривается отдельно |
|
| Большие файлы (>2 часов) | Среднее | Учитывать память выбранного backend; чанкование рассматривается отдельно |
|
||||||
| OOM на GPU с 4 GB VRAM | Среднее | CUDA использует float16; при OOM — fallback на CPU с предупреждением или явный более лёгкий профиль |
|
| OOM на GPU с 4 GB VRAM | Среднее | CUDA использует float16; при OOM явной CUDA — ошибка с подсказкой выбрать меньшую модель или CPU |
|
||||||
| Файл без речи (тишина, музыка, шум) | Низкое | Создаётся транскрипт с шапкой метаданных и `*Речь не обнаружена.*` в теле + предупреждение в stderr |
|
| Файл без речи (тишина, музыка, шум) | Низкое | Создаётся транскрипт с шапкой метаданных и `*Речь не обнаружена.*` в теле + предупреждение в stderr |
|
||||||
|
|
||||||
## 7. Вне скоупа MVP
|
## 7. Вне скоупа MVP
|
||||||
|
|||||||
+108
-52
@@ -1,78 +1,134 @@
|
|||||||
# ADR-001: Preload libcublas из pip-пакета для GPU на Linux/WSL2
|
# ADR-001: Необязательная CUDA и загрузка cuBLAS из окружения
|
||||||
|
|
||||||
**Статус**: Принято
|
**Статус**: Принято; пересмотр обязательной cuBLAS от 2026-03-18
|
||||||
**Дата**: 2026-03-18
|
**Дата**: 2026-03-18
|
||||||
|
**Обновлено**: 2026-09-21, задача [#2](https://git.dementev.space/ddmitry/local-transcriber/issues/2)
|
||||||
|
|
||||||
## Контекст
|
## Контекст
|
||||||
|
|
||||||
ctranslate2 (backend faster-whisper) в runtime делает `dlopen("libcublas.so.12")`,
|
CTranslate2 загружает cuBLAS динамически и не поставляет её в своём wheel.
|
||||||
но не бандлит эту библиотеку в свой wheel — ожидает её в системе.
|
Прежняя обязательная зависимость добавляла около 581 MB скачивания на Linux
|
||||||
Без установленного CUDA toolkit `uv run transcribe --device cuda` падает с ошибкой.
|
x86_64, даже если пользователь работал только через ONNX CPU.
|
||||||
|
Наличие `nvidia-smi` доказывает наличие утилиты драйвера, но не совместимость GPU
|
||||||
|
с runtime. Автоматический выбор CUDA мог приводить к ошибке на старом GPU.
|
||||||
|
|
||||||
Ключевые факты:
|
Прежнее утверждение о недоступности Windows wheel `nvidia-cublas-cu12` было
|
||||||
- `libcuda.so.1` приходит от NVIDIA driver (всегда есть, если GPU есть)
|
ошибочным: такой wheel есть и для закреплённой версии 12.9.1.4. Следовательно,
|
||||||
- `libcublas.so.12` отсутствует в wheel ctranslate2 на обеих платформах
|
системный CUDA Toolkit не должен быть обязательным способом подключения Windows.
|
||||||
- **cuDNN не нужен** — в `libctranslate2.so` ноль символов cudnn (проверено `nm -D` и `strings`)
|
|
||||||
- На Windows ctranslate2 делает `os.add_dll_directory` в своём `__init__.py`,
|
|
||||||
но только для DLL внутри пакета; `cublas64_12.dll` тоже не бандлится
|
|
||||||
|
|
||||||
## Решение
|
## Решение
|
||||||
|
|
||||||
### nvidia-cublas-cu12 как pip-зависимость
|
CUDA подключается через extra `cuda` на Linux/WSL x86_64 и Windows x64.
|
||||||
|
Драйвер NVIDIA остаётся внешним требованием; Windows также требует Visual C++
|
||||||
|
Runtime x64. Установка extra не включает CUDA: пользователь выбирает её через
|
||||||
|
CLI или TOML. `auto` всегда выбирает ONNX CPU, ASR и VAD получают явный
|
||||||
|
`CPUExecutionProvider`. Команды установки и обновления — в
|
||||||
|
[README](../../README.md#подключение-cuda).
|
||||||
|
|
||||||
В `pyproject.toml`:
|
CTranslate2 4.8.1 и cuBLAS 12.9.1.4 закреплены в `pyproject.toml`, чтобы
|
||||||
```
|
установка через `uv tool` и из клона использовала проверенное сочетание.
|
||||||
"nvidia-cublas-cu12>=12.4; sys_platform == 'linux' and platform_machine == 'x86_64'"
|
В cuBLAS 12.9.2.10 появилась дополнительная зависимость NVRTC. Обновление
|
||||||
```
|
версий требует повторной проверки библиотек и bootstrap; одного обновления
|
||||||
|
lock-файла недостаточно.
|
||||||
|
|
||||||
Нижняя граница `>=12.4` — ctranslate2 собран с CUDA 12.4. Пакет доступен только
|
### Linux: preload по полному пути
|
||||||
для Linux x86_64 (на Windows и macOS не устанавливается по platform marker).
|
|
||||||
|
|
||||||
### ctypes.CDLL preload вместо LD_LIBRARY_PATH
|
`_cuda_bootstrap.py` загружает `libcublas.so.12` через `ctypes.CDLL` с
|
||||||
|
`RTLD_GLOBAL`. Линкер затем разрешает запрос CTranslate2 по soname.
|
||||||
|
Изменение `LD_LIBRARY_PATH` внутри процесса не решает задачу: glibc кеширует
|
||||||
|
пути поиска. Путь optional-пакета определяется через `nvidia.cublas.__path__`,
|
||||||
|
что поддерживает namespace package без `__file__`.
|
||||||
|
|
||||||
`_cuda_bootstrap.py` загружает `libcublas.so.12` по полному пути через
|
### Windows: регистрация каталога и preload DLL
|
||||||
`ctypes.CDLL(path, mode=RTLD_GLOBAL)` **до** первого `import ctranslate2`.
|
|
||||||
|
|
||||||
Почему не `os.environ["LD_LIBRARY_PATH"]`: на Linux/glibc динамический линкер (`ld.so`)
|
Bootstrap регистрирует `nvidia/cublas/bin` через `os.add_dll_directory`, затем
|
||||||
кеширует пути поиска при старте процесса и **не перечитывает** `LD_LIBRARY_PATH`
|
загружает по абсолютным путям сначала `cublasLt64_12.dll`, затем
|
||||||
из environ в рамках уже запущенного процесса.
|
`cublas64_12.dll`. Handles библиотек и регистрации каталога хранятся до
|
||||||
|
завершения процесса. Повторный вызов не загружает их заново. Это обеспечивает
|
||||||
|
доступность уже загруженных DLL для динамического загрузчика CTranslate2 и
|
||||||
|
не требует изменения системного PATH.
|
||||||
|
|
||||||
Почему `RTLD_GLOBAL`: без этого флага символы cublas не видны другим `.so`,
|
Bootstrap вызывается только при создании CUDA-модели, до локального импорта
|
||||||
загруженным позже (в т.ч. `libctranslate2.so`).
|
`WhisperModel`. Если CTranslate2 ранее импортирован для декодирования аудио,
|
||||||
|
preload всё равно выполняется до создания CUDA-модели. CPU-путь bootstrap не
|
||||||
|
вызывает. Отсутствие optional-пакета безопасно: системная cuBLAS всё ещё может
|
||||||
|
обслужить явно выбранную CUDA. Ошибка загрузки найденной Windows DLL сохраняется.
|
||||||
|
|
||||||
Динамический линкер кеширует загруженные библиотеки по soname — когда ctranslate2
|
### Ошибки и явный выбор
|
||||||
потом вызовет `dlopen("libcublas.so.12")`, линкер вернёт уже загруженный handle.
|
|
||||||
|
|
||||||
### strict_device для явного --device
|
Явный `device` из CLI или TOML передаёт `strict_device=True` при загрузке и
|
||||||
|
транскрипции. CUDA не заменяется молча на CPU. Подсказки различают отсутствие
|
||||||
|
библиотек, проблемы драйвера, несовместимость GPU/runtime и нехватку памяти;
|
||||||
|
отказ по неподдерживаемому типу вычислений учитывает устройство, чтобы
|
||||||
|
одинаковая ошибка CTranslate2 на CPU не получала CUDA-подсказку. Исходное
|
||||||
|
сообщение сохраняется. Неизвестная ошибка не получает произвольный
|
||||||
|
совет установить Toolkit. Это одинаково для одного файла и батча.
|
||||||
|
|
||||||
`--device cuda` / `--device cpu` → `strict_device=True` → CUDA-ошибка = raise, без fallback.
|
Прежний механизм fallback оркестратора при `strict_device=False` сохранён
|
||||||
`--device auto` → `strict_device=False` → текущее поведение с fallback на CPU.
|
для существующих программных callers. Автоматический CLI-путь теперь ONNX,
|
||||||
|
поэтому он не попадает в CUDA fallback.
|
||||||
|
|
||||||
Мотивация: silent fallback для часового файла = 60 минут вместо 5.
|
## Проверка зависимостей Windows
|
||||||
|
|
||||||
## Последствия и tradeoffs
|
Статическая проверка 2026-09-21: скачаны wheels из PyPI, проверены SHA-256,
|
||||||
|
для DLL/PYD прочитаны PE imports через `objdump -p` и строки динамической
|
||||||
|
загрузки. Модели распознавания не скачивались.
|
||||||
|
|
||||||
**~400 MB на CPU-only Linux x86_64**: nvidia-cublas-cu12 ставится на все Linux x86_64,
|
- `ctranslate2-4.8.1-cp313-cp313-win_amd64.whl`, SHA-256
|
||||||
включая машины без GPU. Bootstrap при этом preload'ит libcublas (overhead ~1 ms),
|
`d52499f05a60a791aeadee28d609efa130142f376d1ea76b2b1c593bb01f8827`.
|
||||||
но она не используется, т.к. ctranslate2 не получит запрос на CUDA device.
|
`ctranslate2.dll` содержит имена `cublas64_12.dll` и `nvcuda.dll`;
|
||||||
|
ссылок на cuDNN и динамическую cudart в этой DLL не обнаружено.
|
||||||
|
В wheel есть `cudnn64_9.dll`, но её наличие не означает использование
|
||||||
|
cuDNN проверенной рабочей DLL. OpenMP DLL поставляется внутри wheel;
|
||||||
|
импорты MSVCP/VCRUNTIME требуют Visual C++ Runtime.
|
||||||
|
- `nvidia_cublas_cu12-12.9.1.4-py3-none-win_amd64.whl`, SHA-256
|
||||||
|
`1e5fee10662e6e52bd71dec533fbbd4971bb70a5f24f3bc3793e5c2e9dc640bf`.
|
||||||
|
DLL находятся в `nvidia/cublas/bin`. cuBLAS импортирует cuBLAS Lt и
|
||||||
|
KERNEL32; cuBLAS Lt импортирует KERNEL32. В строках cuBLAS Lt также найдена
|
||||||
|
динамическая ссылка на `nvrtc64_120_0.dll`, которой нет в PE imports.
|
||||||
|
Обязательность NVRTC для Whisper этим статическим анализом не установлена;
|
||||||
|
первый реальный Windows-прогон должен проверить и этот путь загрузки.
|
||||||
|
`nvblas64_12.dll` не требуется проверенному пути CTranslate2.
|
||||||
|
|
||||||
Альтернатива — `[project.optional-dependencies]` + `uv sync --extra cuda`,
|
Основания: [CTranslate2 4.8.1 на PyPI](https://pypi.org/project/ctranslate2/4.8.1/#files),
|
||||||
но тогда теряется zero-config UX. Для v1 оставляем как обязательную зависимость.
|
[cuBLAS 12.9.1.4 на PyPI](https://pypi.org/project/nvidia-cublas-cu12/12.9.1.4/#files),
|
||||||
|
[загрузчик CTranslate2](https://github.com/OpenNMT/CTranslate2/blob/v4.8.1/python/ctranslate2/__init__.py),
|
||||||
|
[метаданные cuBLAS 12.9.2.10](https://pypi.org/pypi/nvidia-cublas-cu12/12.9.2.10/json).
|
||||||
|
|
||||||
**Windows GPU**: nvidia-cublas-cu12 недоступен как pip-пакет для Windows.
|
Это обоснование состава extra для конкретных wheels, а не подтверждение
|
||||||
Единственный путь — системный CUDA toolkit (`choco install cuda` / `winget install -e --id Nvidia.CUDA`).
|
транскрипции на Windows GPU. Реальный прогон Windows x64 с драйвером и extra,
|
||||||
CLI выводит эту подсказку при CUDA-ошибке на `sys.platform == "win32"`.
|
без Toolkit и ручной правки PATH, **не выполнен**. Linux-тесты с подменой
|
||||||
|
Windows API не заменяют такую проверку.
|
||||||
|
|
||||||
**Namespace package**: `nvidia.cublas` — namespace package (`__file__` is `None`),
|
## Проверка поставки и поведения
|
||||||
для определения директории используется `__path__[0]`, а не `__file__`.
|
|
||||||
|
|
||||||
## Отклонённые альтернативы
|
На Linux выполнены `uv run pytest`: 524 passed, 2 skipped. Пропуски —
|
||||||
|
Windows-only кодировка OEM и интеграционный bootstrap без optional-пакета.
|
||||||
|
До удаления cuBLAS интеграционный тест Linux bootstrap проходил.
|
||||||
|
Обычный `uv sync --locked` удаляет прежнюю обязательную cuBLAS;
|
||||||
|
`uv sync --extra cuda --locked --dry-run` возвращает её в план установки.
|
||||||
|
|
||||||
| Альтернатива | Почему отклонена |
|
Метаданные собранного wheel проверены для 12 сочетаний платформы, архитектуры
|
||||||
|---|---|
|
и extra. Полное разрешение зависимостей wheel через `uv pip compile` для Linux
|
||||||
| `nvidia-cudnn-cu12` в зависимостях | ctranslate2 не использует cuDNN — ноль символов, проверено через `nm -D` |
|
и Windows, с extra и без, подтверждает отсутствие NVIDIA-пакетов в обычной
|
||||||
| Self-reexec с `LD_LIBRARY_PATH` | ctypes.CDLL решает задачу без перезапуска процесса; self-reexec создаёт проблемы с сигналами, tty, fd |
|
установке и наличие только cuBLAS 12.9.1.4 при выборе extra.
|
||||||
| `os.environ["LD_LIBRARY_PATH"] += ...` | glibc кеширует пути при старте, не перечитывает environ |
|
|
||||||
| `DeviceResolution` dataclass + `resolve_device()` | Один `bool strict_device` решает ту же задачу проще |
|
Формы `uv tool install` для клона, клона с extra и именованной Git-зависимости
|
||||||
| ctranslate2 preflight (`get_supported_compute_types`) | Латентность; try/catch при загрузке модели не хуже |
|
с extra проверены в отдельном временном каталоге tools. Для проверки самой
|
||||||
| Bootstrap console_scripts entrypoint | Не нужен — достаточно вызова в начале `transcriber.py` |
|
команды зависимости были исключены; их разрешение проверено отдельно выше.
|
||||||
|
Git-транспорт проверялся через локальный репозиторий и ветку реализации,
|
||||||
|
а не через ещё не опубликованную ветку на сервере. Версия uv — 0.12.17.
|
||||||
|
|
||||||
|
## Последствия и альтернативы
|
||||||
|
|
||||||
|
- Обычная установка не требует NVIDIA-пакетов. FasterWhisper CPU сохраняется.
|
||||||
|
- Дополнительные библиотеки скачиваются только по выбору пользователя.
|
||||||
|
- Единый extra заменяет требование установить полный Toolkit на Windows;
|
||||||
|
существующая системная установка библиотек остаётся допустимой.
|
||||||
|
- Отклонено сохранение обязательной cuBLAS ради готовности CUDA сразу после
|
||||||
|
установки: это увеличивает CPU-установку и не решает несовместимость GPU.
|
||||||
|
- Отклонено автоматическое включение CUDA по драйверу или extra: установка
|
||||||
|
библиотек не должна менять движок и модель по умолчанию.
|
||||||
|
- Перезапуск процесса с `LD_LIBRARY_PATH` не нужен; явная загрузка библиотек
|
||||||
|
решает проблему без изменения обработки сигналов и файловых дескрипторов.
|
||||||
|
- CUDA extra на Windows ARM64, Linux ARM и macOS в эту задачу не входит.
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
**Статус**: Принято
|
**Статус**: Принято
|
||||||
**Дата**: 2026-03-21
|
**Дата**: 2026-03-21
|
||||||
**Обновлено**: 2026-08-12
|
**Обновлено**: 2026-09-21
|
||||||
|
|
||||||
## Контекст
|
## Контекст
|
||||||
|
|
||||||
@@ -32,8 +32,9 @@ Protocol вместо ABC — бэкенды не наследуются, дос
|
|||||||
### Ленивые импорты
|
### Ленивые импорты
|
||||||
|
|
||||||
Бэкенды импортируются только при выборе — `get_backend(device)` делает import внутри.
|
Бэкенды импортируются только при выборе — `get_backend(device)` делает import внутри.
|
||||||
Импорт faster-whisper запускает CUDA bootstrap (~1ms), импорт openvino-genai загружает ~50MB
|
CUDA bootstrap вызывается только при создании CUDA-модели (см. ADR-001).
|
||||||
shared libraries. Ни то, ни другое не должно происходить, если бэкенд не выбран.
|
Импорт openvino-genai загружает ~50MB shared libraries и не должен происходить,
|
||||||
|
если бэкенд не выбран.
|
||||||
|
|
||||||
### Device как селектор бэкенда
|
### Device как селектор бэкенда
|
||||||
|
|
||||||
@@ -41,7 +42,7 @@ shared libraries. Ни то, ни другое не должно происхо
|
|||||||
- `cuda`, `cpu` → FasterWhisperBackend
|
- `cuda`, `cpu` → FasterWhisperBackend
|
||||||
- `openvino` → OpenVINOBackend
|
- `openvino` → OpenVINOBackend
|
||||||
- `onnx` → OnnxAsrBackend
|
- `onnx` → OnnxAsrBackend
|
||||||
- `auto` → CUDA при наличии `nvidia-smi`, иначе ONNX на CPU
|
- `auto` → ONNX на CPU независимо от `nvidia-smi` и установки extra
|
||||||
|
|
||||||
### load_model() — единственный владелец pipeline
|
### load_model() — единственный владелец pipeline
|
||||||
|
|
||||||
@@ -54,7 +55,7 @@ CLI не вызывает ensure_model_available отдельно — это у
|
|||||||
Fallback живёт в `transcriber.py` (оркестратор), не в бэкендах:
|
Fallback живёт в `transcriber.py` (оркестратор), не в бэкендах:
|
||||||
- CUDA ошибка → CPU (FasterWhisper)
|
- CUDA ошибка → CPU (FasterWhisper)
|
||||||
- OpenVINO ошибка → CPU (FasterWhisper)
|
- OpenVINO ошибка → CPU (FasterWhisper)
|
||||||
- `strict_device=True` (явный `--device`) → ошибка без fallback
|
- `strict_device=True` (явный device из CLI или TOML) → ошибка без fallback
|
||||||
|
|
||||||
При fallback в батч-режиме обновляются model, backend, model_path и actual_device
|
При fallback в батч-режиме обновляются model, backend, model_path и actual_device
|
||||||
через TranscribeFileResult — следующий файл использует правильный бэкенд.
|
через TranscribeFileResult — следующий файл использует правильный бэкенд.
|
||||||
@@ -76,8 +77,8 @@ OpenVINO модели предквантизированы (int8/fp16), compute_
|
|||||||
### Зависимости бэкендов по умолчанию
|
### Зависимости бэкендов по умолчанию
|
||||||
|
|
||||||
faster-whisper, onnx-asr/onnxruntime и openvino-genai ставятся вместе. Модели
|
faster-whisper, onnx-asr/onnxruntime и openvino-genai ставятся вместе. Модели
|
||||||
скачиваются только для активного бэкенда. CUDA (`nvidia-cublas-cu12`) остаётся
|
скачиваются только для активного бэкенда. cuBLAS подключается через extra `cuda`
|
||||||
conditional для Linux x86_64. OpenVINO — conditional для x86_64/AMD64, кроме
|
для Linux/WSL x86_64 и Windows x64 (см. ADR-001). OpenVINO — conditional для x86_64/AMD64, кроме
|
||||||
macOS; ONNX обеспечивает автоматический CPU-путь на остальных платформах.
|
macOS; ONNX обеспечивает автоматический CPU-путь на остальных платформах.
|
||||||
|
|
||||||
## Последствия
|
## Последствия
|
||||||
|
|||||||
@@ -102,10 +102,12 @@ Mm-hmm-редукция и иностранные вставки **не обна
|
|||||||
## Решение
|
## Решение
|
||||||
|
|
||||||
**Принять onnx-asr как CPU-бэкенд автоматического профиля.
|
**Принять onnx-asr как CPU-бэкенд автоматического профиля.
|
||||||
GigaAM v3 E2E RNN-T — модель по умолчанию на машинах без CUDA.**
|
GigaAM v3 E2E RNN-T — модель автоматического профиля на всех машинах.**
|
||||||
|
|
||||||
Порядок `--device auto`: CUDA при наличии `nvidia-smi`, иначе ONNX. OpenVINO и
|
С 2026-09-21 `--device auto` всегда выбирает ONNX CPU; для ASR и VAD явно
|
||||||
FasterWhisper CPU остаются доступными через явный CLI-аргумент или конфиг.
|
передаётся `CPUExecutionProvider`, независимо от доступных providers. CUDA,
|
||||||
|
OpenVINO и FasterWhisper CPU доступны через явный CLI-аргумент или конфиг.
|
||||||
|
Подключение CUDA описано в [ADR-001](001-cuda-bootstrap.md).
|
||||||
|
|
||||||
GigaAM понимает только русскую речь, поэтому для остальных языков автоматический
|
GigaAM понимает только русскую речь, поэтому для остальных языков автоматический
|
||||||
профиль не годится — нужен явный Whisper. Несовместимый язык работу не
|
профиль не годится — нужен явный Whisper. Несовместимый язык работу не
|
||||||
|
|||||||
+25
-26
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Режимы `--device`
|
## Режимы `--device`
|
||||||
|
|
||||||
- `auto` (по умолчанию) — CUDA при наличии `nvidia-smi`, иначе ONNX на CPU
|
- `auto` (по умолчанию) — всегда ONNX на CPU
|
||||||
- `cuda` — строго NVIDIA GPU, ошибка если недоступен
|
- `cuda` — строго NVIDIA GPU, ошибка если недоступен
|
||||||
- `openvino` — авто-выбор OpenVINO GPU или CPU
|
- `openvino` — авто-выбор OpenVINO GPU или CPU
|
||||||
- `openvino-gpu` — строго Intel GPU через OpenVINO
|
- `openvino-gpu` — строго Intel GPU через OpenVINO
|
||||||
@@ -13,8 +13,8 @@
|
|||||||
|
|
||||||
| Оборудование | Рекомендуемый `--device` | Бэкенд | Ожидаемая скорость |
|
| Оборудование | Рекомендуемый `--device` | Бэкенд | Ожидаемая скорость |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| NVIDIA GPU (6+ GB VRAM) | `auto` / `cuda` | faster-whisper (CTranslate2) | 7-19x реалтайм |
|
| NVIDIA GPU (6+ GB VRAM) | `cuda` + extra | faster-whisper (CTranslate2) | 7-19x реалтайм |
|
||||||
| Любой CPU без NVIDIA | `auto` / `onnx` | ONNX GigaAM RNN-T | 10-14x реалтайм* |
|
| Любой CPU | `auto` / `onnx` | ONNX GigaAM RNN-T | 10-14x реалтайм* |
|
||||||
| Intel Arc iGPU / dGPU | `openvino-gpu` | OpenVINO GenAI (GPU) | TBD |
|
| Intel Arc iGPU / dGPU | `openvino-gpu` | OpenVINO GenAI (GPU) | TBD |
|
||||||
| Intel/AMD x86 CPU | `openvino-cpu` | OpenVINO GenAI (CPU) | 3-10x реалтайм* |
|
| Intel/AMD x86 CPU | `openvino-cpu` | OpenVINO GenAI (CPU) | 3-10x реалтайм* |
|
||||||
| Любой CPU, FasterWhisper | `cpu` | faster-whisper (CTranslate2) | ~1.5x реалтайм |
|
| Любой CPU, FasterWhisper | `cpu` | faster-whisper (CTranslate2) | ~1.5x реалтайм |
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
\* По результатам контрольных прогонов на Intel и AMD CPU. Реальная скорость
|
\* По результатам контрольных прогонов на Intel и AMD CPU. Реальная скорость
|
||||||
зависит от CPU, модели и записи.
|
зависит от CPU, модели и записи.
|
||||||
|
|
||||||
Автоматический профиль без NVIDIA рассчитан на русскую речь: GigaAM других
|
Автоматический профиль рассчитан на русскую речь: GigaAM других
|
||||||
языков не понимает. Для них берите Whisper — `openvino-cpu` на x86 или `cpu`
|
языков не понимает. Для них берите Whisper — `openvino-cpu` на x86 или `cpu`
|
||||||
на любой платформе.
|
на любой платформе.
|
||||||
|
|
||||||
@@ -211,22 +211,16 @@ SMT/Hyper-Threading не помогает — 16 потоков на 8-ядер
|
|||||||
|
|
||||||
## Настройка по платформам
|
## Настройка по платформам
|
||||||
|
|
||||||
### Linux / WSL2 (x86_64)
|
Для Linux/WSL x86_64 и Windows x64 используется один extra `cuda` и явный
|
||||||
|
выбор `--device cuda` либо `device = "cuda"` в TOML. Команды установки из Git,
|
||||||
|
клона и для разработки приведены в [README](../README.md#подключение-cuda).
|
||||||
|
Обычный запуск всегда использует ONNX CPU, независимо от драйвера и extra.
|
||||||
|
|
||||||
Библиотека cuBLAS ставится автоматически (зависимость `nvidia-cublas-cu12` подтягивается при установке).
|
Драйвер NVIDIA устанавливается отдельно. Полный системный CUDA Toolkit не
|
||||||
Дополнительных шагов не требуется.
|
нужен для пути через extra; на Windows требуется Visual C++ Runtime x64.
|
||||||
|
Состав Windows DLL проверен статически; реальная транскрипция без Toolkit
|
||||||
> На ARM (aarch64) cuBLAS через pip недоступен — нужен системный CUDA toolkit.
|
пока не проверена. Версии и границы проверки — [ADR-001](adr/001-cuda-bootstrap.md).
|
||||||
|
CUDA extra для ARM в этой задаче не поддерживается.
|
||||||
### Windows
|
|
||||||
|
|
||||||
Нужен системный **CUDA 12** (ctranslate2 4.7 не совместим с CUDA 11 и 13):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
winget install -e --id Nvidia.CUDA --version 12.9 # требует запуска от имени администратора
|
|
||||||
```
|
|
||||||
|
|
||||||
После установки перезапустите терминал.
|
|
||||||
|
|
||||||
## Совместимость GPU
|
## Совместимость GPU
|
||||||
|
|
||||||
@@ -331,14 +325,19 @@ threshold не помогла — проблема inherent для модели
|
|||||||
Убедитесь, что `nvidia-smi` возвращает информацию о GPU. Драйвер NVIDIA предоставляет `libcuda.so.1`,
|
Убедитесь, что `nvidia-smi` возвращает информацию о GPU. Драйвер NVIDIA предоставляет `libcuda.so.1`,
|
||||||
без которого CUDA не работает — его нельзя поставить через pip.
|
без которого CUDA не работает — его нельзя поставить через pip.
|
||||||
|
|
||||||
### Windows: ошибка при загрузке модели на GPU
|
### Отсутствующие библиотеки и несовместимый GPU
|
||||||
|
|
||||||
GPU на Windows требует **CUDA 12** (ctranslate2 4.7 не совместим с CUDA 11 и 13). Установите:
|
При отсутствии cuBLAS подключите extra в том окружении, откуда запускается
|
||||||
```bash
|
программа, по [инструкции README](../README.md#подключение-cuda).
|
||||||
winget install -e --id Nvidia.CUDA --version 12.9 # требует запуска от имени администратора
|
На Windows ошибка загрузки DLL может также означать отсутствие Visual C++ Runtime.
|
||||||
```
|
|
||||||
После установки перезапустите терминал.
|
`no kernel image` / `invalid device function` означают несовместимость GPU
|
||||||
|
с выбранной сборкой runtime. Установка extra её не исправит. Ошибки драйвера
|
||||||
|
требуют проверки драйвера; нехватка VRAM — меньшей модели или явного выбора CPU.
|
||||||
|
Исходная ошибка сохраняется. CLI/TOML с явной CUDA не переходят молча на CPU
|
||||||
|
ни при загрузке, ни при распознавании.
|
||||||
|
|
||||||
### ARM (aarch64)
|
### ARM (aarch64)
|
||||||
|
|
||||||
cuBLAS через pip недоступен на ARM — нужен системный CUDA toolkit.
|
CUDA extra не поддерживается проектом на этой архитектуре. Наличие отдельных
|
||||||
|
NVIDIA wheels для ARM само по себе не подтверждает рабочий путь транскрипции.
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
# Владение выполнением транскрипции
|
# Владение выполнением транскрипции
|
||||||
|
|
||||||
**Статус:** согласовано для постановки задач. Реализация не начата.
|
**Статус:** согласовано. Правила выбора исполнения и optional CUDA реализованы
|
||||||
|
в #2; рефакторинг владения выполнением не начат. Windows GPU-прогон без Toolkit
|
||||||
|
остаётся невыполненным (см. ADR-001).
|
||||||
|
|
||||||
## Зачем
|
## Зачем
|
||||||
|
|
||||||
@@ -14,11 +16,11 @@
|
|||||||
отдельно. Это увеличивает interface: caller должен знать порядок вызовов
|
отдельно. Это увеличивает interface: caller должен знать порядок вызовов
|
||||||
и поддерживать согласованность значений.
|
и поддерживать согласованность значений.
|
||||||
|
|
||||||
`device` одновременно обозначает движок и аппаратное исполнение. Значение
|
`device` одновременно обозначает движок и аппаратное исполнение. До #2 значение
|
||||||
`onnx` выбирает adapter, но не закрепляет CPU: установленный `onnx-asr 0.12.0`
|
`onnx` выбирало adapter, но не закрепляло CPU: `onnx-asr` по умолчанию использует
|
||||||
по умолчанию использует доступные execution providers. При этом CLI безусловно
|
доступные execution providers, хотя CLI писал `ONNX (CPU)`. В #2 для ASR и VAD
|
||||||
пишет `ONNX (CPU)`. Параметр `cpu_threads` принимается ONNX adapter, но не
|
закреплён CPU provider. Параметр `cpu_threads` всё ещё принимается ONNX adapter,
|
||||||
используется при загрузке ASR и VAD.
|
но не используется при загрузке ASR и VAD.
|
||||||
|
|
||||||
## Согласованные ограничения
|
## Согласованные ограничения
|
||||||
|
|
||||||
@@ -72,7 +74,9 @@ model/backend/path/device. Locality — изменения загрузки и
|
|||||||
## Выбор исполнения и ошибки
|
## Выбор исполнения и ошибки
|
||||||
|
|
||||||
Ручное включение CUDA состоит из двух явных действий: установки зависимостей
|
Ручное включение CUDA состоит из двух явных действий: установки зависимостей
|
||||||
и выбора CUDA через CLI либо TOML. CUDA не участвует
|
и выбора CUDA через CLI либо TOML. Extra `cuda` поддерживает Linux/WSL x86_64
|
||||||
|
и Windows x64 без обязательного системного Toolkit; совместимый драйвер
|
||||||
|
устанавливается отдельно. CUDA не участвует
|
||||||
в `auto`, даже если найден `nvidia-smi` или установлены её библиотеки.
|
в `auto`, даже если найден `nvidia-smi` или установлены её библиотеки.
|
||||||
Так установка драйвера не меняет движок и модель по умолчанию.
|
Так установка драйвера не меняет движок и модель по умолчанию.
|
||||||
|
|
||||||
@@ -121,7 +125,8 @@ Mock-тесты не подтверждают ускорение или совм
|
|||||||
## Документация и прежние решения
|
## Документация и прежние решения
|
||||||
|
|
||||||
README должен различать обычную установку и подключение CUDA: дополнительные
|
README должен различать обычную установку и подключение CUDA: дополнительные
|
||||||
зависимости Linux/WSL, требования Windows, явный запуск, настройку TOML
|
зависимости через один extra на Linux/WSL x86_64 и Windows x64, внешние
|
||||||
|
требования драйвера и Visual C++ Runtime, явный запуск, настройку TOML
|
||||||
и диагностику отсутствующих библиотек или несовместимого железа. Команды
|
и диагностику отсутствующих библиотек или несовместимого железа. Команды
|
||||||
установки из клона и через `uv tool` проверяются при реализации. CUDA extra
|
установки из клона и через `uv tool` проверяются при реализации. CUDA extra
|
||||||
оформляется отдельным изменением, чтобы поставку можно было проверить
|
оформляется отдельным изменением, чтобы поставку можно было проверить
|
||||||
|
|||||||
+8
-1
@@ -8,16 +8,23 @@ dependencies = [
|
|||||||
"typer>=0.24.1,<1",
|
"typer>=0.24.1,<1",
|
||||||
"rich>=14.3.3,<15",
|
"rich>=14.3.3,<15",
|
||||||
"faster-whisper>=1.2.1,<2",
|
"faster-whisper>=1.2.1,<2",
|
||||||
|
# CUDA-зависимости Windows проверены для этой сборки; см. ADR-001.
|
||||||
|
"ctranslate2==4.8.1",
|
||||||
"httpx>=0.28,<1",
|
"httpx>=0.28,<1",
|
||||||
"socksio>=1.0.0,<2",
|
"socksio>=1.0.0,<2",
|
||||||
# При обновлении сверить блок tool.uv.dependency-metadata ниже.
|
# При обновлении сверить блок tool.uv.dependency-metadata ниже.
|
||||||
"sherpa-onnx==1.13.6",
|
"sherpa-onnx==1.13.6",
|
||||||
"nvidia-cublas-cu12>=12.4,<13; sys_platform == 'linux' and platform_machine == 'x86_64'",
|
|
||||||
"openvino-genai>=2026.3.0.0,<2026.4; 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",
|
"onnx-asr[cpu,hub]>=0.12,<0.13",
|
||||||
"onnxruntime>=1.28,<2",
|
"onnxruntime>=1.28,<2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
cuda = [
|
||||||
|
# В 12.9.2 добавилась NVRTC: обновлять после проверки DLL и bootstrap.
|
||||||
|
"nvidia-cublas-cu12==12.9.1.4; (sys_platform == 'linux' and platform_machine == 'x86_64') or (sys_platform == 'win32' and (platform_machine == 'AMD64' or platform_machine == 'x86_64'))",
|
||||||
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
transcribe = "local_transcriber.cli:app"
|
transcribe = "local_transcriber.cli:app"
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
"""
|
"""
|
||||||
Preload CUDA-библиотек из pip-пакетов до импорта ctranslate2.
|
Подготовка CUDA-библиотек из extra перед созданием CUDA-модели.
|
||||||
|
|
||||||
Проблема: ctranslate2 на Linux делает dlopen("libcublas.so.12"),
|
Проблема: ctranslate2 на Linux делает dlopen("libcublas.so.12"),
|
||||||
но не знает, что библиотека лежит внутри pip-пакета nvidia-cublas-cu12.
|
но не знает, что библиотека лежит внутри pip-пакета nvidia-cublas-cu12.
|
||||||
На Windows ctranslate2 решает это сам через os.add_dll_directory.
|
На Windows ctranslate2 не добавляет nvidia/cublas/bin в поиск DLL.
|
||||||
|
Регистрируем каталог и загружаем cuBLAS Lt, затем cuBLAS по полному пути.
|
||||||
|
Храним DLL и регистрацию каталога до завершения процесса.
|
||||||
|
|
||||||
Решение: загружаем libcublas.so.12 по полному пути через ctypes.CDLL
|
Решение: загружаем libcublas.so.12 по полному пути через ctypes.CDLL
|
||||||
с флагом RTLD_GLOBAL до первого import ctranslate2. Динамический линкер
|
с флагом RTLD_GLOBAL до создания CUDA-модели. Динамический линкер
|
||||||
кеширует загруженные библиотеки по soname — когда ctranslate2 потом
|
кеширует загруженные библиотеки по soname — когда ctranslate2 потом
|
||||||
вызовет dlopen("libcublas.so.12"), линкер вернёт уже загруженный handle.
|
вызовет dlopen("libcublas.so.12"), линкер вернёт уже загруженный handle.
|
||||||
|
|
||||||
@@ -21,20 +23,24 @@ import glob
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
# Закрытие регистрации удаляет каталог из поиска DLL, поэтому храним handles.
|
||||||
|
_dll_directories: dict[str, object] = {}
|
||||||
|
_dll_libraries: dict[str, object] = {}
|
||||||
|
|
||||||
|
|
||||||
def ensure_cublas_loadable() -> None:
|
def ensure_cublas_loadable() -> None:
|
||||||
"""Загружает libcublas из nvidia-cublas-cu12 в адресное пространство процесса.
|
"""Загружает cuBLAS из nvidia-cublas-cu12 в адресное пространство процесса.
|
||||||
|
|
||||||
Вызывать ДО первого import ctranslate2.
|
Вызывать перед созданием CUDA-модели; CPU-путь не требует bootstrap.
|
||||||
Безопасно вызывать многократно и на платформах без nvidia-cublas-cu12.
|
Безопасно вызывать многократно и на платформах без nvidia-cublas-cu12.
|
||||||
"""
|
"""
|
||||||
if sys.platform != "linux":
|
if sys.platform not in ("linux", "win32"):
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import nvidia.cublas # type: ignore[import-untyped]
|
import nvidia.cublas # type: ignore[import-untyped]
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# nvidia-cublas-cu12 не установлен (Windows, macOS, или CPU-only setup)
|
# Optional-пакет не установлен; системная cuBLAS всё ещё может работать.
|
||||||
return
|
return
|
||||||
|
|
||||||
# nvidia.cublas может быть namespace package (__file__ == None),
|
# nvidia.cublas может быть namespace package (__file__ == None),
|
||||||
@@ -42,6 +48,21 @@ def ensure_cublas_loadable() -> None:
|
|||||||
cublas_paths = getattr(nvidia.cublas, "__path__", None)
|
cublas_paths = getattr(nvidia.cublas, "__path__", None)
|
||||||
if not cublas_paths:
|
if not cublas_paths:
|
||||||
return
|
return
|
||||||
|
if sys.platform == "win32":
|
||||||
|
for package_path in cublas_paths:
|
||||||
|
bin_dir = os.path.join(package_path, "bin")
|
||||||
|
if not os.path.isdir(bin_dir):
|
||||||
|
continue
|
||||||
|
if bin_dir not in _dll_directories:
|
||||||
|
_dll_directories[bin_dir] = os.add_dll_directory(bin_dir)
|
||||||
|
# cuBLAS зависит от Lt; явный preload также обслуживает LoadLibrary
|
||||||
|
# внутри CTranslate2. Порядок здесь существенен.
|
||||||
|
for filename in ("cublasLt64_12.dll", "cublas64_12.dll"):
|
||||||
|
dll_path = os.path.join(bin_dir, filename)
|
||||||
|
if dll_path not in _dll_libraries:
|
||||||
|
_dll_libraries[dll_path] = ctypes.WinDLL(dll_path)
|
||||||
|
return
|
||||||
|
return
|
||||||
cublas_lib_dir = os.path.join(cublas_paths[0], "lib")
|
cublas_lib_dir = os.path.join(cublas_paths[0], "lib")
|
||||||
if not os.path.isdir(cublas_lib_dir):
|
if not os.path.isdir(cublas_lib_dir):
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -6,16 +6,11 @@ from collections.abc import Callable
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
# CUDA bootstrap — должен быть ДО импорта faster_whisper / ctranslate2
|
from huggingface_hub import snapshot_download
|
||||||
|
from huggingface_hub.errors import LocalEntryNotFoundError
|
||||||
|
|
||||||
from local_transcriber._cuda_bootstrap import ensure_cublas_loadable
|
from local_transcriber._cuda_bootstrap import ensure_cublas_loadable
|
||||||
|
from local_transcriber.types import (
|
||||||
ensure_cublas_loadable()
|
|
||||||
|
|
||||||
from faster_whisper import WhisperModel # noqa: E402
|
|
||||||
from huggingface_hub import snapshot_download # noqa: E402
|
|
||||||
from huggingface_hub.errors import LocalEntryNotFoundError # noqa: E402
|
|
||||||
|
|
||||||
from local_transcriber.types import ( # noqa: E402
|
|
||||||
Segment,
|
Segment,
|
||||||
TranscribeResult,
|
TranscribeResult,
|
||||||
Word,
|
Word,
|
||||||
@@ -94,6 +89,11 @@ class FasterWhisperBackend:
|
|||||||
|
|
||||||
cpu_threads: число потоков для CPU inference (0 = дефолт библиотеки, обычно 4).
|
cpu_threads: число потоков для CPU inference (0 = дефолт библиотеки, обычно 4).
|
||||||
"""
|
"""
|
||||||
|
if device == "cuda":
|
||||||
|
ensure_cublas_loadable()
|
||||||
|
|
||||||
|
from faster_whisper import WhisperModel
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return WhisperModel(
|
return WhisperModel(
|
||||||
model_path,
|
model_path,
|
||||||
|
|||||||
@@ -200,8 +200,9 @@ class OnnxAsrBackend:
|
|||||||
model = onnx_asr.load_model(
|
model = onnx_asr.load_model(
|
||||||
model=model_path,
|
model=model_path,
|
||||||
quantization=quantization,
|
quantization=quantization,
|
||||||
|
providers=["CPUExecutionProvider"],
|
||||||
)
|
)
|
||||||
vad = onnx_asr.load_vad("silero")
|
vad = onnx_asr.load_vad("silero", providers=["CPUExecutionProvider"])
|
||||||
self._vad = vad
|
self._vad = vad
|
||||||
return model.with_vad(vad).with_timestamps()
|
return model.with_vad(vad).with_timestamps()
|
||||||
|
|
||||||
@@ -278,7 +279,7 @@ class OnnxAsrBackend:
|
|||||||
fallback = _whisper_fallback_model(model_name)
|
fallback = _whisper_fallback_model(model_name)
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Модель '{model_name}' относится к Whisper и не поддерживается "
|
f"Модель '{model_name}' относится к Whisper и не поддерживается "
|
||||||
"ONNX-бэкендом. Без CUDA --device auto выбирает ONNX; "
|
"ONNX-бэкендом. --device auto всегда выбирает ONNX CPU; "
|
||||||
f"укажите --device openvino-cpu --model {model_name} на x86, "
|
f"укажите --device openvino-cpu --model {model_name} на x86, "
|
||||||
f"--device cpu --model {fallback} на любой платформе "
|
f"--device cpu --model {fallback} на любой платформе "
|
||||||
f"или --device cuda --model {fallback} при NVIDIA GPU."
|
f"или --device cuda --model {fallback} при NVIDIA GPU."
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ from .speaker_diarizer import SpeakerDiarizer, load_speaker_diarizer
|
|||||||
from .transcriber import (
|
from .transcriber import (
|
||||||
Segment,
|
Segment,
|
||||||
TranscribeResult,
|
TranscribeResult,
|
||||||
_is_cuda_error,
|
|
||||||
_transcribe_file,
|
_transcribe_file,
|
||||||
|
cuda_error_hint,
|
||||||
load_model,
|
load_model,
|
||||||
)
|
)
|
||||||
from .types import (
|
from .types import (
|
||||||
@@ -65,6 +65,13 @@ app = typer.Typer()
|
|||||||
console = Console(stderr=True)
|
console = Console(stderr=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _print_cuda_hint(exc: BaseException, device: str | None = None) -> None:
|
||||||
|
"""Показывает подсказку, не заменяя исходную ошибку."""
|
||||||
|
hint = cuda_error_hint(exc, device=device)
|
||||||
|
if hint:
|
||||||
|
console.print(hint, style="yellow", markup=False)
|
||||||
|
|
||||||
|
|
||||||
def _show_cli_warning(
|
def _show_cli_warning(
|
||||||
message: Warning | str,
|
message: Warning | str,
|
||||||
*_args: object,
|
*_args: object,
|
||||||
@@ -341,6 +348,7 @@ def main(
|
|||||||
)
|
)
|
||||||
raise SystemExit(2)
|
raise SystemExit(2)
|
||||||
|
|
||||||
|
resolved_device: str | None = None
|
||||||
try:
|
try:
|
||||||
config = load_config()
|
config = load_config()
|
||||||
cli_values: CliValues = {
|
cli_values: CliValues = {
|
||||||
@@ -398,21 +406,15 @@ def main(
|
|||||||
except SystemExit:
|
except SystemExit:
|
||||||
raise
|
raise
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
|
_print_cuda_hint(exc, resolved_device)
|
||||||
console.print(f"Ошибка: {exc}", style="red bold")
|
console.print(f"Ошибка: {exc}", style="red bold")
|
||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
except (FileNotFoundError,) as exc:
|
except (FileNotFoundError,) as exc:
|
||||||
|
_print_cuda_hint(exc, resolved_device)
|
||||||
console.print(f"Ошибка: {exc}", style="red bold")
|
console.print(f"Ошибка: {exc}", style="red bold")
|
||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
if _is_cuda_error(exc) and sys.platform == "win32":
|
_print_cuda_hint(exc, resolved_device)
|
||||||
console.print(
|
|
||||||
"GPU на Windows требует CUDA toolkit (включает cuBLAS).\n"
|
|
||||||
"Установите одним из способов:\n"
|
|
||||||
" choco install cuda\n"
|
|
||||||
" winget install -e --id Nvidia.CUDA\n"
|
|
||||||
"После установки перезапустите терминал.",
|
|
||||||
style="yellow",
|
|
||||||
)
|
|
||||||
if verbose:
|
if verbose:
|
||||||
console.print_exception()
|
console.print_exception()
|
||||||
else:
|
else:
|
||||||
@@ -758,6 +760,7 @@ def _run_batch(
|
|||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
raise
|
raise
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
_print_cuda_hint(exc, actual_device)
|
||||||
if verbose:
|
if verbose:
|
||||||
console.print_exception()
|
console.print_exception()
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -190,11 +190,60 @@ def ensure_model_available(
|
|||||||
|
|
||||||
|
|
||||||
def _is_cuda_error(exc: BaseException) -> bool:
|
def _is_cuda_error(exc: BaseException) -> bool:
|
||||||
"""Проверка CUDA ошибок — используется в cli.py для Windows-диагностики."""
|
"""Проверяет, относится ли ошибка к CUDA-бэкенду."""
|
||||||
msg = str(exc).lower()
|
msg = str(exc).lower()
|
||||||
return any(k in msg for k in ("cuda", "cublas", "cudnn", "out of memory"))
|
return any(k in msg for k in ("cuda", "cublas", "cudnn", "out of memory"))
|
||||||
|
|
||||||
|
|
||||||
|
def cuda_error_hint(exc: BaseException, *, device: str | None = None) -> str | None:
|
||||||
|
"""Подсказывает действие только для распознанной причины CUDA-ошибки."""
|
||||||
|
msg = str(exc).lower()
|
||||||
|
if device == "cuda" and "requested " in msg and "compute type" in msg and (
|
||||||
|
"do not support efficient" in msg
|
||||||
|
):
|
||||||
|
return (
|
||||||
|
"Тип вычислений несовместим с выбранным GPU/CUDA runtime. "
|
||||||
|
"Установка extra не добавит аппаратную поддержку. Выберите "
|
||||||
|
"поддерживаемый --compute-type или --device onnx / --device cpu."
|
||||||
|
)
|
||||||
|
if any(k in msg for k in (
|
||||||
|
"no kernel image", "invalid device function", "unsupported gpu",
|
||||||
|
"cublas_status_arch_mismatch", "cuda_error_no_binary_for_gpu",
|
||||||
|
)):
|
||||||
|
return (
|
||||||
|
"GPU несовместим с выбранным CUDA runtime. Установка extra не исправит "
|
||||||
|
"аппаратную несовместимость. Используйте --device onnx или --device cpu."
|
||||||
|
)
|
||||||
|
if any(k in msg for k in (
|
||||||
|
"driver version is insufficient", "no cuda-capable device",
|
||||||
|
"cuda error: no device", "cuda_error_no_device", "libcuda.so", "nvcuda.dll",
|
||||||
|
)):
|
||||||
|
return (
|
||||||
|
"CUDA не видит совместимого GPU/драйвера. Проверьте драйвер NVIDIA "
|
||||||
|
"и совместимость устройства с runtime. Extra не устанавливает драйвер."
|
||||||
|
)
|
||||||
|
if any(k in msg for k in ("cublas", "cudnn", "cudart")) and any(
|
||||||
|
k in msg for k in (
|
||||||
|
"not found", "cannot be loaded", "could not load", "could not find",
|
||||||
|
"cannot open shared object", "no such file", "winerror 126",
|
||||||
|
)
|
||||||
|
):
|
||||||
|
return (
|
||||||
|
"Не найдены CUDA-библиотеки или их зависимости. Подключите extra cuda "
|
||||||
|
"в окружении приложения:\n"
|
||||||
|
" Из клона: uv tool install --force '.[cuda]'\n"
|
||||||
|
" Для разработки: uv sync --extra cuda\n"
|
||||||
|
"При установке из Git используйте инструкцию CUDA в README. "
|
||||||
|
"На Windows также нужен Visual C++ Runtime x64."
|
||||||
|
)
|
||||||
|
if "out of memory" in msg and any(k in msg for k in ("cuda", "cublas", "cudnn")):
|
||||||
|
return (
|
||||||
|
"Недостаточно памяти для выбранного CUDA-пути. Выберите меньшую модель "
|
||||||
|
"или явно используйте --device onnx / --device cpu."
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _is_backend_error(exc: BaseException, device: str) -> bool:
|
def _is_backend_error(exc: BaseException, device: str) -> bool:
|
||||||
"""Определяет, связана ли ошибка с конкретным бэкендом (а не с пользовательскими данными)."""
|
"""Определяет, связана ли ошибка с конкретным бэкендом (а не с пользовательскими данными)."""
|
||||||
if isinstance(exc, WordTimestampsUnavailableError):
|
if isinstance(exc, WordTimestampsUnavailableError):
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import glob
|
import glob
|
||||||
import platform
|
import platform
|
||||||
import shutil
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import warnings
|
import warnings
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -16,15 +15,13 @@ SUPPORTED_EXTENSIONS = {
|
|||||||
def detect_device(requested: str = "auto") -> str:
|
def detect_device(requested: str = "auto") -> str:
|
||||||
"""Определяет устройство для вычислений.
|
"""Определяет устройство для вычислений.
|
||||||
|
|
||||||
При ``requested="auto"`` выбирает CUDA при наличии ``nvidia-smi``,
|
При ``requested="auto"`` всегда выбирает ONNX на CPU.
|
||||||
иначе ONNX на CPU.
|
CUDA включается только явным выбором пользователя.
|
||||||
``"openvino"`` — алиас для авто-детекта внутри OpenVINO
|
``"openvino"`` — алиас для авто-детекта внутри OpenVINO
|
||||||
(GPU если доступен, иначе CPU).
|
(GPU если доступен, иначе CPU).
|
||||||
Возвращает всегда конкретное значение (не абстрактный ``"openvino"``).
|
Возвращает всегда конкретное значение (не абстрактный ``"openvino"``).
|
||||||
"""
|
"""
|
||||||
if requested == "auto":
|
if requested == "auto":
|
||||||
if shutil.which("nvidia-smi") is not None:
|
|
||||||
return "cuda"
|
|
||||||
return "onnx"
|
return "onnx"
|
||||||
if requested == "openvino":
|
if requested == "openvino":
|
||||||
if _is_openvino_gpu_available():
|
if _is_openvino_gpu_available():
|
||||||
|
|||||||
@@ -7,6 +7,25 @@ from local_transcriber.backends.faster_whisper import FasterWhisperBackend
|
|||||||
from local_transcriber.types import Word
|
from local_transcriber.types import Word
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("device", ["cpu", "cuda"])
|
||||||
|
def test_bootstrap_only_for_explicit_cuda(monkeypatch, device):
|
||||||
|
"""CPU-модель не загружает optional CUDA-библиотеки."""
|
||||||
|
bootstrap = MagicMock()
|
||||||
|
model_factory = MagicMock()
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"local_transcriber.backends.faster_whisper.ensure_cublas_loadable", bootstrap
|
||||||
|
)
|
||||||
|
monkeypatch.setattr("faster_whisper.WhisperModel", model_factory)
|
||||||
|
|
||||||
|
model = FasterWhisperBackend().create_model("/model", device, "float32")
|
||||||
|
|
||||||
|
assert model is model_factory.return_value
|
||||||
|
assert bootstrap.call_count == (1 if device == "cuda" else 0)
|
||||||
|
model_factory.assert_called_once_with(
|
||||||
|
"/model", device=device, compute_type="float32", cpu_threads=0
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_transcribe_returns_canonical_words(tmp_path):
|
def test_transcribe_returns_canonical_words(tmp_path):
|
||||||
audio = tmp_path / "audio.wav"
|
audio = tmp_path / "audio.wav"
|
||||||
raw_word = SimpleNamespace(start=0.2, end=0.7, word=" Привет")
|
raw_word = SimpleNamespace(start=0.2, end=0.7, word=" Привет")
|
||||||
|
|||||||
+114
-3
@@ -688,7 +688,7 @@ def test_cli_load_model_called_with_model_name(tmp_path):
|
|||||||
|
|
||||||
|
|
||||||
def test_cli_windows_cuda_diagnostic(tmp_path):
|
def test_cli_windows_cuda_diagnostic(tmp_path):
|
||||||
"""CUDA error on Windows prints choco/winget install hint."""
|
"""При проблеме драйвера Windows не предлагает установку Toolkit."""
|
||||||
audio = tmp_path / "test.mp3"
|
audio = tmp_path / "test.mp3"
|
||||||
audio.write_bytes(b"fake")
|
audio.write_bytes(b"fake")
|
||||||
model = _make_model()
|
model = _make_model()
|
||||||
@@ -712,8 +712,10 @@ def test_cli_windows_cuda_diagnostic(tmp_path):
|
|||||||
out = runner.invoke(app, [str(audio), "--device", "cuda"])
|
out = runner.invoke(app, [str(audio), "--device", "cuda"])
|
||||||
|
|
||||||
assert out.exit_code == 1
|
assert out.exit_code == 1
|
||||||
assert "choco install cuda" in out.output
|
assert "драйвер" in out.output
|
||||||
assert "winget install" in out.output
|
assert "CUDA error: no device" in out.output
|
||||||
|
assert "winget install" not in out.output
|
||||||
|
assert "uv sync --extra" not in out.output
|
||||||
|
|
||||||
|
|
||||||
def test_cli_linux_cuda_error_no_windows_hint(tmp_path):
|
def test_cli_linux_cuda_error_no_windows_hint(tmp_path):
|
||||||
@@ -744,6 +746,115 @@ def test_cli_linux_cuda_error_no_windows_hint(tmp_path):
|
|||||||
assert "choco install cuda" not in out.output
|
assert "choco install cuda" not in out.output
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("from_config", [False, True])
|
||||||
|
@pytest.mark.parametrize("phase", ["load", "single", "batch"])
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("message", "hint", "install_hint"),
|
||||||
|
[
|
||||||
|
("Library cublas64_12.dll is not found or cannot be loaded", "Не найдены", True),
|
||||||
|
("libcublas.so.12: cannot open shared object file", "Не найдены", True),
|
||||||
|
("CUDA error: no kernel image is available", "GPU несовместим", False),
|
||||||
|
("CUDA driver version is insufficient for CUDA runtime version", "драйвер", False),
|
||||||
|
("CUDA out of memory", "Недостаточно памяти", False),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_explicit_cuda_errors_preserve_cause_without_fallback(
|
||||||
|
tmp_path, monkeypatch, from_config, phase, message, hint, install_hint
|
||||||
|
):
|
||||||
|
"""CLI/TOML сохраняют CUDA при загрузке, обработке одного файла и батча."""
|
||||||
|
monkeypatch.chdir(tmp_path)
|
||||||
|
files = [tmp_path / "one.wav"]
|
||||||
|
if phase == "batch":
|
||||||
|
files.append(tmp_path / "two.wav")
|
||||||
|
for file in files:
|
||||||
|
file.write_bytes(b"audio")
|
||||||
|
if from_config:
|
||||||
|
(tmp_path / ".transcriber.toml").write_text('device = "cuda"\n')
|
||||||
|
args = [str(file) for file in files]
|
||||||
|
if not from_config:
|
||||||
|
args += ["--device", "cuda"]
|
||||||
|
backend = _make_backend()
|
||||||
|
error = RuntimeError(message)
|
||||||
|
if phase == "load":
|
||||||
|
backend.create_model.side_effect = error
|
||||||
|
else:
|
||||||
|
backend.transcribe.side_effect = error
|
||||||
|
|
||||||
|
with patch("local_transcriber.transcriber.get_backend", return_value=backend) as get_backend:
|
||||||
|
out = runner.invoke(app, args)
|
||||||
|
|
||||||
|
assert out.exit_code == 1
|
||||||
|
assert message in out.output
|
||||||
|
assert hint in out.output
|
||||||
|
assert ("uv sync --extra cuda" in out.output) is install_hint
|
||||||
|
assert "winget install" not in out.output
|
||||||
|
assert "Переключение на CPU" not in out.output
|
||||||
|
assert all(call.args[0] == "cuda" for call in get_backend.call_args_list)
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_cli_stays_on_onnx_with_nvidia_driver(tmp_path, monkeypatch):
|
||||||
|
"""Обычный запуск с nvidia-smi использует прежнюю CPU-модель ONNX."""
|
||||||
|
audio = tmp_path / "test.wav"
|
||||||
|
audio.write_bytes(b"audio")
|
||||||
|
backend = _make_backend()
|
||||||
|
backend.actual_ov_device = None
|
||||||
|
backend.actual_compute_type = "int8"
|
||||||
|
backend.transcribe.return_value = _make_result(device_used="onnx")
|
||||||
|
with (
|
||||||
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
|
patch("shutil.which", return_value="/usr/bin/nvidia-smi"),
|
||||||
|
patch("local_transcriber.transcriber.get_backend", return_value=backend) as get_backend,
|
||||||
|
patch("local_transcriber.cli.write_transcript"),
|
||||||
|
):
|
||||||
|
out = runner.invoke(app, [str(audio)])
|
||||||
|
|
||||||
|
assert out.exit_code == 0, out.output
|
||||||
|
get_backend.assert_called_once_with("onnx", compute_type_explicit=False)
|
||||||
|
assert backend.ensure_model_available.call_args.args[:2] == ("gigaam-v3-e2e-rnnt", "int8")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("from_config", [False, True])
|
||||||
|
@pytest.mark.parametrize("phase", ["load", "single", "batch"])
|
||||||
|
@pytest.mark.parametrize("device", ["cuda", "cpu"])
|
||||||
|
@pytest.mark.parametrize("compute_type", ["float16", "int8"])
|
||||||
|
def test_unsupported_compute_type_hint_requires_cuda_context(
|
||||||
|
tmp_path, monkeypatch, from_config, phase, device, compute_type
|
||||||
|
):
|
||||||
|
"""Одинаковая ошибка CTranslate2 получает CUDA-подсказку только на CUDA."""
|
||||||
|
monkeypatch.chdir(tmp_path)
|
||||||
|
files = [tmp_path / "one.wav"]
|
||||||
|
if phase == "batch":
|
||||||
|
files.append(tmp_path / "two.wav")
|
||||||
|
for file in files:
|
||||||
|
file.write_bytes(b"audio")
|
||||||
|
args = [str(file) for file in files]
|
||||||
|
if from_config:
|
||||||
|
(tmp_path / ".transcriber.toml").write_text(
|
||||||
|
f'device = "{device}"\ncompute_type = "{compute_type}"\n'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
args += ["--device", device, "--compute-type", compute_type]
|
||||||
|
message = (
|
||||||
|
f"Requested {compute_type} compute type, but the target device or backend "
|
||||||
|
f"do not support efficient {compute_type} computation."
|
||||||
|
)
|
||||||
|
backend = _make_backend()
|
||||||
|
error = ValueError(message)
|
||||||
|
if phase == "load":
|
||||||
|
backend.create_model.side_effect = error
|
||||||
|
else:
|
||||||
|
backend.transcribe.side_effect = error
|
||||||
|
|
||||||
|
with patch("local_transcriber.transcriber.get_backend", return_value=backend):
|
||||||
|
out = runner.invoke(app, args)
|
||||||
|
|
||||||
|
assert out.exit_code == 1
|
||||||
|
assert message in " ".join(out.output.split())
|
||||||
|
assert ("Тип вычислений несовместим" in out.output) is (device == "cuda")
|
||||||
|
assert "uv sync --extra cuda" not in out.output
|
||||||
|
assert "Переключение на CPU" not in out.output
|
||||||
|
|
||||||
|
|
||||||
def test_cli_device_fallback_warning(tmp_path):
|
def test_cli_device_fallback_warning(tmp_path):
|
||||||
"""When auto-detected device differs from actual, show fallback warning."""
|
"""When auto-detected device differs from actual, show fallback warning."""
|
||||||
audio = tmp_path / "test.mp3"
|
audio = tmp_path / "test.mp3"
|
||||||
|
|||||||
@@ -48,12 +48,73 @@ def test_ensure_cublas_loads_library(monkeypatch, tmp_path):
|
|||||||
assert calls[0][1] == ctypes.RTLD_GLOBAL
|
assert calls[0][1] == ctypes.RTLD_GLOBAL
|
||||||
|
|
||||||
|
|
||||||
def test_ensure_cublas_skips_non_linux(monkeypatch):
|
def test_ensure_cublas_skips_macos(monkeypatch):
|
||||||
"""На не-Linux платформах -- no-op."""
|
"""На macOS bootstrap не загружает CUDA."""
|
||||||
monkeypatch.setattr(sys, "platform", "win32")
|
monkeypatch.setattr(sys, "platform", "darwin")
|
||||||
ensure_cublas_loadable() # не должно бросать исключений
|
ensure_cublas_loadable() # не должно бросать исключений
|
||||||
|
|
||||||
|
|
||||||
|
def test_windows_optional_package_absent(monkeypatch):
|
||||||
|
"""Windows работает без extra и не меняет поиск DLL."""
|
||||||
|
monkeypatch.setattr(sys, "platform", "win32")
|
||||||
|
monkeypatch.setitem(sys.modules, "nvidia.cublas", None)
|
||||||
|
ensure_cublas_loadable()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("broken", [False, True])
|
||||||
|
def test_windows_preloads_dlls_and_keeps_handles(monkeypatch, tmp_path, broken):
|
||||||
|
"""DLL загружаются по порядку один раз, ошибки не скрываются."""
|
||||||
|
from local_transcriber import _cuda_bootstrap as bootstrap
|
||||||
|
|
||||||
|
bin_dir = tmp_path / "bin"
|
||||||
|
bin_dir.mkdir()
|
||||||
|
nvidia = types.ModuleType("nvidia")
|
||||||
|
nvidia.__path__ = []
|
||||||
|
cublas = types.ModuleType("nvidia.cublas")
|
||||||
|
cublas.__path__ = [str(tmp_path)]
|
||||||
|
nvidia.cublas = cublas
|
||||||
|
monkeypatch.setitem(sys.modules, "nvidia", nvidia)
|
||||||
|
monkeypatch.setitem(sys.modules, "nvidia.cublas", cublas)
|
||||||
|
monkeypatch.setattr(sys, "platform", "win32")
|
||||||
|
monkeypatch.setattr(bootstrap, "_dll_directories", {})
|
||||||
|
monkeypatch.setattr(bootstrap, "_dll_libraries", {})
|
||||||
|
calls = []
|
||||||
|
directory_handle = object()
|
||||||
|
library_handle = object()
|
||||||
|
failure = OSError("cublas64_12.dll: dependency not found")
|
||||||
|
|
||||||
|
def add_directory(path):
|
||||||
|
calls.append(("directory", path))
|
||||||
|
return directory_handle
|
||||||
|
|
||||||
|
def load_library(path):
|
||||||
|
calls.append(("load", path))
|
||||||
|
if broken:
|
||||||
|
raise failure
|
||||||
|
return library_handle
|
||||||
|
|
||||||
|
monkeypatch.setattr(os, "add_dll_directory", add_directory, raising=False)
|
||||||
|
monkeypatch.setattr(ctypes, "WinDLL", load_library, raising=False)
|
||||||
|
original_path = os.environ.get("PATH")
|
||||||
|
|
||||||
|
if broken:
|
||||||
|
with pytest.raises(OSError) as caught:
|
||||||
|
ensure_cublas_loadable()
|
||||||
|
assert caught.value is failure
|
||||||
|
assert bootstrap._dll_libraries == {}
|
||||||
|
else:
|
||||||
|
ensure_cublas_loadable()
|
||||||
|
ensure_cublas_loadable()
|
||||||
|
assert calls == [
|
||||||
|
("directory", str(bin_dir)),
|
||||||
|
("load", str(bin_dir / "cublasLt64_12.dll")),
|
||||||
|
("load", str(bin_dir / "cublas64_12.dll")),
|
||||||
|
]
|
||||||
|
assert list(bootstrap._dll_libraries.values()) == [library_handle] * 2
|
||||||
|
assert bootstrap._dll_directories[str(bin_dir)] is directory_handle
|
||||||
|
assert os.environ.get("PATH") == original_path
|
||||||
|
|
||||||
|
|
||||||
def _nvidia_cublas_installed() -> bool:
|
def _nvidia_cublas_installed() -> bool:
|
||||||
"""Проверяет, что pip-пакет nvidia-cublas-cu12 установлен."""
|
"""Проверяет, что pip-пакет nvidia-cublas-cu12 установлен."""
|
||||||
try:
|
try:
|
||||||
|
|||||||
+25
-3
@@ -1,6 +1,7 @@
|
|||||||
"""Tests for onnx-asr backend."""
|
"""Tests for onnx-asr backend."""
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -64,12 +65,12 @@ class TestEnsureModelAvailable:
|
|||||||
def with_timestamps(self):
|
def with_timestamps(self):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def fake_load_model(*, model, quantization):
|
def fake_load_model(*, model, quantization, providers):
|
||||||
quantizations.append(quantization)
|
quantizations.append(quantization)
|
||||||
return FakeAsrAdapter()
|
return FakeAsrAdapter()
|
||||||
|
|
||||||
monkeypatch.setattr("onnx_asr.load_model", fake_load_model)
|
monkeypatch.setattr("onnx_asr.load_model", fake_load_model)
|
||||||
monkeypatch.setattr("onnx_asr.load_vad", lambda model: None)
|
monkeypatch.setattr("onnx_asr.load_vad", lambda model, **kwargs: None)
|
||||||
|
|
||||||
backend = OnnxAsrBackend(compute_type_explicit=False)
|
backend = OnnxAsrBackend(compute_type_explicit=False)
|
||||||
model_id = backend.ensure_model_available(
|
model_id = backend.ensure_model_available(
|
||||||
@@ -87,6 +88,26 @@ class TestEnsureModelAvailable:
|
|||||||
|
|
||||||
|
|
||||||
class TestCreateModel:
|
class TestCreateModel:
|
||||||
|
def test_cpu_provider_is_explicit_for_asr_and_vad(self, monkeypatch):
|
||||||
|
"""Доступность CoreML/CUDA не меняет исполнение ASR и VAD."""
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"onnxruntime.get_available_providers",
|
||||||
|
lambda: ["CoreMLExecutionProvider", "CUDAExecutionProvider", "CPUExecutionProvider"],
|
||||||
|
)
|
||||||
|
load_asr = MagicMock()
|
||||||
|
load_vad = MagicMock()
|
||||||
|
monkeypatch.setattr("onnx_asr.load_model", load_asr)
|
||||||
|
monkeypatch.setattr("onnx_asr.load_vad", load_vad)
|
||||||
|
|
||||||
|
OnnxAsrBackend().create_model("gigaam-v3-e2e-rnnt", "onnx", "int8")
|
||||||
|
|
||||||
|
load_asr.assert_called_once_with(
|
||||||
|
model="gigaam-v3-e2e-rnnt",
|
||||||
|
quantization="int8",
|
||||||
|
providers=["CPUExecutionProvider"],
|
||||||
|
)
|
||||||
|
load_vad.assert_called_once_with("silero", providers=["CPUExecutionProvider"])
|
||||||
|
|
||||||
def test_wraps_vad_model_with_timestamps(self, monkeypatch):
|
def test_wraps_vad_model_with_timestamps(self, monkeypatch):
|
||||||
timestamped_model = object()
|
timestamped_model = object()
|
||||||
|
|
||||||
@@ -99,7 +120,7 @@ class TestCreateModel:
|
|||||||
return FakeVadAdapter()
|
return FakeVadAdapter()
|
||||||
|
|
||||||
monkeypatch.setattr("onnx_asr.load_model", lambda **kwargs: FakeAsrAdapter())
|
monkeypatch.setattr("onnx_asr.load_model", lambda **kwargs: FakeAsrAdapter())
|
||||||
monkeypatch.setattr("onnx_asr.load_vad", lambda model: object())
|
monkeypatch.setattr("onnx_asr.load_vad", lambda model, **kwargs: object())
|
||||||
|
|
||||||
model = OnnxAsrBackend().create_model("gigaam-v3-e2e-rnnt", "onnx", "int8")
|
model = OnnxAsrBackend().create_model("gigaam-v3-e2e-rnnt", "onnx", "int8")
|
||||||
|
|
||||||
@@ -127,6 +148,7 @@ class TestCreateModel:
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
monkeypatch.setattr("onnx_asr.load_model", fake_load_model)
|
monkeypatch.setattr("onnx_asr.load_model", fake_load_model)
|
||||||
|
monkeypatch.setattr("onnx_asr.load_vad", lambda model, **kwargs: None)
|
||||||
|
|
||||||
backend = OnnxAsrBackend()
|
backend = OnnxAsrBackend()
|
||||||
backend.actual_compute_type = "int8"
|
backend.actual_compute_type = "int8"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from local_transcriber.transcriber import (
|
|||||||
Segment,
|
Segment,
|
||||||
TranscribeResult,
|
TranscribeResult,
|
||||||
_transcribe_file,
|
_transcribe_file,
|
||||||
|
cuda_error_hint,
|
||||||
ensure_model_available,
|
ensure_model_available,
|
||||||
load_model,
|
load_model,
|
||||||
transcribe,
|
transcribe,
|
||||||
@@ -16,6 +17,21 @@ from local_transcriber.types import WordTimestampsUnavailableError
|
|||||||
# === Helpers ===
|
# === Helpers ===
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"error",
|
||||||
|
[RuntimeError("out of memory"), RuntimeError("CUDA unknown error")],
|
||||||
|
)
|
||||||
|
def test_unknown_error_does_not_suggest_cuda_installation(error):
|
||||||
|
"""CPU OOM и неизвестная причина не получают совет по установке CUDA."""
|
||||||
|
assert cuda_error_hint(error) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_windows_loader_error_suggests_extra():
|
||||||
|
"""OSError загрузчика DLL распознаётся без обёртки RuntimeError."""
|
||||||
|
error = OSError("[WinError 126] Could not find module 'cublas64_12.dll'")
|
||||||
|
assert "uv sync --extra cuda" in cuda_error_hint(error)
|
||||||
|
|
||||||
|
|
||||||
def _make_result(
|
def _make_result(
|
||||||
count: int = 2,
|
count: int = 2,
|
||||||
language: str = "ru",
|
language: str = "ru",
|
||||||
|
|||||||
+5
-5
@@ -66,17 +66,17 @@ def test_detect_device_explicit_passthrough():
|
|||||||
assert detect_device("openvino-cpu") == "openvino-cpu"
|
assert detect_device("openvino-cpu") == "openvino-cpu"
|
||||||
|
|
||||||
|
|
||||||
def test_detect_device_auto_cuda_when_nvidia_smi_available():
|
def test_detect_device_auto_onnx_when_nvidia_smi_available():
|
||||||
"""При доступном nvidia-smi auto выбирает CUDA."""
|
"""При доступном nvidia-smi auto остаётся на ONNX CPU."""
|
||||||
with patch(
|
with patch(
|
||||||
"local_transcriber.utils.shutil.which", return_value="/usr/bin/nvidia-smi"
|
"shutil.which", return_value="/usr/bin/nvidia-smi"
|
||||||
):
|
):
|
||||||
assert detect_device("auto") == "cuda"
|
assert detect_device("auto") == "onnx"
|
||||||
|
|
||||||
|
|
||||||
def test_detect_device_auto_onnx_without_cuda():
|
def test_detect_device_auto_onnx_without_cuda():
|
||||||
"""Без CUDA auto выбирает ONNX CPU."""
|
"""Без CUDA auto выбирает ONNX CPU."""
|
||||||
with patch("local_transcriber.utils.shutil.which", return_value=None):
|
with patch("shutil.which", return_value=None):
|
||||||
assert detect_device("auto") == "onnx"
|
assert detect_device("auto") == "onnx"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -288,9 +288,9 @@ name = "local-transcriber"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
{ name = "ctranslate2" },
|
||||||
{ name = "faster-whisper" },
|
{ name = "faster-whisper" },
|
||||||
{ name = "httpx" },
|
{ name = "httpx" },
|
||||||
{ name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
|
||||||
{ name = "onnx-asr", extra = ["cpu", "hub"] },
|
{ name = "onnx-asr", extra = ["cpu", "hub"] },
|
||||||
{ name = "onnxruntime" },
|
{ name = "onnxruntime" },
|
||||||
{ name = "openvino-genai", marker = "(platform_machine == 'AMD64' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and sys_platform != 'darwin')" },
|
{ name = "openvino-genai", marker = "(platform_machine == 'AMD64' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and sys_platform != 'darwin')" },
|
||||||
@@ -300,6 +300,11 @@ dependencies = [
|
|||||||
{ name = "typer" },
|
{ name = "typer" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[package.optional-dependencies]
|
||||||
|
cuda = [
|
||||||
|
{ name = "nvidia-cublas-cu12", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32') or (platform_machine == 'x86_64' and sys_platform == 'win32')" },
|
||||||
|
]
|
||||||
|
|
||||||
[package.dev-dependencies]
|
[package.dev-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
{ name = "kaldi-native-fbank" },
|
{ name = "kaldi-native-fbank" },
|
||||||
@@ -311,9 +316,10 @@ dev = [
|
|||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
|
{ name = "ctranslate2", specifier = "==4.8.1" },
|
||||||
{ name = "faster-whisper", specifier = ">=1.2.1,<2" },
|
{ name = "faster-whisper", specifier = ">=1.2.1,<2" },
|
||||||
{ name = "httpx", specifier = ">=0.28,<1" },
|
{ name = "httpx", specifier = ">=0.28,<1" },
|
||||||
{ name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'", specifier = ">=12.4,<13" },
|
{ name = "nvidia-cublas-cu12", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'cuda') or (platform_machine == 'AMD64' and sys_platform == 'win32' and extra == 'cuda') or (platform_machine == 'x86_64' and sys_platform == 'win32' and extra == 'cuda')", specifier = "==12.9.1.4" },
|
||||||
{ name = "onnx-asr", extras = ["cpu", "hub"], specifier = ">=0.12,<0.13" },
|
{ name = "onnx-asr", extras = ["cpu", "hub"], specifier = ">=0.12,<0.13" },
|
||||||
{ name = "onnxruntime", specifier = ">=1.28,<2" },
|
{ 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.3.0.0,<2026.4" },
|
{ 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" },
|
||||||
@@ -322,6 +328,7 @@ requires-dist = [
|
|||||||
{ name = "socksio", specifier = ">=1.0.0,<2" },
|
{ name = "socksio", specifier = ">=1.0.0,<2" },
|
||||||
{ name = "typer", specifier = ">=0.24.1,<1" },
|
{ name = "typer", specifier = ">=0.24.1,<1" },
|
||||||
]
|
]
|
||||||
|
provides-extras = ["cuda"]
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
dev = [
|
dev = [
|
||||||
@@ -445,6 +452,7 @@ version = "12.9.1.4"
|
|||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/77/3c/aa88abe01f3be3d1f8f787d1d33dc83e76fec05945f9a28fbb41cfb99cd5/nvidia_cublas_cu12-12.9.1.4-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:453611eb21a7c1f2c2156ed9f3a45b691deda0440ec550860290dc901af5b4c2", size = 581242350, upload-time = "2025-06-05T20:04:51.979Z" },
|
{ url = "https://files.pythonhosted.org/packages/77/3c/aa88abe01f3be3d1f8f787d1d33dc83e76fec05945f9a28fbb41cfb99cd5/nvidia_cublas_cu12-12.9.1.4-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:453611eb21a7c1f2c2156ed9f3a45b691deda0440ec550860290dc901af5b4c2", size = 581242350, upload-time = "2025-06-05T20:04:51.979Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/45/a1/a17fade6567c57452cfc8f967a40d1035bb9301db52f27808167fbb2be2f/nvidia_cublas_cu12-12.9.1.4-py3-none-win_amd64.whl", hash = "sha256:1e5fee10662e6e52bd71dec533fbbd4971bb70a5f24f3bc3793e5c2e9dc640bf", size = 553153899, upload-time = "2025-06-05T20:13:35.556Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
Reference in new issue
Block a user