Repository navigation
[Model] Run MiniCPM-o 4.5 speaker embeddings on CUDA - #9178
amy-why-3459 merged 3 commits into
Conversation
Signed-off-by: natureofnature <wzliu@connect.hku.hk>
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/ar_runtime.md, docs/design/module/model_integration.md. Module owners: @tzhouam @fake0fan @Gaohan123 Routing: @tzhouam via module of the changed files, CODEOWNERS; @fake0fan via module of the changed files; @Gaohan123 via module of the changed files @natureofnature, 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. |
amy-why-3459
left a comment
There was a problem hiding this comment.
Change looks good to me. The use_trt=False keyword keeps the shared StepAudio2Token2WavCore.enable_device_spk_embedding() default (TRT on CUDA) for other callers, token2wav_trt still takes its own branch first, and get_campplus_torch is cached per (onnx_path, device index), so multiple Code2Wav replicas share one CAM++ copy. One doc precision nit inline.
| per request. Cache epochs remain separate unless cross-turn batching is enabled. | ||
|
|
||
| The MiniCPM Code2Wav stage runs CAM++ speaker embedding on CUDA and NPU using | ||
| the in-tree FP32 torch model loaded from the checkpoint's `campplus.onnx`. |
There was a problem hiding this comment.
Small precision nit: on CUDA the reference preparation (chunk 0 and the idle prefetch) runs inside _stage_matmul_policy, which turns on torch.backends.cuda.matmul.allow_tf32 under the default token2wav_allow_tf32, and cuDNN convs use TF32 by PyTorch default. So with default settings CAM++ isn't strictly FP32 on CUDA. Your parity table already covers TF32 on/off, so behavior is fine; it might just be worth saying "FP32 weights; matmuls follow token2wav_allow_tf32" here so the accuracy-triage guidance (token2wav_device_spk: false) is read correctly.
There was a problem hiding this comment.
Agree with @amy-why-3459. In ac6df62, both chunk-0 preparation and idle prefetch still enter _stage_matmul_policy, while CampplusTorch keeps weights and inputs in FP32. Non-blocking: describing this as FP32 weights with the stage's CUDA matmul policy (and separate cuDNN policy) would make the precision guidance clearer.
natureofnature
left a comment
There was a problem hiding this comment.
Reviewed all five changed files and traced backend selection, prompt preparation, CAM++ weight loading/device caching, and speaker conditioning. The CPU opt-out and explicit TRT branch remain intact, and other callers retain the shared helper's TRT default. The reported final-base H800 MRv2 checks, TF32-on/off embedding parity, and focused tests cover the changed CUDA path; current CI checks pass. This was static review, without rerunning those tests. The remaining documentation suggestion is non-blocking and noted in the existing thread.
【Conclusion: Found no blocking issues in commit ac6df62.】
【Action recommended: None. Optional: Clarify FP32 weights versus CUDA compute precision in the documentation.】
Signed-off-by: natureofnature <wzliu@connect.hku.hk>
natureofnature
left a comment
There was a problem hiding this comment.
Rechecked the update against ac6df62. The documentation now correctly distinguishes FP32 weights and inputs from CUDA matmul and cuDNN compute policies, resolving the earlier non-blocking suggestion. Runtime code and tests are unchanged, so the previously reviewed CUDA-path validation still applies. Current-head documentation, CodeQL, pre-commit, wheel builds and DCO checks have all passed; I did not rerun the hardware tests.
【Conclusion: Found no blocking issues in commit 3f39c71.】
【Action recommended: None.】
Signed-off-by: natureofnature <wzliu@connect.hku.hk> Co-authored-by: amy-why-3459 <wuhaiyan17@huawei.com>
Purpose
MiniCPM-o 4.5's default CUDA Code2Wav path runs CAM++ speaker embedding through CPU ONNX Runtime. Reference preparation still runs separately for each voice, so this host work delays the output of a batch even when its CFM decode can batch different references.
Use the existing torch CAM++ implementation on CUDA with FP32 weights and inputs, loading the same
campplus.onnxweights. CUDA reference preparation follows the stage's matmul policy (token2wav_allow_tf32, enabled by default); cuDNN's convolution TF32 policy is independent. FP32 tensors do not imply strict FP32 arithmetic.token2wav_device_spk: falseretains the CPU fallback, and explicitly enablingtoken2wav_trtretains its TensorRT path. Other callers of the shared device-speaker helper keep their existing default behavior.Scheduling capacity, chunk sizes, sampling, CFM/HiFT precision, and MPS settings are unchanged. The existing MiniCPM performance contract documents the CUDA path and fallback.
Contributor intent
1. What outcome do you want to achieve?
Improve MiniCPM-o 4.5 serving throughput and first-audio latency with different reference voices.
2. What is the highest-leverage variable for that outcome?
The serial CPU work in reference preparation before Code2Wav can return a batch.
3. How will this work move that variable?
Run speaker embedding on CUDA using the existing torch implementation.
4. Whose customer or user needs does this address, and what are those needs?
Users serving concurrent MiniCPM-o 4.5 speech requests on one GPU who need responsive first audio and higher throughput with their reference voices.
Test Plan
vLLM Version: 0.32.0; PyTorch 2.13.0+cu130; ONNX 1.23.2; ONNX Runtime 1.31.0. Recorded NVIDIA driver: 535.161.08.
vLLM-Omni Commit: measured PR revision
ac6df62263a9ac8b27b593fd43a34c3dde1c1bc4on main3e40008bbeedfdad7d82e1fd25e76db5820025c0. The full C1/4/8/16/32 sweep was frozen at baseline18c3bd5d2d4323bc7adca1ac3c465c4a53bbfb6e, with and without this PR's production patch. After #8911 changed the shared Talker path, the PR was rebased and a separate C16 A/B plus C1/C8 quality checks were run on the final revisions. All three changed production files match between the two campaigns; all 32 official benchmark files match between sources. Follow-up commit3f39c7187a9faa535943a796c9276ff0fa14f683only clarifies precision documentation; production and test files are unchanged.vllm bench serve --omni,openai-chat-omnibackend, C1/4/8/16/32.openbmb/MiniCPM-o-4_5at073dbbc8c5bc0af2d789e1ce12e7c17a6be746e1.8f5e1aa2a35d42f42e940074c1983358b9491f89: 64 fixed evaluation requests with 63 reference WAVs. Exact IDs and hashes: input manifest.enforce_eager: false; Code2Wav usesenforce_eager: truewith its internal CFM/HiFT CUDA graphs enabled.Test Result
Throughput is synthesized audio-seconds per benchmark wall-second. RTF is the official CLI's mean
audio_rtf. TTFA columns average the official per-run percentiles.Full-resolution PNG · SVG. Lines show means and shading shows the min–max across four measured rounds. This figure summarizes the full sweep on baseline
18c3bd5d2; the latest-main C16 confirmation is reported separately below.Per-run ranges, request throughput, and output duration
Ranges cover all four measured rounds per source/C. Each row uses 64 requests per round.
Observed peak GPU memory from 1-second NVML samples across the primary starts (including startup): 55035 MiB main → 55057 MiB PR. These are sampled device-memory peaks, not allocator high-water marks.
Generated audio duration varies slightly with stochastic speech sampling. Both audio throughput and request throughput are reported. These results apply to this H800 reference-audio workload; H200 and BF16 CFM were not tested.
Latest-main confirmation (after #8911): two measured 64-request C16 rounds per source after fresh server starts, with the same settings and official CLI. All 256 measured requests succeeded. These results are separate from the full sweep above.
Mean request throughput: 5.848 → 7.059 req/s. Mean output audio per 64 requests: 237.44 → 235.54 s.
The first attempted main start failed during initial HiFT warmup with a cuDNN engine error followed by a CUDA illegal memory access, before any measured rounds. The replacement pair and the primary comparison were selected before inspecting candidate performance. All 2,560 primary measured requests succeeded. The additional successful PR sweep is retained separately: 640/640 requests, mean audio-s/s C1=8.50, C4=18.54, C8=23.98, C16=24.70, C32=25.18.
Speaker embedding parity: 63 real reference WAVs, with CUDA matmul TF32 both off and on (126 cases). Minimum cosine similarity of normalized embeddings: 0.9999985; maximum normalized absolute difference: 4.23e-4. Isolated mean forward time, after warming each input shape: 42.61 ms CPU → 10.52 ms CUDA. This component measurement is separate from the serving benchmark.
Native Seed-TTS WER: separate quality runs, 64/64 requests per source and case. The values are mean per-request normalized WER from the pinned Whisper model below.
18c3bd5d23e40008bb3e40008bbThe initial C8 difference is retained: two utterances with extra trailing ASR text account for about 2.00 percentage points of the 2.13-point difference. The follow-up C8 comparison goes in the opposite direction and also contains long-tail baseline outputs. The two C8 comparisons also use different baseline revisions, so they are not repeated trials of the same source pair. These small samples do not establish a quality improvement, statistical equivalence, or the absence of a regression. Attribution remains unresolved, including any contribution from this PR's CUDA speaker-embedding change; sampling or concurrency variability has not been established as the cause.
In the C1 comparison, all 64 waveform durations match and 62 per-utterance WER values match. The two differences are
thirtyversus30in ASR output, andtwo theories abound atversustwo theories abounded(the reference saysTwo theories abound to). The official scores above are unchanged.All MRv2 ASR evaluations completed. Saved WAVs were validated as nonempty 24 kHz mono PCM16. Speaker similarity/MOS and NPU/TensorRT hardware were not measured.
V1 limitation: the V1 content check failed in this vLLM 0.32.0 environment. The PR returned 16/16 HTTP successes, but all outputs were about 82 seconds and Thinker emitted repeated
!up to the 256-token limit. A separate unmodified18c3bd5d2baseline reproducer showed the same text/length failure on both tested requests. This is retained as a baseline V1 failure; V1 end-to-end quality is not counted as passed.Focused regressions:
Local pre-commit passed, including mypy, test marks, and forbidden-import/CUDA-call checks.
Reproduction: inputs, server configuration, and official benchmark commands
Run from each source checkout. The full sweep uses baseline
18c3bd5d2d4323bc7adca1ac3c465c4a53bbfb6ewith and without this PR's patch; the latest-main C16 check uses the two final revisions reported above. SetMODEL_PATHto the pinned model snapshot above. Paths below can be placed in any working directory.Create the two native Seed-TTS input directories from the fixed manifest:
Both measured source copies use the same one-line compatibility adjustment for the vLLM 0.32.0 embedding API; it is excluded from this PR:
In another terminal with the same checkout, model, and input paths:
The reverse-order pair uses C32/16/8/4/1. Each source starts a fresh service for each sweep.
Quality evaluation is a separate native CLI run, excluded from the performance
table. Set
ASR_PATHto the local snapshot ofopenai/whisper-large-v3@06f233fe06e710322aca913c1bc4249a0d71fce1and use the samebenchfunction:The V1 smoke uses a fresh candidate server with the same overrides and
minicpmo_4_5_turn.yamlasbase_config, and 16 evaluation requests at C8.The baseline V1 reproducer uses baseline
18c3bd5d2, the same V1 overrides,two warmup requests, two discarded evaluation requests, and two scored requests
at C8. It checks the termination failure; it is excluded from performance results.