From cabcda75852b625b0850551c92df49dec9b873fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kami=C5=84ski?= Date: Fri, 11 Jul 2025 08:22:12 +0000 Subject: [PATCH] [CI][Benchmarks] Refactor Compute Runtime builds - don't rebuild any of the Compute Runtime components if none of them has changed - rebuild Compute Runtime and its components if needed - refactor components directories' names to be more consistent - overall refactor the compute_runtime module - make Suite.setup() an abstract method --- devops/scripts/benchmarks/benches/base.py | 3 +- devops/scripts/benchmarks/benches/benchdnn.py | 2 +- devops/scripts/benchmarks/benches/compute.py | 2 +- devops/scripts/benchmarks/benches/gromacs.py | 2 +- devops/scripts/benchmarks/benches/llamacpp.py | 2 +- .../scripts/benchmarks/benches/syclbench.py | 2 +- devops/scripts/benchmarks/benches/test.py | 2 +- devops/scripts/benchmarks/benches/umf.py | 4 +- devops/scripts/benchmarks/benches/velocity.py | 2 +- devops/scripts/benchmarks/git_project.py | 166 +++++++++++ .../benchmarks/utils/compute_runtime.py | 280 +++++++++--------- 11 files changed, 310 insertions(+), 157 deletions(-) create mode 100644 devops/scripts/benchmarks/git_project.py diff --git a/devops/scripts/benchmarks/benches/base.py b/devops/scripts/benchmarks/benches/base.py index ea12ae04562e6..24087793d8a84 100644 --- a/devops/scripts/benchmarks/benches/base.py +++ b/devops/scripts/benchmarks/benches/base.py @@ -199,7 +199,8 @@ def benchmarks(self) -> list[Benchmark]: def name(self) -> str: pass - def setup(self): + @abstractmethod + def setup(self) -> None: return def additional_metadata(self) -> dict[str, BenchmarkMetadata]: diff --git a/devops/scripts/benchmarks/benches/benchdnn.py b/devops/scripts/benchmarks/benches/benchdnn.py index 0a2f0d67f6ba2..27202f20a0a90 100644 --- a/devops/scripts/benchmarks/benches/benchdnn.py +++ b/devops/scripts/benchmarks/benches/benchdnn.py @@ -58,7 +58,7 @@ def benchmarks(self) -> list: ) return benchmarks - def setup(self): + def setup(self) -> None: if options.sycl is None: return diff --git a/devops/scripts/benchmarks/benches/compute.py b/devops/scripts/benchmarks/benches/compute.py index 8ccfd929c9de9..0249b009cdbbb 100644 --- a/devops/scripts/benchmarks/benches/compute.py +++ b/devops/scripts/benchmarks/benches/compute.py @@ -52,7 +52,7 @@ def git_url(self) -> str: def git_hash(self) -> str: return "83b9ae3ebb3563552409f3a317cdc1cf3d3ca6bd" - def setup(self): + def setup(self) -> None: if options.sycl is None: return diff --git a/devops/scripts/benchmarks/benches/gromacs.py b/devops/scripts/benchmarks/benches/gromacs.py index 786699498d844..9981b41a22a92 100644 --- a/devops/scripts/benchmarks/benches/gromacs.py +++ b/devops/scripts/benchmarks/benches/gromacs.py @@ -50,7 +50,7 @@ def benchmarks(self) -> list[Benchmark]: # GromacsBenchmark(self, "0192", "rf", "eager"), ] - def setup(self): + def setup(self) -> None: self.gromacs_src = git_clone( self.directory, "gromacs-repo", diff --git a/devops/scripts/benchmarks/benches/llamacpp.py b/devops/scripts/benchmarks/benches/llamacpp.py index be2fe74c516a5..429e8eb787e4c 100644 --- a/devops/scripts/benchmarks/benches/llamacpp.py +++ b/devops/scripts/benchmarks/benches/llamacpp.py @@ -28,7 +28,7 @@ def git_url(self) -> str: def git_hash(self) -> str: return "916c83bfe7f8b08ada609c3b8e583cf5301e594b" - def setup(self): + def setup(self) -> None: if options.sycl is None: return diff --git a/devops/scripts/benchmarks/benches/syclbench.py b/devops/scripts/benchmarks/benches/syclbench.py index e851d17e0d9be..89ad0bbfea839 100644 --- a/devops/scripts/benchmarks/benches/syclbench.py +++ b/devops/scripts/benchmarks/benches/syclbench.py @@ -26,7 +26,7 @@ def git_url(self) -> str: def git_hash(self) -> str: return "31fc70be6266193c4ba60eb1fe3ce26edee4ca5b" - def setup(self): + def setup(self) -> None: if options.sycl is None: return diff --git a/devops/scripts/benchmarks/benches/test.py b/devops/scripts/benchmarks/benches/test.py index bfc1cfcd55323..6c7a7626ed815 100644 --- a/devops/scripts/benchmarks/benches/test.py +++ b/devops/scripts/benchmarks/benches/test.py @@ -16,7 +16,7 @@ class TestSuite(Suite): def __init__(self): return - def setup(self): + def setup(self) -> None: return def name(self) -> str: diff --git a/devops/scripts/benchmarks/benches/umf.py b/devops/scripts/benchmarks/benches/umf.py index 0010c5f9faac6..f575f23b8595a 100644 --- a/devops/scripts/benchmarks/benches/umf.py +++ b/devops/scripts/benchmarks/benches/umf.py @@ -26,9 +26,9 @@ def __init__(self, directory): def name(self) -> str: return "UMF" - def setup(self): + def setup(self) -> None: if not isUMFAvailable(): - return [] + return self.built = True def benchmarks(self) -> list[Benchmark]: diff --git a/devops/scripts/benchmarks/benches/velocity.py b/devops/scripts/benchmarks/benches/velocity.py index d4ceae393144b..0a8071bd946c5 100644 --- a/devops/scripts/benchmarks/benches/velocity.py +++ b/devops/scripts/benchmarks/benches/velocity.py @@ -29,7 +29,7 @@ def git_url(self) -> str: def git_hash(self) -> str: return "b22215c16f789100449c34bf4eaa3fb178983d69" - def setup(self): + def setup(self) -> None: if options.sycl is None: return diff --git a/devops/scripts/benchmarks/git_project.py b/devops/scripts/benchmarks/git_project.py new file mode 100644 index 0000000000000..c59d84abe4954 --- /dev/null +++ b/devops/scripts/benchmarks/git_project.py @@ -0,0 +1,166 @@ +# Copyright (C) 2025 Intel Corporation +# Part of the Unified-Runtime Project, under the Apache License v2.0 with LLVM Exceptions. +# See LICENSE.TXT +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +from pathlib import Path +import shutil + +from utils.logger import log +from utils.utils import run +from options import options + + +class GitProject: + def __init__( + self, + url: str, + ref: str, + directory: Path, + name: str, + force_rebuild: bool = False, + ) -> None: + self._url = url + self._ref = ref + self._directory = directory + self._name = name + self._force_rebuild = force_rebuild + self._rebuild_needed = self._git_clone() + + @property + def src_dir(self) -> Path: + return self._directory / f"{self._name}-src" + + @property + def build_dir(self) -> Path: + return self._directory / f"{self._name}-build" + + @property + def install_dir(self) -> Path: + return self._directory / f"{self._name}-install" + + def needs_rebuild(self, check_build=False, check_install=False) -> bool: + """Checks if the project needs to be rebuilt. + + Args: + check_build (bool): If True, checks if the build directory exists and has some files. + check_install (bool): If True, checks if the install directory exists and has some files. + + Returns: + bool: True if the project needs to be rebuilt, False otherwise. + """ + log.debug(f"Checking if project {self._name} needs rebuild.") + if self._force_rebuild: + log.debug( + f"Force rebuild is enabled for project {self._name}, rebuild needed." + ) + if Path(self.build_dir).exists(): + shutil.rmtree(self.build_dir) + return True + elif self._rebuild_needed: + return True + if check_build: + if self.build_dir.exists() and any( + path.is_file() for path in self.build_dir.glob("**/*") + ): + log.debug( + f"Build directory {self.build_dir} exists and is not empty, no rebuild needed." + ) + else: + log.debug( + f"Build directory {self.build_dir} does not exist or does not contain any file, rebuild needed." + ) + return True + if check_install: + if self.install_dir.exists() and any( + path.is_file() for path in self.install_dir.glob("**/*") + ): + log.debug( + f"Install directory {self.install_dir} exists and is not empty, no rebuild needed." + ) + else: + log.debug( + f"Install directory {self.install_dir} does not exist or does not contain any file, rebuild needed." + ) + return True + return False + + def configure( + self, + extra_args: list | None = None, + install_prefix=True, + add_sycl: bool = False, + ) -> None: + """Configures the project.""" + cmd = [ + "cmake", + f"-S {self.src_dir}", + f"-B {self.build_dir}", + f"-DCMAKE_BUILD_TYPE=Release", + ] + if install_prefix: + cmd.append(f"-DCMAKE_INSTALL_PREFIX={self.install_dir}") + if extra_args: + cmd.extend(extra_args) + + run(cmd, add_sycl=add_sycl) + + def build( + self, + target: str = "", + add_sycl: bool = False, + ld_library: list = [], + timeout: int | None = None, + ) -> None: + """Builds the project.""" + target_arg = f"--target {target}" if target else "" + run( + f"cmake --build {self.build_dir} {target_arg} -j {options.build_jobs}", + add_sycl=add_sycl, + ld_library=ld_library, + timeout=timeout, + ) + + def install(self) -> None: + """Installs the project.""" + run(f"cmake --install {self.build_dir}") + + def _git_clone(self) -> bool: + """Clone a git repository into a specified directory at a specific commit. + Returns: + bool: True if the repository was cloned or updated, False if it was already up-to-date. + """ + log.debug(f"Cloning {self._url} into {self.src_dir} at commit {self._ref}") + if self.src_dir.exists() and Path(self.src_dir, ".git").exists(): + log.debug( + f"Repository {self._url} already exists at {self.src_dir}, checking for updates." + ) + run("git fetch", cwd=self.src_dir) + target_commit = ( + run(f"git rev-parse {self._ref}", cwd=self.src_dir) + .stdout.decode() + .strip() + ) + current_commit = ( + run("git rev-parse HEAD", cwd=self.src_dir).stdout.decode().strip() + ) + if current_commit != target_commit: + log.debug( + f"Current commit {current_commit} does not match target {target_commit}, checking out {self._ref}." + ) + run("git reset --hard", cwd=self.src_dir) + run(f"git checkout {self._ref}", cwd=self.src_dir) + else: + log.debug( + f"Current commit {current_commit} matches target {target_commit}, no update needed." + ) + return False + elif not self.src_dir.exists(): + run(f"git clone --recursive {self._url} {self.src_dir}") + run(f"git checkout {self._ref}", cwd=self.src_dir) + else: + raise Exception( + f"The directory {self.src_dir} exists but is not a git repository." + ) + log.debug(f"Cloned {self._url} into {self.src_dir} at commit {self._ref}") + return True diff --git a/devops/scripts/benchmarks/utils/compute_runtime.py b/devops/scripts/benchmarks/utils/compute_runtime.py index 24d78e86587dd..c093008c8c712 100644 --- a/devops/scripts/benchmarks/utils/compute_runtime.py +++ b/devops/scripts/benchmarks/utils/compute_runtime.py @@ -9,9 +9,10 @@ from .utils import * from options import options +from git_project import GitProject -def replace_in_file(file_path, search_pattern, replacement): +def replace_in_file(file_path: Path, search_pattern: str, replacement: str) -> None: with open(file_path, "r") as file: content = file.read() @@ -49,116 +50,100 @@ def env_vars(self) -> dict: ), } - def build_gmmlib(self, repo, commit): + def build_gmmlib(self, repo, commit) -> tuple[Path, bool]: log.info("Building GMMLib...") - self.gmmlib_repo = git_clone(options.workdir, "gmmlib-repo", repo, commit) - self.gmmlib_build = os.path.join(options.workdir, "gmmlib-build") - self.gmmlib_install = os.path.join(options.workdir, "gmmlib-install") - configure_command = [ - "cmake", - f"-B {self.gmmlib_build}", - f"-S {self.gmmlib_repo}", - f"-DCMAKE_INSTALL_PREFIX={self.gmmlib_install}", - f"-DCMAKE_BUILD_TYPE=Release", - ] - run(configure_command) - run(f"cmake --build {self.gmmlib_build} -j {options.build_jobs}") - run(f"cmake --install {self.gmmlib_build}") - log.info("GMMLib build complete.") - return self.gmmlib_install - - def build_level_zero(self, repo, commit): + project = GitProject(repo, commit, Path(options.workdir), "gmmlib") + rebuilt = False + if project.needs_rebuild(check_install=True): + project.configure(install_prefix=True) + project.build() + project.install() + rebuilt = True + log.info("GMMLib build complete.") + else: + log.info("GMMLib build skipped, already built.") + return project.install_dir, rebuilt + + def build_level_zero(self, repo, commit) -> tuple[Path, bool]: log.info("Building Level Zero...") - self.level_zero_repo = git_clone( - options.workdir, "level-zero-repo", repo, commit - ) - self.level_zero_build = os.path.join(options.workdir, "level-zero-build") - self.level_zero_install = os.path.join(options.workdir, "level-zero-install") - - cmakelists_path = os.path.join(self.level_zero_repo, "CMakeLists.txt") + project = GitProject(repo, commit, Path(options.workdir), "level-zero") + cmakelists_path = project.src_dir / "CMakeLists.txt" # there's a bug in level-zero CMakeLists.txt that makes it install headers into incorrect location. replace_in_file( cmakelists_path, r"DESTINATION \./include/", "DESTINATION include/" ) - configure_command = [ - "cmake", - f"-B {self.level_zero_build}", - f"-S {self.level_zero_repo}", - f"-DCMAKE_INSTALL_PREFIX={self.level_zero_install}", - f"-DCMAKE_BUILD_TYPE=Release", - ] - run(configure_command) - run(f"cmake --build {self.level_zero_build} -j {options.build_jobs}") - run(f"cmake --install {self.level_zero_build}") - log.info("Level Zero build complete.") - return self.level_zero_install - - def build_igc(self, repo, commit): + rebuilt = False + if project.needs_rebuild(check_install=True): + project.configure(install_prefix=True) + project.build() + project.install() + rebuilt = True + log.info("Level Zero build complete.") + else: + log.info("Level Zero build skipped, already built.") + return project.install_dir, rebuilt + + def build_igc(self, repo, commit) -> tuple[Path, bool]: log.info("Building IGC...") - self.igc_repo = git_clone(options.workdir, "igc", repo, commit) - self.vc_intr = git_clone( - options.workdir, - "vc-intrinsics", - "https://github.com/intel/vc-intrinsics", - "9d255266e1df8f1dc5d11e1fbb03213acfaa4fc7", - ) - self.llvm_project = git_clone( - options.workdir, - "llvm-project", - "https://github.com/llvm/llvm-project", - "llvmorg-15.0.7", - ) - llvm_projects = os.path.join(self.llvm_project, "llvm", "projects") - self.ocl = git_clone( - llvm_projects, - "opencl-clang", - "https://github.com/intel/opencl-clang", - "ocl-open-150", - ) - self.translator = git_clone( - llvm_projects, - "llvm-spirv", - "https://github.com/KhronosGroup/SPIRV-LLVM-Translator", - "llvm_release_150", - ) - self.spirv_tools = git_clone( - options.workdir, - "SPIRV-Tools", - "https://github.com/KhronosGroup/SPIRV-Tools.git", - "f289d047f49fb60488301ec62bafab85573668cc", - ) - self.spirv_headers = git_clone( - options.workdir, - "SPIRV-Headers", - "https://github.com/KhronosGroup/SPIRV-Headers.git", - "0e710677989b4326ac974fd80c5308191ed80965", - ) - - self.igc_build = os.path.join(options.workdir, "igc-build") - self.igc_install = os.path.join(options.workdir, "igc-install") - configure_command = [ - "cmake", - "-DCMAKE_C_FLAGS=-Wno-error", - "-DCMAKE_CXX_FLAGS=-Wno-error", - f"-B {self.igc_build}", - f"-S {self.igc_repo}", - f"-DCMAKE_INSTALL_PREFIX={self.igc_install}", - f"-DCMAKE_BUILD_TYPE=Release", - ] - run(configure_command) - - # set timeout to 2h. IGC takes A LONG time to build if building from scratch. - run( - f"cmake --build {self.igc_build} -j {options.build_jobs}", - timeout=60 * 60 * 2, - ) - # cmake --install doesn't work... - run("make install", cwd=self.igc_build) - log.info("IGC build complete.") - return self.igc_install - - def read_manifest(self, manifest_path): + igc_project = GitProject(repo, commit, Path(options.workdir), "igc") + rebuilt = False + if igc_project.needs_rebuild(check_install=True): + # Clone igc dependencies by creating a GitProject instance for each dependency. + GitProject( + "https://github.com/intel/vc-intrinsics", + "9d255266e1df8f1dc5d11e1fbb03213acfaa4fc7", + Path(options.workdir), + "vc-intrinsics", + ) + llvm_project = GitProject( + "https://github.com/llvm/llvm-project", + "llvmorg-15.0.7", + Path(options.workdir), + "llvm-project", + ) + llvm_projects = llvm_project.src_dir / "llvm" / "projects" + GitProject( + "https://github.com/intel/opencl-clang", + "ocl-open-150", + llvm_projects, + "opencl-clang", + ) + GitProject( + "https://github.com/KhronosGroup/SPIRV-LLVM-Translator", + "llvm_release_150", + llvm_projects, + "llvm-spirv", + ) + GitProject( + "https://github.com/KhronosGroup/SPIRV-Tools.git", + "f289d047f49fb60488301ec62bafab85573668cc", + Path(options.workdir), + "SPIRV-Tools", + ) + GitProject( + "https://github.com/KhronosGroup/SPIRV-Headers.git", + "0e710677989b4326ac974fd80c5308191ed80965", + Path(options.workdir), + "SPIRV-Headers", + ) + + configure_args = [ + "-DCMAKE_C_FLAGS=-Wno-error", + "-DCMAKE_CXX_FLAGS=-Wno-error", + ] + igc_project.configure(extra_args=configure_args, install_prefix=True) + # set timeout to 2h. IGC takes A LONG time to build if building from scratch. + igc_project.build(timeout=60 * 60 * 2) + # cmake --install doesn't work... + run("make install", cwd=igc_project.build_dir) + rebuilt = True + log.info("IGC build complete.") + else: + log.info("IGC build skipped, already built.") + return igc_project.install_dir, rebuilt + + def read_manifest(self, manifest_path: Path) -> dict: with open(manifest_path, "r") as file: manifest = yaml.safe_load(file) return manifest @@ -172,62 +157,63 @@ def get_repo_info(self, manifest, component_name): return None, None def build_compute_runtime(self): - self.compute_runtime_repo = git_clone( - options.workdir, - "compute-runtime-repo", + project = GitProject( "https://github.com/intel/compute-runtime.git", options.compute_runtime_tag, - ) - self.compute_runtime_build = os.path.join( - options.workdir, "compute-runtime-build" + Path(options.workdir), + "compute-runtime", ) - manifest_path = os.path.join( - self.compute_runtime_repo, "manifests", "manifest.yml" - ) + manifest_path = project.src_dir / "manifests" / "manifest.yml" manifest = self.read_manifest(manifest_path) level_zero_repo, level_zero_commit = self.get_repo_info(manifest, "level_zero") - self.level_zero = self.build_level_zero(level_zero_repo, level_zero_commit) + self.level_zero, self.level_zero_rebuilt = self.build_level_zero( + level_zero_repo, level_zero_commit + ) gmmlib_repo, gmmlib_commit = self.get_repo_info(manifest, "gmmlib") - self.gmmlib = self.build_gmmlib(gmmlib_repo, gmmlib_commit) + self.gmmlib, self.gmmlib_rebuilt = self.build_gmmlib(gmmlib_repo, gmmlib_commit) if options.build_igc: igc_repo, igc_commit = self.get_repo_info(manifest, "igc") - self.igc = self.build_igc(igc_repo, igc_commit) - - cmakelists_path = os.path.join( - self.compute_runtime_repo, "level_zero", "cmake", "FindLevelZero.cmake" - ) - # specifying custom L0 is problematic... - replace_in_file( - cmakelists_path, r"(\$\{LEVEL_ZERO_ROOT\}\s*)", r"\1NO_DEFAULT_PATH\n" - ) - - cmakelists_path = os.path.join(self.compute_runtime_repo, "CMakeLists.txt") - # Remove -Werror... - replace_in_file(cmakelists_path, r"\s-Werror(?:=[a-zA-Z]*)?", "") - - log.info("Building Compute Runtime...") - configure_command = [ - "cmake", - f"-B {self.compute_runtime_build}", - f"-S {self.compute_runtime_repo}", - "-DCMAKE_BUILD_TYPE=Release", - "-DNEO_ENABLE_i915_PRELIM_DETECTION=1", - "-DNEO_ENABLE_I915_PRELIM_DETECTION=1", - "-DNEO_SKIP_UNIT_TESTS=1", - f"-DGMM_DIR={self.gmmlib}", - f"-DLEVEL_ZERO_ROOT={self.level_zero}", - ] - if options.build_igc: - configure_command.append(f"-DIGC_DIR={self.igc}") - - run(configure_command) - run(f"cmake --build {self.compute_runtime_build} -j {options.build_jobs}") - log.info("Compute Runtime build complete.") - return self.compute_runtime_build + self.igc, self.igc_rebuilt = self.build_igc(igc_repo, igc_commit) + + if ( + project.needs_rebuild(check_build=True) + or self.level_zero_rebuilt + or self.gmmlib_rebuilt + or (options.build_igc and self.igc_rebuilt) + ): + cmakelists_path = ( + project.src_dir / "level_zero" / "cmake" / "FindLevelZero.cmake" + ) + # specifying custom L0 is problematic... + replace_in_file( + cmakelists_path, r"(\$\{LEVEL_ZERO_ROOT\}\s*)", r"\1NO_DEFAULT_PATH\n" + ) + + cmakelists_path = project.src_dir / "CMakeLists.txt" + # Remove -Werror... + replace_in_file(cmakelists_path, r"\s-Werror(?:=[a-zA-Z]*)?", "") + + log.info("Building Compute Runtime...") + extra_config_args = [ + "-DNEO_ENABLE_i915_PRELIM_DETECTION=1", + "-DNEO_ENABLE_I915_PRELIM_DETECTION=1", + "-DNEO_SKIP_UNIT_TESTS=1", + f"-DGMM_DIR={self.gmmlib}", + f"-DLEVEL_ZERO_ROOT={self.level_zero}", + ] + if options.build_igc: + extra_config_args.append(f"-DIGC_DIR={self.igc}") + + project.configure(extra_args=extra_config_args) + project.build() + log.info("Compute Runtime build complete.") + else: + log.info("Compute Runtime build skipped, already built.") + return project.build_dir def get_compute_runtime() -> ComputeRuntime: # ComputeRuntime singleton