dnn: add the CANN backend (#22634)

* cann backend impl v1

* cann backend impl v2: use opencv parsers to build models for cann

* adjust fc according to the new transA and transB

* put cann net in cann backend node and reuse forwardLayer

* use fork() to create a child process and compile cann model

* remove legacy code

* remove debug code

* fall bcak to CPU backend if there is one layer not supoorted by CANN backend

* fix netInput forward
This commit is contained in:
Yuantao Feng 2022-12-21 14:04:41 +08:00 committed by GitHub
parent a08c98cdfb
commit a2b3acfc6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 2208 additions and 28 deletions

View File

@ -468,6 +468,9 @@ OCV_OPTION(WITH_TIMVX "Include Tim-VX support" OFF
OCV_OPTION(WITH_OBSENSOR "Include obsensor support (Orbbec RGB-D modules: Astra+/Femto)" ON
VISIBLE_IF (WIN32 AND NOT ARM AND NOT WINRT) OR ( UNIX AND NOT APPLE AND NOT ANDROID)
VERIFY HAVE_OBSENSOR)
OCV_OPTION(WITH_CANN "Include CANN support" OFF
VISIBLE_IF TRUE
VERIFY HAVE_CANN)
# OpenCV build components
# ===================================================
@ -753,6 +756,9 @@ endif()
if(WITH_TIMVX)
include(cmake/OpenCVFindTIMVX.cmake)
endif()
if(WITH_CANN)
include(cmake/OpenCVFindCANN.cmake)
endif()
# ----------------------------------------------------------------------------
# Detect other 3rd-party libraries/tools
@ -1736,6 +1742,15 @@ if(WITH_ONNX OR HAVE_ONNX)
endif()
endif()
if(WITH_CANN)
status("")
status(" CANN:" HAVE_CANN THEN "YES" ELSE "NO")
if(HAVE_CANN)
status(" Include path" CANN_INCLUDE_DIRS THEN "${CANN_INCLUDE_DIRS}" ELSE "NO")
status(" Link libraries:" CANN_LIBRARIES THEN "${CANN_LIBRARIES}" ELSE "NO")
endif()
endif()
# ========================== python ==========================
if(BUILD_opencv_python2)
status("")

109
cmake/OpenCVFindCANN.cmake Normal file
View File

@ -0,0 +1,109 @@
ocv_check_environment_variables(CANN_INSTALL_DIR)
if("cann${CANN_INSTALL_DIR}" STREQUAL "cann" AND DEFINED ENV{ASCEND_TOOLKIT_HOME})
set(CANN_INSTALL_DIR $ENV{ASCEND_TOOLKIT_HOME})
message(STATUS "CANN: updated CANN_INSTALL_DIR from ASCEND_TOOLKIT_HOME=$ENV{ASCEND_TOOLKIT_HOME}")
endif()
if(CANN_INSTALL_DIR)
# Supported platforms: x86-64, arm64
if(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64")
elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "amd64")
else()
set(HAVE_CANN OFF)
message(STATUS "CANN: CANN toolkit supports x86-64 and arm64 but not ${CMAKE_SYSTEM_PROCESSOR}. Turning off HAVE_CANN")
return()
endif()
# Supported OS: linux (because of we need fork() to build models in child process)
# done via checks in cann.cpp
# FIXME: remove the check if a better model building solution is found
# include
set(incs_cann "${CANN_INSTALL_DIR}/include")
list(APPEND incs_cann "${CANN_INSTALL_DIR}/opp")
# libs
# * libascendcl.so
set(lib_ascendcl "${CANN_INSTALL_DIR}/acllib/lib64")
find_library(found_lib_ascendcl NAMES ascendcl PATHS ${lib_ascendcl} NO_DEFAULT_PATH)
if(found_lib_ascendcl)
set(lib_ascendcl ${found_lib_ascendcl})
message(STATUS "CANN: libascendcl.so is found at ${lib_ascendcl}")
else()
message(STATUS "CANN: Missing libascendcl.so. Turning off HAVE_CANN")
set(HAVE_CANN OFF)
return()
endif()
# * libgraph.so
set(lib_graph "${CANN_INSTALL_DIR}/compiler/lib64")
find_library(found_lib_graph NAMES graph PATHS ${lib_graph} NO_DEFAULT_PATH)
if(found_lib_graph)
set(lib_graph ${found_lib_graph})
message(STATUS "CANN: libgraph.so is found at ${lib_graph}")
else()
message(STATUS "CANN: Missing libgraph.so. Turning off HAVE_CANN")
set(HAVE_CANN OFF)
return()
endif()
# * libge_compiler.so
set(lib_ge_compiler "${CANN_INSTALL_DIR}/compiler/lib64")
find_library(found_lib_ge_compiler NAMES ge_compiler PATHS ${lib_ge_compiler} NO_DEFAULT_PATH)
if(found_lib_ge_compiler)
set(lib_ge_compiler ${found_lib_ge_compiler})
message(STATUS "CANN: libge_compiler.so is found at ${lib_ge_compiler}")
else()
message(STATUS "CANN: Missing libge_compiler.so. Turning off HAVE_CANN")
set(HAVE_CANN OFF)
return()
endif()
# * libopsproto.so
set(lib_opsproto "${CANN_INSTALL_DIR}/opp/op_proto/built-in")
find_library(found_lib_opsproto NAMES opsproto PATHS ${lib_opsproto} NO_DEFAULT_PATH)
if(found_lib_opsproto)
set(lib_opsproto ${found_lib_opsproto})
message(STATUS "CANN: libopsproto.so is found at ${lib_opsproto}")
else()
message(STATUS "CANN: Missing libopsproto.so. Turning off HAVE_CANN")
set(HAVE_CANN OFF)
return()
endif()
set(libs_cann "")
list(APPEND libs_cann ${lib_ascendcl})
list(APPEND libs_cann ${lib_opsproto})
list(APPEND libs_cann ${lib_graph})
list(APPEND libs_cann ${lib_ge_compiler})
try_compile(VALID_ASCENDCL
"${OpenCV_BINARY_DIR}"
"${OpenCV_SOURCE_DIR}/cmake/checks/cann.cpp"
CMAKE_FLAGS "-DINCLUDE_DIRECTORIES:STRING=${incs_cann}"
"-DLINK_LIBRARIES:STRING=${libs_cann}"
OUTPUT_VARIABLE ASCEND_TRY_OUT)
if(NOT ${VALID_ASCENDCL})
message(WARNING "Cannot use CANN")
set(HAVE_CANN OFF)
return()
endif()
set(HAVE_CANN ON)
endif()
if(HAVE_CANN)
set(CANN_INCLUDE_DIRS ${incs_cann})
set(CANN_LIBRARIES ${libs_cann})
ocv_add_external_target(cann "${CANN_INCLUDE_DIRS}" "${CANN_LIBRARIES}" "HAVE_CANN")
ocv_warnings_disable(CMAKE_C_FLAGS -Wignored-qualifiers)
ocv_warnings_disable(CMAKE_CXX_FLAGS -Wignored-qualifiers)
endif()
MARK_AS_ADVANCED(
incs_cann
libs_cann
lib_ascendcl
lib_graph
lib_ge_compiler
)

20
cmake/checks/cann.cpp Normal file
View File

@ -0,0 +1,20 @@
#include <acl/acl.h>
#include <unistd.h> // fork()
#include <iostream>
int main(int /*argc*/, char** /*argv*/)
{
int ret = aclInit(NULL);
if (ret != 0)
{
std::cerr << "Failed to initialize Ascend, ret = " << ret;
}
ret = aclFinalize();
if (ret != 0)
{
std::cerr << "Failed to de-initialize Ascend, ret = " << ret;
}
return 0;
}

View File

@ -31,6 +31,10 @@ if(HAVE_TIMVX)
ocv_target_compile_definitions(${the_module} PRIVATE "HAVE_TIMVX=1")
endif()
if(HAVE_CANN)
ocv_target_compile_definitions(${the_module} PRIVATE "HAVE_CANN=1")
endif()
ocv_option(OPENCV_DNN_CUDA "Build with CUDA support"
HAVE_CUDA
AND HAVE_CUBLAS
@ -162,6 +166,11 @@ if(HAVE_TIMVX)
list(APPEND libs -Wl,--whole-archive ${TIMVX_LIBRARY} -Wl,--no-whole-archive)
endif()
if(HAVE_CANN)
list(APPEND include_dirs ${CANN_INCLUDE_DIRS})
list(APPEND libs -Wl,--whole-archive ${CANN_LIBRARIES} -Wl,--no-whole-archive)
endif()
set(webnn_srcs "")
if(NOT EMSCRIPTEN)
if(HAVE_WEBNN)
@ -264,3 +273,10 @@ if(TARGET ocv.3rdparty.openvino AND OPENCV_TEST_DNN_OPENVINO)
ocv_target_link_libraries(opencv_test_dnn ocv.3rdparty.openvino)
endif()
endif()
ocv_option(OPENCV_TEST_DNN_CANN "Build test with CANN" (TARGET ocv.3rdparty.cann))
if(TARGET ocv.3rdparty.cann AND OPENCV_TEST_DNN_CANN)
if(TARGET opencv_test_dnn)
ocv_target_link_libraries(opencv_test_dnn ocv.3rdparty.cann)
endif()
endif()

View File

@ -81,6 +81,7 @@ CV__DNN_INLINE_NS_BEGIN
DNN_BACKEND_CUDA,
DNN_BACKEND_WEBNN,
DNN_BACKEND_TIMVX,
DNN_BACKEND_CANN,
#if defined(__OPENCV_BUILD) || defined(BUILD_PLUGIN)
#if !defined(OPENCV_BINDING_PARSER)
DNN_BACKEND_INFERENCE_ENGINE_NGRAPH = 1000000, // internal - use DNN_BACKEND_INFERENCE_ENGINE + setInferenceEngineBackendType()
@ -343,6 +344,15 @@ CV__DNN_INLINE_NS_BEGIN
const std::vector<Ptr<BackendWrapper> > &outputsWrapper,
bool isLast);
/**
* @brief Returns a CANN backend node
*
* @param inputsWrapper layer inputs
* @param index layer id for op name
* @param nodes inputs of this node
*/
virtual Ptr<BackendNode> initCann(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes);
/**
* @brief Automatic Halide scheduling based on layer hyper-parameters.
* @param[in] node Backend node with Halide functions.

View File

@ -84,6 +84,12 @@ Ptr<BackendNode> Layer::initTimVX(void* timVxInfo,
return Ptr<BackendNode>();
}
Ptr<BackendNode> Layer::initCann(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes)
{
CV_Error(Error::StsNotImplemented, "CANN pipeline of " + type + " layers is not defined.");
return Ptr<BackendNode>();
}
Ptr<BackendNode> Layer::tryAttach(const Ptr<BackendNode>& node)
{
return Ptr<BackendNode>();

View File

@ -16,6 +16,7 @@ Implementation of Batch Normalization layer.
#include "../op_inf_engine.hpp"
#include "../ie_ngraph.hpp"
#include "../op_webnn.hpp"
#include "../op_cann.hpp"
#include <opencv2/dnn/shape_utils.hpp>
@ -40,6 +41,7 @@ public:
Mat weights_, bias_;
UMat umat_weight, umat_bias;
mutable int dims;
float momentum;
BatchNormLayerImpl(const LayerParams& params)
@ -55,6 +57,9 @@ public:
hasWeights = hasBias = true;
epsilon = params.get<float>("eps", 1E-5);
// std::cout << params.get<float>("momentum", 0.9) << std::endl;
momentum = params.get<float>("momentum", 0.9);
size_t n = blobs[0].total();
CV_Assert(blobs[1].total() == n &&
blobs[0].isContinuous() && blobs[1].isContinuous() &&
@ -177,7 +182,8 @@ public:
return (backendId == DNN_BACKEND_OPENCV) ||
backendId == DNN_BACKEND_CUDA ||
(backendId == DNN_BACKEND_HALIDE && haveHalide()) ||
backendId == DNN_BACKEND_WEBNN;
backendId == DNN_BACKEND_WEBNN ||
backendId == DNN_BACKEND_CANN;
}
#ifdef HAVE_OPENCL
@ -385,6 +391,66 @@ public:
}
#endif // HAVE_HALIDE
#ifdef HAVE_CANN
virtual Ptr<BackendNode> initCann(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes) CV_OVERRIDE
{
CV_Assert(nodes.size() == 1);
CV_Assert(blobs.size() == 4); // must have scale, offset, mean and variance
auto x = inputsWrapper[0].dynamicCast<CannBackendWrapper>();
auto channel = x->host->size[1];
// create operator
std::string op_name = cv::format("bn_%d", index);
auto op = std::make_shared<ge::op::BatchNorm>(op_name);
// set attributes
op->set_attr_epsilon(epsilon);
op->set_attr_data_format("NCHW");
op->set_attr_is_training(false);
// set inputs
// set inputs : x
auto op_x = nodes[0].dynamicCast<CannBackendNode>()->getOp();
op->set_input_x_by_name(*op_x, "y");
auto x_desc = x->getTensorDesc();
op->update_input_desc_x(*x_desc);
// set inputs : scale (blobs[2])
std::vector<int> shape_{channel};
auto op_const_scale = std::make_shared<CannConstOp>(blobs[2].data, blobs[2].type(), shape_, cv::format("%s_scale", op_name.c_str()));
op->set_input_scale(*(op_const_scale->getOp()));
op->update_input_desc_scale(*(op_const_scale->getTensorDesc()));
// set inputs : offset (blobs[3])
auto op_const_offset = std::make_shared<CannConstOp>(blobs[3].data, blobs[3].type(), shape_, cv::format("%s_offset", op_name.c_str()));
op->set_input_offset(*(op_const_offset->getOp()));
op->update_input_desc_offset(*(op_const_offset->getTensorDesc()));
// set inputs : mean (blobs[0])
auto op_const_mean = std::make_shared<CannConstOp>(blobs[0].data, blobs[0].type(), shape_, cv::format("%s_mean", op_name.c_str()));
op->set_input_mean(*(op_const_mean->getOp()));
op->update_input_desc_mean(*(op_const_mean->getTensorDesc()));
// set inputs : variance (blobs[1])
auto op_const_var = std::make_shared<CannConstOp>(blobs[1].data, blobs[1].type(), shape_, cv::format("%s_var", op_name.c_str()));
op->set_input_variance(*(op_const_var->getOp()));
op->update_input_desc_variance(*(op_const_var->getTensorDesc()));
// set outputs
auto output_y_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
op->update_output_desc_y(*output_y_desc);
auto output_bm_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
op->update_output_desc_batch_mean(*output_bm_desc);
auto output_bv_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
op->update_output_desc_batch_variance(*output_bv_desc);
auto output_rs1_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
op->update_output_desc_reserve_space_1(*output_rs1_desc);
auto output_rs2_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
op->update_output_desc_reserve_space_2(*output_rs2_desc);
auto output_rs3_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
op->update_output_desc_reserve_space_3(*output_rs3_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
#endif // HAVE_CANN
#ifdef HAVE_DNN_NGRAPH
virtual Ptr<BackendNode> initNgraph(const std::vector<Ptr<BackendWrapper> >& inputs, const std::vector<Ptr<BackendNode> >& nodes) CV_OVERRIDE

View File

@ -43,6 +43,7 @@
#include "../op_cuda.hpp"
#include "../op_inf_engine.hpp"
#include "../ie_ngraph.hpp"
#include "../op_cann.hpp"
#ifdef HAVE_CUDA
#include "../cuda4dnn/primitives/reshape.hpp"
@ -68,7 +69,8 @@ public:
return true;
#endif
return backendId == DNN_BACKEND_OPENCV ||
backendId == DNN_BACKEND_CUDA;
backendId == DNN_BACKEND_CUDA ||
backendId == DNN_BACKEND_CANN;
}
bool getMemoryShapes(const std::vector<MatShape> &inputs,
@ -118,6 +120,28 @@ public:
inputs[i].copyTo(outputs[i]);
}
#ifdef HAVE_CANN
virtual Ptr<BackendNode> initCann(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes) CV_OVERRIDE
{
auto x = inputsWrapper[0].dynamicCast<CannBackendWrapper>();
auto x_desc = x->getTensorDesc();
auto op_x = nodes[0].dynamicCast<CannBackendNode>()->getOp();
auto output_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
// create operator
std::string op_name = cv::format("identity_%d", index);
auto op = std::make_shared<ge::op::Identity>(op_name);
// set inputs
op->set_input_x_by_name(*op_x, "y");
op->update_input_desc_x(*x_desc);
// set output
op->update_output_desc_y(*output_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
#endif
#ifdef HAVE_DNN_NGRAPH
virtual Ptr<BackendNode> initNgraph(const std::vector<Ptr<BackendWrapper> >& inputs,

View File

@ -49,6 +49,7 @@
#include "../op_vkcom.hpp"
#include "../op_webnn.hpp"
#include "../op_timvx.hpp"
#include "../op_cann.hpp"
#ifdef HAVE_OPENCL
#include "opencl_kernels_dnn.hpp"
@ -140,7 +141,8 @@ public:
backendId == DNN_BACKEND_CUDA ||
(backendId == DNN_BACKEND_HALIDE && haveHalide() && axis == 1 && !padding) || // By channels
(backendId == DNN_BACKEND_WEBNN && !padding) ||
(backendId == DNN_BACKEND_VKCOM && haveVulkan() && !padding);
(backendId == DNN_BACKEND_VKCOM && haveVulkan() && !padding) ||
(backendId == DNN_BACKEND_CANN && !padding);
}
template <class T>
@ -364,6 +366,38 @@ public:
return Ptr<BackendNode>();
}
#ifdef HAVE_CANN
virtual Ptr<BackendNode> initCann(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes) CV_OVERRIDE
{
CV_Assert(inputsWrapper.size() == nodes.size());
// create operator
std::string op_name = cv::format("concat_%d", index);
auto op = std::make_shared<ge::op::ConcatD>(op_name);
// set attributes
int N = inputsWrapper.size();
op->set_attr_concat_dim(axis);
op->set_attr_N(N);
// set inputs : x (dynamic)
op->create_dynamic_input_x(N);
for (int i = 0; i < N; i++)
{
auto x_i = inputsWrapper[i].dynamicCast<CannBackendWrapper>();
auto x_i_desc = x_i->getTensorDesc();
auto op_x_i = nodes[i].dynamicCast<CannBackendNode>()->getOp();
op->set_dynamic_input_x(i, *op_x_i, "y");
op->update_dynamic_input_desc_x(i, *x_i_desc);
}
// set outputs
auto output_y_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
op->update_output_desc_y(*output_y_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
#endif
#ifdef HAVE_DNN_NGRAPH
virtual Ptr<BackendNode> initNgraph(const std::vector<Ptr<BackendWrapper> >& inputs,

View File

@ -11,6 +11,9 @@
#include "layers_common.hpp"
#include "../ie_ngraph.hpp"
#include "../op_webnn.hpp"
#include "../op_cann.hpp"
#include <opencv2/dnn/shape_utils.hpp>
#ifdef HAVE_OPENCL
#include "opencl_kernels_dnn.hpp"
@ -40,7 +43,8 @@ public:
#endif
return backendId == DNN_BACKEND_OPENCV ||
backendId == DNN_BACKEND_WEBNN ||
backendId == DNN_BACKEND_CUDA;
backendId == DNN_BACKEND_CUDA ||
backendId == DNN_BACKEND_CANN;
}
virtual bool getMemoryShapes(const std::vector<MatShape> &inputs,
@ -79,6 +83,40 @@ public:
blobs[0].copyTo(outputs[0]);
}
#ifdef HAVE_CANN
virtual Ptr<BackendNode> initCann(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes) CV_OVERRIDE
{
auto mat_shape = shape(blobs[0]);
std::vector<int64_t> mat_shape_{mat_shape.begin(), mat_shape.end()};
auto ge_shape = ge::Shape(mat_shape_);
auto ge_dtype = ge::DT_FLOAT;
switch (blobs[0].type())
{
case CV_32F: break;
case CV_32S: ge_dtype = ge::DT_INT32; break;
default: CV_Error(Error::StsNotImplemented, "Unsuppported data type");
}
auto size_of_type = sizeof(float);
switch (blobs[0].type())
{
case CV_32F: break;
case CV_32S: size_of_type = sizeof(int); break;
default: CV_Error(Error::StsNotImplemented, "Unsuppported data type");
}
auto desc = std::make_shared<ge::TensorDesc>(ge_shape, ge::FORMAT_NCHW, ge_dtype);
auto ge_tensor = std::make_shared<ge::Tensor>();
ge_tensor->SetTensorDesc(*desc);
ge_tensor->SetData(blobs[0].data, ge_shape.GetShapeSize() * size_of_type);
std::string op_name = cv::format("const_%d", index);
auto op = std::make_shared<ge::op::Const>(op_name);
op->set_attr_value(*ge_tensor);
return Ptr<BackendNode>(new CannBackendNode(op));
}
#endif // HAVE_CANN
#ifdef HAVE_DNN_NGRAPH
virtual Ptr<BackendNode> initNgraph(const std::vector<Ptr<BackendWrapper> >& inputs,

View File

@ -48,6 +48,7 @@
#include "../ie_ngraph.hpp"
#include "../op_vkcom.hpp"
#include "../op_webnn.hpp"
#include "../op_cann.hpp"
#include <opencv2/core/utils/configuration.private.hpp>
#include <opencv2/core/utils/logger.hpp>
@ -369,6 +370,17 @@ public:
return true;
}
#endif
#ifdef HAVE_CANN
if (backendId == DNN_BACKEND_CANN)
{
if (ksize != 2)
{
CV_LOG_WARNING(NULL, "CANN supports Conv2D for now");
return false;
}
return true;
}
#endif // HAVE_CANN
return false;
}
@ -768,6 +780,68 @@ public:
return Ptr<BackendNode>();
}
#ifdef HAVE_CANN
virtual Ptr<BackendNode> initCann(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes) CV_OVERRIDE
{
CV_Assert(!blobs.empty());
CV_Assert(inputsWrapper.size() == 1);
CV_Assert(nodes.size() == 1);
bool has_bias = hasBias() || fusedBias;
auto x = inputsWrapper[0].dynamicCast<CannBackendWrapper>();
const int x_in_channel = x->host->size[1];
const int filter_out_channel = blobs[0].size[1];
const int groups = x_in_channel / filter_out_channel;
// create operator
std::string op_name = cv::format("conv2d_%d", index);
auto op = std::make_shared<ge::op::Conv2D>(op_name);
// set attributes
op->set_attr_strides(ge::Operator::OpListInt(
{1, 1, (int64_t)strides[0], (int64_t)strides[1]}
));
op->set_attr_pads(ge::Operator::OpListInt(
{(int64_t)pads_begin[1], (int64_t)pads_end[1], (int64_t)pads_begin[0], (int64_t)pads_end[0]}
));
op->set_attr_dilations(ge::Operator::OpListInt(
{1, 1, (int64_t)dilations[0], (int64_t)dilations[1]}
));
op->set_attr_groups(groups);
op->set_attr_data_format("NCHW");
// set inputs
// set inputs : x
auto op_x = nodes[0].dynamicCast<CannBackendNode>()->getOp();
op->set_input_x_by_name(*op_x, "y");
auto x_desc = x->getTensorDesc();
op->update_input_desc_x(*x_desc);
// set inputs : weight
const Mat& w_mat = blobs[0];
auto op_const_weight = std::make_shared<CannConstOp>(w_mat.data, w_mat.type(), shape(w_mat), cv::format("%s_w", op_name.c_str()));
op->set_input_filter(*(op_const_weight->getOp()));
op->update_input_desc_filter(*(op_const_weight->getTensorDesc()));
// set inputs : bias
if (has_bias)
{
int out_channel = blobs[0].size[0];
Mat b_mat({out_channel}, CV_32F, &biasvec[0]);
std::vector<int> bias_shape{out_channel};
auto op_const_bias = std::make_shared<CannConstOp>(b_mat.data, b_mat.type(), bias_shape, cv::format("%s_b", op_name.c_str()));
op->set_input_bias(*(op_const_bias->getOp()));
op->update_input_desc_bias(*(op_const_bias->getTensorDesc()));
}
// set outputs
auto output_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
op->update_output_desc_y(*output_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
#endif
#ifdef HAVE_DNN_NGRAPH
virtual Ptr<BackendNode> initNgraph(const std::vector<Ptr<BackendWrapper> > &inputs,

View File

@ -48,6 +48,7 @@
#include "../ie_ngraph.hpp"
#include "../op_vkcom.hpp"
#include "../op_webnn.hpp"
#include "../op_cann.hpp"
#include <opencv2/dnn/shape_utils.hpp>
#include <iostream>
@ -186,6 +187,12 @@ public:
return Ptr<BackendNode>();
}
#ifdef HAVE_CANN
virtual Ptr<BackendNode> initCann(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes) CV_OVERRIDE
{
return func.initCannOp(inputsWrapper, index, nodes);
}
#endif // HAVE_CANN
#ifdef HAVE_DNN_NGRAPH
virtual Ptr<BackendNode> initNgraph(const std::vector<Ptr<BackendWrapper> >& inputs, const std::vector<Ptr<BackendNode> >& nodes) CV_OVERRIDE
@ -350,7 +357,8 @@ struct ReLUFunctor : public BaseFunctor
return backendId == DNN_BACKEND_OPENCV ||
backendId == DNN_BACKEND_CUDA ||
backendId == DNN_BACKEND_HALIDE ||
backendId == DNN_BACKEND_VKCOM;
backendId == DNN_BACKEND_VKCOM ||
backendId == DNN_BACKEND_CANN;
}
void apply(const float* srcptr, float* dstptr, int len, size_t planeSize, int cn0, int cn1) const
@ -450,6 +458,42 @@ struct ReLUFunctor : public BaseFunctor
}
#endif // HAVE_HALIDE
#ifdef HAVE_CANN
Ptr<BackendNode> initCannOp(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes)
{
auto x = inputsWrapper[0].dynamicCast<CannBackendWrapper>();
auto op_x = nodes[0].dynamicCast<CannBackendNode>()->getOp();
auto x_desc = x->getTensorDesc();
auto output_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
if (slope)
{
std::string op_name = cv::format("leakyrelu_%d", index);
auto op = std::make_shared<ge::op::LeakyRelu>(op_name);
op->set_input_x_by_name(*op_x, "y");
op->update_input_desc_x(*x_desc);
op->set_attr_negative_slope(slope);
op->update_output_desc_y(*output_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
std::string op_name = cv::format("relu_%d", index);
auto op = std::make_shared<ge::op::Relu>(op_name); // FIXIT: Relu6?
op->set_input_x_by_name(*op_x, "y");
op->update_input_desc_x(*x_desc);
op->update_output_desc_y(*output_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
#endif
#ifdef HAVE_DNN_NGRAPH
std::shared_ptr<ngraph::Node> initNgraphAPI(const std::shared_ptr<ngraph::Node>& node)
{
@ -525,7 +569,8 @@ struct ReLU6Functor : public BaseFunctor
return backendId == DNN_BACKEND_OPENCV ||
backendId == DNN_BACKEND_CUDA ||
backendId == DNN_BACKEND_HALIDE ||
backendId == DNN_BACKEND_WEBNN;
backendId == DNN_BACKEND_WEBNN ||
backendId == DNN_BACKEND_CANN;
}
void apply(const float* srcptr, float* dstptr, int len, size_t planeSize, int cn0, int cn1) const
@ -607,6 +652,37 @@ struct ReLU6Functor : public BaseFunctor
}
#endif // HAVE_HALIDE
#ifdef HAVE_CANN
Ptr<BackendNode> initCannOp(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes)
{
auto x = inputsWrapper[0].dynamicCast<CannBackendWrapper>();
std::string op_name = cv::format("clip_%d", index);
auto op = std::make_shared<ge::op::ClipByValue>(op_name);
auto op_x = nodes[0].dynamicCast<CannBackendNode>()->getOp();
op->set_input_x_by_name(*op_x, "y");
auto x_desc = x->getTensorDesc();
op->update_input_desc_x(*x_desc);
Mat min_value_mat(1, 1, CV_32F, Scalar(minValue));
std::vector<int> shape_{1};
auto op_const_minv = std::make_shared<CannConstOp>(min_value_mat.data, min_value_mat.type(), shape_, cv::format("%s_min_value", op_name.c_str()));
op->set_input_clip_value_min(*(op_const_minv->getOp()));
op->update_input_desc_clip_value_min(*(op_const_minv->getTensorDesc()));
Mat max_value_mat(1, 1, CV_32F, Scalar(maxValue));
auto op_const_maxv = std::make_shared<CannConstOp>(max_value_mat.data, max_value_mat.type(), shape_, cv::format("%s_max_value", op_name.c_str()));
op->set_input_clip_value_min(*(op_const_maxv->getOp()));
op->update_input_desc_clip_value_min(*(op_const_maxv->getTensorDesc()));
auto output_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
op->update_output_desc_y(*output_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
#endif
#ifdef HAVE_DNN_NGRAPH
std::shared_ptr<ngraph::Node> initNgraphAPI(const std::shared_ptr<ngraph::Node>& node)
@ -728,6 +804,12 @@ struct BaseDefaultFunctor : public BaseFunctor
}
#endif // HAVE_HALIDE
#ifdef HAVE_CANN
Ptr<BackendNode> initCannOp(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes)
{
CV_Error(Error::StsNotImplemented, "");
}
#endif // HAVE_CANN
#ifdef HAVE_DNN_NGRAPH
std::shared_ptr<ngraph::Node> initNgraphAPI(const std::shared_ptr<ngraph::Node>& node)
@ -767,7 +849,8 @@ struct TanHFunctor : public BaseDefaultFunctor<TanHFunctor>
#endif
return backendId == DNN_BACKEND_OPENCV ||
backendId == DNN_BACKEND_CUDA ||
backendId == DNN_BACKEND_HALIDE;
backendId == DNN_BACKEND_HALIDE ||
backendId == DNN_BACKEND_CANN;
}
inline float calculate(float x) const
@ -790,6 +873,26 @@ struct TanHFunctor : public BaseDefaultFunctor<TanHFunctor>
}
#endif // HAVE_HALIDE
#ifdef HAVE_CANN
Ptr<BackendNode> initCannOp(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes)
{
auto x = inputsWrapper[0].dynamicCast<CannBackendWrapper>();
std::string op_name = cv::format("tanh_%d", index);
auto op = std::make_shared<ge::op::Tanh>(op_name);
auto op_x = nodes[0].dynamicCast<CannBackendNode>()->getOp();
op->set_input_x_by_name(*op_x, "y");
auto x_desc = x->getTensorDesc();
op->update_input_desc_x(*x_desc);
auto output_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
op->update_output_desc_y(*output_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
#endif // HAVE_CANN
#ifdef HAVE_DNN_NGRAPH
std::shared_ptr<ngraph::Node> initNgraphAPI(const std::shared_ptr<ngraph::Node>& node)
{
@ -811,7 +914,9 @@ struct SwishFunctor : public BaseDefaultFunctor<SwishFunctor>
{
return backendId == DNN_BACKEND_OPENCV ||
backendId == DNN_BACKEND_CUDA ||
backendId == DNN_BACKEND_HALIDE || backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH;
backendId == DNN_BACKEND_HALIDE ||
backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH ||
backendId == DNN_BACKEND_CANN;
}
inline float calculate(float x) const
@ -834,6 +939,28 @@ struct SwishFunctor : public BaseDefaultFunctor<SwishFunctor>
}
#endif // HAVE_HALIDE
#ifdef HAVE_CANN
Ptr<BackendNode> initCannOp(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes)
{
auto x = inputsWrapper[0].dynamicCast<CannBackendWrapper>();
std::string op_name = cv::format("swish_%d", index);
auto op = std::make_shared<ge::op::Swish>(op_name);
op->set_attr_scale(1.0f);
auto op_x = nodes[0].dynamicCast<CannBackendNode>()->getOp();
op->set_input_x_by_name(*op_x, "y");
auto x_desc = x->getTensorDesc();
op->update_input_desc_x(*x_desc);
auto output_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
op->update_output_desc_y(*output_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
#endif // HAVE_CANN
#ifdef HAVE_DNN_NGRAPH
std::shared_ptr<ngraph::Node> initNgraphAPI(const std::shared_ptr<ngraph::Node>& node)
{
@ -856,7 +983,9 @@ struct MishFunctor : public BaseDefaultFunctor<MishFunctor>
{
return backendId == DNN_BACKEND_OPENCV ||
backendId == DNN_BACKEND_CUDA ||
backendId == DNN_BACKEND_HALIDE || backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH;
backendId == DNN_BACKEND_HALIDE ||
backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH ||
backendId == DNN_BACKEND_CANN;
}
inline float calculate(float x) const
@ -887,6 +1016,26 @@ struct MishFunctor : public BaseDefaultFunctor<MishFunctor>
}
#endif // HAVE_HALIDE
#ifdef HAVE_CANN
Ptr<BackendNode> initCannOp(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes)
{
auto x = inputsWrapper[0].dynamicCast<CannBackendWrapper>();
std::string op_name = cv::format("mish_%d", index);
auto op = std::make_shared<ge::op::Mish>(op_name);
auto op_x = nodes[0].dynamicCast<CannBackendNode>()->getOp();
op->set_input_x_by_name(*op_x, "y");
auto x_desc = x->getTensorDesc();
op->update_input_desc_x(*x_desc);
auto output_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
op->update_output_desc_y(*output_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
#endif // HAVE_CANN
#ifdef HAVE_DNN_NGRAPH
std::shared_ptr<ngraph::Node> initNgraphAPI(const std::shared_ptr<ngraph::Node>& node)
{
@ -918,7 +1067,8 @@ struct SigmoidFunctor : public BaseDefaultFunctor<SigmoidFunctor>
#endif
return backendId == DNN_BACKEND_OPENCV ||
backendId == DNN_BACKEND_CUDA ||
backendId == DNN_BACKEND_HALIDE;
backendId == DNN_BACKEND_HALIDE ||
backendId == DNN_BACKEND_CANN;
}
inline float calculate(float x) const
@ -941,6 +1091,25 @@ struct SigmoidFunctor : public BaseDefaultFunctor<SigmoidFunctor>
}
#endif // HAVE_HALIDE
#ifdef HAVE_CANN
Ptr<BackendNode> initCannOp(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes)
{
auto x = inputsWrapper[0].dynamicCast<CannBackendWrapper>();
std::string op_name = cv::format("sigmoid_%d", index);
auto op = std::make_shared<ge::op::Sigmoid>(op_name);
auto op_x = nodes[0].dynamicCast<CannBackendNode>()->getOp();
op->set_input_x_by_name(*op_x, "y");
auto x_desc = x->getTensorDesc();
op->update_input_desc_x(*x_desc);
auto output_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
op->update_output_desc_y(*output_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
#endif // HAVE_CANN
#ifdef HAVE_DNN_NGRAPH
std::shared_ptr<ngraph::Node> initNgraphAPI(const std::shared_ptr<ngraph::Node>& node)
@ -970,7 +1139,8 @@ struct ELUFunctor : public BaseDefaultFunctor<ELUFunctor>
#endif
return backendId == DNN_BACKEND_OPENCV ||
backendId == DNN_BACKEND_CUDA ||
backendId == DNN_BACKEND_HALIDE;
backendId == DNN_BACKEND_HALIDE ||
backendId == DNN_BACKEND_CANN;
}
inline float calculate(float x) const
@ -998,6 +1168,28 @@ struct ELUFunctor : public BaseDefaultFunctor<ELUFunctor>
}
#endif // HAVE_HALIDE
#ifdef HAVE_CANN
Ptr<BackendNode> initCannOp(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes)
{
auto x = inputsWrapper[0].dynamicCast<CannBackendWrapper>();
std::string op_name = cv::format("elu_%d", index);
auto op = std::make_shared<ge::op::Elu>(op_name);
op->set_attr_alpha(alpha);
auto op_x = nodes[0].dynamicCast<CannBackendNode>()->getOp();
op->set_input_x_by_name(*op_x, "y");
auto x_desc = x->getTensorDesc();
op->update_input_desc_x(*x_desc);
auto output_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
op->update_output_desc_y(*output_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
#endif // HAVE_CANN
#ifdef HAVE_DNN_NGRAPH
std::shared_ptr<ngraph::Node> initNgraphAPI(const std::shared_ptr<ngraph::Node>& node)
{
@ -1023,7 +1215,8 @@ struct AbsValFunctor : public BaseDefaultFunctor<AbsValFunctor>
#endif
return backendId == DNN_BACKEND_OPENCV ||
backendId == DNN_BACKEND_CUDA ||
backendId == DNN_BACKEND_HALIDE;
backendId == DNN_BACKEND_HALIDE ||
backendId == DNN_BACKEND_CANN;
}
inline float calculate(float x) const
@ -1046,6 +1239,25 @@ struct AbsValFunctor : public BaseDefaultFunctor<AbsValFunctor>
}
#endif // HAVE_HALIDE
#ifdef HAVE_CANN
Ptr<BackendNode> initCannOp(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes)
{
auto x = inputsWrapper[0].dynamicCast<CannBackendWrapper>();
std::string op_name = cv::format("abs_%d", index);
auto op = std::make_shared<ge::op::Abs>(op_name);
auto op_x = nodes[0].dynamicCast<CannBackendNode>()->getOp();
op->set_input_x_by_name(*op_x, "y");
auto x_desc = x->getTensorDesc();
op->update_input_desc_x(*x_desc);
auto output_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
op->update_output_desc_y(*output_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
#endif // HAVE_CANN
#ifdef HAVE_DNN_NGRAPH
std::shared_ptr<ngraph::Node> initNgraphAPI(const std::shared_ptr<ngraph::Node>& node)
@ -1071,7 +1283,8 @@ struct BNLLFunctor : public BaseDefaultFunctor<BNLLFunctor>
{
return backendId == DNN_BACKEND_OPENCV ||
backendId == DNN_BACKEND_CUDA ||
backendId == DNN_BACKEND_HALIDE;
backendId == DNN_BACKEND_HALIDE ||
backendId == DNN_BACKEND_CANN;
}
inline float calculate(float x) const
@ -1087,6 +1300,26 @@ struct BNLLFunctor : public BaseDefaultFunctor<BNLLFunctor>
}
#endif
#ifdef HAVE_CANN
Ptr<BackendNode> initCannOp(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes)
{
auto x = inputsWrapper[0].dynamicCast<CannBackendWrapper>();
std::string op_name = cv::format("bnll_%d", index);
auto op = std::make_shared<ge::op::BNLL>(op_name);
auto op_x = nodes[0].dynamicCast<CannBackendNode>()->getOp();
op->set_input_x_by_name(*op_x, "y");
auto x_desc = x->getTensorDesc();
op->update_input_desc_x(*x_desc);
auto output_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
op->update_output_desc_y(*output_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
#endif // HAVE_CANN
#ifdef HAVE_HALIDE
void attachHalide(const Halide::Expr& input, Halide::Func& top)
{
@ -1123,6 +1356,26 @@ struct CeilFunctor : public BaseDefaultFunctor<CeilFunctor>
}
#endif
#ifdef HAVE_CANN
Ptr<BackendNode> initCannOp(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes)
{
auto x = inputsWrapper[0].dynamicCast<CannBackendWrapper>();
std::string op_name = cv::format("bnll_%d", index);
auto op = std::make_shared<ge::op::BNLL>(op_name);
auto op_x = nodes[0].dynamicCast<CannBackendNode>()->getOp();
op->set_input_x_by_name(*op_x, "y");
auto x_desc = x->getTensorDesc();
op->update_input_desc_x(*x_desc);
auto output_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
op->update_output_desc_y(*output_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
#endif // HAVE_CANN
#ifdef HAVE_HALIDE
void attachHalide(const Halide::Expr& input, Halide::Func& top)
{
@ -1143,7 +1396,10 @@ struct FloorFunctor : public BaseDefaultFunctor<FloorFunctor>
bool supportBackend(int backendId, int)
{
return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA || backendId == DNN_BACKEND_HALIDE;
return backendId == DNN_BACKEND_OPENCV ||
backendId == DNN_BACKEND_CUDA ||
backendId == DNN_BACKEND_HALIDE ||
backendId == DNN_BACKEND_CANN;
}
inline float calculate(float x) const
@ -1158,6 +1414,26 @@ struct FloorFunctor : public BaseDefaultFunctor<FloorFunctor>
}
#endif
#ifdef HAVE_CANN
Ptr<BackendNode> initCannOp(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes)
{
auto x = inputsWrapper[0].dynamicCast<CannBackendWrapper>();
std::string op_name = cv::format("floor_%d", index);
auto op = std::make_shared<ge::op::Floor>(op_name);
auto op_x = nodes[0].dynamicCast<CannBackendNode>()->getOp();
op->set_input_x_by_name(*op_x, "y");
auto x_desc = x->getTensorDesc();
op->update_input_desc_x(*x_desc);
auto output_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
op->update_output_desc_y(*output_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
#endif // HAVE_CANN
#ifdef HAVE_HALIDE
void attachHalide(const Halide::Expr& input, Halide::Func& top)
{
@ -1992,6 +2268,12 @@ struct PowerFunctor : public BaseFunctor
}
#endif // HAVE_HALIDE
#ifdef HAVE_CANN
Ptr<BackendNode> initCannOp(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes)
{
CV_Error(Error::StsNotImplemented, "");
}
#endif // HAVE_CANN
#ifdef HAVE_DNN_NGRAPH
std::shared_ptr<ngraph::Node> initNgraphAPI(const std::shared_ptr<ngraph::Node>& node)
@ -2240,6 +2522,31 @@ struct ChannelsPReLUFunctor : public BaseFunctor
}
#endif // HAVE_HALIDE
#ifdef HAVE_CANN
Ptr<BackendNode> initCannOp(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes)
{
auto x = inputsWrapper[0].dynamicCast<CannBackendWrapper>();
auto op_x = nodes[0].dynamicCast<CannBackendNode>()->getOp();
auto x_desc = x->getTensorDesc();
auto output_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
std::string op_name = cv::format("prelu_%d", index);
auto op = std::make_shared<ge::op::PRelu>(op_name);
op->set_input_x_by_name(*op_x, "y");
op->update_input_desc_x(*x_desc);
std::vector<int> shape_{scale.size[0]}; // scale should be a 1d of shape [n] tensor, and it is a 2d mat of shape [n, 1] in opencv
auto op_const_slope = std::make_shared<CannConstOp>(scale.data, scale.type(), shape_, cv::format("%s_weight", op_name.c_str()));
op->set_input_weight(*(op_const_slope->getOp()));
op->update_input_desc_weight(*(op_const_slope->getTensorDesc()));
op->update_output_desc_y(*output_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
#endif // HAVE_CANN
#ifdef HAVE_DNN_NGRAPH
std::shared_ptr<ngraph::Node> initNgraphAPI(const std::shared_ptr<ngraph::Node>& node)

View File

@ -46,6 +46,8 @@
#include "../op_halide.hpp"
#include "../op_inf_engine.hpp"
#include "../ie_ngraph.hpp"
#include "../op_cann.hpp"
#include <opencv2/dnn/shape_utils.hpp>
#ifdef HAVE_OPENCL
@ -169,6 +171,11 @@ public:
return channelsMode == ELTWISE_CHANNNELS_SAME;
#endif
#ifdef HAVE_CANN
if (backendId == DNN_BACKEND_CANN)
return channelsMode == ELTWISE_CHANNNELS_SAME && coeffs.empty();
#endif
if (backendId == DNN_BACKEND_CUDA)
{
if(channelsModeInput == ELTWISE_CHANNNELS_INPUT_0 || channelsModeInput == ELTWISE_CHANNNELS_INPUT_0_TRUNCATE)
@ -841,6 +848,47 @@ public:
return Ptr<BackendNode>();
}
#ifdef HAVE_CANN
virtual Ptr<BackendNode> initCann(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes) CV_OVERRIDE
{
CV_Assert(inputsWrapper.size() == 2);
CV_Assert(nodes.size() == 2);
auto op_x1 = nodes[0].dynamicCast<CannBackendNode>()->getOp();
auto x1 = inputsWrapper[0].dynamicCast<CannBackendWrapper>();
auto x1_desc = x1->getTensorDesc();
auto op_x2 = nodes[1].dynamicCast<CannBackendNode>()->getOp();
auto x2 = inputsWrapper[1].dynamicCast<CannBackendWrapper>();
auto x2_desc = x2->getTensorDesc();
auto output_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
std::shared_ptr<ge::Operator> eltwise_operator = nullptr;
// add, mul, div, max, min
switch (op)
{
#define BUILD_CANN_ELTWISE_OP(op_type, class_name, op_name) \
case op_type: { \
auto eltwise_op = \
std::make_shared<ge::op::class_name>(op_name); \
eltwise_op->set_input_x1_by_name(*op_x1, "y"); \
eltwise_op->set_input_x2_by_name(*op_x2, "y"); \
eltwise_op->update_input_desc_x1(*x1_desc); \
eltwise_op->update_input_desc_x2(*x2_desc); \
eltwise_op->update_output_desc_y(*output_desc); \
eltwise_operator = eltwise_op; \
} break;
BUILD_CANN_ELTWISE_OP(SUM, Add, cv::format("add_%d", index));
BUILD_CANN_ELTWISE_OP(PROD, Mul, cv::format("mul_%d", index));
BUILD_CANN_ELTWISE_OP(DIV, Xdivy, cv::format("div_%d", index));
BUILD_CANN_ELTWISE_OP(MAX, Maximum, cv::format("max_%d", index));
BUILD_CANN_ELTWISE_OP(MIN, Minimum, cv::format("min_%d", index));
#undef BUILD_CANN_ELTWISE_OP
default: CV_Error(Error::StsNotImplemented, "Unsupported eltwise operation");
}
return Ptr<BackendNode>(new CannBackendNode(eltwise_operator));
}
#endif // HAVE_CANN
#ifdef HAVE_DNN_NGRAPH
virtual Ptr<BackendNode> initNgraph(const std::vector<Ptr<BackendWrapper> >& inputs,

View File

@ -45,6 +45,7 @@
#include "../op_cuda.hpp"
#include "../op_inf_engine.hpp"
#include "../ie_ngraph.hpp"
#include "../op_cann.hpp"
#include <float.h>
#include <algorithm>
@ -77,7 +78,8 @@ public:
return true;
#endif
return backendId == DNN_BACKEND_OPENCV ||
backendId == DNN_BACKEND_CUDA;
backendId == DNN_BACKEND_CUDA ||
backendId == DNN_BACKEND_CANN;
}
bool getMemoryShapes(const std::vector<MatShape> &inputs,
@ -173,6 +175,33 @@ public:
}
}
#ifdef HAVE_CANN
virtual Ptr<BackendNode> initCann(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes) CV_OVERRIDE
{
auto x = inputsWrapper[0].dynamicCast<CannBackendWrapper>();
auto x_desc = x->getTensorDesc();
auto op_x = nodes[0].dynamicCast<CannBackendNode>()->getOp();
auto output_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
std::string op_name = cv::format("flatten_%d", index);
auto op = std::make_shared<ge::op::FlattenV2>(op_name);
// set attributes
int num_axes = x->host->dims;
int start_axis = normalize_axis(_startAxis, num_axes);
int end_axis = normalize_axis(_endAxis, num_axes);
op->set_attr_axis(start_axis);
op->set_attr_end_axis(end_axis);
// set inputs
op->set_input_x_by_name(*op_x, "y");
op->update_input_desc_x(*x_desc);
// set outputs
op->update_output_desc_y(*output_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
#endif
#ifdef HAVE_DNN_NGRAPH
virtual Ptr<BackendNode> initNgraph(const std::vector<Ptr<BackendWrapper> >& inputs,

View File

@ -47,6 +47,7 @@
#include "../op_inf_engine.hpp"
#include "../ie_ngraph.hpp"
#include "../op_webnn.hpp"
#include "../op_cann.hpp"
#include <opencv2/dnn/shape_utils.hpp>
@ -184,7 +185,8 @@ public:
return backendId == DNN_BACKEND_OPENCV ||
(backendId == DNN_BACKEND_CUDA && !tranAorB) ||
(backendId == DNN_BACKEND_HALIDE && haveHalide() && axis == 1 && !tranAorB) ||
(backendId == DNN_BACKEND_WEBNN && axis == 1 && !tranAorB);
(backendId == DNN_BACKEND_WEBNN && axis == 1 && !tranAorB) ||
backendId == DNN_BACKEND_CANN;;
}
virtual bool setActivation(const Ptr<ActivationLayer>& layer) CV_OVERRIDE
@ -660,6 +662,65 @@ public:
return Ptr<BackendNode>();
}
#ifdef HAVE_CANN
virtual Ptr<BackendNode> initCann(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes) CV_OVERRIDE
{
auto x1 = inputsWrapper[0].dynamicCast<CannBackendWrapper>();
auto x1_desc = x1->getTensorDesc();
auto op_x1 = nodes[0].dynamicCast<CannBackendNode>()->getOp();
auto output_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
std::string op_name = cv::format("matmul_%d", index);
auto op = std::make_shared<ge::op::MatMulV2>(op_name);
if (!blobs.empty()) // if B is const
{
// set attributes
op->set_attr_transpose_x1(false);
// weightMat always needs to be transposed, since CPU backend
// implementation is input * weight.im2row
op->set_attr_transpose_x2(true);
// set inputs
// set inputs : x2 (weight)
auto op_const_weight = std::make_shared<CannConstOp>(weightsMat.data, weightsMat.type(), shape(weightsMat), cv::format("%s_w", op_name.c_str()));
op->set_input_x2_by_name(*(op_const_weight->getOp()), "y");
op->update_input_desc_x2(*(op_const_weight->getTensorDesc()));
}
else
{
// A and B are variable inputs; non-const bias is not considered
CV_Assert(inputsWrapper.size() == 2);
CV_Assert(nodes.size() == 2);
// set attributes
op->set_attr_transpose_x1(transA);
op->set_attr_transpose_x2(transB);
// set inputs : x2 (weight)
auto op_x2 = nodes[1].dynamicCast<CannBackendNode>()->getOp();
auto x2_desc = inputsWrapper[1].dynamicCast<CannBackendWrapper>()->getTensorDesc();
op->set_input_x2_by_name(*op_x2, "y");
op->update_input_desc_x2(*x2_desc);
}
// set inputs
// set inputs : x1 (input)
op->set_input_x1_by_name(*op_x1, "y");
op->update_input_desc_x1(*x1_desc);
// set inputs : bias (bias)
auto bias_mat = bias ? biasMat : Mat::zeros(1, weightsMat.size[0], weightsMat.type());
std::vector<int> bias_shape{weightsMat.size[0]};
auto op_const_bias = std::make_shared<CannConstOp>(bias_mat.data, bias_mat.type(), bias_shape, cv::format("%s_b", op_name.c_str()));
op->set_input_bias(*(op_const_bias->getOp()));
op->update_input_desc_bias(*(op_const_bias->getTensorDesc()));
// set outputs
op->update_output_desc_y(*output_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
#endif
#ifdef HAVE_DNN_NGRAPH
virtual Ptr<BackendNode> initNgraph(const std::vector<Ptr<BackendWrapper> >& inputs,

View File

@ -47,6 +47,7 @@
#include "../op_inf_engine.hpp"
#include "../ie_ngraph.hpp"
#include "../op_vkcom.hpp"
#include "../op_cann.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/dnn/shape_utils.hpp"
@ -106,7 +107,8 @@ public:
return backendId == DNN_BACKEND_OPENCV ||
backendId == DNN_BACKEND_CUDA ||
backendId == DNN_BACKEND_HALIDE ||
(backendId == DNN_BACKEND_VKCOM && haveVulkan() && (size % 2 == 1) && (type == CHANNEL_NRM));
(backendId == DNN_BACKEND_VKCOM && haveVulkan() && (size % 2 == 1) && (type == CHANNEL_NRM)) ||
backendId == DNN_BACKEND_CANN;
}
#ifdef HAVE_OPENCL
@ -442,6 +444,38 @@ public:
#endif // HAVE_HALIDE
}
#ifdef HAVE_CANN
virtual Ptr<BackendNode> initCann(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes) CV_OVERRIDE
{
auto x = inputsWrapper[0].dynamicCast<CannBackendWrapper>();
// create operator
std::string op_name = cv::format("lrn_%d", index);
auto op = std::make_shared<ge::op::LRN>(op_name);
// set attributes
op->set_attr_depth_radius(size);
op->set_attr_bias(bias);
op->set_attr_alpha(alpha);
op->set_attr_beta(beta);
op->set_attr_norm_region("ACROSS_CHANNELS");
if (type == SPATIAL_NRM)
op->set_attr_norm_region("WITHIN_CHANNEL");
// set inputs
// set inputs : x
auto op_x = nodes[0].dynamicCast<CannBackendNode>()->getOp();
op->set_input_x_by_name(*op_x, "y");
auto x_desc = x->getTensorDesc();
op->update_input_desc_x(*x_desc);
// set outputs
auto output_y_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
op->update_output_desc_y(*output_y_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
#endif // HAVE_CANN
#ifdef HAVE_DNN_NGRAPH
virtual Ptr<BackendNode> initNgraph(const std::vector<Ptr<BackendWrapper> >& inputs, const std::vector<Ptr<BackendNode> >& nodes) CV_OVERRIDE

View File

@ -5,6 +5,8 @@
#include "../precomp.hpp"
#include "layers_common.hpp"
#include "../op_cuda.hpp"
#include "../op_cann.hpp"
#include <opencv2/dnn/shape_utils.hpp>
#include <algorithm>
@ -97,6 +99,11 @@ public:
virtual bool supportBackend(int backendId) CV_OVERRIDE
{
#ifdef HAVE_CANN
if (backendId == DNN_BACKEND_CANN)
return op == OPERATION::ADD || op == OPERATION::PROD || op == OPERATION::DIV ||
op == OPERATION::DIV || op == OPERATION::MAX || op == OPERATION::MIN;
#endif
if (op == OPERATION::MAX || op == OPERATION::MIN || op == OPERATION::SUM ||
op == OPERATION::PROD || op == OPERATION::DIV)
return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA;
@ -682,6 +689,48 @@ public:
}
#endif
#ifdef HAVE_CANN
virtual Ptr<BackendNode> initCann(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes) CV_OVERRIDE
{
CV_Assert(inputsWrapper.size() == 2);
CV_Assert(nodes.size() == 2);
auto op_x1 = nodes[0].dynamicCast<CannBackendNode>()->getOp();
auto x1 = inputsWrapper[0].dynamicCast<CannBackendWrapper>();
auto x1_desc = x1->getTensorDesc();
auto op_x2 = nodes[1].dynamicCast<CannBackendNode>()->getOp();
auto x2 = inputsWrapper[1].dynamicCast<CannBackendWrapper>();
auto x2_desc = x2->getTensorDesc();
auto output_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
std::shared_ptr<ge::Operator> eltwise_operator = nullptr;
// add, mul, div, max, min
switch (op)
{
#define BUILD_CANN_ELTWISE_OP(op_type, class_name, op_name) \
case op_type: { \
auto eltwise_op = \
std::make_shared<ge::op::class_name>(op_name); \
eltwise_op->set_input_x1_by_name(*op_x1, "y"); \
eltwise_op->set_input_x2_by_name(*op_x2, "y"); \
eltwise_op->update_input_desc_x1(*x1_desc); \
eltwise_op->update_input_desc_x2(*x2_desc); \
eltwise_op->update_output_desc_y(*output_desc); \
eltwise_operator = eltwise_op; \
} break;
BUILD_CANN_ELTWISE_OP(OPERATION::ADD, Add, cv::format("add_%d", index));
BUILD_CANN_ELTWISE_OP(OPERATION::PROD, Mul, cv::format("mul_%d", index));
BUILD_CANN_ELTWISE_OP(OPERATION::DIV, Xdivy, cv::format("div_%d", index));
BUILD_CANN_ELTWISE_OP(OPERATION::MAX, Maximum, cv::format("max_%d", index));
BUILD_CANN_ELTWISE_OP(OPERATION::MIN, Minimum, cv::format("min_%d", index));
#undef BUILD_CANN_ELTWISE_OP
default: CV_Error(Error::StsNotImplemented, "Unsupported eltwise operation");
}
return Ptr<BackendNode>(new CannBackendNode(eltwise_operator));
}
#endif // HAVE_CANN
virtual bool tryQuantize(const std::vector<std::vector<float> > &scales,
const std::vector<std::vector<int> > &zeropoints, LayerParams& params) CV_OVERRIDE
{

View File

@ -15,6 +15,7 @@ Implementation of padding layer, which adds paddings to input blob.
#include "../op_halide.hpp"
#include "../op_inf_engine.hpp"
#include "../ie_ngraph.hpp"
#include "../op_cann.hpp"
#include <vector>
@ -113,7 +114,8 @@ public:
#endif
return backendId == DNN_BACKEND_OPENCV ||
backendId == DNN_BACKEND_CUDA ||
(backendId == DNN_BACKEND_HALIDE && haveHalide() && dstRanges.size() == 4);
(backendId == DNN_BACKEND_HALIDE && haveHalide() && dstRanges.size() == 4) ||
backendId == DNN_BACKEND_CANN;
}
void forward(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr, OutputArrayOfArrays internals_arr) CV_OVERRIDE
@ -219,6 +221,50 @@ public:
return Ptr<BackendNode>();
}
#ifdef HAVE_CANN
virtual Ptr<BackendNode> initCann(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes) CV_OVERRIDE
{
auto x = inputsWrapper[0].dynamicCast<CannBackendWrapper>();
// create operator
std::string op_name = cv::format("pad_%d", index);
auto op = std::make_shared<ge::op::PadV3>(op_name);
// set attributes
op->set_attr_mode(paddingType.c_str());
// set inputs
// set inputs : x
auto op_x = nodes[0].dynamicCast<CannBackendNode>()->getOp();
op->set_input_x_by_name(*op_x, "y");
auto x_desc = x->getTensorDesc();
op->update_input_desc_x(*x_desc);
// set inputs : paddings
std::vector<int> pads;
for (int i = 0; i < paddings.size(); i++)
{
pads.push_back(paddings[i].first);
pads.push_back(paddings[i].second);
}
std::vector<int> pads_shape{(int)pads.size()};
Mat paddings_mat(pads_shape, CV_32S, &pads[0]);
auto op_const_paddings = std::make_shared<CannConstOp>(paddings_mat.data, paddings_mat.type(), pads_shape, cv::format("%s_paddings", op_name.c_str()));
op->set_input_paddings(*(op_const_paddings->getOp()));
op->update_input_desc_paddings(*(op_const_paddings->getTensorDesc()));
// set inputs : constant_values
std::vector<int> constant_values_shape{1};
Mat constant_values_mat(1, 1, CV_32F, Scalar(paddingValue));
auto op_const_constant_values = std::make_shared<CannConstOp>(constant_values_mat.data, constant_values_mat.type(), constant_values_shape, cv::format("%s_constant_values", op_name.c_str()));
op->set_input_constant_values(*(op_const_constant_values->getOp()));
op->update_input_desc_constant_values(*(op_const_constant_values->getTensorDesc()));
// set outputs
auto output_y_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
op->update_output_desc_y(*output_y_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
#endif
#ifdef HAVE_DNN_NGRAPH
virtual Ptr<BackendNode> initNgraph(const std::vector<Ptr<BackendWrapper> >& inputs,

View File

@ -48,6 +48,7 @@
#include "../op_vkcom.hpp"
#include "../op_webnn.hpp"
#include "../op_timvx.hpp"
#include "../op_cann.hpp"
#include <float.h>
#include <algorithm>
@ -143,7 +144,8 @@ public:
return backendId == DNN_BACKEND_OPENCV ||
backendId == DNN_BACKEND_CUDA ||
backendId == DNN_BACKEND_WEBNN ||
(backendId == DNN_BACKEND_VKCOM && haveVulkan());
(backendId == DNN_BACKEND_VKCOM && haveVulkan()) ||
backendId == DNN_BACKEND_CANN;
}
bool getMemoryShapes(const std::vector<MatShape> &inputs,
@ -438,6 +440,34 @@ public:
}
}
#ifdef HAVE_CANN
virtual Ptr<BackendNode> initCann(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes) CV_OVERRIDE
{
auto x = inputsWrapper[0].dynamicCast<CannBackendWrapper>();
// create operator
std::string op_name = cv::format("permute_%d", index);
auto op = std::make_shared<ge::op::Permute>(op_name);
// set attributes
op->set_attr_order(ge::Operator::OpListInt(
_order.begin(), _order.end()
));
// set inputs
// set inputs : x
auto op_x = nodes[0].dynamicCast<CannBackendNode>()->getOp();
op->set_input_x_by_name(*op_x, "y");
auto x_desc = x->getTensorDesc();
op->update_input_desc_x(*x_desc);
// set outputs
auto output_y_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
op->update_output_desc_y(*output_y_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
#endif // HAVE_CANN
#ifdef HAVE_DNN_NGRAPH
virtual Ptr<BackendNode> initNgraph(const std::vector<Ptr<BackendWrapper> >& inputs,

View File

@ -47,6 +47,7 @@
#include "../op_halide.hpp"
#include "../op_inf_engine.hpp"
#include "../op_webnn.hpp"
#include "../op_cann.hpp"
#ifdef HAVE_DNN_NGRAPH
#include "../ie_ngraph.hpp"
@ -199,6 +200,12 @@ public:
{
return type == MAX || type == AVE || type == ROI;
}
#ifdef HAVE_CANN
if (backendId == DNN_BACKEND_CANN)
{
return type == MAX || type == AVE;
}
#endif
#ifdef HAVE_INF_ENGINE
if (backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH)
{
@ -540,6 +547,82 @@ public:
return Ptr<BackendNode>();
}
#ifdef HAVE_CANN
virtual Ptr<BackendNode> initCann(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes) CV_OVERRIDE
{
auto x = inputsWrapper[0].dynamicCast<CannBackendWrapper>();
auto op_x = nodes[0].dynamicCast<CannBackendNode>()->getOp();
auto x_desc = x->getTensorDesc();
auto output_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
std::string op_name_base = cv::format("pooling_%d", index);
if (type == MAX)
{
std::string op_name = cv::format("max_%s", op_name_base.c_str());
auto op = std::make_shared<ge::op::MaxPoolV3>(op_name);
// set attributes
op->set_attr_ksize(ge::Operator::OpListInt(
{1, 1, (int64_t)kernel_size[0], (int64_t)kernel_size[1]}
));
op->set_attr_strides(ge::Operator::OpListInt(
{1, 1, (int64_t)strides[0], (int64_t)strides[1]}
));
std::string cann_pad_mode{"CALCULATED"};
if (padMode == "SAME" || padMode == "VALID")
cann_pad_mode = padMode;
op->set_attr_padding_mode(cann_pad_mode.c_str());
op->set_attr_pads(ge::Operator::OpListInt(
{(int64_t)pads_begin[0], (int64_t)pads_end[0], (int64_t)pads_begin[1], (int64_t)pads_end[1]}
));
op->set_attr_data_format("NCHW");
op->set_attr_global_pooling(globalPooling);
op->set_attr_ceil_mode(ceilMode);
// set inputs
op->set_input_x_by_name(*op_x, "y");
op->update_input_desc_x(*x_desc);
// set outputs
op->update_output_desc_y(*output_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
else if (type == AVE)
{
std::string op_name = cv::format("avg_%s", op_name_base.c_str());
auto op = std::make_shared<ge::op::AvgPoolV2>(op_name);
// set attributes
op->set_attr_ksize(ge::Operator::OpListInt(
{1, 1, (int64_t)kernel_size[0], (int64_t)kernel_size[1]}
));
op->set_attr_strides(ge::Operator::OpListInt(
{1, 1, (int64_t)strides[0], (int64_t)strides[1]}
));
std::string cann_pad_mode{"CALCULATED"};
if (padMode == "SAME" || padMode == "VALID")
cann_pad_mode = padMode;
op->set_attr_padding_mode(cann_pad_mode.c_str());
op->set_attr_pads(ge::Operator::OpListInt(
{(int64_t)pads_begin[0], (int64_t)pads_end[0], (int64_t)pads_begin[1], (int64_t)pads_end[1]}
));
op->set_attr_global_pooling(globalPooling);
op->set_attr_ceil_mode(ceilMode);
auto cann_exclusive = !avePoolPaddedArea;
op->set_attr_exclusive(cann_exclusive);
// set inputs
op->set_input_x_by_name(*op_x, "y");
op->update_input_desc_x(*x_desc);
// set outputs
op->update_output_desc_y(*output_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
else
CV_Error(Error::StsNotImplemented, "Unsupported pooling type");
}
#endif
#ifdef HAVE_DNN_NGRAPH
virtual Ptr<BackendNode> initNgraph(const std::vector<Ptr<BackendWrapper> >& inputs,

View File

@ -47,6 +47,7 @@
#include "../ie_ngraph.hpp"
#include "../op_webnn.hpp"
#include "../op_timvx.hpp"
#include "../op_cann.hpp"
#include <opencv2/dnn/shape_utils.hpp>
@ -163,8 +164,8 @@ public:
ReshapeLayerImpl(const LayerParams& params)
{
setParamsFrom(params);
int axis = params.get<int>("axis", 0);
int numAxes = params.get<int>("num_axes", -1);
axis = params.get<int>("axis", 0);
numAxes = params.get<int>("num_axes", -1);
hasDynamicShapes = params.get<bool>("has_dynamic_shapes", false);
shapesInitialized = !hasDynamicShapes;
@ -224,7 +225,8 @@ public:
#endif
return backendId == DNN_BACKEND_OPENCV ||
backendId == DNN_BACKEND_CUDA ||
backendId == DNN_BACKEND_WEBNN;
backendId == DNN_BACKEND_WEBNN ||
backendId == DNN_BACKEND_CANN;
}
bool getMemoryShapes(const std::vector<MatShape> &inputs,
@ -324,6 +326,39 @@ public:
}
}
#ifdef HAVE_CANN
virtual Ptr<BackendNode> initCann(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes) CV_OVERRIDE
{
auto x = inputsWrapper[0].dynamicCast<CannBackendWrapper>();
// create operator
std::string op_name = cv::format("reshape_%d", index);
auto op = std::make_shared<ge::op::Reshape>(op_name);
// set attributes
op->set_attr_axis(axis);
op->set_attr_num_axes(numAxes);
// set inputs
// set inputs : x
auto op_x = nodes[0].dynamicCast<CannBackendNode>()->getOp();
op->set_input_x_by_name(*op_x, "y");
auto x_desc = x->getTensorDesc();
op->update_input_desc_x(*x_desc);
// set inputs : shape
std::vector<int> shape_of_shape{(int)newShapeDesc.size()};
Mat shape_mat(shape_of_shape, CV_32S, newShapeDesc.data());
auto op_const_shape = std::make_shared<CannConstOp>(shape_mat.data, shape_mat.type(), shape_of_shape, cv::format("%s_shape", op_name.c_str()));
op->set_input_shape(*(op_const_shape->getOp()));
op->update_input_desc_shape(*(op_const_shape->getTensorDesc()));
// set outputs
auto output_y_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
op->update_output_desc_y(*output_y_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
#endif // HAVE_CANN
#ifdef HAVE_DNN_NGRAPH
virtual Ptr<BackendNode> initNgraph(const std::vector<Ptr<BackendWrapper> >& inputs,
@ -464,6 +499,8 @@ public:
}
private:
int axis;
int numAxes;
std::vector<MatShape> outShapes;
std::vector<int> dynamicShapes; // Which axes shapes are dynamic and require reinitialization with new input
std::vector<int> inputIndices; // Which axes from input are needed to compute correct output shape

View File

@ -8,6 +8,7 @@
#include "layers_common.hpp"
#include "../op_cuda.hpp"
#include "../op_inf_engine.hpp"
#include "../op_cann.hpp"
#include <opencv2/imgproc.hpp>
#ifdef HAVE_DNN_NGRAPH
@ -77,6 +78,9 @@ public:
if (backendId == DNN_BACKEND_CUDA)
return interpolation == "nearest" || interpolation == "bilinear" || interpolation == "opencv_linear";
if (backendId == DNN_BACKEND_CANN)
return interpolation == "nearest" || interpolation == "bilinear" || interpolation == "opencv_linear";
#ifdef HAVE_INF_ENGINE
if (backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH)
{
@ -307,6 +311,67 @@ public:
CV_Error(Error::StsNotImplemented, "Unknown interpolation: " + interpolation);
}
#ifdef HAVE_CANN
virtual Ptr<BackendNode> initCann(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes) CV_OVERRIDE
{
auto x = inputsWrapper[0].dynamicCast<CannBackendWrapper>();
auto x_desc = x->getTensorDesc();
auto op_x = nodes[0].dynamicCast<CannBackendNode>()->getOp();
auto output_y_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
// create operator
std::string op_name = cv::format("resize_%d", index);
if (interpolation == "nearest")
{
auto op = std::make_shared<ge::op::ResizeNearestNeighborV2>(op_name);
// set attributes
op->set_attr_align_corners(alignCorners);
op->set_attr_half_pixel_centers(halfPixelCenters);
// set inputs : x
op->set_input_x_by_name(*op_x, "y");
op->update_input_desc_x(*x_desc);
// set inputs : size
std::vector<int> shape_of_size_mat{2};
Mat size_mat(2, 1, CV_32S, Scalar(outHeight, outWidth));
auto op_const_size = std::make_shared<CannConstOp>(size_mat.data, size_mat.type(), shape_of_size_mat, cv::format("%s_size", op_name.c_str()));
op->set_input_size(*(op_const_size->getOp()));
op->update_input_desc_size(*(op_const_size->getTensorDesc()));
// set outputs
op->update_output_desc_y(*output_y_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
else if (interpolation == "opencv_linear" || interpolation == "bilinear")
{
auto op = std::make_shared<ge::op::ResizeBilinearV2>(op_name);
// set attributes
op->set_attr_align_corners(alignCorners);
op->set_attr_half_pixel_centers(halfPixelCenters);
// set inputs : x
op->set_input_x_by_name(*op_x, "y");
op->update_input_desc_x(*x_desc);
// set inputs : size
std::vector<int> shape_of_size_mat{2};
Mat size_mat(2, 1, CV_32S, Scalar(outHeight, outWidth));
auto op_const_size = std::make_shared<CannConstOp>(size_mat.data, size_mat.type(), shape_of_size_mat, cv::format("%s_size", op_name.c_str()));
op->set_input_size(*(op_const_size->getOp()));
op->update_input_desc_size(*(op_const_size->getTensorDesc()));
// set outputs
op->update_output_desc_y(*output_y_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
else
CV_Error(Error::StsNotImplemented, "Unsupported interpolation by CANN backend: " + interpolation);
}
#endif // HAVE_CANN
#ifdef HAVE_DNN_NGRAPH
virtual Ptr<BackendNode> initNgraph(const std::vector<Ptr<BackendWrapper> >& inputs,

View File

@ -44,6 +44,7 @@
#include "../op_cuda.hpp"
#include "../op_inf_engine.hpp"
#include "../ie_ngraph.hpp"
#include "../op_cann.hpp"
#include "layers_common.hpp"
#include <opencv2/dnn/shape_utils.hpp>
@ -198,7 +199,7 @@ public:
if (backendId == DNN_BACKEND_CUDA)
return !hasSteps;
#endif
return backendId == DNN_BACKEND_OPENCV;
return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CANN;
}
bool getMemoryShapes(const std::vector<MatShape> &inputs,
@ -589,6 +590,66 @@ public:
}
}
#ifdef HAVE_CANN
virtual Ptr<BackendNode> initCann(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes) CV_OVERRIDE
{
CV_Assert(sliceRanges.size() == 1);
CV_Assert(sliceSteps.size() == 1);
CV_Assert(sliceRanges[0].size() == sliceSteps[0].size());
auto x = inputsWrapper[0].dynamicCast<CannBackendWrapper>();
const int dims = x->host->dims;
// create operator
std::string op_name = cv::format("slice_%d", index);
auto op = std::make_shared<ge::op::StridedSliceV2>(op_name);
// retrieve begins, ends, axes and steps
std::vector<int> begins, ends, axes, steps;
for (int i = 0; i < sliceRanges[0].size(); i++)
{
begins.push_back(sliceRanges[0][i].start);
ends.push_back(sliceRanges[0][i].end);
axes.push_back(i);
steps.push_back(sliceSteps[0][i]);
}
std::vector<int> shape_{dims};
// set inputs
// set inputs : x
auto op_x = nodes[0].dynamicCast<CannBackendNode>()->getOp();
op->set_input_x_by_name(*op_x, "y");
auto x_desc = x->getTensorDesc();
op->update_input_desc_x(*x_desc);
// set inputs : begin
Mat begin_mat(shape_, CV_32S, &begins[0]);
auto op_const_begin = std::make_shared<CannConstOp>(begin_mat.data, begin_mat.type(), shape_, cv::format("%s_begin", op_name.c_str()));
op->set_input_begin(*(op_const_begin->getOp()));
op->update_input_desc_begin(*(op_const_begin->getTensorDesc()));
// set inputs : end
Mat end_mat(shape_, CV_32S, &ends[0]);
auto op_const_end = std::make_shared<CannConstOp>(end_mat.data, end_mat.type(), shape_, cv::format("%s_end", op_name.c_str()));
op->set_input_end(*(op_const_end->getOp()));
op->update_input_desc_end(*(op_const_end->getTensorDesc()));
// set inputs : axes
Mat axes_mat(shape_, CV_32S, &axes[0]);
auto op_const_axes = std::make_shared<CannConstOp>(axes_mat.data, axes_mat.type(), shape_, cv::format("%s_axes", op_name.c_str()));
op->set_input_axes(*(op_const_axes->getOp()));
op->update_input_desc_axes(*(op_const_axes->getTensorDesc()));
// set inputs : strides
Mat strides_mat(shape_, CV_32S, &steps[0]);
auto op_const_strides = std::make_shared<CannConstOp>(strides_mat.data, strides_mat.type(), shape_, cv::format("%s_strides", op_name.c_str()));
op->set_input_strides(*(op_const_strides->getOp()));
op->update_input_desc_strides(*(op_const_strides->getTensorDesc()));
// set outputs
auto output_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
op->update_output_desc_y(*output_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
#endif
#ifdef HAVE_DNN_NGRAPH
virtual Ptr<BackendNode> initNgraph(const std::vector<Ptr<BackendWrapper> >& inputs,

View File

@ -48,6 +48,7 @@
#include "../ie_ngraph.hpp"
#include "../op_vkcom.hpp"
#include "../op_webnn.hpp"
#include "../op_cann.hpp"
#include <algorithm>
#include <stdlib.h>
@ -116,7 +117,8 @@ public:
return backendId == DNN_BACKEND_OPENCV ||
backendId == DNN_BACKEND_CUDA ||
(backendId == DNN_BACKEND_HALIDE && haveHalide() && axisRaw == 1) ||
(backendId == DNN_BACKEND_VKCOM && haveVulkan());
(backendId == DNN_BACKEND_VKCOM && haveVulkan()) ||
backendId == DNN_BACKEND_CANN;
}
#ifdef HAVE_OPENCL
@ -362,6 +364,34 @@ public:
return Ptr<BackendNode>();
}
#ifdef HAVE_CANN
virtual Ptr<BackendNode> initCann(const std::vector<Ptr<BackendWrapper> > &inputsWrapper, const int index, const std::vector<Ptr<BackendNode> >& nodes) CV_OVERRIDE
{
auto x = inputsWrapper[0].dynamicCast<CannBackendWrapper>();
// create operator
std::string op_name = cv::format("softmax_%d", index);
auto op = std::make_shared<ge::op::SoftmaxV2>(op_name);
// set attributes
op->set_attr_axes(ge::Operator::OpListInt(
{(int64_t)axisRaw}
));
// set inputs
// set inputs : x
auto op_x = nodes[0].dynamicCast<CannBackendNode>()->getOp();
op->set_input_x_by_name(*op_x, "y");
auto x_desc = x->getTensorDesc();
op->update_input_desc_x(*x_desc);
// set outputs
auto output_y_desc = std::make_shared<ge::TensorDesc>(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT);
op->update_output_desc_y(*output_y_desc);
return Ptr<BackendNode>(new CannBackendNode(op));
}
#endif // HAVE_CANN
#ifdef HAVE_DNN_NGRAPH
virtual Ptr<BackendNode> initNgraph(const std::vector<Ptr<BackendWrapper> >& inputs,

View File

@ -13,6 +13,7 @@
#include "op_cuda.hpp"
#include "op_webnn.hpp"
#include "op_timvx.hpp"
#include "op_cann.hpp"
namespace cv {
namespace dnn {
@ -115,6 +116,10 @@ Ptr<BackendWrapper> wrapMat(int backendId, int targetId, cv::Mat& m)
return Ptr<BackendWrapper>(new TimVXBackendWrapper(m));
#endif // HAVE_TIMVX
}
else if (backendId == DNN_BACKEND_CANN)
{
CV_Assert(0 && "Internal error: DNN_BACKEND_CANN must be implemented through inheritance");
}
else
CV_Error(Error::StsNotImplemented, "Unknown backend identifier");
return Ptr<BackendWrapper>(); // TODO Error?

View File

@ -0,0 +1,348 @@
// 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.
#include "precomp.hpp"
#include <opencv2/core/utils/logger.hpp>
#include "net_impl.hpp"
namespace cv { namespace dnn {
CV__DNN_INLINE_NS_BEGIN
#ifdef HAVE_CANN
static std::shared_ptr<ge::ModelBufferData> compileCannGraph(std::shared_ptr<ge::Graph> graph);
class NetImplCann CV_FINAL : public Net::Impl
{
public:
typedef Net::Impl Base;
bool newWasSupported, netWasConverted;
explicit NetImplCann(const Ptr<Net::Impl>& basePtr)
: Net::Impl()
{
CV_LOG_INFO(NULL, "Initializing NetImplCann");
basePtr_ = basePtr;
newWasSupported = true;
netWasConverted = false;
init();
CV_LOG_INFO(NULL, "Finished initializing NetImplCann");
}
void init()
{
CV_TRACE_FUNCTION();
CV_Assert(basePtr_);
Net::Impl& base = *basePtr_;
CV_Assert(!base.netWasAllocated);
CV_Assert(!base.netWasQuantized); // does not support quantized net for now
netInputLayer = base.netInputLayer;
blobsToKeep = base.blobsToKeep;
layers = base.layers;
for (MapIdToLayerData::iterator it = layers.begin(); it != layers.end(); it++)
{
LayerData& ld = it->second;
ld.resetAllocation();
}
layerNameToId = base.layerNameToId;
outputNameToId = base.outputNameToId;
preferableBackend = DNN_BACKEND_CANN;
preferableTarget = DNN_TARGET_NPU; // force using NPU
hasDynamicShapes = base.hasDynamicShapes;
CV_Assert(base.backendWrappers.empty()); //backendWrappers = base.backendWrappers;
lastLayerId = base.lastLayerId;
netWasAllocated = base.netWasAllocated;
netWasQuantized = base.netWasQuantized;
fusion = base.fusion;
}
bool empty() const override
{
return Base::empty();
}
void setPreferableBackend(Net& net, int backendId) override
{
if (backendId == preferableBackend)
return; // no-op
else
CV_Error(Error::StsError, "DNN: Can't switch backend from CANN to other");
Ptr<Net::Impl>& impl_ptr_ref = accessor::DnnNetAccessor::getImplPtrRef(net);
impl_ptr_ref = basePtr_;
basePtr_->setPreferableBackend(net, backendId);
}
void setPreferableTarget(int targetId) override
{
if (targetId != preferableTarget)
{
CV_Error(Error::StsError, "DNN: Can't switch target from NPU to other");
}
}
Ptr<BackendWrapper> wrap(Mat& host) override
{
return Ptr<BackendWrapper>(new CannBackendWrapper(host));
}
// void fuseLayers(const std::vector<LayerPin>& blobsToKeep_); // fusion is done in the CANN graph engine
void initBackend(const std::vector<LayerPin>& blobsToKeep_) override;
void forwardLayer(LayerData& ld) override;
};
void NetImplCann::initBackend(const std::vector<LayerPin>& blobsToKeep_)
{
CV_TRACE_FUNCTION();
CV_CheckEQ(preferableBackend, DNN_BACKEND_CANN, "");
// netWasAllocated turns to false if requested output is changed or input shape changes
if (netWasConverted && netWasAllocated)
return;
if (!netWasConverted)
{
newWasSupported = true;
for (MapIdToLayerData::iterator it = layers.begin(); it != layers.end(); ++it)
{
auto& ld = it->second;
auto layer = ld.layerInstance;
if (ld.id != 0 && !layer->supportBackend(preferableBackend))
{
newWasSupported = false;
CV_LOG_INFO(NULL, "DNN/CANN: layer (name=" << ld.name << ", type=" << ld.type << ") is not supported by CANN backend. Going back to CPU backend");
}
}
}
if (!newWasSupported)
return ;
// convert layers to CANN operators,
// collect graph input and output operators,
// collect and input and output wrappers
int firstOutputLayerId = -1;
std::vector<Ptr<BackendNode> > netInputNodes;
std::vector<ge::Operator> graphInputOps, graphOutputOps;
std::vector<Ptr<BackendWrapper>> graphInputWrappers, graphOutputWrappers;
CV_LOG_INFO(NULL, "DNN/CANN: converting layers to CANN operators");
for (MapIdToLayerData::iterator it = layers.begin(); it != layers.end(); ++it)
{
LayerData& ld = it->second;
auto layer = ld.layerInstance;
if (ld.id == 0)
{
for (int i = 0; i < ld.outputBlobsWrappers.size(); i++)
{
std::string inputName = netInputLayer->outNames.empty() ? cv::format("%s_%d", ld.name.c_str(), i) : netInputLayer->outNames[i];
auto inputOp = std::make_shared<ge::op::Data>(inputName);
// retrieve tensor description
auto wrapper = ld.outputBlobsWrappers[i];
graphInputWrappers.push_back(wrapper);
auto cannWrapper = wrapper.dynamicCast<CannBackendWrapper>();
CV_Assert(!cannWrapper.empty());
inputOp->update_input_desc_x(*(cannWrapper->desc_));
inputOp->update_output_desc_y(*(cannWrapper->desc_));
graphInputOps.push_back(*inputOp);
netInputNodes.push_back(Ptr<BackendNode>(new CannBackendNode(inputOp)));
}
}
else
{
ld.skip = true; // skip all cann operators
std::vector<Ptr<BackendNode> > layerInputNodes;
for (int i = 0; i < ld.inputBlobsId.size(); i++)
{
int layerInputLid = ld.inputBlobsId[i].lid;
int layerInputOid = ld.inputBlobsId[i].oid;
if (layerInputLid == 0)
{
layerInputNodes.push_back(netInputNodes[layerInputOid]);
}
else // here we do not consider an op with multiple outputs
{
layerInputNodes.push_back(layers[layerInputLid].backendNodes[preferableBackend]);
}
}
CV_LOG_INFO(NULL, "DNN/CANN: converting layer " << ld.name << "@" << ld.type << "@" << ld.id << " to CANN operator");
auto backendNode = layer->initCann(ld.inputBlobsWrappers, ld.id, layerInputNodes);
// collect outputs
bool isOutputNode = ld.consumers.size() == 0 ? true : false;
if (isOutputNode)
{
if (firstOutputLayerId < 0)
firstOutputLayerId = ld.id;
auto cannNode = backendNode.dynamicCast<CannBackendNode>();
graphOutputOps.push_back(*(cannNode->getOp()));
// assume cann graph outputs and dnn net outputs have the same order
for (int i = 0; i < ld.outputBlobsWrappers.size(); ++i)
{
graphOutputWrappers.push_back(ld.outputBlobsWrappers[i]);
}
}
ld.backendNodes[preferableBackend] = backendNode;
}
}
CV_LOG_INFO(NULL, "DNN/CANN: done converting layers to CANN operators");
// build graph from collected graph inputs and outputs
CV_LOG_INFO(NULL, "DNN/CANN: building ge::Graph");
std::string graphName = cv::format("graph_%d", 0);
std::shared_ptr<ge::Graph> graph = std::make_shared<ge::Graph>(graphName.c_str());
(void)graph->SetInputs(graphInputOps);
(void)graph->SetOutputs(graphOutputOps);
CV_LOG_INFO(NULL, "DNN/CANN: done building ge::Graph");
// convert ge::Graph to OM buffer
CV_LOG_INFO(NULL, "DNN/CANN: converting ge::Graph to OM buffer");
std::shared_ptr<ge::ModelBufferData> modelBuffer = compileCannGraph(graph);
CV_LOG_INFO(NULL, "DNN/CANN: OM buffer size = " << modelBuffer->length);
CV_LOG_INFO(NULL, "DNN/CANN: done building ge::Graph to OM buffer");
// keep net in the first output node and mark the node runnable
auto& ld = layers[firstOutputLayerId];
auto cannNode = ld.backendNodes[preferableBackend].dynamicCast<CannBackendNode>();
std::shared_ptr<CannNet> net = std::shared_ptr<CannNet>(new CannNet());
net->loadModelBuffer(modelBuffer);
net->bindInputWrappers(graphInputWrappers);
net->bindOutputWrappers(graphOutputWrappers);
cannNode->net = net;
ld.skip = false;
netWasConverted = true;
}
void NetImplCann::forwardLayer(LayerData& ld)
{
CV_TRACE_FUNCTION();
auto layer = ld.layerInstance;
if (!ld.skip)
{
auto it = ld.backendNodes.find(preferableBackend);
if (ld.id == 0 || it == ld.backendNodes.end()) // input layer
{
return Base::forwardLayer(ld);
}
CV_Assert(it != ld.backendNodes.end());
const Ptr<BackendNode>& node = it->second;
CV_Assert(!node.empty());
auto cannNode = node.dynamicCast<CannBackendNode>();
CV_Assert(!cannNode.empty());
CV_Assert(cannNode->net);
TickMeter tm;
tm.start();
cannNode->net->forward();
tm.stop();
int64_t t = tm.getTimeTicks();
layersTimings[ld.id] = (t > 0) ? t : 1;
}
else
{
layersTimings[ld.id] = 0;
}
ld.flag = 1;
}
std::shared_ptr<ge::ModelBufferData> compileCannGraph(std::shared_ptr<ge::Graph> graph)
{
const size_t hdrsize = 32;
std::shared_ptr<ge::ModelBufferData> out_buffer = std::make_shared<ge::ModelBufferData>();
size_t buf_size = (1 << 27), model_size; // default buf_size 128 MB
for (int iter = 0; iter < 2; ++iter)
{
size_t* shared_buf = (size_t*)mmap(NULL, buf_size + hdrsize, PROT_READ|PROT_WRITE,
MAP_SHARED|MAP_ANONYMOUS, -1, 0);
uint8_t* model_data = (uint8_t*)(shared_buf + 1);
pid_t child;
int childstate = 0;
bool ok;
if ((child=fork()) == 0)
{
// initialize engine
std::map<ge::AscendString, ge::AscendString> options = {
{ge::AscendString(ge::ir_option::SOC_VERSION), ge::AscendString("Ascend310")},
};
ACL_CHECK_GRAPH_RET(ge::aclgrphBuildInitialize(options));
// build
std::shared_ptr<ge::ModelBufferData> om_model = std::make_shared<ge::ModelBufferData>();
std::map<ge::AscendString, ge::AscendString> build_options;
ACL_CHECK_GRAPH_RET(aclgrphBuildModel(*graph, build_options, *om_model));
#if 0
// (optional). Dump model
AscendString graph_name;
graph.GetName(graph_name);
aclgrphDumpGraph(graph, graph_name.GetString(), 7);
// (optional). Save model
aclgrphSaveModel(graph_name.GetString(), *om_model);
#endif
// finalize engine
ge::aclgrphBuildFinalize();
// send model from child to parent
size_t model_size = om_model->length;
*shared_buf = model_size;
if (model_size > buf_size)
{
exit(1);
}
else
{
memcpy(model_data, om_model->data.get(), model_size);
exit(0);
}
}
waitpid (child, &childstate, 0);
model_size = *shared_buf;
ok = WIFEXITED(childstate) && WEXITSTATUS(childstate) == 0;
if (ok)
{
CV_LOG_INFO(NULL, "Compile success, model size = " << model_size);
out_buffer->data = std::shared_ptr<uint8_t>(new uint8_t[model_size]);
memcpy(out_buffer->data.get(), model_data, model_size);
out_buffer->length = model_size;
}
munmap(shared_buf, buf_size + hdrsize);
if (ok) break;
buf_size = model_size;
}
return out_buffer;
}
void switchToCannBackend(Net& net)
{
CV_TRACE_FUNCTION();
Ptr<Net::Impl>& impl_ptr_ref = accessor::DnnNetAccessor::getImplPtrRef(net);
CV_Assert(impl_ptr_ref);
CV_LOG_INFO(NULL, "DNN: switching to CANN backend... (networkID=" << impl_ptr_ref->networkId << ")");
Ptr<NetImplCann> impl_ptr_cann = makePtr<NetImplCann>(impl_ptr_ref);
impl_ptr_ref = impl_ptr_cann;
}
#endif // HAVE_CANN
CV__DNN_INLINE_NS_END
}} // namespace cv::dnn

View File

@ -518,8 +518,8 @@ void Net::Impl::allocateLayer(int lid, const LayersShapesMap& layersShapes)
for (int i = 0; i < ld.outputBlobs.size(); ++i)
ld.outputBlobsWrappers[i] = wrap(ld.outputBlobs[i]);
/* CUDA backend has its own system for internal blobs; we don't need these */
ld.internalBlobsWrappers.resize((preferableBackend == DNN_BACKEND_CUDA || preferableBackend == DNN_BACKEND_TIMVX) ? 0 : ld.internals.size());
/* CUDA & CANN backend has its own system for internal blobs; we don't need these */
ld.internalBlobsWrappers.resize((preferableBackend == DNN_BACKEND_CUDA || preferableBackend == DNN_BACKEND_TIMVX || preferableBackend == DNN_BACKEND_CANN) ? 0 : ld.internals.size());
for (int i = 0; i < ld.internalBlobsWrappers.size(); ++i)
ld.internalBlobsWrappers[i] = wrap(ld.internals[i]);
@ -1566,6 +1566,7 @@ string Net::Impl::dump(bool forceAllocation) const
case DNN_BACKEND_CUDA: backend = "CUDA/"; break;
case DNN_BACKEND_WEBNN: backend = "WEBNN/"; break;
case DNN_BACKEND_TIMVX: backend = "TIMVX/"; break;
case DNN_BACKEND_CANN: backend = "CANN/"; break;
// don't use default:
}
out << "digraph G {\n";

View File

@ -12,6 +12,7 @@
#include "op_cuda.hpp"
#include "op_webnn.hpp"
#include "op_timvx.hpp"
#include "op_cann.hpp"
#include <opencv2/dnn/shape_utils.hpp>
#include <opencv2/imgproc.hpp>

View File

@ -85,6 +85,10 @@ Ptr<BackendWrapper> Net::Impl::wrap(Mat& host)
return Ptr<BackendWrapper>(new TimVXBackendWrapper(baseBuffer, host));
#endif
}
else if (preferableBackend == DNN_BACKEND_CANN)
{
CV_Assert(0 && "Internal error: DNN_BACKEND_CANN must be implemented through inheritance");
}
else
CV_Error(Error::StsNotImplemented, "Unknown backend identifier");
}
@ -146,6 +150,10 @@ void Net::Impl::initBackend(const std::vector<LayerPin>& blobsToKeep_)
CV_Error(Error::StsNotImplemented, "This OpenCV version is built without support of TimVX");
#endif
}
else if (preferableBackend == DNN_BACKEND_CANN)
{
CV_Assert(0 && "Internal error: DNN_BACKEND_CANN must be implemented through inheritance");
}
else
{
CV_Error(Error::StsNotImplemented, cv::format("Unknown backend identifier: %d", preferableBackend));
@ -179,6 +187,14 @@ void Net::Impl::setPreferableBackend(Net& net, int backendId)
networkBackend.switchBackend(net);
#else
CV_Error(Error::StsNotImplemented, "OpenVINO backend is not available in the current OpenCV build");
#endif
}
else if (backendId == DNN_BACKEND_CANN)
{
#ifdef HAVE_CANN
switchToCannBackend(net);
#else
CV_Error(Error::StsNotImplemented, "CANN backend is not availlable in the current OpenCV build");
#endif
}
else

