diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/CMakeLists.txt | 15 | ||||
-rw-r--r-- | tests/ExecuteNetwork/ExecuteNetwork.cpp | 672 | ||||
-rw-r--r-- | tests/ModelAccuracyTool-Armnn/ModelAccuracyTool-Armnn.cpp | 108 | ||||
-rw-r--r-- | tests/NetworkExecutionUtils/NetworkExecutionUtils.hpp | 676 |
4 files changed, 735 insertions, 736 deletions
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index dfcf4b48e0..19612543b6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -294,6 +294,21 @@ endif() if(BUILD_ACCURACY_TOOL) macro(AccuracyTool executorName) target_link_libraries(${executorName} ${CMAKE_THREAD_LIBS_INIT}) + if (BUILD_ARMNN_SERIALIZER) + target_link_libraries(${executorName} armnnSerializer) + endif() + if (BUILD_CAFFE_PARSER) + target_link_libraries(${executorName} armnnCaffeParser) + endif() + if (BUILD_TF_PARSER) + target_link_libraries(${executorName} armnnTfParser) + endif() + if (BUILD_TF_LITE_PARSER) + target_link_libraries(${executorName} armnnTfLiteParser) + endif() + if (BUILD_ONNX_PARSER) + target_link_libraries(${executorName} armnnOnnxParser) + endif() if(OPENCL_LIBRARIES) target_link_libraries(${executorName} ${OPENCL_LIBRARIES}) endif() diff --git a/tests/ExecuteNetwork/ExecuteNetwork.cpp b/tests/ExecuteNetwork/ExecuteNetwork.cpp index 8ca69d8292..60353dbf3f 100644 --- a/tests/ExecuteNetwork/ExecuteNetwork.cpp +++ b/tests/ExecuteNetwork/ExecuteNetwork.cpp @@ -2,678 +2,8 @@ // Copyright © 2017 Arm Ltd. All rights reserved. // SPDX-License-Identifier: MIT // -#include <armnn/ArmNN.hpp> -#include <armnn/TypesUtils.hpp> -#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 <Logging.hpp> -#include <Profiling.hpp> - -#include <boost/algorithm/string/trim.hpp> -#include <boost/algorithm/string/split.hpp> -#include <boost/algorithm/string/classification.hpp> -#include <boost/program_options.hpp> -#include <boost/variant.hpp> - -#include <iostream> -#include <fstream> -#include <functional> -#include <future> -#include <algorithm> -#include <iterator> - -namespace -{ - -// Configure boost::program_options for command-line parsing and validation. -namespace po = boost::program_options; - -template<typename T, typename TParseElementFunc> -std::vector<T> ParseArrayImpl(std::istream& stream, TParseElementFunc parseElementFunc, const char * chars = "\t ,:") -{ - std::vector<T> result; - // Processes line-by-line. - std::string line; - while (std::getline(stream, line)) - { - std::vector<std::string> 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) - { - BOOST_LOG_TRIVIAL(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&) - { - BOOST_LOG_TRIVIAL(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", "input-tensor-data"); - CheckOptionDependency(vm, "model-path", "output-name"); - CheckOptionDependency(vm, "input-tensor-shape", "model-path"); -} - -template<armnn::DataType NonQuantizedType> -auto ParseDataArray(std::istream & stream); - -template<armnn::DataType QuantizedType> -auto ParseDataArray(std::istream& stream, - const float& quantizationScale, - const int32_t& quantizationOffset); - -template<> -auto ParseDataArray<armnn::DataType::Float32>(std::istream & stream) -{ - return ParseArrayImpl<float>(stream, [](const std::string& s) { return std::stof(s); }); -} - -template<> -auto ParseDataArray<armnn::DataType::Signed32>(std::istream & stream) -{ - return ParseArrayImpl<int>(stream, [](const std::string & s) { return std::stoi(s); }); -} - -template<> -auto ParseDataArray<armnn::DataType::QuantisedAsymm8>(std::istream& stream, - const float& quantizationScale, - const int32_t& quantizationOffset) -{ - return ParseArrayImpl<uint8_t>(stream, - [&quantizationScale, &quantizationOffset](const std::string & s) - { - return boost::numeric_cast<uint8_t>( - armnn::Quantize<u_int8_t>(std::stof(s), - quantizationScale, - quantizationOffset)); - }); -} - -std::vector<unsigned int> ParseArray(std::istream& stream) -{ - return ParseArrayImpl<unsigned int>(stream, - [](const std::string& s) { return boost::numeric_cast<unsigned int>(std::stoi(s)); }); -} - -std::vector<std::string> ParseStringList(const std::string & inputString, const char * delimiter) -{ - std::stringstream stream(inputString); - return ParseArrayImpl<std::string>(stream, [](const std::string& s) { return boost::trim_copy(s); }, delimiter); -} - -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()); -} - -struct TensorPrinter : public boost::static_visitor<> -{ - TensorPrinter(const std::string& binding, const armnn::TensorInfo& info) - : m_OutputBinding(binding) - , m_Scale(info.GetQuantizationScale()) - , m_Offset(info.GetQuantizationOffset()) - {} - - void operator()(const std::vector<float>& values) - { - ForEachValue(values, [](float value){ - printf("%f ", value); - }); - } - - void operator()(const std::vector<uint8_t>& values) - { - auto& scale = m_Scale; - auto& offset = m_Offset; - ForEachValue(values, [&scale, &offset](uint8_t value) - { - printf("%f ", armnn::Dequantize(value, scale, offset)); - }); - } - - void operator()(const std::vector<int>& values) - { - ForEachValue(values, [](int value) - { - printf("%d ", value); - }); - } - -private: - template<typename Container, typename Delegate> - void ForEachValue(const Container& c, Delegate delegate) - { - std::cout << m_OutputBinding << ": "; - for (const auto& value : c) - { - delegate(value); - } - printf("\n"); - } - - std::string m_OutputBinding; - float m_Scale=0.0f; - int m_Offset=0; -}; - - -} // namespace - -template<typename TParser, typename TDataType> -int MainImpl(const char* modelPath, - bool isModelBinary, - const std::vector<armnn::BackendId>& computeDevices, - const std::vector<string>& inputNames, - const std::vector<std::unique_ptr<armnn::TensorShape>>& inputTensorShapes, - const std::vector<string>& inputTensorDataFilePaths, - const std::vector<string>& inputTypes, - const std::vector<string>& outputTypes, - const std::vector<string>& outputNames, - bool enableProfiling, - bool enableFp16TurboMode, - const double& thresholdTime, - const size_t subgraphId, - const std::shared_ptr<armnn::IRuntime>& runtime = nullptr) -{ - using TContainer = boost::variant<std::vector<float>, std::vector<int>, std::vector<unsigned char>>; - - std::vector<TContainer> inputDataContainers; - - try - { - // Creates an InferenceModel, which will parse the model and load it into an IRuntime. - typename InferenceModel<TParser, TDataType>::Params params; - params.m_ModelPath = modelPath; - params.m_IsModelBinary = isModelBinary; - params.m_ComputeDevices = computeDevices; - - for(const std::string& inputName: inputNames) - { - params.m_InputBindings.push_back(inputName); - } - - for(unsigned int i = 0; i < inputTensorShapes.size(); ++i) - { - params.m_InputShapes.push_back(*inputTensorShapes[i]); - } - - for(const std::string& outputName: outputNames) - { - params.m_OutputBindings.push_back(outputName); - } - - params.m_SubgraphId = subgraphId; - params.m_EnableFp16TurboMode = enableFp16TurboMode; - InferenceModel<TParser, TDataType> model(params, enableProfiling, runtime); - - for(unsigned int i = 0; i < inputTensorDataFilePaths.size(); ++i) - { - std::ifstream inputTensorFile(inputTensorDataFilePaths[i]); - - if (inputTypes[i].compare("float") == 0) - { - inputDataContainers.push_back( - ParseDataArray<armnn::DataType::Float32>(inputTensorFile)); - } - else if (inputTypes[i].compare("int") == 0) - { - inputDataContainers.push_back( - ParseDataArray<armnn::DataType::Signed32>(inputTensorFile)); - } - else if (inputTypes[i].compare("qasymm8") == 0) - { - auto inputBinding = model.GetInputBindingInfo(); - inputDataContainers.push_back( - ParseDataArray<armnn::DataType::QuantisedAsymm8>(inputTensorFile, - inputBinding.second.GetQuantizationScale(), - inputBinding.second.GetQuantizationOffset())); - } - else - { - BOOST_LOG_TRIVIAL(fatal) << "Unsupported tensor data type \"" << inputTypes[i] << "\". "; - return EXIT_FAILURE; - } - - inputTensorFile.close(); - } - - const size_t numOutputs = params.m_OutputBindings.size(); - std::vector<TContainer> outputDataContainers; - - for (unsigned int i = 0; i < numOutputs; ++i) - { - if (outputTypes[i].compare("float") == 0) - { - outputDataContainers.push_back(std::vector<float>(model.GetOutputSize(i))); - } - else if (outputTypes[i].compare("int") == 0) - { - outputDataContainers.push_back(std::vector<int>(model.GetOutputSize(i))); - } - else if (outputTypes[i].compare("qasymm8") == 0) - { - outputDataContainers.push_back(std::vector<uint8_t>(model.GetOutputSize(i))); - } - else - { - BOOST_LOG_TRIVIAL(fatal) << "Unsupported tensor data type \"" << outputTypes[i] << "\". "; - return EXIT_FAILURE; - } - } - - // model.Run returns the inference time elapsed in EnqueueWorkload (in milliseconds) - auto inference_duration = model.Run(inputDataContainers, outputDataContainers); - - // Print output tensors - const auto& infosOut = model.GetOutputBindingInfos(); - for (size_t i = 0; i < numOutputs; i++) - { - const armnn::TensorInfo& infoOut = infosOut[i].second; - TensorPrinter printer(params.m_OutputBindings[i], infoOut); - boost::apply_visitor(printer, outputDataContainers[i]); - } - - BOOST_LOG_TRIVIAL(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 (thresholdTime != 0.0) - { - BOOST_LOG_TRIVIAL(info) << "Threshold time: " << std::setprecision(2) - << std::fixed << thresholdTime << " ms"; - auto thresholdMinusInference = thresholdTime - inference_duration.count(); - BOOST_LOG_TRIVIAL(info) << "Threshold time - Inference time: " << std::setprecision(2) - << std::fixed << thresholdMinusInference << " ms" << "\n"; - - if (thresholdMinusInference < 0) - { - BOOST_LOG_TRIVIAL(fatal) << "Elapsed inference time is greater than provided threshold time.\n"; - return EXIT_FAILURE; - } - } - - - } - catch (armnn::Exception const& e) - { - BOOST_LOG_TRIVIAL(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<armnn::BackendId>& computeDevice, - const std::string& path, - const std::string& inputNames, - const std::string& inputTensorDataFilePaths, - const std::string& inputTypes, - const std::string& outputTypes, - const std::string& outputNames, - bool enableProfiling, - bool enableFp16TurboMode, - const double& thresholdTime, - const size_t subgraphId, - const std::shared_ptr<armnn::IRuntime>& runtime = nullptr) -{ - std::string modelFormat = boost::trim_copy(format); - std::string modelPath = boost::trim_copy(path); - std::vector<std::string> inputNamesVector = ParseStringList(inputNames, ","); - std::vector<std::string> inputTensorShapesVector = ParseStringList(inputTensorShapesStr, ";"); - std::vector<std::string> inputTensorDataFilePathsVector = ParseStringList( - inputTensorDataFilePaths, ","); - std::vector<std::string> outputNamesVector = ParseStringList(outputNames, ","); - std::vector<std::string> inputTypesVector = ParseStringList(inputTypes, ","); - std::vector<std::string> outputTypesVector = ParseStringList(outputTypes, ","); - - // 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 - { - BOOST_LOG_TRIVIAL(fatal) << "Unknown model format: '" << modelFormat << "'. Please include 'binary' or 'text'"; - return EXIT_FAILURE; - } - - if ((inputTensorShapesVector.size() != 0) && (inputTensorShapesVector.size() != inputNamesVector.size())) - { - BOOST_LOG_TRIVIAL(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())) - { - BOOST_LOG_TRIVIAL(fatal) << "input-name and input-tensor-data 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"); - } - if (outputTypesVector.size() == 0) - { - //Defaults the value of all outputs to "float" - outputTypesVector.assign(outputNamesVector.size(), "float"); - } - else if ((inputTypesVector.size() != 0) && (inputTypesVector.size() != inputNamesVector.size())) - { - BOOST_LOG_TRIVIAL(fatal) << "input-name and input-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<std::unique_ptr<armnn::TensorShape>> inputTensorShapes; - - if (!inputTensorShapesVector.empty()) - { - inputTensorShapes.reserve(inputTensorShapesVector.size()); - - for(const std::string& shape : inputTensorShapesVector) - { - std::stringstream ss(shape); - std::vector<unsigned int> dims = ParseArray(ss); - - try - { - // Coverity fix: An exception of type armnn::InvalidArgumentException is thrown and never caught. - inputTensorShapes.push_back(std::make_unique<armnn::TensorShape>(dims.size(), dims.data())); - } - catch (const armnn::InvalidArgumentException& e) - { - BOOST_LOG_TRIVIAL(fatal) << "Cannot create tensor shape: " << e.what(); - return EXIT_FAILURE; - } - } - } - - // Check that threshold time is not less than zero - if (thresholdTime < 0) - { - BOOST_LOG_TRIVIAL(fatal) << "Threshold time supplied as a commoand line argument is less than zero."; - return EXIT_FAILURE; - } - - // Forward to implementation based on the parser type - if (modelFormat.find("armnn") != std::string::npos) - { -#if defined(ARMNN_SERIALIZER) - return MainImpl<armnnDeserializer::IDeserializer, float>( - modelPath.c_str(), isModelBinary, computeDevice, - inputNamesVector, inputTensorShapes, - inputTensorDataFilePathsVector, inputTypesVector, - outputTypesVector, outputNamesVector, enableProfiling, - enableFp16TurboMode, thresholdTime, subgraphId, runtime); -#else - BOOST_LOG_TRIVIAL(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<armnnCaffeParser::ICaffeParser, float>(modelPath.c_str(), isModelBinary, computeDevice, - inputNamesVector, inputTensorShapes, - inputTensorDataFilePathsVector, inputTypesVector, - outputTypesVector, outputNamesVector, enableProfiling, - enableFp16TurboMode, thresholdTime, subgraphId, runtime); -#else - BOOST_LOG_TRIVIAL(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<armnnOnnxParser::IOnnxParser, float>(modelPath.c_str(), isModelBinary, computeDevice, - inputNamesVector, inputTensorShapes, - inputTensorDataFilePathsVector, inputTypesVector, - outputTypesVector, outputNamesVector, enableProfiling, - enableFp16TurboMode, thresholdTime, subgraphId, runtime); -#else - BOOST_LOG_TRIVIAL(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<armnnTfParser::ITfParser, float>(modelPath.c_str(), isModelBinary, computeDevice, - inputNamesVector, inputTensorShapes, - inputTensorDataFilePathsVector, inputTypesVector, - outputTypesVector, outputNamesVector, enableProfiling, - enableFp16TurboMode, thresholdTime, subgraphId, runtime); -#else - BOOST_LOG_TRIVIAL(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) - { - BOOST_LOG_TRIVIAL(fatal) << "Unknown model format: '" << modelFormat << "'. Only 'binary' format supported \ - for tflite files"; - return EXIT_FAILURE; - } - return MainImpl<armnnTfLiteParser::ITfLiteParser, float>(modelPath.c_str(), isModelBinary, computeDevice, - inputNamesVector, inputTensorShapes, - inputTensorDataFilePathsVector, inputTypesVector, - outputTypesVector, outputNamesVector, enableProfiling, - enableFp16TurboMode, thresholdTime, subgraphId, - runtime); -#else - BOOST_LOG_TRIVIAL(fatal) << "Unknown model format: '" << modelFormat << - "'. Please include 'caffe', 'tensorflow', 'tflite' or 'onnx'"; - return EXIT_FAILURE; -#endif - } - else - { - BOOST_LOG_TRIVIAL(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<armnn::IRuntime>& runtime, - const bool enableProfiling, const bool enableFp16TurboMode, const double& thresholdTime) -{ - std::string modelFormat; - std::string modelPath; - std::string inputNames; - std::string inputTensorShapes; - std::string inputTensorDataFilePaths; - std::string outputNames; - std::string inputTypes; - std::string outputTypes; - - 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<std::vector<armnn::BackendId>>()->multitoken(), - backendsMessage.c_str()) - ("input-name,i", po::value(&inputNames), "Identifier of the input tensors in the network separated by comma.") - ("subgraph-number,n", po::value<size_t>(&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), - "Path to files containing the input data as a flat array separated by whitespace. " - "Several paths can be passed separating them by comma.") - ("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).") - ("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."); - } - 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"); - BOOST_LOG_TRIVIAL(fatal) << "Fatal internal error: " << e.what(); - return EXIT_FAILURE; - } - - std::vector<const char*> 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<int>(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 preferred order of compute devices. - std::vector<armnn::BackendId> computeDevices = vm["compute"].as<std::vector<armnn::BackendId>>(); - - // 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<std::string&>(invalidBackends))) - { - BOOST_LOG_TRIVIAL(fatal) << "The list of preferred devices contains invalid backend IDs: " - << invalidBackends; - return EXIT_FAILURE; - } - - return RunTest(modelFormat, inputTensorShapes, computeDevices, modelPath, inputNames, - inputTensorDataFilePaths, inputTypes, outputTypes, outputNames, - enableProfiling, enableFp16TurboMode, thresholdTime, subgraphId); -} +#include "../NetworkExecutionUtils/NetworkExecutionUtils.hpp" // MAIN int main(int argc, const char* argv[]) diff --git a/tests/ModelAccuracyTool-Armnn/ModelAccuracyTool-Armnn.cpp b/tests/ModelAccuracyTool-Armnn/ModelAccuracyTool-Armnn.cpp index 3abfb3c2ec..aec4d70271 100644 --- a/tests/ModelAccuracyTool-Armnn/ModelAccuracyTool-Armnn.cpp +++ b/tests/ModelAccuracyTool-Armnn/ModelAccuracyTool-Armnn.cpp @@ -4,9 +4,9 @@ // #include "ModelAccuracyChecker.hpp" -#include "../InferenceTest.hpp" #include "../ImagePreprocessor.hpp" #include "armnnDeserializer/IDeserializer.hpp" +#include "../NetworkExecutionUtils/NetworkExecutionUtils.hpp" #include <boost/filesystem.hpp> #include <boost/range/iterator_range.hpp> @@ -14,70 +14,8 @@ using namespace armnn::test; -namespace po = boost::program_options; - -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(); -} - -template<typename T, typename TParseElementFunc> -std::vector<T> ParseArrayImpl(std::istream& stream, TParseElementFunc parseElementFunc, const char * chars = "\t ,:") -{ - std::vector<T> result; - // Processes line-by-line. - std::string line; - while (std::getline(stream, line)) - { - std::vector<std::string> 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) - { - BOOST_LOG_TRIVIAL(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&) - { - BOOST_LOG_TRIVIAL(error) << "'" << token << "' is not a valid number. It has been ignored."; - } - } - } - } - - return result; -} - map<std::string, int> LoadValidationLabels(const string & validationLabelPath); -template<armnn::DataType NonQuantizedType> -auto ParseDataArray(std::istream & stream); - -template<> -auto ParseDataArray<armnn::DataType::Float32>(std::istream & stream) -{ - return ParseArrayImpl<float>(stream, [](const std::string& s) { return std::stof(s); }); -} - int main(int argc, char* argv[]) { try @@ -94,6 +32,7 @@ int main(int argc, char* argv[]) std::vector<armnn::BackendId> defaultBackends = {armnn::Compute::CpuAcc, armnn::Compute::CpuRef}; std::string modelPath; std::string dataDir; + std::string inputType = "float"; std::string inputName; std::string outputName; std::string validationLabelPath; @@ -112,6 +51,9 @@ int main(int argc, char* argv[]) backendsMessage.c_str()) ("data-dir,d", po::value<std::string>(&dataDir)->required(), "Path to directory containing the ImageNet test data") + ("input-type,y", po::value(&inputType), "The data type of the input tensors." + "If unset, defaults to \"float\" for all defined inputs. " + "Accepted values (float, int or qasymm8)") ("input-name,i", po::value<std::string>(&inputName)->required(), "Identifier of the input tensors in the network separated by comma.") ("output-name,o", po::value<std::string>(&outputName)->required(), @@ -217,14 +159,50 @@ int main(int argc, char* argv[]) if(ValidateDirectory(dataDir)) { + InferenceModel<armnnDeserializer::IDeserializer, float>::Params params; + params.m_ModelPath = modelPath; + params.m_IsModelBinary = true; + params.m_ComputeDevices = computeDevice; + params.m_InputBindings.push_back(inputName); + params.m_OutputBindings.push_back(outputName); + + using TParser = armnnDeserializer::IDeserializer; + InferenceModel<TParser, float> model(params, false); for (auto & imageEntry : boost::make_iterator_range(directory_iterator(pathToDataDir), {})) { cout << "Processing image: " << imageEntry << "\n"; std::ifstream inputTensorFile(imageEntry.path().string()); vector<TContainer> inputDataContainers; - inputDataContainers.push_back(ParseDataArray<armnn::DataType::Float32>(inputTensorFile)); - vector<TContainer> outputDataContainers = {vector<float>(1001)}; + vector<TContainer> outputDataContainers; + + if (inputType.compare("float") == 0) + { + inputDataContainers.push_back( + ParseDataArray<armnn::DataType::Float32>(inputTensorFile)); + outputDataContainers = {vector<float>(1001)}; + } + else if (inputType.compare("int") == 0) + { + inputDataContainers.push_back( + ParseDataArray<armnn::DataType::Signed32>(inputTensorFile)); + outputDataContainers = {vector<int>(1001)}; + } + else if (inputType.compare("qasymm8") == 0) + { + auto inputBinding = model.GetInputBindingInfo(); + inputDataContainers.push_back( + ParseDataArray<armnn::DataType::QuantisedAsymm8>( + inputTensorFile, + inputBinding.second.GetQuantizationScale(), + inputBinding.second.GetQuantizationOffset())); + outputDataContainers = {vector<uint8_t >(1001)}; + } + else + { + BOOST_LOG_TRIVIAL(fatal) << "Unsupported tensor data type \"" << inputType << "\". "; + return EXIT_FAILURE; + } status = runtime->EnqueueWorkload(networkId, armnnUtils::MakeInputTensors(inputBindings, inputDataContainers), diff --git a/tests/NetworkExecutionUtils/NetworkExecutionUtils.hpp b/tests/NetworkExecutionUtils/NetworkExecutionUtils.hpp new file mode 100644 index 0000000000..9d7e368dad --- /dev/null +++ b/tests/NetworkExecutionUtils/NetworkExecutionUtils.hpp @@ -0,0 +1,676 @@ +// +// Copyright © 2017 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// +#include <armnn/ArmNN.hpp> +#include <armnn/TypesUtils.hpp> + +#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 <Logging.hpp> +#include <Profiling.hpp> + +#include <boost/algorithm/string/trim.hpp> +#include <boost/algorithm/string/split.hpp> +#include <boost/algorithm/string/classification.hpp> +#include <boost/program_options.hpp> +#include <boost/variant.hpp> + +#include <iostream> +#include <fstream> +#include <functional> +#include <future> +#include <algorithm> +#include <iterator> + +namespace +{ + +// Configure boost::program_options for command-line parsing and validation. +namespace po = boost::program_options; + +template<typename T, typename TParseElementFunc> +std::vector<T> ParseArrayImpl(std::istream& stream, TParseElementFunc parseElementFunc, const char * chars = "\t ,:") +{ + std::vector<T> result; + // Processes line-by-line. + std::string line; + while (std::getline(stream, line)) + { + std::vector<std::string> 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) + { + BOOST_LOG_TRIVIAL(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&) + { + BOOST_LOG_TRIVIAL(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", "input-tensor-data"); + CheckOptionDependency(vm, "model-path", "output-name"); + CheckOptionDependency(vm, "input-tensor-shape", "model-path"); +} + +template<armnn::DataType NonQuantizedType> +auto ParseDataArray(std::istream & stream); + +template<armnn::DataType QuantizedType> +auto ParseDataArray(std::istream& stream, + const float& quantizationScale, + const int32_t& quantizationOffset); + +template<> +auto ParseDataArray<armnn::DataType::Float32>(std::istream & stream) +{ + return ParseArrayImpl<float>(stream, [](const std::string& s) { return std::stof(s); }); +} + +template<> +auto ParseDataArray<armnn::DataType::Signed32>(std::istream & stream) +{ + return ParseArrayImpl<int>(stream, [](const std::string & s) { return std::stoi(s); }); +} + +template<> +auto ParseDataArray<armnn::DataType::QuantisedAsymm8>(std::istream& stream, + const float& quantizationScale, + const int32_t& quantizationOffset) +{ + return ParseArrayImpl<uint8_t>(stream, + [&quantizationScale, &quantizationOffset](const std::string & s) + { + return boost::numeric_cast<uint8_t>( + armnn::Quantize<u_int8_t>(std::stof(s), + quantizationScale, + quantizationOffset)); + }); +} + +std::vector<unsigned int> ParseArray(std::istream& stream) +{ + return ParseArrayImpl<unsigned int>(stream, + [](const std::string& s) { return boost::numeric_cast<unsigned int>(std::stoi(s)); }); +} + +std::vector<std::string> ParseStringList(const std::string & inputString, const char * delimiter) +{ + std::stringstream stream(inputString); + return ParseArrayImpl<std::string>(stream, [](const std::string& s) { return boost::trim_copy(s); }, delimiter); +} + +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()); +} + +struct TensorPrinter : public boost::static_visitor<> +{ + TensorPrinter(const std::string& binding, const armnn::TensorInfo& info) + : m_OutputBinding(binding) + , m_Scale(info.GetQuantizationScale()) + , m_Offset(info.GetQuantizationOffset()) + {} + + void operator()(const std::vector<float>& values) + { + ForEachValue(values, [](float value){ + printf("%f ", value); + }); + } + + void operator()(const std::vector<uint8_t>& values) + { + auto& scale = m_Scale; + auto& offset = m_Offset; + ForEachValue(values, [&scale, &offset](uint8_t value) + { + printf("%f ", armnn::Dequantize(value, scale, offset)); + }); + } + + void operator()(const std::vector<int>& values) + { + ForEachValue(values, [](int value) + { + printf("%d ", value); + }); + } + +private: + template<typename Container, typename Delegate> + void ForEachValue(const Container& c, Delegate delegate) + { + std::cout << m_OutputBinding << ": "; + for (const auto& value : c) + { + delegate(value); + } + printf("\n"); + } + + std::string m_OutputBinding; + float m_Scale=0.0f; + int m_Offset=0; +}; + + +} // namespace + +template<typename TParser, typename TDataType> +int MainImpl(const char* modelPath, + bool isModelBinary, + const std::vector<armnn::BackendId>& computeDevices, + const std::vector<string>& inputNames, + const std::vector<std::unique_ptr<armnn::TensorShape>>& inputTensorShapes, + const std::vector<string>& inputTensorDataFilePaths, + const std::vector<string>& inputTypes, + const std::vector<string>& outputTypes, + const std::vector<string>& outputNames, + bool enableProfiling, + bool enableFp16TurboMode, + const double& thresholdTime, + const size_t subgraphId, + const std::shared_ptr<armnn::IRuntime>& runtime = nullptr) +{ + using TContainer = boost::variant<std::vector<float>, std::vector<int>, std::vector<unsigned char>>; + + std::vector<TContainer> inputDataContainers; + + try + { + // Creates an InferenceModel, which will parse the model and load it into an IRuntime. + typename InferenceModel<TParser, TDataType>::Params params; + params.m_ModelPath = modelPath; + params.m_IsModelBinary = isModelBinary; + params.m_ComputeDevices = computeDevices; + + for(const std::string& inputName: inputNames) + { + params.m_InputBindings.push_back(inputName); + } + + for(unsigned int i = 0; i < inputTensorShapes.size(); ++i) + { + params.m_InputShapes.push_back(*inputTensorShapes[i]); + } + + for(const std::string& outputName: outputNames) + { + params.m_OutputBindings.push_back(outputName); + } + + params.m_SubgraphId = subgraphId; + params.m_EnableFp16TurboMode = enableFp16TurboMode; + InferenceModel<TParser, TDataType> model(params, enableProfiling, runtime); + + for(unsigned int i = 0; i < inputTensorDataFilePaths.size(); ++i) + { + std::ifstream inputTensorFile(inputTensorDataFilePaths[i]); + + if (inputTypes[i].compare("float") == 0) + { + inputDataContainers.push_back( + ParseDataArray<armnn::DataType::Float32>(inputTensorFile)); + } + else if (inputTypes[i].compare("int") == 0) + { + inputDataContainers.push_back( + ParseDataArray<armnn::DataType::Signed32>(inputTensorFile)); + } + else if (inputTypes[i].compare("qasymm8") == 0) + { + auto inputBinding = model.GetInputBindingInfo(); + inputDataContainers.push_back( + ParseDataArray<armnn::DataType::QuantisedAsymm8>(inputTensorFile, + inputBinding.second.GetQuantizationScale(), + inputBinding.second.GetQuantizationOffset())); + } + else + { + BOOST_LOG_TRIVIAL(fatal) << "Unsupported tensor data type \"" << inputTypes[i] << "\". "; + return EXIT_FAILURE; + } + + inputTensorFile.close(); + } + + const size_t numOutputs = params.m_OutputBindings.size(); + std::vector<TContainer> outputDataContainers; + + for (unsigned int i = 0; i < numOutputs; ++i) + { + if (outputTypes[i].compare("float") == 0) + { + outputDataContainers.push_back(std::vector<float>(model.GetOutputSize(i))); + } + else if (outputTypes[i].compare("int") == 0) + { + outputDataContainers.push_back(std::vector<int>(model.GetOutputSize(i))); + } + else if (outputTypes[i].compare("qasymm8") == 0) + { + outputDataContainers.push_back(std::vector<uint8_t>(model.GetOutputSize(i))); + } + else + { + BOOST_LOG_TRIVIAL(fatal) << "Unsupported tensor data type \"" << outputTypes[i] << "\". "; + return EXIT_FAILURE; + } + } + + // model.Run returns the inference time elapsed in EnqueueWorkload (in milliseconds) + auto inference_duration = model.Run(inputDataContainers, outputDataContainers); + + // Print output tensors + const auto& infosOut = model.GetOutputBindingInfos(); + for (size_t i = 0; i < numOutputs; i++) + { + const armnn::TensorInfo& infoOut = infosOut[i].second; + TensorPrinter printer(params.m_OutputBindings[i], infoOut); + boost::apply_visitor(printer, outputDataContainers[i]); + } + + BOOST_LOG_TRIVIAL(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 (thresholdTime != 0.0) + { + BOOST_LOG_TRIVIAL(info) << "Threshold time: " << std::setprecision(2) + << std::fixed << thresholdTime << " ms"; + auto thresholdMinusInference = thresholdTime - inference_duration.count(); + BOOST_LOG_TRIVIAL(info) << "Threshold time - Inference time: " << std::setprecision(2) + << std::fixed << thresholdMinusInference << " ms" << "\n"; + + if (thresholdMinusInference < 0) + { + BOOST_LOG_TRIVIAL(fatal) << "Elapsed inference time is greater than provided threshold time.\n"; + return EXIT_FAILURE; + } + } + + + } + catch (armnn::Exception const& e) + { + BOOST_LOG_TRIVIAL(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<armnn::BackendId>& computeDevice, + const std::string& path, + const std::string& inputNames, + const std::string& inputTensorDataFilePaths, + const std::string& inputTypes, + const std::string& outputTypes, + const std::string& outputNames, + bool enableProfiling, + bool enableFp16TurboMode, + const double& thresholdTime, + const size_t subgraphId, + const std::shared_ptr<armnn::IRuntime>& runtime = nullptr) +{ + std::string modelFormat = boost::trim_copy(format); + std::string modelPath = boost::trim_copy(path); + std::vector<std::string> inputNamesVector = ParseStringList(inputNames, ","); + std::vector<std::string> inputTensorShapesVector = ParseStringList(inputTensorShapesStr, ";"); + std::vector<std::string> inputTensorDataFilePathsVector = ParseStringList( + inputTensorDataFilePaths, ","); + std::vector<std::string> outputNamesVector = ParseStringList(outputNames, ","); + std::vector<std::string> inputTypesVector = ParseStringList(inputTypes, ","); + std::vector<std::string> outputTypesVector = ParseStringList(outputTypes, ","); + + // 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 + { + BOOST_LOG_TRIVIAL(fatal) << "Unknown model format: '" << modelFormat << "'. Please include 'binary' or 'text'"; + return EXIT_FAILURE; + } + + if ((inputTensorShapesVector.size() != 0) && (inputTensorShapesVector.size() != inputNamesVector.size())) + { + BOOST_LOG_TRIVIAL(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())) + { + BOOST_LOG_TRIVIAL(fatal) << "input-name and input-tensor-data 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"); + } + if (outputTypesVector.size() == 0) + { + //Defaults the value of all outputs to "float" + outputTypesVector.assign(outputNamesVector.size(), "float"); + } + else if ((inputTypesVector.size() != 0) && (inputTypesVector.size() != inputNamesVector.size())) + { + BOOST_LOG_TRIVIAL(fatal) << "input-name and input-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<std::unique_ptr<armnn::TensorShape>> inputTensorShapes; + + if (!inputTensorShapesVector.empty()) + { + inputTensorShapes.reserve(inputTensorShapesVector.size()); + + for(const std::string& shape : inputTensorShapesVector) + { + std::stringstream ss(shape); + std::vector<unsigned int> dims = ParseArray(ss); + + try + { + // Coverity fix: An exception of type armnn::InvalidArgumentException is thrown and never caught. + inputTensorShapes.push_back(std::make_unique<armnn::TensorShape>(dims.size(), dims.data())); + } + catch (const armnn::InvalidArgumentException& e) + { + BOOST_LOG_TRIVIAL(fatal) << "Cannot create tensor shape: " << e.what(); + return EXIT_FAILURE; + } + } + } + + // Check that threshold time is not less than zero + if (thresholdTime < 0) + { + BOOST_LOG_TRIVIAL(fatal) << "Threshold time supplied as a commoand line argument is less than zero."; + return EXIT_FAILURE; + } + + // Forward to implementation based on the parser type + if (modelFormat.find("armnn") != std::string::npos) + { +#if defined(ARMNN_SERIALIZER) + return MainImpl<armnnDeserializer::IDeserializer, float>( + modelPath.c_str(), isModelBinary, computeDevice, + inputNamesVector, inputTensorShapes, + inputTensorDataFilePathsVector, inputTypesVector, + outputTypesVector, outputNamesVector, enableProfiling, + enableFp16TurboMode, thresholdTime, subgraphId, runtime); +#else + BOOST_LOG_TRIVIAL(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<armnnCaffeParser::ICaffeParser, float>(modelPath.c_str(), isModelBinary, computeDevice, + inputNamesVector, inputTensorShapes, + inputTensorDataFilePathsVector, inputTypesVector, + outputTypesVector, outputNamesVector, enableProfiling, + enableFp16TurboMode, thresholdTime, subgraphId, runtime); +#else + BOOST_LOG_TRIVIAL(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<armnnOnnxParser::IOnnxParser, float>(modelPath.c_str(), isModelBinary, computeDevice, + inputNamesVector, inputTensorShapes, + inputTensorDataFilePathsVector, inputTypesVector, + outputTypesVector, outputNamesVector, enableProfiling, + enableFp16TurboMode, thresholdTime, subgraphId, runtime); +#else + BOOST_LOG_TRIVIAL(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<armnnTfParser::ITfParser, float>(modelPath.c_str(), isModelBinary, computeDevice, + inputNamesVector, inputTensorShapes, + inputTensorDataFilePathsVector, inputTypesVector, + outputTypesVector, outputNamesVector, enableProfiling, + enableFp16TurboMode, thresholdTime, subgraphId, runtime); +#else + BOOST_LOG_TRIVIAL(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) + { + BOOST_LOG_TRIVIAL(fatal) << "Unknown model format: '" << modelFormat << "'. Only 'binary' format supported \ + for tflite files"; + return EXIT_FAILURE; + } + return MainImpl<armnnTfLiteParser::ITfLiteParser, float>(modelPath.c_str(), isModelBinary, computeDevice, + inputNamesVector, inputTensorShapes, + inputTensorDataFilePathsVector, inputTypesVector, + outputTypesVector, outputNamesVector, enableProfiling, + enableFp16TurboMode, thresholdTime, subgraphId, + runtime); +#else + BOOST_LOG_TRIVIAL(fatal) << "Unknown model format: '" << modelFormat << + "'. Please include 'caffe', 'tensorflow', 'tflite' or 'onnx'"; + return EXIT_FAILURE; +#endif + } + else + { + BOOST_LOG_TRIVIAL(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<armnn::IRuntime>& runtime, + const bool enableProfiling, const bool enableFp16TurboMode, const double& thresholdTime) +{ + std::string modelFormat; + std::string modelPath; + std::string inputNames; + std::string inputTensorShapes; + std::string inputTensorDataFilePaths; + std::string outputNames; + std::string inputTypes; + std::string outputTypes; + + 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<std::vector<armnn::BackendId>>()->multitoken(), + backendsMessage.c_str()) + ("input-name,i", po::value(&inputNames), "Identifier of the input tensors in the network separated by comma.") + ("subgraph-number,n", po::value<size_t>(&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), + "Path to files containing the input data as a flat array separated by whitespace. " + "Several paths can be passed separating them by comma.") + ("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).") + ("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."); + } + 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"); + BOOST_LOG_TRIVIAL(fatal) << "Fatal internal error: " << e.what(); + return EXIT_FAILURE; + } + + std::vector<const char*> 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<int>(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 preferred order of compute devices. + std::vector<armnn::BackendId> computeDevices = vm["compute"].as<std::vector<armnn::BackendId>>(); + + // 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<std::string&>(invalidBackends))) + { + BOOST_LOG_TRIVIAL(fatal) << "The list of preferred devices contains invalid backend IDs: " + << invalidBackends; + return EXIT_FAILURE; + } + + return RunTest(modelFormat, inputTensorShapes, computeDevices, modelPath, inputNames, + inputTensorDataFilePaths, inputTypes, outputTypes, outputNames, + enableProfiling, enableFp16TurboMode, thresholdTime, subgraphId); +}
\ No newline at end of file |