Skip to content
Snippets Groups Projects
Commit a1d5b011 authored by Houssem ROUIS's avatar Houssem ROUIS Committed by Olivier BICHLER
Browse files

feat: Add backend-agnostic forward kernel for Slice

parent 542c2c66
No related branches found
No related tags found
No related merge requests found
......@@ -25,6 +25,12 @@
namespace Aidge {
class Slice_OpImpl : public OperatorImpl {
public:
Slice_OpImpl(const Operator& op, const std::string& backend = ""): OperatorImpl(op, backend) {}
void forward() override;
};
enum class SliceAttr { Starts, Ends, Axes, Steps };
class Slice_Op
......@@ -32,13 +38,13 @@ class Slice_Op
public Registrable<Slice_Op, std::string, std::function<std::shared_ptr<OperatorImpl>(const Slice_Op &)>> {
public:
static const std::string Type;
private:
using Attributes_ = StaticAttributes<SliceAttr,
std::vector<std::int64_t>,
std::vector<std::int64_t>,
std::vector<std::int8_t>,
std::vector<std::int64_t>>;
private:
template <SliceAttr e> using attr = typename Attributes_::template attr<e>;
const std::shared_ptr<Attributes_> mAttributes;
......@@ -50,7 +56,6 @@ public:
const std::vector<std::int8_t>& axes,
const std::vector<std::int64_t>& steps);
/**
* @brief Copy-constructor. Copy the operator attributes and its output tensor(s), but not its
* input tensors (the new operator has no input associated).
......@@ -58,7 +63,6 @@ public:
*/
Slice_Op(const Slice_Op &op);
public:
/**
* @brief Clone the operator using its copy-constructor.
* @see Operator::Slice_Op
......@@ -103,4 +107,4 @@ template <>
const char *const EnumStrings<Aidge::SliceAttr>::data[] = { "starts", "ends", "axes", "steps" };
}
#endif /* AIDGE_CORE_OPERATOR_RELU_H_ */
#endif /* AIDGE_CORE_OPERATOR_SLICE_H_ */
......@@ -24,6 +24,9 @@
#include "aidge/data/Tensor.hpp"
#include "aidge/utils/ErrorHandling.hpp"
#include "aidge/utils/Types.h"
#include "aidge/data/Data.hpp"
#include "aidge/utils/Registrar.hpp"
const std::string Aidge::Slice_Op::Type = "Slice";
......@@ -43,17 +46,18 @@ Aidge::Slice_Op::Slice_Op(const std::vector<std::int64_t>& starts,
attr<SliceAttr::Ends>(ends),
attr<SliceAttr::Axes>(axes),
attr<SliceAttr::Steps>(steps)))
{}
{
mImpl = std::make_shared<Slice_OpImpl>(*this);
}
Aidge::Slice_Op::Slice_Op(const Aidge::Slice_Op &op)
: OperatorTensor(op),
mAttributes(op.mAttributes)
Aidge::Slice_Op::Slice_Op(const Aidge::Slice_Op& op)
: OperatorTensor(op), mAttributes(op.mAttributes)
{
if (!op.backend().empty()) {
SET_IMPL_MACRO(Slice_Op, *this, op.backend());
}
else {
mImpl = nullptr;
mImpl = std::make_shared<Slice_OpImpl>(*this);
}
}
......@@ -61,6 +65,82 @@ std::shared_ptr<Aidge::Operator> Aidge::Slice_Op::clone() const {
return std::make_shared<Slice_Op>(*this);
}
// Helper function to calculate the linear index for multi-dimensional data
size_t getLinearIndex(const std::vector<size_t>& dims, const std::vector<size_t>& indices) {
size_t linearIndex = 0;
size_t stride = 1;
for (int i = dims.size() - 1; i >= 0; --i) {
linearIndex += indices[i] * stride;
stride *= dims[i];
}
return linearIndex;
}
void Aidge::Slice_OpImpl::forward() {
const Slice_Op& op = dynamic_cast<const Slice_Op&>(mOp);
if (!op.getInput(0)) {
AIDGE_THROW_OR_ABORT(std::runtime_error, "{}: input #0 should be associated with a Tensor", op.Type);
}
AIDGE_ASSERT((op.axes().size() == op.ends().size()) &&
(op.axes().size() == op.starts().size()),
"Starts, Ends and Axes arguments should be the same size.");
const std::vector<size_t> inputDims = op.getInput(0)->dims();
std::vector<size_t> indices(inputDims.size(), 0); // Initialize indices for each dimension
// Create an array of ranges for each axis
std::vector<std::vector<int>> ranges(inputDims.size());
// Generate ranges dynamically for each dimension
for (size_t axisIdx = 0; axisIdx < inputDims.size(); ++axisIdx) {
if (std::find(op.axes().begin(), op.axes().end(), axisIdx) != op.axes().end()) {
// This axis is being sliced
int start = op.starts()[axisIdx];
int end = op.ends()[axisIdx];
int step = op.steps()[axisIdx];
start = start >= 0 ? start: start + inputDims[axisIdx];
end = end >= 0 ? end: end + inputDims[axisIdx];
// Generate the range of indices for this axis
for (int idx = start; (step > 0) ? (idx < end) : (idx > end); idx += step) {
ranges[axisIdx].push_back(idx);
}
} else {
// This axis is not being sliced, keep its full range (just one index in the range)
ranges[axisIdx].push_back(0);
}
}
// Use iterative stack to handle all dimensions dynamically
std::vector<size_t> currentIndex(inputDims.size(), 0); // Track current index in each dimension
std::vector<size_t> stackPointer(inputDims.size(), 0); // Pointers to ranges for each dimension
size_t dim = 0; // Start at the first dimension
size_t offset = 0; // Offset in the output tensor
while (dim < inputDims.size()) {
if (stackPointer[dim] < ranges[dim].size()) {
// Set the current index for this dimension
currentIndex[dim] = ranges[dim][stackPointer[dim]];
stackPointer[dim]++;
if (dim == inputDims.size() - 1) {
// We've reached the last dimension, process this index combination
size_t linearIndex = getLinearIndex(inputDims, currentIndex);
op.getOutput(0)->getImpl()->copy(op.getInput(0)->getImpl()->rawPtr(linearIndex), 1, offset);
offset++;
} else {
// Move to the next dimension
dim++;
}
} else {
// Reset this dimension and move back to the previous one
stackPointer[dim] = 0;
dim--;
}
}
}
bool Aidge::Slice_Op::dimsForwarded() const {
if ((getInput(1) && !getInput(1)->undefined())
......@@ -191,7 +271,7 @@ bool Aidge::Slice_Op::forwardDims(bool allowDataDependency) {
}
}
const std::size_t sliceLength = static_cast<std::size_t>(std::ceil((static_cast<float>(end) - static_cast<float>(start)) / static_cast<float>(step)));
const std::size_t sliceLength = static_cast<std::size_t>(std::ceil((static_cast<float>(end) - static_cast<float>(start)) / static_cast<float>((step))));
// Check if slice length is valid
if (sliceLength > getInput(0)->dims()[axis])
{
......@@ -208,7 +288,12 @@ bool Aidge::Slice_Op::forwardDims(bool allowDataDependency) {
}
void Aidge::Slice_Op::setBackend(const std::string& name, Aidge::DeviceIdx_t device) {
SET_IMPL_MACRO(Slice_Op, *this, name);
if (Registrar<Slice_Op>::exists({name})){
SET_IMPL_MACRO(Slice_Op, *this, name);
}
else {
mImpl = std::make_shared<Slice_OpImpl>(*this);
}
mOutputs[0]->setBackend(name, device);
}
......
/********************************************************************************
* Copyright (c) 2023 CEA-List
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
********************************************************************************/
#include <catch2/catch_test_macros.hpp>
#include "aidge/data/Tensor.hpp"
#include "aidge/operator/Slice.hpp"
using namespace Aidge;
TEST_CASE("[cpu/operator] Slice(forward)", "[Slice][CPU]") {
SECTION("1D Tensor") {
std::shared_ptr<Tensor> input0 = std::make_shared<Tensor>(Array1D<int,10> {
{0, 1, -2,-3, 4,-5,-6, 7, 8, 9}
});
std::shared_ptr<Tensor> expectedOutput = std::make_shared<Tensor>(Array1D<int,3> {
{0, 1, -2}
});
std::shared_ptr<Tensor> starts = std::make_shared<Tensor>(Array1D<int,1>{{0}});
std::shared_ptr<Tensor> ends = std::make_shared<Tensor>(Array1D<int,1>{{3}});
std::shared_ptr<Tensor> axes = std::make_shared<Tensor>(Array1D<int,1>{{0}});
std::shared_ptr<Node> mySlice = Slice();
auto op = std::static_pointer_cast<OperatorTensor>(mySlice -> getOperator());
mySlice->getOperator()->associateInput(0,input0);
mySlice->getOperator()->associateInput(1,starts);
mySlice->getOperator()->associateInput(2,ends);
mySlice->getOperator()->associateInput(3,axes);
mySlice->getOperator()->setDataType(DataType::Int32);
mySlice->getOperator()->setBackend("cpu");
mySlice->forward();
REQUIRE(*(op->getOutput(0)) == *expectedOutput);
REQUIRE(op->getOutput(0)->dims() == expectedOutput->dims());
REQUIRE(op->getOutput(0)->dataType() == expectedOutput->dataType());
}
SECTION("2D Tensor") {
std::shared_ptr<Tensor> input0 = std::make_shared<Tensor>(Array2D<int,2,10> {
{
{ 0, 1, 2,-3, 4,-5,-6, 7, 8, 9},
{-5, 4, 2,-3, 4,-5,-6, 7,-1,10}
}
});
std::shared_ptr<Tensor> expectedOutput = std::make_shared<Tensor>(Array2D<int,2,3> {
{
{-5,-6, 7},
{-5,-6, 7}
}
});
std::shared_ptr<Tensor> starts = std::make_shared<Tensor>(Array1D<int,2>{{0,5}});
std::shared_ptr<Tensor> ends = std::make_shared<Tensor>(Array1D<int,2>{{2,8}});
std::shared_ptr<Tensor> axes = std::make_shared<Tensor>(Array1D<int,2>{{0,1}});
std::shared_ptr<Node> mySlice = Slice();
auto op = std::static_pointer_cast<OperatorTensor>(mySlice -> getOperator());
mySlice->getOperator()->associateInput(0,input0);
mySlice->getOperator()->associateInput(1,starts);
mySlice->getOperator()->associateInput(2,ends);
mySlice->getOperator()->associateInput(3,axes);
mySlice->getOperator()->setDataType(DataType::Int32);
mySlice->getOperator()->setBackend("cpu");
mySlice->forward();
op->getOutput(0)->print();
REQUIRE(*(op->getOutput(0)) == *expectedOutput);
REQUIRE(op->getOutput(0)->dims() == expectedOutput->dims());
REQUIRE(op->getOutput(0)->dataType() == expectedOutput->dataType());
}
SECTION("3D Tensor") {
std::shared_ptr<Tensor> input0 = std::make_shared<Tensor>(Array3D<int,2,2,10> {
{
{
{ 0, 1, 2,-3, 4,-5,-6, 7, 8, 9},
{-5, 4, 2,-3, 4,-5,-6, 7,-1,10}
},
{
{ 0, 1, 2,-3, 4,-5,-6, 7, 8, 9},
{-5, 4, 2,-3, 4,-5,-6, 7,-1,10}
}
}
});
std::shared_ptr<Tensor> expectedOutput = std::make_shared<Tensor>(Array3D<int,1,1,3> {
{
{
{ 4,-5,-6}
}
}
});
std::shared_ptr<Tensor> starts = std::make_shared<Tensor>(Array1D<int,3>{{0,1,4}});
std::shared_ptr<Tensor> ends = std::make_shared<Tensor>(Array1D<int,3>{{1,2,7}});
std::shared_ptr<Tensor> axes = std::make_shared<Tensor>(Array1D<int,3>{{0,1,2}});
std::shared_ptr<Node> mySlice = Slice();
auto op = std::static_pointer_cast<OperatorTensor>(mySlice -> getOperator());
mySlice->getOperator()->associateInput(0,input0);
mySlice->getOperator()->associateInput(1,starts);
mySlice->getOperator()->associateInput(2,ends);
mySlice->getOperator()->associateInput(3,axes);
mySlice->getOperator()->setDataType(DataType::Int32);
mySlice->getOperator()->setBackend("cpu");
mySlice->forward();
// mySlice->getOperator()->output(0).print();
REQUIRE(*(op->getOutput(0)) == *expectedOutput);
REQUIRE(op->getOutput(0)->dims() == expectedOutput->dims());
REQUIRE(op->getOutput(0)->dataType() == expectedOutput->dataType());
}
SECTION("4D Tensor") {
std::shared_ptr<Tensor> input0 = std::make_shared<Tensor>(Array4D<int,2,2,2,10> {
{
{
{
{ 0, 1, 2,-3, 4,-5,-6, 7, 8, 9},
{-5, 4, 2,-3, 4,-5,-6, 7,-1,10}
},
{
{ 0, 1, 2,-3, 4,-5,-6, 7, 8, 9},
{-5, 4, 2,-3, 4,-5,-6, 7,-1,10}
}
},
{
{
{ 0, 1, 2,-3, 6,-5,-6, 7, 8, 9},
{-5, 4, 2,-3, 4,-5,-6, 7,-1,10}
},
{
{ 0, 1, 2,-3, 4,-5,-6, 7, 8, 9},
{-5, 4, 2,-3,11,-5,-6, 7,-1,10}
}
}
}
});
std::shared_ptr<Tensor> expectedOutput = std::make_shared<Tensor>(Array4D<int,2,2,2,10> {
{
{
{
{ 0, 1, 2,-3, 4,-5,-6, 7, 8, 9},
{-5, 4, 2,-3, 4,-5,-6, 7,-1,10}
},
{
{ 0, 1, 2,-3, 4,-5,-6, 7, 8, 9},
{-5, 4, 2,-3, 4,-5,-6, 7,-1,10}
}
},
{
{
{ 0, 1, 2,-3, 6,-5,-6, 7, 8, 9},
{-5, 4, 2,-3, 4,-5,-6, 7,-1,10}
},
{
{ 0, 1, 2,-3, 4,-5,-6, 7, 8, 9},
{-5, 4, 2,-3,11,-5,-6, 7,-1,10}
}
}
}
});
std::shared_ptr<Tensor> starts = std::make_shared<Tensor>(Array1D<int,4>{{0,0,0,0}});
std::shared_ptr<Tensor> ends = std::make_shared<Tensor>(Array1D<int,4>{{2,2,2,10}});
std::shared_ptr<Tensor> axes = std::make_shared<Tensor>(Array1D<int,4>{{0,1,2,3}});
std::shared_ptr<Node> mySlice = Slice();
auto op = std::static_pointer_cast<OperatorTensor>(mySlice -> getOperator());
mySlice->getOperator()->associateInput(0,input0);
mySlice->getOperator()->associateInput(1,starts);
mySlice->getOperator()->associateInput(2,ends);
mySlice->getOperator()->associateInput(3,axes);
mySlice->getOperator()->setDataType(DataType::Int32);
mySlice->getOperator()->setBackend("cpu");
mySlice->forward();
// op->getOutput(0)->print();
REQUIRE(*(op->getOutput(0)) == *expectedOutput);
REQUIRE(op->getOutput(0)->dims() == expectedOutput->dims());
REQUIRE(op->getOutput(0)->dataType() == expectedOutput->dataType());
}
SECTION("Attributes instead of inputs") {
std::shared_ptr<Tensor> input0 = std::make_shared<Tensor>(Array4D<int,2,2,2,10> {
{
{
{
{ 0, 1, 2,-3, 4,-5,-6, 7, 8, 9},
{-5, 4, 2,-3, 4,-5,-6, 7,-1,10}
},
{
{ 0, 1, 2,-3, 4,-5,-6, 7, 8, 9},
{-5, 4, 2,-3, 4,-5,-6, 7,-1,10}
}
},
{
{
{ 0, 1, 2,-3, 6,-5,-6, 7, 8, 9},
{-5, 4, 2,-3, 4,-5,-6, 7,-1,10}
},
{
{ 0, 1, 2,-3, 4,-5,-6, 7, 8, 9},
{-5, 4, 2,-3,11,-5,-6, 7,-1,10}
}
}
}
});
std::shared_ptr<Tensor> expectedOutput = std::make_shared<Tensor>(Array4D<int,1,1,1,5> {
{
{
{
{ 0, 1, 2,-3, 4}
}
}
}
});
std::shared_ptr<Node> mySlice = Slice({0,0,0,0}, {1,1,1,5}, {0,1,2,3}, {1,1,1,1});
auto op = std::static_pointer_cast<OperatorTensor>(mySlice -> getOperator());
mySlice->getOperator()->associateInput(0,input0);
mySlice->getOperator()->setDataType(DataType::Int32);
mySlice->getOperator()->setBackend("cpu");
mySlice->forward();
// op->getOutput(0)->print();
REQUIRE(*(op->getOutput(0)) == *expectedOutput);
REQUIRE(op->getOutput(0)->dims() == expectedOutput->dims());
REQUIRE(op->getOutput(0)->dataType() == expectedOutput->dataType());
}
SECTION("Different Steps") {
std::shared_ptr<Tensor> input0 = std::make_shared<Tensor>(Array3D<int,4,2,8> {
{
{
{ 0, 1, 2,-3, 4,-5,-6,7},
{-5, 4, 2,-3, 4,-5,-6,-7}
},
{
{ 10, 11, 12,-13, 14,-15,-16,17},
{-15, 14, 12,-13, 14,-15,-16,-17}
},
{
{ 20, 21, 22,-23, 24,-25,-26,27},
{-25, 24, 22,-23, 24,-25,-26,-27}
},
{
{ 30, 31, 32,-33, 34,-35,-36,37},
{-35, 34, 32,-33, 34,-35,-36,-37}
}
}
});
std::shared_ptr<Tensor> expectedOutput = std::make_shared<Tensor>(Array3D<int,2,1,3> {
{
{
{ 7, 4, 1}
},
{
{ 27, 24, 21}
}
}
});
std::shared_ptr<Node> mySlice = Slice({0,0,7}, {4,1,0}, {0,1,2}, {2,1,-3});
// on Axis 0: from 0 to 4 by step of 2
// on Axis 1: from 0 to 1 by step of 1
// on Axis 2: from 7 to 0 by step of -3 (reverse the order of elements)
auto op = std::static_pointer_cast<OperatorTensor>(mySlice -> getOperator());
mySlice->getOperator()->associateInput(0,input0);
mySlice->getOperator()->setDataType(DataType::Int32);
mySlice->getOperator()->setBackend("cpu");
mySlice->forward();
// op->getOutput(0)->print();
REQUIRE(*(op->getOutput(0)) == *expectedOutput);
REQUIRE(op->getOutput(0)->dims() == expectedOutput->dims());
REQUIRE(op->getOutput(0)->dataType() == expectedOutput->dataType());
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment