blob: 713629f554abe490fdd4cc07a4134d11ad44c4f7 [file] [log] [blame]
Sadik Armagan8f397a12022-06-17 15:38:22 +01001//
2// Copyright © 2022 Arm Ltd and Contributors. All rights reserved.
3// SPDX-License-Identifier: MIT
4//
5
6#define LOG_TAG "arm-armnn-sl"
7
8#include "CanonicalUtils.hpp"
9
10#include <armnn/Utils.hpp>
11#include <armnn/utility/Assert.hpp>
12#include <armnnSerializer/ISerializer.hpp>
13#include <armnnUtils/Permute.hpp>
14
15#include <ghc/filesystem.hpp>
16namespace fs = ghc::filesystem;
17#include <half/half.hpp>
18#include <log/log.h>
19
20#include <cassert>
21#include <cerrno>
22#include <cinttypes>
23#include <cstdio>
24#include <sstream>
25#include <time.h>
26#include <variant>
27
28namespace armnn
29{
30using Half = half_float::half; //import half float implementation
31} //namespace armnn
32
33using namespace android;
34using namespace android::nn;
35
36namespace armnn_driver
37{
38const armnn::PermutationVector g_DontPermute{};
39
40void SwizzleAndroidNn4dTensorToArmNn(armnn::TensorInfo& tensorInfo,
41 const void* input,
42 void* output,
43 const armnn::PermutationVector& mappings)
44{
45 assert(tensorInfo.GetNumDimensions() == 4U);
46
47 armnn::DataType dataType = tensorInfo.GetDataType();
48 switch (dataType)
49 {
50 case armnn::DataType::Float16:
51 case armnn::DataType::Float32:
52 case armnn::DataType::QAsymmU8:
53 case armnn::DataType::QSymmS8:
54 case armnn::DataType::QAsymmS8:
55 // First swizzle tensor info
56 tensorInfo = armnnUtils::Permuted(tensorInfo, mappings);
57 // Then swizzle tensor data
58 armnnUtils::Permute(tensorInfo.GetShape(), mappings, input, output, armnn::GetDataTypeSize(dataType));
59 break;
60 default:
61 VLOG(DRIVER) << "Unknown armnn::DataType for swizzling";
62 assert(0);
63 }
64}
65
66void* GetMemoryFromPool(DataLocation location, const std::vector<android::nn::RunTimePoolInfo>& memPools)
67{
68 // find the location within the pool
69 assert(location.poolIndex < memPools.size());
70
71 const android::nn::RunTimePoolInfo& memPool = memPools[location.poolIndex];
72 uint8_t* memPoolBuffer = memPool.getBuffer();
73 uint8_t* memory = memPoolBuffer + location.offset;
74 return memory;
75}
76
77void* GetMemoryFromPointer(const Request::Argument& requestArg)
78{
79 // get the pointer memory
80 auto ptrMemory = std::visit([](std::variant<const void*, void*>&& memory)
81 {
82 if (std::holds_alternative<const void*>(memory))
83 {
84 auto ptr = std::get<const void*>(memory);
85 auto ptrMemory = static_cast<const uint8_t*>(ptr);
86 return const_cast<uint8_t*>(ptrMemory);
87 }
88 else
89 {
90 auto ptr = std::get<void*>(memory);
91 return static_cast<uint8_t*>(ptr);
92 }
93 }, requestArg.location.pointer);
94 return ptrMemory;
95}
96
97armnn::TensorInfo GetTensorInfoForOperand(const Operand& operand)
98{
99 using namespace armnn;
100 bool perChannel = false;
101 bool isScalar = false;
102
103 DataType type;
104 switch (operand.type)
105 {
106 case OperandType::TENSOR_BOOL8:
107 type = armnn::DataType::Boolean;
108 break;
109 case OperandType::TENSOR_FLOAT32:
110 type = armnn::DataType::Float32;
111 break;
112 case OperandType::TENSOR_FLOAT16:
113 type = armnn::DataType::Float16;
114 break;
115 case OperandType::TENSOR_QUANT8_ASYMM:
116 type = armnn::DataType::QAsymmU8;
117 break;
118 case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
119 perChannel=true;
120 ARMNN_FALLTHROUGH;
121 case OperandType::TENSOR_QUANT8_SYMM:
122 type = armnn::DataType::QSymmS8;
123 break;
124 case OperandType::TENSOR_QUANT16_SYMM:
125 type = armnn::DataType::QSymmS16;
126 break;
127 case OperandType::TENSOR_INT32:
128 type = armnn::DataType::Signed32;
129 break;
130 case OperandType::INT32:
131 type = armnn::DataType::Signed32;
132 isScalar = true;
133 break;
134 case OperandType::TENSOR_QUANT8_ASYMM_SIGNED:
135 type = armnn::DataType::QAsymmS8;
136 break;
137 default:
138 throw UnsupportedOperand<OperandType>(operand.type);
139 }
140
141 TensorInfo ret;
142 if (isScalar)
143 {
144 ret = TensorInfo(TensorShape(armnn::Dimensionality::Scalar), type);
145 }
146 else
147 {
148 if (operand.dimensions.size() == 0)
149 {
150 TensorShape tensorShape(Dimensionality::NotSpecified);
151 ret = TensorInfo(tensorShape, type);
152 }
153 else
154 {
155 bool dimensionsSpecificity[5] = { true, true, true, true, true };
156 int count = 0;
157 std::for_each(operand.dimensions.data(),
158 operand.dimensions.data() + operand.dimensions.size(),
159 [&](const unsigned int val)
160 {
161 if (val == 0)
162 {
163 dimensionsSpecificity[count] = false;
164 }
165 count++;
166 });
167
168 TensorShape tensorShape(operand.dimensions.size(), operand.dimensions.data(), dimensionsSpecificity);
169 ret = TensorInfo(tensorShape, type);
170 }
171 }
172
173 if (perChannel)
174 {
175 // ExtraParams is expected to be of type channelQuant
176 const auto& perAxisQuantParams = std::get<Operand::SymmPerChannelQuantParams>(operand.extraParams);
177
178 ret.SetQuantizationScales(perAxisQuantParams.scales);
179 ret.SetQuantizationDim(MakeOptional<unsigned int>(perAxisQuantParams.channelDim));
180 }
181 else
182 {
183 ret.SetQuantizationScale(operand.scale);
184 ret.SetQuantizationOffset(operand.zeroPoint);
185 }
186 return ret;
187}
188
189std::string GetOperandSummary(const Operand& operand)
190{
191 std::stringstream ss;
192 ss << "operand dimensions: [ ";
193 for (unsigned int i = 0; i < operand.dimensions.size(); ++i)
194 {
195 ss << operand.dimensions[i] << " ";
196 }
197 ss << "] operand type: " << operand.type;
198 return ss.str();
199}
200
201using DumpElementFunction = void (*)(const armnn::ConstTensor& tensor,
202 unsigned int elementIndex,
203 std::ofstream& fileStream);
204
205namespace
206{
207template <typename ElementType, typename PrintableType = ElementType>
208void DumpTensorElement(const armnn::ConstTensor& tensor, unsigned int elementIndex, std::ofstream& fileStream)
209{
210 const ElementType* elements = reinterpret_cast<const ElementType*>(tensor.GetMemoryArea());
211 fileStream << static_cast<PrintableType>(elements[elementIndex]) << " ";
212}
213
214} // namespace
215
216void DumpTensor(const std::string& dumpDir,
217 const std::string& requestName,
218 const std::string& tensorName,
219 const armnn::ConstTensor& tensor)
220{
221 // The dump directory must exist in advance.
222 fs::path dumpPath = dumpDir;
223 const fs::path fileName = dumpPath / (requestName + "_" + tensorName + ".dump");
224
225 std::ofstream fileStream;
226 fileStream.open(fileName.c_str(), std::ofstream::out | std::ofstream::trunc);
227
228 if (!fileStream.good())
229 {
230 VLOG(DRIVER) << "Could not open file %s for writing" << fileName.c_str();
231 return;
232 }
233
234 DumpElementFunction dumpElementFunction = nullptr;
235
236 switch (tensor.GetDataType())
237 {
238 case armnn::DataType::Float32:
239 {
240 dumpElementFunction = &DumpTensorElement<float>;
241 break;
242 }
243 case armnn::DataType::QAsymmU8:
244 {
245 dumpElementFunction = &DumpTensorElement<uint8_t, uint32_t>;
246 break;
247 }
248 case armnn::DataType::Signed32:
249 {
250 dumpElementFunction = &DumpTensorElement<int32_t>;
251 break;
252 }
253 case armnn::DataType::Float16:
254 {
255 dumpElementFunction = &DumpTensorElement<armnn::Half>;
256 break;
257 }
258 case armnn::DataType::QAsymmS8:
259 {
260 dumpElementFunction = &DumpTensorElement<int8_t, int32_t>;
261 break;
262 }
263 case armnn::DataType::Boolean:
264 {
265 dumpElementFunction = &DumpTensorElement<bool>;
266 break;
267 }
268 default:
269 {
270 dumpElementFunction = nullptr;
271 }
272 }
273
274 if (dumpElementFunction != nullptr)
275 {
276 const unsigned int numDimensions = tensor.GetNumDimensions();
277 const armnn::TensorShape shape = tensor.GetShape();
278
279 if (!shape.AreAllDimensionsSpecified())
280 {
281 fileStream << "Cannot dump tensor elements: not all dimensions are specified" << std::endl;
282 return;
283 }
284 fileStream << "# Number of elements " << tensor.GetNumElements() << std::endl;
285
286 if (numDimensions == 0)
287 {
288 fileStream << "# Shape []" << std::endl;
289 return;
290 }
291 fileStream << "# Shape [" << shape[0];
292 for (unsigned int d = 1; d < numDimensions; ++d)
293 {
294 fileStream << "," << shape[d];
295 }
296 fileStream << "]" << std::endl;
297 fileStream << "Each line contains the data of each of the elements of dimension0. In NCHW and NHWC, each line"
298 " will be a batch" << std::endl << std::endl;
299
300 // Split will create a new line after all elements of the first dimension
301 // (in a 4, 3, 2, 3 tensor, there will be 4 lines of 18 elements)
302 unsigned int split = 1;
303 if (numDimensions == 1)
304 {
305 split = shape[0];
306 }
307 else
308 {
309 for (unsigned int i = 1; i < numDimensions; ++i)
310 {
311 split *= shape[i];
312 }
313 }
314
315 // Print all elements in the tensor
316 for (unsigned int elementIndex = 0; elementIndex < tensor.GetNumElements(); ++elementIndex)
317 {
318 (*dumpElementFunction)(tensor, elementIndex, fileStream);
319
320 if ( (elementIndex + 1) % split == 0 )
321 {
322 fileStream << std::endl;
323 }
324 }
325 fileStream << std::endl;
326 }
327 else
328 {
329 fileStream << "Cannot dump tensor elements: Unsupported data type "
330 << static_cast<unsigned int>(tensor.GetDataType()) << std::endl;
331 }
332
333 if (!fileStream.good())
334 {
335 VLOG(DRIVER) << "An error occurred when writing to file %s" << fileName.c_str();
336 }
337}
338
339void DumpJsonProfilingIfRequired(bool gpuProfilingEnabled,
340 const std::string& dumpDir,
341 armnn::NetworkId networkId,
342 const armnn::IProfiler* profiler)
343{
344 // Check if profiling is required.
345 if (!gpuProfilingEnabled)
346 {
347 return;
348 }
349
350 // The dump directory must exist in advance.
351 if (dumpDir.empty())
352 {
353 return;
354 }
355
356 ARMNN_ASSERT(profiler);
357
358 // Set the name of the output profiling file.
359 fs::path dumpPath = dumpDir;
360 const fs::path fileName = dumpPath / (std::to_string(networkId) + "_profiling.json");
361
362 // Open the ouput file for writing.
363 std::ofstream fileStream;
364 fileStream.open(fileName.c_str(), std::ofstream::out | std::ofstream::trunc);
365
366 if (!fileStream.good())
367 {
368 VLOG(DRIVER) << "Could not open file %s for writing" << fileName.c_str();
369 return;
370 }
371
372 // Write the profiling info to a JSON file.
373 profiler->Print(fileStream);
374}
375
376std::string ExportNetworkGraphToDotFile(const armnn::IOptimizedNetwork& optimizedNetwork,
377 const std::string& dumpDir)
378{
379 std::string fileName;
380 // The dump directory must exist in advance.
381 if (dumpDir.empty())
382 {
383 return fileName;
384 }
385
386 std::string timestamp = GetFileTimestamp();
387 if (timestamp.empty())
388 {
389 return fileName;
390 }
391
392 // Set the name of the output .dot file.
393 fs::path dumpPath = dumpDir;
394 fs::path tempFilePath = dumpPath / (timestamp + "_networkgraph.dot");
395 fileName = tempFilePath.string();
396
397 VLOG(DRIVER) << "Exporting the optimized network graph to file: %s" << fileName.c_str();
398
399 // Write the network graph to a dot file.
400 std::ofstream fileStream;
401 fileStream.open(fileName, std::ofstream::out | std::ofstream::trunc);
402
403 if (!fileStream.good())
404 {
405 VLOG(DRIVER) << "Could not open file %s for writing" << fileName.c_str();
406 return fileName;
407 }
408
409 if (optimizedNetwork.SerializeToDot(fileStream) != armnn::Status::Success)
410 {
411 VLOG(DRIVER) << "An error occurred when writing to file %s" << fileName.c_str();
412 }
413 return fileName;
414}
415
416std::string SerializeNetwork(const armnn::INetwork& network,
417 const std::string& dumpDir,
418 std::vector<uint8_t>& dataCacheData,
419 bool dataCachingActive)
420{
421 std::string fileName;
422 bool bSerializeToFile = true;
423 if (dumpDir.empty())
424 {
425 bSerializeToFile = false;
426 }
427 else
428 {
429 std::string timestamp = GetFileTimestamp();
430 if (timestamp.empty())
431 {
432 bSerializeToFile = false;
433 }
434 }
435 if (!bSerializeToFile && !dataCachingActive)
436 {
437 return fileName;
438 }
439
440 auto serializer(armnnSerializer::ISerializer::Create());
441 // Serialize the Network
442 serializer->Serialize(network);
443 if (dataCachingActive)
444 {
445 std::stringstream stream;
446 auto serialized = serializer->SaveSerializedToStream(stream);
447 if (serialized)
448 {
449 std::string const serializedString{stream.str()};
450 std::copy(serializedString.begin(),
451 serializedString.end(),
452 std::back_inserter(dataCacheData));
453 }
454 }
455
456 if (bSerializeToFile)
457 {
458 // Set the name of the output .armnn file.
459 fs::path dumpPath = dumpDir;
460 std::string timestamp = GetFileTimestamp();
461 fs::path tempFilePath = dumpPath / (timestamp + "_network.armnn");
462 fileName = tempFilePath.string();
463
464 // Save serialized network to a file
465 std::ofstream serializedFile(fileName, std::ios::out | std::ios::binary);
466 auto serialized = serializer->SaveSerializedToStream(serializedFile);
467 if (!serialized)
468 {
469 VLOG(DRIVER) << "An error occurred when serializing to file %s" << fileName.c_str();
470 }
471 }
472 return fileName;
473}
474
475bool IsDynamicTensor(const armnn::TensorInfo& tensorInfo)
476{
477 if (tensorInfo.GetShape().GetDimensionality() == armnn::Dimensionality::NotSpecified)
478 {
479 return true;
480 }
481 // Account for the usage of the TensorShape empty constructor
482 if (tensorInfo.GetNumDimensions() == 0)
483 {
484 return true;
485 }
486 return !tensorInfo.GetShape().AreAllDimensionsSpecified();
487}
488
489bool AreDynamicTensorsSupported() //TODO
490{
491 return true;
492}
493
494bool isQuantizedOperand(const OperandType& operandType)
495{
496 if (operandType == OperandType::TENSOR_QUANT8_ASYMM ||
497 operandType == OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL ||
498 operandType == OperandType::TENSOR_QUANT8_SYMM ||
499 operandType == OperandType::TENSOR_QUANT16_SYMM ||
500 operandType == OperandType::TENSOR_QUANT8_ASYMM_SIGNED)
501 {
502 return true;
503 }
504 else
505 {
506 return false;
507 }
508}
509
510std::string GetModelSummary(const Model& model)
511{
512 std::stringstream result;
513
514 result << model.main.inputIndexes.size() << " input(s), "
515 << model.main.operations.size() << " operation(s), "
516 << model.main.outputIndexes.size() << " output(s), "
517 << model.main.operands.size() << " operand(s) "
518 << std::endl;
519
520 result << "Inputs: ";
521 for (uint32_t i = 0; i < model.main.inputIndexes.size(); i++)
522 {
523 result << GetOperandSummary(model.main.operands[model.main.inputIndexes[i]]) << ", ";
524 }
525 result << std::endl;
526
527 result << "Operations: ";
528 for (uint32_t i = 0; i < model.main.operations.size(); i++)
529 {
530 result << model.main.operations[i].type << ", ";
531 }
532 result << std::endl;
533
534 result << "Outputs: ";
535 for (uint32_t i = 0; i < model.main.outputIndexes.size(); i++)
536 {
537 result << GetOperandSummary(model.main.operands[model.main.outputIndexes[i]]) << ", ";
538 }
539 result << std::endl;
540
541 return result.str();
542}
543
544std::string GetFileTimestamp()
545{
546 // used to get a timestamp to name diagnostic files (the ArmNN serialized graph
547 // and getSupportedOperations.txt files)
548 timespec ts;
549 int iRet = clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
550 std::stringstream ss;
551 if (iRet == 0)
552 {
553 ss << std::to_string(ts.tv_sec) << "_" << std::to_string(ts.tv_nsec);
554 }
555 else
556 {
557 VLOG(DRIVER) << "clock_gettime failed with errno " <<
558 std::to_string(errno).c_str() << " : " <<
559 std::strerror(errno);
560 }
561 return ss.str();
562}
563
564void RenameExportedFiles(const std::string& existingSerializedFileName,
565 const std::string& existingDotFileName,
566 const std::string& dumpDir,
567 const armnn::NetworkId networkId)
568{
569 if (dumpDir.empty())
570 {
571 return;
572 }
573 RenameFile(existingSerializedFileName, std::string("_network.armnn"), dumpDir, networkId);
574 RenameFile(existingDotFileName, std::string("_networkgraph.dot"), dumpDir, networkId);
575}
576
577void RenameFile(const std::string& existingName,
578 const std::string& extension,
579 const std::string& dumpDir,
580 const armnn::NetworkId networkId)
581{
582 if (existingName.empty() || dumpDir.empty())
583 {
584 return;
585 }
586
587 fs::path dumpPath = dumpDir;
588 const fs::path newFileName = dumpPath / (std::to_string(networkId) + extension);
589 int iRet = rename(existingName.c_str(), newFileName.c_str());
590 if (iRet != 0)
591 {
592 std::stringstream ss;
593 ss << "rename of [" << existingName << "] to [" << newFileName << "] failed with errno "
594 << std::to_string(errno) << " : " << std::strerror(errno);
595 VLOG(DRIVER) << ss.str().c_str();
596 }
597}
598
599void CommitPools(std::vector<::android::nn::RunTimePoolInfo>& memPools)
600{
601 // Commit output buffers.
602 // Note that we update *all* pools, even if they aren't actually used as outputs -
603 // this is simpler and is what the CpuExecutor does.
604 for (auto& pool : memPools)
605 {
606 // Type android::nn::RunTimePoolInfo has changed between Android P & Q and Android R, where
607 // update() has been removed and flush() added.
608 pool.flush();
609 }
610}
611} // namespace armnn_driver