252 lines
9.8 KiB
C++
252 lines
9.8 KiB
C++
|
|
/*
|
||
|
|
* Copyright (C) 2022 The Android Open Source Project
|
||
|
|
*
|
||
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
|
* you may not use this file except in compliance with the License.
|
||
|
|
* You may obtain a copy of the License at
|
||
|
|
*
|
||
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
*
|
||
|
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
|
* See the License for the specific language governing permissions and
|
||
|
|
* limitations under the License.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <android-base/logging.h>
|
||
|
|
#include <android-base/properties.h>
|
||
|
|
#include <ftw.h>
|
||
|
|
#include <gtest/gtest.h>
|
||
|
|
#include <unistd.h>
|
||
|
|
|
||
|
|
#include <algorithm>
|
||
|
|
#include <cassert>
|
||
|
|
#include <cmath>
|
||
|
|
#include <fstream>
|
||
|
|
#include <iostream>
|
||
|
|
#include <limits>
|
||
|
|
#include <map>
|
||
|
|
#include <memory>
|
||
|
|
#include <set>
|
||
|
|
#include <string>
|
||
|
|
#include <thread>
|
||
|
|
#include <utility>
|
||
|
|
#include <vector>
|
||
|
|
|
||
|
|
#include "AndroidVersionUtil.h"
|
||
|
|
#include "GeneratedTestUtils.h"
|
||
|
|
#include "NeuralNetworks.h"
|
||
|
|
#include "NeuralNetworksTypes.h"
|
||
|
|
#include "TestHarness.h"
|
||
|
|
#include "TestNeuralNetworksWrapper.h"
|
||
|
|
#include "TestUtils.h"
|
||
|
|
|
||
|
|
#pragma clang diagnostic push
|
||
|
|
#pragma clang diagnostic ignored "-Wunused-parameter"
|
||
|
|
#include "tensorflow/lite/interpreter.h"
|
||
|
|
#include "tensorflow/lite/kernels/register.h"
|
||
|
|
#include "tensorflow/lite/model.h"
|
||
|
|
#pragma clang diagnostic pop
|
||
|
|
|
||
|
|
#ifdef NNTEST_CTS
|
||
|
|
#define NNTEST_COMPUTE_MODE
|
||
|
|
#endif
|
||
|
|
|
||
|
|
namespace android::nn::generated_tests {
|
||
|
|
using namespace test_wrapper;
|
||
|
|
using namespace test_helper;
|
||
|
|
|
||
|
|
class CompatibilityLayerGeneratedTests : public GeneratedTestBase {
|
||
|
|
protected:
|
||
|
|
void SetUp() override;
|
||
|
|
void TearDown() override;
|
||
|
|
|
||
|
|
// Test driver for those generated from packages/modules/NeuralNetworks/runtime/test/specs
|
||
|
|
void execute(const TestModel& testModel);
|
||
|
|
|
||
|
|
bool mTestDynamicOutputShape = false;
|
||
|
|
bool mTestSupported = true;
|
||
|
|
};
|
||
|
|
|
||
|
|
class CompatibilityLayerGeneratedTestsSupported : public CompatibilityLayerGeneratedTests {};
|
||
|
|
class CompatibilityLayerGeneratedTestsUnsupported : public CompatibilityLayerGeneratedTests {};
|
||
|
|
class CompatibilityLayerGeneratedTestsDynamicOutput : public CompatibilityLayerGeneratedTests {};
|
||
|
|
|
||
|
|
void CompatibilityLayerGeneratedTests::execute(const TestModel& testModel) {
|
||
|
|
GeneratedModel model;
|
||
|
|
createModel(testModel, mTestDynamicOutputShape, &model);
|
||
|
|
if (testModel.expectFailure && !model.isValid()) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
ASSERT_EQ(model.finish(), Result::NO_ERROR);
|
||
|
|
ASSERT_TRUE(model.isValid());
|
||
|
|
|
||
|
|
Compilation compilation(&model);
|
||
|
|
Result result = compilation.finish();
|
||
|
|
if (!mTestSupported && result != Result::NO_ERROR) return;
|
||
|
|
ASSERT_EQ(result, Result::NO_ERROR);
|
||
|
|
|
||
|
|
Execution execution(&compilation);
|
||
|
|
|
||
|
|
// Model inputs.
|
||
|
|
for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) {
|
||
|
|
const auto& operand = testModel.main.operands[testModel.main.inputIndexes[i]];
|
||
|
|
ASSERT_EQ(Result::NO_ERROR,
|
||
|
|
execution.setInput(i, operand.data.get<void>(), operand.data.size()));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Model outputs.
|
||
|
|
std::vector<TestBuffer> outputs;
|
||
|
|
for (uint32_t i = 0; i < testModel.main.outputIndexes.size(); i++) {
|
||
|
|
const auto& operand = testModel.main.operands[testModel.main.outputIndexes[i]];
|
||
|
|
const size_t bufferSize = std::max<size_t>(operand.data.size(), 1);
|
||
|
|
outputs.emplace_back(bufferSize);
|
||
|
|
|
||
|
|
ASSERT_EQ(Result::NO_ERROR,
|
||
|
|
execution.setOutput(i, outputs.back().getMutable<void>(), bufferSize));
|
||
|
|
}
|
||
|
|
|
||
|
|
result = execution.compute(Execution::ComputeMode::SYNC);
|
||
|
|
ASSERT_EQ(result, Result::NO_ERROR);
|
||
|
|
|
||
|
|
// If a conv filter under/overflows, "compatibleTest" will report
|
||
|
|
// unsupported, but the actual conversion will result in NO_ERROR because
|
||
|
|
// it is treated as a warning, rather than an error. Because of the accuracy
|
||
|
|
// loss, we should not check test results in such a case.
|
||
|
|
//
|
||
|
|
// TODO(b/237410741): A potentially better approach is to have
|
||
|
|
// "compatibleTest" report three status: fully supported, supported with
|
||
|
|
// accuracy loss, and not supported.
|
||
|
|
if (mTestSupported) {
|
||
|
|
checkResults(testModel, outputs);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void CompatibilityLayerGeneratedTests::SetUp() {
|
||
|
|
GeneratedTestBase::SetUp();
|
||
|
|
}
|
||
|
|
|
||
|
|
void CompatibilityLayerGeneratedTests::TearDown() {
|
||
|
|
GeneratedTestBase::TearDown();
|
||
|
|
}
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
|
||
|
|
bool compatibleTest(const TestModel& testModel) {
|
||
|
|
static const std::vector<TestOperationType> kSupportedOperationTypes{
|
||
|
|
TestOperationType::CONV_2D, TestOperationType::ADD,
|
||
|
|
TestOperationType::DEPTHWISE_CONV_2D, TestOperationType::LOGISTIC};
|
||
|
|
static const std::vector<TestOperandType> kSupportedOperandTypes{
|
||
|
|
TestOperandType::TENSOR_FLOAT32, TestOperandType::TENSOR_INT32,
|
||
|
|
TestOperandType::TENSOR_QUANT8_ASYMM_SIGNED, TestOperandType::BOOL,
|
||
|
|
TestOperandType::INT32};
|
||
|
|
|
||
|
|
if (testModel.hasControlFlow()) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool result = true;
|
||
|
|
const TestSubgraph& mainSubgraph = testModel.main;
|
||
|
|
|
||
|
|
result &= std::all_of(
|
||
|
|
mainSubgraph.operations.begin(), mainSubgraph.operations.end(),
|
||
|
|
[&mainSubgraph](const TestOperation& operation) {
|
||
|
|
bool isOperationCompatible = true;
|
||
|
|
// ensure that tensors are nhwc and filter is constant
|
||
|
|
if (operation.type == TestOperationType::CONV_2D ||
|
||
|
|
operation.type == TestOperationType::DEPTHWISE_CONV_2D) {
|
||
|
|
size_t implicitIsNchwIdx =
|
||
|
|
(operation.type == TestOperationType::CONV_2D) ? 7 : 8;
|
||
|
|
size_t explicitIsNchwIdx = implicitIsNchwIdx + 3;
|
||
|
|
bool isImplicitPadding =
|
||
|
|
operation.inputs.size() <= implicitIsNchwIdx ||
|
||
|
|
mainSubgraph.operands[operation.inputs[implicitIsNchwIdx]].type ==
|
||
|
|
TestOperandType::BOOL;
|
||
|
|
size_t isNchwIdx = isImplicitPadding ? implicitIsNchwIdx : explicitIsNchwIdx;
|
||
|
|
|
||
|
|
if (operation.inputs.size() > static_cast<uint32_t>(isNchwIdx)) {
|
||
|
|
isOperationCompatible &=
|
||
|
|
!(*mainSubgraph.operands[operation.inputs[isNchwIdx]]
|
||
|
|
.data.get<bool>());
|
||
|
|
}
|
||
|
|
|
||
|
|
const int kFilterIdx = 1;
|
||
|
|
const TestOperand& filterOperand =
|
||
|
|
mainSubgraph.operands[operation.inputs[kFilterIdx]];
|
||
|
|
TestOperandLifeTime filterLifetime = filterOperand.lifetime;
|
||
|
|
isOperationCompatible &=
|
||
|
|
(filterLifetime == TestOperandLifeTime::CONSTANT_COPY) ||
|
||
|
|
(filterLifetime == TestOperandLifeTime::CONSTANT_REFERENCE);
|
||
|
|
|
||
|
|
// check that making filter operands symmetrical does not over/underflow
|
||
|
|
// this is because the outputs of the model will be different from expected if
|
||
|
|
// the operand value changes with the under/overflow
|
||
|
|
if (filterOperand.type == TestOperandType::TENSOR_QUANT8_ASYMM_SIGNED) {
|
||
|
|
const int8_t* data = filterOperand.data.get<int8_t>();
|
||
|
|
size_t dataSize = filterOperand.data.size();
|
||
|
|
|
||
|
|
for (int32_t i = 0; i < static_cast<int32_t>(dataSize); i++) {
|
||
|
|
int32_t newValue =
|
||
|
|
static_cast<int32_t>(data[i]) - filterOperand.zeroPoint;
|
||
|
|
if (newValue < std::numeric_limits<int8_t>::min() ||
|
||
|
|
newValue > std::numeric_limits<int8_t>::max()) {
|
||
|
|
isOperationCompatible = false;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
isOperationCompatible &=
|
||
|
|
std::find(kSupportedOperationTypes.begin(), kSupportedOperationTypes.end(),
|
||
|
|
operation.type) != kSupportedOperationTypes.end();
|
||
|
|
|
||
|
|
return isOperationCompatible;
|
||
|
|
});
|
||
|
|
|
||
|
|
result &= std::all_of(mainSubgraph.operands.begin(), mainSubgraph.operands.end(),
|
||
|
|
[](const TestOperand& operand) {
|
||
|
|
return std::find(kSupportedOperandTypes.begin(),
|
||
|
|
kSupportedOperandTypes.end(),
|
||
|
|
operand.type) != kSupportedOperandTypes.end();
|
||
|
|
});
|
||
|
|
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
TEST_P(CompatibilityLayerGeneratedTestsSupported, CompatibilityLayerSupported) {
|
||
|
|
mTestSupported = true;
|
||
|
|
execute(testModel);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_P(CompatibilityLayerGeneratedTestsUnsupported, CompatibilityLayerUnsupported) {
|
||
|
|
mTestSupported = false;
|
||
|
|
execute(testModel);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_P(CompatibilityLayerGeneratedTestsDynamicOutput, CompatibilityLayerDynamicOutput) {
|
||
|
|
mTestDynamicOutputShape = true;
|
||
|
|
mTestSupported = false;
|
||
|
|
execute(testModel);
|
||
|
|
}
|
||
|
|
|
||
|
|
INSTANTIATE_GENERATED_TEST(CompatibilityLayerGeneratedTestsSupported,
|
||
|
|
[](const TestModel& testModel) {
|
||
|
|
return !testModel.expectFailure && compatibleTest(testModel);
|
||
|
|
});
|
||
|
|
|
||
|
|
INSTANTIATE_GENERATED_TEST(CompatibilityLayerGeneratedTestsUnsupported,
|
||
|
|
[](const TestModel& testModel) {
|
||
|
|
return !testModel.expectFailure && !compatibleTest(testModel);
|
||
|
|
});
|
||
|
|
|
||
|
|
INSTANTIATE_GENERATED_TEST(CompatibilityLayerGeneratedTestsDynamicOutput,
|
||
|
|
[](const TestModel& testModel) {
|
||
|
|
return !testModel.expectFailure && !testModel.hasScalarOutputs();
|
||
|
|
});
|
||
|
|
|
||
|
|
} // namespace android::nn::generated_tests
|