From 139755666cf518ecbe0e7e91c525f959f897c4f4 Mon Sep 17 00:00:00 2001 From: Hsiangkai Wang Date: Wed, 12 Feb 2025 10:33:34 +0000 Subject: [PATCH 1/5] [mlir][tosa] Make TOSA RESIZE's scale, offset, border as Input Move the `scale`, `offset`, and `border` parameters of the RESIZE operator in the MLIR TOSA dialect from attributes to inputs and update lit tests appropriately. Add the verifier of the `tosa::ResizeOp` operation. Co-authored-by: Tai Ly Co-authored-by: Luke Hutton --- mlir/include/mlir/Dialect/Tosa/IR/TosaOps.td | 7 +- .../mlir/Dialect/Tosa/Utils/ConversionUtils.h | 3 + .../Conversion/TosaToLinalg/TosaToLinalg.cpp | 21 ++- .../Dialect/Tosa/IR/TosaCanonicalizations.cpp | 19 ++- mlir/lib/Dialect/Tosa/IR/TosaOps.cpp | 95 +++++++++++- .../Tosa/Transforms/TosaValidation.cpp | 121 ++++++++++++++- .../Dialect/Tosa/Utils/ConversionUtils.cpp | 18 +++ .../TosaToLinalg/tosa-to-linalg-resize.mlir | 140 ++++++++---------- mlir/test/Dialect/Tosa/canonicalize.mlir | 10 +- mlir/test/Dialect/Tosa/invalid.mlir | 59 ++++++++ mlir/test/Dialect/Tosa/level_check.mlir | 22 ++- mlir/test/Dialect/Tosa/ops.mlir | 5 +- mlir/test/Dialect/Tosa/tosa-infer-shapes.mlir | 40 ++++- 13 files changed, 446 insertions(+), 114 deletions(-) diff --git a/mlir/include/mlir/Dialect/Tosa/IR/TosaOps.td b/mlir/include/mlir/Dialect/Tosa/IR/TosaOps.td index b8755da8db32e..5dfef9130177c 100644 --- a/mlir/include/mlir/Dialect/Tosa/IR/TosaOps.td +++ b/mlir/include/mlir/Dialect/Tosa/IR/TosaOps.td @@ -1822,9 +1822,9 @@ def Tosa_ResizeOp : Tosa_InferShapedTypeOp<"resize"> { let arguments = (ins Tosa_Tensor4D:$input, - Tosa_IntArrayAttr4:$scale, - Tosa_IntArrayAttr2:$offset, - Tosa_IntArrayAttr2:$border, + Rank4TosaShape:$scale, + Rank2TosaShape:$offset, + Rank2TosaShape:$border, Tosa_ResizeTypeAttr:$mode ); @@ -1833,6 +1833,7 @@ def Tosa_ResizeOp : Tosa_InferShapedTypeOp<"resize"> { ); let hasFolder = 1; + let hasVerifier = 1; } //===----------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Tosa/Utils/ConversionUtils.h b/mlir/include/mlir/Dialect/Tosa/Utils/ConversionUtils.h index 4e2f1b9cb19a9..3e80a7321ad8c 100644 --- a/mlir/include/mlir/Dialect/Tosa/Utils/ConversionUtils.h +++ b/mlir/include/mlir/Dialect/Tosa/Utils/ConversionUtils.h @@ -240,6 +240,9 @@ SmallVector convertFromMlirShape(ArrayRef shape); bool getConstShapeValue(Operation *op, llvm::SmallVector &result_shape); +// returns a small vector of int64_t values that attr contains +SmallVector convertFromIntAttr(const DenseElementsAttr &attr, + const int rank); } // namespace tosa } // namespace mlir diff --git a/mlir/lib/Conversion/TosaToLinalg/TosaToLinalg.cpp b/mlir/lib/Conversion/TosaToLinalg/TosaToLinalg.cpp index d849c782bf08b..7b70b3ab8afc9 100644 --- a/mlir/lib/Conversion/TosaToLinalg/TosaToLinalg.cpp +++ b/mlir/lib/Conversion/TosaToLinalg/TosaToLinalg.cpp @@ -1387,7 +1387,10 @@ class ResizeUnaryConverter : public OpRewritePattern { return success(); } - ArrayRef scale = op.getScale(); + SmallVector scale; + if (!tosa::getConstShapeValue(op.getScale().getDefiningOp(), scale)) { + return failure(); + } // Collapse the unit width and height away. SmallVector reassociationMap(2); @@ -1488,8 +1491,9 @@ class MaterializeResizeBroadcast : public OpRewritePattern { resizeShape.push_back(channels); auto resizeTy = resultTy.clone(resizeShape); - auto resize = - builder.create(resizeTy, input, op->getAttrs()); + auto resize = builder.create(resizeTy, input, op.getScale(), + op.getOffset(), op.getBorder(), + op.getMode()); // Collapse an unit result dims. SmallVector reassociationMap(2); @@ -1604,9 +1608,14 @@ class GenericResizeConverter : public OpRewritePattern { Value inY = b.create(b.getI32Type(), y); Value inX = b.create(b.getI32Type(), x); - ArrayRef offset = op.getOffset(); - ArrayRef border = op.getBorder(); - ArrayRef scale = op.getScale(); + SmallVector scale, offset, border; + if (!tosa::getConstShapeValue(op.getScale().getDefiningOp(), scale) || + !tosa::getConstShapeValue(op.getOffset().getDefiningOp(), offset) || + !tosa::getConstShapeValue(op.getBorder().getDefiningOp(), border)) { + return rewriter.notifyMatchFailure( + op, "tosa.resize scale/offset/border should have compile time " + "constant values."); + } Value yScaleN, yScaleD, xScaleN, xScaleD; yScaleN = b.create(b.getI32IntegerAttr(scale[0])); diff --git a/mlir/lib/Dialect/Tosa/IR/TosaCanonicalizations.cpp b/mlir/lib/Dialect/Tosa/IR/TosaCanonicalizations.cpp index 69b3f6d674167..b9bcedb7fe71d 100644 --- a/mlir/lib/Dialect/Tosa/IR/TosaCanonicalizations.cpp +++ b/mlir/lib/Dialect/Tosa/IR/TosaCanonicalizations.cpp @@ -1034,9 +1034,22 @@ OpFoldResult PadOp::fold(FoldAdaptor adaptor) { // Fold away cases where a tosa.resize operation returns a copy // of the input image. OpFoldResult ResizeOp::fold(FoldAdaptor adaptor) { - ArrayRef offset = getOffset(); - ArrayRef border = getBorder(); - ArrayRef scale = getScale(); + auto scaleAttr = + llvm::dyn_cast_if_present(adaptor.getScale()); + auto offsetAttr = + llvm::dyn_cast_if_present(adaptor.getOffset()); + auto borderAttr = + llvm::dyn_cast_if_present(adaptor.getBorder()); + if (!scaleAttr || !offsetAttr || !borderAttr) { + return {}; + } + + auto scale = tosa::convertFromIntAttr(scaleAttr, /* rank = */ 4); + auto offset = tosa::convertFromIntAttr(offsetAttr, /* rank = */ 2); + auto border = tosa::convertFromIntAttr(borderAttr, /* rank = */ 2); + if (scale.size() != 4 || offset.size() != 2 || border.size() != 2) { + return {}; + } // Check unit scaling. if (scale[0] != scale[1] || scale[2] != scale[3]) { diff --git a/mlir/lib/Dialect/Tosa/IR/TosaOps.cpp b/mlir/lib/Dialect/Tosa/IR/TosaOps.cpp index 4928be38476a9..e703e01597687 100644 --- a/mlir/lib/Dialect/Tosa/IR/TosaOps.cpp +++ b/mlir/lib/Dialect/Tosa/IR/TosaOps.cpp @@ -1685,9 +1685,14 @@ LogicalResult tosa::ResizeOp::inferReturnTypeComponents( (inputWidth == ShapedType::kDynamic)) return failure(); - llvm::ArrayRef scaleInt = adaptor.getScale(); - llvm::ArrayRef offsetInt = adaptor.getOffset(); - llvm::ArrayRef borderInt = adaptor.getBorder(); + SmallVector scaleInt, offsetInt, borderInt; + if (!tosa::getConstShapeValue(adaptor.getScale().getDefiningOp(), scaleInt) || + !tosa::getConstShapeValue(adaptor.getOffset().getDefiningOp(), + offsetInt) || + !tosa::getConstShapeValue(adaptor.getBorder().getDefiningOp(), + borderInt)) { + return failure(); + } // Compute the output shape based on attributes: scale, offset, and border. outputShape[1] = @@ -1704,6 +1709,90 @@ LogicalResult tosa::ResizeOp::inferReturnTypeComponents( return success(); } +LogicalResult tosa::ResizeOp::verify() { + const Value input = getInput(); + const Value output = getOutput(); + const RankedTensorType inputType = + llvm::dyn_cast(input.getType()); + const RankedTensorType outputType = + llvm::dyn_cast(output.getType()); + + if (!inputType) + return emitOpError("expect a ranked input tensor"); + if (!outputType) + return emitOpError("expect a ranked output tensor"); + + const int64_t oh = outputType.getDimSize(1); + const int64_t ow = outputType.getDimSize(2); + const int64_t ih = inputType.getDimSize(1); + const int64_t iw = inputType.getDimSize(2); + + SmallVector scaleValues; + SmallVector offsetValues; + SmallVector borderValues; + if (!tosa::getConstShapeValue(getScale().getDefiningOp(), scaleValues) || + !tosa::getConstShapeValue(getOffset().getDefiningOp(), offsetValues) || + !tosa::getConstShapeValue(getBorder().getDefiningOp(), borderValues)) { + // Skip following checks if shape is not constant + return success(); + } + + if (llvm::any_of(scaleValues, [](int64_t s) { return s <= 0; })) + return emitOpError("expect all scale values to be > 0, got ") + << scaleValues; + + const int64_t scaleYN = scaleValues[0]; + const int64_t scaleYD = scaleValues[1]; + const int64_t scaleXN = scaleValues[2]; + const int64_t scaleXD = scaleValues[3]; + + const int64_t offsetY = offsetValues[0]; + const int64_t offsetX = offsetValues[1]; + + const int64_t borderY = borderValues[0]; + const int64_t borderX = borderValues[1]; + + auto idivCheck = [](const int64_t lhs, + const int64_t rhs) -> std::optional { + if (lhs % rhs != 0) + return std::nullopt; + return lhs / rhs; + }; + + if (ih != ShapedType::kDynamic) { + const std::optional calculatedOutHeightMinusOne = + idivCheck((ih - 1) * scaleYN - offsetY + borderY, scaleYD); + if (!calculatedOutHeightMinusOne.has_value()) + return emitOpError("expected (input_height - 1) * scale_y_n - offset_y + " + "border_y ") + << "to be wholly divisible by scale_y_d, got ((" << ih + << " - 1) * " << scaleYN << " - " << offsetY << " + " << borderY + << ") / " << scaleYD; + const int64_t calculatedOutHeight = calculatedOutHeightMinusOne.value() + 1; + if (oh != ShapedType::kDynamic && calculatedOutHeight != oh) + return emitOpError("calculated output height did not match expected: ") + << "calculated=" << calculatedOutHeight << ", expected=" << oh; + } + + if (iw != ShapedType::kDynamic) { + const int64_t scaledInWidth = (iw - 1) * scaleXN - offsetX + borderX; + const std::optional calculatedOutWidthMinusOne = + idivCheck(scaledInWidth, scaleXD); + if (!calculatedOutWidthMinusOne.has_value()) + return emitOpError("expected (input_width - 1) * scale_x_n - offset_x + " + "border_x ") + << "to be wholly divisible by scale_x_d, got ((" << iw + << " - 1) * " << scaleXN << " - " << offsetX << " + " << borderX + << ") / " << scaleXD; + const int64_t calculatedOutWidth = calculatedOutWidthMinusOne.value() + 1; + if (ow != ShapedType::kDynamic && calculatedOutWidth != ow) + return emitOpError("calculated output width did not match expected: ") + << "calculated=" << calculatedOutWidth << ", expected=" << ow; + } + + return success(); +} + LogicalResult tosa::ScatterOp::inferReturnTypeComponents( MLIRContext *context, ::std::optional location, ScatterOp::Adaptor adaptor, diff --git a/mlir/lib/Dialect/Tosa/Transforms/TosaValidation.cpp b/mlir/lib/Dialect/Tosa/Transforms/TosaValidation.cpp index 678bb47935bd2..e62caf997d6b0 100644 --- a/mlir/lib/Dialect/Tosa/Transforms/TosaValidation.cpp +++ b/mlir/lib/Dialect/Tosa/Transforms/TosaValidation.cpp @@ -18,6 +18,7 @@ #include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/Dialect/Tosa/IR/TosaOps.h" +#include "mlir/Dialect/Tosa/Utils/ConversionUtils.h" #include "mlir/IR/Builders.h" #include "mlir/IR/BuiltinOps.h" #include "mlir/IR/Matchers.h" @@ -119,6 +120,9 @@ struct TosaValidation : public tosa::impl::TosaValidationBase { // check variable read/write data types against variable declarations LogicalResult applyVariableCheck(Operation *op); + // check error if conditions + LogicalResult applyErrorIfCheck(Operation *op); + private: void populateConstantOperandChecks() { constCheckers.emplace_back(checkConstantOperandPad); @@ -383,11 +387,14 @@ struct TosaValidation : public tosa::impl::TosaValidationBase { // Resize op: level check max scales bool levelCheckResize(Operation *op) { if (auto resize = dyn_cast(op)) { - auto scale = resize.getScale(); - int16_t scaleYN = scale[0]; - int16_t scaleYD = scale[1]; - int16_t scaleXN = scale[2]; - int16_t scaleXD = scale[3]; + SmallVector scale; + if (!tosa::getConstShapeValue(resize.getScale().getDefiningOp(), scale)) { + return false; + } + const int64_t scaleYN = scale[0]; + const int64_t scaleYD = scale[1]; + const int64_t scaleXN = scale[2]; + const int64_t scaleXD = scale[3]; if (!levelCheckScale(op, scaleYN / scaleYD, "scale_y_n/scale_y_d <= MAX_SCALE") || !levelCheckScale(op, scaleXN / scaleXD, @@ -519,6 +526,106 @@ LogicalResult TosaValidation::applyVariableCheck(Operation *op) { return success(); } +bool checkErrorIfResize(Operation *op) { + if (auto resize = dyn_cast(op)) { + const Value input = resize.getInput(); + const Value output = resize.getOutput(); + const RankedTensorType inputType = + llvm::dyn_cast(input.getType()); + const RankedTensorType outputType = + llvm::dyn_cast(output.getType()); + + if (!inputType || !outputType) { + op->emitOpError("expect ranked input/output tensor"); + return false; + } + + // Ensure the image size is supported by GPU APIs and that for integer + // implementations, position * stride does not overflow int32_t. + if (inputType.hasStaticShape() && outputType.hasStaticShape()) { + const SmallVector sizes = { + outputType.getDimSize(1), outputType.getDimSize(2), + inputType.getDimSize(1), inputType.getDimSize(2)}; + const int64_t *maxDim = llvm::max_element(sizes); + if (maxDim != sizes.end() && *maxDim >= 16384) { + op->emitOpError("expect input/output height/width dims to be < 16384, ") + << "got [OH, OW, IH, IW] = " << sizes; + return false; + } + } + + SmallVector scale; + if (!tosa::getConstShapeValue(resize.getScale().getDefiningOp(), scale)) { + return false; + } + + const int64_t scaleYN = scale[0]; + const int64_t scaleYD = scale[1]; + const int64_t scaleXN = scale[2]; + const int64_t scaleXD = scale[3]; + + // Ensure scale values don't overflow int32 accumulator + if (scaleYN > (1 << 11) || scaleXN > (1 << 11)) { + op->emitOpError("expect all scale numerator values to be <= (1 << 11), " + "got scale_y_n=") + << scaleYN << ", scale_x_n=" << scaleXN; + return false; + } + + if (scaleYD >= 16 * scaleYN || scaleXD >= 16 * scaleXN) { + op->emitOpError("expect a downscale ratio larger than 1/16, got y=") + << scaleYN << "/" << scaleYD << ", x=" << scaleXN << "/" << scaleXD; + return false; + } + + SmallVector offset; + SmallVector border; + if (!tosa::getConstShapeValue(resize.getOffset().getDefiningOp(), offset) || + !tosa::getConstShapeValue(resize.getBorder().getDefiningOp(), border)) { + return false; + } + + const int64_t offsetY = offset[0]; + const int64_t offsetX = offset[1]; + const int64_t borderY = border[0]; + const int64_t borderX = border[1]; + + // Set a consistent lower limit of 1/16 downscale to simplify + // implementations + if (offsetY < -scaleYN || offsetY >= 16 * scaleYN) { + op->emitOpError( + "expect offsetY / scaleYNumerator to be in range [-1, 16), got ") + << offsetY << "/" << scaleYN; + return false; + } + if (offsetX < -scaleXN || offsetX >= 16 * scaleXN) { + op->emitOpError( + "expect offsetX / scaleXNumerator to be in range [-1, 16), got ") + << offsetX << "/" << scaleXN; + return false; + } + if (borderY < -16 * scaleYN || borderY >= scaleYN) { + op->emitOpError( + "expect borderY / scaleYNumerator to be in range [-16, 1), got ") + << borderY << "/" << scaleYN; + return false; + } + if (borderX < -16 * scaleXN || borderX >= scaleXN) { + op->emitOpError( + "expect borderX / scaleXNumerator to be in range [-16, 1), got ") + << borderX << "/" << scaleXN; + return false; + } + } + return true; +} + +LogicalResult TosaValidation::applyErrorIfCheck(Operation *op) { + if (!checkErrorIfResize(op)) + return failure(); + return success(); +} + bool TosaValidation::isValidElementType(Type type) { if (isa(type)) { if (!isEnabledProfile(TosaProfileEnum::MainInference)) @@ -582,6 +689,10 @@ void TosaValidation::runOnOperation() { // do variable type checks if (failed(applyVariableCheck(op))) signalPassFailure(); + + // do error if checks + if (StrictOperationSpecAlignment && failed(applyErrorIfCheck(op))) + signalPassFailure(); }); } } // namespace diff --git a/mlir/lib/Dialect/Tosa/Utils/ConversionUtils.cpp b/mlir/lib/Dialect/Tosa/Utils/ConversionUtils.cpp index 8ab12d038849f..d1a8732dac212 100644 --- a/mlir/lib/Dialect/Tosa/Utils/ConversionUtils.cpp +++ b/mlir/lib/Dialect/Tosa/Utils/ConversionUtils.cpp @@ -198,3 +198,21 @@ bool mlir::tosa::getConstShapeValue(Operation *op, // for undefined op, return false. return false; } + +// returns a small vector of int64_t values that attr contains +SmallVector +mlir::tosa::convertFromIntAttr(const DenseElementsAttr &attr, const int rank) { + if (attr.isSplat()) { + int64_t v = attr.getSplatValue().getSExtValue(); + return SmallVector(rank, v); + } + + if (auto int_array_attr = llvm::dyn_cast(attr)) { + SmallVector vec; + for (APInt val : int_array_attr.getValues()) { + vec.push_back(val.getSExtValue()); + } + return vec; + } + return {}; +} diff --git a/mlir/test/Conversion/TosaToLinalg/tosa-to-linalg-resize.mlir b/mlir/test/Conversion/TosaToLinalg/tosa-to-linalg-resize.mlir index d42d0a46692d4..ad9f03d1c1d60 100644 --- a/mlir/test/Conversion/TosaToLinalg/tosa-to-linalg-resize.mlir +++ b/mlir/test/Conversion/TosaToLinalg/tosa-to-linalg-resize.mlir @@ -2,7 +2,10 @@ // CHECK-LABEL: @unary_resize_nearest_fp32 func.func @unary_resize_nearest_fp32(%arg0 : tensor<3x1x1x7xf32>) -> tensor<3x1x1x7xf32> { - %resize = "tosa.resize"(%arg0) {mode = "NEAREST_NEIGHBOR", scale = array, offset = array, border = array} : (tensor<3x1x1x7xf32>) -> tensor<3x1x1x7xf32> + %scale = tosa.const_shape { value = dense<[2, 2, 1, 1]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %resize = tosa.resize %arg0, %scale, %offset, %border {mode = "NEAREST_NEIGHBOR"} : (tensor<3x1x1x7xf32>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<3x1x1x7xf32> // CHECK: return %arg0 return %resize : tensor<3x1x1x7xf32> } @@ -11,7 +14,10 @@ func.func @unary_resize_nearest_fp32(%arg0 : tensor<3x1x1x7xf32>) -> tensor<3x1x // CHECK-LABEL: @unary_resize_nearest_fp16 func.func @unary_resize_nearest_fp16(%arg0 : tensor<3x1x1x7xf16>) -> tensor<3x1x1x7xf16> { - %resize = "tosa.resize"(%arg0) {mode = "NEAREST_NEIGHBOR", scale = array, offset = array, border = array} : (tensor<3x1x1x7xf16>) -> tensor<3x1x1x7xf16> + %scale = tosa.const_shape { value = dense<[2, 2, 1, 1]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %resize = tosa.resize %arg0, %scale, %offset, %border {mode = "NEAREST_NEIGHBOR"} : (tensor<3x1x1x7xf16>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<3x1x1x7xf16> // CHECK: return %arg0 return %resize : tensor<3x1x1x7xf16> } @@ -20,7 +26,10 @@ func.func @unary_resize_nearest_fp16(%arg0 : tensor<3x1x1x7xf16>) -> tensor<3x1x // CHECK-LABEL: @unary_resize_bilinear_fp32 func.func @unary_resize_bilinear_fp32(%arg0 : tensor<3x1x1x7xf32>) -> tensor<3x1x1x7xf32> { - %resize = "tosa.resize"(%arg0) {mode = "BILINEAR", scale = array, offset = array, border = array} : (tensor<3x1x1x7xf32>) -> tensor<3x1x1x7xf32> + %scale = tosa.const_shape { value = dense<[2, 2, 1, 1]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %resize = tosa.resize %arg0, %scale, %offset, %border {mode = "BILINEAR"} : (tensor<3x1x1x7xf32>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<3x1x1x7xf32> // CHECK: return %arg0 return %resize : tensor<3x1x1x7xf32> } @@ -29,7 +38,10 @@ func.func @unary_resize_bilinear_fp32(%arg0 : tensor<3x1x1x7xf32>) -> tensor<3x1 // CHECK-LABEL: @unary_resize_bilinear_fp16 func.func @unary_resize_bilinear_fp16(%arg0 : tensor<3x1x1x7xf16>) -> tensor<3x1x1x7xf16> { - %resize = "tosa.resize"(%arg0) {mode = "BILINEAR", scale = array, offset = array, border = array} : (tensor<3x1x1x7xf16>) -> tensor<3x1x1x7xf16> + %scale = tosa.const_shape { value = dense<[2, 2, 1, 1]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %resize = tosa.resize %arg0, %scale, %offset, %border {mode = "BILINEAR"} : (tensor<3x1x1x7xf16>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<3x1x1x7xf16> // CHECK: return %arg0 return %resize : tensor<3x1x1x7xf16> } @@ -38,71 +50,22 @@ func.func @unary_resize_bilinear_fp16(%arg0 : tensor<3x1x1x7xf16>) -> tensor<3x1 // CHECK-LABEL: @unary_resize_nearest_i8 func.func @unary_resize_nearest_i8(%arg0 : tensor<3x1x1x7xi8>) -> tensor<3x1x1x7xi8> { - %resize = "tosa.resize"(%arg0) {mode = "NEAREST_NEIGHBOR", scale = array, offset = array, border = array} : (tensor<3x1x1x7xi8>) -> tensor<3x1x1x7xi8> + %scale = tosa.const_shape { value = dense<[2, 1, 3, 1]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %resize = tosa.resize %arg0, %scale, %offset, %border {mode = "NEAREST_NEIGHBOR"} : (tensor<3x1x1x7xi8>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<3x1x1x7xi8> // CHECK: return %arg0 return %resize : tensor<3x1x1x7xi8> } // ----- -// CHECK-LABEL: @broadcast_resize_nearest_f32 -func.func @broadcast_resize_nearest_f32(%arg0 : tensor<3x1x1x7xf32>) -> tensor<3x1x5x7xf32> { - // CHECK: %[[COLLAPSE:.+]] = tensor.collapse_shape %arg0 - // CHECK-NEXT{literal}: [[0], [1, 2, 3]] : tensor<3x1x1x7xf32> into tensor<3x7xf32> - // CHECK: %[[EMPTY:.+]] = tensor.empty() : tensor<3x1x5x7xf32> - // CHECK: %[[GENERIC:.+]] = linalg.generic - // CHECK-SAME: indexing_maps = [#map, #map1], iterator_types = ["parallel", "parallel", "parallel", "parallel"]} - // CHECK-SAME: ins(%[[COLLAPSE]] : tensor<3x7xf32>) outs(%[[EMPTY]] : tensor<3x1x5x7xf32>) - // CHECK: ^bb0(%[[IN:.+]]: f32, %[[OUT:.+]]: f32): - // CHECK: linalg.yield %[[IN]] : f32 - %resize = "tosa.resize"(%arg0) {mode = "NEAREST_NEIGHBOR", scale = array, offset = array, border = array} : (tensor<3x1x1x7xf32>) -> tensor<3x1x5x7xf32> - - // CHECK: return %[[GENERIC]] - return %resize : tensor<3x1x5x7xf32> -} - -// ----- - -// CHECK-LABEL: @broadcast_resize_bilinear_i8 -func.func @broadcast_resize_bilinear_i8(%arg0 : tensor<3x1x1x7xi8>) -> tensor<3x4x5x7xi32> { - // CHECK: %[[COLLAPSE:.+]] = tensor.collapse_shape %arg0 - // CHECK-SAME{literal}: [[0], [1, 2, 3]] : tensor<3x1x1x7xi8> into tensor<3x7xi8> - // CHECK: %[[EMPTY:.+]] = tensor.empty() : tensor<3x7xi32> - // CHECK: %[[RESIZE:.+]] = linalg.generic - // CHECK-SAME: {indexing_maps = [#map, #map], iterator_types = ["parallel", "parallel"]} - // CHECK-SAME: ins(%[[COLLAPSE]] : tensor<3x7xi8>) outs(%[[EMPTY]] : tensor<3x7xi32>) - // CHECK: ^bb0(%[[IN:.+]]: i8, %[[OUT:.+]]: i32): - // CHECK: %[[EXT:.+]] = arith.extsi %[[IN]] : i8 to i32 - // CHECK-DAG: %[[C2:.+]] = arith.constant 2 : i32 - // CHECK: %[[MUL:.+]] = arith.muli %[[EXT]], %[[C2]] : i32 - // CHECK-DAG: %[[C3:.+]] = arith.constant 3 : i32 - // CHECK: %[[OUT:.+]] = arith.muli %[[MUL]], %[[C3]] : i32 - // CHECK: linalg.yield %[[OUT]] : i32 - // CHECK: } -> tensor<3x7xi32> - // CHECK: %[[EXPAND:.+]] = tensor.expand_shape %1 - // CHECK-SAME{literal}: [[0], [1, 2, 3]] : tensor<3x7xi32> into tensor<3x1x1x7xi32> - // CHECK: %[[COLLAPSE:.+]] = tensor.collapse_shape %expanded - // CHECK-SAME{literal}:[[0], [1, 2, 3]] : tensor<3x1x1x7xi32> into tensor<3x7xi32> - // CHECK: %[[EMPTY:.+]] = tensor.empty() : tensor<3x4x5x7xi32> - // CHECK: %[[BROADCAST:.+]] = linalg.generic - // CHECK-SAME: indexing_maps = [#map1, #map2], iterator_types = ["parallel", "parallel", "parallel", "parallel"]} - // CHECK-SAME: ins(%[[COLLAPSE]] : tensor<3x7xi32>) outs(%[[EMPTY]] : tensor<3x4x5x7xi32>) { - // CHECK: ^bb0(%[[IN:.+]]: i32, %[[OUT:.+]]: i32): - // CHECK: linalg.yield %[[IN]] : i32 - %resize = "tosa.resize"(%arg0) {mode = "BILINEAR", scale = array, offset = array, border = array} : (tensor<3x1x1x7xi8>) -> tensor<3x4x5x7xi32> - - // CHECK: return %[[BROADCAST]] - return %resize : tensor<3x4x5x7xi32> -} - -// ----- - // CHECK-LABEL: @unary_resize_bilinear_i32 func.func @unary_resize_bilinear_i32(%arg0 : tensor<3x1x1x7xi8>) -> tensor<3x1x1x7xi32> { // CHECK: %[[COLLAPSE:.+]] = tensor.collapse_shape %arg0 // CHECK-SAME{literal}: [[0], [1, 2, 3]] : tensor<3x1x1x7xi8> into tensor<3x7xi8> // CHECK: %[[EMPTY:.+]] = tensor.empty() : tensor<3x7xi32> - // CHECK: %[[GENERIC:.+]] = linalg.generic + // CHECK: %[[GENERIC:.+]] = linalg.generic // CHECK-SAME: indexing_maps = [#map, #map] // CHECK-SAME: iterator_types = ["parallel", "parallel"]} // CHECK-SAME: ins(%[[COLLAPSE]] : tensor<3x7xi8>) outs(%[[EMPTY]] : tensor<3x7xi32>) { @@ -111,12 +74,15 @@ func.func @unary_resize_bilinear_i32(%arg0 : tensor<3x1x1x7xi8>) -> tensor<3x1x1 // CHECK-DAG: %[[C2:.+]] = arith.constant 2 : i32 // CHECK: %[[MUL0:.+]] = arith.muli %[[EXT]], %[[C2]] : i32 // CHECK-DAG: %[[C1:.+]] = arith.constant 2 : i32 - // CHECK: %4 = arith.muli %3, %[[C1]] : i32 - // CHECK: linalg.yield %4 : i32 + // CHECK: %7 = arith.muli %6, %[[C1]] : i32 + // CHECK: linalg.yield %7 : i32 // CHECK: } -> tensor<3x7xi32> // CHECK: %[[EXPAND:.+]] = tensor.expand_shape %[[GENERIC:.+]] // CHECK-SAME{literal} [[0], [1, 2, 3]] : tensor<3x7xi32> into tensor<3x1x1x7xi32> - %resize = "tosa.resize"(%arg0) {mode = "BILINEAR", scale = array, offset = array, border = array} : (tensor<3x1x1x7xi8>) -> tensor<3x1x1x7xi32> + %scale = tosa.const_shape { value = dense<[2, 1, 2, 1]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %resize = tosa.resize %arg0, %scale, %offset, %border {mode = "BILINEAR"} : (tensor<3x1x1x7xi8>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<3x1x1x7xi32> // CHECK: return %[[EXPAND]] return %resize : tensor<3x1x1x7xi32> @@ -184,7 +150,10 @@ func.func @resize_nearest_int(%arg0: tensor<1x15x13x1xi8>) -> () { // CHECK: linalg.yield %[[EXTRACT]] // Round to the nearest index. - %0 = "tosa.resize"(%arg0) {mode = "NEAREST_NEIGHBOR", scale = array, offset = array, border = array} : (tensor<1x15x13x1xi8>) -> tensor<1x23x179x1xi8> + %scale = tosa.const_shape { value = dense<[11, 7, 89, 6]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %0 = tosa.resize %arg0, %scale, %offset, %border {mode = "NEAREST_NEIGHBOR"} : (tensor<1x15x13x1xi8>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<1x23x179x1xi8> return } @@ -193,7 +162,7 @@ func.func @resize_nearest_int(%arg0: tensor<1x15x13x1xi8>) -> () { // CHECK-LABEL: @resize_bilinear_int // CHECK-SAME: (%[[ARG0:[0-9a-zA-Z_]*]]: func.func @resize_bilinear_int(%arg0: tensor<1x19x20x1xi8>) { - // CHECK: %[[INIT:.+]] = tensor.empty() : tensor<1x304x320x1xi48> + // CHECK: %[[INIT:.+]] = tensor.empty() : tensor<1x289x305x1xi48> // CHECK: %[[GENERIC:.+]] = linalg.generic // CHECK: %[[IDX_0:.+]] = linalg.index 0 // CHECK: %[[IDX_1:.+]] = linalg.index 1 @@ -285,7 +254,10 @@ func.func @resize_bilinear_int(%arg0: tensor<1x19x20x1xi8>) { // CHECK: linalg.yield %[[RESULT]] // Round to the nearest index. - %0 = "tosa.resize"(%arg0) {mode = "BILINEAR", scale = array, offset = array, border = array} : (tensor<1x19x20x1xi8>) -> tensor<1x304x320x1xi48> + %scale = tosa.const_shape { value = dense<[16, 1, 16, 1]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %0 = tosa.resize %arg0, %scale, %offset, %border {mode = "BILINEAR"} : (tensor<1x19x20x1xi8>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<1x289x305x1xi48> return } @@ -349,7 +321,10 @@ func.func @resize_nearest_fp32(%input: tensor<1x50x48x1xf32>) -> () { // CHECK: %[[EXTRACT:.+]] = tensor.extract %arg0[%[[IDX0]], %[[IDY]], %[[IDX]], %[[IDX3]]] // CHECK: linalg.yield %[[EXTRACT]] - %output = "tosa.resize"(%input) {mode = "NEAREST_NEIGHBOR", scale = array, offset = array, border = array} : (tensor<1x50x48x1xf32>) -> tensor<1x1600x1536x1xf32> + %scale = tosa.const_shape { value = dense<[64, 2, 64, 2]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<[-31, -31]> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<[31, 31]> : tensor<2xindex> } : () -> !tosa.shape<2> + %output = tosa.resize %input, %scale, %offset, %border {mode = "NEAREST_NEIGHBOR"} : (tensor<1x50x48x1xf32>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<1x1600x1536x1xf32> return } @@ -357,7 +332,7 @@ func.func @resize_nearest_fp32(%input: tensor<1x50x48x1xf32>) -> () { // CHECK-LABEL: @resize_bilinear_fp func.func @resize_bilinear_fp(%input: tensor<1x23x24x1xf32>) -> () { - // CHECK: %[[INIT:.+]] = tensor.empty() : tensor<1x92x96x1xf32> + // CHECK: %[[INIT:.+]] = tensor.empty() : tensor<1x89x93x1xf32> // CHECK: %[[GENERIC:.+]] = linalg.generic // CHECK: %[[IDX_0:.+]] = linalg.index 0 // CHECK: %[[IDX_1:.+]] = linalg.index 1 @@ -441,7 +416,10 @@ func.func @resize_bilinear_fp(%input: tensor<1x23x24x1xf32>) -> () { // CHECK: linalg.yield %[[RESULT]] // Round by bilinear interpolation - %output = "tosa.resize"(%input) {mode = "BILINEAR", scale = array, offset = array, border = array} : (tensor<1x23x24x1xf32>) -> tensor<1x92x96x1xf32> + %scale = tosa.const_shape { value = dense<[4, 1, 4, 1]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %output = tosa.resize %input, %scale, %offset, %border {mode = "BILINEAR"} : (tensor<1x23x24x1xf32>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<1x89x93x1xf32> return } @@ -455,7 +433,10 @@ func.func @resize_dyn(%input: tensor) -> () { // CHECK: %[[BATCH:.+]] = tensor.dim %arg0, %[[C0]] // CHECK: %[[INIT:.+]] = tensor.empty(%[[BATCH]]) : tensor // CHECK: %[[GENERIC:.+]] = linalg.generic - %output = "tosa.resize"(%input) { scale = array, offset = array, border = array, mode = "BILINEAR" } : (tensor) -> (tensor) + %scale = tosa.const_shape { value = dense<[4, 2, 4, 2]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<[-1, -1]> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<[1, 1]> : tensor<2xindex> } : () -> !tosa.shape<2> + %output = tosa.resize %input, %scale, %offset, %border { mode = "BILINEAR" } : (tensor, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> (tensor) return } @@ -463,14 +444,17 @@ func.func @resize_dyn(%input: tensor) -> () { // CHECK-LABEL: @resize_bilinear_int48 func.func @resize_bilinear_int48(%arg0: tensor<1x19x19x1xi16>) { - %0 = "tosa.resize"(%arg0) {mode = "BILINEAR", scale = array, offset = array, border = array} : (tensor<1x19x19x1xi16>) -> tensor<1x289x289x1xi48> + %scale = tosa.const_shape { value = dense<[16, 1, 16, 1]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %0 = tosa.resize %arg0, %scale, %offset, %border {mode = "BILINEAR"} : (tensor<1x19x19x1xi16>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<1x289x289x1xi48> return } // ----- // CHECK-LABEL: skip_interpolate_bilinear_i8 -func.func @skip_interpolate_bilinear_i8(%arg0 : tensor<3x1x2x7xi8>) -> tensor<3x1x5x7xi32> { +func.func @skip_interpolate_bilinear_i8(%arg0 : tensor<3x1x2x7xi8>) -> tensor<3x1x4x7xi32> { // CHECK: %[[GENERIC:.+]] = linalg.generic // CHECK: %[[BATCH:.+]] = linalg.index 0 // CHECK: %[[CHANNEL:.+]] = linalg.index 3 @@ -486,14 +470,17 @@ func.func @skip_interpolate_bilinear_i8(%arg0 : tensor<3x1x2x7xi8>) -> tensor<3x // CHECK: %[[ADD:.+]] = arith.addi %[[MUL0]], %[[MUL1]] // CHECK: %[[RES:.+]] = arith.muli %[[ADD]], %[[C2]] // CHECK: linalg.yield %[[RES]] - %resize = "tosa.resize"(%arg0) {mode = "BILINEAR", scale = array, offset = array, border = array} : (tensor<3x1x2x7xi8>) -> tensor<3x1x5x7xi32> + %scale = tosa.const_shape { value = dense<[2, 1, 3, 1]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %resize = tosa.resize %arg0, %scale, %offset, %border {mode = "BILINEAR"} : (tensor<3x1x2x7xi8>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<3x1x4x7xi32> // CHECK: return %[[GENERIC]] - return %resize : tensor<3x1x5x7xi32> + return %resize : tensor<3x1x4x7xi32> } // CHECK-LABEL: skip_interpolate_bilinear_f32 -func.func @skip_interpolate_bilinear_f32(%arg0 : tensor<3x1x2x7xf32>) -> tensor<3x1x5x7xf32> { +func.func @skip_interpolate_bilinear_f32(%arg0 : tensor<3x1x2x7xf32>) -> tensor<3x1x4x7xf32> { // CHECK: %[[GENERIC:.+]] = linalg.generic // CHECK: %[[BATCH:.+]] = linalg.index 0 : index // CHECK: %[[CHANNEL:.+]] = linalg.index 3 : index @@ -505,8 +492,11 @@ func.func @skip_interpolate_bilinear_f32(%arg0 : tensor<3x1x2x7xf32>) -> tensor< // CHECK: %[[MUL1:.+]] = arith.mulf %[[EXTRACT1]], %[[DX]] // CHECK: %[[ADD:.+]] = arith.addf %[[MUL0]], %[[MUL1]] // CHECK: linalg.yield %[[ADD]] - %resize = "tosa.resize"(%arg0) {mode = "BILINEAR", scale = array, offset = array, border = array} : (tensor<3x1x2x7xf32>) -> tensor<3x1x5x7xf32> + %scale = tosa.const_shape { value = dense<[2, 1, 3, 1]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %resize = tosa.resize %arg0, %scale, %offset, %border {mode = "BILINEAR"} : (tensor<3x1x2x7xf32>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<3x1x4x7xf32> // CHECK: return %[[GENERIC]] - return %resize : tensor<3x1x5x7xf32> + return %resize : tensor<3x1x4x7xf32> } diff --git a/mlir/test/Dialect/Tosa/canonicalize.mlir b/mlir/test/Dialect/Tosa/canonicalize.mlir index 24d572244a9b0..0e177a076ee7a 100644 --- a/mlir/test/Dialect/Tosa/canonicalize.mlir +++ b/mlir/test/Dialect/Tosa/canonicalize.mlir @@ -721,7 +721,10 @@ func.func @single_bit_reshape() -> tensor<1xi1> { // CHECK-LABEL: @fold_resize_nearest func.func @fold_resize_nearest(%arg0 : tensor<1x15x13x1xi8>) -> tensor<1x15x13x1xi8> { // CHECK: return %arg0 - %resize = tosa.resize %arg0 {mode = "NEAREST_NEIGHBOR" , scale = array, offset = array, border = array} : (tensor<1x15x13x1xi8>) -> tensor<1x15x13x1xi8> + %scale = tosa.const_shape { value = dense<[2, 2, 1, 1]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %resize = tosa.resize %arg0, %scale, %offset, %border {mode = "NEAREST_NEIGHBOR"} : (tensor<1x15x13x1xi8>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<1x15x13x1xi8> return %resize : tensor<1x15x13x1xi8> } @@ -730,7 +733,10 @@ func.func @fold_resize_nearest(%arg0 : tensor<1x15x13x1xi8>) -> tensor<1x15x13x1 // CHECK-LABEL: @fold_resize_bilinear func.func @fold_resize_bilinear(%arg0 : tensor<1x15x13x1xi8>) -> tensor<1x15x13x1xi8> { // CHECK: return %arg0 - %resize = tosa.resize %arg0 {mode = "BILINEAR" , scale = array, offset = array, border = array} : (tensor<1x15x13x1xi8>) -> tensor<1x15x13x1xi8> + %scale = tosa.const_shape { value = dense<[2, 2, 1, 1]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %resize = tosa.resize %arg0, %scale, %offset, %border {mode = "BILINEAR"} : (tensor<1x15x13x1xi8>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<1x15x13x1xi8> return %resize : tensor<1x15x13x1xi8> } diff --git a/mlir/test/Dialect/Tosa/invalid.mlir b/mlir/test/Dialect/Tosa/invalid.mlir index 913191be86f85..9575b73afc6a4 100644 --- a/mlir/test/Dialect/Tosa/invalid.mlir +++ b/mlir/test/Dialect/Tosa/invalid.mlir @@ -1127,3 +1127,62 @@ func.func @test_mul_non_broadcast(%arg0: tensor<13x21x2xf32>, %arg1: tensor<3x1x %0 = tosa.mul %arg0, %arg1, %shift : (tensor<13x21x2xf32>, tensor<3x1x3xf32>, tensor<1xi8>) -> tensor<13x21x3xf32> return %0 : tensor<13x21x3xf32> } + +// ----- +// CHECK-LABEL: test_resize_invalid_scale_values +func.func @test_resize_invalid_scale_values(%arg0: tensor<1x8x8x8xf32>) -> tensor { + %scale = tosa.const_shape { value = dense<[2, 0, -1, 2]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + // expected-error@+1 {{'tosa.resize' op expect all scale values to be > 0, got 2, 0, -1, 2}} + %1 = tosa.resize %arg0, %scale, %offset, %border { mode = "BILINEAR" } : (tensor<1x8x8x8xf32>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor + return %1 : tensor +} + +// ----- + +// CHECK-LABEL: test_resize_invalid_wholly_divisible_height +func.func @test_resize_invalid_wholly_divisible_height(%arg0: tensor<1x8x8x8xf32>) -> tensor<1x8x8x8xf32> { + %scale = tosa.const_shape { value = dense<[1, 3, 1, 1]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + // expected-error@+1 {{'tosa.resize' op expected (input_height - 1) * scale_y_n - offset_y + border_y to be wholly divisible by scale_y_d, got ((8 - 1) * 1 - 0 + 0) / 3}} + %1 = tosa.resize %arg0, %scale, %offset, %border { mode = "BILINEAR" } : (tensor<1x8x8x8xf32>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<1x8x8x8xf32> + return %1 : tensor<1x8x8x8xf32> +} + +// ----- + +// CHECK-LABEL: test_resize_invalid_output_height +func.func @test_resize_invalid_output_height(%arg0: tensor<1x8x8x8xf32>) -> tensor<1x9x8x8xf32> { + %scale = tosa.const_shape { value = dense<[2, 1, 1, 1]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + // expected-error@+1 {{'tosa.resize' op calculated output height did not match expected: calculated=15, expected=9}} + %1 = tosa.resize %arg0, %scale, %offset, %border { mode = "BILINEAR" } : (tensor<1x8x8x8xf32>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<1x9x8x8xf32> + return %1 : tensor<1x9x8x8xf32> +} + +// ----- + +// CHECK-LABEL: test_resize_invalid_wholly_divisible_width +func.func @test_resize_invalid_wholly_divisible_width(%arg0: tensor<1x8x8x8xf32>) -> tensor<1x8x8x8xf32> { + %scale = tosa.const_shape { value = dense<[1, 1, 1, 3]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + // expected-error@+1 {{'tosa.resize' op expected (input_width - 1) * scale_x_n - offset_x + border_x to be wholly divisible by scale_x_d, got ((8 - 1) * 1 - 0 + 0) / 3}} + %1 = tosa.resize %arg0, %scale, %offset, %border { mode = "BILINEAR" } : (tensor<1x8x8x8xf32>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<1x8x8x8xf32> + return %1 : tensor<1x8x8x8xf32> +} + +// ----- + +// CHECK-LABEL: test_resize_invalid_output_width +func.func @test_resize_invalid_output_width(%arg0: tensor<1x8x8x8xf32>) -> tensor<1x8x9x8xf32> { + %scale = tosa.const_shape { value = dense<[1, 1, 2, 1]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + // expected-error@+1 {{'tosa.resize' op calculated output width did not match expected: calculated=15, expected=9}} + %1 = tosa.resize %arg0, %scale, %offset, %border { mode = "BILINEAR" } : (tensor<1x8x8x8xf32>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<1x8x9x8xf32> + return %1 : tensor<1x8x9x8xf32> +} diff --git a/mlir/test/Dialect/Tosa/level_check.mlir b/mlir/test/Dialect/Tosa/level_check.mlir index a7f76f2d0fa64..6f49195d30e97 100644 --- a/mlir/test/Dialect/Tosa/level_check.mlir +++ b/mlir/test/Dialect/Tosa/level_check.mlir @@ -676,20 +676,26 @@ func.func @test_transpose_conv2d_stride_x(%arg0: tensor<1x32x32x8xf32>, %arg1: t // ----- -func.func @test_resize_scale_y(%arg0: tensor<1x32x32x8xf32>) -> tensor<1x64x64x8xf32> { +func.func @test_resize_scale_y(%arg0: tensor<1x32x32x8xf32>) -> tensor<1x7970x64x8xf32> { + %scale = tosa.const_shape { value = dense<[257, 1, 4, 2]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<[-1, -1]> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<[1, 1]> : tensor<2xindex> } : () -> !tosa.shape<2> // expected-error@+1 {{'tosa.resize' op failed level check: scale_y_n/scale_y_d <= MAX_SCALE}} - %1 = "tosa.resize"(%arg0) { scale = array, offset = array, border = array, mode = "BILINEAR"} : - (tensor<1x32x32x8xf32>) -> tensor<1x64x64x8xf32> - return %1 : tensor<1x64x64x8xf32> + %1 = tosa.resize %arg0, %scale, %offset, %border {mode = "BILINEAR"} : + (tensor<1x32x32x8xf32>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<1x7970x64x8xf32> + return %1 : tensor<1x7970x64x8xf32> } // ----- -func.func @test_resize_scale_x(%arg0: tensor<1x32x32x8xf32>) -> tensor<1x64x64x8xf32> { +func.func @test_resize_scale_x(%arg0: tensor<1x32x32x8xf32>) -> tensor<1x64x7970x8xf32> { + %scale = tosa.const_shape { value = dense<[4, 2, 257, 1]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<[-1, -1]> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<[1, 1]> : tensor<2xindex> } : () -> !tosa.shape<2> // expected-error@+1 {{'tosa.resize' op failed level check: scale_x_n/scale_x_d <= MAX_SCALE}} - %1 = "tosa.resize"(%arg0) { scale = array, offset = array, border = array, mode = "BILINEAR"} : - (tensor<1x32x32x8xf32>) -> tensor<1x64x64x8xf32> - return %1 : tensor<1x64x64x8xf32> + %1 = tosa.resize %arg0, %scale, %offset, %border {mode = "BILINEAR"} : + (tensor<1x32x32x8xf32>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<1x64x7970x8xf32> + return %1 : tensor<1x64x7970x8xf32> } // ----- diff --git a/mlir/test/Dialect/Tosa/ops.mlir b/mlir/test/Dialect/Tosa/ops.mlir index 348849cfaa572..e4a7ee8508548 100644 --- a/mlir/test/Dialect/Tosa/ops.mlir +++ b/mlir/test/Dialect/Tosa/ops.mlir @@ -677,7 +677,10 @@ func.func @test_scatter(%arg0: tensor<13x21x3xf32>, %arg1: tensor<13x26xi32>, %a // ----- // CHECK-LABEL: resize func.func @test_resize(%arg0: tensor<1x32x32x8xf32>) -> tensor<1x64x64x8xf32> { - %1 = tosa.resize %arg0 { scale = array, offset = array, border = array, mode = "BILINEAR" } : (tensor<1x32x32x8xf32>) -> tensor<1x64x64x8xf32> + %scale = tosa.const_shape { value = dense<[4, 2, 4, 2]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<[-1, -1]> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<[1, 1]> : tensor<2xindex> } : () -> !tosa.shape<2> + %1 = tosa.resize %arg0, %scale, %offset, %border { mode = "BILINEAR" } : (tensor<1x32x32x8xf32>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<1x64x64x8xf32> return %1 : tensor<1x64x64x8xf32> } diff --git a/mlir/test/Dialect/Tosa/tosa-infer-shapes.mlir b/mlir/test/Dialect/Tosa/tosa-infer-shapes.mlir index 7dc9b048085fa..4adc43c611b51 100644 --- a/mlir/test/Dialect/Tosa/tosa-infer-shapes.mlir +++ b/mlir/test/Dialect/Tosa/tosa-infer-shapes.mlir @@ -1040,8 +1040,11 @@ func.func @transpose_conv2d_strided(%arg0: tensor<1x5x7x1xf32>, %arg1: tensor<1x // CHECK-LABEL: @resize_int_horizontal func.func @resize_int_horizontal(%arg0: tensor<1x15x13x1xi8>) { + %scale = tosa.const_shape { value = dense<[11, 7, 89, 6]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> // CHECK: -> tensor<1x23x179x1xi8> - %0 = tosa.resize %arg0 {mode = "NEAREST_NEIGHBOR", scale = array, offset = array, border = array} : (tensor<1x15x13x1xi8>) -> tensor + %0 = tosa.resize %arg0, %scale, %offset, %border {mode = "NEAREST_NEIGHBOR"} : (tensor<1x15x13x1xi8>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor return } @@ -1049,8 +1052,11 @@ func.func @resize_int_horizontal(%arg0: tensor<1x15x13x1xi8>) { // CHECK-LABEL: @resize_int_vertical func.func @resize_int_vertical(%arg0: tensor<1x49x42x1xi16>) { + %scale = tosa.const_shape { value = dense<[37, 16, 219, 41]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> // CHECK: -> tensor<1x112x220x1xi16> - %0 = tosa.resize %arg0 {mode = "NEAREST_NEIGHBOR", scale = array, offset = array, border = array} : (tensor<1x49x42x1xi16>) -> tensor + %0 = tosa.resize %arg0, %scale, %offset, %border {mode = "NEAREST_NEIGHBOR"} : (tensor<1x49x42x1xi16>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor return } @@ -1058,8 +1064,11 @@ func.func @resize_int_vertical(%arg0: tensor<1x49x42x1xi16>) { // CHECK-LABEL: @resize_int_power_of_two_upscale func.func @resize_int_power_of_two_upscale(%arg0: tensor<1x23x19x1xi8>) { + %scale = tosa.const_shape { value = dense<[16, 1, 16, 1]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> // CHECK: -> tensor<1x353x289x1xi32> - %0 = tosa.resize %arg0 {mode = "BILINEAR", scale = array, offset = array, border = array} : (tensor<1x23x19x1xi8>) -> tensor + %0 = tosa.resize %arg0, %scale, %offset, %border {mode = "BILINEAR"} : (tensor<1x23x19x1xi8>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor return } @@ -1067,24 +1076,33 @@ func.func @resize_int_power_of_two_upscale(%arg0: tensor<1x23x19x1xi8>) { // CHECK-LABEL: @resize_int_power_of_two_upscale_offsetted func.func @resize_int_power_of_two_upscale_offsetted(%arg0: tensor<1x41x26x1xi16>) { + %scale = tosa.const_shape { value = dense<[16, 2, 16, 2]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<[-7, -7]> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<[7, 7]> : tensor<2xindex> } : () -> !tosa.shape<2> // CHECK: -> tensor<1x328x208x1xi48> - %0 = tosa.resize %arg0 {mode = "BILINEAR", scale = array, offset = array, border = array} : (tensor<1x41x26x1xi16>) -> tensor + %0 = tosa.resize %arg0, %scale, %offset, %border {mode = "BILINEAR"} : (tensor<1x41x26x1xi16>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor return } // ----- // CHECK-LABEL: @resize_fp_horizontal func.func @resize_fp_horizontal(%arg0: tensor<1x50x48x1xf32>) { + %scale = tosa.const_shape { value = dense<[15, 7, 84, 47]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> // CHECK: -> tensor<1x106x85x1xf32> - %0 = tosa.resize %arg0 {mode = "BILINEAR", scale = array, offset = array, border = array} : (tensor<1x50x48x1xf32>) -> tensor + %0 = tosa.resize %arg0, %scale, %offset, %border {mode = "BILINEAR"} : (tensor<1x50x48x1xf32>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor return } // ----- // CHECK-LABEL: @resize_fp_vertical func.func @resize_fp_vertical(%arg0: tensor<1x50x48x1xf32>) { + %scale = tosa.const_shape { value = dense<[127, 49, 12, 47]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> // CHECK: -> tensor<1x128x13x1xf32> - %0 = tosa.resize %arg0 {mode = "NEAREST_NEIGHBOR", scale = array, offset = array, border = array} : (tensor<1x50x48x1xf32>) -> tensor + %0 = tosa.resize %arg0, %scale, %offset, %border {mode = "NEAREST_NEIGHBOR"} : (tensor<1x50x48x1xf32>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor return } @@ -1092,8 +1110,11 @@ func.func @resize_fp_vertical(%arg0: tensor<1x50x48x1xf32>) { // CHECK-LABEL: @resize_fp_power_of_two_upscale func.func @resize_fp_power_of_two_upscale(%arg0: tensor<1x23x23x1xf32>) { + %scale = tosa.const_shape { value = dense<[4, 1, 4, 1]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> // CHECK: -> tensor<1x89x89x1xf32> - %0 = tosa.resize %arg0 {mode = "BILINEAR", scale = array, offset = array, border = array} : (tensor<1x23x23x1xf32>) -> tensor + %0 = tosa.resize %arg0, %scale, %offset, %border {mode = "BILINEAR"} : (tensor<1x23x23x1xf32>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor return } @@ -1101,8 +1122,11 @@ func.func @resize_fp_power_of_two_upscale(%arg0: tensor<1x23x23x1xf32>) { // CHECK-LABEL: @resize_fp_power_of_two_upscale_offsetted func.func @resize_fp_power_of_two_upscale_offsetted(%arg0: tensor<1x50x48x1xf32>) { + %scale = tosa.const_shape { value = dense<[64, 2, 64, 2]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<[-31, -31]> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<[31, 31]> : tensor<2xindex> } : () -> !tosa.shape<2> // CHECK: -> tensor<1x1600x1536x1xf32> - %0 = tosa.resize %arg0 {mode = "NEAREST_NEIGHBOR", scale = array, offset = array, border = array} : (tensor<1x50x48x1xf32>) -> tensor + %0 = tosa.resize %arg0, %scale, %offset, %border {mode = "NEAREST_NEIGHBOR"} : (tensor<1x50x48x1xf32>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor return } From 2c9578bf046de47de350d199586e6a1619a1cdc4 Mon Sep 17 00:00:00 2001 From: Hsiangkai Wang Date: Wed, 12 Feb 2025 11:30:27 +0000 Subject: [PATCH 2/5] Address comments --- .../Tosa/Transforms/TosaValidation.cpp | 163 +++++++++--------- 1 file changed, 83 insertions(+), 80 deletions(-) diff --git a/mlir/lib/Dialect/Tosa/Transforms/TosaValidation.cpp b/mlir/lib/Dialect/Tosa/Transforms/TosaValidation.cpp index e62caf997d6b0..0f067bb0a7756 100644 --- a/mlir/lib/Dialect/Tosa/Transforms/TosaValidation.cpp +++ b/mlir/lib/Dialect/Tosa/Transforms/TosaValidation.cpp @@ -527,96 +527,99 @@ LogicalResult TosaValidation::applyVariableCheck(Operation *op) { } bool checkErrorIfResize(Operation *op) { - if (auto resize = dyn_cast(op)) { - const Value input = resize.getInput(); - const Value output = resize.getOutput(); - const RankedTensorType inputType = - llvm::dyn_cast(input.getType()); - const RankedTensorType outputType = - llvm::dyn_cast(output.getType()); - - if (!inputType || !outputType) { - op->emitOpError("expect ranked input/output tensor"); - return false; - } + auto resize = dyn_cast(op); + if (!resize) + return true; - // Ensure the image size is supported by GPU APIs and that for integer - // implementations, position * stride does not overflow int32_t. - if (inputType.hasStaticShape() && outputType.hasStaticShape()) { - const SmallVector sizes = { - outputType.getDimSize(1), outputType.getDimSize(2), - inputType.getDimSize(1), inputType.getDimSize(2)}; - const int64_t *maxDim = llvm::max_element(sizes); - if (maxDim != sizes.end() && *maxDim >= 16384) { - op->emitOpError("expect input/output height/width dims to be < 16384, ") - << "got [OH, OW, IH, IW] = " << sizes; - return false; - } - } + const Value input = resize.getInput(); + const Value output = resize.getOutput(); + const RankedTensorType inputType = + llvm::dyn_cast(input.getType()); + const RankedTensorType outputType = + llvm::dyn_cast(output.getType()); - SmallVector scale; - if (!tosa::getConstShapeValue(resize.getScale().getDefiningOp(), scale)) { + if (!inputType || !outputType) { + op->emitOpError("expect ranked input/output tensor"); + return false; + } + + // Ensure the image size is supported by GPU APIs and that for integer + // implementations, position * stride does not overflow int32_t. + if (inputType.hasStaticShape() && outputType.hasStaticShape()) { + const SmallVector sizes = { + outputType.getDimSize(1), outputType.getDimSize(2), + inputType.getDimSize(1), inputType.getDimSize(2)}; + const int64_t *maxDim = llvm::max_element(sizes); + if (maxDim != sizes.end() && *maxDim >= 16384) { + op->emitOpError("expect input/output height/width dims to be < 16384, ") + << "got [OH, OW, IH, IW] = " << sizes; return false; } + } - const int64_t scaleYN = scale[0]; - const int64_t scaleYD = scale[1]; - const int64_t scaleXN = scale[2]; - const int64_t scaleXD = scale[3]; + SmallVector scale; + if (!tosa::getConstShapeValue(resize.getScale().getDefiningOp(), scale)) { + return false; + } - // Ensure scale values don't overflow int32 accumulator - if (scaleYN > (1 << 11) || scaleXN > (1 << 11)) { - op->emitOpError("expect all scale numerator values to be <= (1 << 11), " - "got scale_y_n=") - << scaleYN << ", scale_x_n=" << scaleXN; - return false; - } + const int64_t scaleYN = scale[0]; + const int64_t scaleYD = scale[1]; + const int64_t scaleXN = scale[2]; + const int64_t scaleXD = scale[3]; - if (scaleYD >= 16 * scaleYN || scaleXD >= 16 * scaleXN) { - op->emitOpError("expect a downscale ratio larger than 1/16, got y=") - << scaleYN << "/" << scaleYD << ", x=" << scaleXN << "/" << scaleXD; - return false; - } + // Ensure scale values don't overflow int32 accumulator + if (scaleYN > (1 << 11) || scaleXN > (1 << 11)) { + op->emitOpError("expect all scale numerator values to be <= (1 << 11), " + "got scale_y_n=") + << scaleYN << ", scale_x_n=" << scaleXN; + return false; + } - SmallVector offset; - SmallVector border; - if (!tosa::getConstShapeValue(resize.getOffset().getDefiningOp(), offset) || - !tosa::getConstShapeValue(resize.getBorder().getDefiningOp(), border)) { - return false; - } + if (scaleYD >= 16 * scaleYN || scaleXD >= 16 * scaleXN) { + op->emitOpError("expect a downscale ratio larger than 1/16, got y=") + << scaleYN << "/" << scaleYD << ", x=" << scaleXN << "/" << scaleXD; + return false; + } - const int64_t offsetY = offset[0]; - const int64_t offsetX = offset[1]; - const int64_t borderY = border[0]; - const int64_t borderX = border[1]; - - // Set a consistent lower limit of 1/16 downscale to simplify - // implementations - if (offsetY < -scaleYN || offsetY >= 16 * scaleYN) { - op->emitOpError( - "expect offsetY / scaleYNumerator to be in range [-1, 16), got ") - << offsetY << "/" << scaleYN; - return false; - } - if (offsetX < -scaleXN || offsetX >= 16 * scaleXN) { - op->emitOpError( - "expect offsetX / scaleXNumerator to be in range [-1, 16), got ") - << offsetX << "/" << scaleXN; - return false; - } - if (borderY < -16 * scaleYN || borderY >= scaleYN) { - op->emitOpError( - "expect borderY / scaleYNumerator to be in range [-16, 1), got ") - << borderY << "/" << scaleYN; - return false; - } - if (borderX < -16 * scaleXN || borderX >= scaleXN) { - op->emitOpError( - "expect borderX / scaleXNumerator to be in range [-16, 1), got ") - << borderX << "/" << scaleXN; - return false; - } + SmallVector offset; + SmallVector border; + if (!tosa::getConstShapeValue(resize.getOffset().getDefiningOp(), offset) || + !tosa::getConstShapeValue(resize.getBorder().getDefiningOp(), border)) { + return false; } + + const int64_t offsetY = offset[0]; + const int64_t offsetX = offset[1]; + // Set a consistent lower limit of 1/16 downscale to simplify + // implementations + if (offsetY < -scaleYN || offsetY >= 16 * scaleYN) { + op->emitOpError( + "expect offsetY / scaleYNumerator to be in range [-1, 16), got ") + << offsetY << "/" << scaleYN; + return false; + } + if (offsetX < -scaleXN || offsetX >= 16 * scaleXN) { + op->emitOpError( + "expect offsetX / scaleXNumerator to be in range [-1, 16), got ") + << offsetX << "/" << scaleXN; + return false; + } + + const int64_t borderY = border[0]; + const int64_t borderX = border[1]; + if (borderY < -16 * scaleYN || borderY >= scaleYN) { + op->emitOpError( + "expect borderY / scaleYNumerator to be in range [-16, 1), got ") + << borderY << "/" << scaleYN; + return false; + } + if (borderX < -16 * scaleXN || borderX >= scaleXN) { + op->emitOpError( + "expect borderX / scaleXNumerator to be in range [-16, 1), got ") + << borderX << "/" << scaleXN; + return false; + } + return true; } From d3a6828f0ed51992e6786237cc464ed75f93fc39 Mon Sep 17 00:00:00 2001 From: Hsiangkai Wang Date: Thu, 13 Feb 2025 17:10:08 +0000 Subject: [PATCH 3/5] add back test cases --- .../TosaToLinalg/tosa-to-linalg-resize.mlir | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/mlir/test/Conversion/TosaToLinalg/tosa-to-linalg-resize.mlir b/mlir/test/Conversion/TosaToLinalg/tosa-to-linalg-resize.mlir index ad9f03d1c1d60..557a1ed8686b6 100644 --- a/mlir/test/Conversion/TosaToLinalg/tosa-to-linalg-resize.mlir +++ b/mlir/test/Conversion/TosaToLinalg/tosa-to-linalg-resize.mlir @@ -1,4 +1,4 @@ -// RUN: mlir-opt --split-input-file -pass-pipeline="builtin.module(func.func(tosa-to-linalg))" %s -o -| FileCheck %s +// RUN: mlir-opt --split-input-file -pass-pipeline="builtin.module(func.func(tosa-to-linalg))" %s -verify-diagnostics -o - | FileCheck %s // CHECK-LABEL: @unary_resize_nearest_fp32 func.func @unary_resize_nearest_fp32(%arg0 : tensor<3x1x1x7xf32>) -> tensor<3x1x1x7xf32> { @@ -60,6 +60,30 @@ func.func @unary_resize_nearest_i8(%arg0 : tensor<3x1x1x7xi8>) -> tensor<3x1x1x7 // ----- +func.func @broadcast_resize_nearest_f32(%arg0 : tensor<3x1x1x7xf32>) -> tensor<3x1x5x7xf32> { + %scale = tosa.const_shape { value = dense<[2, 1, 3, 1]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + // expected-error @+1 {{'tosa.resize' op calculated output width did not match expected: calculated=1, expected=5}} + %resize = tosa.resize %arg0, %scale, %offset, %border {mode = "NEAREST_NEIGHBOR"} : (tensor<3x1x1x7xf32>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<3x1x5x7xf32> + + return %resize : tensor<3x1x5x7xf32> +} + +// ----- + +func.func @broadcast_resize_bilinear_i8(%arg0 : tensor<3x1x1x7xi8>) -> tensor<3x4x5x7xi32> { + %scale = tosa.const_shape { value = dense<[2, 1, 3, 1]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + // expected-error @+1 {{'tosa.resize' op calculated output height did not match expected: calculated=1, expected=4}} + %resize = tosa.resize %arg0, %scale, %offset, %border {mode = "BILINEAR"} : (tensor<3x1x1x7xi8>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<3x4x5x7xi32> + + return %resize : tensor<3x4x5x7xi32> +} + +// ----- + // CHECK-LABEL: @unary_resize_bilinear_i32 func.func @unary_resize_bilinear_i32(%arg0 : tensor<3x1x1x7xi8>) -> tensor<3x1x1x7xi32> { // CHECK: %[[COLLAPSE:.+]] = tensor.collapse_shape %arg0 From cdca4a7ef86ebf340adef9ed06e82c9d48564799 Mon Sep 17 00:00:00 2001 From: Hsiangkai Wang Date: Thu, 13 Feb 2025 22:01:10 +0000 Subject: [PATCH 4/5] Remove 'materialize broadcast' later --- mlir/lib/Dialect/Tosa/IR/TosaOps.cpp | 12 +++++- .../TosaToLinalg/tosa-to-linalg-resize.mlir | 39 +++++++++++++++++-- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/mlir/lib/Dialect/Tosa/IR/TosaOps.cpp b/mlir/lib/Dialect/Tosa/IR/TosaOps.cpp index e703e01597687..f6e2862a61a9a 100644 --- a/mlir/lib/Dialect/Tosa/IR/TosaOps.cpp +++ b/mlir/lib/Dialect/Tosa/IR/TosaOps.cpp @@ -1759,7 +1759,11 @@ LogicalResult tosa::ResizeOp::verify() { return lhs / rhs; }; - if (ih != ShapedType::kDynamic) { + // Don't check with input height that could be broadcast (ih != 1) + // since Linalg, a consumer of TOSA, expects broadcasting support + // in resize to be available. Taking the cautious approach for now, + // we can consider removing support for broadcasting later. + if (ih != ShapedType::kDynamic && ih != 1) { const std::optional calculatedOutHeightMinusOne = idivCheck((ih - 1) * scaleYN - offsetY + borderY, scaleYD); if (!calculatedOutHeightMinusOne.has_value()) @@ -1774,7 +1778,11 @@ LogicalResult tosa::ResizeOp::verify() { << "calculated=" << calculatedOutHeight << ", expected=" << oh; } - if (iw != ShapedType::kDynamic) { + // Don't check with input width that could be broadcast (iw != 1) + // since Linalg, a consumer of TOSA, expects broadcasting support + // in resize to be available. Taking the cautious approach for now, + // we can consider removing support for broadcasting later. + if (iw != ShapedType::kDynamic && iw != 1) { const int64_t scaledInWidth = (iw - 1) * scaleXN - offsetX + borderX; const std::optional calculatedOutWidthMinusOne = idivCheck(scaledInWidth, scaleXD); diff --git a/mlir/test/Conversion/TosaToLinalg/tosa-to-linalg-resize.mlir b/mlir/test/Conversion/TosaToLinalg/tosa-to-linalg-resize.mlir index 557a1ed8686b6..5a2ee7d9e8720 100644 --- a/mlir/test/Conversion/TosaToLinalg/tosa-to-linalg-resize.mlir +++ b/mlir/test/Conversion/TosaToLinalg/tosa-to-linalg-resize.mlir @@ -1,4 +1,4 @@ -// RUN: mlir-opt --split-input-file -pass-pipeline="builtin.module(func.func(tosa-to-linalg))" %s -verify-diagnostics -o - | FileCheck %s +// RUN: mlir-opt --split-input-file -pass-pipeline="builtin.module(func.func(tosa-to-linalg))" %s -o -| FileCheck %s // CHECK-LABEL: @unary_resize_nearest_fp32 func.func @unary_resize_nearest_fp32(%arg0 : tensor<3x1x1x7xf32>) -> tensor<3x1x1x7xf32> { @@ -60,11 +60,19 @@ func.func @unary_resize_nearest_i8(%arg0 : tensor<3x1x1x7xi8>) -> tensor<3x1x1x7 // ----- +// CHECK-LABEL: @broadcast_resize_nearest_f32 func.func @broadcast_resize_nearest_f32(%arg0 : tensor<3x1x1x7xf32>) -> tensor<3x1x5x7xf32> { + // CHECK: %[[COLLAPSE:.+]] = tensor.collapse_shape %arg0 + // CHECK-NEXT{literal}: [[0], [1, 2, 3]] : tensor<3x1x1x7xf32> into tensor<3x7xf32> + // CHECK: %[[EMPTY:.+]] = tensor.empty() : tensor<3x1x5x7xf32> + // CHECK: %[[GENERIC:.+]] = linalg.generic + // CHECK-SAME: indexing_maps = [#map, #map1], iterator_types = ["parallel", "parallel", "parallel", "parallel"]} + // CHECK-SAME: ins(%[[COLLAPSE]] : tensor<3x7xf32>) outs(%[[EMPTY]] : tensor<3x1x5x7xf32>) + // CHECK: ^bb0(%[[IN:.+]]: f32, %[[OUT:.+]]: f32): + // CHECK: linalg.yield %[[IN]] : f32 %scale = tosa.const_shape { value = dense<[2, 1, 3, 1]> : tensor<4xindex> } : () -> !tosa.shape<4> %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> - // expected-error @+1 {{'tosa.resize' op calculated output width did not match expected: calculated=1, expected=5}} %resize = tosa.resize %arg0, %scale, %offset, %border {mode = "NEAREST_NEIGHBOR"} : (tensor<3x1x1x7xf32>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<3x1x5x7xf32> return %resize : tensor<3x1x5x7xf32> @@ -72,11 +80,36 @@ func.func @broadcast_resize_nearest_f32(%arg0 : tensor<3x1x1x7xf32>) -> tensor<3 // ----- +// CHECK-LABEL: @broadcast_resize_bilinear_i8 func.func @broadcast_resize_bilinear_i8(%arg0 : tensor<3x1x1x7xi8>) -> tensor<3x4x5x7xi32> { + // CHECK: %[[COLLAPSE:.+]] = tensor.collapse_shape %arg0 + // CHECK-SAME{literal}: [[0], [1, 2, 3]] : tensor<3x1x1x7xi8> into tensor<3x7xi8> + // CHECK: %[[EMPTY:.+]] = tensor.empty() : tensor<3x7xi32> + // CHECK: %[[RESIZE:.+]] = linalg.generic + // CHECK-SAME: {indexing_maps = [#map, #map], iterator_types = ["parallel", "parallel"]} + // CHECK-SAME: ins(%[[COLLAPSE]] : tensor<3x7xi8>) outs(%[[EMPTY]] : tensor<3x7xi32>) + // CHECK: ^bb0(%[[IN:.+]]: i8, %[[OUT:.+]]: i32): + // CHECK: %[[EXT:.+]] = arith.extsi %[[IN]] : i8 to i32 + // CHECK-DAG: %[[C2:.+]] = arith.constant 2 : i32 + // CHECK: %[[MUL:.+]] = arith.muli %[[EXT]], %[[C2]] : i32 + // CHECK-DAG: %[[C3:.+]] = arith.constant 3 : i32 + // CHECK: %[[OUT:.+]] = arith.muli %[[MUL]], %[[C3]] : i32 + // CHECK: linalg.yield %[[OUT]] : i32 + // CHECK: } -> tensor<3x7xi32> + // CHECK: %[[EXPAND:.+]] = tensor.expand_shape %[[RESIZE]] + // CHECK-SAME{literal}: [[0], [1, 2, 3]] output_shape [3, 1, 1, 7] : + // CHECK-SAME: tensor<3x7xi32> into tensor<3x1x1x7xi32> + // CHECK: %[[COLLAPSE_0:.+]] = tensor.collapse_shape %[[EXPAND]] + // CHECK-SAME{literal}:[[0], [1, 2, 3]] : tensor<3x1x1x7xi32> into tensor<3x7xi32> + // CHECK: %[[EMPTY_0:.+]] = tensor.empty() : tensor<3x4x5x7xi32> + // CHECK: %[[BROADCAST:.+]] = linalg.generic + // CHECK-SAME: indexing_maps = [#map1, #map2], iterator_types = ["parallel", "parallel", "parallel", "parallel"]} + // CHECK-SAME: ins(%[[COLLAPSE_0]] : tensor<3x7xi32>) outs(%[[EMPTY_0]] : tensor<3x4x5x7xi32>) { + // CHECK: ^bb0(%[[IN:.+]]: i32, %[[OUT:.+]]: i32): + // CHECK: linalg.yield %[[IN]] : i32 %scale = tosa.const_shape { value = dense<[2, 1, 3, 1]> : tensor<4xindex> } : () -> !tosa.shape<4> %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> - // expected-error @+1 {{'tosa.resize' op calculated output height did not match expected: calculated=1, expected=4}} %resize = tosa.resize %arg0, %scale, %offset, %border {mode = "BILINEAR"} : (tensor<3x1x1x7xi8>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<3x4x5x7xi32> return %resize : tensor<3x4x5x7xi32> From aa11bdd7767502bb5330d59819aaa927f80662d5 Mon Sep 17 00:00:00 2001 From: Hsiangkai Wang Date: Thu, 13 Feb 2025 23:01:04 +0000 Subject: [PATCH 5/5] Add output size checking to TosaValidation pass --- .../Tosa/Transforms/TosaValidation.cpp | 60 +++++++++++++++++++ mlir/test/Dialect/Tosa/invalid.mlir | 26 ++++++++ 2 files changed, 86 insertions(+) diff --git a/mlir/lib/Dialect/Tosa/Transforms/TosaValidation.cpp b/mlir/lib/Dialect/Tosa/Transforms/TosaValidation.cpp index 0f067bb0a7756..cb023e4743de8 100644 --- a/mlir/lib/Dialect/Tosa/Transforms/TosaValidation.cpp +++ b/mlir/lib/Dialect/Tosa/Transforms/TosaValidation.cpp @@ -620,6 +620,66 @@ bool checkErrorIfResize(Operation *op) { return false; } + // The following section of code is mostly duplicated with ResizeOp::verify(). + // + // In TOSA specification, we do not support broadcast behavior. + // However, there is a rewrite pattern to materialize broadcast ResizeOp. + // It makes invalid TOSA ResizeOp into valid one. To avoid breaking + // existing code, we keep the rewrite pattern untouched. So, we need + // loose the checking in ResizeOp::verify() to support broadcast ResizeOp. + // + // Here is a strict checking to conform TOSA specification. + // FIXME: Remove the duplicated checkings when broadcast ResizeOp is removed. + auto idivCheck = [](const int64_t lhs, + const int64_t rhs) -> std::optional { + if (lhs % rhs != 0) + return std::nullopt; + return lhs / rhs; + }; + + const int64_t oh = outputType.getDimSize(1); + const int64_t ow = outputType.getDimSize(2); + const int64_t ih = inputType.getDimSize(1); + const int64_t iw = inputType.getDimSize(2); + + if (ih != ShapedType::kDynamic) { + const std::optional calculatedOutHeightMinusOne = + idivCheck((ih - 1) * scaleYN - offsetY + borderY, scaleYD); + if (!calculatedOutHeightMinusOne.has_value()) { + op->emitOpError("expected (input_height - 1) * scale_y_n - offset_y + " + "border_y ") + << "to be wholly divisible by scale_y_d, got ((" << ih << " - 1) * " + << scaleYN << " - " << offsetY << " + " << borderY << ") / " + << scaleYD; + return false; + } + const int64_t calculatedOutHeight = calculatedOutHeightMinusOne.value() + 1; + if (oh != ShapedType::kDynamic && calculatedOutHeight != oh) { + op->emitOpError("calculated output height did not match expected: ") + << "calculated=" << calculatedOutHeight << ", expected=" << oh; + return false; + } + } + + if (iw != ShapedType::kDynamic) { + const std::optional calculatedOutWidthMinusOne = + idivCheck((iw - 1) * scaleXN - offsetX + borderX, scaleXD); + if (!calculatedOutWidthMinusOne.has_value()) { + op->emitOpError("expected (input_width - 1) * scale_x_n - offset_x + " + "border_x ") + << "to be wholly divisible by scale_x_d, got ((" << iw << " - 1) * " + << scaleXN << " - " << offsetX << " + " << borderX << ") / " + << scaleXD; + return false; + } + const int64_t calculatedOutWidth = calculatedOutWidthMinusOne.value() + 1; + if (ow != ShapedType::kDynamic && calculatedOutWidth != ow) { + op->emitOpError("calculated output width did not match expected: ") + << "calculated=" << calculatedOutWidth << ", expected=" << ow; + return false; + } + } + return true; } diff --git a/mlir/test/Dialect/Tosa/invalid.mlir b/mlir/test/Dialect/Tosa/invalid.mlir index 9575b73afc6a4..d2a9373da9424 100644 --- a/mlir/test/Dialect/Tosa/invalid.mlir +++ b/mlir/test/Dialect/Tosa/invalid.mlir @@ -1186,3 +1186,29 @@ func.func @test_resize_invalid_output_width(%arg0: tensor<1x8x8x8xf32>) -> tenso %1 = tosa.resize %arg0, %scale, %offset, %border { mode = "BILINEAR" } : (tensor<1x8x8x8xf32>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<1x8x9x8xf32> return %1 : tensor<1x8x9x8xf32> } + +// ----- + +// CHECK-LABEL: broadcast_resize_nearest_f32 +func.func @broadcast_resize_nearest_f32(%arg0 : tensor<3x1x1x7xf32>) -> tensor<3x1x5x7xf32> { + %scale = tosa.const_shape { value = dense<[2, 1, 3, 1]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + // expected-error@+1 {{'tosa.resize' op calculated output width did not match expected: calculated=1, expected=5}} + %resize = tosa.resize %arg0, %scale, %offset, %border {mode = "NEAREST_NEIGHBOR"} : (tensor<3x1x1x7xf32>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<3x1x5x7xf32> + + return %resize : tensor<3x1x5x7xf32> +} + +// ----- + +// CHECK-LABEL: broadcast_resize_bilinear_i8 +func.func @broadcast_resize_bilinear_i8(%arg0 : tensor<3x1x1x7xi8>) -> tensor<3x4x5x7xi32> { + %scale = tosa.const_shape { value = dense<[2, 1, 3, 1]> : tensor<4xindex> } : () -> !tosa.shape<4> + %offset = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + %border = tosa.const_shape { value = dense<0> : tensor<2xindex> } : () -> !tosa.shape<2> + // expected-error@+1 {{'tosa.resize' op calculated output height did not match expected: calculated=1, expected=4}} + %resize = tosa.resize %arg0, %scale, %offset, %border {mode = "BILINEAR"} : (tensor<3x1x1x7xi8>, !tosa.shape<4>, !tosa.shape<2>, !tosa.shape<2>) -> tensor<3x4x5x7xi32> + + return %resize : tensor<3x4x5x7xi32> +}