From 452c58080e9f8f577de87e0c07d0097aac97f3b8 Mon Sep 17 00:00:00 2001 From: Finn Williams Date: Mon, 20 Jun 2022 13:48:20 +0100 Subject: IVGCVSW-6650 Refactor ExecuteNetwork * Remove InferenceModel * Add automatic IO type, shape and name configuration * Depreciate various redundant options * Add internal output comparison Signed-off-by: Finn Williams Change-Id: I2eca248bc91e1655a99ed94990efb8059f541fa9 --- .../NetworkExecutionUtils.hpp | 279 +++++++++++++++++---- 1 file changed, 226 insertions(+), 53 deletions(-) (limited to 'tests/NetworkExecutionUtils/NetworkExecutionUtils.hpp') diff --git a/tests/NetworkExecutionUtils/NetworkExecutionUtils.hpp b/tests/NetworkExecutionUtils/NetworkExecutionUtils.hpp index bc2868ab35..14d7fe5551 100644 --- a/tests/NetworkExecutionUtils/NetworkExecutionUtils.hpp +++ b/tests/NetworkExecutionUtils/NetworkExecutionUtils.hpp @@ -1,63 +1,83 @@ // -// Copyright © 2017 Arm Ltd and Contributors. All rights reserved. +// Copyright © 2022 Arm Ltd and Contributors. All rights reserved. // SPDX-License-Identifier: MIT // #pragma once -#include -#include #include #include -#include +#include +#include #include #include +#include +#include +/** + * Given a measured duration and a threshold time tell the user whether we succeeded or not. + * + * @param duration the measured inference duration. + * @param thresholdTime the threshold time in milliseconds. + * @return false if the measured time exceeded the threshold. + */ +bool CheckInferenceTimeThreshold(const std::chrono::duration& duration, + const double& thresholdTime); -std::vector ParseArray(std::istream& stream); - -/// Splits a given string at every accurance of delimiter into a vector of string -std::vector ParseStringList(const std::string& inputString, const char* delimiter); - -struct TensorPrinter +inline bool CheckRequestedBackendsAreValid(const std::vector& backendIds, + armnn::Optional invalidBackendIds = armnn::EmptyOptional()) { - TensorPrinter(const std::string& binding, - const armnn::TensorInfo& info, - const std::string& outputTensorFile, - bool dequantizeOutput, - bool printToConsole = true); - - void operator()(const std::vector& values); - - void operator()(const std::vector& values); - - void operator()(const std::vector& values); + if (backendIds.empty()) + { + return false; + } - void operator()(const std::vector& values); + armnn::BackendIdSet validBackendIds = armnn::BackendRegistryInstance().GetBackendIds(); -private: - template - void ForEachValue(const Container& c, Delegate delegate); + bool allValid = true; + for (const auto& backendId : backendIds) + { + if (std::find(validBackendIds.begin(), validBackendIds.end(), backendId) == validBackendIds.end()) + { + allValid = false; + if (invalidBackendIds) + { + if (!invalidBackendIds.value().empty()) + { + invalidBackendIds.value() += ", "; + } + invalidBackendIds.value() += backendId; + } + } + } + return allValid; +} - template - void WriteToFile(const std::vector& values); +std::vector ParseArray(std::istream& stream); - std::string m_OutputBinding; - float m_Scale; - int m_Offset; - std::string m_OutputTensorFile; - bool m_DequantizeOutput; - bool m_PrintToConsole; -}; +/// Splits a given string at every accurance of delimiter into a vector of string +std::vector ParseStringList(const std::string& inputString, const char* delimiter); -using QuantizationParams = std::pair; +/// Dequantize an array of a given type +/// @param array Type erased array to dequantize +/// @param numElements Elements in the array +/// @param array Type erased array to dequantize +template +std::vector DequantizeArray(const void* array, unsigned int numElements, float scale, int32_t offset) +{ + const T* quantizedArray = reinterpret_cast(array); + std::vector dequantizedVector; + dequantizedVector.reserve(numElements); + for (unsigned int i = 0; i < numElements; ++i) + { + float f = armnn::Dequantize(*(quantizedArray + i), scale, offset); + dequantizedVector.push_back(f); + } + return dequantizedVector; +} -void PopulateTensorWithData(armnnUtils::TContainer& tensorData, - unsigned int numElements, - const std::string& dataTypeStr, - const armnn::Optional& qParams, - const armnn::Optional& dataFile); +void LogAndThrow(std::string eMsg); /** * Verifies if the given string is a valid path. Reports invalid paths to std::err. @@ -75,6 +95,152 @@ bool ValidatePath(const std::string& file, const bool expectFile); * */ bool ValidatePaths(const std::vector& fileVec, const bool expectFile); +/// Returns a function of read the given type as a string +template ::value>* = nullptr> +std::function GetParseElementFunc() +{ + return [](const std::string& s) { return armnn::numeric_cast(std::stoi(s)); }; +} + +template ::value>* = nullptr> +std::function GetParseElementFunc() +{ + return [](const std::string& s) { return std::stof(s); }; +} + +template +void PopulateTensorWithData(T* tensor, + const unsigned int numElements, + const armnn::Optional& dataFile, + const std::string& inputName) +{ + const bool readFromFile = dataFile.has_value() && !dataFile.value().empty(); + + std::ifstream inputTensorFile; + if (!readFromFile) + { + std::fill(tensor, tensor + numElements, 0); + return; + } + else + { + inputTensorFile = std::ifstream(dataFile.value()); + } + + auto parseElementFunc = GetParseElementFunc(); + std::string line; + unsigned int index = 0; + while (std::getline(inputTensorFile, line)) + { + std::vector tokens = armnn::stringUtils::StringTokenizer(line, "\t ,:"); + for (const std::string& token : tokens) + { + if (!token.empty()) // See https://stackoverflow.com/questions/10437406/ + { + try + { + if (index == numElements) + { + ARMNN_LOG(error) << "Number of elements: " << (index +1) << " in file \"" << dataFile.value() + << "\" does not match number of elements: " << numElements + << " for input \"" << inputName << "\"."; + } + *(tensor + index) = parseElementFunc(token); + index++; + } + catch (const std::exception&) + { + ARMNN_LOG(error) << "'" << token << "' is not a valid number. It has been ignored."; + } + } + } + } + + if (index != numElements) + { + ARMNN_LOG(error) << "Number of elements: " << (index +1) << " in file \"" << inputName + << "\" does not match number of elements: " << numElements + << " for input \"" << inputName << "\"."; + } +} + +template +void WriteToFile(const std::string& outputTensorFileName, + const std::string& outputName, + const T* const array, + const unsigned int numElements) +{ + std::ofstream outputTensorFile; + outputTensorFile.open(outputTensorFileName, std::ofstream::out | std::ofstream::trunc); + if (outputTensorFile.is_open()) + { + outputTensorFile << outputName << ": "; + std::copy(array, array + numElements, std::ostream_iterator(outputTensorFile, " ")); + } + else + { + ARMNN_LOG(info) << "Output Tensor File: " << outputTensorFileName << " could not be opened!"; + } + outputTensorFile.close(); +} + +struct OutputWriteInfo +{ + const armnn::Optional& m_OutputTensorFile; + const std::string& m_OutputName; + const armnn::Tensor& m_Tensor; + const bool m_PrintTensor; +}; + +template +void PrintTensor(OutputWriteInfo& info, const char* formatString) +{ + const T* array = reinterpret_cast(info.m_Tensor.GetMemoryArea()); + + if (info.m_OutputTensorFile.has_value()) + { + WriteToFile(info.m_OutputTensorFile.value(), + info.m_OutputName, + array, + info.m_Tensor.GetNumElements()); + } + + if (info.m_PrintTensor) + { + for (unsigned int i = 0; i < info.m_Tensor.GetNumElements(); i++) + { + printf(formatString, array[i]); + } + } +} + +template +void PrintQuantizedTensor(OutputWriteInfo& info) +{ + std::vector dequantizedValues; + auto tensor = info.m_Tensor; + dequantizedValues = DequantizeArray(tensor.GetMemoryArea(), + tensor.GetNumElements(), + tensor.GetInfo().GetQuantizationScale(), + tensor.GetInfo().GetQuantizationOffset()); + + if (info.m_OutputTensorFile.has_value()) + { + WriteToFile(info.m_OutputTensorFile.value(), + info.m_OutputName, + dequantizedValues.data(), + tensor.GetNumElements()); + } + + if (info.m_PrintTensor) + { + std::for_each(dequantizedValues.begin(), dequantizedValues.end(), [&](float value) + { + printf("%f ", value); + }); + } +} + template std::vector ParseArrayImpl(std::istream& stream, TParseElementFunc parseElementFunc, const char* chars = "\t ,:") { @@ -103,21 +269,28 @@ std::vector ParseArrayImpl(std::istream& stream, TParseElementFunc parseEleme return result; } -template -void PopulateTensorWithDataGeneric(std::vector& tensorData, - unsigned int numElements, - const armnn::Optional& dataFile, - TParseElementFunc parseFunction) +/// Compute the root-mean-square error (RMSE) +/// @param expected +/// @param actual +/// @param size size of the tensor +/// @return float the RMSE +template +float ComputeRMSE(const void* expected, const void* actual, const size_t size) { - const bool readFromFile = dataFile.has_value() && !dataFile.value().empty(); + auto typedExpected = reinterpret_cast(expected); + auto typedActual = reinterpret_cast(actual); - std::ifstream inputTensorFile; - if (readFromFile) + T errorSum = 0; + + for (unsigned int i = 0; i < size; i++) { - inputTensorFile = std::ifstream(dataFile.value()); + if (std::abs(typedExpected[i] - typedActual[i]) != 0) + { + std::cout << ""; + } + errorSum += std::pow(std::abs(typedExpected[i] - typedActual[i]), 2); } - tensorData = readFromFile ? - ParseArrayImpl(inputTensorFile, parseFunction) : - std::vector(numElements, static_cast(0)); -} + float rmse = std::sqrt(armnn::numeric_cast(errorSum) / armnn::numeric_cast(size / sizeof(T))); + return rmse; +} \ No newline at end of file -- cgit v1.2.1