본문으로 건너뛰기

[faster-qwen3-tts] 생성 요청 직렬화 및 모델 캐싱 도입

PR 링크: andimarafioti/faster-qwen3-tts#27 상태: Merged | 변경: +296 / -237

들어가며

faster-qwen3-tts의 데모 서버에서 두 가지 문제가 있었다: (1) 동시 생성 요청이 CUDA graph의 static buffer를 공유하여 출력이 corrupted되고, (2) 모델을 전환할 때마다(0.6B ↔ 1.7B) 처음부터 다시 로드하여 30초 이상 대기가 필요했다.

핵심 코드 분석

생성 직렬화

_generation_lock = asyncio.Lock()
_generation_waiters: int = 0  # 대기 중인 요청 수

async def generate_stream(text, ...):
    global _generation_waiters
    _generation_waiters += 1
    try:
        async with _generation_lock:
            # CUDA graph은 static buffer를 사용하므로
            # 한 번에 하나의 생성만 가능
            ...
    finally:
        _generation_waiters -= 1

모델 캐싱

Before:

# 모델 로드 시 기존 모델 삭제 후 새로 로드
model = FasterQwen3TTS.from_pretrained(model_name, ...)

After:

_model_cache: dict[str, FasterQwen3TTS] = {}

async def load_model(model_name, ...):
    if model_name in _model_cache:
        # 캐시에서 즉시 반환
        _active_model_name = model_name
        return
    model = FasterQwen3TTS.from_pretrained(model_name, ...)
    _model_cache[model_name] = model

Preset voice 캐시 사전 로딩

def _prime_preset_voice_cache(model: FasterQwen3TTS) -> None:
    """모델 로드 시 preset voice의 voice_clone_prompt를 미리 계산"""
    for preset in PRESET_VOICES.values():
        model._prepare_generation(
            text="warmup",
            ref_audio=preset["ref_audio"],
            ref_text=preset["ref_text"],
            ...
        )

왜 이게 좋은가

  1. 데이터 무결성: CUDA graph의 static buffer는 동시 접근 시 race condition이 발생한다. asyncio Lock으로 이를 원천 차단한다.
  2. UX 개선: 모델 캐싱으로 0.6B → 1.7B → 0.6B 전환 시 두 번째부터는 즉시 로드된다.
  3. 대기열 가시성: _generation_waiters 카운터로 현재 대기 중인 요청 수를 UI에서 표시할 수 있다.

정리

CUDA graph 기반 서버에서 동시성 제어는 선택이 아닌 필수다. Static buffer의 in-place 업데이트 특성을 이해하고 생성을 직렬화하며, 모델 캐싱으로 전환 비용을 줄인 실용적인 PR이다.

참고 자료


이 글은 AI(Claude)의 도움을 받아 작성되었습니다. 코드 분석과 해석에서 오류가 있을 수 있으니, 정확한 내용은 원본 PR을 참고해주세요.

댓글

관련 포스트

PR Analysis 의 다른글