// // Copyright © 2017 Arm Ltd. All rights reserved. // SPDX-License-Identifier: MIT // #pragma once #include "LayersFwd.hpp" #include "IGraphObservable.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace armnn { class SubGraph; class Graph { public: template static LayerType* PtrCast(Layer* const layer) { return boost::polymorphic_downcast(layer); } using LayerList = std::list; using Iterator = LayerList::const_iterator; // Const so pointers in the list can't be modified externally. using IteratorDifference = Iterator::difference_type; using ConstIterator = boost::transform_iterator), Iterator>; using ConstIteratorInputs = boost::transform_iterator), Iterator>; using ConstIteratorOutputs = boost::transform_iterator), Iterator>; /// Wrapper class returned by Graph::GetInputLayers() struct InputLayersAccessor { explicit InputLayersAccessor(const Graph& graph) : m_Graph(graph) {} ConstIteratorInputs begin() const { return { m_Graph.m_Layers.begin(), &(PtrCast) }; } ConstIteratorInputs end() const { return { std::next(m_Graph.m_Layers.begin(), static_cast(m_Graph.GetNumInputs())), &(PtrCast) }; } const Graph& m_Graph; }; /// Wrapper class returned by Graph::GetOutputLayers() struct OutputLayersAccessor { explicit OutputLayersAccessor(const Graph& graph) : m_Graph(graph) {} ConstIteratorOutputs begin() const { return { std::prev(m_Graph.m_Layers.end(), static_cast(m_Graph.GetNumOutputs())), &(PtrCast) }; } ConstIteratorOutputs end() const { return { m_Graph.m_Layers.end(), &(PtrCast) }; } const Graph& m_Graph; }; Graph() : m_LayersInOrder(true) {} Graph(const Graph& other); Graph& operator=(const Graph& other) = delete; ~Graph() { for (auto&& layer : m_Layers) { delete layer; } } Status Print() const; Status SerializeToDot(std::ostream& stream); /// Adds a new layer, of type LayerType, to the graph constructed with the arguments passed. template LayerT* AddLayer(Args&&... args); /// Inserts a new layer between the output slot currently connected to insertBefore /// and insertBefore itself. template LayerT* InsertNewLayer(InputSlot& insertBefore, Args&&... args); /// Inserts a new layer between insertAfter and the input slot(s) currently connected to it template LayerT* InsertNewLayer(OutputSlot& insertAfter, Args&&... args); /// Deletes the layer at the specified position and returns an iterator pointing /// to the next element after the one being deleted. Iterator EraseLayer(Iterator pos); /// Deletes the layer and returns an iterator pointing to the next layer in the graph /// (next in the list, after the one being deleted). Sets @a layer to nullptr on return. /// Templated to support pointers to any layer type. template Iterator EraseLayer(LayerT*& layer); /// Returns iterator pointing to the beginning of the list. Lowercase for range-based for loops. Iterator begin() { return m_Layers.begin(); } /// Returns iterator pointing to the end of the list. Lowercase for range-based for loops. Iterator end() { return m_Layers.end(); } /// Returns const iterator pointing to the beginning of the list. Lowercase for range-based for loops. ConstIterator begin() const { return {m_Layers.begin(), &(PtrCast)}; } /// Returns const iterator pointing to the end of the list. Lowercase for range-based for loops. ConstIterator end() const { return {m_Layers.end(), &(PtrCast)}; } /// Returns const iterator pointing to the beginning of the list. Lowercase for range-based for loops. ConstIterator cbegin() const { return begin(); } /// Returns const iterator pointing to the end of the list. Lowercase for range-based for loops. ConstIterator cend() const { return end(); } /// Sorts layers in topological order and return this. Graph& TopologicalSort() { const_cast(this)->TopologicalSort(); return *this; } const Graph& TopologicalSort() const; size_t GetNumInputs() const { return m_InputIds.size(); } size_t GetNumOutputs() const { return m_OutputIds.size(); } /// Returns a wrapper object with begin(), end() methods to iterate over the input layers /// in a range-based for loop. InputLayersAccessor GetInputLayers() const { return InputLayersAccessor(*this); } /// Returns a wrapper object with begin(), end() methods to iterate over the output layers /// in a range-based for loop. OutputLayersAccessor GetOutputLayers() const { return OutputLayersAccessor(*this); } size_t GetNumLayers() const { return m_Layers.size(); } /// Allocates memory for all tensors under output tensor handers of each layer. Status AllocateDynamicBuffers(); /// Modifies the graph in-place, removing edges connecting layers using different compute devices, /// and relinking them via an intermediary copy layers. void AddCopyLayers(); /// Substitutes the given sub-graph with either a new layer or a new sub-graph. /// In either case, the given layer or all the layers in the given sub-graph must belong to this graph. void SubstituteSubGraph(std::unique_ptr subGraph, IConnectableLayer* substituteLayer); void SubstituteSubGraph(std::unique_ptr subGraph, const SubGraph& substituteSubGraph); void InferTensorInfos(); void AttachObservable(IGraphObservable* const observable, GraphEvent notifyOnEvent) { m_Views[notifyOnEvent].emplace_back(observable); } void DetachObservable(IGraphObservable* const observable, GraphEvent notifyOnEvent) { m_Views[notifyOnEvent].remove(observable); } private: template class LayerInGraphBase; template class LayerInGraph; Iterator ForwardToEndOfInputs(Iterator it) const { while ((it != m_Layers.end()) && ((*it)->GetType() == LayerType::Input)) { ++it; } return it; } Iterator RewindToBeginOfOutputs(Iterator it) const { while ((it != m_Layers.begin()) && ((*std::prev(it))->GetType() == LayerType::Output)) { --it; } return it; } /// Gets the position of a layer in the graph. Iterator GetPosInGraph(Layer& layer); void NotifyObservables(GraphEvent event, Layer* graphState) { // Iterate over all observables observing this event for (auto& observable : m_Views[event]) { observable->Update(graphState); } } std::unordered_set m_InputIds; std::unordered_set m_OutputIds; std::unordered_map m_PosInGraphMap; void ReplaceSubGraphConnections(const SubGraph& subGraph, IConnectableLayer* substituteLayer); void ReplaceSubGraphConnections(const SubGraph& subGraph, const SubGraph& substituteSubGraph); void EraseSubGraphLayers(const SubGraph &subGraph); /// Mutable to allow sorting on const object. mutable LayerList m_Layers; mutable bool m_LayersInOrder; std::map> m_Views; }; /// Common base class for layers in the graph. template class Graph::LayerInGraphBase : public LayerT { protected: template LayerInGraphBase(Graph& graph, Iterator insertBefore, Args&&... args) : LayerT(std::forward(args)...), m_Graph(graph) { m_Graph.m_PosInGraphMap.emplace(this, m_Graph.m_Layers.emplace(insertBefore, this)); } ~LayerInGraphBase() { const size_t numErased = m_Graph.m_PosInGraphMap.erase(this); boost::ignore_unused(numErased); BOOST_ASSERT(numErased == 1); } Graph& m_Graph; }; /// Input/Output layers specialize this template. template class Graph::LayerInGraph final : public LayerInGraphBase { public: template LayerInGraph(Graph& graph, Args&&... args) : LayerInGraphBase(graph, // Insert at the back of the intermediate layers (before outputs). std::prev(graph.end(), IteratorDifference(graph.GetNumOutputs())), std::forward(args)...) { } template LayerInGraph(Graph& graph, Iterator insertBefore, Args&&... args) : LayerInGraphBase(graph, // Make sure it's inserted after all inputs and before all outputs. graph.ForwardToEndOfInputs(graph.RewindToBeginOfOutputs(insertBefore)), std::forward(args)...) { } }; /// Inputs add/remove their binding id to m_InputIds in the graph. template <> class Graph::LayerInGraph final : public LayerInGraphBase { public: template LayerInGraph(Graph& graph, Args&&... args) : LayerInGraphBase(graph, // Always add to the back of the inputs. std::next(graph.begin(), IteratorDifference(graph.GetNumInputs())), std::forward(args)...) { const bool isNewId = m_Graph.m_InputIds.emplace(GetBindingId()).second; if (!isNewId) { throw InvalidArgumentException("A layer already exists with the specified id"); } } template LayerInGraph(Graph& graph, Iterator, Args&&... args) // Ignore Iterator argument. Always add to the back of the inputs. : LayerInGraph(graph, std::forward(args)...) { } ~LayerInGraph() override { const size_t numErased = m_Graph.m_InputIds.erase(GetBindingId()); boost::ignore_unused(numErased); BOOST_ASSERT(numErased == 1); } }; /// Outputs add/remove their binding id to m_OutputIds in the graph. template <> class Graph::LayerInGraph final : public LayerInGraphBase { public: template LayerInGraph(Graph& graph, Args&&... args) : LayerInGraphBase(graph, // Always add to the back of the outputs. graph.end(), std::forward(args)...) { const bool isNewId = m_Graph.m_OutputIds.emplace(GetBindingId()).second; if (!isNewId) { throw InvalidArgumentException("A layer already exists with the specified id"); } } ~LayerInGraph() override { const size_t numErased = m_Graph.m_OutputIds.erase(GetBindingId()); boost::ignore_unused(numErased); BOOST_ASSERT(numErased == 1); } }; inline Graph::Iterator Graph::GetPosInGraph(Layer& layer) { auto it = m_PosInGraphMap.find(&layer); BOOST_ASSERT(it != m_PosInGraphMap.end()); return it->second; } template inline LayerT* Graph::AddLayer(Args&&... args) { m_LayersInOrder = m_LayersInOrder && ((LayerEnumOf() == LayerType::Input) || (LayerEnumOf() == LayerType::Output)); LayerT* const layer = new LayerInGraph(*this, std::forward(args)...); NotifyObservables(GraphEvent::LayerAdded, layer); return layer; } template inline LayerT* Graph::InsertNewLayer(InputSlot& insertBefore, Args&&... args) { // Insert after the parent if any, or before the child otherwise, so the topological order is kept. OutputSlot* parentOut = insertBefore.GetConnectedOutputSlot(); const Iterator pos = (parentOut != nullptr) ? std::next(GetPosInGraph(parentOut->GetOwningLayer())) : GetPosInGraph(insertBefore.GetOwningLayer()); LayerT* const layer = new LayerInGraph(*this, pos, std::forward(args)...); insertBefore.Insert(*layer); NotifyObservables(GraphEvent::LayerAdded, layer); return layer; } template inline LayerT* Graph::InsertNewLayer(OutputSlot& insertAfter, Args&&... args) { Layer& owningLayer = insertAfter.GetOwningLayer(); const Iterator pos = std::next(GetPosInGraph(owningLayer)); LayerT* const layer = new LayerInGraph(*this, pos, std::forward(args)...); BOOST_ASSERT(layer->GetNumInputSlots() == 1); insertAfter.MoveAllConnections(layer->GetOutputSlot()); insertAfter.Connect(layer->GetInputSlot(0)); NotifyObservables(GraphEvent::LayerAdded, layer); return layer; } inline Graph::Iterator Graph::EraseLayer(Iterator pos) { NotifyObservables(GraphEvent::LayerErased, *pos); delete *pos; return m_Layers.erase(pos); } template inline Graph::Iterator Graph::EraseLayer(LayerT*& layer) { BOOST_ASSERT(layer != nullptr); Iterator next = EraseLayer(GetPosInGraph(*layer)); layer = nullptr; return next; } } // namespace armnn