blob: 8c9723843241f291aa873675fdf7a6a953b74d25 [file] [log] [blame]
Laurent Carlier749294b2020-06-01 09:03:17 +01001//
Teresa Charlin83b42912022-07-07 14:24:59 +01002// Copyright © 2022 Arm Ltd and Contributors. All rights reserved.
Francis Murtaghbee4bc92019-06-18 12:30:37 +01003// SPDX-License-Identifier: MIT
4//
Francis Murtaghbee4bc92019-06-18 12:30:37 +01005
Jan Eilers45274902020-10-15 18:34:43 +01006#pragma once
7
Colm Donelana98e79a2022-12-06 21:32:29 +00008#include <armnn/BackendRegistry.hpp> // for BackendRegistryInstance
9#include <armnn/Logging.hpp> // for ScopedRecord, ARMNN_LOG
10#include <armnn/utility/NumericCast.hpp> // for numeric_cast
11#include <armnn/utility/StringUtils.hpp> // for StringTokenizer
12#include <armnn/BackendId.hpp> // for BackendId, BackendIdSet
13#include <armnn/Optional.hpp> // for Optional, EmptyOptional
14#include <armnn/Tensor.hpp> // for Tensor, TensorInfo
15#include <armnn/TypesUtils.hpp> // for Dequantize
16#include <chrono> // for duration
17#include <functional> // for function
Finn Williams56870182020-11-20 13:57:53 +000018#include <fstream>
Teresa Charlin83b42912022-07-07 14:24:59 +010019#include <iomanip>
Colm Donelana98e79a2022-12-06 21:32:29 +000020#include <iostream> // for ofstream, basic_istream
21#include <ratio> // for milli
22#include <string> // for string, getline, basic_string
23#include <type_traits> // for enable_if_t, is_floating_point
24#include <unordered_set> // for operator!=, operator==, _No...
25#include <vector> // for vector
26#include <math.h> // for pow, sqrt
27#include <stdint.h> // for int32_t
28#include <stdio.h> // for printf, size_t
29#include <stdlib.h> // for abs
30#include <algorithm> // for find, for_each
Francis Murtaghbee4bc92019-06-18 12:30:37 +010031
Teresa Charlin83b42912022-07-07 14:24:59 +010032/**
33 * Given a measured duration and a threshold time tell the user whether we succeeded or not.
34 *
35 * @param duration the measured inference duration.
36 * @param thresholdTime the threshold time in milliseconds.
37 * @return false if the measured time exceeded the threshold.
38 */
39bool CheckInferenceTimeThreshold(const std::chrono::duration<double, std::milli>& duration,
40 const double& thresholdTime);
41
42inline bool CheckRequestedBackendsAreValid(const std::vector<armnn::BackendId>& backendIds,
43 armnn::Optional<std::string&> invalidBackendIds = armnn::EmptyOptional())
44{
45 if (backendIds.empty())
46 {
47 return false;
48 }
49
50 armnn::BackendIdSet validBackendIds = armnn::BackendRegistryInstance().GetBackendIds();
51
52 bool allValid = true;
53 for (const auto& backendId : backendIds)
54 {
55 if (std::find(validBackendIds.begin(), validBackendIds.end(), backendId) == validBackendIds.end())
56 {
57 allValid = false;
58 if (invalidBackendIds)
59 {
60 if (!invalidBackendIds.value().empty())
61 {
62 invalidBackendIds.value() += ", ";
63 }
64 invalidBackendIds.value() += backendId;
65 }
66 }
67 }
68 return allValid;
69}
Francis Murtaghbee4bc92019-06-18 12:30:37 +010070
Jan Eilers45274902020-10-15 18:34:43 +010071std::vector<unsigned int> ParseArray(std::istream& stream);
Francis Murtaghbee4bc92019-06-18 12:30:37 +010072
Jan Eilers45274902020-10-15 18:34:43 +010073/// Splits a given string at every accurance of delimiter into a vector of string
74std::vector<std::string> ParseStringList(const std::string& inputString, const char* delimiter);
Francis Murtaghbee4bc92019-06-18 12:30:37 +010075
Teresa Charlin83b42912022-07-07 14:24:59 +010076/// Dequantize an array of a given type
77/// @param array Type erased array to dequantize
78/// @param numElements Elements in the array
79/// @param array Type erased array to dequantize
80template <typename T>
81std::vector<float> DequantizeArray(const void* array, unsigned int numElements, float scale, int32_t offset)
Francis Murtaghbee4bc92019-06-18 12:30:37 +010082{
Teresa Charlin83b42912022-07-07 14:24:59 +010083 const T* quantizedArray = reinterpret_cast<const T*>(array);
84 std::vector<float> dequantizedVector;
85 dequantizedVector.reserve(numElements);
86 for (unsigned int i = 0; i < numElements; ++i)
87 {
88 float f = armnn::Dequantize(*(quantizedArray + i), scale, offset);
89 dequantizedVector.push_back(f);
90 }
91 return dequantizedVector;
92}
Francis Murtaghbee4bc92019-06-18 12:30:37 +010093
Teresa Charlin83b42912022-07-07 14:24:59 +010094void LogAndThrow(std::string eMsg);
Aron Virginas-Tarc82c8732019-10-24 17:07:43 +010095
Jan Eilers45274902020-10-15 18:34:43 +010096/**
97 * Verifies if the given string is a valid path. Reports invalid paths to std::err.
98 * @param file string - A string containing the path to check
99 * @param expectFile bool - If true, checks for a regular file.
100 * @return bool - True if given string is a valid path., false otherwise.
101 * */
102bool ValidatePath(const std::string& file, const bool expectFile);
Aron Virginas-Tarc82c8732019-10-24 17:07:43 +0100103
Jan Eilers45274902020-10-15 18:34:43 +0100104/**
105 * Verifies if a given vector of strings are valid paths. Reports invalid paths to std::err.
106 * @param fileVec vector of string - A vector of string containing the paths to check
107 * @param expectFile bool - If true, checks for a regular file.
108 * @return bool - True if all given strings are valid paths., false otherwise.
109 * */
Finn Williams56870182020-11-20 13:57:53 +0000110bool ValidatePaths(const std::vector<std::string>& fileVec, const bool expectFile);
111
Teresa Charlin83b42912022-07-07 14:24:59 +0100112/// Returns a function of read the given type as a string
113template <typename Integer, typename std::enable_if_t<std::is_integral<Integer>::value>* = nullptr>
114std::function<Integer(const std::string&)> GetParseElementFunc()
115{
116 return [](const std::string& s) { return armnn::numeric_cast<Integer>(std::stoi(s)); };
117}
118
119template <typename Float, std::enable_if_t<std::is_floating_point<Float>::value>* = nullptr>
120std::function<Float(const std::string&)> GetParseElementFunc()
121{
122 return [](const std::string& s) { return std::stof(s); };
123}
124
125template <typename T>
126void PopulateTensorWithData(T* tensor,
127 const unsigned int numElements,
128 const armnn::Optional<std::string>& dataFile,
129 const std::string& inputName)
130{
131 const bool readFromFile = dataFile.has_value() && !dataFile.value().empty();
132
133 std::ifstream inputTensorFile;
134 if (!readFromFile)
135 {
136 std::fill(tensor, tensor + numElements, 0);
137 return;
138 }
139 else
140 {
141 inputTensorFile = std::ifstream(dataFile.value());
142 }
143
144 auto parseElementFunc = GetParseElementFunc<T>();
145 std::string line;
146 unsigned int index = 0;
147 while (std::getline(inputTensorFile, line))
148 {
149 std::vector<std::string> tokens = armnn::stringUtils::StringTokenizer(line, "\t ,:");
150 for (const std::string& token : tokens)
151 {
152 if (!token.empty()) // See https://stackoverflow.com/questions/10437406/
153 {
154 try
155 {
156 if (index == numElements)
157 {
158 ARMNN_LOG(error) << "Number of elements: " << (index +1) << " in file \"" << dataFile.value()
159 << "\" does not match number of elements: " << numElements
160 << " for input \"" << inputName << "\".";
161 }
162 *(tensor + index) = parseElementFunc(token);
163 index++;
164 }
165 catch (const std::exception&)
166 {
167 ARMNN_LOG(error) << "'" << token << "' is not a valid number. It has been ignored.";
168 }
169 }
170 }
171 }
172
173 if (index != numElements)
174 {
175 ARMNN_LOG(error) << "Number of elements: " << (index +1) << " in file \"" << inputName
176 << "\" does not match number of elements: " << numElements
177 << " for input \"" << inputName << "\".";
178 }
179}
180
181template<typename T>
182void WriteToFile(const std::string& outputTensorFileName,
183 const std::string& outputName,
184 const T* const array,
185 const unsigned int numElements)
186{
187 std::ofstream outputTensorFile;
188 outputTensorFile.open(outputTensorFileName, std::ofstream::out | std::ofstream::trunc);
189 if (outputTensorFile.is_open())
190 {
191 outputTensorFile << outputName << ": ";
Adam Jalkemo7bbf5652022-10-18 16:56:09 +0200192 for (std::size_t i = 0; i < numElements; ++i)
193 {
194 outputTensorFile << +array[i] << " ";
195 }
Teresa Charlin83b42912022-07-07 14:24:59 +0100196 }
197 else
198 {
199 ARMNN_LOG(info) << "Output Tensor File: " << outputTensorFileName << " could not be opened!";
200 }
201 outputTensorFile.close();
202}
203
204struct OutputWriteInfo
205{
206 const armnn::Optional<std::string>& m_OutputTensorFile;
207 const std::string& m_OutputName;
208 const armnn::Tensor& m_Tensor;
209 const bool m_PrintTensor;
210};
211
212template <typename T>
213void PrintTensor(OutputWriteInfo& info, const char* formatString)
214{
215 const T* array = reinterpret_cast<const T*>(info.m_Tensor.GetMemoryArea());
216
217 if (info.m_OutputTensorFile.has_value())
218 {
219 WriteToFile(info.m_OutputTensorFile.value(),
220 info.m_OutputName,
221 array,
222 info.m_Tensor.GetNumElements());
223 }
224
225 if (info.m_PrintTensor)
226 {
227 for (unsigned int i = 0; i < info.m_Tensor.GetNumElements(); i++)
228 {
229 printf(formatString, array[i]);
230 }
231 }
232}
233
234template <typename T>
235void PrintQuantizedTensor(OutputWriteInfo& info)
236{
237 std::vector<float> dequantizedValues;
238 auto tensor = info.m_Tensor;
239 dequantizedValues = DequantizeArray<T>(tensor.GetMemoryArea(),
240 tensor.GetNumElements(),
241 tensor.GetInfo().GetQuantizationScale(),
242 tensor.GetInfo().GetQuantizationOffset());
243
244 if (info.m_OutputTensorFile.has_value())
245 {
246 WriteToFile(info.m_OutputTensorFile.value(),
247 info.m_OutputName,
248 dequantizedValues.data(),
249 tensor.GetNumElements());
250 }
251
252 if (info.m_PrintTensor)
253 {
254 std::for_each(dequantizedValues.begin(), dequantizedValues.end(), [&](float value)
255 {
256 printf("%f ", value);
257 });
258 }
259}
260
Finn Williams56870182020-11-20 13:57:53 +0000261template<typename T, typename TParseElementFunc>
262std::vector<T> ParseArrayImpl(std::istream& stream, TParseElementFunc parseElementFunc, const char* chars = "\t ,:")
263{
264 std::vector<T> result;
265 // Processes line-by-line.
266 std::string line;
267 while (std::getline(stream, line))
268 {
269 std::vector<std::string> tokens = armnn::stringUtils::StringTokenizer(line, chars);
270 for (const std::string& token : tokens)
271 {
272 if (!token.empty()) // See https://stackoverflow.com/questions/10437406/
273 {
274 try
275 {
276 result.push_back(parseElementFunc(token));
277 }
278 catch (const std::exception&)
279 {
280 ARMNN_LOG(error) << "'" << token << "' is not a valid number. It has been ignored.";
281 }
282 }
283 }
284 }
285
286 return result;
287}
288
Teresa Charlin83b42912022-07-07 14:24:59 +0100289/// Compute the root-mean-square error (RMSE)
290/// @param expected
291/// @param actual
292/// @param size size of the tensor
293/// @return float the RMSE
294template<typename T>
295float ComputeRMSE(const void* expected, const void* actual, const size_t size)
Finn Williams56870182020-11-20 13:57:53 +0000296{
Teresa Charlin83b42912022-07-07 14:24:59 +0100297 auto typedExpected = reinterpret_cast<const T*>(expected);
298 auto typedActual = reinterpret_cast<const T*>(actual);
Finn Williams56870182020-11-20 13:57:53 +0000299
Teresa Charlin83b42912022-07-07 14:24:59 +0100300 T errorSum = 0;
301
302 for (unsigned int i = 0; i < size; i++)
Finn Williams56870182020-11-20 13:57:53 +0000303 {
Teresa Charlin83b42912022-07-07 14:24:59 +0100304 if (std::abs(typedExpected[i] - typedActual[i]) != 0)
305 {
306 std::cout << "";
307 }
308 errorSum += std::pow(std::abs(typedExpected[i] - typedActual[i]), 2);
Finn Williams56870182020-11-20 13:57:53 +0000309 }
310
Teresa Charlin83b42912022-07-07 14:24:59 +0100311 float rmse = std::sqrt(armnn::numeric_cast<float>(errorSum) / armnn::numeric_cast<float>(size / sizeof(T)));
312 return rmse;
313}