From eb4e123b0e385b173a20261bad90a28a4fff753c Mon Sep 17 00:00:00 2001 From: "Li, Ian" Date: Wed, 30 Apr 2025 15:48:09 -0700 Subject: [PATCH 01/16] Add L0 driver code for detecting compute-runtime versions --- devops/scripts/benchmarks/main.py | 21 +++ .../benchmarks/utils/detect_versions.cpp | 86 ++++++++++ .../benchmarks/utils/detect_versions.py | 157 ++++++++++++++++++ 3 files changed, 264 insertions(+) create mode 100644 devops/scripts/benchmarks/utils/detect_versions.cpp create mode 100644 devops/scripts/benchmarks/utils/detect_versions.py diff --git a/devops/scripts/benchmarks/main.py b/devops/scripts/benchmarks/main.py index 397632e138978..51cbee8cb3738 100755 --- a/devops/scripts/benchmarks/main.py +++ b/devops/scripts/benchmarks/main.py @@ -18,11 +18,13 @@ from utils.utils import prepare_workdir from utils.compute_runtime import * from utils.validate import Validate +from utils.detect_versions import DetectVersion from presets import enabled_suites, presets import argparse import re import statistics +import os # Update this if you are changing the layout of the results files INTERNAL_WORKDIR_VERSION = "2.0" @@ -530,6 +532,19 @@ def validate_and_parse_env_args(env_args): default=options.git_commit_override, ) + parser.add_argument( + "--detect-version", + type=str, + help="Detect versions of software used: comma-separated list with choices from [dpcpp, compute-runtime]", + default=None + ) + parser.add_argument( + "--detect-version-cpp-path", + type=Path, + help="Location of detect_version.cpp used to query e.g. DPC++, L0", + default=Path(f"{os.path.dirname(__file__)}/utils/detect_version.cpp") + ) + args = parser.parse_args() additional_env_vars = validate_and_parse_env_args(args.env) @@ -581,6 +596,12 @@ def validate_and_parse_env_args(env_args): options.github_repo_override = args.github_repo options.git_commit_override = args.git_commit + # Automatically detect versions: + if args.detect_version is not None: + DetectVersion.init( + args.detect_version_cpp_path + ) + benchmark_filter = re.compile(args.filter) if args.filter else None main( diff --git a/devops/scripts/benchmarks/utils/detect_versions.cpp b/devops/scripts/benchmarks/utils/detect_versions.cpp new file mode 100644 index 0000000000000..0b4d169f8ecf2 --- /dev/null +++ b/devops/scripts/benchmarks/utils/detect_versions.cpp @@ -0,0 +1,86 @@ +#include +#include +#include +#include + +#include + +#define _assert(cond, msg) \ + if (!(cond)) { \ + std::cout << std::endl << "Error: " << msg << std::endl; \ + exit(1); \ + } + +#define _success(res) res == ZE_RESULT_SUCCESS + +std::string query_dpcpp_ver() { + return std::string(__clang_version__); +} + +std::string query_l0_driver_ver() { + // Initialize L0 drivers: + ze_init_driver_type_desc_t driver_type = {}; + driver_type.stype = ZE_STRUCTURE_TYPE_INIT_DRIVER_TYPE_DESC; + driver_type.flags = ZE_INIT_DRIVER_TYPE_FLAG_GPU; + driver_type.pNext = nullptr; + + uint32_t driver_count = 0; + ze_result_t result = zeInitDrivers(&driver_count, nullptr, &driver_type); + _assert(_success(result), "Failed to initialize L0.") + _assert(driver_count > 0, "No L0 drivers available.") + + std::vector drivers(driver_count); + result = zeInitDrivers(&driver_count, drivers.data(), &driver_type); + _assert(_success(result), "Could not fetch L0 drivers.") + + // Check support for fetching driver version strings: + uint32_t ext_count = 0; + result = zeDriverGetExtensionProperties(drivers[0], &ext_count, nullptr); + _assert(_success(result), "Failed to obtain L0 extensions count.") + _assert(ext_count > 0, "No L0 extensions available.") + + std::vector extensions(ext_count); + result = zeDriverGetExtensionProperties(drivers[0], &ext_count, + extensions.data()); + _assert(_success(result), "Failed to obtain L0 extensions.") + bool version_ext_support = false; + for (const auto &extension : extensions) { + // std::cout << extension.name << std::endl; + if (strcmp(extension.name, "ZE_intel_get_driver_version_string")) { + version_ext_support = true; + } + } + _assert(version_ext_support, + "ZE_intel_get_driver_version_string extension is not supported."); + + // Fetch L0 driver version: + ze_result_t (*pfnGetDriverVersionFn)(ze_driver_handle_t, char*, size_t*); + result = zeDriverGetExtensionFunctionAddress( + drivers[0], + "zeIntelGetDriverVersionString", + (void **) &pfnGetDriverVersionFn + ); + _assert(_success(result), "Failed to obtain GetDriverVersionString fn."); + + size_t ver_str_len = 0; + result = pfnGetDriverVersionFn(drivers[0], nullptr, &ver_str_len); + _assert(_success(result), "Call to GetDriverVersionString failed."); + + std::cout << "ver_str_len: " << ver_str_len << std::endl; + ver_str_len ++; // ver_str_len does not account for '\0' + char *ver_str = (char *) calloc(ver_str_len, sizeof(char)); + result = pfnGetDriverVersionFn(drivers[0], ver_str, &ver_str_len); + _assert(_success(result), "Failed to write driver version string."); + + std::string res(ver_str); + free(ver_str); + return res; +} + +int main() { + std::string dpcpp_ver = query_dpcpp_ver(); + std::cout << "DPCPP_VER='" << dpcpp_ver << "'" << std::endl; + + std::string l0_ver = query_l0_driver_ver(); + std::cout << "L0_VER='" << l0_ver << "'" << std::endl; +} diff --git a/devops/scripts/benchmarks/utils/detect_versions.py b/devops/scripts/benchmarks/utils/detect_versions.py new file mode 100644 index 0000000000000..765b81fa1efdf --- /dev/null +++ b/devops/scripts/benchmarks/utils/detect_versions.py @@ -0,0 +1,157 @@ +import os +import re +import json +import urllib +import tempfile +import subprocess +from urllib import request +from pathlib import Path + +class DetectVersion: + _instance = None + + def __init__(self): + raise RuntimeError("Use init() to init and instance() to get instead.") + + @classmethod + def init(cls, detect_ver_path: Path, dpcpp_exec: str = "clang++"): + """ + Constructs the singleton instance for DetectVersion, and initializes by + building and run detect _version.cpp, which outputs: + - L0 driver version via ZE_intel_get_driver_version_string extension, + - DPC++ version via `__clang_version__` builtin. + + Remind: DO NOT allow user input in args. + + Parameters: + detect_ver_path (Path): Path to detect_version.cpp + dpcpp_exec (str): Name of DPC++ executable + """ + if cls._instance is not None: + return cls._instance + + detect_ver_exe = tempfile.mktemp() + result = subprocess.run( + [dpcpp_exec, "-lze_loader", detect_ver_path, "-o", detect_ver_exe], + check=True, + env=os.environ, + ) + result = subprocess.run( + [detect_ver_exe], + check=True, + text=True, + capture_output=True, + env=os.environ, + ) + # Variables are printed to stdout, each var is on its own line + result_vars = result.stdout.strip().split('\n') + + def get_var(var_name: str) -> str: + var_str = next( + filter(lambda v: re.match(f"^{var_name}='.*'", v), result_vars) + ) + return var_str[len(f"{var_name}='"):-len("'")] + + cls._instance = cls.__new__(cls) + cls._instance.l0_ver = get_var("L0_VER") + cls._instance.dpcpp_ver = get_var("DPCPP_VER") + return cls._instance + + @classmethod + def instance(cls): + """ + Returns singleton instance of DetectVersion if it has been initialized + via init(), otherwise return None. + """ + return cls._instance + + def get_l0_ver(self) -> str: + """ + Returns the full L0 version string. + """ + return self.l0_ver + + def get_dpcpp_ver(self) -> str: + """ + Returns the full DPC++ version / clang version string of DPC++ used. + """ + return self.dpcpp_ver + + def get_dpcpp_commit(self) -> str: + # clang++ formats are in ( ): if this + # regex does not match, it is likely this is not upstream clang. + git_info_match = re.search(r'(http.+ [0-9a-f]+)', self.dpcpp_ver) + if git_info_match is None: + return None + git_info = git_info_match.group(0) + return git_info[1:-1].split(' ')[1] + + def get_compute_runtime_ver(self) -> str: + """ + Returns the compute-runtime version by deriving from l0 version. + """ + # L0 version strings follows semver: major.minor.patch+optional + # compute-runtime version tags follow year.WW.patch.optional instead, + # but patch, pptional is still the same across both. + # + # We use patch+optional from L0 to figure out compute-runtime version: + patch = re.sub(r'^\d+\.\d+\.', '', self.l0_ver) + patch = re.sub(r'\+', '.', patch, count=1) + + # TODO unauthenticated users only get 60 API calls per hour: this will + # not work if we enable benchmark CI in precommit. + url = "https://api.github.com/repos/intel/compute-runtime/tags" + MAX_PAGINATION_CALLS = 2 + + try: + for _ in range(MAX_PAGINATION_CALLS): + res = request.urlopen(url) + tags = [ tag["name"] for tag in json.loads(res.read()) ] + + for tag in tags: + tag_patch = re.sub(r'^\d+\.\d+\.', '', tag) + # compute-runtime's cmake files produces "optional" fields + # padded with 0's: this means e.g. L0 version string + # 1.6.32961.200000 could be either compute-runtime ver. + # 25.09.32961.2, 25.09.32961.20, or even 25.09.32961.200. + # + # Thus, we take the longest match. Since the github api + # provides tags from newer -> older, we take the first tag + # that matches as it would be the "longest" ver. to match. + if tag_patch == patch[:len(tag_patch)]: + return tag + + def get_link_name(link: str) -> str: + rel_str = re.search(r'rel="\w+"', link).group(0) + return rel_str[len('rel="'):-len('"')] + + def get_link_url(link: str) -> str: + return link[link.index('<')+1:link.index('>')] + + links = { + get_link_name(link): get_link_url(link) + for link in res.getheader("Link").split(", ") + } + + if "next" in links: + url = links["next"] + else: + break + + except urllib.error.HTTPError as e: + print(f"HTTP error {e.code}: {e.read().decode('utf-8')}") + + except urllib.error.URLError as e: + print(f"URL error: {e.reason}") + + return "Unknown" + + +def main(): + detect_res = DetectVersion.init(f"{os.path.dirname(__file__)}/detect_versions.cpp") + # print(query_res.get_compute_runtime_ver()) + print(detect_res.get_dpcpp_commit()) + # print(query_res.get_l0_ver(), query_res.get_dpcpp_ver()) + +if __name__ == "__main__": + main() From 86263e1f1850a0a8b505f6101bea0e3df21c6f58 Mon Sep 17 00:00:00 2001 From: "Li, Ian" Date: Thu, 1 May 2025 13:16:45 -0700 Subject: [PATCH 02/16] instrument main with detect_versions --- devops/scripts/benchmarks/main.py | 32 ++++++++++---- devops/scripts/benchmarks/options.py | 23 ++++++++++ .../benchmarks/utils/detect_versions.py | 30 ++++++++++--- devops/scripts/benchmarks/utils/validate.py | 44 +++++++++---------- 4 files changed, 91 insertions(+), 38 deletions(-) diff --git a/devops/scripts/benchmarks/main.py b/devops/scripts/benchmarks/main.py index 51cbee8cb3738..cb4926ba0f84e 100755 --- a/devops/scripts/benchmarks/main.py +++ b/devops/scripts/benchmarks/main.py @@ -503,7 +503,7 @@ def validate_and_parse_env_args(env_args): type=lambda ts: Validate.timestamp( ts, throw=argparse.ArgumentTypeError( - "Specified timestamp not in YYYYMMDD_HHMMSS format." + "Specified timestamp not in YYYYMMDD_HHMMSS format" ), ), help="Manually specify timestamp used in metadata", @@ -514,7 +514,7 @@ def validate_and_parse_env_args(env_args): type=lambda gh_repo: Validate.github_repo( gh_repo, throw=argparse.ArgumentTypeError( - "Specified github repo not in / format." + "Specified github repo not in / format" ), ), help="Manually specify github repo metadata of component tested (e.g. SYCL, UMF)", @@ -525,7 +525,7 @@ def validate_and_parse_env_args(env_args): type=lambda commit: Validate.commit_hash( commit, throw=argparse.ArgumentTypeError( - "Specified commit is not a valid commit hash." + "Specified commit is not a valid commit hash" ), ), help="Manually specify commit hash metadata of component tested (e.g. SYCL, UMF)", @@ -534,15 +534,19 @@ def validate_and_parse_env_args(env_args): parser.add_argument( "--detect-version", - type=str, - help="Detect versions of software used: comma-separated list with choices from [dpcpp, compute-runtime]", + type=lambda comma_separated_str: Validate.on_re( + comma_separated_str, + r'[a-z_,]+', + throw=argparse.ArgumentTypeError("Specified --detect-version is not a comma-separated list") + ), + help="Detect versions of software used: comma-separated list with choices from dpcpp,compute_runtime", default=None ) parser.add_argument( "--detect-version-cpp-path", type=Path, help="Location of detect_version.cpp used to query e.g. DPC++, L0", - default=Path(f"{os.path.dirname(__file__)}/utils/detect_version.cpp") + default=None ) args = parser.parse_args() @@ -598,9 +602,19 @@ def validate_and_parse_env_args(env_args): # Automatically detect versions: if args.detect_version is not None: - DetectVersion.init( - args.detect_version_cpp_path - ) + detect_ver_path = args.detect_version_cpp_path + if detect_ver_path is None: + detect_ver_path = Path(f"{os.path.dirname(__file__)}/utils/detect_version.cpp") + if not detect_ver_path.is_file(): + parser.error(f"Unable to find detect_version.cpp at {detect_ver_path}, please specify --detect-version-cpp-path") + elif not detect_ver_path.is_file(): + parser.error(f"Specified --detect-version-cpp-path is not a valid file") + + enabled_software = args.detect_version.split(',') + options.detect_versions.sycl = "sycl" in enabled_software + options.detect_versions.compute_runtime = "compute_runtime" in enabled_software + + DetectVersion.init(detect_ver_path) benchmark_filter = re.compile(args.filter) if args.filter else None diff --git a/devops/scripts/benchmarks/options.py b/devops/scripts/benchmarks/options.py index 0cb5895cfa34f..655ef7654739f 100644 --- a/devops/scripts/benchmarks/options.py +++ b/devops/scripts/benchmarks/options.py @@ -16,6 +16,27 @@ class MarkdownSize(Enum): FULL = "full" +@dataclass +class DetectVersionsOptions: + """ + Options for automatic version detection + """ + # Software to detect versions for: + sycl: bool = False + compute_runtime: bool = False + # umf: bool = False + # level_zero: bool = False + + # Placeholder text, should automatic version detection fail: This text will + # only be used if automatic version detection for x software is explicitly + # specified. + not_found_placeholder = "unknown" # None + + # TODO unauthenticated users only get 60 API calls per hour: this will not + # work if we enable benchmark CI in precommit. + compute_runtime_tag_api: str = "https://api.github.com/repos/intel/compute-runtime/tags" + max_api_calls = 2 + @dataclass class Options: workdir: str = None @@ -64,5 +85,7 @@ class Options: github_repo_override: str = None git_commit_override: str = None + detect_versions: DetectVersionsOptions = field(default_factory=DetectVersionsOptions) + options = Options() diff --git a/devops/scripts/benchmarks/utils/detect_versions.py b/devops/scripts/benchmarks/utils/detect_versions.py index 765b81fa1efdf..c824d1821735a 100644 --- a/devops/scripts/benchmarks/utils/detect_versions.py +++ b/devops/scripts/benchmarks/utils/detect_versions.py @@ -1,5 +1,6 @@ import os import re +import sys import json import urllib import tempfile @@ -7,6 +8,10 @@ from urllib import request from pathlib import Path +if __name__ == "__main__": + sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) +from options import options + class DetectVersion: _instance = None @@ -77,14 +82,26 @@ def get_dpcpp_ver(self) -> str: """ return self.dpcpp_ver - def get_dpcpp_commit(self) -> str: + def get_dpcpp_git_info(self) -> [str, str]: # clang++ formats are in ( ): if this # regex does not match, it is likely this is not upstream clang. git_info_match = re.search(r'(http.+ [0-9a-f]+)', self.dpcpp_ver) if git_info_match is None: return None git_info = git_info_match.group(0) - return git_info[1:-1].split(' ')[1] + return git_info[1:-1].split(' ') + + def get_dpcpp_commit(self) -> str: + git_info = self.get_dpcpp_git_info() + if git_info is None: + return options.detect_versions.not_found_placeholder + return git_info[1] + + def get_dpcpp_repo(self) -> str: + git_info = self.get_dpcpp_git_info() + if git_info is None: + return options.detect_versions.not_found_placeholder + return git_info[0] def get_compute_runtime_ver(self) -> str: """ @@ -100,11 +117,10 @@ def get_compute_runtime_ver(self) -> str: # TODO unauthenticated users only get 60 API calls per hour: this will # not work if we enable benchmark CI in precommit. - url = "https://api.github.com/repos/intel/compute-runtime/tags" - MAX_PAGINATION_CALLS = 2 + url = options.detect_versions.compute_runtime_tag_api try: - for _ in range(MAX_PAGINATION_CALLS): + for _ in range(options.detect_versions.max_api_calls): res = request.urlopen(url) tags = [ tag["name"] for tag in json.loads(res.read()) ] @@ -144,7 +160,7 @@ def get_link_url(link: str) -> str: except urllib.error.URLError as e: print(f"URL error: {e.reason}") - return "Unknown" + return options.detect_versions.not_found_placeholder def main(): @@ -154,4 +170,4 @@ def main(): # print(query_res.get_l0_ver(), query_res.get_dpcpp_ver()) if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/devops/scripts/benchmarks/utils/validate.py b/devops/scripts/benchmarks/utils/validate.py index b0a2658865562..8d90bca8b1148 100644 --- a/devops/scripts/benchmarks/utils/validate.py +++ b/devops/scripts/benchmarks/utils/validate.py @@ -1,32 +1,32 @@ import re +class Validate: + """Static class containing methods for validating various fields""" -def validate_on_re(val: str, regex: re.Pattern, throw: Exception = None): - """ - Returns True if val is matched by pattern defined by regex, otherwise False. - - If `throw` argument is not None: return val as-is if val matches regex, - otherwise raise error defined by throw. - """ - is_matching: bool = re.compile(regex).match(val) is not None - - if throw is None: - return is_matching - elif not is_matching: - raise throw - else: - return val + @staticmethod + def on_re(val: str, regex: re.Pattern, throw: Exception = None): + """ + Returns True if val is matched by pattern defined by regex, otherwise + False. + If `throw` argument is not None: return val as-is if val matches regex, + otherwise raise error defined by throw. + """ + is_matching: bool = re.compile(regex).match(val) is not None -class Validate: - """Static class containing methods for validating various fields""" + if throw is None: + return is_matching + elif not is_matching: + raise throw + else: + return val @staticmethod def runner_name(runner_name: str, throw: Exception = None): """ Returns True if runner_name is clean (no illegal characters). """ - return validate_on_re(runner_name, r"^[a-zA-Z0-9_]+$", throw=throw) + return Validate.on_re(runner_name, r"^[a-zA-Z0-9_]+$", throw=throw) @staticmethod def timestamp(t: str, throw: Exception = None): @@ -36,7 +36,7 @@ def timestamp(t: str, throw: Exception = None): If throw argument is specified: return t as-is if t is in aforementioned format, otherwise raise error defined by throw. """ - return validate_on_re( + return Validate.on_re( t, r"^\d{4}(0[1-9]|1[0-2])([0-2][0-9]|3[01])_([01][0-9]|2[0-3])[0-5][0-9][0-5][0-9]$", throw=throw, @@ -50,7 +50,7 @@ def github_repo(repo: str, throw: Exception = None): If throw argument is specified: return repo as-is if repo is in aforementioned format, otherwise raise error defined by throw. """ - return validate_on_re( + return Validate.on_re( re.sub(r"^https?://github.com/", "", repo), r"^[a-zA-Z0-9_-]{1,39}/[a-zA-Z0-9_.-]{1,100}$", throw=throw, @@ -67,6 +67,6 @@ def commit_hash(commit: str, throw: Exception = None, trunc: int = 40): """ commit_re = r"^[a-f0-9]{7,40}$" if throw is None: - return validate_on_re(commit, commit_re) + return Validate.on_re(commit, commit_re) else: - return validate_on_re(commit, commit_re, throw=throw)[:trunc] + return Validate.on_re(commit, commit_re, throw=throw)[:trunc] From b21be5f679139f9caa4195297620dfcf90fece52 Mon Sep 17 00:00:00 2001 From: "Li, Ian" Date: Thu, 1 May 2025 14:22:11 -0700 Subject: [PATCH 03/16] change wording, add frontend --- devops/scripts/benchmarks/main.py | 12 +++--- devops/scripts/benchmarks/options.py | 4 +- .../benchmarks/utils/detect_versions.py | 42 ++++++++++++++++--- 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/devops/scripts/benchmarks/main.py b/devops/scripts/benchmarks/main.py index cb4926ba0f84e..918c429da8f8c 100755 --- a/devops/scripts/benchmarks/main.py +++ b/devops/scripts/benchmarks/main.py @@ -534,12 +534,12 @@ def validate_and_parse_env_args(env_args): parser.add_argument( "--detect-version", - type=lambda comma_separated_str: Validate.on_re( - comma_separated_str, + type=lambda components: Validate.on_re( + components, r'[a-z_,]+', throw=argparse.ArgumentTypeError("Specified --detect-version is not a comma-separated list") ), - help="Detect versions of software used: comma-separated list with choices from dpcpp,compute_runtime", + help="Detect versions of components used: comma-separated list with choices from dpcpp,compute_runtime", default=None ) parser.add_argument( @@ -610,9 +610,9 @@ def validate_and_parse_env_args(env_args): elif not detect_ver_path.is_file(): parser.error(f"Specified --detect-version-cpp-path is not a valid file") - enabled_software = args.detect_version.split(',') - options.detect_versions.sycl = "sycl" in enabled_software - options.detect_versions.compute_runtime = "compute_runtime" in enabled_software + enabled_components = args.detect_version.split(',') + options.detect_versions.sycl = "sycl" in enabled_components + options.detect_versions.compute_runtime = "compute_runtime" in enabled_components DetectVersion.init(detect_ver_path) diff --git a/devops/scripts/benchmarks/options.py b/devops/scripts/benchmarks/options.py index 655ef7654739f..0870bfc40f9eb 100644 --- a/devops/scripts/benchmarks/options.py +++ b/devops/scripts/benchmarks/options.py @@ -21,14 +21,14 @@ class DetectVersionsOptions: """ Options for automatic version detection """ - # Software to detect versions for: + # Components to detect versions for: sycl: bool = False compute_runtime: bool = False # umf: bool = False # level_zero: bool = False # Placeholder text, should automatic version detection fail: This text will - # only be used if automatic version detection for x software is explicitly + # only be used if automatic version detection for x component is explicitly # specified. not_found_placeholder = "unknown" # None diff --git a/devops/scripts/benchmarks/utils/detect_versions.py b/devops/scripts/benchmarks/utils/detect_versions.py index c824d1821735a..9fce71a2a46fc 100644 --- a/devops/scripts/benchmarks/utils/detect_versions.py +++ b/devops/scripts/benchmarks/utils/detect_versions.py @@ -11,6 +11,7 @@ if __name__ == "__main__": sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) from options import options +from utils.validate import Validate class DetectVersion: _instance = None @@ -163,11 +164,42 @@ def get_link_url(link: str) -> str: return options.detect_versions.not_found_placeholder -def main(): +def main(components: [str]): detect_res = DetectVersion.init(f"{os.path.dirname(__file__)}/detect_versions.cpp") - # print(query_res.get_compute_runtime_ver()) - print(detect_res.get_dpcpp_commit()) - # print(query_res.get_l0_ver(), query_res.get_dpcpp_ver()) + + str2fn = { + "dpcpp_repo": detect_res.get_dpcpp_repo, + "dpcpp_commit": detect_res.get_dpcpp_commit, + "l0_ver": detect_res.get_l0_ver, + "compute_runtime_ver": detect_res.get_compute_runtime_ver + } + + def remove_undefined_components(component: str) -> bool: + if component not in str2fn: + print(f"# Warning: unknown component {component}", file=sys.stderr) + return False + return True + + components_clean = filter(remove_undefined_components, components) + + output = map(lambda c: f"{c.upper()}={str2fn[c]()}", components_clean) + for s in output: + print(s) if __name__ == "__main__": - main() \ No newline at end of file + parser = argparse.ArgumentParser(description="Get version information for specified components.") + parser.add_argument( + "components", + type=lambda components: Validate.on_re( + components, + r'[a-z_,]+', + throw=argparse.ArgumentTypeError("Specified --components is not a comma-separated list") + ), + help=""" + Comma-separated list of components to get version information for. + Valid options: dpcpp_repo,dpcpp_commit,l0_ver,compute_runtime_ver + """ + ) + args = parser.parse_args() + + main(map(lambda c: c.strip(), args.components.split(','))) \ No newline at end of file From 0c777801675f9ba8b6b300f20055aefe71a318aa Mon Sep 17 00:00:00 2001 From: "Li, Ian" Date: Thu, 1 May 2025 14:30:53 -0700 Subject: [PATCH 04/16] Fix bug --- .../scripts/benchmarks/utils/detect_versions.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/devops/scripts/benchmarks/utils/detect_versions.py b/devops/scripts/benchmarks/utils/detect_versions.py index 9fce71a2a46fc..5bda6be1ad0c7 100644 --- a/devops/scripts/benchmarks/utils/detect_versions.py +++ b/devops/scripts/benchmarks/utils/detect_versions.py @@ -7,11 +7,11 @@ import subprocess from urllib import request from pathlib import Path +import argparse if __name__ == "__main__": sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) from options import options -from utils.validate import Validate class DetectVersion: _instance = None @@ -86,7 +86,7 @@ def get_dpcpp_ver(self) -> str: def get_dpcpp_git_info(self) -> [str, str]: # clang++ formats are in ( ): if this # regex does not match, it is likely this is not upstream clang. - git_info_match = re.search(r'(http.+ [0-9a-f]+)', self.dpcpp_ver) + git_info_match = re.search(r'\(http.+ [0-9a-f]+\)', self.dpcpp_ver) if git_info_match is None: return None git_info = git_info_match.group(0) @@ -176,25 +176,20 @@ def main(components: [str]): def remove_undefined_components(component: str) -> bool: if component not in str2fn: - print(f"# Warning: unknown component {component}", file=sys.stderr) + print(f"# Warn: unknown component: {component}", file=sys.stderr) return False return True components_clean = filter(remove_undefined_components, components) - output = map(lambda c: f"{c.upper()}={str2fn[c]()}", components_clean) - for s in output: + for s in map(lambda c: f"{c.upper()}={str2fn[c]()}", components_clean): print(s) + if __name__ == "__main__": parser = argparse.ArgumentParser(description="Get version information for specified components.") parser.add_argument( - "components", - type=lambda components: Validate.on_re( - components, - r'[a-z_,]+', - throw=argparse.ArgumentTypeError("Specified --components is not a comma-separated list") - ), + "components", type=str, help=""" Comma-separated list of components to get version information for. Valid options: dpcpp_repo,dpcpp_commit,l0_ver,compute_runtime_ver From 274be61de7d7b88665d87dcd83fd0c11412ad675 Mon Sep 17 00:00:00 2001 From: "Li, Ian" Date: Thu, 1 May 2025 15:02:19 -0700 Subject: [PATCH 05/16] Hook up benchmark script to detect_versions --- devops/scripts/benchmarks/history.py | 17 +++++++++++------ devops/scripts/benchmarks/main.py | 19 ++++++++++++++++--- .../benchmarks/utils/detect_versions.py | 15 ++++++++++++++- 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/devops/scripts/benchmarks/history.py b/devops/scripts/benchmarks/history.py index ae8ac16c264bf..24c75d98d7fa7 100644 --- a/devops/scripts/benchmarks/history.py +++ b/devops/scripts/benchmarks/history.py @@ -94,9 +94,12 @@ def git_info_from_path(path: Path) -> (str, str): return git_hash, github_repo if options.git_commit_override is None or options.github_repo_override is None: - git_hash, github_repo = git_info_from_path( - os.path.dirname(os.path.abspath(__file__)) - ) + if options.detect_versions.sycl: + github_repo, git_hash = DetectVersion.instance().get_dpcpp_git_info() + else: + git_hash, github_repo = git_info_from_path( + os.path.dirname(os.path.abspath(__file__)) + ) else: git_hash, github_repo = ( options.git_commit_override, @@ -119,9 +122,11 @@ def git_info_from_path(path: Path) -> (str, str): throw=ValueError("Illegal characters found in specified RUNNER_NAME."), ) - compute_runtime = ( - options.compute_runtime_tag if options.build_compute_runtime else None - ) + compute_runtime = None + if options.build_compute_runtime: + compute_runtime = options.compute_runtime_tag + elif options.detect_versions.compute_runtime: + compute_runtime = DetectVersion.instance().get_compute_runtime_ver() return BenchmarkRun( name=name, diff --git a/devops/scripts/benchmarks/main.py b/devops/scripts/benchmarks/main.py index 918c429da8f8c..45096080b8b32 100755 --- a/devops/scripts/benchmarks/main.py +++ b/devops/scripts/benchmarks/main.py @@ -604,9 +604,9 @@ def validate_and_parse_env_args(env_args): if args.detect_version is not None: detect_ver_path = args.detect_version_cpp_path if detect_ver_path is None: - detect_ver_path = Path(f"{os.path.dirname(__file__)}/utils/detect_version.cpp") + detect_ver_path = Path(f"{os.path.dirname(__file__)}/utils/detect_versions.cpp") if not detect_ver_path.is_file(): - parser.error(f"Unable to find detect_version.cpp at {detect_ver_path}, please specify --detect-version-cpp-path") + parser.error(f"Unable to find detect_versions.cpp at {detect_ver_path}, please specify --detect-version-cpp-path") elif not detect_ver_path.is_file(): parser.error(f"Specified --detect-version-cpp-path is not a valid file") @@ -614,7 +614,20 @@ def validate_and_parse_env_args(env_args): options.detect_versions.sycl = "sycl" in enabled_components options.detect_versions.compute_runtime = "compute_runtime" in enabled_components - DetectVersion.init(detect_ver_path) + detect_res = DetectVersion.init(detect_ver_path) + # Since get_compute_runtime_ver requires github API calls, make sure + # these calls are successful first before running benchmarks. We don't + # want to benchmarks only to be unable to detect compute-runtime + # version. + if options.detect_versions.compute_runtime: + for attempt in range(2): + if attempt > 0: + print(f"detect_version: Failed to get compute_runtime version. Commencing attempt #{attempt}") + detect_res.get_compute_runtime_ver() + if detect_res.get_compute_runtime_ver_cached() is not None: + break + if detect_res.get_compute_runtime_ver_cached() is None: + raise RuntimeError("Unable to parse compute_runtime tag from github tags API!") benchmark_filter = re.compile(args.filter) if args.filter else None diff --git a/devops/scripts/benchmarks/utils/detect_versions.py b/devops/scripts/benchmarks/utils/detect_versions.py index 5bda6be1ad0c7..ec10a065967f6 100644 --- a/devops/scripts/benchmarks/utils/detect_versions.py +++ b/devops/scripts/benchmarks/utils/detect_versions.py @@ -61,6 +61,9 @@ def get_var(var_name: str) -> str: cls._instance = cls.__new__(cls) cls._instance.l0_ver = get_var("L0_VER") cls._instance.dpcpp_ver = get_var("DPCPP_VER") + cls._instance.dpcpp_exec = dpcpp_exec + # Do not make 2 API calls if compute_runtime_ver was already obtained + cls._instance.compute_runtime_ver_cache = None return cls._instance @classmethod @@ -84,11 +87,14 @@ def get_dpcpp_ver(self) -> str: return self.dpcpp_ver def get_dpcpp_git_info(self) -> [str, str]: + """ + Returns: (git_repo, commit_hash) + """ # clang++ formats are in ( ): if this # regex does not match, it is likely this is not upstream clang. git_info_match = re.search(r'\(http.+ [0-9a-f]+\)', self.dpcpp_ver) if git_info_match is None: - return None + raise RuntimeError(f"detect_version: Unable to obtain git info from {self.dpcpp_exec}, are you sure you are using DPC++?") git_info = git_info_match.group(0) return git_info[1:-1].split(' ') @@ -104,10 +110,16 @@ def get_dpcpp_repo(self) -> str: return options.detect_versions.not_found_placeholder return git_info[0] + def get_compute_runtime_ver_cached(self) -> str: + return self.compute_runtime_ver_cache + def get_compute_runtime_ver(self) -> str: """ Returns the compute-runtime version by deriving from l0 version. """ + if self.compute_runtime_ver_cache is not None: + return self.compute_runtime_ver_cache + # L0 version strings follows semver: major.minor.patch+optional # compute-runtime version tags follow year.WW.patch.optional instead, # but patch, pptional is still the same across both. @@ -136,6 +148,7 @@ def get_compute_runtime_ver(self) -> str: # provides tags from newer -> older, we take the first tag # that matches as it would be the "longest" ver. to match. if tag_patch == patch[:len(tag_patch)]: + self.compute_runtime_ver_cache = tag return tag def get_link_name(link: str) -> str: From d8ab6225873cf636c6f14b5537e1b761d4bdda8c Mon Sep 17 00:00:00 2001 From: "Li, Ian" Date: Fri, 2 May 2025 09:13:08 -0700 Subject: [PATCH 06/16] test changes in ci --- devops/actions/run-tests/benchmark/action.yml | 5 +++-- devops/scripts/benchmarks/history.py | 1 + devops/scripts/benchmarks/main.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/devops/actions/run-tests/benchmark/action.yml b/devops/actions/run-tests/benchmark/action.yml index 182e08422b9dd..d82ec3acfbb75 100644 --- a/devops/actions/run-tests/benchmark/action.yml +++ b/devops/actions/run-tests/benchmark/action.yml @@ -142,8 +142,9 @@ runs: --output-dir "./llvm-ci-perf-results/" \ --preset "$PRESET" \ --timestamp-override "$SAVE_TIMESTAMP" \ - --github-repo "$sycl_git_repo" \ - --git-commit "$sycl_git_commit" + --detect-version dpcpp,compute_runtime + # --github-repo "$sycl_git_repo" \ + # --git-commit "$sycl_git_commit" echo "-----" python3 ./devops/scripts/benchmarks/compare.py to_hist \ --name "$SAVE_NAME" \ diff --git a/devops/scripts/benchmarks/history.py b/devops/scripts/benchmarks/history.py index 24c75d98d7fa7..ab51f4573c1e9 100644 --- a/devops/scripts/benchmarks/history.py +++ b/devops/scripts/benchmarks/history.py @@ -95,6 +95,7 @@ def git_info_from_path(path: Path) -> (str, str): if options.git_commit_override is None or options.github_repo_override is None: if options.detect_versions.sycl: + print("[TEST] Obtain data from detect_version") github_repo, git_hash = DetectVersion.instance().get_dpcpp_git_info() else: git_hash, github_repo = git_info_from_path( diff --git a/devops/scripts/benchmarks/main.py b/devops/scripts/benchmarks/main.py index 45096080b8b32..6d3ed1e0dd730 100755 --- a/devops/scripts/benchmarks/main.py +++ b/devops/scripts/benchmarks/main.py @@ -539,7 +539,7 @@ def validate_and_parse_env_args(env_args): r'[a-z_,]+', throw=argparse.ArgumentTypeError("Specified --detect-version is not a comma-separated list") ), - help="Detect versions of components used: comma-separated list with choices from dpcpp,compute_runtime", + help="Detect versions of components used: comma-separated list with choices from sycl,compute_runtime", default=None ) parser.add_argument( From 4cb45ce5b0c6b261265afeb8d20c84b71b42f056 Mon Sep 17 00:00:00 2001 From: "Li, Ian" Date: Fri, 2 May 2025 09:55:49 -0700 Subject: [PATCH 07/16] Fix bug --- devops/scripts/benchmarks/history.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/devops/scripts/benchmarks/history.py b/devops/scripts/benchmarks/history.py index ab51f4573c1e9..e7d7491e3e58e 100644 --- a/devops/scripts/benchmarks/history.py +++ b/devops/scripts/benchmarks/history.py @@ -13,6 +13,8 @@ from utils.utils import run from utils.validate import Validate +from utils.detect_versions import DetectVersion + class BenchmarkHistory: runs = [] From 3648a355691b1e2588fbf93b2732ae583a7d87bf Mon Sep 17 00:00:00 2001 From: "Li, Ian" Date: Fri, 2 May 2025 10:46:16 -0700 Subject: [PATCH 08/16] Fix bug --- devops/actions/run-tests/benchmark/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devops/actions/run-tests/benchmark/action.yml b/devops/actions/run-tests/benchmark/action.yml index d82ec3acfbb75..c86102cb80932 100644 --- a/devops/actions/run-tests/benchmark/action.yml +++ b/devops/actions/run-tests/benchmark/action.yml @@ -142,7 +142,7 @@ runs: --output-dir "./llvm-ci-perf-results/" \ --preset "$PRESET" \ --timestamp-override "$SAVE_TIMESTAMP" \ - --detect-version dpcpp,compute_runtime + --detect-version sycl,compute_runtime # --github-repo "$sycl_git_repo" \ # --git-commit "$sycl_git_commit" echo "-----" From 7d927f73fb4414cb6731d9d15f5f459b77a693e5 Mon Sep 17 00:00:00 2001 From: "Li, Ian" Date: Mon, 5 May 2025 10:40:33 -0700 Subject: [PATCH 09/16] Remove test code --- devops/actions/run-tests/benchmark/action.yml | 15 ----------- devops/scripts/benchmarks/compare.py | 27 +++++-------------- devops/scripts/benchmarks/history.py | 1 - 3 files changed, 6 insertions(+), 37 deletions(-) diff --git a/devops/actions/run-tests/benchmark/action.yml b/devops/actions/run-tests/benchmark/action.yml index c86102cb80932..35b08d3287f38 100644 --- a/devops/actions/run-tests/benchmark/action.yml +++ b/devops/actions/run-tests/benchmark/action.yml @@ -110,19 +110,6 @@ runs: pip install --user --break-system-packages -r ./devops/scripts/benchmarks/requirements.txt echo "-----" - # clang builds have git repo / commit hashes in their --version output, - # same goes for dpcpp. Obtain git repo / commit hash info this way: - - # First line of --version is formatted 'clang version ... ( )' - # thus we parse for ( ): - sycl_git_info="$(clang++ --version | head -n 1 | grep -oE '\([^ ]+ [a-f0-9]+\)$' | tr -d '()')" - if [ -z "$sycl_git_info" ]; then - echo "Error: Unable to deduce SYCL build source repo/commit: Are you sure dpcpp variable is in PATH?" - exit 1 - fi - sycl_git_repo="$(printf "$sycl_git_info" | cut -d' ' -f1)" - sycl_git_commit="$(printf "$sycl_git_info" | cut -d' ' -f2)" - case "$ONEAPI_DEVICE_SELECTOR" in level_zero:*) SAVE_SUFFIX="L0" ;; level_zero_v2:*) SAVE_SUFFIX="L0v2" ;; @@ -143,8 +130,6 @@ runs: --preset "$PRESET" \ --timestamp-override "$SAVE_TIMESTAMP" \ --detect-version sycl,compute_runtime - # --github-repo "$sycl_git_repo" \ - # --git-commit "$sycl_git_commit" echo "-----" python3 ./devops/scripts/benchmarks/compare.py to_hist \ --name "$SAVE_NAME" \ diff --git a/devops/scripts/benchmarks/compare.py b/devops/scripts/benchmarks/compare.py index c44d06f718039..929502a882fea 100644 --- a/devops/scripts/benchmarks/compare.py +++ b/devops/scripts/benchmarks/compare.py @@ -145,6 +145,9 @@ def validate_benchmark_result(result: BenchmarkRun) -> bool: def reset_aggregate() -> dict: return { + # TODO compare determine which command args have an + # impact on perf results, and do not compare arg results + # are incomparable "command_args": set(test_run.command[1:]), "aggregate": aggregator(starting_elements=[test_run.value]), } @@ -153,27 +156,9 @@ def reset_aggregate() -> dict: if test_run.name not in average_aggregate: average_aggregate[test_run.name] = reset_aggregate() else: - # Check that we are comparing runs with the same cmd args: - if ( - set(test_run.command[1:]) - == average_aggregate[test_run.name]["command_args"] - ): - average_aggregate[test_run.name]["aggregate"].add( - test_run.value - ) - else: - # If the command args used between runs are different, - # discard old run data and prefer new command args - # - # This relies on the fact that paths from get_result_paths() - # is sorted from older to newer - print( - f"Warning: Command args for {test_run.name} from {result_path} is different from prior runs." - ) - print( - "DISCARDING older data and OVERRIDING with data using new arg." - ) - average_aggregate[test_run.name] = reset_aggregate() + average_aggregate[test_run.name]["aggregate"].add( + test_run.value + ) return { name: BenchmarkHistoricAverage( diff --git a/devops/scripts/benchmarks/history.py b/devops/scripts/benchmarks/history.py index e7d7491e3e58e..f661d536ea4e7 100644 --- a/devops/scripts/benchmarks/history.py +++ b/devops/scripts/benchmarks/history.py @@ -97,7 +97,6 @@ def git_info_from_path(path: Path) -> (str, str): if options.git_commit_override is None or options.github_repo_override is None: if options.detect_versions.sycl: - print("[TEST] Obtain data from detect_version") github_repo, git_hash = DetectVersion.instance().get_dpcpp_git_info() else: git_hash, github_repo = git_info_from_path( From a447867f1f3529e04247cb28e4d1b973e4d1a71e Mon Sep 17 00:00:00 2001 From: "Li, Ian" Date: Mon, 5 May 2025 12:28:56 -0700 Subject: [PATCH 10/16] Remove more checking for args --- devops/scripts/benchmarks/compare.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/devops/scripts/benchmarks/compare.py b/devops/scripts/benchmarks/compare.py index 929502a882fea..2b0f5119ffc19 100644 --- a/devops/scripts/benchmarks/compare.py +++ b/devops/scripts/benchmarks/compare.py @@ -202,9 +202,9 @@ def halfway_round(value: int, n: int): for test in target.results: if test.name not in hist_avg: continue - if hist_avg[test.name].command_args != set(test.command[1:]): - print(f"Warning: skipped {test.name} due to command args mismatch.") - continue + # TODO compare command args which have an impact on performance + # (i.e. ignore --save-name): if command results are incomparable, + # skip the result. delta = 1 - ( test.value / hist_avg[test.name].value From c67e05206710a9610d7dfc780857c57ea4b5ea78 Mon Sep 17 00:00:00 2001 From: "Li, Ian" Date: Tue, 6 May 2025 08:16:05 -0700 Subject: [PATCH 11/16] Remove some odd choices --- devops/scripts/benchmarks/history.py | 4 ++++ devops/scripts/benchmarks/main.py | 13 ------------- devops/scripts/benchmarks/options.py | 3 ++- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/devops/scripts/benchmarks/history.py b/devops/scripts/benchmarks/history.py index f661d536ea4e7..c91faeb56331c 100644 --- a/devops/scripts/benchmarks/history.py +++ b/devops/scripts/benchmarks/history.py @@ -129,6 +129,10 @@ def git_info_from_path(path: Path) -> (str, str): compute_runtime = options.compute_runtime_tag elif options.detect_versions.compute_runtime: compute_runtime = DetectVersion.instance().get_compute_runtime_ver() + if detect_res.get_compute_runtime_ver_cached() is None: + print("Warning: Could not find compute_runtime version via github tags API.") + else: + compute_runtime = "unknown" return BenchmarkRun( name=name, diff --git a/devops/scripts/benchmarks/main.py b/devops/scripts/benchmarks/main.py index 6d3ed1e0dd730..36891b50d2032 100755 --- a/devops/scripts/benchmarks/main.py +++ b/devops/scripts/benchmarks/main.py @@ -615,19 +615,6 @@ def validate_and_parse_env_args(env_args): options.detect_versions.compute_runtime = "compute_runtime" in enabled_components detect_res = DetectVersion.init(detect_ver_path) - # Since get_compute_runtime_ver requires github API calls, make sure - # these calls are successful first before running benchmarks. We don't - # want to benchmarks only to be unable to detect compute-runtime - # version. - if options.detect_versions.compute_runtime: - for attempt in range(2): - if attempt > 0: - print(f"detect_version: Failed to get compute_runtime version. Commencing attempt #{attempt}") - detect_res.get_compute_runtime_ver() - if detect_res.get_compute_runtime_ver_cached() is not None: - break - if detect_res.get_compute_runtime_ver_cached() is None: - raise RuntimeError("Unable to parse compute_runtime tag from github tags API!") benchmark_filter = re.compile(args.filter) if args.filter else None diff --git a/devops/scripts/benchmarks/options.py b/devops/scripts/benchmarks/options.py index 0870bfc40f9eb..e9309e1361f8a 100644 --- a/devops/scripts/benchmarks/options.py +++ b/devops/scripts/benchmarks/options.py @@ -35,7 +35,8 @@ class DetectVersionsOptions: # TODO unauthenticated users only get 60 API calls per hour: this will not # work if we enable benchmark CI in precommit. compute_runtime_tag_api: str = "https://api.github.com/repos/intel/compute-runtime/tags" - max_api_calls = 2 + # Max amount of api calls permitted on each run of the benchmark scripts + max_api_calls = 4 @dataclass class Options: From a07c2112cee4d2b9ce01e828e8264c1cada2b9c6 Mon Sep 17 00:00:00 2001 From: "Li, Ian" Date: Tue, 6 May 2025 08:28:33 -0700 Subject: [PATCH 12/16] add newline --- devops/scripts/benchmarks/utils/detect_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devops/scripts/benchmarks/utils/detect_versions.py b/devops/scripts/benchmarks/utils/detect_versions.py index ec10a065967f6..31827e5da0525 100644 --- a/devops/scripts/benchmarks/utils/detect_versions.py +++ b/devops/scripts/benchmarks/utils/detect_versions.py @@ -210,4 +210,4 @@ def remove_undefined_components(component: str) -> bool: ) args = parser.parse_args() - main(map(lambda c: c.strip(), args.components.split(','))) \ No newline at end of file + main(map(lambda c: c.strip(), args.components.split(','))) From 3b0c9967152e544a675909a47822e1b9cc08ae45 Mon Sep 17 00:00:00 2001 From: "Li, Ian" Date: Tue, 6 May 2025 08:47:49 -0700 Subject: [PATCH 13/16] Fix bug --- devops/scripts/benchmarks/history.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/devops/scripts/benchmarks/history.py b/devops/scripts/benchmarks/history.py index c91faeb56331c..0149cab65319d 100644 --- a/devops/scripts/benchmarks/history.py +++ b/devops/scripts/benchmarks/history.py @@ -128,7 +128,8 @@ def git_info_from_path(path: Path) -> (str, str): if options.build_compute_runtime: compute_runtime = options.compute_runtime_tag elif options.detect_versions.compute_runtime: - compute_runtime = DetectVersion.instance().get_compute_runtime_ver() + detect_res = DetectVersion.instance() + compute_runtime = detect_res.get_compute_runtime_ver() if detect_res.get_compute_runtime_ver_cached() is None: print("Warning: Could not find compute_runtime version via github tags API.") else: From 233c062e7f35d604a5d613fbd588ec6b7b04fa95 Mon Sep 17 00:00:00 2001 From: "Li, Ian" Date: Tue, 6 May 2025 11:20:20 -0700 Subject: [PATCH 14/16] darker format python --- devops/scripts/benchmarks/compare.py | 4 +- devops/scripts/benchmarks/history.py | 4 +- devops/scripts/benchmarks/main.py | 26 +- devops/scripts/benchmarks/options.py | 11 +- .../benchmarks/utils/detect_versions.py | 372 +++++++++--------- 5 files changed, 218 insertions(+), 199 deletions(-) diff --git a/devops/scripts/benchmarks/compare.py b/devops/scripts/benchmarks/compare.py index 2b0f5119ffc19..eea5e450e6729 100644 --- a/devops/scripts/benchmarks/compare.py +++ b/devops/scripts/benchmarks/compare.py @@ -156,9 +156,7 @@ def reset_aggregate() -> dict: if test_run.name not in average_aggregate: average_aggregate[test_run.name] = reset_aggregate() else: - average_aggregate[test_run.name]["aggregate"].add( - test_run.value - ) + average_aggregate[test_run.name]["aggregate"].add(test_run.value) return { name: BenchmarkHistoricAverage( diff --git a/devops/scripts/benchmarks/history.py b/devops/scripts/benchmarks/history.py index 0149cab65319d..ae0e240ba6c96 100644 --- a/devops/scripts/benchmarks/history.py +++ b/devops/scripts/benchmarks/history.py @@ -131,7 +131,9 @@ def git_info_from_path(path: Path) -> (str, str): detect_res = DetectVersion.instance() compute_runtime = detect_res.get_compute_runtime_ver() if detect_res.get_compute_runtime_ver_cached() is None: - print("Warning: Could not find compute_runtime version via github tags API.") + print( + "Warning: Could not find compute_runtime version via github tags API." + ) else: compute_runtime = "unknown" diff --git a/devops/scripts/benchmarks/main.py b/devops/scripts/benchmarks/main.py index 36891b50d2032..10528aa5adb68 100755 --- a/devops/scripts/benchmarks/main.py +++ b/devops/scripts/benchmarks/main.py @@ -536,17 +536,19 @@ def validate_and_parse_env_args(env_args): "--detect-version", type=lambda components: Validate.on_re( components, - r'[a-z_,]+', - throw=argparse.ArgumentTypeError("Specified --detect-version is not a comma-separated list") + r"[a-z_,]+", + throw=argparse.ArgumentTypeError( + "Specified --detect-version is not a comma-separated list" + ), ), help="Detect versions of components used: comma-separated list with choices from sycl,compute_runtime", - default=None + default=None, ) parser.add_argument( "--detect-version-cpp-path", type=Path, help="Location of detect_version.cpp used to query e.g. DPC++, L0", - default=None + default=None, ) args = parser.parse_args() @@ -604,16 +606,22 @@ def validate_and_parse_env_args(env_args): if args.detect_version is not None: detect_ver_path = args.detect_version_cpp_path if detect_ver_path is None: - detect_ver_path = Path(f"{os.path.dirname(__file__)}/utils/detect_versions.cpp") + detect_ver_path = Path( + f"{os.path.dirname(__file__)}/utils/detect_versions.cpp" + ) if not detect_ver_path.is_file(): - parser.error(f"Unable to find detect_versions.cpp at {detect_ver_path}, please specify --detect-version-cpp-path") + parser.error( + f"Unable to find detect_versions.cpp at {detect_ver_path}, please specify --detect-version-cpp-path" + ) elif not detect_ver_path.is_file(): parser.error(f"Specified --detect-version-cpp-path is not a valid file") - enabled_components = args.detect_version.split(',') + enabled_components = args.detect_version.split(",") options.detect_versions.sycl = "sycl" in enabled_components - options.detect_versions.compute_runtime = "compute_runtime" in enabled_components - + options.detect_versions.compute_runtime = ( + "compute_runtime" in enabled_components + ) + detect_res = DetectVersion.init(detect_ver_path) benchmark_filter = re.compile(args.filter) if args.filter else None diff --git a/devops/scripts/benchmarks/options.py b/devops/scripts/benchmarks/options.py index e9309e1361f8a..c0b385ebead17 100644 --- a/devops/scripts/benchmarks/options.py +++ b/devops/scripts/benchmarks/options.py @@ -21,6 +21,7 @@ class DetectVersionsOptions: """ Options for automatic version detection """ + # Components to detect versions for: sycl: bool = False compute_runtime: bool = False @@ -30,11 +31,13 @@ class DetectVersionsOptions: # Placeholder text, should automatic version detection fail: This text will # only be used if automatic version detection for x component is explicitly # specified. - not_found_placeholder = "unknown" # None + not_found_placeholder = "unknown" # None # TODO unauthenticated users only get 60 API calls per hour: this will not # work if we enable benchmark CI in precommit. - compute_runtime_tag_api: str = "https://api.github.com/repos/intel/compute-runtime/tags" + compute_runtime_tag_api: str = ( + "https://api.github.com/repos/intel/compute-runtime/tags" + ) # Max amount of api calls permitted on each run of the benchmark scripts max_api_calls = 4 @@ -86,7 +89,9 @@ class Options: github_repo_override: str = None git_commit_override: str = None - detect_versions: DetectVersionsOptions = field(default_factory=DetectVersionsOptions) + detect_versions: DetectVersionsOptions = field( + default_factory=DetectVersionsOptions + ) options = Options() diff --git a/devops/scripts/benchmarks/utils/detect_versions.py b/devops/scripts/benchmarks/utils/detect_versions.py index 31827e5da0525..a8b8985f7f886 100644 --- a/devops/scripts/benchmarks/utils/detect_versions.py +++ b/devops/scripts/benchmarks/utils/detect_versions.py @@ -10,204 +10,210 @@ import argparse if __name__ == "__main__": - sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) from options import options + class DetectVersion: - _instance = None - - def __init__(self): - raise RuntimeError("Use init() to init and instance() to get instead.") - - @classmethod - def init(cls, detect_ver_path: Path, dpcpp_exec: str = "clang++"): - """ - Constructs the singleton instance for DetectVersion, and initializes by - building and run detect _version.cpp, which outputs: - - L0 driver version via ZE_intel_get_driver_version_string extension, - - DPC++ version via `__clang_version__` builtin. - - Remind: DO NOT allow user input in args. - - Parameters: - detect_ver_path (Path): Path to detect_version.cpp - dpcpp_exec (str): Name of DPC++ executable - """ - if cls._instance is not None: - return cls._instance - - detect_ver_exe = tempfile.mktemp() - result = subprocess.run( - [dpcpp_exec, "-lze_loader", detect_ver_path, "-o", detect_ver_exe], - check=True, - env=os.environ, - ) - result = subprocess.run( - [detect_ver_exe], - check=True, - text=True, - capture_output=True, - env=os.environ, - ) - # Variables are printed to stdout, each var is on its own line - result_vars = result.stdout.strip().split('\n') - - def get_var(var_name: str) -> str: - var_str = next( - filter(lambda v: re.match(f"^{var_name}='.*'", v), result_vars) - ) - return var_str[len(f"{var_name}='"):-len("'")] - - cls._instance = cls.__new__(cls) - cls._instance.l0_ver = get_var("L0_VER") - cls._instance.dpcpp_ver = get_var("DPCPP_VER") - cls._instance.dpcpp_exec = dpcpp_exec - # Do not make 2 API calls if compute_runtime_ver was already obtained - cls._instance.compute_runtime_ver_cache = None - return cls._instance - - @classmethod - def instance(cls): - """ - Returns singleton instance of DetectVersion if it has been initialized - via init(), otherwise return None. - """ - return cls._instance - - def get_l0_ver(self) -> str: - """ - Returns the full L0 version string. - """ - return self.l0_ver - - def get_dpcpp_ver(self) -> str: - """ - Returns the full DPC++ version / clang version string of DPC++ used. - """ - return self.dpcpp_ver - - def get_dpcpp_git_info(self) -> [str, str]: - """ - Returns: (git_repo, commit_hash) - """ - # clang++ formats are in ( ): if this - # regex does not match, it is likely this is not upstream clang. - git_info_match = re.search(r'\(http.+ [0-9a-f]+\)', self.dpcpp_ver) - if git_info_match is None: - raise RuntimeError(f"detect_version: Unable to obtain git info from {self.dpcpp_exec}, are you sure you are using DPC++?") - git_info = git_info_match.group(0) - return git_info[1:-1].split(' ') - - def get_dpcpp_commit(self) -> str: - git_info = self.get_dpcpp_git_info() - if git_info is None: - return options.detect_versions.not_found_placeholder - return git_info[1] - - def get_dpcpp_repo(self) -> str: - git_info = self.get_dpcpp_git_info() - if git_info is None: - return options.detect_versions.not_found_placeholder - return git_info[0] - - def get_compute_runtime_ver_cached(self) -> str: - return self.compute_runtime_ver_cache - - def get_compute_runtime_ver(self) -> str: - """ - Returns the compute-runtime version by deriving from l0 version. - """ - if self.compute_runtime_ver_cache is not None: - return self.compute_runtime_ver_cache - - # L0 version strings follows semver: major.minor.patch+optional - # compute-runtime version tags follow year.WW.patch.optional instead, - # but patch, pptional is still the same across both. - # - # We use patch+optional from L0 to figure out compute-runtime version: - patch = re.sub(r'^\d+\.\d+\.', '', self.l0_ver) - patch = re.sub(r'\+', '.', patch, count=1) - - # TODO unauthenticated users only get 60 API calls per hour: this will - # not work if we enable benchmark CI in precommit. - url = options.detect_versions.compute_runtime_tag_api - - try: - for _ in range(options.detect_versions.max_api_calls): - res = request.urlopen(url) - tags = [ tag["name"] for tag in json.loads(res.read()) ] - - for tag in tags: - tag_patch = re.sub(r'^\d+\.\d+\.', '', tag) - # compute-runtime's cmake files produces "optional" fields - # padded with 0's: this means e.g. L0 version string - # 1.6.32961.200000 could be either compute-runtime ver. - # 25.09.32961.2, 25.09.32961.20, or even 25.09.32961.200. - # - # Thus, we take the longest match. Since the github api - # provides tags from newer -> older, we take the first tag - # that matches as it would be the "longest" ver. to match. - if tag_patch == patch[:len(tag_patch)]: - self.compute_runtime_ver_cache = tag - return tag - - def get_link_name(link: str) -> str: - rel_str = re.search(r'rel="\w+"', link).group(0) - return rel_str[len('rel="'):-len('"')] - - def get_link_url(link: str) -> str: - return link[link.index('<')+1:link.index('>')] - - links = { - get_link_name(link): get_link_url(link) - for link in res.getheader("Link").split(", ") - } - - if "next" in links: - url = links["next"] - else: - break - - except urllib.error.HTTPError as e: - print(f"HTTP error {e.code}: {e.read().decode('utf-8')}") - - except urllib.error.URLError as e: - print(f"URL error: {e.reason}") - - return options.detect_versions.not_found_placeholder + _instance = None + + def __init__(self): + raise RuntimeError("Use init() to init and instance() to get instead.") + + @classmethod + def init(cls, detect_ver_path: Path, dpcpp_exec: str = "clang++"): + """ + Constructs the singleton instance for DetectVersion, and initializes by + building and run detect _version.cpp, which outputs: + - L0 driver version via ZE_intel_get_driver_version_string extension, + - DPC++ version via `__clang_version__` builtin. + + Remind: DO NOT allow user input in args. + + Parameters: + detect_ver_path (Path): Path to detect_version.cpp + dpcpp_exec (str): Name of DPC++ executable + """ + if cls._instance is not None: + return cls._instance + + detect_ver_exe = tempfile.mktemp() + result = subprocess.run( + [dpcpp_exec, "-lze_loader", detect_ver_path, "-o", detect_ver_exe], + check=True, + env=os.environ, + ) + result = subprocess.run( + [detect_ver_exe], + check=True, + text=True, + capture_output=True, + env=os.environ, + ) + # Variables are printed to stdout, each var is on its own line + result_vars = result.stdout.strip().split("\n") + + def get_var(var_name: str) -> str: + var_str = next( + filter(lambda v: re.match(f"^{var_name}='.*'", v), result_vars) + ) + return var_str[len(f"{var_name}='") : -len("'")] + + cls._instance = cls.__new__(cls) + cls._instance.l0_ver = get_var("L0_VER") + cls._instance.dpcpp_ver = get_var("DPCPP_VER") + cls._instance.dpcpp_exec = dpcpp_exec + # Do not make 2 API calls if compute_runtime_ver was already obtained + cls._instance.compute_runtime_ver_cache = None + return cls._instance + + @classmethod + def instance(cls): + """ + Returns singleton instance of DetectVersion if it has been initialized + via init(), otherwise return None. + """ + return cls._instance + + def get_l0_ver(self) -> str: + """ + Returns the full L0 version string. + """ + return self.l0_ver + + def get_dpcpp_ver(self) -> str: + """ + Returns the full DPC++ version / clang version string of DPC++ used. + """ + return self.dpcpp_ver + + def get_dpcpp_git_info(self) -> [str, str]: + """ + Returns: (git_repo, commit_hash) + """ + # clang++ formats are in ( ): if this + # regex does not match, it is likely this is not upstream clang. + git_info_match = re.search(r"\(http.+ [0-9a-f]+\)", self.dpcpp_ver) + if git_info_match is None: + raise RuntimeError( + f"detect_version: Unable to obtain git info from {self.dpcpp_exec}, are you sure you are using DPC++?" + ) + git_info = git_info_match.group(0) + return git_info[1:-1].split(" ") + + def get_dpcpp_commit(self) -> str: + git_info = self.get_dpcpp_git_info() + if git_info is None: + return options.detect_versions.not_found_placeholder + return git_info[1] + + def get_dpcpp_repo(self) -> str: + git_info = self.get_dpcpp_git_info() + if git_info is None: + return options.detect_versions.not_found_placeholder + return git_info[0] + + def get_compute_runtime_ver_cached(self) -> str: + return self.compute_runtime_ver_cache + + def get_compute_runtime_ver(self) -> str: + """ + Returns the compute-runtime version by deriving from l0 version. + """ + if self.compute_runtime_ver_cache is not None: + return self.compute_runtime_ver_cache + + # L0 version strings follows semver: major.minor.patch+optional + # compute-runtime version tags follow year.WW.patch.optional instead, + # but patch, pptional is still the same across both. + # + # We use patch+optional from L0 to figure out compute-runtime version: + patch = re.sub(r"^\d+\.\d+\.", "", self.l0_ver) + patch = re.sub(r"\+", ".", patch, count=1) + + # TODO unauthenticated users only get 60 API calls per hour: this will + # not work if we enable benchmark CI in precommit. + url = options.detect_versions.compute_runtime_tag_api + + try: + for _ in range(options.detect_versions.max_api_calls): + res = request.urlopen(url) + tags = [tag["name"] for tag in json.loads(res.read())] + + for tag in tags: + tag_patch = re.sub(r"^\d+\.\d+\.", "", tag) + # compute-runtime's cmake files produces "optional" fields + # padded with 0's: this means e.g. L0 version string + # 1.6.32961.200000 could be either compute-runtime ver. + # 25.09.32961.2, 25.09.32961.20, or even 25.09.32961.200. + # + # Thus, we take the longest match. Since the github api + # provides tags from newer -> older, we take the first tag + # that matches as it would be the "longest" ver. to match. + if tag_patch == patch[: len(tag_patch)]: + self.compute_runtime_ver_cache = tag + return tag + + def get_link_name(link: str) -> str: + rel_str = re.search(r'rel="\w+"', link).group(0) + return rel_str[len('rel="') : -len('"')] + + def get_link_url(link: str) -> str: + return link[link.index("<") + 1 : link.index(">")] + + links = { + get_link_name(link): get_link_url(link) + for link in res.getheader("Link").split(", ") + } + + if "next" in links: + url = links["next"] + else: + break + + except urllib.error.HTTPError as e: + print(f"HTTP error {e.code}: {e.read().decode('utf-8')}") + + except urllib.error.URLError as e: + print(f"URL error: {e.reason}") + + return options.detect_versions.not_found_placeholder def main(components: [str]): - detect_res = DetectVersion.init(f"{os.path.dirname(__file__)}/detect_versions.cpp") + detect_res = DetectVersion.init(f"{os.path.dirname(__file__)}/detect_versions.cpp") - str2fn = { - "dpcpp_repo": detect_res.get_dpcpp_repo, - "dpcpp_commit": detect_res.get_dpcpp_commit, - "l0_ver": detect_res.get_l0_ver, - "compute_runtime_ver": detect_res.get_compute_runtime_ver - } + str2fn = { + "dpcpp_repo": detect_res.get_dpcpp_repo, + "dpcpp_commit": detect_res.get_dpcpp_commit, + "l0_ver": detect_res.get_l0_ver, + "compute_runtime_ver": detect_res.get_compute_runtime_ver, + } - def remove_undefined_components(component: str) -> bool: - if component not in str2fn: - print(f"# Warn: unknown component: {component}", file=sys.stderr) - return False - return True + def remove_undefined_components(component: str) -> bool: + if component not in str2fn: + print(f"# Warn: unknown component: {component}", file=sys.stderr) + return False + return True - components_clean = filter(remove_undefined_components, components) + components_clean = filter(remove_undefined_components, components) - for s in map(lambda c: f"{c.upper()}={str2fn[c]()}", components_clean): - print(s) + for s in map(lambda c: f"{c.upper()}={str2fn[c]()}", components_clean): + print(s) if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Get version information for specified components.") + parser = argparse.ArgumentParser( + description="Get version information for specified components." + ) parser.add_argument( - "components", type=str, - help=""" + "components", + type=str, + help=""" Comma-separated list of components to get version information for. Valid options: dpcpp_repo,dpcpp_commit,l0_ver,compute_runtime_ver - """ - ) + """, + ) args = parser.parse_args() - main(map(lambda c: c.strip(), args.components.split(','))) + main(map(lambda c: c.strip(), args.components.split(","))) From 6915b3ea55e07d968958ba8d8a8754a1da1cc32d Mon Sep 17 00:00:00 2001 From: "Li, Ian" Date: Tue, 6 May 2025 11:41:27 -0700 Subject: [PATCH 15/16] Apply clang-format --- .../benchmarks/utils/detect_versions.cpp | 126 +++++++++--------- 1 file changed, 61 insertions(+), 65 deletions(-) diff --git a/devops/scripts/benchmarks/utils/detect_versions.cpp b/devops/scripts/benchmarks/utils/detect_versions.cpp index 0b4d169f8ecf2..b1de373054a24 100644 --- a/devops/scripts/benchmarks/utils/detect_versions.cpp +++ b/devops/scripts/benchmarks/utils/detect_versions.cpp @@ -1,86 +1,82 @@ +#include +#include #include #include -#include -#include #include -#define _assert(cond, msg) \ - if (!(cond)) { \ - std::cout << std::endl << "Error: " << msg << std::endl; \ - exit(1); \ - } +#define _assert(cond, msg) \ + if (!(cond)) { \ + std::cout << std::endl << "Error: " << msg << std::endl; \ + exit(1); \ + } #define _success(res) res == ZE_RESULT_SUCCESS -std::string query_dpcpp_ver() { - return std::string(__clang_version__); -} +std::string query_dpcpp_ver() { return std::string(__clang_version__); } std::string query_l0_driver_ver() { - // Initialize L0 drivers: - ze_init_driver_type_desc_t driver_type = {}; - driver_type.stype = ZE_STRUCTURE_TYPE_INIT_DRIVER_TYPE_DESC; - driver_type.flags = ZE_INIT_DRIVER_TYPE_FLAG_GPU; - driver_type.pNext = nullptr; + // Initialize L0 drivers: + ze_init_driver_type_desc_t driver_type = {}; + driver_type.stype = ZE_STRUCTURE_TYPE_INIT_DRIVER_TYPE_DESC; + driver_type.flags = ZE_INIT_DRIVER_TYPE_FLAG_GPU; + driver_type.pNext = nullptr; + + uint32_t driver_count = 0; + ze_result_t result = zeInitDrivers(&driver_count, nullptr, &driver_type); + _assert(_success(result), "Failed to initialize L0."); + _assert(driver_count > 0, "No L0 drivers available."); + + std::vector drivers(driver_count); + result = zeInitDrivers(&driver_count, drivers.data(), &driver_type); + _assert(_success(result), "Could not fetch L0 drivers."); - uint32_t driver_count = 0; - ze_result_t result = zeInitDrivers(&driver_count, nullptr, &driver_type); - _assert(_success(result), "Failed to initialize L0.") - _assert(driver_count > 0, "No L0 drivers available.") + // Check support for fetching driver version strings: + uint32_t ext_count = 0; + result = zeDriverGetExtensionProperties(drivers[0], &ext_count, nullptr); + _assert(_success(result), "Failed to obtain L0 extensions count."); + _assert(ext_count > 0, "No L0 extensions available."); - std::vector drivers(driver_count); - result = zeInitDrivers(&driver_count, drivers.data(), &driver_type); - _assert(_success(result), "Could not fetch L0 drivers.") + std::vector extensions(ext_count); + result = + zeDriverGetExtensionProperties(drivers[0], &ext_count, extensions.data()); + _assert(_success(result), "Failed to obtain L0 extensions."); + bool version_ext_support = false; + for (const auto &extension : extensions) { + // std::cout << extension.name << std::endl; + if (strcmp(extension.name, "ZE_intel_get_driver_version_string")) { + version_ext_support = true; + } + } + _assert(version_ext_support, + "ZE_intel_get_driver_version_string extension is not supported."); - // Check support for fetching driver version strings: - uint32_t ext_count = 0; - result = zeDriverGetExtensionProperties(drivers[0], &ext_count, nullptr); - _assert(_success(result), "Failed to obtain L0 extensions count.") - _assert(ext_count > 0, "No L0 extensions available.") - - std::vector extensions(ext_count); - result = zeDriverGetExtensionProperties(drivers[0], &ext_count, - extensions.data()); - _assert(_success(result), "Failed to obtain L0 extensions.") - bool version_ext_support = false; - for (const auto &extension : extensions) { - // std::cout << extension.name << std::endl; - if (strcmp(extension.name, "ZE_intel_get_driver_version_string")) { - version_ext_support = true; - } - } - _assert(version_ext_support, - "ZE_intel_get_driver_version_string extension is not supported."); + // Fetch L0 driver version: + ze_result_t (*pfnGetDriverVersionFn)(ze_driver_handle_t, char *, size_t *); + result = zeDriverGetExtensionFunctionAddress(drivers[0], + "zeIntelGetDriverVersionString", + (void **)&pfnGetDriverVersionFn); + _assert(_success(result), "Failed to obtain GetDriverVersionString fn."); - // Fetch L0 driver version: - ze_result_t (*pfnGetDriverVersionFn)(ze_driver_handle_t, char*, size_t*); - result = zeDriverGetExtensionFunctionAddress( - drivers[0], - "zeIntelGetDriverVersionString", - (void **) &pfnGetDriverVersionFn - ); - _assert(_success(result), "Failed to obtain GetDriverVersionString fn."); - - size_t ver_str_len = 0; - result = pfnGetDriverVersionFn(drivers[0], nullptr, &ver_str_len); - _assert(_success(result), "Call to GetDriverVersionString failed."); + size_t ver_str_len = 0; + result = pfnGetDriverVersionFn(drivers[0], nullptr, &ver_str_len); + _assert(_success(result), "Call to GetDriverVersionString failed."); - std::cout << "ver_str_len: " << ver_str_len << std::endl; - ver_str_len ++; // ver_str_len does not account for '\0' - char *ver_str = (char *) calloc(ver_str_len, sizeof(char)); - result = pfnGetDriverVersionFn(drivers[0], ver_str, &ver_str_len); - _assert(_success(result), "Failed to write driver version string."); + std::cout << "ver_str_len: " << ver_str_len << std::endl; + ver_str_len++; // ver_str_len does not account for '\0' + char *ver_str = (char *)calloc(ver_str_len, sizeof(char)); + result = pfnGetDriverVersionFn(drivers[0], ver_str, &ver_str_len); + _assert(_success(result), "Failed to write driver version string."); - std::string res(ver_str); - free(ver_str); - return res; + std::string res(ver_str); + free(ver_str); + return res; } int main() { - std::string dpcpp_ver = query_dpcpp_ver(); - std::cout << "DPCPP_VER='" << dpcpp_ver << "'" << std::endl; + std::string dpcpp_ver = query_dpcpp_ver(); + std::cout << "DPCPP_VER='" << dpcpp_ver << "'" << std::endl; - std::string l0_ver = query_l0_driver_ver(); - std::cout << "L0_VER='" << l0_ver << "'" << std::endl; + std::string l0_ver = query_l0_driver_ver(); + std::cout << "L0_VER='" << l0_ver << "'" << std::endl; } From 2bb49599a117e6919f362c2a6f32701ae5ee3feb Mon Sep 17 00:00:00 2001 From: "Li, Ian" Date: Wed, 7 May 2025 14:44:13 -0700 Subject: [PATCH 16/16] Add a way to predefine a cache beforehand --- .../benchmarks/utils/detect_versions.py | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/devops/scripts/benchmarks/utils/detect_versions.py b/devops/scripts/benchmarks/utils/detect_versions.py index a8b8985f7f886..c3c05b8fdcbe5 100644 --- a/devops/scripts/benchmarks/utils/detect_versions.py +++ b/devops/scripts/benchmarks/utils/detect_versions.py @@ -14,6 +14,17 @@ from options import options +def _get_patch_from_ver(ver: str) -> str: + """Extract patch from a version string.""" + # L0 version strings follows semver: major.minor.patch+optional + # compute-runtime version tags follow year.WW.patch.optional instead, + # but both follow a quasi-semver versioning where the patch, optional + # is still the same across both version string formats. + patch = re.sub(r"^\d+\.\d+\.", "", ver) + patch = re.sub(r"\+", ".", patch, count=1) + return patch + + class DetectVersion: _instance = None @@ -63,8 +74,22 @@ def get_var(var_name: str) -> str: cls._instance.l0_ver = get_var("L0_VER") cls._instance.dpcpp_ver = get_var("DPCPP_VER") cls._instance.dpcpp_exec = dpcpp_exec - # Do not make 2 API calls if compute_runtime_ver was already obtained + + # Populate the computer-runtime version string cache: Since API calls + # are expensive, we want to avoid API calls when possible, i.e.: + # - Avoid a second API call if compute_runtime_ver was already obtained + # - Avoid an API call altogether if the user provides a valid + # COMPUTE_RUNTIME_TAG_CACHE environment variable. cls._instance.compute_runtime_ver_cache = None + l0_ver_patch = _get_patch_from_ver(get_var("L0_VER")) + env_cache_ver = os.getenv("COMPUTE_RUNTIME_TAG_CACHE", default="") + env_cache_patch = _get_patch_from_ver(env_cache_ver) + # L0 patch often gets padded with 0's: if the environment variable + # matches up with the prefix of the l0 version patch, the cache is + # indeed referring to the same version. + if env_cache_patch == l0_ver_patch[: len(env_cache_patch)]: + cls._instance.compute_runtime_ver_cache = env_cache_ver + return cls._instance @classmethod @@ -123,13 +148,7 @@ def get_compute_runtime_ver(self) -> str: if self.compute_runtime_ver_cache is not None: return self.compute_runtime_ver_cache - # L0 version strings follows semver: major.minor.patch+optional - # compute-runtime version tags follow year.WW.patch.optional instead, - # but patch, pptional is still the same across both. - # - # We use patch+optional from L0 to figure out compute-runtime version: - patch = re.sub(r"^\d+\.\d+\.", "", self.l0_ver) - patch = re.sub(r"\+", ".", patch, count=1) + patch = _get_patch_from_ver(self.l0_ver) # TODO unauthenticated users only get 60 API calls per hour: this will # not work if we enable benchmark CI in precommit. @@ -141,7 +160,7 @@ def get_compute_runtime_ver(self) -> str: tags = [tag["name"] for tag in json.loads(res.read())] for tag in tags: - tag_patch = re.sub(r"^\d+\.\d+\.", "", tag) + tag_patch = _get_patch_from_ver(tag) # compute-runtime's cmake files produces "optional" fields # padded with 0's: this means e.g. L0 version string # 1.6.32961.200000 could be either compute-runtime ver.