Skip to content

Commit cdf10d7

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 <luke.hutton@arm.com>
1 parent 976e413 commit cdf10d7

File tree

4 files changed

+238
-110
lines changed

4 files changed

+238
-110
lines changed

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

+96-2
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());
@@ -2603,8 +2691,14 @@ LogicalResult MaxPool2dOp::inferReturnTypeComponents(
26032691
}
26042692

26052693
LogicalResult MaxPool2dOp::verify() {
2606-
return verifySameElementTypes(*this, /* intype = */ getInput().getType(),
2607-
/* outType = */ getOutput().getType());
2694+
if (failed(verifySameElementTypes(*this, /* intype = */ getInput().getType(),
2695+
/* outType = */ getOutput().getType())))
2696+
return failure();
2697+
2698+
if (failed(verifyPoolingOp(*this)))
2699+
return failure();
2700+
2701+
return success();
26082702
}
26092703

26102704
LogicalResult DepthwiseConv2DOp::inferReturnTypeComponents(

mlir/test/Dialect/Tosa/invalid.mlir

+99
Original file line numberDiff line numberDiff line change
@@ -1717,3 +1717,102 @@ func.func @test_negate_output_zp_non_zero(%arg0: tensor<1x16x16x8xf32>) -> tenso
17171717
: (tensor<1x16x16x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x16x16x8xf32>
17181718
return %0 : tensor<1x16x16x8xf32>
17191719
}
1720+
1721+
// -----
1722+
1723+
func.func @test_avgpool2d_invalid_kernel(%arg0: tensor<1x32x32x8xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x32x32x8xf32> {
1724+
// expected-error@+1 {{'tosa.avg_pool2d' op expect all kernel values to be >= 1, got 0, -1}}
1725+
%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} :
1726+
(tensor<1x32x32x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x32x32x8xf32>
1727+
return %0 : tensor<1x32x32x8xf32>
1728+
}
1729+
1730+
// -----
1731+
1732+
func.func @test_avgpool2d_invalid_stride(%arg0: tensor<1x32x32x8xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x32x32x8xf32> {
1733+
// expected-error@+1 {{'tosa.avg_pool2d' op expect all stride values to be >= 1, got 1, 0}}
1734+
%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} :
1735+
(tensor<1x32x32x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x32x32x8xf32>
1736+
return %0 : tensor<1x32x32x8xf32>
1737+
}
1738+
1739+
// -----
1740+
1741+
func.func @test_avgpool2d_invalid_padding(%arg0: tensor<1x32x32x8xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x32x32x8xf32> {
1742+
// expected-error@+1 {{'tosa.avg_pool2d' op expect all padding values to be >= 0, got 0, 0, 0, -1}}
1743+
%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} :
1744+
(tensor<1x32x32x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x32x32x8xf32>
1745+
return %0 : tensor<1x32x32x8xf32>
1746+
}
1747+
1748+
// -----
1749+
1750+
func.func @test_avgpool2d_padding_not_less_than_kernel_x(%arg0: tensor<1x32x32x8xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x32x32x8xf32> {
1751+
// 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}}
1752+
%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} :
1753+
(tensor<1x32x32x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x32x32x8xf32>
1754+
return %0 : tensor<1x32x32x8xf32>
1755+
}
1756+
1757+
// -----
1758+
1759+
func.func @test_avgpool2d_padding_not_less_than_kernel_y(%arg0: tensor<1x32x32x8xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x32x32x8xf32> {
1760+
// 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}}
1761+
%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} :
1762+
(tensor<1x32x32x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x32x32x8xf32>
1763+
return %0 : tensor<1x32x32x8xf32>
1764+
}
1765+
1766+
// -----
1767+
1768+
func.func @test_avgpool2d_wholly_divisible_height(%arg0: tensor<1x32x32x8xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x32x32x8xf32> {
1769+
// 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}}
1770+
%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} :
1771+
(tensor<1x32x32x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x32x32x8xf32>
1772+
return %0 : tensor<1x32x32x8xf32>
1773+
}
1774+
1775+
// -----
1776+
1777+
func.func @test_avgpool2d_wholly_divisible_width(%arg0: tensor<1x32x32x8xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x32x32x8xf32> {
1778+
// 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}}
1779+
%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} :
1780+
(tensor<1x32x32x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x32x32x8xf32>
1781+
return %0 : tensor<1x32x32x8xf32>
1782+
}
1783+
1784+
// -----
1785+
1786+
func.func @test_avgpool2d_unexpected_output_height(%arg0: tensor<1x32x32x8xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x33x32x8xf32> {
1787+
// expected-error@+1 {{'tosa.avg_pool2d' op calculated output height did not match expected: calculated=32, expected=33}}
1788+
%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} :
1789+
(tensor<1x32x32x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x33x32x8xf32>
1790+
return %0 : tensor<1x33x32x8xf32>
1791+
}
1792+
1793+
// -----
1794+
1795+
func.func @test_avgpool2d_unexpected_output_width(%arg0: tensor<1x32x32x8xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x?x33x8xf32> {
1796+
// expected-error@+1 {{'tosa.avg_pool2d' op calculated output width did not match expected: calculated=32, expected=33}}
1797+
%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} :
1798+
(tensor<1x32x32x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x?x33x8xf32>
1799+
return %0 : tensor<1x?x33x8xf32>
1800+
}
1801+
1802+
// -----
1803+
1804+
func.func @test_maxpool2d_invalid_kernel(%arg0: tensor<1x32x32x8xf32>) -> tensor<1x2x32x8xf32> {
1805+
// expected-error@+1 {{'tosa.max_pool2d' op expect all kernel values to be >= 1, got 0, 1}}
1806+
%0 = "tosa.max_pool2d"(%arg0) {kernel = array<i64: 0, 1>, pad = array<i64: 0, 0, 0, 0>, stride = array<i64: 1, 1>} :
1807+
(tensor<1x32x32x8xf32>) -> tensor<1x2x32x8xf32>
1808+
return %0 : tensor<1x2x32x8xf32>
1809+
}
1810+
1811+
// -----
1812+
1813+
func.func @test_maxpool2d_unexpected_output_width(%arg0: tensor<1x32x32x8xf32>) -> tensor<1x32x2x8xf32> {
1814+
// expected-error@+1 {{'tosa.max_pool2d' op calculated output width did not match expected: calculated=32, expected=2}}
1815+
%0 = "tosa.max_pool2d"(%arg0) {kernel = array<i64: 1, 1>, pad = array<i64: 0, 0, 0, 0>, stride = array<i64: 1, 1>} :
1816+
(tensor<1x32x32x8xf32>) -> tensor<1x32x2x8xf32>
1817+
return %0 : tensor<1x32x2x8xf32>
1818+
}

0 commit comments

Comments
 (0)