[Perf][CI] Use Whisper on GPU when memory permits (#5675)

Signed-off-by: akshatvishu <akshatnayak197@gmail.com>
Signed-off-by: andyluo7 <andy.luo@amd.com>
Co-authored-by: andyluo7 <andy.luo@amd.com>
This commit is contained in:
akshatvishuandandyluo7 authored and GitHub committed 2026-09-04 16:35:48 -07:00
1 parent b6b992ac7b
commit ae57e406fd
4 files changed
+64 -12

No files matched your search

+7 -4
View File
@@ -79,6 +79,7 @@ steps:
steps:
- label: "TTS · Qwen3-TTS CustomVoice Test"
source_file_dependencies:
- tests/helpers/media.py
- tests/e2e/online_serving/test_qwen3_tts_customvoice.py
- tests/e2e/offline_inference/test_qwen3_tts_customvoice.py
- vllm_omni/model_executor/models/qwen3_tts/
@@ -101,6 +102,7 @@ steps:
- label: "TTS · Qwen3-TTS Base Test"
source_file_dependencies:
- tests/helpers/media.py
- tests/e2e/online_serving/test_qwen3_tts_base.py
- tests/e2e/offline_inference/test_qwen3_tts_base.py
- vllm_omni/model_executor/models/qwen3_tts/
@@ -161,10 +163,11 @@ steps:
commands:
- pytest -s -v tests/e2e/offline_inference/test_minicpmo_4_5.py tests/e2e/online_serving/test_minicpmo_4_5.py -m 'advanced_model and cuda' --run-level 'advanced_model'
# h100_2, not h100_1: advanced_model asserts transcribe every audio
# response with Whisper, and tests/helpers/media.py only uses an
# accelerator for Whisper when a spare GPU exists. On a single card it
# grades on CPU: ~8-10 min per clip vs ~30 s, which alone overruns the
# 50-minute job timeout (build 2956).
# response with Whisper, and tests/helpers/media.py requires at least
# 16 GiB free VRAM on a device to run Whisper on GPU. When the model
# server leaves insufficient memory, Whisper falls back to CPU:
# ~8-10 min per clip vs ~30 s, which overruns the 50-minute job timeout
# (build 2956). h100_2 guarantees a second GPU with ample free VRAM.
mirror_hardwares: h100_2
- label: "Omni · MiniCPM-o 4.5 Duplex Test"
+1
View File
@@ -250,6 +250,7 @@ steps:
- label: "TTS · Qwen3-TTS CustomVoice Test"
source_file_dependencies:
- tests/helpers/media.py
- tests/e2e/online_serving/test_qwen3_tts_customvoice.py
- vllm_omni/model_executor/models/qwen3_tts/
- vllm_omni/model_executor/models/common/qwen3_code_predictor.py
+12 -6
View File
@@ -31,6 +31,8 @@ from PIL import Image
logger = logging.getLogger(__name__)
_MIN_FREE_VRAM = 16 * 1024**3
_synthetic_media_fallback_dir: Path | None = None
@@ -711,12 +713,16 @@ def _select_whisper_device() -> str:
if current_omni_platform.is_available():
n = current_omni_platform.get_device_count()
# Single-GPU runners (e.g. the L4 nightly): the model server already
# occupies device 0, and a Whisper model resident there competes with
# it for VRAM and OOMs. Only borrow an accelerator when a spare device
# exists; otherwise validate on CPU.
if n > 1:
device_index = n - 1
# Prefer the highest-index device with enough room for Whisper. The
# 16 GiB floor protects low-memory single-GPU runners such as the L4
# nightly, where the model server already occupies device 0, from the
# server-plus-Whisper OOM fixed in #3822. Keep the CPU fallback when no
# device has enough room.
for candidate_index in range(n - 1, -1, -1):
candidate_device = current_omni_platform.get_torch_device(candidate_index)
if current_omni_platform.get_free_memory(candidate_device) >= _MIN_FREE_VRAM:
device_index = candidate_index
break
if device_index is None:
return "cpu"
+44 -2
View File
@@ -1,9 +1,10 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
import sys
from concurrent.futures.process import BrokenProcessPool
from contextlib import nullcontext
from multiprocessing.context import BaseContext
from types import SimpleNamespace
import numpy as np
@@ -69,6 +70,7 @@ class _FakeExecutor:
def __init__(self, outcomes=()):
self._outcomes = list(outcomes)
self.init_kwargs: dict[str, object] = {}
self.submitted: list[tuple] = []
self.shutdown_calls = 0
@@ -113,6 +115,44 @@ def _reset_transcriber_singletons():
media._WHISPER_MODELS.clear()
@pytest.mark.parametrize(
("free_memory_by_device", "expected_device", "expected_probe_order"),
[
([media._MIN_FREE_VRAM], "cuda:0", [0]),
([media._MIN_FREE_VRAM] * 3, "cuda:2", [2]),
([8 * 1024**3, 20 * 1024**3, 24 * 1024**3], "cuda:2", [2]),
([media._MIN_FREE_VRAM - 1], "cpu", [0]),
([20 * 1024**3, 8 * 1024**3], "cuda:0", [1, 0]),
([8 * 1024**3, 12 * 1024**3], "cpu", [1, 0]),
],
)
def test_select_whisper_device_by_available_memory(
monkeypatch, free_memory_by_device, expected_device, expected_probe_order
):
probed_devices = []
def get_free_memory(device):
index = int(device.rsplit(":", 1)[1])
probed_devices.append(index)
return free_memory_by_device[index]
platform = SimpleNamespace(
is_available=lambda: True,
get_device_count=lambda: len(free_memory_by_device),
get_torch_device=lambda index: f"cuda:{index}",
get_free_memory=get_free_memory,
set_device=lambda device: None,
)
monkeypatch.setitem(
sys.modules,
"vllm_omni.platforms",
SimpleNamespace(current_omni_platform=platform),
)
assert media._select_whisper_device() == expected_device
assert probed_devices == expected_probe_order
def test_bytes_entrypoint_forwards_language_to_subprocess(monkeypatch, tmp_path):
"""Cover the two hops the tests above skip: bytes -> file -> executor.submit.
@@ -147,7 +187,9 @@ def test_parent_reuses_one_spawned_worker_across_calls(monkeypatch):
assert len(created) == 1
assert len(created[0].submitted) == 2
assert created[0].init_kwargs["max_workers"] == 1
assert created[0].init_kwargs["mp_context"].get_start_method() == "spawn"
mp_context = created[0].init_kwargs["mp_context"]
assert isinstance(mp_context, BaseContext)
assert mp_context.get_start_method() == "spawn"
def test_release_forces_a_new_process_pool(monkeypatch):