Repository navigation
[Perf] Avoid thread offload in full duplex orchestrator when possible - #9491
AlessioNetti wants to merge 3 commits into
Conversation
- For audio samples that are already in PCM float32 format, convert_input_audio_with_rate is a no-op. However, offloading this to the shared async queue still introduces mutex contention. This change skips the offload when possible. Signed-off-by: Alessio Netti <26897207+AlessioNetti@users.noreply.github.com>
- By default, session commands (e.g., new input audio frames enqueueing) are submitted via offloading to a thread pool with run_in_executor(), which creates significant overhead under high concurrency. This commit implements a fast path that attempts a direct put_nowait() enqueue, and falls back to the previous waiting path if the queue is full. Signed-off-by: Alessio Netti <26897207+AlessioNetti@users.noreply.github.com>
- The DuplexOmniEngine._submit_command() method now takes as input a DuplexSessionCommandMessage argument, as well as a "block" boolean - This allows for submit_command_async() to attempt non-blocking enqueue and blocking fallback using the same message object, with _submit_command() using put() in both cases Signed-off-by: Alessio Netti <26897207+AlessioNetti@users.noreply.github.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
|
This PR appears to belong to: docs/design/module/engine_orchestration.md, docs/design/module/observability.md. Module owners: @fake0fan @tzhouam @lishunyang12 Routing: @fake0fan via module of the changed files, CODEOWNERS; @tzhouam via module of the changed files, CODEOWNERS; @lishunyang12 via module of the changed files @AlessioNetti, please review your own changes and leave a short self-review comment describing what you checked. PRs without author self-review may not be assigned a reviewer. Please take a look when you have a chance. If you would like an automated review, mention @vllm-omni-review-bot in a comment. |
Omni ReviewBot triage noteAutomated triage of commit
These are automated triage suggestions only — the final decision belongs to the maintainers. |
hsliuustc0106
left a comment
There was a problem hiding this comment.
@AlessioNetti — performance-evidence TODO for 40916cfdc254c96a320d5e2c08f57652e49fa646.
Source inspection supports the native-PCM bypass and uncontended queue fast path, but the supplied CPU test count does not verify the reported RTF, context-switch and preprocessing improvements.
Please attach reproducible matched A/B results for baseline b06d34f355c948cea6cdbe6fce1aed485cec7c4a versus this head: Hibiki checkpoint and external plugin revision, hardware/software, launch/benchmark commands, audio/chunk workload and concurrency 64/96, warmup/repetitions, raw metrics and variability. Include output completeness/parity and sustained audio cadence to rule out dropped work.
Acceptance: identical workload/configuration on both commits reproduces the claimed metrics with the correctness guard passing, or narrow the performance claims to the evidence available. This is an evidence request, not a demonstrated code defect. Review was source-only; no local or GPU tests were executed here.
|
Hi @hsliuustc0106 - thanks for the feedback. I'm unable to share the full Hibiki model implementation and much of the frontend code, but I will attach as much evidence as possible. First, below you can find min, median and max per-request latency values from our internal benchmark. Each request feeds 60s of audio split into 160ms frames (which are internally further split into 80ms frames as expected by the Mimi encoder), and produces 60,960ms of output audio (750 + 12 80ms frames). We evaluated concurrency levels 64 and 96, issuing
The measurements were collected on a host equipped with an NVIDIA RTX Pro 6000 Blackwell GPU (CUDA 13.3), and an AMD EPYC 9454 48-Core CPU. The I also included a self-contained synthetic CPU benchmark, which you can find as a gist here. This micro-benchmark mocks away any model-related work (including model-specific post-processing) but uses a real vllm-omni full-duplex orchestrator, and measures several key latency metrics. It can be called as in the following: Note that by default the micro-benchmark will not pace input audio frames, but will send them in a single burst - to enable paced execution the Using the command above (again, in burst mode), I obtained the following measurements for the vllm-omni baseline at concurrency levels 64 and 96: And for this PR's head: You can observe the same stark reduction in voluntary context switches and pre-processing latency that I mentioned in the PR description. Please do let me know if you'd like me to push the micro-benchmark as well, and if these results address your concerns. |
Omni ReviewBot routing recordAssigned Strict on zcode (GLM-5.3-Flash) under experiment |
Omni ReviewBot routing recordAssigned Strict on claude-code (mimo-v2.6-pro) under experiment |
Omni ReviewBot routing recordAssigned Strict on zcode (GLM-5.3-Flash) under experiment |
Omni ReviewBot routing recordAssigned Strict on claude-code (qwen3.8-max) under experiment |
Omni ReviewBot routing recordAssigned Strict on claude-code (qwen3.8-flash) under experiment |
Contributor intent (required human answers)
1. What outcome do you want to achieve?
We want to minimize orchestrator host overhead for full duplex models and increase maximum real-time concurrency accordingly. Our changes are targeted at small models such as Hibiki zero, where even a single-GPU deployment is sufficient for most scenarios and host overhead is more critical.
2. What is the highest-leverage variable for that outcome?
It looks like the full duplex orchestrator thread is a real bottleneck, especially for models such as Hibiki zero. This is mainly due to the fact that the orchestrator runs in a serial asyncio loop, and makes use of thread pool offloading for several tasks (e.g., waveform conversion). This leads to noticeable GIL contention and saturation at high concurrency regimes.
3. How will this work move that variable?
By avoiding thread offloading for certain operations (e.g., when waveform conversion is a no-op), we can materially reduce GIL contention and handoff overhead, and improve the real-time factor at high concurrency.
4. Whose customer or user needs does this address, and what are those needs?
The main need we are addressing is to minimize cost of deployment, i.e., allow as many concurrent streams as possible on the same GPU (or same set of GPUs), and ideally reach a regime where host overhead is no longer a concern.
Purpose
We use a custom vllm-omni implementation of the Moshi/Hibiki architecture (as described in PR 8554) that is integrated as an out-of-tree full duplex plugin.
At high concurrency regimes, we noticed via pyspy profiling that the orchestrator thread is easily saturated, which will lead to latency falling behind real-time. It appears that this performance hit is coming from GIL contention with the main frontend thread, as well as from the handoffs associated with the thread pool used for offloading.
This PR attacks this bottleneck and implements two small optimizations that significantly improve latency at no cost:
DuplexSessionRunner._on_append_audio(), waveform conversion inconvert_input_audio_with_rate()is offloaded to a thread pool even when it is a no-op (i.e., when audio is alreadypcm_f32le). We now skip this round-trip entirely when audio is already in the right format;DuplexOmniEngine.submit_command_async()always performs thread offloading to call the blocking Janus queue'sput()method. This can be avoided if the queue has capacity - we now first attempt a non-blockingput()in the main thread, and proceed with thread offloading only if the queue is full. Note that the queue's default size is 256.The two optimizations above lead to a measurable impact on latency, with a ~0.1 improvement on the real-time factor that allows us to reach concurrency 64 when producing ~61s of output audio. We observe a larger improvement at concurrency 96, where latency remains however slower than real-time.
At a lower level, we observed a ~41% reduction in voluntary context switches in the main process, and a large reduction in pre-processing median times (i.e.,
_on_append_audio()) at concurrency 96, from 53.89ms down to 0.003ms.Test Plan
vLLM Version: 0.32.0
vLLM-Omni commit: b06d34f35
Tested branch commit: 40916cfdc
Prerequisites: Repository test dependencies installed. CPU-only tests; no GPU or model checkpoint required.
Added or strengthened coverage:
test_input_conversion_offload_only_for_encoded_formatstest_full_command_queue_offloads_wait_and_preserves_sequential_ordertest_submit_command_is_one_way_on_the_request_queuetest_audio_admission_is_held_until_preprocessing_finishesRun the added and updated coverage:
Test Result
Note
To avoid agent spam, vLLM-Omni limits contributors without write access to 5 open PRs. If this blocks well-intentioned critical work, please contact a committer to request bypass list access.
(anything written below this line will be removed by GitHub Actions)