blob: 622d4b111d4fbbde2b02decadedd7eee1a6a147e [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
Sadik Armagan9d9dd222022-07-15 10:22:49 +0100201template <typename TensorType>
202using DumpElementFunction = void (*)(const TensorType& tensor,
Sadik Armagan8f397a12022-06-17 15:38:22 +0100203 unsigned int elementIndex,
204 std::ofstream& fileStream);
205
206namespace
207{
Sadik Armagan9d9dd222022-07-15 10:22:49 +0100208template <typename TensorType, typename ElementType, typename PrintableType = ElementType>
209void DumpTensorElement(const TensorType& tensor, unsigned int elementIndex, std::ofstream& fileStream)
Sadik Armagan8f397a12022-06-17 15:38:22 +0100210{
211 const ElementType* elements = reinterpret_cast<const ElementType*>(tensor.GetMemoryArea());
212 fileStream << static_cast<PrintableType>(elements[elementIndex]) << " ";
213}
214
215} // namespace
Sadik Armagan9d9dd222022-07-15 10:22:49 +0100216template <typename TensorType>
Sadik Armagan8f397a12022-06-17 15:38:22 +0100217void DumpTensor(const std::string& dumpDir,
218 const std::string& requestName,
219 const std::string& tensorName,
Sadik Armagan9d9dd222022-07-15 10:22:49 +0100220 const TensorType& tensor)
Sadik Armagan8f397a12022-06-17 15:38:22 +0100221{
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
Sadik Armagan9d9dd222022-07-15 10:22:49 +0100235 DumpElementFunction<TensorType> dumpElementFunction = nullptr;
Sadik Armagan8f397a12022-06-17 15:38:22 +0100236
237 switch (tensor.GetDataType())
238 {
239 case armnn::DataType::Float32:
240 {
Sadik Armagan9d9dd222022-07-15 10:22:49 +0100241 dumpElementFunction = &DumpTensorElement<TensorType, float>;
Sadik Armagan8f397a12022-06-17 15:38:22 +0100242 break;
243 }
244 case armnn::DataType::QAsymmU8:
245 {
Sadik Armagan9d9dd222022-07-15 10:22:49 +0100246 dumpElementFunction = &DumpTensorElement<TensorType, uint8_t, uint32_t>;
Sadik Armagan8f397a12022-06-17 15:38:22 +0100247 break;
248 }
249 case armnn::DataType::Signed32:
250 {
Sadik Armagan9d9dd222022-07-15 10:22:49 +0100251 dumpElementFunction = &DumpTensorElement<TensorType, int32_t>;
Sadik Armagan8f397a12022-06-17 15:38:22 +0100252 break;
253 }
254 case armnn::DataType::Float16:
255 {
Sadik Armagan9d9dd222022-07-15 10:22:49 +0100256 dumpElementFunction = &DumpTensorElement<TensorType, armnn::Half>;
Sadik Armagan8f397a12022-06-17 15:38:22 +0100257 break;
258 }
259 case armnn::DataType::QAsymmS8:
260 {
Sadik Armagan9d9dd222022-07-15 10:22:49 +0100261 dumpElementFunction = &DumpTensorElement<TensorType, int8_t, int32_t>;
Sadik Armagan8f397a12022-06-17 15:38:22 +0100262 break;
263 }
264 case armnn::DataType::Boolean:
265 {
Sadik Armagan9d9dd222022-07-15 10:22:49 +0100266 dumpElementFunction = &DumpTensorElement<TensorType, bool>;
Sadik Armagan8f397a12022-06-17 15:38:22 +0100267 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
Sadik Armagan9d9dd222022-07-15 10:22:49 +0100340template void DumpTensor<armnn::ConstTensor>(const std::string& dumpDir,
341 const std::string& requestName,
342 const std::string& tensorName,
343 const armnn::ConstTensor& tensor);
344
345template void DumpTensor<armnn::Tensor>(const std::string& dumpDir,
346 const std::string& requestName,
347 const std::string& tensorName,
348 const armnn::Tensor& tensor);
349
Sadik Armagan8f397a12022-06-17 15:38:22 +0100350void 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
387std::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
427std::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
451 auto serializer(armnnSerializer::ISerializer::Create());
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
486bool IsDynamicTensor(const armnn::TensorInfo& tensorInfo)
487{
488 if (tensorInfo.GetShape().GetDimensionality() == armnn::Dimensionality::NotSpecified)
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
David Monahanf6585542023-05-08 13:14:32 +0100500bool AreDynamicTensorsSupported()
Sadik Armagan8f397a12022-06-17 15:38:22 +0100501{
502 return true;
503}
504
505bool 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
521std::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
555std::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
575void 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
588void 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
610void 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