// // Copyright © 2017 Arm Ltd. All rights reserved. // See LICENSE file in the project root for full license information. // #define LOG_TAG "ArmnnDriverTests" #define BOOST_TEST_MODULE armnn_driver_tests #include #include #include "../ArmnnDriver.hpp" #include "../SystemPropertiesUtils.hpp" #include "OperationsUtils.h" #include namespace android { namespace hardware { namespace neuralnetworks { namespace V1_0 { std::ostream& operator<<(std::ostream& os, ErrorStatus stat) { return os << static_cast(stat); } } } } } BOOST_AUTO_TEST_SUITE(DriverTests) using namespace armnn_driver; using namespace android::nn; using namespace android; BOOST_AUTO_TEST_CASE(Init) { // Making the driver object on the stack causes a weird libc error, so make it on the heap instead auto driver = std::make_unique(DriverOptions(armnn::Compute::CpuRef)); DeviceStatus status = driver->getStatus(); // Note double-parentheses to avoid compile error from Boost trying to printf the DeviceStatus BOOST_TEST((status == DeviceStatus::AVAILABLE)); } BOOST_AUTO_TEST_CASE(TestCapabilities) { // Making the driver object on the stack causes a weird libc error, so make it on the heap instead auto driver = std::make_unique(DriverOptions(armnn::Compute::CpuRef)); ErrorStatus error; Capabilities cap; ArmnnDriver::getCapabilities_cb cb = [&](ErrorStatus status, const Capabilities& capabilities) { error = status; cap = capabilities; }; driver->getCapabilities(cb); BOOST_TEST((int)error == (int)ErrorStatus::NONE); BOOST_TEST(cap.float32Performance.execTime > 0.f); BOOST_TEST(cap.float32Performance.powerUsage > 0.f); BOOST_TEST(cap.quantized8Performance.execTime > 0.f); BOOST_TEST(cap.quantized8Performance.powerUsage > 0.f); } BOOST_AUTO_TEST_CASE(SystemProperties) { // Test default value { auto p = __system_property_find("thisDoesNotExist"); BOOST_TEST((p == nullptr)); int defaultValue = ParseSystemProperty("thisDoesNotExist", -4); BOOST_TEST((defaultValue == -4)); } // Test default value from bad data type { __system_property_set("thisIsNotFloat", "notfloat"); float defaultValue = ParseSystemProperty("thisIsNotFloat", 0.1f); BOOST_TEST((defaultValue == 0.1f)); } // Test fetching bool values { __system_property_set("myTestBool", "1"); bool b = ParseSystemProperty("myTestBool", false); BOOST_TEST((b == true)); } { __system_property_set("myTestBool", "0"); bool b = ParseSystemProperty("myTestBool", true); BOOST_TEST((b == false)); } // Test fetching int { __system_property_set("myTestInt", "567"); int i = ParseSystemProperty("myTestInt", 890); BOOST_TEST((i==567)); } // Test fetching float { __system_property_set("myTestFloat", "1.2f"); float f = ParseSystemProperty("myTestFloat", 3.4f); BOOST_TEST((f==1.2f)); } } // The following are helpers for writing unit tests for the driver namespace { struct ExecutionCallback : public IExecutionCallback { ExecutionCallback() : mNotified(false) { } Return notify(ErrorStatus status) override { (void)status; ALOGI("ExecutionCallback::notify invoked"); std::lock_guard executionLock(mMutex); mNotified = true; mCondition.notify_one(); return Void(); } /// wait until the callback has notified us that it is done Return wait() { ALOGI("ExecutionCallback::wait invoked"); std::unique_lock executionLock(mMutex); while (!mNotified) { mCondition.wait(executionLock); } mNotified = false; return Void(); } private: // use a mutex and a condition variable to wait for asynchronous callbacks std::mutex mMutex; std::condition_variable mCondition; // and a flag, in case we are notified before the wait call bool mNotified; }; class PreparedModelCallback : public IPreparedModelCallback { public: PreparedModelCallback() { } ~PreparedModelCallback() override { } Return notify(ErrorStatus status, const sp& preparedModel) override { m_ErrorStatus = status; m_PreparedModel = preparedModel; return Void(); } ErrorStatus GetErrorStatus() { return m_ErrorStatus; } sp GetPreparedModel() { return m_PreparedModel; } private: ErrorStatus m_ErrorStatus; sp m_PreparedModel; }; // lifted from common/Utils.cpp hidl_memory allocateSharedMemory(int64_t size) { hidl_memory memory; const std::string& type = "ashmem"; android::sp allocator = IAllocator::getService(type); allocator->allocate(size, [&](bool success, const hidl_memory& mem) { if (!success) { ALOGE("unable to allocate %li bytes of %s", size, type.c_str()); } else { memory = mem; } }); return memory; } android::sp AddPoolAndGetData(uint32_t size, Request& request) { hidl_memory pool; android::sp allocator = IAllocator::getService("ashmem"); allocator->allocate(sizeof(float) * size, [&](bool success, const hidl_memory& mem) { BOOST_TEST(success); pool = mem; }); request.pools.resize(request.pools.size() + 1); request.pools[request.pools.size() - 1] = pool; android::sp mapped = mapMemory(pool); mapped->update(); return mapped; } void AddPoolAndSetData(uint32_t size, Request& request, float* data) { android::sp memory = AddPoolAndGetData(size, request); float* dst = static_cast(static_cast(memory->getPointer())); memcpy(dst, data, size * sizeof(float)); } void AddOperand(Model& model, const Operand& op) { model.operands.resize(model.operands.size() + 1); model.operands[model.operands.size() - 1] = op; } void AddIntOperand(Model& model, int32_t value) { DataLocation location = {}; location.offset = model.operandValues.size(); location.length = sizeof(int32_t); Operand op = {}; op.type = OperandType::INT32; op.dimensions = hidl_vec{}; op.lifetime = OperandLifeTime::CONSTANT_COPY; op.location = location; model.operandValues.resize(model.operandValues.size() + location.length); *reinterpret_cast(&model.operandValues[location.offset]) = value; AddOperand(model, op); } template OperandType TypeToOperandType(); template<> OperandType TypeToOperandType() { return OperandType::TENSOR_FLOAT32; }; template<> OperandType TypeToOperandType() { return OperandType::TENSOR_INT32; }; template void AddTensorOperand(Model& model, hidl_vec dimensions, T* values) { uint32_t totalElements = 1; for (uint32_t dim : dimensions) { totalElements *= dim; } DataLocation location = {}; location.offset = model.operandValues.size(); location.length = totalElements * sizeof(T); Operand op = {}; op.type = TypeToOperandType(); op.dimensions = dimensions; op.lifetime = OperandLifeTime::CONSTANT_COPY; op.location = location; model.operandValues.resize(model.operandValues.size() + location.length); for (uint32_t i = 0; i < totalElements; i++) { *(reinterpret_cast(&model.operandValues[location.offset]) + i) = values[i]; } AddOperand(model, op); } void AddInputOperand(Model& model, hidl_vec dimensions) { Operand op = {}; op.type = OperandType::TENSOR_FLOAT32; op.dimensions = dimensions; op.lifetime = OperandLifeTime::MODEL_INPUT; AddOperand(model, op); model.inputIndexes.resize(model.inputIndexes.size() + 1); model.inputIndexes[model.inputIndexes.size() - 1] = model.operands.size() - 1; } void AddOutputOperand(Model& model, hidl_vec dimensions) { Operand op = {}; op.type = OperandType::TENSOR_FLOAT32; op.dimensions = dimensions; op.lifetime = OperandLifeTime::MODEL_OUTPUT; AddOperand(model, op); model.outputIndexes.resize(model.outputIndexes.size() + 1); model.outputIndexes[model.outputIndexes.size() - 1] = model.operands.size() - 1; } android::sp PrepareModel(const Model& model, ArmnnDriver& driver) { sp cb(new PreparedModelCallback()); driver.prepareModel(model, cb); BOOST_TEST((cb->GetErrorStatus() == ErrorStatus::NONE)); BOOST_TEST((cb->GetPreparedModel() != nullptr)); return cb->GetPreparedModel(); } void Execute(android::sp preparedModel, const Request& request) { sp cb(new ExecutionCallback()); BOOST_TEST(preparedModel->execute(request, cb) == ErrorStatus::NONE); ALOGI("Execute: waiting for callback to be invoked"); cb->wait(); } sp ExecuteNoWait(android::sp preparedModel, const Request& request) { sp cb(new ExecutionCallback()); BOOST_TEST(preparedModel->execute(request, cb) == ErrorStatus::NONE); ALOGI("ExecuteNoWait: returning callback object"); return cb; } } // Add our own test here since we fail the fc tests which Google supplies (because of non-const weights) BOOST_AUTO_TEST_CASE(FullyConnected) { // this should ideally replicate fully_connected_float.model.cpp // but that uses slightly weird dimensions which I don't think we need to support for now auto driver = std::make_unique(DriverOptions(armnn::Compute::CpuRef)); Model model = {}; // add operands int32_t actValue = 0; float weightValue[] = {2, 4, 1}; float biasValue[] = {4}; AddInputOperand(model, hidl_vec{1, 3}); AddTensorOperand(model, hidl_vec{1, 3}, weightValue); AddTensorOperand(model, hidl_vec{1}, biasValue); AddIntOperand(model, actValue); AddOutputOperand(model, hidl_vec{1, 1}); // make the fully connected operation model.operations.resize(1); model.operations[0].type = OperationType::FULLY_CONNECTED; model.operations[0].inputs = hidl_vec{0, 1, 2, 3}; model.operations[0].outputs = hidl_vec{4}; // make the prepared model android::sp preparedModel = PrepareModel(model, *driver); // construct the request DataLocation inloc = {}; inloc.poolIndex = 0; inloc.offset = 0; inloc.length = 3 * sizeof(float); RequestArgument input = {}; input.location = inloc; input.dimensions = hidl_vec{}; DataLocation outloc = {}; outloc.poolIndex = 1; outloc.offset = 0; outloc.length = 1 * sizeof(float); RequestArgument output = {}; output.location = outloc; output.dimensions = hidl_vec{}; Request request = {}; request.inputs = hidl_vec{input}; request.outputs = hidl_vec{output}; // set the input data (matching source test) float indata[] = {2, 32, 16}; AddPoolAndSetData(3, request, indata); // add memory for the output android::sp outMemory = AddPoolAndGetData(1, request); float* outdata = static_cast(static_cast(outMemory->getPointer())); // run the execution Execute(preparedModel, request); // check the result BOOST_TEST(outdata[0] == 152); } // Add our own test for concurrent execution // The main point of this test is to check that multiple requests can be // executed without waiting for the callback from previous execution. // The operations performed are not significant. BOOST_AUTO_TEST_CASE(ConcurrentExecute) { ALOGI("ConcurrentExecute: entry"); auto driver = std::make_unique(DriverOptions(armnn::Compute::CpuRef)); Model model = {}; // add operands int32_t actValue = 0; float weightValue[] = {2, 4, 1}; float biasValue[] = {4}; AddInputOperand(model, hidl_vec{1, 3}); AddTensorOperand(model, hidl_vec{1, 3}, weightValue); AddTensorOperand(model, hidl_vec{1}, biasValue); AddIntOperand(model, actValue); AddOutputOperand(model, hidl_vec{1, 1}); // make the fully connected operation model.operations.resize(1); model.operations[0].type = OperationType::FULLY_CONNECTED; model.operations[0].inputs = hidl_vec{0, 1, 2, 3}; model.operations[0].outputs = hidl_vec{4}; // make the prepared models const size_t maxRequests = 5; android::sp preparedModels[maxRequests]; for (size_t i = 0; i < maxRequests; ++i) { preparedModels[i] = PrepareModel(model, *driver); } // construct the request data DataLocation inloc = {}; inloc.poolIndex = 0; inloc.offset = 0; inloc.length = 3 * sizeof(float); RequestArgument input = {}; input.location = inloc; input.dimensions = hidl_vec{}; DataLocation outloc = {}; outloc.poolIndex = 1; outloc.offset = 0; outloc.length = 1 * sizeof(float); RequestArgument output = {}; output.location = outloc; output.dimensions = hidl_vec{}; // build the requests Request requests[maxRequests]; android::sp outMemory[maxRequests]; float* outdata[maxRequests]; for (size_t i = 0; i < maxRequests; ++i) { requests[i].inputs = hidl_vec{input}; requests[i].outputs = hidl_vec{output}; // set the input data (matching source test) float indata[] = {2, 32, 16}; AddPoolAndSetData(3, requests[i], indata); // add memory for the output outMemory[i] = AddPoolAndGetData(1, requests[i]); outdata[i] = static_cast(static_cast(outMemory[i]->getPointer())); } // invoke the execution of the requests ALOGI("ConcurrentExecute: executing requests"); sp cb[maxRequests]; for (size_t i = 0; i < maxRequests; ++i) { cb[i] = ExecuteNoWait(preparedModels[i], requests[i]); } // wait for the requests to complete ALOGI("ConcurrentExecute: waiting for callbacks"); for (size_t i = 0; i < maxRequests; ++i) { cb[i]->wait(); } // check the results ALOGI("ConcurrentExecute: validating results"); for (size_t i = 0; i < maxRequests; ++i) { BOOST_TEST(outdata[i][0] == 152); } ALOGI("ConcurrentExecute: exit"); } BOOST_AUTO_TEST_CASE(GetSupportedOperations) { auto driver = std::make_unique(DriverOptions(armnn::Compute::CpuRef)); ErrorStatus error; std::vector sup; ArmnnDriver::getSupportedOperations_cb cb = [&](ErrorStatus status, const std::vector& supported) { error = status; sup = supported; }; Model model1 = {}; // add operands int32_t actValue = 0; float weightValue[] = {2, 4, 1}; float biasValue[] = {4}; AddInputOperand(model1, hidl_vec{1, 3}); AddTensorOperand(model1, hidl_vec{1, 3}, weightValue); AddTensorOperand(model1, hidl_vec{1}, biasValue); AddIntOperand(model1, actValue); AddOutputOperand(model1, hidl_vec{1, 1}); // make a correct fully connected operation model1.operations.resize(2); model1.operations[0].type = OperationType::FULLY_CONNECTED; model1.operations[0].inputs = hidl_vec{0, 1, 2, 3}; model1.operations[0].outputs = hidl_vec{4}; // make an incorrect fully connected operation AddIntOperand(model1, actValue); AddOutputOperand(model1, hidl_vec{1, 1}); model1.operations[1].type = OperationType::FULLY_CONNECTED; model1.operations[1].inputs = hidl_vec{4}; model1.operations[1].outputs = hidl_vec{5}; driver->getSupportedOperations(model1, cb); BOOST_TEST((int)error == (int)ErrorStatus::NONE); BOOST_TEST(sup[0] == true); BOOST_TEST(sup[1] == false); // Broadcast add/mul are not supported Model model2 = {}; AddInputOperand(model2, hidl_vec{1, 1, 3, 4}); AddInputOperand(model2, hidl_vec{4}); AddOutputOperand(model2, hidl_vec{1, 1, 3, 4}); AddOutputOperand(model2, hidl_vec{1, 1, 3, 4}); model2.operations.resize(2); model2.operations[0].type = OperationType::ADD; model2.operations[0].inputs = hidl_vec{0,1}; model2.operations[0].outputs = hidl_vec{2}; model2.operations[1].type = OperationType::MUL; model2.operations[1].inputs = hidl_vec{0,1}; model2.operations[1].outputs = hidl_vec{3}; driver->getSupportedOperations(model2, cb); BOOST_TEST((int)error == (int)ErrorStatus::NONE); BOOST_TEST(sup[0] == false); BOOST_TEST(sup[1] == false); Model model3 = {}; // Add unsupported operation, should return no error but we don't support it AddInputOperand(model3, hidl_vec{1, 1, 1, 8}); AddIntOperand(model3, 2); AddOutputOperand(model3, hidl_vec{1, 2, 2, 2}); model3.operations.resize(1); model3.operations[0].type = OperationType::DEPTH_TO_SPACE; model1.operations[0].inputs = hidl_vec{0, 1}; model3.operations[0].outputs = hidl_vec{2}; driver->getSupportedOperations(model3, cb); BOOST_TEST((int)error == (int)ErrorStatus::NONE); BOOST_TEST(sup[0] == false); // Add invalid operation Model model4 = {}; AddIntOperand(model4, 0); model4.operations.resize(1); model4.operations[0].type = static_cast(100); model4.operations[0].outputs = hidl_vec{0}; driver->getSupportedOperations(model4, cb); BOOST_TEST((int)error == (int)ErrorStatus::INVALID_ARGUMENT); } // The purpose of this test is to ensure that when encountering an unsupported operation // it is skipped and getSupportedOperations() continues (rather than failing and stopping). // As per IVGCVSW-710. BOOST_AUTO_TEST_CASE(UnsupportedLayerContinueOnFailure) { auto driver = std::make_unique(DriverOptions(armnn::Compute::CpuRef)); ErrorStatus error; std::vector sup; ArmnnDriver::getSupportedOperations_cb cb = [&](ErrorStatus status, const std::vector& supported) { error = status; sup = supported; }; Model model = {}; // operands int32_t actValue = 0; float weightValue[] = {2, 4, 1}; float biasValue[] = {4}; // broadcast add is unsupported at the time of writing this test, but any unsupported layer will do AddInputOperand(model, hidl_vec{1, 1, 3, 4}); AddInputOperand(model, hidl_vec{4}); AddOutputOperand(model, hidl_vec{1, 1, 3, 4}); // fully connected AddInputOperand(model, hidl_vec{1, 3}); AddTensorOperand(model, hidl_vec{1, 3}, weightValue); AddTensorOperand(model, hidl_vec{1}, biasValue); AddIntOperand(model, actValue); AddOutputOperand(model, hidl_vec{1, 1}); // broadcast mul is unsupported AddOutputOperand(model, hidl_vec{1, 1, 3, 4}); model.operations.resize(3); // unsupported model.operations[0].type = OperationType::ADD; model.operations[0].inputs = hidl_vec{0,1}; model.operations[0].outputs = hidl_vec{2}; // supported model.operations[1].type = OperationType::FULLY_CONNECTED; model.operations[1].inputs = hidl_vec{3, 4, 5, 6}; model.operations[1].outputs = hidl_vec{7}; // unsupported model.operations[2].type = OperationType::MUL; model.operations[2].inputs = hidl_vec{0,1}; model.operations[2].outputs = hidl_vec{8}; // we are testing that the unsupported layers return false and the test continues // rather than failing and stopping. driver->getSupportedOperations(model, cb); BOOST_TEST((int)error == (int)ErrorStatus::NONE); BOOST_TEST(sup[0] == false); BOOST_TEST(sup[1] == true); BOOST_TEST(sup[2] == false); } // The purpose of this test is to ensure that when encountering an failure // during mem pool mapping we properly report an error to the framework via a callback BOOST_AUTO_TEST_CASE(ModelToINetworkConverterMemPoolFail) { auto driver = std::make_unique(armnn::Compute::CpuRef); ErrorStatus error; std::vector sup; ArmnnDriver::getSupportedOperations_cb cb = [&](ErrorStatus status, const std::vector& supported) { error = status; sup = supported; }; Model model = {}; model.pools = hidl_vec{hidl_memory("Unsuported hidl memory type", nullptr, 0)}; //memory pool mapping should fail, we should report an error driver->getSupportedOperations(model, cb); BOOST_TEST((int)error == (int)ErrorStatus::GENERAL_FAILURE); } namespace { void PaddingTestImpl(android::nn::PaddingScheme paddingScheme) { auto driver = std::make_unique(DriverOptions(armnn::Compute::CpuRef)); Model model = {}; uint32_t outSize = paddingScheme == kPaddingSame ? 2 : 1; // add operands float weightValue[] = {1, -1, 0, 1}; float biasValue[] = {0}; AddInputOperand(model, hidl_vec{1, 2, 3, 1}); AddTensorOperand(model, hidl_vec{1, 2, 2, 1}, weightValue); AddTensorOperand(model, hidl_vec{1}, biasValue); AddIntOperand(model, (int32_t)paddingScheme); // padding AddIntOperand(model, 2); // stride x AddIntOperand(model, 2); // stride y AddIntOperand(model, 0); // no activation AddOutputOperand(model, hidl_vec{1, 1, outSize, 1}); // make the convolution operation model.operations.resize(1); model.operations[0].type = OperationType::CONV_2D; model.operations[0].inputs = hidl_vec{0, 1, 2, 3, 4, 5, 6}; model.operations[0].outputs = hidl_vec{7}; // make the prepared model android::sp preparedModel = PrepareModel(model, *driver); // construct the request DataLocation inloc = {}; inloc.poolIndex = 0; inloc.offset = 0; inloc.length = 6 * sizeof(float); RequestArgument input = {}; input.location = inloc; input.dimensions = hidl_vec{}; DataLocation outloc = {}; outloc.poolIndex = 1; outloc.offset = 0; outloc.length = outSize * sizeof(float); RequestArgument output = {}; output.location = outloc; output.dimensions = hidl_vec{}; Request request = {}; request.inputs = hidl_vec{input}; request.outputs = hidl_vec{output}; // set the input data (matching source test) float indata[] = {4, 1, 0, 3, -1, 2}; AddPoolAndSetData(6, request, indata); // add memory for the output android::sp outMemory = AddPoolAndGetData(outSize, request); float* outdata = static_cast(static_cast(outMemory->getPointer())); // run the execution Execute(preparedModel, request); // check the result if (paddingScheme == kPaddingValid) { BOOST_TEST(outdata[0] == 2); } else if (paddingScheme == kPaddingSame) { BOOST_TEST(outdata[0] == 2); BOOST_TEST(outdata[1] == 0); } else { BOOST_TEST(false); } } } BOOST_AUTO_TEST_CASE(ConvValidPadding) { PaddingTestImpl(kPaddingValid); } BOOST_AUTO_TEST_CASE(ConvSamePadding) { PaddingTestImpl(kPaddingSame); } BOOST_AUTO_TEST_CASE(TestFullyConnected4dInput) { auto driver = std::make_unique(DriverOptions(armnn::Compute::CpuRef)); ErrorStatus error; std::vector sup; ArmnnDriver::getSupportedOperations_cb cb = [&](ErrorStatus status, const std::vector& supported) { error = status; sup = supported; }; Model model = {}; // operands int32_t actValue = 0; float weightValue[] = {1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1}; //identity float biasValue[] = {0, 0, 0, 0, 0, 0, 0, 0}; // fully connected operation AddInputOperand(model, hidl_vec{1, 1, 1, 8}); AddTensorOperand(model, hidl_vec{8, 8}, weightValue); AddTensorOperand(model, hidl_vec{8}, biasValue); AddIntOperand(model, actValue); AddOutputOperand(model, hidl_vec{1, 8}); model.operations.resize(1); model.operations[0].type = OperationType::FULLY_CONNECTED; model.operations[0].inputs = hidl_vec{0,1,2,3}; model.operations[0].outputs = hidl_vec{4}; // make the prepared model android::sp preparedModel = PrepareModel(model, *driver); // construct the request DataLocation inloc = {}; inloc.poolIndex = 0; inloc.offset = 0; inloc.length = 8 * sizeof(float); RequestArgument input = {}; input.location = inloc; input.dimensions = hidl_vec{}; DataLocation outloc = {}; outloc.poolIndex = 1; outloc.offset = 0; outloc.length = 8 * sizeof(float); RequestArgument output = {}; output.location = outloc; output.dimensions = hidl_vec{}; Request request = {}; request.inputs = hidl_vec{input}; request.outputs = hidl_vec{output}; // set the input data float indata[] = {1,2,3,4,5,6,7,8}; AddPoolAndSetData(8, request, indata); // add memory for the output android::sp outMemory = AddPoolAndGetData(8, request); float* outdata = static_cast(static_cast(outMemory->getPointer())); // run the execution Execute(preparedModel, request); // check the result BOOST_TEST(outdata[0] == 1); BOOST_TEST(outdata[1] == 2); BOOST_TEST(outdata[2] == 3); BOOST_TEST(outdata[3] == 4); BOOST_TEST(outdata[4] == 5); BOOST_TEST(outdata[5] == 6); BOOST_TEST(outdata[6] == 7); BOOST_TEST(outdata[7] == 8); } BOOST_AUTO_TEST_CASE(TestFullyConnected4dInputReshape) { auto driver = std::make_unique(DriverOptions(armnn::Compute::CpuRef)); ErrorStatus error; std::vector sup; ArmnnDriver::getSupportedOperations_cb cb = [&](ErrorStatus status, const std::vector& supported) { error = status; sup = supported; }; Model model = {}; // operands int32_t actValue = 0; float weightValue[] = {1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1}; //identity float biasValue[] = {0, 0, 0, 0, 0, 0, 0, 0}; // fully connected operation AddInputOperand(model, hidl_vec{1, 2, 2, 2}); AddTensorOperand(model, hidl_vec{8, 8}, weightValue); AddTensorOperand(model, hidl_vec{8}, biasValue); AddIntOperand(model, actValue); AddOutputOperand(model, hidl_vec{1, 8}); model.operations.resize(1); model.operations[0].type = OperationType::FULLY_CONNECTED; model.operations[0].inputs = hidl_vec{0,1,2,3}; model.operations[0].outputs = hidl_vec{4}; // make the prepared model android::sp preparedModel = PrepareModel(model, *driver); // construct the request DataLocation inloc = {}; inloc.poolIndex = 0; inloc.offset = 0; inloc.length = 8 * sizeof(float); RequestArgument input = {}; input.location = inloc; input.dimensions = hidl_vec{}; DataLocation outloc = {}; outloc.poolIndex = 1; outloc.offset = 0; outloc.length = 8 * sizeof(float); RequestArgument output = {}; output.location = outloc; output.dimensions = hidl_vec{}; Request request = {}; request.inputs = hidl_vec{input}; request.outputs = hidl_vec{output}; // set the input data float indata[] = {1,2,3,4,5,6,7,8}; AddPoolAndSetData(8, request, indata); // add memory for the output android::sp outMemory = AddPoolAndGetData(8, request); float* outdata = static_cast(static_cast(outMemory->getPointer())); // run the execution Execute(preparedModel, request); // check the result BOOST_TEST(outdata[0] == 1); BOOST_TEST(outdata[1] == 2); BOOST_TEST(outdata[2] == 3); BOOST_TEST(outdata[3] == 4); BOOST_TEST(outdata[4] == 5); BOOST_TEST(outdata[5] == 6); BOOST_TEST(outdata[6] == 7); BOOST_TEST(outdata[7] == 8); } BOOST_AUTO_TEST_SUITE_END()