[ROCm][CI] Expand basic correctness target suites (#46573)
Signed-off-by: Andreas Karatzas <akaratza@amd.com> Signed-off-by: Matthew Wong <Matthew.Wong2@amd.com> Co-authored-by: Matthew Wong <Matthew.Wong2@amd.com> Co-authored-by: Matt <156021403+mawong-amd@users.noreply.github.com>
Andreas Karatzas committed
Jun 24, 2026 at 23:18 UTC
3f5a1e1733200760169ff31ebe60a271072b199e
4 files changed
+174
-60
.buildkite/test-amd.yaml
+4
-4
@@ -132,7 +132,7 @@ steps:
132
- vllm/_aiter_ops.py
133
- vllm/platforms/rocm.py
134
commands:
135
- - TARGET_TEST_SUITE=L4 pytest basic_correctness/ -v -s -m 'distributed(num_gpus=2)'
135
+ - TARGET_TEST_SUITE=MI250 pytest basic_correctness/ -v -s -m 'distributed(num_gpus=2)'
136
- CUDA_VISIBLE_DEVICES=0,1 pytest -v -s model_executor/model_loader/test_sharded_state_loader.py -m '(not slow_test)'
137
- pytest models/test_transformers.py -v -s -m 'distributed(num_gpus=2)'
138
- pytest models/language -v -s -m 'distributed(num_gpus=2)'
@@ -646,7 +646,7 @@ steps:
646
commands:
647
- export VLLM_WORKER_MULTIPROC_METHOD=spawn
648
- pytest -v -s basic_correctness/test_mem.py
649
- - pytest -v -s basic_correctness/test_basic_correctness.py
649
+ - VLLM_TARGET_TEST_SUITE=MI300 pytest -v -s basic_correctness/test_basic_correctness.py
650
- pytest -v -s basic_correctness/test_cpu_offload.py
651
652
- label: Distributed Model Tests (2 GPUs) # TBD
@@ -668,7 +668,7 @@ steps:
668
- tests/model_executor/model_loader/test_sharded_state_loader.py
669
- tests/models/
670
commands:
671
- - TARGET_TEST_SUITE=L4 pytest basic_correctness/ -v -s -m 'distributed(num_gpus=2)'
671
+ - TARGET_TEST_SUITE=MI300 pytest basic_correctness/ -v -s -m 'distributed(num_gpus=2)'
672
- CUDA_VISIBLE_DEVICES=0,1 pytest -v -s model_executor/model_loader/test_sharded_state_loader.py -m '(not slow_test)'
673
- pytest models/test_transformers.py -v -s -m 'distributed(num_gpus=2)'
674
- pytest models/language -v -s -m 'distributed(num_gpus=2)'
@@ -887,7 +887,7 @@ steps:
887
commands:
888
- pytest -v -s distributed/test_custom_all_reduce.py
889
- torchrun --nproc_per_node=2 distributed/test_ca_buffer_sharing.py
890
- - TARGET_TEST_SUITE=A100 pytest basic_correctness/ -v -s -m 'distributed(num_gpus=2)'
890
+ - TARGET_TEST_SUITE=MI300 pytest basic_correctness/ -v -s -m 'distributed(num_gpus=2)'
891
- pytest -v -s -x lora/test_mixtral.py
892
893
- label: Distributed Torchrun + Examples (4 GPUs) # TBD
tests/basic_correctness/test_basic_correctness.py
+71
-12
@@ -29,7 +29,53 @@ MODELS = [
29
"meta-llama/Llama-3.2-1B-Instruct",
30
]
31
32
-TARGET_TEST_SUITE = os.environ.get("TARGET_TEST_SUITE", "L4")
32
+TARGET_TEST_SUITE_ENV = "VLLM_TARGET_TEST_SUITE"
33
+LEGACY_TARGET_TEST_SUITE_ENV = "TARGET_TEST_SUITE"
34
+
35
+GENERIC_DISTRIBUTED_TEST_SUITES = ("L4", "MI250", "MI300", "MI325", "MI355")
36
+ALL_DISTRIBUTED_TEST_SUITES = (*GENERIC_DISTRIBUTED_TEST_SUITES, "A100")
37
+
38
+
39
+def _default_target_test_suite() -> str:
40
+ if not current_platform.is_rocm():
41
+ return "L4"
42
+
43
+ try:
44
+ device_name = current_platform.get_device_name().upper()
45
+ except Exception:
46
+ device_name = ""
47
+
48
+ if "MI355" in device_name:
49
+ return "MI355"
50
+ if "MI300" in device_name:
51
+ return "MI300"
52
+ if "MI325" in device_name:
53
+ return "MI325"
54
+ if "MI250" in device_name:
55
+ return "MI250"
56
+
57
+ try:
58
+ from vllm.platforms import rocm as rocm_platform
59
+
60
+ if rocm_platform.on_gfx950():
61
+ return "MI355"
62
+ if rocm_platform.on_gfx942():
63
+ return "MI300"
64
+ except Exception:
65
+ pass
66
+
67
+ return "MI250"
68
+
69
+
70
+def _resolve_target_test_suite() -> str:
71
+ for env_name in (TARGET_TEST_SUITE_ENV, LEGACY_TARGET_TEST_SUITE_ENV):
72
+ value = os.environ.get(env_name, "").strip().upper()
73
+ if value:
74
+ return value
75
+ return _default_target_test_suite()
76
+
77
+
78
+TARGET_TEST_SUITE = _resolve_target_test_suite()
79
80
81
def test_vllm_gc_ed():
@@ -131,14 +177,27 @@ def test_models(
177
178
@multi_gpu_test(num_gpus=2)
179
@pytest.mark.parametrize(
134
- "model, distributed_executor_backend, attention_backend, test_suite, extra_env",
180
+ (
181
+ "model, distributed_executor_backend, attention_backend, "
182
+ "target_test_suites, extra_env"
183
+ ),
184
[
136
- ("facebook/opt-125m", "ray", "", "L4", {}),
137
- ("facebook/opt-125m", "mp", "", "L4", {}),
138
- ("meta-llama/Llama-3.2-1B-Instruct", "ray", "", "L4", {}),
139
- ("meta-llama/Llama-3.2-1B-Instruct", "mp", "", "L4", {}),
140
- ("facebook/opt-125m", "ray", "", "A100", {}),
141
- ("facebook/opt-125m", "mp", "", "A100", {}),
185
+ ("facebook/opt-125m", "ray", "", ALL_DISTRIBUTED_TEST_SUITES, {}),
186
+ ("facebook/opt-125m", "mp", "", ALL_DISTRIBUTED_TEST_SUITES, {}),
187
+ (
188
+ "meta-llama/Llama-3.2-1B-Instruct",
189
+ "ray",
190
+ "",
191
+ GENERIC_DISTRIBUTED_TEST_SUITES,
192
+ {},
193
+ ),
194
+ (
195
+ "meta-llama/Llama-3.2-1B-Instruct",
196
+ "mp",
197
+ "",
198
+ GENERIC_DISTRIBUTED_TEST_SUITES,
199
+ {},
200
+ ),
201
],
202
)
203
@pytest.mark.parametrize("enable_prompt_embeds", [True, False])
@@ -150,19 +209,19 @@ def test_models_distributed(
209
model: str,
210
distributed_executor_backend: str,
211
attention_backend: str,
153
- test_suite: str,
212
+ target_test_suites: tuple[str, ...],
213
extra_env: dict[str, str],
214
enable_prompt_embeds: bool,
215
) -> None:
157
- if test_suite != TARGET_TEST_SUITE:
158
- pytest.skip(f"Skip test for {test_suite}")
216
+ if TARGET_TEST_SUITE and TARGET_TEST_SUITE not in target_test_suites:
217
+ pytest.skip(f"Skip test for {TARGET_TEST_SUITE}")
218
219
with monkeypatch.context() as monkeypatch_context:
220
if (
221
model == "meta-llama/Llama-3.2-1B-Instruct"
222
and distributed_executor_backend == "ray"
223
and attention_backend == ""
165
- and test_suite == "L4"
224
+ and TARGET_TEST_SUITE == "L4"
225
and enable_prompt_embeds
226
): # noqa
227
pytest.skip("enable_prompt_embeds does not work with ray compiled dag.")
tests/conftest.py
+24
-1
@@ -64,6 +64,7 @@ from vllm.logprobs import Logprob
64
from vllm.multimodal.media import MediaWithBytes
65
from vllm.multimodal.utils import fetch_image
66
from vllm.outputs import RequestOutput
67
+from vllm.platforms import current_platform
68
from vllm.sampling_params import BeamSearchParams
69
from vllm.transformers_utils.utils import maybe_model_redirect
70
from vllm.utils.collection_utils import is_list_of
@@ -852,6 +853,24 @@ class HfRunner:
853
return self.model.predict(prompts, *args, convert_to_tensor=True, **kwargs)
854
855
def __enter__(self):
856
+ if current_platform.is_rocm():
857
+ # Record starting memory usage stats on ROCm so that we can wait for
858
+ # memory to roughly settle back below these levels on shutdown. This is
859
+ # helpful in cases where the HfRunner is initialized after significant GPU
860
+ # memory is already occupied, e.g. in
861
+ # tests/basic_correctness/test_basic_correctness.py::test_models_distributed
862
+ from tests.utils import (
863
+ get_physical_device_indices,
864
+ record_gpu_memory_usage_stats,
865
+ )
866
+
867
+ if (device_count := current_platform.device_count()) > 0:
868
+ devices = get_physical_device_indices(devices=list(range(device_count)))
869
+ mem_usage_stats = record_gpu_memory_usage_stats(devices=devices)
870
+ self.threshold_ratios = {
871
+ device: 0.05 + mem_used / mem_tot
872
+ for device, (mem_used, mem_tot) in mem_usage_stats.items()
873
+ }
874
return self
875
876
def __exit__(self, exc_type, exc_value, traceback):
@@ -861,7 +880,11 @@ class HfRunner:
880
cleanup_dist_env_and_memory()
881
# ROCm frees VRAM lazily; wait so a runner started right after this HF
882
# model exits does not OOM on its startup memory guard.
864
- wait_for_rocm_memory_to_settle()
883
+ wait_for_rocm_memory_to_settle(
884
+ threshold_ratio=getattr(self, "threshold_ratios", None)
885
+ )
886
+ if hasattr(self, "threshold_ratios"):
887
+ del self.threshold_ratios
888
889
890
@pytest.fixture(scope="session")
tests/utils.py
+75
-43
@@ -1464,7 +1464,7 @@ def error_on_warning(category: type[Warning] = Warning):
1464
yield
1465
1466
1467
-def get_physical_device_indices(devices):
1467
+def get_physical_device_indices(devices: list[int]):
1468
visible_devices = os.environ.get("CUDA_VISIBLE_DEVICES")
1469
if visible_devices is None:
1470
return devices
@@ -1475,82 +1475,114 @@ def get_physical_device_indices(devices):
1475
1476
1477
@_nvml()
1478
+def record_gpu_memory_usage_stats(
1479
+ *,
1480
+ devices: list[int],
1481
+) -> dict[int, tuple[float, float]]:
1482
+ output: dict[int, tuple[float, float]] = {}
1483
+ for device in devices:
1484
+ if current_platform.is_rocm():
1485
+ dev_handle = amdsmi_get_processor_handles()[device]
1486
+ mem_info = amdsmi_get_gpu_vram_usage(dev_handle)
1487
+ gb_used = mem_info["vram_used"] / 2**10
1488
+ gb_total = mem_info["vram_total"] / 2**10
1489
+ else:
1490
+ dev_handle = nvmlDeviceGetHandleByIndex(device)
1491
+ mem_info = nvmlDeviceGetMemoryInfo(dev_handle)
1492
+ gb_used = mem_info.used / 2**30
1493
+ gb_total = mem_info.total / 2**30
1494
+ output[device] = (gb_used, gb_total)
1495
+ return output
1496
+
1497
+
1498
def wait_for_gpu_memory_to_clear(
1499
*,
1500
devices: list[int],
1481
- threshold_bytes: int | None = None,
1482
- threshold_ratio: float | None = None,
1501
+ threshold_bytes: int | dict[int, int] | None = None,
1502
+ threshold_ratio: float | dict[int, float] | None = None,
1503
timeout_s: float = 120,
1504
) -> None:
1505
assert threshold_bytes is not None or threshold_ratio is not None
1486
- if (
1487
- current_platform.is_rocm()
1488
- and threshold_ratio is not None
1489
- and threshold_ratio < 0.05
1490
- ):
1506
+ devices = get_physical_device_indices(devices)
1507
+ if isinstance(threshold_bytes, int):
1508
+ threshold_bytes = {device: threshold_bytes for device in devices}
1509
+ elif isinstance(threshold_bytes, dict):
1510
+ assert threshold_bytes.keys() == set(devices)
1511
+ if isinstance(threshold_ratio, float):
1512
+ threshold_ratio = {device: threshold_ratio for device in devices}
1513
+ elif isinstance(threshold_ratio, dict):
1514
+ assert threshold_ratio.keys() == set(devices)
1515
+ if current_platform.is_rocm() and threshold_ratio is not None:
1516
# ROCm can keep a small runtime/driver footprint resident even after
1517
# all model allocations are gone. On MI300 this has been observed
1518
# around 2.5 GiB, which is above a strict 1% idle threshold but nowhere
1519
# near the amount of free memory needed by the next vLLM runner.
1495
- min_threshold_bytes = 4 * 1024**3
1496
- threshold_bytes = max(threshold_bytes or 0, min_threshold_bytes)
1520
+ min_threshold_b = 4 * 1024**3
1521
+ if threshold_bytes is None:
1522
+ threshold_bytes = {}
1523
+ for device, ratio in threshold_ratio.items():
1524
+ threshold_bytes[device] = max(
1525
+ threshold_bytes.get(device, 0), min_threshold_b if ratio < 0.05 else 0
1526
+ )
1527
1528
# Use nvml instead of pytorch to reduce measurement error from torch cuda
1529
# context.
1500
- devices = get_physical_device_indices(devices)
1530
start_time = time.time()
1531
while True:
1503
- output: dict[int, str] = {}
1504
- output_raw: dict[int, tuple[float, float]] = {}
1505
- for device in devices:
1506
- if current_platform.is_rocm():
1507
- dev_handle = amdsmi_get_processor_handles()[device]
1508
- mem_info = amdsmi_get_gpu_vram_usage(dev_handle)
1509
- gb_used = mem_info["vram_used"] / 2**10
1510
- gb_total = mem_info["vram_total"] / 2**10
1511
- else:
1512
- dev_handle = nvmlDeviceGetHandleByIndex(device)
1513
- mem_info = nvmlDeviceGetMemoryInfo(dev_handle)
1514
- gb_used = mem_info.used / 2**30
1515
- gb_total = mem_info.total / 2**30
1516
- output_raw[device] = (gb_used, gb_total)
1517
- output[device] = f"{gb_used:.02f}/{gb_total:.02f}"
1518
-
1532
+ output_raw = record_gpu_memory_usage_stats(devices=devices)
1533
+ output = {
1534
+ device: f"{gb_used:.02f}/{gb_total:.02f}"
1535
+ for device, (gb_used, gb_total) in output_raw.items()
1536
+ }
1537
print("gpu memory used/total (GiB): ", end="")
1538
for k, v in output.items():
1539
print(f"{k}={v}; ", end="")
1540
print("")
1541
1542
if threshold_bytes is not None and threshold_ratio is not None:
1525
- threshold_gib = threshold_bytes / 2**30
1526
- threshold = f"max({threshold_gib:.2f} GiB, {threshold_ratio:.3f})"
1543
+ threshold_gib = {
1544
+ device: threshold_b / 2**30
1545
+ for device, threshold_b in threshold_bytes.items()
1546
+ }
1547
+ threshold = "; ".join(
1548
+ f"{device=}: max({threshold_gib[device]:.2f} GiB, "
1549
+ f"{threshold_ratio[device]:.3f})"
1550
+ for device in devices
1551
+ )
1552
all_free = all(
1528
- used <= max(threshold_gib, total * threshold_ratio)
1529
- for used, total in output_raw.values()
1553
+ used <= max(threshold_gib[device], total * threshold_ratio[device])
1554
+ for device, (used, total) in output_raw.items()
1555
)
1556
elif threshold_bytes is not None:
1532
- threshold_gib = threshold_bytes / 2**30
1533
- threshold = f"{threshold_gib} GiB"
1534
- all_free = all(used <= threshold_gib for used, _ in output_raw.values())
1557
+ threshold_gib = {
1558
+ device: threshold_b / 2**30
1559
+ for device, threshold_b in threshold_bytes.items()
1560
+ }
1561
+ threshold = "; ".join(
1562
+ f"{device=}: {threshold_gib[device]:.2f} GiB" for device in devices
1563
+ )
1564
+ all_free = all(
1565
+ used <= threshold_gib[device]
1566
+ for device, (used, _) in output_raw.items()
1567
+ )
1568
else:
1569
assert threshold_ratio is not None
1537
- threshold = f"{threshold_ratio:.3f}"
1570
+ threshold = "; ".join(
1571
+ f"{device=}: {threshold_ratio[device]:.3f}" for device in devices
1572
+ )
1573
all_free = all(
1539
- used / total <= threshold_ratio for used, total in output_raw.values()
1574
+ used / total <= threshold_ratio[device]
1575
+ for device, (used, total) in output_raw.items()
1576
)
1577
1578
dur_s = time.time() - start_time
1579
if all_free:
1544
- print(
1545
- f"Done waiting for free GPU memory on devices {devices=} "
1546
- f"({threshold=}) {dur_s=:.02f}"
1547
- )
1580
+ print(f"Done waiting for free GPU memory on ({threshold=}) {dur_s=:.02f}")
1581
break
1582
1583
if dur_s >= timeout_s:
1584
raise ValueError(
1552
- f"Memory of devices {devices=} not free after "
1553
- f"{dur_s=:.02f} ({threshold=})"
1585
+ f"Memory of devices not free after {dur_s=:.02f} ({threshold=})"
1586
)
1587
1588
time.sleep(5)
@@ -1558,7 +1590,7 @@ def wait_for_gpu_memory_to_clear(
1590
1591
def wait_for_rocm_memory_to_settle(
1592
*,
1561
- threshold_ratio: float = 0.1,
1593
+ threshold_ratio: float | dict[int, float] | None = 0.1,
1594
timeout_s: float = 240,
1595
) -> None:
1596
"""Block until ROCm device VRAM usage drops below ``threshold_ratio``.