Repository navigation
Changes to integrate Test Filter Standardization for miopen in TheRock #3595
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c833173
8e681bc
6547fbd
8de7262
8b25708
85c0b01
24c4195
04bccb7
0b2fa8b
61aa13c
0fd6551
c2f6376
1bc1ff9
b2b943c
bf98091
479f891
84c4799
6b3b744
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
dileepr1 marked this conversation as resolved.
Resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| # Test Runner | ||
|
|
||
| `test_runner.py` is a **generic test runner** used by GitHub Actions to run component tests (e.g. MIOpen). It relies on a standardized CTest naming and labeling scheme so that only tests valid for the current scenario are run. | ||
|
|
||
| This document describes how the mechanism works, what the component must provide, and how it ties into CI. | ||
|
|
||
| ______________________________________________________________________ | ||
|
|
||
| ## Overview | ||
|
|
||
| 1. The script is invoked by the **Test component** workflow with env vars set (component name, GPU arch, test type, sharding). | ||
| 1. The script expects the component under test to have a ctest based interface, with labels corresponding to test categories like quick, standard, comprehensive and full(which can be run for scenarios like smoke test, pre-commit etc) | ||
| 1. The component can opt to always exclude some tests or based on condition like OS and GPU where the test is getting run | ||
| 1. The script discovers which **GPU-specific test suites** exist by running `ctest --print-labels` and parsing labels of the form `ex_gpu_{gpu_arch}`. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're discussing how to handle systems having variable GPU memory. This is being driven by APUs such as Strix Halo. We probably don't need a complete answer for this PR, but it wouldn't hurt to start thinking about it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The particular issue is that many existing tests assuming having a certain amount of GPU memory. For MIOpen, this wasn't part of the design so we don't know the actual minimum, but roughly 12GB. We were seeing lots of failures on a Strix Halo having 16GB system memory, in which case the memory allocated to GPU was appx 4GB 'dedicated' and 4.9GB 'shared' for a total of 8.9GB.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this can be easily handled if we know the gfx models and their available memory and the memory needed for each of the tests before hand by using the exclude_gpu_gfx* section in test_categories.yaml. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately, the latter is the case on APU. CPU and GPU memory come from the same physical RAM sticks and the OS divides it up. GPU memory can be user-modified not only by changing the system RAM, but also modifying firmware and/or software settings. The memory configuration has to be queried before each run just prior to launching the tests. |
||
| 1. It builds and runs a **ctest** command with the right labels and options (category, parallelism, sharding, etc.). | ||
|
|
||
| No component-specific logic is required beyond using the expected test names and CTest labels. | ||
|
|
||
| ______________________________________________________________________ | ||
|
|
||
| ## Test Naming and Labels (Component Contract) | ||
|
|
||
| For this runner to work, the component’s CTest configuration must follow this convention. | ||
|
|
||
| ### CTest labels (discovery) | ||
|
|
||
| The script runs `ctest --print-labels --test-dir <component>` and collects every label that starts with **`ex_gpu_`**. The suffix is treated as the GPU architecture (e.g. `ex_gpu_gfx110X` → `gfx110X`, `ex_gpu_gfx950` → `gfx950`). Only labels whose suffix starts with `gfx` are used. | ||
|
|
||
| ### CTest labels (selection) | ||
|
|
||
| - **Category:** Tests are selected by **label** for the run (e.g. “run all tests with label `quick`” or `standard`). The script uses `-L <category>`. | ||
| - **GPU-specific suites:** For a given `gpu_arch`, the script looks for a label `ex_gpu_{gpu_arch}` (e.g. `ex_gpu_gfx1150`, `ex_gpu_gfx11X`). It uses `-L ex_gpu_<arch>` when it finds a matching label, or `-LE ex_gpu` to exclude all GPU-specific tests when no match is found or when no GPU is specified. | ||
|
|
||
| So the component must assign GPU-specific tests the label `ex_gpu_{gpu_arch}` (and typically a category label like `quick` or `standard`). | ||
|
|
||
| ______________________________________________________________________ | ||
|
|
||
| ## Execution Flow | ||
|
|
||
| 1. **Resolve component directory** | ||
| Map `TEST_COMPONENT` (e.g. `miopen`) to the test directory name (e.g. `MIOpen`) via `COMPONENT_DIR_MAPPING`. Fail if the test directory does not exist under `THEROCK_BIN_DIR`. | ||
| 1. **Discover GPU suites** | ||
| Run `ctest --print-labels --test-dir {THEROCK_BIN_DIR}/{TEST_COMPONENT}`. Collect every label that starts with `ex_gpu_` and whose suffix starts with `gfx` (e.g. `ex_gpu_gfx110X` → `gfx110X`). This yields the set of **available GPU architectures** for which a label exists. | ||
|
dileepr1 marked this conversation as resolved.
Resolved
|
||
| These are the set of GPU's for which some test exclusions apply - i.e there are some tests which should not be run on these GPU models. The exp_gpu\_ labels with these gpu models points to the ctest entries where the tests are excluded. | ||
| 1. **Choose category** | ||
| From `TEST_TYPE`: `smoke` → `quick`, else → `standard`. | ||
| 1. **Resolve GPU arch** | ||
| Parse `AMDGPU_FAMILIES` for the first `gfx...` token (e.g. `gfx1151`). If missing or generic, the script will exclude all GPU-specific tests (`-LE ex_gpu`). | ||
| 1. **Match GPU to suite** | ||
| Using `find_matching_gpu_arch()` (from `github_actions_utils`): | ||
|
|
||
| - Prefer **exact** match in the discovered set (e.g. `gfx1151`). | ||
| - Else try **wildcard** patterns from most to least specific (e.g. for `gfx1151`: `gfx115X`, then `gfx11X`). | ||
| - If a match is found, add `-L ex_gpu_{matching_arch}`; otherwise add `-LE ex_gpu`. | ||
|
|
||
| 6. **Build ctest command** | ||
|
|
||
| - `ctest -L <category>` (and optionally `-L ex_gpu_<arch>` or `-LE ex_gpu`). | ||
| - Common options: `--output-on-failure`, `--parallel <N>`, `--test-dir`, `-V`, `--tests-information <SHARD_INDEX>,<TOTAL_SHARDS>`. | ||
| - Parallelism: default 8; can be adjusted according to `AMDGPU_FAMILIES` | ||
|
|
||
| 7. **Run ctest** | ||
| Execute the command in `THEROCK_DIR` with the environment that includes `ROCM_PATH`, `GTEST_SHARD_INDEX`, and `GTEST_TOTAL_SHARDS`. | ||
|
|
||
| ______________________________________________________________________ | ||
|
|
||
| ## CI Integration | ||
|
|
||
| - **Workflow:** `.github/workflows/test_component.yml` runs the test step with env vars such as `TEST_COMPONENT`, `TEST_TYPE`, `AMDGPU_FAMILIES`, `SHARD_INDEX`, `TOTAL_SHARDS`. | ||
| - **Test script:** For components that use this mechanism, the test script is set to `python .../test_runner.py` in `build_tools/github_actions/fetch_test_configurations.py` (e.g. MIOpen). | ||
| - **Sharding:** The workflow matrix uses `shard_arr` from the same config; the script passes sharding to ctest via `--tests-information` and to GTest via `GTEST_SHARD_INDEX` / `GTEST_TOTAL_SHARDS`. | ||
|
|
||
| ______________________________________________________________________ | ||
|
|
||
| ## Adding a New Component to This Runner | ||
|
|
||
| 1. **In the component (e.g. CMake/CTest):** | ||
|
|
||
| - Name GPU-specific tests `{target}_{category}_{gpu_arch}_suite`. | ||
| - Assign labels `ex_gpu_{gpu_arch}` and the category label (`quick` / `standard` or equivalent). | ||
|
|
||
| 2. **In TheRock:** | ||
|
|
||
| - In `test_filters.py`, add the job name → directory mapping in `COMPONENT_DIR_MAPPING`. | ||
| - In `fetch_test_configurations.py`, set the component’s `test_script` to `python .../test_filters.py` and set `job_name` (and shards, timeout, etc.) as needed. | ||
|
|
||
| After that, the generic flow (discovery → match GPU → run ctest with the right labels) applies without further changes to `test_filters.py` for that component. | ||
|
dileepr1 marked this conversation as resolved.
Resolved
dileepr1 marked this conversation as resolved.
Resolved
dileepr1 marked this conversation as resolved.
Resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,253 @@ | ||
| #!/usr/bin/env python3 | ||
| """ | ||
| This is a generic test runner that can test multiple components. | ||
| This works on components in rocm-libraries/rocm-systems which use test_categories.yml for test categorization. | ||
|
|
||
| Environment variables used: | ||
| TEST_COMPONENT: Job name of the component to test (e.g., "miopen", "rocrand", "hiprand") | ||
| This is automatically set by the GitHub Actions workflow from the job_name field. | ||
| The script maps these job names to actual test directory names (e.g., "miopen" -> "MIOpen") | ||
| Defaults to "miopen" if not set. | ||
| TEST_TYPE: "smoke" runs tests with "quick" category, otherwise runs "standard" category | ||
| AMDGPU_FAMILIES: Parsed to extract GPU architecture (e.g., "gfx1151") | ||
|
|
||
| The script discovers GPU-specific labels via ctest --print-labels and runs the appropriate tests for the current GPU architecture. | ||
| """ | ||
|
|
||
| import sys | ||
| import subprocess | ||
| import re | ||
| import os | ||
|
|
||
| import logging | ||
| import shlex | ||
| from pathlib import Path | ||
|
|
||
| sys.path.insert(0, os.fspath(Path(__file__).resolve().parent.parent)) | ||
| from github_actions_utils import find_matching_gpu_arch | ||
|
|
||
| THEROCK_BIN_DIR = os.getenv("THEROCK_BIN_DIR") | ||
| SCRIPT_DIR = Path(__file__).resolve().parent | ||
| THEROCK_DIR = SCRIPT_DIR.parent.parent.parent | ||
| TEST_TYPE = os.getenv("TEST_TYPE", "smoke") | ||
| AMDGPU_FAMILIES = os.getenv("AMDGPU_FAMILIES") | ||
|
|
||
| # Map job names to actual test directory names | ||
| # The job names come from TEST_COMPONENT env var (set by GitHub Actions workflow) | ||
| # and need to be mapped to the actual directory names in THEROCK_BIN_DIR | ||
| COMPONENT_DIR_MAPPING = { | ||
| "miopen": "MIOpen", | ||
| "rocrand": "rocRAND", | ||
| "hiprand": "hipRAND", | ||
| "rocthrust": "rocthrust", | ||
| "rocprim": "rocprim", | ||
| "rocwmma": "rocwmma", | ||
| "hipcub": "hipcub", | ||
| "hipdnn": "hipdnn", | ||
| "hipdnn-samples": "hipdnn_samples", | ||
| "miopen_plugin": "miopen_legacy_plugin", | ||
| # Add more mappings as needed | ||
| } | ||
|
|
||
| # Get the test component from environment (required - no default) | ||
| test_component_job_name = os.getenv("TEST_COMPONENT") | ||
| if not test_component_job_name: | ||
| print( | ||
| "ERROR: TEST_COMPONENT environment variable is required but not set.", | ||
| file=sys.stderr, | ||
| ) | ||
| sys.exit(1) | ||
|
|
||
| TEST_COMPONENT = COMPONENT_DIR_MAPPING.get( | ||
| test_component_job_name, test_component_job_name | ||
| ) | ||
|
|
||
| # GTest sharding | ||
| SHARD_INDEX = os.getenv("SHARD_INDEX", 1) | ||
| TOTAL_SHARDS = os.getenv("TOTAL_SHARDS", 1) | ||
|
|
||
| # CTest parallel jobs (use fewer in less capable platforms) | ||
| ctest_parallel_count = 8 | ||
| if AMDGPU_FAMILIES and "gfx1152" in AMDGPU_FAMILIES: | ||
| ctest_parallel_count = 4 | ||
| elif AMDGPU_FAMILIES and "gfx1153" in AMDGPU_FAMILIES: | ||
| ctest_parallel_count = 4 | ||
|
|
||
| # CTest per-test timeout (default 2 hours, in seconds) | ||
| # There should be a timeout set from component level, but this can be used as an override | ||
| ctest_timeout_seconds = 7200 | ||
|
|
||
| environ_vars = os.environ.copy() | ||
| # Set the GTEST env vars for Gtest based tests | ||
| # Set ROCM_PATH for tests that rely on it | ||
| environ_vars["GTEST_SHARD_INDEX"] = str(int(SHARD_INDEX) - 1) | ||
| environ_vars["GTEST_TOTAL_SHARDS"] = str(TOTAL_SHARDS) | ||
| ROCM_PATH = Path(THEROCK_BIN_DIR).resolve().parent | ||
| environ_vars["ROCM_PATH"] = str(ROCM_PATH) | ||
|
|
||
| logging.basicConfig(level=logging.INFO) | ||
| ############################################## | ||
|
|
||
|
|
||
| def get_available_gpu_suite_tests(): | ||
| """ | ||
| Get all available GPU architecture labels from ctest --print-labels. | ||
|
|
||
| Parses labels of the form ex_gpu_{gpu_arch} (e.g. ex_gpu_gfx110X, ex_gpu_gfx950). | ||
| Returns a set of gpu_arch strings (e.g., 'gfx110X', 'gfx115X', 'gfx950'). | ||
| """ | ||
| test_dir = Path(THEROCK_BIN_DIR) / TEST_COMPONENT | ||
| if not test_dir.exists() or not test_dir.is_dir(): | ||
| print(f"Error: Test directory does not exist: {test_dir}", file=sys.stderr) | ||
| sys.exit(1) | ||
|
|
||
| try: | ||
| # Ensure the component has at least one test | ||
| list_result = subprocess.run( | ||
| ["ctest", "-N", "--test-dir", str(test_dir)], | ||
| capture_output=True, | ||
| text=True, | ||
| check=True, | ||
| ) | ||
| total_tests = sum( | ||
| 1 | ||
| for line in list_result.stdout.splitlines() | ||
| if re.search(r"Test\s+#\d+:", line) | ||
| ) | ||
| if total_tests == 0: | ||
| print( | ||
| f"Error: No tests found in {test_dir}. Cannot run test suite.", | ||
| file=sys.stderr, | ||
| ) | ||
| sys.exit(1) | ||
|
|
||
| result = subprocess.run( | ||
| ["ctest", "--print-labels", "--test-dir", str(test_dir)], | ||
| capture_output=True, | ||
| text=True, | ||
| check=True, | ||
| ) | ||
|
|
||
| gpu_archs = set() | ||
| prefix = "ex_gpu_" | ||
| for line in result.stdout.splitlines(): | ||
| label = line.strip() | ||
| if label.startswith(prefix): | ||
| gpu_arch = label[len(prefix) :] | ||
| if gpu_arch.startswith("gfx"): | ||
| gpu_archs.add(gpu_arch) | ||
|
|
||
| return gpu_archs | ||
| except subprocess.CalledProcessError as e: | ||
| print(f"Error running ctest --print-labels: {e}", file=sys.stderr) | ||
| sys.exit(1) | ||
| except FileNotFoundError: | ||
| print( | ||
| "Error: ctest not found. Make sure CMake/CTest is installed.", | ||
| file=sys.stderr, | ||
| ) | ||
| sys.exit(1) | ||
|
|
||
|
|
||
| def build_ctest_command(category, gpu_arch, available_gpu_archs): | ||
| """ | ||
| Build the appropriate ctest command based on the category and GPU architecture. | ||
|
|
||
| Returns a list of command arguments suitable for subprocess.run() | ||
| """ | ||
| cmd = ["ctest", "-L", category] | ||
|
|
||
| # Add common ctest parameters | ||
| cmd.extend( | ||
| [ | ||
| "--output-on-failure", | ||
| "--parallel", | ||
| f"{ctest_parallel_count}", | ||
| "--timeout", | ||
| str(ctest_timeout_seconds), | ||
| "--test-dir", | ||
| f"{THEROCK_BIN_DIR}/{TEST_COMPONENT}", | ||
| "-V", # Always run in verbose mode | ||
| # Shards the tests by running a specific set of tests based on starting test (shard_index) and stride (total_shards) | ||
| "--tests-information", | ||
| f"{SHARD_INDEX},,{TOTAL_SHARDS}", | ||
|
dileepr1 marked this conversation as resolved.
Resolved
|
||
| ] | ||
| ) | ||
|
|
||
| if gpu_arch.lower() in ["generic", "none", ""]: | ||
| # For generic/unspecified GPU, exclude all GPU-specific suite tests | ||
| cmd.extend(["-LE", "ex_gpu"]) | ||
| return cmd | ||
|
|
||
| # Find the appropriate GPU suite | ||
| matching_arch = find_matching_gpu_arch(gpu_arch, available_gpu_archs) | ||
|
|
||
| if matching_arch: | ||
| # Run the specific GPU suite using the ex_gpu label | ||
| gpu_label = f"ex_gpu_{matching_arch}" | ||
| cmd.extend(["-L", gpu_label]) | ||
| print(f"# Using GPU suite label: {gpu_label}") | ||
| else: | ||
| # No specific GPU suite found, run standard tests excluding all GPU-specific ones | ||
| cmd.extend(["-LE", "ex_gpu"]) | ||
| print(f"# No GPU suite found for {gpu_arch}, excluding all ex_gpu tests") | ||
|
|
||
| return cmd | ||
|
|
||
|
|
||
| def main(): | ||
| # Use only two categories for now - quick and standard - depending on TEST_TYPE. | ||
| if TEST_TYPE and TEST_TYPE.lower() == "smoke": | ||
| category = "quick" | ||
| else: | ||
| category = "standard" | ||
|
|
||
| # Use AMDGPU_FAMILIES from environment variable, extract gfx<xxx> part | ||
| gpu_arch = "" | ||
| if AMDGPU_FAMILIES: | ||
| # Extract gfx<xxx> pattern from AMDGPU_FAMILIES string | ||
| # Pattern matches: gfx followed by alphanumeric characters (e.g., gfx1151, gfx950, gfx11X) | ||
| match = re.search(r"gfx[0-9a-zA-Z]+", AMDGPU_FAMILIES) | ||
| if match: | ||
| gpu_arch = match.group(0) | ||
| else: | ||
| print( | ||
| f"# Warning: Could not extract GPU architecture from AMDGPU_FAMILIES='{AMDGPU_FAMILIES}', using default '{gpu_arch}'" | ||
| ) | ||
|
|
||
| print( | ||
| f"# TEST_COMPONENT: {test_component_job_name} -> Test Directory: {TEST_COMPONENT}" | ||
| ) | ||
| print(f"# TEST_TYPE: {TEST_TYPE} -> Category: {category}") | ||
| print(f"# AMDGPU_FAMILIES: {AMDGPU_FAMILIES} -> GPU Architecture: {gpu_arch}") | ||
| print() | ||
|
|
||
| # Get available GPU suite tests from ctest | ||
| print("# Discovering available GPU suite tests...") | ||
| available_gpu_archs = get_available_gpu_suite_tests() | ||
|
|
||
| if available_gpu_archs: | ||
| print(f"# Found {len(available_gpu_archs)} GPU suite test(s)") | ||
| print(f"# Available GPU architectures: {sorted(available_gpu_archs)}") | ||
| else: | ||
| print("# Warning: No GPU specific test suites available") | ||
| print() | ||
|
|
||
| # Build the ctest command | ||
| cmd = build_ctest_command(category, gpu_arch, available_gpu_archs) | ||
|
|
||
| print(f"# Running: {' '.join(cmd)}") | ||
| print() | ||
|
|
||
| # Execute the command | ||
| try: | ||
| logging.info(f"++ Exec [{THEROCK_DIR}]$ {shlex.join(cmd)}") | ||
| result = subprocess.run(cmd, cwd=THEROCK_DIR, env=environ_vars, check=False) | ||
| return result.returncode | ||
| except Exception as e: | ||
| print(f"Error running ctest: {e}", file=sys.stderr) | ||
| return 1 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| sys.exit(main()) | ||
Uh oh!
There was an error while loading. Please reload this page.