feat(cli): --threads для управления CPU-потоками + бенчмарк CUDA compute_type

- Зачем:
  - CTranslate2 по умолчанию использует 4 потока; на многоядерных CPU (8+ ядер)
    это неоптимально — --threads 8 даёт +13% ускорения.
  - Не было данных по int8_float32/int8_float16 на NVIDIA GPU.

- Что:
  - --threads / -t: новый CLI-флаг, пробрасывается через load_model →
    backend.create_model(cpu_threads=...) → WhisperModel(cpu_threads=...).
  - Валидация min=0 на входе (typer), Backend протокол синхронизирован.
  - docs/gpu.md: результаты бенчмарка 6 комбинаций CUDA compute_type
    (medium/large-v3 × float16/int8_float32/int8_float16) на двух файлах
    (16 мин и 46 мин). Ключевой вывод: float16 — оптимальный дефолт;
    large-v3 ненадёжен на длинных записях.
  - README: --threads добавлен в таблицу опций.
  - Фикс теста: test_resolve_repo_explicit_unsupported_pair_raises обновлён
    под добавление medium fp16 модели.

- Проверка:
  - uv run pytest: 157 passed.
  - transcribe file.mp4 --device cpu --threads 8: 277с vs 320с (дефолт).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-24 22:49:16 +03:00
co-authored by Claude Opus 4.6
parent 8ae74d2748
commit 857267763c
10 changed files with 226 additions and 40 deletions
+9 -3
View File
@@ -22,11 +22,13 @@ def load_model(
on_status: Callable[[str], None] | None = None,
strict_device: bool = False,
compute_type_explicit: bool = False,
cpu_threads: int = 0,
) -> tuple[Any, str, Any, str]:
"""Загружает модель: ensure + create с fallback.
Возвращает (model, actual_device, backend, model_path).
compute_type_explicit: True если пользователь явно указал --compute-type.
cpu_threads: число потоков для CPU inference (0 = дефолт библиотеки).
"""
backend = get_backend(device, compute_type_explicit=compute_type_explicit)
actual_device = device
@@ -35,7 +37,7 @@ def load_model(
try:
_notify_status(on_status, f"Инициализирую модель на {device}...")
model = backend.create_model(model_path, device, compute_type)
model = backend.create_model(model_path, device, compute_type, cpu_threads=cpu_threads)
# Резолвим actual_device по реальному OpenVINO device
ov_dev = getattr(backend, "actual_ov_device", None)
if ov_dev == "GPU" and actual_device != "openvino-gpu":
@@ -55,7 +57,7 @@ def load_model(
backend = get_backend("cpu")
model_path = backend.ensure_model_available(model_name, compute_type, on_status)
_notify_status(on_status, "Инициализирую модель на cpu...")
model = backend.create_model(model_path, "cpu", compute_type)
model = backend.create_model(model_path, "cpu", compute_type, cpu_threads=cpu_threads)
else:
raise
@@ -74,6 +76,7 @@ def _transcribe_file(
on_segment: Callable[[Segment], None] | None = None,
on_status: Callable[[str], None] | None = None,
strict_device: bool = False,
cpu_threads: int = 0,
) -> TranscribeFileResult:
"""Транскрибирует один файл. При mid-stream fallback перезагружает модель."""
lang_arg = language if language and language != "auto" else None
@@ -95,7 +98,7 @@ def _transcribe_file(
backend = get_backend("cpu")
model_path = backend.ensure_model_available(model_name, compute_type, on_status)
_notify_status(on_status, "Инициализирую модель на cpu...")
model = backend.create_model(model_path, "cpu", compute_type)
model = backend.create_model(model_path, "cpu", compute_type, cpu_threads=cpu_threads)
_notify_status(on_status, "Транскрибирую...")
result = backend.transcribe(model, file_path, lang_arg, on_segment, on_status)
result.device_used = actual_device
@@ -120,16 +123,19 @@ def transcribe(
on_segment: Callable[[Segment], None] | None = None,
on_status: Callable[[str], None] | None = None,
strict_device: bool = False,
cpu_threads: int = 0,
) -> TranscribeResult:
"""High-level API: загрузка модели + транскрипция за один вызов."""
model, actual_device, backend, model_path = load_model(
model_name, device, compute_type, on_status, strict_device,
compute_type_explicit=True, # Python API — caller explicitly chose compute_type
cpu_threads=cpu_threads,
)
tfr = _transcribe_file(
model, actual_device, backend, model_path,
file_path, model_name, compute_type,
language, on_segment, on_status, strict_device,
cpu_threads=cpu_threads,
)
return tfr.result