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 17 commits into
base: sycl
Choose a base branch
from
Open
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 +547 to +552
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.


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