A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 11, 00:00 UTC
[ROCm][CI] Fix entrypoints tests and Python-only installation test on ROCm (#28979)
Signed-off-by: Andreas Karatzas <akaratza@amd.com>
Andreas Karatzas committed
Dec 24, 2025 at 00:42 UTC
0247a91e00d6a50b2218c1e7c82c72e26dba6cb1
26 files changed
+432
-116
setup.py
+126
-26
@@ -50,15 +50,15 @@ elif not (sys.platform.startswith("linux") or sys.platform.startswith("darwin"))
50
sys.platform,
51
)
52
VLLM_TARGET_DEVICE = "empty"
53
-elif (
54
- sys.platform.startswith("linux")
55
- and torch.version.cuda is None
56
- and os.getenv("VLLM_TARGET_DEVICE") is None
57
- and torch.version.hip is None
58
-):
59
- # if cuda or hip is not available and VLLM_TARGET_DEVICE is not set,
60
- # fallback to cpu
61
- VLLM_TARGET_DEVICE = "cpu"
53
+elif sys.platform.startswith("linux") and os.getenv("VLLM_TARGET_DEVICE") is None:
54
+ if torch.version.hip is not None:
55
+ VLLM_TARGET_DEVICE = "rocm"
56
+ logger.info("Auto-detected ROCm")
57
+ elif torch.version.cuda is not None:
58
+ VLLM_TARGET_DEVICE = "cuda"
59
+ logger.info("Auto-detected CUDA")
60
+ else:
61
+ VLLM_TARGET_DEVICE = "cpu"
62
63
64
def is_sccache_available() -> bool:
@@ -108,20 +108,26 @@ class cmake_build_ext(build_ext):
108
num_jobs = os.cpu_count()
109
110
nvcc_threads = None
111
- if _is_cuda() and get_nvcc_cuda_version() >= Version("11.2"):
112
- # `nvcc_threads` is either the value of the NVCC_THREADS
113
- # environment variable (if defined) or 1.
114
- # when it is set, we reduce `num_jobs` to avoid
115
- # overloading the system.
116
- nvcc_threads = envs.NVCC_THREADS
117
- if nvcc_threads is not None:
118
- nvcc_threads = int(nvcc_threads)
119
- logger.info(
120
- "Using NVCC_THREADS=%d as the number of nvcc threads.", nvcc_threads
121
- )
122
- else:
123
- nvcc_threads = 1
124
- num_jobs = max(1, num_jobs // nvcc_threads)
111
+ if _is_cuda() and CUDA_HOME is not None:
112
+ try:
113
+ nvcc_version = get_nvcc_cuda_version()
114
+ if nvcc_version >= Version("11.2"):
115
+ # `nvcc_threads` is either the value of the NVCC_THREADS
116
+ # environment variable (if defined) or 1.
117
+ # when it is set, we reduce `num_jobs` to avoid
118
+ # overloading the system.
119
+ nvcc_threads = envs.NVCC_THREADS
120
+ if nvcc_threads is not None:
121
+ nvcc_threads = int(nvcc_threads)
122
+ logger.info(
123
+ "Using NVCC_THREADS=%d as the number of nvcc threads.",
124
+ nvcc_threads,
125
+ )
126
+ else:
127
+ nvcc_threads = 1
128
+ num_jobs = max(1, num_jobs // nvcc_threads)
129
+ except Exception as e:
130
+ logger.warning("Failed to get NVCC version: %s", e)
131
132
return num_jobs, nvcc_threads
133
@@ -199,9 +205,9 @@ class cmake_build_ext(build_ext):
205
# Default build tool to whatever cmake picks.
206
build_tool = []
207
# Make sure we use the nvcc from CUDA_HOME
202
- if _is_cuda():
208
+ if _is_cuda() and CUDA_HOME is not None:
209
cmake_args += [f"-DCMAKE_CUDA_COMPILER={CUDA_HOME}/bin/nvcc"]
204
- elif _is_hip():
210
+ elif _is_hip() and ROCM_HOME is not None:
211
cmake_args += [f"-DROCM_PATH={ROCM_HOME}"]
212
213
other_cmake_args = os.environ.get("CMAKE_ARGS")
@@ -339,6 +345,89 @@ class precompiled_wheel_utils:
345
wheels = json.loads(resp.read().decode("utf-8"))
346
return wheels, repo_url
347
348
+ @staticmethod
349
+ def is_rocm_system() -> bool:
350
+ """Detect ROCm without relying on torch (for build environment)."""
351
+ if os.getenv("ROCM_PATH"):
352
+ return True
353
+ if os.path.isdir("/opt/rocm"):
354
+ return True
355
+ if which("rocminfo") is not None:
356
+ return True
357
+ try:
358
+ import torch
359
+
360
+ return torch.version.hip is not None
361
+ except ImportError:
362
+ return False
363
+
364
+ @staticmethod
365
+ def find_local_rocm_wheel() -> str | None:
366
+ """Search for a local vllm wheel in common locations."""
367
+ import glob
368
+
369
+ for pattern in ["/vllm-workspace/dist/vllm-*.whl", "./dist/vllm-*.whl"]:
370
+ wheels = glob.glob(pattern)
371
+ if wheels:
372
+ return sorted(wheels)[-1]
373
+ return None
374
+
375
+ @staticmethod
376
+ def fetch_wheel_from_pypi_index(index_url: str, package: str = "vllm") -> str:
377
+ """Fetch the latest wheel URL from a PyPI-style simple index."""
378
+ import platform
379
+ from html.parser import HTMLParser
380
+ from urllib.parse import urljoin
381
+ from urllib.request import urlopen
382
+
383
+ arch = platform.machine()
384
+
385
+ class WheelLinkParser(HTMLParser):
386
+ def __init__(self):
387
+ super().__init__()
388
+ self.wheels = []
389
+
390
+ def handle_starttag(self, tag, attrs):
391
+ if tag == "a":
392
+ for name, value in attrs:
393
+ if name == "href" and value.endswith(".whl"):
394
+ self.wheels.append(value)
395
+
396
+ simple_url = f"{index_url.rstrip('/')}/{package}/"
397
+ print(f"Fetching wheel list from {simple_url}")
398
+ with urlopen(simple_url) as resp:
399
+ html = resp.read().decode("utf-8")
400
+
401
+ parser = WheelLinkParser()
402
+ parser.feed(html)
403
+
404
+ for wheel in reversed(parser.wheels):
405
+ if arch in wheel:
406
+ if wheel.startswith("http"):
407
+ return wheel
408
+ return urljoin(simple_url, wheel)
409
+
410
+ raise ValueError(f"No compatible wheel found for {arch} at {simple_url}")
411
+
412
+ @staticmethod
413
+ def determine_wheel_url_rocm() -> tuple[str, str | None]:
414
+ """Determine the precompiled wheel for ROCm."""
415
+ # Search for local wheel first
416
+ local_wheel = precompiled_wheel_utils.find_local_rocm_wheel()
417
+ if local_wheel is not None:
418
+ print(f"Found local ROCm wheel: {local_wheel}")
419
+ return local_wheel, None
420
+
421
+ # Fall back to AMD's PyPI index
422
+ index_url = os.getenv(
423
+ "VLLM_ROCM_WHEEL_INDEX", "https://pypi.amd.com/vllm-rocm/simple"
424
+ )
425
+ print(f"Fetching ROCm precompiled wheel from {index_url}")
426
+ wheel_url = precompiled_wheel_utils.fetch_wheel_from_pypi_index(index_url)
427
+ download_filename = wheel_url.split("/")[-1].split("#")[0]
428
+ print(f"Using ROCm precompiled wheel: {wheel_url}")
429
+ return wheel_url, download_filename
430
+
431
@staticmethod
432
def determine_wheel_url() -> tuple[str, str | None]:
433
"""
@@ -359,6 +448,11 @@ class precompiled_wheel_utils:
448
print(f"Using user-specified precompiled wheel location: {wheel_location}")
449
return wheel_location, None
450
else:
451
+ # ROCm: use local wheel or AMD's PyPI index
452
+ # TODO: When we have ROCm nightly wheels, we can update this logic.
453
+ if precompiled_wheel_utils.is_rocm_system():
454
+ return precompiled_wheel_utils.determine_wheel_url_rocm()
455
+
456
import platform
457
458
arch = platform.machine()
@@ -465,6 +559,8 @@ class precompiled_wheel_utils:
559
"vllm/vllm_flash_attn/_vllm_fa2_C.abi3.so",
560
"vllm/vllm_flash_attn/_vllm_fa3_C.abi3.so",
561
"vllm/cumem_allocator.abi3.so",
562
+ # ROCm-specific libraries
563
+ "vllm/_rocm_C.abi3.so",
564
]
565
566
flash_attn_regex = re.compile(
@@ -601,6 +697,8 @@ def get_rocm_version():
697
# Get the Rocm version from the ROCM_HOME/bin/librocm-core.so
698
# see https://github.com/ROCm/rocm-core/blob/d11f5c20d500f729c393680a01fa902ebf92094b/rocm_version.cpp#L21
699
try:
700
+ if ROCM_HOME is None:
701
+ return None
702
librocm_core_file = Path(ROCM_HOME) / "lib" / "librocm-core.so"
703
if not librocm_core_file.is_file():
704
return None
@@ -745,7 +843,9 @@ if _is_hip():
843
844
if _is_cuda():
845
ext_modules.append(CMakeExtension(name="vllm.vllm_flash_attn._vllm_fa2_C"))
748
- if envs.VLLM_USE_PRECOMPILED or get_nvcc_cuda_version() >= Version("12.3"):
846
+ if envs.VLLM_USE_PRECOMPILED or (
847
+ CUDA_HOME and get_nvcc_cuda_version() >= Version("12.3")
848
+ ):
849
# FA3 requires CUDA 12.3 or later
850
ext_modules.append(CMakeExtension(name="vllm.vllm_flash_attn._vllm_fa3_C"))
851
# Optional since this doesn't get built (produce an .so file) when
tests/entrypoints/openai/conftest.py
+24
@@ -5,6 +5,30 @@ import pytest
5
from vllm.assets.audio import AudioAsset
6
7
8
+def add_attention_backend(server_args, attention_config):
9
+ """Append attention backend CLI arg if specified.
10
+
11
+ Args:
12
+ server_args: List of server arguments to extend in-place.
13
+ attention_config: Dict with 'backend' key, or None.
14
+ """
15
+ if attention_config and "backend" in attention_config:
16
+ server_args.extend(["--attention-backend", attention_config["backend"]])
17
+
18
+
19
+@pytest.fixture(scope="module")
20
+def rocm_aiter_fa_attention():
21
+ """Return attention config for transcription/translation tests on ROCm.
22
+
23
+ On ROCm, audio tests require ROCM_AITER_FA attention backend.
24
+ """
25
+ from vllm.platforms import current_platform
26
+
27
+ if current_platform.is_rocm():
28
+ return {"backend": "ROCM_AITER_FA"}
29
+ return None
30
+
31
+
32
@pytest.fixture
33
def mary_had_lamb():
34
path = AudioAsset("mary_had_lamb").get_local_path()
tests/entrypoints/openai/test_chat.py
+5
-5
@@ -254,12 +254,11 @@ async def test_single_chat_session(client: openai.AsyncOpenAI, model_name: str):
254
{"role": "system", "content": "you are a helpful assistant"},
255
{"role": "user", "content": "what is 1+1?"},
256
]
257
-
257
# test single completion
258
chat_completion = await client.chat.completions.create(
259
model=model_name,
260
messages=messages,
262
- max_completion_tokens=10,
261
+ max_completion_tokens=5,
262
logprobs=True,
263
top_logprobs=5,
264
)
@@ -267,13 +266,14 @@ async def test_single_chat_session(client: openai.AsyncOpenAI, model_name: str):
266
assert len(chat_completion.choices) == 1
267
268
choice = chat_completion.choices[0]
269
+
270
assert choice.finish_reason == "length"
271
assert chat_completion.usage == openai.types.CompletionUsage(
272
- completion_tokens=10, prompt_tokens=37, total_tokens=47
272
+ completion_tokens=5, prompt_tokens=37, total_tokens=42
273
)
274
275
message = choice.message
276
- assert message.content is not None and len(message.content) >= 10
276
+ assert message.content is not None and len(message.content) >= 5
277
assert message.role == "assistant"
278
messages.append({"role": "assistant", "content": message.content})
279
@@ -282,7 +282,7 @@ async def test_single_chat_session(client: openai.AsyncOpenAI, model_name: str):
282
chat_completion = await client.chat.completions.create(
283
model=model_name,
284
messages=messages,
285
- max_completion_tokens=10,
285
+ max_completion_tokens=5,
286
)
287
message = chat_completion.choices[0].message
288
assert message.content is not None and len(message.content) >= 0
tests/entrypoints/openai/test_optional_middleware.py
+1
@@ -39,6 +39,7 @@ def server(request: pytest.FixtureRequest):
39
"2",
40
*passed_params,
41
]
42
+
43
with RemoteOpenAIServer(MODEL_NAME, args) as remote_server:
44
yield remote_server
45
tests/entrypoints/openai/test_response_api_with_harmony.py
+6
-1
@@ -504,7 +504,11 @@ async def test_web_search(client: OpenAI, model_name: str):
504
@pytest.mark.asyncio
505
@pytest.mark.parametrize("model_name", [MODEL_NAME])
506
async def test_code_interpreter(client: OpenAI, model_name: str):
507
- response = await client.responses.create(
507
+ # Code interpreter may need more time for container init + code execution
508
+ timeout_value = client.timeout * 3
509
+ client_with_timeout = client.with_options(timeout=timeout_value)
510
+
511
+ response = await client_with_timeout.responses.create(
512
model=model_name,
513
# TODO: Ideally should be able to set max tool calls
514
# to prevent multi-turn, but it is not currently supported
@@ -868,6 +872,7 @@ async def test_output_messages_enabled(client: OpenAI, model_name: str, server):
872
873
@pytest.mark.asyncio
874
@pytest.mark.parametrize("model_name", [MODEL_NAME])
875
+@pytest.mark.flaky(reruns=3)
876
async def test_function_call_with_previous_input_messages(
877
client: OpenAI, model_name: str
878
):
tests/entrypoints/openai/test_serving_tokens.py
+25
-4
@@ -93,6 +93,7 @@ async def test_same_response_as_chat_completions(client, tokenizer, messages):
93
add_generation_prompt=True,
94
enable_thinking=False, # default with Qwen3
95
)
96
+
97
for ignore_eos in [True, False]:
98
payload = {
99
"model": MODEL_NAME,
@@ -108,9 +109,8 @@ async def test_same_response_as_chat_completions(client, tokenizer, messages):
109
}
110
generate_resp = await client.post(GEN_ENDPOINT, json=payload)
111
generate_data = generate_resp.json()
111
- generate_res = tokenizer.decode(
112
- generate_data["choices"][0]["token_ids"], skip_special_tokens=True
113
- )
112
+ gen_token_ids = generate_data["choices"][0]["token_ids"]
113
+ generate_res = tokenizer.decode(gen_token_ids, skip_special_tokens=True)
114
115
payload = {
116
"model": MODEL_NAME,
@@ -119,12 +119,33 @@ async def test_same_response_as_chat_completions(client, tokenizer, messages):
119
"temperature": 0.0,
120
"stream": False,
121
"ignore_eos": ignore_eos,
122
- "chat_template_kwargs": dict(enable_thinking=False),
122
+ "chat_template_kwargs": {"enable_thinking": False},
123
}
124
completions_resp = await client.post("/v1/chat/completions", json=payload)
125
completions_data = completions_resp.json()
126
completions_res = completions_data["choices"][0]["message"]["content"]
127
128
+ if ignore_eos:
129
+ # When ignoring EOS, only compare up to the first EOS token
130
+ # Post-EOS generation is undefined and may differ
131
+ eos_tokens = {
132
+ tokenizer.eos_token_id,
133
+ *tokenizer.additional_special_tokens_ids,
134
+ }
135
+ # Find first EOS in generated tokens
136
+ eos_pos = None
137
+ for i, tid in enumerate(gen_token_ids):
138
+ if tid in eos_tokens:
139
+ eos_pos = i
140
+ break
141
+ if eos_pos is not None:
142
+ gen_token_ids_truncated = gen_token_ids[:eos_pos]
143
+ generate_res = tokenizer.decode(
144
+ gen_token_ids_truncated, skip_special_tokens=True
145
+ )
146
+ # Truncate completions_res to same length for comparison
147
+ completions_res = completions_res[: len(generate_res)]
148
+
149
assert generate_res == completions_res
150
151
tests/entrypoints/openai/test_shutdown.py
+23
-11
@@ -9,10 +9,16 @@ import time
9
import openai
10
import pytest
11
12
+from vllm.platforms import current_platform
13
from vllm.utils.network_utils import get_open_port
14
15
MODEL_NAME = "hmellor/tiny-random-LlamaForCausalLM"
16
17
+# GPU initialization might take take longer
18
+_IS_ROCM = current_platform.is_rocm()
19
+_SERVER_STARTUP_TIMEOUT = 120
20
+_PROCESS_EXIT_TIMEOUT = 15
21
+
22
23
@pytest.mark.asyncio
24
async def test_shutdown_on_engine_failure():
@@ -45,9 +51,11 @@ async def test_shutdown_on_engine_failure():
51
"2",
52
"--disable-frontend-multiprocessing",
53
],
48
- stdout=subprocess.PIPE,
49
- stderr=subprocess.PIPE,
50
- text=True,
54
+ # ROCm: Disable stdout/stderr pipe capture. Subprocess hangs when
55
+ # stdout/stderr pipes are enabled during ROCm GPU initialization.
56
+ stdout=None if _IS_ROCM else subprocess.PIPE,
57
+ stderr=None if _IS_ROCM else subprocess.PIPE,
58
+ text=None if _IS_ROCM else True,
59
preexec_fn=lambda: signal.signal(signal.SIGINT, signal.SIG_IGN),
60
)
61
@@ -61,7 +69,7 @@ async def test_shutdown_on_engine_failure():
69
)
70
71
# Poll until server is ready
64
- while time.time() - start_time < 30:
72
+ while time.time() - start_time < _SERVER_STARTUP_TIMEOUT:
73
try:
74
await client.completions.create(
75
model=MODEL_NAME, prompt="Hello", max_tokens=1
@@ -70,14 +78,18 @@ async def test_shutdown_on_engine_failure():
78
except Exception:
79
time.sleep(0.5)
80
if proc.poll() is not None:
73
- stdout, stderr = proc.communicate(timeout=1)
74
- pytest.fail(
75
- f"Server died during startup. stdout: {stdout}, stderr: {stderr}"
76
- )
81
+ if _IS_ROCM:
82
+ pytest.fail(f"Server died during startup: {proc.returncode}")
83
+ else:
84
+ stdout, stderr = proc.communicate(timeout=1)
85
+ pytest.fail(
86
+ f"Server died during startup. "
87
+ f"stdout: {stdout}, stderr: {stderr}"
88
+ )
89
else:
90
proc.terminate()
79
- proc.wait(timeout=5)
80
- pytest.fail("Server failed to start in 30 seconds")
91
+ proc.wait(timeout=_PROCESS_EXIT_TIMEOUT)
92
+ pytest.fail(f"Server failed to start in {_SERVER_STARTUP_TIMEOUT} seconds")
93
94
# Kill server to simulate crash
95
proc.terminate()
@@ -89,5 +101,5 @@ async def test_shutdown_on_engine_failure():
101
model=MODEL_NAME, prompt="This should fail", max_tokens=1
102
)
103
92
- return_code = proc.wait(timeout=5)
104
+ return_code = proc.wait(timeout=_PROCESS_EXIT_TIMEOUT)
105
assert return_code is not None
tests/entrypoints/openai/test_transcription_validation.py
+16
-4
@@ -7,6 +7,7 @@ import json
7
import pytest
8
9
from ...utils import RemoteOpenAIServer
10
+from .conftest import add_attention_backend
11
12
MISTRAL_FORMAT_ARGS = [
13
"--tokenizer_mode",
@@ -20,12 +21,14 @@ MISTRAL_FORMAT_ARGS = [
21
22
@pytest.mark.asyncio
23
@pytest.mark.parametrize("model_name", ["mistralai/Voxtral-Mini-3B-2507"])
23
-async def test_basic_audio(mary_had_lamb, model_name):
24
+async def test_basic_audio(mary_had_lamb, model_name, rocm_aiter_fa_attention):
25
server_args = ["--enforce-eager"]
26
27
if model_name.startswith("mistralai"):
28
server_args += MISTRAL_FORMAT_ARGS
29
30
+ add_attention_backend(server_args, rocm_aiter_fa_attention)
31
+
32
# Based on https://github.com/openai/openai-cookbook/blob/main/examples/Whisper_prompting_guide.ipynb.
33
with RemoteOpenAIServer(model_name, server_args) as remote_server:
34
client = remote_server.get_async_client()
@@ -44,8 +47,13 @@ async def test_basic_audio(mary_had_lamb, model_name):
47
48
49
@pytest.mark.asyncio
47
-async def test_basic_audio_with_lora(mary_had_lamb):
50
+async def test_basic_audio_with_lora(mary_had_lamb, rocm_aiter_fa_attention):
51
"""Ensure STT (transcribe) requests can pass LoRA through to generate."""
52
+ # ROCm SPECIFIC CONFIGURATION:
53
+ # To ensure the test passes on ROCm, we modify the max model length to 512.
54
+ # We DO NOT apply this to other platforms to maintain strict upstream parity.
55
+ from vllm.platforms import current_platform
56
+
57
model_name = "ibm-granite/granite-speech-3.3-2b"
58
lora_model_name = "speech"
59
server_args = [
@@ -56,11 +64,13 @@ async def test_basic_audio_with_lora(mary_had_lamb):
64
"--lora-modules",
65
f"{lora_model_name}={model_name}",
66
"--max-model-len",
59
- "2048",
67
+ "512" if current_platform.is_rocm() else "2048",
68
"--max-num-seqs",
69
"1",
70
]
71
72
+ add_attention_backend(server_args, rocm_aiter_fa_attention)
73
+
74
# Based on https://github.com/openai/openai-cookbook/blob/main/examples/Whisper_prompting_guide.ipynb.
75
with RemoteOpenAIServer(model_name, server_args) as remote_server:
76
client = remote_server.get_async_client()
@@ -79,12 +89,14 @@ async def test_basic_audio_with_lora(mary_had_lamb):
89
90
91
@pytest.mark.asyncio
82
-async def test_basic_audio_gemma(foscolo):
92
+async def test_basic_audio_gemma(foscolo, rocm_aiter_fa_attention):
93
# Gemma accuracy on some of the audio samples we use is particularly bad,
94
# hence we use a different one here. WER is evaluated separately.
95
model_name = "google/gemma-3n-E2B-it"
96
server_args = ["--enforce-eager"]
97
98
+ add_attention_backend(server_args, rocm_aiter_fa_attention)
99
+
100
with RemoteOpenAIServer(
101
model_name, server_args, max_wait_seconds=480
102
) as remote_server:
tests/entrypoints/openai/test_translation_validation.py
+25
-6
@@ -14,16 +14,26 @@ import pytest_asyncio
14
import soundfile as sf
15
16
from ...utils import RemoteOpenAIServer
17
+from .conftest import add_attention_backend
18
19
SERVER_ARGS = ["--enforce-eager"]
20
21
22
+def _get_server_args(attention_config):
23
+ """Get server args with attention backend if specified."""
24
+ args = SERVER_ARGS.copy()
25
+ add_attention_backend(args, attention_config)
26
+ return args
27
+
28
+
29
@pytest.fixture(
30
scope="module", params=["openai/whisper-small", "google/gemma-3n-E2B-it"]
31
)
24
-def server(request):
32
+def server(request, rocm_aiter_fa_attention):
33
# Parametrize over model name
26
- with RemoteOpenAIServer(request.param, SERVER_ARGS) as remote_server:
34
+ with RemoteOpenAIServer(
35
+ request.param, _get_server_args(rocm_aiter_fa_attention)
36
+ ) as remote_server:
37
yield remote_server, request.param
38
39
@@ -35,10 +45,12 @@ async def client_and_model(server):
45
46
47
@pytest.mark.asyncio
38
-async def test_non_asr_model(foscolo):
48
+async def test_non_asr_model(foscolo, rocm_aiter_fa_attention):
49
# text to text model
50
model_name = "JackFram/llama-68m"
41
- with RemoteOpenAIServer(model_name, SERVER_ARGS) as remote_server:
51
+ with RemoteOpenAIServer(
52
+ model_name, _get_server_args(rocm_aiter_fa_attention)
53
+ ) as remote_server:
54
client = remote_server.get_async_client()
55
res = await client.audio.translations.create(
56
model=model_name, file=foscolo, temperature=0.0
@@ -49,8 +61,13 @@ async def test_non_asr_model(foscolo):
61
62
63
@pytest.mark.asyncio
52
-async def test_basic_audio_with_lora(mary_had_lamb):
64
+async def test_basic_audio_with_lora(mary_had_lamb, rocm_aiter_fa_attention):
65
"""Ensure STT (translate) requests can pass LoRA through to generate."""
66
+ # ROCm SPECIFIC CONFIGURATION:
67
+ # To ensure the test passes on ROCm, we modify the max model length to 512.
68
+ # We DO NOT apply this to other platforms to maintain strict upstream parity.
69
+ from vllm.platforms import current_platform
70
+
71
# NOTE - careful to call this test before the module scoped server
72
# fixture, otherwise it'll OOMkill the CI
73
model_name = "ibm-granite/granite-speech-3.3-2b"
@@ -63,11 +80,13 @@ async def test_basic_audio_with_lora(mary_had_lamb):
80
"--lora-modules",
81
f"{lora_model_name}={model_name}",
82
"--max-model-len",
66
- "2048",
83
+ "512" if current_platform.is_rocm() else "2048",
84
"--max-num-seqs",
85
"1",
86
]
87
88
+ add_attention_backend(server_args, rocm_aiter_fa_attention)
89
+
90
# Based on https://github.com/openai/openai-cookbook/blob/main/examples/Whisper_prompting_guide.ipynb.
91
with RemoteOpenAIServer(model_name, server_args) as remote_server:
92
client = remote_server.get_async_client()
tests/entrypoints/openai/test_video.py
+16
-1
@@ -8,6 +8,7 @@ import pytest
8
import pytest_asyncio
9
10
from vllm.multimodal.utils import encode_video_url, fetch_video
11
+from vllm.platforms import current_platform
12
13
from ...utils import RemoteOpenAIServer
14
@@ -37,7 +38,16 @@ def server():
38
json.dumps({"video": MAXIMUM_VIDEOS}),
39
]
40
40
- with RemoteOpenAIServer(MODEL_NAME, args) as remote_server:
41
+ # ROCm: Increase timeouts to handle potential network delays and slower
42
+ # video processing when downloading multiple videos from external sources
43
+ env_overrides = {}
44
+ if current_platform.is_rocm():
45
+ env_overrides = {
46
+ "VLLM_VIDEO_FETCH_TIMEOUT": "120",
47
+ "VLLM_ENGINE_ITERATION_TIMEOUT_S": "300",
48
+ }
49
+
50
+ with RemoteOpenAIServer(MODEL_NAME, args, env_dict=env_overrides) as remote_server:
51
yield remote_server
52
53
@@ -287,6 +297,11 @@ async def test_chat_streaming_video(
297
@pytest.mark.parametrize(
298
"video_urls", [TEST_VIDEO_URLS[:i] for i in range(2, len(TEST_VIDEO_URLS))]
299
)
300
+@pytest.mark.flaky(
301
+ reruns=2,
302
+ reruns_delay=5,
303
+ condition=current_platform.is_rocm(),
304
+)
305
async def test_multi_video_input(
306
client: openai.AsyncOpenAI, model_name: str, video_urls: list[str]
307
):
tests/entrypoints/openai/test_vision.py
+40
-2
@@ -10,6 +10,7 @@ from transformers import AutoProcessor
10
11
from vllm.multimodal.base import MediaWithBytes
12
from vllm.multimodal.utils import encode_image_url, fetch_image
13
+from vllm.platforms import current_platform
14
15
from ...utils import RemoteOpenAIServer
16
@@ -43,6 +44,27 @@ EXPECTED_MM_BEAM_SEARCH_RES = [
44
],
45
]
46
47
+EXPECTED_MM_BEAM_SEARCH_RES_ROCM = [
48
+ # MultiHeadAttention attn_backend: FLASH_ATTN
49
+ # with Triton Attention backend
50
+ [
51
+ "The image shows a wooden boardwalk leading through a",
52
+ "The image shows a wooden boardwalk extending into a",
53
+ ],
54
+ [
55
+ "The image shows two parrots perched on",
56
+ "The image shows two birds perched on a cur",
57
+ ],
58
+ [
59
+ "The image shows a Venn diagram with three over",
60
+ "The image contains a Venn diagram with three over",
61
+ ],
62
+ [
63
+ "This image displays a gradient of colors ranging from",
64
+ "This image displays a gradient of colors transitioning from",
65
+ ],
66
+]
67
+
68
69
@pytest.fixture(scope="module")
70
def server():
@@ -59,7 +81,16 @@ def server():
81
json.dumps({"image": MAXIMUM_IMAGES}),
82
]
83
62
- with RemoteOpenAIServer(MODEL_NAME, args) as remote_server:
84
+ # ROCm: Increase timeouts to handle potential network delays and slower
85
+ # video processing when downloading multiple videos from external sources
86
+ env_overrides = {}
87
+ if current_platform.is_rocm():
88
+ env_overrides = {
89
+ "VLLM_VIDEO_FETCH_TIMEOUT": "120",
90
+ "VLLM_ENGINE_ITERATION_TIMEOUT_S": "300",
91
+ }
92
+
93
+ with RemoteOpenAIServer(MODEL_NAME, args, env_dict=env_overrides) as remote_server:
94
yield remote_server
95
96
@@ -288,9 +319,16 @@ async def test_single_chat_session_image_base64encoded_beamsearch(
319
image_idx: int,
320
url_encoded_image: dict[str, str],
321
):
322
+ # ROCm: Switch expected results based on platform
323
+ from vllm.platforms import current_platform
324
+
325
# NOTE: This test also validates that we pass MM data through beam search
326
raw_image_url = TEST_IMAGE_ASSETS[image_idx]
293
- expected_res = EXPECTED_MM_BEAM_SEARCH_RES[image_idx]
327
+
328
+ if current_platform.is_rocm():
329
+ expected_res = EXPECTED_MM_BEAM_SEARCH_RES_ROCM[image_idx]
330
+ else:
331
+ expected_res = EXPECTED_MM_BEAM_SEARCH_RES[image_idx]
332
333
messages = dummy_messages_from_image_url(url_encoded_image[raw_image_url])
334
tests/entrypoints/openai/test_vision_embeds.py
+1
@@ -33,6 +33,7 @@ def _terratorch_dummy_messages():
33
]
34
35
36
+@pytest.mark.asyncio
37
@pytest.mark.parametrize(
38
"model_name", ["ibm-nasa-geospatial/Prithvi-EO-2.0-300M-TL-Sen1Floods11"]
39
)
tests/entrypoints/pooling/basic/test_encode.py
+7
-5
@@ -9,11 +9,6 @@ from vllm import LLM, PoolingParams
9
from vllm.distributed import cleanup_dist_env_and_memory
10
from vllm.platforms import current_platform
11
12
-if current_platform.is_rocm():
13
- pytest.skip(
14
- "Encoder self-attention is not implemented on ROCm.", allow_module_level=True
15
- )
16
-
12
MODEL_NAME = "intfloat/multilingual-e5-small"
13
14
PROMPTS = [
@@ -35,6 +30,12 @@ TOKEN_IDS = [
30
31
@pytest.fixture(scope="module")
32
def llm():
33
+ # ROCm: Use FLEX_ATTENTION backend as it's the only attention backend
34
+ # that supports encoder-only models on ROCm.
35
+ attention_config = None
36
+ if current_platform.is_rocm():
37
+ attention_config = {"backend": "FLEX_ATTENTION"}
38
+
39
# pytest caches the fixture so we use weakref.proxy to
40
# enable garbage collection
41
llm = LLM(
@@ -44,6 +45,7 @@ def llm():
45
gpu_memory_utilization=0.75,
46
enforce_eager=True,
47
seed=0,
48
+ attention_config=attention_config,
49
)
50
51
yield weakref.proxy(llm)
tests/entrypoints/pooling/basic/test_truncation.py
+4
-5
@@ -9,11 +9,6 @@ import pytest_asyncio
9
from tests.utils import RemoteOpenAIServer
10
from vllm.platforms import current_platform
11
12
-if current_platform.is_rocm():
13
- pytest.skip(
14
- "Encoder self-attention is not implemented on ROCm.", allow_module_level=True
15
- )
16
-
12
MODEL_NAME = "sentence-transformers/all-MiniLM-L12-v2"
13
max_model_len = 128
14
@@ -44,6 +39,10 @@ def server():
39
str(max_model_len),
40
]
41
42
+ # ROCm: Use Flex Attention to support encoder-only self-attention.
43
+ if current_platform.is_rocm():
44
+ args.extend(["--attention-backend", "FLEX_ATTENTION"])
45
+
46
with RemoteOpenAIServer(MODEL_NAME, args) as remote_server:
47
yield remote_server
48
tests/entrypoints/pooling/embed/conftest.py
new
+28
@@ -0,0 +1,28 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+"""Pytest configuration for vLLM pooling embed tests."""
4
+
5
+import warnings
6
+
7
+import torch
8
+
9
+from vllm.platforms import current_platform
10
+
11
+
12
+def pytest_collection_modifyitems(config, items):
13
+ """Configure ROCm-specific settings based on collected tests."""
14
+ if not current_platform.is_rocm():
15
+ return
16
+
17
+ # Disable Flash/MemEfficient SDP on ROCm to avoid HF Transformers
18
+ # accuracy issues: https://github.com/vllm-project/vllm/issues/30167
19
+ # TODO: Remove once ROCm SDP accuracy issues are resolved on HuggingFace
20
+ torch.backends.cuda.enable_flash_sdp(False)
21
+ torch.backends.cuda.enable_mem_efficient_sdp(False)
22
+ torch.backends.cuda.enable_math_sdp(True)
23
+ warnings.warn(
24
+ "ROCm: Disabled flash_sdp and mem_efficient_sdp, enabled math_sdp "
25
+ "to avoid HuggingFace Transformers accuracy issues",
26
+ UserWarning,
27
+ stacklevel=1,
28
+ )
tests/entrypoints/pooling/embed/test_correctness_mteb.py
+4
-5
@@ -13,11 +13,6 @@ from tests.models.language.pooling_mteb_test.mteb_utils import (
13
from tests.utils import RemoteOpenAIServer
14
from vllm.platforms import current_platform
15
16
-if current_platform.is_rocm():
17
- pytest.skip(
18
- "Encoder self-attention is not implemented on ROCm.", allow_module_level=True
19
- )
20
-
16
os.environ["VLLM_LOGGING_LEVEL"] = "WARNING"
17
18
MODEL_NAME = "intfloat/e5-small"
@@ -28,6 +23,10 @@ MAIN_SCORE = 0.7422994752439667
23
def server():
24
args = ["--runner", "pooling", "--enforce-eager", "--disable-uvicorn-access-log"]
25
26
+ # ROCm: Use Flex Attention to support encoder-only self-attention.
27
+ if current_platform.is_rocm():
28
+ args.extend(["--attention-backend", "FLEX_ATTENTION"])
29
+
30
with RemoteOpenAIServer(MODEL_NAME, args) as remote_server:
31
yield remote_server
32
tests/entrypoints/pooling/embed/test_offline.py
+7
-5
@@ -11,11 +11,6 @@ from vllm import LLM, PoolingParams
11
from vllm.distributed import cleanup_dist_env_and_memory
12
from vllm.platforms import current_platform
13
14
-if current_platform.is_rocm():
15
- pytest.skip(
16
- "Encoder self-attention is not implemented on ROCm.", allow_module_level=True
17
- )
18
-
14
MODEL_NAME = "intfloat/multilingual-e5-small"
15
16
prompts = ["The chef prepared a delicious meal."]
@@ -23,6 +18,12 @@ prompts = ["The chef prepared a delicious meal."]
18
19
@pytest.fixture(scope="module")
20
def llm():
21
+ # ROCm: Use FLEX_ATTENTION backend as it's the only attention backend
22
+ # that supports encoder-only models on ROCm.
23
+ attention_config = None
24
+ if current_platform.is_rocm():
25
+ attention_config = {"backend": "FLEX_ATTENTION"}
26
+
27
# pytest caches the fixture so we use weakref.proxy to
28
# enable garbage collection
29
llm = LLM(
@@ -32,6 +33,7 @@ def llm():
33
gpu_memory_utilization=0.75,
34
enforce_eager=True,
35
seed=0,
36
+ attention_config=attention_config,
37
)
38
39
yield weakref.proxy(llm)
tests/entrypoints/pooling/embed/test_online.py
+13
-5
@@ -28,16 +28,20 @@ from vllm.utils.serial_utils import (
28
decode_pooling_output,
29
)
30
31
-if current_platform.is_rocm():
32
- pytest.skip(
33
- "Encoder self-attention is not implemented on ROCm.", allow_module_level=True
34
- )
35
-
31
MODEL_NAME = "intfloat/multilingual-e5-small"
32
DUMMY_CHAT_TEMPLATE = """{% for message in messages %}{{message['role'] + ': ' + message['content'] + '\\n'}}{% endfor %}""" # noqa: E501
33
DTYPE = "bfloat16"
34
35
36
+if current_platform.is_rocm():
37
+ # Disable Flash/MemEfficient SDP on ROCm to avoid HF Transformers
38
+ # accuracy issues: https://github.com/vllm-project/vllm/issues/30167
39
+ # TODO: Remove once ROCm SDP accuracy issues are resolved on HuggingFace
40
+ torch.backends.cuda.enable_flash_sdp(False)
41
+ torch.backends.cuda.enable_mem_efficient_sdp(False)
42
+ torch.backends.cuda.enable_math_sdp(True)
43
+
44
+
45
@pytest.fixture(scope="module")
46
def server():
47
args = [
@@ -53,6 +57,10 @@ def server():
57
DUMMY_CHAT_TEMPLATE,
58
]
59
60
+ # ROCm: Use Flex Attention to support encoder-only self-attention.
61
+ if current_platform.is_rocm():
62
+ args.extend(["--attention-backend", "FLEX_ATTENTION"])
63
+
64
with RemoteOpenAIServer(MODEL_NAME, args) as remote_server:
65
yield remote_server
66
tests/entrypoints/pooling/embed/test_online_dimensions.py
+4
-5
@@ -14,11 +14,6 @@ from tests.utils import RemoteOpenAIServer
14
from vllm.entrypoints.pooling.embed.protocol import EmbeddingResponse
15
from vllm.platforms import current_platform
16
17
-if current_platform.is_rocm():
18
- pytest.skip(
19
- "Encoder self-attention is not implemented on ROCm.", allow_module_level=True
20
- )
21
-
17
MODELS = [
18
EmbedModelInfo("intfloat/multilingual-e5-small", is_matryoshka=False),
19
EmbedModelInfo(
@@ -62,6 +57,10 @@ def server(model_info, dtype: str):
57
["--trust_remote_code", "--hf_overrides", '{"matryoshka_dimensions":[256]}']
58
)
59
60
+ # ROCm: Use Flex Attention to support encoder-only self-attention.
61
+ if current_platform.is_rocm():
62
+ args.extend(["--attention-backend", "FLEX_ATTENTION"])
63
+
64
with RemoteOpenAIServer(model_info.name, args) as remote_server:
65
yield remote_server
66
tests/entrypoints/pooling/embed/test_online_long_text.py
+4
-5
@@ -18,11 +18,6 @@ from tests.utils import RemoteOpenAIServer
18
from vllm.entrypoints.pooling.embed.protocol import EmbeddingResponse
19
from vllm.platforms import current_platform
20
21
-if current_platform.is_rocm():
22
- pytest.skip(
23
- "Encoder self-attention is not implemented on ROCm.", allow_module_level=True
24
- )
25
-
21
22
def _generate_random_text(word_count: int) -> str:
23
"""Generate random text with approximately the specified word count."""
@@ -228,6 +223,10 @@ def server_with_chunked_processing():
223
"0.8",
224
]
225
226
+ # ROCm: Use Flex Attention to support encoder-only self-attention.
227
+ if current_platform.is_rocm():
228
+ args.extend(["--attention-backend", "FLEX_ATTENTION"])
229
+
230
with RemoteOpenAIServer(MODEL_NAME, args) as remote_server:
231
yield remote_server
232
tests/entrypoints/pooling/score/test_correctness_mteb.py
+4
-5
@@ -15,11 +15,6 @@ from tests.models.language.pooling_mteb_test.mteb_utils import (
15
from tests.utils import RemoteOpenAIServer
16
from vllm.platforms import current_platform
17
18
-if current_platform.is_rocm():
19
- pytest.skip(
20
- "Encoder self-attention is not implemented on ROCm.", allow_module_level=True
21
- )
22
-
18
os.environ["VLLM_LOGGING_LEVEL"] = "WARNING"
19
20
MODEL_NAME = "cross-encoder/ms-marco-MiniLM-L-6-v2"
@@ -30,6 +25,10 @@ st_main_score = 0.33457
25
def server():
26
args = ["--runner", "pooling", "--enforce-eager", "--disable-uvicorn-access-log"]
27
28
+ # ROCm: Use Flex Attention to support encoder-only self-attention.
29
+ if current_platform.is_rocm():
30
+ args.extend(["--attention-backend", "FLEX_ATTENTION"])
31
+
32
with RemoteOpenAIServer(MODEL_NAME, args) as remote_server:
33
yield remote_server
34
tests/entrypoints/pooling/score/test_offline.py
+7
-5
@@ -11,16 +11,17 @@ from vllm import LLM, PoolingParams
11
from vllm.distributed import cleanup_dist_env_and_memory
12
from vllm.platforms import current_platform
13
14
-if current_platform.is_rocm():
15
- pytest.skip(
16
- "Encoder self-attention is not implemented on ROCm.", allow_module_level=True
17
- )
18
-
14
MODEL_NAME = "tomaarsen/Qwen3-Reranker-0.6B-seq-cls"
15
16
17
@pytest.fixture(scope="module")
18
def llm():
19
+ # ROCm: Use FLEX_ATTENTION backend as it's the only attention backend
20
+ # that supports encoder-only models on ROCm.
21
+ attention_config = None
22
+ if current_platform.is_rocm():
23
+ attention_config = {"backend": "FLEX_ATTENTION"}
24
+
25
# pytest caches the fixture so we use weakref.proxy to
26
# enable garbage collection
27
llm = LLM(
@@ -30,6 +31,7 @@ def llm():
31
gpu_memory_utilization=0.75,
32
enforce_eager=True,
33
seed=0,
34
+ attention_config=attention_config,
35
)
36
37
yield weakref.proxy(llm)
tests/entrypoints/pooling/score/test_online_rerank.py
+4
-5
@@ -11,11 +11,6 @@ from vllm.entrypoints.pooling.pooling.protocol import PoolingResponse
11
from vllm.entrypoints.pooling.score.protocol import RerankResponse
12
from vllm.platforms import current_platform
13
14
-if current_platform.is_rocm():
15
- pytest.skip(
16
- "Encoder self-attention is not implemented on ROCm.", allow_module_level=True
17
- )
18
-
14
MODEL_NAME = "BAAI/bge-reranker-base"
15
DTYPE = "bfloat16"
16
@@ -24,6 +19,10 @@ DTYPE = "bfloat16"
19
def server():
20
args = ["--enforce-eager", "--max-model-len", "100", "--dtype", DTYPE]
21
22
+ # ROCm: Use Flex Attention to support encoder-only self-attention.
23
+ if current_platform.is_rocm():
24
+ args.extend(["--attention-backend", "FLEX_ATTENTION"])
25
+
26
with RemoteOpenAIServer(MODEL_NAME, args) as remote_server:
27
yield remote_server
28
tests/entrypoints/pooling/score/test_online_score.py
+4
-5
@@ -12,11 +12,6 @@ from tests.utils import RemoteOpenAIServer
12
from vllm.entrypoints.pooling.score.protocol import ScoreResponse
13
from vllm.platforms import current_platform
14
15
-if current_platform.is_rocm():
16
- pytest.skip(
17
- "Encoder self-attention is not implemented on ROCm.", allow_module_level=True
18
- )
19
-
15
MODELS = [
16
{"name": "BAAI/bge-reranker-v2-m3", "is_cross_encoder": True},
17
{"name": "BAAI/bge-base-en-v1.5", "is_cross_encoder": False},
@@ -44,6 +39,10 @@ def model(request):
39
def server(model: dict[str, Any]):
40
args = ["--enforce-eager", "--max-model-len", "100", "--dtype", DTYPE]
41
42
+ # ROCm: Use Flex Attention to support encoder-only self-attention.
43
+ if current_platform.is_rocm():
44
+ args.extend(["--attention-backend", "FLEX_ATTENTION"])
45
+
46
with RemoteOpenAIServer(model["name"], args) as remote_server:
47
yield remote_server
48
tests/standalone_tests/pytorch_nightly_dependency.sh
+6
-1
@@ -4,6 +4,11 @@
4
set -e
5
set -x
6
7
+if command -v rocminfo >/dev/null 2>&1; then
8
+ echo "Skipping test for ROCm platform"
9
+ exit 0
10
+fi
11
+
12
cd /vllm-workspace/
13
14
rm -rf .venv
@@ -36,7 +41,7 @@ if diff before.txt after.txt; then
41
echo "torch version not overridden."
42
else
43
echo "torch version overridden by nightly_torch_test.txt, \
39
- if the dependency is not triggered by the pytroch nightly test,\
44
+ if the dependency is not triggered by the pytorch nightly test,\
45
please add the dependency to the list 'white_list' in tools/pre_commit/generate_nightly_torch_test.py"
46
exit 1
47
fi
vllm/entrypoints/pooling/embed/conftest.py
new
+28
@@ -0,0 +1,28 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+"""Pytest configuration for vLLM pooling embed tests."""
4
+
5
+import warnings
6
+
7
+import torch
8
+
9
+from vllm.platforms import current_platform
10
+
11
+
12
+def pytest_collection_modifyitems(config, items):
13
+ """Configure ROCm-specific settings based on collected tests."""
14
+ if not current_platform.is_rocm():
15
+ return
16
+
17
+ # Disable Flash/MemEfficient SDP on ROCm to avoid HF Transformers
18
+ # accuracy issues: https://github.com/vllm-project/vllm/issues/30167
19
+ # TODO: Remove once ROCm SDP accuracy issues are resolved on HuggingFace
20
+ torch.backends.cuda.enable_flash_sdp(False)
21
+ torch.backends.cuda.enable_mem_efficient_sdp(False)
22
+ torch.backends.cuda.enable_math_sdp(True)
23
+ warnings.warn(
24
+ "ROCm: Disabled flash_sdp and mem_efficient_sdp, enabled math_sdp "
25
+ "to avoid HuggingFace Transformers accuracy issues",
26
+ UserWarning,
27
+ stacklevel=1,
28
+ )