blob: 75b1ee8179775fe6ceae5a17bea324f1fcc806a3 [file] [log] [blame]
Laurent Carlier749294b2020-06-01 09:03:17 +01001//
Jim Flynn6398a982020-05-27 17:05:21 +01002// Copyright © 2017 Arm Ltd and Contributors. All rights reserved.
David Beckecb56cd2018-09-05 12:52:57 +01003// SPDX-License-Identifier: MIT
telsoa014fcda012018-03-09 14:13:49 +00004//
Jim Flynn34430252022-03-04 15:03:58 +00005
6#include "ArmNNProfilingServiceInitialiser.hpp"
telsoa014fcda012018-03-09 14:13:49 +00007#include "Runtime.hpp"
8
Jim Flynn9c85b412022-03-16 00:27:43 +00009#include <ProfilingOptionsConverter.hpp>
10
David Beck056be3c2018-10-22 13:16:00 +010011#include <armnn/Version.hpp>
Matteo Martincighc601aa62019-10-29 15:03:22 +000012#include <armnn/BackendRegistry.hpp>
Jan Eilers15fcc7e2021-07-14 13:50:15 +010013#include <armnn/BackendHelper.hpp>
Matthew Benthamf48afc62020-01-15 17:55:08 +000014#include <armnn/Logging.hpp>
Matteo Martincighe54aa062019-08-05 14:12:11 +010015
Matteo Martincighe5b8eb92019-11-28 15:45:42 +000016#include <armnn/backends/IBackendContext.hpp>
Jim Flynn9c85b412022-03-16 00:27:43 +000017
18#include <armnn/profiling/ArmNNProfiling.hpp>
19
20#include <armnn/utility/PolymorphicDowncast.hpp>
21#include <armnn/utility/Timer.hpp>
22
Matteo Martincighe54aa062019-08-05 14:12:11 +010023#include <backendsCommon/DynamicBackendUtils.hpp>
Jim Flynne1fdd282021-10-26 21:26:10 +010024#include <backendsCommon/memoryOptimizerStrategyLibrary/MemoryOptimizerStrategyLibrary.hpp>
telsoa014fcda012018-03-09 14:13:49 +000025
Jim Flynn3e9bc192022-03-23 23:01:26 +000026#include <client/include/backends/IBackendProfiling.hpp>
27
Nikhil Raj77fe76b2021-06-09 14:55:32 +010028#include <common/include/LabelsAndEventClasses.hpp>
29
surmeh013537c2c2018-05-18 16:31:43 +010030#include <iostream>
31
telsoa014fcda012018-03-09 14:13:49 +000032
33using namespace armnn;
34using namespace std;
35
36namespace armnn
37{
Kevin Mayd92a6e42021-02-04 10:27:41 +000038IRuntime::IRuntime() : pRuntimeImpl( new RuntimeImpl(armnn::IRuntime::CreationOptions())) {}
39
40IRuntime::IRuntime(const IRuntime::CreationOptions& options) : pRuntimeImpl(new RuntimeImpl(options)) {}
41
42IRuntime::~IRuntime() = default;
telsoa014fcda012018-03-09 14:13:49 +000043
44IRuntime* IRuntime::CreateRaw(const CreationOptions& options)
45{
Kevin Mayd92a6e42021-02-04 10:27:41 +000046 return new IRuntime(options);
telsoa014fcda012018-03-09 14:13:49 +000047}
48
49IRuntimePtr IRuntime::Create(const CreationOptions& options)
50{
51 return IRuntimePtr(CreateRaw(options), &IRuntime::Destroy);
52}
53
54void IRuntime::Destroy(IRuntime* runtime)
55{
Kevin Mayd92a6e42021-02-04 10:27:41 +000056 delete runtime;
telsoa014fcda012018-03-09 14:13:49 +000057}
58
Kevin Mayd92a6e42021-02-04 10:27:41 +000059Status IRuntime::LoadNetwork(NetworkId& networkIdOut, IOptimizedNetworkPtr network)
60{
61 return pRuntimeImpl->LoadNetwork(networkIdOut, std::move(network));
62}
63
64Status IRuntime::LoadNetwork(NetworkId& networkIdOut,
65 IOptimizedNetworkPtr network,
66 std::string& errorMessage)
67{
68 return pRuntimeImpl->LoadNetwork(networkIdOut, std::move(network), errorMessage);
69}
70
71Status IRuntime::LoadNetwork(NetworkId& networkIdOut,
72 IOptimizedNetworkPtr network,
73 std::string& errorMessage,
74 const INetworkProperties& networkProperties)
75{
76 return pRuntimeImpl->LoadNetwork(networkIdOut, std::move(network), errorMessage, networkProperties);
77}
78
Cathal Corbett5b8093c2021-10-22 11:12:07 +010079armnn::TensorInfo IRuntime::GetInputTensorInfo(NetworkId networkId, LayerBindingId layerId) const
Kevin Mayd92a6e42021-02-04 10:27:41 +000080{
81 return pRuntimeImpl->GetInputTensorInfo(networkId, layerId);
82}
83
Cathal Corbett5b8093c2021-10-22 11:12:07 +010084armnn::TensorInfo IRuntime::GetOutputTensorInfo(NetworkId networkId, LayerBindingId layerId) const
Kevin Mayd92a6e42021-02-04 10:27:41 +000085{
86 return pRuntimeImpl->GetOutputTensorInfo(networkId, layerId);
87}
88
Narumol Prangnawarate2af6f42022-01-28 17:59:18 +000089std::vector<ImportedInputId> IRuntime::ImportInputs(NetworkId networkId, const InputTensors& inputTensors,
90 MemorySource forceImportMemorySource)
Finn Williamsf37b9702021-09-01 18:06:04 +010091{
Narumol Prangnawarate2af6f42022-01-28 17:59:18 +000092 return pRuntimeImpl->ImportInputs(networkId, inputTensors, forceImportMemorySource);
Finn Williamsf37b9702021-09-01 18:06:04 +010093}
94
Narumol Prangnawarate2af6f42022-01-28 17:59:18 +000095std::vector<ImportedOutputId> IRuntime::ImportOutputs(NetworkId networkId, const OutputTensors& outputTensors,
96 MemorySource forceImportMemorySource)
Finn Williams8636bc72021-10-02 15:06:39 +010097{
Narumol Prangnawarate2af6f42022-01-28 17:59:18 +000098 return pRuntimeImpl->ImportOutputs(networkId, outputTensors, forceImportMemorySource);
Finn Williams8636bc72021-10-02 15:06:39 +010099}
100
101void IRuntime::ClearImportedInputs(NetworkId networkId, const std::vector<ImportedInputId> inputIds)
102{
103 return pRuntimeImpl->ClearImportedInputs(networkId, inputIds);
104}
105void IRuntime::ClearImportedOutputs(NetworkId networkId, const std::vector<ImportedOutputId> outputIds)
106{
107 return pRuntimeImpl->ClearImportedOutputs(networkId, outputIds);
108}
Finn Williamsf37b9702021-09-01 18:06:04 +0100109
Kevin Mayd92a6e42021-02-04 10:27:41 +0000110Status IRuntime::EnqueueWorkload(NetworkId networkId,
111 const InputTensors& inputTensors,
Narumol Prangnawarate2af6f42022-01-28 17:59:18 +0000112 const OutputTensors& outputTensors,
113 std::vector<ImportedInputId> preImportedInputIds,
114 std::vector<ImportedOutputId> preImportedOutputIds)
Kevin Mayd92a6e42021-02-04 10:27:41 +0000115{
Narumol Prangnawarate2af6f42022-01-28 17:59:18 +0000116 return pRuntimeImpl->EnqueueWorkload(networkId, inputTensors, outputTensors,
117 preImportedInputIds, preImportedOutputIds);
Kevin Mayd92a6e42021-02-04 10:27:41 +0000118}
119
Mike Kelly55a8ffd2021-04-07 20:10:49 +0100120Status IRuntime::Execute(IWorkingMemHandle& workingMemHandle,
121 const InputTensors& inputTensors,
Finn Williamsf37b9702021-09-01 18:06:04 +0100122 const OutputTensors& outputTensors,
Finn Williams8636bc72021-10-02 15:06:39 +0100123 std::vector<ImportedInputId> preImportedInputs,
124 std::vector<ImportedOutputId> preImportedOutputs)
Mike Kelly55a8ffd2021-04-07 20:10:49 +0100125{
Jim Flynn34430252022-03-04 15:03:58 +0000126 return pRuntimeImpl->Execute(workingMemHandle,
127 inputTensors,
128 outputTensors,
129 preImportedInputs,
130 preImportedOutputs);
Mike Kelly55a8ffd2021-04-07 20:10:49 +0100131}
132
Kevin Mayd92a6e42021-02-04 10:27:41 +0000133Status IRuntime::UnloadNetwork(NetworkId networkId)
134{
135 return pRuntimeImpl->UnloadNetwork(networkId);
136}
137
138const IDeviceSpec& IRuntime::GetDeviceSpec() const
139{
140 return pRuntimeImpl->GetDeviceSpec();
141}
142
Mike Kelly55a8ffd2021-04-07 20:10:49 +0100143std::unique_ptr<IWorkingMemHandle> IRuntime::CreateWorkingMemHandle(NetworkId networkId)
144{
145 return pRuntimeImpl->CreateWorkingMemHandle(networkId);
146}
147
Kevin Mayd92a6e42021-02-04 10:27:41 +0000148const std::shared_ptr<IProfiler> IRuntime::GetProfiler(NetworkId networkId) const
149{
150 return pRuntimeImpl->GetProfiler(networkId);
151}
152
153void IRuntime::RegisterDebugCallback(NetworkId networkId, const DebugCallbackFunction& func)
154{
155 return pRuntimeImpl->RegisterDebugCallback(networkId, func);
156}
157
158int RuntimeImpl::GenerateNetworkId()
telsoa014fcda012018-03-09 14:13:49 +0000159{
160 return m_NetworkIdCounter++;
161}
162
Kevin Mayd92a6e42021-02-04 10:27:41 +0000163Status RuntimeImpl::LoadNetwork(NetworkId& networkIdOut, IOptimizedNetworkPtr inNetwork)
telsoa014fcda012018-03-09 14:13:49 +0000164{
telsoa01c577f2c2018-08-31 09:22:23 +0100165 std::string ignoredErrorMessage;
166 return LoadNetwork(networkIdOut, std::move(inNetwork), ignoredErrorMessage);
167}
168
Kevin Mayd92a6e42021-02-04 10:27:41 +0000169Status RuntimeImpl::LoadNetwork(NetworkId& networkIdOut,
170 IOptimizedNetworkPtr inNetwork,
171 std::string& errorMessage)
David Monahan4f1e8e42019-09-04 09:22:10 +0100172{
Jan Eilersc1c872f2021-07-22 13:17:04 +0100173 INetworkProperties networkProperties(
174 false, MemorySource::Undefined, MemorySource::Undefined);
David Monahan4f1e8e42019-09-04 09:22:10 +0100175 return LoadNetwork(networkIdOut, std::move(inNetwork), errorMessage, networkProperties);
176}
177
Kevin Mayd92a6e42021-02-04 10:27:41 +0000178Status RuntimeImpl::LoadNetwork(NetworkId& networkIdOut,
179 IOptimizedNetworkPtr inNetwork,
180 std::string& errorMessage,
181 const INetworkProperties& networkProperties)
telsoa01c577f2c2018-08-31 09:22:23 +0100182{
Derek Lambertie155bbf2021-10-13 14:32:12 +0100183 // Register the profiler
184 auto profiler = inNetwork->GetProfiler();
185 ProfilerManager::GetInstance().RegisterProfiler(profiler.get());
186
telsoa014fcda012018-03-09 14:13:49 +0000187 IOptimizedNetwork* rawNetwork = inNetwork.release();
David Beck1b61be52018-11-08 09:19:14 +0000188
189 networkIdOut = GenerateNetworkId();
190
191 for (auto&& context : m_BackendContexts)
192 {
193 context.second->BeforeLoadNetwork(networkIdOut);
194 }
195
telsoa014fcda012018-03-09 14:13:49 +0000196 unique_ptr<LoadedNetwork> loadedNetwork = LoadedNetwork::MakeLoadedNetwork(
Francis Murtagh3d2b4b22021-02-15 18:23:17 +0000197 std::unique_ptr<IOptimizedNetwork>(rawNetwork),
David Monahan4f1e8e42019-09-04 09:22:10 +0100198 errorMessage,
Sadik Armagan3184c902020-03-18 10:57:30 +0000199 networkProperties,
Jim Flynnaf947722022-03-02 11:04:47 +0000200 m_ProfilingService.get());
telsoa014fcda012018-03-09 14:13:49 +0000201
202 if (!loadedNetwork)
203 {
204 return Status::Failure;
205 }
206
telsoa01c577f2c2018-08-31 09:22:23 +0100207 {
Jim Flynn870b96c2022-03-25 21:24:56 +0000208#if !defined(ARMNN_DISABLE_THREADS)
telsoa01c577f2c2018-08-31 09:22:23 +0100209 std::lock_guard<std::mutex> lockGuard(m_Mutex);
Jim Flynn870b96c2022-03-25 21:24:56 +0000210#endif
telsoa01c577f2c2018-08-31 09:22:23 +0100211
212 // Stores the network
213 m_LoadedNetworks[networkIdOut] = std::move(loadedNetwork);
214 }
telsoa014fcda012018-03-09 14:13:49 +0000215
David Beck1b61be52018-11-08 09:19:14 +0000216 for (auto&& context : m_BackendContexts)
217 {
218 context.second->AfterLoadNetwork(networkIdOut);
219 }
220
Jim Flynnaf947722022-03-02 11:04:47 +0000221 if (m_ProfilingService->IsProfilingEnabled())
Keith Davise394bd92019-12-02 15:12:19 +0000222 {
Jim Flynnaf947722022-03-02 11:04:47 +0000223 m_ProfilingService->IncrementCounterValue(arm::pipe::NETWORK_LOADS);
Keith Davise394bd92019-12-02 15:12:19 +0000224 }
225
telsoa014fcda012018-03-09 14:13:49 +0000226 return Status::Success;
telsoa014fcda012018-03-09 14:13:49 +0000227}
228
Kevin Mayd92a6e42021-02-04 10:27:41 +0000229Status RuntimeImpl::UnloadNetwork(NetworkId networkId)
telsoa014fcda012018-03-09 14:13:49 +0000230{
David Beck1b61be52018-11-08 09:19:14 +0000231 bool unloadOk = true;
232 for (auto&& context : m_BackendContexts)
David Beck9efb57d2018-11-05 13:40:33 +0000233 {
David Beck1b61be52018-11-08 09:19:14 +0000234 unloadOk &= context.second->BeforeUnloadNetwork(networkId);
David Beck9efb57d2018-11-05 13:40:33 +0000235 }
David Beck1b61be52018-11-08 09:19:14 +0000236
237 if (!unloadOk)
238 {
Kevin Mayd92a6e42021-02-04 10:27:41 +0000239 ARMNN_LOG(warning) << "RuntimeImpl::UnloadNetwork(): failed to unload "
Derek Lamberti08446972019-11-26 16:38:31 +0000240 "network with ID:" << networkId << " because BeforeUnloadNetwork failed";
David Beck1b61be52018-11-08 09:19:14 +0000241 return Status::Failure;
242 }
David Beck9efb57d2018-11-05 13:40:33 +0000243
Cathal Corbett5aa9fd72022-02-25 15:33:28 +0000244 std::unique_ptr<arm::pipe::TimelineUtilityMethods> timelineUtils =
Jim Flynnaf947722022-03-02 11:04:47 +0000245 arm::pipe::TimelineUtilityMethods::GetTimelineUtils(*m_ProfilingService.get());
telsoa014fcda012018-03-09 14:13:49 +0000246 {
Jim Flynn870b96c2022-03-25 21:24:56 +0000247#if !defined(ARMNN_DISABLE_THREADS)
telsoa01c577f2c2018-08-31 09:22:23 +0100248 std::lock_guard<std::mutex> lockGuard(m_Mutex);
Jim Flynn870b96c2022-03-25 21:24:56 +0000249#endif
telsoa01c577f2c2018-08-31 09:22:23 +0100250
Jim Flynnf7713212020-07-14 09:50:59 +0100251 // If timeline recording is on mark the Network end of life
252 if (timelineUtils)
253 {
254 auto search = m_LoadedNetworks.find(networkId);
255 if (search != m_LoadedNetworks.end())
256 {
Cathal Corbett5aa9fd72022-02-25 15:33:28 +0000257 arm::pipe::ProfilingGuid networkGuid = search->second->GetNetworkGuid();
Jim Flynnf7713212020-07-14 09:50:59 +0100258 timelineUtils->RecordEvent(networkGuid,
Cathal Corbett5aa9fd72022-02-25 15:33:28 +0000259 arm::pipe::LabelsAndEventClasses::ARMNN_PROFILING_EOL_EVENT_CLASS);
Jim Flynnf7713212020-07-14 09:50:59 +0100260 }
261 }
Narumol Prangnawaratec5463d2022-02-04 17:50:20 +0000262
telsoa01c577f2c2018-08-31 09:22:23 +0100263 if (m_LoadedNetworks.erase(networkId) == 0)
264 {
Kevin Mayd92a6e42021-02-04 10:27:41 +0000265 ARMNN_LOG(warning) << "WARNING: RuntimeImpl::UnloadNetwork(): " << networkId << " not found!";
telsoa01c577f2c2018-08-31 09:22:23 +0100266 return Status::Failure;
267 }
Sadik Armagan3184c902020-03-18 10:57:30 +0000268
Jim Flynnaf947722022-03-02 11:04:47 +0000269 if (m_ProfilingService->IsProfilingEnabled())
Keith Davise394bd92019-12-02 15:12:19 +0000270 {
Jim Flynnaf947722022-03-02 11:04:47 +0000271 m_ProfilingService->IncrementCounterValue(arm::pipe::NETWORK_UNLOADS);
Keith Davise394bd92019-12-02 15:12:19 +0000272 }
David Beck1b61be52018-11-08 09:19:14 +0000273 }
David Beck9efb57d2018-11-05 13:40:33 +0000274
David Beck1b61be52018-11-08 09:19:14 +0000275 for (auto&& context : m_BackendContexts)
276 {
277 context.second->AfterUnloadNetwork(networkId);
telsoa01c577f2c2018-08-31 09:22:23 +0100278 }
279
Derek Lambertie155bbf2021-10-13 14:32:12 +0100280 // Unregister the profiler
281 ProfilerManager::GetInstance().RegisterProfiler(nullptr);
282
Kevin Mayd92a6e42021-02-04 10:27:41 +0000283 ARMNN_LOG(debug) << "RuntimeImpl::UnloadNetwork(): Unloaded network with ID: " << networkId;
telsoa014fcda012018-03-09 14:13:49 +0000284 return Status::Success;
285}
286
Kevin Mayd92a6e42021-02-04 10:27:41 +0000287const std::shared_ptr<IProfiler> RuntimeImpl::GetProfiler(NetworkId networkId) const
telsoa01c577f2c2018-08-31 09:22:23 +0100288{
289 auto it = m_LoadedNetworks.find(networkId);
290 if (it != m_LoadedNetworks.end())
291 {
292 auto& loadedNetwork = it->second;
293 return loadedNetwork->GetProfiler();
294 }
295
296 return nullptr;
297}
298
Cathal Corbett19793552022-03-04 10:36:34 +0000299void RuntimeImpl::ReportStructure(arm::pipe::IProfilingService& profilingService)
Keith Davis33ed2212020-03-30 10:43:41 +0100300{
Cathal Corbett19793552022-03-04 10:36:34 +0000301 if (profilingService.IsProfilingEnabled())
Keith Davis33ed2212020-03-30 10:43:41 +0100302 {
Cathal Corbett19793552022-03-04 10:36:34 +0000303 LoadedNetworks::iterator it = m_LoadedNetworks.begin();
304 while (it != m_LoadedNetworks.end())
305 {
306 auto& loadedNetwork = it->second;
307 loadedNetwork->SendNetworkStructure(profilingService);
308 // Increment the Iterator to point to next entry
309 it++;
310 }
Keith Davis33ed2212020-03-30 10:43:41 +0100311 }
312}
313
Jim Flynn34430252022-03-04 15:03:58 +0000314void RuntimeImpl::InitialiseProfilingService(arm::pipe::IProfilingService& profilingService)
315{
316 ArmNNProfilingServiceInitialiser initialiser;
317 initialiser.InitialiseProfilingService(profilingService);
318}
319
Kevin Mayd92a6e42021-02-04 10:27:41 +0000320RuntimeImpl::RuntimeImpl(const IRuntime::CreationOptions& options)
Jim Flynnaf947722022-03-02 11:04:47 +0000321 : m_NetworkIdCounter(0)
telsoa014fcda012018-03-09 14:13:49 +0000322{
Jim Flynn34430252022-03-04 15:03:58 +0000323 m_ProfilingService = arm::pipe::IProfilingService::CreateProfilingService(
Jim Flynn9c85b412022-03-16 00:27:43 +0000324 arm::pipe::MAX_ARMNN_COUNTER,
325 *this,
326 arm::pipe::ARMNN_SOFTWARE_INFO,
327 arm::pipe::ARMNN_SOFTWARE_VERSION,
328 arm::pipe::ARMNN_HARDWARE_VERSION,
329 *this);
alered01a7227ac2020-05-07 14:58:29 +0100330 const auto start_time = armnn::GetTimeNow();
Sadik Armagan4a0844d2022-01-26 09:57:05 +0000331 ARMNN_LOG(info) << "ArmNN v" << ARMNN_VERSION;
Keith Davis33ed2212020-03-30 10:43:41 +0100332 if ( options.m_ProfilingOptions.m_TimelineEnabled && !options.m_ProfilingOptions.m_EnableProfiling )
333 {
Jan Eilersc1c872f2021-07-22 13:17:04 +0100334 throw RuntimeException(
335 "It is not possible to enable timeline reporting without profiling being enabled");
Keith Davis33ed2212020-03-30 10:43:41 +0100336 }
337
Matteo Martincighe54aa062019-08-05 14:12:11 +0100338 // Load any available/compatible dynamic backend before the runtime
339 // goes through the backend registry
340 LoadDynamicBackends(options.m_DynamicBackendsPath);
341
Cathal Corbett5aa9fd72022-02-25 15:33:28 +0000342 armnn::BackendIdSet supportedBackends;
David Beck1b61be52018-11-08 09:19:14 +0000343 for (const auto& id : BackendRegistryInstance().GetBackendIds())
344 {
345 // Store backend contexts for the supported ones
Matthew Bentham9a61fa62020-02-04 10:03:55 +0000346 try {
David Beck1b61be52018-11-08 09:19:14 +0000347 auto factoryFun = BackendRegistryInstance().GetFactory(id);
Colm Donelan380c1a02021-08-17 00:52:23 +0100348 ARMNN_ASSERT(factoryFun != nullptr);
David Beck1b61be52018-11-08 09:19:14 +0000349 auto backend = factoryFun();
Colm Donelan380c1a02021-08-17 00:52:23 +0100350 ARMNN_ASSERT(backend != nullptr);
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100351 ARMNN_ASSERT(backend.get() != nullptr);
David Beck1b61be52018-11-08 09:19:14 +0000352
Jan Eilersc1c872f2021-07-22 13:17:04 +0100353 auto customAllocatorMapIterator = options.m_CustomAllocatorMap.find(id);
Francis Murtagh62573b62021-08-12 11:55:21 +0100354 if (customAllocatorMapIterator != options.m_CustomAllocatorMap.end() &&
355 customAllocatorMapIterator->second == nullptr)
356 {
Colm Donelan380c1a02021-08-17 00:52:23 +0100357 // We need to manually clean up the dynamic backends before throwing an exception.
358 DynamicBackendUtils::DeregisterDynamicBackends(m_DeviceSpec.GetDynamicBackends());
359 m_DeviceSpec.ClearDynamicBackends();
Francis Murtagh62573b62021-08-12 11:55:21 +0100360 throw armnn::Exception("Allocator associated with id " + id.Get() + " is null");
361 }
Jan Eilersc1c872f2021-07-22 13:17:04 +0100362
Jan Eilers15fcc7e2021-07-14 13:50:15 +0100363 // If the runtime is created in protected mode only add backends that support this mode
364 if (options.m_ProtectedMode)
365 {
366 // check if backend supports ProtectedMode
367 using BackendCapability = BackendOptions::BackendOption;
368 BackendCapability protectedContentCapability {"ProtectedContentAllocation", true};
369 if (!HasCapability(protectedContentCapability, id))
370 {
371 // Protected Content Allocation is not supported by the backend
372 // backend should not be registered
373 ARMNN_LOG(warning) << "Backend "
374 << id
Sadik Armagan4a0844d2022-01-26 09:57:05 +0000375 << " is not registered as does not support protected content allocation.";
Jan Eilers15fcc7e2021-07-14 13:50:15 +0100376 continue;
377 }
Jan Eilersc1c872f2021-07-22 13:17:04 +0100378 // The user is responsible to provide a custom memory allocator which allows to allocate
379 // protected memory
380 if (customAllocatorMapIterator != options.m_CustomAllocatorMap.end())
Jan Eilers15fcc7e2021-07-14 13:50:15 +0100381 {
Jan Eilersc1c872f2021-07-22 13:17:04 +0100382 std::string err;
383 if (customAllocatorMapIterator->second->GetMemorySourceType()
384 == armnn::MemorySource::DmaBufProtected)
385 {
386 if (!backend->UseCustomMemoryAllocator(customAllocatorMapIterator->second, err))
387 {
388 ARMNN_LOG(error) << "The backend "
389 << id
390 << " reported an error when entering protected mode. Backend won't be"
391 << " used. ErrorMsg: " << err;
392 continue;
393 }
394 // No errors so register the Custom Allocator with the BackendRegistry
395 BackendRegistryInstance().RegisterAllocator(id, customAllocatorMapIterator->second);
396 }
397 else
398 {
399 ARMNN_LOG(error) << "The CustomAllocator provided with the runtime options doesn't support "
400 "protected memory. Protected mode can't be activated. The backend "
Jan Eilers15fcc7e2021-07-14 13:50:15 +0100401 << id
Jan Eilersc1c872f2021-07-22 13:17:04 +0100402 << " is not going to be used. MemorySource must be MemorySource::DmaBufProtected";
403 continue;
404 }
405 }
406 else
407 {
408 ARMNN_LOG(error) << "Protected mode can't be activated for backend: "
409 << id
410 << " no custom allocator was provided to the runtime options.";
Jan Eilers15fcc7e2021-07-14 13:50:15 +0100411 continue;
412 }
413 }
Jan Eilersc1c872f2021-07-22 13:17:04 +0100414 else
415 {
416 // If a custom memory allocator is provided make the backend use that instead of the default
417 if (customAllocatorMapIterator != options.m_CustomAllocatorMap.end())
418 {
419 std::string err;
420 if (!backend->UseCustomMemoryAllocator(customAllocatorMapIterator->second, err))
421 {
422 ARMNN_LOG(error) << "The backend "
423 << id
424 << " reported an error when trying to use the provided custom allocator."
425 " Backend won't be used."
426 << " ErrorMsg: " << err;
427 continue;
428 }
429 // No errors so register the Custom Allocator with the BackendRegistry
430 BackendRegistryInstance().RegisterAllocator(id, customAllocatorMapIterator->second);
431 }
432 }
Sadik Armaganb8a26d82021-10-04 15:13:11 +0100433
434 // check if custom memory optimizer strategy map is set
435 if (!options.m_MemoryOptimizerStrategyMap.empty())
436 {
437 auto customMemoryOptimizerStrategyMapIterator = options.m_MemoryOptimizerStrategyMap.find(id);
438 // if a memory optimizer strategy is provided make the backend use that instead of the default
439 if (customMemoryOptimizerStrategyMapIterator != options.m_MemoryOptimizerStrategyMap.end())
440 {
441 // no errors.. register the memory optimizer strategy with the BackendRegistry
442 BackendRegistryInstance().RegisterMemoryOptimizerStrategy(
443 id, customMemoryOptimizerStrategyMapIterator->second);
444
445 ARMNN_LOG(info) << "MemoryOptimizerStrategy "
446 << customMemoryOptimizerStrategyMapIterator->second->GetName()
447 << " set for the backend " << id << ".";
448 }
449 }
450 else
451 {
452 // check if to use one of the existing memory optimizer strategies is set
453 std::string memoryOptimizerStrategyName = "";
454 ParseOptions(options.m_BackendOptions, id, [&](std::string name, const BackendOptions::Var& value)
455 {
456 if (name == "MemoryOptimizerStrategy")
457 {
458 memoryOptimizerStrategyName = ParseStringBackendOption(value, "");
459 }
460 });
461 if (memoryOptimizerStrategyName != "")
462 {
Jim Flynne1fdd282021-10-26 21:26:10 +0100463 std::shared_ptr<IMemoryOptimizerStrategy> strategy =
464 GetMemoryOptimizerStrategy(memoryOptimizerStrategyName);
465
466 if (!strategy)
Sadik Armaganb8a26d82021-10-04 15:13:11 +0100467 {
Jim Flynne1fdd282021-10-26 21:26:10 +0100468 ARMNN_LOG(warning) << "MemoryOptimizerStrategy: " << memoryOptimizerStrategyName
Sadik Armagan4a0844d2022-01-26 09:57:05 +0000469 << " was not found.";
Jim Flynne1fdd282021-10-26 21:26:10 +0100470 }
471 else
472 {
473 using BackendCapability = BackendOptions::BackendOption;
474 auto strategyType = GetMemBlockStrategyTypeName(strategy->GetMemBlockStrategyType());
475 BackendCapability memOptimizeStrategyCapability {strategyType, true};
476 if (HasCapability(memOptimizeStrategyCapability, id))
477 {
478 BackendRegistryInstance().RegisterMemoryOptimizerStrategy(id, strategy);
479
480 ARMNN_LOG(info) << "MemoryOptimizerStrategy: "
481 << memoryOptimizerStrategyName << " set for the backend " << id << ".";
482 }
483 else
484 {
485 ARMNN_LOG(warning) << "Backend "
486 << id
487 << " does not have multi-axis packing capability and cannot support"
Sadik Armagan4a0844d2022-01-26 09:57:05 +0000488 << "MemoryOptimizerStrategy: " << memoryOptimizerStrategyName << ".";
Jim Flynne1fdd282021-10-26 21:26:10 +0100489 }
Sadik Armaganb8a26d82021-10-04 15:13:11 +0100490 }
491 }
492 }
493
David Beck1b61be52018-11-08 09:19:14 +0000494 auto context = backend->CreateBackendContext(options);
495
496 // backends are allowed to return nullptrs if they
497 // don't wish to create a backend specific context
498 if (context)
499 {
500 m_BackendContexts.emplace(std::make_pair(id, std::move(context)));
501 }
Matthew Bentham9a61fa62020-02-04 10:03:55 +0000502 supportedBackends.emplace(id);
Colm Donelan1aff3932020-02-05 17:48:59 +0000503
Cathal Corbett5aa9fd72022-02-25 15:33:28 +0000504 unique_ptr<arm::pipe::IBackendProfiling> profilingIface =
Jim Flynn3e9bc192022-03-23 23:01:26 +0000505 arm::pipe::IBackendProfiling::CreateBackendProfiling(
Jim Flynnaf947722022-03-02 11:04:47 +0000506 arm::pipe::ConvertExternalProfilingOptions(options.m_ProfilingOptions),
Jim Flynn3e9bc192022-03-23 23:01:26 +0000507 *m_ProfilingService.get(),
508 id.Get());
Colm Donelan1aff3932020-02-05 17:48:59 +0000509
510 // Backends may also provide a profiling context. Ask for it now.
511 auto profilingContext = backend->CreateBackendProfilingContext(options, profilingIface);
512 // Backends that don't support profiling will return a null profiling context.
513 if (profilingContext)
514 {
Finn Williamsfe5a24b2020-04-09 16:05:28 +0100515 // Pass the context onto the profiling service.
Jim Flynnaf947722022-03-02 11:04:47 +0000516 m_ProfilingService->AddBackendProfilingContext(id, profilingContext);
Colm Donelan1aff3932020-02-05 17:48:59 +0000517 }
David Beck1b61be52018-11-08 09:19:14 +0000518 }
Matthew Bentham9a61fa62020-02-04 10:03:55 +0000519 catch (const BackendUnavailableException&)
520 {
521 // Ignore backends which are unavailable
522 }
David Beck1b61be52018-11-08 09:19:14 +0000523 }
Finn Williamsfe5a24b2020-04-09 16:05:28 +0100524
Jim Flynnaf947722022-03-02 11:04:47 +0000525 BackendRegistryInstance().SetProfilingService(*m_ProfilingService.get());
Finn Williamsfe5a24b2020-04-09 16:05:28 +0100526 // pass configuration info to the profiling service
Jim Flynnaf947722022-03-02 11:04:47 +0000527 m_ProfilingService->ConfigureProfilingService(
Cathal Corbett5aa9fd72022-02-25 15:33:28 +0000528 arm::pipe::ConvertExternalProfilingOptions(options.m_ProfilingOptions));
Jim Flynn6398a982020-05-27 17:05:21 +0100529 if (options.m_ProfilingOptions.m_EnableProfiling)
530 {
531 // try to wait for the profiling service to initialise
Jim Flynnaf947722022-03-02 11:04:47 +0000532 m_ProfilingService->WaitForProfilingServiceActivation(3000);
Jim Flynn6398a982020-05-27 17:05:21 +0100533 }
Finn Williamsfe5a24b2020-04-09 16:05:28 +0100534
Matthew Bentham9a61fa62020-02-04 10:03:55 +0000535 m_DeviceSpec.AddSupportedBackends(supportedBackends);
alered01a7227ac2020-05-07 14:58:29 +0100536
537 ARMNN_LOG(info) << "Initialization time: " << std::setprecision(2)
Sadik Armagan4a0844d2022-01-26 09:57:05 +0000538 << std::fixed << armnn::GetTimeDuration(start_time).count() << " ms.";
surmeh01bceff2f2018-03-29 16:29:27 +0100539}
540
Kevin Mayd92a6e42021-02-04 10:27:41 +0000541RuntimeImpl::~RuntimeImpl()
surmeh01bceff2f2018-03-29 16:29:27 +0100542{
Jan Eilers17d34da2021-12-08 16:15:12 +0000543 const auto startTime = armnn::GetTimeNow();
surmeh01bceff2f2018-03-29 16:29:27 +0100544 std::vector<int> networkIDs;
surmeh013537c2c2018-05-18 16:31:43 +0100545 try
546 {
547 // Coverity fix: The following code may throw an exception of type std::length_error.
548 std::transform(m_LoadedNetworks.begin(), m_LoadedNetworks.end(),
549 std::back_inserter(networkIDs),
550 [](const auto &pair) { return pair.first; });
551 }
552 catch (const std::exception& e)
553 {
554 // Coverity fix: BOOST_LOG_TRIVIAL (typically used to report errors) may throw an
555 // exception of type std::length_error.
556 // Using stderr instead in this context as there is no point in nesting try-catch blocks here.
557 std::cerr << "WARNING: An error has occurred when getting the IDs of the networks to unload: " << e.what()
558 << "\nSome of the loaded networks may not be unloaded" << std::endl;
559 }
560 // We then proceed to unload all the networks which IDs have been appended to the list
561 // up to the point the exception was thrown (if any).
surmeh01bceff2f2018-03-29 16:29:27 +0100562
563 for (auto networkID : networkIDs)
564 {
surmeh013537c2c2018-05-18 16:31:43 +0100565 try
566 {
567 // Coverity fix: UnloadNetwork() may throw an exception of type std::length_error,
568 // boost::log::v2s_mt_posix::odr_violation or boost::log::v2s_mt_posix::system_error
569 UnloadNetwork(networkID);
570 }
571 catch (const std::exception& e)
572 {
573 // Coverity fix: BOOST_LOG_TRIVIAL (typically used to report errors) may throw an
574 // exception of type std::length_error.
575 // Using stderr instead in this context as there is no point in nesting try-catch blocks here.
576 std::cerr << "WARNING: An error has occurred when unloading network " << networkID << ": " << e.what()
577 << std::endl;
578 }
telsoa014fcda012018-03-09 14:13:49 +0000579 }
Narumol Prangnawarat60a20fb2019-12-09 17:24:41 +0000580
581 // Clear all dynamic backends.
582 DynamicBackendUtils::DeregisterDynamicBackends(m_DeviceSpec.GetDynamicBackends());
583 m_DeviceSpec.ClearDynamicBackends();
Colm Donelan1aff3932020-02-05 17:48:59 +0000584 m_BackendContexts.clear();
Finn Williams45a73622020-05-15 18:41:05 +0100585
586 BackendRegistryInstance().SetProfilingService(armnn::EmptyOptional());
alered01a7227ac2020-05-07 14:58:29 +0100587 ARMNN_LOG(info) << "Shutdown time: " << std::setprecision(2)
Sadik Armagan4a0844d2022-01-26 09:57:05 +0000588 << std::fixed << armnn::GetTimeDuration(startTime).count() << " ms.";
telsoa014fcda012018-03-09 14:13:49 +0000589}
590
Kevin Mayd92a6e42021-02-04 10:27:41 +0000591LoadedNetwork* RuntimeImpl::GetLoadedNetworkPtr(NetworkId networkId) const
surmeh013537c2c2018-05-18 16:31:43 +0100592{
Jim Flynn870b96c2022-03-25 21:24:56 +0000593#if !defined(ARMNN_DISABLE_THREADS)
surmeh013537c2c2018-05-18 16:31:43 +0100594 std::lock_guard<std::mutex> lockGuard(m_Mutex);
Jim Flynn870b96c2022-03-25 21:24:56 +0000595#endif
surmeh013537c2c2018-05-18 16:31:43 +0100596 return m_LoadedNetworks.at(networkId).get();
597}
598
Kevin Mayd92a6e42021-02-04 10:27:41 +0000599TensorInfo RuntimeImpl::GetInputTensorInfo(NetworkId networkId, LayerBindingId layerId) const
telsoa014fcda012018-03-09 14:13:49 +0000600{
surmeh013537c2c2018-05-18 16:31:43 +0100601 return GetLoadedNetworkPtr(networkId)->GetInputTensorInfo(layerId);
telsoa014fcda012018-03-09 14:13:49 +0000602}
603
Kevin Mayd92a6e42021-02-04 10:27:41 +0000604TensorInfo RuntimeImpl::GetOutputTensorInfo(NetworkId networkId, LayerBindingId layerId) const
telsoa014fcda012018-03-09 14:13:49 +0000605{
surmeh013537c2c2018-05-18 16:31:43 +0100606 return GetLoadedNetworkPtr(networkId)->GetOutputTensorInfo(layerId);
telsoa014fcda012018-03-09 14:13:49 +0000607}
608
Narumol Prangnawarate2af6f42022-01-28 17:59:18 +0000609std::vector<ImportedInputId> RuntimeImpl::ImportInputs(NetworkId networkId, const InputTensors& inputTensors,
610 MemorySource forceImportMemorySource)
Finn Williamsf37b9702021-09-01 18:06:04 +0100611{
Narumol Prangnawarate2af6f42022-01-28 17:59:18 +0000612 return GetLoadedNetworkPtr(networkId)->ImportInputs(inputTensors, forceImportMemorySource);
Finn Williamsf37b9702021-09-01 18:06:04 +0100613}
614
Narumol Prangnawarate2af6f42022-01-28 17:59:18 +0000615std::vector<ImportedOutputId> RuntimeImpl::ImportOutputs(NetworkId networkId, const OutputTensors& outputTensors,
616 MemorySource forceImportMemorySource)
Finn Williams8636bc72021-10-02 15:06:39 +0100617{
Narumol Prangnawarate2af6f42022-01-28 17:59:18 +0000618 return GetLoadedNetworkPtr(networkId)->ImportOutputs(outputTensors, forceImportMemorySource);
Finn Williams8636bc72021-10-02 15:06:39 +0100619}
Finn Williamsf37b9702021-09-01 18:06:04 +0100620
Finn Williams8636bc72021-10-02 15:06:39 +0100621void RuntimeImpl::ClearImportedInputs(NetworkId networkId, const std::vector<ImportedInputId> inputIds)
622{
623 return GetLoadedNetworkPtr(networkId)->ClearImportedInputs(inputIds);
624}
625void RuntimeImpl::ClearImportedOutputs(NetworkId networkId, const std::vector<ImportedOutputId> outputIds)
626{
627 return GetLoadedNetworkPtr(networkId)->ClearImportedOutputs(outputIds);
628}
Derek Lamberti03614f62018-10-02 15:52:46 +0100629
Kevin Mayd92a6e42021-02-04 10:27:41 +0000630Status RuntimeImpl::EnqueueWorkload(NetworkId networkId,
telsoa01c577f2c2018-08-31 09:22:23 +0100631 const InputTensors& inputTensors,
Narumol Prangnawarate2af6f42022-01-28 17:59:18 +0000632 const OutputTensors& outputTensors,
633 std::vector<ImportedInputId> preImportedInputIds,
634 std::vector<ImportedOutputId> preImportedOutputIds)
telsoa014fcda012018-03-09 14:13:49 +0000635{
Jan Eilers17d34da2021-12-08 16:15:12 +0000636 const auto startTime = armnn::GetTimeNow();
637
surmeh013537c2c2018-05-18 16:31:43 +0100638 LoadedNetwork* loadedNetwork = GetLoadedNetworkPtr(networkId);
Mike Kelly55a8ffd2021-04-07 20:10:49 +0100639
640 if (!loadedNetwork)
641 {
Sadik Armagan4a0844d2022-01-26 09:57:05 +0000642 ARMNN_LOG(error) << "A Network with an id of " << networkId << " does not exist.";
Mike Kelly55a8ffd2021-04-07 20:10:49 +0100643 return Status::Failure;
644 }
645 if (loadedNetwork->IsAsyncEnabled())
646 {
Sadik Armagan4a0844d2022-01-26 09:57:05 +0000647 ARMNN_LOG(error) << "Network " << networkId << " is async enabled.";
Mike Kelly55a8ffd2021-04-07 20:10:49 +0100648 return Status::Failure;
649 }
Narumol Prangnawarat5b4d0d52020-06-23 11:45:56 +0100650 ProfilerManager::GetInstance().RegisterProfiler(loadedNetwork->GetProfiler().get());
651
652 ARMNN_SCOPED_PROFILING_EVENT(Compute::Undefined, "EnqueueWorkload");
Derek Lamberti03614f62018-10-02 15:52:46 +0100653
654 static thread_local NetworkId lastId = networkId;
655 if (lastId != networkId)
656 {
657 LoadedNetworkFuncSafe(lastId, [](LoadedNetwork* network)
658 {
659 network->FreeWorkingMemory();
660 });
661 }
662 lastId=networkId;
663
Narumol Prangnawarate2af6f42022-01-28 17:59:18 +0000664 auto status = loadedNetwork->EnqueueWorkload(inputTensors, outputTensors,
665 preImportedInputIds, preImportedOutputIds);
Jan Eilers17d34da2021-12-08 16:15:12 +0000666
David Monahan1fc448a2022-02-24 15:55:56 +0000667
668 // Check if we imported, if not there's no need to call the After EnqueueWorkload events
669 if (!preImportedInputIds.empty() || !preImportedOutputIds.empty())
670 {
671 // Call After EnqueueWorkload events
672 for (auto&& context : m_BackendContexts)
673 {
674 context.second->AfterEnqueueWorkload(networkId);
675 }
676 }
Jan Eilers17d34da2021-12-08 16:15:12 +0000677 ARMNN_LOG(info) << "Execution time: " << std::setprecision(2)
Sadik Armagan4a0844d2022-01-26 09:57:05 +0000678 << std::fixed << armnn::GetTimeDuration(startTime).count() << " ms.";
Jan Eilers17d34da2021-12-08 16:15:12 +0000679 return status;
telsoa014fcda012018-03-09 14:13:49 +0000680}
681
Mike Kelly55a8ffd2021-04-07 20:10:49 +0100682Status RuntimeImpl::Execute(IWorkingMemHandle& iWorkingMemHandle,
683 const InputTensors& inputTensors,
Finn Williamsf37b9702021-09-01 18:06:04 +0100684 const OutputTensors& outputTensors,
Finn Williams8636bc72021-10-02 15:06:39 +0100685 std::vector<ImportedInputId> preImportedInputs,
686 std::vector<ImportedOutputId> preImportedOutputs)
Mike Kelly55a8ffd2021-04-07 20:10:49 +0100687{
Jan Eilers17d34da2021-12-08 16:15:12 +0000688 const auto startTime = armnn::GetTimeNow();
689
Mike Kelly55a8ffd2021-04-07 20:10:49 +0100690 NetworkId networkId = iWorkingMemHandle.GetNetworkId();
691 LoadedNetwork* loadedNetwork = GetLoadedNetworkPtr(networkId);
692
693 if (!loadedNetwork)
694 {
Sadik Armagan4a0844d2022-01-26 09:57:05 +0000695 ARMNN_LOG(error) << "A Network with an id of " << networkId << " does not exist.";
Mike Kelly55a8ffd2021-04-07 20:10:49 +0100696 return Status::Failure;
697 }
698 if (!loadedNetwork->IsAsyncEnabled())
699 {
Sadik Armagan4a0844d2022-01-26 09:57:05 +0000700 ARMNN_LOG(error) << "Attempting execute " << networkId << " when it is not async enabled.";
Mike Kelly55a8ffd2021-04-07 20:10:49 +0100701 return Status::Failure;
702 }
703 ProfilerManager::GetInstance().RegisterProfiler(loadedNetwork->GetProfiler().get());
704
705 ARMNN_SCOPED_PROFILING_EVENT(Compute::Undefined, "Execute");
706
Jan Eilers17d34da2021-12-08 16:15:12 +0000707 auto status = loadedNetwork->Execute(inputTensors,
708 outputTensors,
709 iWorkingMemHandle,
710 preImportedInputs,
711 preImportedOutputs);
712
713 ARMNN_LOG(info) << "Execution time: " << std::setprecision(2)
Sadik Armagan4a0844d2022-01-26 09:57:05 +0000714 << std::fixed << armnn::GetTimeDuration(startTime).count() << " ms.";
Jan Eilers17d34da2021-12-08 16:15:12 +0000715
716 return status;
Mike Kelly55a8ffd2021-04-07 20:10:49 +0100717}
718
719/// Create a new unique WorkingMemHandle object. Create multiple handles if you wish to have
720/// overlapped Execution by calling this function from different threads.
721std::unique_ptr<IWorkingMemHandle> RuntimeImpl::CreateWorkingMemHandle(NetworkId networkId)
722{
723 LoadedNetwork* loadedNetwork = GetLoadedNetworkPtr(networkId);
724
725 if (!loadedNetwork)
726 {
Sadik Armagan4a0844d2022-01-26 09:57:05 +0000727 ARMNN_LOG(error) << "A Network with an id of " << networkId << " does not exist.";
Mike Kelly55a8ffd2021-04-07 20:10:49 +0100728 return nullptr;
729 }
730 if (!loadedNetwork->IsAsyncEnabled())
731 {
Sadik Armagan4a0844d2022-01-26 09:57:05 +0000732 ARMNN_LOG(error) << "Network " << networkId << " is not async enabled.";
Mike Kelly55a8ffd2021-04-07 20:10:49 +0100733 return nullptr;
734 }
735 ProfilerManager::GetInstance().RegisterProfiler(loadedNetwork->GetProfiler().get());
736
737 ARMNN_SCOPED_PROFILING_EVENT(Compute::Undefined, "CreateWorkingMemHandle");
738
739 static thread_local NetworkId lastId = networkId;
740 if (lastId != networkId)
741 {
742 LoadedNetworkFuncSafe(lastId, [](LoadedNetwork* network)
743 {
744 network->FreeWorkingMemory();
745 });
746 }
747 lastId=networkId;
748
749 return loadedNetwork->CreateWorkingMemHandle(networkId);
750}
751
Kevin Mayd92a6e42021-02-04 10:27:41 +0000752void RuntimeImpl::RegisterDebugCallback(NetworkId networkId, const DebugCallbackFunction& func)
Nattapat Chaimanowong6e948202019-03-22 14:01:46 +0000753{
754 LoadedNetwork* loadedNetwork = GetLoadedNetworkPtr(networkId);
755 loadedNetwork->RegisterDebugCallback(func);
756}
757
Kevin Mayd92a6e42021-02-04 10:27:41 +0000758void RuntimeImpl::LoadDynamicBackends(const std::string& overrideBackendPath)
Matteo Martincighe54aa062019-08-05 14:12:11 +0100759{
760 // Get the paths where to load the dynamic backends from
761 std::vector<std::string> backendPaths = DynamicBackendUtils::GetBackendPaths(overrideBackendPath);
762
763 // Get the shared objects to try to load as dynamic backends
764 std::vector<std::string> sharedObjects = DynamicBackendUtils::GetSharedObjects(backendPaths);
765
766 // Create a list of dynamic backends
Matteo Martincigh0c2b2892019-08-05 14:12:11 +0100767 m_DynamicBackends = DynamicBackendUtils::CreateDynamicBackends(sharedObjects);
768
769 // Register the dynamic backends in the backend registry
Cathal Corbett5aa9fd72022-02-25 15:33:28 +0000770 armnn::BackendIdSet registeredBackendIds = DynamicBackendUtils::RegisterDynamicBackends(m_DynamicBackends);
Matteo Martincigh89533902019-08-15 12:08:06 +0100771
772 // Add the registered dynamic backend ids to the list of supported backends
Narumol Prangnawarat60a20fb2019-12-09 17:24:41 +0000773 m_DeviceSpec.AddSupportedBackends(registeredBackendIds, true);
telsoa014fcda012018-03-09 14:13:49 +0000774}
Matteo Martincighe54aa062019-08-05 14:12:11 +0100775
776} // namespace armnn