| # 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 |
| |
| from library import * |
| from matmul import ReferenceMatmulOp |
| from batch_matmul import ReferenceBatchMatmulOp |
| from pathlib import Path |
| import subprocess |
| |
| |
| class IreeToolsLauncher: |
| """Launcher for IREE tools.""" |
| |
| def __init__(self, args, operation): |
| self.operation = operation |
| |
| self.generated_path = Path(args.generated_dir, 'generated', |
| args.mlir_dialect) |
| |
| self.args = args |
| self.benchmark_dispatch_repeat_count = args.batch_size |
| self.batch_size = args.batch_size |
| |
| # paths to source dispatch mlir, compiled vmfb, and logs. |
| self.operation_path = self.generated_path.joinpath( |
| OperationKindNames[operation.operation_kind], operation.name()) |
| |
| self.source_mlir_file = self.operation_path.joinpath( |
| operation.name()).with_suffix(".mlir") |
| |
| # path to cached numpy refernece input and expected output files. |
| self.op_reference_cache_path = Path(args.generated_dir, 'generated', |
| 'reference_cache', operation.name()) |
| |
| if not self.op_reference_cache_path.exists(): |
| self.op_reference_cache_path.mkdir(parents=True, exist_ok=True) |
| |
| # path to iree-compile tool. (for compiling the input mlir file to vmfb) |
| self.iree_compile_path = Path(args.iree_bin_dir, 'iree-compile') |
| |
| # path to iree-benchmark-module tool. (for performance benchmarking and profiling) |
| self.iree_benchmark_module_path = Path(args.iree_bin_dir, |
| 'iree-benchmark-module') |
| |
| # path to iree-run-module tool. (for verification) |
| self.iree_run_module_path = Path(args.iree_bin_dir, 'iree-run-module') |
| |
| # output vmfb files for verification and profiling. |
| vmfb_filename = f"{operation.name()}" |
| |
| if operation.operation_kind == OperationKind.SplitkMatmul: |
| split_k_suffix = "_".join( |
| ['split_k_slice', str(operation.split_k_slices)]) |
| vmfb_filename = f"{vmfb_filename}_{split_k_suffix}" |
| |
| self.vmfb_verify_filepath = self.operation_path.joinpath( |
| self.operation.name()).with_name(f"{vmfb_filename}_verify.vmfb") |
| self.vmfb_profile_filepath = self.operation_path.joinpath( |
| self.operation.name()).with_name(f"{vmfb_filename}_profile.vmfb") |
| |
| # reference implementation for the operation_kind. |
| self.reference_impl_map = { |
| OperationKind.Matmul: ReferenceMatmulOp, |
| OperationKind.SplitkMatmul: ReferenceMatmulOp, |
| OperationKind.BatchMatmul: ReferenceBatchMatmulOp, |
| } |
| |
| def iree_compile(self, compilation_mode): |
| """Compiles the input mlir file to vmfb file.""" |
| |
| benchmark_dispatch_repeat_count = self.benchmark_dispatch_repeat_count if compilation_mode == CompilationMode.Profile else 1 |
| vmfb_filepath = self.vmfb_profile_filepath if compilation_mode == CompilationMode.Profile else self.vmfb_verify_filepath |
| |
| # Base iree-compile commandline |
| cmd = [ |
| f'{self.iree_compile_path}', |
| f'{self.source_mlir_file}', |
| "-o", |
| f'{vmfb_filepath}', |
| ] |
| |
| # General compilation options |
| cmd += [f"--iree-hal-target-backends={self.args.device}"] |
| |
| if self.args.device == "cuda": |
| cmd += [f"--iree-hal-cuda-llvm-target-arch={self.args.cuda_arch}"] |
| if self.operation.operation_kind == OperationKind.SplitkMatmul: |
| cmd += [ |
| f"--iree-flow-split-matmul-reduction={self.operation.split_k_slices}" |
| ] |
| if self.args.use_mma_sync: |
| cmd += [f"--iree-codegen-llvmgpu-use-mma-sync"] |
| if self.args.use_wmma: |
| cmd += [f"--iree-codegen-llvmgpu-use-wmma"] |
| |
| # Compilation options for profiling |
| cmd += [ |
| f"--iree-hal-benchmark-dispatch-repeat-count={benchmark_dispatch_repeat_count}" |
| ] |
| |
| # Appends print ir options at the end of the command line. |
| if self.args.mlir_print_ir_after_all: |
| cmd += [f"--mlir-print-ir-after-all"] |
| |
| if not vmfb_filepath.exists() or self.args.force_compile: |
| complie_mode_str = CompilationModeNames[compilation_mode] |
| |
| print(f"[Compiling ({complie_mode_str})] {' '.join(cmd)}") |
| |
| iree_compile_stdout_filepath = self.operation_path.joinpath( |
| 'iree_compile_cmd_stdout.mlir') |
| |
| with open(iree_compile_stdout_filepath, "w") as fp: |
| subprocess.run(cmd, stderr=fp) |
| |
| elif self.args.verbose: |
| print( |
| f"Skipping compilation of operation: {vmfb_filepath} since it already exists." |
| ) |
| |
| def verify(self, configuration): |
| """Verifies the operation with a given configuration.""" |
| # First compile the operation to a vmfb file. |
| self.iree_compile(CompilationMode.Verify) |
| |
| # Verify using random data distribution. |
| reference_run = self.reference_impl_map[self.operation.operation_kind]( |
| self.operation, self.op_reference_cache_path, Distribution.Random, |
| Distribution.Random) |
| |
| if not reference_run.is_cached(): |
| reference_run() |
| |
| # Commandline `iree-run-module` for verification. |
| cmd = [ |
| f'{self.iree_run_module_path}', f'--module={self.vmfb_verify_filepath}', |
| f'--device={self.args.device}' |
| ] |
| |
| # Operation-specific verification command-line. |
| cmd.append(f'--function={self.operation.name()}_{configuration.name()}') |
| for input_file_path in reference_run.get_input_filepaths(): |
| cmd.append(f'--input=@{input_file_path}') |
| for output_file_path in reference_run.get_output_filepaths(): |
| cmd.append(f'--expected_output=@{output_file_path}') |
| |
| # Print the command if verbose. |
| if self.args.verbose: |
| print(f"[Verification] {' '.join(cmd)}") |
| |
| # Launch verification. |
| cmd_output = subprocess.check_output(cmd, text=True) |
| |
| # Save the verification command and the output, only if requested |
| # (file writing could slow down the verification). |
| if self.args.save_cmds: |
| filepath = self.operation_path.joinpath("iree_run_module.stdout") |
| with open(filepath, "w") as fp: |
| fp.write(f"[Command] $ {' '.join(cmd)}\n") |
| fp.write(cmd_output) |
| |
| # Parse the verification output. |
| m = re.search(r"\[(?P<verification_result>[a-zA-Z]+)\]", cmd_output) |
| if m is None: |
| raise ValueError( |
| f"Failed to parse verification output by iree-run-module: {cmd_output}" |
| ) |
| verification_result = m.group('verification_result') |
| |
| if self.args.verbose or verification_result != "SUCCESS": |
| print(cmd_output) |
| |
| return verification_result |
| |
| def profile(self, configuration): |
| """Profiles the operation with a given configuration.""" |
| # First compile the operation to a vmfb file. |
| self.iree_compile(CompilationMode.Profile) |
| |
| # Commandline `iree-benchmark-module` for profiling. |
| cmd = [ |
| f'{self.iree_benchmark_module_path}', |
| f'--module={self.vmfb_profile_filepath}', f'--device={self.args.device}' |
| ] |
| |
| # Profiling specific flags. |
| cmd += [f'--benchmark_repetitions={self.args.benchmark_repetitions}'] |
| cmd += [f'--batch_size={self.batch_size}'] |
| |
| # Operation-specific profiling command-line. |
| cmd += [f'--function={self.operation.name()}_{configuration.name()}'] |
| cmd += [f'--input={self.operation.lhs_npy_shape()}'] |
| cmd += [f'--input={self.operation.rhs_npy_shape()}'] |
| |
| # Print the command if verbose. |
| if self.args.verbose: |
| print(f"[Profiling] {' '.join(cmd)}") |
| |
| # Launch profiling. |
| cmd_output = subprocess.check_output(cmd, |
| text=True, |
| stderr=subprocess.STDOUT) |
| |
| # Save the profiling command and the output, only if requested |
| # (file writing could slow down the profiling). |
| if self.args.save_cmds: |
| filepath = self.operation_path.joinpath("iree_benchmark_module.stdout") |
| with open(filepath, "w") as fp: |
| fp.write(f"[Command] $ {' '.join(cmd)}\n") |
| fp.write(cmd_output) |
| |
| # Parse the profiling output. |
| m = re.search(r"real_time_median\s+(?P<runtime>\d+.\d+)\s+ms", cmd_output) |
| if m is None: |
| raise ValueError( |
| f"Failed to parse runtime from benchmark result: {cmd_output}") |
| runtime_in_ms = float(m.group('runtime')) |
| return runtime_in_ms |