From 42477c1d3e7ddf74863e84ab79dbe6f42e4a0ba3 Mon Sep 17 00:00:00 2001 From: Kevin May Date: Thu, 26 Mar 2020 13:34:14 +0000 Subject: IVGCVSW-4447 Add Hal 1_3 Support * Add new 1.3 files HalPolicy, ArmnnDriver, ArmnnDriverImpl * Add new .rc file for 1.3 service * Add ArmnnPreparedModel_1_3 and implement new functions * Update Android.mk with 1.3 driver and service * Refactor ifdef to include ARMNN_ANDROID_NN_V1_3 * Create Utils getMainModel for new 1.3 Model Main Subgraph * Use android Utils to convertToV1_X in ArmnnPrepapredModel_1_3 * Refactor HAL 1.2 convert functions into ConversionUtils_1_2.hpp * Replace ArmnnBurstExecutorWithCache with call to ExecutionBurstServer Signed-off-by: Kevin May Change-Id: I514069e9e1b16bcd1c4abfb5d563d25ac22d02e3 --- ArmnnPreparedModel_1_3.cpp | 698 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 698 insertions(+) create mode 100644 ArmnnPreparedModel_1_3.cpp (limited to 'ArmnnPreparedModel_1_3.cpp') diff --git a/ArmnnPreparedModel_1_3.cpp b/ArmnnPreparedModel_1_3.cpp new file mode 100644 index 00000000..155f8b25 --- /dev/null +++ b/ArmnnPreparedModel_1_3.cpp @@ -0,0 +1,698 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#define LOG_TAG "ArmnnDriver" + +#include "ArmnnPreparedModel_1_3.hpp" +#include "Utils.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace android; +using namespace android::hardware; + +namespace { + +static const Timing g_NoTiming = {.timeOnDevice = UINT64_MAX, .timeInDriver = UINT64_MAX}; +using namespace armnn_driver; +using TimePoint = std::chrono::steady_clock::time_point; + +TimePoint Now() +{ + return std::chrono::steady_clock::now(); +} + +unsigned long MicrosecondsDuration(TimePoint endPoint, TimePoint startPoint) +{ + return static_cast(std::chrono::duration_cast( + endPoint - startPoint).count()); +} + +void NotifyCallbackAndCheck(const ::android::sp& callback, + V1_3::ErrorStatus errorStatus, + std::vector, + const Timing, + std::string callingFunction) +{ + Return returned = callback->notify(convertToV1_0(errorStatus)); + // This check is required, if the callback fails and it isn't checked it will bring down the service + if (!returned.isOk()) + { + ALOGE("ArmnnDriver::%s: hidl callback failed to return properly: %s", + callingFunction.c_str(), returned.description().c_str()); + } +} + +void NotifyCallbackAndCheck(const ::android::sp& callback, + V1_3::ErrorStatus errorStatus, + std::vector outputShapes, + const Timing timing, + std::string callingFunction) +{ + Return returned = callback->notify_1_2(convertToV1_0(errorStatus), outputShapes, timing); + // This check is required, if the callback fails and it isn't checked it will bring down the service + if (!returned.isOk()) + { + ALOGE("ArmnnDriver::%s: hidl callback failed to return properly: %s", + callingFunction.c_str(), returned.description().c_str()); + } +} + +void NotifyCallbackAndCheck(const ::android::sp& callback, + V1_3::ErrorStatus errorStatus, + std::vector outputShapes, + const Timing timing, + std::string callingFunction) +{ + Return returned = callback->notify_1_3(errorStatus, outputShapes, timing); + // This check is required, if the callback fails and it isn't checked it will bring down the service + if (!returned.isOk()) + { + ALOGE("ArmnnDriver::%s: hidl callback failed to return properly: %s", + callingFunction.c_str(), returned.description().c_str()); + } +} + +bool ValidateRequestArgument(const RequestArgument& requestArg, const armnn::TensorInfo& tensorInfo) +{ + if (requestArg.dimensions.size() != 0) + { + if (requestArg.dimensions.size() != tensorInfo.GetNumDimensions()) + { + ALOGE("Mismatched dimensions (request argument: %zu, expected: %u)", + requestArg.dimensions.size(), tensorInfo.GetNumDimensions()); + return false; + } + + for (unsigned int d = 0; d < tensorInfo.GetNumDimensions(); ++d) + { + if (requestArg.dimensions[d] != tensorInfo.GetShape()[d]) + { + ALOGE("Mismatched size for dimension %d (request argument: %u, expected %u)", + d, requestArg.dimensions[d], tensorInfo.GetShape()[d]); + return false; + } + } + } + + return true; +} + +armnn::Tensor GetTensorForRequestArgument(const RequestArgument& requestArg, + const armnn::TensorInfo& tensorInfo, + const std::vector<::android::nn::RunTimePoolInfo>& requestPools) +{ + if (!ValidateRequestArgument(requestArg, tensorInfo)) + { + return armnn::Tensor(); + } + + return armnn::Tensor(tensorInfo, GetMemoryFromPool(requestArg.location, requestPools)); +} + +inline std::string BuildTensorName(const char* tensorNamePrefix, std::size_t index) +{ + return tensorNamePrefix + std::to_string(index); +} + +} // anonymous namespace + +using namespace android::hardware; + +namespace armnn_driver +{ + +template +RequestThread + ArmnnPreparedModel_1_3::m_RequestThread; + +template +template +void ArmnnPreparedModel_1_3::DumpTensorsIfRequired(char const* tensorNamePrefix, + const TensorBindingCollection& tensorBindings) +{ + if (!m_RequestInputsAndOutputsDumpDir.empty()) + { + const std::string requestName = boost::str(boost::format("%1%_%2%.dump") % m_NetworkId % m_RequestCount); + for (std::size_t i = 0u; i < tensorBindings.size(); ++i) + { + DumpTensor(m_RequestInputsAndOutputsDumpDir, + requestName, + BuildTensorName(tensorNamePrefix, i), + tensorBindings[i].second); + } + } +} + +template +ArmnnPreparedModel_1_3::ArmnnPreparedModel_1_3(armnn::NetworkId networkId, + armnn::IRuntime* runtime, + const V1_3::Model& model, + const std::string& requestInputsAndOutputsDumpDir, + const bool gpuProfilingEnabled) + : m_NetworkId(networkId) + , m_Runtime(runtime) + , m_Model(model) + , m_RequestCount(0) + , m_RequestInputsAndOutputsDumpDir(requestInputsAndOutputsDumpDir) + , m_GpuProfilingEnabled(gpuProfilingEnabled) +{ + // Enable profiling if required. + m_Runtime->GetProfiler(m_NetworkId)->EnableProfiling(m_GpuProfilingEnabled); +} + +template +ArmnnPreparedModel_1_3::~ArmnnPreparedModel_1_3() +{ + // Get a hold of the profiler used by this model. + std::shared_ptr profiler = m_Runtime->GetProfiler(m_NetworkId); + + // Unload the network associated with this model. + m_Runtime->UnloadNetwork(m_NetworkId); + + // Dump the profiling info to a file if required. + DumpJsonProfilingIfRequired(m_GpuProfilingEnabled, m_RequestInputsAndOutputsDumpDir, m_NetworkId, profiler.get()); +} + +template +Return ArmnnPreparedModel_1_3::execute(const V1_0::Request& request, + const ::android::sp& callback) +{ + if (callback.get() == nullptr) + { + ALOGE("ArmnnPreparedModel_1_3::execute invalid callback passed"); + return V1_0::ErrorStatus::INVALID_ARGUMENT; + } + + auto cb = [callback](V1_3::ErrorStatus errorStatus, + std::vector outputShapes, + const Timing& timing, + std::string callingFunction) + { + NotifyCallbackAndCheck(callback, errorStatus, outputShapes, timing, callingFunction); + }; + + + return convertToV1_0(Execute(convertToV1_3(request), MeasureTiming::NO, cb)); +} + +template +Return ArmnnPreparedModel_1_3::execute_1_2( + const V1_0::Request& request, + MeasureTiming measureTiming, + const sp& callback) +{ + if (callback.get() == nullptr) + { + ALOGE("ArmnnPreparedModel_1_3::execute_1_2 invalid callback passed"); + return V1_0::ErrorStatus::INVALID_ARGUMENT; + } + + auto cb = [callback](V1_3::ErrorStatus errorStatus, + std::vector outputShapes, + const Timing& timing, + std::string callingFunction) + { + NotifyCallbackAndCheck(callback, errorStatus, outputShapes, timing, callingFunction); + }; + + return convertToV1_0(Execute(convertToV1_3(request), measureTiming, cb)); +} + +template +Return ArmnnPreparedModel_1_3::execute_1_3( + const V1_3::Request& request, + MeasureTiming measureTiming, + const V1_3::OptionalTimePoint&, + const sp& callback) +{ + if (callback.get() == nullptr) + { + ALOGE("ArmnnPreparedModel_1_3::execute_1_3 invalid callback passed"); + return V1_3::ErrorStatus::INVALID_ARGUMENT; + } + + auto cb = [callback](V1_3::ErrorStatus errorStatus, + std::vector outputShapes, + const Timing& timing, + std::string callingFunction) + { + NotifyCallbackAndCheck(callback, errorStatus, outputShapes, timing, callingFunction); + }; + + return Execute(request, measureTiming, cb); +} + +template +Return ArmnnPreparedModel_1_3::executeFenced(const V1_3::Request&, + const hidl_vec&, + MeasureTiming, + const OptionalTimePoint&, + const OptionalTimeoutDuration&, + executeFenced_cb cb) +{ + cb(ErrorStatus::DEVICE_UNAVAILABLE, hidl_handle(nullptr), nullptr); + return Void(); +} + +template +Return ArmnnPreparedModel_1_3::PrepareMemoryForInputs( + armnn::InputTensors& inputs, + const V1_3::Request& request, + const std::vector& memPools) +{ + inputs.reserve(request.inputs.size()); + for (unsigned int i = 0; i < request.inputs.size(); i++) + { + const auto& inputArg = request.inputs[i]; + + const armnn::TensorInfo inputTensorInfo = m_Runtime->GetInputTensorInfo(m_NetworkId, i); + const armnn::Tensor inputTensor = GetTensorForRequestArgument(inputArg, inputTensorInfo, memPools); + + if (inputTensor.GetMemoryArea() == nullptr) + { + ALOGE("Cannot execute request. Error converting request input %u to tensor", i); + return V1_3::ErrorStatus::GENERAL_FAILURE; + } + + inputs.emplace_back(i, inputTensor); + } + + return V1_3::ErrorStatus::NONE; +} + +template +Return ArmnnPreparedModel_1_3::PrepareMemoryForOutputs( + armnn::OutputTensors& outputs, + std::vector &outputShapes, + const V1_3::Request& request, + const std::vector& memPools) +{ + outputs.reserve(request.outputs.size()); + for (unsigned int i = 0; i < request.outputs.size(); i++) + { + const auto& outputArg = request.outputs[i]; + + const armnn::TensorInfo outputTensorInfo = m_Runtime->GetOutputTensorInfo(m_NetworkId, i); + const armnn::Tensor outputTensor = GetTensorForRequestArgument(outputArg, outputTensorInfo, memPools); + if (outputTensor.GetMemoryArea() == nullptr) + { + ALOGE("Cannot execute request. Error converting request output %u to tensor", i); + return V1_3::ErrorStatus::GENERAL_FAILURE; + } + + const size_t outputSize = outputTensorInfo.GetNumBytes(); + const size_t bufferSize = memPools.at(outputArg.location.poolIndex).getHidlMemory().size(); + if (bufferSize < outputSize) + { + ALOGW("ArmnnPreparedModel_1_3::Execute failed"); + return V1_3::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE; + } + + outputs.emplace_back(i, outputTensor); + outputShapes[i] = ComputeShape(outputTensorInfo); + } + + return V1_3::ErrorStatus::NONE; +} + +template +std::tuple, Timing, std::string> + ArmnnPreparedModel_1_3::PrepareMemoryForIO(armnn::InputTensors& inputs, + armnn::OutputTensors& outputs, + std::vector& memPools, + const V1_3::Request& request) +{ + if (!setRunTimePoolInfosFromMemoryPools(&memPools, request.pools)) + { + return {ErrorStatus::GENERAL_FAILURE, {}, g_NoTiming, "ArmnnPreparedModel_1_3::execute"}; + } + + // add the inputs and outputs with their data + try + { + if (PrepareMemoryForInputs(inputs, request, memPools) != V1_3::ErrorStatus::NONE) + { + return {ErrorStatus::GENERAL_FAILURE, {}, g_NoTiming, "ArmnnPreparedModel_1_3::execute"}; + } + + std::vector outputShapes(request.outputs.size()); + + auto errorStatus = PrepareMemoryForOutputs(outputs, outputShapes, request, memPools); + if (errorStatus != V1_3::ErrorStatus::NONE) + { + return {errorStatus, outputShapes, g_NoTiming, "ArmnnPreparedModel_1_3::execute"}; + } + } + catch (armnn::Exception& e) + { + ALOGW("armnn::Exception caught while preparing for EnqueueWorkload: %s", e.what()); + return {ErrorStatus::GENERAL_FAILURE, {}, g_NoTiming, "ArmnnPreparedModel_1_3::execute"}; + } + catch (std::exception& e) + { + ALOGE("std::exception caught while preparing for EnqueueWorkload: %s", e.what()); + return {ErrorStatus::GENERAL_FAILURE, {}, g_NoTiming, "ArmnnPreparedModel_1_3::execute"}; + } + + return {V1_3::ErrorStatus::NONE, {}, g_NoTiming, "ArmnnPreparedModel_1_3::execute"}; +} + +template +template +Return ArmnnPreparedModel_1_3::ExecuteSynchronously(const V1_3::Request& request, + CallbackContext cbCtx) +{ + if (cbCtx.ctx.measureTimings == MeasureTiming::YES) + { + cbCtx.ctx.driverStart = Now(); + } + + if (!android::nn::validateRequest(convertToV1_3(request), m_Model)) + { + ALOGE("ArmnnPreparedModel_1_3::ExecuteSynchronously invalid request model"); + cbCtx.callback(V1_3::ErrorStatus::INVALID_ARGUMENT, + {}, + g_NoTiming, + "ArmnnPreparedModel_1_3::ExecuteSynchronously invalid request model"); + return Void(); + } + + if (!android::nn::validateRequest(request, m_Model)) + { + ALOGE("ArmnnPreparedModel_1_3::ExecuteSynchronously invalid request model"); + cbCtx.callback(V1_3::ErrorStatus::INVALID_ARGUMENT, + {}, + g_NoTiming, + "ArmnnPreparedModel_1_3::ExecuteSynchronously invalid request model"); + } + + + // map the memory pool into shared pointers + // use a shared memory pools vector on the heap, as it is passed to the request thread + auto memPools = std::make_shared>(); + + // allocate the tensors on the heap, as they are passed to the request thread + auto inputs = std::make_shared(); + auto outputs = std::make_shared(); + + auto [status, outputShapes, timing, message] = PrepareMemoryForIO(*inputs, *outputs, *memPools, request); + if (status != V1_3::ErrorStatus::NONE) + { + cbCtx.callback(status, outputShapes, timing, message); + } + + ALOGV("ArmnnPreparedModel_1_3::ExecuteSynchronously() before Execution"); + + ExecuteGraph(memPools, *inputs, *outputs, cbCtx); + return Void(); +} + +template +Return ArmnnPreparedModel_1_3::executeSynchronously(const V1_0::Request& request, + MeasureTiming measureTiming, + executeSynchronously_cb cb) +{ + ALOGV("ArmnnPreparedModel_1_3::executeSynchronously(): %s", GetModelSummary(m_Model).c_str()); + m_RequestCount++; + + if (cb == nullptr) + { + ALOGE("ArmnnPreparedModel_1_3::executeSynchronously invalid callback passed"); + return Void(); + } + + auto cbWrapper = [cb](V1_3::ErrorStatus errorStatus, + std::vector outputShapes, + const Timing& timing, + std::string) + { + cb(convertToV1_0(errorStatus), outputShapes, timing); + }; + + CallbackContext_1_3 cbCtx; + cbCtx.callback = cbWrapper; + cbCtx.ctx.measureTimings = measureTiming; + + ExecuteSynchronously(convertToV1_3(request), cbCtx); + return Void(); +} + +template +Return ArmnnPreparedModel_1_3::executeSynchronously_1_3(const V1_3::Request& request, + MeasureTiming measureTiming, + const V1_3::OptionalTimePoint& deadline, + executeSynchronously_1_3_cb cb) +{ + ALOGV("ArmnnPreparedModel_1_3::executeSynchronously_1_3(): %s", GetModelSummary(m_Model).c_str()); + m_RequestCount++; + + if (cb == nullptr) + { + ALOGE("ArmnnPreparedModel_1_3::executeSynchronously_1_3 invalid callback passed"); + return Void(); + } + + if (deadline.getDiscriminator() != OptionalTimePoint::hidl_discriminator::none) + { + ALOGE("ArmnnPreparedModel_1_3::executeSynchronously_1_3 invalid request model"); + cb(V1_3::ErrorStatus::INVALID_ARGUMENT, {}, g_NoTiming); + return Void(); + } + + auto cbWrapper = [cb](V1_3::ErrorStatus errorStatus, + std::vector outputShapes, + const Timing& timing, + std::string) + { + cb(errorStatus, outputShapes, timing); + }; + + CallbackContext_1_3 cbCtx; + cbCtx.callback = cbWrapper; + cbCtx.ctx.measureTimings = measureTiming; + + ExecuteSynchronously(request, cbCtx); + return Void(); +} + +template +Return ArmnnPreparedModel_1_3::configureExecutionBurst( + const sp& callback, + const MQDescriptorSync& requestChannel, + const MQDescriptorSync& resultChannel, + V1_3::IPreparedModel::configureExecutionBurst_cb cb) +{ + ALOGV("ArmnnPreparedModel_1_3::configureExecutionBurst"); + const sp burst = ExecutionBurstServer::create(callback, + requestChannel, + resultChannel, + this); + + if (burst == nullptr) + { + cb(V1_0::ErrorStatus::GENERAL_FAILURE, {}); + } + else + { + cb(V1_0::ErrorStatus::NONE, burst); + } + return Void(); +} + +template +template +bool ArmnnPreparedModel_1_3::ExecuteGraph( + std::shared_ptr>& pMemPools, + armnn::InputTensors& inputTensors, + armnn::OutputTensors& outputTensors, + CallbackContext cb) +{ + ALOGV("ArmnnPreparedModel_1_3::ExecuteGraph(...)"); + + TimePoint driverEnd, deviceStart, deviceEnd; + + DumpTensorsIfRequired("Input", inputTensors); + + std::vector outputShapes(outputTensors.size()); + for (unsigned int i = 0; i < outputTensors.size(); i++) + { + std::pair outputTensorPair = outputTensors[i]; + const armnn::Tensor outputTensor = outputTensorPair.second; + const armnn::TensorInfo outputTensorInfo = outputTensor.GetInfo(); + + outputShapes[i] = ComputeShape(outputTensorInfo); + } + + // run it + try + { + if (cb.ctx.measureTimings == MeasureTiming::YES) + { + deviceStart = Now(); + } + + armnn::Status status = m_Runtime->EnqueueWorkload(m_NetworkId, inputTensors, outputTensors); + + if (cb.ctx.measureTimings == MeasureTiming::YES) + { + deviceEnd = Now(); + } + if (status != armnn::Status::Success) + { + ALOGW("EnqueueWorkload failed"); + cb.callback(V1_3::ErrorStatus::GENERAL_FAILURE, {}, g_NoTiming, + "ArmnnPreparedModel_1_3::ExecuteGraph"); + return false; + } + } + catch (armnn::Exception& e) + { + ALOGW("armnn:Exception caught from EnqueueWorkload: %s", e.what()); + cb.callback(V1_3::ErrorStatus::GENERAL_FAILURE, {}, g_NoTiming, "ArmnnPreparedModel_1_3::ExecuteGraph"); + return false; + } + catch (std::exception& e) + { + ALOGE("std::exception caught from EnqueueWorkload: %s", e.what()); + cb.callback(V1_3::ErrorStatus::GENERAL_FAILURE, {}, g_NoTiming, "ArmnnPreparedModel_1_3::ExecuteGraph"); + return false; + } + + CommitPools(*pMemPools); + + DumpTensorsIfRequired("Output", outputTensors); + + if (cb.ctx.measureTimings == MeasureTiming::YES) + { + driverEnd = Now(); + Timing timing; + timing.timeOnDevice = MicrosecondsDuration(deviceEnd, deviceStart); + timing.timeInDriver = MicrosecondsDuration(driverEnd, cb.ctx.driverStart); + ALOGV("ArmnnPreparedModel_1_2::execute timing - Device = %lu Driver = %lu", timing.timeOnDevice, + timing.timeInDriver); + cb.callback(V1_3::ErrorStatus::NONE, outputShapes, timing, "ArmnnPreparedModel_1_3::ExecuteGraph"); + } else { + cb.callback(V1_3::ErrorStatus::NONE, outputShapes, g_NoTiming, "ArmnnPreparedModel_1_3::ExecuteGraph"); + } + + return true; +} + +template +bool ArmnnPreparedModel_1_3::ExecuteWithDummyInputs() +{ + std::vector> storage; + armnn::InputTensors inputTensors; + for (unsigned int i = 0; i < getMainModel(m_Model).inputIndexes.size(); i++) + { + const armnn::TensorInfo inputTensorInfo = m_Runtime->GetInputTensorInfo(m_NetworkId, i); + storage.emplace_back(inputTensorInfo.GetNumBytes()); + const armnn::ConstTensor inputTensor(inputTensorInfo, storage.back().data()); + + inputTensors.emplace_back(i, inputTensor); + } + + armnn::OutputTensors outputTensors; + for (unsigned int i = 0; i < getMainModel(m_Model).outputIndexes.size(); i++) + { + const armnn::TensorInfo outputTensorInfo = m_Runtime->GetOutputTensorInfo(m_NetworkId, i); + storage.emplace_back(outputTensorInfo.GetNumBytes()); + const armnn::Tensor outputTensor(outputTensorInfo, storage.back().data()); + + outputTensors.emplace_back(i, outputTensor); + } + + auto nullCallback = [](V1_3::ErrorStatus, std::vector, const Timing&, std::string) {}; + CallbackContext_1_3 callbackContext; + callbackContext.callback = nullCallback; + callbackContext.ctx.measureTimings = MeasureTiming::NO; + auto memPools = std::make_shared>(); + return ExecuteGraph(memPools, + inputTensors, + outputTensors, + callbackContext); +} + +template +Return ArmnnPreparedModel_1_3::Execute(const V1_3::Request& request, + MeasureTiming measureTiming, + CallbackAsync_1_3 callback) +{ + ExecutionContext_1_3 ctx; + if (measureTiming == MeasureTiming::YES) + { + ctx.measureTimings = measureTiming; + ctx.driverStart = Now(); + } + + ALOGV("ArmnnPreparedModel_1_3::execute(): %s", GetModelSummary(m_Model).c_str()); + m_RequestCount++; + + if (!android::nn::validateRequest(request, m_Model)) + { + callback(V1_3::ErrorStatus::INVALID_ARGUMENT, {}, g_NoTiming, "ArmnnPreparedModel_1_3::execute"); + return V1_3::ErrorStatus::INVALID_ARGUMENT; + } + + if (!m_RequestInputsAndOutputsDumpDir.empty()) + { + ALOGD("Dumping inputs and outputs for request %" PRIuPTR, reinterpret_cast(&callback)); + } + + // map the memory pool into shared pointers + // use a shared memory pools vector on the heap, as it is passed to the request thread + auto memPools = std::make_shared>(); + + // allocate the tensors on the heap, as they are passed to the request thread + auto inputTensors = std::make_shared(); + auto outputTensors = std::make_shared(); + + auto [status, outShapes, timing, message] = PrepareMemoryForIO(*inputTensors, *outputTensors, + *memPools, request); + if (status != V1_3::ErrorStatus::NONE) + { + callback(status, outShapes, timing, message); + } + + switch(status) + { + case V1_3::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE: + return V1_3::ErrorStatus::NONE; + case V1_3::ErrorStatus::GENERAL_FAILURE: + return V1_3::ErrorStatus::GENERAL_FAILURE; + default: + {} + } + + ALOGV("ArmnnPreparedModel_1_3::execute(...) before PostMsg"); + + // post the request for asynchronous execution + CallbackContext_1_3 cb; + cb.callback = callback; + cb.ctx = ctx; + m_RequestThread.PostMsg(this, memPools, inputTensors, outputTensors, cb); + ALOGV("ArmnnPreparedModel_1_3::execute(...) after PostMsg"); + return V1_3::ErrorStatus::NONE; +} + +#ifdef ARMNN_ANDROID_NN_V1_3 +template class ArmnnPreparedModel_1_3; +template bool ArmnnPreparedModel_1_3::ExecuteGraph( + std::shared_ptr>& pMemPools, + armnn::InputTensors& pInputTensors, + armnn::OutputTensors& pOutputTensors, + CallbackContext_1_3 cb); +#endif + +} // namespace armnn_driver -- cgit v1.2.1