fix(tts-server): bound admission, not generation, by the 5s deadline - #30
Merged
Merged
Conversation
Root cause of the live 503 that dropped a response turn. api.py computed deadline = now + DEFAULT_SYNTHESIS_ADMISSION_TIMEOUT_SECONDS at request entry and then enforced it over the whole request: the polling loop raised SynthesisAdmissionTimeout while the worker already held the model lock and was generating, and a successfully completed result was discarded if it landed after the deadline. The name said admission; the semantics were 'total request must finish in 5 s'. Measured idle generation is 1.5-3.5 s, so a busy shared GPU crossed it routinely. CloneRuntime.synthesize now takes an 'admitted' event and sets it once the model lock is held and the post-acquire deadline check has passed - never earlier. _run_stable_synthesis enforces the deadline only while that event is unset, so queue and lock-wait time stay bounded (a caller may already be gone) while in-flight generation runs to completion, bounded by client disconnect and the client's own HTTP timeout. Also makes the budget configurable via SYNTHESIS_ADMISSION_TIMEOUT_SECONDS (malformed or non-positive falls back to the default rather than refusing to start) and records the investigation in TODO.md.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Root cause
The live
503that silently dropped a response turn came from the TTS service itself, not from load shedding.dgx/tts/api.py::_run_stable_synthesissetdeadline = monotonic() + DEFAULT_SYNTHESIS_ADMISSION_TIMEOUT_SECONDS(5.0 s) at request entry and then enforced it over the entire request:SynthesisAdmissionTimeoutonce the deadline passed even though the worker thread already held the model lock and was generatingapi.py:99mapped that to503 "synthesis admission timed out"The name said admission; the semantics were "the whole request must finish in 5 s".
create_appwas called without the argument (server.py), so the 5 s was effectively hardcoded.Evidence
Lock contention, worker restart, and Traefik shedding are all ruled out:
qwen3-ttsaccess log:18:28:54 200,18:29:08 503,18:29:11 200— serialized ~14 s apart with no499between them, so the model lock was free and admission was instant. The 5 s elapsed inside generation.proxy-traefik-1logged zero 503s that hour.Up (healthy)."Ja."0.3–0.5 s, the sentence that failed 1.5–1.7 s, a long sentence 2.9–3.5 s — ~70 % of the budget with the GPU idle.companion-llmreportedRunning: 1with active generation at 18:29:05 andqwen3-asrtranscribed at 18:29:01.7. A ~3.3× slowdown on a 1.5 s sentence crosses 5 s.Fix
CloneRuntime.synthesizetakes anadmittedevent and sets it once the model lock is held and the post-acquire deadline check has passed — never earlier._run_stable_synthesisenforces the deadline only whileadmittedis unset. Queue time and lock wait stay bounded (the caller may already be gone); in-flight generation runs to completion, bounded by client disconnect (already detected) and the client's own 30 s HTTP timeout.SYNTHESIS_ADMISSION_TIMEOUT_SECONDS. A malformed or non-positive value falls back to the default rather than refusing to start — a TTS outage takes every call down with it.Deliberately preserved: a request that never reaches admission is still rejected at the deadline, so a worker stuck in a saturated executor queue cannot start the model late for a caller who has gone away. The existing tests for that behavior (
test_stable_timeout_includes_saturated_executor_queue_time,test_stable_absolute_deadline_prevents_late_executor_model_start) pass unchanged.Testing
TDD — each test watched failing first:
test_admitted_synthesis_outlives_the_admission_deadline— admitted before the deadline, completes after → result delivered, cancel event not settest_slow_generation_returns_audio_not_503— end-to-end through the route: generation 3× the budget still returns200 audio/wavtest_synthesize_signals_admission_before_generation— the event is set by the time the model is calledtest_admission_timeout_leaves_admission_unsignalled— a request that times out waiting for the lock never signals admissiontest_admission_timeout_from_env_*— override read; empty/malformed/zero/negative fall backtest_stable_absolute_deadline_rejects_late_successrenamed to..._rejects_never_admitted_late_success, since that is what it actually pins nowvenv/bin/pytest -q→ 343 passed.ruff format --check/ruff checkclean.Deployment
Needs a GPU image rebuild and a stack update on the DGX; the agent-side retry already masks the symptom in the meantime.