Skip to content

[RFC] Offload blocking TTS/speech ops to thread pool to unblock event loop#2511

Merged
linyueqian merged 3 commits intovllm-project:mainfrom
scyyh11:fix/offload-tts-blocking-ops
Apr 7, 2026
Merged

[RFC] Offload blocking TTS/speech ops to thread pool to unblock event loop#2511
linyueqian merged 3 commits intovllm-project:mainfrom
scyyh11:fix/offload-tts-blocking-ops

Conversation

@scyyh11
Copy link
Copy Markdown
Contributor

@scyyh11 scyyh11 commented Apr 6, 2026

Purpose

Offload synchronous CPU-bound TTS preprocessing operations to a shared ThreadPoolExecutor(max_workers=1) to prevent blocking the asyncio event loop under concurrent TTS requests. This is the same class of fix as vllm-project/vllm#34789 applied to vllm-omni's TTS/speech serving layer.

Offloaded methods (called from async def _prepare_speech_generation()):

  • _build_voxtral_prompt: sync tokenizer.encode_speech_request()
  • _build_fish_speech_prompt: lazy AutoTokenizer.from_pretrained() + tokenization + np.save()
  • _estimate_prompt_len: lazy AutoTokenizer.from_pretrained() + model computation

Intentionally kept synchronous:

  • _build_tts_params: offloading would break atomicity with validation for uploaded-voice requests, since delete_voice() can mutate self.uploaded_speakers between the await point and the dict access.

max_workers=1 serializes tokenizer access to avoid Rust RefCell "Already borrowed" errors from concurrent use.

Closes #2476

Test Plan

Unit tests:

pytest tests/entrypoints/openai_api/test_serving_speech.py -v --timeout=60

Event loop responsiveness benchmark (Qwen3-TTS, 500 reqs @ concurrency 50):

# Terminal 1: TTS benchmark
python benchmarks/qwen3-tts/vllm_omni/bench_tts_serve.py \
    --port 8000 --num-prompts 500 --max-concurrency 50 \
    --config-name "async_chunk" --result-dir results/

# Terminal 2: /health latency during TTS load
for i in $(seq 1 100); do
  curl -w "%{time_total}\n" -s -o /dev/null http://localhost:8000/health
  sleep 0.1
done

Test Result

Environment: A100-SXM4-80GB, Python 3.11.5, vllm 0.19.0, Qwen3-TTS-12Hz-0.6B-CustomVoice

Unit Tests

112 passed, 0 failed (3.37s)

Event Loop Responsiveness — /health latency during 500 reqs @ concurrency 50

/health Metric Main Fix Improvement
P50 0.93ms 1.01ms ~same
P95 3.34ms 2.58ms 23% better
P99 5.66ms 4.37ms 23% better
Max 18.96ms 10.77ms 43% better

Throughput — 500 reqs @ concurrency 50

Metric Main Fix Diff
Request throughput (req/s) 0.79 0.82 +3.4%
Audio throughput (audio-s/s) 4.46 4.62 +3.4%
P99 E2E (ms) 72483 70594 -2.6%
P99 RTF 16.66 15.07 -9.5%

No regression. Fix branch is slightly faster (~3-4% throughput, ~10% better P99 RTF).

Shutdown

SIGTERM under load: server exited within <1s, no hung workers. shutdown(wait=False, cancel_futures=True) cancels queued work cleanly.


Essential Elements of an Effective PR Description Checklist
  • The purpose of the PR, such as "Fix some issue (link existing issues this PR will resolve)".
  • The test plan. Please provide the test scripts & test commands. Please state the reasons if your codes don't require additional test scripts. For test file guidelines, please check the test style doc
  • The test results. Please paste the results comparison before and after, or the e2e results.
  • (Optional) The necessary documentation update, such as updating supported_models.md and examples for a new model. Please run mkdocs serve to sync the documentation editions to ./docs.
  • (Optional) Release notes update. If your change is user-facing, please update the release notes draft.

@scyyh11 scyyh11 requested a review from hsliuustc0106 as a code owner April 6, 2026 00:32
@scyyh11 scyyh11 force-pushed the fix/offload-tts-blocking-ops branch from 44ed9ac to d31d27d Compare April 6, 2026 01:45
…ent loop

Offload synchronous CPU-bound TTS preprocessing operations to a shared
ThreadPoolExecutor(max_workers=1) to prevent blocking the asyncio event
loop under concurrent TTS requests. This is the same class of fix as
vllm-project/vllm#34789 applied to the renderer pipeline.

Affected methods in _prepare_speech_generation():
- _build_voxtral_prompt: sync tokenizer.encode_speech_request()
- _build_fish_speech_prompt: lazy AutoTokenizer.from_pretrained() + tokenization + np.save()
- _estimate_prompt_len: lazy AutoTokenizer.from_pretrained() + model computation

_build_tts_params is intentionally kept synchronous: offloading it would
break atomicity with validation for uploaded-voice requests, since
delete_voice() can mutate self.uploaded_speakers between the await point
and the dict access.

max_workers=1 serializes tokenizer access to avoid Rust RefCell
"Already borrowed" errors from concurrent use.

Closes: vllm-project#2476

Signed-off-by: Bvicii <yizhanhuang2002@gmail.com>
@scyyh11 scyyh11 force-pushed the fix/offload-tts-blocking-ops branch from d31d27d to a1dbcd6 Compare April 6, 2026 01:49
@scyyh11
Copy link
Copy Markdown
Contributor Author

scyyh11 commented Apr 6, 2026

@linyueqian PTAL

@scyyh11 scyyh11 changed the title [Bugfix] Offload blocking TTS/speech ops to thread pool to unblock event loop [RFC] Offload blocking TTS/speech ops to thread pool to unblock event loop Apr 7, 2026
@scyyh11
Copy link
Copy Markdown
Contributor Author

scyyh11 commented Apr 7, 2026

@hsliuustc0106 Hi, I think this PR is ready for review now, PTAL, thank you.

Copy link
Copy Markdown
Collaborator

@linyueqian linyueqian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@linyueqian linyueqian added the ready label to trigger buildkite CI label Apr 7, 2026
@linyueqian linyueqian merged commit badbe8e into vllm-project:main Apr 7, 2026
8 checks passed
vraiti pushed a commit to vraiti/vllm-omni that referenced this pull request Apr 9, 2026
… loop (vllm-project#2511)

Signed-off-by: Bvicii <yizhanhuang2002@gmail.com>
@scyyh11 scyyh11 deleted the fix/offload-tts-blocking-ops branch April 20, 2026 22:02
bob-021206 pushed a commit to jasonlee-1024/vllm-omni that referenced this pull request Apr 21, 2026
… loop (vllm-project#2511)

Signed-off-by: Bvicii <yizhanhuang2002@gmail.com>
Signed-off-by: bob-021206 <binyan_github@163.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready label to trigger buildkite CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[RFC]: Offload blocking TTS/speech ops to thread pool to unblock event loop

2 participants