329
modules/dnn/src/op_cann.cpp Normal file
View File

@ -0,0 +1,329 @@
// 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.
#include "precomp.hpp"
#include "op_cann.hpp"
#include <mutex>
#include <map>
#include <cstring> // memcpy
#include <opencv2/dnn/shape_utils.hpp>
#include <opencv2/core/utils/logger.hpp>
namespace cv { namespace dnn {
#ifdef HAVE_CANN
std::shared_ptr<AclEnvGuard> AclEnvGuard::global_acl_env_ = nullptr;
std::mutex AclEnvGuard::global_acl_env_mutex_;
AclEnvGuard::AclEnvGuard()
{
CV_LOG_INFO(NULL, "Start to initialize CANN");
ACL_CHECK_RET(aclInit(NULL));
CV_LOG_INFO(NULL, "[Success] initialized CANN");
}
AclEnvGuard::~AclEnvGuard()
{
CV_LOG_INFO(NULL, "Start to finalize CANN");
ACL_CHECK_RET(aclFinalize());
CV_LOG_INFO(NULL, "[Success] finalized CANN");
}
std::shared_ptr<AclEnvGuard> AclEnvGuard::GetAclEnv()
{
std::shared_ptr<AclEnvGuard> acl_env;
std::lock_guard<std::mutex> lock(global_acl_env_mutex_);
acl_env = global_acl_env_;
if (acl_env != nullptr)
{
CV_LOG_INFO(NULL, "CANN has been initialized. Skipping...");
}
else
{
acl_env = std::make_shared<AclEnvGuard>();
global_acl_env_ = acl_env;
}
return acl_env;
}
CannConstOp::CannConstOp(const uint8_t* data, const int dtype, const std::vector<int>& shape, const std::string& name)
{
std::vector<int64_t> shape_{shape.begin(), shape.end()};
auto ge_shape = ge::Shape(shape_);
auto ge_dtype = ge::DT_FLOAT;
switch (dtype)
{
case CV_32F: break;
case CV_32S: ge_dtype = ge::DT_INT32; break;
default: CV_Error(Error::StsNotImplemented, "Unsupported data type");
}
auto size_of_type = sizeof(float);
switch (dtype)
{
case CV_32F: break;
case CV_32S: size_of_type = sizeof(int); break;
default: CV_Error(Error::StsNotImplemented, "Unsupported data type");
}
desc_ = std::make_shared<ge::TensorDesc>(ge_shape, ge::FORMAT_NCHW, ge_dtype);
auto ge_tensor = std::make_shared<ge::Tensor>();
ge_tensor->SetTensorDesc(*desc_);
ge_tensor->SetData(data, ge_shape.GetShapeSize() * size_of_type);
op_ = std::make_shared<ge::op::Const>(name);
op_->set_attr_value(*ge_tensor);
}
CannBackendNode::CannBackendNode(const std::shared_ptr<ge::Operator>& op)
: BackendNode(DNN_BACKEND_CANN), op_(op) { }
std::shared_ptr<ge::Operator> CannBackendNode::getOp() { return op_; }
CannBackendWrapper::CannBackendWrapper(const Mat& m)
: BackendWrapper(DNN_BACKEND_CANN, DNN_TARGET_NPU), host((Mat*)&m)
{
auto mat_shape = shape(*host);
std::vector<int64_t> shape_{mat_shape.begin(), mat_shape.end()};
auto ge_shape = ge::Shape(shape_);
desc_ = std::make_shared<ge::TensorDesc>(ge_shape, ge::FORMAT_NCHW, ge::DT_FLOAT);
}
void CannBackendWrapper::copyToHost()
{
CV_LOG_DEBUG(NULL, "Not implemented");
}
void CannBackendWrapper::setHostDirty()
{
CV_LOG_DEBUG(NULL, "Not implemented");
}
CannNet::~CannNet()
{
CV_LOG_INFO(NULL, "In ~CannNet, inputs = " << inputs << ", outputs = " << outputs);
if (!model_desc)
{
CV_LOG_INFO(NULL, "[Failed] Tried to deconstruct CannNet but model is not loaded");
return;
}
// free datasets: inputs, outputs
if (inputs)
{
CV_LOG_INFO(NULL, "In ~CannNet: destroy inputs");
destroyDataset(&inputs);
}
if (outputs)
{
CV_LOG_INFO(NULL, "In ~CannNet: destroy outputs");
destroyDataset(&outputs);
}
// unload model
ACL_CHECK_RET(aclmdlUnload(model_id));
// destroy model_desc
ACL_CHECK_RET(aclmdlDestroyDesc(model_desc));
model_desc = nullptr;
CV_LOG_INFO(NULL, "[Success] Unloaded model (id=" << model_id << ")");
// destroy context
if (context != nullptr)
{
ACL_CHECK_RET(aclrtDestroyContext(context));
context = nullptr;
}
// reset device
if (context == nullptr)
{
ACL_CHECK_RET(aclrtResetDevice(device_id));
}
}
bool CannNet::empty() const
{
return (model_desc == nullptr);
}
void CannNet::loadModelBuffer(std::shared_ptr<ge::ModelBufferData> modelBuffer)
{
model.clear();
model.resize(modelBuffer->length);
std::memcpy(reinterpret_cast<void*>(model.data()),
reinterpret_cast<void*>(modelBuffer->data.get()),
modelBuffer->length);
loadToDevice();
}
void CannNet::bindInputWrappers(const std::vector<Ptr<BackendWrapper>>& inputWrappers)
{
CV_Assert(inputWrappers.size() == getInputNum());
for (size_t i = 0; i < inputWrappers.size(); ++i)
{
auto wrapper = inputWrappers[i].dynamicCast<CannBackendWrapper>();
// verify size
aclmdlIODims model_dims;
ACL_CHECK_RET(aclmdlGetInputDims(model_desc, i, &model_dims));
CV_CheckEQ((int)model_dims.dimCount, wrapper->host->dims, "Dimension of input does not match with model's requirement");
for (size_t j = 0; j < model_dims.dimCount; ++j)
CV_CheckEQ((int)model_dims.dims[j], wrapper->host->size[j], "Size of input does not match with model's requirement");
input_wrappers.push_back(wrapper);
}
}
void CannNet::bindOutputWrappers(const std::vector<Ptr<BackendWrapper>>& outputWrappers)
{
CV_Assert(outputWrappers.size() == getOutputNum());
for (int i = 0; i < outputWrappers.size(); ++i)
{
auto wrapper = outputWrappers[i].dynamicCast<CannBackendWrapper>();
// verify size
aclmdlIODims model_dims;
ACL_CHECK_RET(aclmdlGetOutputDims(model_desc, i, &model_dims));
CV_CheckEQ((int)model_dims.dimCount, wrapper->host->dims, "Dimension of input does not match with model's requirement");
for (size_t j = 0; j < model_dims.dimCount; ++j)
CV_CheckEQ((int)model_dims.dims[j], wrapper->host->size[j], "Size of input does not match with model's requirement");
output_wrappers.push_back(wrapper);
}
}
void CannNet::forward()
{
// send inputs from host to device
CV_LOG_DEBUG(NULL, "DNN/CANN: start sending inputs to device");
for (size_t i = 0; i < input_wrappers.size(); ++i)
{
const void* p_host = (const void*)input_wrappers[i]->host->data;
auto db = aclmdlGetDatasetBuffer(inputs, i);
auto p_device = aclGetDataBufferAddr(db);
auto db_size = aclGetDataBufferSizeV2(db);
ACL_CHECK_RET(aclrtMemcpy(p_device, db_size, p_host, db_size, ACL_MEMCPY_HOST_TO_DEVICE));
}
CV_LOG_DEBUG(NULL, "DNN/CANN: finished sending inputs to device");
// forward
CV_LOG_DEBUG(NULL, "DNN/CANN: start network forward");
ACL_CHECK_RET(aclrtSetCurrentContext(context));
ACL_CHECK_RET(aclmdlExecute(model_id, inputs, outputs));
CV_LOG_DEBUG(NULL, "DNN/CANN: finished network forward");
// fetch ouputs from device to host
CV_LOG_DEBUG(NULL, "DNN/CANN: start fetching outputs to host");
for (size_t i = 0; i < output_wrappers.size(); ++i)
{
void* p_host = (void*)output_wrappers[i]->host->data;
auto db = aclmdlGetDatasetBuffer(outputs, i);
auto p_device = aclGetDataBufferAddr(db);
auto db_size = aclGetDataBufferSizeV2(db);
ACL_CHECK_RET(aclrtMemcpy(p_host, db_size, p_device, db_size, ACL_MEMCPY_DEVICE_TO_HOST));
}
CV_LOG_DEBUG(NULL, "DNN/CANN: finish fetching outputs to host");
}
size_t CannNet::getInputNum() const
{
return aclmdlGetNumInputs(model_desc);
}
size_t CannNet::getOutputNum() const
{
return aclmdlGetNumOutputs(model_desc);
}
void CannNet::init()
{
ACL_CHECK_RET(aclrtSetDevice(device_id));
ACL_CHECK_RET(aclrtCreateContext(&context, device_id));
}
void CannNet::loadToDevice()
{
if (model_desc != nullptr)
{
CV_LOG_INFO(NULL, "Model has been loaded to device. Skipping ...");
return;
}
CV_LOG_INFO(NULL, "Load model to NPU memory");
ACL_CHECK_RET(aclmdlLoadFromMem(reinterpret_cast<const void*>(model.data()), model.size(), &model_id));
CV_LOG_INFO(NULL, "Create model description");
model_desc = aclmdlCreateDesc();
ACL_CHECK_RET(aclmdlGetDesc(model_desc, model_id));
createInputDataset();
createOutputDataset();
}
void CannNet::createInputDataset()
{
inputs = aclmdlCreateDataset();
size_t n_inputs = aclmdlGetNumInputs(model_desc);
size_t length;
for (size_t i = 0; i < n_inputs; i++)
{
length = aclmdlGetInputSizeByIndex(model_desc, i);
CV_LOG_INFO(NULL, "length = " << length);
void* p_device = nullptr;
ACL_CHECK_RET(aclrtMalloc(&p_device, length, ACL_MEM_MALLOC_NORMAL_ONLY));
auto p_data_buffer = aclCreateDataBuffer(p_device, length);
ACL_CHECK_RET(aclmdlAddDatasetBuffer(inputs, p_data_buffer));
}
}
void CannNet::createOutputDataset()
{
outputs = aclmdlCreateDataset();
size_t n_outputs = aclmdlGetNumOutputs(model_desc);
size_t length;
for (size_t i = 0; i < n_outputs; i++)
{
length = aclmdlGetOutputSizeByIndex(model_desc, i);
void* p_device = nullptr;
ACL_CHECK_RET(aclrtMalloc(&p_device, length, ACL_MEM_MALLOC_NORMAL_ONLY));
auto p_data_buffer = aclCreateDataBuffer(p_device, length);
ACL_CHECK_RET(aclmdlAddDatasetBuffer(outputs, p_data_buffer));
}
}
void CannNet::destroyDataset(aclmdlDataset** dataset)
{
if (!dataset)
{
CV_LOG_INFO(NULL, "CANN dataset is not initialized");
return;
}
auto buffer_count = aclmdlGetDatasetNumBuffers(*dataset);
CV_LOG_INFO(NULL, "buffer_count = " << buffer_count);
for (auto i = 0; i < buffer_count; i++)
{
auto data_buffer = aclmdlGetDatasetBuffer(*dataset, i);
auto p_device = aclGetDataBufferAddr(data_buffer);
if (p_device)
{
ACL_CHECK_RET(aclrtFree(p_device)); // 107000?
}
else
{
CV_LOG_INFO(NULL, "Data buffer (i=" << i << ") from ACL dataset is invalid");
}
ACL_CHECK_RET(aclDestroyDataBuffer(data_buffer));
}
ACL_CHECK_RET(aclmdlDestroyDataset(*dataset));
*dataset = nullptr;
CV_LOG_INFO(NULL, "[Success] Destroyed dataset");
}
#endif // HAVE_CANN
}} // namespace cv::dnn

