Files
opencv-MIRROR/modules/dnn/src/layers/slice2_layer.cpp
Abhishek Gola bf0cf34963 Merge pull request #29126 from abhishek-gola:flash_attention
Attention graph fusion with MLAS FlashAttention #29126

Performance numbers for Owl-v2 model on intel i9:
```
ORT: Average inference time over 10 runs: 1411.55 ms (min 1399.75, max 1438.89)
NEW: Average inference time over 10 runs: 1078 ms (min 1048.04, max 1110.61)
```
### 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
- [x] 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
2026-05-27 09:32:27 +03:00

498 lines
18 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 "layers_common.hpp"
#include "../net_impl.hpp"
//#include "../op_cuda.hpp"
//#include "../op_inf_engine.hpp"
//#include "../ie_ngraph.hpp"
//#include "../op_webnn.hpp"
//#include "../op_timvx.hpp"
//#include "../op_cann.hpp"
//#include <opencv2/dnn/shape_utils.hpp>
namespace cv
{
namespace dnn
{
/*
Slice2 layer, as defined in ONNX specification:
https://onnx.ai/onnx/operators/onnx__Slice2.html
Opset's 1 to 13 are covered.
*/
class Slice2LayerImpl CV_FINAL : public Slice2Layer
{
public:
Slice2LayerImpl(const LayerParams& params)
{
setParamsFrom(params);
axes = params.getVector<int>("axes");
starts = params.getVector<int>("starts");
ends = params.getVector<int>("ends");
}
void checkNumInputs(size_t ninputs) const
{
CV_Assert(ninputs == 1 || (3 <= ninputs && ninputs <= 5));
}
virtual bool dynamicOutputShapes() const CV_OVERRIDE
{
Net::Impl* netimpl_ = getNetImpl(this);
size_t ninputs = inputs.size();
for (size_t i = 1; i < ninputs; i++) {
if (!netimpl_->isConstArg(inputs[i]))
return true;
}
return false;
}
virtual bool supportBackend(int backendId) CV_OVERRIDE
{
return backendId == DNN_BACKEND_OPENCV;
}
MatShape getOutShape(const MatShape& inpShape,
const std::vector<int>& starts_,
const std::vector<int>& ends_,
const std::vector<int>& axes_,
const std::vector<int>& steps_,
int* allStarts = nullptr,
int* allEnds = nullptr,
int* allSteps = nullptr) const
{
bool sliceMask[MatShape::MAX_DIMS];
int ndims = inpShape.dims;
int nstarts = (int)starts_.size(), nends = (int)ends_.size();
int naxes = (int)axes_.size(), nsteps = (int)steps_.size();
CV_Assert_N(nstarts > 0, nstarts <= ndims, nstarts == nends);
CV_Assert(naxes == 0 || naxes == nstarts);
CV_Assert(nsteps == 0 || nsteps == nstarts);
MatShape outShape = inpShape;
for (int i = 0; i < ndims; i++) {
sliceMask[i] = false;
if (allStarts)
allStarts[i] = 0;
if (allEnds)
allEnds[i] = inpShape[i];
if (allSteps)
allSteps[i] = 1;
}
for (int i = 0; i < nstarts; i++) {
int axis = i;
if (!axes_.empty()) {
axis = axes_[i];
axis = normalize_axis(axis, ndims);
if (sliceMask[axis]) {
CV_Error(Error::StsBadArg, "duplicate axis occurs in Slice");
}
}
sliceMask[axis] = true;
int inpsz = inpShape[axis];
int start = starts_[i];
int end = ends_[i];
int step = 1;
if (!steps_.empty())
step = steps_[i];
CV_Assert(step != 0);
start = start < 0 ? std::max(start + inpsz, 0) :
std::min(start, inpsz - (step < 0));
end = end < 0 ? std::max(end + inpsz, -(step < 0)) :
std::min(end, inpsz);
if (allStarts)
allStarts[axis] = start;
if (allSteps)
allSteps[axis] = step;
int outsz = step > 0 ? (end - start + step-1)/step :
(start - end - step-1)/(-step);
if (outsz < 0) {
outsz = 0;
end = start;
}
if (allEnds)
allEnds[axis] = end;
outShape[axis] = outsz;
}
return outShape;
}
bool isDataShuffling() const CV_OVERRIDE { return true; }
int getLayouts(const std::vector<DataLayout>& actualInputs,
std::vector<DataLayout>& desiredInputs,
const int requiredOutputs,
std::vector<DataLayout>& outputs) const CV_OVERRIDE
{
auto* netimpl_ = getNetImpl(this);
DataLayout defaultLayout = netimpl_->originalLayout;
const size_t ninputs = actualInputs.size();
desiredInputs = actualInputs;
outputs.assign(requiredOutputs, DATA_LAYOUT_UNKNOWN);
const bool inputIsBlock = ninputs >= 1 && actualInputs[0] == DATA_LAYOUT_BLOCK;
std::vector<int> resolvedAxes = axes;
if (resolvedAxes.empty() && this->inputs.size() > 3 &&
netimpl_->isConstArg(this->inputs[3])) {
Mat axesT = netimpl_->argTensor(this->inputs[3]);
tensorToIntVec(axesT, resolvedAxes);
}
bool axesOK = !resolvedAxes.empty();
if (axesOK) {
const int channelAxis = (defaultLayout == DATA_LAYOUT_NCHW) ? 1 :
(defaultLayout == DATA_LAYOUT_NHWC) ? 3 : -1;
for (int a : resolvedAxes) {
if (a < 0 || a == channelAxis) { axesOK = false; break; }
}
}
if (inputIsBlock && axesOK) {
outputs.assign(requiredOutputs, DATA_LAYOUT_BLOCK);
} else if (inputIsBlock) {
desiredInputs[0] = defaultLayout;
}
return outputs[0] == DATA_LAYOUT_BLOCK ? netimpl_->defaultC0 : 0;
}
bool getMemoryShapes(const std::vector<MatShape> &inputs,
const int,
std::vector<MatShape> &outputs,
std::vector<MatShape> &internals) const CV_OVERRIDE
{
size_t ninputs = inputs.size();
checkNumInputs(ninputs);
std::vector<int> tempStarts, tempEnds, tempAxes, steps;
const std::vector<int> *starts_ = &starts, *ends_ = &ends, *axes_ = &axes;
if (ninputs > 1) {
Net::Impl* netimpl_ = getNetImpl(this);
Mat startsTensor = netimpl_->argTensor(this->inputs[1]);
tensorToIntVec(startsTensor, tempStarts);
starts_ = &tempStarts;
Mat endsTensor = netimpl_->argTensor(this->inputs[2]);
tensorToIntVec(endsTensor, tempEnds);
ends_ = &tempEnds;
if (ninputs > 3) {
Mat axesTensor = netimpl_->argTensor(this->inputs[3]);
tensorToIntVec(axesTensor, tempAxes);
axes_ = &tempAxes;
}
if (ninputs > 4) {
Mat stepsTensor = netimpl_->argTensor(this->inputs[4]);
tensorToIntVec(stepsTensor, steps);
}
}
MatShape outShape = getOutShape(inputs[0], *starts_, *ends_, *axes_, steps);
outputs.assign(1, outShape);
internals.clear();
return true;
}
void getTypes(const std::vector<MatType>& inputs,
const int requiredOutputs,
const int requiredInternals,
std::vector<MatType>& outputs,
std::vector<MatType>& internals) const CV_OVERRIDE
{
size_t ninputs = inputs.size();
checkNumInputs(ninputs);
outputs.assign(requiredOutputs, inputs[0]);
CV_Assert(requiredInternals == 0);
internals.clear();
}
void finalize(InputArrayOfArrays, OutputArrayOfArrays outputs_arr) CV_OVERRIDE
{
}
private:
template <typename T>
class ParallelSlice : public cv::ParallelLoopBody
{
public:
// parallel_axis: dim to split across threads. Leading dims (0..parallel_axis-1) are
// collapsed into a constant offset since they have size 1 in the output.
ParallelSlice(const Mat& inp, Mat& out,
const std::vector<Range>& ranges,
const std::vector<int>& steps,
int parallel_axis)
: inp_(inp), out_(out), ranges_(ranges), steps_(steps),
parallel_axis_(parallel_axis)
{
dims_ = inp.dims;
es_ = inp.elemSize();
inp_strides_.resize(dims_);
out_strides_.resize(dims_);
for(int i=0; i<dims_; ++i) {
inp_strides_[i] = inp.step.p[i];
out_strides_[i] = out.step.p[i];
}
preamble_src_ = 0;
for (int d = 0; d < parallel_axis_; d++) {
preamble_src_ += (size_t)ranges_[d].start * inp_strides_[d];
}
}
void operator()(const Range& range) const CV_OVERRIDE
{
const int axis = parallel_axis_;
const int b = ranges_[axis].start;
const int s = steps_[axis];
const uchar* src_base = inp_.ptr() + preamble_src_;
uchar* dst_base = out_.ptr();
if (s == 1 && axis < dims_ - 1 && is_fully_contiguous(axis)) {
const size_t unit_bytes = inp_strides_[axis];
const size_t count = (size_t)(range.end - range.start);
const size_t src_off = (size_t)(b + range.start) * unit_bytes;
const size_t dst_off = (size_t)range.start * out_strides_[axis];
std::memcpy(dst_base + dst_off, src_base + src_off, count * unit_bytes);
return;
}
for (int i = range.start; i < range.end; ++i)
{
int k = b + i * s;
size_t src_offset = (size_t)k * inp_strides_[axis];
size_t dst_offset = (size_t)i * out_strides_[axis];
if (axis == dims_ - 1)
std::memcpy(dst_base + dst_offset, src_base + src_offset, es_);
else
recursive_copy(axis + 1, src_base + src_offset, dst_base + dst_offset);
}
}
void recursive_copy(int dim, const uchar* src_ptr, uchar* dst_ptr) const
{
if (dim >= dims_) return;
int begin = ranges_[dim].start;
int end = ranges_[dim].end;
int step = steps_[dim];
if (dim == dims_ - 1)
{
if (step == 1)
{
size_t count = end - begin;
std::memcpy(dst_ptr, src_ptr + begin * inp_strides_[dim], count * es_);
}
else
{
const uchar* s_ptr = src_ptr + begin * inp_strides_[dim];
uchar* d_ptr = dst_ptr;
size_t s_stride = step * inp_strides_[dim];
size_t d_stride = out_strides_[dim];
if (step > 0)
{
for (int k = begin; k < end; k += step)
{
*(T*)d_ptr = *(const T*)s_ptr;
s_ptr += s_stride;
d_ptr += d_stride;
}
}
else
{
for (int k = begin; k > end; k += step)
{
*(T*)d_ptr = *(const T*)s_ptr;
s_ptr += s_stride;
d_ptr += d_stride;
}
}
}
return;
}
if (step == 1 && is_fully_contiguous(dim))
{
size_t count = end - begin;
size_t bytes = count * inp_strides_[dim];
std::memcpy(dst_ptr, src_ptr + begin * inp_strides_[dim], bytes);
return;
}
size_t src_stride = step * inp_strides_[dim];
size_t dst_stride = out_strides_[dim];
const uchar* s_ptr = src_ptr + begin * inp_strides_[dim];
uchar* d_ptr = dst_ptr;
if (step > 0)
{
for (int k = begin; k < end; k += step)
{
recursive_copy(dim + 1, s_ptr, d_ptr);
s_ptr += src_stride;
d_ptr += dst_stride;
}
}
else
{
for (int k = begin; k > end; k += step)
{
recursive_copy(dim + 1, s_ptr, d_ptr);
s_ptr += src_stride;
d_ptr += dst_stride;
}
}
}
bool is_fully_contiguous(int dim) const
{
size_t expected_step = es_;
for (int d = dims_ - 1; d >= dim; --d)
{
if (inp_.step[d] != expected_step) return false;
if (d > dim) {
if (steps_[d] != 1) return false;
if (ranges_[d].start != 0 || ranges_[d].end != inp_.size[d]) return false;
expected_step *= inp_.size[d];
}
}
return true;
}
private:
const Mat& inp_;
Mat& out_;
const std::vector<Range>& ranges_;
const std::vector<int>& steps_;
int dims_;
size_t es_;
std::vector<size_t> inp_strides_;
std::vector<size_t> out_strides_;
int parallel_axis_;
size_t preamble_src_;
};
template <typename T>
void run_parallel(const Mat& inp, Mat& out, const std::vector<Range>& ranges, const std::vector<int>& steps)
{
int dims = inp.dims;
// Skip leading size-1 dims; otherwise the whole slice is one stripe.
int parallel_axis = 0;
while (parallel_axis < dims - 1 && out.size[parallel_axis] == 1) {
parallel_axis++;
}
int parallel_size = out.size[parallel_axis];
ParallelSlice<T> body(inp, out, ranges, steps, parallel_axis);
// One stripe per thread; per-element body is tiny so dynamic chunking
// would burn cycles on task sync.
int nthreads = std::max(1, getNumThreads());
double nstripes = std::min((double)parallel_size, (double)nthreads);
parallel_for_(Range(0, parallel_size), body, nstripes);
}
void forward(InputArrayOfArrays inputs_arr,
OutputArrayOfArrays outputs_arr,
OutputArrayOfArrays) CV_OVERRIDE
{
CV_TRACE_FUNCTION();
CV_TRACE_ARG_VALUE(name, "name", name.c_str());
Size size = inputs_arr.size();
int ninputs = size.area();
checkNumInputs(ninputs);
int inpType = inputs_arr.type(0);
MatShape inpShape = inputs_arr.shape(0);
std::vector<int> tempStarts, tempEnds, tempAxes, steps;
const std::vector<int> *starts_ = &starts, *ends_ = &ends, *axes_ = &axes;
if (ninputs > 1) {
Mat startsTensor = inputs_arr.getMat(1);
tensorToIntVec(startsTensor, tempStarts);
starts_ = &tempStarts;
Mat endsTensor = inputs_arr.getMat(2);
tensorToIntVec(endsTensor, tempEnds);
ends_ = &tempEnds;
if (ninputs > 3) {
Mat axesTensor = inputs_arr.getMat(3);
tensorToIntVec(axesTensor, tempAxes);
axes_ = &tempAxes;
}
if (ninputs > 4) {
Mat stepsTensor = inputs_arr.getMat(4);
tensorToIntVec(stepsTensor, steps);
}
}
int allStarts[MatShape::MAX_DIMS];
int allEnds[MatShape::MAX_DIMS];
int allSteps[MatShape::MAX_DIMS];
MatShape outShape = getOutShape(inpShape, *starts_, *ends_, *axes_, steps,
allStarts, allEnds, allSteps);
std::vector<Range> ranges;
std::vector<int> steps_vec;
for (int i = 0; i < inpShape.dims; ++i) {
ranges.push_back(Range(allStarts[i], allEnds[i]));
steps_vec.push_back(allSteps[i]);
}
int outKind = outputs_arr.kind();
CV_Assert(outKind == _InputArray::STD_VECTOR_MAT ||
outKind == _InputArray::STD_VECTOR_UMAT);
if (outKind == _InputArray::STD_VECTOR_MAT) {
Mat inp = inputs_arr.getMat(0);
std::vector<Mat>& outs = outputs_arr.getMatVecRef();
outs.resize(1);
outs[0].fit(outShape, inpType);
if (inp.depth() == CV_32S) run_parallel<int32_t>(inp, outs[0], ranges, steps_vec);
else if (inp.depth() == CV_64S) run_parallel<int64_t>(inp, outs[0], ranges, steps_vec);
else if (inp.depth() == CV_16F) run_parallel<int16_t>(inp, outs[0], ranges, steps_vec);
else if (inp.depth() == CV_8S) run_parallel<int8_t>(inp, outs[0], ranges, steps_vec);
else if (inp.depth() == CV_8U) run_parallel<uint8_t>(inp, outs[0], ranges, steps_vec);
else if (inp.depth() == CV_Bool) run_parallel<uint8_t>(inp, outs[0], ranges, steps_vec);
else run_parallel<float>(inp, outs[0], ranges, steps_vec);
} else {
Mat inp = inputs_arr.getMat(0);
std::vector<UMat>& outs = outputs_arr.getUMatVecRef();
outs.resize(1);
outs[0].fit(outShape, inpType);
Mat temp(outShape, inpType);
if (inp.depth() == CV_32S) run_parallel<int32_t>(inp, temp, ranges, steps_vec);
else if (inp.depth() == CV_64S) run_parallel<int64_t>(inp, temp, ranges, steps_vec);
else if (inp.depth() == CV_16F) run_parallel<int16_t>(inp, temp, ranges, steps_vec);
else if (inp.depth() == CV_8S) run_parallel<int8_t>(inp, temp, ranges, steps_vec);
else if (inp.depth() == CV_8U) run_parallel<uint8_t>(inp, temp, ranges, steps_vec);
else if (inp.depth() == CV_Bool) run_parallel<uint8_t>(inp, temp, ranges, steps_vec);
else run_parallel<float>(inp, temp, ranges, steps_vec);
temp.copyTo(outs[0]);
}
}
};
Ptr<Slice2Layer> Slice2Layer::create(const LayerParams& params)
{
return Ptr<Slice2Layer>(new Slice2LayerImpl(params));
}
}
}