diff --git a/modules/gapi/CMakeLists.txt b/modules/gapi/CMakeLists.txt index a5241b6da5..cd12f904af 100644 --- a/modules/gapi/CMakeLists.txt +++ b/modules/gapi/CMakeLists.txt @@ -37,6 +37,7 @@ file(GLOB gapi_ext_hdrs "${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/own/*.hpp" "${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/render/*.hpp" "${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/streaming/*.hpp" + "${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/plaidml/*.hpp" "${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/util/*.hpp" ) @@ -109,6 +110,10 @@ set(gapi_srcs src/backends/render/grenderocvbackend.cpp src/backends/render/grenderocv.cpp + #PlaidML Backend + src/backends/plaidml/gplaidmlcore.cpp + src/backends/plaidml/gplaidmlbackend.cpp + # Compound src/backends/common/gcompoundbackend.cpp src/backends/common/gcompoundkernel.cpp @@ -138,5 +143,12 @@ if(TARGET opencv_test_gapi) target_link_libraries(opencv_test_gapi PRIVATE ade) endif() +if(HAVE_PLAIDML) + ocv_target_compile_definitions(opencv_gapi PRIVATE -DHAVE_PLAIDML) + ocv_target_compile_definitions(opencv_test_gapi PRIVATE -DHAVE_PLAIDML) + ocv_target_link_libraries(opencv_gapi LINK_PRIVATE ${PLAIDML_LIBRARIES}) + ocv_target_include_directories(opencv_gapi SYSTEM PRIVATE ${PLAIDML_INCLUDE_DIRS}) +endif() + ocv_add_perf_tests() ocv_add_samples() diff --git a/modules/gapi/cmake/init.cmake b/modules/gapi/cmake/init.cmake index 12e22120e1..026adf3e7c 100644 --- a/modules/gapi/cmake/init.cmake +++ b/modules/gapi/cmake/init.cmake @@ -1,10 +1,11 @@ OCV_OPTION(WITH_ADE "Enable ADE framework (required for Graph API module)" ON) +OCV_OPTION(WITH_PLAIDML "Include PlaidML2 support" OFF) if(NOT WITH_ADE) return() endif() -if (ade_DIR) +if(ade_DIR) # if ade_DIR is set, use ADE-supplied CMake script # to set up variables to the prebuilt ADE find_package(ade 0.1.0) @@ -15,3 +16,10 @@ if(NOT TARGET ade) # downloaded one (if there any) include("${CMAKE_CURRENT_LIST_DIR}/DownloadADE.cmake") endif() + +if(WITH_PLAIDML) + find_package(PlaidML2 CONFIG QUIET) + if (PLAIDML_FOUND) + set(HAVE_PLAIDML TRUE) + endif() +endif() diff --git a/modules/gapi/include/opencv2/gapi/plaidml/core.hpp b/modules/gapi/include/opencv2/gapi/plaidml/core.hpp new file mode 100644 index 0000000000..47ac486e5c --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/plaidml/core.hpp @@ -0,0 +1,20 @@ +// 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) 2019 Intel Corporation + + +#ifndef OPENCV_GAPI_PLAIDML_CORE_HPP +#define OPENCV_GAPI_PLAIDML_CORE_HPP + +#include // GKernelPackage +#include // GAPI_EXPORTS + +namespace cv { namespace gapi { namespace core { namespace plaidml { + +GAPI_EXPORTS GKernelPackage kernels(); + +}}}} + +#endif // OPENCV_GAPI_PLAIDML_CORE_HPP diff --git a/modules/gapi/include/opencv2/gapi/plaidml/gplaidmlkernel.hpp b/modules/gapi/include/opencv2/gapi/plaidml/gplaidmlkernel.hpp new file mode 100644 index 0000000000..7ce00cfa35 --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/plaidml/gplaidmlkernel.hpp @@ -0,0 +1,140 @@ +// 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) 2019 Intel Corporation +// + + +#ifndef OPENCV_GAPI_GPLAIDMLKERNEL_HPP +#define OPENCV_GAPI_GPLAIDMLKERNEL_HPP + +#include +#include + +namespace plaidml +{ +namespace edsl +{ + class Tensor; +} // namespace edsl +} // namespace plaidml + +namespace cv +{ +namespace gapi +{ +namespace plaidml +{ + +GAPI_EXPORTS cv::gapi::GBackend backend(); + +} // namespace plaidml +} // namespace gapi + +struct GPlaidMLContext +{ + // Generic accessor API + template + const T& inArg(int input) { return m_args.at(input).get(); } + + // Syntax sugar + const plaidml::edsl::Tensor& inTensor(int input) + { + return inArg(input); + } + + plaidml::edsl::Tensor& outTensor(int output) + { + return *(m_results.at(output).get()); + } + + std::vector m_args; + std::unordered_map m_results; +}; + +class GAPI_EXPORTS GPlaidMLKernel +{ +public: + using F = std::function; + + GPlaidMLKernel() = default; + explicit GPlaidMLKernel(const F& f) : m_f(f) {}; + + void apply(GPlaidMLContext &ctx) const + { + GAPI_Assert(m_f); + m_f(ctx); + } + +protected: + F m_f; +}; + + +namespace detail +{ + +template struct plaidml_get_in; +template<> struct plaidml_get_in +{ + static const plaidml::edsl::Tensor& get(GPlaidMLContext& ctx, int idx) + { + return ctx.inTensor(idx); + } +}; + +template struct plaidml_get_in +{ + static T get(GPlaidMLContext &ctx, int idx) { return ctx.inArg(idx); } +}; + +template struct plaidml_get_out; +template<> struct plaidml_get_out +{ + static plaidml::edsl::Tensor& get(GPlaidMLContext& ctx, int idx) + { + return ctx.outTensor(idx); + } +}; + +template +struct PlaidMLCallHelper; + +template +struct PlaidMLCallHelper, std::tuple > +{ + template + static void call_impl(GPlaidMLContext &ctx, detail::Seq, detail::Seq) + { + Impl::run(plaidml_get_in::get(ctx, IIs)..., plaidml_get_out::get(ctx, OIs)...); + } + + static void call(GPlaidMLContext& ctx) + { + call_impl(ctx, + typename detail::MkSeq::type(), + typename detail::MkSeq::type()); + } +}; + +} // namespace detail + +template +class GPlaidMLKernelImpl: public cv::detail::PlaidMLCallHelper, + public cv::detail::KernelTag +{ + using P = detail::PlaidMLCallHelper; + +public: + using API = K; + + static cv::gapi::GBackend backend() { return cv::gapi::plaidml::backend(); } + static cv::GPlaidMLKernel kernel() { return GPlaidMLKernel(&P::call); } +}; + +#define GAPI_PLAIDML_KERNEL(Name, API) struct Name: public cv::GPlaidMLKernelImpl + +} // namespace cv + +#endif // OPENCV_GAPI_GPLAIDMLKERNEL_HPP diff --git a/modules/gapi/include/opencv2/gapi/plaidml/plaidml.hpp b/modules/gapi/include/opencv2/gapi/plaidml/plaidml.hpp new file mode 100644 index 0000000000..f35f591676 --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/plaidml/plaidml.hpp @@ -0,0 +1,40 @@ +// 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) 2019 Intel Corporation + + +#ifndef OPENCV_GAPI_PLAIDML_PLAIDML_HPP +#define OPENCV_GAPI_PLAIDML_PLAIDML_HPP + +#include +#include // CompileArgTag + +namespace cv +{ +namespace gapi +{ +namespace plaidml +{ + +struct config +{ + std::string dev_id; + std::string trg_id; +}; + +} // namespace plaidml +} // namespace gapi + +namespace detail +{ + template<> struct CompileArgTag + { + static const char* tag() { return "gapi.plaidml.config"; } + }; +} // namespace detail + +} // namespace cv + +#endif // OPENCV_GAPI_PLAIDML_PLAIDML_HPP diff --git a/modules/gapi/src/api/gbackend.cpp b/modules/gapi/src/api/gbackend.cpp index c88d58116e..6c76b6e393 100644 --- a/modules/gapi/src/api/gbackend.cpp +++ b/modules/gapi/src/api/gbackend.cpp @@ -40,6 +40,16 @@ cv::gapi::GBackend::Priv::compile(const ade::Graph&, return {}; } +std::unique_ptr +cv::gapi::GBackend::Priv::compile(const ade::Graph& graph, + const GCompileArgs& args, + const std::vector& nodes, + const std::vector&, + const std::vector&) const +{ + return compile(graph, args, nodes); +} + void cv::gapi::GBackend::Priv::addBackendPasses(ade::ExecutionEngineSetupContext &) { // Do nothing by default, plugins may override this to diff --git a/modules/gapi/src/api/gbackend_priv.hpp b/modules/gapi/src/api/gbackend_priv.hpp index afc77b2163..38e35f343e 100644 --- a/modules/gapi/src/api/gbackend_priv.hpp +++ b/modules/gapi/src/api/gbackend_priv.hpp @@ -18,6 +18,9 @@ #include "opencv2/gapi/gcommon.hpp" #include "opencv2/gapi/gkernel.hpp" +#include "compiler/gmodel.hpp" + + namespace cv { namespace gimpl @@ -41,10 +44,18 @@ public: // there's no need in having both cv::gimpl::GBackend // and cv::gapi::GBackend - these two things can be unified // NOTE - nodes are guaranteed to be topologically sorted. + + // NB: This method is deprecated virtual EPtr compile(const ade::Graph &graph, const GCompileArgs &args, const std::vector &nodes) const; + virtual EPtr compile(const ade::Graph &graph, + const GCompileArgs &args, + const std::vector &nodes, + const std::vector& ins_data, + const std::vector& outs_data) const; + virtual void addBackendPasses(ade::ExecutionEngineSetupContext &); virtual cv::gapi::GKernelPackage auxiliaryKernels() const; diff --git a/modules/gapi/src/backends/plaidml/gplaidmlbackend.cpp b/modules/gapi/src/backends/plaidml/gplaidmlbackend.cpp new file mode 100644 index 0000000000..4de99a1af2 --- /dev/null +++ b/modules/gapi/src/backends/plaidml/gplaidmlbackend.cpp @@ -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) 2019 Intel Corporation + + +#ifdef HAVE_PLAIDML + +#include "precomp.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "compiler/gobjref.hpp" +#include "compiler/gmodel.hpp" + +#include "backends/plaidml/gplaidmlbackend.hpp" +#include "backends/plaidml/plaidml_util.hpp" + +#include "api/gbackend_priv.hpp" // FIXME: Make it part of Backend SDK! + +using GPlaidMLModel = ade::TypedGraph + < cv::gimpl::PlaidMLUnit + , cv::gimpl::Protocol + >; + +// FIXME: Same issue with Typed and ConstTyped +using GConstGPlaidMLModel = ade::ConstTypedGraph + < cv::gimpl::PlaidMLUnit + , cv::gimpl::Protocol + >; + +namespace +{ + class GPlaidMLBackendImpl final: public cv::gapi::GBackend::Priv + { + virtual void unpackKernel(ade::Graph &graph, + const ade::NodeHandle &op_node, + const cv::GKernelImpl &impl) override + { + GPlaidMLModel gm(graph); + auto plaidml_impl = cv::util::any_cast(impl.opaque); + gm.metadata(op_node).set(cv::gimpl::PlaidMLUnit{plaidml_impl}); + } + + virtual EPtr compile(const ade::Graph& graph, + const cv::GCompileArgs& args, + const std::vector& nodes, + const std::vector& ins_data, + const std::vector& outs_data) const override + { + auto has_config = cv::gimpl::getCompileArg(args); + + if (!has_config) + { + cv::util::throw_error(std::runtime_error("Config not found!\n" + "You must pass cv::gapi::plaidml::config to the graph compile arguments")); + } + + const auto& arg = has_config.value(); + return EPtr{new cv::gimpl::GPlaidMLExecutable(cv::gimpl::GPlaidMLExecutable::Config{arg.dev_id, arg.trg_id}, + graph, nodes, ins_data, outs_data)}; + } + }; +} + +cv::gapi::GBackend cv::gapi::plaidml::backend() +{ + static cv::gapi::GBackend this_backend(std::make_shared()); + return this_backend; +} + +void cv::gimpl::GPlaidMLExecutable::initBuffers(const std::vector& data, + std::vector& bindings) +{ + + // NB: This is necessary because we keep a pointer to bindings elements to buffer_map + // In order to them to remain valid it's required to prevant reallocation + bindings.reserve(data.size()); + for (const auto& d : data) + { + GAPI_Assert(d.shape == GShape::GMAT && + "Now PlaidML backend supports only cv::GMat's"); + + const auto& desc = cv::util::get(d.meta); + + auto placeholder = plaidml::edsl::Placeholder( + cv::util::plaidml::depth_from_ocv(desc.depth), + {desc.size.width, desc.size.height, desc.chan}); + + const auto& shape = placeholder.shape(); + plaidml::TensorShape tshape(shape.dtype(), shape.int_dims()); + plaidml::Buffer buffer(m_cfg.dev_id, tshape); + + bindings.push_back(plaidml::exec::Binding{std::move(placeholder), + std::move(buffer)}); + + auto& tensor_map = m_res.slot(); + // FIXME Avoid Copy here !!! + tensor_map.emplace(d.rc, bindings.back().tensor); + + auto& buffer_map = m_res.slot(); + buffer_map.emplace(d.rc, &(bindings.back().buffer)); + } +} + +void cv::gimpl::GPlaidMLExecutable::compile(const std::vector& ins_data, + const std::vector& outs_data) +{ + initBuffers(ins_data, input_bindings_); + initBuffers(outs_data, output_bindings_); + + ade::util::transform(outs_data, std::back_inserter(output_ids_), + [](const cv::gimpl::Data& d) { return d.rc; }); + + GConstGPlaidMLModel gcm(m_g); + for (const auto& nh : m_all_ops) + { + const auto& k = gcm.metadata(nh).get().k; + GPlaidMLContext ctx; + + const auto &op = m_gm.metadata(nh).get(); + ctx.m_args.reserve(op.args.size()); + + using namespace std::placeholders; + ade::util::transform(op.args, + std::back_inserter(ctx.m_args), + std::bind(&GPlaidMLExecutable::packArg, this, _1)); + + for (const auto &out_it : ade::util::indexed(op.outs)) + { + const auto out_port = ade::util::index(out_it); + const auto out_desc = ade::util::value(out_it); + + auto& tensor_map = m_res.slot(); + + // NB: Create tensor if need + auto& tensor = tensor_map[out_desc.id]; + ctx.m_results[out_port] = GArg(&(tensor)); + } + + k.apply(ctx); + } + + std::vector output_tensors; + for (const auto& out_id : output_ids_) + { + auto& tensor_map = m_res.slot(); + // FIXME Avoid copy here !!! + output_tensors.emplace_back(tensor_map[out_id]); + } + + plaidml::edsl::Program program("Program", output_tensors); + binder_.reset(new plaidml::exec::Binder(program)); + + for (const auto& binding : input_bindings_) + { + binder_->set_input(binding.tensor, binding.buffer); + } + + for (const auto& binding : output_bindings_) + { + binder_->set_output(binding.tensor, binding.buffer); + } + + exec_ = binder_->compile(); +} + +cv::gimpl::GPlaidMLExecutable::GPlaidMLExecutable(cv::gimpl::GPlaidMLExecutable::Config cfg, + const ade::Graph& g, + const std::vector& nodes, + const std::vector& ins_data, + const std::vector& outs_data) + : m_cfg(std::move(cfg)), m_g(g), m_gm(m_g) +{ + auto is_op = [&](ade::NodeHandle nh) { + return m_gm.metadata(nh).get().t == NodeType::OP; + }; + + std::copy_if(nodes.begin(), nodes.end(), std::back_inserter(m_all_ops), is_op); + + compile(ins_data, outs_data); +} + +void cv::gimpl::GPlaidMLExecutable::run(std::vector &&input_objs, + std::vector &&output_objs) +{ + for (auto& it : input_objs) bindInArg (it.first, it.second); + + exec_->run(); + + for (auto& it : output_objs) bindOutArg(it.first, it.second); +} + +void cv::gimpl::GPlaidMLExecutable::bindInArg(const RcDesc &rc, const GRunArg &arg) +{ + switch (rc.shape) + { + case GShape::GMAT: + { + auto& tensor_map = m_res.slot(); + auto it = tensor_map.find(rc.id); + GAPI_Assert(it != tensor_map.end()); + + switch (arg.index()) + { + case GRunArg::index_of(): + { + auto& arg_mat = util::get(arg); + binder_->input(it->second).copy_from(arg_mat.data); + } + break; +#if !defined(GAPI_STANDALONE) + case GRunArg::index_of() : + { + auto& arg_mat = util::get(arg); + binder_->input(it->second).copy_from(arg_mat.data); + } + break; +#endif // !defined(GAPI_STANDALONE) + default: util::throw_error(std::logic_error("content type of the runtime argument does not match to resource description ?")); + } + } + break; + + default: + util::throw_error(std::logic_error("Unsupported GShape type")); + } +} + +void cv::gimpl::GPlaidMLExecutable::bindOutArg(const RcDesc &rc, const GRunArgP &arg) +{ + switch (rc.shape) + { + case GShape::GMAT: + { + auto& tensor_map = m_res.slot(); + auto it = tensor_map.find(rc.id); + GAPI_Assert(it != tensor_map.end()); + + switch (arg.index()) + { + case GRunArgP::index_of(): + { + auto& arg_mat = *util::get(arg); + binder_->output(it->second).copy_into(arg_mat.data); + } + break; +#if !defined(GAPI_STANDALONE) + case GRunArgP::index_of() : + { + auto& arg_mat = *util::get(arg); + binder_->output(it->second).copy_into(arg_mat.data); + } + break; +#endif // !defined(GAPI_STANDALONE) + default: util::throw_error(std::logic_error("content type of the runtime argument does not match to resource description ?")); + } + } + break; + + default: + util::throw_error(std::logic_error("Unsupported GShape type")); + } +} + +cv::GArg cv::gimpl::GPlaidMLExecutable::packArg(const GArg &arg) +{ + GAPI_Assert( arg.kind != cv::detail::ArgKind::GMAT + && arg.kind != cv::detail::ArgKind::GSCALAR + && arg.kind != cv::detail::ArgKind::GARRAY); + + if (arg.kind != cv::detail::ArgKind::GOBJREF) + { + // All other cases - pass as-is, with no transformations to GArg contents. + return arg; + } + GAPI_Assert(arg.kind == cv::detail::ArgKind::GOBJREF); + + const cv::gimpl::RcDesc &ref = arg.get(); + switch (ref.shape) + { + case GShape::GMAT: + { + auto& tensor_map = m_res.slot(); + auto it = tensor_map.find(ref.id); + GAPI_Assert(it != tensor_map.end()); + return GArg(it->second); + } + break; + default: + util::throw_error(std::logic_error("Unsupported GShape type")); + break; + } +} + +#endif // HAVE_PLAIDML diff --git a/modules/gapi/src/backends/plaidml/gplaidmlbackend.hpp b/modules/gapi/src/backends/plaidml/gplaidmlbackend.hpp new file mode 100644 index 0000000000..f22a53daec --- /dev/null +++ b/modules/gapi/src/backends/plaidml/gplaidmlbackend.hpp @@ -0,0 +1,100 @@ +// 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) 2019 Intel Corporation + +#ifdef HAVE_PLAIDML + +#ifndef OPENCV_GAPI_GPLAIDMLBACKEND_HPP +#define OPENCV_GAPI_GPLAIDMLBACKEND_HPP + +#include // map +#include // unordered_map +#include // tuple +#include // type_list_index + +#include +#include +#include + +#include "api/gorigin.hpp" +#include "backends/common/gbackend.hpp" + +#include "compiler/gislandmodel.hpp" + +#include +#include + +namespace cv { namespace gimpl { + +struct PlaidMLUnit +{ + static const char *name() { return "PlaidMLKernel"; } + GPlaidMLKernel k; +}; + +class GPlaidMLExecutable final: public GIslandExecutable +{ +public: + struct Config + { + std::string dev_id; + std::string trg_id; + }; + + GPlaidMLExecutable(Config cfg, + const ade::Graph& graph, + const std::vector& nodes, + const std::vector& ins_data, + const std::vector& outs_data); + + virtual inline bool canReshape() const override { return false; } + + virtual inline void reshape(ade::Graph&, const GCompileArgs&) override + { + util::throw_error(std::logic_error("GPlaidMLExecutable::reshape() should never be called")); + } + + virtual void run(std::vector &&input_objs, + std::vector &&output_objs) override; + +private: + void initBuffers(const std::vector& ins_data, + std::vector& bindings); + + void bindInArg (const RcDesc &rc, const GRunArg &arg); + void bindOutArg (const RcDesc &rc, const GRunArgP &arg); + + void compile(const std::vector& ins_data, + const std::vector& outs_data); + + // FIXME User also can pass config via compile args ? + void initConfig(); + + GArg packArg(const GArg &arg); + + Config m_cfg; + + const ade::Graph &m_g; + GModel::ConstGraph m_gm; + + std::vector m_all_ops; + + std::vector output_ids_; + + std::unique_ptr binder_; + std::shared_ptr exec_; + + std::vector input_bindings_; + std::vector output_bindings_; + + using Mag = detail::magazine; + Mag m_res; +}; + +}} + +#endif // OPENCV_GAPI_GPLAIDMLBACKEND_HPP + +#endif // HAVE_PLAIDML diff --git a/modules/gapi/src/backends/plaidml/gplaidmlcore.cpp b/modules/gapi/src/backends/plaidml/gplaidmlcore.cpp new file mode 100644 index 0000000000..e81e4b7ea6 --- /dev/null +++ b/modules/gapi/src/backends/plaidml/gplaidmlcore.cpp @@ -0,0 +1,54 @@ +// 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) 2019 Intel Corporation + + +#ifdef HAVE_PLAIDML + +#include "precomp.hpp" + +#include +#include +#include + +#include + +#define GAPI_PLAIDML_LOGICAL_OP(Name, API, Op) \ +GAPI_PLAIDML_KERNEL(Name, API) \ +{ \ + static void run(const plaidml::edsl::Tensor& src1, \ + const plaidml::edsl::Tensor& src2, \ + plaidml::edsl::Tensor& dst) \ + { \ + dst = src1 Op src2; \ + }; \ +}; \ + +#define GAPI_PLAIDML_ARITHMETIC_OP(Name, API, Op) \ +GAPI_PLAIDML_KERNEL(Name, API) \ +{ \ + static void run(const plaidml::edsl::Tensor& src1, \ + const plaidml::edsl::Tensor& src2, \ + int, /* dtype */ \ + plaidml::edsl::Tensor& dst) \ + { \ + dst = src1 Op src2; \ + }; \ +}; \ + +GAPI_PLAIDML_LOGICAL_OP(GPlaidMLAnd, cv::gapi::core::GAnd, &); +GAPI_PLAIDML_LOGICAL_OP(GPlaidMLXor, cv::gapi::core::GXor, ^); +GAPI_PLAIDML_LOGICAL_OP(GPlaidMLOr , cv::gapi::core::GOr , |) + +GAPI_PLAIDML_ARITHMETIC_OP(GPlaidMLAdd, cv::gapi::core::GAdd, +); +GAPI_PLAIDML_ARITHMETIC_OP(GPlaidMLSub, cv::gapi::core::GSub, -); + +cv::gapi::GKernelPackage cv::gapi::core::plaidml::kernels() +{ + static auto pkg = cv::gapi::kernels(); + return pkg; +} + +#endif // HACE_PLAIDML diff --git a/modules/gapi/src/backends/plaidml/plaidml_util.hpp b/modules/gapi/src/backends/plaidml/plaidml_util.hpp new file mode 100644 index 0000000000..1664ec7d4b --- /dev/null +++ b/modules/gapi/src/backends/plaidml/plaidml_util.hpp @@ -0,0 +1,42 @@ +// 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) 2019 Intel Corporation + + +#ifdef HAVE_PLAIDML + +#ifndef OPENCV_GAPI_PLAIDML_UTIL_HPP +#define OPENCV_GAPI_PLAIDML_UTIL_HPP + +#include + +namespace cv +{ +namespace util +{ +namespace plaidml +{ + +inline plaidml_datatype depth_from_ocv(int depth) +{ + switch(depth) + { + case CV_8U : return PLAIDML_DATA_UINT8; + case CV_8S : return PLAIDML_DATA_INT8; + case CV_16U : return PLAIDML_DATA_UINT16; + case CV_16S : return PLAIDML_DATA_INT16; + case CV_32S : return PLAIDML_DATA_INT32; + case CV_32F : return PLAIDML_DATA_FLOAT32; + case CV_64F : return PLAIDML_DATA_FLOAT64; + default: util::throw_error("Unrecognized OpenCV depth"); + } +}; + +} +} +} +#endif // OPENCV_GAPI_PLAIDML_UTIL_HPP + +#endif // HAVE_PLAIDML diff --git a/modules/gapi/src/compiler/gislandmodel.cpp b/modules/gapi/src/compiler/gislandmodel.cpp index 7f03ee9a37..0b27825160 100644 --- a/modules/gapi/src/compiler/gislandmodel.cpp +++ b/modules/gapi/src/compiler/gislandmodel.cpp @@ -16,6 +16,8 @@ #include "api/gbackend_priv.hpp" // GBackend::Priv().compile() #include "compiler/gmodel.hpp" #include "compiler/gislandmodel.hpp" +#include "compiler/gmodel.hpp" + #include "logger.hpp" // GAPI_LOG namespace cv { namespace gimpl { @@ -257,6 +259,20 @@ void GIslandModel::compileIslands(Graph &g, const ade::Graph &orig_g, const GCom { if (NodeKind::ISLAND == g.metadata(nh).get().k) { + auto nodes_to_data = [&](const ade::NodeHandle& dnh) + { + GAPI_Assert(g.metadata(dnh).get().k == NodeKind::SLOT); + const auto& orig_data_nh = g.metadata(dnh).get().original_data_node; + const auto& data = gm.metadata(orig_data_nh).get(); + return data; + }; + + std::vector ins_data; + ade::util::transform(nh->inNodes(), std::back_inserter(ins_data), nodes_to_data); + + std::vector outs_data; + ade::util::transform(nh->outNodes(), std::back_inserter(outs_data), nodes_to_data); + auto island_obj = g.metadata(nh).get().object; auto island_ops = island_obj->contents(); @@ -268,8 +284,9 @@ void GIslandModel::compileIslands(Graph &g, const ade::Graph &orig_g, const GCom }); auto island_exe = island_obj->backend().priv() - .compile(orig_g, args, topo_sorted_list); + .compile(orig_g, args, topo_sorted_list, ins_data, outs_data); GAPI_Assert(nullptr != island_exe); + g.metadata(nh).set(IslandExec{std::move(island_exe)}); } } diff --git a/modules/gapi/src/compiler/gislandmodel.hpp b/modules/gapi/src/compiler/gislandmodel.hpp index a3dcdbbd43..a1edd9ae6e 100644 --- a/modules/gapi/src/compiler/gislandmodel.hpp +++ b/modules/gapi/src/compiler/gislandmodel.hpp @@ -96,6 +96,7 @@ protected: // * Is instantiated by the last step of the Islands fusion procedure; // * Is orchestrated by a GExecutor instance. // + class GIslandExecutable { public: diff --git a/modules/gapi/src/executor/gexecutor.cpp b/modules/gapi/src/executor/gexecutor.cpp index 8a3e24b834..d316f5bd41 100644 --- a/modules/gapi/src/executor/gexecutor.cpp +++ b/modules/gapi/src/executor/gexecutor.cpp @@ -62,7 +62,8 @@ cv::gimpl::GExecutor::GExecutor(std::unique_ptr &&g_model) m_ops.emplace_back(OpDesc{ std::move(input_rcs) , std::move(output_rcs) - , m_gim.metadata(nh).get().object}); + , m_gim.metadata(nh).get().object + }); } break; diff --git a/modules/gapi/test/gapi_plaidml_pipelines.cpp b/modules/gapi/test/gapi_plaidml_pipelines.cpp new file mode 100644 index 0000000000..a090c99d61 --- /dev/null +++ b/modules/gapi/test/gapi_plaidml_pipelines.cpp @@ -0,0 +1,178 @@ +// 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) 2019 Intel Corporation + + +#ifdef HAVE_PLAIDML + +#include "test_precomp.hpp" + +#include +#include +#include "logger.hpp" + +#include +#include + +namespace opencv_test +{ + +inline cv::gapi::plaidml::config getConfig() +{ + auto read_var_from_env = [](const char* env) + { + const char* raw = std::getenv(env); + if (!raw) + { + cv::util::throw_error(std::runtime_error(std::string(env) + " is't set")); + } + + return std::string(raw); + }; + + auto dev_id = read_var_from_env("PLAIDML_DEVICE"); + auto trg_id = read_var_from_env("PLAIDML_TARGET"); + + return cv::gapi::plaidml::config{std::move(dev_id), + std::move(trg_id)}; +} + +TEST(GAPI_PlaidML_Pipelines, SimpleArithmetic) +{ + cv::Size size(1920, 1080); + int type = CV_8UC1; + + cv::Mat in_mat1(size, type); + cv::Mat in_mat2(size, type); + + // NB: What about overflow ? PlaidML doesn't handle it + cv::randu(in_mat1, cv::Scalar::all(0), cv::Scalar::all(127)); + cv::randu(in_mat2, cv::Scalar::all(0), cv::Scalar::all(127)); + + cv::Mat out_mat(size, type, cv::Scalar::all(0)); + cv::Mat ref_mat(size, type, cv::Scalar::all(0)); + + ////////////////////////////// G-API ////////////////////////////////////// + cv::GMat in1, in2; + auto out = in1 + in2; + + cv::GComputation comp(cv::GIn(in1, in2), cv::GOut(out)); + comp.apply(cv::gin(in_mat1, in_mat2), cv::gout(out_mat), + cv::compile_args(getConfig(), + cv::gapi::use_only{cv::gapi::core::plaidml::kernels()})); + + ////////////////////////////// OpenCV ///////////////////////////////////// + cv::add(in_mat1, in_mat2, ref_mat, cv::noArray(), type); + + EXPECT_EQ(0, cv::norm(out_mat, ref_mat)); +} + +// FIXME PlaidML cpu backend does't support bitwise operations +TEST(GAPI_PlaidML_Pipelines, DISABLED_ComplexArithmetic) +{ + cv::Size size(1920, 1080); + int type = CV_8UC1; + + cv::Mat in_mat1(size, type); + cv::Mat in_mat2(size, type); + + cv::randu(in_mat1, cv::Scalar::all(0), cv::Scalar::all(255)); + cv::randu(in_mat2, cv::Scalar::all(0), cv::Scalar::all(255)); + + cv::Mat out_mat(size, type, cv::Scalar::all(0)); + cv::Mat ref_mat(size, type, cv::Scalar::all(0)); + + ////////////////////////////// G-API ////////////////////////////////////// + cv::GMat in1, in2; + auto out = in1 | (in2 ^ (in1 & (in2 + (in1 - in2)))); + + cv::GComputation comp(cv::GIn(in1, in2), cv::GOut(out)); + comp.apply(cv::gin(in_mat1, in_mat2), cv::gout(out_mat), + cv::compile_args(getConfig(), + cv::gapi::use_only{cv::gapi::core::plaidml::kernels()})); + + ////////////////////////////// OpenCV ///////////////////////////////////// + cv::subtract(in_mat1, in_mat2, ref_mat, cv::noArray(), type); + cv::add(in_mat2, ref_mat, ref_mat, cv::noArray(), type); + cv::bitwise_and(in_mat1, ref_mat, ref_mat); + cv::bitwise_xor(in_mat2, ref_mat, ref_mat); + cv::bitwise_or(in_mat1, ref_mat, ref_mat); + + EXPECT_EQ(0, cv::norm(out_mat, ref_mat)); +} + +TEST(GAPI_PlaidML_Pipelines, TwoInputOperations) +{ + cv::Size size(1920, 1080); + int type = CV_8UC1; + + constexpr int kNumInputs = 4; + std::vector in_mat(kNumInputs, cv::Mat(size, type)); + for (int i = 0; i < kNumInputs; ++i) + { + cv::randu(in_mat[i], cv::Scalar::all(0), cv::Scalar::all(60)); + } + + cv::Mat out_mat(size, type, cv::Scalar::all(0)); + cv::Mat ref_mat(size, type, cv::Scalar::all(0)); + + ////////////////////////////// G-API ////////////////////////////////////// + cv::GMat in[4]; + auto out = (in[3] - in[0]) + (in[2] - in[1]); + + cv::GComputation comp(cv::GIn(in[0], in[1], in[2], in[3]), cv::GOut(out)); + + // FIXME Doesn't work just apply(in_mat, out_mat, ...) + comp.apply(cv::gin(in_mat[0], in_mat[1], in_mat[2], in_mat[3]), cv::gout(out_mat), + cv::compile_args(getConfig(), + cv::gapi::use_only{cv::gapi::core::plaidml::kernels()})); + + ////////////////////////////// OpenCV ///////////////////////////////////// + cv::subtract(in_mat[3], in_mat[0], ref_mat, cv::noArray(), type); + cv::add(ref_mat, in_mat[2], ref_mat, cv::noArray(), type); + cv::subtract(ref_mat, in_mat[1], ref_mat, cv::noArray(), type); + + EXPECT_EQ(0, cv::norm(out_mat, ref_mat)); +} + +TEST(GAPI_PlaidML_Pipelines, TwoOutputOperations) +{ + cv::Size size(1920, 1080); + int type = CV_8UC1; + + constexpr int kNumInputs = 4; + std::vector in_mat(kNumInputs, cv::Mat(size, type)); + for (int i = 0; i < kNumInputs; ++i) + { + cv::randu(in_mat[i], cv::Scalar::all(0), cv::Scalar::all(60)); + } + + std::vector out_mat(kNumInputs, cv::Mat(size, type, cv::Scalar::all(0))); + std::vector ref_mat(kNumInputs, cv::Mat(size, type, cv::Scalar::all(0))); + + ////////////////////////////// G-API ////////////////////////////////////// + cv::GMat in[4], out[2]; + out[0] = in[0] + in[3]; + out[1] = in[1] + in[2]; + + cv::GComputation comp(cv::GIn(in[0], in[1], in[2], in[3]), cv::GOut(out[0], out[1])); + + // FIXME Doesn't work just apply(in_mat, out_mat, ...) + comp.apply(cv::gin(in_mat[0], in_mat[1], in_mat[2], in_mat[3]), + cv::gout(out_mat[0], out_mat[1]), + cv::compile_args(getConfig(), + cv::gapi::use_only{cv::gapi::core::plaidml::kernels()})); + + ////////////////////////////// OpenCV ///////////////////////////////////// + cv::add(in_mat[0], in_mat[3], ref_mat[0], cv::noArray(), type); + cv::add(in_mat[1], in_mat[2], ref_mat[1], cv::noArray(), type); + + EXPECT_EQ(0, cv::norm(out_mat[0], ref_mat[0])); + EXPECT_EQ(0, cv::norm(out_mat[1], ref_mat[1])); +} + +} // namespace opencv_test + +#endif // HAVE_PLAIDML diff --git a/modules/gapi/test/gapi_sample_pipelines.cpp b/modules/gapi/test/gapi_sample_pipelines.cpp index 5b560aa7cf..e41cd067a0 100644 --- a/modules/gapi/test/gapi_sample_pipelines.cpp +++ b/modules/gapi/test/gapi_sample_pipelines.cpp @@ -11,7 +11,8 @@ #include #include "logger.hpp" -#include +#include + namespace opencv_test { @@ -315,4 +316,5 @@ TEST(GAPI_Pipeline, CanUseOwnMatAsOutput) // FIXME add overload for apply(cv::gapi::own::Mat in, cv::gapi::own::Mat& out) EXPECT_NO_THROW(comp.apply({in_own_mat}, {out_own_mat})); } + } // namespace opencv_test