From a4983cec09a3e24bf4e99abd31aa11842e8b365f Mon Sep 17 00:00:00 2001 From: Finn Williams Date: Thu, 23 Jul 2020 12:55:12 +0100 Subject: IVGCVSW-4931 Update NN Driver to support dynamic tensors * Change NN Driver m_Network to now have ShapeInferenceMethod::InferAndValidate * Implement dynamic tensor support for: - ArgMinMax layer - Pooling2d layer - Activation layer * Skip dynamic tensor tests for any HAL other than 1.3 Change-Id: Icf66c968e49cdd4822b8c79c5f18b3f9e97dc53f Signed-off-by: Finn Williams Signed-off-by: Teresa Charlin --- ArmnnPreparedModel.cpp | 2 +- ArmnnPreparedModel_1_2.cpp | 11 +++- ArmnnPreparedModel_1_3.cpp | 47 ++++++++++---- ConversionUtils.cpp | 9 +++ ConversionUtils.hpp | 151 ++++++++++++++++++++++++++++++------------- ModelToINetworkConverter.cpp | 11 +++- Utils.cpp | 99 +++++++++++++++++++++++++--- Utils.hpp | 3 + 8 files changed, 263 insertions(+), 70 deletions(-) diff --git a/ArmnnPreparedModel.cpp b/ArmnnPreparedModel.cpp index f990d3bc..8aa28d7e 100644 --- a/ArmnnPreparedModel.cpp +++ b/ArmnnPreparedModel.cpp @@ -48,7 +48,7 @@ bool ValidateRequestArgument(const RequestArgument& requestArg, const armnn::Ten for (unsigned int d = 0; d < tensorInfo.GetNumDimensions(); ++d) { - if (requestArg.dimensions[d] != tensorInfo.GetShape()[d]) + if (requestArg.dimensions[d] != 0 && requestArg.dimensions[d] != tensorInfo.GetShape()[d]) { ALOGE("Mismatched size for dimension %d (request argument: %u, expected %u)", d, requestArg.dimensions[d], tensorInfo.GetShape()[d]); diff --git a/ArmnnPreparedModel_1_2.cpp b/ArmnnPreparedModel_1_2.cpp index 76ef4265..dd5bdae7 100644 --- a/ArmnnPreparedModel_1_2.cpp +++ b/ArmnnPreparedModel_1_2.cpp @@ -80,7 +80,7 @@ bool ValidateRequestArgument(const RequestArgument& requestArg, const armnn::Ten for (unsigned int d = 0; d < tensorInfo.GetNumDimensions(); ++d) { - if (requestArg.dimensions[d] != tensorInfo.GetShape()[d]) + if (requestArg.dimensions[d] != 0 && requestArg.dimensions[d] != tensorInfo.GetShape()[d]) { ALOGE("Mismatched size for dimension %d (request argument: %u, expected %u)", d, requestArg.dimensions[d], tensorInfo.GetShape()[d]); @@ -259,10 +259,17 @@ Return ArmnnPreparedModel_1_2::PrepareMemoryForOu } const size_t outputSize = outputTensorInfo.GetNumBytes(); + + if (outputArg.location.length < outputSize) + { + ALOGW("ArmnnPreparedModel_1_2::Execute failed: outputArg.location.length < outputSize"); + return V1_0::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE; + } + const size_t bufferSize = memPools.at(outputArg.location.poolIndex).getHidlMemory().size(); if (bufferSize < outputSize) { - ALOGW("ArmnnPreparedModel_1_2::Execute failed"); + ALOGW("ArmnnPreparedModel_1_2::Execute failed: bufferSize < outputSize"); return V1_0::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE; } diff --git a/ArmnnPreparedModel_1_3.cpp b/ArmnnPreparedModel_1_3.cpp index c7adc6c4..a27c7a39 100644 --- a/ArmnnPreparedModel_1_3.cpp +++ b/ArmnnPreparedModel_1_3.cpp @@ -101,7 +101,7 @@ bool ValidateRequestArgument(const RequestArgument& requestArg, const armnn::Ten for (unsigned int d = 0; d < tensorInfo.GetNumDimensions(); ++d) { - if (requestArg.dimensions[d] != tensorInfo.GetShape()[d]) + if (requestArg.dimensions[d] != 0 && requestArg.dimensions[d] != tensorInfo.GetShape()[d]) { ALOGE("Mismatched size for dimension %d (request argument: %u, expected %u)", d, requestArg.dimensions[d], tensorInfo.GetShape()[d]); @@ -309,6 +309,13 @@ Return ArmnnPreparedModel_1_3::executeFenced(const V1_3::Reque ALOGW("ArmnnPreparedModel_1_3::executeFenced parameter loopTimeoutDuration is set but not supported."); } + if (!android::nn::validateRequest(request, m_Model, /*allowUnspecifiedOutput=*/false)) + { + ALOGV("ArmnnPreparedModel_1_3::executeFenced outputs must be specified for fenced execution "); + cb(ErrorStatus::INVALID_ARGUMENT, hidl_handle(nullptr), nullptr); + return Void(); + } + ExecutionContext_1_3 ctx; if (measureTiming == MeasureTiming::YES) { @@ -319,12 +326,6 @@ Return ArmnnPreparedModel_1_3::executeFenced(const V1_3::Reque ALOGV("ArmnnPreparedModel_1_3::executeFenced(): %s", GetModelSummary(m_Model).c_str()); m_RequestCount++; - if (!android::nn::validateRequest(request, m_Model)) - { - cb(ErrorStatus::INVALID_ARGUMENT, hidl_handle(nullptr), nullptr); - return Void(); - } - if (!m_RequestInputsAndOutputsDumpDir.empty()) { ALOGD("Dumping inputs and outputs for request %" PRIuPTR, reinterpret_cast(&cb)); @@ -442,7 +443,7 @@ Return ArmnnPreparedModel_1_3::PrepareMemoryForOu { const auto& outputArg = request.outputs[i]; - const armnn::TensorInfo outputTensorInfo = m_Runtime->GetOutputTensorInfo(m_NetworkId, i); + armnn::TensorInfo outputTensorInfo = m_Runtime->GetOutputTensorInfo(m_NetworkId, i); const armnn::Tensor outputTensor = GetTensorForRequestArgument(outputArg, outputTensorInfo, memPools); if (outputTensor.GetMemoryArea() == nullptr) { @@ -450,16 +451,40 @@ Return ArmnnPreparedModel_1_3::PrepareMemoryForOu return V1_3::ErrorStatus::GENERAL_FAILURE; } + unsigned int count = 0; + std::for_each(outputArg.dimensions.begin(), outputArg.dimensions.end(), [&](auto dim) + { + if (dim != 0) + { + outputTensorInfo.GetShape()[count] = dim; + } + else + { + outputTensorInfo.GetShape()[count] = outputArg.dimensions.size(); + } + + count++; + }); + const size_t outputSize = outputTensorInfo.GetNumBytes(); + + outputs.emplace_back(i, outputTensor); + outputShapes[i] = ComputeShape(outputTensorInfo); + + if (outputArg.location.length < outputSize) + { + ALOGW("ArmnnPreparedModel_1_3::Execute failed"); + outputShapes[i].isSufficient = false; + return V1_3::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE; + } + const size_t bufferSize = memPools.at(outputArg.location.poolIndex).getHidlMemory().size(); if (bufferSize < outputSize) { ALOGW("ArmnnPreparedModel_1_3::Execute failed"); + outputShapes[i].isSufficient = false; return V1_3::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE; } - - outputs.emplace_back(i, outputTensor); - outputShapes[i] = ComputeShape(outputTensorInfo); } return V1_3::ErrorStatus::NONE; diff --git a/ConversionUtils.cpp b/ConversionUtils.cpp index 4c773964..b03ffbda 100644 --- a/ConversionUtils.cpp +++ b/ConversionUtils.cpp @@ -38,6 +38,15 @@ void LayerInputHandle::Connect(armnn::IInputSlot& inputSlot) } } +void LayerInputHandle::Disconnect(armnn::IInputSlot& inputSlot) +{ + ARMNN_ASSERT(IsValid()); + if (m_OutputSlot) + { + m_OutputSlot->Disconnect(inputSlot); + } +} + const armnn::TensorInfo& LayerInputHandle::GetTensorInfo() const { return m_TensorInfo; diff --git a/ConversionUtils.hpp b/ConversionUtils.hpp index 5dc9993d..474d1a58 100644 --- a/ConversionUtils.hpp +++ b/ConversionUtils.hpp @@ -62,6 +62,8 @@ public: void Connect(armnn::IInputSlot& inputSlot); + void Disconnect(armnn::IInputSlot& inputSlot); + const armnn::TensorInfo& GetTensorInfo() const; private: @@ -1380,7 +1382,8 @@ bool SetupAndTrackLayerOutputSlot(const HalOperation& operation, uint32_t layerOutputIndex, const HalModel& model, ConversionData& data, - const armnn::TensorInfo* overrideOutputInfo = nullptr) + const armnn::TensorInfo* overrideOutputInfo = nullptr, + const std::function & validateFunc = nullptr) { using HalOperand = typename HalPolicy::Operand; @@ -1392,18 +1395,39 @@ bool SetupAndTrackLayerOutputSlot(const HalOperation& operation, armnn::IOutputSlot& outputSlot = layer.GetOutputSlot(layerOutputIndex); - const uint32_t operandIndex = operation.outputs[operationOutputIndex]; - data.m_OutputSlotForOperand[operandIndex] = &outputSlot; - - if (overrideOutputInfo == nullptr) + bool isSupported = false; + if (validateFunc && + layer.GetInputSlot(0).GetConnection() && + IsDynamicTensor(outputSlot.GetTensorInfo())) { - outputSlot.SetTensorInfo(GetTensorInfoForOperand(*outputOperand)); + outputSlot.IsTensorInfoSet(); + validateFunc(outputSlot.GetTensorInfo(), isSupported); + + if(!isSupported) + { + for (unsigned int inputSlotIndex = 0; inputSlotIndex < layer.GetNumInputSlots(); ++inputSlotIndex) + { + layer.GetInputSlot(inputSlotIndex).GetConnection()->Disconnect(layer.GetInputSlot(inputSlotIndex)); + } + + return false; + } } else { - outputSlot.SetTensorInfo(*overrideOutputInfo); + if (overrideOutputInfo == nullptr) + { + outputSlot.SetTensorInfo(GetTensorInfoForOperand(*outputOperand)); + } + else + { + outputSlot.SetTensorInfo(*overrideOutputInfo); + } } + const uint32_t operandIndex = operation.outputs[operationOutputIndex]; + data.m_OutputSlotForOperand[operandIndex] = &outputSlot; + return true; } @@ -1452,7 +1476,8 @@ bool SetupAndTrackLayerOutputSlot(const HalOperation& operation, armnn::IConnectableLayer& layer, const HalModel& model, ConversionData& data, - const armnn::TensorInfo* overrideOutputInfo = nullptr) + const armnn::TensorInfo* overrideOutputInfo = nullptr, + const std::function & validateFunc = nullptr) { return SetupAndTrackLayerOutputSlot(operation, outputIndex, @@ -1460,7 +1485,8 @@ bool SetupAndTrackLayerOutputSlot(const HalOperation& operation, outputIndex, model, data, - overrideOutputInfo); + overrideOutputInfo, + validateFunc); } templateGetInputSlot(0)); - return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data); + return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc); } templateGetInputSlot(0)); - return SetupAndTrackLayerOutputSlot(operation, 0, *endLayer, model, data); + if (!isSupported) + { + return false; + } + + return SetupAndTrackLayerOutputSlot(operation, 0, *endLayer, model, data, nullptr, validateFunc); } templateGetInputSlot(0)); - return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data); + return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc); } template::Convert() totalPoolSize += pool.size(); } + using NetworkOptions = std::vector; + NetworkOptions networkOptions; + armnn::BackendOptions shapeInferenceMethodOption("ShapeInferenceMethod", + { + { "InferAndValidate", true } + }); + + networkOptions.push_back(shapeInferenceMethodOption); + // Create armnn::INetwork - m_Data.m_Network = armnn::INetwork::Create(); + m_Data.m_Network = armnn::INetwork::Create(networkOptions); // add operations to it // track which layer outputs each operand diff --git a/Utils.cpp b/Utils.cpp index 8a2812ad..db1b6e68 100644 --- a/Utils.cpp +++ b/Utils.cpp @@ -80,7 +80,8 @@ void* GetMemoryFromPool(DataLocation location, const std::vector(operand.type); } - armnn::TensorInfo ret(operand.dimensions.size(), operand.dimensions.data(), type); + TensorInfo ret; + 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); + } ret.SetQuantizationScale(operand.scale); ret.SetQuantizationOffset(operand.zeroPoint); @@ -143,7 +167,31 @@ armnn::TensorInfo GetTensorInfoForOperand(const V1_2::Operand& operand) throw UnsupportedOperand(operand.type); } - TensorInfo ret(operand.dimensions.size(), operand.dimensions.data(), type); + TensorInfo ret; + 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 @@ -219,7 +267,29 @@ armnn::TensorInfo GetTensorInfoForOperand(const V1_3::Operand& operand) } else { - ret = TensorInfo(operand.dimensions.size(), operand.dimensions.data(), type); + 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) @@ -501,10 +571,22 @@ std::string ExportNetworkGraphToDotFile(const armnn::IOptimizedNetwork& optimize return fileName; } -bool IsDynamicTensor(const armnn::TensorInfo& outputInfo) +bool IsDynamicTensor(const armnn::TensorInfo& tensorInfo) +{ + if (tensorInfo.GetShape().GetDimensionality() == armnn::Dimensionality::NotSpecified) + { + return true; + } + return !tensorInfo.GetShape().AreAllDimensionsSpecified(); +} + +bool AreDynamicTensorsSupported() { - // Dynamic tensors have at least one 0-sized dimension - return outputInfo.GetNumElements() == 0u; +#if defined(ARMNN_ANDROID_NN_V1_3) + return true; +#else + return false; +#endif } std::string GetFileTimestamp() @@ -568,7 +650,4 @@ void CommitPools(std::vector<::android::nn::RunTimePoolInfo>& memPools) #endif } } - - - } // namespace armnn_driver diff --git a/Utils.hpp b/Utils.hpp index d58d2735..86eb6db4 100644 --- a/Utils.hpp +++ b/Utils.hpp @@ -143,6 +143,9 @@ void RenameGraphDotFile(const std::string& oldName, const std::string& dumpDir, /// Checks if a tensor info represents a dynamic tensor bool IsDynamicTensor(const armnn::TensorInfo& outputInfo); +/// Checks for ArmNN support of dynamic tensors. +bool AreDynamicTensorsSupported(void); + std::string GetFileTimestamp(); #if defined(ARMNN_ANDROID_NN_V1_2) || defined(ARMNN_ANDROID_NN_V1_3) -- cgit v1.2.1