perf: bound speech proxy synthesis
This commit is contained in:
parent
9517cca00c
commit
760a3640ba
@ -32,8 +32,9 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
|
|||||||
- **Docker Socket Isolation.** Compose deployments now place the host Docker socket behind a dedicated network proxy;
|
- **Docker Socket Isolation.** Compose deployments now place the host Docker socket behind a dedicated network proxy;
|
||||||
the web container no longer mounts the socket or joins the host Docker group. The client retains direct Unix-socket
|
the web container no longer mounts the socket or joins the host Docker group. The client retains direct Unix-socket
|
||||||
compatibility for deployments that intentionally configure it.
|
compatibility for deployments that intentionally configure it.
|
||||||
- **TTS Queue Bound.** Preview, style-variation, and seed-finder synthesis now share a two-slot process-local queue;
|
- **TTS Queue Bound.** Preview, style-variation, seed-finder, and OpenAI-compatible speech-proxy synthesis now share
|
||||||
excess requests receive a retryable 429 after a bounded wait instead of creating unbounded worker threads.
|
a two-slot process-local queue; excess requests receive a retryable 429 after a bounded wait instead of creating
|
||||||
|
unbounded worker threads.
|
||||||
- **STT Queue Bound.** Transcription and STT benchmark calls now share a single bounded process-local slot, preventing
|
- **STT Queue Bound.** Transcription and STT benchmark calls now share a single bounded process-local slot, preventing
|
||||||
concurrent recognition requests from overcommitting the inference device.
|
concurrent recognition requests from overcommitting the inference device.
|
||||||
- **Visual Identity Versioning.** Consistent profile/reference results now carry the standardized v3 prompt marker,
|
- **Visual Identity Versioning.** Consistent profile/reference results now carry the standardized v3 prompt marker,
|
||||||
|
|||||||
4
TODO.md
4
TODO.md
@ -121,8 +121,8 @@ the first block before investing in architecture or polish.
|
|||||||
- [ ] Prevent duplicate generation when a user clicks twice.
|
- [ ] Prevent duplicate generation when a user clicks twice.
|
||||||
- [ ] Centralize provider health checks and error handling.
|
- [ ] Centralize provider health checks and error handling.
|
||||||
- [ ] Limit concurrency to 1–2 image generations, 1–2 voice generations, and 1 STT request per user. Consistent
|
- [ ] Limit concurrency to 1–2 image generations, 1–2 voice generations, and 1 STT request per user. Consistent
|
||||||
portrait/reference rendering and preview/style/seed-finder synthesis are capped at two active jobs; extend the
|
portrait/reference rendering, preview/style/seed-finder synthesis, and the OpenAI-compatible speech proxy are
|
||||||
cap to full speech-proxy paths; STT transcription and benchmarks are capped at one active job.
|
capped at two active jobs; STT transcription and benchmarks are capped at one active job.
|
||||||
- [ ] Use bounded server queues.
|
- [ ] Use bounded server queues.
|
||||||
|
|
||||||
## P0 — Security
|
## P0 — Security
|
||||||
|
|||||||
@ -696,6 +696,8 @@ async def tts_preview(request: Request):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
audio, media_type = await _bounded_tts_call(_preview_request_audio, text, voice, settings, instruct, backend)
|
audio, media_type = await _bounded_tts_call(_preview_request_audio, text, voice, settings, instruct, backend)
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(502, f"TTS error: {e}")
|
raise HTTPException(502, f"TTS error: {e}")
|
||||||
audio, media_type, ext, duration_sec, clipped = _prepare_proxy_audio(audio, media_type, response_format)
|
audio, media_type, ext, duration_sec, clipped = _prepare_proxy_audio(audio, media_type, response_format)
|
||||||
@ -733,6 +735,8 @@ async def tts_style_variation(request: Request):
|
|||||||
try:
|
try:
|
||||||
audio, media_type = await _bounded_tts_call(_preview_request_audio, text, source_voice, settings, instruct, backend)
|
audio, media_type = await _bounded_tts_call(_preview_request_audio, text, source_voice, settings, instruct, backend)
|
||||||
audio, media_type, _ext, duration, _clipped = _prepare_proxy_audio(audio, media_type, "wav")
|
audio, media_type, _ext, duration, _clipped = _prepare_proxy_audio(audio, media_type, "wav")
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(502, f"Style variation synthesis failed: {e}")
|
raise HTTPException(502, f"Style variation synthesis failed: {e}")
|
||||||
|
|
||||||
@ -959,7 +963,9 @@ async def openai_speech_proxy(request: Request):
|
|||||||
|
|
||||||
if backend == "streaming" and not virtual and response_format == "wav" and not route_has_sounds and not route_has_speed:
|
if backend == "streaming" and not virtual and response_format == "wav" and not route_has_sounds and not route_has_speed:
|
||||||
try:
|
try:
|
||||||
resp = await asyncio.to_thread(_open_tts_stream_response, text, voice, settings, style_instruction)
|
resp = await _bounded_tts_call(_open_tts_stream_response, text, voice, settings, style_instruction)
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_routing_log_request(
|
_routing_log_request(
|
||||||
request, status="error", app=request_app, requested_voice=original_voice,
|
request, status="error", app=request_app, requested_voice=original_voice,
|
||||||
@ -996,43 +1002,45 @@ async def openai_speech_proxy(request: Request):
|
|||||||
instruct = str(preset.get("description", "")).strip()
|
instruct = str(preset.get("description", "")).strip()
|
||||||
if not instruct:
|
if not instruct:
|
||||||
raise RuntimeError(f"Virtual voice '{voice}' has no description")
|
raise RuntimeError(f"Virtual voice '{voice}' has no description")
|
||||||
audio, media_type = await asyncio.to_thread(
|
audio, media_type = await _bounded_tts_call(
|
||||||
_voice_design_request_audio,
|
_voice_design_request_audio,
|
||||||
instruct, text, str(preset.get("language", "Auto")), settings,
|
instruct, text, str(preset.get("language", "Auto")), settings,
|
||||||
str(preset.get("gender", "")),
|
str(preset.get("gender", "")),
|
||||||
)
|
)
|
||||||
elif backend == "voice_design":
|
elif backend == "voice_design":
|
||||||
audio, media_type = await asyncio.to_thread(
|
audio, media_type = await _bounded_tts_call(
|
||||||
_voice_design_voice_request_audio,
|
_voice_design_voice_request_audio,
|
||||||
voice, text, settings, style_instruction, str(data.get("language") or "Auto"),
|
voice, text, settings, style_instruction, str(data.get("language") or "Auto"),
|
||||||
)
|
)
|
||||||
elif backend == "streaming":
|
elif backend == "streaming":
|
||||||
resp = await asyncio.to_thread(_open_tts_stream_response, text, voice, settings, style_instruction)
|
resp = await _bounded_tts_call(_open_tts_stream_response, text, voice, settings, style_instruction)
|
||||||
audio, media_type = await asyncio.to_thread(_read_tts_stream_response, resp)
|
audio, media_type = await _bounded_tts_call(_read_tts_stream_response, resp)
|
||||||
if not audio:
|
if not audio:
|
||||||
raise RuntimeError("backend returned empty audio")
|
raise RuntimeError("backend returned empty audio")
|
||||||
elif backend == "nvidia_magpie":
|
elif backend == "nvidia_magpie":
|
||||||
audio, media_type = await asyncio.to_thread(
|
audio, media_type = await _bounded_tts_call(
|
||||||
_tts_request_audio, text, voice, settings, style_instruction,
|
_tts_request_audio, text, voice, settings, style_instruction,
|
||||||
_preview_backend_base_url(settings, "nvidia_magpie"),
|
_preview_backend_base_url(settings, "nvidia_magpie"),
|
||||||
settings.get("tts_api_key", ""), "nvidia_magpie", "nvidia_magpie",
|
settings.get("tts_api_key", ""), "nvidia_magpie", "nvidia_magpie",
|
||||||
)
|
)
|
||||||
elif backend in {"nvidia_zeroshot", "nvidia_flow"}:
|
elif backend in {"nvidia_zeroshot", "nvidia_flow"}:
|
||||||
audio, media_type = await asyncio.to_thread(
|
audio, media_type = await _bounded_tts_call(
|
||||||
_nvidia_clone_request_audio, text, voice, settings,
|
_nvidia_clone_request_audio, text, voice, settings,
|
||||||
"flow" if backend == "nvidia_flow" else "zeroshot",
|
"flow" if backend == "nvidia_flow" else "zeroshot",
|
||||||
str(data.get("audio_prompt_transcript") or ""),
|
str(data.get("audio_prompt_transcript") or ""),
|
||||||
str(data.get("language") or ""),
|
str(data.get("language") or ""),
|
||||||
)
|
)
|
||||||
elif backend == "fishspeech":
|
elif backend == "fishspeech":
|
||||||
audio, media_type = await asyncio.to_thread(
|
audio, media_type = await _bounded_tts_call(
|
||||||
_fishspeech_request_audio, text, voice, settings,
|
_fishspeech_request_audio, text, voice, settings,
|
||||||
style_instruction, str(data.get("language") or ""),
|
style_instruction, str(data.get("language") or ""),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
audio, media_type = await asyncio.to_thread(
|
audio, media_type = await _bounded_tts_call(
|
||||||
_tts_request_audio, text, voice, settings, style_instruction,
|
_tts_request_audio, text, voice, settings, style_instruction,
|
||||||
)
|
)
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_routing_log_request(
|
_routing_log_request(
|
||||||
request, status="error", app=request_app, requested_voice=original_voice,
|
request, status="error", app=request_app, requested_voice=original_voice,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user