blob: 93c812f7ec93ca8b6987aa5838956ec9641fe1bd [file] [edit]
/*
* Copyright (C) 2021 MediaTek Inc., this file is modified on 02/26/2021
* by MediaTek Inc. based on MIT License .
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the ""Software""), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "delegate/mtk_neuron/neuron_delegate_validation.h"
#include <limits>
#include <memory>
#include <string>
#include <vector>
#include "common/minimal_logging.h"
#include "delegate/mtk_neuron/neuron_delegate.h"
#include "delegate/mtk_neuron/neuron_delegate_kernel.h"
#include "delegate/mtk_neuron/neuron_delegate_utils.h"
#include "delegate/mtk_neuron/neuron_implementation.h"
#include "tensorflow/lite/builtin_ops.h"
#include "tensorflow/lite/c/builtin_op_data.h"
#include "tensorflow/lite/c/c_api.h"
#include "tensorflow/lite/context_util.h"
#include "tensorflow/lite/util.h"
namespace tflite {
namespace neuron {
namespace {
constexpr int32_t kMinMagicNumberForNeuron33 = 28;
struct OpValidationContext {
bool is_valid;
std::vector<NeuronValidationFailure>* validation_failures;
};
#define EXPECT_INPUT_TYPE_IN(actual_type, ...) \
ExpectTypeIn(actual_type, {__VA_ARGS__}, \
NeuronValidationFailureType::kUnsupportedInputType, \
"Input type not in expected list " #__VA_ARGS__, &val_ctx)
void AddValidationFailure(NeuronValidationFailureType failure_type,
const char* message, OpValidationContext* val_ctx) {
val_ctx->is_valid = false;
if (val_ctx->validation_failures) {
val_ctx->validation_failures->push_back({failure_type, message});
}
}
template <typename... Args>
void AddValidationFailureFmt(OpValidationContext* val_ctx,
NeuronValidationFailureType failure_type,
const char* message_fmt, Args... args) {
val_ctx->is_valid = false;
#ifdef NEURON_VERBOSE_VALIDATION
if (val_ctx->validation_failures) {
size_t req_buf_size = snprintf(nullptr, 0, message_fmt, args...) + 1;
std::unique_ptr<char[]> tmp_buf(new char[req_buf_size]);
snprintf(tmp_buf.get(), req_buf_size, message_fmt, args...);
val_ctx->validation_failures->push_back({failure_type, tmp_buf.get()});
}
#endif
}
bool Expect(bool condition, NeuronValidationFailureType failure_type,
const char* message, OpValidationContext* val_ctx) {
if (!condition) {
AddValidationFailure(failure_type, message, val_ctx);
return false;
}
return true;
}
template <typename... Args>
bool ExpectFmt(bool condition, OpValidationContext* val_ctx,
NeuronValidationFailureType failure_type,
const char* message_fmt, Args... args) {
if (!condition) {
AddValidationFailureFmt(val_ctx, failure_type, message_fmt, args...);
return false;
}
return true;
}
bool ExpectTypeIn(TfLiteType actual_type,
std::initializer_list<TfLiteType> allowed_types,
NeuronValidationFailureType failure_type, const char* msg,
OpValidationContext* val_ctx) {
return Expect(std::find(allowed_types.begin(), allowed_types.end(),
actual_type) != allowed_types.end(),
failure_type, msg, val_ctx);
}
bool ExpectMinAndroidSdkVersion(int curr_version, int min_version,
OpValidationContext* val_ctx) {
return ExpectFmt(curr_version >= min_version, val_ctx,
NeuronValidationFailureType::kUnsupportedAndroidVersion,
"Android sdk version less than %d", min_version);
}
bool ExpectMaxOpVersion(int curr_version, int max_version,
OpValidationContext* val_ctx) {
return ExpectFmt(curr_version <= max_version, val_ctx,
NeuronValidationFailureType::kUnsupportedOperatorVersion,
"OP Version higher than %d", max_version);
}
bool ExpectOpVersion(int curr_version, int max_version,
OpValidationContext* val_ctx) {
return ExpectFmt(curr_version <= max_version, val_ctx,
NeuronValidationFailureType::kUnsupportedOperatorVersion,
"OP Version different from %d", max_version);
}
bool ExpectIsFloatOperator(const TfLiteOpaqueContext* context,
const TfLiteOpaqueNode* node,
OpValidationContext* val_ctx) {
const auto input_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 0));
return Expect(IsFloat(input_type),
NeuronValidationFailureType::kUnsupportedInputType,
"Input should be Float", val_ctx);
}
bool ExpectIsFloatOrUint8Operator(const TfLiteOpaqueContext* context,
const TfLiteOpaqueNode* node,
OpValidationContext* val_ctx) {
const auto input_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 0));
return Expect(IsFloatOrUInt8(input_type),
NeuronValidationFailureType::kUnsupportedInputType,
"Input should be Float or UINT8", val_ctx);
}
bool ExpectIsFloatOrQuant8Operator(const TfLiteOpaqueContext* context,
const TfLiteOpaqueNode* node,
OpValidationContext* val_ctx) {
const auto input_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 0));
return Expect(IsFloat(input_type) || IsQuantized(input_type),
NeuronValidationFailureType::kUnsupportedInputType,
"Input should be Float or Quant8", val_ctx);
}
bool IsFloatQuantizedOrInt32(TfLiteType type) {
switch (type) {
case kTfLiteFloat32:
case kTfLiteUInt8:
case kTfLiteInt8:
case kTfLiteInt32:
return true;
default:
return false;
}
}
bool ExpectIsFloatQuant8OrInt32Operator(const TfLiteOpaqueContext* context,
const TfLiteOpaqueNode* node,
OpValidationContext* val_ctx) {
const auto input_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 0));
return Expect(IsFloatQuantizedOrInt32(input_type),
NeuronValidationFailureType::kUnsupportedInputType,
"Input should be Float, Quant8, or Int32", val_ctx);
}
// When using Neuron, the condition below must be true
// for quantized versions of the following ops:
// * CONV_2D
// * DEPTHWISE_CONV_2D
// * FULLY_CONNECTED (where filter actually stands for weights)
bool ExpectIsRestrictedScalesCompliant(const TfLiteOpaqueContext* context,
const TfLiteOpaqueNode* node,
OpValidationContext* val_ctx) {
const float input_scale = TfLiteOpaqueTensorGetQuantizationParams(
TfLiteOpaqueNodeGetInput(context, node, 0))
.scale;
const float filter_scale = TfLiteOpaqueTensorGetQuantizationParams(
TfLiteOpaqueNodeGetInput(context, node, 1))
.scale;
float output_scale =
TfLiteOpaqueTensorGetQuantizationParams(
TfLiteOpaqueNodeGetOutput(const_cast<TfLiteOpaqueContext*>(context),
node, 0))
.scale;
return Expect(
input_scale * filter_scale < output_scale,
NeuronValidationFailureType::kNotRestrictedScaleCompliant,
"When using Neuron, input_scale * filter_scale < output_scale:", val_ctx);
}
// Pack one node to NeuronModel_getSupportedOperations
bool ExpectIsSupportedOperationForOneNode(
const TfLiteRegistrationExternal* registration,
const TfLiteOpaqueNode* node, TfLiteOpaqueContext* context,
const NeuronApi* neuronapi, const NeuronDelegateOptions* options,
OpValidationContext* val_ctx) {
bool is_supported = false;
auto kernel_state =
std::make_unique<NeuronStableDelegateKernel>(neuronapi, *options);
if (kernel_state->GetSupportedOperationsForOneNode(
context, const_cast<TfLiteOpaqueNode*>(node),
const_cast<TfLiteRegistrationExternal*>(registration),
&is_supported) != kTfLiteOk) {
AddValidationFailure(NeuronValidationFailureType::kUnsupportedOperator,
"GetSupportedOperationsForOneNode returns error",
val_ctx);
return false;
}
return Expect(is_supported, NeuronValidationFailureType::kUnsupportedOperator,
"result of getSupportedOperations is false", val_ctx);
}
} // namespace
// In SPLIT_V, it is legal to specify -1 in size_splits representing an unknown
// split size taking as many values as possible. This function computes and
// returns the actual value of this unknown size, or returns -1 if all split
// sizes are known. The caller is responsible for making sure the size_splits
// and axis tensor are constants.
int ComputeSplitVUnknownSplitSize(const TfLiteOpaqueContext* context,
const TfLiteOpaqueNode* node) {
const auto& input = TfLiteOpaqueNodeGetInput(context, node, 0);
const auto& size_splits_tensor = TfLiteOpaqueNodeGetInput(context, node, 1);
const auto& axis_tensor = TfLiteOpaqueNodeGetInput(context, node, 2);
const auto* size_splits =
reinterpret_cast<int32_t*>(TfLiteOpaqueTensorData(size_splits_tensor));
int num_splits = TfLiteOpaqueTensorDim(size_splits_tensor, 0);
bool has_unknown_split_size = false;
int sum_of_known_split_sizes = 0;
for (int i = 0; i < num_splits; i++) {
if (size_splits[i] == -1) {
has_unknown_split_size = true;
} else {
sum_of_known_split_sizes += size_splits[i];
}
}
int axis = reinterpret_cast<int32_t*>(TfLiteOpaqueTensorData(axis_tensor))[0];
axis = axis < 0 ? axis + TfLiteOpaqueTensorNumDims(input) : axis;
int total_size = TfLiteOpaqueTensorDim(input, axis);
return has_unknown_split_size ? total_size - sum_of_known_split_sizes : -1;
}
TfLiteStatus PreGetSupportedForWholeGraph(TfLiteOpaqueContext* context,
const NeuronApi* neuronapi,
const NeuronDelegateOptions* options,
std::vector<int>* is_supported) {
TfLiteIntArray* execution_plan = nullptr;
if (TfLiteOpaqueContextGetExecutionPlan(context, &execution_plan) !=
kTfLiteOk) {
TFLITE_LOG_PROD(tflite::TFLITE_LOG_ERROR,
"PreOpCheck: Fail to get exexution plan");
return kTfLiteError;
}
int num_node = execution_plan->size;
TfLiteOpaqueNode* ref_node;
TfLiteRegistrationExternal* ref_reg;
std::vector<NeuronValidationFailure> failure;
// Check if all nodes pass the validate function
for (int i = 0; i < num_node; i++) {
if (TfLiteOpaqueContextGetNodeAndRegistration(context, i, &ref_node,
&ref_reg) != kTfLiteOk) {
TFLITE_LOG_PROD(tflite::TFLITE_LOG_ERROR,
"PreOpCheck: Fail to get Node and Registration.");
return kTfLiteError;
}
bool node_result =
Validate(ref_reg, ref_node, context, neuronapi, options, &failure);
if (!node_result) {
TFLITE_LOG_PROD(
tflite::TFLITE_LOG_ERROR,
"PreOpCheck: OP %s (v%d) is not supported (%s)",
tflite::EnumNameBuiltinOperator(static_cast<BuiltinOperator>(
TfLiteOperatorGetBuiltInCode(ref_reg))),
TfLiteRegistrationExternalGetVersion(ref_reg),
failure.size() > 0 ? failure[0].message.c_str() : "");
TFLITE_LOG_PROD(tflite::TFLITE_LOG_ERROR,
"PreOpCheck: Fail validation because of above op.");
return kTfLiteError;
}
}
// GetSupportedOperationsForWholeGraph
auto support_flags = std::make_unique<bool[]>(num_node);
auto kernel_state =
std::make_unique<NeuronStableDelegateKernel>(neuronapi, *options);
if (kernel_state->GetSupportedOperationsForWholeGraph(
context, support_flags.get()) != kTfLiteOk) {
TFLITE_LOG_PROD(
tflite::TFLITE_LOG_ERROR,
"PreOpCheck: GetSupportedOperationsForWholeGraph returns error");
return kTfLiteError;
}
is_supported->clear();
for (int i = 0; i < num_node; i++) {
TFLITE_LOG_PROD(tflite::TFLITE_LOG_VERBOSE,
"PreOpCheck: support_flags[%d] = %d", i, support_flags[i]);
is_supported->push_back(support_flags[i] ? 1 : 0);
}
TFLITE_LOG_PROD(tflite::TFLITE_LOG_VERBOSE, "PreOpCheck: succesfully");
return kTfLiteOk;
}
bool Validate(const TfLiteRegistrationExternal* registration,
const TfLiteOpaqueNode* node, TfLiteOpaqueContext* context,
const NeuronApi* neuronapi, const NeuronDelegateOptions* options,
std::vector<NeuronValidationFailure>* map_failures) {
OpValidationContext val_ctx{true, map_failures};
const auto builtin_code = TfLiteOperatorGetBuiltInCode(registration);
const auto version = TfLiteRegistrationExternalGetVersion(registration);
switch (builtin_code) {
case kTfLiteBuiltinAdd: {
ExpectMaxOpVersion(version, 2, &val_ctx);
ExpectIsFloatOrQuant8Operator(context, node, &val_ctx);
} break;
case kTfLiteBuiltinArgMax:
case kTfLiteBuiltinArgMin: {
ExpectMaxOpVersion(version, 2, &val_ctx);
const TfLiteType input_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 0));
const TfLiteType axis_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 1));
EXPECT_INPUT_TYPE_IN(input_type, kTfLiteFloat16, kTfLiteFloat32,
kTfLiteUInt8, kTfLiteInt8);
Expect(axis_type == kTfLiteInt32,
NeuronValidationFailureType::kUnsupportedOutputType,
"Neuron only supports int32 axis.", &val_ctx);
if (builtin_code == kTfLiteBuiltinArgMax) {
auto builtin = reinterpret_cast<TfLiteArgMaxParams*>(
TfLiteOpaqueNodeGetBuiltinData(node));
Expect(builtin->output_type == kTfLiteInt32,
NeuronValidationFailureType::kUnsupportedOutputType,
"Neuron only supports int32 output.", &val_ctx);
} else {
auto builtin = reinterpret_cast<TfLiteArgMinParams*>(
TfLiteOpaqueNodeGetBuiltinData(node));
Expect(builtin->output_type == kTfLiteInt32,
NeuronValidationFailureType::kUnsupportedOutputType,
"Neuron only supports int32 output.", &val_ctx);
}
} break;
case kTfLiteBuiltinMul: {
ExpectMaxOpVersion(version, 2, &val_ctx);
ExpectIsFloatOrQuant8Operator(context, node, &val_ctx);
} break;
case kTfLiteBuiltinAveragePool2d: {
ExpectMaxOpVersion(version, 2, &val_ctx);
ExpectIsFloatOrQuant8Operator(context, node, &val_ctx);
} break;
case kTfLiteBuiltinMaxPool2d: {
ExpectMaxOpVersion(version, 2, &val_ctx);
ExpectIsFloatOrQuant8Operator(context, node, &val_ctx);
} break;
case kTfLiteBuiltinL2Pool2d: {
ExpectOpVersion(version, 1, &val_ctx);
ExpectIsFloatOrQuant8Operator(context, node, &val_ctx);
} break;
case kTfLiteBuiltinConv2d: {
ExpectMaxOpVersion(version, 3, &val_ctx);
const TfLiteType input_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 0));
const TfLiteType filter_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 1));
if (input_type == kTfLiteUInt8) {
ExpectIsRestrictedScalesCompliant(context, node, &val_ctx);
}
Expect(filter_type != kTfLiteInt4,
NeuronValidationFailureType::kUnsupportedOutputType,
"Unsupported filter of type kTfLiteInt4", &val_ctx);
// TODO(Code): Add support for Conv2D with omitted bias.
Expect(TfLiteOpaqueNodeNumberOfInputs(node) == 3,
NeuronValidationFailureType::kMissingRequiredOperand,
"Conv2D with omitted bias not supported", &val_ctx);
} break;
case kTfLiteBuiltinDepthwiseConv2d: {
ExpectMaxOpVersion(version, 3, &val_ctx);
ExpectIsFloatOrQuant8Operator(context, node, &val_ctx);
const TfLiteType input_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 0));
const TfLiteType filter_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 1));
if (input_type == kTfLiteUInt8) {
ExpectIsRestrictedScalesCompliant(context, node, &val_ctx);
}
Expect(filter_type != kTfLiteInt4,
NeuronValidationFailureType::kUnsupportedOutputType,
"Unsupported filter of type kTfLiteInt4", &val_ctx);
} break;
case kTfLiteBuiltinFullyConnected: {
ExpectMaxOpVersion(version, 5, &val_ctx);
const TfLiteType input_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 0));
const TfLiteType filter_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 1));
if (input_type == kTfLiteUInt8) {
ExpectIsRestrictedScalesCompliant(context, node, &val_ctx);
}
Expect(filter_type != kTfLiteInt4,
NeuronValidationFailureType::kUnsupportedOutputType,
"Unsupported filter of type kTfLiteInt4", &val_ctx);
const int* inputs;
int num_inputs;
TF_LITE_OPAQUE_ENSURE_STATUS(
TfLiteOpaqueNodeInputs(node, &inputs, &num_inputs));
const bool is_bias_present = TfLiteOpaqueNodeNumberOfInputs(node) == 3 &&
inputs[2] != kTfLiteOptionalTensor;
if (is_bias_present) {
const TfLiteType bias_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 2));
Expect(bias_type != kTfLiteInt64,
NeuronValidationFailureType::kUnsupportedOutputType,
"Unsupported bias of type kTfLiteInt64", &val_ctx);
}
} break;
case kTfLiteBuiltinHardSwish: {
ExpectIsFloatOrQuant8Operator(context, node, &val_ctx);
} break;
case kTfLiteBuiltinSoftmax: {
ExpectOpVersion(version, 2, &val_ctx);
ExpectIsFloatOrQuant8Operator(context, node, &val_ctx);
const auto& output = TfLiteOpaqueNodeGetOutput(context, node, 0);
ExpectTypeIn(TfLiteOpaqueTensorType(output), {kTfLiteFloat32},
NeuronValidationFailureType::kUnsupportedOutputType,
"Output type should be one of kTfLiteFloat32.", &val_ctx);
const auto& input = TfLiteOpaqueNodeGetInput(context, node, 0);
const int input_rank = TfLiteOpaqueTensorNumDims(input);
Expect(input_rank <= 4,
NeuronValidationFailureType::kUnsupportedOperandRank,
"Input rank should be <= 4", &val_ctx);
} break;
case kTfLiteBuiltinReshape: {
ExpectOpVersion(version, 1, &val_ctx);
ExpectIsFloatQuant8OrInt32Operator(context, node, &val_ctx);
if (TfLiteOpaqueNodeNumberOfInputs(node) >= 2) {
Expect(TfLiteOpaqueTensorGetAllocationType(
TfLiteOpaqueNodeGetInput(context, node, 1)) == kTfLiteMmapRo,
NeuronValidationFailureType::kInputTensorShouldHaveConstantShape,
"The shape input tensor must be constant.", &val_ctx);
}
if (TfLiteOpaqueNodeNumberOfInputs(node) == 1) {
// reject scalar reshaping
auto* params = reinterpret_cast<TfLiteReshapeParams*>(
TfLiteOpaqueNodeGetBuiltinData(node));
int num_dimensions = params->num_dimensions;
if (num_dimensions == 1 && params->shape[0] == 0) {
// Legacy tflite models use a shape parameter of [0] to indicate
// scalars.
num_dimensions = 0;
}
Expect(num_dimensions > 0,
NeuronValidationFailureType::kUnsupportedOperandRank,
"New shape rank should be > 0", &val_ctx);
}
} break;
case kTfLiteBuiltinResizeBilinear: {
ExpectMaxOpVersion(version, 3, &val_ctx);
const auto& input = TfLiteOpaqueNodeGetInput(context, node, 0);
Expect(TfLiteOpaqueTensorNumDims(input) == 4,
NeuronValidationFailureType::kUnsupportedOperandRank,
"Input should have rank 4", &val_ctx);
Expect(TfLiteOpaqueNodeNumberOfInputs(node) >= 2,
NeuronValidationFailureType::kUnsupportedOperatorVariant,
"Expected at least 2 inputs", &val_ctx);
if (TfLiteOpaqueNodeNumberOfInputs(node) >= 2) {
Expect(TfLiteOpaqueTensorGetAllocationType(
TfLiteOpaqueNodeGetInput(context, node, 1)) == kTfLiteMmapRo,
NeuronValidationFailureType::kInputTensorShouldHaveConstantShape,
"The size input tensor must be constant.", &val_ctx);
}
} break;
case kTfLiteBuiltinResizeNearestNeighbor: {
ExpectMaxOpVersion(version, 3, &val_ctx);
} break;
case kTfLiteBuiltinSqueeze: {
ExpectOpVersion(version, 1, &val_ctx);
} break;
case kTfLiteBuiltinL2Normalization: {
ExpectMaxOpVersion(version, 2, &val_ctx);
auto builtin = reinterpret_cast<TfLiteL2NormParams*>(
TfLiteOpaqueNodeGetBuiltinData(node));
Expect(builtin->activation == kTfLiteActNone,
NeuronValidationFailureType::kNoActivationExpected,
"Expected no activation", &val_ctx);
} break;
case kTfLiteBuiltinConcatenation: {
ExpectMaxOpVersion(version, 2, &val_ctx);
Expect(reinterpret_cast<TfLiteConcatenationParams*>(
TfLiteOpaqueNodeGetBuiltinData(node))
->activation == kTfLiteActNone,
NeuronValidationFailureType::kNoActivationExpected,
"No activation function supported", &val_ctx);
Expect(TfLiteOpaqueTensorNumDims(
TfLiteOpaqueNodeGetInput(context, node, 0)) <= 4,
NeuronValidationFailureType::kUnsupportedOperandRank,
"Input rank should be less than 4", &val_ctx);
const auto& input_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 0));
EXPECT_INPUT_TYPE_IN(input_type, kTfLiteFloat16, kTfLiteFloat32,
kTfLiteUInt8, kTfLiteInt8);
} break;
case kTfLiteBuiltinDequantize: {
// Allow dequantizing fp16->fp32.
const auto& input = TfLiteOpaqueNodeGetInput(context, node, 0);
if (TfLiteOpaqueTensorType(input) == kTfLiteFloat16 &&
TfLiteOpaqueTensorGetAllocationType(input) == kTfLiteMmapRo) {
return true;
}
ExpectOpVersion(version, 2, &val_ctx);
EXPECT_INPUT_TYPE_IN(TfLiteOpaqueTensorType(input), kTfLiteUInt8,
kTfLiteInt8);
} break;
case kTfLiteBuiltinFloor: {
ExpectOpVersion(version, 1, &val_ctx);
} break;
case kTfLiteBuiltinRelu:
case kTfLiteBuiltinReluN1To1:
case kTfLiteBuiltinRelu6:
case kTfLiteBuiltinLogistic:
case kTfLiteBuiltinTanh: {
ExpectMaxOpVersion(version, 2, &val_ctx);
ExpectIsFloatOrQuant8Operator(context, node, &val_ctx);
} break;
case kTfLiteBuiltinSub: {
ExpectMaxOpVersion(version, 3, &val_ctx);
const int input0_rank =
TfLiteOpaqueTensorNumDims(TfLiteOpaqueNodeGetInput(context, node, 0));
const int input1_rank =
TfLiteOpaqueTensorNumDims(TfLiteOpaqueNodeGetInput(context, node, 1));
Expect(input0_rank <= 4 && input1_rank <= 4,
NeuronValidationFailureType::kUnsupportedOperandRank,
"Input rank must be <= 4", &val_ctx);
} break;
case kTfLiteBuiltinDiv: {
ExpectOpVersion(version, 1, &val_ctx);
} break;
case kTfLiteBuiltinPad:
case kTfLiteBuiltinPadv2: {
ExpectMaxOpVersion(version, 2, &val_ctx);
ExpectIsFloatOrQuant8Operator(context, node, &val_ctx);
const TfLiteOpaqueTensor* input_tensor =
TfLiteOpaqueNodeGetInput(context, node, 0);
for (int i = 0; i < TfLiteOpaqueTensorNumDims(input_tensor); i++) {
Expect(TfLiteOpaqueTensorDim(input_tensor, i) != 0,
NeuronValidationFailureType::kUnsupportedOperandValue,
"Neuron pad ops do not support input tensors with no elements",
&val_ctx);
}
Expect(TfLiteOpaqueNodeNumberOfInputs(node) >= 2,
NeuronValidationFailureType::kUnsupportedOperatorVariant,
"Expecting at least 2 inputs", &val_ctx);
} break;
case kTfLiteBuiltinSpaceToBatchNd: {
ExpectMaxOpVersion(version, 2, &val_ctx);
} break;
case kTfLiteBuiltinBatchToSpaceNd: {
ExpectMaxOpVersion(version, 2, &val_ctx);
auto crops = TfLiteOpaqueNodeGetInput(context, node, 2);
auto crops_data =
reinterpret_cast<int32_t*>(TfLiteOpaqueTensorData(crops));
Expect(crops_data && TfLiteOpaqueTensorByteSize(crops) == 16 &&
crops_data[0] == 0 && crops_data[1] == 0 &&
crops_data[2] == 0 && crops_data[3] == 0,
NeuronValidationFailureType::kUnsupportedOperandValue,
"All crops should be 0.", &val_ctx);
} break;
case kTfLiteBuiltinStridedSlice: {
ExpectMaxOpVersion(version, 2, &val_ctx);
} break;
case kTfLiteBuiltinTranspose: {
ExpectMaxOpVersion(version, 2, &val_ctx);
// Note that the permutation input tensor value dictates the output
// dimensions.
// TODO(Code): Support dynamically-sized tensors in delegates.
Expect((TfLiteOpaqueNodeNumberOfInputs(node) > 1) &&
(TfLiteOpaqueTensorGetAllocationType(TfLiteOpaqueNodeGetInput(
context, node, 1)) == kTfLiteMmapRo),
NeuronValidationFailureType::kInputTensorShouldHaveConstantShape,
"Dynamically-sized tensors not supported.", &val_ctx);
} break;
case kTfLiteBuiltinAbs:
case kTfLiteBuiltinExp:
case kTfLiteBuiltinLog:
case kTfLiteBuiltinPow: {
ExpectOpVersion(version, 1, &val_ctx);
const auto input_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 0));
EXPECT_INPUT_TYPE_IN(input_type, kTfLiteFloat16, kTfLiteFloat32);
} break;
case kTfLiteBuiltinSlice: {
ExpectMaxOpVersion(version, 2, &val_ctx);
const auto input_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 0));
const auto begin_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 1));
const auto size_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 2));
EXPECT_INPUT_TYPE_IN(input_type, kTfLiteFloat32, kTfLiteInt32,
kTfLiteUInt8, kTfLiteInt8);
Expect(begin_type == kTfLiteInt32,
NeuronValidationFailureType::kUnsupportedInputType,
"Begin type should be Int32", &val_ctx);
Expect(size_type == kTfLiteInt32,
NeuronValidationFailureType::kUnsupportedInputType,
"Size type should be Int32", &val_ctx);
} break;
case kTfLiteBuiltinTransposeConv: {
ExpectMaxOpVersion(version, 3, &val_ctx);
Expect((TfLiteOpaqueNodeNumberOfInputs(node) > 1) &&
(TfLiteOpaqueTensorGetAllocationType(TfLiteOpaqueNodeGetInput(
context, node, 0)) == kTfLiteMmapRo) &&
(TfLiteOpaqueTensorGetAllocationType(TfLiteOpaqueNodeGetInput(
context, node, 1)) == kTfLiteMmapRo),
NeuronValidationFailureType::kInputTensorShouldHaveConstantShape,
"Dynamically-sized tensors not supported.", &val_ctx);
} break;
case kTfLiteBuiltinSqrt: {
ExpectOpVersion(version, 1, &val_ctx);
} break;
case kTfLiteBuiltinSpaceToDepth: {
ExpectMaxOpVersion(version, 2, &val_ctx);
ExpectIsFloatOrQuant8Operator(context, node, &val_ctx);
} break;
case kTfLiteBuiltinMean: {
ExpectMaxOpVersion(version, 2, &val_ctx);
Expect(TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(
context, node, 0)) == kTfLiteFloat32 ||
IsQuantized(TfLiteOpaqueTensorType(
TfLiteOpaqueNodeGetInput(context, node, 0))),
NeuronValidationFailureType::kUnsupportedInputType,
"Expected Float32 or Quantized input", &val_ctx);
} break;
case kTfLiteBuiltinEmbeddingLookup: {
ExpectOpVersion(version, 1, &val_ctx);
} break;
case kTfLiteBuiltinHashtableLookup: {
ExpectOpVersion(version, 1, &val_ctx);
} break;
case kTfLiteBuiltinMaximum:
case kTfLiteBuiltinMinimum: {
ExpectMaxOpVersion(version, 3, &val_ctx);
const auto input_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 0));
EXPECT_INPUT_TYPE_IN(input_type, kTfLiteFloat32, kTfLiteUInt8,
kTfLiteInt8, kTfLiteInt32);
const TfLiteOpaqueTensor* operand0 =
TfLiteOpaqueNodeGetInput(context, node, 0);
if (TfLiteOpaqueTensorNumDims(operand0) == 0) {
Expect(TfLiteOpaqueTensorGetAllocationType(operand0) == kTfLiteMmapRo,
NeuronValidationFailureType::kUnsupportedInputType,
"Scalar operand should be constant", &val_ctx);
}
const TfLiteOpaqueTensor* operand1 =
TfLiteOpaqueNodeGetInput(context, node, 1);
if (TfLiteOpaqueTensorNumDims(operand1) == 0) {
Expect(TfLiteOpaqueTensorGetAllocationType(operand1) == kTfLiteMmapRo,
NeuronValidationFailureType::kUnsupportedInputType,
"Scalar operand should be constant", &val_ctx);
}
} break;
case kTfLiteBuiltinCast: {
ExpectOpVersion(version, 1, &val_ctx);
const TfLiteType input_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 0));
const TfLiteType output_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetOutput(context, node, 0));
EXPECT_INPUT_TYPE_IN(input_type, kTfLiteInt32, kTfLiteUInt8, kTfLiteInt8);
ExpectTypeIn(output_type, {kTfLiteInt32, kTfLiteUInt8, kTfLiteInt8},
NeuronValidationFailureType::kUnsupportedOutputType,
"Output type should be one of kTfLiteInt32, "
"kTfLiteUInt8, kTfLiteInt8.",
&val_ctx);
} break;
case kTfLiteBuiltinLeakyRelu:
case kTfLiteBuiltinPrelu: {
ExpectOpVersion(version, 1, &val_ctx);
const auto input_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 0));
EXPECT_INPUT_TYPE_IN(input_type, kTfLiteFloat32, kTfLiteUInt8,
kTfLiteInt8);
} break;
case kTfLiteBuiltinLogicalOr:
case kTfLiteBuiltinLogicalAnd:
case kTfLiteBuiltinLogicalNot: {
ExpectOpVersion(version, 1, &val_ctx);
const auto input_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 0));
Expect(input_type == kTfLiteBool,
NeuronValidationFailureType::kUnsupportedInputType,
"Input should be bool", &val_ctx);
} break;
case kTfLiteBuiltinLess:
case kTfLiteBuiltinLessEqual:
case kTfLiteBuiltinGreater:
case kTfLiteBuiltinGreaterEqual:
case kTfLiteBuiltinEqual:
case kTfLiteBuiltinNotEqual: {
ExpectMaxOpVersion(version, 2, &val_ctx);
const auto input_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 0));
EXPECT_INPUT_TYPE_IN(input_type, kTfLiteFloat32, kTfLiteUInt8,
kTfLiteInt8, kTfLiteBool, kTfLiteInt32);
} break;
case kTfLiteBuiltinNeg: {
ExpectMaxOpVersion(version, 2, &val_ctx);
const auto input_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 0));
EXPECT_INPUT_TYPE_IN(input_type, kTfLiteFloat32, kTfLiteInt32);
} break;
case kTfLiteBuiltinTopkV2: {
ExpectMaxOpVersion(version, 2, &val_ctx);
const auto& input_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 0));
EXPECT_INPUT_TYPE_IN(input_type, kTfLiteFloat32, kTfLiteInt32,
kTfLiteUInt8, kTfLiteInt8);
const auto& k_param = TfLiteOpaqueNodeGetInput(context, node, 1);
Expect(TfLiteOpaqueTensorType(k_param) == kTfLiteInt32 &&
TfLiteOpaqueTensorGetAllocationType(k_param) == kTfLiteMmapRo,
NeuronValidationFailureType::kUnsupportedInputType,
"K param should be a constant of type Int32", &val_ctx);
} break;
case kTfLiteBuiltinSelect: {
ExpectMaxOpVersion(version, 2, &val_ctx);
const auto value_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 1));
EXPECT_INPUT_TYPE_IN(value_type, kTfLiteFloat32, kTfLiteInt32,
kTfLiteUInt8, kTfLiteInt8);
const TfLiteOpaqueTensor* condition_tensor =
TfLiteOpaqueNodeGetInput(context, node, 0);
const TfLiteOpaqueTensor* input_tensor =
TfLiteOpaqueNodeGetInput(context, node, 1);
bool same_num_of_dim = TfLiteOpaqueTensorNumDims(condition_tensor) ==
TfLiteOpaqueTensorNumDims(input_tensor);
Expect(same_num_of_dim,
NeuronValidationFailureType::kUnsupportedOperandValue,
"Condition and inputs tensors shuld have the number of dims",
&val_ctx);
if (same_num_of_dim) {
for (int i = 0; i < TfLiteOpaqueTensorNumDims(condition_tensor); i++) {
Expect(TfLiteOpaqueTensorDim(condition_tensor, i) ==
TfLiteOpaqueTensorDim(input_tensor, i),
NeuronValidationFailureType::kUnsupportedOperandValue,
"Condition and inputs tensors shuld have the same shape",
&val_ctx);
}
}
} break;
case kTfLiteBuiltinGather: {
ExpectOpVersion(version, 2, &val_ctx);
const auto input_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 0));
const auto& positions = TfLiteOpaqueNodeGetInput(context, node, 1);
// TODO: neuropilot workaround for gather(remove float32 support)
EXPECT_INPUT_TYPE_IN(input_type, kTfLiteFloat16, kTfLiteInt32,
kTfLiteUInt8, kTfLiteInt8);
Expect(TfLiteOpaqueTensorType(positions) == kTfLiteInt32,
NeuronValidationFailureType::kUnsupportedInputType,
"Positions type should be one of kTfLiteInt32", &val_ctx);
Expect(TfLiteOpaqueTensorNumDims(positions) != 0,
NeuronValidationFailureType::kUnsupportedOperandRank,
"0-dimension args are not supported by Neuron.", &val_ctx);
} break;
case kTfLiteBuiltinSplit: {
ExpectOpVersion(version, 3, &val_ctx);
const TfLiteOpaqueTensor* input =
TfLiteOpaqueNodeGetInput(context, node, 1);
EXPECT_INPUT_TYPE_IN(TfLiteOpaqueTensorType(input), kTfLiteFloat32,
kTfLiteUInt8, kTfLiteInt8, kTfLiteInt32);
const TfLiteOpaqueTensor* axis =
TfLiteOpaqueNodeGetInput(context, node, 0);
Expect(TfLiteOpaqueTensorType(axis) == kTfLiteInt32 &&
TfLiteOpaqueTensorGetAllocationType(axis) == kTfLiteMmapRo,
NeuronValidationFailureType::kUnsupportedInputType,
"Neuron only supports constant int32 axis tensor.", &val_ctx);
} break;
case kTfLiteBuiltinSplitV: {
ExpectOpVersion(version, 2, &val_ctx);
// Tensor indices: value: 0, size_splits: 1, axis: 2
const TfLiteOpaqueTensor* input =
TfLiteOpaqueNodeGetInput(context, node, 0);
const TfLiteOpaqueTensor* size_splits =
TfLiteOpaqueNodeGetInput(context, node, 1);
const TfLiteOpaqueTensor* axis =
TfLiteOpaqueNodeGetInput(context, node, 2);
EXPECT_INPUT_TYPE_IN(TfLiteOpaqueTensorType(input), kTfLiteFloat32,
kTfLiteUInt8, kTfLiteInt8, kTfLiteInt32);
bool size_splits_is_int32_const_vector =
TfLiteOpaqueTensorType(size_splits) == kTfLiteInt32 &&
TfLiteOpaqueTensorNumDims(size_splits) == 1 &&
TfLiteOpaqueTensorGetAllocationType(size_splits) == kTfLiteMmapRo;
bool axis_is_int32_const =
TfLiteOpaqueTensorType(axis) == kTfLiteInt32 &&
TfLiteOpaqueTensorGetAllocationType(axis) == kTfLiteMmapRo;
Expect(size_splits_is_int32_const_vector,
NeuronValidationFailureType::kUnsupportedInputType,
"Neuron only supports constant int32 size_splits vector.",
&val_ctx);
Expect(axis_is_int32_const,
NeuronValidationFailureType::kUnsupportedInputType,
"Neuron only supports constant int32 axis tensor.", &val_ctx);
if (size_splits_is_int32_const_vector && axis_is_int32_const) {
int32_t* size_splits_data =
reinterpret_cast<int32_t*>(TfLiteOpaqueTensorData(size_splits));
Expect(std::all_of(
size_splits_data,
size_splits_data + TfLiteOpaqueTensorDim(size_splits, 0),
[](auto size) { return size != 0; }),
NeuronValidationFailureType::kUnsupportedInputType,
"Neuron only supports non-zero split sizes.", &val_ctx);
#if 1
Expect(ComputeSplitVUnknownSplitSize(context, node) != 0,
NeuronValidationFailureType::kUnsupportedInputType,
"Neuron only supports non-zero split sizes.", &val_ctx);
#endif
}
} break;
case kTfLiteBuiltinQuantize: {
ExpectOpVersion(version, 2, &val_ctx);
const auto value_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 0));
Expect(value_type == kTfLiteFloat32 || value_type == kTfLiteInt16 ||
IsQuantized(value_type),
NeuronValidationFailureType::kUnsupportedInputType,
"Value should be quantized or Float32.", &val_ctx);
if (value_type == kTfLiteInt16 || IsQuantized(value_type)) {
const auto quantization_params =
TfLiteOpaqueTensorGetQuantizationParams(
TfLiteOpaqueNodeGetInput(context, node, 0));
Expect(quantization_params.scale > 0.f,
NeuronValidationFailureType::kUnsupportedQuantizationParameters,
"Input quantization scale should be > 0.", &val_ctx);
}
const auto output_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetOutput(context, node, 0));
ExpectTypeIn(
output_type, {kTfLiteInt16, kTfLiteUInt8, kTfLiteInt8},
NeuronValidationFailureType::kUnsupportedOutputType,
"Output should be kTfLiteInt16 or kTfLiteUInt8 or kTfLiteInt8.",
&val_ctx);
const auto quantization_params = TfLiteOpaqueTensorGetQuantizationParams(
TfLiteOpaqueNodeGetOutput(context, node, 0));
Expect(quantization_params.scale > 0.f,
NeuronValidationFailureType::kUnsupportedQuantizationParameters,
"Output quantization scale should be > 0.", &val_ctx);
} break;
case kTfLiteBuiltinReduceAny:
case kTfLiteBuiltinReduceMin:
case kTfLiteBuiltinReduceMax: {
ExpectOpVersion(version, 2, &val_ctx);
const TfLiteOpaqueTensor* axis_tensor =
TfLiteOpaqueNodeGetInput(context, node, 1);
if (TfLiteOpaqueTensorNumDims(axis_tensor) == 0) {
Expect(TfLiteOpaqueTensorNumDims(axis_tensor) > 0,
NeuronValidationFailureType::kUnsupportedOperator,
"axis dimension size should be > 0.", &val_ctx);
}
} break;
case kTfLiteBuiltinDepthToSpace: {
const TfLiteType input_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 0));
EXPECT_INPUT_TYPE_IN(input_type, kTfLiteFloat32, kTfLiteUInt8,
kTfLiteInt8);
} break;
case kTfLiteBuiltinSum: {
ExpectOpVersion(version, 1, &val_ctx);
const TfLiteOpaqueTensor* axis_tensor =
TfLiteOpaqueNodeGetInput(context, node, 1);
if (TfLiteOpaqueTensorNumDims(axis_tensor) == 0) {
Expect(TfLiteOpaqueTensorNumDims(axis_tensor) > 0,
NeuronValidationFailureType::kUnsupportedOperator,
"axis dimension size should be > 0.", &val_ctx);
}
} break;
case kTfLiteBuiltinPack: {
ExpectOpVersion(version, 2, &val_ctx);
const auto input_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 0));
EXPECT_INPUT_TYPE_IN(input_type, kTfLiteInt32, kTfLiteFloat32,
kTfLiteInt8);
auto builtin = reinterpret_cast<TfLitePackParams*>(
TfLiteOpaqueNodeGetBuiltinData(node));
Expect(
builtin->axis != -1 &&
builtin->axis != TfLiteOpaqueTensorNumDims(
TfLiteOpaqueNodeGetInput(context, node, 0)),
NeuronValidationFailureType::kUnsupportedOperandValue,
"Neuron does not support axis being the last dimension", &val_ctx);
} break;
case kTfLiteBuiltinSquaredDifference:
case kTfLiteBuiltinRsqrt:
case kTfLiteBuiltinMirrorPad:
case kTfLiteBuiltinUnpack: {
// Use BuiltinOP as MTK EXT OP
} break;
case kTfLiteBuiltinReverseV2: {
// Use BuiltinOP as MTK EXT OP
const TfLiteType input_type =
TfLiteOpaqueTensorType(TfLiteOpaqueNodeGetInput(context, node, 0));
Expect(input_type != kTfLiteInt64,
NeuronValidationFailureType::kUnsupportedOutputType,
"Unsupported filter of type kTfLiteInt64", &val_ctx);
} break;
#ifndef MTK_INTERNAL_USE
case kTfLiteBuiltinCustom: {
if (strcmp(TfLiteRegistrationExternalGetCustomName(registration),
"cros-mtk-pre-compile") == 0) {
break;
}
int32_t magic_number = -1;
// get NP magic number
if (neuronapi->Neuron_getNeuroPilotMagicNumber != nullptr) {
RETURN_TFLITE_ERROR_IF_NEURON_ERROR(
context, neuronapi->Neuron_getNeuroPilotMagicNumber(&magic_number),
"get magic number");
}
if (strcmp(TfLiteRegistrationExternalGetCustomName(registration),
"TFLite_Detection_PostProcess") == 0 &&
magic_number >= kMinMagicNumberForNeuron33) {
ExpectIsSupportedOperationForOneNode(registration, node, context,
neuronapi, options, &val_ctx);
break;
}
} // no break, pass to default
#endif
default:
// All other operators are not mapped.
TFLITE_LOG_PROD(tflite::TFLITE_LOG_WARNING,
"Unsupported operation type %d", builtin_code);
AddValidationFailure(NeuronValidationFailureType::kUnsupportedOperator,
"Unsupported operation type.", &val_ctx);
}
if (val_ctx.is_valid) {
if (options->operation_check_mode == kPerNodeOperationCheck) {
ExpectIsSupportedOperationForOneNode(registration, node, context,
neuronapi, options, &val_ctx);
}
}
return val_ctx.is_valid;
}
} // namespace neuron
} // namespace tflite