// // Copyright © 2017 Arm Ltd and Contributors. All rights reserved. // SPDX-License-Identifier: MIT // #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace armnn; namespace { void CreateLSTMLayerHelper(Graph &graph, bool CifgEnabled) { LstmDescriptor layerDesc; layerDesc.m_ActivationFunc = 4; layerDesc.m_ClippingThresCell = 0.2f; layerDesc.m_ClippingThresProj = 0.4f; layerDesc.m_CifgEnabled = CifgEnabled; layerDesc.m_PeepholeEnabled = false; layerDesc.m_ProjectionEnabled = false; LstmLayer* const layer = graph.AddLayer(layerDesc, "layer"); unsigned int batchSize = 3; unsigned int inputSize = 2; unsigned int numUnits = 4; unsigned int outputSize = 4; layer->m_BasicParameters.m_InputToForgetWeights = std::make_unique (TensorInfo({ numUnits, inputSize }, DataType::Float32)); layer->m_BasicParameters.m_InputToCellWeights = std::make_unique (TensorInfo({ numUnits, inputSize }, DataType::Float32)); layer->m_BasicParameters.m_InputToOutputWeights = std::make_unique (TensorInfo({ numUnits, inputSize }, DataType::Float32)); layer->m_BasicParameters.m_RecurrentToForgetWeights = std::make_unique (TensorInfo({ numUnits, outputSize }, DataType::Float32)); layer->m_BasicParameters.m_RecurrentToCellWeights = std::make_unique (TensorInfo({ numUnits, outputSize }, DataType::Float32)); layer->m_BasicParameters.m_RecurrentToOutputWeights = std::make_unique (TensorInfo({ numUnits, outputSize }, DataType::Float32)); layer->m_BasicParameters.m_ForgetGateBias = std::make_unique (TensorInfo({ numUnits }, DataType::Float32)); layer->m_BasicParameters.m_CellBias = std::make_unique (TensorInfo({ numUnits }, DataType::Float32)); layer->m_BasicParameters.m_OutputGateBias = std::make_unique (TensorInfo({ numUnits }, DataType::Float32)); layer->m_BasicParameters.m_InputToForgetWeights->Allocate(); layer->m_BasicParameters.m_InputToCellWeights->Allocate(); layer->m_BasicParameters.m_InputToOutputWeights->Allocate(); layer->m_BasicParameters.m_RecurrentToForgetWeights->Allocate(); layer->m_BasicParameters.m_RecurrentToCellWeights->Allocate(); layer->m_BasicParameters.m_RecurrentToOutputWeights->Allocate(); layer->m_BasicParameters.m_ForgetGateBias->Allocate(); layer->m_BasicParameters.m_CellBias->Allocate(); layer->m_BasicParameters.m_OutputGateBias->Allocate(); if (!layerDesc.m_CifgEnabled) { layer->m_CifgParameters.m_InputToInputWeights = std::make_unique (TensorInfo({ numUnits, inputSize }, DataType::Float32)); layer->m_CifgParameters.m_RecurrentToInputWeights = std::make_unique (TensorInfo({ numUnits, outputSize }, DataType::Float32)); layer->m_CifgParameters.m_InputGateBias = std::make_unique (TensorInfo({ numUnits }, DataType::Float32)); layer->m_CifgParameters.m_InputToInputWeights->Allocate(); layer->m_CifgParameters.m_RecurrentToInputWeights->Allocate(); layer->m_CifgParameters.m_InputGateBias->Allocate(); } if (layerDesc.m_ProjectionEnabled) { layer->m_ProjectionParameters.m_ProjectionWeights = std::make_unique (TensorInfo({ outputSize, numUnits }, DataType::Float32)); layer->m_ProjectionParameters.m_ProjectionBias = std::make_unique (TensorInfo({ outputSize }, DataType::Float32)); layer->m_ProjectionParameters.m_ProjectionWeights->Allocate(); layer->m_ProjectionParameters.m_ProjectionBias->Allocate(); } if (layerDesc.m_PeepholeEnabled) { if (!layerDesc.m_CifgEnabled) { layer->m_PeepholeParameters.m_CellToInputWeights = std::make_unique (TensorInfo({ numUnits }, DataType::Float32)); layer->m_PeepholeParameters.m_CellToInputWeights->Allocate(); } layer->m_PeepholeParameters.m_CellToForgetWeights = std::make_unique (TensorInfo({ numUnits }, DataType::Float32)); layer->m_PeepholeParameters.m_CellToOutputWeights = std::make_unique (TensorInfo({ numUnits }, DataType::Float32)); layer->m_PeepholeParameters.m_CellToForgetWeights->Allocate(); layer->m_PeepholeParameters.m_CellToOutputWeights->Allocate(); } // create input and output layers Layer* const input = graph.AddLayer(0, "input"); Layer* const outputStateIn = graph.AddLayer(1, "outputStateIn"); Layer* const cellStateIn = graph.AddLayer(2, "cellStateIn"); Layer* const scratchBuffer = graph.AddLayer(0, "scratchBuffer"); Layer* const outputStateOut = graph.AddLayer(1, "outputStateOut"); Layer* const cellStateOut = graph.AddLayer(2, "cellStateOut"); Layer* const output = graph.AddLayer(3, "output"); // connect up armnn::TensorInfo lstmTensorInfo1({ batchSize, inputSize }, DataType::Float32); armnn::TensorInfo lstmTensorInfo2({ batchSize, numUnits}, DataType::Float32); armnn::TensorInfo lstmTensorInfo3({ batchSize, outputSize }, DataType::Float32); armnn::TensorInfo lstmTensorInfoScratchBuff({ batchSize, numUnits * (layerDesc.m_CifgEnabled ? 3 : 4) }, DataType::Float32); Connect(input, layer, lstmTensorInfo1, 0, 0); Connect(cellStateIn, layer, lstmTensorInfo2, 0, 1); Connect(outputStateIn, layer, lstmTensorInfo3, 0, 2); Connect(layer, scratchBuffer, lstmTensorInfoScratchBuff, 0, 0); Connect(layer, outputStateOut, lstmTensorInfo3, 1, 0); Connect(layer, cellStateOut, lstmTensorInfo2, 2, 0); Connect(layer, output, lstmTensorInfo3, 3, 0); } class MockLayerSupport : public LayerSupportBase { public: bool IsLayerSupported(const LayerType& type, const std::vector& infos, const BaseDescriptor& descriptor, const Optional& /*lstmParamsInfo*/, const Optional& /*quantizedLstmParamsInfo*/, Optional reasonIfUnsupported) const override { switch (type) { case LayerType::Input: return IsInputSupported(infos[0], reasonIfUnsupported); case LayerType::Output: return IsOutputSupported(infos[0], reasonIfUnsupported); case LayerType::Activation: return IsActivationSupported(infos[0], infos[1], *(PolymorphicDowncast(&descriptor)), reasonIfUnsupported); default: return false; } } bool IsInputSupported(const TensorInfo& /*input*/, Optional /*reasonIfUnsupported = EmptyOptional()*/) const override { return true; } bool IsOutputSupported(const TensorInfo& /*input*/, Optional /*reasonIfUnsupported = EmptyOptional()*/) const override { return true; } bool IsActivationSupported(const TensorInfo& /*input0*/, const TensorInfo& /*output*/, const ActivationDescriptor& /*descriptor*/, Optional /*reasonIfUnsupported = EmptyOptional()*/) const override { return true; } }; template class CustomAllocatorBackend : public IBackendInternal { public: CustomAllocatorBackend() : m_BackendCapabilities(NamePolicy::GetIdStatic(), {{"NullCapability", false}}), m_CustomAllocator(false) {}; CustomAllocatorBackend(const BackendCapabilities& capabilities) : m_BackendCapabilities(capabilities), m_CustomAllocator(false) {}; ~CustomAllocatorBackend() = default; static const BackendId& GetIdStatic() { return NamePolicy::GetIdStatic(); } const BackendId& GetId() const override { return GetIdStatic(); } IBackendInternal::IMemoryManagerUniquePtr CreateMemoryManager() const override { return nullptr; }; IBackendInternal::IWorkloadFactoryPtr CreateWorkloadFactory(const IBackendInternal::IMemoryManagerSharedPtr&) const override { return nullptr; } IBackendInternal::IBackendContextPtr CreateBackendContext(const IRuntime::CreationOptions&) const override { return nullptr; } IBackendInternal::ILayerSupportSharedPtr GetLayerSupport() const override { return std::make_shared(); } OptimizationViews OptimizeSubgraphView(const SubgraphView&) const override { return {}; }; BackendCapabilities GetCapabilities() const override { return m_BackendCapabilities; }; virtual bool UseCustomMemoryAllocator(std::shared_ptr allocator, armnn::Optional errMsg) override { IgnoreUnused(errMsg, allocator); m_CustomAllocator = true; return m_CustomAllocator; } BackendCapabilities m_BackendCapabilities; bool m_CustomAllocator; }; template class NoProtectedModeMockBackend : public IBackendInternal { public: NoProtectedModeMockBackend() : m_BackendCapabilities(NamePolicy::GetIdStatic(), {{"NullCapability", false}}) {}; NoProtectedModeMockBackend(const BackendCapabilities& capabilities) : m_BackendCapabilities(capabilities) {}; ~NoProtectedModeMockBackend() = default; static const BackendId& GetIdStatic() { return NamePolicy::GetIdStatic(); } const BackendId& GetId() const override { return GetIdStatic(); } IBackendInternal::IMemoryManagerUniquePtr CreateMemoryManager() const override { return nullptr; }; IBackendInternal::IWorkloadFactoryPtr CreateWorkloadFactory(const IBackendInternal::IMemoryManagerSharedPtr&) const override { return nullptr; } IBackendInternal::IBackendContextPtr CreateBackendContext(const IRuntime::CreationOptions&) const override { return nullptr; } IBackendInternal::ILayerSupportSharedPtr GetLayerSupport() const override { return std::make_shared(); } OptimizationViews OptimizeSubgraphView(const SubgraphView&) const override { return {}; }; BackendCapabilities GetCapabilities() const override { return m_BackendCapabilities; }; BackendCapabilities m_BackendCapabilities; }; } // namespace TEST_SUITE("Optimizer") { using namespace armnn::optimizations; TEST_CASE("LSTMValidateTensorShapesFromInputsCIFGDisabledTest") { Graph graph; //Helper function creates graph containing LSTM layer with required input and output layers CreateLSTMLayerHelper(graph, false); //This function used to call ValidateShapesFromInputs(); CHECK_NOTHROW(graph.InferTensorInfos()); } TEST_CASE("LSTMValidateTensorShapesFromInputsCIFGEnabledTest") { Graph graph; //Helper function creates graph containing LSTM layer with required input and output layers CreateLSTMLayerHelper(graph, true); //This function used to call ValidateShapesFromInputs(); CHECK_NOTHROW(graph.InferTensorInfos()); } TEST_CASE("InsertConvertersTest") { const armnn::TensorInfo info({ 1, 5, 2, 3 }, armnn::DataType::Float16); armnn::Graph graph; armnn::LayerBindingId inputId = 0; armnn::Layer* head = graph.AddLayer(0, "output"); head = graph.InsertNewLayer(head->GetInputSlot(0), ""); head->GetOutputHandler().SetTensorInfo(info); graph.InsertNewLayer(head->GetInputSlot(1), inputId++, "") ->GetOutputHandler().SetTensorInfo(info); head = graph.InsertNewLayer(head->GetInputSlot(0), ""); head->GetOutputHandler().SetTensorInfo(info); head = graph.InsertNewLayer(head->GetInputSlot(0), ""); head->GetOutputHandler().SetTensorInfo(info); graph.InsertNewLayer(head->GetInputSlot(0), inputId++, "") ->GetOutputHandler().SetTensorInfo(info); // Check graph layer sequence before inserting convert layers CHECK(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType)); // Check layers have Float16 DataType for (auto& layer : graph) { if(layer->GetType()==LayerType::Floor || layer->GetType() == LayerType::Addition) { ARMNN_ASSERT(layer->GetOutputSlot(0).GetTensorInfo().GetDataType() == DataType::Float16); ARMNN_ASSERT(layer->GetDataType() == DataType::Float16); } } // Insert convert layers either side of unsupported layer for (auto& layer : graph) { if(layer->GetType()==LayerType::Floor || layer->GetType() == LayerType::Addition) { InsertConvertFp16ToFp32LayersBefore(graph, *layer); InsertConvertFp32ToFp16LayersAfter(graph, *layer); } } // Check layers have correct DataType after inserting convert layers for (auto& layer : graph) { if (layer->GetType()==LayerType::Floor || layer->GetType() == LayerType::Addition) { ARMNN_ASSERT(layer->GetOutputSlot(0).GetTensorInfo().GetDataType() == DataType::Float32); ARMNN_ASSERT(layer->GetDataType() == DataType::Float32); } else if (layer->GetType() == LayerType::ConvertFp16ToFp32) { ARMNN_ASSERT(layer->GetOutputSlot(0).GetTensorInfo().GetDataType() == DataType::Float32); ARMNN_ASSERT(layer->GetDataType() == DataType::Float16); } else if (layer->GetType() == LayerType::ConvertFp32ToFp16) { ARMNN_ASSERT(layer->GetOutputSlot(0).GetTensorInfo().GetDataType() == DataType::Float16); ARMNN_ASSERT(layer->GetDataType() == DataType::Float32); } } // Check sequence of layers after inserting convert layers CHECK(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType)); } void CreateConvolution2dGraph(Graph &graph, const unsigned int* inputShape, const unsigned int* weightsShape, const unsigned int* outputShape, DataLayout dataLayout = DataLayout::NCHW) { armnn::TensorInfo inputInfo(4, inputShape, DataType::Float32); armnn::TensorInfo outputInfo(4, outputShape, DataType::Float32); std::vector weightsVector(90); armnn::ConstTensor weights( armnn::TensorInfo(4, weightsShape, armnn::DataType::Float32, 0.0f, 0, true), weightsVector); Convolution2dDescriptor desc; desc.m_BiasEnabled = false; desc.m_StrideX = 1; desc.m_StrideY = 1; desc.m_DataLayout = dataLayout; Layer* input = graph.AddLayer(0, "input"); input->GetOutputSlot().SetTensorInfo(inputInfo); Convolution2dLayer* layer = graph.AddLayer(desc, "conv2d"); layer->m_Weight = std::make_unique(weights); layer->GetOutputSlot().SetTensorInfo(outputInfo); Layer* output = graph.AddLayer(0, "output"); input->GetOutputSlot().Connect(layer->GetInputSlot(0)); layer->GetOutputSlot().Connect(output->GetInputSlot(0)); } TEST_CASE("Conv2dValidateTensorShapesFromInputs") { Graph graph; const unsigned int inputShape[] = { 1, 3, 8, 16 }; const unsigned int weightsShape[] = { 2, 3, 5, 3 }; const unsigned int outputShape[] = { 1, 2, 4, 14 }; CreateConvolution2dGraph(graph, inputShape, weightsShape, outputShape); CHECK_NOTHROW(graph.InferTensorInfos()); } TEST_CASE("Conv2dValidateTensorShapesFromInputsNhwc") { Graph graph; const unsigned int inputShape[] = { 1, 8, 16, 3 }; const unsigned int weightsShape[] = { 2, 5, 3, 3 }; const unsigned int outputShape[] = { 1, 4, 14, 2 }; CreateConvolution2dGraph(graph, inputShape, weightsShape, outputShape, DataLayout::NHWC); CHECK_NOTHROW(graph.InferTensorInfos()); } void CreateDepthwiseConvolution2dGraph(Graph &graph, const unsigned int* inputShape, const unsigned int* weightsShape, const unsigned int* outputShape, DataLayout dataLayout = DataLayout::NCHW) { armnn::TensorInfo inputInfo(4, inputShape, DataType::Float32); armnn::TensorInfo outputInfo(4, outputShape, DataType::Float32); std::vector weightsVector(18); armnn::ConstTensor weights( armnn::TensorInfo(4, weightsShape, armnn::DataType::Float32, 0.0f, 0, true), weightsVector); DepthwiseConvolution2dDescriptor desc; desc.m_BiasEnabled = false; desc.m_StrideX = 1; desc.m_StrideY = 1; desc.m_DataLayout = dataLayout; Layer* input = graph.AddLayer(0, "input"); input->GetOutputSlot().SetTensorInfo(inputInfo); DepthwiseConvolution2dLayer* layer = graph.AddLayer(desc, "depthwiseConv2d"); layer->m_Weight = std::make_unique(weights); layer->GetOutputSlot().SetTensorInfo(outputInfo); Layer* output = graph.AddLayer(0, "output"); input->GetOutputSlot().Connect(layer->GetInputSlot(0)); layer->GetOutputSlot().Connect(output->GetInputSlot(0)); } TEST_CASE("DepthwiseConv2dValidateTensorShapesFromInputs") { Graph graph; const unsigned int inputShape[] = { 1, 2, 3, 3 }; const unsigned int weightsShape[] = { 1, 3, 3, 2 }; const unsigned int outputShape[] = { 1, 2, 1, 1 }; CreateDepthwiseConvolution2dGraph(graph, inputShape, weightsShape, outputShape); CHECK_NOTHROW(graph.InferTensorInfos()); } TEST_CASE("DepthwiseConv2dValidateTensorShapesFromInputsNhwc") { Graph graph; const unsigned int inputShape[] = { 1, 3, 3, 2 }; const unsigned int weightsShape[] = { 1, 3, 3, 2 }; const unsigned int outputShape[] = { 1, 1, 1, 2 }; CreateDepthwiseConvolution2dGraph(graph, inputShape, weightsShape, outputShape, DataLayout::NHWC); CHECK_NOTHROW(graph.InferTensorInfos()); } void CreatePooling2dGraph(Graph& graph, const unsigned int* inputShape, const unsigned int* outputShape, DataLayout dataLayout = DataLayout::NCHW) { armnn::TensorInfo inputInfo(4, inputShape, DataType::Float32); armnn::TensorInfo outputInfo(4, outputShape, DataType::Float32); Pooling2dDescriptor desc; desc.m_PoolType = armnn::PoolingAlgorithm::Average; desc.m_PoolWidth = desc.m_PoolHeight = 100; desc.m_StrideX = desc.m_StrideY = 5; desc.m_PadLeft = 50; desc.m_PadRight = 50; desc.m_PadTop = 50; desc.m_PadBottom = 50; desc.m_PaddingMethod = armnn::PaddingMethod::Exclude; desc.m_DataLayout = dataLayout; Layer* input = graph.AddLayer(0, "input"); input->GetOutputSlot().SetTensorInfo(inputInfo); Pooling2dLayer* layer = graph.AddLayer(desc, "pooling2d"); layer->GetOutputSlot().SetTensorInfo(outputInfo); Layer* output = graph.AddLayer(0, "output"); input->GetOutputSlot().Connect(layer->GetInputSlot(0)); layer->GetOutputSlot().Connect(output->GetInputSlot(0)); } TEST_CASE("Pooling2dValidateTensorShapesFromInputs") { Graph graph; const unsigned int inputShape[] = { 5, 3, 52, 60 }; const unsigned int outputShape[] = { 5, 3, 11, 13 }; CreatePooling2dGraph(graph, inputShape, outputShape, DataLayout::NCHW); CHECK_NOTHROW(graph.InferTensorInfos()); } TEST_CASE("Pooling2dValidateTensorShapesFromInputsNhwc") { Graph graph; const unsigned int inputShape[] = { 5, 52, 60, 3 }; const unsigned int outputShape[] = { 5, 11, 13, 3 }; CreatePooling2dGraph(graph, inputShape, outputShape, DataLayout::NHWC); CHECK_NOTHROW(graph.InferTensorInfos()); } void CreateResizeBilinearGraph(Graph& graph, const unsigned int* inputShape, const unsigned int* outputShape, DataLayout dataLayout = DataLayout::NCHW) { TensorInfo inputInfo(4, inputShape, DataType::Float32); TensorInfo outputInfo(4, outputShape, DataType::Float32); ResizeDescriptor desc; desc.m_Method = ResizeMethod::Bilinear; desc.m_TargetHeight = 3; desc.m_TargetWidth = 4; desc.m_DataLayout = dataLayout; Layer* input = graph.AddLayer(0, "input"); input->GetOutputSlot().SetTensorInfo(inputInfo); ResizeLayer* layer = graph.AddLayer(desc, "resizeBilinear"); layer->GetOutputSlot().SetTensorInfo(outputInfo); Layer* output = graph.AddLayer(0, "output"); input->GetOutputSlot().Connect(layer->GetInputSlot(0)); layer->GetOutputSlot().Connect(output->GetInputSlot(0)); } TEST_CASE("ResizeBilinearValidateTensorShapesFromInputs") { Graph graph; const unsigned int inputShape[] = { 1, 2, 4, 5 }; const unsigned int outputShape[] = { 1, 2, 3, 4 }; CreateResizeBilinearGraph(graph, inputShape, outputShape); CHECK_NOTHROW(graph.InferTensorInfos()); } TEST_CASE("ResizeBilinearValidateTensorShapesFromInputsNhwc") { Graph graph; const unsigned int inputShape[] = { 1, 4, 5, 2 }; const unsigned int outputShape[] = { 1, 3, 4, 2 }; CreateResizeBilinearGraph(graph, inputShape, outputShape, DataLayout::NHWC); CHECK_NOTHROW(graph.InferTensorInfos()); } void CreateGatherGraph(Graph& graph, const armnn::TensorInfo& paramsInfo, const armnn::TensorInfo& indicesInfo, const armnn::TensorInfo& outputInfo) { Layer* input0 = graph.AddLayer(0, "params"); input0->GetOutputSlot().SetTensorInfo(paramsInfo); Layer* input1 = graph.AddLayer(1, "indices"); input1->GetOutputSlot().SetTensorInfo(indicesInfo); GatherDescriptor descriptor; GatherLayer* layer = graph.AddLayer(descriptor, "gather"); layer->GetOutputSlot().SetTensorInfo(outputInfo); Layer* output = graph.AddLayer(0, "output"); input0->GetOutputSlot().Connect(layer->GetInputSlot(0)); input1->GetOutputSlot().Connect(layer->GetInputSlot(1)); layer->GetOutputSlot().Connect(output->GetInputSlot(0)); } TEST_CASE("GatherValidateTensorShapesFromInputs") { Graph graph; armnn::TensorInfo paramsInfo({10, 5}, DataType::Float32); armnn::TensorInfo indicesInfo({3}, DataType::Signed32); armnn::TensorInfo outputInfo({3, 5}, DataType::Float32); CreateGatherGraph(graph, paramsInfo, indicesInfo, outputInfo); CHECK_NOTHROW(graph.InferTensorInfos()); } TEST_CASE("GatherValidateTensorShapesFromInputs1DParams") { Graph graph; armnn::TensorInfo paramsInfo({8}, DataType::Float32); armnn::TensorInfo indicesInfo({5}, DataType::Signed32); armnn::TensorInfo outputInfo( {5}, DataType::Float32); CreateGatherGraph(graph, paramsInfo, indicesInfo, outputInfo); CHECK_NOTHROW(graph.InferTensorInfos()); } TEST_CASE("GatherValidateTensorShapesFromInputsMultiDimIndices") { Graph graph; armnn::TensorInfo paramsInfo({3, 2, 5}, DataType::Float32); armnn::TensorInfo indicesInfo({2, 2}, DataType::Signed32); armnn::TensorInfo outputInfo({2, 2, 2, 5}, DataType::Float32); CreateGatherGraph(graph, paramsInfo, indicesInfo, outputInfo); CHECK_NOTHROW(graph.InferTensorInfos()); } TEST_CASE("DetectionPostProcessValidateTensorShapes") { Graph graph; armnn::TensorInfo boxEncodingsInfo({1, 10, 4}, DataType::QAsymmU8); armnn::TensorInfo scoresInfo({1, 10, 4}, DataType::QAsymmU8); std::vector anchorsVector(40); armnn::ConstTensor anchors(armnn::TensorInfo({10, 4}, armnn::DataType::QAsymmU8, 0.0f, 0, true), anchorsVector); armnn::TensorInfo detectionBoxesInfo({1, 3, 4}, DataType::QAsymmU8); armnn::TensorInfo detectionScoresInfo({1, 3}, DataType::QAsymmU8); armnn::TensorInfo detectionClassesInfo({1, 3}, DataType::QAsymmU8); armnn::TensorInfo numDetectionInfo({1}, DataType::QAsymmU8); Layer* input0 = graph.AddLayer(0, "boxEncodings"); input0->GetOutputSlot().SetTensorInfo(boxEncodingsInfo); Layer* input1 = graph.AddLayer(1, "score"); input1->GetOutputSlot().SetTensorInfo(scoresInfo); DetectionPostProcessDescriptor descriptor; descriptor.m_MaxDetections = 3; DetectionPostProcessLayer* layer = graph.AddLayer(descriptor, "detectionPostProcess"); layer->m_Anchors = std::make_unique(anchors); layer->GetOutputSlot(0).SetTensorInfo(detectionBoxesInfo); layer->GetOutputSlot(1).SetTensorInfo(detectionScoresInfo); layer->GetOutputSlot(2).SetTensorInfo(detectionClassesInfo); layer->GetOutputSlot(3).SetTensorInfo(numDetectionInfo); input0->GetOutputSlot().Connect(layer->GetInputSlot(0)); input1->GetOutputSlot().Connect(layer->GetInputSlot(1)); CHECK_NOTHROW(graph.InferTensorInfos()); } TEST_CASE("BackendCapabilityTest") { BackendId backendId = "MockBackend"; armnn::BackendOptions::BackendOption nonConstWeights{"NonConstWeights", true}; // MockBackend does not support the NonConstWeights capability CHECK(!armnn::HasCapability(nonConstWeights, backendId)); CHECK(!armnn::HasCapability("NonConstWeights", backendId)); // MockBackend does not support the AsyncExecution capability CHECK(!armnn::GetCapability("AsyncExecution", backendId).has_value()); } TEST_CASE("BackendHintTest") { class TestBackendAssignment : public StrategyBase { public: void ExecuteStrategy(const armnn::IConnectableLayer* layer, const armnn::BaseDescriptor& descriptor, const std::vector& constants, const char* name, const armnn::LayerBindingId id = 0) override { armnn::IgnoreUnused(descriptor, constants, id, name); switch (layer->GetType()) { case armnn::LayerType::Input: { auto inputLayer = PolymorphicDowncast(layer); const auto connectedLayerBackendId = inputLayer->GetOutputSlot(0).GetOwningLayer().GetBackendId(); CHECK((inputLayer->GetBackendId() == connectedLayerBackendId)); break; } case armnn::LayerType::Output: { auto outputLayer = PolymorphicDowncast(layer); CHECK((outputLayer->GetBackendId() == "MockBackend")); break; } case armnn::LayerType::Activation: { auto activation = PolymorphicDowncast(layer); CHECK((activation->GetBackendId() == "CustomBackend")); break; } default: { m_DefaultStrategy.Apply(GetLayerTypeAsCString(layer->GetType())); } } } }; struct CustomPolicy { static const BackendId& GetIdStatic() { static BackendId id = "CustomBackend"; return id; } }; struct MockPolicy { static const BackendId& GetIdStatic() { static BackendId id = "MockBackend"; return id; } }; auto& backendRegistry = BackendRegistryInstance(); backendRegistry.Register("MockBackend", []() { return std::make_unique>(); }); backendRegistry.Register("CustomBackend", []() { return std::make_unique>(); }); // Define the network auto network = INetwork::Create(); ActivationDescriptor desc; desc.m_Function = ActivationFunction::Linear; std::unique_ptr graph = std::make_unique(); auto input = graph->AddLayer(0, "input"); auto act = graph->AddLayer(desc, "activation"); auto output = graph->AddLayer(0, "output"); BackendId customBackendId("CustomBackend"); act->BackendSelectionHint(customBackendId); input->GetOutputSlot(0).Connect(act->GetInputSlot(0)); act->GetOutputSlot(0).Connect(output->GetInputSlot(0)); OptimizedNetworkImpl optNet(std::move(graph)); // Get the optimized graph Graph& optGraph = optNet.GetGraph(); std::vector prefs{ "MockBackend", "CustomBackend" }; BackendIdSet availableBackends = { "CustomBackend", "MockBackend" }; DeviceSpec spec(availableBackends); BackendSettings backendSettings(prefs, spec); // Assign an available backend to each layer Graph::Iterator firstLayer = optGraph.begin(); Graph::Iterator lastLayer = optGraph.end(); OptimizedNetworkImpl* optNetObjPtr = &optNet; OptimizationResult res = AssignBackends(optNetObjPtr, backendSettings, firstLayer, lastLayer, EmptyOptional()); CHECK(res.IsOk()); TestBackendAssignment visitor; for (auto it = firstLayer; it != lastLayer; ++it) { (*it)->ExecuteStrategy(visitor); } // Clean up the registry for the next test. backendRegistry.Deregister("MockBackend"); backendRegistry.Deregister("CustomBackend"); } // Tests that OptimizeForExclusiveConnections works, fusing when needed, using BatchNorm fusing as example TEST_CASE("OptimizeForExclusiveConnectionsFuseTest") { using namespace armnn; // Define layers information Convolution2dDescriptor convolution2dDescriptor; convolution2dDescriptor.m_BiasEnabled = false; convolution2dDescriptor.m_DataLayout = DataLayout::NHWC; BatchNormalizationDescriptor batchNormDescriptor; batchNormDescriptor.m_DataLayout = DataLayout::NHWC; const unsigned int inputDimensionSizes[] = { 1, 4, 4, 3 }; // NHWCin const unsigned int weightsDimensionSizes[] = { 1, 2, 2, 3 }; // CoutHWCin const unsigned int outputDimensionSizes[] = { 1, 3, 3, 1 }; // NHWCout const unsigned int outputChannelSize[] = { outputDimensionSizes[3] }; // Cout TensorInfo inputInfo(4, inputDimensionSizes, DataType::Float32); TensorInfo outputInfo(4, outputDimensionSizes, DataType::Float32); std::vector weightsVector = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; ConstTensor weights(TensorInfo(4, weightsDimensionSizes, DataType::Float32, 0.0f, 0, true), weightsVector); std::vector betaVector = { 0.1f }; std::vector gammaVector = { 0.5f }; std::vector meanVector = { 0 }; std::vector varianceVector = { 1 }; ConstTensor beta(TensorInfo(1, outputChannelSize, DataType::Float32, 0.0f, 0, true), betaVector); ConstTensor gamma(TensorInfo(1, outputChannelSize, DataType::Float32, 0.0f, 0, true), gammaVector); ConstTensor mean(TensorInfo(1, outputChannelSize, DataType::Float32, 0.0f, 0, true), meanVector); ConstTensor variance(TensorInfo(1, outputChannelSize, DataType::Float32, 0.0f, 0, true), varianceVector); // Define the network Graph graph; auto input = graph.AddLayer(0, "input"); auto conv = graph.AddLayer(convolution2dDescriptor, "convolution"); auto batchNorm = graph.AddLayer(batchNormDescriptor, "batchNorm"); auto output = graph.AddLayer(0, "output"); // Set layer information input->GetOutputSlot().SetTensorInfo(inputInfo); conv->GetOutputSlot().SetTensorInfo(outputInfo); batchNorm->GetOutputSlot().SetTensorInfo(outputInfo); conv->m_Weight = std::make_unique(weights); batchNorm->m_Beta = std::make_unique(beta); batchNorm->m_Gamma = std::make_unique(gamma); batchNorm->m_Mean = std::make_unique(mean); batchNorm->m_Variance = std::make_unique(variance); if (convolution2dDescriptor.m_BiasEnabled) { std::vector biasVector = { 11 }; ConstTensor bias(TensorInfo(1, outputChannelSize, DataType::Float32, 0.0f, 0, true), biasVector); conv->m_Bias = std::make_unique(bias); } // Connect layers input->GetOutputSlot(0).Connect(conv->GetInputSlot(0)); conv->GetOutputSlot(0).Connect(batchNorm->GetInputSlot(0)); batchNorm->GetOutputSlot(0).Connect(output->GetInputSlot(0)); CHECK(4 == graph.GetNumLayers()); CHECK(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType)); // Optimize graph armnn::Optimizer::Pass(graph, MakeOptimizations(FuseBatchNormIntoConvolution2DFloat32())); auto checkFusedConv2d = [](const armnn::Layer* const layer) -> bool { return IsLayerOfType(layer) && (layer->GetNameStr() == "fused-batchNorm-into-convolution"); }; CHECK(3 == graph.GetNumLayers()); CHECK(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, checkFusedConv2d, &IsLayerOfType)); } // Tests that OptimizeForExclusiveConnections works, not fusing when not needed, using BatchNorm fusing as example TEST_CASE("OptimizeForExclusiveConnectionsWithoutFuseTest") { // Define the network Graph graph; Convolution2dDescriptor convolution2dDescriptor; BatchNormalizationDescriptor batchNormDescriptor; auto input = graph.AddLayer(0, "input"); auto conv = graph.AddLayer(convolution2dDescriptor, "convolution"); auto batchNorm = graph.AddLayer(batchNormDescriptor, "batchNorm"); auto output = graph.AddLayer(0, "output"); auto output2 = graph.AddLayer(1, "output2"); // Connect layers input->GetOutputSlot(0).Connect(conv->GetInputSlot(0)); conv->GetOutputSlot(0).Connect(batchNorm->GetInputSlot(0)); batchNorm->GetOutputSlot(0).Connect(output->GetInputSlot(0)); conv->GetOutputSlot(0).Connect(output2->GetInputSlot(0)); CHECK(5 == graph.GetNumLayers()); CHECK(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType)); // Optimize graph armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(FuseBatchNormIntoConvolution2DFloat32())); CHECK(5 == graph.GetNumLayers()); CHECK(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType)); } } // Optimizer TestSuite