blob: 3aad955b645f2fa38184b7fbe46c5e8f590c2ef6 [file] [log] [blame]
telsoa015307bc12018-03-09 13:51:08 +00001//
2// Copyright © 2017 Arm Ltd. All rights reserved.
3// See LICENSE file in the project root for full license information.
4//
5
6#define LOG_TAG "ArmnnDriver"
7
8#include "ArmnnPreparedModel.hpp"
9#include "Utils.hpp"
10
11#include <boost/format.hpp>
12#include <log/log.h>
13#include <OperationsUtils.h>
14
surmeh01deb3bdb2018-07-05 12:06:04 +010015#if defined(ARMNN_ANDROID_P)
16// The headers of the ML framework have changed between Android O and Android P.
17// The validation functions have been moved into their own header, ValidateHal.h.
18#include <ValidateHal.h>
19#endif
20
telsoa015307bc12018-03-09 13:51:08 +000021#include <cassert>
22#include <cinttypes>
23
24using namespace android;
25
26namespace
27{
28using namespace armnn_driver;
29
30void NotifyCallbackAndCheck(const ::android::sp<IExecutionCallback>& callback, ErrorStatus errorStatus,
31 std::string callingFunction)
32{
33 Return<void> returned = callback->notify(errorStatus);
34 // This check is required, if the callback fails and it isn't checked it will bring down the service
35 if (!returned.isOk())
36 {
37 ALOGE("ArmnnDriver::%s: hidl callback failed to return properly: %s",
38 callingFunction.c_str(), returned.description().c_str());
39 }
40}
41
42bool ValidateRequestArgument(const RequestArgument& requestArg, const armnn::TensorInfo& tensorInfo)
43{
44 if (requestArg.dimensions.size() != 0)
45 {
46 if (requestArg.dimensions.size() != tensorInfo.GetNumDimensions())
47 {
48 ALOGE("Mismatched dimensions (request argument: %zu, expected: %u)",
49 requestArg.dimensions.size(), tensorInfo.GetNumDimensions());
50 return false;
51 }
52
53 for (unsigned int d = 0; d < tensorInfo.GetNumDimensions(); ++d)
54 {
55 if (requestArg.dimensions[d] != tensorInfo.GetShape()[d])
56 {
57 ALOGE("Mismatched size for dimension %d (request argument: %u, expected %u)",
58 d, requestArg.dimensions[d], tensorInfo.GetShape()[d]);
59 return false;
60 }
61 }
62 }
63
64 return true;
65}
66
67armnn::Tensor GetTensorForRequestArgument(const RequestArgument& requestArg,
68 const armnn::TensorInfo& tensorInfo,
69 const std::vector<::android::nn::RunTimePoolInfo>& requestPools)
70{
71 if (!ValidateRequestArgument(requestArg, tensorInfo))
72 {
73 return armnn::Tensor();
74 }
75
76 return armnn::Tensor(tensorInfo, GetMemoryFromPool(requestArg.location, requestPools));
77}
78
79inline std::string BuildTensorName(const char* tensorNamePrefix, std::size_t index)
80{
81 return tensorNamePrefix + std::to_string(index);
82}
83
84}
85
86namespace armnn_driver
87{
88
89RequestThread ArmnnPreparedModel::m_RequestThread;
90
91template <typename TensorBindingCollection>
92void ArmnnPreparedModel::DumpTensorsIfRequired(char const* tensorNamePrefix,
93 const TensorBindingCollection& tensorBindings)
94{
95 if (!m_RequestInputsAndOutputsDumpDir.empty())
96 {
97 const std::string requestName = boost::str(boost::format("%1%_%2%.dump") % m_NetworkId % m_RequestCount);
98 for (std::size_t i = 0u; i < tensorBindings.size(); ++i)
99 {
100 DumpTensor(m_RequestInputsAndOutputsDumpDir,
101 requestName,
102 BuildTensorName(tensorNamePrefix, i),
103 tensorBindings[i].second);
104 }
105 }
106}
107
108ArmnnPreparedModel::ArmnnPreparedModel(armnn::NetworkId networkId,
109 armnn::IRuntime* runtime,
surmeh01deb3bdb2018-07-05 12:06:04 +0100110 const V1_0::Model& model,
telsoa015307bc12018-03-09 13:51:08 +0000111 const std::string& requestInputsAndOutputsDumpDir)
112: m_NetworkId(networkId)
113, m_Runtime(runtime)
114, m_Model(model)
115, m_RequestCount(0)
116, m_RequestInputsAndOutputsDumpDir(requestInputsAndOutputsDumpDir)
117{
118}
119
120ArmnnPreparedModel::~ArmnnPreparedModel()
121{
122 //unload the network associated with this model
123 m_Runtime->UnloadNetwork(m_NetworkId);
124}
125
126Return<ErrorStatus> ArmnnPreparedModel::execute(const Request& request,
127 const ::android::sp<IExecutionCallback>& callback)
128{
129 ALOGV("ArmnnPreparedModel::execute(): %s", GetModelSummary(m_Model).c_str());
130 m_RequestCount++;
131
132 if (callback.get() == nullptr) {
133 ALOGE("ArmnnPreparedModel::execute invalid callback passed");
134 return ErrorStatus::INVALID_ARGUMENT;
135 }
136
137 if (!android::nn::validateRequest(request, m_Model))
138 {
139 NotifyCallbackAndCheck(callback, ErrorStatus::INVALID_ARGUMENT, "ArmnnPreparedModel::execute");
140 return ErrorStatus::INVALID_ARGUMENT;
141 }
142
143 if (!m_RequestInputsAndOutputsDumpDir.empty())
144 {
145 ALOGD("Dumping inputs and outputs for request %" PRIuPTR, reinterpret_cast<std::uintptr_t>(callback.get()));
146 }
147
148 // allocate the tensors on the heap, as they are passed to the request thread
149 auto pInputTensors = std::make_shared<armnn::InputTensors>();
150 auto pOutputTensors = std::make_shared<armnn::OutputTensors>();
151
152 // map the memory pool into shared pointers
153 // use a shared memory pools vector on the heap, as it is passed to the request thread
154 auto pMemPools = std::make_shared<std::vector<android::nn::RunTimePoolInfo>>();
155 if (!setRunTimePoolInfosFromHidlMemories(pMemPools.get(), request.pools))
156 {
157 NotifyCallbackAndCheck(callback, ErrorStatus::GENERAL_FAILURE, "ArmnnPreparedModel::execute");
158 return ErrorStatus::GENERAL_FAILURE;
159 }
160
161 // add the inputs and outputs with their data
162 try
163 {
164 pInputTensors->reserve(request.inputs.size());
165 for (unsigned int i = 0; i < request.inputs.size(); i++)
166 {
167 const auto& inputArg = request.inputs[i];
168
169 const armnn::TensorInfo inputTensorInfo = m_Runtime->GetInputTensorInfo(m_NetworkId, i);
170 const armnn::Tensor inputTensor = GetTensorForRequestArgument(inputArg, inputTensorInfo, *pMemPools);
171 if (inputTensor.GetMemoryArea() == nullptr)
172 {
173 ALOGE("Cannot execute request. Error converting request input %u to tensor", i);
174 return ErrorStatus::GENERAL_FAILURE;
175 }
176
177 pInputTensors->emplace_back(i, inputTensor);
178 }
179
180 pOutputTensors->reserve(request.outputs.size());
181 for (unsigned int i = 0; i < request.outputs.size(); i++)
182 {
183 const auto& outputArg = request.outputs[i];
184
185 const armnn::TensorInfo outputTensorInfo = m_Runtime->GetOutputTensorInfo(m_NetworkId, i);
186 const armnn::Tensor outputTensor = GetTensorForRequestArgument(outputArg, outputTensorInfo, *pMemPools);
187 if (outputTensor.GetMemoryArea() == nullptr)
188 {
189 ALOGE("Cannot execute request. Error converting request output %u to tensor", i);
190 return ErrorStatus::GENERAL_FAILURE;
191 }
192
193 pOutputTensors->emplace_back(i, outputTensor);
194 }
195 }
196 catch (armnn::Exception& e)
197 {
198 ALOGW("armnn::Exception caught while preparing for EnqueueWorkload: %s", e.what());
199 NotifyCallbackAndCheck(callback, ErrorStatus::GENERAL_FAILURE, "ArmnnPreparedModel::execute");
200 return ErrorStatus::GENERAL_FAILURE;
201 }
202
203 ALOGV("ArmnnPreparedModel::execute(...) before PostMsg");
204 // post the request for asynchronous execution
205 m_RequestThread.PostMsg(this, pMemPools, pInputTensors, pOutputTensors, callback);
206 ALOGV("ArmnnPreparedModel::execute(...) after PostMsg");
207
208 return ErrorStatus::NONE; // successfully queued
209}
210
211void ArmnnPreparedModel::ExecuteGraph(std::shared_ptr<std::vector<::android::nn::RunTimePoolInfo>>& pMemPools,
212 std::shared_ptr<armnn::InputTensors>& pInputTensors,
213 std::shared_ptr<armnn::OutputTensors>& pOutputTensors,
214 const ::android::sp<IExecutionCallback>& callback)
215{
216 ALOGV("ArmnnPreparedModel::ExecuteGraph(...)");
217
218 DumpTensorsIfRequired("Input", *pInputTensors);
219
220 // run it
221 try
222 {
223 m_Runtime->EnqueueWorkload(m_NetworkId, *pInputTensors, *pOutputTensors);
224 }
225 catch (armnn::Exception& e)
226 {
227 ALOGW("armnn::Exception caught from EnqueueWorkload: %s", e.what());
228 NotifyCallbackAndCheck(callback, ErrorStatus::GENERAL_FAILURE, "ArmnnPreparedModel::ExecuteGraph");
229 return;
230 }
231
232 DumpTensorsIfRequired("Output", *pOutputTensors);
233
234 // Commit output buffers.
235 // Note that we update *all* pools, even if they aren't actually used as outputs -
236 // this is simpler and is what the CpuExecutor does.
237 for (android::nn::RunTimePoolInfo& pool : *pMemPools)
238 {
239 pool.update();
240 }
241
242 NotifyCallbackAndCheck(callback, ErrorStatus::NONE, "ExecuteGraph");
243}
244
245void ArmnnPreparedModel::ExecuteWithDummyInputs()
246{
247 std::vector<std::vector<char>> storage;
248 armnn::InputTensors inputTensors;
249 for (unsigned int i = 0; i < m_Model.inputIndexes.size(); i++)
250 {
251 const armnn::TensorInfo inputTensorInfo = m_Runtime->GetInputTensorInfo(m_NetworkId, i);
252 storage.emplace_back(inputTensorInfo.GetNumBytes());
253 const armnn::ConstTensor inputTensor(inputTensorInfo, storage.back().data());
254
255 inputTensors.emplace_back(i, inputTensor);
256 }
257
258 armnn::OutputTensors outputTensors;
259 for (unsigned int i = 0; i < m_Model.outputIndexes.size(); i++)
260 {
261 const armnn::TensorInfo outputTensorInfo = m_Runtime->GetOutputTensorInfo(m_NetworkId, i);
262 storage.emplace_back(outputTensorInfo.GetNumBytes());
263 const armnn::Tensor outputTensor(outputTensorInfo, storage.back().data());
264
265 outputTensors.emplace_back(i, outputTensor);
266 }
267
268 try
269 {
270 m_Runtime->EnqueueWorkload(m_NetworkId, inputTensors, outputTensors);
271 }
272 catch (armnn::Exception& e)
273 {
274 ALOGW("ExecuteWithDummyInputs: armnn::Exception caught from EnqueueWorkload: %s", e.what());
275 }
276}
277
surmeh01deb3bdb2018-07-05 12:06:04 +0100278AndroidNnCpuExecutorPreparedModel::AndroidNnCpuExecutorPreparedModel(const V1_0::Model& model,
telsoa015307bc12018-03-09 13:51:08 +0000279 const std::string& requestInputsAndOutputsDumpDir)
280: m_Model(model)
281, m_RequestInputsAndOutputsDumpDir(requestInputsAndOutputsDumpDir)
282, m_RequestCount(0)
283{
284}
285
286bool AndroidNnCpuExecutorPreparedModel::Initialize()
287{
288 return setRunTimePoolInfosFromHidlMemories(&m_ModelPoolInfos, m_Model.pools);
289}
290
291Return<ErrorStatus> AndroidNnCpuExecutorPreparedModel::execute(const Request& request,
292 const ::android::sp<IExecutionCallback>& callback)
293{
294 m_RequestCount++;
295 std::vector<android::nn::RunTimePoolInfo> requestPoolInfos;
296
297 if (!setRunTimePoolInfosFromHidlMemories(&requestPoolInfos, request.pools))
298 {
299 NotifyCallbackAndCheck(callback, ErrorStatus::GENERAL_FAILURE, "AndroidNnCpuExecutorPreparedModel::execute");
300 return ErrorStatus::GENERAL_FAILURE;
301 }
302
303 if (!m_RequestInputsAndOutputsDumpDir.empty())
304 {
305 ALOGD("Dumping inputs and outputs for request %" PRIuPTR, reinterpret_cast<std::uintptr_t>(callback.get()));
306 }
307
308 DumpTensorsIfRequired(
309 "Input",
310 m_Model.inputIndexes,
311 request.inputs,
312 requestPoolInfos);
313
314 android::nn::CpuExecutor executor;
315 const int n = executor.run(m_Model, request, m_ModelPoolInfos, requestPoolInfos);
316 ErrorStatus executionStatus =
317 n == ANEURALNETWORKS_NO_ERROR ? ErrorStatus::NONE : ErrorStatus::GENERAL_FAILURE;
318
319 DumpTensorsIfRequired(
320 "Output",
321 m_Model.outputIndexes,
322 request.outputs,
323 requestPoolInfos);
324
325 NotifyCallbackAndCheck(callback, ErrorStatus::GENERAL_FAILURE, "AndroidNnCpuExecutorPreparedModel::execute");
326 return executionStatus;
327}
328
329void AndroidNnCpuExecutorPreparedModel::DumpTensorsIfRequired(
330 char const* tensorNamePrefix,
331 const hidl_vec<uint32_t>& operandIndices,
332 const hidl_vec<RequestArgument>& requestArgs,
333 const std::vector<android::nn::RunTimePoolInfo>& requestPoolInfos)
334{
335 if (m_RequestInputsAndOutputsDumpDir.empty())
336 {
337 return;
338 }
339
340 for (std::size_t i = 0; i < requestArgs.size(); ++i)
341 {
342 const Operand& operand = m_Model.operands[operandIndices[i]];
343 const armnn::TensorInfo tensorInfo = GetTensorInfoForOperand(operand);
344 const armnn::Tensor tensor = GetTensorForRequestArgument(requestArgs[i], tensorInfo, requestPoolInfos);
345 const std::string tensorName = BuildTensorName(tensorNamePrefix, i);
346 if (tensor.GetMemoryArea() != nullptr)
347 {
348 std::string requestName = boost::str(boost::format("%1%_%2%.dump") % this % m_RequestCount);
349 DumpTensor(m_RequestInputsAndOutputsDumpDir, requestName, tensorName, tensor);
350 }
351 else
352 {
353 ALOGE("Cannot dump tensor %s. An error occurred converting the associated request argument to a tensor.",
354 tensorName.c_str());
355 }
356 }
357}
358
359}