164
modules/dnn/src/op_cann.hpp Normal file
View File

@ -0,0 +1,164 @@
// 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.
#ifndef OPENCV_DNN_OP_CANN_HPP
#define OPENCV_DNN_OP_CANN_HPP
#ifdef HAVE_CANN
#include "acl/acl.h" // acl* functions
#include "graph/graph.h" // ge::Graph; ge::Operator from operator.h
#include "graph/ge_error_codes.h" // GRAPH_SUCCESS, ...
#include "op_proto/built-in/inc/all_ops.h" // ge::Conv2D, ...
#include "graph/tensor.h" // ge::Shape, ge::Tensor, ge::TensorDesc
#include "graph/types.h" // DT_FLOAT, ... ; FORMAT_NCHW, ...
#include "ge/ge_api_types.h" // ge::ir_option::SOC_VERSION
#include "ge/ge_ir_build.h" // build graph
// for fork()
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#endif // HAVE_CANN
#include <vector>
#ifdef HAVE_CANN
#define ACL_CHECK_RET(f) \
{ \
if (f != ACL_SUCCESS) \
{ \
CV_LOG_ERROR(NULL, "CANN check failed, ret = " << f); \
CV_Error(Error::StsError, "CANN check failed"); \
} \
}
#define ACL_CHECK_GRAPH_RET(f) \
{ \
if (f != ge::GRAPH_SUCCESS) \
{ \
CV_LOG_ERROR(NULL, "CANN graph check failed, ret = " << f); \
CV_Error(Error::StsError, "CANN graph check failed"); \
} \
}
#endif
namespace cv { namespace dnn {
#ifdef HAVE_CANN
CV__DNN_INLINE_NS_BEGIN
void switchToCannBackend(Net& net);
CV__DNN_INLINE_NS_END
class CannNet;
class AclEnvGuard {
public:
explicit AclEnvGuard();
~AclEnvGuard();
static std::shared_ptr<AclEnvGuard> GetAclEnv();
private:
static std::shared_ptr<AclEnvGuard> global_acl_env_;
static std::mutex global_acl_env_mutex_;
};
class CannConstOp
{
public:
CannConstOp(const uint8_t* data, const int dtype, const std::vector<int>& shape, const std::string& name);
std::shared_ptr<ge::op::Const> getOp() { return op_; }
std::shared_ptr<ge::TensorDesc> getTensorDesc() { return desc_; }
private:
std::shared_ptr<ge::op::Const> op_;
std::shared_ptr<ge::TensorDesc> desc_;
};
class CannBackendNode : public BackendNode
{
public:
CannBackendNode(const std::shared_ptr<ge::Operator>& op);
std::shared_ptr<ge::Operator> getOp();
std::shared_ptr<CannNet> net;
private:
std::shared_ptr<ge::Operator> op_;
};
class CannBackendWrapper : public BackendWrapper
{
public:
CannBackendWrapper(const Mat& m);
~CannBackendWrapper() { }
std::shared_ptr<ge::TensorDesc> getTensorDesc() { return desc_; }
virtual void copyToHost() CV_OVERRIDE;
virtual void setHostDirty() CV_OVERRIDE;
Mat* host;
std::shared_ptr<ge::TensorDesc> desc_;
};
class CannNet
{
public:
explicit CannNet(int deviceId = 0)
: device_id(deviceId)
{
init();
acl_env = AclEnvGuard::GetAclEnv();
}
~CannNet(); // release private members
bool empty() const;
void loadModelBuffer(std::shared_ptr<ge::ModelBufferData> modelBuffer);
void bindInputWrappers(const std::vector<Ptr<BackendWrapper>>& inputWrappers);
void bindOutputWrappers(const std::vector<Ptr<BackendWrapper>>& outputWrappers);
void forward();
size_t getInputNum() const;
size_t getOutputNum() const;
private:
void init();
void loadToDevice(); // call aclInit before this API is called
void createInputDataset();
void createOutputDataset();
int getOutputIndexByName(const std::string& name);
void destroyDataset(aclmdlDataset** dataset);
std::shared_ptr<AclEnvGuard> acl_env;
std::vector<Ptr<CannBackendWrapper>> input_wrappers;
std::vector<Ptr<CannBackendWrapper>> output_wrappers;
uint32_t model_id{0};
aclmdlDesc* model_desc{nullptr};
std::vector<uint8_t> model;
aclmdlDataset* inputs{nullptr};
aclmdlDataset* outputs{nullptr};
int device_id{0};
aclrtContext context{nullptr};
};
#endif // HAVE_CANN
}} // namespace cv::dnn
#endif // OPENCV_DNN_OP_CANN_HPP

