Files
talkscore-asr/app/worker.py
T
Vladimir BryzgalovandClaude Opus 5 41f3651cc8 Параллельные задачи считаются в отдельных процессах
Замер подтвердил догадку: sherpa-onnx и onnxruntime держат GIL. Две задачи
в двух потоках идут ровно столько же, сколько подряд (1.04x у диаризации,
1.17x у распознавания) - отсюда и незагруженный процессор при работе.
В двух процессах те же задачи дают 1.59x даже с загрузкой моделей в каждом.

Пул процессов включается при workers > 1. Функции воркера вынесены в модуль,
не тянущий app.main: на Windows дочерний процесс поднимается через spawn и
иначе стартовал бы ещё один веб-сервер. Если процессы не запустятся, сервис
откатывается на однопроцессный режим.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 23:27:07 +05:00

46 lines
2.2 KiB
Python

"""Обработка задачи в отдельном процессе.
Замеры показали, что sherpa-onnx и onnxruntime держат GIL: две задачи в двух
потоках идут ровно столько же, сколько подряд (выигрыш 1.04x). Поэтому
процессор простаивает, а воркеры-потоки бесполезны. В процессах те же две
задачи дают 1.59x даже с загрузкой моделей в каждом.
Модуль намеренно не импортирует app.main: на Windows дочерний процесс
поднимается через spawn и заново импортирует модуль с функцией. Если бы
это был main, в каждом процессе поднимался бы ещё один веб-сервер.
"""
import logging
from pathlib import Path
from app.pipeline import Pipeline
log = logging.getLogger(__name__)
# Живёт внутри процесса-воркера: модели грузятся один раз на процесс.
_pipeline: Pipeline | None = None
def init_worker(models_dir: str, threads: int, replacements_path: str, base_dir: str) -> None:
"""Готовит процесс к работе: грузит модели один раз."""
global _pipeline
logging.basicConfig(level=logging.WARNING)
_pipeline = Pipeline(models_dir=Path(models_dir), threads=threads,
replacements_path=Path(replacements_path),
base_dir=Path(base_dir))
_pipeline.warmup()
def run_job(audio_path: str, num_speakers: int, ffmpeg: str) -> dict:
"""Переводит файл в WAV и распознаёт. Выполняется в процессе-воркере."""
import tempfile
from app.pipeline import to_wav16k
if _pipeline is None:
raise RuntimeError("процесс-воркер не инициализирован")
with tempfile.TemporaryDirectory() as tmp:
wav = Path(tmp) / "audio.wav"
to_wav16k(Path(audio_path), wav, ffmpeg)
return _pipeline.transcribe(wav, num_speakers=num_speakers)