Path: blob/master/modules/gapi/src/compiler/passes/exec.cpp
16354 views
// This file is part of OpenCV project.1// It is subject to the license terms in the LICENSE file found in the top-level directory2// of this distribution and at http://opencv.org/license.html.3//4// Copyright (C) 2018 Intel Corporation567#include "precomp.hpp"89#include <string>10#include <list> // list11#include <iomanip> // setw, etc12#include <fstream> // ofstream13#include <memory>14#include <functional>1516#include <ade/util/algorithm.hpp> // contains17#include <ade/util/chain_range.hpp> // chain1819#include "opencv2/gapi/util/optional.hpp" // util::optional20#include "logger.hpp" // GAPI_LOG2122#include "compiler/gmodel.hpp"23#include "compiler/gislandmodel.hpp"24#include "compiler/passes/passes.hpp"25#include "compiler/passes/helpers.hpp"26#include "compiler/transactions.hpp"2728////////////////////////////////////////////////////////////////////////////////29//30// N.B.31// Merge is a binary operation (LHS `Merge` RHS) where LHS may be arbitrary32//33// After every merge, the procedure starts from the beginning (in the topological34// order), thus trying to merge next "unmerged" island to the latest merged one.35//36////////////////////////////////////////////////////////////////////////////////3738// Uncomment to dump more info on merge process39// FIXME: make it user-configurable run-time option40// #define DEBUG_MERGE4142namespace cv43{44namespace gimpl45{46namespace47{48bool fusionIsTrivial(const ade::Graph &src_graph)49{50// Fusion is considered trivial if there only one51// active backend and no user-defined islands52// FIXME:53// Also check the cases backend can't handle54// (e.x. GScalar connecting two fluid ops should split the graph)55const GModel::ConstGraph g(src_graph);56const auto& active_backends = g.metadata().get<ActiveBackends>().backends;57return active_backends.size() == 1 &&58ade::util::all_of(g.nodes(), [&](ade::NodeHandle nh) {59return !g.metadata(nh).contains<Island>();60});61}6263void fuseTrivial(GIslandModel::Graph &g, const ade::Graph &src_graph)64{65const GModel::ConstGraph src_g(src_graph);6667const auto& backend = *src_g.metadata().get<ActiveBackends>().backends.cbegin();68const auto& proto = src_g.metadata().get<Protocol>();69GIsland::node_set all, in_ops, out_ops;7071all.insert(src_g.nodes().begin(), src_g.nodes().end());7273for (const auto nh : proto.in_nhs)74{75all.erase(nh);76in_ops.insert(nh->outNodes().begin(), nh->outNodes().end());77}78for (const auto nh : proto.out_nhs)79{80all.erase(nh);81out_ops.insert(nh->inNodes().begin(), nh->inNodes().end());82}8384auto isl = std::make_shared<GIsland>(backend,85std::move(all),86std::move(in_ops),87std::move(out_ops),88util::optional<std::string>{});8990auto ih = GIslandModel::mkIslandNode(g, std::move(isl));9192for (const auto nh : proto.in_nhs)93{94auto slot = GIslandModel::mkSlotNode(g, nh);95g.link(slot, ih);96}97for (const auto nh : proto.out_nhs)98{99auto slot = GIslandModel::mkSlotNode(g, nh);100g.link(ih, slot);101}102}103104struct MergeContext105{106using CycleCausers = std::pair< std::shared_ptr<GIsland>,107std::shared_ptr<GIsland> >;108109struct CycleHasher final110{111std::size_t operator()(const CycleCausers& p) const112{113std::size_t a = std::hash<GIsland*>()(p.first.get());114std::size_t b = std::hash<GIsland*>()(p.second.get());115return a ^ (b << 1);116}117};118119// Set of Islands pairs which cause a cycle if merged.120// Every new merge produces a new Island, and if Islands were121// merged (and thus dropped from GIslandModel), the objects may122// still be alive as included into this set.123std::unordered_set<CycleCausers, CycleHasher> cycle_causers;124};125126bool canMerge(const GIslandModel::Graph &g,127const ade::NodeHandle a_nh,128const ade::NodeHandle /*slot_nh*/,129const ade::NodeHandle b_nh,130const MergeContext &ctx = MergeContext())131{132auto a_ptr = g.metadata(a_nh).get<FusedIsland>().object;133auto b_ptr = g.metadata(b_nh).get<FusedIsland>().object;134GAPI_Assert(a_ptr.get());135GAPI_Assert(b_ptr.get());136137// Islands with different affinity can't be merged138if (a_ptr->backend() != b_ptr->backend())139return false;140141// Islands which cause a cycle can't be merged as well142// (since the flag is set, the procedure already tried to143// merge these islands in the past)144if (ade::util::contains(ctx.cycle_causers, std::make_pair(a_ptr, b_ptr))||145ade::util::contains(ctx.cycle_causers, std::make_pair(b_ptr, a_ptr)))146return false;147148// There may be user-defined islands. Initially user-defined149// islands also are built from single operations and then merged150// by this procedure, but there is some exceptions.151// User-specified island can't be merged to an internal island152if ( ( a_ptr->is_user_specified() && !b_ptr->is_user_specified())153|| (!a_ptr->is_user_specified() && b_ptr->is_user_specified()))154{155return false;156}157else if (a_ptr->is_user_specified() && b_ptr->is_user_specified())158{159// These islads are _different_ user-specified Islands160// FIXME: today it may only differ by name161if (a_ptr->name() != b_ptr->name())162return false;163}164165// FIXME: add a backend-specified merge checker166return true;167}168169inline bool isProducedBy(const ade::NodeHandle &slot,170const ade::NodeHandle &island)171{172// A data slot may have only 0 or 1 producer173if (slot->inNodes().size() == 0)174return false;175176return slot->inNodes().front() == island;177}178179inline bool isConsumedBy(const ade::NodeHandle &slot,180const ade::NodeHandle &island)181{182auto it = std::find_if(slot->outNodes().begin(),183slot->outNodes().end(),184[&](const ade::NodeHandle &nh) {185return nh == island;186});187return it != slot->outNodes().end();188}189190/**191* Find a candidate Island for merge for the given Island nh.192*193* @param g Island Model where merge occurs194* @param nh GIsland node, either LHS or RHS of probable merge195* @param ctx Merge context, may contain some cached stuff to avoid196* double/triple/etc checking197* @return Tuple of Island handle, Data slot handle (which connects them),198* and a position of found handle with respect to nh (IN/OUT)199*/200std::tuple<ade::NodeHandle, ade::NodeHandle, Direction>201findCandidate(const GIslandModel::Graph &g,202ade::NodeHandle nh,203const MergeContext &ctx = MergeContext())204{205using namespace std::placeholders;206207// Find a first matching candidate GIsland for merge208// among inputs209for (const auto& input_data_nh : nh->inNodes())210{211if (input_data_nh->inNodes().size() != 0)212{213// Data node must have a single producer only214GAPI_DbgAssert(input_data_nh->inNodes().size() == 1);215auto input_data_prod_nh = input_data_nh->inNodes().front();216if (canMerge(g, input_data_prod_nh, input_data_nh, nh, ctx))217return std::make_tuple(input_data_prod_nh,218input_data_nh,219Direction::In);220}221} // for(inNodes)222223// Ok, now try to find it among the outputs224for (const auto& output_data_nh : nh->outNodes())225{226auto mergeTest = [&](ade::NodeHandle cons_nh) -> bool {227return canMerge(g, nh, output_data_nh, cons_nh, ctx);228};229auto cand_it = std::find_if(output_data_nh->outNodes().begin(),230output_data_nh->outNodes().end(),231mergeTest);232if (cand_it != output_data_nh->outNodes().end())233return std::make_tuple(*cand_it,234output_data_nh,235Direction::Out);236} // for(outNodes)237// Empty handle, no good candidates238return std::make_tuple(ade::NodeHandle(),239ade::NodeHandle(),240Direction::Invalid);241}242243// A cancellable merge of two GIslands, "a" and "b", connected via "slot"244class MergeAction245{246ade::Graph &m_g;247const ade::Graph &m_orig_g;248GIslandModel::Graph m_gim;249ade::NodeHandle m_prod;250ade::NodeHandle m_slot;251ade::NodeHandle m_cons;252253Change::List m_changes;254255struct MergeObjects256{257using NS = GIsland::node_set;258NS all; // same as in GIsland259NS in_ops; // same as in GIsland260NS out_ops; // same as in GIsland261NS opt_interim_slots; // can be dropped (optimized out)262NS non_opt_interim_slots;// can't be dropped (extern. link)263};264MergeObjects identify() const;265266public:267MergeAction(ade::Graph &g,268const ade::Graph &orig_g,269ade::NodeHandle prod,270ade::NodeHandle slot,271ade::NodeHandle cons)272: m_g(g)273, m_orig_g(orig_g)274, m_gim(GIslandModel::Graph(m_g))275, m_prod(prod)276, m_slot(slot)277, m_cons(cons)278{279}280281void tryMerge(); // Try to merge islands Prod and Cons282void rollback(); // Roll-back changes if merge has been done but broke the model283void commit(); // Fix changes in the model after successful merge284};285286// Merge proceduce is a replacement of two Islands, Prod and Cons,287// connected via !Slot!, with a new Island, which contain all Prod288// nodes + all Cons nodes, and reconnected in the graph properly:289//290// Merge(Prod, !Slot!, Cons)291//292// [Slot 2]293// :294// v295// ... [Slot 0] -> Prod -> !Slot! -> Cons -> [Slot 3] -> ...296// ... [Slot 1] -' : '-> [Slot 4] -> ...297// V298// ...299// results into:300//301// ... [Slot 0] -> Merged -> [Slot 3] ...302// ... [Slot 1] : :-> [Slot 4] ...303// ... [Slot 2] ' '-> !Slot! ...304//305// The rules are the following:306// 1) All Prod input slots become Merged input slots;307// - Example: Slot 0 Slot 1308// 2) Any Cons input slots which come from Islands different to Prod309// also become Merged input slots;310// - Example: Slot 2311// 3) All Cons output slots become Merged output slots;312// - Example: Slot 3, Slot 4313// 4) All Prod output slots which are not consumed by Cons314// also become Merged output slots;315// - (not shown on the example)316// 5) If the !Slot! which connects Prod and Cons is consumed317// exclusively by Cons, it is optimized out (dropped) from the model;318// 6) If the !Slot! is used by consumers other by Cons, it319// becomes an extra output of Merged320// 7) !Slot! may be not the only connection between Prod and Cons,321// but as a result of merge operation, all such connections322// should be handles as described for !Slot!323324MergeAction::MergeObjects MergeAction::identify() const325{326auto lhs = m_gim.metadata(m_prod).get<FusedIsland>().object;327auto rhs = m_gim.metadata(m_cons).get<FusedIsland>().object;328329GIsland::node_set interim_slots;330331GIsland::node_set merged_all(lhs->contents());332merged_all.insert(rhs->contents().begin(), rhs->contents().end());333334GIsland::node_set merged_in_ops(lhs->in_ops()); // (1)335for (auto cons_in_slot_nh : m_cons->inNodes()) // (2)336{337if (isProducedBy(cons_in_slot_nh, m_prod))338{339interim_slots.insert(cons_in_slot_nh);340// at this point, interim_slots are not sync with merged_all341// (merged_all will be updated with interim_slots which342// will be optimized out).343}344else345{346const auto extra_in_ops = rhs->consumers(m_g, cons_in_slot_nh);347merged_in_ops.insert(extra_in_ops.begin(), extra_in_ops.end());348}349}350351GIsland::node_set merged_out_ops(rhs->out_ops()); // (3)352for (auto prod_out_slot_nh : m_prod->outNodes()) // (4)353{354if (!isConsumedBy(prod_out_slot_nh, m_cons))355{356merged_out_ops.insert(lhs->producer(m_g, prod_out_slot_nh));357}358}359360// (5,6,7)361GIsland::node_set opt_interim_slots;362GIsland::node_set non_opt_interim_slots;363364auto is_non_opt = [&](const ade::NodeHandle &slot_nh) {365// If a data object associated with this slot is a part366// of GComputation _output_ protocol, it can't be optimzied out367const auto data_nh = m_gim.metadata(slot_nh).get<DataSlot>().original_data_node;368const auto &data = GModel::ConstGraph(m_orig_g).metadata(data_nh).get<Data>();369if (data.storage == Data::Storage::OUTPUT)370return true;371372// Otherwise, a non-optimizeable data slot is the one consumed373// by some other island than "cons"374const auto it = std::find_if(slot_nh->outNodes().begin(),375slot_nh->outNodes().end(),376[&](ade::NodeHandle &&nh)377{return nh != m_cons;});378return it != slot_nh->outNodes().end();379};380for (auto slot_nh : interim_slots)381{382// Put all intermediate data nodes (which are BOTH optimized383// and not-optimized out) to Island contents.384merged_all.insert(m_gim.metadata(slot_nh)385.get<DataSlot>().original_data_node);386387GIsland::node_set &dst = is_non_opt(slot_nh)388? non_opt_interim_slots // there are consumers other than m_cons389: opt_interim_slots; // there's no consumers other than m_cons390dst.insert(slot_nh);391}392393// (4+6).394// BTW, (4) could be "All Prod output slots read NOT ONLY by Cons"395for (auto non_opt_slot_nh : non_opt_interim_slots)396{397merged_out_ops.insert(lhs->producer(m_g, non_opt_slot_nh));398}399return MergeObjects{400merged_all, merged_in_ops, merged_out_ops,401opt_interim_slots, non_opt_interim_slots402};403}404405// FIXME(DM): Probably this procedure will be refactored dramatically one day...406void MergeAction::tryMerge()407{408// _: Understand the contents and I/O connections of a new merged Island409MergeObjects mo = identify();410auto lhs_obj = m_gim.metadata(m_prod).get<FusedIsland>().object;411auto rhs_obj = m_gim.metadata(m_cons).get<FusedIsland>().object;412GAPI_Assert( ( lhs_obj->is_user_specified() && rhs_obj->is_user_specified())413|| (!lhs_obj->is_user_specified() && !rhs_obj->is_user_specified()));414cv::util::optional<std::string> maybe_user_tag;415if (lhs_obj->is_user_specified() && rhs_obj->is_user_specified())416{417GAPI_Assert(lhs_obj->name() == rhs_obj->name());418maybe_user_tag = cv::util::make_optional(lhs_obj->name());419}420421// A: Create a new Island and add it to the graph422auto backend = m_gim.metadata(m_prod).get<FusedIsland>()423.object->backend();424auto merged = std::make_shared<GIsland>(backend,425std::move(mo.all),426std::move(mo.in_ops),427std::move(mo.out_ops),428std::move(maybe_user_tag));429// FIXME: move this debugging to some user-controllable log-level430#ifdef DEBUG_MERGE431merged->debug();432#endif433auto new_nh = GIslandModel::mkIslandNode(m_gim, std::move(merged));434m_changes.enqueue<Change::NodeCreated>(new_nh);435436// B: Disconnect all Prod's input Slots from Prod,437// connect it to Merged438std::vector<ade::EdgeHandle> input_edges(m_prod->inEdges().begin(),439m_prod->inEdges().end());440for (auto in_edge : input_edges)441{442m_changes.enqueue<Change::NewLink>(m_g, in_edge->srcNode(), new_nh);443m_changes.enqueue<Change::DropLink>(m_g, m_prod, in_edge);444}445446// C: Disconnect all Cons' output Slots from Cons,447// connect it to Merged448std::vector<ade::EdgeHandle> output_edges(m_cons->outEdges().begin(),449m_cons->outEdges().end());450for (auto out_edge : output_edges)451{452m_changes.enqueue<Change::NewLink>(m_g, new_nh, out_edge->dstNode());453m_changes.enqueue<Change::DropLink>(m_g, m_cons, out_edge);454}455456// D: Process the intermediate slots (betweed Prod n Cons).457// D/1 - Those which are optimized out are just removed from the model458for (auto opt_slot_nh : mo.opt_interim_slots)459{460GAPI_Assert(1 == opt_slot_nh->inNodes().size() );461GAPI_Assert(m_prod == opt_slot_nh->inNodes().front());462463std::vector<ade::EdgeHandle> edges_to_drop;464ade::util::copy(ade::util::chain(opt_slot_nh->inEdges(),465opt_slot_nh->outEdges()),466std::back_inserter(edges_to_drop));467for (auto eh : edges_to_drop)468{469m_changes.enqueue<Change::DropLink>(m_g, opt_slot_nh, eh);470}471m_changes.enqueue<Change::DropNode>(opt_slot_nh);472}473// D/2 - Those which are used externally are connected to new nh474// as outputs.475for (auto non_opt_slot_nh : mo.non_opt_interim_slots)476{477GAPI_Assert(1 == non_opt_slot_nh->inNodes().size() );478GAPI_Assert(m_prod == non_opt_slot_nh->inNodes().front());479m_changes.enqueue<Change::DropLink>(m_g, non_opt_slot_nh,480non_opt_slot_nh->inEdges().front());481482std::vector<ade::EdgeHandle> edges_to_probably_drop483(non_opt_slot_nh->outEdges().begin(),484non_opt_slot_nh->outEdges().end());;485for (auto eh : edges_to_probably_drop)486{487if (eh->dstNode() == m_cons)488{489// drop only edges to m_cons, as there's other consumers490m_changes.enqueue<Change::DropLink>(m_g, non_opt_slot_nh, eh);491}492}493m_changes.enqueue<Change::NewLink>(m_g, new_nh, non_opt_slot_nh);494}495496// E. All Prod's output edges which are directly related to Merge (e.g.497// connected to Cons) were processed on step (D).498// Relink the remaining output links499std::vector<ade::EdgeHandle> prod_extra_out_edges500(m_prod->outEdges().begin(),501m_prod->outEdges().end());502for (auto extra_out : prod_extra_out_edges)503{504m_changes.enqueue<Change::NewLink>(m_g, new_nh, extra_out->dstNode());505m_changes.enqueue<Change::DropLink>(m_g, m_prod, extra_out);506}507508// F. All Cons' input edges which are directly related to merge (e.g.509// connected to Prod) were processed on step (D) as well,510// remaining should become Merged island's input edges511std::vector<ade::EdgeHandle> cons_extra_in_edges512(m_cons->inEdges().begin(),513m_cons->inEdges().end());514for (auto extra_in : cons_extra_in_edges)515{516m_changes.enqueue<Change::NewLink>(m_g, extra_in->srcNode(), new_nh);517m_changes.enqueue<Change::DropLink>(m_g, m_cons, extra_in);518}519520// G. Finally, drop the original Island nodes. DropNode() does521// the sanity check for us (both nodes should have 0 edges).522m_changes.enqueue<Change::DropNode>(m_prod);523m_changes.enqueue<Change::DropNode>(m_cons);524}525526void MergeAction::rollback()527{528m_changes.rollback(m_g);529}530void MergeAction::commit()531{532m_changes.commit(m_g);533}534535#ifdef DEBUG_MERGE536void merge_debug(const ade::Graph &g, int iteration)537{538std::stringstream filename;539filename << "fusion_" << static_cast<const void*>(&g)540<< "_" << std::setw(4) << std::setfill('0') << iteration541<< ".dot";542std::ofstream ofs(filename.str());543passes::dumpDot(g, ofs);544}545#endif546547void fuseGeneral(ade::Graph& im, const ade::Graph& g)548{549GIslandModel::Graph gim(im);550MergeContext mc;551552bool there_was_a_merge = false;553std::size_t iteration = 0u;554do555{556there_was_a_merge = false;557558// FIXME: move this debugging to some user-controllable log level559#ifdef DEBUG_MERGE560GAPI_LOG_INFO(NULL, "Before next merge attempt " << iteration << "...");561merge_debug(g, iteration);562#endif563iteration++;564auto sorted = pass_helpers::topoSort(im);565for (auto nh : sorted)566{567if (NodeKind::ISLAND == gim.metadata(nh).get<NodeKind>().k)568{569ade::NodeHandle cand_nh;570ade::NodeHandle cand_slot;571Direction dir = Direction::Invalid;572std::tie(cand_nh, cand_slot, dir) = findCandidate(gim, nh, mc);573if (cand_nh != nullptr && dir != Direction::Invalid)574{575auto lhs_nh = (dir == Direction::In ? cand_nh : nh);576auto rhs_nh = (dir == Direction::Out ? cand_nh : nh);577578auto l_obj = gim.metadata(lhs_nh).get<FusedIsland>().object;579auto r_obj = gim.metadata(rhs_nh).get<FusedIsland>().object;580GAPI_LOG_INFO(NULL, r_obj->name() << " can be merged into " << l_obj->name());581// Try to do a merge. If merge was succesfull, check if the582// graph have cycles (cycles are prohibited at this point).583// If there are cycles, roll-back the merge and mark a pair of584// these Islands with a special tag - "cycle-causing".585MergeAction action(im, g, lhs_nh, cand_slot, rhs_nh);586action.tryMerge();587if (pass_helpers::hasCycles(im))588{589GAPI_LOG_INFO(NULL,590"merge(" << l_obj->name() << "," << r_obj->name() <<591") caused cycle, rolling back...");592action.rollback();593// don't try to merge these two islands next time (findCandidate will use that)594mc.cycle_causers.insert({l_obj, r_obj});595}596else597{598GAPI_LOG_INFO(NULL,599"merge(" << l_obj->name() << "," << r_obj->name() <<600") was successful!");601action.commit();602#ifdef DEBUG_MERGE603GIslandModel::syncIslandTags(gim, g);604#endif605there_was_a_merge = true;606break; // start do{}while from the beginning607}608} // if(can merge)609} // if(ISLAND)610} // for(all nodes)611}612while (there_was_a_merge);613}614} // anonymous namespace615616void passes::fuseIslands(ade::passes::PassContext &ctx)617{618std::shared_ptr<ade::Graph> gptr(new ade::Graph);619GIslandModel::Graph gim(*gptr);620621if (fusionIsTrivial(ctx.graph))622{623fuseTrivial(gim, ctx.graph);624}625else626{627GIslandModel::generateInitial(gim, ctx.graph);628fuseGeneral(*gptr.get(), ctx.graph);629}630GModel::Graph(ctx.graph).metadata().set(IslandModel{std::move(gptr)});631}632633void passes::syncIslandTags(ade::passes::PassContext &ctx)634{635GModel::Graph gm(ctx.graph);636std::shared_ptr<ade::Graph> gptr(gm.metadata().get<IslandModel>().model);637GIslandModel::Graph gim(*gptr);638GIslandModel::syncIslandTags(gim, ctx.graph);639}640}} // namespace cv::gimpl641642643