opencv/modules/dnn/src/layers/cumsum_layer.cpp
alexlyulkov 6af0394cd2
Merge pull request #25458 from alexlyulkov:al/dnn-openvino-int-support
Added int support for OpenVINO dnn backend #25458

Modified dnn OpenVINO integration to support type inference and int operations.

Added OpenVINO support to Cast, CumSum, Expand, Gather, GatherElements, Scatter, ScatterND, Tile layers.
I tried to add Reduce layer, but looks like OpenVINO uses float values inside Reduce operation so it can't pass our int tests.

OpenVINO uses int32 precision for int64 operations, so I've modified input values for int64 tests when backend is OpenVINO.

OpenVINO has a strange behavior with custom layers and int64 values. After model compilation OpenVINO may change types, so the model can have different output type. That's why these tests were disabled:
- Test_ArgMax_Int.random/0, where GetParam() = (4, NGRAPH/CPU)
- Test_ArgMax_Int.random/6, where GetParam() = (11, NGRAPH/CPU)
- Test_Reduce_Int.random/6, where GetParam() = (11, NGRAPH/CPU)
- Test_Reduce_Int.two_axes/6, where GetParam() = (11, NGRAPH/CPU)

Also these tests were temporary disabled, they didn't work on both 4.x and 5.x branches:
- Test_Caffe_layers.layer_prelu_fc/0, where GetParam() = NGRAPH/CPU
- Test_ONNX_layers.LSTM_Activations/0, where GetParam() = NGRAPH/CPU
- Test_ONNX_layers.Quantized_Convolution/0, where GetParam() = NGRAPH/CPU
- Test_ONNX_layers.Quantized_Eltwise_Scalar/0, where GetParam() = NGRAPH/CPU
- Test_TFLite.EfficientDet_int8/0, where GetParam() = NGRAPH/CPU


### Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [ ] There is a reference to the original bug report and related work
- [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
      Patch to opencv_extra has the same branch name.
- [x] The feature is well documented and sample code can be built with the project CMake
2024-05-15 11:51:59 +03:00

204 lines
7.2 KiB
C++

// 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_inf_engine.hpp"
#include "../ie_ngraph.hpp"
#include "layers_common.hpp"
#include <opencv2/dnn/shape_utils.hpp>
namespace cv
{
namespace dnn
{
class CumSumLayerImpl CV_FINAL : public CumSumLayer
{
public:
CumSumLayerImpl(const LayerParams &params)
{
axis_raw = params.get<int>("axis", 0);
exclusive_raw = params.get<int>("exclusive", 0);
reverse_raw = params.get<int>("reverse", 0);
setParamsFrom(params);
}
virtual bool supportBackend(int backendId) CV_OVERRIDE
{
return backendId == DNN_BACKEND_OPENCV ||
backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH;
}
bool getMemoryShapes(const std::vector<MatShape> &inputs,
const int requiredOutputs,
std::vector<MatShape> &outputs,
std::vector<MatShape> &internals) const CV_OVERRIDE
{
Layer::getMemoryShapes(inputs, requiredOutputs, outputs, internals);
return exclusive_raw == 0;
}
virtual void getTypes(const std::vector<MatType>& inputs,
const int requiredOutputs,
const int requiredInternals,
std::vector<MatType>& outputs,
std::vector<MatType>& internals) const CV_OVERRIDE
{
CV_CheckType(inputs[0], inputs[0] == CV_32F || inputs[0] == CV_32S || inputs[0] == CV_64S || inputs[0] == CV_16F, "");
outputs.assign(1, inputs[0]);
}
void forward(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr, OutputArrayOfArrays internals_arr) CV_OVERRIDE
{
CV_TRACE_FUNCTION();
CV_TRACE_ARG_VALUE(name, "name", name.c_str());
if (inputs_arr.depth() == CV_16F)
{
forward_fallback(inputs_arr, outputs_arr, internals_arr);
return;
}
std::vector<Mat> inputs, outputs, internals;
inputs_arr.getMatVector(inputs);
outputs_arr.getMatVector(outputs);
CV_CheckTypeEQ(inputs[0].depth(), outputs[0].depth(), "");
switch(inputs[0].depth())
{
case CV_32F:
forwardImpl<float>(inputs, outputs);
break;
case CV_32S:
forwardImpl<int32_t>(inputs, outputs);
break;
case CV_64S:
forwardImpl<int64_t>(inputs, outputs);
break;
default:
CV_Error(Error::BadDepth, "");
}
}
template <typename T>
void forwardImpl(const std::vector<Mat>& inputs, std::vector<Mat>& outputs)
{
// Get input tensor.
const auto& src_mat = inputs[0];
const T* src_ptr = src_mat.ptr<T>();
// Get target axis.
int axis = inputs.size() > 1 ? parseAxis(inputs[1]) : axis_raw;
axis = normalize_axis(axis, src_mat.dims);
// Get output tensor.
auto& dst_mat = outputs[0];
T* dst_ptr = dst_mat.ptr<T>();
// Get flags.
const auto exclusive = exclusive_raw == 1;
const auto reverse = reverse_raw == 1;
// Data with [dim_1, .. , dim_k-1, target_dim, dim_k+1, .. , dim_n]
// dimensions is represented here as [outer_dim, target_dim, inner_dim]
const size_t outer_size = src_mat.total(0, axis);
const size_t target_size = src_mat.size[axis];
const size_t inner_size = src_mat.total(axis + 1);
const size_t outer_step_length = target_size * inner_size;
// Calculating steps in target dimensions
const int target_start = reverse ? target_size - 1 : 0;
const int target_stop = reverse ? -1 : target_size;
const int target_delta = reverse ? -1 : 1;
const int target_step = target_delta * inner_size;
// If exclusive, the j-th output element would be the sum of the first (j-1) elements.
// Otherwise, it would be the sum of the first j elements.
const int exclusive_delta = exclusive ? target_step : 0;
for (size_t outer_idx = 0; outer_idx < outer_size; outer_idx++)
{
const size_t target_offset = outer_idx * outer_step_length;
// Handle first element of target dimension.
size_t first_inner_offset = target_offset + target_start * inner_size;
if (exclusive)
for (size_t inner_idx = 0; inner_idx < inner_size; inner_idx++)
dst_ptr[first_inner_offset + inner_idx] = 0;
else
for (size_t inner_idx = 0; inner_idx < inner_size; inner_idx++)
dst_ptr[first_inner_offset + inner_idx] = src_ptr[first_inner_offset + inner_idx];
// Handle remaining elements of target dimension.
for (int target_idx = target_start + target_delta; target_idx != target_stop; target_idx += target_delta)
{
const size_t inner_offset = target_offset + target_idx * inner_size;
for (size_t inner_idx = 0; inner_idx < inner_size; inner_idx++)
{
dst_ptr[inner_offset + inner_idx] = dst_ptr[inner_offset - target_step + inner_idx] +
src_ptr[inner_offset - exclusive_delta + inner_idx];
}
}
}
}
int parseAxis(const Mat& axis_mat) {
CV_CheckEQ(axis_mat.total(), 1u, "Axis tensor should contain single value");
if (axis_mat.type() == CV_32SC1)
return axis_mat.at<int32_t>(0);
else
{
Mat axis_mat_int;
axis_mat.convertTo(axis_mat_int, CV_32SC1);
return axis_mat_int.at<int32_t>(0);
}
}
#ifdef HAVE_DNN_NGRAPH
virtual Ptr<BackendNode> initNgraph(const std::vector<Ptr<BackendWrapper> >& inputs,
const std::vector<Ptr<BackendNode> >& nodes) CV_OVERRIDE
{
std::shared_ptr<ov::op::v0::CumSum> cumsum;
if (nodes.size() == 2)
{
int32_t axis_shape = 1;
auto axis_scalar = std::make_shared<ov::op::v1::Reshape>(
nodes[1].dynamicCast<InfEngineNgraphNode>()->node,
std::make_shared<ov::op::v0::Constant>(ov::element::i32, ov::Shape{}, &axis_shape),
false);
cumsum = std::make_shared<ov::op::v0::CumSum>(
nodes[0].dynamicCast<InfEngineNgraphNode>()->node,
std::make_shared<ov::op::v0::Convert>(axis_scalar, ov::element::i32),
exclusive_raw,
reverse_raw);
}
else
{
cumsum = std::make_shared<ov::op::v0::CumSum>(
nodes[0].dynamicCast<InfEngineNgraphNode>()->node,
std::make_shared<ov::op::v0::Constant>(ov::element::i32, ov::Shape{}, &axis_raw),
exclusive_raw,
reverse_raw);
}
return Ptr<BackendNode>(new InfEngineNgraphNode(cumsum));
}
#endif // HAVE_DNN_NGRAPH
int axis_raw;
int exclusive_raw;
int reverse_raw;
};
Ptr<CumSumLayer> CumSumLayer::create(const LayerParams& params)
{
return Ptr<CumSumLayer>(new CumSumLayerImpl(params));
}
}
}