Files
TheRock/rocm-libraries/projects/hipblaslt/tensilelite/tasks.py
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

437 lines
16 KiB
Python

# Copyright (C) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
from invoke.exceptions import Exit
from invoke.tasks import task
import os
import pathlib
import shlex
import shutil
import subprocess
import sys
_TASKS_DIR = pathlib.Path(__file__).parent.resolve()
# Ensure the Tensile package (shipped next to this file) is importable when
# invoke runs from the tensilelite root, regardless of cwd/sys.path state.
if str(_TASKS_DIR) not in sys.path:
sys.path.insert(0, str(_TASKS_DIR))
from Tensile.RocisaStatus import _rocisa_install_status
def _cmake_bool(value):
return "ON" if value else "OFF"
def _detect_rocm():
"""Detect ROCm installation path.
Priority: ROCM_PATH env > rocm-sdk path --root > /opt/rocm.
"""
env_path = os.environ.get("ROCM_PATH")
if env_path:
return env_path
if shutil.which("rocm-sdk"):
try:
result = subprocess.check_output(
["rocm-sdk", "path", "--root"], stderr=subprocess.DEVNULL
).decode().strip()
if result:
return result
except subprocess.CalledProcessError:
pass
return "/opt/rocm"
def detect_gpu_arch():
try:
result = subprocess.run(["rocm_agent_enumerator", "-v"], capture_output=True, text=True, timeout=5, check=True)
if result.returncode == 0:
target = next((line.strip() for line in result.stdout.splitlines() if line.startswith("gfx") and line.strip() != "gfx000"), None)
if target:
return target
except FileNotFoundError:
print("Error: 'rocm_agent_enumerator' command not found. Please install ROCm.", file=sys.stderr)
except subprocess.TimeoutExpired:
print("Error: GPU detection timed out. Hardware might be unresponsive.", file=sys.stderr)
except Exception as e:
print(f"An unexpected error occurred during GPU detection: {e}", file=sys.stderr)
print(f"Failed to detect a valid GPU architecture (gfx target not found).", file=sys.stderr)
return None
@task
def get_gpu_arch(c):
print(detect_gpu_arch())
@task(
help={
"rocisa_dir": "Path to the rocisa source directory (default: rocisa/ next to this file).",
"stinkytofu_prefix": "Install prefix for the stinkytofu build (default: build_tmp/stinkytofu-install).",
}
)
def rocisa(c, rocisa_dir=None, stinkytofu_prefix=None):
"""Install rocisa as an editable pip package.
Not required before `invoke build-client` — the client build enables
HIPBLASLT_BUNDLE_PYTHON_DEPS automatically when rocisa is absent. Run
this task to make rocisa importable system-wide (outside the build
directory), or after changes to rocisa's pyproject.toml or CMakeLists.txt.
Builds and installs stinkytofu locally first so rocisa uses
find_package(stinkytofu) — mirroring how TheRock wires the two together.
"""
_pip_install_rocisa(c, rocisa_dir, stinkytofu_prefix)
def _load_stinkytofu_tasks():
"""Import shared/stinkytofu/tasks.py without triggering its venv guard.
The venv check was moved into build() so this import is side-effect-free.
"""
import importlib.util
spec = importlib.util.spec_from_file_location(
"stinkytofu_tasks",
_TASKS_DIR.parent.parent.parent / "shared" / "stinkytofu" / "tasks.py",
)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod
def _build_and_install_stinkytofu(c, install_prefix: pathlib.Path, rocm: str) -> None:
"""Build stinkytofu and install it to install_prefix so rocisa can find_package it.
Build flags come from stinkytofu_tasks.cmake_build_args() — the single source
of truth — so a new required cmake option only needs to be added there.
Compiler selection mirrors shared/stinkytofu/tasks.py `invoke build`.
cmake is incremental, so repeat calls are a fast no-op when nothing changed.
"""
stinkytofu_src = _TASKS_DIR.parent.parent.parent / "shared" / "stinkytofu"
build_dir = install_prefix.parent / "stinkytofu-build"
build_dir.mkdir(parents=True, exist_ok=True)
rocm_s = rocm if isinstance(rocm, str) else str(rocm)
_cxx = shutil.which("amdclang++") or f"{rocm_s}/bin/amdclang++"
_cc = shutil.which("amdclang") or f"{rocm_s}/bin/amdclang"
st = _load_stinkytofu_tasks()
cmake_cmd = [
"cmake",
"-S", str(stinkytofu_src),
"-B", str(build_dir),
"-DCMAKE_BUILD_TYPE=Release",
f"-DROCM_PATH={rocm_s}",
f"-DCMAKE_CXX_COMPILER={_cxx}",
f"-DCMAKE_C_COMPILER={_cc}",
# amd_comgr lives in the SDK venv (off the loader path), so bake its dir
# into the installed libstinkytofu RPATH for this dev/standalone build.
"-DSTINKYTOFU_INSTALL_RPATH_USE_LINK_PATH=ON",
# tests/python OFF for the rocisa integration build; examples ON (default).
*st.cmake_build_args(install_prefix=install_prefix, tests=False, python=False),
]
if shutil.which("ninja"):
cmake_cmd.append("-G Ninja")
if shutil.which("ccache"):
cmake_cmd += [
"-DCMAKE_C_COMPILER_LAUNCHER=ccache",
"-DCMAKE_CXX_COMPILER_LAUNCHER=ccache",
]
c.run(shlex.join(cmake_cmd))
c.run(shlex.join(["cmake", "--build", str(build_dir), "--parallel"]))
c.run(shlex.join(["cmake", "--install", str(build_dir)]))
def _pip_install_rocisa(c, rocisa_dir=None, stinkytofu_prefix=None):
"""Editable-install rocisa via scikit-build-core.
Factored out of the `rocisa` task so `build_client` can reuse it to keep
the editable install fresh.
Builds stinkytofu and installs it to stinkytofu_prefix (default:
build_tmp/stinkytofu-install next to this file) so rocisa's CMake finds it
via find_package(stinkytofu) — the same path TheRock uses. This exercises
the installed package layout (stinkytofuConfig.cmake, exported targets) so
breakage is caught early in the dev/CI workflow.
"""
src = pathlib.Path(rocisa_dir).resolve() if rocisa_dir else _TASKS_DIR / "rocisa"
rocm = _detect_rocm()
prefix = (
pathlib.Path(stinkytofu_prefix).resolve()
if stinkytofu_prefix
else _TASKS_DIR / "build_tmp" / "stinkytofu-install"
)
_build_and_install_stinkytofu(c, prefix, rocm)
cmake_args = (
f"-DROCM_PATH={rocm}"
f" -DROCISA_INCLUDE_BUILD_INFO=ON"
)
if shutil.which("ccache"):
cmake_args += " -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache"
env = dict(os.environ, CMAKE_ARGS=cmake_args)
# Append (don't clobber) the stinkytofu install prefix so find_package
# resolves it, while preserving the CMAKE_PREFIX_PATH that scikit-build-core
# injects for nanobind. find_package searches the env var and the cache var.
_existing_prefix = env.get("CMAKE_PREFIX_PATH")
env["CMAKE_PREFIX_PATH"] = (
f"{prefix}{os.pathsep}{_existing_prefix}" if _existing_prefix else str(prefix)
)
env.setdefault("CMAKE_BUILD_PARALLEL_LEVEL", str(os.cpu_count() or 1))
c.run(f"pip install --no-build-isolation -e {shlex.quote(str(src))}", env=env)
def _maybe_rebuild_rocisa(c, rocisa_dir=None):
"""Refresh the editable rocisa so `import rocisa` picks up C++ edits.
Only acts when rocisa is installed editable (pip install -e). When
absent, rocisa is built by CMake via HIPBLASLT_BUNDLE_PYTHON_DEPS.
When non-editable (e.g. tox), does nothing.
Degrades to a warning — never a hard failure — when the build backend
(scikit-build-core / nanobind) is unavailable.
"""
import importlib.util
if _rocisa_install_status() != "editable":
return
missing = [m for m in ("scikit_build_core", "nanobind") if importlib.util.find_spec(m) is None]
if missing:
print(
"warning: editable rocisa is installed but its build backend is "
f"unavailable ({', '.join(missing)}); skipping rocisa rebuild. If you "
"changed rocisa C++ sources, run 'invoke rocisa' where the build deps exist.",
file=sys.stderr,
)
return
try:
print("Rebuilding editable rocisa to pick up any C++ source changes...")
_pip_install_rocisa(c, rocisa_dir)
except Exception as e:
print(
f"warning: rocisa rebuild failed ({e}); continuing with the client build. "
"Run 'invoke rocisa' manually to refresh the bindings.",
file=sys.stderr,
)
@task(
help={
"clean": "Remove the client build directory before building.",
"configure": "Run CMake configuration for the client.",
"build": "Build the tensilelite-client executable.",
"build_dir": "Path to client build dir.",
"build_type": "CMake build type (e.g. Release, Debug).",
"gpu_targets": "Comma-separated list of GPU targets (e.g. gfx90a,gfx1101).",
"rocm_path": "Path to a ROCm install whose amdclang/amdclang++ should be used.",
"export_compile_commands": "Enable CMAKE_EXPORT_COMPILE_COMMANDS.",
"bundle_python_deps": "Force HIPBLASLT_BUNDLE_PYTHON_DEPS on or off; auto-enabled when rocisa is not pip-installed.",
"enable_rocprof": "Build tensilelite-client with rocprof.",
"cxx_flags_release": "Override CMAKE_CXX_FLAGS_RELEASE (for example, -O3 to keep asserts enabled in Release).",
"rebuild_rocisa": "Re-install the editable rocisa (if present) so rocisa C++ edits are picked up; pass --no-rebuild-rocisa to skip.",
"enable_asan": "Enable AddressSanitizer.",
"enable_tsan": "Enable ThreadSanitizer.",
}
)
def build_client(
c,
clean=False,
configure=True,
build=True,
build_dir="build_tmp",
build_type="Release",
gpu_targets=None,
rocm_path=None,
export_compile_commands=False,
bundle_python_deps=False,
enable_rocprof=False,
cxx_flags_release=None,
rebuild_rocisa=True,
enable_asan=False,
enable_tsan=False,
):
"""Build the tensilelite-client C++ executable.
To run Tensile after building, use: Tensile/bin/Tensile <args>
When rocisa is not pip-installed, HIPBLASLT_BUNDLE_PYTHON_DEPS is
enabled automatically so CMake builds it in the client build
directory. When rocisa is installed editable, the bindings are
refreshed to pick up C++ edits (disable with --no-rebuild-rocisa).
"""
if enable_asan and enable_tsan:
raise Exit("Error: ASAN and TSAN cannot be enabled simultaneously", code=1)
if gpu_targets is None:
gpu_targets = detect_gpu_arch()
if not gpu_targets:
raise Exit("Error: No GPU detected and no gpu_targets provided", code=1)
print(f"warning: No GPU targets specified. Detected and using: {gpu_targets}")
if rocm_path:
cmake_c_compiler = os.path.join(rocm_path, "bin", "amdclang")
cmake_cxx_compiler = os.path.join(rocm_path, "bin", "amdclang++")
for compiler in (cmake_c_compiler, cmake_cxx_compiler):
try:
subprocess.run([compiler, "--version"], capture_output=True, timeout=5, check=True)
except FileNotFoundError:
raise Exit(f"Error: compiler not found at {compiler}", code=1)
except subprocess.SubprocessError as e:
raise Exit(f"Error: compiler check failed for {compiler}: {e}", code=1)
if rebuild_rocisa:
_maybe_rebuild_rocisa(c)
if not bundle_python_deps and _rocisa_install_status() == "absent":
print("rocisa is not pip-installed; enabling HIPBLASLT_BUNDLE_PYTHON_DEPS=ON "
"so CMake builds it in the client build directory.")
bundle_python_deps = True
if clean and os.path.exists(build_dir):
c.run(f"rm -rf {shlex.quote(build_dir)}")
if configure:
os.makedirs(build_dir, exist_ok=True)
cmake_cmd = [
"cmake",
"--preset",
"tensilelite",
"-S", str(_TASKS_DIR.parent),
"-B", build_dir,
f"-DCMAKE_BUILD_TYPE={build_type}",
f"-DGPU_TARGETS={gpu_targets}",
f"-DTENSILELITE_CLIENT_ENABLE_ROCPROFSDK={_cmake_bool(enable_rocprof)}",
]
if cxx_flags_release is not None:
cmake_cmd.append(f"-DCMAKE_CXX_FLAGS_RELEASE={cxx_flags_release}")
if rocm_path:
cmake_cmd.append(f"-DCMAKE_C_COMPILER={cmake_c_compiler}")
cmake_cmd.append(f"-DCMAKE_CXX_COMPILER={cmake_cxx_compiler}")
if shutil.which("ccache"):
cmake_cmd.append("-DCMAKE_C_COMPILER_LAUNCHER=ccache")
cmake_cmd.append("-DCMAKE_CXX_COMPILER_LAUNCHER=ccache")
if export_compile_commands:
cmake_cmd.append("-DCMAKE_EXPORT_COMPILE_COMMANDS=ON")
if enable_asan:
cmake_cmd.append("-DTENSILELITE_ENABLE_HOST_ASAN=ON")
if enable_tsan:
cmake_cmd.append("-DTENSILELITE_ENABLE_HOST_TSAN=ON")
cmake_cmd.append(f"-DHIPBLASLT_BUNDLE_PYTHON_DEPS={_cmake_bool(bundle_python_deps)}")
c.run(shlex.join(cmake_cmd))
if build:
c.run(shlex.join(["cmake", "--build", build_dir, "--parallel"]))
@task
def precommit_install(c):
"""Install the hipblaslt/TensileLite git pre-commit hook (run once after `uv sync`).
Clears core.hooksPath only when it points at the default hooks dir; bails if
it points somewhere custom.
"""
root = subprocess.check_output(
["git", "rev-parse", "--show-toplevel"], text=True
).strip()
common = subprocess.check_output(
["git", "rev-parse", "--git-common-dir"], text=True, cwd=root
).strip()
common_path = pathlib.Path(common)
if not common_path.is_absolute():
common_path = (pathlib.Path(root) / common_path).resolve()
default_hooks = str(common_path / "hooks")
hooks_path = subprocess.run(
["git", "config", "--get", "core.hooksPath"],
cwd=root, capture_output=True, text=True,
).stdout.strip()
if hooks_path:
if os.path.realpath(hooks_path) == os.path.realpath(default_hooks):
print(f"core.hooksPath is set to the default ({hooks_path}); clearing it "
"(redundant; git-lfs hooks are unaffected).")
with c.cd(root):
c.run("git config --unset-all core.hooksPath")
else:
raise Exit(
f"Refusing to install: core.hooksPath is set to a custom path "
f"({hooks_path}), not the default ({default_hooks}). Resolve that "
"first.",
code=1,
)
config = "projects/hipblaslt/.pre-commit-config.yaml"
with c.cd(root):
c.run(f"pre-commit install --config {shlex.quote(config)}", pty=True)
@task(
help={
"build_dir": "Path to coverage build dir.",
"gpu_targets": "GPU targets (e.g. gfx90a,gfx942).",
"rocm_path": "Path to ROCm installation.",
"clean": "Remove build directory before building.",
}
)
def build_coverage(
c,
build_dir="build_cov",
gpu_targets=None,
rocm_path=None,
clean=False,
):
"""Build TensileLite with code coverage instrumentation.
Builds rocisa, tensilelite-host, and client with LLVM coverage flags.
Run tests with tox -e coverage-cpp to generate coverage reports.
"""
if gpu_targets is None:
gpu_targets = detect_gpu_arch()
if not gpu_targets:
print("Error: No GPU detected and no gpu_targets provided.")
return
if clean and os.path.exists(build_dir):
c.run(f"rm -rf {shlex.quote(build_dir)}")
os.makedirs(build_dir, exist_ok=True)
rocm = rocm_path or _detect_rocm()
cmake_c = os.path.join(rocm, "bin", "amdclang")
cmake_cxx = os.path.join(rocm, "bin", "amdclang++")
cmake_cmd = [
"cmake",
"--preset", "tensilelite",
"-S", "../",
"-B", build_dir,
"-DCMAKE_BUILD_TYPE=Debug",
f"-DGPU_TARGETS={gpu_targets}",
f"-DCMAKE_C_COMPILER={cmake_c}",
f"-DCMAKE_CXX_COMPILER={cmake_cxx}",
"-DTENSILELITE_ENABLE_COVERAGE=ON",
"-DROCISA_ENABLE_COVERAGE=ON",
"-DTENSILELITE_BUILD_TESTING=ON",
"-DHIPBLASLT_ENABLE_YAML=OFF", # Use msgpack, LLVM headers may not be available
]
if shutil.which("ccache"):
cmake_cmd.extend([
"-DCMAKE_C_COMPILER_LAUNCHER=ccache",
"-DCMAKE_CXX_COMPILER_LAUNCHER=ccache",
])
c.run(shlex.join(cmake_cmd))
c.run(shlex.join(["cmake", "--build", build_dir, "--parallel"]))