docs(execution): описаны владение выполнением и диагностика
ADR-002 и ADR-003 отражают Transcriber как единственного владельца загрузки и fallback; README получил раздел «Диагностика исполнения» и уточнение --threads для ONNX; AGENTS.md — новые тестовые паттерны. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
0f8ecb5bd3
commit
a8241b1d6b
5 files changed
+82
-38
No files matched your search
@@ -28,8 +28,9 @@ CLI (cli.py)
|
||||
→ config.py cascade: CLI arg → .transcriber.toml → device-aware default → hardcoded
|
||||
→ utils.py detect_device(), validate files, expand globs (Windows workaround)
|
||||
→ context_menu.py Windows SendTo: Transcribe.cmd install/uninstall (--install-menu / --uninstall-menu)
|
||||
→ transcriber.py load_model() → get_backend(device) → ensure_model_available → create_model
|
||||
_transcribe_file() with mid-stream CUDA→CPU fallback
|
||||
→ transcriber.py Transcriber(ExecutionRequest): resolves auto + device defaults,
|
||||
get_backend(device) → ensure_model_available → create_model once per run,
|
||||
transcribe(file) per file; fallback and ExecutionInfo stay inside
|
||||
→ formatter.py segments → markdown with timestamps, paragraph grouping (>2s pause or >60s)
|
||||
```
|
||||
|
||||
@@ -47,19 +48,21 @@ Three backends implement the `Backend` Protocol (structural typing, no inheritan
|
||||
|
||||
### Key design decisions
|
||||
|
||||
- **Two-level fallback**: GPU→CPU at model load time AND mid-stream during transcription (GPU visible via nvidia-smi but insufficient VRAM).
|
||||
- **Execution module** (`Transcriber`): one owner per run for model handle, adapter, actual device and allowed GPU→CPU fallback (load time and mid-stream). Callers get `TranscribeResult` + `ExecutionInfo` (requested vs actual device, engine, model, compute type, threads, `runtime_info()` from the adapter for `--verbose`). Fallback is reachable only with `strict_device=False` (Python API): CLI explicit device is strict, `auto` is ONNX CPU.
|
||||
- **CUDA bootstrap** (`_cuda_bootstrap.py`): preloads `libcublas.so.12` via `ctypes.CDLL(RTLD_GLOBAL)` before importing ctranslate2, because pip's `nvidia-cublas-cu12` installs to a non-standard path and `LD_LIBRARY_PATH` can't be changed at runtime (glibc caches it).
|
||||
- **Batch mode**: 3-phase pipeline (prescan → load model once → transcribe all). `TranscribeFileResult` carries updated model/backend/device state between files.
|
||||
- **Device-aware defaults**: `compute_type` and `model` vary by device (float16 for CUDA, int8 for OpenVINO, float32 for CPU). Defined in `config.py` `DEVICE_DEFAULTS`.
|
||||
- **Batch mode**: 3-phase pipeline (prescan → create `Transcriber` → transcribe all). No state is carried between files by the CLI.
|
||||
- **Device-aware defaults**: `compute_type` and `model` vary by device (float16 for CUDA, int8 for OpenVINO, float32 for CPU). Defined in `config.py` `DEVICE_DEFAULTS`, applied by `Transcriber` when the request leaves them `None`.
|
||||
- **ONNX thread budget**: `--threads` sets `intra_op_num_threads` for both ASR and VAD sessions via `SessionOptions`; 0 keeps onnxruntime defaults.
|
||||
- **OpenVINO uses pre-quantized models** — `compute_type` selects which HF repo to download, not a runtime parameter.
|
||||
|
||||
## Testing
|
||||
|
||||
All tests mock backends — no real model downloads or transcription. Key test patterns:
|
||||
|
||||
- CLI tests: `typer.testing.CliRunner` + mocks for `load_config`, `detect_device`, `load_model`, `_transcribe_file`, `write_transcript`
|
||||
- `_single_patches()` — helper assembling standard happy-path mock set
|
||||
- `_make_result()` / `_make_tfr()` — factories for test data
|
||||
- CLI tests: `typer.testing.CliRunner` with the real `Transcriber` and a fake adapter patched at `local_transcriber.transcriber.get_backend`; `load_config`, `validate_input_file`, `write_transcript` mocked
|
||||
- `_cli_run()` — context manager assembling that standard set, yields `(backend, write_transcript)`
|
||||
- `_make_result()` / `_make_backend()` — factories for test data
|
||||
- Module tests (`test_transcriber.py`): `Transcriber` through its interface with `_make_run_backend()`; adapter tests patch the libraries (`onnx_asr.load_model`, `faster_whisper.WhisperModel`)
|
||||
|
||||
## Common tasks
|
||||
|
||||
|
||||
@@ -302,11 +302,27 @@ transcribe --uninstall-menu
|
||||
| `--output` | `-o` | `<файл>-transcript.md` | Путь к выходному файлу |
|
||||
| `--device` | `-d` | `auto` | Устройство (auto, cpu, cuda, openvino, openvino-gpu, openvino-cpu, onnx) |
|
||||
| `--compute-type` | — | float16 (CUDA) / int8 (ONNX/OpenVINO) / float32 (CPU) | Тип вычислений |
|
||||
| `--threads` | `-t` | 0 (авто) | Потоки CPU (рекомендуется = число физ. ядер) |
|
||||
| `--threads` | `-t` | 0 (авто) | Потоки CPU (рекомендуется = число физ. ядер); для ONNX задаёт потоки и ASR, и VAD |
|
||||
| `--diarize` / `--no-diarize` | — | `false` | Включить или отключить разделение на реплики говорящих |
|
||||
| `--speakers` | — | авто | Ожидаемое число говорящих; включает диаризацию и несовместим с `--no-diarize` |
|
||||
| `--force` | `-f` | — | Перезаписать существующие транскрипты |
|
||||
| `--verbose` | `-v` | — | Подробный вывод |
|
||||
| `--verbose` | `-v` | — | Подробный вывод: сегменты по ходу, движок, потоки, версии runtime |
|
||||
|
||||
### Диагностика исполнения
|
||||
|
||||
Перед распознаванием программа печатает модель, устройство и тип вычислений,
|
||||
которые выбраны фактически: после `auto` это `onnx`, после `openvino` —
|
||||
`openvino-gpu` или `openvino-cpu`. Если фактическое устройство отличается
|
||||
от запрошенного, выводится строка `Запрошено ..., используется ...`.
|
||||
|
||||
С `--verbose` добавляются движок, бюджет потоков и версии runtime: для ONNX —
|
||||
`onnxruntime`, `onnx-asr`, доступные providers и те, что заданы сессиям ASR
|
||||
и VAD; для CUDA — `ctranslate2` и число видимых CUDA-устройств; для OpenVINO —
|
||||
версия и доступные устройства. Этих строк достаточно, чтобы разобрать проблему
|
||||
на чужой машине без доступа к записи: попросите прислать вывод с `--verbose`.
|
||||
|
||||
Наличие provider в списке доступных не означает, что распознавание идёт
|
||||
на GPU или NPU: сессии получают только явно заданный список, сейчас это CPU.
|
||||
|
||||
## Платформы
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
**Статус**: Принято
|
||||
**Дата**: 2026-03-18
|
||||
**Обновлено**: 2026-09-21 — решения 2 и 4 пересмотрены (см. пометки)
|
||||
|
||||
## Контекст
|
||||
|
||||
@@ -17,19 +18,23 @@
|
||||
|
||||
**Почему**: загрузка модели (large-v3) занимает ~10 секунд и ~3 GB RAM/VRAM. Повторный запуск по уже обработанным файлам должен быть дешёвым no-op.
|
||||
|
||||
### 2. TranscribeFileResult: возврат обновлённого состояния модели
|
||||
### 2. Состояние модели между файлами
|
||||
|
||||
При mid-stream CUDA fallback `_transcribe_file()` перезагружает модель на CPU внутри себя. Чтобы следующие файлы в батче не грузили модель повторно, результат включает обновлённые `model` и `actual_device`.
|
||||
**Пересмотрено 2026-09-21.** Исходное решение: `_transcribe_file()` возвращал
|
||||
`TranscribeFileResult` с обновлёнными `model`, `backend`, `model_path`
|
||||
и `actual_device`, а CLI переносил их в следующий файл после mid-stream
|
||||
fallback. Это делало caller владельцем согласованности четырёх полей.
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class TranscribeFileResult:
|
||||
result: TranscribeResult
|
||||
model: WhisperModel # может измениться при fallback
|
||||
actual_device: str # может измениться при fallback
|
||||
```
|
||||
Теперь состояние не покидает module выполнения: `Transcriber` создаётся один
|
||||
раз на запуск после prescan, загружает модель при первом файле и сам держит
|
||||
загруженную модель, adapter и фактическое исполнение. Fallback меняет
|
||||
состояние внутри module, и следующий файл батча идёт уже по нему. Caller
|
||||
получает `TranscribeResult` и `ExecutionInfo` (запрошенное и выбранное
|
||||
исполнение, движок, модель, квантизация, потоки, версии runtime).
|
||||
|
||||
**Альтернатива**: передавать модель по ссылке через mutable контейнер — менее явно и сложнее тестировать.
|
||||
**Альтернатива**: mutable-контейнер с моделью, передаваемый по ссылке, —
|
||||
отклонена тогда же; owner-объект решает ту же задачу явно и тестируется
|
||||
через свой interface.
|
||||
|
||||
### 3. Конфиг: CWD → глобальный, без мержа
|
||||
|
||||
@@ -43,15 +48,18 @@ class TranscribeFileResult:
|
||||
|
||||
**Приоритет значений**: CLI > конфиг > хардкод.
|
||||
|
||||
### 4. `_transcribe_file()` — внутренний helper
|
||||
### 4. Публичный `transcribe()` — путь одного файла
|
||||
|
||||
Публичный API (`transcribe()`) сохранён без изменений. Новая функция `_transcribe_file()` — внутренний helper с префиксом `_`, не часть публичного контракта.
|
||||
|
||||
`transcribe()` стала тонкой обёрткой: `load_model()` + `_transcribe_file()` → `TranscribeResult`.
|
||||
**Пересмотрено 2026-09-21.** Внутренние helpers `load_model()`
|
||||
и `_transcribe_file()` удалены. `transcribe()` сохранён и стал обёрткой над
|
||||
тем же `Transcriber`: `model_name` и `compute_type` без значения берут
|
||||
умолчания выбранного исполнения, `device="auto"` означает ONNX CPU, как в CLI.
|
||||
|
||||
## Последствия
|
||||
|
||||
- Обратная совместимость CLI: `transcribe file.mp4` работает как раньше
|
||||
- Все существующие тесты проходят без изменений сигнатур
|
||||
- Batch mode: модель загружается один раз для всех файлов
|
||||
- Batch mode: модель загружается один раз для всех файлов; CLI не переносит
|
||||
состояние между файлами
|
||||
- Fallback из CLI недостижим: явный device — strict, `auto` — ONNX CPU без
|
||||
переходов. Механизм сохранён для `transcribe(..., strict_device=False)`
|
||||
- `--force` флаг для перезаписи существующих транскриптов в батч-режиме
|
||||
@@ -44,21 +44,38 @@ CUDA bootstrap вызывается только при создании CUDA-м
|
||||
- `onnx` → OnnxAsrBackend
|
||||
- `auto` → ONNX на CPU независимо от `nvidia-smi` и установки extra
|
||||
|
||||
### load_model() — единственный владелец pipeline
|
||||
### Transcriber — единственный владелец загрузки и исполнения
|
||||
|
||||
`load_model()` выполняет ensure_model_available + create_model в одном вызове.
|
||||
CLI не вызывает ensure_model_available отдельно — это убирает двойной resolution
|
||||
и гарантирует, что модель скачивается для правильного бэкенда.
|
||||
Обновлено 2026-09-21. `Transcriber(ExecutionRequest)` в `transcriber.py`
|
||||
разрешает `auto` и device-aware умолчания, выполняет ensure_model_available +
|
||||
create_model в одном месте и держит модель, adapter и фактическое исполнение
|
||||
на весь запуск. CLI не вызывает `get_backend` и не хранит handles.
|
||||
|
||||
Внутри module различаются три вещи: поддерживаемая модель, движок
|
||||
распознавания (`faster-whisper`, `openvino`, `onnx-asr`) и способ исполнения.
|
||||
Значение `device` из CLI/TOML сохраняется как вход: таблица `_ENGINES`
|
||||
сопоставляет ему движок, а аппаратную часть (CPU, CUDA, Intel GPU, список
|
||||
providers) трактует adapter. Новое исполнение — строка таблицы и его
|
||||
понимание в adapter'е, не новая иерархия adapters.
|
||||
|
||||
### Cross-backend fallback
|
||||
|
||||
Fallback живёт в `transcriber.py` (оркестратор), не в бэкендах:
|
||||
Fallback живёт внутри `Transcriber`, не в бэкендах и не в CLI:
|
||||
- CUDA ошибка → CPU (FasterWhisper)
|
||||
- OpenVINO ошибка → CPU (FasterWhisper)
|
||||
- `strict_device=True` (явный device из CLI или TOML) → ошибка без fallback
|
||||
- strict запуск (явный device из CLI или TOML) → ошибка без fallback
|
||||
|
||||
При fallback в батч-режиме обновляются model, backend, model_path и actual_device
|
||||
через TranscribeFileResult — следующий файл использует правильный бэкенд.
|
||||
После перехода module сам держит новое состояние; следующий файл батча
|
||||
использует его без участия caller. `WordTimestampsUnavailableError`
|
||||
и ошибки пользовательских данных переходом не считаются.
|
||||
|
||||
### Диагностика исполнения
|
||||
|
||||
Каждый adapter отдаёт `runtime_info()` — версии runtime и фактическую
|
||||
конфигурацию (для ONNX — доступные providers и те, что заданы сессиям ASR
|
||||
и VAD, квантизация, бюджет потоков). `Transcriber` включает их в
|
||||
`ExecutionInfo`; CLI печатает их в `--verbose`. Наличие provider в wheel не
|
||||
считается доказательством выполнения графа на GPU/NPU.
|
||||
|
||||
### Аудио для OpenVINO
|
||||
|
||||
@@ -83,7 +100,7 @@ macOS; ONNX обеспечивает автоматический CPU-путь
|
||||
|
||||
## Последствия
|
||||
|
||||
- Обратная совместимость: `transcribe()` сохранён; `load_model()` изменил сигнатуру (возвращает 4-tuple вместо 2-tuple, добавлен `compute_type_explicit`)
|
||||
- Обратная совместимость: `transcribe()` сохранён; `load_model()` и `_transcribe_file()` удалены (2026-09-21)
|
||||
- Новый бэкенд добавляется одним файлом в `backends/` + регистрацией в `__init__.py`
|
||||
- Модели скачиваются по запросу — CUDA пользователь не качает OpenVINO модели, и наоборот
|
||||
- ARM и macOS: OpenVINO не ставится (platform markers), auto использует ONNX
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Владение выполнением транскрипции
|
||||
|
||||
**Статус:** согласовано. Правила выбора исполнения и optional CUDA реализованы
|
||||
в #2; рефакторинг владения выполнением не начат. Windows GPU-прогон без Toolkit
|
||||
остаётся невыполненным (см. ADR-001).
|
||||
**Статус:** реализовано. Правила выбора исполнения и optional CUDA — в #2,
|
||||
владение выполнением (`Transcriber`), бюджет потоков ONNX и диагностика —
|
||||
в #56. Windows GPU-прогон без Toolkit остаётся невыполненным (см. ADR-001).
|
||||
|
||||
## Зачем
|
||||
|
||||
|
||||
Reference in new issue
Block a user