diff --git a/include/aidge/backend/cpu.hpp b/include/aidge/backend/cpu.hpp index 8b0001c2230ed6ad6b928f7ec8eed5340ae37087..f705268a7f89d308be8bc9f9d143cdfecb33590e 100644 --- a/include/aidge/backend/cpu.hpp +++ b/include/aidge/backend/cpu.hpp @@ -38,6 +38,7 @@ #include "aidge/backend/cpu/operator/FCImpl.hpp" #include "aidge/backend/cpu/operator/FoldImpl.hpp" #include "aidge/backend/cpu/operator/GlobalAveragePoolingImpl.hpp" +#include "aidge/backend/cpu/operator/HardmaxImpl.hpp" #include "aidge/backend/cpu/operator/HeavisideImpl.hpp" #include "aidge/backend/cpu/operator/LRNImpl.hpp" #include "aidge/backend/cpu/operator/LeakyReLUImpl.hpp" @@ -65,4 +66,4 @@ #include "aidge/backend/cpu/data/TensorImpl.hpp" -#endif /* AIDGE_CPU_IMPORTS_H_ */ \ No newline at end of file +#endif /* AIDGE_CPU_IMPORTS_H_ */ diff --git a/include/aidge/backend/cpu/operator/HardmaxImpl.hpp b/include/aidge/backend/cpu/operator/HardmaxImpl.hpp new file mode 100644 index 0000000000000000000000000000000000000000..de9b86cfe66da658fe1c46a01ba4ebea23db912d --- /dev/null +++ b/include/aidge/backend/cpu/operator/HardmaxImpl.hpp @@ -0,0 +1,36 @@ +/******************************************************************************** + * Copyright (c) 2024 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 + * + ********************************************************************************/ + +#ifndef AIDGE_CPU_OPERATOR_HARDMAXIMPL_H_ +#define AIDGE_CPU_OPERATOR_HARDMAXIMPL_H_ + +#include "aidge/backend/cpu/operator/OperatorImpl.hpp" +#include "aidge/operator/Hardmax.hpp" +#include "aidge/utils/Registrar.hpp" +#include "aidge/utils/Types.h" + +namespace Aidge { + +// Operator implementation entry point for the backend +// template args +// 1st : Operator of the kernels to implements +// 2nd : signature of the forward kernel function +// 3rd (optionnal): signature of the backward kernel function +using HardmaxImpl_cpu = OperatorImpl_cpu< + Hardmax_Op, + void(std::int32_t, const std::vector<DimSize_t> &, const void *, void *)>; + +// Implementation entry point registration to Operator +REGISTRAR(Hardmax_Op, "cpu", Aidge::HardmaxImpl_cpu::create); + +} // namespace Aidge + +#endif /* _AIDGE_CPU_OPERATOR_HARDMAXIMPL_H_ */ diff --git a/include/aidge/backend/cpu/operator/HardmaxImpl_kernels.hpp b/include/aidge/backend/cpu/operator/HardmaxImpl_kernels.hpp new file mode 100644 index 0000000000000000000000000000000000000000..de196641d968e95d5036be3db0b3ef0fa63607b8 --- /dev/null +++ b/include/aidge/backend/cpu/operator/HardmaxImpl_kernels.hpp @@ -0,0 +1,97 @@ +/******************************************************************************** + * Copyright (c) 2024 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 + * + ********************************************************************************/ + +#ifndef AIDGE_CPU_OPERATOR_HARDMAXIMPL_FORWARD_KERNEL_H_ +#define AIDGE_CPU_OPERATOR_HARDMAXIMPL_FORWARD_KERNEL_H_ + +#include <cmath> +#include <cstddef> +#include <numeric> + +#include "aidge/backend/cpu/operator/HardmaxImpl.hpp" +#include "aidge/data/Data.hpp" +#include "aidge/operator/Hardmax.hpp" +#include "aidge/utils/Registrar.hpp" + +namespace Aidge { + +// This is the actual implementation of the Hardmax forward kernel +template <class I, class O> +void HardmaxImpl_cpu_forward_kernel(std::int32_t axis_, + const std::vector<DimSize_t> &dims, + const void *input_, + void *output_) { + // We start by casting our arguments + const I *input = static_cast<const I *>(input_); + O *output = static_cast<O *>(output_); + // Cast axis to a size_t + const std::size_t axis = axis_ >= 0 ? axis_ : axis_ + dims.size(); + + // We fill all the output tensor with 0, we will set to 1 only the max + // element later + std::size_t totalElements = + std::accumulate(dims.cbegin(), + dims.cend(), + std::size_t(1), + std::multiplies<std::size_t>()); + std::fill(output, output + totalElements, 0); + + std::size_t postAxisStride = 1; + for (std::size_t i = axis + 1; i < dims.size(); ++i) { + postAxisStride *= dims[i]; + } + std::size_t preAxisStride = 1; + for (std::size_t i = 0; i < axis; ++i) { + preAxisStride *= dims[i]; + } + // For each index on all the axes before and after 'axis', we have a + // different max element to find + for (std::size_t i = 0; i < preAxisStride; ++i) { + for (std::size_t j = 0; j < postAxisStride; ++j) { + // Init the max with first element + std::size_t maxIdx = 0; + I maxVal = input[i * postAxisStride * dims[axis] + j]; + // Loop over the elements on 'axis' + for (std::size_t k = 1; k < dims[axis]; ++k) { + I currVal = input[i * postAxisStride * dims[axis] + + k * postAxisStride + j]; + // Update max elements + if (currVal > maxVal) { + maxIdx = k; + maxVal = currVal; + } + } + output[i * postAxisStride * dims[axis] + maxIdx * postAxisStride + + j] = 1; + } + } +} + +// Then we add the Registrar declaration for different input/output types +REGISTRAR(HardmaxImpl_cpu, + {DataType::Float32}, + {ProdConso::defaultModel, + Aidge::HardmaxImpl_cpu_forward_kernel<float, float>, + nullptr}); +REGISTRAR(HardmaxImpl_cpu, + {DataType::Int32}, + {ProdConso::defaultModel, + Aidge::HardmaxImpl_cpu_forward_kernel<std::int32_t, std::int32_t>, + nullptr}); +REGISTRAR(HardmaxImpl_cpu, + {DataType::Float64}, + {ProdConso::defaultModel, + Aidge::HardmaxImpl_cpu_forward_kernel<double, double>, + nullptr}); + +} // namespace Aidge + +#endif /* AIDGE_CPU_OPERATOR_HARDMAXIMPL_FORWARD_KERNEL_H_ */ diff --git a/src/operator/HardmaxImpl.cpp b/src/operator/HardmaxImpl.cpp new file mode 100644 index 0000000000000000000000000000000000000000..328bd1f2669d2b8dfd436b512e092b4441ab14cd --- /dev/null +++ b/src/operator/HardmaxImpl.cpp @@ -0,0 +1,52 @@ +/******************************************************************************** + * Copyright (c) 2024 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 <cassert> +#include <vector> + +#include "aidge/data/Tensor.hpp" +#include "aidge/operator/Hardmax.hpp" +#include "aidge/utils/Types.h" + +#include "aidge/backend/cpu/operator/HardmaxImpl.hpp" +#include "aidge/backend/cpu/operator/HardmaxImpl_kernels.hpp" + +// This function is a specialization of the operator OperatorImpl_cpu::forward +// function It's purpose is to : +// 1. Retrieve all tensors for the operations, hence ensuring that all +// in/outputs are indeed connected. +// 2. Assert thing that couldn't be checked when creating the operator. +// 3. Retrieve the best operator implementation regarding the input tensor +// types and the operator configuration. +template <> void Aidge::HardmaxImpl_cpu::forward() { + const Hardmax_Op &op_ = dynamic_cast<const Hardmax_Op &>(mOp); + + // Check if input is provided + assert(op_.getInput(0) && "missing input"); + + // Find the correct kernel type + const auto impl = + Registrar<HardmaxImpl_cpu>::create(getBestMatch(getRequiredSpec())); + + // Call kernel + impl.forward(op_.axis(), + op_.getInput(0)->dims(), + op_.getInput(0)->getImpl()->rawPtr(), + op_.getOutput(0)->getImpl()->rawPtr()); +} + +// As there is currently no backward kernel for this operator. +// This function is a placeholder. +template <> void Aidge::HardmaxImpl_cpu::backward() { + AIDGE_THROW_OR_ABORT( + std::runtime_error, + "Backward not yet implemented for Hardmax_Op on backend cpu"); +} diff --git a/unit_tests/operator/Test_HardmaxImpl.cpp b/unit_tests/operator/Test_HardmaxImpl.cpp new file mode 100644 index 0000000000000000000000000000000000000000..848f0223d18272e30c74e41a8324645443491825 --- /dev/null +++ b/unit_tests/operator/Test_HardmaxImpl.cpp @@ -0,0 +1,73 @@ +/******************************************************************************** + * Copyright (c) 2024 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 <memory> + +#include "aidge/data/Tensor.hpp" +#include "aidge/operator/Hardmax.hpp" + +#include "aidge/backend/cpu/operator/HardmaxImpl.hpp" +#include "aidge/utils/TensorUtils.hpp" + +using namespace Aidge; + +TEST_CASE("[cpu/operator] Hardmax(forward)", "[Hardmax][CPU]") { + + SECTION("3D Tensor") { + std::shared_ptr<Tensor> myInput = std::make_shared<Tensor>( + Array3D<float, 2, 3, 4>{{{{1.0, 2.0, 3.0, 4.0}, + {8.0, 0.0, 17.0, 1.0}, + {5.0, 10.0, 6.0, 0.0}}, + {{7.0, 1.0, 9.0, 4.0}, + {0.0, 8.0, 4.0, 2.0}, + {9.0, 2.0, 0.0, 5.0}}}}); + SECTION("Axis 2") { + Tensor myOutput = + Tensor(Array3D<float, 2, 3, 4>{{{{0.0, 0.0, 0.0, 1.0}, + {0.0, 0.0, 1.0, 0.0}, + {0.0, 1.0, 0.0, 0.0}}, + {{0.0, 0.0, 1.0, 0.0}, + {0.0, 1.0, 0.0, 0.0}, + {1.0, 0.0, 0.0, 0.0}}}}); + + std::shared_ptr<Node> myHardmax = Hardmax(2); + auto op = std::static_pointer_cast<OperatorTensor>( + myHardmax->getOperator()); + op->associateInput(0, myInput); + op->setDataType(DataType::Float32); + op->setBackend("cpu"); + myHardmax->forward(); + + REQUIRE(*(op->getOutput(0)) == myOutput); + } + + SECTION("Axis 1") { + Tensor myOutput = + Tensor(Array3D<float, 2, 3, 4>{{{{0.0, 0.0, 0.0, 1.0}, + {1.0, 0.0, 1.0, 0.0}, + {0.0, 1.0, 0.0, 0.0}}, + {{0.0, 0.0, 1.0, 0.0}, + {0.0, 1.0, 0.0, 0.0}, + {1.0, 0.0, 0.0, 1.0}}}}); + + std::shared_ptr<Node> myHardmax = Hardmax(1); + auto op = std::static_pointer_cast<OperatorTensor>( + myHardmax->getOperator()); + op->associateInput(0, myInput); + op->setDataType(DataType::Float32); + op->setBackend("cpu"); + myHardmax->forward(); + + REQUIRE(*(op->getOutput(0)) == myOutput); + } + } +}