blob: 0714fb80e5b26ccf4a469ab64ff0920a2f108416 [file] [log] [blame]
/*
* Copyright (C) 2018 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 "HalInterfaces.h"
#include "Manager.h"
#include "NeuralNetworks.h"
#include "NeuralNetworksOEM.h"
#include "SampleDriver.h"
#include "TestNeuralNetworksWrapper.h"
#include "Utils.h"
#include "ValidateHal.h"
#include <gtest/gtest.h>
#include <map>
#include <queue>
namespace {
using Device = ::android::nn::Device;
using DeviceManager = ::android::nn::DeviceManager;
using ExecutePreference = ::android::nn::test_wrapper::ExecutePreference;
using HidlModel = ::android::hardware::neuralnetworks::V1_2::Model;
using Result = ::android::nn::test_wrapper::Result;
using SampleDriver = ::android::nn::sample_driver::SampleDriver;
using WrapperModel = ::android::nn::test_wrapper::Model;
using WrapperOperandType = ::android::nn::test_wrapper::OperandType;
using WrapperType = ::android::nn::test_wrapper::Type;
template <typename T>
using sp = ::android::sp<T>;
// This is an IDevice for testing purposes. The test driver has customized
// getCapabilities_1_1 and getSupportedOperations_1_2.
class TestDriver : public SampleDriver {
public:
TestDriver(const char* name, Capabilities capabilities, const std::vector<bool>& supportedOps)
: SampleDriver(name), mCapabilities(capabilities), mSupportedOps(supportedOps) {}
~TestDriver() override {}
Return<void> getCapabilities_1_1(getCapabilities_1_1_cb cb) override {
cb(ErrorStatus::NONE, mCapabilities);
return Void();
}
Return<void> getSupportedOperations_1_2(const Model& model,
getSupportedOperations_cb cb) override {
if (!android::nn::validateModel(model)) {
cb(ErrorStatus::INVALID_ARGUMENT, std::vector<bool>());
return Void();
}
const size_t count = model.operations.size();
std::vector<bool> supported(count);
std::transform(
model.operations.begin(), model.operations.end(), supported.begin(),
[this](Operation op) { return mSupportedOps[static_cast<int32_t>(op.type)]; });
cb(ErrorStatus::NONE, supported);
return Void();
}
private:
Capabilities mCapabilities;
std::vector<bool> mSupportedOps;
};
class IntrospectionControlTest : public ::testing::Test {
protected:
virtual void SetUp() {}
virtual void TearDown() {
if (mEvent) {
ANeuralNetworksEvent_free(mEvent);
}
if (mExecution) {
ANeuralNetworksExecution_free(mExecution);
}
if (mCompilation) {
ANeuralNetworksCompilation_free(mCompilation);
}
DeviceManager::get()->forTest_reInitializeDeviceList();
}
struct DeviceSpecification {
DeviceSpecification(const std::string& name, Capabilities capabilities,
std::vector<bool>& supportedOps)
: mName(name), mCapabilities(capabilities), mSupportedOps(supportedOps) {}
std::string mName;
Capabilities mCapabilities;
std::vector<bool> mSupportedOps;
};
// From a vector of DeviceSpecification, register new Devices.
void registerDevices(std::vector<DeviceSpecification> specifications) {
for (const auto& specification : specifications) {
DeviceManager::get()->forTest_registerDevice(
specification.mName.c_str(),
new TestDriver(specification.mName.c_str(), specification.mCapabilities,
specification.mSupportedOps));
}
}
bool selectDeviceByName(const std::string& name) {
uint32_t numDevices = 0;
EXPECT_EQ(ANeuralNetworks_getDeviceCount(&numDevices), ANEURALNETWORKS_NO_ERROR);
EXPECT_GE(numDevices, (uint32_t)1);
for (uint32_t i = 0; i < numDevices; i++) {
ANeuralNetworksDevice* device = nullptr;
EXPECT_EQ(ANeuralNetworks_getDevice(i, &device), ANEURALNETWORKS_NO_ERROR);
const char* buffer = nullptr;
int result = ANeuralNetworksDevice_getName(device, &buffer);
if (result == ANEURALNETWORKS_NO_ERROR && name.compare(buffer) == 0) {
mDevices.push_back(device);
return true;
}
}
return false;
}
bool isSupportedOpListExpected(const std::vector<bool>& expected) {
const uint32_t kMaxNumberOperations = 256;
EXPECT_LE(expected.size(), kMaxNumberOperations);
ANeuralNetworksModel* modelHandle = mModel.getHandle();
bool supported[kMaxNumberOperations] = {false};
EXPECT_EQ(ANeuralNetworksModel_getSupportedOperationsForDevices(
modelHandle, mDevices.data(), mDevices.size(), supported),
ANEURALNETWORKS_NO_ERROR);
return std::equal(expected.begin(), expected.end(), supported);
}
int prepareForExecution() {
ANeuralNetworksModel* modelHandle = mModel.getHandle();
int result = ANeuralNetworksCompilation_createForDevices(modelHandle, mDevices.data(),
mDevices.size(), &mCompilation);
if (result != ANEURALNETWORKS_NO_ERROR) {
return result;
}
EXPECT_EQ(ANeuralNetworksCompilation_finish(mCompilation), ANEURALNETWORKS_NO_ERROR);
EXPECT_EQ(ANeuralNetworksExecution_create(mCompilation, &mExecution),
ANEURALNETWORKS_NO_ERROR);
return ANEURALNETWORKS_NO_ERROR;
}
std::vector<ANeuralNetworksDevice*> mDevices;
ANeuralNetworksEvent* mEvent = nullptr;
ANeuralNetworksExecution* mExecution = nullptr;
ANeuralNetworksCompilation* mCompilation = nullptr;
WrapperModel mModel;
};
void createSimpleAddModel(WrapperModel* model) {
WrapperOperandType type0(WrapperType::TENSOR_FLOAT32, {2});
WrapperOperandType type1(WrapperType::INT32, {});
// Phase 1, operands
auto op1 = model->addOperand(&type0);
auto op2 = model->addOperand(&type0);
auto act = model->addOperand(&type1);
auto op3 = model->addOperand(&type0);
// Phase 2, operations
static int32_t act_init[] = {0};
model->setOperandValue(act, act_init, sizeof(act_init));
model->addOperation(ANEURALNETWORKS_ADD, {op1, op2, act}, {op3});
// Phase 3, inputs and outputs
model->identifyInputsAndOutputs({op1, op2}, {op3});
model->finish();
ASSERT_TRUE(model->isValid());
}
// TODO(b/117983761): update the test to make sure the model is actually running on the test device.
// This test verifies that a simple ADD model is able to run on a single device that claims being
// able to handle all operations.
TEST_F(IntrospectionControlTest, SimpleAddModel) {
// This is needed before we have the CPU fallback path being treated as a Device.
// TODO(miaowang): remove once b/72506261 is fixed.
if (DeviceManager::get()->getUseCpuOnly()) {
GTEST_SKIP();
}
createSimpleAddModel(&mModel);
std::string driverName = "test-all";
std::vector<bool> ops(android::nn::kNumberOfOperationTypes, true);
registerDevices({{
driverName,
{.float32Performance = {.execTime = 0.9, .powerUsage = 0.9},
.quantized8Performance = {.execTime = 0.9, .powerUsage = 0.9},
.relaxedFloat32toFloat16Performance = {.execTime = 0.9, .powerUsage = 0.9}},
ops,
}});
EXPECT_TRUE(selectDeviceByName(driverName));
EXPECT_TRUE(isSupportedOpListExpected({true}));
EXPECT_EQ(prepareForExecution(), ANEURALNETWORKS_NO_ERROR);
float input1[2] = {1.0f, 2.0f};
float input2[2] = {3.0f, 4.0f};
float output[2];
EXPECT_EQ(ANeuralNetworksExecution_setInput(mExecution, 0, nullptr, input1, sizeof(input1)),
ANEURALNETWORKS_NO_ERROR);
EXPECT_EQ(ANeuralNetworksExecution_setInput(mExecution, 1, nullptr, input2, sizeof(input2)),
ANEURALNETWORKS_NO_ERROR);
EXPECT_EQ(ANeuralNetworksExecution_setOutput(mExecution, 0, nullptr, output, sizeof(output)),
ANEURALNETWORKS_NO_ERROR);
EXPECT_EQ(ANeuralNetworksExecution_startCompute(mExecution, &mEvent), ANEURALNETWORKS_NO_ERROR);
EXPECT_EQ(ANeuralNetworksEvent_wait(mEvent), ANEURALNETWORKS_NO_ERROR);
EXPECT_EQ(output[0], input1[0] + input2[0]);
EXPECT_EQ(output[1], input1[1] + input2[1]);
}
const float kSimpleMultiplier = 2.0f;
void createAddMulModel(WrapperModel* model, bool reverseOrder) {
WrapperOperandType type0(WrapperType::TENSOR_FLOAT32, {2});
WrapperOperandType type1(WrapperType::INT32, {});
// Phase 1, operands
auto op1 = model->addOperand(&type0);
auto op2 = model->addOperand(&type0);
auto act = model->addOperand(&type1);
auto op3 = model->addOperand(&type0);
auto op4 = model->addOperand(&type0);
auto op5 = model->addOperand(&type0);
// Phase 2, operations
static int32_t act_init[] = {0};
model->setOperandValue(act, act_init, sizeof(act_init));
static float multiplier[] = {kSimpleMultiplier, kSimpleMultiplier};
model->setOperandValue(op4, multiplier, sizeof(multiplier));
if (reverseOrder) {
// In this case, add MUL first, but the execution order is still ADD -> MUL.
model->addOperation(ANEURALNETWORKS_MUL, {op3, op4, act}, {op5});
model->addOperation(ANEURALNETWORKS_ADD, {op1, op2, act}, {op3});
} else {
model->addOperation(ANEURALNETWORKS_ADD, {op1, op2, act}, {op3});
model->addOperation(ANEURALNETWORKS_MUL, {op3, op4, act}, {op5});
}
// Phase 3, inputs and outputs
model->identifyInputsAndOutputs({op1, op2}, {op5});
model->finish();
ASSERT_TRUE(model->isValid());
}
// This test verifies that a device that could only handle ADD would correctly report that an
// ADD->MUL model could not be fully supported.
TEST_F(IntrospectionControlTest, PartialModelNotSupported) {
// This is needed before we have the CPU fallback path being treated as a Device.
// TODO(miaowang): remove once b/72506261 is fixed.
if (DeviceManager::get()->getUseCpuOnly()) {
GTEST_SKIP();
}
createAddMulModel(&mModel, false);
Capabilities capabilities = {
.float32Performance = {.execTime = 0.9, .powerUsage = 0.9},
.quantized8Performance = {.execTime = 0.9, .powerUsage = 0.9},
.relaxedFloat32toFloat16Performance = {.execTime = 0.9, .powerUsage = 0.9}};
std::string addOnlyDriver = "test-onlyAdd";
std::vector<bool> addOnlyOp(android::nn::kNumberOfOperationTypes, false);
addOnlyOp[ANEURALNETWORKS_ADD] = true;
registerDevices({{addOnlyDriver, capabilities, addOnlyOp}});
EXPECT_TRUE(selectDeviceByName(addOnlyDriver));
EXPECT_TRUE(isSupportedOpListExpected({true, false}));
}
// This test verifies that a device that could only handle ADD would correctly report that an
// ADD->MUL model could not be fully supported. Also verifies that the indices of returned
// supported op list correctly map to the order of operations being added by the user.
TEST_F(IntrospectionControlTest, PartialModelNotSupportedOrder) {
// This is needed before we have the CPU fallback path being treated as a Device.
// TODO(miaowang): remove once b/72506261 is fixed.
if (DeviceManager::get()->getUseCpuOnly()) {
GTEST_SKIP();
}
createAddMulModel(&mModel, true);
Capabilities capabilities = {
.float32Performance = {.execTime = 0.9, .powerUsage = 0.9},
.quantized8Performance = {.execTime = 0.9, .powerUsage = 0.9},
.relaxedFloat32toFloat16Performance = {.execTime = 0.9, .powerUsage = 0.9}};
std::string addOnlyDriver = "test-onlyAdd";
std::vector<bool> addOnlyOp(android::nn::kNumberOfOperationTypes, false);
addOnlyOp[ANEURALNETWORKS_ADD] = true;
registerDevices({{addOnlyDriver, capabilities, addOnlyOp}});
EXPECT_TRUE(selectDeviceByName(addOnlyDriver));
EXPECT_TRUE(isSupportedOpListExpected({false, true}));
}
// TODO(miaowang): update the test to make sure the model is actually running on the test devices.
// This test verifies that an ADD->MUL model is able to run on two selected devices that together
// can handle all operations.
TEST_F(IntrospectionControlTest, ModelNeedTwoDevices) {
// This is needed before we have the CPU fallback path being treated as a Device.
// TODO(miaowang): remove once b/72506261 is fixed.
if (DeviceManager::get()->getUseCpuOnly()) {
GTEST_SKIP();
}
createAddMulModel(&mModel, false);
Capabilities capabilities = {
.float32Performance = {.execTime = 0.9, .powerUsage = 0.9},
.quantized8Performance = {.execTime = 0.9, .powerUsage = 0.9},
.relaxedFloat32toFloat16Performance = {.execTime = 0.9, .powerUsage = 0.9}};
std::string addOnlyDriver = "test-onlyAdd";
std::vector<bool> addOnlyOp(android::nn::kNumberOfOperationTypes, false);
addOnlyOp[ANEURALNETWORKS_ADD] = true;
std::string mulOnlyDriver = "test-onlyMul";
std::vector<bool> mulOnlyOp(android::nn::kNumberOfOperationTypes, false);
mulOnlyOp[ANEURALNETWORKS_MUL] = true;
registerDevices({
{addOnlyDriver, capabilities, addOnlyOp},
{mulOnlyDriver, capabilities, mulOnlyOp},
});
EXPECT_TRUE(selectDeviceByName(addOnlyDriver));
EXPECT_TRUE(selectDeviceByName(mulOnlyDriver));
EXPECT_TRUE(isSupportedOpListExpected({true, true}));
EXPECT_EQ(prepareForExecution(), ANEURALNETWORKS_NO_ERROR);
float input1[2] = {1.0f, 2.0f};
float input2[2] = {3.0f, 4.0f};
float output[2];
EXPECT_EQ(ANeuralNetworksExecution_setInput(mExecution, 0, nullptr, input1, sizeof(input1)),
ANEURALNETWORKS_NO_ERROR);
EXPECT_EQ(ANeuralNetworksExecution_setInput(mExecution, 1, nullptr, input2, sizeof(input2)),
ANEURALNETWORKS_NO_ERROR);
EXPECT_EQ(ANeuralNetworksExecution_setOutput(mExecution, 0, nullptr, output, sizeof(output)),
ANEURALNETWORKS_NO_ERROR);
EXPECT_EQ(ANeuralNetworksExecution_startCompute(mExecution, &mEvent), ANEURALNETWORKS_NO_ERROR);
EXPECT_EQ(ANeuralNetworksEvent_wait(mEvent), ANEURALNETWORKS_NO_ERROR);
EXPECT_EQ(output[0], kSimpleMultiplier * (input1[0] + input2[0]));
EXPECT_EQ(output[1], kSimpleMultiplier * (input1[1] + input2[1]));
}
} // namespace