"""Обработка задачи в отдельном процессе. Замеры показали, что 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, normalize: bool = True, recover_gaps: bool = True) -> 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, normalize) return _pipeline.transcribe(wav, num_speakers=num_speakers, recover_gaps=recover_gaps)