Skip to content

Commit 7a602e2

Browse files
committed
[mlir][tosa] Add error if verification to pooling operators
This commit adds the following checks to avg_pool2d and max_pool2d TOSA operations: - check kernel values are >= 1 - check stride values are >= 1 - check padding values are >= 0 - check padding values are less than kernel sizes - check output shape matches the expected output shape Change-Id: I6ef97ba40ef3448b4ddd974990b8c3ce009221c5 Signed-off-by: Luke Hutton <[email protected]>
1 parent 4f469ae commit 7a602e2

File tree

5 files changed

+239
-112
lines changed

5 files changed

+239
-112
lines changed

mlir/lib/Dialect/Tosa/IR/TosaOps.cpp

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,95 @@ LogicalResult tosa::ArgMaxOp::verify() {
485485
return success();
486486
}
487487

488+
template <typename T>
489+
static LogicalResult verifyPoolingOp(T op) {
490+
const llvm::ArrayRef<int64_t> kernel = op.getKernel();
491+
if (llvm::any_of(kernel, [](int64_t s) { return s < 1; }))
492+
return op.emitOpError("expect all kernel values to be >= 1, got ")
493+
<< kernel;
494+
495+
const llvm::ArrayRef<int64_t> strides = op.getStride();
496+
if (llvm::any_of(strides, [](int64_t s) { return s < 1; }))
497+
return op.emitOpError("expect all stride values to be >= 1, got ")
498+
<< strides;
499+
500+
const llvm::ArrayRef<int64_t> padding = op.getPad();
501+
if (llvm::any_of(padding, [](int64_t p) { return p < 0; }))
502+
return op.emitOpError("expect all padding values to be >= 0, got ")
503+
<< padding;
504+
505+
// Padding must be less than kernel size to avoid a divide-by-zero
506+
const int64_t kernelX = kernel[1];
507+
const int64_t padLeft = padding[2];
508+
const int64_t padRight = padding[3];
509+
if (padRight >= kernelX || padLeft >= kernelX)
510+
return op.emitOpError("expected left/right padding to be less than the "
511+
"width of the kernel, got pad_left=")
512+
<< padLeft << ", pad_right=" << padRight << ", kernel_x=" << kernelX;
513+
514+
const int64_t kernelY = kernel[0];
515+
const int64_t padTop = padding[0];
516+
const int64_t padBottom = padding[1];
517+
if (padTop >= kernelY || padBottom >= kernelY)
518+
return op.emitOpError("expected top/bottom padding to be less than the "
519+
"height of the kernel, got pad_top=")
520+
<< padTop << ", pad_bottom=" << padBottom
521+
<< ", kernel_y=" << kernelY;
522+
523+
const auto inputType =
524+
llvm::dyn_cast<RankedTensorType>(op.getInput().getType());
525+
const auto outputType =
526+
llvm::dyn_cast<RankedTensorType>(op.getResult().getType());
527+
if (!inputType || !outputType)
528+
return success();
529+
530+
const auto verifyOutputSize =
531+
[&op](const int64_t inputSize, const int64_t outputSize,
532+
const int64_t kernelSize, const int64_t strideSize,
533+
const int64_t padBefore, const int64_t padAfter,
534+
const llvm::StringRef dimName, const llvm::StringRef dimAxis,
535+
const llvm::StringRef padBeforeName,
536+
const llvm::StringRef padAfterName) -> LogicalResult {
537+
if (ShapedType::isDynamic(inputSize))
538+
return success();
539+
540+
const std::optional<int64_t> calculatedOutSizeMinusOne =
541+
idivCheck(inputSize + padBefore + padAfter - kernelSize, strideSize);
542+
if (!calculatedOutSizeMinusOne.has_value())
543+
return op.emitOpError("expected input_")
544+
<< dimName << " + pad_" << padBeforeName << " + pad_"
545+
<< padAfterName << " - kernel_" << dimAxis
546+
<< " to be wholly divisible by stride_" << dimAxis << ", got ("
547+
<< inputSize << " + " << padBefore << " + " << padAfter << " - "
548+
<< kernelSize << ") / " << strideSize;
549+
550+
const int64_t calculatedOutSize = calculatedOutSizeMinusOne.value() + 1;
551+
if (!ShapedType::isDynamic(outputSize) && calculatedOutSize != outputSize)
552+
return op.emitOpError("calculated output ")
553+
<< dimName << " did not match expected: "
554+
<< "calculated=" << calculatedOutSize
555+
<< ", expected=" << outputSize;
556+
557+
return success();
558+
};
559+
560+
if (failed(verifyOutputSize(inputType.getDimSize(1), outputType.getDimSize(1),
561+
kernel[0], strides[0], padding[0], padding[1],
562+
"height", "y", "top", "bottom")))
563+
return failure();
564+
565+
if (failed(verifyOutputSize(inputType.getDimSize(2), outputType.getDimSize(2),
566+
kernel[1], strides[1], padding[2], padding[3],
567+
"width", "x", "left", "right")))
568+
return failure();
569+
570+
return success();
571+
}
572+
488573
LogicalResult tosa::AvgPool2dOp::verify() {
574+
if (failed(verifyPoolingOp(*this)))
575+
return failure();
576+
489577
const Type inputETy = getStorageElementTypeOrSelf(getInput().getType());
490578
const Type resultETy = getStorageElementTypeOrSelf(getOutput().getType());
491579
const Type inputZpETy = getStorageElementTypeOrSelf(getInputZp().getType());
@@ -2524,8 +2612,14 @@ LogicalResult MaxPool2dOp::inferReturnTypeComponents(
25242612
}
25252613

25262614
LogicalResult MaxPool2dOp::verify() {
2527-
return verifySameElementTypes(*this, /* intype = */ getInput().getType(),
2528-
/* outType = */ getOutput().getType());
2615+
if (failed(verifySameElementTypes(*this, /* intype = */ getInput().getType(),
2616+
/* outType = */ getOutput().getType())))
2617+
return failure();
2618+
2619+
if (failed(verifyPoolingOp(*this)))
2620+
return failure();
2621+
2622+
return success();
25292623
}
25302624

25312625
LogicalResult DepthwiseConv2DOp::inferReturnTypeComponents(

mlir/test/Dialect/Tosa/invalid.mlir

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ func.func @test_pad_invalid_padConst_rank(%arg0: tensor<13x21xf32>, %arg1: tenso
287287
// -----
288288

289289
func.func @test_pad_padding_shape_mismatch(%arg0: tensor<13x21x3xf32>) -> tensor<13x21x3xf32> {
290-
%0 = tosa.const_shape {value = dense<1> : tensor<4xindex>} : () -> !tosa.shape<4>
290+
%0 = tosa.const_shape {value = dense<1> : tensor<4xindex>} : () -> !tosa.shape<4>
291291
%pad_const = "tosa.const"() {value = dense<3.14> : tensor<1xf32>} : () -> tensor<1xf32>
292292
// expected-error@+1 {{'tosa.pad' op expected padding tensor dim 0 to have size 6 (2*rank(shape1)) but got size 4}}
293293
%1 = tosa.pad %arg0, %0, %pad_const : (tensor<13x21x3xf32>, !tosa.shape<4>, tensor<1xf32>) -> tensor<13x21x3xf32>
@@ -1431,3 +1431,102 @@ func.func @test_argmax_invalid_output_shape(%arg0: tensor<1x2x3xf32>) -> tensor<
14311431
%0 = tosa.argmax %arg0 {axis = 0 : i32}: (tensor<1x2x3xf32>) -> tensor<1x2x3xi32>
14321432
return %0 : tensor<1x2x3xi32>
14331433
}
1434+
1435+
// -----
1436+
1437+
func.func @test_avgpool2d_invalid_kernel(%arg0: tensor<1x32x32x8xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x32x32x8xf32> {
1438+
// expected-error@+1 {{'tosa.avg_pool2d' op expect all kernel values to be >= 1, got 0, -1}}
1439+
%0 = "tosa.avg_pool2d"(%arg0, %arg1, %arg2) {kernel = array<i64: 0, -1>, pad = array<i64: 0, 0, 0, 0>, stride = array<i64: 1, 1>, acc_type = f32} :
1440+
(tensor<1x32x32x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x32x32x8xf32>
1441+
return %0 : tensor<1x32x32x8xf32>
1442+
}
1443+
1444+
// -----
1445+
1446+
func.func @test_avgpool2d_invalid_stride(%arg0: tensor<1x32x32x8xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x32x32x8xf32> {
1447+
// expected-error@+1 {{'tosa.avg_pool2d' op expect all stride values to be >= 1, got 1, 0}}
1448+
%0 = "tosa.avg_pool2d"(%arg0, %arg1, %arg2) {kernel = array<i64: 1, 1>, pad = array<i64: 0, 0, 0, 0>, stride = array<i64: 1, 0>, acc_type = f32} :
1449+
(tensor<1x32x32x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x32x32x8xf32>
1450+
return %0 : tensor<1x32x32x8xf32>
1451+
}
1452+
1453+
// -----
1454+
1455+
func.func @test_avgpool2d_invalid_padding(%arg0: tensor<1x32x32x8xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x32x32x8xf32> {
1456+
// expected-error@+1 {{'tosa.avg_pool2d' op expect all padding values to be >= 0, got 0, 0, 0, -1}}
1457+
%0 = "tosa.avg_pool2d"(%arg0, %arg1, %arg2) {kernel = array<i64: 1, 1>, pad = array<i64: 0, 0, 0, -1>, stride = array<i64: 1, 1>, acc_type = f32} :
1458+
(tensor<1x32x32x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x32x32x8xf32>
1459+
return %0 : tensor<1x32x32x8xf32>
1460+
}
1461+
1462+
// -----
1463+
1464+
func.func @test_avgpool2d_padding_not_less_than_kernel_x(%arg0: tensor<1x32x32x8xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x32x32x8xf32> {
1465+
// expected-error@+1 {{'tosa.avg_pool2d' op expected left/right padding to be less than the width of the kernel, got pad_left=0, pad_right=1, kernel_x=1}}
1466+
%0 = "tosa.avg_pool2d"(%arg0, %arg1, %arg2) {kernel = array<i64: 1, 1>, pad = array<i64: 0, 0, 0, 1>, stride = array<i64: 1, 1>, acc_type = f32} :
1467+
(tensor<1x32x32x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x32x32x8xf32>
1468+
return %0 : tensor<1x32x32x8xf32>
1469+
}
1470+
1471+
// -----
1472+
1473+
func.func @test_avgpool2d_padding_not_less_than_kernel_y(%arg0: tensor<1x32x32x8xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x32x32x8xf32> {
1474+
// expected-error@+1 {{'tosa.avg_pool2d' op expected top/bottom padding to be less than the height of the kernel, got pad_top=2, pad_bottom=0, kernel_y=1}}
1475+
%0 = "tosa.avg_pool2d"(%arg0, %arg1, %arg2) {kernel = array<i64: 1, 1>, pad = array<i64: 2, 0, 0, 0>, stride = array<i64: 1, 1>, acc_type = f32} :
1476+
(tensor<1x32x32x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x32x32x8xf32>
1477+
return %0 : tensor<1x32x32x8xf32>
1478+
}
1479+
1480+
// -----
1481+
1482+
func.func @test_avgpool2d_wholly_divisible_height(%arg0: tensor<1x32x32x8xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x32x32x8xf32> {
1483+
// expected-error@+1 {{'tosa.avg_pool2d' op expected input_height + pad_top + pad_bottom - kernel_y to be wholly divisible by stride_y, got (32 + 0 + 0 - 1) / 2}}
1484+
%0 = "tosa.avg_pool2d"(%arg0, %arg1, %arg2) {kernel = array<i64: 1, 1>, pad = array<i64: 0, 0, 0, 0>, stride = array<i64: 2, 1>, acc_type = f32} :
1485+
(tensor<1x32x32x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x32x32x8xf32>
1486+
return %0 : tensor<1x32x32x8xf32>
1487+
}
1488+
1489+
// -----
1490+
1491+
func.func @test_avgpool2d_wholly_divisible_width(%arg0: tensor<1x32x32x8xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x32x32x8xf32> {
1492+
// expected-error@+1 {{'tosa.avg_pool2d' op expected input_width + pad_left + pad_right - kernel_x to be wholly divisible by stride_x, got (32 + 0 + 0 - 1) / 2}}
1493+
%0 = "tosa.avg_pool2d"(%arg0, %arg1, %arg2) {kernel = array<i64: 1, 1>, pad = array<i64: 0, 0, 0, 0>, stride = array<i64: 1, 2>, acc_type = f32} :
1494+
(tensor<1x32x32x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x32x32x8xf32>
1495+
return %0 : tensor<1x32x32x8xf32>
1496+
}
1497+
1498+
// -----
1499+
1500+
func.func @test_avgpool2d_unexpected_output_height(%arg0: tensor<1x32x32x8xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x33x32x8xf32> {
1501+
// expected-error@+1 {{'tosa.avg_pool2d' op calculated output height did not match expected: calculated=32, expected=33}}
1502+
%0 = "tosa.avg_pool2d"(%arg0, %arg1, %arg2) {kernel = array<i64: 1, 1>, pad = array<i64: 0, 0, 0, 0>, stride = array<i64: 1, 1>, acc_type = f32} :
1503+
(tensor<1x32x32x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x33x32x8xf32>
1504+
return %0 : tensor<1x33x32x8xf32>
1505+
}
1506+
1507+
// -----
1508+
1509+
func.func @test_avgpool2d_unexpected_output_width(%arg0: tensor<1x32x32x8xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x?x33x8xf32> {
1510+
// expected-error@+1 {{'tosa.avg_pool2d' op calculated output width did not match expected: calculated=32, expected=33}}
1511+
%0 = "tosa.avg_pool2d"(%arg0, %arg1, %arg2) {kernel = array<i64: 1, 1>, pad = array<i64: 0, 0, 0, 0>, stride = array<i64: 1, 1>, acc_type = f32} :
1512+
(tensor<1x32x32x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x?x33x8xf32>
1513+
return %0 : tensor<1x?x33x8xf32>
1514+
}
1515+
1516+
// -----
1517+
1518+
func.func @test_maxpool2d_invalid_kernel(%arg0: tensor<1x32x32x8xf32>) -> tensor<1x2x32x8xf32> {
1519+
// expected-error@+1 {{'tosa.max_pool2d' op expect all kernel values to be >= 1, got 0, 1}}
1520+
%0 = "tosa.max_pool2d"(%arg0) {kernel = array<i64: 0, 1>, pad = array<i64: 0, 0, 0, 0>, stride = array<i64: 1, 1>} :
1521+
(tensor<1x32x32x8xf32>) -> tensor<1x2x32x8xf32>
1522+
return %0 : tensor<1x2x32x8xf32>
1523+
}
1524+
1525+
// -----
1526+
1527+
func.func @test_maxpool2d_unexpected_output_width(%arg0: tensor<1x32x32x8xf32>) -> tensor<1x32x2x8xf32> {
1528+
// expected-error@+1 {{'tosa.max_pool2d' op calculated output width did not match expected: calculated=32, expected=2}}
1529+
%0 = "tosa.max_pool2d"(%arg0) {kernel = array<i64: 1, 1>, pad = array<i64: 0, 0, 0, 0>, stride = array<i64: 1, 1>} :
1530+
(tensor<1x32x32x8xf32>) -> tensor<1x32x2x8xf32>
1531+
return %0 : tensor<1x32x2x8xf32>
1532+
}

mlir/test/Dialect/Tosa/invalid_extension.mlir

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,3 @@ func.func @test_while_loop(%arg0: tensor<10xi32>, %arg1: tensor<i32>) {
6969
}
7070
return
7171
}
72-

0 commit comments

Comments
 (0)