// // Copyright © 2017 Arm Ltd. All rights reserved. // SPDX-License-Identifier: MIT // #include #include #if defined(ARMNN_SERIALIZER) #include "armnnDeserializer/IDeserializer.hpp" #endif #if defined(ARMNN_CAFFE_PARSER) #include "armnnCaffeParser/ICaffeParser.hpp" #endif #if defined(ARMNN_TF_PARSER) #include "armnnTfParser/ITfParser.hpp" #endif #if defined(ARMNN_TF_LITE_PARSER) #include "armnnTfLiteParser/ITfLiteParser.hpp" #endif #if defined(ARMNN_ONNX_PARSER) #include "armnnOnnxParser/IOnnxParser.hpp" #endif #include "CsvReader.hpp" #include "../InferenceTest.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { // Configure boost::program_options for command-line parsing and validation. namespace po = boost::program_options; template std::vector ParseArrayImpl(std::istream& stream, TParseElementFunc parseElementFunc, const char * chars = "\t ,:") { std::vector result; // Processes line-by-line. std::string line; while (std::getline(stream, line)) { std::vector tokens; try { // Coverity fix: boost::split() may throw an exception of type boost::bad_function_call. boost::split(tokens, line, boost::algorithm::is_any_of(chars), boost::token_compress_on); } catch (const std::exception& e) { ARMNN_LOG(error) << "An error occurred when splitting tokens: " << e.what(); continue; } for (const std::string& token : tokens) { if (!token.empty()) // See https://stackoverflow.com/questions/10437406/ { try { result.push_back(parseElementFunc(token)); } catch (const std::exception&) { ARMNN_LOG(error) << "'" << token << "' is not a valid number. It has been ignored."; } } } } return result; } bool CheckOption(const po::variables_map& vm, const char* option) { // Check that the given option is valid. if (option == nullptr) { return false; } // Check whether 'option' is provided. return vm.find(option) != vm.end(); } void CheckOptionDependency(const po::variables_map& vm, const char* option, const char* required) { // Check that the given options are valid. if (option == nullptr || required == nullptr) { throw po::error("Invalid option to check dependency for"); } // Check that if 'option' is provided, 'required' is also provided. if (CheckOption(vm, option) && !vm[option].defaulted()) { if (CheckOption(vm, required) == 0 || vm[required].defaulted()) { throw po::error(std::string("Option '") + option + "' requires option '" + required + "'."); } } } void CheckOptionDependencies(const po::variables_map& vm) { CheckOptionDependency(vm, "model-path", "model-format"); CheckOptionDependency(vm, "model-path", "input-name"); CheckOptionDependency(vm, "model-path", "output-name"); CheckOptionDependency(vm, "input-tensor-shape", "model-path"); } template auto ParseDataArray(std::istream & stream); template auto ParseDataArray(std::istream& stream, const float& quantizationScale, const int32_t& quantizationOffset); template<> auto ParseDataArray(std::istream & stream) { return ParseArrayImpl(stream, [](const std::string& s) { return std::stof(s); }); } template<> auto ParseDataArray(std::istream & stream) { return ParseArrayImpl(stream, [](const std::string & s) { return std::stoi(s); }); } template<> auto ParseDataArray(std::istream& stream) { return ParseArrayImpl(stream, [](const std::string& s) { return boost::numeric_cast(std::stoi(s)); }); } template<> auto ParseDataArray(std::istream& stream, const float& quantizationScale, const int32_t& quantizationOffset) { return ParseArrayImpl(stream, [&quantizationScale, &quantizationOffset](const std::string & s) { return boost::numeric_cast( armnn::Quantize(std::stof(s), quantizationScale, quantizationOffset)); }); } std::vector ParseArray(std::istream& stream) { return ParseArrayImpl(stream, [](const std::string& s) { return boost::numeric_cast(std::stoi(s)); }); } std::vector ParseStringList(const std::string & inputString, const char * delimiter) { std::stringstream stream(inputString); return ParseArrayImpl(stream, [](const std::string& s) { return boost::trim_copy(s); }, delimiter); } void RemoveDuplicateDevices(std::vector& 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()); } struct TensorPrinter : public boost::static_visitor<> { TensorPrinter(const std::string& binding, const armnn::TensorInfo& info, const std::string& outputTensorFile, bool dequantizeOutput) : m_OutputBinding(binding) , m_Scale(info.GetQuantizationScale()) , m_Offset(info.GetQuantizationOffset()) , m_OutputTensorFile(outputTensorFile) , m_DequantizeOutput(dequantizeOutput) {} void operator()(const std::vector& values) { ForEachValue(values, [](float value) { printf("%f ", value); }); WriteToFile(values); } void operator()(const std::vector& values) { if(m_DequantizeOutput) { auto& scale = m_Scale; auto& offset = m_Offset; std::vector dequantizedValues; ForEachValue(values, [&scale, &offset, &dequantizedValues](uint8_t value) { auto dequantizedValue = armnn::Dequantize(value, scale, offset); printf("%f ", dequantizedValue); dequantizedValues.push_back(dequantizedValue); }); WriteToFile(dequantizedValues); } else { const std::vector intValues(values.begin(), values.end()); operator()(intValues); } } void operator()(const std::vector& values) { ForEachValue(values, [](int value) { printf("%d ", value); }); WriteToFile(values); } private: template void ForEachValue(const Container& c, Delegate delegate) { std::cout << m_OutputBinding << ": "; for (const auto& value : c) { delegate(value); } printf("\n"); } template void WriteToFile(const std::vector& values) { if (!m_OutputTensorFile.empty()) { std::ofstream outputTensorFile; outputTensorFile.open(m_OutputTensorFile, std::ofstream::out | std::ofstream::trunc); if (outputTensorFile.is_open()) { outputTensorFile << m_OutputBinding << ": "; std::copy(values.begin(), values.end(), std::ostream_iterator(outputTensorFile, " ")); } else { ARMNN_LOG(info) << "Output Tensor File: " << m_OutputTensorFile << " could not be opened!"; } outputTensorFile.close(); } } std::string m_OutputBinding; float m_Scale=0.0f; int m_Offset=0; std::string m_OutputTensorFile; bool m_DequantizeOutput = false; }; template> std::vector GenerateDummyTensorData(unsigned int numElements) { return std::vector(numElements, static_cast(0)); } using TContainer = boost::variant, std::vector, std::vector>; using QuantizationParams = std::pair; void PopulateTensorWithData(TContainer& tensorData, unsigned int numElements, const std::string& dataTypeStr, const armnn::Optional& qParams, const armnn::Optional& dataFile) { const bool readFromFile = dataFile.has_value() && !dataFile.value().empty(); const bool quantizeData = qParams.has_value(); std::ifstream inputTensorFile; if (readFromFile) { inputTensorFile = std::ifstream(dataFile.value()); } if (dataTypeStr.compare("float") == 0) { if (quantizeData) { const float qScale = qParams.value().first; const int qOffset = qParams.value().second; tensorData = readFromFile ? ParseDataArray(inputTensorFile, qScale, qOffset) : GenerateDummyTensorData(numElements); } else { tensorData = readFromFile ? ParseDataArray(inputTensorFile) : GenerateDummyTensorData(numElements); } } else if (dataTypeStr.compare("int") == 0) { tensorData = readFromFile ? ParseDataArray(inputTensorFile) : GenerateDummyTensorData(numElements); } else if (dataTypeStr.compare("qasymm8") == 0) { tensorData = readFromFile ? ParseDataArray(inputTensorFile) : GenerateDummyTensorData(numElements); } else { std::string errorMessage = "Unsupported tensor data type " + dataTypeStr; ARMNN_LOG(fatal) << errorMessage; inputTensorFile.close(); throw armnn::Exception(errorMessage); } inputTensorFile.close(); } } // anonymous namespace bool generateTensorData = true; struct ExecuteNetworkParams { using TensorShapePtr = std::unique_ptr; const char* m_ModelPath; bool m_IsModelBinary; std::vector m_ComputeDevices; std::string m_DynamicBackendsPath; std::vector m_InputNames; std::vector m_InputTensorShapes; std::vector m_InputTensorDataFilePaths; std::vector m_InputTypes; bool m_QuantizeInput; std::vector m_OutputTypes; std::vector m_OutputNames; std::vector m_OutputTensorFiles; bool m_DequantizeOutput; bool m_EnableProfiling; bool m_EnableFp16TurboMode; double m_ThresholdTime; bool m_PrintIntermediate; size_t m_SubgraphId; bool m_EnableLayerDetails = false; bool m_GenerateTensorData; bool m_ParseUnsupported = false; }; template int MainImpl(const ExecuteNetworkParams& params, const std::shared_ptr& runtime = nullptr) { using TContainer = boost::variant, std::vector, std::vector>; std::vector inputDataContainers; try { // Creates an InferenceModel, which will parse the model and load it into an IRuntime. typename InferenceModel::Params inferenceModelParams; inferenceModelParams.m_ModelPath = params.m_ModelPath; inferenceModelParams.m_IsModelBinary = params.m_IsModelBinary; inferenceModelParams.m_ComputeDevices = params.m_ComputeDevices; inferenceModelParams.m_DynamicBackendsPath = params.m_DynamicBackendsPath; inferenceModelParams.m_PrintIntermediateLayers = params.m_PrintIntermediate; inferenceModelParams.m_VisualizePostOptimizationModel = params.m_EnableLayerDetails; inferenceModelParams.m_ParseUnsupported = params.m_ParseUnsupported; for(const std::string& inputName: params.m_InputNames) { inferenceModelParams.m_InputBindings.push_back(inputName); } for(unsigned int i = 0; i < params.m_InputTensorShapes.size(); ++i) { inferenceModelParams.m_InputShapes.push_back(*params.m_InputTensorShapes[i]); } for(const std::string& outputName: params.m_OutputNames) { inferenceModelParams.m_OutputBindings.push_back(outputName); } inferenceModelParams.m_SubgraphId = params.m_SubgraphId; inferenceModelParams.m_EnableFp16TurboMode = params.m_EnableFp16TurboMode; InferenceModel model(inferenceModelParams, params.m_EnableProfiling, params.m_DynamicBackendsPath, runtime); const size_t numInputs = inferenceModelParams.m_InputBindings.size(); for(unsigned int i = 0; i < numInputs; ++i) { armnn::Optional qParams = params.m_QuantizeInput ? armnn::MakeOptional(model.GetInputQuantizationParams()) : armnn::EmptyOptional(); armnn::Optional dataFile = params.m_GenerateTensorData ? armnn::EmptyOptional() : armnn::MakeOptional(params.m_InputTensorDataFilePaths[i]); unsigned int numElements = model.GetInputSize(i); if (params.m_InputTensorShapes.size() > i && params.m_InputTensorShapes[i]) { // If the user has provided a tensor shape for the current input, // override numElements numElements = params.m_InputTensorShapes[i]->GetNumElements(); } TContainer tensorData; PopulateTensorWithData(tensorData, numElements, params.m_InputTypes[i], qParams, dataFile); inputDataContainers.push_back(tensorData); } const size_t numOutputs = inferenceModelParams.m_OutputBindings.size(); std::vector outputDataContainers; for (unsigned int i = 0; i < numOutputs; ++i) { if (params.m_OutputTypes[i].compare("float") == 0) { outputDataContainers.push_back(std::vector(model.GetOutputSize(i))); } else if (params.m_OutputTypes[i].compare("int") == 0) { outputDataContainers.push_back(std::vector(model.GetOutputSize(i))); } else if (params.m_OutputTypes[i].compare("qasymm8") == 0) { outputDataContainers.push_back(std::vector(model.GetOutputSize(i))); } else { ARMNN_LOG(fatal) << "Unsupported tensor data type \"" << params.m_OutputTypes[i] << "\". "; return EXIT_FAILURE; } } // model.Run returns the inference time elapsed in EnqueueWorkload (in milliseconds) auto inference_duration = model.Run(inputDataContainers, outputDataContainers); if (params.m_GenerateTensorData) { ARMNN_LOG(warning) << "The input data was generated, note that the output will not be useful"; } // Print output tensors const auto& infosOut = model.GetOutputBindingInfos(); for (size_t i = 0; i < numOutputs; i++) { const armnn::TensorInfo& infoOut = infosOut[i].second; auto outputTensorFile = params.m_OutputTensorFiles.empty() ? "" : params.m_OutputTensorFiles[i]; TensorPrinter printer(inferenceModelParams.m_OutputBindings[i], infoOut, outputTensorFile, params.m_DequantizeOutput); boost::apply_visitor(printer, outputDataContainers[i]); } ARMNN_LOG(info) << "\nInference time: " << std::setprecision(2) << std::fixed << inference_duration.count() << " ms"; // If thresholdTime == 0.0 (default), then it hasn't been supplied at command line if (params.m_ThresholdTime != 0.0) { ARMNN_LOG(info) << "Threshold time: " << std::setprecision(2) << std::fixed << params.m_ThresholdTime << " ms"; auto thresholdMinusInference = params.m_ThresholdTime - inference_duration.count(); ARMNN_LOG(info) << "Threshold time - Inference time: " << std::setprecision(2) << std::fixed << thresholdMinusInference << " ms" << "\n"; if (thresholdMinusInference < 0) { std::string errorMessage = "Elapsed inference time is greater than provided threshold time."; ARMNN_LOG(fatal) << errorMessage; } } } catch (armnn::Exception const& e) { ARMNN_LOG(fatal) << "Armnn Error: " << e.what(); return EXIT_FAILURE; } return EXIT_SUCCESS; } // This will run a test int RunTest(const std::string& format, const std::string& inputTensorShapesStr, const vector& computeDevices, const std::string& dynamicBackendsPath, const std::string& path, const std::string& inputNames, const std::string& inputTensorDataFilePaths, const std::string& inputTypes, bool quantizeInput, const std::string& outputTypes, const std::string& outputNames, const std::string& outputTensorFiles, bool dequantizeOuput, bool enableProfiling, bool enableFp16TurboMode, const double& thresholdTime, bool printIntermediate, const size_t subgraphId, bool enableLayerDetails = false, bool parseUnsupported = false, const std::shared_ptr& runtime = nullptr) { std::string modelFormat = boost::trim_copy(format); std::string modelPath = boost::trim_copy(path); std::vector inputNamesVector = ParseStringList(inputNames, ","); std::vector inputTensorShapesVector = ParseStringList(inputTensorShapesStr, ":"); std::vector inputTensorDataFilePathsVector = ParseStringList( inputTensorDataFilePaths, ","); std::vector outputNamesVector = ParseStringList(outputNames, ","); std::vector inputTypesVector = ParseStringList(inputTypes, ","); std::vector outputTypesVector = ParseStringList(outputTypes, ","); std::vector outputTensorFilesVector = ParseStringList(outputTensorFiles, ","); // Parse model binary flag from the model-format string we got from the command-line bool isModelBinary; if (modelFormat.find("bin") != std::string::npos) { isModelBinary = true; } else if (modelFormat.find("txt") != std::string::npos || modelFormat.find("text") != std::string::npos) { isModelBinary = false; } else { ARMNN_LOG(fatal) << "Unknown model format: '" << modelFormat << "'. Please include 'binary' or 'text'"; return EXIT_FAILURE; } if ((inputTensorShapesVector.size() != 0) && (inputTensorShapesVector.size() != inputNamesVector.size())) { ARMNN_LOG(fatal) << "input-name and input-tensor-shape must have the same amount of elements."; return EXIT_FAILURE; } if ((inputTensorDataFilePathsVector.size() != 0) && (inputTensorDataFilePathsVector.size() != inputNamesVector.size())) { ARMNN_LOG(fatal) << "input-name and input-tensor-data must have the same amount of elements."; return EXIT_FAILURE; } if ((outputTensorFilesVector.size() != 0) && (outputTensorFilesVector.size() != outputNamesVector.size())) { ARMNN_LOG(fatal) << "output-name and write-outputs-to-file must have the same amount of elements."; return EXIT_FAILURE; } if (inputTypesVector.size() == 0) { //Defaults the value of all inputs to "float" inputTypesVector.assign(inputNamesVector.size(), "float"); } else if ((inputTypesVector.size() != 0) && (inputTypesVector.size() != inputNamesVector.size())) { ARMNN_LOG(fatal) << "input-name and input-type must have the same amount of elements."; return EXIT_FAILURE; } if (outputTypesVector.size() == 0) { //Defaults the value of all outputs to "float" outputTypesVector.assign(outputNamesVector.size(), "float"); } else if ((outputTypesVector.size() != 0) && (outputTypesVector.size() != outputNamesVector.size())) { ARMNN_LOG(fatal) << "output-name and output-type must have the same amount of elements."; return EXIT_FAILURE; } // Parse input tensor shape from the string we got from the command-line. std::vector> inputTensorShapes; if (!inputTensorShapesVector.empty()) { inputTensorShapes.reserve(inputTensorShapesVector.size()); for(const std::string& shape : inputTensorShapesVector) { std::stringstream ss(shape); std::vector dims = ParseArray(ss); try { // Coverity fix: An exception of type armnn::InvalidArgumentException is thrown and never caught. inputTensorShapes.push_back(std::make_unique(dims.size(), dims.data())); } catch (const armnn::InvalidArgumentException& e) { ARMNN_LOG(fatal) << "Cannot create tensor shape: " << e.what(); return EXIT_FAILURE; } } } // Check that threshold time is not less than zero if (thresholdTime < 0) { ARMNN_LOG(fatal) << "Threshold time supplied as a command line argument is less than zero."; return EXIT_FAILURE; } ExecuteNetworkParams params; params.m_ModelPath = modelPath.c_str(); params.m_IsModelBinary = isModelBinary; params.m_ComputeDevices = computeDevices; params.m_DynamicBackendsPath = dynamicBackendsPath; params.m_InputNames = inputNamesVector; params.m_InputTensorShapes = std::move(inputTensorShapes); params.m_InputTensorDataFilePaths = inputTensorDataFilePathsVector; params.m_InputTypes = inputTypesVector; params.m_QuantizeInput = quantizeInput; params.m_OutputTypes = outputTypesVector; params.m_OutputNames = outputNamesVector; params.m_OutputTensorFiles = outputTensorFilesVector; params.m_DequantizeOutput = dequantizeOuput; params.m_EnableProfiling = enableProfiling; params.m_EnableFp16TurboMode = enableFp16TurboMode; params.m_ThresholdTime = thresholdTime; params.m_PrintIntermediate = printIntermediate; params.m_SubgraphId = subgraphId; params.m_EnableLayerDetails = enableLayerDetails; params.m_GenerateTensorData = inputTensorDataFilePathsVector.empty(); params.m_ParseUnsupported = parseUnsupported; // Warn if ExecuteNetwork will generate dummy input data if (params.m_GenerateTensorData) { ARMNN_LOG(warning) << "No input files provided, input tensors will be filled with 0s."; } // Forward to implementation based on the parser type if (modelFormat.find("armnn") != std::string::npos) { #if defined(ARMNN_SERIALIZER) return MainImpl(params, runtime); #else ARMNN_LOG(fatal) << "Not built with serialization support."; return EXIT_FAILURE; #endif } else if (modelFormat.find("caffe") != std::string::npos) { #if defined(ARMNN_CAFFE_PARSER) return MainImpl(params, runtime); #else ARMNN_LOG(fatal) << "Not built with Caffe parser support."; return EXIT_FAILURE; #endif } else if (modelFormat.find("onnx") != std::string::npos) { #if defined(ARMNN_ONNX_PARSER) return MainImpl(params, runtime); #else ARMNN_LOG(fatal) << "Not built with Onnx parser support."; return EXIT_FAILURE; #endif } else if (modelFormat.find("tensorflow") != std::string::npos) { #if defined(ARMNN_TF_PARSER) return MainImpl(params, runtime); #else ARMNN_LOG(fatal) << "Not built with Tensorflow parser support."; return EXIT_FAILURE; #endif } else if(modelFormat.find("tflite") != std::string::npos) { #if defined(ARMNN_TF_LITE_PARSER) if (! isModelBinary) { ARMNN_LOG(fatal) << "Unknown model format: '" << modelFormat << "'. Only 'binary' format supported \ for tflite files"; return EXIT_FAILURE; } return MainImpl(params, runtime); #else ARMNN_LOG(fatal) << "Unknown model format: '" << modelFormat << "'. Please include 'caffe', 'tensorflow', 'tflite' or 'onnx'"; return EXIT_FAILURE; #endif } else { ARMNN_LOG(fatal) << "Unknown model format: '" << modelFormat << "'. Please include 'caffe', 'tensorflow', 'tflite' or 'onnx'"; return EXIT_FAILURE; } } int RunCsvTest(const armnnUtils::CsvRow &csvRow, const std::shared_ptr& runtime, const bool enableProfiling, const bool enableFp16TurboMode, const double& thresholdTime, const bool printIntermediate, bool enableLayerDetails = false, bool parseUnuspported = false) { boost::ignore_unused(runtime); std::string modelFormat; std::string modelPath; std::string inputNames; std::string inputTensorShapes; std::string inputTensorDataFilePaths; std::string outputNames; std::string inputTypes; std::string outputTypes; std::string dynamicBackendsPath; std::string outputTensorFiles; size_t subgraphId = 0; const std::string backendsMessage = std::string("The preferred order of devices to run layers on by default. ") + std::string("Possible choices: ") + armnn::BackendRegistryInstance().GetBackendIdsAsString(); po::options_description desc("Options"); try { desc.add_options() ("model-format,f", po::value(&modelFormat), "armnn-binary, caffe-binary, caffe-text, tflite-binary, onnx-binary, onnx-text, tensorflow-binary or " "tensorflow-text.") ("model-path,m", po::value(&modelPath), "Path to model file, e.g. .armnn, .caffemodel, .prototxt, " ".tflite, .onnx") ("compute,c", po::value>()->multitoken(), backendsMessage.c_str()) ("dynamic-backends-path,b", po::value(&dynamicBackendsPath), "Path where to load any available dynamic backend from. " "If left empty (the default), dynamic backends will not be used.") ("input-name,i", po::value(&inputNames), "Identifier of the input tensors in the network separated by comma.") ("subgraph-number,n", po::value(&subgraphId)->default_value(0), "Id of the subgraph to be " "executed. Defaults to 0.") ("input-tensor-shape,s", po::value(&inputTensorShapes), "The shape of the input tensors in the network as a flat array of integers separated by comma. " "Several shapes can be passed separating them by semicolon. " "This parameter is optional, depending on the network.") ("input-tensor-data,d", po::value(&inputTensorDataFilePaths)->default_value(""), "Path to files containing the input data as a flat array separated by whitespace. " "Several paths can be passed separating them by comma. If not specified, the network will be run with dummy " "data (useful for profiling).") ("input-type,y",po::value(&inputTypes), "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).") ("quantize-input,q",po::bool_switch()->default_value(false), "If this option is enabled, all float inputs will be quantized to qasymm8. " "If unset, default to not quantized. " "Accepted values (true or false)") ("output-type,z",po::value(&outputTypes), "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).") ("output-name,o", po::value(&outputNames), "Identifier of the output tensors in the network separated by comma.") ("dequantize-output,l",po::bool_switch()->default_value(false), "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)") ("write-outputs-to-file,w", po::value(&outputTensorFiles), "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."); } catch (const std::exception& e) { // Coverity points out that default_value(...) can throw a bad_lexical_cast, // and that desc.add_options() can throw boost::io::too_few_args. // They really won't in any of these cases. BOOST_ASSERT_MSG(false, "Caught unexpected exception"); ARMNN_LOG(fatal) << "Fatal internal error: " << e.what(); return EXIT_FAILURE; } std::vector clOptions; clOptions.reserve(csvRow.values.size()); for (const std::string& value : csvRow.values) { clOptions.push_back(value.c_str()); } po::variables_map vm; try { po::store(po::parse_command_line(static_cast(clOptions.size()), clOptions.data(), desc), vm); po::notify(vm); CheckOptionDependencies(vm); } catch (const po::error& e) { std::cerr << e.what() << std::endl << std::endl; std::cerr << desc << std::endl; return EXIT_FAILURE; } // Get the value of the switch arguments. bool quantizeInput = vm["quantize-input"].as(); bool dequantizeOutput = vm["dequantize-output"].as(); // Get the preferred order of compute devices. std::vector computeDevices = vm["compute"].as>(); // Remove duplicates from the list of compute devices. RemoveDuplicateDevices(computeDevices); // Check that the specified compute devices are valid. std::string invalidBackends; if (!CheckRequestedBackendsAreValid(computeDevices, armnn::Optional(invalidBackends))) { ARMNN_LOG(fatal) << "The list of preferred devices contains invalid backend IDs: " << invalidBackends; return EXIT_FAILURE; } return RunTest(modelFormat, inputTensorShapes, computeDevices, dynamicBackendsPath, modelPath, inputNames, inputTensorDataFilePaths, inputTypes, quantizeInput, outputTypes, outputNames, outputTensorFiles, dequantizeOutput, enableProfiling, enableFp16TurboMode, thresholdTime, printIntermediate, subgraphId, enableLayerDetails, parseUnuspported); }