| # Copyright 2022 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 dispatch import * | |
| from matmul import MatmulOperation, MatmulCompilationInfo, CudaMatmulGenerator | |
| class BatchMatmulOperation(MatmulOperation): | |
| """Data structure to describe a batch matrix multiplication operation.""" | |
| def __init__(self, bmm_shape, lhs, rhs, result): | |
| assert len(bmm_shape) == 4, "Batch matmul shape must be 4D" | |
| super().__init__( | |
| bmm_shape[1:], lhs, rhs, result, bmm_shape[0], 1, OperationKind.BatchMatmul | |
| ) | |
| def name(self): | |
| return ( | |
| f"{OperationKindNames[self.operation_kind]}_" | |
| f"{self.batch_count}x{self.M}x{self.N}x{self.K}_" | |
| f"{DataTypeName[self.lhs.datatype]}{ShortLayoutTypeName[self.lhs.layout]}_" | |
| f"{DataTypeName[self.rhs.datatype]}{ShortLayoutTypeName[self.rhs.layout]}_" | |
| f"{DataTypeName[self.result.datatype]}{ShortLayoutTypeName[self.result.layout]}" | |
| ) | |
| def lhs_npy_shape(self): | |
| return f"{self.batch_count}x{super().lhs_npy_shape()}" | |
| def rhs_npy_shape(self): | |
| return f"{self.batch_count}x{super().rhs_npy_shape()}" | |
| def result_npy_shape(self): | |
| return f"{self.batch_count}x{super().result_npy_shape()}" | |
| class EmitLinalgBatchMatmulDispatch: | |
| """Emitters for the `linalg.batch_matmul` dispatch.""" | |
| def __init__(self): | |
| self.mlir_dialect = MlirDialect.Linalg | |
| self.linalg_row_row_matmul_template = """ | |
| // Dispatch linalg.batch_matmul row-row layout | |
| func.func @${operation_name}_${compilation_info_name}( | |
| %lhs: tensor<${batch_count}x${problem_m}x${problem_k}x${datatype_lhs}>, | |
| %rhs: tensor<${batch_count}x${problem_k}x${problem_n}x${datatype_rhs}>) -> tensor<${batch_count}x${problem_m}x${problem_n}x${datatype_result}> | |
| { | |
| %c0 = arith.constant 0.0 : ${datatype_result} | |
| %init = tensor.empty() : tensor<${batch_count}x${problem_m}x${problem_n}x${datatype_result}> | |
| %inital_result = linalg.fill ins(%c0 : ${datatype_result}) outs(%init : tensor<${batch_count}x${problem_m}x${problem_n}x${datatype_result}>) -> tensor<${batch_count}x${problem_m}x${problem_n}x${datatype_result}> | |
| %result = linalg.batch_matmul ${compilation_info_attribute} | |
| ins(%lhs, %rhs: tensor<${batch_count}x${problem_m}x${problem_k}x${datatype_lhs}>, tensor<${batch_count}x${problem_k}x${problem_n}x${datatype_rhs}>) | |
| outs(%inital_result: tensor<${batch_count}x${problem_m}x${problem_n}x${datatype_result}>) -> tensor<${batch_count}x${problem_m}x${problem_n}x${datatype_result}> | |
| return %result : tensor<${batch_count}x${problem_m}x${problem_n}x${datatype_result}> | |
| } | |
| """ | |
| def emit(self, dispatch): | |
| """Emit the matmul operation in the MLIR dialect for a single compilation info""" | |
| compilation_info_attribute_template = ( | |
| """{compilation_info = #${compilation_info_name}}""" | |
| ) | |
| compilation_info_attribute_str = SubstituteTemplate( | |
| compilation_info_attribute_template, | |
| {"compilation_info_name": dispatch.configuration.name()}, | |
| ) | |
| compilation_info_attribute = ( | |
| compilation_info_attribute_str | |
| if dispatch.configuration.config_type != CompilationConfigType.Default | |
| else "" | |
| ) | |
| values = { | |
| "operation_name": dispatch.operation.name(), | |
| "compilation_info_attribute": compilation_info_attribute, | |
| "batch_count": str(dispatch.operation.batch_count), | |
| "problem_m": str(dispatch.operation.M), | |
| "problem_n": str(dispatch.operation.N), | |
| "problem_k": str(dispatch.operation.K), | |
| "datatype_lhs": DataTypeName[dispatch.operation.lhs.datatype], | |
| "datatype_rhs": DataTypeName[dispatch.operation.rhs.datatype], | |
| "datatype_result": DataTypeName[dispatch.operation.result.datatype], | |
| "compilation_info_name": dispatch.configuration.name(), | |
| } | |
| return SubstituteTemplate(self.linalg_row_row_matmul_template, values) | |
| class ReferenceBatchMatmulOp(ReferenceOpInterface): | |
| """Reference implementation for the batch matmul operation in numpy.""" | |
| def __init__(self, bmm_operation, op_reference_cache_path, dist_lhs, dist_rhs): | |
| self.bmm_operation = bmm_operation | |
| self.op_reference_cache_path = op_reference_cache_path | |
| if not self.op_reference_cache_path.exists(): | |
| self.op_reference_cache_path.mkdir() | |
| # Problem shape. | |
| self.batch_count = bmm_operation.batch_count | |
| self.M = bmm_operation.M | |
| self.N = bmm_operation.N | |
| self.K = bmm_operation.K | |
| # Data type for the input and result matrices. | |
| self.dtype_lhs = DataTypeNumPyTag[bmm_operation.lhs.datatype] | |
| self.dtype_rhs = DataTypeNumPyTag[bmm_operation.rhs.datatype] | |
| self.dtype_result = DataTypeNumPyTag[bmm_operation.result.datatype] | |
| # Distribution of the input tensors. | |
| self.dist_lhs = dist_lhs | |
| self.dist_rhs = dist_rhs | |
| # Filename for the left hand side input tensor. | |
| self.filename_lhs = ( | |
| "batch_count{batch_count}xm{problem_m}xk{problem_k}_" | |
| "{tensor_description}_{dist}_lhs.npy".format( | |
| batch_count=self.batch_count, | |
| problem_m=self.M, | |
| problem_k=self.K, | |
| tensor_description=self.bmm_operation.lhs.name(), | |
| dist=DistributionName[self.dist_lhs], | |
| ) | |
| ) | |
| # Filename for the right hand side input tensor. | |
| self.filename_rhs = ( | |
| "batch_count{batch_count}xk{problem_k}xn{problem_n}_" | |
| "{tensor_description}_{dist}_rhs.npy".format( | |
| batch_count=self.batch_count, | |
| problem_k=self.K, | |
| problem_n=self.N, | |
| tensor_description=self.bmm_operation.rhs.name(), | |
| dist=DistributionName[self.dist_rhs], | |
| ) | |
| ) | |
| # Filename for the reference result tensor. | |
| self.filename_reference_result = ( | |
| "batch_count{batch_count}xm{problem_m}xn{problem_n}_" | |
| "{tensor_description}_reference_result.npy".format( | |
| batch_count=self.batch_count, | |
| problem_m=self.M, | |
| problem_n=self.N, | |
| tensor_description=self.bmm_operation.result.name(), | |
| ) | |
| ) | |
| # Filepath for input and output files. | |
| self.filepath_lhs = self.op_reference_cache_path.joinpath(self.filename_lhs) | |
| self.filepath_rhs = self.op_reference_cache_path.joinpath(self.filename_rhs) | |
| self.filepath_reference_result = self.op_reference_cache_path.joinpath( | |
| self.filename_reference_result | |
| ) | |
| def get_input_filepaths(self): | |
| """Returns the list of input file paths.""" | |
| return [self.filepath_lhs, self.filepath_rhs] | |
| def get_output_filepaths(self): | |
| """Returns the list of expected output file paths.""" | |
| return [self.filepath_reference_result] | |
| def __call__(self): | |
| """Generates input data, runs reference numpy.matmul, and save npy files to the output directory.""" | |
| # Generate the input data as np.array for the matmul operation. | |
| lhs_np_array = get_np_array( | |
| self.bmm_operation.lhs, (self.batch_count, self.M, self.K), self.dist_lhs | |
| ) | |
| rhs_np_array = get_np_array( | |
| self.bmm_operation.rhs, (self.batch_count, self.K, self.N), self.dist_rhs | |
| ) | |
| # Run the reference np.matmul and generate result np.array. | |
| result = np.matmul(lhs_np_array, rhs_np_array) | |
| # Save the input data as np.array for the matmul operation. | |
| np.save(self.filepath_lhs, np.array(lhs_np_array, dtype=self.dtype_lhs)) | |
| np.save(self.filepath_rhs, np.array(rhs_np_array, dtype=self.dtype_rhs)) | |
| # Save the expected result as an np.array. | |
| np.save( | |
| self.filepath_reference_result, np.array(result, dtype=self.dtype_result) | |
| ) | |
| ############################################################################## | |
| class CudaBatchMatmulGenerator(CudaMatmulGenerator): | |
| """Batch matmul dispatch generator class.""" | |
| def __init__(self, args): | |
| """Initializes the batch matmul dispatch generator.""" | |
| super().__init__(args) | |
| # Predefined batch matmul problem shapes. | |
| self.batch_matmul_shapes = [[16, 512, 64, 512]] | |
| # Batch matmul dispatches collection. | |
| self.dispatches_collection_list = [] | |
| def _append_matmul_dispatch_collection( | |
| self, bmm_shapes, data_type, configuration_list | |
| ): | |
| """Update the batch matmul dispatch collection with the given configuration list.""" | |
| # Create dispatches collection for each problem shape with the configuration list. | |
| for bmm_shape in bmm_shapes: | |
| operation = BatchMatmulOperation( | |
| bmm_shape, | |
| TensorDescription(data_type[0], LayoutType.RowMajor), | |
| TensorDescription(data_type[1], LayoutType.RowMajor), | |
| TensorDescription(data_type[2], LayoutType.RowMajor), | |
| ) | |
| # Filter out configurations that are not supported by LLVM GPU CUDA backend. | |
| supported_configuration_list = self._cuda_supported_configuration_list( | |
| operation, configuration_list | |
| ) | |
| # Add default configuration if enabled. | |
| if self.args.default_config: | |
| supported_configuration_list.append( | |
| MatmulCompilationInfo( | |
| [], [], OperationKind.BatchMatmul, CompilationConfigType.Default | |
| ) | |
| ) | |
| # Append the dispatches collection. | |
| self.dispatches_collection_list.append( | |
| DispatchCollection(operation, supported_configuration_list) | |
| ) | |
| def _cuda_matmul_tensor_cores_f16(self): | |
| """Appends a list of matmul dispatches for GPU TensorCore F16 data type.""" | |
| configuration_list = self._get_matmul_custom_compilation_info_list( | |
| self.tile_descriptions_tensor_cores_f16, | |
| self.translation_infos, | |
| OperationKind.BatchMatmul, | |
| ) | |
| data_type = [DataType.f16, DataType.f16, DataType.f16] | |
| self._append_matmul_dispatch_collection( | |
| self.batch_matmul_shapes, data_type, configuration_list | |
| ) | |
| def _cuda_matmul_tensor_cores_f32(self): | |
| """Appends a list of matmul dispatches for GPU TensorCore F32 data type.""" | |
| configuration_list = self._get_matmul_custom_compilation_info_list( | |
| self.tile_descriptions_tensor_cores_f32, | |
| self.translation_infos, | |
| OperationKind.BatchMatmul, | |
| ) | |
| data_type = [DataType.f32, DataType.f32, DataType.f32] | |
| self._append_matmul_dispatch_collection( | |
| self.batch_matmul_shapes, data_type, configuration_list | |
| ) | |
| def generate(self): | |
| """Generates a list of matmul operations.""" | |
| self._cuda_matmul_tensor_cores_f16() | |
| self._cuda_matmul_tensor_cores_f32() | |
| return self.dispatches_collection_list |