diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d895be5..b210275 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -62,7 +62,21 @@ target_include_directories(onnf_shape_inference target_link_libraries(onnf_shape_inference ${MLIRLibs}) add_dependencies(onnf_shape_inference gen_krnl_ops) -add_library(onnf_lower_frontend conversion/onnx_to_krnl/convert_onnx_to_krnl.cpp) +add_library(onnf_lower_frontend + conversion/onnx_to_krnl/onnx_to_krnl_common.cpp + conversion/onnx_to_krnl/onnx_to_krnl_common.hpp + conversion/onnx_to_krnl/math/elementwise.cpp + conversion/onnx_to_krnl/math/gemm.cpp + conversion/onnx_to_krnl/math/matmul.cpp + conversion/onnx_to_krnl/math/reduction.cpp + conversion/onnx_to_krnl/math/softmax.cpp + conversion/onnx_to_krnl/nn/conv.cpp + conversion/onnx_to_krnl/nn/normalization.cpp + conversion/onnx_to_krnl/tensor/identity.cpp + conversion/onnx_to_krnl/tensor/reshape.cpp + conversion/onnx_to_krnl/tensor/transpose.cpp + conversion/onnx_to_krnl/tensor/unsqueeze.cpp + conversion/onnx_to_krnl/convert_onnx_to_krnl.cpp) target_include_directories(onnf_lower_frontend PRIVATE ${ONNF_SRC_ROOT} ${ONNF_BIN_ROOT} ${ONNF_SRC_ROOT}) diff --git a/src/conversion/onnx_to_krnl/convert_onnx_to_krnl.cpp b/src/conversion/onnx_to_krnl/convert_onnx_to_krnl.cpp index 84d4be8..ffc7219 100644 --- a/src/conversion/onnx_to_krnl/convert_onnx_to_krnl.cpp +++ b/src/conversion/onnx_to_krnl/convert_onnx_to_krnl.cpp @@ -8,404 +8,11 @@ // Krnl IR and standard operations. // //===----------------------------------------------------------------------===// -#include -#include "mlir/Dialect/AffineOps/AffineOps.h" -#include "mlir/Dialect/StandardOps/Ops.h" -#include "mlir/Pass/Pass.h" -#include "mlir/Transforms/DialectConversion.h" -#include "llvm/ADT/ArrayRef.h" -#include "llvm/ADT/Sequence.h" - -#include "src/dialect/krnl/krnl_helper.hpp" -#include "src/dialect/krnl/krnl_ops.hpp" -#include "src/dialect/onnx/onnx_ops.hpp" -#include "src/pass/passes.hpp" +#include "src/conversion/onnx_to_krnl/onnx_to_krnl_common.hpp" using namespace mlir; -//===----------------------------------------------------------------------===// -// FrontendToAffine RewritePatterns -//===----------------------------------------------------------------------===// - -/// Check is all dimensions are known at compile time. -static bool hasAllConstantDimensions(MemRefType type) { - auto memRefShape = type.getShape(); - for (int i = 0; i < memRefShape.size(); ++i) - if (memRefShape[i] < 0) - return false; - return true; -} - -/// Get the corresponding MemRefType of a given TensorType/MemRefType. -static MemRefType convertToMemRefType(Type type) { - MemRefType memRefType; - auto tensorType = type.dyn_cast(); - if (tensorType) { - assert(tensorType.hasRank() && "expected only ranked shapes"); - memRefType = - MemRefType::get(tensorType.getShape(), tensorType.getElementType()); - } else { - memRefType = type.dyn_cast(); - } - return memRefType; -} - -/// Insert an allocation and deallocation for the given MemRefType. -static Value insertAllocAndDealloc(MemRefType type, Location loc, - PatternRewriter &rewriter, - bool insertDealloc, - ArrayRef operands = {}) { - // Put together alloc operands for any dynamic dimensions of the memref. - AllocOp alloc; - if (!operands.empty()) { - auto memRefShape = type.getShape(); - auto rank = memRefShape.size(); - - std::map fromOperands; - for (int reversedIdx = 0; reversedIdx < rank; ++reversedIdx) { - int memRefDimIdx = rank - 1 - reversedIdx; - if (memRefShape[memRefDimIdx] < 0) { // unknown dimension - Value maxDim = nullptr; - for (int i = 0; i < operands.size(); i++) { - auto operandShape = - operands[i].getType().cast().getShape(); - int operandDimIdx = operandShape.size() - 1 - reversedIdx; - - if (operandDimIdx < 0) - continue; - - // In case of operations with broadcasting, the dimension of the - // alloc result is the maximum size along each dimension of the - // operands. - auto operandDim = - rewriter.create(loc, operands[i], operandDimIdx); - if (maxDim) { - auto maxCondition = rewriter.create(loc, CmpIPredicate::sgt, - operandDim, maxDim); - maxDim = rewriter.create(loc, maxCondition, operandDim, - maxDim); - } else { - maxDim = operandDim; - } - } - fromOperands.insert(std::make_pair(memRefDimIdx, maxDim)); - } - } - - SmallVector allocOperands; - for (int i = 0; i < rank; ++i) - if (memRefShape[i] < 0) - allocOperands.push_back(fromOperands[i]); - alloc = rewriter.create(loc, type, allocOperands); - } else { - alloc = rewriter.create(loc, type); - } - - // Make sure to allocate at the beginning of the block if - // all dimensions are known. - auto *parentBlock = alloc.getOperation()->getBlock(); - if (hasAllConstantDimensions(type)) - alloc.getOperation()->moveBefore(&parentBlock->front()); - - if (insertDealloc) { - auto dealloc = rewriter.create(loc, alloc); - dealloc.getOperation()->moveBefore(&parentBlock->back()); - } - - return alloc; -} - -// Determine if current function returns the result value of the -// current op being lowered. If it does then dealloc should not be -// inserted. -static bool checkInsertDealloc(Operation *currentOp) { - auto parentBlock = currentOp->getBlock(); - - bool insertDealloc = true; - parentBlock->walk([&insertDealloc, currentOp](ReturnOp op) { - assert(currentOp->getNumResults() < 2 && - "No more than one result supported (for now)."); - // If there is at least one result to investigate. - if (currentOp->getNumResults() > 0) { - auto result = currentOp->getResult(0); - for (const auto &operand : op.getOperands()) - if (operand == result) - insertDealloc = false; - } - }); - - return insertDealloc; -} - -// Create a mapping from result type's dimensions to input type's dimensions, -// given that the result type is the result of a reduction op over the input -// type. -std::map -getReductionMapping(MemRefType inputTy, ArrayRef axes, bool keepdims) { - std::map OutInDimMap; - int64_t rank = inputTy.getRank(); - - // Mark reduction axes. - std::vector isReductionAxis; - for (decltype(rank) i = 0; i < rank; ++i) { - if (std::find(axes.begin(), axes.end(), i) != axes.end()) - isReductionAxis.push_back(true); - else - isReductionAxis.push_back(false); - } - - for (decltype(rank) inIndex = 0, outIndex = 0; inIndex < rank; ++inIndex) { - // If it is a reduction axis, there is no relationship among dimensions. - if (isReductionAxis[inIndex]) { - if (keepdims) - outIndex++; - } else { - OutInDimMap.insert(std::make_pair(outIndex, inIndex)); - outIndex++; - } - } - - return OutInDimMap; -} - -// Add bounds associated with the op operand to the KRNL iteration pack. -// Dynamic dimenions are supported. -static void addDimensionToPack(ConversionPatternRewriter &rewriter, - Location loc, KrnlIterateOperandPack &pack, - Value operand, int index) { - auto shape = operand.getType().cast().getShape(); - if (shape[index] < 0) { - pack.pushConstantBound(0); - pack.pushOperandBound( - rewriter.create(loc, operand, index).getResult()); - } else { - pack.pushConstantBound(0); - pack.pushConstantBound(shape[index]); - } -} - -// Function that defines the KRNL dialect loops and their respective -// optimized version. -static KrnlOptimizeLoopsOp -emitOptimizedLoops(ConversionPatternRewriter &rewriter, Location loc, - std::vector &loops, - std::vector &optimizedLoops, int64_t numLoops) { - // Define loops. - auto loopsOp = rewriter.create(loc, numLoops); - loops.reserve(numLoops); - for (auto result : loopsOp.getResults()) - loops.push_back(result); - - // Define optimized version of the loops. - auto optimizedLoopsOp = rewriter.create(loc, numLoops); - optimizedLoops.reserve(numLoops); - for (auto result : optimizedLoopsOp.getResults()) - optimizedLoops.push_back(result); - - return optimizedLoopsOp; -} - -// Function that emits the loops and their optimized version. -// The function returns a reference to the inner optimization block. -static Block *defineLoops(ConversionPatternRewriter &rewriter, Location loc, - std::vector &loops, - std::vector &optimizedLoops, - int64_t numLoops) { - KrnlOptimizeLoopsOp optimizedLoopsOp = - emitOptimizedLoops(rewriter, loc, loops, optimizedLoops, numLoops); - return &optimizedLoopsOp.region().front(); -} - -// Function which emits a basic set of loops and optimized loops -// for a given operation argument. A reference to the loop optimization -// block is returned in the last argument of the function. -static void emitKrnlLoopsAndIterationForOperand( - ConversionPatternRewriter &rewriter, Location loc, Value operand, - std::vector &originalLoops, KrnlOptimizeLoopsOp &optimizedLoopsOp, - KrnlIterateOp &iterateOp) { - // Operand shape. - auto shape = operand.getType().cast().getShape(); - - // Number of loops. - int64_t rank = shape.size(); - - // Define loops and optimized loops. - std::vector optimizedLoops; - optimizedLoopsOp = - emitOptimizedLoops(rewriter, loc, originalLoops, optimizedLoops, rank); - - KrnlIterateOperandPack pack(rewriter, originalLoops, optimizedLoops); - // Iterate over the loop nest. - for (int i = 0; i < rank; ++i) - addDimensionToPack(rewriter, loc, pack, operand, i); - - iterateOp = rewriter.create(loc, pack); -} - -unsigned getMemRefEltSizeInBytes(MemRefType memRefType) { - auto elementType = memRefType.getElementType(); - - unsigned sizeInBits; - if (elementType.isIntOrFloat()) { - sizeInBits = elementType.getIntOrFloatBitWidth(); - } else { - auto vectorType = elementType.cast(); - sizeInBits = - vectorType.getElementTypeBitWidth() * vectorType.getNumElements(); - } - return llvm::divideCeil(sizeInBits, 8); -} - -// Get run-time dimension information for unknown dimensions used for -// broadcasting. -std::map> -getBroadcastedDimInfo(Location loc, ConversionPatternRewriter &rewriter, - MemRefType memRefType, ArrayRef operands) { - auto memRefShape = memRefType.getShape(); - int64_t rank = memRefShape.size(); - // For unknown dimensions, we need to get dimension values at runtime in - // order to do broadcasting. - std::map> DimInfo; - // For each result dimension, compute the number of sharing operands. - // Sharing operands are operands sharing the same index (counting from the - // rightmost to the leftmost) for a given dimension. - std::map sharedDimCount; - for (int reversedIdx = 0; reversedIdx < rank; ++reversedIdx) { - int dimIdx = rank - 1 - reversedIdx; - sharedDimCount[dimIdx] = 0; - for (int i = 0; i < operands.size(); ++i) { - auto shape = operands[i].getType().cast().getShape(); - if (reversedIdx <= shape.size() - 1) - sharedDimCount[dimIdx]++; - } - } - // An unknown dimension can have a value of 1 or N (N > 1). - // If its value is 1, it is broadcasted dimension. - // Otherwise, non-broadcasted dimension. - // We only care about unknown dimensions whose number of sharing operands is - // more than one, since they are potentially broadcasted dimensions. - for (int i = 0; i < operands.size(); ++i) { - std::map broadcastedDims; - auto shape = operands[i].getType().cast().getShape(); - int size = shape.size(); - for (int j = 0; j < shape.size(); ++j) { - if (shape[j] < 0 and sharedDimCount[rank - size + j] > 1) { - auto dim = rewriter.create(loc, operands[i], j).getResult(); - auto one = rewriter.create(loc, 1); - auto isBroadcasted = - rewriter.create(loc, CmpIPredicate::eq, dim, one); - broadcastedDims.insert(std::make_pair(j, isBroadcasted)); - } - } - DimInfo.insert(std::make_pair(i, broadcastedDims)); - } - return DimInfo; -} - -// Extract induction variables that are used for broadcasting values of a -// given operand. -std::vector -getLoopIVsForBroadcasting(Location loc, ConversionPatternRewriter &rewriter, - ArrayRef loopIVs, Value operand, - std::map broadcastedDims) { - // `operand` must has a ranked type. This should have been checked by the - // shape inference pass. - auto operandShape = operand.getType().cast().getShape(); - auto rank = operandShape.size(); - auto loopCount = loopIVs.size(); - - std::vector newLoopIVs; - for (unsigned reversedIdx = 0; reversedIdx < rank; ++reversedIdx) { - auto dimIdx = rank - 1 - reversedIdx; - auto loopIdx = loopCount - 1 - reversedIdx; - if (operandShape[dimIdx] == 1) { - // Broadcasted dimension - auto zero = rewriter.create(loc, 0); - newLoopIVs.insert(newLoopIVs.begin(), zero); - } else if ((operandShape[dimIdx] == -1) && - (broadcastedDims.find(dimIdx) != broadcastedDims.end())) { - // Unknown dimension, it can have a value of 1 or N (N > 1). - // If its value is 1, it is broadcasted dimension. - // Otherwise, non-broadcasted dimension. - auto zero = rewriter.create(loc, 0); - auto idx = rewriter.create(loc, broadcastedDims[dimIdx], zero, - loopIVs[loopIdx]); - newLoopIVs.insert(newLoopIVs.begin(), idx); - } else { - // Non-broadcasted dimension - newLoopIVs.insert(newLoopIVs.begin(), loopIVs[loopIdx]); - } - } - return newLoopIVs; -} - -namespace { - -// This is to get a scalar operation of a given type for a specific operation. -template -struct ScalarOp { - using FOp = void; - using IOp = void; -}; - -template -using ScalarFOp = typename ScalarOp::FOp; -template -using ScalarIOp = typename ScalarOp::IOp; - -// Get the identity element of a operation. -// Return NULL if the function does not have identity. -template -DataType getIdentityValue() { - return NULL; -} - -//===----------------------------------------------------------------------===// -// This is used in the innermost loop of a KrnlIterateOp to insert computation -// composed of one or many scalar ops. -// Use template specialization for each of different ONNX operations. -//===----------------------------------------------------------------------===// -template -Value mapToLowerScalarOp(Operation *op, ArrayRef result_types, - ArrayRef operands, - ConversionPatternRewriter &rewriter) { - auto loc = op->getLoc(); - Type element_type = operands.front().getType(); - if (element_type.isa()) { - return rewriter.create>(loc, result_types, operands, - mlir::None); - } else if (element_type.isa()) { - return rewriter.create>(loc, result_types, operands, - mlir::None); - } else { - emitError(loc, "unsupported element type"); - return nullptr; - } -} - -// We divide the operator lowering into different categories. -// These categories are mostly similar to the operator categories in ONNX: -// https://github.com/onnx/onnx/tree/master/onnx/defs. -// Besides, it is better to put operators with the same computation pattern into -// the same category, e.g. element-wise operators will belong to the elementwise -// category. - -// Math -#include "src/conversion/onnx_to_krnl/rewrite_patterns/math/elementwise.inc" -#include "src/conversion/onnx_to_krnl/rewrite_patterns/math/gemm.inc" -#include "src/conversion/onnx_to_krnl/rewrite_patterns/math/reduction.inc" -#include "src/conversion/onnx_to_krnl/rewrite_patterns/math/softmax.inc" -#include "src/conversion/onnx_to_krnl/rewrite_patterns/math/matmul.inc" -// Tensor -#include "src/conversion/onnx_to_krnl/rewrite_patterns/tensor/identity.inc" -#include "src/conversion/onnx_to_krnl/rewrite_patterns/tensor/reshape.inc" -#include "src/conversion/onnx_to_krnl/rewrite_patterns/tensor/transpose.inc" -#include "src/conversion/onnx_to_krnl/rewrite_patterns/tensor/unsqueeze.inc" -// Neural network -#include "src/conversion/onnx_to_krnl/rewrite_patterns/nn/conv.inc" -#include "src/conversion/onnx_to_krnl/rewrite_patterns/nn/normalization.inc" - //===----------------------------------------------------------------------===// // EntryPoint Op lowering to Krnl Entry Point. //===----------------------------------------------------------------------===// @@ -427,39 +34,6 @@ public: } }; -//===----------------------------------------------------------------------===// -// Conversion from Tensor type to the Standard dialect MemRef type. -//===----------------------------------------------------------------------===// - -struct TensorTypeConverter : public TypeConverter { - using TypeConverter::TypeConverter; - - TensorTypeConverter() { - addConversion(convertType); - } - - static LogicalResult convertType(Type t, SmallVectorImpl &results) { - if (auto type = convertToMemRefType(t)) { - results.push_back(type); - return success(); - } - - results.push_back(t); - return success(); - } - - /// Return true if the inputs and outputs of the given function type are - /// legal. [Taken from MLIR and adapted to only check the legality of the - /// inputs. Once unranked results can be handled gracefully this - /// override needs to be removed in favour of the original MLIR one.] - bool isSignatureLegal(FunctionType funcType) { - return llvm::all_of(funcType.getInputs(), - [this](Type type) { return isLegal(type); }); - } -}; - -} // end anonymous namespace. - //===----------------------------------------------------------------------===// // Frontend to Krnl Dialect lowering pass //===----------------------------------------------------------------------===// diff --git a/src/conversion/onnx_to_krnl/rewrite_patterns/math/elementwise.inc b/src/conversion/onnx_to_krnl/math/elementwise.cpp similarity index 99% rename from src/conversion/onnx_to_krnl/rewrite_patterns/math/elementwise.inc rename to src/conversion/onnx_to_krnl/math/elementwise.cpp index 945d4da..b397281 100644 --- a/src/conversion/onnx_to_krnl/rewrite_patterns/math/elementwise.inc +++ b/src/conversion/onnx_to_krnl/math/elementwise.cpp @@ -1,4 +1,4 @@ -//===----- elementwise.inc - Elementwise Ops ------------------------------===// +//===----- elementwise.cpp - Elementwise Ops ------------------------------===// // // Copyright 2019 The IBM Research Authors. // @@ -8,6 +8,10 @@ // //===----------------------------------------------------------------------===// +#include "src/conversion/onnx_to_krnl/onnx_to_krnl_common.hpp" + +using namespace mlir; + template <> struct ScalarOp { using FOp = AddFOp; diff --git a/src/conversion/onnx_to_krnl/rewrite_patterns/math/gemm.inc b/src/conversion/onnx_to_krnl/math/gemm.cpp similarity index 98% rename from src/conversion/onnx_to_krnl/rewrite_patterns/math/gemm.inc rename to src/conversion/onnx_to_krnl/math/gemm.cpp index ee395b5..0eed272 100644 --- a/src/conversion/onnx_to_krnl/rewrite_patterns/math/gemm.inc +++ b/src/conversion/onnx_to_krnl/math/gemm.cpp @@ -1,4 +1,4 @@ -//===----- gemm.inc - Lowering Gemm Op ------------------------------------===// +//===----- gemm.cpp - Lowering Gemm Op ------------------------------------===// // // Copyright 2019 The IBM Research Authors. // @@ -8,6 +8,10 @@ // //===----------------------------------------------------------------------===// +#include "src/conversion/onnx_to_krnl/onnx_to_krnl_common.hpp" + +using namespace mlir; + template struct ONNXGemmOpLowering : public ConversionPattern { ONNXGemmOpLowering(MLIRContext *ctx) diff --git a/src/conversion/onnx_to_krnl/rewrite_patterns/math/matmul.inc b/src/conversion/onnx_to_krnl/math/matmul.cpp similarity index 98% rename from src/conversion/onnx_to_krnl/rewrite_patterns/math/matmul.inc rename to src/conversion/onnx_to_krnl/math/matmul.cpp index 1af1f1b..a3cb26a 100644 --- a/src/conversion/onnx_to_krnl/rewrite_patterns/math/matmul.inc +++ b/src/conversion/onnx_to_krnl/math/matmul.cpp @@ -1,4 +1,4 @@ -//===----- matmul.inc - Lowering Matmul Op --------------------------------===// +//===----- matmul.cpp - Lowering Matmul Op --------------------------------===// // // Copyright 2019 The IBM Research Authors. // @@ -8,6 +8,10 @@ // //===----------------------------------------------------------------------===// +#include "src/conversion/onnx_to_krnl/onnx_to_krnl_common.hpp" + +using namespace mlir; + struct ONNXMatMulOpLowering : public ConversionPattern { ONNXMatMulOpLowering(MLIRContext *ctx) : ConversionPattern(mlir::ONNXMatMulOp::getOperationName(), 1, ctx) {} diff --git a/src/conversion/onnx_to_krnl/rewrite_patterns/math/reduction.inc b/src/conversion/onnx_to_krnl/math/reduction.cpp similarity index 98% rename from src/conversion/onnx_to_krnl/rewrite_patterns/math/reduction.inc rename to src/conversion/onnx_to_krnl/math/reduction.cpp index 9b94861..42b074a 100644 --- a/src/conversion/onnx_to_krnl/rewrite_patterns/math/reduction.inc +++ b/src/conversion/onnx_to_krnl/math/reduction.cpp @@ -1,4 +1,4 @@ -//===----- reduction.inc - Lowering Reduction Ops -------------------------===// +//===----- reduction.cpp - Lowering Reduction Ops -------------------------===// // // Copyright 2019 The IBM Research Authors. // @@ -8,6 +8,10 @@ // //===----------------------------------------------------------------------===// +#include "src/conversion/onnx_to_krnl/onnx_to_krnl_common.hpp" + +using namespace mlir; + // Identity values template <> float getIdentityValue(){ diff --git a/src/conversion/onnx_to_krnl/rewrite_patterns/math/softmax.inc b/src/conversion/onnx_to_krnl/math/softmax.cpp similarity index 98% rename from src/conversion/onnx_to_krnl/rewrite_patterns/math/softmax.inc rename to src/conversion/onnx_to_krnl/math/softmax.cpp index 3f24a6e..3277635 100644 --- a/src/conversion/onnx_to_krnl/rewrite_patterns/math/softmax.inc +++ b/src/conversion/onnx_to_krnl/math/softmax.cpp @@ -1,4 +1,4 @@ -//===----- softmax.inc - Softmax Op ---------------------------------------===// +//===----- softmax.cpp - Softmax Op ---------------------------------------===// // // Copyright 2019 The IBM Research Authors. // @@ -8,6 +8,10 @@ // //===----------------------------------------------------------------------===// +#include "src/conversion/onnx_to_krnl/onnx_to_krnl_common.hpp" + +using namespace mlir; + struct ONNXSoftmaxOpLowering : public ConversionPattern { ONNXSoftmaxOpLowering(MLIRContext *ctx) : ConversionPattern(mlir::ONNXSoftmaxOp::getOperationName(), 1, ctx) {} diff --git a/src/conversion/onnx_to_krnl/rewrite_patterns/nn/conv.inc b/src/conversion/onnx_to_krnl/nn/conv.cpp similarity index 98% rename from src/conversion/onnx_to_krnl/rewrite_patterns/nn/conv.inc rename to src/conversion/onnx_to_krnl/nn/conv.cpp index 6e3afe1..851668a 100644 --- a/src/conversion/onnx_to_krnl/rewrite_patterns/nn/conv.inc +++ b/src/conversion/onnx_to_krnl/nn/conv.cpp @@ -1,4 +1,4 @@ -//===----- conv.inc - Lowering Convolution Op -----------------------------===// +//===----- conv.cpp - Lowering Convolution Op -----------------------------===// // // Copyright 2019 The IBM Research Authors. // @@ -8,6 +8,10 @@ // //===----------------------------------------------------------------------===// +#include "src/conversion/onnx_to_krnl/onnx_to_krnl_common.hpp" + +using namespace mlir; + struct ONNXConvNoBiasOpLowering : public ConversionPattern { ONNXConvNoBiasOpLowering(MLIRContext *ctx) : ConversionPattern(mlir::ONNXConvNoBiasOp::getOperationName(), 1, ctx) {} diff --git a/src/conversion/onnx_to_krnl/rewrite_patterns/nn/normalization.inc b/src/conversion/onnx_to_krnl/nn/normalization.cpp similarity index 97% rename from src/conversion/onnx_to_krnl/rewrite_patterns/nn/normalization.inc rename to src/conversion/onnx_to_krnl/nn/normalization.cpp index cb98b13..d151f0a 100644 --- a/src/conversion/onnx_to_krnl/rewrite_patterns/nn/normalization.inc +++ b/src/conversion/onnx_to_krnl/nn/normalization.cpp @@ -1,4 +1,4 @@ -//===----- normalization.inc - Lowering Normalization Ops -----------------===// +//===----- normalization.cpp - Lowering Normalization Ops -----------------===// // // Copyright 2019 The IBM Research Authors. // @@ -8,6 +8,10 @@ // //===----------------------------------------------------------------------===// +#include "src/conversion/onnx_to_krnl/onnx_to_krnl_common.hpp" + +using namespace mlir; + struct ONNXBatchNormalizationTestModeOpLowering : public ConversionPattern { ONNXBatchNormalizationTestModeOpLowering(MLIRContext *ctx) : ConversionPattern( diff --git a/src/conversion/onnx_to_krnl/onnx_to_krnl_common.cpp b/src/conversion/onnx_to_krnl/onnx_to_krnl_common.cpp new file mode 100644 index 0000000..16bc499 --- /dev/null +++ b/src/conversion/onnx_to_krnl/onnx_to_krnl_common.cpp @@ -0,0 +1,324 @@ +//====-- onnx_to_krnl_common.cpp - ONNX dialects to Krnl lowering ---------===// +// +// Copyright 2019 The IBM Research Authors. +// +// ============================================================================= +// +// This file contains common code shared by the functions performing the +// lowering to the KRNL dialect. +// +//===----------------------------------------------------------------------===// + +#include "src/conversion/onnx_to_krnl/onnx_to_krnl_common.hpp" + +/// Check is all dimensions are known at compile time. +bool hasAllConstantDimensions(MemRefType type) { + auto memRefShape = type.getShape(); + for (int i = 0; i < memRefShape.size(); ++i) + if (memRefShape[i] < 0) + return false; + return true; +} + +/// Get the corresponding MemRefType of a given TensorType/MemRefType. +MemRefType convertToMemRefType(Type type) { + MemRefType memRefType; + auto tensorType = type.dyn_cast(); + if (tensorType) { + assert(tensorType.hasRank() && "expected only ranked shapes"); + memRefType = + MemRefType::get(tensorType.getShape(), tensorType.getElementType()); + } else { + memRefType = type.dyn_cast(); + } + return memRefType; +} + +/// Insert an allocation and deallocation for the given MemRefType. +Value insertAllocAndDealloc(MemRefType type, Location loc, + PatternRewriter &rewriter, + bool insertDealloc, + ArrayRef operands) { + // Put together alloc operands for any dynamic dimensions of the memref. + AllocOp alloc; + if (!operands.empty()) { + auto memRefShape = type.getShape(); + auto rank = memRefShape.size(); + + std::map fromOperands; + for (int reversedIdx = 0; reversedIdx < rank; ++reversedIdx) { + int memRefDimIdx = rank - 1 - reversedIdx; + if (memRefShape[memRefDimIdx] < 0) { // unknown dimension + Value maxDim = nullptr; + for (int i = 0; i < operands.size(); i++) { + auto operandShape = + operands[i].getType().cast().getShape(); + int operandDimIdx = operandShape.size() - 1 - reversedIdx; + + if (operandDimIdx < 0) + continue; + + // In case of operations with broadcasting, the dimension of the + // alloc result is the maximum size along each dimension of the + // operands. + auto operandDim = + rewriter.create(loc, operands[i], operandDimIdx); + if (maxDim) { + auto maxCondition = rewriter.create(loc, CmpIPredicate::sgt, + operandDim, maxDim); + maxDim = rewriter.create(loc, maxCondition, operandDim, + maxDim); + } else { + maxDim = operandDim; + } + } + fromOperands.insert(std::make_pair(memRefDimIdx, maxDim)); + } + } + + SmallVector allocOperands; + for (int i = 0; i < rank; ++i) + if (memRefShape[i] < 0) + allocOperands.push_back(fromOperands[i]); + alloc = rewriter.create(loc, type, allocOperands); + } else { + alloc = rewriter.create(loc, type); + } + + // Make sure to allocate at the beginning of the block if + // all dimensions are known. + auto *parentBlock = alloc.getOperation()->getBlock(); + if (hasAllConstantDimensions(type)) + alloc.getOperation()->moveBefore(&parentBlock->front()); + + if (insertDealloc) { + auto dealloc = rewriter.create(loc, alloc); + dealloc.getOperation()->moveBefore(&parentBlock->back()); + } + + return alloc; +} + +// Determine if current function returns the result value of the +// current op being lowered. If it does then dealloc should not be +// inserted. +bool checkInsertDealloc(Operation *currentOp) { + auto parentBlock = currentOp->getBlock(); + + bool insertDealloc = true; + parentBlock->walk([&insertDealloc, currentOp](ReturnOp op) { + assert(currentOp->getNumResults() < 2 && + "No more than one result supported (for now)."); + // If there is at least one result to investigate. + if (currentOp->getNumResults() > 0) { + auto result = currentOp->getResult(0); + for (const auto &operand : op.getOperands()) + if (operand == result) + insertDealloc = false; + } + }); + + return insertDealloc; +} + +// Create a mapping from result type's dimensions to input type's dimensions, +// given that the result type is the result of a reduction op over the input +// type. +std::map +getReductionMapping(MemRefType inputTy, ArrayRef axes, bool keepdims) { + std::map OutInDimMap; + int64_t rank = inputTy.getRank(); + + // Mark reduction axes. + std::vector isReductionAxis; + for (decltype(rank) i = 0; i < rank; ++i) { + if (std::find(axes.begin(), axes.end(), i) != axes.end()) + isReductionAxis.push_back(true); + else + isReductionAxis.push_back(false); + } + + for (decltype(rank) inIndex = 0, outIndex = 0; inIndex < rank; ++inIndex) { + // If it is a reduction axis, there is no relationship among dimensions. + if (isReductionAxis[inIndex]) { + if (keepdims) + outIndex++; + } else { + OutInDimMap.insert(std::make_pair(outIndex, inIndex)); + outIndex++; + } + } + + return OutInDimMap; +} + +// Add bounds associated with the op operand to the KRNL iteration pack. +// Dynamic dimenions are supported. +void addDimensionToPack(ConversionPatternRewriter &rewriter, + Location loc, KrnlIterateOperandPack &pack, + Value operand, int index) { + auto shape = operand.getType().cast().getShape(); + if (shape[index] < 0) { + pack.pushConstantBound(0); + pack.pushOperandBound( + rewriter.create(loc, operand, index).getResult()); + } else { + pack.pushConstantBound(0); + pack.pushConstantBound(shape[index]); + } +} + +// Function that defines the KRNL dialect loops and their respective +// optimized version. +KrnlOptimizeLoopsOp +emitOptimizedLoops(ConversionPatternRewriter &rewriter, Location loc, + std::vector &loops, + std::vector &optimizedLoops, int64_t numLoops) { + // Define loops. + auto loopsOp = rewriter.create(loc, numLoops); + loops.reserve(numLoops); + for (auto result : loopsOp.getResults()) + loops.push_back(result); + + // Define optimized version of the loops. + auto optimizedLoopsOp = rewriter.create(loc, numLoops); + optimizedLoops.reserve(numLoops); + for (auto result : optimizedLoopsOp.getResults()) + optimizedLoops.push_back(result); + + return optimizedLoopsOp; +} + +// Function that emits the loops and their optimized version. +// The function returns a reference to the inner optimization block. +Block *defineLoops(ConversionPatternRewriter &rewriter, Location loc, + std::vector &loops, + std::vector &optimizedLoops, + int64_t numLoops) { + KrnlOptimizeLoopsOp optimizedLoopsOp = + emitOptimizedLoops(rewriter, loc, loops, optimizedLoops, numLoops); + return &optimizedLoopsOp.region().front(); +} + +// Function which emits a basic set of loops and optimized loops +// for a given operation argument. A reference to the loop optimization +// block is returned in the last argument of the function. +void emitKrnlLoopsAndIterationForOperand( + ConversionPatternRewriter &rewriter, Location loc, Value operand, + std::vector &originalLoops, KrnlOptimizeLoopsOp &optimizedLoopsOp, + KrnlIterateOp &iterateOp) { + // Operand shape. + auto shape = operand.getType().cast().getShape(); + + // Number of loops. + int64_t rank = shape.size(); + + // Define loops and optimized loops. + std::vector optimizedLoops; + optimizedLoopsOp = + emitOptimizedLoops(rewriter, loc, originalLoops, optimizedLoops, rank); + + KrnlIterateOperandPack pack(rewriter, originalLoops, optimizedLoops); + // Iterate over the loop nest. + for (int i = 0; i < rank; ++i) + addDimensionToPack(rewriter, loc, pack, operand, i); + + iterateOp = rewriter.create(loc, pack); +} + +unsigned getMemRefEltSizeInBytes(MemRefType memRefType) { + auto elementType = memRefType.getElementType(); + + unsigned sizeInBits; + if (elementType.isIntOrFloat()) { + sizeInBits = elementType.getIntOrFloatBitWidth(); + } else { + auto vectorType = elementType.cast(); + sizeInBits = + vectorType.getElementTypeBitWidth() * vectorType.getNumElements(); + } + return llvm::divideCeil(sizeInBits, 8); +} + +// Get run-time dimension information for unknown dimensions used for +// broadcasting. +std::map> +getBroadcastedDimInfo(Location loc, ConversionPatternRewriter &rewriter, + MemRefType memRefType, ArrayRef operands) { + auto memRefShape = memRefType.getShape(); + int64_t rank = memRefShape.size(); + // For unknown dimensions, we need to get dimension values at runtime in + // order to do broadcasting. + std::map> DimInfo; + // For each result dimension, compute the number of sharing operands. + // Sharing operands are operands sharing the same index (counting from the + // rightmost to the leftmost) for a given dimension. + std::map sharedDimCount; + for (int reversedIdx = 0; reversedIdx < rank; ++reversedIdx) { + int dimIdx = rank - 1 - reversedIdx; + sharedDimCount[dimIdx] = 0; + for (int i = 0; i < operands.size(); ++i) { + auto shape = operands[i].getType().cast().getShape(); + if (reversedIdx <= shape.size() - 1) + sharedDimCount[dimIdx]++; + } + } + // An unknown dimension can have a value of 1 or N (N > 1). + // If its value is 1, it is broadcasted dimension. + // Otherwise, non-broadcasted dimension. + // We only care about unknown dimensions whose number of sharing operands is + // more than one, since they are potentially broadcasted dimensions. + for (int i = 0; i < operands.size(); ++i) { + std::map broadcastedDims; + auto shape = operands[i].getType().cast().getShape(); + int size = shape.size(); + for (int j = 0; j < shape.size(); ++j) { + if (shape[j] < 0 and sharedDimCount[rank - size + j] > 1) { + auto dim = rewriter.create(loc, operands[i], j).getResult(); + auto one = rewriter.create(loc, 1); + auto isBroadcasted = + rewriter.create(loc, CmpIPredicate::eq, dim, one); + broadcastedDims.insert(std::make_pair(j, isBroadcasted)); + } + } + DimInfo.insert(std::make_pair(i, broadcastedDims)); + } + return DimInfo; +} + +// Extract induction variables that are used for broadcasting values of a +// given operand. +std::vector +getLoopIVsForBroadcasting(Location loc, ConversionPatternRewriter &rewriter, + ArrayRef loopIVs, Value operand, + std::map broadcastedDims) { + // `operand` must has a ranked type. This should have been checked by the + // shape inference pass. + auto operandShape = operand.getType().cast().getShape(); + auto rank = operandShape.size(); + auto loopCount = loopIVs.size(); + + std::vector newLoopIVs; + for (unsigned reversedIdx = 0; reversedIdx < rank; ++reversedIdx) { + auto dimIdx = rank - 1 - reversedIdx; + auto loopIdx = loopCount - 1 - reversedIdx; + if (operandShape[dimIdx] == 1) { + // Broadcasted dimension + auto zero = rewriter.create(loc, 0); + newLoopIVs.insert(newLoopIVs.begin(), zero); + } else if ((operandShape[dimIdx] == -1) && + (broadcastedDims.find(dimIdx) != broadcastedDims.end())) { + // Unknown dimension, it can have a value of 1 or N (N > 1). + // If its value is 1, it is broadcasted dimension. + // Otherwise, non-broadcasted dimension. + auto zero = rewriter.create(loc, 0); + auto idx = rewriter.create(loc, broadcastedDims[dimIdx], zero, + loopIVs[loopIdx]); + newLoopIVs.insert(newLoopIVs.begin(), idx); + } else { + // Non-broadcasted dimension + newLoopIVs.insert(newLoopIVs.begin(), loopIVs[loopIdx]); + } + } + return newLoopIVs; +} diff --git a/src/conversion/onnx_to_krnl/onnx_to_krnl_common.hpp b/src/conversion/onnx_to_krnl/onnx_to_krnl_common.hpp new file mode 100644 index 0000000..bd22d95 --- /dev/null +++ b/src/conversion/onnx_to_krnl/onnx_to_krnl_common.hpp @@ -0,0 +1,217 @@ +//====-- onnx_to_krnl_common.hpp - ONNX dialects to Krnl lowering ---------===// +// +// Copyright 2019 The IBM Research Authors. +// +// ============================================================================= +// +// This file contains common code shared by the functions performing the +// lowering to the KRNL dialect. +// +//===----------------------------------------------------------------------===// + +#pragma once + +#include + +#include "mlir/Dialect/AffineOps/AffineOps.h" +#include "mlir/Dialect/StandardOps/Ops.h" +#include "mlir/Pass/Pass.h" +#include "mlir/Transforms/DialectConversion.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/Sequence.h" +#include "mlir/IR/PatternMatch.h" + +#include "src/dialect/krnl/krnl_helper.hpp" +#include "src/dialect/krnl/krnl_ops.hpp" +#include "src/dialect/onnx/onnx_ops.hpp" +#include "src/pass/passes.hpp" + +using namespace mlir; + +//===----------------------------------------------------------------------===// +// Common functions used when lowering the ONNX frontend dialect to KRNL. +//===----------------------------------------------------------------------===// + +/// Check is all dimensions are known at compile time. +bool hasAllConstantDimensions(MemRefType type); + +/// Get the corresponding MemRefType of a given TensorType/MemRefType. +MemRefType convertToMemRefType(Type type); + +/// Insert an allocation and deallocation for the given MemRefType. +Value insertAllocAndDealloc(MemRefType type, Location loc, + PatternRewriter &rewriter, + bool insertDealloc, + ArrayRef operands = {}); + +// Determine if current function returns the result value of the +// current op being lowered. If it does then dealloc should not be +// inserted. +bool checkInsertDealloc(Operation *currentOp); + +// Create a mapping from result type's dimensions to input type's dimensions, +// given that the result type is the result of a reduction op over the input +// type. +std::map +getReductionMapping(MemRefType inputTy, ArrayRef axes, bool keepdims); + +// Add bounds associated with the op operand to the KRNL iteration pack. +// Dynamic dimenions are supported. +void addDimensionToPack(ConversionPatternRewriter &rewriter, + Location loc, KrnlIterateOperandPack &pack, + Value operand, int index); + +// Function that defines the KRNL dialect loops and their respective +// optimized version. +KrnlOptimizeLoopsOp +emitOptimizedLoops(ConversionPatternRewriter &rewriter, Location loc, + std::vector &loops, + std::vector &optimizedLoops, int64_t numLoops); + +// Function that emits the loops and their optimized version. +// The function returns a reference to the inner optimization block. +Block *defineLoops(ConversionPatternRewriter &rewriter, Location loc, + std::vector &loops, + std::vector &optimizedLoops, + int64_t numLoops); + +// Function which emits a basic set of loops and optimized loops +// for a given operation argument. A reference to the loop optimization +// block is returned in the last argument of the function. +void emitKrnlLoopsAndIterationForOperand( + ConversionPatternRewriter &rewriter, Location loc, Value operand, + std::vector &originalLoops, KrnlOptimizeLoopsOp &optimizedLoopsOp, + KrnlIterateOp &iterateOp); + +unsigned getMemRefEltSizeInBytes(MemRefType memRefType); + +// Get run-time dimension information for unknown dimensions used for +// broadcasting. +std::map> +getBroadcastedDimInfo(Location loc, ConversionPatternRewriter &rewriter, + MemRefType memRefType, ArrayRef operands); + +// Extract induction variables that are used for broadcasting values of a +// given operand. +std::vector +getLoopIVsForBroadcasting(Location loc, ConversionPatternRewriter &rewriter, + ArrayRef loopIVs, Value operand, + std::map broadcastedDims); + +//===----------------------------------------------------------------------===// +// This is to get a scalar operation of a given type for a specific operation. +//===----------------------------------------------------------------------===// +template +struct ScalarOp { + using FOp = void; + using IOp = void; +}; + +template +using ScalarFOp = typename ScalarOp::FOp; +template +using ScalarIOp = typename ScalarOp::IOp; + +// Get the identity element of a operation. +// Return NULL if the function does not have identity. +template +DataType getIdentityValue() { + return NULL; +} + +//===----------------------------------------------------------------------===// +// This is used in the innermost loop of a KrnlIterateOp to insert computation +// composed of one or many scalar ops. +// Use template specialization for each of different ONNX operations. +//===----------------------------------------------------------------------===// +template +Value mapToLowerScalarOp(Operation *op, ArrayRef result_types, + ArrayRef operands, + ConversionPatternRewriter &rewriter) { + auto loc = op->getLoc(); + Type element_type = operands.front().getType(); + if (element_type.isa()) { + return rewriter.create>(loc, result_types, operands, + mlir::None); + } else if (element_type.isa()) { + return rewriter.create>(loc, result_types, operands, + mlir::None); + } else { + emitError(loc, "unsupported element type"); + return nullptr; + } +} + +//===----------------------------------------------------------------------===// +// Conversion from Tensor type to the Standard dialect MemRef type. +//===----------------------------------------------------------------------===// + +struct TensorTypeConverter : public TypeConverter { + using TypeConverter::TypeConverter; + + TensorTypeConverter() { + addConversion(convertType); + } + + static LogicalResult convertType(Type t, SmallVectorImpl &results) { + if (auto type = convertToMemRefType(t)) { + results.push_back(type); + return success(); + } + + results.push_back(t); + return success(); + } + + /// Return true if the inputs and outputs of the given function type are + /// legal. [Taken from MLIR and adapted to only check the legality of the + /// inputs. Once unranked results can be handled gracefully this + /// override needs to be removed in favour of the original MLIR one.] + bool isSignatureLegal(FunctionType funcType) { + return llvm::all_of(funcType.getInputs(), + [this](Type type) { return isLegal(type); }); + } +}; + +//===----------------------------------------------------------------------===// +// Functions to add lowering patterns for frontend operations. +//===----------------------------------------------------------------------===// + +// `math` directory methods: + +void populateLoweringONNXElementwiseOpPattern( + OwningRewritePatternList &patterns, MLIRContext *ctx); + +void populateLoweringONNXGemmOpPattern(OwningRewritePatternList &patterns, + MLIRContext *ctx); + +void populateLoweringONNXMatMulOpPattern( + OwningRewritePatternList &patterns, MLIRContext *ctx); + +void populateLoweringONNXReductionOpPattern( + OwningRewritePatternList &patterns, MLIRContext *ctx); + +void populateLoweringONNXSoftmaxOpPattern( + OwningRewritePatternList &patterns, MLIRContext *ctx); + +// `nn` directory methods: + +void populateLoweringONNXConvOpPattern( + OwningRewritePatternList &patterns, MLIRContext *ctx); + +void populateLoweringONNXNormalizationOpPattern( + OwningRewritePatternList &patterns, MLIRContext *ctx); + +// `tensor` directory methods: + +void populateLoweringONNXUnsqueezeOpPattern( + OwningRewritePatternList &patterns, MLIRContext *ctx); + +void populateLoweringONNXTransposeOpPattern( + OwningRewritePatternList &patterns, MLIRContext *ctx); + +void populateLoweringONNXReshapeOpPattern( + OwningRewritePatternList &patterns, MLIRContext *ctx); + +void populateLoweringONNXIdentityOpPattern( + OwningRewritePatternList &patterns, MLIRContext *ctx); diff --git a/src/conversion/onnx_to_krnl/rewrite_patterns/tensor/identity.inc b/src/conversion/onnx_to_krnl/tensor/identity.cpp similarity index 85% rename from src/conversion/onnx_to_krnl/rewrite_patterns/tensor/identity.inc rename to src/conversion/onnx_to_krnl/tensor/identity.cpp index 2ff1633..45985af 100644 --- a/src/conversion/onnx_to_krnl/rewrite_patterns/tensor/identity.inc +++ b/src/conversion/onnx_to_krnl/tensor/identity.cpp @@ -1,4 +1,4 @@ -//===----- identity.inc - Lowering Identity Op ----------------------------===// +//===----- identity.cpp - Lowering Identity Op ----------------------------===// // // Copyright 2019 The IBM Research Authors. // @@ -8,6 +8,10 @@ // //===----------------------------------------------------------------------===// +#include "src/conversion/onnx_to_krnl/onnx_to_krnl_common.hpp" + +using namespace mlir; + struct ONNXIdentityOpLowering : public ConversionPattern { ONNXIdentityOpLowering(MLIRContext *ctx) : ConversionPattern(mlir::ONNXIdentityOp::getOperationName(), 1, ctx) {} diff --git a/src/conversion/onnx_to_krnl/rewrite_patterns/tensor/reshape.inc b/src/conversion/onnx_to_krnl/tensor/reshape.cpp similarity index 97% rename from src/conversion/onnx_to_krnl/rewrite_patterns/tensor/reshape.inc rename to src/conversion/onnx_to_krnl/tensor/reshape.cpp index b64494f..6489a71 100644 --- a/src/conversion/onnx_to_krnl/rewrite_patterns/tensor/reshape.inc +++ b/src/conversion/onnx_to_krnl/tensor/reshape.cpp @@ -1,4 +1,4 @@ -//===----- reshape.inc - Lowering Reshape Op ------------------------------===// +//===----- reshape.cpp - Lowering Reshape Op ------------------------------===// // // Copyright 2019 The IBM Research Authors. // @@ -8,6 +8,10 @@ // //===----------------------------------------------------------------------===// +#include "src/conversion/onnx_to_krnl/onnx_to_krnl_common.hpp" + +using namespace mlir; + struct ONNXReshapeOpLowering : public ConversionPattern { ONNXReshapeOpLowering(MLIRContext *ctx) : ConversionPattern(mlir::ONNXReshapeOp::getOperationName(), 1, ctx) {} diff --git a/src/conversion/onnx_to_krnl/rewrite_patterns/tensor/transpose.inc b/src/conversion/onnx_to_krnl/tensor/transpose.cpp similarity index 96% rename from src/conversion/onnx_to_krnl/rewrite_patterns/tensor/transpose.inc rename to src/conversion/onnx_to_krnl/tensor/transpose.cpp index 3bb897a..0a6c8f4 100644 --- a/src/conversion/onnx_to_krnl/rewrite_patterns/tensor/transpose.inc +++ b/src/conversion/onnx_to_krnl/tensor/transpose.cpp @@ -1,4 +1,4 @@ -//===----- transpose.inc - Lowering Transpose Op --------------------------===// +//===----- transpose.cpp - Lowering Transpose Op --------------------------===// // // Copyright 2019 The IBM Research Authors. // @@ -8,6 +8,10 @@ // //===----------------------------------------------------------------------===// +#include "src/conversion/onnx_to_krnl/onnx_to_krnl_common.hpp" + +using namespace mlir; + struct ONNXTransposeOpLowering : public ConversionPattern { ONNXTransposeOpLowering(MLIRContext *ctx) : ConversionPattern(mlir::ONNXTransposeOp::getOperationName(), 1, ctx) {} diff --git a/src/conversion/onnx_to_krnl/rewrite_patterns/tensor/unsqueeze.inc b/src/conversion/onnx_to_krnl/tensor/unsqueeze.cpp similarity index 95% rename from src/conversion/onnx_to_krnl/rewrite_patterns/tensor/unsqueeze.inc rename to src/conversion/onnx_to_krnl/tensor/unsqueeze.cpp index 6d5289d..070a91c 100644 --- a/src/conversion/onnx_to_krnl/rewrite_patterns/tensor/unsqueeze.inc +++ b/src/conversion/onnx_to_krnl/tensor/unsqueeze.cpp @@ -1,4 +1,4 @@ -//===----- unsqueeze.inc - Lowering Unsqueeze Op --------------------------===// +//===----- unsqueeze.cpp - Lowering Unsqueeze Op --------------------------===// // // Copyright 2019 The IBM Research Authors. // @@ -8,6 +8,10 @@ // //===----------------------------------------------------------------------===// +#include "src/conversion/onnx_to_krnl/onnx_to_krnl_common.hpp" + +using namespace mlir; + struct ONNXUnsqueezeOpLowering : public ConversionPattern { ONNXUnsqueezeOpLowering(MLIRContext *ctx) : ConversionPattern(mlir::ONNXUnsqueezeOp::getOperationName(), 1, ctx) {}