| 1 | #!/usr/bin/env python3 |
| 2 | # SPDX-License-Identifier: Apache-2.0 |
| 3 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 4 | """Compile-guard tests for the ROCm RDNA3 W4A16 kernels (dense + MoE). |
| 5 | |
| 6 | Verifies that the gfx1100 compilation and dispatch guards are hermetic: |
| 7 | - On gfx1100: all ops exist, dispatch selects RDNA3 kernels. |
| 8 | - On CDNA (gfx942/gfx950) or other non-gfx1100: ops must NOT exist, |
| 9 | dispatch must fall through to Triton/Marlin, and no RDNA3 code |
| 10 | path is reachable. |
| 11 | |
| 12 | The negative (non-gfx1100) tests verify at three layers: |
| 13 | 1. Compile-level: on non-gfx1100 hardware, the RDNA3 ops are absent |
| 14 | from the compiled _rocm_C extension — real binary verification. |
| 15 | 2. Static source analysis: parses CMakeLists.txt and torch_bindings.cpp |
| 16 | to verify that all RDNA3 .cu files and op registrations are inside |
| 17 | gfx1100-only guards. |
| 18 | 3. Runtime mock: patches on_gfx1100() to False and verifies that the |
| 19 | Python dispatch chain rejects the RDNA3 path. |
| 20 | |
| 21 | Run `pytest tests/kernels/quantization/test_rdna3_compile_guards.py`. |
| 22 | """ |
| 23 | |
| 24 | from pathlib import Path |
| 25 | from unittest.mock import patch |
| 26 | |
| 27 | import pytest |
| 28 | import regex as re |
| 29 | import torch |
| 30 | |
| 31 | import vllm |
| 32 | from vllm.platforms import current_platform |
| 33 | |
| 34 | if not current_platform.is_rocm(): |
| 35 | pytest.skip("RDNA3 compile-guard tests are ROCm-only", allow_module_level=True) |
| 36 | |
| 37 | from vllm.model_executor.layers.quantization.utils.quant_utils import ( # noqa: E402 |
| 38 | kInt4Static, |
| 39 | kInt4Static32, |
| 40 | kInt4Static32Asym, |
| 41 | kInt4StaticAsym, |
| 42 | kInt8Static, |
| 43 | ) |
| 44 | from vllm.platforms.rocm import on_gfx1100 # noqa: E402 |
| 45 | |
| 46 | gfx1100_only = pytest.mark.skipif( |
| 47 | not on_gfx1100(), |
| 48 | reason="Requires gfx1100 hardware", |
| 49 | ) |
| 50 | |
| 51 | not_gfx1100 = pytest.mark.skipif( |
| 52 | on_gfx1100(), |
| 53 | reason="This test verifies non-gfx1100 builds — skip on gfx1100", |
| 54 | ) |
| 55 | |
| 56 | RDNA3_OPS = ["gptq_gemm_rdna3", "gptq_gemm_rdna3_wmma", "moe_gptq_gemm_rdna3"] |
| 57 | RDNA3_CU_FILES = [ |
| 58 | "q_gemm_rdna3.cu", |
| 59 | "q_gemm_rdna3_wmma.cu", |
| 60 | "moe_q_gemm_rdna3.cu", |
| 61 | ] |
| 62 | |
| 63 | |
| 64 | def _find_repo_root() -> Path | None: |
| 65 | """Walk up from this file to find the repo root (has CMakeLists.txt).""" |
| 66 | for parent in [Path(__file__).resolve(), *Path(__file__).resolve().parents]: |
| 67 | if (parent / "CMakeLists.txt").exists() and (parent / "csrc").is_dir(): |
| 68 | return parent |
| 69 | return None |
| 70 | |
| 71 | |
| 72 | REPO_ROOT = _find_repo_root() |
| 73 | |
| 74 | # Directory of the *installed* vllm python package. The .py guard checks read |
| 75 | # from here so they verify the code that is actually imported at runtime — this |
| 76 | # works even on CI images that ship the wheel instead of the python source tree |
| 77 | # (where only csrc/ + CMakeLists.txt are checked out for building). |
| 78 | VLLM_PKG_DIR: Path | None = ( |
| 79 | Path(vllm.__file__).parent if getattr(vllm, "__file__", None) else None |
| 80 | ) |
| 81 | |
| 82 | needs_source = pytest.mark.skipif( |
| 83 | REPO_ROOT is None, |
| 84 | reason="C/CMake source tree not available (installed package only)", |
| 85 | ) |
| 86 | |
| 87 | |
| 88 | def _read_source_or_skip(*relparts: str) -> str: |
| 89 | """Read a C/CMake source file from the repo tree, or skip if absent. |
| 90 | |
| 91 | Used for csrc/ and CMakeLists.txt — these only exist in a source checkout, |
| 92 | not in the installed wheel. |
| 93 | """ |
| 94 | assert REPO_ROOT is not None # callers are gated by @needs_source |
| 95 | path = REPO_ROOT.joinpath(*relparts) |
| 96 | if not path.exists(): |
| 97 | pytest.skip(f"{path} not present in this source tree") |
| 98 | return path.read_text() |
| 99 | |
| 100 | |
| 101 | def _read_pkg_source_or_skip(*relparts: str) -> str: |
| 102 | """Read a python source file from the installed vllm package. |
| 103 | |
| 104 | Reflects the code actually loaded at runtime, so these guard checks run in |
| 105 | CI against the wheel — no source checkout required. Only skips for an |
| 106 | exotic install layout (namespace/zipimport) where __file__ is unavailable. |
| 107 | """ |
| 108 | if VLLM_PKG_DIR is None: |
| 109 | pytest.skip("vllm package directory not resolvable (zip/namespace?)") |
| 110 | assert VLLM_PKG_DIR is not None # narrow for mypy (skip above is NoReturn) |
| 111 | path = VLLM_PKG_DIR.joinpath(*relparts) |
| 112 | if not path.exists(): |
| 113 | pytest.skip(f"{path} not present in installed vllm package") |
| 114 | return path.read_text() |
| 115 | |
| 116 | |
| 117 | # ============================================================================ |
| 118 | # Part A: POSITIVE — on gfx1100, ops exist and dispatch works |
| 119 | # ============================================================================ |
| 120 | |
| 121 | |
| 122 | @gfx1100_only |
| 123 | @pytest.mark.parametrize("op_name", RDNA3_OPS) |
| 124 | def test_op_registered_on_gfx1100(op_name): |
| 125 | """On gfx1100, all RDNA3 ops must be registered in _rocm_C.""" |
| 126 | assert hasattr(torch.ops, "_rocm_C"), "_rocm_C module not loaded" |
| 127 | assert hasattr(torch.ops._rocm_C, op_name), ( |
| 128 | f"_rocm_C.{op_name} not registered — " |
| 129 | "check CMakeLists.txt VLLM_ROCM_HAS_GFX1100 " |
| 130 | "and torch_bindings.cpp #ifdef VLLM_ROCM_GFX1100" |
| 131 | ) |
| 132 | |
| 133 | |
| 134 | @gfx1100_only |
| 135 | def test_all_ops_present_or_all_absent(): |
| 136 | """The 3 RDNA3 ops are behind the same #ifdef — all present or all absent. |
| 137 | |
| 138 | Catches someone accidentally moving an op outside the guard. |
| 139 | """ |
| 140 | has_rocm_c = hasattr(torch.ops, "_rocm_C") |
| 141 | if not has_rocm_c: |
| 142 | pytest.skip("_rocm_C not loaded") |
| 143 | |
| 144 | present = {op: hasattr(torch.ops._rocm_C, op) for op in RDNA3_OPS} |
| 145 | values = set(present.values()) |
| 146 | assert len(values) == 1, ( |
| 147 | f"Guard inconsistency — some RDNA3 ops registered, others not: " |
| 148 | f"{present}. Check torch_bindings.cpp #ifdef VLLM_ROCM_GFX1100 block." |
| 149 | ) |
| 150 | |
| 151 | |
| 152 | # ============================================================================ |
| 153 | # Part B: NEGATIVE — compile-level verification on non-gfx1100 |
| 154 | # ============================================================================ |
| 155 | |
| 156 | |
| 157 | @not_gfx1100 |
| 158 | @pytest.mark.parametrize("op_name", RDNA3_OPS) |
| 159 | def test_op_absent_on_non_gfx1100(op_name): |
| 160 | """On non-gfx1100 (CDNA), RDNA3 ops must NOT exist in _rocm_C. |
| 161 | |
| 162 | This is the real compile-level check: the binary was built without |
| 163 | gfx1100 support, so the ops should not have been compiled or registered. |
| 164 | """ |
| 165 | if not hasattr(torch.ops, "_rocm_C"): |
| 166 | return |
| 167 | assert not hasattr(torch.ops._rocm_C, op_name), ( |
| 168 | f"_rocm_C.{op_name} is registered on non-gfx1100 hardware — " |
| 169 | "compile guard is broken: check CMakeLists.txt " |
| 170 | "VLLM_ROCM_HAS_GFX1100 and torch_bindings.cpp #ifdef" |
| 171 | ) |
| 172 | |
| 173 | |
| 174 | @not_gfx1100 |
| 175 | def test_rocm_moe_not_supported_on_non_gfx1100(): |
| 176 | """The RDNA3 MoE experts must not be selectable on non-gfx1100 hardware.""" |
| 177 | from vllm.model_executor.layers.fused_moe.experts.rdna3_moe import ( |
| 178 | Rdna3WNA16Experts, |
| 179 | ) |
| 180 | |
| 181 | assert Rdna3WNA16Experts._supports_current_device() is False, ( |
| 182 | "Rdna3WNA16Experts reported support on non-gfx1100 — dispatch guard is broken" |
| 183 | ) |
| 184 | |
| 185 | |
| 186 | @not_gfx1100 |
| 187 | def test_dense_kernel_rejects_on_non_gfx1100(): |
| 188 | """RDNA3W4A16LinearKernel.can_implement must reject on non-gfx1100.""" |
| 189 | from vllm.model_executor.kernels.linear.mixed_precision.MPLinearKernel import ( # noqa: E501 |
| 190 | MPLinearLayerConfig, |
| 191 | ) |
| 192 | from vllm.model_executor.kernels.linear.mixed_precision.rdna3_w4a16 import ( # noqa: E501 |
| 193 | RDNA3W4A16LinearKernel, |
| 194 | ) |
| 195 | from vllm.scalar_type import scalar_types |
| 196 | |
| 197 | config = MPLinearLayerConfig( |
| 198 | full_weight_shape=(1024, 256), |
| 199 | partition_weight_shape=(1024, 256), |
| 200 | weight_type=scalar_types.uint4b8, |
| 201 | act_type=torch.float16, |
| 202 | group_size=128, |
| 203 | zero_points=False, |
| 204 | ) |
| 205 | ok, reason = RDNA3W4A16LinearKernel.can_implement(config) |
| 206 | assert ok is False, f"RDNA3 dense kernel accepted on non-gfx1100: {reason}" |
| 207 | |
| 208 | |
| 209 | # ============================================================================ |
| 210 | # Part C: Static source analysis (build-level guards) |
| 211 | # ============================================================================ |
| 212 | |
| 213 | |
| 214 | @needs_source |
| 215 | class TestCMakeGuards: |
| 216 | """Verify CMakeLists.txt only compiles RDNA3 .cu files for gfx1100.""" |
| 217 | |
| 218 | @staticmethod |
| 219 | def _read_cmake(): |
| 220 | return _read_source_or_skip("CMakeLists.txt") |
| 221 | |
| 222 | def test_rdna3_cu_files_inside_gfx1100_conditional(self): |
| 223 | """All RDNA3 .cu files must be listed inside the |
| 224 | ``if(VLLM_GPU_ARCHES MATCHES "gfx1100")`` block, not unconditionally. |
| 225 | """ |
| 226 | cmake = self._read_cmake() |
| 227 | for cu_file in RDNA3_CU_FILES: |
| 228 | assert cu_file in cmake, f"{cu_file} not found in CMakeLists.txt" |
| 229 | |
| 230 | lines = cmake.splitlines() |
| 231 | in_gfx1100_block = False |
| 232 | for line in lines: |
| 233 | if 'VLLM_GPU_ARCHES MATCHES "gfx1100"' in line: |
| 234 | in_gfx1100_block = True |
| 235 | if in_gfx1100_block and "endif()" in line: |
| 236 | in_gfx1100_block = False |
| 237 | if cu_file in line: |
| 238 | assert in_gfx1100_block, ( |
| 239 | f"{cu_file} is listed OUTSIDE the gfx1100 " |
| 240 | f"conditional in CMakeLists.txt — CDNA builds " |
| 241 | f"would compile RDNA3 code. Line: {line.strip()}" |
| 242 | ) |
| 243 | |
| 244 | def test_compile_definition_only_for_gfx1100(self): |
| 245 | """VLLM_ROCM_GFX1100 compile definition must be conditional.""" |
| 246 | cmake = self._read_cmake() |
| 247 | lines = cmake.splitlines() |
| 248 | in_gfx1100_block = False |
| 249 | for line in lines: |
| 250 | if "VLLM_ROCM_HAS_GFX1100)" in line: |
| 251 | in_gfx1100_block = True |
| 252 | if in_gfx1100_block and "endif()" in line: |
| 253 | in_gfx1100_block = False |
| 254 | if "VLLM_ROCM_GFX1100" in line and "target_compile_definitions" in line: |
| 255 | assert in_gfx1100_block, ( |
| 256 | "VLLM_ROCM_GFX1100 compile definition is set outside " |
| 257 | "the VLLM_ROCM_HAS_GFX1100 conditional — CDNA builds " |
| 258 | f"would define it. Line: {line.strip()}" |
| 259 | ) |
| 260 | |
| 261 | |
| 262 | @needs_source |
| 263 | class TestTorchBindingsGuards: |
| 264 | """Verify torch_bindings.cpp gates all RDNA3 ops behind #ifdef.""" |
| 265 | |
| 266 | @staticmethod |
| 267 | def _read_bindings(): |
| 268 | return _read_source_or_skip("csrc", "rocm", "torch_bindings.cpp") |
| 269 | |
| 270 | def test_all_rdna3_ops_inside_ifdef(self): |
| 271 | """Every rdna3 op def/impl must be between #ifdef VLLM_ROCM_GFX1100 |
| 272 | and #endif. If any is outside, a CDNA build would try to register |
| 273 | the op and link a symbol that doesn't exist. |
| 274 | """ |
| 275 | src = self._read_bindings() |
| 276 | lines = src.splitlines() |
| 277 | |
| 278 | inside_guard = False |
| 279 | rdna3_lines_outside = [] |
| 280 | |
| 281 | for i, line in enumerate(lines, 1): |
| 282 | if "#ifdef VLLM_ROCM_GFX1100" in line: |
| 283 | inside_guard = True |
| 284 | elif line.strip() == "#endif" and inside_guard: |
| 285 | inside_guard = False |
| 286 | |
| 287 | if ( |
| 288 | "rdna3" in line.lower() |
| 289 | and not line.strip().startswith("//") |
| 290 | and not inside_guard |
| 291 | ): |
| 292 | rdna3_lines_outside.append((i, line.strip())) |
| 293 | |
| 294 | assert not rdna3_lines_outside, ( |
| 295 | "RDNA3 op references found OUTSIDE #ifdef VLLM_ROCM_GFX1100 " |
| 296 | "in torch_bindings.cpp — these would break CDNA builds:\n" |
| 297 | + "\n".join(f" L{n}: {s}" for n, s in rdna3_lines_outside) |
| 298 | ) |
| 299 | |
| 300 | def test_no_unconditional_rdna3_includes(self): |
| 301 | """No #include of RDNA3-specific headers outside the guard.""" |
| 302 | src = self._read_bindings() |
| 303 | lines = src.splitlines() |
| 304 | |
| 305 | inside_guard = False |
| 306 | for i, line in enumerate(lines, 1): |
| 307 | if "#ifdef VLLM_ROCM_GFX1100" in line: |
| 308 | inside_guard = True |
| 309 | elif line.strip() == "#endif" and inside_guard: |
| 310 | inside_guard = False |
| 311 | |
| 312 | if "#include" in line and "rdna3" in line.lower(): |
| 313 | assert inside_guard, ( |
| 314 | f"L{i}: RDNA3 include outside gfx1100 guard: {line.strip()}" |
| 315 | ) |
| 316 | |
| 317 | |
| 318 | class TestCustomOpsGuards: |
| 319 | """Verify _custom_ops.py gates register_fake behind hasattr checks.""" |
| 320 | |
| 321 | @staticmethod |
| 322 | def _read_custom_ops(): |
| 323 | return _read_pkg_source_or_skip("_custom_ops.py") |
| 324 | |
| 325 | def test_register_fake_guarded_by_hasattr(self): |
| 326 | """Every register_fake for an RDNA3 op must be preceded by a hasattr |
| 327 | check — otherwise it would crash on import on CDNA where the ops |
| 328 | don't exist. |
| 329 | """ |
| 330 | src = self._read_custom_ops() |
| 331 | for op in RDNA3_OPS: |
| 332 | pattern = rf'register_fake\(\s*"_rocm_C::{op}"\s*\)' |
| 333 | match = re.search(pattern, src) |
| 334 | if match is None: |
| 335 | continue |
| 336 | preceding = src[: match.start()] |
| 337 | last_hasattr = preceding.rfind(f'hasattr(torch.ops._rocm_C, "{op}")') |
| 338 | assert last_hasattr != -1, ( |
| 339 | f'register_fake("_rocm_C::{op}") is not preceded by a ' |
| 340 | f"hasattr check — would crash on CDNA import" |
| 341 | ) |
| 342 | gap = preceding[last_hasattr:].count("\n") |
| 343 | assert gap <= 5, ( |
| 344 | f"hasattr guard for {op} is {gap} lines before " |
| 345 | f"register_fake — suspiciously far; verify it's the " |
| 346 | f"actual guard and not a coincidence" |
| 347 | ) |
| 348 | |
| 349 | def test_no_toplevel_rocm_c_import(self): |
| 350 | """No top-level ``from vllm._rocm_C import`` — would crash on CDNA.""" |
| 351 | src = self._read_custom_ops() |
| 352 | for line in src.splitlines(): |
| 353 | stripped = line.strip() |
| 354 | if stripped.startswith("#") or stripped.startswith("//"): |
| 355 | continue |
| 356 | assert "from vllm._rocm_C import" not in stripped, ( |
| 357 | f"Top-level import of _rocm_C in _custom_ops.py would " |
| 358 | f"crash on CDNA: {stripped}" |
| 359 | ) |
| 360 | |
| 361 | |
| 362 | # ============================================================================ |
| 363 | # Part D: Runtime mock (simulate CDNA on gfx1100 hardware) |
| 364 | # ============================================================================ |
| 365 | |
| 366 | |
| 367 | class TestMoEDispatchMocked: |
| 368 | """Mock on_gfx1100() to False and verify RDNA3 MoE is unreachable.""" |
| 369 | |
| 370 | def test_kernel_unavailable_when_mocked_cdna(self): |
| 371 | """The device gate must reject when not on gfx1100.""" |
| 372 | from vllm.model_executor.layers.fused_moe.experts.rdna3_moe import ( |
| 373 | rdna3_moe_kernel_available, |
| 374 | ) |
| 375 | |
| 376 | with patch("vllm.platforms.rocm.on_gfx1100", return_value=False): |
| 377 | assert rdna3_moe_kernel_available() is False |
| 378 | |
| 379 | @pytest.mark.parametrize( |
| 380 | "weight_key", |
| 381 | [kInt8Static, kInt4StaticAsym, kInt4Static32Asym, None], |
| 382 | ) |
| 383 | def test_quant_scheme_rejects_non_symmetric_int4(self, weight_key): |
| 384 | """Only symmetric int4 weight-only schemes reach the kernel.""" |
| 385 | from vllm.model_executor.layers.fused_moe.experts.rdna3_moe import ( |
| 386 | Rdna3WNA16Experts, |
| 387 | ) |
| 388 | |
| 389 | assert Rdna3WNA16Experts._supports_quant_scheme(weight_key, None) is False |
| 390 | |
| 391 | @pytest.mark.parametrize("weight_key", [kInt4Static, kInt4Static32]) |
| 392 | def test_quant_scheme_accepts_symmetric_int4(self, weight_key): |
| 393 | from vllm.model_executor.layers.fused_moe.experts.rdna3_moe import ( |
| 394 | Rdna3WNA16Experts, |
| 395 | ) |
| 396 | |
| 397 | assert Rdna3WNA16Experts._supports_quant_scheme(weight_key, None) is True |
| 398 | |
| 399 | def test_kernel_unavailable_when_op_missing(self): |
| 400 | """The device gate returns False when the C++ op doesn't exist.""" |
| 401 | from vllm.model_executor.layers.fused_moe.experts.rdna3_moe import ( |
| 402 | rdna3_moe_kernel_available, |
| 403 | ) |
| 404 | |
| 405 | fake_rocm_c = type("FakeRocmC", (), {"gptq_gemm_rdna3": None})() |
| 406 | with patch.object(torch, "ops", create=True) as mock_ops: |
| 407 | mock_ops._rocm_C = fake_rocm_c |
| 408 | assert rdna3_moe_kernel_available() is False |
| 409 | |
| 410 | def test_kernel_unavailable_when_rocm_c_absent(self): |
| 411 | """The device gate returns False when _rocm_C doesn't exist at all.""" |
| 412 | from vllm.model_executor.layers.fused_moe.experts.rdna3_moe import ( |
| 413 | rdna3_moe_kernel_available, |
| 414 | ) |
| 415 | |
| 416 | fake_ops = type("FakeOps", (), {})() |
| 417 | with patch.object(torch, "ops", fake_ops): |
| 418 | assert rdna3_moe_kernel_available() is False |
| 419 | |
| 420 | |
| 421 | class TestDenseKernelSelectionMocked: |
| 422 | """Mock on_gfx1100() and verify dense RDNA3 kernel is not selected.""" |
| 423 | |
| 424 | @gfx1100_only |
| 425 | def test_can_implement_rejects_when_mocked_cdna(self): |
| 426 | """RDNA3W4A16LinearKernel.can_implement must reject on mocked CDNA.""" |
| 427 | from vllm.model_executor.kernels.linear.mixed_precision.MPLinearKernel import ( # noqa: E501 |
| 428 | MPLinearLayerConfig, |
| 429 | ) |
| 430 | from vllm.model_executor.kernels.linear.mixed_precision.rdna3_w4a16 import ( # noqa: E501 |
| 431 | RDNA3W4A16LinearKernel, |
| 432 | ) |
| 433 | from vllm.scalar_type import scalar_types |
| 434 | |
| 435 | config = MPLinearLayerConfig( |
| 436 | full_weight_shape=(1024, 256), |
| 437 | partition_weight_shape=(1024, 256), |
| 438 | weight_type=scalar_types.uint4b8, |
| 439 | act_type=torch.float16, |
| 440 | group_size=128, |
| 441 | zero_points=False, |
| 442 | ) |
| 443 | ok, _ = RDNA3W4A16LinearKernel.can_implement(config) |
| 444 | assert ok is True |
| 445 | |
| 446 | with ( |
| 447 | patch("vllm.platforms.rocm.on_gfx1100", return_value=False), |
| 448 | patch("vllm.platforms.rocm._ON_GFX1100", False), |
| 449 | ): |
| 450 | ok, reason = RDNA3W4A16LinearKernel.can_implement(config) |
| 451 | assert ok is False, f"RDNA3 kernel accepted on simulated CDNA: {reason}" |
| 452 | |
| 453 | @gfx1100_only |
| 454 | def test_chooser_skips_rdna3_when_mocked_cdna(self): |
| 455 | """choose_mp_linear_kernel must NOT return RDNA3 on mocked CDNA.""" |
| 456 | from vllm.model_executor.kernels.linear import ( |
| 457 | choose_mp_linear_kernel, |
| 458 | ) |
| 459 | from vllm.model_executor.kernels.linear.mixed_precision.MPLinearKernel import ( # noqa: E501 |
| 460 | MPLinearLayerConfig, |
| 461 | ) |
| 462 | from vllm.scalar_type import scalar_types |
| 463 | |
| 464 | config = MPLinearLayerConfig( |
| 465 | full_weight_shape=(1024, 256), |
| 466 | partition_weight_shape=(1024, 256), |
| 467 | weight_type=scalar_types.uint4b8, |
| 468 | act_type=torch.float16, |
| 469 | group_size=128, |
| 470 | zero_points=False, |
| 471 | ) |
| 472 | with ( |
| 473 | patch("vllm.platforms.rocm.on_gfx1100", return_value=False), |
| 474 | patch("vllm.platforms.rocm._ON_GFX1100", False), |
| 475 | ): |
| 476 | chosen = choose_mp_linear_kernel(config) |
| 477 | assert chosen.__name__ != "RDNA3W4A16LinearKernel", ( |
| 478 | "RDNA3 kernel was selected on simulated CDNA — " |
| 479 | "choose_mp_linear_kernel guard is broken" |
| 480 | ) |
| 481 | |
| 482 | |
| 483 | class TestWNA16OracleWiring: |
| 484 | """The RDNA3 backend must reach the kernel only through the oracle.""" |
| 485 | |
| 486 | def test_backend_maps_to_rdna3_experts(self): |
| 487 | from vllm.model_executor.layers.fused_moe.experts.rdna3_moe import ( |
| 488 | Rdna3WNA16Experts, |
| 489 | ) |
| 490 | from vllm.model_executor.layers.fused_moe.oracle.int_wna16 import ( |
| 491 | WNA16MoEBackend, |
| 492 | backend_to_kernel_cls, |
| 493 | map_wna16_backend, |
| 494 | ) |
| 495 | |
| 496 | assert backend_to_kernel_cls(WNA16MoEBackend.RDNA3) == [Rdna3WNA16Experts] |
| 497 | assert map_wna16_backend("rdna3") == WNA16MoEBackend.RDNA3 |
| 498 | |
| 499 | def test_backend_is_offered_before_the_triton_fallback(self): |
| 500 | """Priority order: the native kernel outranks Triton when supported.""" |
| 501 | from vllm.model_executor.layers.fused_moe.oracle.int_wna16 import ( |
| 502 | WNA16MoEBackend, |
| 503 | _get_priority_backends, |
| 504 | ) |
| 505 | |
| 506 | backends = _get_priority_backends() |
| 507 | assert backends.index(WNA16MoEBackend.RDNA3) < backends.index( |
| 508 | WNA16MoEBackend.TRITON |
| 509 | ) |