perf: bound stt concurrency
This commit is contained in:
parent
c1ad0cdc46
commit
6c75acbf72
@ -34,6 +34,8 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
|
||||
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;
|
||||
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
|
||||
concurrent recognition requests from overcommitting the inference device.
|
||||
- **Security Regression Coverage.** Added a Python test gate for character metadata validation, private-download URL
|
||||
rejection and oversized JSON request rejection.
|
||||
- **Navigation Reliability.** Rehearser phase requests now survive the feature-module initialization boundary.
|
||||
|
||||
2
TODO.md
2
TODO.md
@ -122,7 +122,7 @@ the first block before investing in architecture or polish.
|
||||
- [ ] 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
|
||||
portrait/reference rendering and preview/style/seed-finder synthesis are capped at two active jobs; extend the
|
||||
cap to full speech-proxy and STT paths.
|
||||
cap to full speech-proxy paths; STT transcription and benchmarks are capped at one active job.
|
||||
- [ ] Use bounded server queues.
|
||||
|
||||
## P0 — Security
|
||||
|
||||
@ -24,6 +24,19 @@ from core.audio import _to_wav_16k
|
||||
from core.voice import _AUDIO_EXTS, _UPLOAD_EXTS
|
||||
|
||||
router = APIRouter()
|
||||
_STT_GENERATION_SLOT = asyncio.Semaphore(1)
|
||||
|
||||
|
||||
async def _bounded_stt_call(fn, *args):
|
||||
"""Serialize heavyweight STT work so one process cannot overcommit a GPU."""
|
||||
try:
|
||||
await asyncio.wait_for(_STT_GENERATION_SLOT.acquire(), timeout=30)
|
||||
except asyncio.TimeoutError:
|
||||
raise HTTPException(429, "STT generation queue is busy; retry shortly")
|
||||
try:
|
||||
return await asyncio.to_thread(fn, *args)
|
||||
finally:
|
||||
_STT_GENERATION_SLOT.release()
|
||||
|
||||
# ── STT backend definitions ───────────────────────────────────────────────────
|
||||
|
||||
@ -481,7 +494,7 @@ async def transcribe(request: Request):
|
||||
raw_backend = data.get("backend") or data.get("stt_backend") or settings.get("stt_preferred_backend") or "configured"
|
||||
backend = _clean_stt_backend(str(raw_backend))
|
||||
try:
|
||||
text, used_backend = await asyncio.to_thread(_transcribe_audio, src, settings, backend)
|
||||
text, used_backend = await _bounded_stt_call(_transcribe_audio, src, settings, backend)
|
||||
return {"text": text, "backend": used_backend}
|
||||
except Exception as e:
|
||||
raise HTTPException(502, f"STT error ({backend}): {e}")
|
||||
@ -505,7 +518,7 @@ async def transcribe_bytes(
|
||||
wav_tmp = _to_wav_16k(tmp)
|
||||
settings = _load_settings()
|
||||
stt_backend = _clean_stt_backend(backend)
|
||||
text, used_backend = await asyncio.to_thread(_transcribe_audio, wav_tmp, settings, stt_backend)
|
||||
text, used_backend = await _bounded_stt_call(_transcribe_audio, wav_tmp, settings, stt_backend)
|
||||
return {"text": text, "backend": used_backend}
|
||||
except HTTPException:
|
||||
raise
|
||||
@ -571,7 +584,7 @@ async def stt_benchmark(
|
||||
for engine in engines:
|
||||
if not isinstance(engine, dict):
|
||||
continue
|
||||
results.append(await asyncio.to_thread(_benchmark_stt_engine, wav_tmp, engine, settings, reference))
|
||||
results.append(await _bounded_stt_call(_benchmark_stt_engine, wav_tmp, engine, settings, reference))
|
||||
ok_rows = [r for r in results if r.get("ok")]
|
||||
best_time = min((r.get("time_sec") for r in ok_rows if isinstance(r.get("time_sec"), (int, float))), default=None)
|
||||
best_accuracy = max((r.get("accuracy") for r in ok_rows if isinstance(r.get("accuracy"), (int, float))), default=None)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user