aboutsummaryrefslogtreecommitdiff
path: root/tests/ExecuteNetwork/ExecuteNetworkProgramOptions.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/ExecuteNetwork/ExecuteNetworkProgramOptions.cpp')
-rw-r--r--tests/ExecuteNetwork/ExecuteNetworkProgramOptions.cpp414
1 files changed, 414 insertions, 0 deletions
diff --git a/tests/ExecuteNetwork/ExecuteNetworkProgramOptions.cpp b/tests/ExecuteNetwork/ExecuteNetworkProgramOptions.cpp
new file mode 100644
index 0000000000..8434adf691
--- /dev/null
+++ b/tests/ExecuteNetwork/ExecuteNetworkProgramOptions.cpp
@@ -0,0 +1,414 @@
+//
+// Copyright © 2020 Arm Ltd and Contributors. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#include "ExecuteNetworkProgramOptions.hpp"
+#include "NetworkExecutionUtils/NetworkExecutionUtils.hpp"
+#include "InferenceTest.hpp"
+
+#include <armnn/BackendRegistry.hpp>
+#include <armnn/Exceptions.hpp>
+#include <armnn/utility/Assert.hpp>
+#include <armnn/utility/StringUtils.hpp>
+#include <armnn/Logging.hpp>
+
+#include <fmt/format.h>
+
+bool CheckOption(const cxxopts::ParseResult& result,
+ const char* option)
+{
+ // Check that the given option is valid.
+ if (option == nullptr)
+ {
+ return false;
+ }
+
+ // Check whether 'option' is provided.
+ return ((result.count(option)) ? true : false);
+}
+
+void CheckOptionDependency(const cxxopts::ParseResult& result,
+ const char* option,
+ const char* required)
+{
+ // Check that the given options are valid.
+ if (option == nullptr || required == nullptr)
+ {
+ throw cxxopts::OptionParseException("Invalid option to check dependency for");
+ }
+
+ // Check that if 'option' is provided, 'required' is also provided.
+ if (CheckOption(result, option) && !result[option].has_default())
+ {
+ if (CheckOption(result, required) == 0 || result[required].has_default())
+ {
+ throw cxxopts::OptionParseException(
+ std::string("Option '") + option + "' requires option '" + required + "'.");
+ }
+ }
+}
+
+void CheckOptionDependencies(const cxxopts::ParseResult& result)
+{
+ CheckOptionDependency(result, "model-path", "model-format");
+ CheckOptionDependency(result, "input-tensor-shape", "model-path");
+ CheckOptionDependency(result, "tuning-level", "tuning-path");
+}
+
+void RemoveDuplicateDevices(std::vector<armnn::BackendId>& computeDevices)
+{
+ // Mark the duplicate devices as 'Undefined'.
+ for (auto i = computeDevices.begin(); i != computeDevices.end(); ++i)
+ {
+ for (auto j = std::next(i); j != computeDevices.end(); ++j)
+ {
+ if (*j == *i)
+ {
+ *j = armnn::Compute::Undefined;
+ }
+ }
+ }
+
+ // Remove 'Undefined' devices.
+ computeDevices.erase(std::remove(computeDevices.begin(), computeDevices.end(), armnn::Compute::Undefined),
+ computeDevices.end());
+}
+
+/// Takes a vector of backend strings and returns a vector of backendIDs. Removes duplicate entries.
+std::vector<armnn::BackendId> GetBackendIDs(const std::vector<std::string>& backendStrings)
+{
+ std::vector<armnn::BackendId> backendIDs;
+ for (const auto& b : backendStrings)
+ {
+ backendIDs.push_back(armnn::BackendId(b));
+ }
+
+ RemoveDuplicateDevices(backendIDs);
+
+ return backendIDs;
+}
+
+/// Provides a segfault safe way to get cxxopts option values by checking if the option was defined.
+/// If the option wasn't defined it returns an empty object.
+template<typename optionType>
+optionType GetOptionValue(std::string&& optionName, const cxxopts::ParseResult& result)
+{
+ optionType out;
+ if(result.count(optionName))
+ {
+ out = result[optionName].as<optionType>();
+ }
+ return out;
+}
+
+void LogAndThrowFatal(std::string errorMessage)
+{
+ throw armnn::InvalidArgumentException (errorMessage);
+}
+
+void CheckRequiredOptions(const cxxopts::ParseResult& result)
+{
+
+ // For each option in option-group "a) Required
+ std::vector<std::string> requiredOptions{"compute",
+ "model-format",
+ "model-path",
+ "input-name",
+ "output-name"};
+
+ bool requiredMissing = false;
+ for(auto const& str : requiredOptions)
+ {
+ if(!(result.count(str) > 0))
+ {
+ ARMNN_LOG(error) << fmt::format("The program option '{}' is mandatory but wasn't provided.", str);
+ requiredMissing = true;
+ }
+ }
+ if(requiredMissing)
+ {
+ throw armnn::InvalidArgumentException ("Some required arguments are missing");
+ }
+}
+
+void ProgramOptions::ValidateExecuteNetworkParams()
+{
+ m_ExNetParams.ValidateParams();
+}
+
+void ProgramOptions::ValidateRuntimeOptions()
+{
+ if (m_RuntimeOptions.m_ProfilingOptions.m_TimelineEnabled &&
+ !m_RuntimeOptions.m_ProfilingOptions.m_EnableProfiling)
+ {
+ LogAndThrowFatal("Timeline profiling requires external profiling to be turned on");
+ }
+}
+
+
+ProgramOptions::ProgramOptions() : m_CxxOptions{"ExecuteNetwork",
+ "Executes a neural network model using the provided input "
+ "tensor. Prints the resulting output tensor."}
+{
+ try
+ {
+ // cxxopts doesn't provide a mechanism to ensure required options are given. There is a
+ // separate function CheckRequiredOptions() for that.
+ m_CxxOptions.add_options("a) Required")
+ ("c,compute",
+ "Which device to run layers on by default. Possible choices: "
+ + armnn::BackendRegistryInstance().GetBackendIdsAsString()
+ + " NOTE: Compute devices need to be passed as a comma separated list without whitespaces "
+ "e.g. CpuRef,CpuAcc",
+ cxxopts::value<std::string>())
+
+ ("f,model-format",
+ "armnn-binary, caffe-binary, caffe-text, onnx-binary, onnx-text, tflite-binary, tensorflow-binary or "
+ "tensorflow-text.",
+ cxxopts::value<std::string>())
+
+ ("m,model-path",
+ "Path to model file, e.g. .armnn, .caffemodel, .prototxt, .tflite, .onnx",
+ cxxopts::value<std::string>(m_ExNetParams.m_ModelPath))
+
+ ("i,input-name",
+ "Identifier of the input tensors in the network separated by comma.",
+ cxxopts::value<std::string>())
+
+ ("o,output-name",
+ "Identifier of the output tensors in the network separated by comma.",
+ cxxopts::value<std::string>());
+
+ m_CxxOptions.add_options("b) General")
+ ("b,dynamic-backends-path",
+ "Path where to load any available dynamic backend from. "
+ "If left empty (the default), dynamic backends will not be used.",
+ cxxopts::value<std::string>(m_RuntimeOptions.m_DynamicBackendsPath))
+
+ ("d,input-tensor-data",
+ "Path to files containing the input data as a flat array separated by whitespace. "
+ "Several paths can be passed by separating them with a comma. If not specified, the network will be "
+ "run with dummy data (useful for profiling).",
+ cxxopts::value<std::string>()->default_value(""))
+
+ ("h,help", "Display usage information")
+
+ ("infer-output-shape",
+ "Infers output tensor shape from input tensor shape and validate where applicable (where supported by "
+ "parser)",
+ cxxopts::value<bool>(m_ExNetParams.m_InferOutputShape)->default_value("false")->implicit_value("true"))
+
+ ("iterations",
+ "Number of iterations to run the network for, default is set to 1",
+ cxxopts::value<size_t>(m_ExNetParams.m_Iterations)->default_value("1"))
+
+ ("l,dequantize-output",
+ "If this option is enabled, all quantized outputs will be dequantized to float. "
+ "If unset, default to not get dequantized. "
+ "Accepted values (true or false)",
+ cxxopts::value<bool>(m_ExNetParams.m_DequantizeOutput)->default_value("false")->implicit_value("true"))
+
+ ("p,print-intermediate-layers",
+ "If this option is enabled, the output of every graph layer will be printed.",
+ cxxopts::value<bool>(m_ExNetParams.m_PrintIntermediate)->default_value("false")
+ ->implicit_value("true"))
+
+ ("parse-unsupported",
+ "Add unsupported operators as stand-in layers (where supported by parser)",
+ cxxopts::value<bool>(m_ExNetParams.m_ParseUnsupported)->default_value("false")->implicit_value("true"))
+
+ ("q,quantize-input",
+ "If this option is enabled, all float inputs will be quantized to qasymm8. "
+ "If unset, default to not quantized. Accepted values (true or false)",
+ cxxopts::value<bool>(m_ExNetParams.m_QuantizeInput)->default_value("false")->implicit_value("true"))
+
+ ("r,threshold-time",
+ "Threshold time is the maximum allowed time for inference measured in milliseconds. If the actual "
+ "inference time is greater than the threshold time, the test will fail. By default, no threshold "
+ "time is used.",
+ cxxopts::value<double>(m_ExNetParams.m_ThresholdTime)->default_value("0.0"))
+
+ ("s,input-tensor-shape",
+ "The shape of the input tensors in the network as a flat array of integers separated by comma."
+ "Several shapes can be passed by separating them with a colon (:).",
+ cxxopts::value<std::string>())
+
+ ("v,visualize-optimized-model",
+ "Enables built optimized model visualizer. If unset, defaults to off.",
+ cxxopts::value<bool>(m_ExNetParams.m_EnableLayerDetails)->default_value("false")
+ ->implicit_value("true"))
+
+ ("w,write-outputs-to-file",
+ "Comma-separated list of output file paths keyed with the binding-id of the output slot. "
+ "If left empty (the default), the output tensors will not be written to a file.",
+ cxxopts::value<std::string>())
+
+ ("x,subgraph-number",
+ "Id of the subgraph to be executed. Defaults to 0.",
+ cxxopts::value<size_t>(m_ExNetParams.m_SubgraphId)->default_value("0"))
+
+ ("y,input-type",
+ "The type of the input tensors in the network separated by comma. "
+ "If unset, defaults to \"float\" for all defined inputs. "
+ "Accepted values (float, int or qasymm8).",
+ cxxopts::value<std::string>())
+
+ ("z,output-type",
+ "The type of the output tensors in the network separated by comma. "
+ "If unset, defaults to \"float\" for all defined outputs. "
+ "Accepted values (float, int or qasymm8).",
+ cxxopts::value<std::string>());
+
+ m_CxxOptions.add_options("c) Optimization")
+ ("bf16-turbo-mode",
+ "If this option is enabled, FP32 layers, "
+ "weights and biases will be converted to BFloat16 where the backend supports it",
+ cxxopts::value<bool>(m_ExNetParams.m_EnableBf16TurboMode)
+ ->default_value("false")->implicit_value("true"))
+
+ ("enable-fast-math",
+ "Enables fast_math options in backends that support it. Using the fast_math flag can lead to "
+ "performance improvements but may result in reduced or different precision.",
+ cxxopts::value<bool>(m_ExNetParams.m_EnableFastMath)->default_value("false")->implicit_value("true"))
+
+ ("fp16-turbo-mode",
+ "If this option is enabled, FP32 layers, "
+ "weights and biases will be converted to FP16 where the backend supports it",
+ cxxopts::value<bool>(m_ExNetParams.m_EnableFp16TurboMode)
+ ->default_value("false")->implicit_value("true"))
+
+ ("tuning-level",
+ "Sets the tuning level which enables a tuning run which will update/create a tuning file. "
+ "Available options are: 1 (Rapid), 2 (Normal), 3 (Exhaustive). "
+ "Requires tuning-path to be set, default is set to 0 (No tuning run)",
+ cxxopts::value<int>(m_ExNetParams.m_TuningLevel)->default_value("0"))
+
+ ("tuning-path",
+ "Path to tuning file. Enables use of CL tuning",
+ cxxopts::value<std::string>(m_ExNetParams.m_TuningPath));
+
+ m_CxxOptions.add_options("d) Profiling")
+ ("a,enable-external-profiling",
+ "If enabled external profiling will be switched on",
+ cxxopts::value<bool>(m_RuntimeOptions.m_ProfilingOptions.m_EnableProfiling)
+ ->default_value("false")->implicit_value("true"))
+
+ ("e,event-based-profiling",
+ "Enables built in profiler. If unset, defaults to off.",
+ cxxopts::value<bool>(m_ExNetParams.m_EnableProfiling)->default_value("false")->implicit_value("true"))
+
+ ("g,file-only-external-profiling",
+ "If enabled then the 'file-only' test mode of external profiling will be enabled",
+ cxxopts::value<bool>(m_RuntimeOptions.m_ProfilingOptions.m_FileOnly)
+ ->default_value("false")->implicit_value("true"))
+
+ ("file-format",
+ "If profiling is enabled specifies the output file format",
+ cxxopts::value<std::string>(m_RuntimeOptions.m_ProfilingOptions.m_FileFormat)->default_value("binary"))
+
+ ("j,outgoing-capture-file",
+ "If specified the outgoing external profiling packets will be captured in this binary file",
+ cxxopts::value<std::string>(m_RuntimeOptions.m_ProfilingOptions.m_OutgoingCaptureFile))
+
+ ("k,incoming-capture-file",
+ "If specified the incoming external profiling packets will be captured in this binary file",
+ cxxopts::value<std::string>(m_RuntimeOptions.m_ProfilingOptions.m_IncomingCaptureFile))
+
+ ("timeline-profiling",
+ "If enabled timeline profiling will be switched on, requires external profiling",
+ cxxopts::value<bool>(m_RuntimeOptions.m_ProfilingOptions.m_TimelineEnabled)
+ ->default_value("false")->implicit_value("true"))
+
+ ("u,counter-capture-period",
+ "If profiling is enabled in 'file-only' mode this is the capture period that will be used in the test",
+ cxxopts::value<uint32_t>(m_RuntimeOptions.m_ProfilingOptions.m_CapturePeriod)->default_value("150"));
+ }
+ catch (const std::exception& e)
+ {
+ ARMNN_ASSERT_MSG(false, "Caught unexpected exception");
+ ARMNN_LOG(fatal) << "Fatal internal error: " << e.what();
+ exit(EXIT_FAILURE);
+ }
+}
+
+ProgramOptions::ProgramOptions(int ac, const char* av[]): ProgramOptions()
+{
+ ParseOptions(ac, av);
+}
+
+void ProgramOptions::ParseOptions(int ac, const char* av[])
+{
+ // Parses the command-line.
+ m_CxxResult = m_CxxOptions.parse(ac, av);
+
+ if (m_CxxResult.count("help") || ac <= 1)
+ {
+ std::cout << m_CxxOptions.help() << std::endl;
+ exit(EXIT_SUCCESS);
+ }
+
+ CheckRequiredOptions(m_CxxResult);
+ CheckOptionDependencies(m_CxxResult);
+
+ // Some options can't be assigned directly because they need some post-processing:
+ auto computeDevices = GetOptionValue<std::string>("compute", m_CxxResult);
+ m_ExNetParams.m_ComputeDevices =
+ GetBackendIDs(ParseStringList(computeDevices, ","));
+ m_ExNetParams.m_ModelFormat =
+ armnn::stringUtils::StringTrimCopy(GetOptionValue<std::string>("model-format", m_CxxResult));
+ m_ExNetParams.m_InputNames =
+ ParseStringList(GetOptionValue<std::string>("input-name", m_CxxResult), ",");
+ m_ExNetParams.m_InputTensorDataFilePaths =
+ ParseStringList(GetOptionValue<std::string>("input-tensor-data", m_CxxResult), ",");
+ m_ExNetParams.m_OutputNames =
+ ParseStringList(GetOptionValue<std::string>("output-name", m_CxxResult), ",");
+ m_ExNetParams.m_InputTypes =
+ ParseStringList(GetOptionValue<std::string>("input-type", m_CxxResult), ",");
+ m_ExNetParams.m_OutputTypes =
+ ParseStringList(GetOptionValue<std::string>("output-type", m_CxxResult), ",");
+ m_ExNetParams.m_OutputTensorFiles =
+ ParseStringList(GetOptionValue<std::string>("write-outputs-to-file", m_CxxResult), ",");
+ m_ExNetParams.m_GenerateTensorData =
+ m_ExNetParams.m_InputTensorDataFilePaths.empty();
+
+ // Parse input tensor shape from the string we got from the command-line.
+ std::vector<std::string> inputTensorShapesVector =
+ ParseStringList(GetOptionValue<std::string>("input-tensor-shape", m_CxxResult), ":");
+
+ if (!inputTensorShapesVector.empty())
+ {
+ m_ExNetParams.m_InputTensorShapes.reserve(inputTensorShapesVector.size());
+
+ for(const std::string& shape : inputTensorShapesVector)
+ {
+ std::stringstream ss(shape);
+ std::vector<unsigned int> dims = ParseArray(ss);
+
+ m_ExNetParams.m_InputTensorShapes.push_back(
+ std::make_unique<armnn::TensorShape>(static_cast<unsigned int>(dims.size()), dims.data()));
+ }
+ }
+
+ // We have to validate ExecuteNetworkParams first so that the tuning path and level is validated
+ ValidateExecuteNetworkParams();
+
+ // Parse CL tuning parameters to runtime options
+ if (!m_ExNetParams.m_TuningPath.empty())
+ {
+ m_RuntimeOptions.m_BackendOptions.emplace_back(
+ armnn::BackendOptions
+ {
+ "GpuAcc",
+ {
+ {"TuningLevel", m_ExNetParams.m_TuningLevel},
+ {"TuningFile", m_ExNetParams.m_TuningPath.c_str()},
+ {"KernelProfilingEnabled", m_ExNetParams.m_EnableProfiling}
+ }
+ }
+ );
+ }
+
+ ValidateRuntimeOptions();
+}
+