| """Common utilities that are useful for all the benchmarks.""" | |
| import numpy as np | |
| from mlir import ir | |
| from mlir.dialects import arith | |
| from mlir.dialects import func | |
| from mlir.dialects import memref | |
| from mlir.dialects import scf | |
| from mlir.passmanager import PassManager | |
| def setup_passes(mlir_module): | |
| """Setup pass pipeline parameters for benchmark functions.""" | |
| opt = ( | |
| "parallelization-strategy=none" | |
| ) | |
| pipeline = f"builtin.module(sparsifier{{{opt}}})" | |
| PassManager.parse(pipeline).run(mlir_module.operation) | |
| def create_sparse_np_tensor(dimensions, number_of_elements): | |
| """Constructs a numpy tensor of dimensions `dimensions` that has only a | |
| specific number of nonzero elements, specified by the `number_of_elements` | |
| argument. | |
| """ | |
| tensor = np.zeros(dimensions, np.float64) | |
| tensor_indices_list = [ | |
| [np.random.randint(0, dimension) for dimension in dimensions] | |
| for _ in range(number_of_elements) | |
| ] | |
| for tensor_indices in tensor_indices_list: | |
| current_tensor = tensor | |
| for tensor_index in tensor_indices[:-1]: | |
| current_tensor = current_tensor[tensor_index] | |
| current_tensor[tensor_indices[-1]] = np.random.uniform(1, 100) | |
| return tensor | |
| def get_kernel_func_from_module(module: ir.Module) -> func.FuncOp: | |
| """Takes an mlir module object and extracts the function object out of it. | |
| This function only works for a module with one region, one block, and one | |
| operation. | |
| """ | |
| assert ( | |
| len(module.operation.regions) == 1 | |
| ), "Expected kernel module to have only one region" | |
| assert ( | |
| len(module.operation.regions[0].blocks) == 1 | |
| ), "Expected kernel module to have only one block" | |
| assert ( | |
| len(module.operation.regions[0].blocks[0].operations) == 1 | |
| ), "Expected kernel module to have only one operation" | |
| return module.operation.regions[0].blocks[0].operations[0] | |
| def emit_timer_func() -> func.FuncOp: | |
| """Returns the declaration of nanoTime function. If nanoTime function is | |
| used, the `MLIR_RUNNER_UTILS` and `MLIR_C_RUNNER_UTILS` must be included. | |
| """ | |
| i64_type = ir.IntegerType.get_signless(64) | |
| nanoTime = func.FuncOp("nanoTime", ([], [i64_type]), visibility="private") | |
| nanoTime.attributes["llvm.emit_c_interface"] = ir.UnitAttr.get() | |
| return nanoTime | |
| def emit_benchmark_wrapped_main_func(kernel_func, timer_func): | |
| """Takes a function and a timer function, both represented as FuncOp | |
| objects, and returns a new function. This new function wraps the call to | |
| the original function between calls to the timer_func and this wrapping | |
| in turn is executed inside a loop. The loop is executed | |
| len(kernel_func.type.results) times. This function can be used to | |
| create a "time measuring" variant of a function. | |
| """ | |
| i64_type = ir.IntegerType.get_signless(64) | |
| memref_of_i64_type = ir.MemRefType.get([ir.ShapedType.get_dynamic_size()], i64_type) | |
| wrapped_func = func.FuncOp( | |
| # Same signature and an extra buffer of indices to save timings. | |
| "main", | |
| (kernel_func.arguments.types + [memref_of_i64_type], kernel_func.type.results), | |
| visibility="public", | |
| ) | |
| wrapped_func.attributes["llvm.emit_c_interface"] = ir.UnitAttr.get() | |
| num_results = len(kernel_func.type.results) | |
| with ir.InsertionPoint(wrapped_func.add_entry_block()): | |
| timer_buffer = wrapped_func.arguments[-1] | |
| zero = arith.ConstantOp.create_index(0) | |
| n_iterations = memref.DimOp(timer_buffer, zero) | |
| one = arith.ConstantOp.create_index(1) | |
| iter_args = list(wrapped_func.arguments[-num_results - 1 : -1]) | |
| loop = scf.ForOp(zero, n_iterations, one, iter_args) | |
| with ir.InsertionPoint(loop.body): | |
| start = func.CallOp(timer_func, []) | |
| call = func.CallOp( | |
| kernel_func, | |
| wrapped_func.arguments[: -num_results - 1] + loop.inner_iter_args, | |
| ) | |
| end = func.CallOp(timer_func, []) | |
| time_taken = arith.SubIOp(end, start) | |
| memref.StoreOp(time_taken, timer_buffer, [loop.induction_variable]) | |
| scf.YieldOp(list(call.results)) | |
| func.ReturnOp(loop) | |
| return wrapped_func |