blob: 65c6813139bec695f2bb4056413a1486a53fcc16 [file]
# 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
import json
import numpy as np
import sys
import unittest
import tempfile
from pathlib import Path
import iree.compiler
import iree.runtime
from iree.runtime.benchmark import (
_build_benchmark_args as build_benchmark_args,
_parse_benchmark_results as parse_benchmark_results,
_run_benchmark as run_benchmark,
benchmark_module,
BenchmarkResult,
BenchmarkToolError,
BenchmarkTimeoutError,
)
def create_simple_mul_module(instance):
binary = iree.compiler.compile_str(
"""
module @test_module {
func.func @simple_mul(%arg0: tensor<4xf32>, %arg1: tensor<4xf32>) -> tensor<4xf32> {
%0 = arith.mulf %arg0, %arg1 : tensor<4xf32>
return %0 : tensor<4xf32>
}
}
""",
target_backends=iree.compiler.core.DEFAULT_TESTING_BACKENDS,
)
m = iree.runtime.VmModule.from_flatbuffer(instance, binary)
return m
def create_multiple_entry_functions_module(instance):
binary = iree.compiler.compile_str(
"""
module @test_module {
func.func @entry_1(%arg0: tensor<4xf32>) -> tensor<4xf32> {
%0 = math.absf %arg0 : tensor<4xf32>
return %0 : tensor<4xf32>
}
func.func @entry_2(%arg0: tensor<2xf32>) -> tensor<2xf32> {
%0 = math.absf %arg0 : tensor<2xf32>
return %arg0 : tensor<2xf32>
}
}
""",
target_backends=iree.compiler.core.DEFAULT_TESTING_BACKENDS,
)
m = iree.runtime.VmModule.from_flatbuffer(instance, binary)
return m
def create_large_matmul_module(instance):
binary = iree.compiler.compile_str(
"""
module @test_module {
func.func @large_matmul(%arg0: tensor<4000x4000xf32>, %arg1: tensor<4000x4000xf32>, %arg2: tensor<4000x4000xf32>) -> tensor<4000x4000xf32> {
%0 = linalg.matmul ins(%arg0, %arg1: tensor<4000x4000xf32>, tensor<4000x4000xf32>)
outs(%arg2: tensor<4000x4000xf32>) -> tensor<4000x4000xf32>
return %0 : tensor<4000x4000xf32>
}
}
""",
target_backends=iree.compiler.core.DEFAULT_TESTING_BACKENDS,
)
m = iree.runtime.VmModule.from_flatbuffer(instance, binary)
return m
class BenchmarkTest(unittest.TestCase):
def setUp(self):
super().setUp()
def testBuildBenchmarkArgs(self):
args, flatbuffer = build_benchmark_args(
module="test_module.vmfb",
entry_function="test_func",
inputs=[np.array([1.0, 2.0], dtype=np.float32)],
# kwargs should be passed through as --{k}={v}
device="test_device",
extra_arg="extra_value",
)
ref_args = [
iree.runtime.benchmark_exe(),
"--module=test_module.vmfb",
"--function=test_func",
"--device=test_device",
"--extra_arg=extra_value",
"--input=2xf32=1.0,2.0",
]
self.assertEqual(args, ref_args)
self.assertIsNone(flatbuffer)
def testBuildBenchmarkArgsExecutableWithoutFunction(self):
args, _ = build_benchmark_args(
module="test_module.vmfb",
executable=Path("tools") / "iree-benchmark-module",
)
# Without a function the tool benchmarks all exported functions.
ref_args = [
str(Path("tools") / "iree-benchmark-module"),
"--module=test_module.vmfb",
]
self.assertEqual(args, ref_args)
def testBuildBenchmarkArgsFlagValues(self):
args, _ = build_benchmark_args(
module="test_module.vmfb",
entry_function="test_func",
dispatch_statistics=True,
print_statistics=False,
device=["local-task", "local-sync"],
)
self.assertEqual(
args[3:],
[
"--dispatch_statistics=true",
"--print_statistics=false",
"--device=local-task",
"--device=local-sync",
],
)
def testBuildBenchmarkArgsInputs(self):
all_inputs = [
# empty
[],
# single input
[np.ones([2], dtype=np.float16)],
# multiple inputs
[np.ones([2], dtype=np.float64), np.ones([3], dtype=np.int8)],
# string input
["input_string"],
# explicit values
[np.array([0, 1, 2, 3], dtype=np.uint8)],
]
ref_args = [
[],
["--input=2xf16=1.0"],
["--input=2xf64=1.0", "--input=3xi8=1"],
["--input=input_string"],
["--input=4xi8=0,1,2,3"],
]
for inp, ref in zip(all_inputs, ref_args):
args, flatbuffer = build_benchmark_args(
module="test_module.vmfb",
entry_function="test_func",
inputs=inp,
)
self.assertEqual(args[3:], ref)
self.assertIsNone(flatbuffer)
def testParseBenchmarkResults(self):
report = """{
"context": {},
"benchmarks": [
{"name": "BM_main/process_time/real_time", "run_type": "iteration",
"repetitions": 1, "repetition_index": 0, "threads": 1,
"iterations": 4, "real_time": 2.5, "cpu_time": 3.0,
"time_unit": "ms", "items_per_second": 400.0},
{"name": "BM_main/main_dispatch_0", "iterations": 4,
"real_time": 1.5, "cpu_time": 2.0, "time_unit": "ms",
"calls": 2.0, "percent": 100.0}
]
}"""
# New Google Benchmark metadata must survive without needing to classify
# it as a standard field or a user counter.
report_data = json.loads(report)
report_data["benchmarks"][0]["new_metadata"] = {"version": 2}
report = json.dumps(report_data)
self.assertEqual(
parse_benchmark_results(report),
[
BenchmarkResult(
benchmark_name="BM_main/process_time/real_time",
time="2.5 ms",
cpu_time="3.0 ms",
iterations="4",
report=report_data["benchmarks"][0],
),
BenchmarkResult(
benchmark_name="BM_main/main_dispatch_0",
time="1.5 ms",
cpu_time="2.0 ms",
iterations="4",
report=report_data["benchmarks"][1],
),
],
)
def testParseBenchmarkPercentageAggregate(self):
report = """{
"benchmarks": [
{"name": "BM_main/process_time/real_time_cv",
"run_type": "aggregate", "aggregate_name": "cv",
"aggregate_unit": "percentage", "iterations": 2,
"real_time": 0.125, "cpu_time": 0.25, "time_unit": "ms",
"items_per_second": 0.125}
]
}"""
(result,) = parse_benchmark_results(report)
self.assertEqual(result.time, "12.5 %")
self.assertEqual(result.cpu_time, "25.0 %")
self.assertEqual(result.report["aggregate_unit"], "percentage")
self.assertEqual(result.report["real_time"], 0.125)
self.assertEqual(result.report["items_per_second"], 0.125)
def testParseBenchmarkError(self):
report = json.dumps(
{
"benchmarks": [
{
"name": "BM_main",
"error_occurred": True,
"error_message": "RESOURCE_EXHAUSTED; device allocation failed",
}
]
}
)
with self.assertRaisesRegex(
BenchmarkToolError,
"BM_main: RESOURCE_EXHAUSTED; device allocation failed",
):
parse_benchmark_results(report)
def testBenchmarkToolFailurePreservesDiagnostics(self):
with self.assertRaises(BenchmarkToolError) as caught:
run_benchmark(
[
sys.executable,
"-c",
"import sys; print('output diagnostic'); "
"print('error diagnostic', file=sys.stderr); sys.exit(1)",
]
)
self.assertIn("stdout:\noutput diagnostic", str(caught.exception))
self.assertIn("stderr:\nerror diagnostic", str(caught.exception))
def testBenchmarkModuleUnmatchedFilter(self):
with tempfile.TemporaryDirectory() as directory:
module = Path(directory) / "module.vmfb"
iree.compiler.compile_str(
"func.func @main() { return }", output_file=str(module)
)
results = benchmark_module(
module, entry_function="main", benchmark_filter="does_not_exist"
)
self.assertEqual(results, [])
def testBenchmarkModule(self):
ctx = iree.runtime.SystemContext()
vm_module = create_simple_mul_module(ctx.instance)
arg0 = np.array([1.0, 2.0, 3.0, 4.0], dtype=np.float32)
arg1 = np.array([5.0, 6.0, 7.0, 8.0], dtype=np.float32)
benchmark_results = benchmark_module(
vm_module,
device=iree.compiler.core.DEFAULT_TESTING_DRIVER,
inputs=[arg0, arg1],
)
self.assertEqual(len(benchmark_results), 1)
benchmark_time = float(benchmark_results[0].time.split(" ")[0])
self.assertGreater(benchmark_time, 0)
self.assertGreater(benchmark_results[0].report["items_per_second"], 0)
def testBenchmarkModuleFromFilePath(self):
ctx = iree.runtime.SystemContext()
vm_module = create_simple_mul_module(ctx.instance)
with tempfile.TemporaryDirectory() as tmp_dir:
module_file_path = Path(tmp_dir) / "module.vmfb"
with open(module_file_path, "wb") as f:
f.write(vm_module.stashed_flatbuffer_blob)
arg0 = np.array([1.0, 2.0, 3.0, 4.0], dtype=np.float32)
arg1 = np.array([5.0, 6.0, 7.0, 8.0], dtype=np.float32)
benchmark_results = benchmark_module(
module_file_path,
entry_function="simple_mul",
device=iree.compiler.core.DEFAULT_TESTING_DRIVER,
inputs=[arg0, arg1],
)
self.assertEqual(len(benchmark_results), 1)
benchmark_time = float(benchmark_results[0].time.split(" ")[0])
self.assertGreater(benchmark_time, 0)
def testBenchmarkModuleWithEntryFunction(self):
ctx = iree.runtime.SystemContext()
vm_module = create_multiple_entry_functions_module(ctx.instance)
arg1 = np.array([1.0, 2.0, 3.0, 4.0], dtype=np.float32)
arg2 = np.array([1.0, 2.0], dtype=np.float32)
benchmark_results_1 = benchmark_module(
vm_module,
entry_function="entry_1",
device=iree.compiler.core.DEFAULT_TESTING_DRIVER,
inputs=[arg1],
)
self.assertEqual(len(benchmark_results_1), 1)
benchmark_results_2 = benchmark_module(
vm_module,
entry_function="entry_2",
device=iree.compiler.core.DEFAULT_TESTING_DRIVER,
inputs=[arg2],
)
self.assertEqual(len(benchmark_results_2), 1)
def testBenchmarkModuleTimeout(self):
ctx = iree.runtime.SystemContext()
vm_module = create_large_matmul_module(ctx.instance)
arg0 = np.zeros([4000, 4000], dtype=np.float32)
arg1 = np.zeros([4000, 4000], dtype=np.float32)
arg2 = np.zeros([4000, 4000], dtype=np.float32)
with self.assertRaises(BenchmarkTimeoutError):
_ = benchmark_module(
vm_module,
device=iree.compiler.core.DEFAULT_TESTING_DRIVER,
inputs=[arg0, arg1, arg2],
timeout=0.1,
)
if __name__ == "__main__":
unittest.main()