blob: b499289f614ae90e7cbb575f1aff2f3077a5a394 [file] [log] [blame]
Jan Eilers45274902020-10-15 18:34:43 +01001//
2// Copyright © 2020 Arm Ltd and Contributors. All rights reserved.
3// SPDX-License-Identifier: MIT
4//
5
6#include "ExecuteNetworkProgramOptions.hpp"
7#include "NetworkExecutionUtils/NetworkExecutionUtils.hpp"
8#include "InferenceTest.hpp"
9
10#include <armnn/BackendRegistry.hpp>
11#include <armnn/Exceptions.hpp>
12#include <armnn/utility/Assert.hpp>
13#include <armnn/utility/StringUtils.hpp>
14#include <armnn/Logging.hpp>
15
16#include <fmt/format.h>
17
18bool CheckOption(const cxxopts::ParseResult& result,
19 const char* option)
20{
21 // Check that the given option is valid.
22 if (option == nullptr)
23 {
24 return false;
25 }
26
27 // Check whether 'option' is provided.
28 return ((result.count(option)) ? true : false);
29}
30
31void CheckOptionDependency(const cxxopts::ParseResult& result,
32 const char* option,
33 const char* required)
34{
35 // Check that the given options are valid.
36 if (option == nullptr || required == nullptr)
37 {
38 throw cxxopts::OptionParseException("Invalid option to check dependency for");
39 }
40
41 // Check that if 'option' is provided, 'required' is also provided.
42 if (CheckOption(result, option) && !result[option].has_default())
43 {
44 if (CheckOption(result, required) == 0 || result[required].has_default())
45 {
46 throw cxxopts::OptionParseException(
47 std::string("Option '") + option + "' requires option '" + required + "'.");
48 }
49 }
50}
51
52void CheckOptionDependencies(const cxxopts::ParseResult& result)
53{
54 CheckOptionDependency(result, "model-path", "model-format");
55 CheckOptionDependency(result, "input-tensor-shape", "model-path");
56 CheckOptionDependency(result, "tuning-level", "tuning-path");
57}
58
59void RemoveDuplicateDevices(std::vector<armnn::BackendId>& computeDevices)
60{
61 // Mark the duplicate devices as 'Undefined'.
62 for (auto i = computeDevices.begin(); i != computeDevices.end(); ++i)
63 {
64 for (auto j = std::next(i); j != computeDevices.end(); ++j)
65 {
66 if (*j == *i)
67 {
68 *j = armnn::Compute::Undefined;
69 }
70 }
71 }
72
73 // Remove 'Undefined' devices.
74 computeDevices.erase(std::remove(computeDevices.begin(), computeDevices.end(), armnn::Compute::Undefined),
75 computeDevices.end());
76}
77
78/// Takes a vector of backend strings and returns a vector of backendIDs. Removes duplicate entries.
79std::vector<armnn::BackendId> GetBackendIDs(const std::vector<std::string>& backendStrings)
80{
81 std::vector<armnn::BackendId> backendIDs;
82 for (const auto& b : backendStrings)
83 {
84 backendIDs.push_back(armnn::BackendId(b));
85 }
86
87 RemoveDuplicateDevices(backendIDs);
88
89 return backendIDs;
90}
91
92/// Provides a segfault safe way to get cxxopts option values by checking if the option was defined.
93/// If the option wasn't defined it returns an empty object.
94template<typename optionType>
95optionType GetOptionValue(std::string&& optionName, const cxxopts::ParseResult& result)
96{
97 optionType out;
98 if(result.count(optionName))
99 {
100 out = result[optionName].as<optionType>();
101 }
102 return out;
103}
104
105void LogAndThrowFatal(std::string errorMessage)
106{
107 throw armnn::InvalidArgumentException (errorMessage);
108}
109
110void CheckRequiredOptions(const cxxopts::ParseResult& result)
111{
112
113 // For each option in option-group "a) Required
114 std::vector<std::string> requiredOptions{"compute",
115 "model-format",
116 "model-path",
117 "input-name",
118 "output-name"};
119
120 bool requiredMissing = false;
121 for(auto const& str : requiredOptions)
122 {
123 if(!(result.count(str) > 0))
124 {
125 ARMNN_LOG(error) << fmt::format("The program option '{}' is mandatory but wasn't provided.", str);
126 requiredMissing = true;
127 }
128 }
129 if(requiredMissing)
130 {
131 throw armnn::InvalidArgumentException ("Some required arguments are missing");
132 }
133}
134
135void ProgramOptions::ValidateExecuteNetworkParams()
136{
137 m_ExNetParams.ValidateParams();
138}
139
140void ProgramOptions::ValidateRuntimeOptions()
141{
142 if (m_RuntimeOptions.m_ProfilingOptions.m_TimelineEnabled &&
143 !m_RuntimeOptions.m_ProfilingOptions.m_EnableProfiling)
144 {
145 LogAndThrowFatal("Timeline profiling requires external profiling to be turned on");
146 }
147}
148
149
150ProgramOptions::ProgramOptions() : m_CxxOptions{"ExecuteNetwork",
151 "Executes a neural network model using the provided input "
152 "tensor. Prints the resulting output tensor."}
153{
154 try
155 {
156 // cxxopts doesn't provide a mechanism to ensure required options are given. There is a
157 // separate function CheckRequiredOptions() for that.
158 m_CxxOptions.add_options("a) Required")
159 ("c,compute",
160 "Which device to run layers on by default. Possible choices: "
161 + armnn::BackendRegistryInstance().GetBackendIdsAsString()
162 + " NOTE: Compute devices need to be passed as a comma separated list without whitespaces "
163 "e.g. CpuRef,CpuAcc",
Jan Eilers3dda41d2020-11-11 11:44:14 +0000164 cxxopts::value<std::vector<std::string>>())
Jan Eilers45274902020-10-15 18:34:43 +0100165
166 ("f,model-format",
167 "armnn-binary, caffe-binary, caffe-text, onnx-binary, onnx-text, tflite-binary, tensorflow-binary or "
168 "tensorflow-text.",
169 cxxopts::value<std::string>())
170
Sadik Armagan5d03e312020-11-17 16:43:56 +0000171 ("D,armnn-tflite-delegate",
172 "enable Arm NN TfLite delegate",
173 cxxopts::value<bool>(m_ExNetParams.m_EnableDelegate)->default_value("false")->implicit_value("true"))
174
Jan Eilers45274902020-10-15 18:34:43 +0100175 ("m,model-path",
176 "Path to model file, e.g. .armnn, .caffemodel, .prototxt, .tflite, .onnx",
177 cxxopts::value<std::string>(m_ExNetParams.m_ModelPath))
178
179 ("i,input-name",
180 "Identifier of the input tensors in the network separated by comma.",
181 cxxopts::value<std::string>())
182
183 ("o,output-name",
184 "Identifier of the output tensors in the network separated by comma.",
185 cxxopts::value<std::string>());
186
187 m_CxxOptions.add_options("b) General")
188 ("b,dynamic-backends-path",
189 "Path where to load any available dynamic backend from. "
190 "If left empty (the default), dynamic backends will not be used.",
191 cxxopts::value<std::string>(m_RuntimeOptions.m_DynamicBackendsPath))
192
193 ("d,input-tensor-data",
194 "Path to files containing the input data as a flat array separated by whitespace. "
195 "Several paths can be passed by separating them with a comma. If not specified, the network will be "
196 "run with dummy data (useful for profiling).",
197 cxxopts::value<std::string>()->default_value(""))
198
199 ("h,help", "Display usage information")
200
201 ("infer-output-shape",
202 "Infers output tensor shape from input tensor shape and validate where applicable (where supported by "
203 "parser)",
204 cxxopts::value<bool>(m_ExNetParams.m_InferOutputShape)->default_value("false")->implicit_value("true"))
205
206 ("iterations",
207 "Number of iterations to run the network for, default is set to 1",
208 cxxopts::value<size_t>(m_ExNetParams.m_Iterations)->default_value("1"))
209
210 ("l,dequantize-output",
211 "If this option is enabled, all quantized outputs will be dequantized to float. "
212 "If unset, default to not get dequantized. "
213 "Accepted values (true or false)",
214 cxxopts::value<bool>(m_ExNetParams.m_DequantizeOutput)->default_value("false")->implicit_value("true"))
215
216 ("p,print-intermediate-layers",
217 "If this option is enabled, the output of every graph layer will be printed.",
218 cxxopts::value<bool>(m_ExNetParams.m_PrintIntermediate)->default_value("false")
219 ->implicit_value("true"))
220
221 ("parse-unsupported",
222 "Add unsupported operators as stand-in layers (where supported by parser)",
223 cxxopts::value<bool>(m_ExNetParams.m_ParseUnsupported)->default_value("false")->implicit_value("true"))
224
225 ("q,quantize-input",
226 "If this option is enabled, all float inputs will be quantized to qasymm8. "
227 "If unset, default to not quantized. Accepted values (true or false)",
228 cxxopts::value<bool>(m_ExNetParams.m_QuantizeInput)->default_value("false")->implicit_value("true"))
229
230 ("r,threshold-time",
231 "Threshold time is the maximum allowed time for inference measured in milliseconds. If the actual "
232 "inference time is greater than the threshold time, the test will fail. By default, no threshold "
233 "time is used.",
234 cxxopts::value<double>(m_ExNetParams.m_ThresholdTime)->default_value("0.0"))
235
236 ("s,input-tensor-shape",
237 "The shape of the input tensors in the network as a flat array of integers separated by comma."
238 "Several shapes can be passed by separating them with a colon (:).",
239 cxxopts::value<std::string>())
240
241 ("v,visualize-optimized-model",
242 "Enables built optimized model visualizer. If unset, defaults to off.",
243 cxxopts::value<bool>(m_ExNetParams.m_EnableLayerDetails)->default_value("false")
244 ->implicit_value("true"))
245
246 ("w,write-outputs-to-file",
247 "Comma-separated list of output file paths keyed with the binding-id of the output slot. "
248 "If left empty (the default), the output tensors will not be written to a file.",
249 cxxopts::value<std::string>())
250
251 ("x,subgraph-number",
252 "Id of the subgraph to be executed. Defaults to 0.",
253 cxxopts::value<size_t>(m_ExNetParams.m_SubgraphId)->default_value("0"))
254
255 ("y,input-type",
256 "The type of the input tensors in the network separated by comma. "
257 "If unset, defaults to \"float\" for all defined inputs. "
258 "Accepted values (float, int or qasymm8).",
259 cxxopts::value<std::string>())
260
261 ("z,output-type",
262 "The type of the output tensors in the network separated by comma. "
263 "If unset, defaults to \"float\" for all defined outputs. "
264 "Accepted values (float, int or qasymm8).",
265 cxxopts::value<std::string>());
266
267 m_CxxOptions.add_options("c) Optimization")
268 ("bf16-turbo-mode",
269 "If this option is enabled, FP32 layers, "
270 "weights and biases will be converted to BFloat16 where the backend supports it",
271 cxxopts::value<bool>(m_ExNetParams.m_EnableBf16TurboMode)
272 ->default_value("false")->implicit_value("true"))
273
274 ("enable-fast-math",
275 "Enables fast_math options in backends that support it. Using the fast_math flag can lead to "
276 "performance improvements but may result in reduced or different precision.",
277 cxxopts::value<bool>(m_ExNetParams.m_EnableFastMath)->default_value("false")->implicit_value("true"))
278
279 ("fp16-turbo-mode",
280 "If this option is enabled, FP32 layers, "
281 "weights and biases will be converted to FP16 where the backend supports it",
282 cxxopts::value<bool>(m_ExNetParams.m_EnableFp16TurboMode)
283 ->default_value("false")->implicit_value("true"))
284
285 ("tuning-level",
286 "Sets the tuning level which enables a tuning run which will update/create a tuning file. "
287 "Available options are: 1 (Rapid), 2 (Normal), 3 (Exhaustive). "
288 "Requires tuning-path to be set, default is set to 0 (No tuning run)",
289 cxxopts::value<int>(m_ExNetParams.m_TuningLevel)->default_value("0"))
290
291 ("tuning-path",
292 "Path to tuning file. Enables use of CL tuning",
293 cxxopts::value<std::string>(m_ExNetParams.m_TuningPath));
294
295 m_CxxOptions.add_options("d) Profiling")
296 ("a,enable-external-profiling",
297 "If enabled external profiling will be switched on",
298 cxxopts::value<bool>(m_RuntimeOptions.m_ProfilingOptions.m_EnableProfiling)
299 ->default_value("false")->implicit_value("true"))
300
301 ("e,event-based-profiling",
302 "Enables built in profiler. If unset, defaults to off.",
303 cxxopts::value<bool>(m_ExNetParams.m_EnableProfiling)->default_value("false")->implicit_value("true"))
304
305 ("g,file-only-external-profiling",
306 "If enabled then the 'file-only' test mode of external profiling will be enabled",
307 cxxopts::value<bool>(m_RuntimeOptions.m_ProfilingOptions.m_FileOnly)
308 ->default_value("false")->implicit_value("true"))
309
310 ("file-format",
311 "If profiling is enabled specifies the output file format",
312 cxxopts::value<std::string>(m_RuntimeOptions.m_ProfilingOptions.m_FileFormat)->default_value("binary"))
313
314 ("j,outgoing-capture-file",
315 "If specified the outgoing external profiling packets will be captured in this binary file",
316 cxxopts::value<std::string>(m_RuntimeOptions.m_ProfilingOptions.m_OutgoingCaptureFile))
317
318 ("k,incoming-capture-file",
319 "If specified the incoming external profiling packets will be captured in this binary file",
320 cxxopts::value<std::string>(m_RuntimeOptions.m_ProfilingOptions.m_IncomingCaptureFile))
321
322 ("timeline-profiling",
323 "If enabled timeline profiling will be switched on, requires external profiling",
324 cxxopts::value<bool>(m_RuntimeOptions.m_ProfilingOptions.m_TimelineEnabled)
325 ->default_value("false")->implicit_value("true"))
326
327 ("u,counter-capture-period",
328 "If profiling is enabled in 'file-only' mode this is the capture period that will be used in the test",
329 cxxopts::value<uint32_t>(m_RuntimeOptions.m_ProfilingOptions.m_CapturePeriod)->default_value("150"));
330 }
331 catch (const std::exception& e)
332 {
333 ARMNN_ASSERT_MSG(false, "Caught unexpected exception");
334 ARMNN_LOG(fatal) << "Fatal internal error: " << e.what();
335 exit(EXIT_FAILURE);
336 }
337}
338
339ProgramOptions::ProgramOptions(int ac, const char* av[]): ProgramOptions()
340{
341 ParseOptions(ac, av);
342}
343
344void ProgramOptions::ParseOptions(int ac, const char* av[])
345{
346 // Parses the command-line.
347 m_CxxResult = m_CxxOptions.parse(ac, av);
348
349 if (m_CxxResult.count("help") || ac <= 1)
350 {
351 std::cout << m_CxxOptions.help() << std::endl;
352 exit(EXIT_SUCCESS);
353 }
354
355 CheckRequiredOptions(m_CxxResult);
356 CheckOptionDependencies(m_CxxResult);
357
358 // Some options can't be assigned directly because they need some post-processing:
Jan Eilers3dda41d2020-11-11 11:44:14 +0000359 auto computeDevices = GetOptionValue<std::vector<std::string>>("compute", m_CxxResult);
360 m_ExNetParams.m_ComputeDevices = GetBackendIDs(computeDevices);
Jan Eilers45274902020-10-15 18:34:43 +0100361 m_ExNetParams.m_ModelFormat =
362 armnn::stringUtils::StringTrimCopy(GetOptionValue<std::string>("model-format", m_CxxResult));
363 m_ExNetParams.m_InputNames =
364 ParseStringList(GetOptionValue<std::string>("input-name", m_CxxResult), ",");
365 m_ExNetParams.m_InputTensorDataFilePaths =
366 ParseStringList(GetOptionValue<std::string>("input-tensor-data", m_CxxResult), ",");
367 m_ExNetParams.m_OutputNames =
368 ParseStringList(GetOptionValue<std::string>("output-name", m_CxxResult), ",");
369 m_ExNetParams.m_InputTypes =
370 ParseStringList(GetOptionValue<std::string>("input-type", m_CxxResult), ",");
371 m_ExNetParams.m_OutputTypes =
372 ParseStringList(GetOptionValue<std::string>("output-type", m_CxxResult), ",");
373 m_ExNetParams.m_OutputTensorFiles =
374 ParseStringList(GetOptionValue<std::string>("write-outputs-to-file", m_CxxResult), ",");
375 m_ExNetParams.m_GenerateTensorData =
376 m_ExNetParams.m_InputTensorDataFilePaths.empty();
Francis Murtaghbf18a262020-10-27 15:20:40 +0000377 m_ExNetParams.m_DynamicBackendsPath = m_RuntimeOptions.m_DynamicBackendsPath;
Jan Eilers45274902020-10-15 18:34:43 +0100378
379 // Parse input tensor shape from the string we got from the command-line.
380 std::vector<std::string> inputTensorShapesVector =
381 ParseStringList(GetOptionValue<std::string>("input-tensor-shape", m_CxxResult), ":");
382
383 if (!inputTensorShapesVector.empty())
384 {
385 m_ExNetParams.m_InputTensorShapes.reserve(inputTensorShapesVector.size());
386
387 for(const std::string& shape : inputTensorShapesVector)
388 {
389 std::stringstream ss(shape);
390 std::vector<unsigned int> dims = ParseArray(ss);
391
392 m_ExNetParams.m_InputTensorShapes.push_back(
393 std::make_unique<armnn::TensorShape>(static_cast<unsigned int>(dims.size()), dims.data()));
394 }
395 }
396
397 // We have to validate ExecuteNetworkParams first so that the tuning path and level is validated
398 ValidateExecuteNetworkParams();
399
400 // Parse CL tuning parameters to runtime options
401 if (!m_ExNetParams.m_TuningPath.empty())
402 {
403 m_RuntimeOptions.m_BackendOptions.emplace_back(
404 armnn::BackendOptions
405 {
406 "GpuAcc",
407 {
408 {"TuningLevel", m_ExNetParams.m_TuningLevel},
409 {"TuningFile", m_ExNetParams.m_TuningPath.c_str()},
410 {"KernelProfilingEnabled", m_ExNetParams.m_EnableProfiling}
411 }
412 }
413 );
414 }
415
416 ValidateRuntimeOptions();
417}
418