ArmNN
 22.11
CanonicalUtils.cpp
Go to the documentation of this file.
1 //
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>
13 #include <armnnUtils/Permute.hpp>
14 
15 #include <ghc/filesystem.hpp>
16 namespace 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 
28 namespace armnn
29 {
30 using Half = half_float::half; //import half float implementation
31 } //namespace armnn
32 
33 using namespace android;
34 using namespace android::nn;
35 
36 namespace armnn_driver
37 {
39 
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  {
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 
66 void* 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 
77 void* 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 
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:
108  break;
109  case OperandType::TENSOR_FLOAT32:
111  break;
112  case OperandType::TENSOR_FLOAT16:
114  break;
115  case OperandType::TENSOR_QUANT8_ASYMM:
117  break;
118  case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
119  perChannel=true;
121  case OperandType::TENSOR_QUANT8_SYMM:
123  break;
124  case OperandType::TENSOR_QUANT16_SYMM:
126  break;
127  case OperandType::TENSOR_INT32:
129  break;
130  case OperandType::INT32:
132  isScalar = true;
133  break;
134  case OperandType::TENSOR_QUANT8_ASYMM_SIGNED:
136  break;
137  default:
138  throw UnsupportedOperand<OperandType>(operand.type);
139  }
140 
141  TensorInfo ret;
142  if (isScalar)
143  {
145  }
146  else
147  {
148  if (operand.dimensions.size() == 0)
149  {
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 
189 std::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 
201 template <typename TensorType>
202 using DumpElementFunction = void (*)(const TensorType& tensor,
203  unsigned int elementIndex,
204  std::ofstream& fileStream);
205 
206 namespace
207 {
208 template <typename TensorType, typename ElementType, typename PrintableType = ElementType>
209 void DumpTensorElement(const TensorType& tensor, unsigned int elementIndex, std::ofstream& fileStream)
210 {
211  const ElementType* elements = reinterpret_cast<const ElementType*>(tensor.GetMemoryArea());
212  fileStream << static_cast<PrintableType>(elements[elementIndex]) << " ";
213 }
214 
215 } // namespace
216 template <typename TensorType>
217 void DumpTensor(const std::string& dumpDir,
218  const std::string& requestName,
219  const std::string& tensorName,
220  const TensorType& tensor)
221 {
222  // The dump directory must exist in advance.
223  fs::path dumpPath = dumpDir;
224  const fs::path fileName = dumpPath / (requestName + "_" + tensorName + ".dump");
225 
226  std::ofstream fileStream;
227  fileStream.open(fileName.c_str(), std::ofstream::out | std::ofstream::trunc);
228 
229  if (!fileStream.good())
230  {
231  VLOG(DRIVER) << "Could not open file %s for writing" << fileName.c_str();
232  return;
233  }
234 
235  DumpElementFunction<TensorType> dumpElementFunction = nullptr;
236 
237  switch (tensor.GetDataType())
238  {
240  {
241  dumpElementFunction = &DumpTensorElement<TensorType, float>;
242  break;
243  }
245  {
246  dumpElementFunction = &DumpTensorElement<TensorType, uint8_t, uint32_t>;
247  break;
248  }
250  {
251  dumpElementFunction = &DumpTensorElement<TensorType, int32_t>;
252  break;
253  }
255  {
256  dumpElementFunction = &DumpTensorElement<TensorType, armnn::Half>;
257  break;
258  }
260  {
261  dumpElementFunction = &DumpTensorElement<TensorType, int8_t, int32_t>;
262  break;
263  }
265  {
266  dumpElementFunction = &DumpTensorElement<TensorType, bool>;
267  break;
268  }
269  default:
270  {
271  dumpElementFunction = nullptr;
272  }
273  }
274 
275  if (dumpElementFunction != nullptr)
276  {
277  const unsigned int numDimensions = tensor.GetNumDimensions();
278  const armnn::TensorShape shape = tensor.GetShape();
279 
280  if (!shape.AreAllDimensionsSpecified())
281  {
282  fileStream << "Cannot dump tensor elements: not all dimensions are specified" << std::endl;
283  return;
284  }
285  fileStream << "# Number of elements " << tensor.GetNumElements() << std::endl;
286 
287  if (numDimensions == 0)
288  {
289  fileStream << "# Shape []" << std::endl;
290  return;
291  }
292  fileStream << "# Shape [" << shape[0];
293  for (unsigned int d = 1; d < numDimensions; ++d)
294  {
295  fileStream << "," << shape[d];
296  }
297  fileStream << "]" << std::endl;
298  fileStream << "Each line contains the data of each of the elements of dimension0. In NCHW and NHWC, each line"
299  " will be a batch" << std::endl << std::endl;
300 
301  // Split will create a new line after all elements of the first dimension
302  // (in a 4, 3, 2, 3 tensor, there will be 4 lines of 18 elements)
303  unsigned int split = 1;
304  if (numDimensions == 1)
305  {
306  split = shape[0];
307  }
308  else
309  {
310  for (unsigned int i = 1; i < numDimensions; ++i)
311  {
312  split *= shape[i];
313  }
314  }
315 
316  // Print all elements in the tensor
317  for (unsigned int elementIndex = 0; elementIndex < tensor.GetNumElements(); ++elementIndex)
318  {
319  (*dumpElementFunction)(tensor, elementIndex, fileStream);
320 
321  if ( (elementIndex + 1) % split == 0 )
322  {
323  fileStream << std::endl;
324  }
325  }
326  fileStream << std::endl;
327  }
328  else
329  {
330  fileStream << "Cannot dump tensor elements: Unsupported data type "
331  << static_cast<unsigned int>(tensor.GetDataType()) << std::endl;
332  }
333 
334  if (!fileStream.good())
335  {
336  VLOG(DRIVER) << "An error occurred when writing to file %s" << fileName.c_str();
337  }
338 }
339 
340 template void DumpTensor<armnn::ConstTensor>(const std::string& dumpDir,
341  const std::string& requestName,
342  const std::string& tensorName,
343  const armnn::ConstTensor& tensor);
344 
345 template void DumpTensor<armnn::Tensor>(const std::string& dumpDir,
346  const std::string& requestName,
347  const std::string& tensorName,
348  const armnn::Tensor& tensor);
349 
350 void DumpJsonProfilingIfRequired(bool gpuProfilingEnabled,
351  const std::string& dumpDir,
352  armnn::NetworkId networkId,
353  const armnn::IProfiler* profiler)
354 {
355  // Check if profiling is required.
356  if (!gpuProfilingEnabled)
357  {
358  return;
359  }
360 
361  // The dump directory must exist in advance.
362  if (dumpDir.empty())
363  {
364  return;
365  }
366 
367  ARMNN_ASSERT(profiler);
368 
369  // Set the name of the output profiling file.
370  fs::path dumpPath = dumpDir;
371  const fs::path fileName = dumpPath / (std::to_string(networkId) + "_profiling.json");
372 
373  // Open the ouput file for writing.
374  std::ofstream fileStream;
375  fileStream.open(fileName.c_str(), std::ofstream::out | std::ofstream::trunc);
376 
377  if (!fileStream.good())
378  {
379  VLOG(DRIVER) << "Could not open file %s for writing" << fileName.c_str();
380  return;
381  }
382 
383  // Write the profiling info to a JSON file.
384  profiler->Print(fileStream);
385 }
386 
387 std::string ExportNetworkGraphToDotFile(const armnn::IOptimizedNetwork& optimizedNetwork,
388  const std::string& dumpDir)
389 {
390  std::string fileName;
391  // The dump directory must exist in advance.
392  if (dumpDir.empty())
393  {
394  return fileName;
395  }
396 
397  std::string timestamp = GetFileTimestamp();
398  if (timestamp.empty())
399  {
400  return fileName;
401  }
402 
403  // Set the name of the output .dot file.
404  fs::path dumpPath = dumpDir;
405  fs::path tempFilePath = dumpPath / (timestamp + "_networkgraph.dot");
406  fileName = tempFilePath.string();
407 
408  VLOG(DRIVER) << "Exporting the optimized network graph to file: %s" << fileName.c_str();
409 
410  // Write the network graph to a dot file.
411  std::ofstream fileStream;
412  fileStream.open(fileName, std::ofstream::out | std::ofstream::trunc);
413 
414  if (!fileStream.good())
415  {
416  VLOG(DRIVER) << "Could not open file %s for writing" << fileName.c_str();
417  return fileName;
418  }
419 
420  if (optimizedNetwork.SerializeToDot(fileStream) != armnn::Status::Success)
421  {
422  VLOG(DRIVER) << "An error occurred when writing to file %s" << fileName.c_str();
423  }
424  return fileName;
425 }
426 
427 std::string SerializeNetwork(const armnn::INetwork& network,
428  const std::string& dumpDir,
429  std::vector<uint8_t>& dataCacheData,
430  bool dataCachingActive)
431 {
432  std::string fileName;
433  bool bSerializeToFile = true;
434  if (dumpDir.empty())
435  {
436  bSerializeToFile = false;
437  }
438  else
439  {
440  std::string timestamp = GetFileTimestamp();
441  if (timestamp.empty())
442  {
443  bSerializeToFile = false;
444  }
445  }
446  if (!bSerializeToFile && !dataCachingActive)
447  {
448  return fileName;
449  }
450 
452  // Serialize the Network
453  serializer->Serialize(network);
454  if (dataCachingActive)
455  {
456  std::stringstream stream;
457  auto serialized = serializer->SaveSerializedToStream(stream);
458  if (serialized)
459  {
460  std::string const serializedString{stream.str()};
461  std::copy(serializedString.begin(),
462  serializedString.end(),
463  std::back_inserter(dataCacheData));
464  }
465  }
466 
467  if (bSerializeToFile)
468  {
469  // Set the name of the output .armnn file.
470  fs::path dumpPath = dumpDir;
471  std::string timestamp = GetFileTimestamp();
472  fs::path tempFilePath = dumpPath / (timestamp + "_network.armnn");
473  fileName = tempFilePath.string();
474 
475  // Save serialized network to a file
476  std::ofstream serializedFile(fileName, std::ios::out | std::ios::binary);
477  auto serialized = serializer->SaveSerializedToStream(serializedFile);
478  if (!serialized)
479  {
480  VLOG(DRIVER) << "An error occurred when serializing to file %s" << fileName.c_str();
481  }
482  }
483  return fileName;
484 }
485 
486 bool IsDynamicTensor(const armnn::TensorInfo& tensorInfo)
487 {
489  {
490  return true;
491  }
492  // Account for the usage of the TensorShape empty constructor
493  if (tensorInfo.GetNumDimensions() == 0)
494  {
495  return true;
496  }
497  return !tensorInfo.GetShape().AreAllDimensionsSpecified();
498 }
499 
501 {
502  return true;
503 }
504 
505 bool isQuantizedOperand(const OperandType& operandType)
506 {
507  if (operandType == OperandType::TENSOR_QUANT8_ASYMM ||
508  operandType == OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL ||
509  operandType == OperandType::TENSOR_QUANT8_SYMM ||
510  operandType == OperandType::TENSOR_QUANT16_SYMM ||
511  operandType == OperandType::TENSOR_QUANT8_ASYMM_SIGNED)
512  {
513  return true;
514  }
515  else
516  {
517  return false;
518  }
519 }
520 
521 std::string GetModelSummary(const Model& model)
522 {
523  std::stringstream result;
524 
525  result << model.main.inputIndexes.size() << " input(s), "
526  << model.main.operations.size() << " operation(s), "
527  << model.main.outputIndexes.size() << " output(s), "
528  << model.main.operands.size() << " operand(s) "
529  << std::endl;
530 
531  result << "Inputs: ";
532  for (uint32_t i = 0; i < model.main.inputIndexes.size(); i++)
533  {
534  result << GetOperandSummary(model.main.operands[model.main.inputIndexes[i]]) << ", ";
535  }
536  result << std::endl;
537 
538  result << "Operations: ";
539  for (uint32_t i = 0; i < model.main.operations.size(); i++)
540  {
541  result << model.main.operations[i].type << ", ";
542  }
543  result << std::endl;
544 
545  result << "Outputs: ";
546  for (uint32_t i = 0; i < model.main.outputIndexes.size(); i++)
547  {
548  result << GetOperandSummary(model.main.operands[model.main.outputIndexes[i]]) << ", ";
549  }
550  result << std::endl;
551 
552  return result.str();
553 }
554 
555 std::string GetFileTimestamp()
556 {
557  // used to get a timestamp to name diagnostic files (the ArmNN serialized graph
558  // and getSupportedOperations.txt files)
559  timespec ts;
560  int iRet = clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
561  std::stringstream ss;
562  if (iRet == 0)
563  {
564  ss << std::to_string(ts.tv_sec) << "_" << std::to_string(ts.tv_nsec);
565  }
566  else
567  {
568  VLOG(DRIVER) << "clock_gettime failed with errno " <<
569  std::to_string(errno).c_str() << " : " <<
570  std::strerror(errno);
571  }
572  return ss.str();
573 }
574 
575 void RenameExportedFiles(const std::string& existingSerializedFileName,
576  const std::string& existingDotFileName,
577  const std::string& dumpDir,
578  const armnn::NetworkId networkId)
579 {
580  if (dumpDir.empty())
581  {
582  return;
583  }
584  RenameFile(existingSerializedFileName, std::string("_network.armnn"), dumpDir, networkId);
585  RenameFile(existingDotFileName, std::string("_networkgraph.dot"), dumpDir, networkId);
586 }
587 
588 void RenameFile(const std::string& existingName,
589  const std::string& extension,
590  const std::string& dumpDir,
591  const armnn::NetworkId networkId)
592 {
593  if (existingName.empty() || dumpDir.empty())
594  {
595  return;
596  }
597 
598  fs::path dumpPath = dumpDir;
599  const fs::path newFileName = dumpPath / (std::to_string(networkId) + extension);
600  int iRet = rename(existingName.c_str(), newFileName.c_str());
601  if (iRet != 0)
602  {
603  std::stringstream ss;
604  ss << "rename of [" << existingName << "] to [" << newFileName << "] failed with errno "
605  << std::to_string(errno) << " : " << std::strerror(errno);
606  VLOG(DRIVER) << ss.str().c_str();
607  }
608 }
609 
610 void CommitPools(std::vector<::android::nn::RunTimePoolInfo>& memPools)
611 {
612  // Commit output buffers.
613  // Note that we update *all* pools, even if they aren't actually used as outputs -
614  // this is simpler and is what the CpuExecutor does.
615  for (auto& pool : memPools)
616  {
617  // Type android::nn::RunTimePoolInfo has changed between Android P & Q and Android R, where
618  // update() has been removed and flush() added.
619  pool.flush();
620  }
621 }
622 } // namespace armnn_driver
unsigned int GetNumElements() const
Function that calculates the tensor elements by multiplying all dimension size which are Specified...
Definition: Tensor.cpp:181
const TensorShape & GetShape() const
Definition: Tensor.hpp:191
bool AreAllDimensionsSpecified() const
Checks if there is at least one dimension not specified.
Definition: Tensor.cpp:241
Dimensionality GetDimensionality() const
Function that returns the tensor type.
Definition: Tensor.hpp:92
void * GetMemoryFromPool(DataLocation location, const std::vector< android::nn::RunTimePoolInfo > &memPools)
Returns a pointer to a specific location in a pool`.
void Print(std::ostream &outStream) const
Print stats for events in JSON Format to the given output stream.
Definition: Profiling.cpp:630
Main network class which provides the interface for building up a neural network. ...
Definition: INetwork.hpp:261
std::string GetModelSummary(const Model &model)
Copyright (c) 2021 ARM Limited and Contributors.
void DumpTensor(const std::string &dumpDir, const std::string &requestName, const std::string &tensorName, const TensorType &tensor)
::android::nn::OperandType OperandType
void(*)(const TensorType &tensor, unsigned int elementIndex, std::ofstream &fileStream) DumpElementFunction
const armnn::PermutationVector g_DontPermute
A tensor defined by a TensorInfo (shape and data type) and a mutable backing store.
Definition: Tensor.hpp:319
void SwizzleAndroidNn4dTensorToArmNn(armnn::TensorInfo &tensorInfo, const void *input, void *output, const armnn::PermutationVector &mappings)
Swizzles tensor data in input according to the dimension mappings.
bool AreDynamicTensorsSupported()
Checks for ArmNN support of dynamic tensors.
void RenameExportedFiles(const std::string &existingSerializedFileName, const std::string &existingDotFileName, const std::string &dumpDir, const armnn::NetworkId networkId)
::android::nn::Model Model
Helper classes.
DataType
Definition: Types.hpp:48
std::string SerializeNetwork(const armnn::INetwork &network, const std::string &dumpDir, std::vector< uint8_t > &dataCacheData, bool dataCachingActive)
void * GetMemoryFromPointer(const Request::Argument &requestArg)
DataType GetDataType() const
Definition: Tensor.hpp:198
half_float::half Half
Definition: Half.hpp:22
armnn::TensorInfo GetTensorInfoForOperand(const Operand &operand)
int NetworkId
Definition: IRuntime.hpp:35
A tensor defined by a TensorInfo (shape and data type) and an immutable backing store.
Definition: Tensor.hpp:327
#define ARMNN_FALLTHROUGH
Definition: Utils.hpp:37
#define ARMNN_ASSERT(COND)
Definition: Assert.hpp:14
bool IsDynamicTensor(const armnn::TensorInfo &tensorInfo)
Checks if a tensor info represents a dynamic tensor.
std::string GetOperandSummary(const Operand &operand)
std::string ExportNetworkGraphToDotFile(const armnn::IOptimizedNetwork &optimizedNetwork, const std::string &dumpDir)
::android::nn::Operand Operand
Status SerializeToDot(std::ostream &stream) const
Definition: Network.cpp:485
static ISerializerPtr Create()
Definition: Serializer.cpp:35
bool isQuantizedOperand(const OperandType &operandType)
void CommitPools(std::vector<::android::nn::RunTimePoolInfo > &memPools)
std::string GetFileTimestamp()
void RenameFile(const std::string &existingName, const std::string &extension, const std::string &dumpDir, const armnn::NetworkId networkId)
unsigned int GetNumDimensions() const
Definition: Tensor.hpp:195
Helper classes.
Definition: ArmnnDevice.cpp:37
armnn::TensorShape Permuted(const armnn::TensorShape &srcShape, const armnn::PermutationVector &mappings)
Definition: Permute.cpp:98
void DumpJsonProfilingIfRequired(bool gpuProfilingEnabled, const std::string &dumpDir, armnn::NetworkId networkId, const armnn::IProfiler *profiler)
constexpr unsigned int GetDataTypeSize(DataType dataType)
Definition: TypesUtils.hpp:151