1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-29 23:33:05 +04:00

G-API: Introduce streaming::desync and infer(ROI)

- desync() is a new (and for now, the only one) intrinsic
  which splits the graph execution into asynchronous parts
  when running in Streaming mode;
- desync() makes no effect when compiling in Traditional mode;
- Added tests on desync() working in various scenarios;
- Extended GStreamingExecutor to support desync(); also extended
  GStreamingCompiled() with a new version of pull() returning a
  vector of optional values;
- Fixed various issues with storing the type information & proper
  construction callbacks for GArray<> and GOpaque;

- Introduced a new infer(Roi,GMat) overload with a sample;

- Introduced an internal API for Islands to control fusion
  procedure (to fuse or not to fuse);
- Introduced handleStopStream() callback for island executables;
- Added GCompileArgs to metadata of the graph (required for other
  features).
This commit is contained in:
Dmitry Matveev
2020-03-18 02:38:24 +03:00
parent f345ed564a
commit ca8bb8d053
40 changed files with 2827 additions and 195 deletions
+8
View File
@@ -238,6 +238,11 @@ cv::gimpl::GCompiler::GCompiler(const cv::GComputation &c,
// (no compound backend present here)
m_e.addPass("kernels", "check_islands_content", passes::checkIslandsContent);
// Special stage for intrinsics handling
m_e.addPassStage("intrin");
m_e.addPass("intrin", "desync", passes::intrinDesync);
m_e.addPass("intrin", "finalizeIntrin", passes::intrinFinalize);
//Input metas may be empty when a graph is compiled for streaming
m_e.addPassStage("meta");
if (!m_metas.empty())
@@ -384,6 +389,9 @@ cv::gimpl::GCompiler::GPtr cv::gimpl::GCompiler::generateGraph()
{
GModel::Graph(*g).metadata().set(OriginalInputMeta{m_metas});
}
// FIXME: remove m_args, remove GCompileArgs from backends' method signatures,
// rework backends to access GCompileArgs from graph metadata
GModel::Graph(*g).metadata().set(CompileArgs{m_args});
return g;
}
+18 -2
View File
@@ -175,13 +175,26 @@ void GIslandModel::generateInitial(GIslandModel::Graph &g,
{
auto src_data_nh = in_edge->srcNode();
auto isl_slot_nh = data_to_slot.at(src_data_nh);
g.link(isl_slot_nh, nh); // no other data stored yet
auto isl_new_eh = g.link(isl_slot_nh, nh); // no other data stored yet
// Propagate some special metadata from the GModel to GIslandModel
// TODO: Make it a single place (a function) for both inputs/outputs?
// (since it is duplicated in the below code block)
if (src_g.metadata(in_edge).contains<DesyncEdge>())
{
const auto idx = src_g.metadata(in_edge).get<DesyncEdge>().index;
g.metadata(isl_new_eh).set(DesyncIslEdge{idx});
}
}
for (auto out_edge : src_op_nh->outEdges())
{
auto dst_data_nh = out_edge->dstNode();
auto isl_slot_nh = data_to_slot.at(dst_data_nh);
g.link(nh, isl_slot_nh);
auto isl_new_eh = g.link(nh, isl_slot_nh);
if (src_g.metadata(out_edge).contains<DesyncEdge>())
{
const auto idx = src_g.metadata(out_edge).get<DesyncEdge>().index;
g.metadata(isl_new_eh).set(DesyncIslEdge{idx});
}
}
} // for(all_operations)
}
@@ -254,6 +267,9 @@ void GIslandModel::syncIslandTags(Graph &g, ade::Graph &orig_g)
void GIslandModel::compileIslands(Graph &g, const ade::Graph &orig_g, const GCompileArgs &args)
{
GModel::ConstGraph gm(orig_g);
if (gm.metadata().contains<HasIntrinsics>()) {
util::throw_error(std::logic_error("FATAL: The graph has unresolved intrinsics"));
}
auto original_sorted = gm.metadata().get<ade::passes::TopologicalSortData>();
for (auto nh : g.nodes())
@@ -142,6 +142,14 @@ public:
// at that stage.
virtual void handleNewStream() {}; // do nothing here by default
// This method is called for every IslandExecutable when
// the stream-based execution is stopped.
// All processing is guaranteed to be stopped by this moment,
// with no pending or running 'run()' processes ran in background.
// FIXME: This method is tightly bound to the GStreamingExecutor
// now.
virtual void handleStopStream() {} // do nothing here by default
virtual ~GIslandExecutable() = default;
};
@@ -222,8 +230,19 @@ struct IslandsCompiled
static const char *name() { return "IslandsCompiled"; }
};
// This flag marks an edge in an GIslandModel as "desynchronized"
// i.e. it starts a new desynchronized subgraph
struct DesyncIslEdge
{
static const char *name() { return "DesynchronizedIslandEdge"; }
// Projection from GModel/DesyncEdge.index
int index;
};
namespace GIslandModel
{
using Graph = ade::TypedGraph
< NodeKind
, FusedIsland
@@ -232,6 +251,7 @@ namespace GIslandModel
, Emitter
, Sink
, IslandsCompiled
, DesyncIslEdge
, ade::passes::TopologicalSortData
>;
@@ -244,6 +264,7 @@ namespace GIslandModel
, Emitter
, Sink
, IslandsCompiled
, DesyncIslEdge
, ade::passes::TopologicalSortData
>;
+13 -6
View File
@@ -77,7 +77,7 @@ ade::NodeHandle GModel::mkDataNode(GModel::Graph &g, const GShape shape)
return data_h;
}
void GModel::linkIn(Graph &g, ade::NodeHandle opH, ade::NodeHandle objH, std::size_t in_port)
ade::EdgeHandle GModel::linkIn(Graph &g, ade::NodeHandle opH, ade::NodeHandle objH, std::size_t in_port)
{
// Check if input is already connected
for (const auto& in_e : opH->inEdges())
@@ -96,9 +96,11 @@ void GModel::linkIn(Graph &g, ade::NodeHandle opH, ade::NodeHandle objH, std::si
// Replace an API object with a REF (G* -> GOBJREF)
op.args[in_port] = cv::GArg(RcDesc{gm.rc, gm.shape, {}});
return eh;
}
void GModel::linkOut(Graph &g, ade::NodeHandle opH, ade::NodeHandle objH, std::size_t out_port)
ade::EdgeHandle GModel::linkOut(Graph &g, ade::NodeHandle opH, ade::NodeHandle objH, std::size_t out_port)
{
// FIXME: check validity using kernel prototype
@@ -121,6 +123,8 @@ void GModel::linkOut(Graph &g, ade::NodeHandle opH, ade::NodeHandle objH, std::s
const auto min_out_size = std::max(op.outs.size(), storage_with_port);
op.outs.resize(min_out_size, RcDesc{-1,GShape::GMAT,{}}); // FIXME: Invalid shape instead?
op.outs[out_port] = RcDesc{gm.rc, gm.shape, {}};
return eh;
}
std::vector<ade::NodeHandle> GModel::orderedInputs(const ConstGraph &g, ade::NodeHandle nh)
@@ -210,26 +214,29 @@ ade::NodeHandle GModel::detail::dataNodeOf(const ConstLayoutGraph &g, const GOri
return g.metadata().get<Layout>().object_nodes.at(origin);
}
void GModel::redirectReaders(Graph &g, ade::NodeHandle from, ade::NodeHandle to)
std::vector<ade::EdgeHandle> GModel::redirectReaders(Graph &g, ade::NodeHandle from, ade::NodeHandle to)
{
std::vector<ade::EdgeHandle> ehh(from->outEdges().begin(), from->outEdges().end());
std::vector<ade::EdgeHandle> ohh;
ohh.reserve(ehh.size());
for (auto e : ehh)
{
auto dst = e->dstNode();
auto input = g.metadata(e).get<Input>();
g.erase(e);
linkIn(g, dst, to, input.port);
ohh.push_back(linkIn(g, dst, to, input.port));
}
return ohh;
}
void GModel::redirectWriter(Graph &g, ade::NodeHandle from, ade::NodeHandle to)
ade::EdgeHandle GModel::redirectWriter(Graph &g, ade::NodeHandle from, ade::NodeHandle to)
{
GAPI_Assert(from->inEdges().size() == 1);
auto e = from->inEdges().front();
auto op = e->srcNode();
auto output = g.metadata(e).get<Output>();
g.erase(e);
linkOut(g, op, to, output.port);
return linkOut(g, op, to, output.port);
}
GMetaArgs GModel::collectInputMeta(const GModel::ConstGraph &cg, ade::NodeHandle node)
+66 -4
View File
@@ -211,6 +211,58 @@ struct CustomMetaFunction
CM customOutMeta;
};
// This is a general flag indicating that this GModel has intrinsics.
// In the beginning of the compilation, it is a quick check to
// indicate there are intrinsics.
//
// In the end of the compilation, having this flag is fatal -- all
// intrinsics must be resolved.
struct HasIntrinsics
{
static const char *name() { return "HasIntrinsicsFlag"; }
};
// This is a special tag for both DATA and OP nodes indicating
// which desynchronized path this node belongs to.
// This tag is set by a special complex pass intrinDesync/accept.
struct DesyncPath
{
static const char *name() { return "DesynchronizedPath"; }
// A zero-based index of the desynchronized path in the graph.
// Set by intrinDesync() compiler pass
int index;
};
// This is a special tag for graph Edges indicating that this
// particular edge starts a desynchronized path in the graph.
// At the execution stage, the data coming "through" these edges
// (virtually, of course, since our GModel edges never transfer the
// actual data, they just represent these transfers) is desynchronized
// from the rest of the pipeline, i.e. may be "lost" (stay unconsumed
// and then overwritten with some new data when streaming).
struct DesyncEdge
{
static const char *name() { return "DesynchronizedEdge"; }
// A zero-based index of the desynchronized path in the graph.
// Set by intrinDesync/apply() compiler pass
int index;
};
// This flag marks the island graph as "desynchronized"
struct Desynchronized
{
static const char *name() { return "Desynchronized"; }
};
// Reference to compile args of the computation
struct CompileArgs
{
static const char *name() { return "CompileArgs"; }
GCompileArgs args;
};
namespace GModel
{
using Graph = ade::TypedGraph
@@ -232,6 +284,11 @@ namespace GModel
, CustomMetaFunction
, Streaming
, Deserialized
, HasIntrinsics
, DesyncPath
, DesyncEdge
, Desynchronized
, CompileArgs
>;
// FIXME: How to define it based on GModel???
@@ -254,6 +311,11 @@ namespace GModel
, CustomMetaFunction
, Streaming
, Deserialized
, HasIntrinsics
, DesyncPath
, DesyncEdge
, Desynchronized
, CompileArgs
>;
// FIXME:
@@ -278,11 +340,11 @@ namespace GModel
// Clears logged messages of a node.
GAPI_EXPORTS void log_clear(Graph &g, ade::NodeHandle node);
GAPI_EXPORTS void linkIn (Graph &g, ade::NodeHandle op, ade::NodeHandle obj, std::size_t in_port);
GAPI_EXPORTS void linkOut (Graph &g, ade::NodeHandle op, ade::NodeHandle obj, std::size_t out_port);
GAPI_EXPORTS ade::EdgeHandle linkIn (Graph &g, ade::NodeHandle op, ade::NodeHandle obj, std::size_t in_port);
GAPI_EXPORTS ade::EdgeHandle linkOut (Graph &g, ade::NodeHandle op, ade::NodeHandle obj, std::size_t out_port);
GAPI_EXPORTS void redirectReaders(Graph &g, ade::NodeHandle from, ade::NodeHandle to);
GAPI_EXPORTS void redirectWriter (Graph &g, ade::NodeHandle from, ade::NodeHandle to);
GAPI_EXPORTS std::vector<ade::EdgeHandle> redirectReaders(Graph &g, ade::NodeHandle from, ade::NodeHandle to);
GAPI_EXPORTS ade::EdgeHandle redirectWriter (Graph &g, ade::NodeHandle from, ade::NodeHandle to);
GAPI_EXPORTS std::vector<ade::NodeHandle> orderedInputs (const ConstGraph &g, ade::NodeHandle nh);
GAPI_EXPORTS std::vector<ade::NodeHandle> orderedOutputs(const ConstGraph &g, ade::NodeHandle nh);
+8 -1
View File
@@ -134,12 +134,19 @@ cv::gimpl::Unrolled cv::gimpl::unrollExpr(const GProtoArgs &ins,
// Put the outputs object description of the node
// so that they are not lost if they are not consumed by other operations
GAPI_Assert(call_p.m_k.outCtors.size() == call_p.m_k.outShapes.size());
for (const auto &it : ade::util::indexed(call_p.m_k.outShapes))
{
std::size_t port = ade::util::index(it);
GShape shape = ade::util::value(it);
GOrigin org { shape, node, port, {}, origin.kind };
// FIXME: then use ZIP
HostCtor ctor = call_p.m_k.outCtors[port];
// NB: Probably this fixes all other "missing host ctor"
// problems.
// TODO: Clean-up the old workarounds if it really is.
GOrigin org {shape, node, port, std::move(ctor), origin.kind};
origins.insert(org);
}
+3 -9
View File
@@ -16,15 +16,9 @@ namespace cv
namespace gimpl
{
// Union type for various user-defined type constructors (GArray<T>, GOpaque<T>, etc)
// FIXME: Replace construct-only API with a more generic one
// (probably with bits of introspection)
// Not required for non-user-defined types (GMat, GScalar, etc)
using HostCtor = util::variant
< util::monostate
, detail::ConstructVec
, detail::ConstructOpaque
>;
// HostCtor was there, but then moved to public
// Redeclare here to avoid changing tons of code
using HostCtor = cv::detail::HostCtor;
using ConstVal = util::variant
< util::monostate
+11
View File
@@ -69,6 +69,11 @@ bool cv::GStreamingCompiled::Priv::pull(cv::GRunArgsP &&outs)
return m_exec->pull(std::move(outs));
}
bool cv::GStreamingCompiled::Priv::pull(cv::GOptRunArgsP &&outs)
{
return m_exec->pull(std::move(outs));
}
bool cv::GStreamingCompiled::Priv::try_pull(cv::GRunArgsP &&outs)
{
return m_exec->try_pull(std::move(outs));
@@ -113,6 +118,7 @@ bool cv::GStreamingCompiled::pull(cv::GRunArgsP &&outs)
std::tuple<bool, cv::GRunArgs> cv::GStreamingCompiled::pull()
{
// FIXME: Why it is not @ priv??
GRunArgs run_args;
GRunArgsP outs;
const auto& out_shapes = m_priv->outShapes();
@@ -144,6 +150,11 @@ std::tuple<bool, cv::GRunArgs> cv::GStreamingCompiled::pull()
return std::make_tuple(is_over, run_args);
}
bool cv::GStreamingCompiled::pull(cv::GOptRunArgsP &&outs)
{
return m_priv->pull(std::move(outs));
}
bool cv::GStreamingCompiled::try_pull(cv::GRunArgsP &&outs)
{
return m_priv->try_pull(std::move(outs));
@@ -42,6 +42,7 @@ public:
void setSource(GRunArgs &&args);
void start();
bool pull(cv::GRunArgsP &&outs);
bool pull(cv::GOptRunArgsP &&outs);
bool try_pull(cv::GRunArgsP &&outs);
void stop();
+84 -29
View File
@@ -20,6 +20,7 @@
#include <opencv2/gapi/util/optional.hpp> // util::optional
#include "logger.hpp" // GAPI_LOG
#include "api/gbackend_priv.hpp" // for canMerge()
#include "compiler/gmodel.hpp"
#include "compiler/gislandmodel.hpp"
#include "compiler/passes/passes.hpp"
@@ -54,11 +55,28 @@ namespace
// Also check the cases backend can't handle
// (e.x. GScalar connecting two fluid ops should split the graph)
const GModel::ConstGraph g(src_graph);
if (g.metadata().contains<Desynchronized>()) {
// Fusion of a graph having a desynchronized path is
// definitely non-trivial
return false;
}
const auto& active_backends = g.metadata().get<ActiveBackends>().backends;
return active_backends.size() == 1 &&
ade::util::all_of(g.nodes(), [&](ade::NodeHandle nh) {
return !g.metadata(nh).contains<Island>();
});
if (active_backends.size() != 1u) {
// More than 1 backend involved - non-trivial
return false;
}
const auto& has_island_tags = [&](ade::NodeHandle nh) {
return g.metadata(nh).contains<Island>();
};
if (ade::util::any_of(g.nodes(), has_island_tags)) {
// There are user-defined islands - non-trivial
return false;
}
if (active_backends.begin()->priv().controlsMerge()) {
// If the only backend controls Island Fusion on its own - non-trivial
return false;
}
return true;
}
void fuseTrivial(GIslandModel::Graph &g, const ade::Graph &src_graph)
@@ -125,9 +143,9 @@ namespace
};
bool canMerge(const GIslandModel::Graph &g,
const ade::NodeHandle a_nh,
const ade::NodeHandle /*slot_nh*/,
const ade::NodeHandle b_nh,
const ade::NodeHandle &a_nh,
const ade::NodeHandle &slot_nh,
const ade::NodeHandle &b_nh,
const MergeContext &ctx = MergeContext())
{
auto a_ptr = g.metadata(a_nh).get<FusedIsland>().object;
@@ -142,8 +160,8 @@ namespace
// Islands which cause a cycle can't be merged as well
// (since the flag is set, the procedure already tried to
// merge these islands in the past)
if (ade::util::contains(ctx.cycle_causers, std::make_pair(a_ptr, b_ptr))||
ade::util::contains(ctx.cycle_causers, std::make_pair(b_ptr, a_ptr)))
if ( ade::util::contains(ctx.cycle_causers, std::make_pair(a_ptr, b_ptr))
|| ade::util::contains(ctx.cycle_causers, std::make_pair(b_ptr, a_ptr)))
return false;
// There may be user-defined islands. Initially user-defined
@@ -163,7 +181,13 @@ namespace
return false;
}
// FIXME: add a backend-specified merge checker
// If available, run the backend-specified merge checker
const auto &this_backend_p = a_ptr->backend().priv();
if ( this_backend_p.controlsMerge()
&& !this_backend_p.allowsMerge(g, a_nh, slot_nh, b_nh))
{
return false;
}
return true;
}
@@ -205,10 +229,31 @@ namespace
{
using namespace std::placeholders;
// Before checking for candidates, find and ban neighbor nodes
// (input or outputs) which are connected via desynchronized
// edges.
GIsland::node_set nodes_with_desync_edges;
for (const auto& in_eh : nh->inEdges()) {
if (g.metadata(in_eh).contains<DesyncIslEdge>()) {
nodes_with_desync_edges.insert(in_eh->srcNode());
}
}
for (const auto& output_data_nh : nh->outNodes()) {
for (const auto &out_reader_eh : output_data_nh->outEdges()) {
if (g.metadata(out_reader_eh).contains<DesyncIslEdge>()) {
nodes_with_desync_edges.insert(out_reader_eh->dstNode());
}
}
}
// Find a first matching candidate GIsland for merge
// among inputs
for (const auto& input_data_nh : nh->inNodes())
for (const auto& in_eh : nh->inEdges())
{
if (ade::util::contains(nodes_with_desync_edges, in_eh->srcNode())) {
continue; // desync edges can never be fused
}
const auto& input_data_nh = in_eh->srcNode();
if (input_data_nh->inNodes().size() != 0)
{
// Data node must have a single producer only
@@ -224,14 +269,17 @@ namespace
// Ok, now try to find it among the outputs
for (const auto& output_data_nh : nh->outNodes())
{
auto mergeTest = [&](ade::NodeHandle cons_nh) -> bool {
return canMerge(g, nh, output_data_nh, cons_nh, ctx);
auto mergeTest = [&](ade::EdgeHandle cons_eh) -> bool {
if (ade::util::contains(nodes_with_desync_edges, cons_eh->dstNode())) {
return false; // desync edges can never be fused
}
return canMerge(g, nh, output_data_nh, cons_eh->dstNode(), ctx);
};
auto cand_it = std::find_if(output_data_nh->outNodes().begin(),
output_data_nh->outNodes().end(),
auto cand_it = std::find_if(output_data_nh->outEdges().begin(),
output_data_nh->outEdges().end(),
mergeTest);
if (cand_it != output_data_nh->outNodes().end())
return std::make_tuple(*cand_it,
if (cand_it != output_data_nh->outEdges().end())
return std::make_tuple((*cand_it)->dstNode(),
output_data_nh,
Direction::Out);
} // for(outNodes)
@@ -251,6 +299,7 @@ namespace
ade::NodeHandle m_slot;
ade::NodeHandle m_cons;
using Change = ChangeT<DesyncIslEdge>;
Change::List m_changes;
struct MergeObjects
@@ -423,10 +472,10 @@ namespace
auto backend = m_gim.metadata(m_prod).get<FusedIsland>()
.object->backend();
auto merged = std::make_shared<GIsland>(backend,
std::move(mo.all),
std::move(mo.in_ops),
std::move(mo.out_ops),
std::move(maybe_user_tag));
std::move(mo.all),
std::move(mo.in_ops),
std::move(mo.out_ops),
std::move(maybe_user_tag));
// FIXME: move this debugging to some user-controllable log-level
#ifdef DEBUG_MERGE
merged->debug();
@@ -440,7 +489,9 @@ namespace
m_prod->inEdges().end());
for (auto in_edge : input_edges)
{
m_changes.enqueue<Change::NewLink>(m_g, in_edge->srcNode(), new_nh);
// FIXME: Introduce a Relink primitive instead?
// (combining the both actions into one?)
m_changes.enqueue<Change::NewLink>(m_g, in_edge->srcNode(), new_nh, in_edge);
m_changes.enqueue<Change::DropLink>(m_g, m_prod, in_edge);
}
@@ -450,7 +501,7 @@ namespace
m_cons->outEdges().end());
for (auto out_edge : output_edges)
{
m_changes.enqueue<Change::NewLink>(m_g, new_nh, out_edge->dstNode());
m_changes.enqueue<Change::NewLink>(m_g, new_nh, out_edge->dstNode(), out_edge);
m_changes.enqueue<Change::DropLink>(m_g, m_cons, out_edge);
}
@@ -491,6 +542,10 @@ namespace
m_changes.enqueue<Change::DropLink>(m_g, non_opt_slot_nh, eh);
}
}
// FIXME: No metadata copied here (from where??)
// For DesyncIslEdges it still works, as these tags are
// placed to Data->Op edges and this one is an Op->Data
// edge.
m_changes.enqueue<Change::NewLink>(m_g, new_nh, non_opt_slot_nh);
}
@@ -502,7 +557,7 @@ namespace
m_prod->outEdges().end());
for (auto extra_out : prod_extra_out_edges)
{
m_changes.enqueue<Change::NewLink>(m_g, new_nh, extra_out->dstNode());
m_changes.enqueue<Change::NewLink>(m_g, new_nh, extra_out->dstNode(), extra_out);
m_changes.enqueue<Change::DropLink>(m_g, m_prod, extra_out);
}
@@ -514,7 +569,7 @@ namespace
m_cons->inEdges().end());
for (auto extra_in : cons_extra_in_edges)
{
m_changes.enqueue<Change::NewLink>(m_g, extra_in->srcNode(), new_nh);
m_changes.enqueue<Change::NewLink>(m_g, extra_in->srcNode(), new_nh, extra_in);
m_changes.enqueue<Change::DropLink>(m_g, m_cons, extra_in);
}
@@ -557,10 +612,10 @@ namespace
there_was_a_merge = false;
// FIXME: move this debugging to some user-controllable log level
#ifdef DEBUG_MERGE
#ifdef DEBUG_MERGE
GAPI_LOG_INFO(NULL, "Before next merge attempt " << iteration << "...");
merge_debug(g, iteration);
#endif
#endif
iteration++;
auto sorted = pass_helpers::topoSort(im);
for (auto nh : sorted)
@@ -600,9 +655,9 @@ namespace
"merge(" << l_obj->name() << "," << r_obj->name() <<
") was successful!");
action.commit();
#ifdef DEBUG_MERGE
#ifdef DEBUG_MERGE
GIslandModel::syncIslandTags(gim, g);
#endif
#endif
there_was_a_merge = true;
break; // start do{}while from the beginning
}
+305
View File
@@ -0,0 +1,305 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2020 Intel Corporation
#include "precomp.hpp"
#include <ade/util/algorithm.hpp>
#include <ade/util/zip_range.hpp>
#include <opencv2/gapi/streaming/desync.hpp>// GDesync intrinsic
#include "compiler/gmodel.hpp"
#include "compiler/passes/passes.hpp"
namespace desync {
namespace {
// Drop the desynchronized node `nh` from the graph, reconnect the
// graph structure properly. This is a helper function which is used
// in both drop(g) and apply(g) passes.
//
// @return a vector of new edge handles connecting the "main" graph
// with its desynchronized part.
std::vector<ade::EdgeHandle> drop(cv::gimpl::GModel::Graph &g,
ade::NodeHandle nh) {
using namespace cv::gimpl;
// What we need to do here:
// 1. Connect the readers of its produced data objects
// to the input data objects of desync;
// 2. Drop the data object it produces.
// 3. Drop the desync operation itself;
std::vector<ade::NodeHandle> in_data_objs = GModel::orderedInputs(g, nh);
std::vector<ade::NodeHandle> out_data_objs = GModel::orderedOutputs(g, nh);
std::vector<ade::EdgeHandle> new_links;
GAPI_Assert(in_data_objs.size() == out_data_objs.size());
GAPI_DbgAssert(ade::util::all_of
(out_data_objs,
[&](const ade::NodeHandle &oh) {
return g.metadata(oh).contains<Data>();
}));
// (1)
for (auto &&it: ade::util::zip(ade::util::toRange(in_data_objs),
ade::util::toRange(out_data_objs))) {
auto these_new_links = GModel::redirectReaders(g,
std::get<1>(it),
std::get<0>(it));
new_links.insert(new_links.end(),
these_new_links.begin(),
these_new_links.end());
}
// (2)
for (auto &&old_out_nh : out_data_objs) {
g.erase(old_out_nh);
}
// (3)
g.erase(nh);
return new_links;
}
// Tracing a desynchronizing subgraph is somewhat tricky and happens
// in both directions: downwards and upwards.
//
// The downward process is the basic one: we start with a "desync"
// OP node and go down to the graph using the "output" edges. We check
// if all nodes on this path [can] belong to this desynchronized path
// and don't overlap with others.
//
// An important contract to maintain is that the desynchronized part
// can't have any input references from the "main" graph part or any
// other desynchronized part in the graph. This contract is validated
// by checking every node's input which must belong to the same
// desynchronized part.
//
// Here is the pitfall of this check:
//
// v
// GMat_0
// v
// +----------+
// | desync() | <- This point originates the traceDown process
// +----------+
// v
// GMat_0' <- This node will be tagged for this desync at
// :--------. step 0/1
// v : <- The order how output nodes are visited is not
// +----------+ : specified, we can visit Op2() first (as there
// | Op1() | : is a direct link) bypassing visiting and tagging
// +----------+ : Op1() and GMat_1
// v :
// GMat_1 :
// : .---'
// v v <- When we visit Op2() via the 2nd edge on this
// +----------+ graph, we check if all inputs belong to the same
// | Op2() | desynchronized graph and GMat_1 fails this check
// +----------+ (since the traceDown() process haven't visited
// it yet).
//
// Cases like this originate the traceUp() process: if we find an
// input node in our desynchronized path which doesn't belong to this
// path YET, it is not 100% a problem, and we need to trace it back
// (upwards) to see if it is really a case.
// This recursive function checks the desync_id in the graph upwards.
// The process doesn't continue for nodes which have a valid
// desync_id already.
// The process only continues for nodes which have no desync_id
// assigned. If there's no such nodes anymore, the procedure is
// considered complete and a list of nodes to tag is returned to the
// caller.
//
// If NO inputs of this node have a valid desync_id, the desync
// invariant is broken and the function throws.
void traceUp(cv::gimpl::GModel::Graph &g,
const ade::NodeHandle &nh,
int desync_id,
std::vector<ade::NodeHandle> &path) {
using namespace cv::gimpl;
GAPI_Assert(!nh->inNodes().empty()
&& "traceUp: a desynchronized part of the graph is not isolated?");
if (g.metadata(nh).contains<DesyncPath>()) {
// We may face nodes which have DesyncPath already visited during
// this recursive process (e.g. via some other output or branch in the
// subgraph)
if (g.metadata(nh).get<DesyncPath>().index != desync_id) {
GAPI_Assert(false && "Desynchronization can't be nested!");
}
return; // This object belongs to the desync path - exit early.
}
// Regardless of the result, put this nh to the path
path.push_back(nh);
// Check if the input nodes are OK
std::vector<ade::NodeHandle> nodes_to_trace;
nodes_to_trace.reserve(nh->inNodes().size());
for (auto &&in_nh : nh->inNodes()) {
if (g.metadata(in_nh).contains<DesyncPath>()) {
// We may face nodes which have DesyncPath already visited during
// this recursive process (e.g. via some other output or branch in the
// subgraph)
GAPI_Assert(g.metadata(in_nh).get<DesyncPath>().index == desync_id
&& "Desynchronization can't be nested!");
} else {
nodes_to_trace.push_back(in_nh);
}
}
// If there are nodes to trace, continue the recursion
for (auto &&up_nh : nodes_to_trace) {
traceUp(g, up_nh, desync_id, path);
}
}
// This recursive function propagates the desync_id down to the graph
// starting at nh, and also checks:
// - if this desync path is isolated;
// - if this desync path is not overlapped.
// It also originates the traceUp() process at the points of
// uncertainty (as described in the comment above).
void traceDown(cv::gimpl::GModel::Graph &g,
const ade::NodeHandle &nh,
int desync_id) {
using namespace cv::gimpl;
if (g.metadata(nh).contains<DesyncPath>()) {
// We may face nodes which have DesyncPath already visited during
// this recursive process (e.g. via some other output or branch in the
// subgraph)
GAPI_Assert(g.metadata(nh).get<DesyncPath>().index == desync_id
&& "Desynchronization can't be nested!");
} else {
g.metadata(nh).set(DesyncPath{desync_id});
}
// All inputs of this data object must belong to the same
// desync path.
for (auto &&in_nh : nh->inNodes()) {
// If an input object is not assigned to this desync path,
// it does not means that the object doesn't belong to
// this path. Check it.
std::vector<ade::NodeHandle> path_up;
traceUp(g, in_nh, desync_id, path_up);
// We get here on success. Just set the proper tags for
// the identified input path.
for (auto &&up_nh : path_up) {
g.metadata(up_nh).set(DesyncPath{desync_id});
}
}
// Propagate the tag & check down
for (auto &&out_nh : nh->outNodes()) {
traceDown(g, out_nh, desync_id);
}
}
// Streaming case: ensure the graph has proper isolation of the
// desynchronized parts, set proper Edge metadata hints for
// GStreamingExecutable
void apply(cv::gimpl::GModel::Graph &g) {
using namespace cv::gimpl;
// Stage 0. Trace down the desync operations in the graph.
// Tag them with their unique (per graph) identifiers.
int total_desync = 0;
for (auto &&nh : g.nodes()) {
if (g.metadata(nh).get<NodeType>().t == NodeType::OP) {
const auto &op = g.metadata(nh).get<Op>();
if (op.k.name == cv::gapi::streaming::detail::GDesync::id()) {
GAPI_Assert(!g.metadata(nh).contains<DesyncPath>()
&& "Desynchronization can't be nested!");
const int this_desync_id = total_desync++;
g.metadata(nh).set(DesyncPath{this_desync_id});
for (auto &&out_nh: nh->outNodes()) {
traceDown(g, out_nh, this_desync_id);
}
} // if (desync)
} // if(OP)
} // for(nodes)
// Tracing is done for all desync ops in the graph now.
// Stage 1. Drop the desync operations from the graph, but mark
// the desynchronized edges a special way.
// The desynchronized edge is the edge which connects a main
// subgraph data with a desynchronized subgraph data.
std::vector<ade::NodeHandle> nodes(g.nodes().begin(), g.nodes().end());
for (auto &&nh : nodes) {
if (nh == nullptr) {
// Some nodes could be dropped already during the procedure
// thanks ADE their NodeHandles updated automatically
continue;
}
if (g.metadata(nh).get<NodeType>().t == NodeType::OP) {
const auto &op = g.metadata(nh).get<Op>();
if (op.k.name == cv::gapi::streaming::detail::GDesync::id()) {
auto index = g.metadata(nh).get<DesyncPath>().index;
auto new_links = drop(g, nh);
for (auto &&eh : new_links) {
g.metadata(eh).set(DesyncEdge{index});
}
} // if (desync)
} // if (Op)
} // for(nodes)
// Stage 2. Put a synchronized tag if there were changes applied
if (total_desync > 0) {
g.metadata().set(Desynchronized{});
}
}
// Probably the simplest case: desync makes no sense in the regular
// compilation process, so just drop all its occurences in the graph,
// reconnecting nodes properly.
void drop(cv::gimpl::GModel::Graph &g) {
// FIXME: LOG here that we're dropping the desync operations as
// they have no sense when compiling in the regular mode.
using namespace cv::gimpl;
std::vector<ade::NodeHandle> nodes(g.nodes().begin(), g.nodes().end());
for (auto &&nh : nodes) {
if (nh == nullptr) {
// Some nodes could be dropped already during the procedure
// thanks ADE their NodeHandles updated automatically
continue;
}
if (g.metadata(nh).get<NodeType>().t == NodeType::OP) {
const auto &op = g.metadata(nh).get<Op>();
if (op.k.name == cv::gapi::streaming::detail::GDesync::id()) {
drop(g, nh);
} // if (desync)
} // if (Op)
} // for(nodes)
}
} // anonymous namespace
} // namespace desync
void cv::gimpl::passes::intrinDesync(ade::passes::PassContext &ctx) {
GModel::Graph gr(ctx.graph);
if (!gr.metadata().contains<HasIntrinsics>())
return;
gr.metadata().contains<Streaming>()
? desync::apply(gr) // Streaming compilation
: desync::drop(gr); // Regular compilation
}
// Clears the HasIntrinsics flag if all intrinsics have been handled.
void cv::gimpl::passes::intrinFinalize(ade::passes::PassContext &ctx) {
GModel::Graph gr(ctx.graph);
for (auto &&nh : gr.nodes()) {
if (gr.metadata(nh).get<NodeType>().t == NodeType::OP) {
const auto &op = gr.metadata(nh).get<Op>();
if (is_intrinsic(op.k.name)) {
return;
}
}
}
// If reached here, really clear the flag
gr.metadata().erase<HasIntrinsics>();
}
+46 -2
View File
@@ -14,6 +14,7 @@
#include <opencv2/gapi/gcompoundkernel.hpp> // compound::backend()
#include <opencv2/gapi/gkernel.hpp> // GKernelPackage
#include <opencv2/gapi/infer.hpp> // GNetPackage
#include <opencv2/gapi/streaming/desync.hpp>// GDesync intrinsic
#include "compiler/gmodel.hpp"
#include "compiler/passes/passes.hpp"
@@ -24,6 +25,20 @@
#include "logger.hpp" // GAPI_LOG
#include "api/gproto_priv.hpp" // is_dynamic, rewrap
namespace
{
// FIXME: This may be not the right design choice, but so far it works
const std::vector<std::string> known_intrinsics = {
cv::gapi::streaming::detail::GDesync::id()
};
}
bool cv::gimpl::is_intrinsic(const std::string &s) {
// FIXME: This search might be better in time once we start using string
return std::find(known_intrinsics.begin(),
known_intrinsics.end(),
s) != known_intrinsics.end();
}
namespace
{
struct ImplInfo
@@ -130,8 +145,13 @@ void cv::gimpl::passes::bindNetParams(ade::passes::PassContext &ctx,
}
}
// This pass, given the kernel package, selects a kernel implementation
// for every operation in the graph
// This pass, given the kernel package, selects a kernel
// implementation for every operation in the graph
//
// Starting OpenCV 4.3, G-API may have some special "intrinsic"
// operations. Those can be implemented by backends as regular
// kernels, but if not, they are handled by the framework itself in
// its optimization/execution passes.
void cv::gimpl::passes::resolveKernels(ade::passes::PassContext &ctx,
const gapi::GKernelPackage &kernels)
{
@@ -142,7 +162,25 @@ void cv::gimpl::passes::resolveKernels(ade::passes::PassContext &ctx,
{
if (gr.metadata(nh).get<NodeType>().t == NodeType::OP)
{
// If the operation is known to be intrinsic and is NOT
// implemented in the package, just skip it - there should
// be some pass which handles it.
auto &op = gr.metadata(nh).get<Op>();
if (is_intrinsic(op.k.name) && !kernels.includesAPI(op.k.name)) {
gr.metadata().set(HasIntrinsics{});
continue;
}
// FIXME: And this logic is terribly wrong. The right
// thing is to assign an intrinsic to a particular island
// if and only if it is:
// (a) surrounded by nodes of backend X, AND
// (b) is supported by backend X.
// Here we may have multiple backends supporting an
// intrinsic but only one of those gets selected. And
// this is exactly a situation we need multiple versions
// of the same kernel to be presented in the kernel
// package (as it was designed originally).
cv::gapi::GBackend selected_backend;
cv::GKernelImpl selected_impl;
std::tie(selected_backend, selected_impl) = kernels.lookup(op.k.name);
@@ -181,6 +219,12 @@ void cv::gimpl::passes::expandKernels(ade::passes::PassContext &ctx, const gapi:
if (gr.metadata(nh).get<NodeType>().t == NodeType::OP)
{
const auto& op = gr.metadata(nh).get<Op>();
// FIXME: Essentially the same problem as in the above resolveKernels
if (is_intrinsic(op.k.name) && !kernels.includesAPI(op.k.name)) {
// Note: There's no need to set HasIntrinsics flag here
// since resolveKernels would do it later.
continue;
}
cv::gapi::GBackend selected_backend;
cv::GKernelImpl selected_impl;
+8 -1
View File
@@ -31,7 +31,11 @@ namespace gapi {
struct GNetPackage;
} // namespace gapi
namespace gimpl { namespace passes {
namespace gimpl {
bool is_intrinsic(const std::string &op_name);
namespace passes {
void dumpDot(const ade::Graph &g, std::ostream& os);
void dumpDot(ade::passes::PassContext &ctx, std::ostream& os);
@@ -66,6 +70,9 @@ void applyTransformations(ade::passes::PassContext &ctx,
void addStreaming(ade::passes::PassContext &ctx);
void intrinDesync(ade::passes::PassContext &ctx);
void intrinFinalize(ade::passes::PassContext &ctx);
}} // namespace gimpl::passes
} // namespace cv
+67 -14
View File
@@ -14,6 +14,7 @@
#include <ade/graph.hpp>
#include "opencv2/gapi/util/util.hpp" // Seq
#include "opencv2/gapi/own/assert.hpp"
enum class Direction: int {Invalid, In, Out};
@@ -21,8 +22,50 @@ enum class Direction: int {Invalid, In, Out};
////////////////////////////////////////////////////////////////////////////
////
// TODO: Probably it can be moved to ADE
template<class H, class... Metatypes>
class Preserved
{
using S = typename cv::detail::MkSeq<sizeof...(Metatypes)>::type;
std::tuple<cv::util::optional<Metatypes>...> m_data;
namespace Change
template<class T>
cv::util::optional<T> get(ade::ConstTypedGraph<Metatypes...> g, H h) {
return g.metadata(h).template contains<T>()
? cv::util::make_optional(g.metadata(h).template get<T>())
: cv::util::optional<T>{};
}
template<std::size_t Id>
int set(ade::TypedGraph<Metatypes...> &g, H &h) {
const auto &opt = std::get<Id>(m_data);
if (opt.has_value())
g.metadata(h).set(opt.value());
return 0;
}
template<int... IIs>
void copyTo_impl(ade::TypedGraph<Metatypes...> &g, H h, cv::detail::Seq<IIs...>) {
int unused[] = {0, set<IIs>(g, h)...};
(void) unused;
}
public:
Preserved(const ade::Graph &g, H h) {
ade::ConstTypedGraph<Metatypes...> tg(g);
m_data = std::make_tuple(get<Metatypes>(tg, h)...);
}
void copyTo(ade::Graph &g, H h) {
ade::TypedGraph<Metatypes...> tg(g);
copyTo_impl(tg, h, S{});
}
};
// Do nothing if there's no metadata
template<class H>
class Preserved<H> {
public:
Preserved(const ade::Graph &, H) {}
void copyTo(ade::Graph &, H) {}
};
template<class... Metatypes>
struct ChangeT
{
struct Base
{
@@ -31,6 +74,8 @@ namespace Change
virtual ~Base() = default;
};
template<typename H> using Preserved = ::Preserved<H, Metatypes...>;
class NodeCreated final: public Base
{
ade::NodeHandle m_node;
@@ -39,11 +84,7 @@ namespace Change
virtual void rollback(ade::Graph &g) override { g.erase(m_node); }
};
// NB: Drops all metadata stored in the EdgeHandle,
// which is not restored even in the rollback
// FIXME: either add a way for users to preserve meta manually
// or extend ADE to manipulate with meta such way
// FIXME: maybe extend ADE to clone/copy the whole metadata?
class DropLink final: public Base
{
ade::NodeHandle m_node;
@@ -51,13 +92,15 @@ namespace Change
ade::NodeHandle m_sibling;
Preserved<ade::EdgeHandle> m_meta;
public:
DropLink(ade::Graph &g,
const ade::NodeHandle &node,
const ade::EdgeHandle &edge)
: m_node(node), m_dir(node == edge->srcNode()
? Direction::Out
: Direction::In)
: m_node(node)
, m_dir(node == edge->srcNode() ? Direction::Out : Direction::In)
, m_meta(g, edge)
{
m_sibling = (m_dir == Direction::In
? edge->srcNode()
@@ -67,12 +110,17 @@ namespace Change
virtual void rollback(ade::Graph &g) override
{
// FIXME: Need to preserve metadata here!
// GIslandModel edges now have metadata
ade::EdgeHandle eh;
switch(m_dir)
{
case Direction::In: g.link(m_sibling, m_node); break;
case Direction::Out: g.link(m_node, m_sibling); break;
case Direction::In: eh = g.link(m_sibling, m_node); break;
case Direction::Out: eh = g.link(m_node, m_sibling); break;
default: GAPI_Assert(false);
}
GAPI_Assert(eh != nullptr);
m_meta.copyTo(g, eh);
}
};
@@ -82,10 +130,15 @@ namespace Change
public:
NewLink(ade::Graph &g,
const ade::NodeHandle &prod,
const ade::NodeHandle &cons)
const ade::NodeHandle &prod,
const ade::NodeHandle &cons,
const ade::EdgeHandle &copy_from = ade::EdgeHandle())
: m_edge(g.link(prod, cons))
{
if (copy_from != nullptr)
{
Preserved<ade::EdgeHandle>(g, copy_from).copyTo(g, m_edge);
}
}
virtual void rollback(ade::Graph &g) override
@@ -141,7 +194,7 @@ namespace Change
}
}
};
} // namespace Change
}; // struct Change
////////////////////////////////////////////////////////////////////////////
#endif // OPENCV_GAPI_COMPILER_TRANSACTIONS_HPP