/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include "CompilationBuilder.h" #include "ExecutionBurstServer.h" #include "ExecutionCallback.h" #include "HalUtils.h" #include "Manager.h" #include "ModelBuilder.h" #include "NeuralNetworks.h" #include "PreparedModelCallback.h" #include "TestNeuralNetworksWrapper.h" namespace android { namespace V1_0 = ::android::hardware::neuralnetworks::V1_0; namespace V1_1 = ::android::hardware::neuralnetworks::V1_1; namespace V1_2 = ::android::hardware::neuralnetworks::V1_2; namespace V1_3 = ::android::hardware::neuralnetworks::V1_3; using CompilationBuilder = nn::CompilationBuilder; using Device = nn::Device; using SharedDevice = nn::SharedDevice; using DeviceManager = nn::DeviceManager; using HidlModel = V1_3::Model; using PreparedModelCallback = nn::PreparedModelCallback; using SampleDriver = nn::sample_driver::SampleDriver; using WrapperCompilation = nn::test_wrapper::Compilation; using WrapperEvent = nn::test_wrapper::Event; using WrapperExecution = nn::test_wrapper::Execution; using WrapperModel = nn::test_wrapper::Model; using WrapperOperandType = nn::test_wrapper::OperandType; using WrapperResult = nn::test_wrapper::Result; using WrapperType = nn::test_wrapper::Type; using nn::convertToV1_0; using nn::convertToV1_3; using nn::ErrorStatus; template using MQDescriptorSync = hardware::MQDescriptorSync; namespace { const V1_2::Timing kBadTiming = {.timeOnDevice = UINT64_MAX, .timeInDriver = UINT64_MAX}; // Wraps the latest version of IPreparedModel to allow dummying up the execution status, // and control when the execution finishes. class TestPreparedModelLatest : public V1_3::IPreparedModel { public: // If errorStatus is NONE, then execute behaves normally (and sends back // the actual execution status). Otherwise, don't bother to execute, and // just send back errorStatus (as the execution status, not the launch // status). TestPreparedModelLatest(sp preparedModel, V1_3::ErrorStatus errorStatus) : mPreparedModelV1_0(preparedModel), mPreparedModelV1_2(V1_2::IPreparedModel::castFrom(preparedModel).withDefault(nullptr)), mPreparedModelV1_3(V1_3::IPreparedModel::castFrom(preparedModel).withDefault(nullptr)), mErrorStatus(errorStatus) {} hardware::Return execute( const V1_0::Request& request, const sp& callback) override { CHECK(mPreparedModelV1_0 != nullptr) << "V1_0 prepared model is nullptr."; std::thread([this, request, callback] { dummyExecution(); if (mErrorStatus == V1_3::ErrorStatus::NONE) { // Note that we lose the actual launch status. (void)mPreparedModelV1_0->execute(request, callback); } else { callback->notify(convertToV1_0(mErrorStatus)); } }).detach(); return V1_0::ErrorStatus::NONE; } hardware::Return execute_1_2( const V1_0::Request& request, V1_2::MeasureTiming measure, const sp& callback) override { CHECK(mPreparedModelV1_2 != nullptr) << "V1_2 prepared model is nullptr."; std::thread([this, request, measure, callback] { dummyExecution(); if (mErrorStatus == V1_3::ErrorStatus::NONE) { // Note that we lose the actual launch status. (void)mPreparedModelV1_2->execute_1_2(request, measure, callback); } else if (mErrorStatus == V1_3::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE) { V1_2::OutputShape shape = {.dimensions = {1}, .isSufficient = false}; callback->notify_1_2(convertToV1_0(mErrorStatus), {shape}, kBadTiming); } else { callback->notify_1_2(convertToV1_0(mErrorStatus), {}, kBadTiming); } }).detach(); return V1_0::ErrorStatus::NONE; } hardware::Return execute_1_3( const V1_3::Request& request, V1_2::MeasureTiming measure, const V1_3::OptionalTimePoint& deadline, const V1_3::OptionalTimeoutDuration& loopTimeoutDuration, const sp& callback) override { CHECK(mPreparedModelV1_3 != nullptr) << "V1_3 prepared model is nullptr."; std::thread([this, request, measure, deadline, loopTimeoutDuration, callback] { dummyExecution(); if (mErrorStatus == V1_3::ErrorStatus::NONE) { // Note that we lose the actual launch status. (void)mPreparedModelV1_3->execute_1_3(request, measure, deadline, loopTimeoutDuration, callback); } else if (mErrorStatus == V1_3::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE) { V1_2::OutputShape shape = {.dimensions = {1}, .isSufficient = false}; callback->notify_1_3(mErrorStatus, {shape}, kBadTiming); } else { callback->notify_1_3(mErrorStatus, {}, kBadTiming); } }).detach(); return V1_3::ErrorStatus::NONE; } hardware::Return executeSynchronously(const V1_0::Request& request, V1_2::MeasureTiming measure, executeSynchronously_cb cb) override { CHECK(mPreparedModelV1_2 != nullptr) << "V1_2 prepared model is nullptr."; dummyExecution(); if (mErrorStatus == V1_3::ErrorStatus::NONE) { return mPreparedModelV1_2->executeSynchronously(request, measure, cb); } else if (mErrorStatus == V1_3::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE) { V1_2::OutputShape shape = {.dimensions = {1}, .isSufficient = false}; cb(convertToV1_0(mErrorStatus), {shape}, kBadTiming); return hardware::Void(); } else { cb(convertToV1_0(mErrorStatus), {}, kBadTiming); return hardware::Void(); } } hardware::Return executeSynchronously_1_3( const V1_3::Request& request, V1_2::MeasureTiming measure, const V1_3::OptionalTimePoint& deadline, const V1_3::OptionalTimeoutDuration& loopTimeoutDuration, executeSynchronously_1_3_cb cb) override { CHECK(mPreparedModelV1_3 != nullptr) << "V1_3 prepared model is nullptr."; dummyExecution(); if (mErrorStatus == V1_3::ErrorStatus::NONE) { return mPreparedModelV1_3->executeSynchronously_1_3(request, measure, deadline, loopTimeoutDuration, cb); } else if (mErrorStatus == V1_3::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE) { V1_2::OutputShape shape = {.dimensions = {1}, .isSufficient = false}; cb(mErrorStatus, {shape}, kBadTiming); return hardware::Void(); } else { cb(mErrorStatus, {}, kBadTiming); return hardware::Void(); } } // ExecutionBurstServer::create has an overload that will use // IPreparedModel::executeSynchronously(), so we can rely on that, rather // than having to implement ExecutionBurstServer::IExecutorWithCache. hardware::Return configureExecutionBurst( const sp& callback, const MQDescriptorSync& requestChannel, const MQDescriptorSync& resultChannel, configureExecutionBurst_cb cb) override { CHECK(mPreparedModelV1_2 != nullptr) << "V1_2 prepared model is nullptr."; if (mErrorStatus == V1_3::ErrorStatus::NONE) { const sp burst = nn::ExecutionBurstServer::create(callback, requestChannel, resultChannel, this); cb(burst == nullptr ? V1_0::ErrorStatus::GENERAL_FAILURE : V1_0::ErrorStatus::NONE, burst); return hardware::Void(); } else { cb(convertToV1_0(mErrorStatus), nullptr); return hardware::Void(); } } // Note, due to the limitation of SampleDriver implementation, the call is // synchronous. The test code that exercises this implementation of // SampleDriver is written with that in mind. Therefore, this // implementation is synchronous also. If the SampleDriver is updated to // return real sync fence, this must be updated. hardware::Return executeFenced(const V1_3::Request& request, const hardware::hidl_vec& waitFor, V1_2::MeasureTiming measure, const V1_3::OptionalTimePoint& deadline, const V1_3::OptionalTimeoutDuration& loopTimeoutDuration, const V1_3::OptionalTimeoutDuration& duration, executeFenced_cb cb) override { CHECK(mPreparedModelV1_3 != nullptr) << "V1_3 prepared model is nullptr."; CHECK(mErrorStatus != V1_3::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE) << "executeFenced does not support dynamic output shape"; dummyExecution(); if (mErrorStatus == V1_3::ErrorStatus::NONE) { return mPreparedModelV1_3->executeFenced(request, waitFor, measure, deadline, loopTimeoutDuration, duration, cb); } else { // Due to the limitations of the SampleDriver, all failures look // like launch failures. If the SampleDriver is updated to return // real sync fences, this must be updated. cb(mErrorStatus, hardware::hidl_handle(nullptr), nullptr); } return hardware::Void(); } // We can place the TestPreparedModelLatest system in a "pause" mode where // no execution will complete until the system is taken out of that mode. // Initially, the system is not in that mode. static void pauseExecutions(bool v) { mPauseExecutions.store(v); } // This function is only guaranteed to work in the following pattern: // Consider thread A as primary thread // - thread A: pauseExecutions(true); // - thread A: launch execution (as thread B) // - thread A: waitForExecutionToBegin(), block until call to dummyExecution by // thread B makes mExecutionsInFlight nonzero // - thread B: dummyExecution(), which makes mExecutionsInFlight nonzero and blocks // until thread A calls pauseExecutions(false) // - thread A: waitForExecutionToBegin() returns // - thread A: pauseExecutions(false), allowing dummyExecution() on thread B to continue // - thread B: dummyExecution() zeroes mExecutionsInFlight and returns // - thread B: thread exits static void waitForExecutionToBegin() { CHECK(mPauseExecutions.load()); while (mExecutionsInFlight.load() == 0) { } } private: const sp mPreparedModelV1_0; const sp mPreparedModelV1_2; const sp mPreparedModelV1_3; V1_3::ErrorStatus mErrorStatus; static std::atomic mPauseExecutions; static std::atomic mExecutionsInFlight; static void dummyExecution() { CHECK_EQ(mExecutionsInFlight.fetch_add(1), 0u) << "We do not support concurrent executions"; while (mPauseExecutions.load()) { } mExecutionsInFlight.fetch_sub(1); } }; std::atomic TestPreparedModelLatest::mPauseExecutions = false; std::atomic TestPreparedModelLatest::mExecutionsInFlight = 0; using TestPreparedModel13 = TestPreparedModelLatest; // Like TestPreparedModelLatest, but implementing 1.2 class TestPreparedModel12 : public V1_2::IPreparedModel { public: TestPreparedModel12(sp preparedModel, V1_3::ErrorStatus errorStatus) : mLatestPreparedModel(new TestPreparedModelLatest(preparedModel, errorStatus)) {} hardware::Return execute( const V1_0::Request& request, const sp& callback) override { return mLatestPreparedModel->execute(request, callback); } hardware::Return execute_1_2( const V1_0::Request& request, V1_2::MeasureTiming measure, const sp& callback) override { return mLatestPreparedModel->execute_1_2(request, measure, callback); } hardware::Return executeSynchronously(const V1_0::Request& request, V1_2::MeasureTiming measure, executeSynchronously_cb cb) override { return mLatestPreparedModel->executeSynchronously(request, measure, cb); } hardware::Return configureExecutionBurst( const sp& callback, const MQDescriptorSync& requestChannel, const MQDescriptorSync& resultChannel, configureExecutionBurst_cb cb) override { return mLatestPreparedModel->configureExecutionBurst(callback, requestChannel, resultChannel, cb); } private: const sp mLatestPreparedModel; }; // Like TestPreparedModelLatest, but implementing 1.0 class TestPreparedModel10 : public V1_0::IPreparedModel { public: TestPreparedModel10(sp preparedModel, V1_3::ErrorStatus errorStatus) : mLatestPreparedModel(new TestPreparedModelLatest(preparedModel, errorStatus)) {} hardware::Return execute( const V1_0::Request& request, const sp& callback) override { return mLatestPreparedModel->execute(request, callback); } private: const sp mLatestPreparedModel; }; // Behaves like SampleDriver, except that it produces wrapped IPreparedModel. class TestDriver13 : public SampleDriver { public: // Allow dummying up the error status for execution of all models // prepared from this driver. If errorStatus is NONE, then // execute behaves normally (and sends back the actual execution // status). Otherwise, don't bother to execute, and just send // back errorStatus (as the execution status, not the launch // status). TestDriver13(const std::string& name, V1_3::ErrorStatus errorStatus) : SampleDriver(name.c_str()), mErrorStatus(errorStatus) {} hardware::Return getCapabilities_1_3(getCapabilities_1_3_cb _hidl_cb) override { android::nn::initVLogMask(); const V1_0::PerformanceInfo kPerf = {.execTime = 0.75f, .powerUsage = 0.75f}; V1_3::Capabilities capabilities = { .relaxedFloat32toFloat16PerformanceScalar = kPerf, .relaxedFloat32toFloat16PerformanceTensor = kPerf, .operandPerformance = nn::nonExtensionOperandPerformance(kPerf), .ifPerformance = kPerf, .whilePerformance = kPerf}; _hidl_cb(V1_3::ErrorStatus::NONE, capabilities); return hardware::Void(); } hardware::Return getSupportedOperations_1_3(const HidlModel& model, getSupportedOperations_1_3_cb cb) override { if (nn::validateModel(model)) { std::vector supported(model.main.operations.size(), true); cb(V1_3::ErrorStatus::NONE, supported); } else { cb(V1_3::ErrorStatus::INVALID_ARGUMENT, {}); } return hardware::Void(); } hardware::Return prepareModel_1_3( const HidlModel& model, V1_1::ExecutionPreference preference, V1_3::Priority priority, const V1_3::OptionalTimePoint& deadline, const hardware::hidl_vec& modelCache, const hardware::hidl_vec& dataCache, const nn::HalCacheToken& token, const sp& actualCallback) override { sp localCallback = new PreparedModelCallback; hardware::Return prepareModelReturn = SampleDriver::prepareModel_1_3( model, preference, priority, deadline, modelCache, dataCache, token, localCallback); if (!prepareModelReturn.isOkUnchecked()) { return prepareModelReturn; } if (prepareModelReturn != V1_3::ErrorStatus::NONE) { actualCallback->notify_1_3( convertToV1_3(localCallback->getStatus()), V1_3::IPreparedModel::castFrom(localCallback->getPreparedModel())); return prepareModelReturn; } localCallback->wait(); if (localCallback->getStatus() != ErrorStatus::NONE) { actualCallback->notify_1_3( convertToV1_3(localCallback->getStatus()), V1_3::IPreparedModel::castFrom(localCallback->getPreparedModel())); } else { actualCallback->notify_1_3( V1_3::ErrorStatus::NONE, new TestPreparedModel13(localCallback->getPreparedModel(), mErrorStatus)); } return prepareModelReturn; } hardware::Return prepareModel_1_2( const V1_2::Model& model, V1_1::ExecutionPreference preference, const hardware::hidl_vec& modelCache, const hardware::hidl_vec& dataCache, const nn::HalCacheToken& token, const sp& actualCallback) override { sp localCallback = new PreparedModelCallback; hardware::Return prepareModelReturn = SampleDriver::prepareModel_1_2( model, preference, modelCache, dataCache, token, localCallback); if (!prepareModelReturn.isOkUnchecked()) { return prepareModelReturn; } if (prepareModelReturn != V1_0::ErrorStatus::NONE) { actualCallback->notify_1_2( convertToV1_0(localCallback->getStatus()), V1_2::IPreparedModel::castFrom(localCallback->getPreparedModel())); return prepareModelReturn; } localCallback->wait(); if (localCallback->getStatus() != ErrorStatus::NONE) { actualCallback->notify_1_2( convertToV1_0(localCallback->getStatus()), V1_2::IPreparedModel::castFrom(localCallback->getPreparedModel())); } else { actualCallback->notify_1_2( V1_0::ErrorStatus::NONE, new TestPreparedModel12(localCallback->getPreparedModel(), mErrorStatus)); } return prepareModelReturn; } hardware::Return prepareModel_1_1( const V1_1::Model& model, V1_1::ExecutionPreference preference, const sp& actualCallback) override { sp localCallback = new PreparedModelCallback; hardware::Return prepareModelReturn = SampleDriver::prepareModel_1_1(model, preference, localCallback); if (!prepareModelReturn.isOkUnchecked()) { return prepareModelReturn; } if (prepareModelReturn != V1_0::ErrorStatus::NONE) { actualCallback->notify(convertToV1_0(localCallback->getStatus()), localCallback->getPreparedModel()); return prepareModelReturn; } localCallback->wait(); if (localCallback->getStatus() != ErrorStatus::NONE) { actualCallback->notify(convertToV1_0(localCallback->getStatus()), localCallback->getPreparedModel()); } else { actualCallback->notify( V1_0::ErrorStatus::NONE, new TestPreparedModel10(localCallback->getPreparedModel(), mErrorStatus)); } return prepareModelReturn; } hardware::Return prepareModel( const V1_0::Model& model, const sp& actualCallback) override { return prepareModel_1_1(nn::convertToV1_1(model), V1_1::ExecutionPreference::FAST_SINGLE_ANSWER, actualCallback); } private: V1_3::ErrorStatus mErrorStatus; }; // Like TestDriver, but implementing 1.2 class TestDriver12 : public V1_2::IDevice { public: TestDriver12(const std::string& name, V1_3::ErrorStatus errorStatus) : mLatestDriver(new TestDriver13(name, errorStatus)) {} hardware::Return getCapabilities_1_2(getCapabilities_1_2_cb _hidl_cb) override { return mLatestDriver->getCapabilities_1_2(_hidl_cb); } hardware::Return getCapabilities_1_1(getCapabilities_1_1_cb _hidl_cb) override { return mLatestDriver->getCapabilities_1_1(_hidl_cb); } hardware::Return getCapabilities(getCapabilities_cb _hidl_cb) override { return mLatestDriver->getCapabilities(_hidl_cb); } hardware::Return getSupportedOperations_1_2( const V1_2::Model& model, getSupportedOperations_1_2_cb _hidl_cb) override { return mLatestDriver->getSupportedOperations_1_2(model, _hidl_cb); } hardware::Return getSupportedOperations_1_1( const V1_1::Model& model, getSupportedOperations_1_1_cb _hidl_cb) override { return mLatestDriver->getSupportedOperations_1_1(model, _hidl_cb); } hardware::Return getSupportedOperations(const V1_0::Model& model, getSupportedOperations_cb _hidl_cb) override { return mLatestDriver->getSupportedOperations(model, _hidl_cb); } hardware::Return prepareModel_1_2( const V1_2::Model& model, V1_1::ExecutionPreference preference, const hardware::hidl_vec& modelCache, const hardware::hidl_vec& dataCache, const nn::HalCacheToken& token, const sp& actualCallback) override { return mLatestDriver->prepareModel_1_2(model, preference, modelCache, dataCache, token, actualCallback); } hardware::Return prepareModel_1_1( const V1_1::Model& model, V1_1::ExecutionPreference preference, const sp& actualCallback) override { return mLatestDriver->prepareModel_1_1(model, preference, actualCallback); } hardware::Return prepareModel( const V1_0::Model& model, const sp& actualCallback) override { return mLatestDriver->prepareModel(model, actualCallback); } hardware::Return getStatus() override { return mLatestDriver->getStatus(); } hardware::Return getVersionString(getVersionString_cb _hidl_cb) override { return mLatestDriver->getVersionString(_hidl_cb); } hardware::Return getType(getType_cb _hidl_cb) override { return mLatestDriver->getType(_hidl_cb); } hardware::Return getSupportedExtensions(getSupportedExtensions_cb _hidl_cb) { return mLatestDriver->getSupportedExtensions(_hidl_cb); } hardware::Return getNumberOfCacheFilesNeeded(getNumberOfCacheFilesNeeded_cb _hidl_cb) { return mLatestDriver->getNumberOfCacheFilesNeeded(_hidl_cb); } hardware::Return prepareModelFromCache( const hardware::hidl_vec& modelCache, const hardware::hidl_vec& dataCache, const nn::HalCacheToken& token, const sp& callback) { return mLatestDriver->prepareModelFromCache(modelCache, dataCache, token, callback); } private: const sp mLatestDriver; }; // Like TestDriver, but implementing 1.1 class TestDriver11 : public V1_1::IDevice { public: TestDriver11(const std::string& name, V1_3::ErrorStatus errorStatus) : mLatestDriver(new TestDriver13(name, errorStatus)) {} hardware::Return getCapabilities_1_1(getCapabilities_1_1_cb _hidl_cb) override { return mLatestDriver->getCapabilities_1_1(_hidl_cb); } hardware::Return getSupportedOperations_1_1( const V1_1::Model& model, getSupportedOperations_1_1_cb _hidl_cb) override { return mLatestDriver->getSupportedOperations_1_1(model, _hidl_cb); } hardware::Return prepareModel_1_1( const V1_1::Model& model, V1_1::ExecutionPreference preference, const sp& actualCallback) override { return mLatestDriver->prepareModel_1_1(model, preference, actualCallback); } hardware::Return getStatus() override { return mLatestDriver->getStatus(); } hardware::Return getCapabilities(getCapabilities_cb _hidl_cb) override { return mLatestDriver->getCapabilities(_hidl_cb); } hardware::Return getSupportedOperations(const V1_0::Model& model, getSupportedOperations_cb _hidl_cb) override { return mLatestDriver->getSupportedOperations(model, _hidl_cb); } hardware::Return prepareModel( const V1_0::Model& model, const sp& actualCallback) override { return mLatestDriver->prepareModel(model, actualCallback); } private: const sp mLatestDriver; }; // Like TestDriver, but implementing 1.0 class TestDriver10 : public V1_0::IDevice { public: TestDriver10(const std::string& name, V1_3::ErrorStatus errorStatus) : mLatestDriver(new TestDriver13(name, errorStatus)) {} hardware::Return getCapabilities(getCapabilities_cb _hidl_cb) override { return mLatestDriver->getCapabilities(_hidl_cb); } hardware::Return getSupportedOperations(const V1_0::Model& model, getSupportedOperations_cb _hidl_cb) override { return mLatestDriver->getSupportedOperations(model, _hidl_cb); } hardware::Return prepareModel( const V1_0::Model& model, const sp& actualCallback) override { return mLatestDriver->prepareModel(model, actualCallback); } hardware::Return getStatus() override { return mLatestDriver->getStatus(); } private: const sp mLatestDriver; }; // This class adds some simple utilities on top of WrapperCompilation in order // to provide access to certain features from CompilationBuilder that are not // exposed by the base class. template class TestCompilation : public WrapperCompilation { public: // Allow dummying up the error status for all executions from this // compilation. If errorStatus is NONE, then execute behaves // normally (and sends back the actual execution status). // Otherwise, don't bother to execute, and just send back // errorStatus (as the execution status, not the launch status). TestCompilation(const WrapperModel* model, const std::string& deviceName, V1_3::ErrorStatus errorStatus) { std::vector> devices; auto device = DeviceManager::forTest_makeDriverDevice( nn::makeSharedDevice(deviceName, new DriverClass(deviceName, errorStatus))); devices.push_back(device); nn::ModelBuilder* m = reinterpret_cast(model->getHandle()); CompilationBuilder* c = nullptr; int result = m->createCompilation(&c, devices); EXPECT_EQ(result, 0); // We need to ensure that we use our TestDriver and do not // fall back to CPU. (If we allow CPU fallback, then when our // TestDriver reports an execution failure, we'll re-execute // on CPU, and will not see the failure.) c->forTest_setPartitioning(DeviceManager::kPartitioningWithoutFallback); mCompilation = reinterpret_cast(c); } }; // This class has roughly the same functionality as TestCompilation class. // The major difference is that Introspection API is used to select the device. class TestIntrospectionCompilation : public WrapperCompilation { public: TestIntrospectionCompilation(const WrapperModel* model, const std::string& deviceName) { std::vector mDevices; 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 && deviceName.compare(buffer) == 0) { mDevices.push_back(device); } } // In CPU only mode, DeviceManager::getDrivers() will not be able to // provide the actual device list. We will not be able to find the test // driver with specified deviceName. if (!DeviceManager::get()->getUseCpuOnly()) { EXPECT_EQ(mDevices.size(), (uint32_t)1); int result = ANeuralNetworksCompilation_createForDevices( model->getHandle(), mDevices.data(), mDevices.size(), &mCompilation); EXPECT_EQ(result, ANEURALNETWORKS_NO_ERROR); } } }; template class ExecutionTestTemplate : public ::testing::TestWithParam> { public: ExecutionTestTemplate() : kName(toString(std::get<0>(GetParam()))), kForceErrorStatus(std::get<0>(GetParam())), kExpectResult(std::get<1>(GetParam())), kUseIntrospectionAPI(std::get<2>(GetParam())), mModel(makeModel()) { if (kUseIntrospectionAPI) { DeviceManager::get()->forTest_registerDevice( nn::makeSharedDevice(kName, new DriverClass(kName.c_str(), kForceErrorStatus))); mCompilation = TestIntrospectionCompilation(&mModel, kName); } else { mCompilation = TestCompilation(&mModel, kName, kForceErrorStatus); } } protected: // Unit test method // Set "reusable" to true to test reusable execution; Otherwise, test non-reusable execution. void TestWait(bool reusable); virtual void TearDown() { // Reinitialize the device list since Introspection API path altered it. if (kUseIntrospectionAPI) { DeviceManager::get()->forTest_reInitializeDeviceList(); } } void getDimensionsWhileRunning(WrapperExecution& execution) { TestPreparedModelLatest::waitForExecutionToBegin(); // Cannot query dimensions while execution is running std::vector dimensions; EXPECT_EQ(execution.getOutputOperandDimensions(0, &dimensions), WrapperResult::BAD_STATE); } const std::string kName; // Allow dummying up the error status for execution. If // kForceErrorStatus is NONE, then execution behaves normally (and // sends back the actual execution status). Otherwise, don't // bother to execute, and just send back kForceErrorStatus (as the // execution status, not the launch status). const V1_3::ErrorStatus kForceErrorStatus; // What result do we expect from the execution? (The WrapperResult // equivalent of kForceErrorStatus.) const WrapperResult kExpectResult; // Whether mCompilation is created via Introspection API or not. const bool kUseIntrospectionAPI; WrapperModel mModel; WrapperCompilation mCompilation; void setInputOutput(WrapperExecution* execution) { mInputBuffer = kInputBuffer; mOutputBuffer = kOutputBufferInitial; ASSERT_EQ(execution->setInput(0, &mInputBuffer, sizeof(mInputBuffer)), WrapperResult::NO_ERROR); ASSERT_EQ(execution->setOutput(0, &mOutputBuffer, sizeof(mOutputBuffer)), WrapperResult::NO_ERROR); } const float kInputBuffer = 3.14; const float kOutputBufferInitial = 0; float mInputBuffer; float mOutputBuffer; const float kOutputBufferExpected = 3; const std::vector kOutputDimensionsExpected = {1}; private: static WrapperModel makeModel() { static const WrapperOperandType tensorType(WrapperType::TENSOR_FLOAT32, {1}); WrapperModel model; uint32_t input = model.addOperand(&tensorType); uint32_t output = model.addOperand(&tensorType); model.addOperation(ANEURALNETWORKS_FLOOR, {input}, {output}); model.identifyInputsAndOutputs({input}, {output}); assert(model.finish() == WrapperResult::NO_ERROR); return model; } }; void computeHelper(bool reusable, const std::function& compute) { { SCOPED_TRACE(reusable ? "first time reusable" : "non-reusable"); compute(); } if (reusable) { SCOPED_TRACE("second time reusable"); compute(); } } template void ExecutionTestTemplate::TestWait(bool reusable) { SCOPED_TRACE(kName); // Skip Introspection API tests when CPU only flag is forced on. if (kUseIntrospectionAPI && DeviceManager::get()->getUseCpuOnly()) { GTEST_SKIP(); } ASSERT_EQ(mCompilation.finish(), WrapperResult::NO_ERROR); { SCOPED_TRACE("startCompute"); WrapperExecution execution(&mCompilation); ASSERT_EQ(execution.setReusable(reusable), WrapperResult::NO_ERROR); ASSERT_NO_FATAL_FAILURE(setInputOutput(&execution)); const auto compute = [this, &execution] { TestPreparedModelLatest::pauseExecutions(true); WrapperEvent event; ASSERT_EQ(execution.startCompute(&event), WrapperResult::NO_ERROR); getDimensionsWhileRunning(execution); TestPreparedModelLatest::pauseExecutions(false); ASSERT_EQ(event.wait(), kExpectResult); if (kExpectResult == WrapperResult::NO_ERROR) { ASSERT_EQ(mOutputBuffer, kOutputBufferExpected); } std::vector dimensions; if (kExpectResult == WrapperResult::NO_ERROR || kExpectResult == WrapperResult::OUTPUT_INSUFFICIENT_SIZE) { // Only one output operand, hardcoded as index 0. ASSERT_EQ(execution.getOutputOperandDimensions(0, &dimensions), kExpectResult); ASSERT_EQ(dimensions, kOutputDimensionsExpected); } else { ASSERT_EQ(execution.getOutputOperandDimensions(0, &dimensions), WrapperResult::BAD_STATE); } }; computeHelper(reusable, compute); } { SCOPED_TRACE("compute"); WrapperExecution execution(&mCompilation); ASSERT_EQ(execution.setReusable(reusable), WrapperResult::NO_ERROR); ASSERT_NO_FATAL_FAILURE(setInputOutput(&execution)); const auto compute = [this, &execution] { TestPreparedModelLatest::pauseExecutions(true); std::thread run([this, &execution] { EXPECT_EQ(execution.compute(), kExpectResult); }); getDimensionsWhileRunning(execution); TestPreparedModelLatest::pauseExecutions(false); run.join(); if (kExpectResult == WrapperResult::NO_ERROR) { ASSERT_EQ(mOutputBuffer, kOutputBufferExpected); } std::vector dimensions; if (kExpectResult == WrapperResult::NO_ERROR || kExpectResult == WrapperResult::OUTPUT_INSUFFICIENT_SIZE) { // Only one output operand, hardcoded as index 0. ASSERT_EQ(execution.getOutputOperandDimensions(0, &dimensions), kExpectResult); ASSERT_EQ(dimensions, kOutputDimensionsExpected); } else { ASSERT_EQ(execution.getOutputOperandDimensions(0, &dimensions), WrapperResult::BAD_STATE); } }; computeHelper(reusable, compute); } { SCOPED_TRACE("burstCompute"); // TODO: If a burst API is added to nn::test_wrapper (e.g., // Execution::burstCompute()), then use that, rather than // Execution::compute(WrapperExecution::ComputeMode::BURST). WrapperExecution execution(&mCompilation); ASSERT_EQ(execution.setReusable(reusable), WrapperResult::NO_ERROR); ASSERT_NO_FATAL_FAILURE(setInputOutput(&execution)); const auto compute = [this, &execution] { TestPreparedModelLatest::pauseExecutions(true); std::thread run([this, &execution] { EXPECT_EQ(execution.compute(WrapperExecution::ComputeMode::BURST), kExpectResult); }); getDimensionsWhileRunning(execution); TestPreparedModelLatest::pauseExecutions(false); run.join(); if (kExpectResult == WrapperResult::NO_ERROR) { ASSERT_EQ(mOutputBuffer, kOutputBufferExpected); } std::vector dimensions; if (kExpectResult == WrapperResult::NO_ERROR || kExpectResult == WrapperResult::OUTPUT_INSUFFICIENT_SIZE) { // Only one output operand, hardcoded as index 0. ASSERT_EQ(execution.getOutputOperandDimensions(0, &dimensions), kExpectResult); ASSERT_EQ(dimensions, kOutputDimensionsExpected); } else { ASSERT_EQ(execution.getOutputOperandDimensions(0, &dimensions), WrapperResult::BAD_STATE); } }; computeHelper(reusable, compute); } if (kExpectResult != WrapperResult::OUTPUT_INSUFFICIENT_SIZE) { // computeWithDependencies doesn't support OUTPUT_INSUFFICIENT_SIZE SCOPED_TRACE("computeWithDependencies"); WrapperExecution execution(&mCompilation); ASSERT_EQ(execution.setReusable(reusable), WrapperResult::NO_ERROR); ASSERT_NO_FATAL_FAILURE(setInputOutput(&execution)); const auto compute = [this, &execution] { TestPreparedModelLatest::pauseExecutions(true); WrapperEvent event; // Note, due to the limitation of SampleDriver implementation, the call is synchronous. // If the SampleDriver is updated to return real sync fence, this must be updated. std::thread run([this, &execution, &event] { EXPECT_EQ(execution.startComputeWithDependencies({}, 0, &event), kExpectResult); }); getDimensionsWhileRunning(execution); TestPreparedModelLatest::pauseExecutions(false); run.join(); if (kExpectResult == WrapperResult::NO_ERROR) { ASSERT_EQ(event.wait(), kExpectResult); ASSERT_EQ(mOutputBuffer, kOutputBufferExpected); } else { ASSERT_EQ(event.wait(), WrapperResult::UNEXPECTED_NULL); } std::vector dimensions; if (kExpectResult == WrapperResult::NO_ERROR) { // Only one output operand, hardcoded as index 0. ASSERT_EQ(execution.getOutputOperandDimensions(0, &dimensions), kExpectResult); ASSERT_EQ(dimensions, kOutputDimensionsExpected); } else { ASSERT_EQ(execution.getOutputOperandDimensions(0, &dimensions), WrapperResult::BAD_STATE); } }; computeHelper(reusable, compute); } } auto kTestValues = ::testing::Values( std::make_tuple(V1_3::ErrorStatus::NONE, WrapperResult::NO_ERROR, /* kUseIntrospectionAPI */ false), std::make_tuple(V1_3::ErrorStatus::DEVICE_UNAVAILABLE, WrapperResult::UNAVAILABLE_DEVICE, /* kUseIntrospectionAPI */ false), std::make_tuple(V1_3::ErrorStatus::GENERAL_FAILURE, WrapperResult::OP_FAILED, /* kUseIntrospectionAPI */ false), std::make_tuple(V1_3::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE, WrapperResult::OUTPUT_INSUFFICIENT_SIZE, /* kUseIntrospectionAPI */ false), std::make_tuple(V1_3::ErrorStatus::INVALID_ARGUMENT, WrapperResult::BAD_DATA, /* kUseIntrospectionAPI */ false)); class ExecutionTest13 : public ExecutionTestTemplate {}; TEST_P(ExecutionTest13, Wait) { TestWait(/*reusable=*/false); } TEST_P(ExecutionTest13, WaitReusable) { TestWait(/*reusable=*/true); } INSTANTIATE_TEST_SUITE_P(Flavor, ExecutionTest13, kTestValues); class ExecutionTest12 : public ExecutionTestTemplate {}; TEST_P(ExecutionTest12, Wait) { TestWait(/*reusable=*/false); } TEST_P(ExecutionTest12, WaitReusable) { TestWait(/*reusable=*/true); } INSTANTIATE_TEST_SUITE_P(Flavor, ExecutionTest12, kTestValues); class ExecutionTest11 : public ExecutionTestTemplate {}; TEST_P(ExecutionTest11, Wait) { if (kForceErrorStatus == V1_3::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE) return; TestWait(/*reusable=*/false); } TEST_P(ExecutionTest11, WaitReusable) { if (kForceErrorStatus == V1_3::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE) return; TestWait(/*reusable=*/true); } INSTANTIATE_TEST_SUITE_P(Flavor, ExecutionTest11, kTestValues); class ExecutionTest10 : public ExecutionTestTemplate {}; TEST_P(ExecutionTest10, Wait) { if (kForceErrorStatus == V1_3::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE) return; TestWait(/*reusable=*/false); } TEST_P(ExecutionTest10, WaitReusable) { if (kForceErrorStatus == V1_3::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE) return; TestWait(/*reusable=*/true); } INSTANTIATE_TEST_SUITE_P(Flavor, ExecutionTest10, kTestValues); auto kIntrospectionTestValues = ::testing::Values( std::make_tuple(V1_3::ErrorStatus::NONE, WrapperResult::NO_ERROR, /* kUseIntrospectionAPI */ true), std::make_tuple(V1_3::ErrorStatus::DEVICE_UNAVAILABLE, WrapperResult::UNAVAILABLE_DEVICE, /* kUseIntrospectionAPI */ true), std::make_tuple(V1_3::ErrorStatus::GENERAL_FAILURE, WrapperResult::OP_FAILED, /* kUseIntrospectionAPI */ true), std::make_tuple(V1_3::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE, WrapperResult::OUTPUT_INSUFFICIENT_SIZE, /* kUseIntrospectionAPI */ true), std::make_tuple(V1_3::ErrorStatus::INVALID_ARGUMENT, WrapperResult::BAD_DATA, /* kUseIntrospectionAPI */ true)); INSTANTIATE_TEST_SUITE_P(IntrospectionFlavor, ExecutionTest13, kIntrospectionTestValues); } // namespace } // namespace android