Skip to content

[CI][Benchmarks] Automatically detect component versions in benchmark CI #18339

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 30 commits into
base: sycl
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
eb4e123
Add L0 driver code for detecting compute-runtime versions
ianayl Apr 30, 2025
86263e1
instrument main with detect_versions
ianayl May 1, 2025
b21be5f
change wording, add frontend
ianayl May 1, 2025
0c77780
Fix bug
ianayl May 1, 2025
274be61
Hook up benchmark script to detect_versions
ianayl May 1, 2025
d8ab622
test changes in ci
ianayl May 2, 2025
4cb45ce
Fix bug
ianayl May 2, 2025
3648a35
Fix bug
ianayl May 2, 2025
7d927f7
Remove test code
ianayl May 5, 2025
a447867
Remove more checking for args
ianayl May 5, 2025
c67e052
Remove some odd choices
ianayl May 6, 2025
d611471
Merge branch 'sycl' of https://github.com/intel/llvm into ianayl/benc…
ianayl May 6, 2025
a07c211
add newline
ianayl May 6, 2025
3b0c996
Fix bug
ianayl May 6, 2025
233c062
darker format python
ianayl May 6, 2025
6915b3e
Apply clang-format
ianayl May 6, 2025
2bb4959
Add a way to predefine a cache beforehand
ianayl May 7, 2025
9fa6a1c
Add to workflow for testing
ianayl May 21, 2025
b651ca1
Remove debug messages
ianayl May 21, 2025
083cff0
Fix spelling
ianayl May 21, 2025
5b02887
apply clang-format
ianayl May 21, 2025
54bb593
Add logging for detect versions
ianayl May 22, 2025
c0949da
fix indent
ianayl May 22, 2025
a1c75cd
fix typo
ianayl May 23, 2025
633f51e
better signifier we are detecting versions
ianayl May 28, 2025
23201fe
More logging for detect versions
ianayl May 28, 2025
8a29481
More logging for detect versions
ianayl May 28, 2025
12cdbf5
I broke darker
ianayl May 28, 2025
2de4b33
apply formatting
ianayl May 29, 2025
2dd72e4
Merge branch 'sycl' of https://github.com/intel/llvm into ianayl/benc…
ianayl May 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 1 addition & 15 deletions devops/actions/run-tests/benchmark/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 ... (<repo> <commit>)'
# thus we parse for (<repo> <commit>):
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" ;;
Expand All @@ -142,8 +129,7 @@ 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 sycl,compute_runtime
echo "-----"
python3 ./devops/scripts/benchmarks/compare.py to_hist \
--name "$SAVE_NAME" \
Expand Down
31 changes: 7 additions & 24 deletions devops/scripts/benchmarks/compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]),
}
Expand All @@ -153,27 +156,7 @@ 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(
Expand Down Expand Up @@ -217,9 +200,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
Expand Down
26 changes: 20 additions & 6 deletions devops/scripts/benchmarks/history.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
from utils.utils import run
from utils.validate import Validate

from utils.detect_versions import DetectVersion


class BenchmarkHistory:
runs = []
Expand Down Expand Up @@ -94,9 +96,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,
Expand All @@ -119,9 +124,18 @@ 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 ""
)
compute_runtime = None
if options.build_compute_runtime:
compute_runtime = options.compute_runtime_tag
elif options.detect_versions.compute_runtime:
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:
compute_runtime = "unknown"

return BenchmarkRun(
name=name,
Expand Down
49 changes: 46 additions & 3 deletions devops/scripts/benchmarks/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -501,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",
Expand All @@ -512,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 <owner>/<repo> format."
"Specified github repo not in <owner>/<repo> format"
),
),
help="Manually specify github repo metadata of component tested (e.g. SYCL, UMF)",
Expand All @@ -523,13 +525,32 @@ 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)",
default=options.git_commit_override,
)

parser.add_argument(
"--detect-version",
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 components used: comma-separated list with choices from sycl,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=None,
)
Comment on lines +552 to +557
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does this need to be an argument? We know where this file resides (f"{os.path.dirname(__file__)}/utils/detect_versions.cpp").

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly for incase it is not found where it should be / we want to supply a different one, but maybe I am overcomplicating this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe I am overcomplicating this

;-)
We should do the absolute minimum that does the job.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's already been implemented, isn't it more work to remove it? 😅


args = parser.parse_args()
additional_env_vars = validate_and_parse_env_args(args.env)

Expand Down Expand Up @@ -581,6 +602,28 @@ 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:
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"
)
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"
)
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(",")
options.detect_versions.sycl = "sycl" 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

main(
Expand Down
29 changes: 29 additions & 0 deletions devops/scripts/benchmarks/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,31 @@ class MarkdownSize(Enum):
FULL = "full"


@dataclass
class DetectVersionsOptions:
"""
Options for automatic version detection
"""

# 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 component 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 amount of api calls permitted on each run of the benchmark scripts
max_api_calls = 4

@dataclass
class Options:
workdir: str = None
Expand Down Expand Up @@ -64,5 +89,9 @@ class Options:
github_repo_override: str = None
git_commit_override: str = None

detect_versions: DetectVersionsOptions = field(
default_factory=DetectVersionsOptions
)


options = Options()
82 changes: 82 additions & 0 deletions devops/scripts/benchmarks/utils/detect_versions.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <vector>

#include <level_zero/ze_api.h>

#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<ze_driver_handle_t> 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<ze_driver_extension_properties_t> 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;
}
Loading
Loading