blob: efa168ca2dba5d5ff6b3292a4f5907ad20318114 [file] [log] [blame]
# Copyright 2022 The IREE Authors
#
# Licensed under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
import json
import pathlib
import time
from typing import List, Optional, Sequence, Set, Tuple
from common.benchmark_suite import BenchmarkCase, BenchmarkSuite
from common.benchmark_config import BenchmarkConfig
from common.benchmark_definition import (BenchmarkInfo, BenchmarkResults,
BenchmarkMetrics, BenchmarkRun,
DeviceInfo)
class BenchmarkDriver(object):
"""Abstract driver runs the whole benchmark flow."""
def __init__(self,
device_info: DeviceInfo,
benchmark_config: BenchmarkConfig,
benchmark_suite: BenchmarkSuite,
benchmark_grace_time: float = 0.0,
verbose: bool = False):
self.device_info = device_info
self.config = benchmark_config
self.benchmark_suite = benchmark_suite
self.benchmark_grace_time = benchmark_grace_time
self.verbose = verbose
self.finished_benchmarks: List[Tuple[BenchmarkInfo, pathlib.Path]] = []
self.finished_captures: List[pathlib.Path] = []
self.benchmark_errors = []
self._seen_benchmark_names: Set[str] = set()
def run_benchmark_case(self, benchmark_case: BenchmarkCase,
benchmark_results_filename: Optional[pathlib.Path],
capture_filename: Optional[pathlib.Path]) -> None:
"""Runs the benchmark case and serializes the results.
Args:
benchmark_case: the benchmark_case.
benchmark_results_filename: the path to store the serialized
BenchmarkMetrics. Benchmarking is required if set.
capture_filename: the path to store captured trace. Trace capturing is
required if set.
Raises:
Exception during benchmarking.
"""
raise NotImplementedError("Should be overwritten by a subclass.")
def run(self) -> None:
"""Execute the benchmark flow.
It performs the following steps:
1. Enumerate and filter benchmark cases.
2. Call 'run_benchmark_case' for each benchmark case.
3. Collect the benchmark results and captures.
"""
self.config.benchmark_results_dir.mkdir(parents=True, exist_ok=True)
if self.config.trace_capture_config is not None:
self.config.trace_capture_config.capture_tmp_dir.mkdir(parents=True,
exist_ok=True)
cpu_target_arch = self.device_info.get_cpu_arch()
gpu_target_arch = self.device_info.get_gpu_arch()
detected_architectures = [
arch for arch in [cpu_target_arch, gpu_target_arch] if arch is not None
]
if self.config.use_compatible_filter:
if cpu_target_arch is None:
print("INFO: Detected unsupported CPU architecture in"
f' "{self.device_info}", CPU benchmarking is disabled.')
if gpu_target_arch is None:
print("INFO: Detected unsupported GPU architecture in"
f' "{self.device_info}", GPU benchmarking is disabled.')
compatible_arch_filter = detected_architectures
else:
# No compatible filter on the target architectures.
compatible_arch_filter = None
drivers, loaders = self.__get_available_drivers_and_loaders()
benchmark_cases = self.benchmark_suite.filter_benchmarks(
available_drivers=drivers,
available_loaders=loaders,
target_architectures=compatible_arch_filter,
driver_filter=self.config.driver_filter,
mode_filter=self.config.mode_filter,
model_name_filter=self.config.model_name_filter)
for benchmark_case in benchmark_cases:
benchmark_info = self.__get_benchmark_info_from_case(
benchmark_case=benchmark_case)
benchmark_name = str(benchmark_info)
if benchmark_case.target_arch not in detected_architectures:
print(f"WARNING: Benchmark '{benchmark_name}' may be incompatible"
f" with the detected architectures '{detected_architectures}'"
f" on the device. Pass --compatible-only to skip incompatible"
f" benchmarks.")
# Sanity check for the uniqueness of benchmark names.
if benchmark_name in self._seen_benchmark_names:
raise ValueError(
f"Found duplicate benchmark {benchmark_name} in the suites.")
self._seen_benchmark_names.add(benchmark_name)
results_path, capture_path = self.__get_output_paths(benchmark_name)
# If we continue from the previous results, check and skip if the result
# files exist.
if self.config.continue_from_previous:
if results_path is not None and results_path.exists():
self.finished_benchmarks.append((benchmark_info, results_path))
results_path = None
if capture_path is not None and capture_path.exists():
self.finished_captures.append(capture_path)
capture_path = None
# Skip if no need to benchmark and capture.
if results_path is None and capture_path is None:
continue
print(f"--> Benchmark started: {benchmark_name} <--")
try:
self.run_benchmark_case(benchmark_case, results_path, capture_path)
except Exception as e:
# Delete unfinished results if they exist.
if results_path is not None:
results_path.unlink(missing_ok=True)
if capture_path is not None:
capture_path.unlink(missing_ok=True)
if not self.config.keep_going:
raise e
print(f"Processing of benchmark failed with: {e}")
self.benchmark_errors.append(e)
continue
finally:
# Some grace time.
time.sleep(self.benchmark_grace_time)
print("Benchmark completed")
if results_path:
self.finished_benchmarks.append((benchmark_info, results_path))
if capture_path:
self.finished_captures.append(capture_path)
def get_benchmark_results(self) -> BenchmarkResults:
"""Returns the finished benchmark results."""
results = BenchmarkResults()
results.set_commit(self.config.git_commit_hash)
finished_benchmarks = sorted(self.finished_benchmarks,
key=lambda pair: str(pair[0]))
for info, path in finished_benchmarks:
benchmark_metrics_json_object = json.loads(path.read_text())
benchmark_run = BenchmarkRun(info=info,
metrics=BenchmarkMetrics.from_json_object(
benchmark_metrics_json_object))
results.benchmarks.append(benchmark_run)
return results
def get_benchmark_result_filenames(self) -> Sequence[pathlib.Path]:
"""Returns the json file paths of finished benchmarks."""
return [path for info, path in self.finished_benchmarks]
def get_capture_filenames(self) -> Sequence[pathlib.Path]:
"""Returns the tracy file paths of finished captures."""
return self.finished_captures
def get_benchmark_errors(self):
"""Returns the exceptions captured during benchmarking."""
return self.benchmark_errors
def __get_output_paths(self, benchmark_name: str):
"""Get output paths for the results and capture. The path of results/capture
is None if the benchmark/capture doesn't need to be run.
"""
benchmark_results_filename = None
if self.config.normal_benchmark_tool_dir:
benchmark_results_filename = self.config.benchmark_results_dir / f"{benchmark_name}.json"
capture_filename = None
if self.config.trace_capture_config:
capture_filename = self.config.trace_capture_config.capture_tmp_dir / f"{benchmark_name}.tracy"
return (benchmark_results_filename, capture_filename)
def __get_benchmark_info_from_case(
self, benchmark_case: BenchmarkCase) -> BenchmarkInfo:
run_config = benchmark_case.run_config
run_tags = run_config.module_execution_config.tags
gen_config = run_config.module_generation_config
model_source = str(gen_config.imported_model.model.source_type)
compile_tags = gen_config.compile_config.tags
return BenchmarkInfo(name=run_config.name,
model_name=benchmark_case.model_name,
model_tags=benchmark_case.model_tags,
model_source=model_source,
bench_mode=run_tags,
compile_tags=compile_tags,
driver_info=benchmark_case.driver_info,
device_info=self.device_info,
run_config_id=run_config.composite_id)
def __get_available_drivers_and_loaders(
self) -> Tuple[Sequence[str], Sequence[str]]:
any_tool_dir = (self.config.normal_benchmark_tool_dir
if self.config.normal_benchmark_tool_dir else
self.config.trace_capture_config.traced_benchmark_tool_dir)
config_txt_file_path = any_tool_dir / "build_config.txt"
config_txt_file_lines = config_txt_file_path.read_text().splitlines()
available_drivers = []
available_loaders = []
for line in config_txt_file_lines:
name, value = line.strip().split("=")
if value != "ON":
continue
if name == "IREE_HAL_DRIVER_CUDA":
available_drivers.append("cuda")
elif name == "IREE_HAL_DRIVER_LOCAL_SYNC":
available_drivers.append("local-sync")
elif name == "IREE_HAL_DRIVER_LOCAL_TASK":
available_drivers.append("local-task")
elif name == "IREE_HAL_DRIVER_VULKAN":
available_drivers.append("vulkan")
elif name == "IREE_HAL_EXECUTABLE_LOADER_EMBEDDED_ELF":
available_loaders.append("embedded-elf")
elif name == "IREE_HAL_EXECUTABLE_LOADER_SYSTEM_LIBRARY":
available_loaders.append("system-library")
elif name == "IREE_HAL_EXECUTABLE_LOADER_VMVX_MODULE":
available_loaders.append("vmvx-module")
else:
continue
if self.verbose:
available_drivers_str = ', '.join(available_drivers)
print(f"Available drivers: {available_drivers_str}")
available_loaders_str = ', '.join(available_loaders)
print(f"Available loaders: {available_loaders_str}")
return available_drivers, available_loaders