diff --git a/dnn-providers/hip-kernel-provider/rocke/TESTING.md b/dnn-providers/hip-kernel-provider/rocke/TESTING.md index fd80c8847dc3..fc601a85fc7f 100644 --- a/dnn-providers/hip-kernel-provider/rocke/TESTING.md +++ b/dnn-providers/hip-kernel-provider/rocke/TESTING.md @@ -513,6 +513,50 @@ tests; an older local compiler failure is separate evidence. Review skipped tests explicitly: Torch-free execution, missing native fixtures, and unavailable GPUs each leave different coverage gaps. Host pytest is not a full GPU job replay. +#### 5.1.3 Writing native recipe replay tests + +Native replay APIs can hide a source-build dependency. In particular, +[`online.recipe_cbor_to_llvm()`](platform/python/rocke/portable_ir/src/online.py) +calls `online.load()`, which calls `build_lib()` when it finds no prebuilt shared +library. A test can pass in a source checkout or with a cached library, then fail +in an installed artifact because the source tree and `CMakeLists.txt` are absent. +Do not let replay test setup implicitly configure or build native code. + +When adding or changing a native replay test: + +- Build the native artifact during build/setup, from the same revision as the + Python oracle. Run a prebuilt replay CLI, or supply a prebuilt shared-library + path explicitly before calling the online API. Never fall back to a cached + library or an automatic source build to make the test pass. +- If the prebuilt CLI is unset or missing, skip the native replay lane with a + reason identifying the missing artifact, following the existing portable-IR + tests. Once present, execution errors and IR mismatches are failures; do not + catch them as capability skips. Keep independent Python coverage enabled. +- Let the launcher supply artifact paths. Installed CTest paths must be relative + to its working directory so relocation works. Do not infer artifact presence + or location from test-file paths, nearby source files, or conventional build + directories. Install required content through CMake and the artifact manifest. +- Keep a suite-specific fixture local to its module. Do not mutate shared + environment settings during fixture setup or add an autouse fixture that + changes other suites' native-lane discovery or skips. Shared launcher wiring + or fixtures need an intentional consumer contract and validation of every + affected suite. + +The existing replay CLI target is `rocke_portable_ir_replay_cli`; its installed +location is `tests/portable_ir/`. `ROCKE_REPLAY_CLI` is the single launcher setting +for native CLI replay, including TF32 and the portable-IR suites. Installed CTest +supplies the relative executable path once for the pytest run, enabling all +consumers of that artifact. + +For source execution, build the target first and supply its executable through +`ROCKE_REPLAY_CLI`. Reuse this setting for new replay consumers rather than adding +a kernel-specific setting. When changing its launcher wiring, validate every +consumer, including suites whose native lanes previously skipped. Check absent +and present artifacts, a broken executable, and unrelated suites' skips. +Repeat the native lane in a clean +relocated install without source directories or native caches available; a +source-only pass cannot establish installed-artifact support. + ## 6. Invariants & contracts Cross-cutting properties every change must preserve: diff --git a/dnn-providers/hip-kernel-provider/rocke/platform/CMakeLists.txt b/dnn-providers/hip-kernel-provider/rocke/platform/CMakeLists.txt index abeeafeb8ecb..67199dd4b853 100644 --- a/dnn-providers/hip-kernel-provider/rocke/platform/CMakeLists.txt +++ b/dnn-providers/hip-kernel-provider/rocke/platform/CMakeLists.txt @@ -453,7 +453,7 @@ if(ROCKE_INSTALL_TESTS) string(APPEND _ROCKE_INSTALLED_CTEST_ENTRIES "set_tests_properties(rocke_pytest PROPERTIES\n" " LABELS \"ckc\\;python\\;quick\"\n" - " ENVIRONMENT \"PYTHONPATH=.\"\n" + " ENVIRONMENT \"PYTHONPATH=.;ROCKE_REPLAY_CLI=${ROCKE_TEST_CTEST_REL}tests/portable_ir/rocke_portable_ir_replay_cli${CMAKE_EXECUTABLE_SUFFIX}\"\n" " TIMEOUT 300)\n") endif() diff --git a/dnn-providers/hip-kernel-provider/rocke/platform/tests/CMakeLists.txt b/dnn-providers/hip-kernel-provider/rocke/platform/tests/CMakeLists.txt index 87d32004f5d2..94f501e42acf 100644 --- a/dnn-providers/hip-kernel-provider/rocke/platform/tests/CMakeLists.txt +++ b/dnn-providers/hip-kernel-provider/rocke/platform/tests/CMakeLists.txt @@ -199,6 +199,9 @@ if(BUILD_TESTING) if(UNIX) target_link_libraries(rocke_portable_ir_replay_cli PRIVATE m) endif() + # Python recipe tests need the native VM in relocated test artifacts. + install(TARGETS rocke_portable_ir_replay_cli + RUNTIME DESTINATION "${ROCKE_TEST_INSTALL_DIR}/tests/portable_ir") endif() # --- instances layer -------------------------------------------------------- diff --git a/dnn-providers/hip-kernel-provider/rocke/platform/tests/core/test_tf32.py b/dnn-providers/hip-kernel-provider/rocke/platform/tests/core/test_tf32.py index b06f1ede53a4..3773bb5204eb 100644 --- a/dnn-providers/hip-kernel-provider/rocke/platform/tests/core/test_tf32.py +++ b/dnn-providers/hip-kernel-provider/rocke/platform/tests/core/test_tf32.py @@ -1,6 +1,10 @@ # Copyright (c) Advanced Micro Devices, Inc., or its affiliates. # SPDX-License-Identifier: MIT """Logical TF32 contracts and strict native parity, independent of GPU access.""" +import os +import subprocess +from pathlib import Path + import numpy as np import pytest @@ -358,27 +362,47 @@ def test_parsed_mma_rejects_wrong_operands(): native.lower_serialized_ir(ir, arch="gfx942") +@pytest.fixture(scope="module") +def tf32_recipe_replay_cli(): + """Use the launcher-supplied native VM shared by replay suites.""" + executable = os.environ.get("ROCKE_REPLAY_CLI") + if not executable: + pytest.skip("Set ROCKE_REPLAY_CLI to the prebuilt native replay CLI") + path = Path(executable).resolve() + if not path.is_file(): + pytest.skip(f"TF32 native replay CLI not found: {path}") + return path + + @pytest.mark.parametrize("m", [16, 32]) @pytest.mark.parametrize("mode", PREPARATIONS) -@pytest.mark.skip( - reason=( - "Temporarily disabled (#12612): native recipe replay attempts a source " - "build from installed test artifacts" - ) -) -def test_recipe_replay_preserves_ir(m, mode, monkeypatch): +def test_recipe_replay_preserves_ir(m, mode, tmp_path, tf32_recipe_replay_cli): from rocke.portable_ir.src.recording_builder import record_kernel - from rocke.portable_ir.src import online, recipe_bundle + from rocke.portable_ir.src import recipe_bundle spec = Tf32MmaProbeSpec(m, mode) recorded, recipe = record_kernel(lambda: build_tf32_mma_probe(spec)) assert serialize(recorded) == serialize(build_tf32_mma_probe(spec)) + path = tmp_path / "recipe.cbor" + path.write_bytes(recipe_bundle.cbor_encode(recipe)) for flavor in ("llvm20", "llvm22", "llvm23"): - monkeypatch.setenv("ROCKE_LLVM_FLAVOR", flavor) - replayed, _ = online.recipe_cbor_to_llvm( - recipe_bundle.cbor_encode(recipe), arch="gfx942" + replayed = subprocess.run( + [ + str(tf32_recipe_replay_cli), + "--recipe", + str(path), + "--cbor", + "--arch", + "gfx942", + "--flavor", + flavor, + ], + capture_output=True, + text=True, + check=True, + timeout=30, ) - assert replayed == _lower_kernel_to_llvm_python( + assert replayed.stdout == _lower_kernel_to_llvm_python( recorded, arch="gfx942", llvm_flavor=flavor ) diff --git a/dnn-providers/hip-kernel-provider/rocke/platform/tests/numeric/test_gfx942_tf32_numeric.py b/dnn-providers/hip-kernel-provider/rocke/platform/tests/numeric/test_gfx942_tf32_numeric.py index d05e493e3c04..f1539891dfda 100644 --- a/dnn-providers/hip-kernel-provider/rocke/platform/tests/numeric/test_gfx942_tf32_numeric.py +++ b/dnn-providers/hip-kernel-provider/rocke/platform/tests/numeric/test_gfx942_tf32_numeric.py @@ -1,23 +1,23 @@ # Copyright (c) Advanced Micro Devices, Inc., or its affiliates. # SPDX-License-Identifier: MIT +import importlib.util import os import pytest from rocke.runtime.hip_module import get_device_arch from rocke.examples.gfx942.tf32_numerics import run -@pytest.mark.skip( - reason=( - "Temporarily disabled (#12612): both-backend test requires rocke_engine, " - "absent from installed test artifacts" - ) -) def test_gfx942_tf32_numeric(tmp_path): arch = get_device_arch() if not arch or arch.split(":")[0] != "gfx942": if os.getenv("ROCKE_REQUIRE_GFX942") == "1": pytest.fail(f"Required gfx942 device absent: {arch}") pytest.skip("requires gfx942") - result = run(tmp_path, backend="both") + # The installed wheel has no pybind extension. Keep numerical coverage in + # that layout and demand native parity whenever the binding is supplied. + backend = "python" if importlib.util.find_spec("rocke_engine") is None else "both" + result = run(tmp_path, backend=backend) assert result["status"] == "pass" - assert len(result["results"]) == 24 + engines = {"python"} if backend == "python" else {"python", "cpp"} + assert {row["engine"] for row in result["results"]} == engines + assert len(result["results"]) == 12 * len(engines)