"""This file contains benchmarks for sparse tensors. In particular, it contains benchmarks for both mlir sparse tensor dialect and numpy so that they can be compared against each other. """ import ctypes import numpy as np import os import re import time from mlir import ir from mlir import runtime as rt from mlir.dialects import func from mlir.dialects.linalg.opdsl import lang as dsl from mlir.execution_engine import ExecutionEngine from common import create_sparse_np_tensor from common import emit_timer_func from common import emit_benchmark_wrapped_main_func from common import get_kernel_func_from_module from common import setup_passes @dsl.linalg_structured_op def matmul_dsl( A=dsl.TensorDef(dsl.T, dsl.S.M, dsl.S.K), B=dsl.TensorDef(dsl.T, dsl.S.K, dsl.S.N), C=dsl.TensorDef(dsl.T, dsl.S.M, dsl.S.N, output=True), ): """Helper function for mlir sparse matrix multiplication benchmark.""" C[dsl.D.m, dsl.D.n] += A[dsl.D.m, dsl.D.k] * B[dsl.D.k, dsl.D.n] def benchmark_sparse_mlir_multiplication(): """Benchmark for mlir sparse matrix multiplication. Because its an MLIR benchmark we need to return both a `compiler` function and a `runner` function. """ with ir.Context(), ir.Location.unknown(): module = ir.Module.create() f64 = ir.F64Type.get() param1_type = ir.RankedTensorType.get([1000, 1500], f64) param2_type = ir.RankedTensorType.get([1500, 2000], f64) result_type = ir.RankedTensorType.get([1000, 2000], f64) with ir.InsertionPoint(module.body): @func.FuncOp.from_py_func(param1_type, param2_type, result_type) def sparse_kernel(x, y, z): return matmul_dsl(x, y, outs=[z]) def compiler(): with ir.Context(), ir.Location.unknown(): kernel_func = get_kernel_func_from_module(module) timer_func = emit_timer_func() wrapped_func = emit_benchmark_wrapped_main_func(kernel_func, timer_func) main_module_with_benchmark = ir.Module.parse( str(timer_func) + str(wrapped_func) + str(kernel_func) ) setup_passes(main_module_with_benchmark) c_runner_utils = os.getenv("MLIR_C_RUNNER_UTILS", "") assert os.path.exists(c_runner_utils), ( f"{c_runner_utils} does not exist." f" Please pass a valid value for" f" MLIR_C_RUNNER_UTILS environment variable." ) runner_utils = os.getenv("MLIR_RUNNER_UTILS", "") assert os.path.exists(runner_utils), ( f"{runner_utils} does not exist." f" Please pass a valid value for MLIR_RUNNER_UTILS" f" environment variable." ) engine = ExecutionEngine( main_module_with_benchmark, 3, shared_libs=[c_runner_utils, runner_utils], ) return engine.invoke def runner(engine_invoke): compiled_program_args = [] for argument_type in [result_type, param1_type, param2_type, result_type]: argument_type_str = str(argument_type) dimensions_str = re.sub("<|>|tensor", "", argument_type_str) dimensions = [int(dim) for dim in dimensions_str.split("x")[:-1]] if argument_type == result_type: argument = np.zeros(dimensions, np.float64) else: argument = create_sparse_np_tensor(dimensions, 1000) compiled_program_args.append( ctypes.pointer( ctypes.pointer(rt.get_ranked_memref_descriptor(argument)) ) ) np_timers_ns = np.array([0], dtype=np.int64) compiled_program_args.append( ctypes.pointer( ctypes.pointer(rt.get_ranked_memref_descriptor(np_timers_ns)) ) ) engine_invoke("main", *compiled_program_args) return int(np_timers_ns[0]) return compiler, runner def benchmark_np_matrix_multiplication(): """Benchmark for numpy matrix multiplication. Because its a python benchmark, we don't have any `compiler` function returned. We just return the `runner` function. """ def runner(): argument1 = np.random.uniform(low=0.0, high=100.0, size=(1000, 1500)) argument2 = np.random.uniform(low=0.0, high=100.0, size=(1500, 2000)) start_time = time.time_ns() np.matmul(argument1, argument2) return time.time_ns() - start_time return None, runner