CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
1144 lines
46 KiB
C++
1144 lines
46 KiB
C++
//===- TosaToLinalgNamed.cpp - Lowering Tosa to Linalg Named Ops ----------===//
|
|
//
|
|
// Part of the LLVM Project, 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// These rewriters lower from the Tosa to the Linalg named ops.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "mlir/Conversion/TosaToLinalg/TosaToLinalg.h"
|
|
#include "mlir/Dialect/Arith/IR/Arith.h"
|
|
#include "mlir/Dialect/Linalg/IR/Linalg.h"
|
|
#include "mlir/Dialect/Math/IR/Math.h"
|
|
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
|
#include "mlir/Dialect/Tosa/IR/TosaOps.h"
|
|
#include "mlir/Dialect/Tosa/Utils/ConversionUtils.h"
|
|
#include "mlir/Dialect/Utils/ReshapeOpsUtils.h"
|
|
#include "mlir/IR/PatternMatch.h"
|
|
#include "mlir/Transforms/DialectConversion.h"
|
|
#include "llvm/ADT/SmallVectorExtras.h"
|
|
|
|
#include <type_traits>
|
|
|
|
using namespace mlir;
|
|
using namespace mlir::tosa;
|
|
|
|
static mlir::Value applyPad(Location loc, Value input, ArrayRef<int64_t> pad,
|
|
TypedAttr padAttr, OpBuilder &rewriter) {
|
|
// Input should be padded only if necessary.
|
|
if (llvm::all_of(pad, [](int64_t p) { return p == 0; }))
|
|
return input;
|
|
|
|
ShapedType inputTy = cast<ShapedType>(input.getType());
|
|
Type inputETy = inputTy.getElementType();
|
|
auto inputShape = inputTy.getShape();
|
|
|
|
assert((inputShape.size() * 2) == pad.size());
|
|
|
|
SmallVector<int64_t, 4> paddedShape;
|
|
SmallVector<OpFoldResult, 8> lowIndices;
|
|
SmallVector<OpFoldResult, 8> highIndices;
|
|
for (size_t i : llvm::seq(inputShape.size())) {
|
|
auto lowPad = pad[i * 2];
|
|
auto highPad = pad[i * 2 + 1];
|
|
if (ShapedType::isDynamic(inputShape[i]))
|
|
paddedShape.push_back(inputShape[i]);
|
|
else
|
|
paddedShape.push_back(inputShape[i] + highPad + lowPad);
|
|
lowIndices.push_back(rewriter.getIndexAttr(lowPad));
|
|
highIndices.push_back(rewriter.getIndexAttr(highPad));
|
|
}
|
|
|
|
Value padValue = arith::ConstantOp::create(rewriter, loc, padAttr);
|
|
|
|
return tensor::PadOp::create(rewriter, loc,
|
|
RankedTensorType::get(paddedShape, inputETy),
|
|
input, lowIndices, highIndices, padValue);
|
|
}
|
|
|
|
static mlir::Value
|
|
linalgIntBroadcastExtSIAdd(PatternRewriter &rewriter, Location loc, Value bias,
|
|
Value conv, Value result,
|
|
ArrayRef<AffineMap> indexingMaps) {
|
|
ShapedType resultTy = cast<ShapedType>(conv.getType());
|
|
return linalg::GenericOp::create(
|
|
rewriter, loc, resultTy, ValueRange({bias, conv}), result,
|
|
indexingMaps, getNParallelLoopsAttrs(resultTy.getRank()),
|
|
[](OpBuilder &builder, Location loc, ValueRange args) {
|
|
Value biasVal = args[0];
|
|
Type resType = args[1].getType();
|
|
if (resType != biasVal.getType()) {
|
|
biasVal =
|
|
arith::ExtSIOp::create(builder, loc, resType, biasVal);
|
|
}
|
|
Value added =
|
|
arith::AddIOp::create(builder, loc, biasVal, args[1]);
|
|
linalg::YieldOp::create(builder, loc, added);
|
|
})
|
|
.getResult(0);
|
|
}
|
|
|
|
// Construct the affine map that a linalg generic would use to broadcast the
|
|
// source tensor into the shape of the result tensor.
|
|
static AffineMap getBroadcastingMap(PatternRewriter &rewriter, Value source,
|
|
Value result) {
|
|
ShapedType resultTy = cast<ShapedType>(result.getType());
|
|
ShapedType sourceTy = cast<ShapedType>(source.getType());
|
|
const int64_t resultRank = resultTy.getRank();
|
|
const int64_t sourceRank = sourceTy.getRank();
|
|
|
|
// The source tensor is broadcast to all the outer dimensions of the
|
|
// result tensor.
|
|
SmallVector<AffineExpr> sourceDims;
|
|
// In the case of a rank one source tensor with a single element TOSA
|
|
// specifies that the value be broadcast meaning we need an edge case for a
|
|
// constant map.
|
|
assert(sourceTy.hasStaticShape() &&
|
|
"Dynamic broadcasting shapes not supported!");
|
|
if (sourceRank == 1 && sourceTy.getDimSize(0) == 1) {
|
|
sourceDims.push_back(rewriter.getAffineConstantExpr(0));
|
|
} else {
|
|
for (auto dim : llvm::seq<int64_t>(0, sourceRank)) {
|
|
auto expr = rewriter.getAffineDimExpr(dim + resultRank - sourceRank);
|
|
sourceDims.push_back(expr);
|
|
}
|
|
}
|
|
|
|
return AffineMap::get(/*dimCount=*/resultRank,
|
|
/*symbolCount=*/0, sourceDims, rewriter.getContext());
|
|
}
|
|
|
|
// Broadcast the source value to all the outer dimensions of the result value.
|
|
// If required, the element type is expanded using an arith.extsi or arith.extf
|
|
// operation as appropriate.
|
|
static mlir::Value linalgBroadcastAndMaybeExt(PatternRewriter &rewriter,
|
|
Location loc, Value source,
|
|
Value result) {
|
|
ShapedType resultTy = cast<ShapedType>(result.getType());
|
|
const int64_t resultRank = resultTy.getRank();
|
|
// Creating maps for the input and output of the broacast-like generic op.
|
|
SmallVector<AffineMap, 2> indexingMaps;
|
|
indexingMaps.push_back(getBroadcastingMap(rewriter, source, result));
|
|
indexingMaps.push_back(rewriter.getMultiDimIdentityMap(resultRank));
|
|
|
|
// Build the broadcast-like operation as a linalg.generic.
|
|
return linalg::GenericOp::create(
|
|
rewriter, loc, resultTy, ValueRange({source}), result,
|
|
indexingMaps, getNParallelLoopsAttrs(resultTy.getRank()),
|
|
[&resultTy](OpBuilder &builder, Location loc, ValueRange args) {
|
|
Value biasVal = args[0];
|
|
Type resType = args[1].getType();
|
|
if (resType != biasVal.getType()) {
|
|
biasVal =
|
|
resultTy.getElementType().isFloat()
|
|
? arith::ExtFOp::create(builder, loc, resType, biasVal)
|
|
.getResult()
|
|
: arith::ExtSIOp::create(builder, loc, resType,
|
|
biasVal)
|
|
.getResult();
|
|
}
|
|
linalg::YieldOp::create(builder, loc, biasVal);
|
|
})
|
|
.getResult(0);
|
|
}
|
|
|
|
static mlir::Value reifyConstantDim(int64_t attr,
|
|
ImplicitLocOpBuilder &builder) {
|
|
return arith::ConstantIndexOp::create(builder, attr);
|
|
}
|
|
|
|
// Calculating the output width/height using the formula:
|
|
// H = ((IH+pad_top+pad_bottom-(dilation_y*(KH-1)+1))/stride_y)+1
|
|
// W = ((IW+pad_left+pad_right-(dilation_x*(KW-1)+1))/stride_x)+1
|
|
|
|
static mlir::Value getConvOrPoolOutputDim(Location loc, Value inputDim,
|
|
int64_t padBeforeAttr,
|
|
int64_t padAfterAttr, Value kernelDim,
|
|
int64_t strideAttr,
|
|
int64_t dilationAttr,
|
|
OpBuilder &rewriter) {
|
|
ImplicitLocOpBuilder builder(loc, rewriter);
|
|
auto one = arith::ConstantOp::create(rewriter, loc,
|
|
IntegerAttr::get(inputDim.getType(), 1));
|
|
Value padBefore = reifyConstantDim(padBeforeAttr, builder);
|
|
Value paddedBefore = arith::AddIOp::create(builder, inputDim, padBefore);
|
|
Value padAfter = reifyConstantDim(padAfterAttr, builder);
|
|
Value paddedAfter = arith::AddIOp::create(builder, paddedBefore, padAfter);
|
|
|
|
Value subOne = arith::SubIOp::create(builder, kernelDim, one);
|
|
Value dilation = reifyConstantDim(dilationAttr, builder);
|
|
Value dilated = arith::MulIOp::create(builder, dilation, subOne);
|
|
Value addOne = arith::AddIOp::create(builder, dilated, one);
|
|
|
|
Value subtract = arith::SubIOp::create(builder, paddedAfter, addOne);
|
|
Value stride = reifyConstantDim(strideAttr, builder);
|
|
Value divide = arith::DivUIOp::create(builder, subtract, stride);
|
|
return arith::AddIOp::create(builder, divide, one);
|
|
}
|
|
|
|
// Creates a vector of the dynamic output dims for Conv2D and Depthwise_Conv2D
|
|
static SmallVector<Value> inferDynamicDimsForConv(
|
|
Location loc, Value input, Value weight, ShapedType resultTy,
|
|
ArrayRef<int64_t> padAttr, ArrayRef<int64_t> strideAttr,
|
|
ArrayRef<int64_t> dilationAttr, ArrayRef<int64_t> inputSizeDims,
|
|
ArrayRef<int64_t> kernelSizeDims, OpBuilder &rewriter) {
|
|
ShapedType inputTy = cast<ShapedType>(input.getType());
|
|
int64_t inputRank = inputTy.getRank();
|
|
|
|
SmallVector<Value> dynDims;
|
|
dynDims.resize(resultTy.getRank());
|
|
|
|
for (uint32_t i = 0, s = inputSizeDims.size(); i < s; ++i) {
|
|
int64_t inputDim = inputSizeDims[i];
|
|
int64_t kernelDim = kernelSizeDims[i];
|
|
if (resultTy.isDynamicDim(inputDim)) {
|
|
auto padTop = padAttr[i * 2];
|
|
auto padBottom = padAttr[i * 2 + 1];
|
|
auto stride = strideAttr[i];
|
|
auto dilation = dilationAttr[i];
|
|
Value initDynDim = tensor::DimOp::create(rewriter, loc, input, inputDim);
|
|
Value kernelDynDim =
|
|
tensor::DimOp::create(rewriter, loc, weight, kernelDim);
|
|
// H = F(IH, pad_top, pad_bottom, dilation_y, KH, stride_y)
|
|
dynDims[inputDim] =
|
|
getConvOrPoolOutputDim(loc, initDynDim, padTop, padBottom,
|
|
kernelDynDim, stride, dilation, rewriter);
|
|
}
|
|
}
|
|
|
|
// Get the batch/channels dimensions.
|
|
for (int i = 0; i < inputRank; i++) {
|
|
if (resultTy.isDynamicDim(i) && !dynDims[i])
|
|
dynDims[i] = tensor::DimOp::create(rewriter, loc, input, i);
|
|
}
|
|
|
|
SmallVector<Value> filteredDims = condenseValues(dynDims);
|
|
return filteredDims;
|
|
}
|
|
|
|
// Creates a map to collapse the last dimension of the Depthwise convolution op
|
|
// due to a shape mismatch
|
|
static void createDepthwiseConvCollapseMap(
|
|
int64_t outputRank, SmallVector<ReassociationExprs, 4> &reassociationMap,
|
|
OpBuilder &rewriter) {
|
|
reassociationMap.resize(outputRank);
|
|
for (int i = 0; i < outputRank; i++) {
|
|
reassociationMap[i].push_back(rewriter.getAffineDimExpr(i));
|
|
}
|
|
reassociationMap[outputRank - 1].push_back(
|
|
rewriter.getAffineDimExpr(outputRank));
|
|
}
|
|
|
|
namespace {
|
|
|
|
template <typename TosaConvOp, typename LinalgConvOp, typename LinalgConvQOp>
|
|
class ConvConverter : public OpConversionPattern<TosaConvOp> {
|
|
public:
|
|
using OpConversionPattern<TosaConvOp>::OpConversionPattern;
|
|
LogicalResult
|
|
matchAndRewrite(TosaConvOp op, typename TosaConvOp::Adaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
Location loc = op->getLoc();
|
|
Value input = op->getOperand(0);
|
|
Value weight = op->getOperand(1);
|
|
Value bias = op->getOperand(2);
|
|
|
|
ShapedType inputTy = cast<ShapedType>(input.getType());
|
|
ShapedType weightTy = cast<ShapedType>(weight.getType());
|
|
ShapedType biasTy = cast<ShapedType>(bias.getType());
|
|
ShapedType resultTy = cast<ShapedType>(op->getResult(0).getType());
|
|
|
|
Type inputETy = inputTy.getElementType();
|
|
|
|
DenseI64ArrayAttr padAttr = op.getPadAttr();
|
|
DenseI64ArrayAttr strideTosaAttr = op.getStrideAttr();
|
|
DenseI64ArrayAttr dilationTosaAttr = op.getDilationAttr();
|
|
|
|
Type accETy = op.getAccType();
|
|
Type accTy = RankedTensorType::get(resultTy.getShape(), accETy);
|
|
|
|
// Get and verify zero points.
|
|
FailureOr<int64_t> maybeIZp = op.getInputZeroPoint();
|
|
if (failed(maybeIZp))
|
|
return rewriter.notifyMatchFailure(
|
|
op, "input zero point cannot be statically determined");
|
|
|
|
FailureOr<int64_t> maybeWZp = op.getWeightZeroPoint();
|
|
if (failed(maybeWZp))
|
|
return rewriter.notifyMatchFailure(
|
|
op, "weight zero point cannot be statically determined");
|
|
|
|
const int64_t inputZpVal = *maybeIZp;
|
|
const int64_t weightZpVal = *maybeWZp;
|
|
|
|
if (op.verifyInputZeroPoint(inputZpVal).failed())
|
|
return rewriter.notifyMatchFailure(
|
|
op, "input zero point must be zero for non-int8 integer types");
|
|
|
|
if (op.verifyWeightZeroPoint(weightZpVal).failed())
|
|
return rewriter.notifyMatchFailure(
|
|
op, "weight zero point must be zero for non-int8 integer types");
|
|
|
|
bool hasZp = (inputZpVal != 0) || (weightZpVal != 0);
|
|
|
|
if (!weightTy.hasStaticShape() || !biasTy.hasStaticShape())
|
|
return rewriter.notifyMatchFailure(
|
|
op, "tosa.conv ops require static shapes for weight and bias");
|
|
|
|
if (inputETy.isUnsignedInteger())
|
|
return rewriter.notifyMatchFailure(
|
|
op, "tosa.conv ops does not support unsigned integer input");
|
|
|
|
llvm::SmallVector<int64_t> inputSizeDims;
|
|
llvm::SmallVector<int64_t> kernelSizeDims;
|
|
for (int i = 1; i < resultTy.getRank() - 1; i++) {
|
|
inputSizeDims.push_back(i);
|
|
kernelSizeDims.push_back(i);
|
|
}
|
|
|
|
SmallVector<Value> filteredDims = inferDynamicDimsForConv(
|
|
loc, input, weight, resultTy, padAttr.asArrayRef(),
|
|
strideTosaAttr.asArrayRef(), dilationTosaAttr.asArrayRef(),
|
|
inputSizeDims, kernelSizeDims, rewriter);
|
|
|
|
auto weightShape = weightTy.getShape();
|
|
|
|
// Apply padding as necessary.
|
|
TypedAttr zeroAttr = rewriter.getZeroAttr(inputETy);
|
|
if (hasZp) {
|
|
int64_t intMin =
|
|
APInt::getSignedMinValue(inputETy.getIntOrFloatBitWidth())
|
|
.getSExtValue();
|
|
int64_t intMax =
|
|
APInt::getSignedMaxValue(inputETy.getIntOrFloatBitWidth())
|
|
.getSExtValue();
|
|
|
|
if (inputZpVal < intMin || inputZpVal > intMax)
|
|
return rewriter.notifyMatchFailure(
|
|
op, "tosa.conv op quantization has zp outside of input range");
|
|
|
|
zeroAttr = rewriter.getIntegerAttr(inputETy, inputZpVal);
|
|
}
|
|
|
|
llvm::SmallVector<int64_t> pad;
|
|
pad.resize(2, 0);
|
|
llvm::append_range(pad, padAttr.asArrayRef());
|
|
pad.resize(pad.size() + 2, 0);
|
|
input = applyPad(loc, input, pad, zeroAttr, rewriter);
|
|
|
|
if (4 == inputTy.getRank()) {
|
|
// For 2D convolutions, we need to check if the target convolution op
|
|
// wants a HWCF kernel layout.
|
|
bool wantHwcf =
|
|
hasZp ? std::is_same_v<LinalgConvQOp, linalg::Conv2DNhwcHwcfQOp>
|
|
: std::is_same_v<LinalgConvOp, linalg::Conv2DNhwcHwcfOp>;
|
|
if (wantHwcf) {
|
|
// Transpose the kernel to match dimension ordering of the linalg
|
|
// convolution operation.
|
|
// TODO(suderman): See if this can be efficiently folded - check whether
|
|
// the input is used anywhere else, if not fold the constant.
|
|
SmallVector<int32_t> weightPerm;
|
|
for (int i = 1; i < resultTy.getRank(); i++)
|
|
weightPerm.push_back(i);
|
|
weightPerm.push_back(0);
|
|
|
|
SmallVector<int64_t> newWeightShape;
|
|
for (auto dim : weightPerm)
|
|
newWeightShape.push_back(weightShape[dim]);
|
|
auto weightPermAttr = rewriter.getDenseI32ArrayAttr(weightPerm);
|
|
Type newWeightTy =
|
|
RankedTensorType::get(newWeightShape, weightTy.getElementType());
|
|
weight = tosa::TransposeOp::create(rewriter, loc, newWeightTy, weight,
|
|
weightPermAttr);
|
|
}
|
|
}
|
|
|
|
// For Conv3D transpose the kernel to match dimension ordering of the linalg
|
|
// convolution operation. Conv2D has a 1-1 mapping in linalg so better to
|
|
// map directly and then transpose later if desired.
|
|
if (5 == inputTy.getRank()) {
|
|
// TODO(suderman): See if this can be efficiently folded - check whether
|
|
// the input is used anywhere else, if not fold the constant.
|
|
SmallVector<int32_t> weightPerm;
|
|
for (int i = 1; i < resultTy.getRank(); i++)
|
|
weightPerm.push_back(i);
|
|
weightPerm.push_back(0);
|
|
|
|
SmallVector<int64_t> newWeightShape;
|
|
for (auto dim : weightPerm)
|
|
newWeightShape.push_back(weightShape[dim]);
|
|
auto weightPermAttr = rewriter.getDenseI32ArrayAttr(weightPerm);
|
|
Type newWeightTy =
|
|
RankedTensorType::get(newWeightShape, weightTy.getElementType());
|
|
weight = tosa::TransposeOp::create(rewriter, loc, newWeightTy, weight,
|
|
weightPermAttr);
|
|
}
|
|
|
|
// Extract the attributes for convolution.
|
|
ArrayRef<int64_t> stride = strideTosaAttr;
|
|
ArrayRef<int64_t> dilation = dilationTosaAttr;
|
|
|
|
// Create the convolution op.
|
|
auto strideAttr = rewriter.getI64TensorAttr(stride);
|
|
auto dilationAttr = rewriter.getI64TensorAttr(dilation);
|
|
|
|
Value biasEmptyTensor = tensor::EmptyOp::create(
|
|
rewriter, loc, resultTy.getShape(), accETy, filteredDims);
|
|
|
|
Value broadcastBias =
|
|
linalgBroadcastAndMaybeExt(rewriter, loc, bias, biasEmptyTensor);
|
|
|
|
if (hasZp) {
|
|
auto iZp = rewriter.getI32IntegerAttr(inputZpVal);
|
|
auto kZp = rewriter.getI32IntegerAttr(weightZpVal);
|
|
|
|
auto iZpVal = arith::ConstantOp::create(rewriter, loc, iZp);
|
|
auto kZpVal = arith::ConstantOp::create(rewriter, loc, kZp);
|
|
|
|
Value conv = LinalgConvQOp::create(
|
|
rewriter, loc, resultTy,
|
|
ValueRange{input, weight, iZpVal, kZpVal},
|
|
ValueRange{broadcastBias}, strideAttr, dilationAttr)
|
|
->getResult(0);
|
|
|
|
rewriter.replaceOp(op, conv);
|
|
return success();
|
|
}
|
|
|
|
Value conv = LinalgConvOp::create(
|
|
rewriter, loc, accTy, ValueRange{input, weight},
|
|
ValueRange{broadcastBias}, strideAttr, dilationAttr)
|
|
->getResult(0);
|
|
|
|
// We may need to truncate back to the result type if the accumulator was
|
|
// wider than the result.
|
|
if (resultTy != accTy)
|
|
conv = tosa::CastOp::create(rewriter, loc, resultTy, conv);
|
|
|
|
rewriter.replaceOp(op, conv);
|
|
return success();
|
|
}
|
|
};
|
|
|
|
class DepthwiseConvConverter
|
|
: public OpConversionPattern<tosa::DepthwiseConv2DOp> {
|
|
public:
|
|
using OpConversionPattern<tosa::DepthwiseConv2DOp>::OpConversionPattern;
|
|
LogicalResult
|
|
matchAndRewrite(tosa::DepthwiseConv2DOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
Location loc = op->getLoc();
|
|
Value input = op->getOperand(0);
|
|
Value weight = op->getOperand(1);
|
|
Value bias = op->getOperand(2);
|
|
|
|
ShapedType inputTy = cast<ShapedType>(input.getType());
|
|
ShapedType weightTy = cast<ShapedType>(weight.getType());
|
|
ShapedType biasTy = cast<ShapedType>(bias.getType());
|
|
ShapedType resultTy = cast<ShapedType>(op->getResult(0).getType());
|
|
int64_t resultRank = resultTy.getRank();
|
|
|
|
Type inputETy = inputTy.getElementType();
|
|
Type resultETy = resultTy.getElementType();
|
|
|
|
auto padAttr = cast<DenseI64ArrayAttr>(op->getAttr("pad"));
|
|
auto strideTosaAttr = cast<DenseI64ArrayAttr>(op->getAttr("stride"));
|
|
auto dilationTosaAttr = cast<DenseI64ArrayAttr>(op->getAttr("dilation"));
|
|
|
|
Type accETy = op.getAccType();
|
|
|
|
if (!weightTy.hasStaticShape() || !biasTy.hasStaticShape())
|
|
return rewriter.notifyMatchFailure(
|
|
op, "tosa.depthwise_conv ops require static shapes");
|
|
|
|
// Compute output dynamic dims
|
|
SmallVector<Value> filteredDims = inferDynamicDimsForConv(
|
|
loc, input, weight, resultTy, padAttr.asArrayRef(),
|
|
strideTosaAttr.asArrayRef(), dilationTosaAttr.asArrayRef(),
|
|
/*inputSizeDims=*/{1, 2},
|
|
/*kernelSizeDims=*/{0, 1}, rewriter);
|
|
|
|
// Get and verify zero points.
|
|
|
|
FailureOr<int64_t> maybeIZp = op.getInputZeroPoint();
|
|
FailureOr<int64_t> maybeWZp = op.getWeightZeroPoint();
|
|
if (failed(maybeIZp))
|
|
return rewriter.notifyMatchFailure(
|
|
op, "input zero point cannot be statically determined");
|
|
if (failed(maybeWZp))
|
|
return rewriter.notifyMatchFailure(
|
|
op, "weight zero point cannot be statically determined");
|
|
|
|
const int64_t inputZpVal = *maybeIZp;
|
|
const int64_t weightZpVal = *maybeWZp;
|
|
|
|
if (op.verifyInputZeroPoint(inputZpVal).failed())
|
|
return rewriter.notifyMatchFailure(
|
|
op, "input zero point must be zero for non-int8 integer types");
|
|
|
|
if (op.verifyWeightZeroPoint(weightZpVal).failed())
|
|
return rewriter.notifyMatchFailure(
|
|
op, "weight zero point must be zero for non-int8 integer types");
|
|
|
|
bool hasNullZps = (inputZpVal == 0) && (weightZpVal == 0);
|
|
auto weightShape = weightTy.getShape();
|
|
auto resultShape = resultTy.getShape();
|
|
|
|
// Apply padding as necessary.
|
|
TypedAttr zeroAttr = rewriter.getZeroAttr(inputETy);
|
|
if (!hasNullZps) {
|
|
int64_t intMin =
|
|
APInt::getSignedMinValue(inputETy.getIntOrFloatBitWidth())
|
|
.getSExtValue();
|
|
int64_t intMax =
|
|
APInt::getSignedMaxValue(inputETy.getIntOrFloatBitWidth())
|
|
.getSExtValue();
|
|
|
|
if (inputZpVal < intMin || inputZpVal > intMax)
|
|
return rewriter.notifyMatchFailure(
|
|
op, "tosa.depthwise_conv op quantization has zp outside of input "
|
|
"range");
|
|
|
|
zeroAttr = rewriter.getIntegerAttr(inputETy, inputZpVal);
|
|
}
|
|
|
|
llvm::SmallVector<int64_t> pad;
|
|
pad.resize(2, 0);
|
|
llvm::append_range(pad, padAttr.asArrayRef());
|
|
pad.resize(pad.size() + 2, 0);
|
|
|
|
input = applyPad(loc, input, pad, zeroAttr, rewriter);
|
|
|
|
// Extract the attributes for convolution.
|
|
ArrayRef<int64_t> stride = strideTosaAttr;
|
|
ArrayRef<int64_t> dilation = dilationTosaAttr;
|
|
|
|
// Create the convolution op.
|
|
auto strideAttr = rewriter.getI64TensorAttr(stride);
|
|
auto dilationAttr = rewriter.getI64TensorAttr(dilation);
|
|
ShapedType linalgConvTy =
|
|
RankedTensorType::get({resultShape[0], resultShape[1], resultShape[2],
|
|
weightShape[2], weightShape[3]},
|
|
accETy);
|
|
|
|
auto resultZeroAttr = rewriter.getZeroAttr(accETy);
|
|
Value emptyTensor = tensor::EmptyOp::create(
|
|
rewriter, loc, linalgConvTy.getShape(), accETy, filteredDims);
|
|
Value zero = arith::ConstantOp::create(rewriter, loc, resultZeroAttr);
|
|
Value zeroTensor = linalg::FillOp::create(rewriter, loc, ValueRange{zero},
|
|
ValueRange{emptyTensor})
|
|
.result();
|
|
|
|
Value biasEmptyTensor = tensor::EmptyOp::create(
|
|
rewriter, loc, resultTy.getShape(), resultETy, filteredDims);
|
|
|
|
// Broadcast the initial value to the output tensor before convolving.
|
|
SmallVector<AffineMap, 4> indexingMaps;
|
|
indexingMaps.push_back(getBroadcastingMap(rewriter, bias, biasEmptyTensor));
|
|
indexingMaps.push_back(rewriter.getMultiDimIdentityMap(resultRank));
|
|
indexingMaps.push_back(rewriter.getMultiDimIdentityMap(resultRank));
|
|
|
|
if (hasNullZps) {
|
|
Value conv = linalg::DepthwiseConv2DNhwcHwcmOp::create(
|
|
rewriter, loc, linalgConvTy, ValueRange{input, weight},
|
|
ValueRange{zeroTensor}, strideAttr, dilationAttr)
|
|
.getResult(0);
|
|
|
|
// We may need to truncate back to the result type if the accumulator was
|
|
// wider than the result.
|
|
if (accETy != resultETy)
|
|
conv = tosa::CastOp::create(
|
|
rewriter, loc,
|
|
RankedTensorType::get(cast<ShapedType>(conv.getType()).getShape(),
|
|
resultETy),
|
|
conv);
|
|
|
|
SmallVector<ReassociationExprs, 4> reassociationMap;
|
|
createDepthwiseConvCollapseMap(resultRank, reassociationMap, rewriter);
|
|
Value convReshape = tensor::CollapseShapeOp::create(
|
|
rewriter, loc, resultTy, conv, reassociationMap);
|
|
|
|
Value result =
|
|
linalg::GenericOp::create(
|
|
rewriter, loc, resultTy, ValueRange({bias, convReshape}),
|
|
biasEmptyTensor, indexingMaps, getNParallelLoopsAttrs(resultRank),
|
|
[&](OpBuilder &nestedBuilder, Location nestedLoc,
|
|
ValueRange args) {
|
|
Value added;
|
|
if (llvm::isa<FloatType>(inputETy))
|
|
added = arith::AddFOp::create(nestedBuilder, loc, args[0],
|
|
args[1]);
|
|
else
|
|
added = arith::AddIOp::create(nestedBuilder, loc, args[0],
|
|
args[1]);
|
|
linalg::YieldOp::create(nestedBuilder, nestedLoc, added);
|
|
})
|
|
.getResult(0);
|
|
rewriter.replaceOp(op, result);
|
|
} else {
|
|
IntegerAttr iZp = rewriter.getI32IntegerAttr(inputZpVal);
|
|
IntegerAttr wZp = rewriter.getI32IntegerAttr(weightZpVal);
|
|
auto iZpVal = arith::ConstantOp::create(rewriter, loc, iZp);
|
|
auto kZpVal = arith::ConstantOp::create(rewriter, loc, wZp);
|
|
Value conv = linalg::DepthwiseConv2DNhwcHwcmQOp::create(
|
|
rewriter, loc, linalgConvTy,
|
|
ValueRange{input, weight, iZpVal, kZpVal},
|
|
ValueRange{zeroTensor}, strideAttr, dilationAttr)
|
|
.getResult(0);
|
|
SmallVector<ReassociationExprs, 4> reassociationMap;
|
|
createDepthwiseConvCollapseMap(resultRank, reassociationMap, rewriter);
|
|
Value convReshape = tensor::CollapseShapeOp::create(
|
|
rewriter, loc, resultTy, conv, reassociationMap);
|
|
Value result = linalgIntBroadcastExtSIAdd(
|
|
rewriter, loc, bias, convReshape, biasEmptyTensor, indexingMaps);
|
|
rewriter.replaceOp(op, result);
|
|
}
|
|
return success();
|
|
}
|
|
};
|
|
|
|
class MatMulConverter : public OpConversionPattern<tosa::MatMulOp> {
|
|
public:
|
|
using OpConversionPattern<tosa::MatMulOp>::OpConversionPattern;
|
|
LogicalResult
|
|
matchAndRewrite(tosa::MatMulOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
Location loc = op.getLoc();
|
|
|
|
auto outputTy = cast<ShapedType>(op.getType());
|
|
auto outputElementTy = outputTy.getElementType();
|
|
|
|
SmallVector<Value> dynDims;
|
|
dynDims.resize(cast<ShapedType>(op->getResult(0).getType()).getRank());
|
|
|
|
if (!outputTy.hasRank() || outputTy.isDynamicDim(0)) {
|
|
dynDims[0] = tensor::DimOp::create(rewriter, loc, op->getOperand(0), 0);
|
|
}
|
|
|
|
if (!outputTy.hasRank() || outputTy.isDynamicDim(1)) {
|
|
dynDims[1] = tensor::DimOp::create(rewriter, loc, op->getOperand(0), 1);
|
|
}
|
|
|
|
if (!outputTy.hasRank() || outputTy.isDynamicDim(2)) {
|
|
dynDims[2] = tensor::DimOp::create(rewriter, loc, op->getOperand(1), 2);
|
|
}
|
|
|
|
SmallVector<Value> filteredDims = condenseValues(dynDims);
|
|
|
|
auto zeroAttr = rewriter.getZeroAttr(outputElementTy);
|
|
Value zero = arith::ConstantOp::create(rewriter, loc, zeroAttr);
|
|
auto emptyTensor =
|
|
tensor::EmptyOp::create(rewriter, loc, outputTy.getShape(),
|
|
outputTy.getElementType(), filteredDims);
|
|
Value zeroTensor = linalg::FillOp::create(rewriter, loc, ValueRange{zero},
|
|
ValueRange{emptyTensor})
|
|
.result();
|
|
|
|
FailureOr<int64_t> maybeAZp = op.getAZeroPoint();
|
|
FailureOr<int64_t> maybeBZp = op.getBZeroPoint();
|
|
if (failed(maybeAZp))
|
|
return rewriter.notifyMatchFailure(
|
|
op, "input a zero point cannot be statically determined");
|
|
if (failed(maybeBZp))
|
|
return rewriter.notifyMatchFailure(
|
|
op, "input b zero point cannot be statically determined");
|
|
|
|
const int64_t aZpVal = *maybeAZp;
|
|
const int64_t bZpVal = *maybeBZp;
|
|
|
|
if (op.verifyAZeroPoint(aZpVal).failed())
|
|
return rewriter.notifyMatchFailure(
|
|
op, "input a zero point must be zero for non-int8 integer types");
|
|
|
|
if (op.verifyBZeroPoint(bZpVal).failed())
|
|
return rewriter.notifyMatchFailure(
|
|
op, "input b zero point must be zero for non-int8 integer types");
|
|
|
|
if (aZpVal == 0 && bZpVal == 0) {
|
|
rewriter.replaceOpWithNewOp<linalg::BatchMatmulOp>(
|
|
op, TypeRange{op.getType()},
|
|
ValueRange{adaptor.getA(), adaptor.getB()}, ValueRange{zeroTensor});
|
|
return success();
|
|
}
|
|
|
|
auto aZp = arith::ConstantOp::create(rewriter, loc,
|
|
rewriter.getI32IntegerAttr(aZpVal));
|
|
auto bZp = arith::ConstantOp::create(rewriter, loc,
|
|
rewriter.getI32IntegerAttr(bZpVal));
|
|
rewriter.replaceOpWithNewOp<linalg::QuantizedBatchMatmulOp>(
|
|
op, TypeRange{op.getType()},
|
|
ValueRange{adaptor.getA(), adaptor.getB(), aZp, bZp}, zeroTensor);
|
|
|
|
return success();
|
|
}
|
|
};
|
|
|
|
class MaxPool2dConverter : public OpConversionPattern<tosa::MaxPool2dOp> {
|
|
public:
|
|
using OpConversionPattern::OpConversionPattern;
|
|
|
|
// Compute the dynamic output sizes of the maxpool operation.
|
|
static SmallVector<Value>
|
|
computeDynamicOutputSizes(tosa::MaxPool2dOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) {
|
|
TensorType resultTy = op.getType();
|
|
Location loc = op.getLoc();
|
|
|
|
Value input = adaptor.getInput();
|
|
ArrayRef<int64_t> kernel = op.getKernel();
|
|
ArrayRef<int64_t> pad = op.getPad();
|
|
ArrayRef<int64_t> stride = op.getStride();
|
|
|
|
SmallVector<Value> dynamicDims;
|
|
|
|
// Batch dimension
|
|
if (resultTy.isDynamicDim(0))
|
|
dynamicDims.push_back(tensor::DimOp::create(rewriter, loc, input, 0));
|
|
|
|
// Height/width dimensions
|
|
for (int64_t dim : {1, 2}) {
|
|
if (!resultTy.isDynamicDim(dim))
|
|
continue;
|
|
|
|
// Index into the attribute arrays
|
|
int64_t index = dim - 1;
|
|
|
|
// Input height/width
|
|
Value ihw = tensor::DimOp::create(rewriter, loc, input, dim);
|
|
|
|
// Kernel height/width
|
|
Value khw = arith::ConstantIndexOp::create(rewriter, loc, kernel[index]);
|
|
|
|
// Output height/width
|
|
Value ohw = getConvOrPoolOutputDim(loc, ihw, pad[index * 2],
|
|
pad[index * 2 + 1], khw, stride[index],
|
|
/*dilationAttr=*/1, rewriter);
|
|
dynamicDims.push_back(ohw);
|
|
}
|
|
|
|
// Channel dimension
|
|
if (resultTy.isDynamicDim(3))
|
|
dynamicDims.push_back(tensor::DimOp::create(rewriter, loc, input, 3));
|
|
|
|
return dynamicDims;
|
|
}
|
|
|
|
LogicalResult
|
|
matchAndRewrite(tosa::MaxPool2dOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
Location loc = op.getLoc();
|
|
Value input = adaptor.getInput();
|
|
ShapedType inputTy = cast<ShapedType>(input.getType());
|
|
|
|
bool isUnsigned = op.getType().getElementType().isUnsignedInteger();
|
|
ShapedType resultTy =
|
|
getTypeConverter()->convertType<ShapedType>(op.getType());
|
|
if (!resultTy)
|
|
return rewriter.notifyMatchFailure(op, "failed to convert type");
|
|
Type resultETy = inputTy.getElementType();
|
|
|
|
SmallVector<Value> dynamicDims =
|
|
computeDynamicOutputSizes(op, adaptor, rewriter);
|
|
|
|
// Determine what the initial value needs to be for the max pool op.
|
|
TypedAttr initialAttr;
|
|
if (resultETy.isF32() || resultETy.isBF16() || resultETy.isF16())
|
|
initialAttr = rewriter.getFloatAttr(
|
|
resultETy, APFloat::getLargest(
|
|
cast<FloatType>(resultETy).getFloatSemantics(), true));
|
|
|
|
else if (isUnsigned)
|
|
initialAttr = rewriter.getIntegerAttr(
|
|
resultETy, APInt::getZero(resultETy.getIntOrFloatBitWidth()));
|
|
else if (isa<IntegerType>(resultETy))
|
|
initialAttr = rewriter.getIntegerAttr(
|
|
resultETy,
|
|
APInt::getSignedMinValue(resultETy.getIntOrFloatBitWidth()));
|
|
|
|
if (!initialAttr)
|
|
return rewriter.notifyMatchFailure(
|
|
op, "Unsupported initial value for tosa.maxpool_2d op");
|
|
|
|
// Apply padding as necessary.
|
|
llvm::SmallVector<int64_t> pad;
|
|
pad.resize(2, 0);
|
|
llvm::append_range(pad, op.getPad());
|
|
pad.resize(pad.size() + 2, 0);
|
|
|
|
Value paddedInput = applyPad(loc, input, pad, initialAttr, rewriter);
|
|
|
|
Value initialValue = arith::ConstantOp::create(rewriter, loc, initialAttr);
|
|
|
|
ArrayRef<int64_t> kernel = op.getKernel();
|
|
ArrayRef<int64_t> stride = op.getStride();
|
|
|
|
Attribute strideAttr = rewriter.getI64VectorAttr(stride);
|
|
Attribute dilationAttr = rewriter.getI64VectorAttr({1, 1});
|
|
|
|
// Create the linalg op that performs pooling.
|
|
Value emptyTensor =
|
|
tensor::EmptyOp::create(rewriter, loc, resultTy.getShape(),
|
|
resultTy.getElementType(), dynamicDims);
|
|
|
|
Value filledEmptyTensor =
|
|
linalg::FillOp::create(rewriter, loc, initialValue, emptyTensor)
|
|
.result();
|
|
|
|
Value fakeWindowDims =
|
|
tensor::EmptyOp::create(rewriter, loc, kernel, resultETy);
|
|
|
|
if (isUnsigned) {
|
|
rewriter.replaceOpWithNewOp<linalg::PoolingNhwcMaxUnsignedOp>(
|
|
op, ArrayRef<Type>{resultTy}, ValueRange{paddedInput, fakeWindowDims},
|
|
filledEmptyTensor, strideAttr, dilationAttr);
|
|
return llvm::success();
|
|
}
|
|
|
|
auto resultOp = linalg::PoolingNhwcMaxOp::create(
|
|
rewriter, op->getLoc(), ArrayRef<Type>{resultTy},
|
|
ValueRange{paddedInput, fakeWindowDims}, filledEmptyTensor, strideAttr,
|
|
dilationAttr);
|
|
|
|
NanPropagationMode nanMode = op.getNanMode();
|
|
rewriter.replaceOp(op, resultOp);
|
|
|
|
// NaN propagation has no meaning for non floating point types.
|
|
if (!isa<FloatType>(getElementTypeOrSelf(inputTy)))
|
|
return success();
|
|
|
|
// "PROPAGATE" mode matches the behaviour of the LinAlg named op, so no
|
|
// compare and select materialization is required.
|
|
//
|
|
// In the case of "IGNORE" we need to insert a compare and select. Since
|
|
// we've already produced a named op we will just take its body and modify
|
|
// it to include the appropriate checks. If the current value is NaN the
|
|
// old value of pool will be taken otherwise we use the result.
|
|
if (nanMode == NanPropagationMode::IGNORE) {
|
|
auto genericOp = linalg::GenericOp::create(
|
|
rewriter, loc, resultOp.getType(0), resultOp.getInputs(),
|
|
resultOp.getOutputs(), resultOp.getIndexingMapsArray(),
|
|
resultOp.getIteratorTypesArray(),
|
|
[&](OpBuilder &opBuilder, Location loc, ValueRange blockArgs) {
|
|
IRMapping map;
|
|
auto oldBlock = resultOp.getRegion().begin();
|
|
auto oldArgs = oldBlock->getArguments();
|
|
auto &oldMaxOp = *resultOp.getBlock()->begin();
|
|
map.map(oldArgs, blockArgs);
|
|
auto *newOp = opBuilder.clone(oldMaxOp, map);
|
|
Value isNaN =
|
|
arith::CmpFOp::create(opBuilder, loc, arith::CmpFPredicate::UNO,
|
|
blockArgs.front(), blockArgs.front());
|
|
auto selectOp = arith::SelectOp::create(
|
|
opBuilder, loc, isNaN, blockArgs.back(), newOp->getResult(0));
|
|
linalg::YieldOp::create(opBuilder, loc, selectOp.getResult());
|
|
});
|
|
rewriter.replaceOp(resultOp, genericOp);
|
|
}
|
|
|
|
return success();
|
|
}
|
|
};
|
|
|
|
class AvgPool2dConverter : public OpRewritePattern<tosa::AvgPool2dOp> {
|
|
public:
|
|
using OpRewritePattern<tosa::AvgPool2dOp>::OpRewritePattern;
|
|
|
|
LogicalResult matchAndRewrite(tosa::AvgPool2dOp op,
|
|
PatternRewriter &rewriter) const final {
|
|
Location loc = op.getLoc();
|
|
Value input = op.getInput();
|
|
ShapedType inputTy = cast<ShapedType>(input.getType());
|
|
Type inElementTy = inputTy.getElementType();
|
|
|
|
ShapedType resultTy = cast<ShapedType>(op.getType());
|
|
Type resultETy = cast<ShapedType>(op.getType()).getElementType();
|
|
|
|
Type accETy = op.getAccType();
|
|
ShapedType accTy = resultTy.clone(accETy);
|
|
|
|
auto dynamicDimsOr =
|
|
checkHasDynamicBatchDims(rewriter, op, {input, op.getOutput()});
|
|
if (!dynamicDimsOr.has_value())
|
|
return failure();
|
|
SmallVector<Value> dynamicDims = *dynamicDimsOr;
|
|
|
|
FailureOr<int64_t> maybeIZp = op.getInputZeroPoint();
|
|
FailureOr<int64_t> maybeOZp = op.getOutputZeroPoint();
|
|
if (failed(maybeIZp))
|
|
return rewriter.notifyMatchFailure(
|
|
op, "input zero point could not be statically determined");
|
|
if (failed(maybeOZp))
|
|
return rewriter.notifyMatchFailure(
|
|
op, "output zero point could not be statically determined");
|
|
|
|
const int64_t inputZpVal = *maybeIZp;
|
|
const int64_t outputZpVal = *maybeOZp;
|
|
|
|
// Apply padding as necessary.
|
|
llvm::SmallVector<int64_t> pad;
|
|
pad.resize(2, 0);
|
|
llvm::append_range(pad, op.getPad());
|
|
pad.resize(pad.size() + 2, 0);
|
|
TypedAttr padAttr = rewriter.getZeroAttr(inElementTy);
|
|
// Unsupported element type
|
|
if (!padAttr)
|
|
return failure();
|
|
Value paddedInput = applyPad(loc, input, pad, padAttr, rewriter);
|
|
|
|
auto initialAttr = rewriter.getZeroAttr(accETy);
|
|
Value initialValue = arith::ConstantOp::create(rewriter, loc, initialAttr);
|
|
|
|
ArrayRef<int64_t> kernel = op.getKernel();
|
|
ArrayRef<int64_t> stride = op.getStride();
|
|
|
|
Attribute strideAttr = rewriter.getI64VectorAttr(stride);
|
|
Attribute dilationAttr = rewriter.getI64VectorAttr({1, 1});
|
|
|
|
// Create the linalg op that performs pooling.
|
|
Value poolEmptyTensor = tensor::EmptyOp::create(
|
|
rewriter, loc, accTy.getShape(), accETy, dynamicDims);
|
|
|
|
Value filledEmptyTensor =
|
|
linalg::FillOp::create(rewriter, loc, ValueRange{initialValue},
|
|
ValueRange{poolEmptyTensor})
|
|
.result();
|
|
|
|
Value fakeWindowDims =
|
|
tensor::EmptyOp::create(rewriter, loc, kernel, accETy);
|
|
|
|
// Sum across the pooled region.
|
|
Value poolingOp = linalg::PoolingNhwcSumOp::create(
|
|
rewriter, loc, ArrayRef<Type>{accTy},
|
|
ValueRange{paddedInput, fakeWindowDims},
|
|
filledEmptyTensor, strideAttr, dilationAttr)
|
|
.getResult(0);
|
|
|
|
// Normalize the summed value by the number of elements grouped in each
|
|
// pool.
|
|
Value iH = tensor::DimOp::create(rewriter, loc, poolingOp, 1);
|
|
Value iW = tensor::DimOp::create(rewriter, loc, poolingOp, 2);
|
|
|
|
auto one = arith::ConstantIndexOp::create(rewriter, loc, 1);
|
|
iH = arith::SubIOp::create(rewriter, loc, iH, one);
|
|
iW = arith::SubIOp::create(rewriter, loc, iW, one);
|
|
|
|
Value genericEmptyTensor = tensor::EmptyOp::create(
|
|
rewriter, loc, resultTy.getShape(), resultETy, dynamicDims);
|
|
|
|
auto affineMap = rewriter.getMultiDimIdentityMap(resultTy.getRank());
|
|
auto genericOp = linalg::GenericOp::create(
|
|
rewriter, loc, ArrayRef<Type>({resultTy}), ValueRange{poolingOp},
|
|
ValueRange{genericEmptyTensor},
|
|
ArrayRef<AffineMap>({affineMap, affineMap}),
|
|
getNParallelLoopsAttrs(resultTy.getRank()),
|
|
[&](OpBuilder &b, Location loc, ValueRange args) {
|
|
auto zero = arith::ConstantIndexOp::create(rewriter, loc, 0);
|
|
|
|
// Determines what the portion of valid input is covered by the
|
|
// kernel.
|
|
auto padFn = [&](Value valid, Value pos, int64_t pad) -> Value {
|
|
if (pad == 0)
|
|
return valid;
|
|
|
|
auto padVal = arith::ConstantIndexOp::create(rewriter, loc, pad);
|
|
Value dpos = arith::SubIOp::create(rewriter, loc, pos, padVal);
|
|
|
|
Value offset = arith::MinSIOp::create(rewriter, loc, dpos, zero);
|
|
return arith::AddIOp::create(rewriter, loc, valid, offset)
|
|
->getResult(0);
|
|
};
|
|
|
|
auto coverageFn = [&](int64_t i, Value isize) -> Value {
|
|
Value strideVal =
|
|
arith::ConstantIndexOp::create(rewriter, loc, stride[i - 1]);
|
|
Value val =
|
|
arith::ConstantIndexOp::create(rewriter, loc, kernel[i - 1]);
|
|
|
|
// Find the position relative to the input tensor's ends.
|
|
Value left = linalg::IndexOp::create(rewriter, loc, i);
|
|
Value right = arith::SubIOp::create(rewriter, loc, isize, left);
|
|
left = arith::MulIOp::create(rewriter, loc, left, strideVal);
|
|
right = arith::MulIOp::create(rewriter, loc, right, strideVal);
|
|
|
|
// Determine how much padding was included.
|
|
val = padFn(val, left, pad[i * 2]);
|
|
val = padFn(val, right, pad[i * 2 + 1]);
|
|
return arith::MaxSIOp::create(rewriter, loc, one, val);
|
|
};
|
|
|
|
// Compute the indices from either end.
|
|
Value kH3 = coverageFn(1, iH);
|
|
Value kW3 = coverageFn(2, iW);
|
|
|
|
// Compute the total number of elements and normalize.
|
|
auto count = arith::IndexCastOp::create(
|
|
rewriter, loc, rewriter.getI32Type(),
|
|
arith::MulIOp::create(rewriter, loc, kH3, kW3));
|
|
|
|
// Divide by the number of summed values. For floats this is just
|
|
// a div however for quantized values input normalization had
|
|
// to be applied.
|
|
Value poolVal = args[0];
|
|
if (isa<FloatType>(accETy)) {
|
|
auto countF = arith::SIToFPOp::create(rewriter, loc, accETy, count);
|
|
poolVal = arith::DivFOp::create(rewriter, loc, poolVal, countF)
|
|
->getResult(0);
|
|
if (accETy.getIntOrFloatBitWidth() >
|
|
resultETy.getIntOrFloatBitWidth())
|
|
poolVal =
|
|
arith::TruncFOp::create(rewriter, loc, resultETy, poolVal);
|
|
} else {
|
|
|
|
// If we have quantization information we need to apply an offset
|
|
// for the input zp value.
|
|
if (inputZpVal != 0) {
|
|
auto inputZp = arith::ConstantOp::create(
|
|
rewriter, loc, b.getIntegerAttr(accETy, inputZpVal));
|
|
Value offset =
|
|
arith::MulIOp::create(rewriter, loc, accETy, count, inputZp);
|
|
poolVal =
|
|
arith::SubIOp::create(rewriter, loc, accETy, poolVal, offset);
|
|
}
|
|
|
|
// Compute: k = 32 - count_leading_zeros(value - 1)
|
|
Value one32 = arith::ConstantOp::create(
|
|
rewriter, loc, rewriter.getI32IntegerAttr(1));
|
|
Value thirtyTwo32 = arith::ConstantOp::create(
|
|
rewriter, loc, rewriter.getI32IntegerAttr(32));
|
|
|
|
Value countSubOne =
|
|
arith::SubIOp::create(rewriter, loc, count, one32);
|
|
Value leadingZeros =
|
|
math::CountLeadingZerosOp::create(rewriter, loc, countSubOne);
|
|
Value k =
|
|
arith::SubIOp::create(rewriter, loc, thirtyTwo32, leadingZeros);
|
|
|
|
// Compute: numerator = ((1 << 30) + 1) << k
|
|
Value k64 =
|
|
arith::ExtUIOp::create(rewriter, loc, rewriter.getI64Type(), k);
|
|
Value thirtyShiftPlusOne = arith::ConstantOp::create(
|
|
rewriter, loc, rewriter.getI64IntegerAttr((1 << 30) + 1));
|
|
Value numerator =
|
|
arith::ShLIOp::create(rewriter, loc, thirtyShiftPlusOne, k64);
|
|
|
|
// Compute: scale.multiplier = numerator / value;
|
|
Value count64 = arith::ExtUIOp::create(
|
|
rewriter, loc, rewriter.getI64Type(), count);
|
|
Value multiplier =
|
|
arith::DivUIOp::create(rewriter, loc, numerator, count64);
|
|
multiplier = arith::TruncIOp::create(
|
|
rewriter, loc, rewriter.getI32Type(), multiplier);
|
|
|
|
// Compute: scale.shift = 30 + k
|
|
Value k8 =
|
|
arith::TruncIOp::create(rewriter, loc, rewriter.getI8Type(), k);
|
|
Value thirty8 = arith::ConstantOp::create(
|
|
rewriter, loc, rewriter.getI8IntegerAttr(30));
|
|
Value shift = arith::AddIOp::create(rewriter, loc, k8, thirty8);
|
|
|
|
auto roundingAttr = RoundingModeAttr::get(
|
|
rewriter.getContext(), RoundingMode::SINGLE_ROUND);
|
|
|
|
auto scaled = tosa::ApplyScaleOp::create(
|
|
rewriter, loc, rewriter.getI32Type(), poolVal,
|
|
multiplier, shift, roundingAttr)
|
|
.getResult();
|
|
|
|
// If we have quantization information we need to apply output
|
|
// zeropoint.
|
|
if (outputZpVal != 0) {
|
|
auto outputZp = arith::ConstantOp::create(
|
|
rewriter, loc,
|
|
b.getIntegerAttr(scaled.getType(), outputZpVal));
|
|
scaled = arith::AddIOp::create(rewriter, loc, scaled, outputZp)
|
|
.getResult();
|
|
}
|
|
|
|
// Apply Clip.
|
|
int64_t outBitwidth = resultETy.getIntOrFloatBitWidth();
|
|
|
|
auto min = arith::ConstantIntOp::create(
|
|
rewriter, loc, accETy,
|
|
APInt::getSignedMinValue(outBitwidth).getSExtValue());
|
|
auto max = arith::ConstantIntOp::create(
|
|
rewriter, loc, accETy,
|
|
APInt::getSignedMaxValue(outBitwidth).getSExtValue());
|
|
auto clamp = clampIntHelper(loc, scaled, min, max, rewriter,
|
|
/*isUnsigned=*/false);
|
|
|
|
poolVal = clamp;
|
|
// Convert type.
|
|
if (resultETy != clamp.getType()) {
|
|
poolVal =
|
|
arith::TruncIOp::create(rewriter, loc, resultETy, poolVal);
|
|
}
|
|
}
|
|
|
|
linalg::YieldOp::create(rewriter, loc, poolVal);
|
|
});
|
|
|
|
rewriter.replaceOp(op, genericOp.getResult(0));
|
|
return success();
|
|
}
|
|
};
|
|
|
|
class TransposeConverter : public OpRewritePattern<tosa::TransposeOp> {
|
|
public:
|
|
using OpRewritePattern<tosa::TransposeOp>::OpRewritePattern;
|
|
|
|
LogicalResult matchAndRewrite(tosa::TransposeOp op,
|
|
PatternRewriter &rewriter) const final {
|
|
const llvm::ArrayRef<int32_t> constantPerms = op.getPerms();
|
|
|
|
Location loc = op.getLoc();
|
|
// The verifier should have made sure we have a valid TOSA permutation
|
|
// tensor. isPermutationVector doesn't actually check the TOSA perms we
|
|
// expect.
|
|
SmallVector<OpFoldResult> inputSizes =
|
|
tensor::getMixedSizes(rewriter, loc, op.getInput1());
|
|
auto permutedSizes =
|
|
applyTOSAPermutation<OpFoldResult>(inputSizes, constantPerms);
|
|
|
|
auto permutedInit =
|
|
tensor::EmptyOp::create(rewriter, loc, permutedSizes,
|
|
op.getInput1().getType().getElementType());
|
|
rewriter.replaceOpWithNewOp<linalg::TransposeOp>(
|
|
op, op.getInput1(), permutedInit,
|
|
llvm::map_to_vector(constantPerms,
|
|
[](int32_t v) -> int64_t { return v; }));
|
|
return success();
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
void mlir::tosa::populateTosaToLinalgNamedConversionPatterns(
|
|
const TypeConverter &converter, RewritePatternSet *patterns,
|
|
const TosaToLinalgNamedOptions &options) {
|
|
if (options.preferConv2DKernelLayoutHWCF) {
|
|
patterns->add<ConvConverter<tosa::Conv2DOp, linalg::Conv2DNhwcHwcfOp,
|
|
linalg::Conv2DNhwcHwcfQOp>>(
|
|
patterns->getContext());
|
|
} else {
|
|
patterns->add<ConvConverter<tosa::Conv2DOp, linalg::Conv2DNhwcFhwcOp,
|
|
linalg::Conv2DNhwcFhwcQOp>>(
|
|
patterns->getContext());
|
|
}
|
|
patterns->add<
|
|
// clang-format off
|
|
ConvConverter<tosa::Conv3DOp, linalg::Conv3DNdhwcDhwcfOp, linalg::Conv3DNdhwcDhwcfQOp>,
|
|
DepthwiseConvConverter,
|
|
MatMulConverter,
|
|
AvgPool2dConverter,
|
|
TransposeConverter
|
|
>(patterns->getContext());
|
|
|
|
patterns->add<
|
|
MaxPool2dConverter
|
|
>(converter, patterns->getContext());
|
|
// clang-format on
|
|
}
|