blob: 1d0853b1dd456aa73f83c5e9fe96579d12d1bc50 [file] [log] [blame]
#!/usr/bin/env python3
# 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
"""Exports JSON config for benchmarking and compilation statistics.
Export type: "execution" outputs:
[
<target device name>: {
host_environment: HostEnvironment,
module_dir_paths: [<paths of dependent module directories>],
run_configs: serialized [E2EModelRunConfig]
},
...
]
to be used in build_tools/benchmarks/run_benchmarks_on_*.py
Export type: "compilation" outputs:
{
module_dir_paths: [<paths of dependent module directories>],
generation_configs: serialized [ModuleGenerationConfig]
}
of generation configs defined for compilation statistics, to be used in
build_tools/benchmarks/collect_compilation_statistics.py
"""
import sys
import pathlib
# Add build_tools python dir to the search path.
sys.path.insert(0, str(pathlib.Path(__file__).parent.with_name("python")))
from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Sequence
import argparse
import collections
import dataclasses
import json
import textwrap
from benchmark_suites.iree import benchmark_collections, benchmark_tags
from e2e_test_artifacts import iree_artifacts
from e2e_test_framework import serialization
from e2e_test_framework.definitions import common_definitions, iree_definitions
from e2e_test_framework.definitions import iree_definitions
PresetMatcher = Callable[[Any], bool]
EXECUTION_BENCHMARK_PRESET_MATCHERS: Dict[str, PresetMatcher] = {
"x86_64": lambda config: (
benchmark_tags.X86_64 in config.tags and benchmark_tags.LARGE not in config.tags
),
"x86_64-large": lambda config: (
benchmark_tags.X86_64 in config.tags and benchmark_tags.LARGE in config.tags
),
"cuda": lambda config: (
benchmark_tags.CUDA in config.tags and benchmark_tags.LARGE not in config.tags
),
"cuda-large": lambda config: (
benchmark_tags.CUDA in config.tags and benchmark_tags.LARGE in config.tags
),
"vulkan-nvidia": lambda config: benchmark_tags.VULKAN_NVIDIA in config.tags,
"android-cpu": lambda config: (
config.target_device_spec.architecture.type
== common_definitions.ArchitectureType.CPU
and config.target_device_spec.host_environment.platform == "android"
),
"android-gpu": lambda config: (
config.target_device_spec.architecture.type
== common_definitions.ArchitectureType.GPU
and config.target_device_spec.host_environment.platform == "android"
),
}
COMPILATION_BENCHMARK_PRESET_MATCHERS: Dict[str, PresetMatcher] = {
"comp-stats": lambda gen_config: benchmark_tags.LARGE not in gen_config.tags,
"comp-stats-large": lambda gen_config: benchmark_tags.LARGE in gen_config.tags,
}
def filter_and_group_run_configs(
run_configs: List[iree_definitions.E2EModelRunConfig],
target_device_names: Optional[Set[str]] = None,
preset_matchers: Optional[Sequence[PresetMatcher]] = None,
) -> Dict[str, List[iree_definitions.E2EModelRunConfig]]:
"""Filters run configs and groups by target device name.
Args:
run_configs: source e2e model run configs.
target_device_names: list of target device names, includes all if not set.
preset_matchers: list of preset matcher, matches all if not set.
Returns:
A map of e2e model run configs keyed by target device name.
"""
grouped_run_config_map = collections.defaultdict(list)
for run_config in run_configs:
device_name = run_config.target_device_spec.device_name
if target_device_names is not None and device_name not in target_device_names:
continue
if preset_matchers is not None and not any(
matcher(run_config) for matcher in preset_matchers
):
continue
grouped_run_config_map[device_name].append(run_config)
return grouped_run_config_map
def _get_distinct_module_dir_paths(
module_generation_configs: Iterable[iree_definitions.ModuleGenerationConfig],
root_path: pathlib.PurePath = pathlib.PurePath(),
) -> List[str]:
module_dir_paths = (
str(iree_artifacts.get_module_dir_path(config, root_path=root_path))
for config in module_generation_configs
)
return sorted(set(module_dir_paths))
def _export_execution_handler(
benchmark_presets: Optional[Sequence[PresetMatcher]] = None,
target_device_names: Optional[Sequence[str]] = None,
**_unused_args,
):
_, all_run_configs = benchmark_collections.generate_benchmarks()
target_device_name_set = (
None if target_device_names is None else set(target_device_names)
)
grouped_run_config_map = filter_and_group_run_configs(
all_run_configs,
target_device_names=target_device_name_set,
preset_matchers=benchmark_presets,
)
output_map = {}
for device_name, run_configs in grouped_run_config_map.items():
host_environments = set(
run_config.target_device_spec.host_environment for run_config in run_configs
)
if len(host_environments) > 1:
raise ValueError(
"Device specs of the same device should have the same host environment."
)
host_environment = host_environments.pop()
distinct_module_dir_paths = _get_distinct_module_dir_paths(
config.module_generation_config for config in run_configs
)
output_map[device_name] = {
"host_environment": dataclasses.asdict(host_environment),
"module_dir_paths": distinct_module_dir_paths,
"run_configs": serialization.serialize_and_pack(run_configs),
}
return output_map
def _export_compilation_handler(
benchmark_presets: Optional[Sequence[PresetMatcher]] = None, **_unused_args
):
all_gen_configs, _ = benchmark_collections.generate_benchmarks()
compile_stats_gen_configs = [
config
for config in all_gen_configs
if benchmark_tags.COMPILE_STATS in config.compile_config.tags
]
if benchmark_presets is not None:
match_predicate = lambda gen_config: any(
matcher(gen_config) for matcher in benchmark_presets
)
compile_stats_gen_configs = [
gen_config
for gen_config in compile_stats_gen_configs
if match_predicate(gen_config)
]
distinct_module_dir_paths = _get_distinct_module_dir_paths(
compile_stats_gen_configs
)
return {
"module_dir_paths": distinct_module_dir_paths,
"generation_configs": serialization.serialize_and_pack(
compile_stats_gen_configs
),
}
def _parse_and_strip_list_argument(arg: str) -> List[str]:
return [part.strip() for part in arg.split(",") if part != ""]
def _parse_benchmark_presets(
arg: str, matcher_map: Dict[str, PresetMatcher]
) -> List[PresetMatcher]:
matchers = []
for preset in _parse_and_strip_list_argument(arg):
matcher = matcher_map.get(preset)
if matcher is None:
raise argparse.ArgumentTypeError(
f"Unrecognized benchmark preset: '{preset}'."
)
matchers.append(matcher)
return matchers
def _parse_arguments():
"""Parses command-line options."""
# Makes global options come *after* command.
# See https://stackoverflow.com/q/23296695
subparser_base = argparse.ArgumentParser(add_help=False)
subparser_base.add_argument(
"--output", type=pathlib.Path, help="Path to write the JSON output."
)
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description=textwrap.dedent(
"""
Export type: "execution" outputs:
[
<target device name>: {
host_environment: HostEnvironment,
module_dir_paths: [<paths of dependent module directories>],
run_configs: serialized [E2EModelRunConfig]
},
...
]
to be used in build_tools/benchmarks/run_benchmarks_on_*.py
Export type: "compilation" outputs:
{
module_dir_paths: [<paths of dependent module directories>],
generation_configs: serialized [ModuleGenerationConfig]
}
of generation configs defined for compilation statistics, to be used in
build_tools/benchmarks/collect_compilation_statistics.py
"""
),
)
subparser = parser.add_subparsers(required=True, title="export type")
execution_parser = subparser.add_parser(
"execution",
parents=[subparser_base],
help="Export execution config to run benchmarks.",
)
execution_parser.set_defaults(handler=_export_execution_handler)
execution_parser.add_argument(
"--target_device_names",
type=_parse_and_strip_list_argument,
help=(
"Target device names, separated by comma, not specified means "
"including all devices."
),
)
execution_parser.add_argument(
"--benchmark_presets",
type=lambda arg: _parse_benchmark_presets(
arg, EXECUTION_BENCHMARK_PRESET_MATCHERS
),
help=(
"Presets that select a bundle of benchmarks, separated by comma, "
"multiple presets will be union. Available options: "
f"{','.join(EXECUTION_BENCHMARK_PRESET_MATCHERS.keys())}"
),
)
compilation_parser = subparser.add_parser(
"compilation",
parents=[subparser_base],
help=(
"Export serialized list of module generation configs defined for "
"compilation statistics."
),
)
compilation_parser.set_defaults(handler=_export_compilation_handler)
compilation_parser.add_argument(
"--benchmark_presets",
type=lambda arg: _parse_benchmark_presets(
arg, COMPILATION_BENCHMARK_PRESET_MATCHERS
),
help=(
"Presets `comp-stats*` that select a bundle of compilation"
" benchmarks, separated by comma, multiple presets will be union."
" Available options: "
f"{','.join(COMPILATION_BENCHMARK_PRESET_MATCHERS.keys())}"
),
)
return parser.parse_args()
def main(args: argparse.Namespace):
output_obj = args.handler(**vars(args))
json_data = json.dumps(output_obj, indent=2)
if args.output is None:
print(json_data)
else:
args.output.write_text(json_data)
if __name__ == "__main__":
main(_parse_arguments())