| /* |
| * 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 <SampleDriver.h> |
| #include <ValidateHal.h> |
| #include <gtest/gtest.h> |
| |
| #include <algorithm> |
| #include <atomic> |
| #include <cassert> |
| #include <memory> |
| #include <string> |
| #include <thread> |
| #include <tuple> |
| #include <vector> |
| |
| #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 <typename T> |
| using MQDescriptorSync = hardware::MQDescriptorSync<T>; |
| |
| 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<V1_0::IPreparedModel> 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<V1_0::ErrorStatus> execute( |
| const V1_0::Request& request, const sp<V1_0::IExecutionCallback>& 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<V1_0::ErrorStatus> execute_1_2( |
| const V1_0::Request& request, V1_2::MeasureTiming measure, |
| const sp<V1_2::IExecutionCallback>& 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<V1_3::ErrorStatus> execute_1_3( |
| const V1_3::Request& request, V1_2::MeasureTiming measure, |
| const V1_3::OptionalTimePoint& deadline, |
| const V1_3::OptionalTimeoutDuration& loopTimeoutDuration, |
| const sp<V1_3::IExecutionCallback>& 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<void> 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<void> 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<void> configureExecutionBurst( |
| const sp<V1_2::IBurstCallback>& callback, |
| const MQDescriptorSync<V1_2::FmqRequestDatum>& requestChannel, |
| const MQDescriptorSync<V1_2::FmqResultDatum>& resultChannel, |
| configureExecutionBurst_cb cb) override { |
| CHECK(mPreparedModelV1_2 != nullptr) << "V1_2 prepared model is nullptr."; |
| if (mErrorStatus == V1_3::ErrorStatus::NONE) { |
| const sp<V1_2::IBurstContext> 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<void> executeFenced(const V1_3::Request& request, |
| const hardware::hidl_vec<hardware::hidl_handle>& 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<V1_0::IPreparedModel> mPreparedModelV1_0; |
| const sp<V1_2::IPreparedModel> mPreparedModelV1_2; |
| const sp<V1_3::IPreparedModel> mPreparedModelV1_3; |
| V1_3::ErrorStatus mErrorStatus; |
| |
| static std::atomic<bool> mPauseExecutions; |
| static std::atomic<unsigned int> 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<bool> TestPreparedModelLatest::mPauseExecutions = false; |
| std::atomic<unsigned int> TestPreparedModelLatest::mExecutionsInFlight = 0; |
| |
| using TestPreparedModel13 = TestPreparedModelLatest; |
| |
| // Like TestPreparedModelLatest, but implementing 1.2 |
| class TestPreparedModel12 : public V1_2::IPreparedModel { |
| public: |
| TestPreparedModel12(sp<V1_0::IPreparedModel> preparedModel, V1_3::ErrorStatus errorStatus) |
| : mLatestPreparedModel(new TestPreparedModelLatest(preparedModel, errorStatus)) {} |
| |
| hardware::Return<V1_0::ErrorStatus> execute( |
| const V1_0::Request& request, const sp<V1_0::IExecutionCallback>& callback) override { |
| return mLatestPreparedModel->execute(request, callback); |
| } |
| |
| hardware::Return<V1_0::ErrorStatus> execute_1_2( |
| const V1_0::Request& request, V1_2::MeasureTiming measure, |
| const sp<V1_2::IExecutionCallback>& callback) override { |
| return mLatestPreparedModel->execute_1_2(request, measure, callback); |
| } |
| |
| hardware::Return<void> executeSynchronously(const V1_0::Request& request, |
| V1_2::MeasureTiming measure, |
| executeSynchronously_cb cb) override { |
| return mLatestPreparedModel->executeSynchronously(request, measure, cb); |
| } |
| |
| hardware::Return<void> configureExecutionBurst( |
| const sp<V1_2::IBurstCallback>& callback, |
| const MQDescriptorSync<V1_2::FmqRequestDatum>& requestChannel, |
| const MQDescriptorSync<V1_2::FmqResultDatum>& resultChannel, |
| configureExecutionBurst_cb cb) override { |
| return mLatestPreparedModel->configureExecutionBurst(callback, requestChannel, |
| resultChannel, cb); |
| } |
| |
| private: |
| const sp<V1_3::IPreparedModel> mLatestPreparedModel; |
| }; |
| |
| // Like TestPreparedModelLatest, but implementing 1.0 |
| class TestPreparedModel10 : public V1_0::IPreparedModel { |
| public: |
| TestPreparedModel10(sp<V1_0::IPreparedModel> preparedModel, V1_3::ErrorStatus errorStatus) |
| : mLatestPreparedModel(new TestPreparedModelLatest(preparedModel, errorStatus)) {} |
| |
| hardware::Return<V1_0::ErrorStatus> execute( |
| const V1_0::Request& request, const sp<V1_0::IExecutionCallback>& callback) override { |
| return mLatestPreparedModel->execute(request, callback); |
| } |
| |
| private: |
| const sp<V1_3::IPreparedModel> 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<void> 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<nn::HalVersion::V1_3>(kPerf), |
| .ifPerformance = kPerf, |
| .whilePerformance = kPerf}; |
| _hidl_cb(V1_3::ErrorStatus::NONE, capabilities); |
| return hardware::Void(); |
| } |
| |
| hardware::Return<void> getSupportedOperations_1_3(const HidlModel& model, |
| getSupportedOperations_1_3_cb cb) override { |
| if (nn::validateModel(model)) { |
| std::vector<bool> supported(model.main.operations.size(), true); |
| cb(V1_3::ErrorStatus::NONE, supported); |
| } else { |
| cb(V1_3::ErrorStatus::INVALID_ARGUMENT, {}); |
| } |
| return hardware::Void(); |
| } |
| |
| hardware::Return<V1_3::ErrorStatus> prepareModel_1_3( |
| const HidlModel& model, V1_1::ExecutionPreference preference, V1_3::Priority priority, |
| const V1_3::OptionalTimePoint& deadline, |
| const hardware::hidl_vec<hardware::hidl_handle>& modelCache, |
| const hardware::hidl_vec<hardware::hidl_handle>& dataCache, |
| const nn::HalCacheToken& token, |
| const sp<V1_3::IPreparedModelCallback>& actualCallback) override { |
| sp<PreparedModelCallback> localCallback = new PreparedModelCallback; |
| hardware::Return<V1_3::ErrorStatus> 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<V1_0::ErrorStatus> prepareModel_1_2( |
| const V1_2::Model& model, V1_1::ExecutionPreference preference, |
| const hardware::hidl_vec<hardware::hidl_handle>& modelCache, |
| const hardware::hidl_vec<hardware::hidl_handle>& dataCache, |
| const nn::HalCacheToken& token, |
| const sp<V1_2::IPreparedModelCallback>& actualCallback) override { |
| sp<PreparedModelCallback> localCallback = new PreparedModelCallback; |
| hardware::Return<V1_0::ErrorStatus> 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<V1_0::ErrorStatus> prepareModel_1_1( |
| const V1_1::Model& model, V1_1::ExecutionPreference preference, |
| const sp<V1_0::IPreparedModelCallback>& actualCallback) override { |
| sp<PreparedModelCallback> localCallback = new PreparedModelCallback; |
| hardware::Return<V1_0::ErrorStatus> 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<V1_0::ErrorStatus> prepareModel( |
| const V1_0::Model& model, |
| const sp<V1_0::IPreparedModelCallback>& 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<void> getCapabilities_1_2(getCapabilities_1_2_cb _hidl_cb) override { |
| return mLatestDriver->getCapabilities_1_2(_hidl_cb); |
| } |
| hardware::Return<void> getCapabilities_1_1(getCapabilities_1_1_cb _hidl_cb) override { |
| return mLatestDriver->getCapabilities_1_1(_hidl_cb); |
| } |
| hardware::Return<void> getCapabilities(getCapabilities_cb _hidl_cb) override { |
| return mLatestDriver->getCapabilities(_hidl_cb); |
| } |
| hardware::Return<void> 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<void> 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<void> getSupportedOperations(const V1_0::Model& model, |
| getSupportedOperations_cb _hidl_cb) override { |
| return mLatestDriver->getSupportedOperations(model, _hidl_cb); |
| } |
| hardware::Return<V1_0::ErrorStatus> prepareModel_1_2( |
| const V1_2::Model& model, V1_1::ExecutionPreference preference, |
| const hardware::hidl_vec<hardware::hidl_handle>& modelCache, |
| const hardware::hidl_vec<hardware::hidl_handle>& dataCache, |
| const nn::HalCacheToken& token, |
| const sp<V1_2::IPreparedModelCallback>& actualCallback) override { |
| return mLatestDriver->prepareModel_1_2(model, preference, modelCache, dataCache, token, |
| actualCallback); |
| } |
| hardware::Return<V1_0::ErrorStatus> prepareModel_1_1( |
| const V1_1::Model& model, V1_1::ExecutionPreference preference, |
| const sp<V1_0::IPreparedModelCallback>& actualCallback) override { |
| return mLatestDriver->prepareModel_1_1(model, preference, actualCallback); |
| } |
| hardware::Return<V1_0::ErrorStatus> prepareModel( |
| const V1_0::Model& model, |
| const sp<V1_0::IPreparedModelCallback>& actualCallback) override { |
| return mLatestDriver->prepareModel(model, actualCallback); |
| } |
| hardware::Return<V1_0::DeviceStatus> getStatus() override { return mLatestDriver->getStatus(); } |
| hardware::Return<void> getVersionString(getVersionString_cb _hidl_cb) override { |
| return mLatestDriver->getVersionString(_hidl_cb); |
| } |
| hardware::Return<void> getType(getType_cb _hidl_cb) override { |
| return mLatestDriver->getType(_hidl_cb); |
| } |
| hardware::Return<void> getSupportedExtensions(getSupportedExtensions_cb _hidl_cb) { |
| return mLatestDriver->getSupportedExtensions(_hidl_cb); |
| } |
| hardware::Return<void> getNumberOfCacheFilesNeeded(getNumberOfCacheFilesNeeded_cb _hidl_cb) { |
| return mLatestDriver->getNumberOfCacheFilesNeeded(_hidl_cb); |
| } |
| hardware::Return<V1_0::ErrorStatus> prepareModelFromCache( |
| const hardware::hidl_vec<hardware::hidl_handle>& modelCache, |
| const hardware::hidl_vec<hardware::hidl_handle>& dataCache, |
| const nn::HalCacheToken& token, const sp<V1_2::IPreparedModelCallback>& callback) { |
| return mLatestDriver->prepareModelFromCache(modelCache, dataCache, token, callback); |
| } |
| |
| private: |
| const sp<V1_3::IDevice> 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<void> getCapabilities_1_1(getCapabilities_1_1_cb _hidl_cb) override { |
| return mLatestDriver->getCapabilities_1_1(_hidl_cb); |
| } |
| hardware::Return<void> 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<V1_0::ErrorStatus> prepareModel_1_1( |
| const V1_1::Model& model, V1_1::ExecutionPreference preference, |
| const sp<V1_0::IPreparedModelCallback>& actualCallback) override { |
| return mLatestDriver->prepareModel_1_1(model, preference, actualCallback); |
| } |
| hardware::Return<V1_0::DeviceStatus> getStatus() override { return mLatestDriver->getStatus(); } |
| hardware::Return<void> getCapabilities(getCapabilities_cb _hidl_cb) override { |
| return mLatestDriver->getCapabilities(_hidl_cb); |
| } |
| hardware::Return<void> getSupportedOperations(const V1_0::Model& model, |
| getSupportedOperations_cb _hidl_cb) override { |
| return mLatestDriver->getSupportedOperations(model, _hidl_cb); |
| } |
| hardware::Return<V1_0::ErrorStatus> prepareModel( |
| const V1_0::Model& model, |
| const sp<V1_0::IPreparedModelCallback>& actualCallback) override { |
| return mLatestDriver->prepareModel(model, actualCallback); |
| } |
| |
| private: |
| const sp<V1_3::IDevice> 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<void> getCapabilities(getCapabilities_cb _hidl_cb) override { |
| return mLatestDriver->getCapabilities(_hidl_cb); |
| } |
| hardware::Return<void> getSupportedOperations(const V1_0::Model& model, |
| getSupportedOperations_cb _hidl_cb) override { |
| return mLatestDriver->getSupportedOperations(model, _hidl_cb); |
| } |
| hardware::Return<V1_0::ErrorStatus> prepareModel( |
| const V1_0::Model& model, |
| const sp<V1_0::IPreparedModelCallback>& actualCallback) override { |
| return mLatestDriver->prepareModel(model, actualCallback); |
| } |
| hardware::Return<V1_0::DeviceStatus> getStatus() override { return mLatestDriver->getStatus(); } |
| |
| private: |
| const sp<V1_3::IDevice> 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 <typename DriverClass> |
| 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<std::shared_ptr<Device>> devices; |
| auto device = DeviceManager::forTest_makeDriverDevice( |
| nn::makeSharedDevice(deviceName, new DriverClass(deviceName, errorStatus))); |
| devices.push_back(device); |
| |
| nn::ModelBuilder* m = reinterpret_cast<nn::ModelBuilder*>(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<ANeuralNetworksCompilation*>(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<ANeuralNetworksDevice*> 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 DriverClass> |
| class ExecutionTestTemplate |
| : public ::testing::TestWithParam<std::tuple<V1_3::ErrorStatus, WrapperResult, bool>> { |
| 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<DriverClass>(&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<uint32_t> 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<uint32_t> 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<void()>& compute) { |
| { |
| SCOPED_TRACE(reusable ? "first time reusable" : "non-reusable"); |
| compute(); |
| } |
| if (reusable) { |
| SCOPED_TRACE("second time reusable"); |
| compute(); |
| } |
| } |
| |
| template <class DriverClass> |
| void ExecutionTestTemplate<DriverClass>::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<uint32_t> 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<uint32_t> 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<uint32_t> 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<uint32_t> 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<TestDriver13> {}; |
| 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<TestDriver12> {}; |
| 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<TestDriver11> {}; |
| 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<TestDriver10> {}; |
| 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 |