blob: e4699acd2effb7672d64153e7b6c4b33ff292ecf [file] [log] [blame]
Colm Donelan0dfb2652023-06-22 10:19:17 +01001//
2// Copyright © 2023 Arm Ltd and Contributors. All rights reserved.
3// SPDX-License-Identifier: MIT
4//
5
6#include "FileComparisonExecutor.hpp"
7#include <NetworkExecutionUtils/NetworkExecutionUtils.hpp>
8#include <algorithm>
Colm Donelan94149362023-07-03 16:19:44 +01009#include <ghc/filesystem.hpp>
Colm Donelan0dfb2652023-06-22 10:19:17 +010010#include <iterator>
11
12using namespace armnn;
13
14/**
15 * Given a buffer in the expected format. Extract from it the tensor name, tensor type as strings and return an
16 * index pointing to the start of the data section.
17 *
18 * @param buffer data to be parsed.
19 * @param tensorName the name of the tensor extracted from the header.
20 * @param tensorType the type of the tensor extracted from the header.
21 * @return index pointing to the start of the data in the buffer.
22 */
23unsigned int ExtractHeader(const std::vector<char>& buffer, std::string& tensorName, DataType& tensorType)
24{
25 auto isColon = [](char c) { return c == ':'; };
26 auto isComma = [](char c) { return c == ','; };
27
28 // Find the "," separator marks the end of the tensor name.
29 auto firstComma = std::find_if(buffer.begin(), buffer.end(), isComma);
30 if (firstComma == buffer.end())
31 {
32 throw ParseException("Unable to read tensor name from file.");
33 }
34 tensorName.assign(buffer.begin(), firstComma);
35
36 // The next colon marks the end of the data type string.
37 auto endOfHeader = std::find_if(firstComma, buffer.end(), isColon);
38 if (firstComma == buffer.end())
39 {
40 throw ParseException("Unable to read tensor type from file.");
41 }
42 std::string type(++firstComma, endOfHeader);
43 // Remove any leading or trailing whitespace.
44 type.erase(remove_if(type.begin(), type.end(), isspace), type.end());
45 if (type == "Float16")
46 {
47 tensorType = DataType::Float16;
48 }
49 else if (type == "Float32")
50 {
51 tensorType = DataType::Float32;
52 }
53 else if (type == "QAsymmU8")
54 {
55 tensorType = DataType::QAsymmU8;
56 }
57 else if (type == "Signed32")
58 {
59 tensorType = DataType::Signed32;
60 }
61 else if (type == "Boolean")
62 {
63 tensorType = DataType::Boolean;
64 }
65 else if (type == "QSymmS16")
66 {
67 tensorType = DataType::QSymmS16;
68 }
69 else if (type == "QSymmS8")
70 {
71 tensorType = DataType::QSymmS8;
72 }
73 else if (type == "QAsymmS8")
74 {
75 tensorType = DataType::QAsymmS8;
76 }
77 else if (type == "BFloat16")
78 {
79 tensorType = DataType::BFloat16;
80 }
81 else if (type == "Signed64")
82 {
83 tensorType = DataType::Signed64;
84 }
85 else
86 {
87 throw ParseException("Invalid data type in header.");
88 }
89 // Remember to move the iterator past the colon.
90 return (++endOfHeader - buffer.begin());
91}
92
93/**
94 * Extract the data from the file and return as a typed vector of elements.
95 *
96 * @param buffer data to be parsed.
97 * @param dataStart Index into the vector where the tensor data starts.
98 * @param tensorType the type of the tensor extracted from the header.
99 */
100template <typename T>
101void ReadData(const std::vector<char>& buffer,
102 const unsigned int dataStart,
103 const DataType& tensorType,
104 std::vector<T>& results)
105{
106 unsigned int index = dataStart;
107 while (index < buffer.size())
108 {
109 std::string elementString;
110 // Extract into a string until the next space.
111 while (index < buffer.size() && buffer[index] != ' ')
112 {
113 elementString.push_back(buffer[index]);
114 index++;
115 }
116 if (!elementString.empty())
117 {
118 switch (tensorType)
119 {
120 case DataType::Float32: {
121 results.push_back(std::stof(elementString));
122 break;
123 }
124
125 case DataType::Signed32: {
126 results.push_back(std::stoi(elementString));
127 break;
128 }
129 case DataType::QSymmS8:
130 case DataType::QAsymmS8: {
131 results.push_back(elementString[0]);
132 break;
133 }
134 case DataType::QAsymmU8: {
135 results.push_back(elementString[0]);
136 break;
137 }
138 case DataType::Float16:
139 case DataType::QSymmS16:
140 case DataType::BFloat16:
141 case DataType::Boolean:
142 case DataType::Signed64:
143 default: {
144 LogAndThrow("Unsupported DataType");
145 }
146 }
147 // Finally, skip the space we know is there.
148 index++;
149 }
150 else
151 {
152 if (index < buffer.size())
153 {
154 index++;
155 }
156 }
157 }
158}
159
160/**
161 * Open the given file and read the data out of it to construct a Tensor. This could throw FileNotFoundException
162 * or InvalidArgumentException
163 *
164 * @param fileName the file to be read.
165 * @return a populated tensor.
166 */
167Tensor ReadTensorFromFile(const std::string fileName)
168{
Colm Donelan94149362023-07-03 16:19:44 +0100169 if (!ghc::filesystem::exists(fileName))
Colm Donelan0dfb2652023-06-22 10:19:17 +0100170 {
171 throw FileNotFoundException("The file \"" + fileName + "\" could not be found.");
172 }
173 // The format we are reading in is based on NetworkExecutionUtils::WriteToFile. This could potentially
174 // be an enormous tensor. We'll limit what we can read in to 1Mb.
175 std::uintmax_t maxFileSize = 1048576;
Colm Donelan94149362023-07-03 16:19:44 +0100176 std::uintmax_t fileSize = ghc::filesystem::file_size(fileName);
Colm Donelan0dfb2652023-06-22 10:19:17 +0100177 if (fileSize > maxFileSize)
178 {
179 throw InvalidArgumentException("The file \"" + fileName + "\" exceeds max size of 1 Mb.");
180 }
181
182 // We'll read the entire file into one buffer.
183 std::ifstream file(fileName, std::ios::binary);
184 std::vector<char> buffer(fileSize);
185 if (file.read(buffer.data(), fileSize))
186 {
187 std::string tensorName;
188 DataType tensorType;
189 unsigned int tensorDataStart = ExtractHeader(buffer, tensorName, tensorType);
190 switch (tensorType)
191 {
192 case DataType::Float32: {
193 std::vector<float> floatVector;
194 ReadData(buffer, tensorDataStart, tensorType, floatVector);
195 TensorInfo info({ static_cast<unsigned int>(floatVector.size()), 1, 1, 1 }, DataType::Float32);
196 float* floats = new float[floatVector.size()];
197 memcpy(floats, floatVector.data(), (floatVector.size() * sizeof(float)));
198 return Tensor(info, floats);
199 }
200 case DataType::Signed32: {
201 std::vector<int> intVector;
202 ReadData(buffer, tensorDataStart, tensorType, intVector);
203 TensorInfo info({ static_cast<unsigned int>(intVector.size()), 1, 1, 1 }, DataType::Signed32);
204 int* ints = new int[intVector.size()];
205 memcpy(ints, intVector.data(), (intVector.size() * sizeof(float)));
206 return Tensor(info, ints);
207 }
208 case DataType::QSymmS8: {
209 std::vector<int8_t> intVector;
210 ReadData(buffer, tensorDataStart, tensorType, intVector);
211 TensorInfo info({ static_cast<unsigned int>(intVector.size()), 1, 1, 1 }, DataType::QSymmS8);
212 int8_t* ints = new int8_t[intVector.size()];
213 memcpy(ints, intVector.data(), (intVector.size() * sizeof(float)));
214 return Tensor(info, ints);
215 }
216 case DataType::QAsymmS8: {
217 std::vector<int8_t> intVector;
218 ReadData(buffer, tensorDataStart, tensorType, intVector);
219 TensorInfo info({ static_cast<unsigned int>(intVector.size()), 1, 1, 1 }, DataType::QAsymmS8);
220 int8_t* ints = new int8_t[intVector.size()];
221 memcpy(ints, intVector.data(), (intVector.size() * sizeof(float)));
222 return Tensor(info, ints);
223 }
224 case DataType::QAsymmU8: {
225 std::vector<uint8_t> intVector;
226 ReadData(buffer, tensorDataStart, tensorType, intVector);
227 TensorInfo info({ static_cast<unsigned int>(intVector.size()), 1, 1, 1 }, DataType::QAsymmU8);
228 uint8_t* ints = new uint8_t[intVector.size()];
229 memcpy(ints, intVector.data(), (intVector.size() * sizeof(float)));
230 return Tensor(info, ints);
231 }
232 default:
233 throw InvalidArgumentException("The tensor data could not be read from \"" + fileName + "\"");
234 }
235 }
236 else
237 {
238 throw ParseException("Filed to read the contents of \"" + fileName + "\"");
239 }
240
241 Tensor result;
242 return result;
243}
244
245FileComparisonExecutor::FileComparisonExecutor(const ExecuteNetworkParams& params)
246 : m_Params(params)
247{}
248
249std::vector<const void*> FileComparisonExecutor::Execute()
250{
251 std::string filesToCompare = this->m_Params.m_ComparisonFile;
252 if (filesToCompare.empty())
253 {
254 throw InvalidArgumentException("The file(s) to compare was not set.");
255 }
256 // filesToCompare is one or more files containing output tensors. Iterate and read in the tensors.
257 // We'll assume the string follows the same comma seperated format as write-outputs-to-file.
258 std::stringstream ss(filesToCompare);
259 std::vector<std::string> fileNames;
260 std::string errorString;
261 while (ss.good())
262 {
263 std::string substr;
264 getline(ss, substr, ',');
265 // Check the file exist.
Colm Donelan94149362023-07-03 16:19:44 +0100266 if (!ghc::filesystem::exists(substr))
Colm Donelan0dfb2652023-06-22 10:19:17 +0100267 {
268 errorString += substr + " ";
269 }
270 else
271 {
272 fileNames.push_back(substr);
273 }
274 }
275 if (!errorString.empty())
276 {
277 throw FileNotFoundException("The following file(s) to compare could not be found: " + errorString);
278 }
279 // Read in the tensors into m_OutputTensorsVec
280 OutputTensors outputs;
281 std::vector<const void*> results;
282 for (auto file : fileNames)
283 {
284 Tensor t = ReadTensorFromFile(file);
285 outputs.push_back({ 0, Tensor(t.GetInfo(), t.GetMemoryArea()) });
286 results.push_back(t.GetMemoryArea());
287 }
288 m_OutputTensorsVec.push_back(outputs);
289 return results;
290}
291
292void FileComparisonExecutor::PrintNetworkInfo()
293{
294 std::cout << "Not implemented in this class." << std::endl;
295}
296
297void FileComparisonExecutor::CompareAndPrintResult(std::vector<const void*> otherOutput)
298{
299 unsigned int index = 0;
300 std::string typeString;
301 for (const auto& outputTensors : m_OutputTensorsVec)
302 {
303 for (const auto& outputTensor : outputTensors)
304 {
305 size_t size = outputTensor.second.GetNumBytes();
306 double result = ComputeByteLevelRMSE(outputTensor.second.GetMemoryArea(), otherOutput[index++], size);
307 std::cout << "Byte level root mean square error: " << result << "\n";
308 }
309 }
310}
311
312FileComparisonExecutor::~FileComparisonExecutor()
313{
314 // If there are tensors defined in m_OutputTensorsVec we need to clean up their memory usage.
315 for (OutputTensors opTensor : m_OutputTensorsVec)
316 {
317 for (std::pair<LayerBindingId, class Tensor> pair : opTensor)
318 {
319 Tensor t = pair.second;
320 // Based on the tensor type and size recover the memory.
321 switch (t.GetDataType())
322 {
323 case DataType::Float32:
324 delete[] static_cast<float*>(t.GetMemoryArea());
325 break;
326 case DataType::Signed32:
327 delete[] static_cast<int*>(t.GetMemoryArea());
328 break;
329 case DataType::QSymmS8:
330 delete[] static_cast<int8_t*>(t.GetMemoryArea());
331 break;
332 case DataType::QAsymmS8:
333 delete[] static_cast<int8_t*>(t.GetMemoryArea());
334 break;
335 case DataType::QAsymmU8:
336 delete[] static_cast<uint8_t*>(t.GetMemoryArea());
337 break;
338 default:
339 std::cout << "The data type wasn't created in ReadTensorFromFile" << std::endl;
340 }
341 }
342 }
343
344}