Files
clang-p2996/mlir/lib/Dialect/Tosa/Transforms/TosaValidation.cpp
Tai Ly d713a00270 [TOSA] Add level checks and remove Tensor1DTo4D
Remove Tosa_Tensor1Dto4D and Tosa_TensorUpto4D in the Tosa Dialect
and added level checks to TosaValidation pass to validate per spec.

Signed-off-by: Tai Ly <tai.ly@arm.com>
Change-Id: Icd32137e9f8051f99994cee9f388f20c1a840f4b

Reviewed By: eric-k256

Differential Revision: https://reviews.llvm.org/D154273
2023-07-12 16:56:44 +00:00

457 lines
14 KiB
C++

//===- TosaValidation.cpp ------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Validate if TOSA dialect input matchs with the specification for given
// requirements.
//
//===----------------------------------------------------------------------===//
#include "mlir/Dialect/Tosa/Transforms/Passes.h"
#include "mlir/Dialect/Tosa/Transforms/PassesEnums.cpp.inc"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/Tosa/IR/TosaOps.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/Matchers.h"
#include "mlir/IR/TypeUtilities.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Transforms/DialectConversion.h"
namespace mlir {
namespace tosa {
#define GEN_PASS_DEF_TOSAVALIDATION
#include "mlir/Dialect/Tosa/Transforms/Passes.h.inc"
} // namespace tosa
} // namespace mlir
using namespace mlir;
using namespace mlir::tosa;
namespace {
static LogicalResult checkConstantOperandPad(Operation *op) {
if (auto pad_op = dyn_cast<tosa::PadOp>(op)) {
DenseElementsAttr paddings;
if (!matchPattern(pad_op.getPadding(), m_Constant(&paddings)))
return op->emitOpError("padding of pad is not constant");
DenseElementsAttr pad_const;
// Assume this op is zero-padding if pad_const is not presented.
if (pad_op.getPadConst() &&
!matchPattern(pad_op.getPadConst(), m_Constant(&pad_const)))
return op->emitOpError("pad_const of pad is not constant");
}
return success();
}
static LogicalResult checkConstantOperandTranspose(Operation *op) {
if (auto transpose_op = dyn_cast<tosa::TransposeOp>(op)) {
DenseElementsAttr perms;
if (!matchPattern(transpose_op.getPerms(), m_Constant(&perms)))
return op->emitOpError("perms of transpose is not constant");
}
return success();
}
static LogicalResult checkConstantOperandFullyConnected(Operation *op) {
if (auto fc_op = dyn_cast<tosa::FullyConnectedOp>(op)) {
DenseElementsAttr weight;
if (!matchPattern(fc_op.getWeight(), m_Constant(&weight)))
return op->emitOpError("weight of fully_connected is not constant");
DenseElementsAttr bias;
if (!matchPattern(fc_op.getBias(), m_Constant(&bias)))
return op->emitOpError("bias of fully_connected is not constant");
}
return success();
}
struct tosa_level_t {
int32_t MAX_RANK = 0;
int32_t MAX_KERNEL = 0;
int32_t MAX_STRIDE = 0;
int32_t MAX_SCALE = 0;
// @todo: MAX_LOG2_SIZE value and checks
bool operator==(const tosa_level_t &rhs) {
return MAX_RANK == rhs.MAX_RANK && MAX_KERNEL == rhs.MAX_KERNEL &&
MAX_STRIDE == rhs.MAX_STRIDE && MAX_SCALE == rhs.MAX_SCALE;
}
};
static constexpr tosa_level_t TOSA_LEVEL_EIGHTK = {6, 8192, 8192, 64};
static constexpr tosa_level_t TOSA_LEVEL_NONE = {0, 0, 0, 0};
//===----------------------------------------------------------------------===//
// TOSA Validation Pass.
//===----------------------------------------------------------------------===//
struct TosaValidation : public tosa::impl::TosaValidationBase<TosaValidation> {
public:
explicit TosaValidation() { populateConstantOperandChecks(); }
void runOnOperation() override;
LogicalResult applyConstantOperandCheck(Operation *op) {
for (auto &checker : const_checkers) {
if (failed(checker(op)))
return failure();
}
return success();
}
LogicalResult applyLevelCheck(Operation *op);
private:
void populateConstantOperandChecks() {
const_checkers.emplace_back(checkConstantOperandPad);
const_checkers.emplace_back(checkConstantOperandTranspose);
const_checkers.emplace_back(checkConstantOperandFullyConnected);
}
bool levelCheckKernel(Operation *op, int32_t v,
const std::string &check_desc) {
if (v > tosa_level.MAX_KERNEL) {
op->emitOpError() << "failed level check: " << check_desc;
return false;
}
return true;
}
bool levelCheckStride(Operation *op, int32_t v,
const std::string &check_desc) {
if (v > tosa_level.MAX_STRIDE) {
op->emitOpError() << "failed level check: " << check_desc;
return false;
}
return true;
}
bool levelCheckScale(Operation *op, int32_t v,
const std::string &check_desc) {
if (v > tosa_level.MAX_SCALE) {
op->emitOpError() << "failed level check: " << check_desc;
return false;
}
return true;
}
bool levelCheckRank(Operation *op, const Value &v,
const std::string &check_desc) {
if (ShapedType type = dyn_cast<ShapedType>(v.getType())) {
if (type.getRank() > tosa_level.MAX_RANK) {
op->emitOpError() << "failed level check: " << check_desc;
return false;
}
}
return true;
}
template <typename T>
bool levelCheckRanksFor(Operation *op) {
if (dyn_cast<T>(op)) {
// level check ranks of all operands and results
for (auto v : op->getOperands()) {
if (!levelCheckRank(op, v, "operand rank(shape) <= MAX_RANK"))
return false;
}
for (auto v : op->getResults()) {
if (!levelCheckRank(op, v, "result rank(shape) <= MAX_RANK"))
return false;
}
}
return true;
}
bool levelCheckRanks(Operation *op) {
#define CHECK_RANKS_FOR(tosa_op) \
if (!levelCheckRanksFor<tosa_op##Op>(op)) \
return false;
// tensor operators:
CHECK_RANKS_FOR(ArgMax);
// all activation functions:
CHECK_RANKS_FOR(Clamp);
CHECK_RANKS_FOR(Sigmoid);
CHECK_RANKS_FOR(Tanh);
// all elementwise binary operators:
CHECK_RANKS_FOR(Add);
CHECK_RANKS_FOR(ArithmeticRightShift);
CHECK_RANKS_FOR(BitwiseAnd);
CHECK_RANKS_FOR(BitwiseOr);
CHECK_RANKS_FOR(BitwiseXor);
CHECK_RANKS_FOR(Div);
CHECK_RANKS_FOR(LogicalAnd);
CHECK_RANKS_FOR(LogicalLeftShift);
CHECK_RANKS_FOR(LogicalRightShift);
CHECK_RANKS_FOR(LogicalOr);
CHECK_RANKS_FOR(LogicalXor);
CHECK_RANKS_FOR(Maximum);
CHECK_RANKS_FOR(Minimum);
CHECK_RANKS_FOR(Mul);
CHECK_RANKS_FOR(Pow);
CHECK_RANKS_FOR(Sub);
CHECK_RANKS_FOR(Table);
// all elementwise unary operators:
CHECK_RANKS_FOR(Abs);
CHECK_RANKS_FOR(BitwiseNot);
CHECK_RANKS_FOR(Ceil);
CHECK_RANKS_FOR(Clz);
CHECK_RANKS_FOR(Exp);
CHECK_RANKS_FOR(Floor);
CHECK_RANKS_FOR(Log);
CHECK_RANKS_FOR(LogicalNot);
CHECK_RANKS_FOR(Negate);
CHECK_RANKS_FOR(Reciprocal);
CHECK_RANKS_FOR(Rsqrt);
// all elementwise ternary operators:
CHECK_RANKS_FOR(Select);
// all comparison operators:
CHECK_RANKS_FOR(Equal);
CHECK_RANKS_FOR(Greater);
CHECK_RANKS_FOR(GreaterEqual);
// all reduction operators:
CHECK_RANKS_FOR(ReduceAll);
CHECK_RANKS_FOR(ReduceAny);
CHECK_RANKS_FOR(ReduceMax);
CHECK_RANKS_FOR(ReduceMin);
CHECK_RANKS_FOR(ReduceProd);
CHECK_RANKS_FOR(ReduceSum);
// all data layout operators:
CHECK_RANKS_FOR(Concat);
CHECK_RANKS_FOR(Pad);
CHECK_RANKS_FOR(Reshape);
CHECK_RANKS_FOR(Reverse);
CHECK_RANKS_FOR(Slice);
CHECK_RANKS_FOR(Tile);
CHECK_RANKS_FOR(Transpose);
// all type conversion operators:
CHECK_RANKS_FOR(Cast);
CHECK_RANKS_FOR(Rescale);
// all data nodes operators:
CHECK_RANKS_FOR(Const);
CHECK_RANKS_FOR(Identity);
#undef CHECK_RANKS_FOR
return true;
}
// Pool Op: level check kernel/stride/pad values
template <typename T>
bool levelCheckPool(Operation *op) {
if (auto pool_op = dyn_cast<T>(op)) {
for (auto k : pool_op.getKernel()) {
if (!levelCheckKernel(op, k, "kernel <= MAX_KERNEL")) {
return false;
}
}
for (auto s : pool_op.getStride()) {
if (!levelCheckStride(op, s, "stride <= MAX_STRIDE")) {
return false;
}
}
for (auto p : pool_op.getPad()) {
if (!levelCheckKernel(op, p, "pad <= MAX_KERNEL")) {
return false;
}
}
}
return true;
}
// Conv Op: level check dilation/stride/pad values
template <typename T>
bool levelCheckConv(Operation *op) {
if (auto conv_op = dyn_cast<T>(op)) {
for (auto k : conv_op.getDilation()) {
if (!levelCheckKernel(op, k, "dilation <= MAX_KERNEL")) {
return false;
}
}
for (auto p : conv_op.getPad()) {
if (!levelCheckKernel(op, p, "pad <= MAX_KERNEL")) {
return false;
}
}
for (auto s : conv_op.getStride()) {
if (!levelCheckStride(op, s, "stride <= MAX_STRIDE")) {
return false;
}
}
auto dilation = conv_op.getDilation();
if (ShapedType weight_type =
dyn_cast<ShapedType>(op->getOperand(1).getType())) {
auto shape = weight_type.getShape();
if (isa<tosa::Conv2DOp>(op)) {
assert(shape.size() == 4);
assert(dilation.size() == 2);
if (!levelCheckKernel(op, dilation[0] * shape[1],
"dilation_y * KH <= MAX_KERNEL)") ||
!levelCheckKernel(op, dilation[1] * shape[2],
"dilation_x * KW <= MAX_KERNEL)"))
return false;
} else if (isa<tosa::Conv3DOp>(op)) {
assert(shape.size() == 5);
assert(dilation.size() == 3);
if (!levelCheckKernel(op, dilation[0] * shape[1],
"dilation_d * KD <= MAX_KERNEL)") ||
!levelCheckKernel(op, dilation[1] * shape[2],
"dilation_y * KH <= MAX_KERNEL)") ||
!levelCheckKernel(op, dilation[2] * shape[3],
"dilation_x * KW <= MAX_KERNEL)"))
return false;
} else if (isa<tosa::DepthwiseConv2DOp>(op)) {
assert(shape.size() == 4);
assert(dilation.size() == 2);
if (!levelCheckKernel(op, dilation[0] * shape[0],
"dilation_y * KH <= MAX_KERNEL)") ||
!levelCheckKernel(op, dilation[1] * shape[1],
"dilation_x * KW <= MAX_KERNEL)"))
return false;
}
}
}
return true;
}
// FFT op: level check H, W in input shape [N,H,W]
template <typename T>
bool levelCheckFFT(Operation *op) {
if (isa<T>(op)) {
for (auto v : op->getOperands()) {
if (ShapedType type = dyn_cast<ShapedType>(v.getType())) {
auto shape = type.getShape();
assert(shape.size() == 3);
if (!levelCheckKernel(op, shape[1], "H <= MAX_KERNEL") ||
!levelCheckKernel(op, shape[2], "W <= MAX_KERNEL")) {
return false;
}
}
}
}
return true;
}
// TransposeConv2d op: level check kH/kW, outpad, and stride
bool levelCheckTransposeConv2d(Operation *op) {
if (auto transpose = dyn_cast<tosa::TransposeConv2DOp>(op)) {
if (ShapedType filter_type =
transpose.getFilter().getType().dyn_cast<ShapedType>()) {
auto shape = filter_type.getShape();
assert(shape.size() == 4);
// level check kernel sizes for kH and KW
if (!levelCheckKernel(op, shape[1], "KH <= MAX_KERNEL") ||
!levelCheckKernel(op, shape[2], "KW <= MAX_KERNEL")) {
return false;
}
}
for (auto p : transpose.getOutPad()) {
if (!levelCheckKernel(op, p, "pad <= MAX_KERNEL")) {
return false;
}
}
for (auto s : transpose.getStride()) {
if (!levelCheckStride(op, s, "stride <= MAX_STRIDE")) {
return false;
}
}
}
return true;
}
// Resize op: level check max scales
bool levelCheckResize(Operation *op) {
if (auto resize = dyn_cast<tosa::ResizeOp>(op)) {
auto scale = resize.getScale();
int16_t scale_y_n = scale[0];
int16_t scale_y_d = scale[1];
int16_t scale_x_n = scale[2];
int16_t scale_x_d = scale[3];
if (!levelCheckScale(op, scale_y_n / scale_y_d,
"scale_y_n/scale_y_d <= MAX_SCALE") ||
!levelCheckScale(op, scale_x_n / scale_x_d,
"scale_x_n/scale_x_d <= MAX_SCALE")) {
return false;
}
}
return true;
}
// configure profile and level values from pass options profileName and
// levelName
void configLevelAndProfile() {
profileType = symbolizeEnum<TosaProfileEnum>(profileName);
auto levelType = symbolizeEnum<TosaLevelEnum>(levelName);
tosa_level = TOSA_LEVEL_NONE;
if (levelType == TosaLevelEnum::EightK) {
tosa_level = TOSA_LEVEL_EIGHTK;
}
}
SmallVector<std::function<LogicalResult(Operation *)>> const_checkers;
std::optional<TosaProfileEnum> profileType;
tosa_level_t tosa_level;
};
LogicalResult TosaValidation::applyLevelCheck(Operation *op) {
if (tosa_level == TOSA_LEVEL_NONE) {
// no need to do level checks
return success();
}
if (!levelCheckRanks(op)) {
return failure();
}
// additional level checks from spec 0.70
if (!levelCheckPool<tosa::AvgPool2dOp>(op) ||
!levelCheckConv<tosa::Conv2DOp>(op) ||
!levelCheckConv<tosa::Conv3DOp>(op) ||
!levelCheckConv<tosa::DepthwiseConv2DOp>(op) ||
!levelCheckFFT<tosa::FFT2dOp>(op) ||
!levelCheckPool<tosa::MaxPool2dOp>(op) ||
!levelCheckFFT<tosa::RFFT2dOp>(op) || !levelCheckTransposeConv2d(op) ||
!levelCheckResize(op)) {
return failure();
}
return success();
}
void TosaValidation::runOnOperation() {
configLevelAndProfile();
getOperation().walk([&](Operation *op) {
for (Value operand : op->getOperands()) {
if ((profileType == TosaProfileEnum::BaseInference) &&
isa<FloatType>(getElementTypeOrSelf(operand))) {
return signalPassFailure();
}
if (getElementTypeOrSelf(operand).isF64()) {
return signalPassFailure();
}
}
// Some uses of TOSA rely on the constant operands of particular operations.
if (StrictOperationSpecAlignment && failed(applyConstantOperandCheck(op)))
signalPassFailure();
// do level checks
if (failed(applyLevelCheck(op)))
signalPassFailure();
});
}
} // namespace
std::unique_ptr<Pass> mlir::tosa::createTosaValidationPass() {
return std::make_unique<TosaValidation>();
}