blob: 5a5d377cde736f0e9ce6305ce65ec02205a7306b [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2023 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
"""Miscellaneous tool to help work with benchmark suite and benchmark CI."""
import pathlib
import sys
# Add build_tools python dir to the search path.
sys.path.insert(0, str(pathlib.Path(__file__).parent.with_name("python")))
import argparse
import json
import os
import shlex
import subprocess
from typing import List, Optional, Sequence
from e2e_test_artifacts import model_artifacts, iree_artifacts
from e2e_test_framework import serialization
from e2e_test_framework.definitions import iree_definitions
IREE_COMPILER_NAME = "iree-compile"
def _convert_to_cmd_string(cmds: Sequence[str]) -> str:
if os.name == "nt":
# list2cmdline is an undocumented method for Windows command lines. Python
# doesn't provide an official method for quoting Windows command lines and
# the correct implementation is slightly non-trivial. Use the undocumented
# method for now and can be rewritten with our own implementation later.
# See https://learn.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way
return subprocess.list2cmdline(cmds)
return " ".join(shlex.quote(cmd) for cmd in cmds)
def _dump_cmds_of_generation_config(
gen_config: iree_definitions.ModuleGenerationConfig,
root_path: pathlib.PurePath = pathlib.PurePath(),
):
imported_model = gen_config.imported_model
imported_model_path = iree_artifacts.get_imported_model_path(
imported_model=imported_model, root_path=root_path
)
module_dir_path = iree_artifacts.get_module_dir_path(
module_generation_config=gen_config, root_path=root_path
)
module_path = module_dir_path / iree_artifacts.MODULE_FILENAME
compile_cmds = [
IREE_COMPILER_NAME,
str(imported_model_path),
"-o",
str(module_path),
]
compile_cmds += gen_config.materialize_compile_flags(
module_dir_path=module_dir_path
)
compile_cmd_str = _convert_to_cmd_string(compile_cmds)
if imported_model.import_config.tool == iree_definitions.ImportTool.NONE:
import_cmd_str = "# (Source model is already in MLIR)"
else:
source_model_path = model_artifacts.get_model_path(
model=imported_model.model, root_path=root_path
)
import_cmds = [
imported_model.import_config.tool.value,
str(source_model_path),
"-o",
str(imported_model_path),
]
import_cmds += imported_model.import_config.materialize_import_flags(
model=imported_model.model
)
import_cmd_str = _convert_to_cmd_string(import_cmds)
# Insert a blank line after each command to help read with line wrap.
return ["Compile Module:", compile_cmd_str, "", "Import Model:", import_cmd_str, ""]
def _dump_cmds_from_run_config(
run_config: iree_definitions.E2EModelRunConfig,
root_path: pathlib.PurePath = pathlib.PurePath(),
):
gen_config = run_config.module_generation_config
module_path = (
iree_artifacts.get_module_dir_path(
module_generation_config=gen_config, root_path=root_path
)
/ iree_artifacts.MODULE_FILENAME
)
run_cmds = [run_config.tool.value, f"--module={module_path}"]
run_cmds += run_config.materialize_run_flags()
# Insert a blank line after the command to help read with line wrap.
lines = ["Run Module:", _convert_to_cmd_string(run_cmds), ""]
lines += _dump_cmds_of_generation_config(gen_config=gen_config, root_path=root_path)
return lines
def _dump_cmds_handler(
e2e_test_artifacts_dir: pathlib.Path,
execution_benchmark_config: Optional[pathlib.Path],
compilation_benchmark_config: Optional[pathlib.Path],
benchmark_id: Optional[str],
**_unused_args,
):
lines = []
if execution_benchmark_config is not None:
benchmark_groups = json.loads(execution_benchmark_config.read_text())
for target_device, benchmark_group in benchmark_groups.items():
run_configs = serialization.unpack_and_deserialize(
data=benchmark_group["run_configs"],
root_type=List[iree_definitions.E2EModelRunConfig],
)
for run_config in run_configs:
if benchmark_id is not None and benchmark_id != run_config.composite_id:
continue
lines.append("################")
lines.append("")
lines.append(f"Execution Benchmark ID: {run_config.composite_id}")
lines.append(f"Name: {run_config}")
lines.append(f"Target Device: {target_device}")
lines.append("")
lines += _dump_cmds_from_run_config(
run_config=run_config, root_path=e2e_test_artifacts_dir
)
if compilation_benchmark_config is not None:
benchmark_config = json.loads(compilation_benchmark_config.read_text())
gen_configs = serialization.unpack_and_deserialize(
data=benchmark_config["generation_configs"],
root_type=List[iree_definitions.ModuleGenerationConfig],
)
for gen_config in gen_configs:
if benchmark_id is not None and benchmark_id != gen_config.composite_id:
continue
lines.append("################")
lines.append("")
lines.append(f"Compilation Benchmark ID: {gen_config.composite_id}")
lines.append(f"Name: {gen_config}")
lines.append("")
lines += _dump_cmds_of_generation_config(
gen_config=gen_config, root_path=e2e_test_artifacts_dir
)
print(*lines, sep="\n")
def _parse_arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Miscellaneous tool to help work with benchmark suite and benchmark CI."
)
subparser = parser.add_subparsers(required=True, title="operation")
dump_cmds_parser = subparser.add_parser(
"dump-cmds", help="Dump the commands to compile and run benchmarks manually."
)
dump_cmds_parser.add_argument(
"--e2e_test_artifacts_dir",
type=pathlib.PurePath,
default=pathlib.Path(),
help="E2E test artifacts root path used in the outputs of artifact paths",
)
dump_cmds_parser.add_argument(
"--benchmark_id", type=str, help="Only dump the benchmark with this id"
)
dump_cmds_parser.add_argument(
"--execution_benchmark_config",
type=pathlib.Path,
help="Config file exported from export_benchmark_config.py execution",
)
dump_cmds_parser.add_argument(
"--compilation_benchmark_config",
type=pathlib.Path,
help="Config file exported from export_benchmark_config.py compilation",
)
dump_cmds_parser.set_defaults(handler=_dump_cmds_handler)
args = parser.parse_args()
if (
args.execution_benchmark_config is None
and args.compilation_benchmark_config is None
):
parser.error(
"At least one of --execution_benchmark_config or "
"--compilation_benchmark_config must be set."
)
return args
def main(args: argparse.Namespace):
args.handler(**vars(args))
if __name__ == "__main__":
main(_parse_arguments())