View File

@ -11,6 +11,7 @@
#include "op_cuda.hpp"
#include "op_webnn.hpp"
#include "op_timvx.hpp"
#include "op_cann.hpp"
#include "halide_scheduler.hpp"
@ -122,6 +123,10 @@ private:
backends.push_back(std::make_pair(DNN_BACKEND_TIMVX, DNN_TARGET_NPU));
}
#endif
#ifdef HAVE_CANN
backends.push_back(std::make_pair(DNN_BACKEND_CANN, DNN_TARGET_NPU));
#endif
}
BackendsList backends;

View File

@ -49,6 +49,7 @@
#define CV_TEST_TAG_DNN_SKIP_PARSER "dnn_skip_parser"
#define CV_TEST_TAG_DNN_SKIP_TIMVX "dnn_skip_timvx"
#define CV_TEST_TAG_DNN_SKIP_CANN "dnn_skip_cann"
#ifdef HAVE_INF_ENGINE
#if INF_ENGINE_VER_MAJOR_EQ(2018050000)
@ -139,7 +140,8 @@ testing::internal::ParamGenerator< tuple<Backend, Target> > dnnBackendsAndTarget
bool withVkCom = true,
bool withCUDA = true,
bool withNgraph = true,
bool withWebnn = true
bool withWebnn = true,
bool withCann = true
);
testing::internal::ParamGenerator< tuple<Backend, Target> > dnnBackendsAndTargetsIE();

