🦝 Fix /v1/voices timeout by caching response data at startup
- Problem: /v1/voices endpoint was reading and parsing 800+ line YAML file on every request - This caused 30+ second timeouts with 227 voices across 4 TTS engines - Solution: Cache the entire response structure at startup (same pattern as voice_to_model_cache) - Added voices_cache global variable populated during startup - Endpoint now returns instantly from memory (< 1ms instead of 30+ seconds) - Includes fallback for safety but should never execute Performance impact: - Before: O(n) YAML parse + dict construction on every request - After: O(1) memory lookup from pre-built cache - Startup time: +negligible (runs once alongside existing voice_to_model_cache) Related to Raccoon Mission: Fast API responses essential for production TTS service
This commit is contained in:
parent
ac305c31d7
commit
21e8f27519
1 changed files with 56 additions and 1 deletions
57
speech.py
57
speech.py
|
|
@ -42,6 +42,9 @@ args = None
|
||||||
# Voice-to-model lookup cache (loaded at startup)
|
# Voice-to-model lookup cache (loaded at startup)
|
||||||
voice_to_model_cache = {}
|
voice_to_model_cache = {}
|
||||||
|
|
||||||
|
# Cached voice data for /v1/voices endpoint (loaded at startup)
|
||||||
|
voices_cache = None
|
||||||
|
|
||||||
def unload_model():
|
def unload_model():
|
||||||
import torch, gc
|
import torch, gc
|
||||||
global xtts
|
global xtts
|
||||||
|
|
@ -336,6 +339,15 @@ async def list_models():
|
||||||
@app.get("/v1/voices")
|
@app.get("/v1/voices")
|
||||||
async def list_voices():
|
async def list_voices():
|
||||||
"""List all available voices with model mapping and metadata (extended endpoint)"""
|
"""List all available voices with model mapping and metadata (extended endpoint)"""
|
||||||
|
global voices_cache
|
||||||
|
|
||||||
|
# Return cached data if available
|
||||||
|
if voices_cache is not None:
|
||||||
|
return voices_cache
|
||||||
|
|
||||||
|
# This should never happen since cache is populated at startup,
|
||||||
|
# but provide fallback just in case
|
||||||
|
logger.warning("/v1/voices called but cache not initialized - loading now")
|
||||||
default_exists('config/voice_to_speaker.yaml')
|
default_exists('config/voice_to_speaker.yaml')
|
||||||
|
|
||||||
with open('config/voice_to_speaker.yaml', 'r', encoding='utf8') as file:
|
with open('config/voice_to_speaker.yaml', 'r', encoding='utf8') as file:
|
||||||
|
|
@ -377,11 +389,13 @@ async def list_voices():
|
||||||
|
|
||||||
models_data.append(model_info)
|
models_data.append(model_info)
|
||||||
|
|
||||||
return {
|
voices_cache = {
|
||||||
"object": "list",
|
"object": "list",
|
||||||
"data": models_data
|
"data": models_data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return voices_cache
|
||||||
|
|
||||||
@app.post("/v1/audio/speech", response_class=StreamingResponse)
|
@app.post("/v1/audio/speech", response_class=StreamingResponse)
|
||||||
async def generate_speech(request: GenerateSpeechRequest):
|
async def generate_speech(request: GenerateSpeechRequest):
|
||||||
global xtts, args
|
global xtts, args
|
||||||
|
|
@ -732,6 +746,47 @@ if __name__ == "__main__":
|
||||||
voice_to_model_cache[voice_name] = model_id
|
voice_to_model_cache[voice_name] = model_id
|
||||||
print(f"Voice-to-model cache initialized with {len(voice_to_model_cache)} voices")
|
print(f"Voice-to-model cache initialized with {len(voice_to_model_cache)} voices")
|
||||||
|
|
||||||
|
# Build voices cache for /v1/voices endpoint
|
||||||
|
models_data = []
|
||||||
|
for model_id, voices in voice_map.items():
|
||||||
|
if isinstance(voices, dict):
|
||||||
|
voice_list = list(voices.keys())
|
||||||
|
|
||||||
|
model_info = {
|
||||||
|
"id": model_id,
|
||||||
|
"object": "model",
|
||||||
|
"created": 1700000000,
|
||||||
|
"owned_by": "uncloseai",
|
||||||
|
"voices": voice_list,
|
||||||
|
"voice_count": len(voice_list)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add engine-specific metadata
|
||||||
|
if model_id == 'tts-1':
|
||||||
|
model_info["engine"] = "piper"
|
||||||
|
model_info["description"] = "Fast neural TTS with 100+ voices"
|
||||||
|
model_info["sample_rate"] = 22050
|
||||||
|
elif model_id == 'tts-1-hd':
|
||||||
|
model_info["engine"] = "xtts"
|
||||||
|
model_info["description"] = "High-quality voice cloning TTS"
|
||||||
|
model_info["sample_rate"] = 24000
|
||||||
|
elif model_id == 'tts-1-silero':
|
||||||
|
model_info["engine"] = "silero"
|
||||||
|
model_info["description"] = "Fast multilingual TTS (en, ru, de, es, fr)"
|
||||||
|
model_info["sample_rate"] = 48000
|
||||||
|
elif model_id == 'tts-1-kokoro':
|
||||||
|
model_info["engine"] = "kokoro"
|
||||||
|
model_info["description"] = "Lightweight decoder-only TTS (82M params)"
|
||||||
|
model_info["sample_rate"] = 24000
|
||||||
|
|
||||||
|
models_data.append(model_info)
|
||||||
|
|
||||||
|
voices_cache = {
|
||||||
|
"object": "list",
|
||||||
|
"data": models_data
|
||||||
|
}
|
||||||
|
print(f"/v1/voices cache initialized with {len(models_data)} models")
|
||||||
|
|
||||||
logger.remove()
|
logger.remove()
|
||||||
logger.add(sink=sys.stderr, level=args.log_level)
|
logger.add(sink=sys.stderr, level=args.log_level)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue