b4241d5b00ed4257067faa22359f32e35c6aee72
[platform/upstream/dldt.git] / inference-engine / src / legacy_api / include / graph_tools.hpp
1 // Copyright (C) 2018-2020 Intel Corporation
2 // SPDX-License-Identifier: Apache-2.0
3 //
4
5 #pragma once
6
7 #include <algorithm>
8 #include <functional>
9 #include <list>
10 #include <map>
11 #include <memory>
12 #include <queue>
13 #include <set>
14 #include <string>
15 #include <unordered_map>
16 #include <unordered_set>
17 #include <utility>
18 #include <vector>
19
20 #include "cnn_network_impl.hpp"
21 #include "ie_algorithm.hpp"
22 #include "ie_icnn_network.hpp"
23 #include "layer_transform.hpp"
24
25 IE_SUPPRESS_DEPRECATED_START
26
27 namespace InferenceEngine {
28 namespace details {
29
30 /**
31  * @brief Iterate over all layers followed by certain CNNLayer layer, and suitable to use ranged loops for output layers
32  */
33 class OutLayersIterator {
34     std::vector<DataPtr>::iterator dataCntIteratorCurrent;
35     std::vector<DataPtr>::iterator dataCntIteratorEnd;
36
37     using OutdataIterator = std::map<std::string, CNNLayerPtr>::iterator;
38     bool pointingToEnd = true;
39     OutdataIterator currentIterator;
40
41 public:
42     OutLayersIterator() = default;
43
44     static OutLayersIterator make_begin(std::vector<DataPtr>& origin) {
45         if (origin.empty()) {
46             return {};
47         }
48         OutLayersIterator it;
49
50         it.dataCntIteratorCurrent = origin.begin();
51         it.dataCntIteratorEnd = origin.end();
52         it.moveToNextNonEmptyData();
53
54         return it;
55     }
56
57     bool operator==(const OutLayersIterator& it) const {
58         if (pointingToEnd || it.pointingToEnd) {
59             return pointingToEnd && it.pointingToEnd;
60         }
61         return it.dataCntIteratorCurrent == dataCntIteratorCurrent && it.currentIterator == currentIterator;
62     }
63
64     bool operator!=(const OutLayersIterator& it) const {
65         return !this->operator==(it);
66     }
67
68     void operator++() {
69         if (dataCntIteratorCurrent == dataCntIteratorEnd) {
70             return;
71         }
72
73         if (pointingToEnd) {
74             return;
75         }
76         currentIterator++;
77         if (currentIterator != dataCntIteratorCurrent->get()->getInputTo().end()) {
78             return;
79         }
80
81         dataCntIteratorCurrent++;
82         moveToNextNonEmptyData();
83     }
84
85     CNNLayerPtr operator*() const {
86         return currentIterator->second;
87     }
88
89 protected:
90     void moveToNextNonEmptyData() {
91         pointingToEnd = true;
92         for (; dataCntIteratorCurrent != dataCntIteratorEnd; dataCntIteratorCurrent++) {
93             if (!dataCntIteratorCurrent->get()->getInputTo().empty()) {
94                 currentIterator = dataCntIteratorCurrent->get()->getInputTo().begin();
95                 pointingToEnd = false;
96                 break;
97             }
98         }
99     }
100 };
101
102 class OutInfoWrapper {
103     CNNLayer* origin = nullptr;
104
105 public:
106     explicit OutInfoWrapper(CNNLayer* origin): origin(origin) {}
107     OutLayersIterator begin() const {
108         return OutLayersIterator::make_begin(origin->outData);
109     }
110
111     OutLayersIterator end() const {
112         return {};
113     }
114 };
115
116 inline OutInfoWrapper default_order(CNNLayer* layer) {
117     return OutInfoWrapper(layer);
118 }
119
120 /**
121  * @brief implementation of DFS with visiting checking to avoid multientry
122  * @param visited - set to store visited layers
123  * @param layer - current layer to start DFS from
124  * @param visit - user callback on visited node
125  * @param visitBefore - indicates when callback is happened before all child nodes or after
126  * @return false if cycle detected
127  */
128 template <class T, class Ordering = std::function<OutInfoWrapper(CNNLayer*)>>
129 inline bool DFS(std::unordered_map<CNNLayer*, bool>& visited, const InferenceEngine::CNNLayerPtr& layer, const T& visit,
130                 bool visitBefore, const Ordering& order = &default_order) {
131     if (layer == nullptr) {
132         return true;
133     }
134
135     if (visitBefore) visit(layer);
136     visited[layer.get()] = false;
137     for (auto outLayerPtr : order(layer.get())) {
138         auto i = visited.find(outLayerPtr.get());
139         if (i != visited.end()) {
140             /**
141              * cycle detected we entered still not completed node
142              */
143             if (!i->second) {
144                 return false;
145             }
146             continue;
147         }
148         if (!DFS(visited, outLayerPtr, visit, visitBefore, order)) {
149             return false;
150         }
151     }
152
153     if (!visitBefore) visit(layer);
154     visited[layer.get()] = true;
155     return true;
156 }
157
158 /**
159  * @brief implementation of DFS in unordered graph, mean next layers not just child but also parents
160  * @param visited - set to store visited layers
161  * @param layer - current layer to start UnorderedDFS from
162  * @param visit - user callback on visited node
163  * @param visitBefore - indicates when callback is happened before all child nodes or after
164  */
165 template <class T>
166 inline void UnorderedDFS(std::unordered_set<CNNLayer*>& visited, const InferenceEngine::CNNLayerPtr& layer,
167                          const T& visit, bool visitBefore) {
168     std::queue<InferenceEngine::CNNLayerPtr> layers;
169     auto cycleDFS = [&]() {
170         if (layers.empty()) return;
171         auto cnnLayer = layers.front();
172         layers.pop();
173
174         if (cnnLayer == nullptr) {
175             return;
176         }
177         if (visited.end() != visited.find(cnnLayer.get())) {
178             return;
179         }
180
181         if (visitBefore) visit(cnnLayer);
182         visited.insert(cnnLayer.get());
183
184         // visit childs
185         for (auto& od : cnnLayer->outData) {
186             for (auto nl : od->getInputTo()) {
187                 layers.push(nl.second);
188             }
189         }
190
191         // visit parents
192         for (size_t i = 0; i < cnnLayer->insData.size(); i++) {
193             auto& input = cnnLayer->insData[i];
194             if (!input.lock()) {
195                 THROW_IE_EXCEPTION << "Data " << i << " inserted into layer " << cnnLayer->name << " is nullptr";
196             } else {
197                 auto creatorLayer = input.lock()->getCreatorLayer().lock();
198                 if (creatorLayer) {
199                     layers.push(creatorLayer);
200                 }
201             }
202         }
203
204         if (!visitBefore) visit(cnnLayer);
205     };
206     layers.push(layer);
207     while (!layers.empty()) {
208         cycleDFS();
209     }
210 }
211
212 /**
213  * @brief implementation of DFS with visiting checking to avoid multyentry
214  * @param visited - set to store visited layers
215  * @param layer - current layer to start DFS from
216  * @param visit - user callback on visited node
217  */
218 template <class T>
219 inline void BFS(InferenceEngine::CNNLayerPtr layer, const T& visit, int maxDepth) {
220     std::set<InferenceEngine::CNNLayer*> visited;
221     std::list<InferenceEngine::CNNLayerPtr> nextLayers;
222     nextLayers.push_back(layer);
223
224     int layersOnLevel = 1;
225     for (; !nextLayers.empty() && maxDepth != 0;) {
226         visit(*nextLayers.begin());
227         for (auto& od : (*nextLayers.begin())->outData) {
228             for (auto nl : od->getInputTo()) {
229                 if (visited.find(nl.second.get()) == visited.end()) {
230                     nextLayers.push_back(nl.second);
231                     visited.insert(nl.second.get());
232                 }
233             }
234         }
235         nextLayers.pop_front();
236         // move to nextLayer
237         if (!--layersOnLevel) {
238             layersOnLevel = nextLayers.size();
239             maxDepth--;
240         }
241     }
242 }
243
244 }  // namespace details
245
246 /**
247  * Generic DFS algorithm traverser
248  * @param layer - starting layer
249  * @param visit - callback to be called upon visiting
250  * @param visitBefore - indicates when callback is happened before all child nodes or after
251  */
252 template <class T, class Ordering = std::function<details::OutInfoWrapper(CNNLayer*)>>
253 inline bool CNNNetDFS(const InferenceEngine::CNNLayerPtr& layer, const T& visit, bool visitBefore = true,
254                       const Ordering& order = &details::default_order) {
255     if (layer == nullptr) {
256         return true;
257     }
258
259     std::unordered_map<CNNLayer*, bool> visited;
260     return details::DFS(visited, layer, visit, visitBefore, order);
261 }
262 /**
263  * DFS algorithm with multiple starting data
264  * @param layer - starting data
265  * @param visit - callback to be called upon visiting
266  * @param visitBefore - indicates when callback is happened before all child nodes or after
267  */
268 template <class T>
269 inline bool CNNNetForestDFS(const std::vector<DataPtr>& heads, const T& visit, bool bVisitBefore) {
270     std::unordered_map<CNNLayer*, bool> visited;
271     for (const auto& in : heads) {
272         for (const auto& to : in->getInputTo()) {
273             if (visited.find(to.second.get()) != visited.end()) continue;
274             if (!details::DFS(visited, to.second, visit, bVisitBefore)) {
275                 return false;
276             }
277         }
278     }
279     return true;
280 }
281
282 /**
283  * DFS algorithm with multiple starting nodes
284  * @param layer - starting layer
285  * @param visit - callback to be called upon visiting
286  * @param visitBefore - indicates when callback is happened before all child nodes or after
287  */
288 template <class Forest, class T>
289 inline bool CNNNetForestDFS(const Forest& heads, const T& visit, bool bVisitBefore) {
290     if (heads.empty()) {
291         return true;
292     }
293
294     std::unordered_map<CNNLayer*, bool> visited;
295     for (auto& layer : heads) {
296         if (!details::DFS(visited, layer, visit, bVisitBefore)) {
297             return false;
298         }
299     }
300     return true;
301 }
302
303 /**
304  * DFS algorithm with multiple starting nodes
305  * @param layer - starting layer
306  * @param visit - callback to be called upon visiting
307  * @param visitBefore - indicates when callback is happened before all child nodes or after
308  */
309 template <class Ordering, class Forest, class T>
310 inline bool CNNNetForestDFS(const Forest& heads, const T& visit, bool bVisitBefore, const Ordering& order) {
311     if (heads.empty()) {
312         return true;
313     }
314
315     std::unordered_map<CNNLayer*, bool> visited;
316     for (auto& layer : heads) {
317         if (!details::DFS(visited, layer, visit, bVisitBefore, order)) {
318             return false;
319         }
320     }
321     return true;
322 }
323
324 /**
325  * Generic BFS algorithm traverser
326  * @param layer - starting layer
327  * @param visit - callback to be called upon visiting
328  */
329 template <class T>
330 inline void CNNNetBFS(const InferenceEngine::CNNLayerPtr& layer, const T& visit) {
331     if (!layer) {
332         return;
333     }
334     details::BFS(layer, visit, -1);
335 }
336
337 /**
338  * @brief pointer of previous layers
339  * @param idx - index in previous layer collection
340  * @param layer
341  */
342 inline bool CNNNetHasPrevLayer(const InferenceEngine::CNNLayer* layer, int idx = 0) {
343     IE_ASSERT(layer != nullptr);
344     if (layer->insData.empty() || static_cast<int>(layer->insData.size()) <= idx) {
345         return false;
346     }
347     auto prevData = layer->insData[idx].lock();
348     return !!prevData->getCreatorLayer().lock();
349 }
350
351 /**
352  * @brief to allow storing of LayersSP in collections ordered by  names
353  */
354
355 class LayerNameLess {
356 public:
357     bool operator()(const CNNLayerPtr& lhs, const CNNLayerPtr& rhs) const {
358         return std::less<std::string>()(lhs->name, rhs->name);
359     }
360 };
361
362 using CNNLayerSet = std::set<CNNLayerPtr, LayerNameLess>;
363
364 /**
365  * @brief returns all layers that are input or memory
366  * @param network
367  * @return set of input layers
368  */
369 inline CNNLayerSet CNNNetGetAllInputLayers(const ICNNNetwork& network) {
370     InputsDataMap inputs;
371     network.getInputsInfo(inputs);
372
373     CNNLayerSet inputLayers;
374     std::unordered_set<CNNLayer*> allLayers;
375
376     if (inputs.empty()) return inputLayers;
377
378     for (const auto& input : inputs) {
379         auto& secondLayers = input.second->getInputData()->getInputTo();
380
381         if (secondLayers.empty()) continue;
382
383         details::UnorderedDFS(
384             allLayers, secondLayers.begin()->second,
385             [&](CNNLayerPtr layer) {
386                 if (layer->insData.empty()) {
387                     inputLayers.insert(layer);
388                 }
389             },
390             false);
391     }
392     return inputLayers;
393 }
394
395 /**
396  * @brief returns all layers that are input or memory , search started from arbitrary location in network
397  * @param start layer
398  * @return set of input layers
399  */
400 inline CNNLayerSet CNNNetGetAllInputLayers(CNNLayer* layer) {
401     CNNLayerSet inputLayers;
402     std::unordered_set<CNNLayer*> allLayers;
403
404     CNNLayerPtr layerPtr(layer, [](CNNLayer*) {});
405
406     details::UnorderedDFS(
407         allLayers, layerPtr,
408         [&](CNNLayerPtr layer) {
409             if (layer->insData.empty()) {
410                 inputLayers.insert(layer);
411             }
412         },
413         false);
414     return inputLayers;
415 }
416
417 /**
418  * @brief Sorts CNNNetork graph in topological order, while uses custom ordering when walking among child nodes
419  * @param network - input CNNNetwork
420  * @param ordering - callback that returns output layers for given CNNLayer pointer, see default_order function
421  * @return sorted CNNNetwork layers
422  */
423 template <class LayerOrdering>
424 std::vector<CNNLayerPtr> CNNNetSortTopologicallyEx(const ICNNNetwork& network, LayerOrdering ordering) {
425     std::vector<CNNLayerPtr> stackOfVisited;
426     bool res = CNNNetForestDFS(
427         CNNNetGetAllInputLayers(network),
428         [&](CNNLayerPtr current) {
429             stackOfVisited.push_back(current);
430         },
431         false, ordering);
432
433     if (!res) {
434         THROW_IE_EXCEPTION << "Sorting not possible, due to existed loop.";
435     }
436
437     std::reverse(std::begin(stackOfVisited), std::end(stackOfVisited));
438
439     return stackOfVisited;
440 }
441
442 using CNNNetPtr = std::shared_ptr<ICNNNetwork>;
443 using CNNNetCPtr = std::shared_ptr<const ICNNNetwork>;
444
445 /**
446  * @brief deep copy of the entire network, structure using custom copier for layers
447  * @param input - source network
448  * @param cp - custom copier object, ex: [](CNNLayerPtr lp) { return injectData<EmptyStruct>(lp); }
449  * @return copied network
450  */
451 template <class Copier>
452 inline CNNNetPtr CNNNetCopy(const ICNNNetwork& input, const Copier& cp) {
453     auto net = std::make_shared<details::CNNNetworkImpl>();
454
455     // setting base args
456     IE_SUPPRESS_DEPRECATED_START
457     net->setPrecision(input.getPrecision());
458     IE_SUPPRESS_DEPRECATED_END
459
460     net->setName(input.getName());
461
462     // rest info is layer dependent so have to create graph clone
463     std::unordered_map<CNNLayer*, CNNLayerPtr> oldToNewLayers;
464
465     auto starters = CNNNetGetAllInputLayers(input);
466
467     // 1st pass node creation
468     bool res = CNNNetForestDFS(
469         starters,
470         [&](CNNLayerPtr current) {
471             auto newLayer = cp(current);
472             oldToNewLayers[current.get()] = newLayer;
473             net->addLayer(newLayer);
474         },
475         true);
476
477     if (!res) {
478         THROW_IE_EXCEPTION << "Copying of network not possible, due to existed loop.";
479     }
480
481     // internal utility to locate out data idx in layer
482     auto findOutDataIdx = [&](DataPtr sourceData) {
483         int dataIdx = -1;
484         auto sourceLayer = sourceData->getCreatorLayer().lock();
485         if (!sourceLayer) {
486             THROW_IE_EXCEPTION << "Data " << sourceData->getName() << " has no creator layer";
487         }
488         for (size_t j = 0; j < sourceLayer->outData.size(); j++) {
489             if (sourceData.get() == sourceLayer->outData[j].get()) {
490                 dataIdx = static_cast<int>(j);
491                 break;
492             }
493         }
494         IE_ASSERT(dataIdx != -1);
495         return dataIdx;
496     };
497
498     // compares data, for copied network and in old network
499     auto areEqualDatas = [&](DataPtr source, DataPtr target) {
500         if (source.get() == target.get()) {
501             return true;
502         }
503
504         // dims comparison -
505         // actual dims value might be incorrect dueto syntetic case
506         // , when getbatch() size returns value not reflect in actual data
507
508         if (source->getTensorDesc().getDims().size() != target->getTensorDesc().getDims().size()) {
509             return false;
510         }
511
512         // name comparison
513         if (source->getName() != target->getName()) {
514             return false;
515         }
516
517         // inputTO layers are identical by design
518         return true;
519     };
520     // internal utility to locate input data idx in layer
521     auto findInsDataIdx = [&](DataPtr sourceData, CNNLayerPtr layer) {
522         int dataIdx = -1;
523         auto sourceLayerMap = sourceData->getInputTo();
524         for (auto& layersMapping : sourceLayerMap) {
525             if (layersMapping.second.get() != layer.get()) {
526                 continue;
527             }
528             for (size_t j = 0; j < layer->insData.size(); j++) {
529                 if (areEqualDatas(layer->insData[j].lock(), sourceData)) {
530                     dataIdx = static_cast<int>(j);
531                 }
532             }
533             if (dataIdx != -1) {
534                 break;
535             }
536         }
537         IE_ASSERT(dataIdx != -1);
538         return dataIdx;
539     };
540
541     // 2nd pass edges creation
542     CNNNetForestDFS(
543         starters,
544         [&](CNNLayerPtr current) {
545             auto newLayer = oldToNewLayers[current.get()];
546             // remap output data
547             for (size_t i = 0; i != current->outData.size(); i++) {
548                 newLayer->outData[i]->getCreatorLayer() = CNNLayerWeakPtr(newLayer);
549
550                 // transfer data info for getData routine
551                 net->getData(newLayer->outData[i]->getName()) = newLayer->outData[i];
552
553                 for (auto inputTo = std::begin(newLayer->outData[i]->getInputTo());
554                      inputTo != std::end(newLayer->outData[i]->getInputTo()); inputTo++) {
555                     inputTo->second = oldToNewLayers[inputTo->second.get()];
556                 }
557             }
558             // remap input data
559             for (size_t i = 0; i != current->insData.size(); i++) {
560                 // found that data IDX
561                 auto sourceData = current->insData[i].lock();
562                 auto sourceLayer = sourceData->getCreatorLayer().lock();
563                 if (!sourceLayer) {
564                     THROW_IE_EXCEPTION << "Data " << sourceData->getName() << " has no creator layer";
565                 }
566                 // find insData Entry in outData of sourceLayer
567                 newLayer->insData[i] = oldToNewLayers[sourceLayer.get()]->outData[findOutDataIdx(sourceData)];
568             }
569         },
570         true);
571
572     // transfer input info
573     InputsDataMap inputsInfo;
574     input.getInputsInfo(inputsInfo);
575     std::set<DataPtr> insDatas;
576     for (auto&& info : inputsInfo) {
577         for (auto secondLayer : info.second->getInputData()->getInputTo()) {
578             auto secondLayerNew = oldToNewLayers[secondLayer.second.get()];
579             InputInfo::Ptr infoNew = std::make_shared<InputInfo>();
580             infoNew->setInputData(
581                 secondLayerNew->insData[findInsDataIdx(info.second->getInputData(), secondLayer.second)].lock());
582             infoNew->getPreProcess() = info.second->getPreProcess();
583             net->setInputInfo(infoNew);
584         }
585     }
586
587     // transfer output info
588     OutputsDataMap outmap;
589     input.getOutputsInfo(outmap);
590     for (auto&& data : outmap) {
591         ResponseDesc dsc;
592         if (OK != net->addOutput(data.second->getCreatorLayer().lock()->name, findOutDataIdx(data.second), &dsc)) {
593             THROW_IE_EXCEPTION << dsc.msg;
594         }
595     }
596
597     ResponseDesc dsc;
598     // transfer batch size
599     if (OK != net->setBatchSize(input.getBatchSize(), &dsc)) {
600         THROW_IE_EXCEPTION << dsc.msg;
601     }
602
603     return net;
604 }
605
606 /**
607  * @brief deep copy of the entire network
608  * @param input
609  * @return
610  */
611 inline CNNNetPtr CNNNetCopy(const ICNNNetwork& input) {
612     struct EmptyStruct {};
613     auto copier = [](CNNLayerPtr lp) {
614         return injectData<EmptyStruct>(lp);
615     };
616     return InferenceEngine::CNNNetCopy(input, copier);
617 }
618
619 /**
620  * @brief Replaces layer with newLayer in network
621  * @param network  - graph containing the layer
622  * @param layer    - layer which need to replace
623  * @param newLayer - new layer instead of layer; it must have same name like a layer for replace
624  */
625 void CNNNetSubstituteLayer(InferenceEngine::ICNNNetwork& network, const InferenceEngine::CNNLayerPtr& layer,
626                            const InferenceEngine::CNNLayerPtr& newLayer);
627
628 namespace details {
629
630 /**
631  * The structure to wrap network as lists of input and output data objects
632  * Each layer of network is achievable by DFS started from inputs.
633  *
634  * NB! The input collection may contain a "fake" data object which is not a
635  *     real input to network, but just a holder to keep "const" and "memory"
636  *     layers alive. Fake data object points layers with empty creator field.
637  *     The fake data object always has "UNSPECIFIED" precision attribute.
638  */
639 struct CNNSubnet {
640     std::vector<DataPtr> inputs;
641     std::vector<DataPtr> outputs;
642 };
643
644 /**
645  * @brief Detect all input data object, not only provided as entry point.
646  * @param heads collection of some input into graph
647  * @return all input data objects including "fake" data (layers holder).
648  */
649 inline std::vector<DataPtr> CNNSubnetGetAllInputs(const std::vector<DataPtr>& heads) {
650     CNNLayerSet inputLayers;
651     std::unordered_set<CNNLayer*> allLayers;
652
653     // Define all start layers
654     for (const auto& data : heads) {
655         auto& secondLayers = data->getInputTo();
656
657         if (secondLayers.empty()) continue;
658
659         details::UnorderedDFS(
660                 allLayers, secondLayers.begin()->second,
661                 [&](CNNLayerPtr layer) {
662                     if (layer->insData.empty()) {
663                         inputLayers.insert(layer);
664                     }
665                 },
666                 false);
667     }
668
669     std::vector<DataPtr> res = heads;
670     // Add fake input data to point on not achievable
671     // layers from head (like const placeholders)
672     for (auto& starter : inputLayers) {
673         DataPtr holder(new Data(starter->name + ":input_holder", starter->precision));
674         holder->getInputTo()[starter->name] = starter;
675         res.push_back(holder);
676     }
677
678     return res;
679 }
680
681 /**
682  * @brief Sorts SNNSubnet graph representation in topological order
683  * @param subnet input object
684  * @return layer collection sorted in topological order
685  */
686 inline std::vector<CNNLayerPtr> CNNSubnetSortTopologically(const CNNSubnet& subnet) {
687     std::vector<CNNLayerPtr> stackOfVisited;
688     bool res = CNNNetForestDFS(
689             CNNSubnetGetAllInputs(subnet.inputs),
690             [&](CNNLayerPtr current) {
691                 stackOfVisited.push_back(current);
692             },
693             false);
694     if (!res) {
695         THROW_IE_EXCEPTION << "Sorting not possible, due to existed loop.";
696     }
697
698     std::reverse(stackOfVisited.begin(), stackOfVisited.end());
699     return stackOfVisited;
700 }
701
702 }  // namespace details
703 }  // namespace InferenceEngine
704
705 IE_SUPPRESS_DEPRECATED_END