[ROCm][CI] Add ROCm misc ops and env tests (#53279)

Signed-off-by: Divakar Verma <divakar.verma@amd.com> Co-authored-by: Andreas Karatzas <akaratza@amd.com>

Divakar Verma committed Sep 1, 2026 at 15:03 UTC c0adee92317d436b88b621751122a72c7361229e
1 file changed +226
tests/kernels/core/test_rocm_misc_ops.py new
+226
@@ -0,0 +1,226 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +"""ROCm platform and extension smoke tests.
4 +
5 +These tests catch ROCm-specific regressions early:
6 +
7 +1. **Extension import smoke tests**: Verify that ROCm native extensions load
8 + without crashing. Build/linking issues surface here before runtime.
9 +
10 +2. **GCN arch parsing tests**: Verify _capability_from_gcn_arch correctly
11 + parses all known GPU architectures. Wrong parsing causes silent feature
12 + gate failures or crashes from selecting unsupported kernels.
13 +
14 +3. **AITER ops availability tests**: Verify rocm_aiter_ops accessor methods
15 + work without crashing, regardless of hardware support.
16 +
17 +4. **Env var propagation tests**: Verify ROCm-specific env vars correctly
18 + gate their respective features (e.g., shuffle KV cache, skinny GEMM).
19 +
20 +Related coverage:
21 +- AITER kernel numerics: ``tests/kernels/core/test_rocm_aiter_ops.py``
22 +- Attention backend selection: ``tests/v1/attention/test_rocm_attention_backends_selection.py``
23 +- GEMM dispatch: ``tests/model_executor/layers/test_rocm_unquantized_gemm.py``
24 +""" # noqa: E501
25 +
26 +import importlib
27 +
28 +import pytest
29 +
30 +from vllm.platforms import current_platform
31 +
32 +pytestmark = pytest.mark.skipif(
33 + not current_platform.is_rocm(), reason="ROCm-specific tests"
34 +)
35 +
36 +
37 +# ---------------------------------------------------------------------------
38 +# Extension import smoke tests
39 +# ---------------------------------------------------------------------------
40 +
41 +
42 +def test_rocm_extension_imports():
43 + """ROCm native extension loads without error.
44 +
45 + Catches build/linking issues before runtime. If this fails, the ROCm
46 + build is broken and nothing else will work.
47 + """
48 + import vllm._rocm_C # noqa: F401
49 +
50 +
51 +def test_aiter_ops_module_imports():
52 + """AITER ops module imports without error.
53 +
54 + The _aiter_ops module registers custom ops and initializes AITER state.
55 + Import failures here indicate missing dependencies or incompatible
56 + AITER versions.
57 + """
58 + import vllm._aiter_ops # noqa: F401
59 +
60 +
61 +def test_rocm_platform_module_imports():
62 + """ROCm platform module imports without error.
63 +
64 + The platform module queries hardware via amdsmi and sets up arch flags.
65 + Import failures indicate missing amdsmi or HIP runtime issues.
66 + """
67 + import vllm.platforms.rocm # noqa: F401
68 +
69 +
70 +# ---------------------------------------------------------------------------
71 +# GCN arch parsing tests
72 +# ---------------------------------------------------------------------------
73 +
74 +
75 +@pytest.mark.parametrize(
76 + ("gcn_arch", "expected_major", "expected_minor"),
77 + [
78 + pytest.param("gfx90a", 9, 0, id="MI200-gfx90a"),
79 + pytest.param("gfx942", 9, 4, id="MI300X-gfx942"),
80 + pytest.param("gfx950", 9, 5, id="MI350-gfx950"),
81 + pytest.param("gfx1100", 11, 0, id="RX7900-gfx1100"),
82 + pytest.param("gfx1101", 11, 0, id="RX7800-gfx1101"),
83 + pytest.param("gfx1151", 11, 5, id="StrixHalo-gfx1151"),
84 + pytest.param("gfx1200", 12, 0, id="RDNA4-gfx1200"),
85 + pytest.param("gfx1201", 12, 0, id="RX9070-gfx1201"),
86 + pytest.param("gfx1250", 12, 5, id="CDNA5-gfx1250"),
87 + ],
88 +)
89 +def test_capability_from_gcn_arch_known_gpus(gcn_arch, expected_major, expected_minor):
90 + """_capability_from_gcn_arch parses known GPU architectures correctly.
91 +
92 + This is critical for feature gating - wrong parsing causes:
93 + - FP8 kernels selected on hardware that doesn't support them
94 + - MX format support incorrectly enabled/disabled
95 + - Custom allreduce used on unsupported hardware
96 + """
97 + from vllm.platforms.rocm import _capability_from_gcn_arch
98 +
99 + result = _capability_from_gcn_arch(gcn_arch)
100 + assert result is not None, f"Failed to parse {gcn_arch}"
101 + assert result == (expected_major, expected_minor), (
102 + f"Wrong capability for {gcn_arch}: got {result}, "
103 + f"expected ({expected_major}, {expected_minor})"
104 + )
105 +
106 +
107 +@pytest.mark.parametrize(
108 + "gcn_arch",
109 + [
110 + pytest.param("not_a_gfx_string", id="non-gfx-string"),
111 + pytest.param("cuda_arch", id="cuda-style"),
112 + pytest.param("", id="empty-string"),
113 + ],
114 +)
115 +def test_capability_from_gcn_arch_returns_none_for_non_gfx(gcn_arch):
116 + """_capability_from_gcn_arch returns None for non-GFX strings.
117 +
118 + Non-GFX strings should return None so the caller can fall back to
119 + torch.cuda.get_device_capability.
120 + """
121 + from vllm.platforms.rocm import _capability_from_gcn_arch
122 +
123 + result = _capability_from_gcn_arch(gcn_arch)
124 + assert result is None
125 +
126 +
127 +@pytest.mark.parametrize(
128 + "gcn_arch",
129 + [
130 + pytest.param("gfx8", id="too-few-digits"),
131 + pytest.param("gfx12345", id="too-many-digits"),
132 + pytest.param("gfx700", id="unsupported-major-7"),
133 + pytest.param("gfx1500", id="future-major-15"),
134 + ],
135 +)
136 +def test_capability_from_gcn_arch_raises_for_malformed(gcn_arch):
137 + """_capability_from_gcn_arch raises ValueError for malformed GFX strings.
138 +
139 + Malformed strings that look like GFX but can't be parsed should raise
140 + so the user gets a clear error message asking them to file an issue.
141 + """
142 + from vllm.platforms.rocm import _capability_from_gcn_arch
143 +
144 + with pytest.raises(ValueError, match="GCN arch"):
145 + _capability_from_gcn_arch(gcn_arch)
146 +
147 +
148 +# ---------------------------------------------------------------------------
149 +# AITER ops availability tests
150 +# ---------------------------------------------------------------------------
151 +
152 +
153 +def test_rocm_aiter_ops_accessor_methods_dont_crash():
154 + """rocm_aiter_ops accessor methods work without crashing.
155 +
156 + These methods gate kernel selection. Even if AITER isn't available,
157 + they should return False gracefully rather than crashing.
158 + """
159 + from vllm._aiter_ops import rocm_aiter_ops
160 +
161 + # These should all return bool without crashing
162 + assert isinstance(rocm_aiter_ops.is_enabled(), (bool, type(None)))
163 + assert isinstance(rocm_aiter_ops.is_linear_enabled(), (bool, type(None)))
164 + assert isinstance(rocm_aiter_ops.is_fused_moe_enabled(), (bool, type(None)))
165 + assert isinstance(rocm_aiter_ops.is_mla_enabled(), (bool, type(None)))
166 + assert isinstance(rocm_aiter_ops.is_mha_enabled(), (bool, type(None)))
167 +
168 +
169 +def test_rocm_aiter_ops_is_aiter_found():
170 + """is_aiter_found returns consistent results."""
171 + from vllm._aiter_ops import IS_AITER_FOUND, is_aiter_found
172 +
173 + # The cached global should match the function result
174 + assert is_aiter_found() == IS_AITER_FOUND
175 +
176 +
177 +def test_rocm_aiter_ops_refresh_env_variables_doesnt_crash():
178 + """refresh_env_variables works without crashing.
179 +
180 + This is called after monkeypatching env vars in tests. It should
181 + never crash, even with unusual env var combinations.
182 + """
183 + from vllm._aiter_ops import rocm_aiter_ops
184 +
185 + # Should not raise
186 + rocm_aiter_ops.refresh_env_variables()
187 +
188 +
189 +# ---------------------------------------------------------------------------
190 +# Env var propagation tests
191 +# ---------------------------------------------------------------------------
192 +
193 +
194 +@pytest.mark.parametrize("enabled", [True, False])
195 +def test_shuffle_kv_cache_env_propagates_to_rocm_aiter_ops(enabled, monkeypatch):
196 + """VLLM_ROCM_SHUFFLE_KV_CACHE_LAYOUT propagates to AITER state."""
197 + import vllm.envs as envs
198 + from vllm._aiter_ops import rocm_aiter_ops
199 +
200 + monkeypatch.setenv("VLLM_ROCM_SHUFFLE_KV_CACHE_LAYOUT", "1" if enabled else "0")
201 + importlib.reload(envs)
202 + rocm_aiter_ops.refresh_env_variables()
203 +
204 + assert envs.VLLM_ROCM_SHUFFLE_KV_CACHE_LAYOUT is enabled
205 + assert rocm_aiter_ops.is_shuffle_kv_cache_enabled() is enabled
206 +
207 +
208 +@pytest.mark.parametrize("enabled", [True, False])
209 +def test_skinny_gemm_env_controls_rocm_fp8_scaled_mm_support(enabled, monkeypatch):
210 + """VLLM_ROCM_USE_SKINNY_GEMM gates ROCm FP8 scaled-mm kernel eligibility."""
211 + import vllm.envs as envs
212 + from vllm.model_executor.kernels.linear.scaled_mm.rocm import (
213 + ROCmFP8ScaledMMLinearKernel,
214 + )
215 +
216 + monkeypatch.setenv("VLLM_ROCM_USE_SKINNY_GEMM", "1" if enabled else "0")
217 + importlib.reload(envs)
218 + monkeypatch.setattr("vllm.platforms.rocm.on_mi3xx", lambda: True)
219 +
220 + supported, reason = ROCmFP8ScaledMMLinearKernel.is_supported()
221 +
222 + assert supported is enabled
223 + if enabled:
224 + assert reason is None
225 + else:
226 + assert reason == "requires VLLM_ROCM_USE_SKINNY_GEMM to be enabled."