xref: /aosp_15_r20/external/android-nn-driver/ArmnnPreparedModel.cpp (revision 3e777be0405cee09af5d5785ff37f7cfb5bee59a)
1 //
2 // Copyright © 2017-2023 Arm Ltd and Contributors. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 
6 #define LOG_TAG "ArmnnDriver"
7 
8 #include "ArmnnPreparedModel.hpp"
9 #include "Utils.hpp"
10 
11 #include <armnn/Types.hpp>
12 
13 #include <log/log.h>
14 #include <OperationsUtils.h>
15 #include <ValidateHal.h>
16 
17 #include <chrono>
18 #include <cinttypes>
19 
20 #ifdef ARMNN_ANDROID_S
21 #include <LegacyUtils.h>
22 #endif
23 
24 using namespace android;
25 
26 namespace
27 {
28 using namespace armnn_driver;
29 
NotifyCallbackAndCheck(const::android::sp<V1_0::IExecutionCallback> & callback,V1_0::ErrorStatus errorStatus,std::string callingFunction)30 void NotifyCallbackAndCheck(const ::android::sp<V1_0::IExecutionCallback>& callback, V1_0::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 
ValidateRequestArgument(const V1_0::RequestArgument & requestArg,const armnn::TensorInfo & tensorInfo)42 bool ValidateRequestArgument(const V1_0::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] != 0 && 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 
GetTensorForRequestArgument(const V1_0::RequestArgument & requestArg,const armnn::TensorInfo & tensorInfo,const std::vector<::android::nn::RunTimePoolInfo> & requestPools)67 armnn::Tensor GetTensorForRequestArgument(const V1_0::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 
BuildTensorName(const char * tensorNamePrefix,std::size_t index)79 inline std::string BuildTensorName(const char* tensorNamePrefix, std::size_t index)
80 {
81     return tensorNamePrefix + std::to_string(index);
82 }
83 
84 } // anonymous namespace
85 
86 using namespace android::hardware;
87 
88 namespace armnn_driver
89 {
90 template<typename HalVersion>
91 RequestThread<ArmnnPreparedModel, HalVersion, CallbackContext_1_0>
92     ArmnnPreparedModel<HalVersion>::m_RequestThread;
93 
94 template<typename HalVersion>
95 std::unique_ptr<armnn::Threadpool> ArmnnPreparedModel<HalVersion>::m_Threadpool(nullptr);
96 
97 template<typename HalVersion>
98 template <typename TensorBindingCollection>
DumpTensorsIfRequired(char const * tensorNamePrefix,const TensorBindingCollection & tensorBindings)99 void ArmnnPreparedModel<HalVersion>::DumpTensorsIfRequired(char const* tensorNamePrefix,
100                                                            const TensorBindingCollection& tensorBindings)
101 {
102     if (!m_RequestInputsAndOutputsDumpDir.empty())
103     {
104         const std::string requestName = std::to_string(m_NetworkId) + "_" + std::to_string(m_RequestCount) + ".dump";
105         for (std::size_t i = 0u; i < tensorBindings.size(); ++i)
106         {
107             DumpTensor(m_RequestInputsAndOutputsDumpDir,
108                 requestName,
109                 BuildTensorName(tensorNamePrefix, i),
110                 tensorBindings[i].second);
111         }
112     }
113 }
114 
115 template<typename HalVersion>
ArmnnPreparedModel(armnn::NetworkId networkId,armnn::IRuntime * runtime,const HalModel & model,const std::string & requestInputsAndOutputsDumpDir,const bool gpuProfilingEnabled,const bool asyncModelExecutionEnabled,const unsigned int numberOfThreads,const bool importEnabled,const bool exportEnabled)116 ArmnnPreparedModel<HalVersion>::ArmnnPreparedModel(armnn::NetworkId networkId,
117                                                    armnn::IRuntime* runtime,
118                                                    const HalModel& model,
119                                                    const std::string& requestInputsAndOutputsDumpDir,
120                                                    const bool gpuProfilingEnabled,
121                                                    const bool asyncModelExecutionEnabled,
122                                                    const unsigned int numberOfThreads,
123                                                    const bool importEnabled,
124                                                    const bool exportEnabled)
125     : m_NetworkId(networkId)
126     , m_Runtime(runtime)
127     , m_Model(model)
128     , m_RequestCount(0)
129     , m_RequestInputsAndOutputsDumpDir(requestInputsAndOutputsDumpDir)
130     , m_GpuProfilingEnabled(gpuProfilingEnabled)
131     , m_AsyncModelExecutionEnabled(asyncModelExecutionEnabled)
132     , m_EnableImport(importEnabled)
133     , m_EnableExport(exportEnabled)
134 {
135     // Enable profiling if required.
136     m_Runtime->GetProfiler(m_NetworkId)->EnableProfiling(m_GpuProfilingEnabled);
137 
138     if (m_AsyncModelExecutionEnabled)
139     {
140         std::vector<std::shared_ptr<armnn::IWorkingMemHandle>> memHandles;
141         for (unsigned int i=0; i < numberOfThreads; ++i)
142         {
143             memHandles.emplace_back(m_Runtime->CreateWorkingMemHandle(networkId));
144         }
145 
146         if (!m_Threadpool)
147         {
148             m_Threadpool = std::make_unique<armnn::Threadpool>(numberOfThreads, runtime, memHandles);
149         }
150         else
151         {
152             m_Threadpool->LoadMemHandles(memHandles);
153         }
154 
155         m_WorkingMemHandle = memHandles.back();
156     }
157 }
158 
159 template<typename HalVersion>
~ArmnnPreparedModel()160 ArmnnPreparedModel<HalVersion>::~ArmnnPreparedModel()
161 {
162     // Get a hold of the profiler used by this model.
163     std::shared_ptr<armnn::IProfiler> profiler = m_Runtime->GetProfiler(m_NetworkId);
164     if (profiler && m_GpuProfilingEnabled)
165     {
166         // Dump the profiling info to a file if required.
167         DumpJsonProfilingIfRequired(m_GpuProfilingEnabled, m_RequestInputsAndOutputsDumpDir, m_NetworkId,
168                                     profiler.get());
169     }
170 
171     // Unload the network associated with this model.
172     m_Runtime->UnloadNetwork(m_NetworkId);
173 
174     // Unload the network memhandles from the threadpool
175     if (m_AsyncModelExecutionEnabled)
176     {
177         m_Threadpool->UnloadMemHandles(m_NetworkId);
178     }
179 }
180 
181 template<typename HalVersion>
execute(const V1_0::Request & request,const::android::sp<V1_0::IExecutionCallback> & callback)182 Return<V1_0::ErrorStatus> ArmnnPreparedModel<HalVersion>::execute(
183     const V1_0::Request& request,
184     const ::android::sp<V1_0::IExecutionCallback>& callback)
185 {
186     ALOGV("ArmnnPreparedModel::execute(): %s", GetModelSummary(m_Model).c_str());
187     m_RequestCount++;
188 
189     if (callback.get() == nullptr) {
190         ALOGE("ArmnnPreparedModel::execute invalid callback passed");
191         return V1_0::ErrorStatus::INVALID_ARGUMENT;
192     }
193 
194     if (!android::nn::validateRequest(request, m_Model))
195     {
196         NotifyCallbackAndCheck(callback, V1_0::ErrorStatus::INVALID_ARGUMENT, "ArmnnPreparedModel::execute");
197         return V1_0::ErrorStatus::INVALID_ARGUMENT;
198     }
199 
200     if (!m_RequestInputsAndOutputsDumpDir.empty())
201     {
202         ALOGD("Dumping inputs and outputs for request %" PRIuPTR, reinterpret_cast<std::uintptr_t>(callback.get()));
203     }
204 
205     // allocate the tensors on the heap, as they are passed to the request thread
206     auto pInputTensors = std::make_shared<armnn::InputTensors>();
207     auto pOutputTensors = std::make_shared<armnn::OutputTensors>();
208 
209     // map the memory pool into shared pointers
210     // use a shared memory pools vector on the heap, as it is passed to the request thread
211     auto pMemPools = std::make_shared<std::vector<android::nn::RunTimePoolInfo>>();
212 #if !defined(ARMNN_ANDROID_S)
213     if (!setRunTimePoolInfosFromHidlMemories(pMemPools.get(), request.pools))
214 #else
215     if (!setRunTimePoolInfosFromCanonicalMemories(pMemPools.get(), uncheckedConvert(request.pools)))
216 #endif
217     {
218         NotifyCallbackAndCheck(callback, V1_0::ErrorStatus::GENERAL_FAILURE, "ArmnnPreparedModel::execute");
219         return V1_0::ErrorStatus::GENERAL_FAILURE;
220     }
221 
222     // add the inputs and outputs with their data
223     try
224     {
225         pInputTensors->reserve(request.inputs.size());
226         for (unsigned int i = 0; i < request.inputs.size(); i++)
227         {
228             const auto& inputArg = request.inputs[i];
229             armnn::TensorInfo inputTensorInfo = m_Runtime->GetInputTensorInfo(m_NetworkId, i);
230             // pInputTensors (of type InputTensors) is composed of a vector of ConstTensors.
231             // Therefore, set all TensorInfo isConstant parameters of input Tensors to true.
232             inputTensorInfo.SetConstant();
233             auto result = ValidateRequestArgument<V1_0::ErrorStatus, V1_0::Request>(request,
234                                                                                     inputTensorInfo,
235                                                                                     inputArg,
236                                                                                     "input");
237             if (result != V1_0::ErrorStatus::NONE)
238             {
239                 return result;
240             }
241 
242             const armnn::Tensor inputTensor = GetTensorForRequestArgument(inputArg, inputTensorInfo, *pMemPools);
243             if (inputTensor.GetMemoryArea() == nullptr)
244             {
245                 ALOGE("Cannot execute request. Error converting request input %u to tensor", i);
246                 return V1_0::ErrorStatus::GENERAL_FAILURE;
247             }
248 
249             pInputTensors->emplace_back(i, inputTensor);
250         }
251 
252         pOutputTensors->reserve(request.outputs.size());
253         for (unsigned int i = 0; i < request.outputs.size(); i++)
254         {
255             const auto& outputArg = request.outputs[i];
256             const armnn::TensorInfo outputTensorInfo = m_Runtime->GetOutputTensorInfo(m_NetworkId, i);
257             auto result = ValidateRequestArgument<V1_0::ErrorStatus, V1_0::Request>(request,
258                                                                                     outputTensorInfo,
259                                                                                     outputArg,
260                                                                                     "output");
261 
262             if (result != V1_0::ErrorStatus::NONE)
263             {
264                 return result;
265             }
266 
267             const armnn::Tensor outputTensor = GetTensorForRequestArgument(outputArg, outputTensorInfo, *pMemPools);
268             if (outputTensor.GetMemoryArea() == nullptr)
269             {
270                 ALOGE("Cannot execute request. Error converting request output %u to tensor", i);
271                 return V1_0::ErrorStatus::GENERAL_FAILURE;
272             }
273 
274             pOutputTensors->emplace_back(i, outputTensor);
275         }
276     }
277     catch (armnn::Exception& e)
278     {
279         ALOGW("armnn::Exception caught while preparing for EnqueueWorkload: %s", e.what());
280         NotifyCallbackAndCheck(callback, V1_0::ErrorStatus::GENERAL_FAILURE, "ArmnnPreparedModel::execute");
281         return V1_0::ErrorStatus::GENERAL_FAILURE;
282     }
283     catch (std::exception& e)
284     {
285         ALOGE("std::exception caught while preparing for EnqueueWorkload: %s", e.what());
286         NotifyCallbackAndCheck(callback, V1_0::ErrorStatus::GENERAL_FAILURE, "ArmnnPreparedModel::execute");
287         return V1_0::ErrorStatus::GENERAL_FAILURE;
288     }
289 
290     auto cb = [callback](V1_0::ErrorStatus errorStatus, std::string callingFunction)
291     {
292         NotifyCallbackAndCheck(callback, errorStatus, callingFunction);
293     };
294 
295     CallbackContext_1_0 armnnCb;
296     armnnCb.callback = cb;
297 
298     if (m_AsyncModelExecutionEnabled)
299     {
300         ALOGV("ArmnnPreparedModel::execute(...) before ScheduleGraphForExecution");
301         ScheduleGraphForExecution(pMemPools, pInputTensors, pOutputTensors, armnnCb);
302         ALOGV("ArmnnPreparedModel::execute(...) after ScheduleGraphForExecution");
303         return V1_0::ErrorStatus::NONE;
304     }
305 
306     // post the request for asynchronous execution
307     ALOGV("ArmnnPreparedModel::execute(...) before PostMsg");
308     m_RequestThread.PostMsg(this, pMemPools, pInputTensors, pOutputTensors, armnnCb);
309     ALOGV("ArmnnPreparedModel::execute(...) after PostMsg");
310     return V1_0::ErrorStatus::NONE; // successfully queued
311 }
312 
313 template<typename HalVersion>
ExecuteGraph(std::shared_ptr<std::vector<::android::nn::RunTimePoolInfo>> & pMemPools,armnn::InputTensors & inputTensors,armnn::OutputTensors & outputTensors,CallbackContext_1_0 cb)314 void ArmnnPreparedModel<HalVersion>::ExecuteGraph(
315         std::shared_ptr<std::vector<::android::nn::RunTimePoolInfo>>& pMemPools,
316         armnn::InputTensors& inputTensors,
317         armnn::OutputTensors& outputTensors,
318         CallbackContext_1_0 cb)
319 {
320     ALOGV("ArmnnPreparedModel::ExecuteGraph(...)");
321     // Capture the graph execution start time.
322     std::chrono::time_point<std::chrono::system_clock> graphExecutionStart = std::chrono::system_clock::now();
323 
324     DumpTensorsIfRequired("Input", inputTensors);
325 
326     // run it
327     try
328     {
329         armnn::Status status;
330         if (m_AsyncModelExecutionEnabled)
331         {
332             ALOGW("ArmnnPreparedModel::ExecuteGraph m_AsyncModelExecutionEnabled true");
333             status = m_Runtime->Execute(*m_WorkingMemHandle, inputTensors, outputTensors);
334         }
335         else
336         {
337             ALOGW("ArmnnPreparedModel::ExecuteGraph m_AsyncModelExecutionEnabled false");
338             // Create a vector of Input and Output Ids which can be imported. An empty vector means all will be copied.
339             std::vector<armnn::ImportedInputId> importedInputIds;
340             if (m_EnableImport)
341             {
342                 importedInputIds =  m_Runtime->ImportInputs(m_NetworkId, inputTensors, armnn::MemorySource::Malloc);
343             }
344             std::vector<armnn::ImportedOutputId> importedOutputIds;
345             if (m_EnableExport)
346             {
347                 importedOutputIds = m_Runtime->ImportOutputs(m_NetworkId, outputTensors, armnn::MemorySource::Malloc);
348             }
349             status = m_Runtime->EnqueueWorkload(m_NetworkId, inputTensors, outputTensors,
350                                                 importedInputIds, importedOutputIds);
351         }
352         if (status != armnn::Status::Success)
353         {
354             ALOGW("EnqueueWorkload failed");
355             cb.callback(V1_0::ErrorStatus::GENERAL_FAILURE, "ArmnnPreparedModel::ExecuteGraph");
356             return;
357         }
358     }
359     catch (armnn::Exception& e)
360     {
361         ALOGW("armnn::Exception caught from EnqueueWorkload: %s", e.what());
362         cb.callback(V1_0::ErrorStatus::GENERAL_FAILURE, "ArmnnPreparedModel::ExecuteGraph");
363         return;
364     }
365     catch (std::exception& e)
366     {
367         ALOGE("std::exception caught from EnqueueWorkload: %s", e.what());
368         cb.callback(V1_0::ErrorStatus::GENERAL_FAILURE, "ArmnnPreparedModel::ExecuteGraph");
369         return;
370     }
371 
372     DumpTensorsIfRequired("Output", outputTensors);
373 
374     // Commit output buffers.
375     // Note that we update *all* pools, even if they aren't actually used as outputs -
376     // this is simpler and is what the CpuExecutor does.
377     for (android::nn::RunTimePoolInfo& pool : *pMemPools)
378     {
379         // Type android::nn::RunTimePoolInfo has changed between Android P & Q and Android R, where
380         // update() has been removed and flush() added.
381         #if defined(ARMNN_ANDROID_R) || defined(ARMNN_ANDROID_S) // Use the new Android implementation.
382             pool.flush();
383         #else
384             pool.update();
385         #endif
386     }
387 
388     // Log the total time in this call. This is a good number to compare to that printed out by
389     // RuntimeImpl::EnqueueWorkload. The difference should be the execution overhead of the driver.
390     ALOGI("ArmnnPreparedModel::ExecuteGraph Execution time = %lld µs",
391            std::chrono::duration_cast<std::chrono::microseconds>
392           (std::chrono::system_clock::now() - graphExecutionStart).count());
393 
394     cb.callback(V1_0::ErrorStatus::NONE, "ExecuteGraph");
395 }
396 
397 template<typename HalVersion>
ExecuteWithDummyInputs()398 bool ArmnnPreparedModel<HalVersion>::ExecuteWithDummyInputs()
399 {
400     std::vector<std::vector<char>> storage;
401     armnn::InputTensors inputTensors;
402     for (unsigned int i = 0; i < getMainModel(m_Model).inputIndexes.size(); i++)
403     {
404         armnn::TensorInfo inputTensorInfo = m_Runtime->GetInputTensorInfo(m_NetworkId, i);
405         // pInputTensors (of type InputTensors) is composed of a vector of ConstTensors.
406         // Therefore, set all TensorInfo isConstant parameters of input Tensors to true.
407         inputTensorInfo.SetConstant();
408 
409         storage.emplace_back(inputTensorInfo.GetNumBytes());
410         const armnn::ConstTensor inputTensor(inputTensorInfo, storage.back().data());
411 
412         inputTensors.emplace_back(i, inputTensor);
413     }
414 
415     armnn::OutputTensors outputTensors;
416     for (unsigned int i = 0; i < getMainModel(m_Model).outputIndexes.size(); i++)
417     {
418         const armnn::TensorInfo outputTensorInfo = m_Runtime->GetOutputTensorInfo(m_NetworkId, i);
419         storage.emplace_back(outputTensorInfo.GetNumBytes());
420         const armnn::Tensor outputTensor(outputTensorInfo, storage.back().data());
421 
422         outputTensors.emplace_back(i, outputTensor);
423     }
424 
425     try
426     {
427         armnn::Status status;
428         if (m_AsyncModelExecutionEnabled)
429         {
430             ALOGW("ArmnnPreparedModel::ExecuteGraph m_AsyncModelExecutionEnabled true");
431             status = m_Runtime->Execute(*m_WorkingMemHandle, inputTensors, outputTensors);
432         }
433         else
434         {
435             ALOGW("ArmnnPreparedModel::ExecuteGraph m_AsyncModelExecutionEnabled false");
436             // Create a vector of Input and Output Ids which can be imported. An empty vector means all will be copied.
437             std::vector<armnn::ImportedInputId> importedInputIds;
438             if (m_EnableImport)
439             {
440                 importedInputIds =  m_Runtime->ImportInputs(m_NetworkId, inputTensors, armnn::MemorySource::Malloc);
441             }
442             std::vector<armnn::ImportedOutputId> importedOutputIds;
443             if (m_EnableExport)
444             {
445                 importedOutputIds = m_Runtime->ImportOutputs(m_NetworkId, outputTensors, armnn::MemorySource::Malloc);
446             }
447             status = m_Runtime->EnqueueWorkload(m_NetworkId, inputTensors, outputTensors,
448                                                 importedInputIds, importedOutputIds);
449         }
450         if (status != armnn::Status::Success)
451         {
452             ALOGW("ExecuteWithDummyInputs: EnqueueWorkload failed");
453             return false;
454         }
455     }
456     catch (armnn::Exception& e)
457     {
458         ALOGW("ExecuteWithDummyInputs: armnn::Exception caught from EnqueueWorkload: %s", e.what());
459         return false;
460     }
461     catch (std::exception& e)
462     {
463         ALOGE("ExecuteWithDummyInputs: std::exception caught from EnqueueWorkload: %s", e.what());
464         return false;
465     }
466     return true;
467 }
468 
469 /// Schedule the graph prepared from the request for execution
470 template<typename HalVersion>
471 template<typename CallbackContext>
ScheduleGraphForExecution(std::shared_ptr<std::vector<::android::nn::RunTimePoolInfo>> & pMemPools,std::shared_ptr<armnn::InputTensors> & inputTensors,std::shared_ptr<armnn::OutputTensors> & outputTensors,CallbackContext callbackContext)472 void ArmnnPreparedModel<HalVersion>::ScheduleGraphForExecution(
473         std::shared_ptr<std::vector<::android::nn::RunTimePoolInfo>>& pMemPools,
474         std::shared_ptr<armnn::InputTensors>& inputTensors,
475         std::shared_ptr<armnn::OutputTensors>& outputTensors,
476         CallbackContext callbackContext)
477 {
478     ALOGV("ArmnnPreparedModel::ScheduleGraphForExecution(...)");
479 
480     DumpTensorsIfRequired("Input", *inputTensors);
481 
482 
483     auto tpCb = std::make_shared<
484                 ArmnnThreadPoolCallback<CallbackContext_1_0>>(this,
485                                                               pMemPools,
486                                                               inputTensors,
487                                                               outputTensors,
488                                                               callbackContext);
489 
490     m_Threadpool->Schedule(m_NetworkId,
491                            *tpCb->m_InputTensors,
492                            *tpCb->m_OutputTensors,
493                            armnn::QosExecPriority::Medium,
494                            tpCb);
495     ALOGV("ArmnnPreparedModel::ScheduleGraphForExecution end");
496 }
497 
498 template<typename HalVersion>
499 template <typename CallbackContext>
Notify(armnn::Status status,armnn::InferenceTimingPair timeTaken)500 void ArmnnPreparedModel<HalVersion>::ArmnnThreadPoolCallback<CallbackContext>::Notify(
501         armnn::Status status, armnn::InferenceTimingPair timeTaken)
502 {
503     armnn::IgnoreUnused(status, timeTaken);
504     ALOGV("ArmnnPreparedModel::ArmnnThreadPoolCallback_1_2 Notify");
505 
506     m_Model->DumpTensorsIfRequired("Output", *m_OutputTensors);
507 
508     // Commit output buffers.
509     // Note that we update *all* pools, even if they aren't actually used as outputs -
510     // this is simpler and is what the CpuExecutor does.
511     for (android::nn::RunTimePoolInfo& pool : *m_MemPools)
512     {
513         // Type android::nn::RunTimePoolInfo has changed between Android P & Q and Android R, where
514         // update() has been removed and flush() added.
515         #if defined(ARMNN_ANDROID_R) || defined(ARMNN_ANDROID_S) // Use the new Android implementation.
516             pool.flush();
517         #else
518             pool.update();
519         #endif
520     }
521 
522     m_CallbackContext.callback(V1_0::ErrorStatus::NONE, "ArmnnPreparedModel::ArmnnThreadPoolCallback_1_2 Notify");
523     return;
524 }
525 
526 ///
527 /// Class template specializations
528 ///
529 
530 template class ArmnnPreparedModel<hal_1_0::HalPolicy>;
531 template void ArmnnPreparedModel<hal_1_0::HalPolicy>::ScheduleGraphForExecution<CallbackContext_1_0>(
532         std::shared_ptr<std::vector<::android::nn::RunTimePoolInfo>>& pMemPools,
533         std::shared_ptr<armnn::InputTensors>& inputTensors,
534         std::shared_ptr<armnn::OutputTensors>& outputTensors,
535         CallbackContext_1_0 callbackContext);
536 
537 #ifdef ARMNN_ANDROID_NN_V1_1
538 template class ArmnnPreparedModel<hal_1_1::HalPolicy>;
539 #endif
540 
541 #ifdef ARMNN_ANDROID_NN_V1_2
542 template class ArmnnPreparedModel<hal_1_1::HalPolicy>;
543 template class ArmnnPreparedModel<hal_1_2::HalPolicy>;
544 #endif
545 
546 #ifdef ARMNN_ANDROID_NN_V1_3
547 template class ArmnnPreparedModel<hal_1_1::HalPolicy>;
548 template class ArmnnPreparedModel<hal_1_2::HalPolicy>;
549 template class ArmnnPreparedModel<hal_1_3::HalPolicy>;
550 #endif
551 } // namespace armnn_driver
552