blob: 5fefd0561951126d2061a35b14ce30b59be37ec3 [file] [log] [blame]
telsoa014fcda012018-03-09 14:13:49 +00001//
2// Copyright © 2017 Arm Ltd. All rights reserved.
David Beckecb56cd2018-09-05 12:52:57 +01003// SPDX-License-Identifier: MIT
telsoa014fcda012018-03-09 14:13:49 +00004//
5#pragma once
David Beckf0b48452018-10-19 15:20:56 +01006#include <armnn/ArmNN.hpp>
telsoa01c577f2c2018-08-31 09:22:23 +01007
8#if defined(ARMNN_TF_LITE_PARSER)
David Beckf0b48452018-10-19 15:20:56 +01009#include <armnnTfLiteParser/ITfLiteParser.hpp>
telsoa01c577f2c2018-08-31 09:22:23 +010010#endif
11
12#include <HeapProfiling.hpp>
13#if defined(ARMNN_ONNX_PARSER)
David Beckf0b48452018-10-19 15:20:56 +010014#include <armnnOnnxParser/IOnnxParser.hpp>
telsoa01c577f2c2018-08-31 09:22:23 +010015#endif
telsoa014fcda012018-03-09 14:13:49 +000016
David Beck1b61be52018-11-08 09:19:14 +000017#include <backendsCommon/BackendRegistry.hpp>
Aron Virginas-Tar5cc8e562018-10-23 15:14:46 +010018
surmeh013537c2c2018-05-18 16:31:43 +010019#include <boost/exception/exception.hpp>
20#include <boost/exception/diagnostic_information.hpp>
telsoa014fcda012018-03-09 14:13:49 +000021#include <boost/log/trivial.hpp>
22#include <boost/format.hpp>
23#include <boost/program_options.hpp>
surmeh013537c2c2018-05-18 16:31:43 +010024#include <boost/filesystem.hpp>
David Beckf0b48452018-10-19 15:20:56 +010025#include <boost/lexical_cast.hpp>
telsoa014fcda012018-03-09 14:13:49 +000026
Aron Virginas-Tar5cc8e562018-10-23 15:14:46 +010027#include <fstream>
telsoa014fcda012018-03-09 14:13:49 +000028#include <map>
29#include <string>
telsoa01c577f2c2018-08-31 09:22:23 +010030#include <type_traits>
31
Aron Virginas-Tar5cc8e562018-10-23 15:14:46 +010032namespace
33{
34
35inline bool CheckRequestedBackendsAreValid(const std::vector<armnn::BackendId>& backendIds,
36 armnn::Optional<std::string&> invalidBackendIds = armnn::EmptyOptional())
37{
38 if (backendIds.empty())
39 {
40 return false;
41 }
42
43 armnn::BackendIdSet validBackendIds = armnn::BackendRegistryInstance().GetBackendIds();
44
45 bool allValid = true;
46 for (const auto& backendId : backendIds)
47 {
48 if (std::find(validBackendIds.begin(), validBackendIds.end(), backendId) == validBackendIds.end())
49 {
50 allValid = false;
51 if (invalidBackendIds)
52 {
53 if (!invalidBackendIds.value().empty())
54 {
55 invalidBackendIds.value() += ", ";
56 }
57 invalidBackendIds.value() += backendId;
58 }
59 }
60 }
61 return allValid;
62}
63
64} // anonymous namespace
65
telsoa01c577f2c2018-08-31 09:22:23 +010066namespace InferenceModelInternal
67{
68// This needs to go when the armnnCaffeParser, armnnTfParser and armnnTfLiteParser
69// definitions of BindingPointInfo gets consolidated.
70using BindingPointInfo = std::pair<armnn::LayerBindingId, armnn::TensorInfo>;
71
72using QuantizationParams = std::pair<float,int32_t>;
73
74struct Params
75{
76 std::string m_ModelPath;
77 std::string m_InputBinding;
78 std::string m_OutputBinding;
79 const armnn::TensorShape* m_InputTensorShape;
David Beckf0b48452018-10-19 15:20:56 +010080 std::vector<armnn::BackendId> m_ComputeDevice;
telsoa01c577f2c2018-08-31 09:22:23 +010081 bool m_EnableProfiling;
82 size_t m_SubgraphId;
83 bool m_IsModelBinary;
84 bool m_VisualizePostOptimizationModel;
85 bool m_EnableFp16TurboMode;
86
87 Params()
88 : m_InputTensorShape(nullptr)
89 , m_ComputeDevice{armnn::Compute::CpuRef}
90 , m_EnableProfiling(false)
91 , m_SubgraphId(0)
92 , m_IsModelBinary(true)
93 , m_VisualizePostOptimizationModel(false)
94 , m_EnableFp16TurboMode(false)
95 {}
96};
97
98} // namespace InferenceModelInternal
99
100template <typename IParser>
101struct CreateNetworkImpl
102{
103public:
104 using Params = InferenceModelInternal::Params;
105 using BindingPointInfo = InferenceModelInternal::BindingPointInfo;
106
107 static armnn::INetworkPtr Create(const Params& params,
108 BindingPointInfo& inputBindings,
109 BindingPointInfo& outputBindings)
110 {
111 const std::string& modelPath = params.m_ModelPath;
112
113 // Create a network from a file on disk
114 auto parser(IParser::Create());
115
116 std::map<std::string, armnn::TensorShape> inputShapes;
117 if (params.m_InputTensorShape)
118 {
119 inputShapes[params.m_InputBinding] = *params.m_InputTensorShape;
120 }
121 std::vector<std::string> requestedOutputs{ params.m_OutputBinding };
122 armnn::INetworkPtr network{nullptr, [](armnn::INetwork *){}};
123
124 {
125 ARMNN_SCOPED_HEAP_PROFILING("Parsing");
126 // Handle text and binary input differently by calling the corresponding parser function
127 network = (params.m_IsModelBinary ?
128 parser->CreateNetworkFromBinaryFile(modelPath.c_str(), inputShapes, requestedOutputs) :
129 parser->CreateNetworkFromTextFile(modelPath.c_str(), inputShapes, requestedOutputs));
130 }
131
132 inputBindings = parser->GetNetworkInputBindingInfo(params.m_InputBinding);
133 outputBindings = parser->GetNetworkOutputBindingInfo(params.m_OutputBinding);
134 return network;
135 }
136};
137
138#if defined(ARMNN_TF_LITE_PARSER)
139template <>
140struct CreateNetworkImpl<armnnTfLiteParser::ITfLiteParser>
141{
142public:
143 using IParser = armnnTfLiteParser::ITfLiteParser;
144 using Params = InferenceModelInternal::Params;
145 using BindingPointInfo = InferenceModelInternal::BindingPointInfo;
146
147 static armnn::INetworkPtr Create(const Params& params,
148 BindingPointInfo& inputBindings,
149 BindingPointInfo& outputBindings)
150 {
151 const std::string& modelPath = params.m_ModelPath;
152
153 // Create a network from a file on disk
154 auto parser(IParser::Create());
155
156 armnn::INetworkPtr network{nullptr, [](armnn::INetwork *){}};
157
158 {
159 ARMNN_SCOPED_HEAP_PROFILING("Parsing");
160 network = parser->CreateNetworkFromBinaryFile(modelPath.c_str());
161 }
162
163 inputBindings = parser->GetNetworkInputBindingInfo(params.m_SubgraphId, params.m_InputBinding);
164 outputBindings = parser->GetNetworkOutputBindingInfo(params.m_SubgraphId, params.m_OutputBinding);
165 return network;
166 }
167};
168#endif
169
170#if defined(ARMNN_ONNX_PARSER)
171template <>
172struct CreateNetworkImpl<armnnOnnxParser::IOnnxParser>
173{
174public:
175 using IParser = armnnOnnxParser::IOnnxParser;
176 using Params = InferenceModelInternal::Params;
177 using BindingPointInfo = InferenceModelInternal::BindingPointInfo;
178
179 static armnn::INetworkPtr Create(const Params& params,
180 BindingPointInfo& inputBindings,
181 BindingPointInfo& outputBindings)
182 {
183 const std::string& modelPath = params.m_ModelPath;
184
185 // Create a network from a file on disk
186 auto parser(IParser::Create());
187
188 armnn::INetworkPtr network{nullptr, [](armnn::INetwork *){}};
189
190 {
191 ARMNN_SCOPED_HEAP_PROFILING("Parsing");
192 network = (params.m_IsModelBinary ?
193 parser->CreateNetworkFromBinaryFile(modelPath.c_str()) :
194 parser->CreateNetworkFromTextFile(modelPath.c_str()));
195 }
196
197 inputBindings = parser->GetNetworkInputBindingInfo(params.m_InputBinding);
198 outputBindings = parser->GetNetworkOutputBindingInfo(params.m_OutputBinding);
199 return network;
200 }
201};
202#endif
telsoa014fcda012018-03-09 14:13:49 +0000203
204template<typename TContainer>
telsoa01c577f2c2018-08-31 09:22:23 +0100205inline armnn::InputTensors MakeInputTensors(const InferenceModelInternal::BindingPointInfo& input,
telsoa014fcda012018-03-09 14:13:49 +0000206 const TContainer& inputTensorData)
207{
208 if (inputTensorData.size() != input.second.GetNumElements())
209 {
surmeh013537c2c2018-05-18 16:31:43 +0100210 try
211 {
212 throw armnn::Exception(boost::str(boost::format("Input tensor has incorrect size. Expected %1% elements "
213 "but got %2%.") % input.second.GetNumElements() % inputTensorData.size()));
214 } catch (const boost::exception& e)
215 {
216 // Coverity fix: it should not be possible to get here but boost::str and boost::format can both
telsoa01c577f2c2018-08-31 09:22:23 +0100217 // throw uncaught exceptions, convert them to armnn exceptions and rethrow.
surmeh013537c2c2018-05-18 16:31:43 +0100218 throw armnn::Exception(diagnostic_information(e));
219 }
telsoa014fcda012018-03-09 14:13:49 +0000220 }
221 return { { input.first, armnn::ConstTensor(input.second, inputTensorData.data()) } };
222}
223
224template<typename TContainer>
telsoa01c577f2c2018-08-31 09:22:23 +0100225inline armnn::OutputTensors MakeOutputTensors(const InferenceModelInternal::BindingPointInfo& output,
telsoa014fcda012018-03-09 14:13:49 +0000226 TContainer& outputTensorData)
227{
228 if (outputTensorData.size() != output.second.GetNumElements())
229 {
230 throw armnn::Exception("Output tensor has incorrect size");
231 }
232 return { { output.first, armnn::Tensor(output.second, outputTensorData.data()) } };
233}
234
235template <typename IParser, typename TDataType>
236class InferenceModel
237{
238public:
239 using DataType = TDataType;
telsoa01c577f2c2018-08-31 09:22:23 +0100240 using Params = InferenceModelInternal::Params;
telsoa014fcda012018-03-09 14:13:49 +0000241
242 struct CommandLineOptions
243 {
244 std::string m_ModelDir;
David Beckf0b48452018-10-19 15:20:56 +0100245 std::vector<armnn::BackendId> m_ComputeDevice;
surmeh013537c2c2018-05-18 16:31:43 +0100246 bool m_VisualizePostOptimizationModel;
telsoa01c577f2c2018-08-31 09:22:23 +0100247 bool m_EnableFp16TurboMode;
telsoa014fcda012018-03-09 14:13:49 +0000248 };
249
250 static void AddCommandLineOptions(boost::program_options::options_description& desc, CommandLineOptions& options)
251 {
252 namespace po = boost::program_options;
253
David Beckf0b48452018-10-19 15:20:56 +0100254 std::vector<armnn::BackendId> defaultBackends = {armnn::Compute::CpuAcc, armnn::Compute::CpuRef};
255
Aron Virginas-Tar5cc8e562018-10-23 15:14:46 +0100256 const std::string backendsMessage = "Which device to run layers on by default. Possible choices: "
257 + armnn::BackendRegistryInstance().GetBackendIdsAsString();
258
telsoa014fcda012018-03-09 14:13:49 +0000259 desc.add_options()
260 ("model-dir,m", po::value<std::string>(&options.m_ModelDir)->required(),
telsoa01c577f2c2018-08-31 09:22:23 +0100261 "Path to directory containing model files (.caffemodel/.prototxt/.tflite)")
David Beckf0b48452018-10-19 15:20:56 +0100262 ("compute,c", po::value<std::vector<armnn::BackendId>>(&options.m_ComputeDevice)->default_value
Aron Virginas-Tar5cc8e562018-10-23 15:14:46 +0100263 (defaultBackends), backendsMessage.c_str())
surmeh013537c2c2018-05-18 16:31:43 +0100264 ("visualize-optimized-model,v",
265 po::value<bool>(&options.m_VisualizePostOptimizationModel)->default_value(false),
266 "Produce a dot file useful for visualizing the graph post optimization."
telsoa01c577f2c2018-08-31 09:22:23 +0100267 "The file will have the same name as the model with the .dot extention.")
268 ("fp16-turbo-mode", po::value<bool>(&options.m_EnableFp16TurboMode)->default_value(false),
269 "If this option is enabled FP32 layers, weights and biases will be converted "
270 "to FP16 where the backend supports it.");
telsoa014fcda012018-03-09 14:13:49 +0000271 }
272
telsoa01c577f2c2018-08-31 09:22:23 +0100273 InferenceModel(const Params& params, const std::shared_ptr<armnn::IRuntime>& runtime = nullptr)
274 : m_EnableProfiling(params.m_EnableProfiling)
telsoa014fcda012018-03-09 14:13:49 +0000275 {
telsoa01c577f2c2018-08-31 09:22:23 +0100276 if (runtime)
telsoa014fcda012018-03-09 14:13:49 +0000277 {
telsoa01c577f2c2018-08-31 09:22:23 +0100278 m_Runtime = runtime;
telsoa014fcda012018-03-09 14:13:49 +0000279 }
telsoa01c577f2c2018-08-31 09:22:23 +0100280 else
telsoa014fcda012018-03-09 14:13:49 +0000281 {
telsoa01c577f2c2018-08-31 09:22:23 +0100282 armnn::IRuntime::CreationOptions options;
Nina Drozd549ae372018-09-10 14:26:44 +0100283 options.m_EnableGpuProfiling = m_EnableProfiling;
telsoa01c577f2c2018-08-31 09:22:23 +0100284 m_Runtime = std::move(armnn::IRuntime::Create(options));
surmeh013537c2c2018-05-18 16:31:43 +0100285 }
telsoa014fcda012018-03-09 14:13:49 +0000286
Aron Virginas-Tar5cc8e562018-10-23 15:14:46 +0100287 std::string invalidBackends;
288 if (!CheckRequestedBackendsAreValid(params.m_ComputeDevice, armnn::Optional<std::string&>(invalidBackends)))
289 {
290 throw armnn::Exception("Some backend IDs are invalid: " + invalidBackends);
291 }
292
telsoa01c577f2c2018-08-31 09:22:23 +0100293 armnn::INetworkPtr network = CreateNetworkImpl<IParser>::Create(params, m_InputBindingInfo,
294 m_OutputBindingInfo);
telsoa014fcda012018-03-09 14:13:49 +0000295
surmeh013537c2c2018-05-18 16:31:43 +0100296 armnn::IOptimizedNetworkPtr optNet{nullptr, [](armnn::IOptimizedNetwork *){}};
297 {
298 ARMNN_SCOPED_HEAP_PROFILING("Optimizing");
telsoa01c577f2c2018-08-31 09:22:23 +0100299
300 armnn::OptimizerOptions options;
301 options.m_ReduceFp32ToFp16 = params.m_EnableFp16TurboMode;
302
303 optNet = armnn::Optimize(*network, params.m_ComputeDevice, m_Runtime->GetDeviceSpec(), options);
304 if (!optNet)
305 {
306 throw armnn::Exception("Optimize returned nullptr");
307 }
surmeh013537c2c2018-05-18 16:31:43 +0100308 }
telsoa014fcda012018-03-09 14:13:49 +0000309
surmeh013537c2c2018-05-18 16:31:43 +0100310 if (params.m_VisualizePostOptimizationModel)
311 {
312 boost::filesystem::path filename = params.m_ModelPath;
313 filename.replace_extension("dot");
314 std::fstream file(filename.c_str(),file.out);
315 optNet->SerializeToDot(file);
316 }
317
318 armnn::Status ret;
319 {
320 ARMNN_SCOPED_HEAP_PROFILING("LoadNetwork");
321 ret = m_Runtime->LoadNetwork(m_NetworkIdentifier, std::move(optNet));
322 }
323
telsoa014fcda012018-03-09 14:13:49 +0000324 if (ret == armnn::Status::Failure)
325 {
326 throw armnn::Exception("IRuntime::LoadNetwork failed");
327 }
328 }
329
330 unsigned int GetOutputSize() const
331 {
332 return m_OutputBindingInfo.second.GetNumElements();
333 }
334
335 void Run(const std::vector<TDataType>& input, std::vector<TDataType>& output)
336 {
337 BOOST_ASSERT(output.size() == GetOutputSize());
telsoa01c577f2c2018-08-31 09:22:23 +0100338
339 std::shared_ptr<armnn::IProfiler> profiler = m_Runtime->GetProfiler(m_NetworkIdentifier);
340 if (profiler)
341 {
342 profiler->EnableProfiling(m_EnableProfiling);
343 }
344
telsoa014fcda012018-03-09 14:13:49 +0000345 armnn::Status ret = m_Runtime->EnqueueWorkload(m_NetworkIdentifier,
telsoa01c577f2c2018-08-31 09:22:23 +0100346 MakeInputTensors(input),
347 MakeOutputTensors(output));
Sadik Armagan2b7a1582018-09-05 16:33:58 +0100348
349 // if profiling is enabled print out the results
350 if (profiler && profiler->IsProfilingEnabled())
351 {
352 profiler->Print(std::cout);
353 }
354
telsoa014fcda012018-03-09 14:13:49 +0000355 if (ret == armnn::Status::Failure)
356 {
357 throw armnn::Exception("IRuntime::EnqueueWorkload failed");
358 }
359 }
360
telsoa01c577f2c2018-08-31 09:22:23 +0100361 const InferenceModelInternal::BindingPointInfo & GetInputBindingInfo() const
362 {
363 return m_InputBindingInfo;
364 }
365
366 const InferenceModelInternal::BindingPointInfo & GetOutputBindingInfo() const
367 {
368 return m_OutputBindingInfo;
369 }
370
371 InferenceModelInternal::QuantizationParams GetQuantizationParams() const
372 {
373 return std::make_pair(m_OutputBindingInfo.second.GetQuantizationScale(),
374 m_OutputBindingInfo.second.GetQuantizationOffset());
375 }
376
telsoa014fcda012018-03-09 14:13:49 +0000377private:
telsoa01c577f2c2018-08-31 09:22:23 +0100378 armnn::NetworkId m_NetworkIdentifier;
379 std::shared_ptr<armnn::IRuntime> m_Runtime;
380
381 InferenceModelInternal::BindingPointInfo m_InputBindingInfo;
382 InferenceModelInternal::BindingPointInfo m_OutputBindingInfo;
383 bool m_EnableProfiling;
384
telsoa014fcda012018-03-09 14:13:49 +0000385 template<typename TContainer>
386 armnn::InputTensors MakeInputTensors(const TContainer& inputTensorData)
387 {
388 return ::MakeInputTensors(m_InputBindingInfo, inputTensorData);
389 }
390
391 template<typename TContainer>
392 armnn::OutputTensors MakeOutputTensors(TContainer& outputTensorData)
393 {
394 return ::MakeOutputTensors(m_OutputBindingInfo, outputTensorData);
395 }
telsoa014fcda012018-03-09 14:13:49 +0000396};