View File

@ -31,6 +31,7 @@ void PrintTo(const cv::dnn::Backend& v, std::ostream* os)
case DNN_BACKEND_INFERENCE_ENGINE_NGRAPH: *os << "NGRAPH"; return;
case DNN_BACKEND_WEBNN: *os << "WEBNN"; return;
case DNN_BACKEND_TIMVX: *os << "TIMVX"; return;
case DNN_BACKEND_CANN: *os << "CANN"; return;
} // don't use "default:" to emit compiler warnings
*os << "DNN_BACKEND_UNKNOWN(" << (int)v << ")";
}
@ -251,7 +252,8 @@ testing::internal::ParamGenerator< tuple<Backend, Target> > dnnBackendsAndTarget
bool withVkCom /*= true*/,
bool withCUDA /*= true*/,
bool withNgraph /*= true*/,
bool withWebnn /*= false*/
bool withWebnn /*= false*/,
bool withCann /*= true*/
)
{
bool withVPU = validateVPUType();
@ -311,6 +313,16 @@ testing::internal::ParamGenerator< tuple<Backend, Target> > dnnBackendsAndTarget
CV_UNUSED(withWebnn);
#endif
#ifdef HAVE_CANN
if (withCann)
{
for (auto target : getAvailableTargets(DNN_BACKEND_CANN))
targets.push_back(make_tuple(DNN_BACKEND_CANN, target));
}
#else
CV_UNUSED(withCann);
#endif // HAVE_CANN
{
available = getAvailableTargets(DNN_BACKEND_OPENCV);
for (std::vector< Target >::const_iterator i = available.begin(); i != available.end(); ++i)
@ -477,6 +489,11 @@ void initDNNTests()
registerGlobalSkipTag(
CV_TEST_TAG_DNN_SKIP_TIMVX
);
#endif
#ifdef HAVE_CANN
registerGlobalSkipTag(
CV_TEST_TAG_DNN_SKIP_CANN
);
#endif
registerGlobalSkipTag(
CV_TEST_TAG_DNN_SKIP_ONNX_CONFORMANCE,