Skip to content

Commit c44c905

Browse files
authored
[mlir][tosa] Add error if verification to pooling operators (#130052)
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 Signed-off-by: Luke Hutton <[email protected]>
1 parent f62e168 commit c44c905

File tree

4 files changed

+238
-110
lines changed

4 files changed

+238
-110
lines changed

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

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,95 @@ LogicalResult tosa::ArgMaxOp::verify() {
504504
return success();
505505
}
506506

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

26522740
LogicalResult MaxPool2dOp::verify() {
2653-
return verifySameElementTypes(*this, /* intype = */ getInput().getType(),
2654-
/* outType = */ getOutput().getType());
2741+
if (failed(verifySameElementTypes(*this, /* intype = */ getInput().getType(),
2742+
/* outType = */ getOutput().getType())))
2743+
return failure();
2744+
2745+
if (failed(verifyPoolingOp(*this)))
2746+
return failure();
2747+
2748+
return success();
26552749
}
26562750

26572751
LogicalResult DepthwiseConv2DOp::inferReturnTypeComponents(

mlir/test/Dialect/Tosa/invalid.mlir

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1753,3 +1753,102 @@ func.func @test_negate_output_zp_non_zero(%arg0: tensor<1x16x16x8xf32>) -> tenso
17531753
: (tensor<1x16x16x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x16x16x8xf32>
17541754
return %0 : tensor<1x16x16x8xf32>
17551755
}
1756+
1757+
// -----
1758+
1759+
func.func @test_avgpool2d_invalid_kernel(%arg0: tensor<1x32x32x8xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x32x32x8xf32> {
1760+
// expected-error@+1 {{'tosa.avg_pool2d' op expect all kernel values to be >= 1, got 0, -1}}
1761+
%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} :
1762+
(tensor<1x32x32x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x32x32x8xf32>
1763+
return %0 : tensor<1x32x32x8xf32>
1764+
}
1765+
1766+
// -----
1767+
1768+
func.func @test_avgpool2d_invalid_stride(%arg0: tensor<1x32x32x8xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x32x32x8xf32> {
1769+
// expected-error@+1 {{'tosa.avg_pool2d' op expect all stride values to be >= 1, got 1, 0}}
1770+
%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} :
1771+
(tensor<1x32x32x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x32x32x8xf32>
1772+
return %0 : tensor<1x32x32x8xf32>
1773+
}
1774+
1775+
// -----
1776+
1777+
func.func @test_avgpool2d_invalid_padding(%arg0: tensor<1x32x32x8xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x32x32x8xf32> {
1778+
// expected-error@+1 {{'tosa.avg_pool2d' op expect all padding values to be >= 0, got 0, 0, 0, -1}}
1779+
%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} :
1780+
(tensor<1x32x32x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x32x32x8xf32>
1781+
return %0 : tensor<1x32x32x8xf32>
1782+
}
1783+
1784+
// -----
1785+
1786+
func.func @test_avgpool2d_padding_not_less_than_kernel_x(%arg0: tensor<1x32x32x8xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x32x32x8xf32> {
1787+
// 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}}
1788+
%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} :
1789+
(tensor<1x32x32x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x32x32x8xf32>
1790+
return %0 : tensor<1x32x32x8xf32>
1791+
}
1792+
1793+
// -----
1794+
1795+
func.func @test_avgpool2d_padding_not_less_than_kernel_y(%arg0: tensor<1x32x32x8xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x32x32x8xf32> {
1796+
// 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}}
1797+
%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} :
1798+
(tensor<1x32x32x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x32x32x8xf32>
1799+
return %0 : tensor<1x32x32x8xf32>
1800+
}
1801+
1802+
// -----
1803+
1804+
func.func @test_avgpool2d_wholly_divisible_height(%arg0: tensor<1x32x32x8xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x32x32x8xf32> {
1805+
// 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}}
1806+
%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} :
1807+
(tensor<1x32x32x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x32x32x8xf32>
1808+
return %0 : tensor<1x32x32x8xf32>
1809+
}
1810+
1811+
// -----
1812+
1813+
func.func @test_avgpool2d_wholly_divisible_width(%arg0: tensor<1x32x32x8xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x32x32x8xf32> {
1814+
// 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}}
1815+
%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} :
1816+
(tensor<1x32x32x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x32x32x8xf32>
1817+
return %0 : tensor<1x32x32x8xf32>
1818+
}
1819+
1820+
// -----
1821+
1822+
func.func @test_avgpool2d_unexpected_output_height(%arg0: tensor<1x32x32x8xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x33x32x8xf32> {
1823+
// expected-error@+1 {{'tosa.avg_pool2d' op calculated output height did not match expected: calculated=32, expected=33}}
1824+
%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} :
1825+
(tensor<1x32x32x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x33x32x8xf32>
1826+
return %0 : tensor<1x33x32x8xf32>
1827+
}
1828+
1829+
// -----
1830+
1831+
func.func @test_avgpool2d_unexpected_output_width(%arg0: tensor<1x32x32x8xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x?x33x8xf32> {
1832+
// expected-error@+1 {{'tosa.avg_pool2d' op calculated output width did not match expected: calculated=32, expected=33}}
1833+
%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} :
1834+
(tensor<1x32x32x8xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x?x33x8xf32>
1835+
return %0 : tensor<1x?x33x8xf32>
1836+
}
1837+
1838+
// -----
1839+
1840+
func.func @test_maxpool2d_invalid_kernel(%arg0: tensor<1x32x32x8xf32>) -> tensor<1x2x32x8xf32> {
1841+
// expected-error@+1 {{'tosa.max_pool2d' op expect all kernel values to be >= 1, got 0, 1}}
1842+
%0 = "tosa.max_pool2d"(%arg0) {kernel = array<i64: 0, 1>, pad = array<i64: 0, 0, 0, 0>, stride = array<i64: 1, 1>} :
1843+
(tensor<1x32x32x8xf32>) -> tensor<1x2x32x8xf32>
1844+
return %0 : tensor<1x2x32x8xf32>
1845+
}
1846+
1847+
// -----
1848+
1849+
func.func @test_maxpool2d_unexpected_output_width(%arg0: tensor<1x32x32x8xf32>) -> tensor<1x32x2x8xf32> {
1850+
// expected-error@+1 {{'tosa.max_pool2d' op calculated output width did not match expected: calculated=32, expected=2}}
1851+
%0 = "tosa.max_pool2d"(%arg0) {kernel = array<i64: 1, 1>, pad = array<i64: 0, 0, 0, 0>, stride = array<i64: 1, 1>} :
1852+
(tensor<1x32x32x8xf32>) -> tensor<1x32x2x8xf32>
1853+
return %0 : tensor<1x32x2x8xf32>
1854+
}

0 commit comments

Comments
 (0)