airwaves / mind /backends.py
AndresCarreon's picture
AIRWAVES v0 β€” air-DJ (MediaPipe + Web Audio) + VoxCPM2 hype-man
860eb59 verified
Raw
History Blame Contribute Delete
2.64 kB
"""AIRWAVES GPU-window plumbing: the VoxCPM2 hype-man bank in ONE @spaces.GPU call.
ZeroGPU pickles every ARGUMENT into the forked worker, so the voice actor (which
holds the loaded model) is resolved from module state the worker inherits via
fork β€” only picklable args (the lines + voice id) cross the boundary.
register_gpu_actors() runs before the first GPU call; `spaces` is imported (and
the decorator applied) at import time, which also keeps it ahead of any torch
import. Locally (no `spaces`) the bare function runs.
"""
from __future__ import annotations
import logging
import threading
from typing import Optional
from .voice import make_voice
logger = logging.getLogger("airwaves")
# Pre-fork actor: the loaded voice the GPU worker uses (resolved from module state).
_GPU_ACTORS: dict = {"voice": None}
def register_gpu_actors(voice) -> None:
_GPU_ACTORS["voice"] = voice
def _bank_entry(lines: list, voice_id: str) -> list:
"""Synthesize every line; ONLY picklable args cross into the worker."""
voice = _GPU_ACTORS["voice"] or _defaults()
return [voice.speak(line, voice_id) for line in lines]
# Decorate at import (ZeroGPU discovers @spaces.GPU functions at startup) and
# keep `import spaces` ahead of torch. ~5 short lines fit well inside 120s.
try: # pragma: no cover β€” `spaces` exists only on the HF runtime
import spaces # noqa: PLC0415 - MUST precede torch
_bank_pipeline = spaces.GPU(duration=120)(_bank_entry)
except Exception: # noqa: BLE001 - local dev / tests
_bank_pipeline = _bank_entry
_LOCK = threading.Lock()
_VOICE = None
def _defaults():
"""Process-wide voice singleton resolved from AIRWAVES_BACKEND."""
global _VOICE
with _LOCK:
if _VOICE is None:
_VOICE = make_voice()
return _VOICE
def warm_voice() -> None:
"""Eagerly build + preload the voice (call at app startup) so the forked GPU
worker inherits a loaded model and the first /api/hype stays fast."""
voice = _defaults()
preload = getattr(voice, "preload", None)
if callable(preload):
preload()
def synth_bank(lines: dict, voice_id: str = "club_mc") -> dict:
"""Synthesize every hype line in ONE GPU window; returns {key: wav_bytes}."""
voice = _defaults()
register_gpu_actors(voice) # pin the actor before the worker forks
keys = list(lines.keys())
texts = [lines[k] for k in keys]
if getattr(voice, "name", "") == "vox":
wavs = _bank_pipeline(texts, voice_id) # @spaces.GPU window
else:
wavs = _bank_entry(texts, voice_id) # mock / local
return dict(zip(keys, wavs))