Skip to content

Commit 101c8d8

Browse files
authored
[CI][Benchmarks] Automatically detect component versions in benchmark CI (#18339)
Previously, there was no way to figure out what version of compute-runtime we were using. This PR: - adds a way to detect compute-runtime version via looking up L0 patch version in compute-runtime releases - adds a way to pre-specify a compute-runtime version that should be used before looking up L0 patch in compute-runtime releases - additionally, adds a way to automatically detect SYCL version via `__clang_version__`
1 parent fd1342e commit 101c8d8

File tree

8 files changed

+465
-70
lines changed

8 files changed

+465
-70
lines changed

devops/actions/run-tests/benchmark/action.yml

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -110,19 +110,6 @@ runs:
110110
pip install --user --break-system-packages -r ./devops/scripts/benchmarks/requirements.txt
111111
echo "-----"
112112
113-
# clang builds have git repo / commit hashes in their --version output,
114-
# same goes for dpcpp. Obtain git repo / commit hash info this way:
115-
116-
# First line of --version is formatted 'clang version ... (<repo> <commit>)'
117-
# thus we parse for (<repo> <commit>):
118-
sycl_git_info="$(clang++ --version | head -n 1 | grep -oE '\([^ ]+ [a-f0-9]+\)$' | tr -d '()')"
119-
if [ -z "$sycl_git_info" ]; then
120-
echo "Error: Unable to deduce SYCL build source repo/commit: Are you sure dpcpp variable is in PATH?"
121-
exit 1
122-
fi
123-
sycl_git_repo="$(printf "$sycl_git_info" | cut -d' ' -f1)"
124-
sycl_git_commit="$(printf "$sycl_git_info" | cut -d' ' -f2)"
125-
126113
# By default, the benchmark scripts forceload level_zero
127114
FORCELOAD_ADAPTER="${ONEAPI_DEVICE_SELECTOR%%:*}"
128115
echo "Adapter: $FORCELOAD_ADAPTER"
@@ -149,6 +136,12 @@ runs:
149136
SAVE_NAME="${SAVE_PREFIX}_${MACHINE_TYPE}_${SAVE_SUFFIX}"
150137
echo "SAVE_NAME=$SAVE_NAME" >> $GITHUB_ENV
151138
SAVE_TIMESTAMP="$(date -u +'%Y%m%d_%H%M%S')" # Timestamps are in UTC time
139+
140+
# Cache the compute_runtime version from dependencies.json, but perform a
141+
# check with L0 version before using it: This value is not guaranteed to
142+
# accurately reflect the current compute_runtime version used, as the
143+
# docker images are built nightly.
144+
export COMPUTE_RUNTIME_TAG_CACHE="$(cat ./devops/dependencies.json | jq -r .linux.compute_runtime.github_tag)"
152145

153146
sycl-ls
154147
echo "-----"
@@ -163,8 +156,7 @@ runs:
163156
--output-dir "./llvm-ci-perf-results/" \
164157
--preset "$PRESET" \
165158
--timestamp-override "$SAVE_TIMESTAMP" \
166-
--github-repo "$sycl_git_repo" \
167-
--git-commit "$sycl_git_commit"
159+
--detect-version sycl,compute_runtime
168160
echo "-----"
169161
python3 ./devops/scripts/benchmarks/compare.py to_hist \
170162
--name "$SAVE_NAME" \

devops/scripts/benchmarks/compare.py

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@ def validate_benchmark_result(result: BenchmarkRun) -> bool:
145145

146146
def reset_aggregate() -> dict:
147147
return {
148+
# TODO compare determine which command args have an
149+
# impact on perf results, and do not compare arg results
150+
# are incomparable
148151
"command_args": set(test_run.command[1:]),
149152
"aggregate": aggregator(starting_elements=[test_run.value]),
150153
}
@@ -153,27 +156,7 @@ def reset_aggregate() -> dict:
153156
if test_run.name not in average_aggregate:
154157
average_aggregate[test_run.name] = reset_aggregate()
155158
else:
156-
# Check that we are comparing runs with the same cmd args:
157-
if (
158-
set(test_run.command[1:])
159-
== average_aggregate[test_run.name]["command_args"]
160-
):
161-
average_aggregate[test_run.name]["aggregate"].add(
162-
test_run.value
163-
)
164-
else:
165-
# If the command args used between runs are different,
166-
# discard old run data and prefer new command args
167-
#
168-
# This relies on the fact that paths from get_result_paths()
169-
# is sorted from older to newer
170-
print(
171-
f"Warning: Command args for {test_run.name} from {result_path} is different from prior runs."
172-
)
173-
print(
174-
"DISCARDING older data and OVERRIDING with data using new arg."
175-
)
176-
average_aggregate[test_run.name] = reset_aggregate()
159+
average_aggregate[test_run.name]["aggregate"].add(test_run.value)
177160

178161
return {
179162
name: BenchmarkHistoricAverage(
@@ -217,9 +200,9 @@ def halfway_round(value: int, n: int):
217200
for test in target.results:
218201
if test.name not in hist_avg:
219202
continue
220-
if hist_avg[test.name].command_args != set(test.command[1:]):
221-
print(f"Warning: skipped {test.name} due to command args mismatch.")
222-
continue
203+
# TODO compare command args which have an impact on performance
204+
# (i.e. ignore --save-name): if command results are incomparable,
205+
# skip the result.
223206

224207
delta = 1 - (
225208
test.value / hist_avg[test.name].value

devops/scripts/benchmarks/history.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
from utils.utils import run
1414
from utils.validate import Validate
1515

16+
from utils.detect_versions import DetectVersion
17+
1618

1719
class BenchmarkHistory:
1820
runs = []
@@ -94,9 +96,13 @@ def git_info_from_path(path: Path) -> (str, str):
9496
return git_hash, github_repo
9597

9698
if options.git_commit_override is None or options.github_repo_override is None:
97-
git_hash, github_repo = git_info_from_path(
98-
os.path.dirname(os.path.abspath(__file__))
99-
)
99+
if options.detect_versions.sycl:
100+
print(f"Auto-detecting sycl version...")
101+
github_repo, git_hash = DetectVersion.instance().get_dpcpp_git_info()
102+
else:
103+
git_hash, github_repo = git_info_from_path(
104+
os.path.dirname(os.path.abspath(__file__))
105+
)
100106
else:
101107
git_hash, github_repo = (
102108
options.git_commit_override,
@@ -119,9 +125,19 @@ def git_info_from_path(path: Path) -> (str, str):
119125
throw=ValueError("Illegal characters found in specified RUNNER_NAME."),
120126
)
121127

122-
compute_runtime = (
123-
options.compute_runtime_tag if options.build_compute_runtime else ""
124-
)
128+
compute_runtime = None
129+
if options.build_compute_runtime:
130+
compute_runtime = options.compute_runtime_tag
131+
elif options.detect_versions.compute_runtime:
132+
print(f"Auto-detecting compute_runtime version...")
133+
detect_res = DetectVersion.instance()
134+
compute_runtime = detect_res.get_compute_runtime_ver()
135+
if detect_res.get_compute_runtime_ver_cached() is None:
136+
print(
137+
"Warning: Could not find compute_runtime version via github tags API."
138+
)
139+
else:
140+
compute_runtime = "unknown"
125141

126142
return BenchmarkRun(
127143
name=name,

devops/scripts/benchmarks/main.py

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@
1919
from utils.utils import prepare_workdir
2020
from utils.compute_runtime import *
2121
from utils.validate import Validate
22+
from utils.detect_versions import DetectVersion
2223
from presets import enabled_suites, presets
2324

2425
import argparse
2526
import re
2627
import statistics
28+
import os
2729

2830
# Update this if you are changing the layout of the results files
2931
INTERNAL_WORKDIR_VERSION = "2.0"
@@ -506,7 +508,7 @@ def validate_and_parse_env_args(env_args):
506508
type=lambda ts: Validate.timestamp(
507509
ts,
508510
throw=argparse.ArgumentTypeError(
509-
"Specified timestamp not in YYYYMMDD_HHMMSS format."
511+
"Specified timestamp not in YYYYMMDD_HHMMSS format"
510512
),
511513
),
512514
help="Manually specify timestamp used in metadata",
@@ -517,7 +519,7 @@ def validate_and_parse_env_args(env_args):
517519
type=lambda gh_repo: Validate.github_repo(
518520
gh_repo,
519521
throw=argparse.ArgumentTypeError(
520-
"Specified github repo not in <owner>/<repo> format."
522+
"Specified github repo not in <owner>/<repo> format"
521523
),
522524
),
523525
help="Manually specify github repo metadata of component tested (e.g. SYCL, UMF)",
@@ -528,13 +530,32 @@ def validate_and_parse_env_args(env_args):
528530
type=lambda commit: Validate.commit_hash(
529531
commit,
530532
throw=argparse.ArgumentTypeError(
531-
"Specified commit is not a valid commit hash."
533+
"Specified commit is not a valid commit hash"
532534
),
533535
),
534536
help="Manually specify commit hash metadata of component tested (e.g. SYCL, UMF)",
535537
default=options.git_commit_override,
536538
)
537539

540+
parser.add_argument(
541+
"--detect-version",
542+
type=lambda components: Validate.on_re(
543+
components,
544+
r"[a-z_,]+",
545+
throw=argparse.ArgumentTypeError(
546+
"Specified --detect-version is not a comma-separated list"
547+
),
548+
),
549+
help="Detect versions of components used: comma-separated list with choices from sycl,compute_runtime",
550+
default=None,
551+
)
552+
parser.add_argument(
553+
"--detect-version-cpp-path",
554+
type=Path,
555+
help="Location of detect_version.cpp used to query e.g. DPC++, L0",
556+
default=None,
557+
)
558+
538559
args = parser.parse_args()
539560
additional_env_vars = validate_and_parse_env_args(args.env)
540561

@@ -586,6 +607,28 @@ def validate_and_parse_env_args(env_args):
586607
options.github_repo_override = args.github_repo
587608
options.git_commit_override = args.git_commit
588609

610+
# Automatically detect versions:
611+
if args.detect_version is not None:
612+
detect_ver_path = args.detect_version_cpp_path
613+
if detect_ver_path is None:
614+
detect_ver_path = Path(
615+
f"{os.path.dirname(__file__)}/utils/detect_versions.cpp"
616+
)
617+
if not detect_ver_path.is_file():
618+
parser.error(
619+
f"Unable to find detect_versions.cpp at {detect_ver_path}, please specify --detect-version-cpp-path"
620+
)
621+
elif not detect_ver_path.is_file():
622+
parser.error(f"Specified --detect-version-cpp-path is not a valid file")
623+
624+
enabled_components = args.detect_version.split(",")
625+
options.detect_versions.sycl = "sycl" in enabled_components
626+
options.detect_versions.compute_runtime = (
627+
"compute_runtime" in enabled_components
628+
)
629+
630+
detect_res = DetectVersion.init(detect_ver_path)
631+
589632
benchmark_filter = re.compile(args.filter) if args.filter else None
590633

591634
main(

devops/scripts/benchmarks/options.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,31 @@ class MarkdownSize(Enum):
1616
FULL = "full"
1717

1818

19+
@dataclass
20+
class DetectVersionsOptions:
21+
"""
22+
Options for automatic version detection
23+
"""
24+
25+
# Components to detect versions for:
26+
sycl: bool = False
27+
compute_runtime: bool = False
28+
# umf: bool = False
29+
# level_zero: bool = False
30+
31+
# Placeholder text, should automatic version detection fail: This text will
32+
# only be used if automatic version detection for x component is explicitly
33+
# specified.
34+
not_found_placeholder = "unknown" # None
35+
36+
# TODO unauthenticated users only get 60 API calls per hour: this will not
37+
# work if we enable benchmark CI in precommit.
38+
compute_runtime_tag_api: str = (
39+
"https://api.github.com/repos/intel/compute-runtime/tags"
40+
)
41+
# Max amount of api calls permitted on each run of the benchmark scripts
42+
max_api_calls = 4
43+
1944
@dataclass
2045
class Options:
2146
workdir: str = None
@@ -64,5 +89,9 @@ class Options:
6489
github_repo_override: str = None
6590
git_commit_override: str = None
6691

92+
detect_versions: DetectVersionsOptions = field(
93+
default_factory=DetectVersionsOptions
94+
)
95+
6796

6897
options = Options()
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#include <cstdlib>
2+
#include <cstring>
3+
#include <iostream>
4+
#include <vector>
5+
6+
#include <level_zero/ze_api.h>
7+
8+
#define _assert(cond, msg) \
9+
if (!(cond)) { \
10+
std::cout << std::endl << "Error: " << msg << std::endl; \
11+
exit(1); \
12+
}
13+
14+
#define _success(res) res == ZE_RESULT_SUCCESS
15+
16+
std::string query_dpcpp_ver() { return std::string(__clang_version__); }
17+
18+
std::string query_l0_driver_ver() {
19+
// Initialize L0 drivers:
20+
ze_init_driver_type_desc_t driver_type = {};
21+
driver_type.stype = ZE_STRUCTURE_TYPE_INIT_DRIVER_TYPE_DESC;
22+
driver_type.flags = ZE_INIT_DRIVER_TYPE_FLAG_GPU;
23+
driver_type.pNext = nullptr;
24+
25+
uint32_t driver_count = 0;
26+
ze_result_t result = zeInitDrivers(&driver_count, nullptr, &driver_type);
27+
_assert(_success(result), "Failed to initialize L0.");
28+
_assert(driver_count > 0, "No L0 drivers available.");
29+
30+
std::vector<ze_driver_handle_t> drivers(driver_count);
31+
result = zeInitDrivers(&driver_count, drivers.data(), &driver_type);
32+
_assert(_success(result), "Could not fetch L0 drivers.");
33+
34+
// Check support for fetching driver version strings:
35+
uint32_t ext_count = 0;
36+
result = zeDriverGetExtensionProperties(drivers[0], &ext_count, nullptr);
37+
_assert(_success(result), "Failed to obtain L0 extensions count.");
38+
_assert(ext_count > 0, "No L0 extensions available.");
39+
40+
std::vector<ze_driver_extension_properties_t> extensions(ext_count);
41+
result =
42+
zeDriverGetExtensionProperties(drivers[0], &ext_count, extensions.data());
43+
_assert(_success(result), "Failed to obtain L0 extensions.");
44+
bool version_ext_support = false;
45+
for (const auto &extension : extensions) {
46+
// std::cout << extension.name << std::endl;
47+
if (strcmp(extension.name, "ZE_intel_get_driver_version_string")) {
48+
version_ext_support = true;
49+
}
50+
}
51+
_assert(version_ext_support,
52+
"ZE_intel_get_driver_version_string extension is not supported.");
53+
54+
// Fetch L0 driver version:
55+
ze_result_t (*pfnGetDriverVersionFn)(ze_driver_handle_t, char *, size_t *);
56+
result = zeDriverGetExtensionFunctionAddress(drivers[0],
57+
"zeIntelGetDriverVersionString",
58+
(void **)&pfnGetDriverVersionFn);
59+
_assert(_success(result), "Failed to obtain GetDriverVersionString fn.");
60+
61+
size_t ver_str_len = 0;
62+
result = pfnGetDriverVersionFn(drivers[0], nullptr, &ver_str_len);
63+
_assert(_success(result), "Call to GetDriverVersionString failed.");
64+
65+
std::cout << "ver_str_len: " << ver_str_len << std::endl;
66+
ver_str_len++; // ver_str_len does not account for '\0'
67+
char *ver_str = (char *)calloc(ver_str_len, sizeof(char));
68+
result = pfnGetDriverVersionFn(drivers[0], ver_str, &ver_str_len);
69+
_assert(_success(result), "Failed to write driver version string.");
70+
71+
std::string res(ver_str);
72+
free(ver_str);
73+
return res;
74+
}
75+
76+
int main() {
77+
std::string dpcpp_ver = query_dpcpp_ver();
78+
std::cout << "DPCPP_VER='" << dpcpp_ver << "'" << std::endl;
79+
80+
std::string l0_ver = query_l0_driver_ver();
81+
std::cout << "L0_VER='" << l0_ver << "'" << std::endl;
82+
}

0 commit comments

Comments
 (0)