From 8f397a1efed11e17e9f8cb12b53a72b7e32ab978 Mon Sep 17 00:00:00 2001 From: Sadik Armagan Date: Fri, 17 Jun 2022 15:38:22 +0100 Subject: IVGCVSW-6989 "Merged experimental/armnn_shim_sl" * Updated Serializer CMakeLists.txt to build armnnSerializerObj * Added constant tensors as input support to SL Signed-off-by: Sadik Armagan Change-Id: I22f6cf50147d99a01f7fe70d7446b114a4c57af3 --- shim/sl/canonical/CanonicalUtils.cpp | 611 +++++++++++++++++++++++++++++++++++ 1 file changed, 611 insertions(+) create mode 100644 shim/sl/canonical/CanonicalUtils.cpp (limited to 'shim/sl/canonical/CanonicalUtils.cpp') diff --git a/shim/sl/canonical/CanonicalUtils.cpp b/shim/sl/canonical/CanonicalUtils.cpp new file mode 100644 index 0000000000..713629f554 --- /dev/null +++ b/shim/sl/canonical/CanonicalUtils.cpp @@ -0,0 +1,611 @@ +// +// Copyright © 2022 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#define LOG_TAG "arm-armnn-sl" + +#include "CanonicalUtils.hpp" + +#include +#include +#include +#include + +#include +namespace fs = ghc::filesystem; +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace armnn +{ +using Half = half_float::half; //import half float implementation +} //namespace armnn + +using namespace android; +using namespace android::nn; + +namespace armnn_driver +{ +const armnn::PermutationVector g_DontPermute{}; + +void SwizzleAndroidNn4dTensorToArmNn(armnn::TensorInfo& tensorInfo, + const void* input, + void* output, + const armnn::PermutationVector& mappings) +{ + assert(tensorInfo.GetNumDimensions() == 4U); + + armnn::DataType dataType = tensorInfo.GetDataType(); + switch (dataType) + { + case armnn::DataType::Float16: + case armnn::DataType::Float32: + case armnn::DataType::QAsymmU8: + case armnn::DataType::QSymmS8: + case armnn::DataType::QAsymmS8: + // First swizzle tensor info + tensorInfo = armnnUtils::Permuted(tensorInfo, mappings); + // Then swizzle tensor data + armnnUtils::Permute(tensorInfo.GetShape(), mappings, input, output, armnn::GetDataTypeSize(dataType)); + break; + default: + VLOG(DRIVER) << "Unknown armnn::DataType for swizzling"; + assert(0); + } +} + +void* GetMemoryFromPool(DataLocation location, const std::vector& memPools) +{ + // find the location within the pool + assert(location.poolIndex < memPools.size()); + + const android::nn::RunTimePoolInfo& memPool = memPools[location.poolIndex]; + uint8_t* memPoolBuffer = memPool.getBuffer(); + uint8_t* memory = memPoolBuffer + location.offset; + return memory; +} + +void* GetMemoryFromPointer(const Request::Argument& requestArg) +{ + // get the pointer memory + auto ptrMemory = std::visit([](std::variant&& memory) + { + if (std::holds_alternative(memory)) + { + auto ptr = std::get(memory); + auto ptrMemory = static_cast(ptr); + return const_cast(ptrMemory); + } + else + { + auto ptr = std::get(memory); + return static_cast(ptr); + } + }, requestArg.location.pointer); + return ptrMemory; +} + +armnn::TensorInfo GetTensorInfoForOperand(const Operand& operand) +{ + using namespace armnn; + bool perChannel = false; + bool isScalar = false; + + DataType type; + switch (operand.type) + { + case OperandType::TENSOR_BOOL8: + type = armnn::DataType::Boolean; + break; + case OperandType::TENSOR_FLOAT32: + type = armnn::DataType::Float32; + break; + case OperandType::TENSOR_FLOAT16: + type = armnn::DataType::Float16; + break; + case OperandType::TENSOR_QUANT8_ASYMM: + type = armnn::DataType::QAsymmU8; + break; + case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL: + perChannel=true; + ARMNN_FALLTHROUGH; + case OperandType::TENSOR_QUANT8_SYMM: + type = armnn::DataType::QSymmS8; + break; + case OperandType::TENSOR_QUANT16_SYMM: + type = armnn::DataType::QSymmS16; + break; + case OperandType::TENSOR_INT32: + type = armnn::DataType::Signed32; + break; + case OperandType::INT32: + type = armnn::DataType::Signed32; + isScalar = true; + break; + case OperandType::TENSOR_QUANT8_ASYMM_SIGNED: + type = armnn::DataType::QAsymmS8; + break; + default: + throw UnsupportedOperand(operand.type); + } + + TensorInfo ret; + if (isScalar) + { + ret = TensorInfo(TensorShape(armnn::Dimensionality::Scalar), type); + } + else + { + if (operand.dimensions.size() == 0) + { + TensorShape tensorShape(Dimensionality::NotSpecified); + ret = TensorInfo(tensorShape, type); + } + else + { + bool dimensionsSpecificity[5] = { true, true, true, true, true }; + int count = 0; + std::for_each(operand.dimensions.data(), + operand.dimensions.data() + operand.dimensions.size(), + [&](const unsigned int val) + { + if (val == 0) + { + dimensionsSpecificity[count] = false; + } + count++; + }); + + TensorShape tensorShape(operand.dimensions.size(), operand.dimensions.data(), dimensionsSpecificity); + ret = TensorInfo(tensorShape, type); + } + } + + if (perChannel) + { + // ExtraParams is expected to be of type channelQuant + const auto& perAxisQuantParams = std::get(operand.extraParams); + + ret.SetQuantizationScales(perAxisQuantParams.scales); + ret.SetQuantizationDim(MakeOptional(perAxisQuantParams.channelDim)); + } + else + { + ret.SetQuantizationScale(operand.scale); + ret.SetQuantizationOffset(operand.zeroPoint); + } + return ret; +} + +std::string GetOperandSummary(const Operand& operand) +{ + std::stringstream ss; + ss << "operand dimensions: [ "; + for (unsigned int i = 0; i < operand.dimensions.size(); ++i) + { + ss << operand.dimensions[i] << " "; + } + ss << "] operand type: " << operand.type; + return ss.str(); +} + +using DumpElementFunction = void (*)(const armnn::ConstTensor& tensor, + unsigned int elementIndex, + std::ofstream& fileStream); + +namespace +{ +template +void DumpTensorElement(const armnn::ConstTensor& tensor, unsigned int elementIndex, std::ofstream& fileStream) +{ + const ElementType* elements = reinterpret_cast(tensor.GetMemoryArea()); + fileStream << static_cast(elements[elementIndex]) << " "; +} + +} // namespace + +void DumpTensor(const std::string& dumpDir, + const std::string& requestName, + const std::string& tensorName, + const armnn::ConstTensor& tensor) +{ + // The dump directory must exist in advance. + fs::path dumpPath = dumpDir; + const fs::path fileName = dumpPath / (requestName + "_" + tensorName + ".dump"); + + std::ofstream fileStream; + fileStream.open(fileName.c_str(), std::ofstream::out | std::ofstream::trunc); + + if (!fileStream.good()) + { + VLOG(DRIVER) << "Could not open file %s for writing" << fileName.c_str(); + return; + } + + DumpElementFunction dumpElementFunction = nullptr; + + switch (tensor.GetDataType()) + { + case armnn::DataType::Float32: + { + dumpElementFunction = &DumpTensorElement; + break; + } + case armnn::DataType::QAsymmU8: + { + dumpElementFunction = &DumpTensorElement; + break; + } + case armnn::DataType::Signed32: + { + dumpElementFunction = &DumpTensorElement; + break; + } + case armnn::DataType::Float16: + { + dumpElementFunction = &DumpTensorElement; + break; + } + case armnn::DataType::QAsymmS8: + { + dumpElementFunction = &DumpTensorElement; + break; + } + case armnn::DataType::Boolean: + { + dumpElementFunction = &DumpTensorElement; + break; + } + default: + { + dumpElementFunction = nullptr; + } + } + + if (dumpElementFunction != nullptr) + { + const unsigned int numDimensions = tensor.GetNumDimensions(); + const armnn::TensorShape shape = tensor.GetShape(); + + if (!shape.AreAllDimensionsSpecified()) + { + fileStream << "Cannot dump tensor elements: not all dimensions are specified" << std::endl; + return; + } + fileStream << "# Number of elements " << tensor.GetNumElements() << std::endl; + + if (numDimensions == 0) + { + fileStream << "# Shape []" << std::endl; + return; + } + fileStream << "# Shape [" << shape[0]; + for (unsigned int d = 1; d < numDimensions; ++d) + { + fileStream << "," << shape[d]; + } + fileStream << "]" << std::endl; + fileStream << "Each line contains the data of each of the elements of dimension0. In NCHW and NHWC, each line" + " will be a batch" << std::endl << std::endl; + + // Split will create a new line after all elements of the first dimension + // (in a 4, 3, 2, 3 tensor, there will be 4 lines of 18 elements) + unsigned int split = 1; + if (numDimensions == 1) + { + split = shape[0]; + } + else + { + for (unsigned int i = 1; i < numDimensions; ++i) + { + split *= shape[i]; + } + } + + // Print all elements in the tensor + for (unsigned int elementIndex = 0; elementIndex < tensor.GetNumElements(); ++elementIndex) + { + (*dumpElementFunction)(tensor, elementIndex, fileStream); + + if ( (elementIndex + 1) % split == 0 ) + { + fileStream << std::endl; + } + } + fileStream << std::endl; + } + else + { + fileStream << "Cannot dump tensor elements: Unsupported data type " + << static_cast(tensor.GetDataType()) << std::endl; + } + + if (!fileStream.good()) + { + VLOG(DRIVER) << "An error occurred when writing to file %s" << fileName.c_str(); + } +} + +void DumpJsonProfilingIfRequired(bool gpuProfilingEnabled, + const std::string& dumpDir, + armnn::NetworkId networkId, + const armnn::IProfiler* profiler) +{ + // Check if profiling is required. + if (!gpuProfilingEnabled) + { + return; + } + + // The dump directory must exist in advance. + if (dumpDir.empty()) + { + return; + } + + ARMNN_ASSERT(profiler); + + // Set the name of the output profiling file. + fs::path dumpPath = dumpDir; + const fs::path fileName = dumpPath / (std::to_string(networkId) + "_profiling.json"); + + // Open the ouput file for writing. + std::ofstream fileStream; + fileStream.open(fileName.c_str(), std::ofstream::out | std::ofstream::trunc); + + if (!fileStream.good()) + { + VLOG(DRIVER) << "Could not open file %s for writing" << fileName.c_str(); + return; + } + + // Write the profiling info to a JSON file. + profiler->Print(fileStream); +} + +std::string ExportNetworkGraphToDotFile(const armnn::IOptimizedNetwork& optimizedNetwork, + const std::string& dumpDir) +{ + std::string fileName; + // The dump directory must exist in advance. + if (dumpDir.empty()) + { + return fileName; + } + + std::string timestamp = GetFileTimestamp(); + if (timestamp.empty()) + { + return fileName; + } + + // Set the name of the output .dot file. + fs::path dumpPath = dumpDir; + fs::path tempFilePath = dumpPath / (timestamp + "_networkgraph.dot"); + fileName = tempFilePath.string(); + + VLOG(DRIVER) << "Exporting the optimized network graph to file: %s" << fileName.c_str(); + + // Write the network graph to a dot file. + std::ofstream fileStream; + fileStream.open(fileName, std::ofstream::out | std::ofstream::trunc); + + if (!fileStream.good()) + { + VLOG(DRIVER) << "Could not open file %s for writing" << fileName.c_str(); + return fileName; + } + + if (optimizedNetwork.SerializeToDot(fileStream) != armnn::Status::Success) + { + VLOG(DRIVER) << "An error occurred when writing to file %s" << fileName.c_str(); + } + return fileName; +} + +std::string SerializeNetwork(const armnn::INetwork& network, + const std::string& dumpDir, + std::vector& dataCacheData, + bool dataCachingActive) +{ + std::string fileName; + bool bSerializeToFile = true; + if (dumpDir.empty()) + { + bSerializeToFile = false; + } + else + { + std::string timestamp = GetFileTimestamp(); + if (timestamp.empty()) + { + bSerializeToFile = false; + } + } + if (!bSerializeToFile && !dataCachingActive) + { + return fileName; + } + + auto serializer(armnnSerializer::ISerializer::Create()); + // Serialize the Network + serializer->Serialize(network); + if (dataCachingActive) + { + std::stringstream stream; + auto serialized = serializer->SaveSerializedToStream(stream); + if (serialized) + { + std::string const serializedString{stream.str()}; + std::copy(serializedString.begin(), + serializedString.end(), + std::back_inserter(dataCacheData)); + } + } + + if (bSerializeToFile) + { + // Set the name of the output .armnn file. + fs::path dumpPath = dumpDir; + std::string timestamp = GetFileTimestamp(); + fs::path tempFilePath = dumpPath / (timestamp + "_network.armnn"); + fileName = tempFilePath.string(); + + // Save serialized network to a file + std::ofstream serializedFile(fileName, std::ios::out | std::ios::binary); + auto serialized = serializer->SaveSerializedToStream(serializedFile); + if (!serialized) + { + VLOG(DRIVER) << "An error occurred when serializing to file %s" << fileName.c_str(); + } + } + return fileName; +} + +bool IsDynamicTensor(const armnn::TensorInfo& tensorInfo) +{ + if (tensorInfo.GetShape().GetDimensionality() == armnn::Dimensionality::NotSpecified) + { + return true; + } + // Account for the usage of the TensorShape empty constructor + if (tensorInfo.GetNumDimensions() == 0) + { + return true; + } + return !tensorInfo.GetShape().AreAllDimensionsSpecified(); +} + +bool AreDynamicTensorsSupported() //TODO +{ + return true; +} + +bool isQuantizedOperand(const OperandType& operandType) +{ + if (operandType == OperandType::TENSOR_QUANT8_ASYMM || + operandType == OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL || + operandType == OperandType::TENSOR_QUANT8_SYMM || + operandType == OperandType::TENSOR_QUANT16_SYMM || + operandType == OperandType::TENSOR_QUANT8_ASYMM_SIGNED) + { + return true; + } + else + { + return false; + } +} + +std::string GetModelSummary(const Model& model) +{ + std::stringstream result; + + result << model.main.inputIndexes.size() << " input(s), " + << model.main.operations.size() << " operation(s), " + << model.main.outputIndexes.size() << " output(s), " + << model.main.operands.size() << " operand(s) " + << std::endl; + + result << "Inputs: "; + for (uint32_t i = 0; i < model.main.inputIndexes.size(); i++) + { + result << GetOperandSummary(model.main.operands[model.main.inputIndexes[i]]) << ", "; + } + result << std::endl; + + result << "Operations: "; + for (uint32_t i = 0; i < model.main.operations.size(); i++) + { + result << model.main.operations[i].type << ", "; + } + result << std::endl; + + result << "Outputs: "; + for (uint32_t i = 0; i < model.main.outputIndexes.size(); i++) + { + result << GetOperandSummary(model.main.operands[model.main.outputIndexes[i]]) << ", "; + } + result << std::endl; + + return result.str(); +} + +std::string GetFileTimestamp() +{ + // used to get a timestamp to name diagnostic files (the ArmNN serialized graph + // and getSupportedOperations.txt files) + timespec ts; + int iRet = clock_gettime(CLOCK_MONOTONIC_RAW, &ts); + std::stringstream ss; + if (iRet == 0) + { + ss << std::to_string(ts.tv_sec) << "_" << std::to_string(ts.tv_nsec); + } + else + { + VLOG(DRIVER) << "clock_gettime failed with errno " << + std::to_string(errno).c_str() << " : " << + std::strerror(errno); + } + return ss.str(); +} + +void RenameExportedFiles(const std::string& existingSerializedFileName, + const std::string& existingDotFileName, + const std::string& dumpDir, + const armnn::NetworkId networkId) +{ + if (dumpDir.empty()) + { + return; + } + RenameFile(existingSerializedFileName, std::string("_network.armnn"), dumpDir, networkId); + RenameFile(existingDotFileName, std::string("_networkgraph.dot"), dumpDir, networkId); +} + +void RenameFile(const std::string& existingName, + const std::string& extension, + const std::string& dumpDir, + const armnn::NetworkId networkId) +{ + if (existingName.empty() || dumpDir.empty()) + { + return; + } + + fs::path dumpPath = dumpDir; + const fs::path newFileName = dumpPath / (std::to_string(networkId) + extension); + int iRet = rename(existingName.c_str(), newFileName.c_str()); + if (iRet != 0) + { + std::stringstream ss; + ss << "rename of [" << existingName << "] to [" << newFileName << "] failed with errno " + << std::to_string(errno) << " : " << std::strerror(errno); + VLOG(DRIVER) << ss.str().c_str(); + } +} + +void CommitPools(std::vector<::android::nn::RunTimePoolInfo>& memPools) +{ + // Commit output buffers. + // Note that we update *all* pools, even if they aren't actually used as outputs - + // this is simpler and is what the CpuExecutor does. + for (auto& pool : memPools) + { + // Type android::nn::RunTimePoolInfo has changed between Android P & Q and Android R, where + // update() has been removed and flush() added. + pool.flush(); + } +} +} // namespace armnn_driver -- cgit v1.2.1