From f5d51a18c251e8a842da5c6fab1f1638d095b2c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20KUBLER?= <gregoire.kubler@proton.me> Date: Tue, 10 Sep 2024 11:33:47 +0200 Subject: [PATCH] feat : operator constant of shape --- include/aidge/operator/ConstantOfShape.hpp | 135 ++++++++++++++++++ .../operator/pybind_ConstantOfShape.cpp | 44 ++++++ python_binding/pybind_core.cpp | 2 + src/operator/ConstantOfShape.cpp | 68 +++++++++ unit_tests/operator/Test_ConstantOfShape.cpp | 85 +++++++++++ 5 files changed, 334 insertions(+) create mode 100644 include/aidge/operator/ConstantOfShape.hpp create mode 100644 python_binding/operator/pybind_ConstantOfShape.cpp create mode 100644 src/operator/ConstantOfShape.cpp create mode 100644 unit_tests/operator/Test_ConstantOfShape.cpp diff --git a/include/aidge/operator/ConstantOfShape.hpp b/include/aidge/operator/ConstantOfShape.hpp new file mode 100644 index 000000000..1f62f6a62 --- /dev/null +++ b/include/aidge/operator/ConstantOfShape.hpp @@ -0,0 +1,135 @@ +/******************************************************************************** + * 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 + * + ********************************************************************************/ + +#ifndef AIDGE_CORE_OPERATOR_CONSTANT_OF_SHAPE_H_ +#define AIDGE_CORE_OPERATOR_CONSTANT_OF_SHAPE_H_ + +#include <cstdint> +#include <cstdlib> +#include <functional> +#include <limits> +#include <memory> +#include <string> +#include <vector> + +#include "aidge/data/Data.hpp" +#include "aidge/graph/Node.hpp" +#include "aidge/operator/Operator.hpp" +#include "aidge/data/Tensor.hpp" +#include "aidge/operator/OperatorTensor.hpp" +#include "aidge/utils/ErrorHandling.hpp" +#include "aidge/utils/Registrar.hpp" +#include "aidge/utils/StaticAttributes.hpp" +#include "aidge/utils/Types.h" + +namespace Aidge { + +enum class ConstantOfShapeAttr { + /** + * @brief value to fill the output tensor with. + * Its a scalar tensor holding a value with a fixed datatype + */ + Value, +}; + +/** + * @brief This operator's purpose is to generate a tensor of shape given via + * input and filled with a given value set via attribute. + */ +class ConstantOfShape_Op + : public OperatorTensor, + public Registrable<ConstantOfShape_Op, std::string, + std::shared_ptr<OperatorImpl>( + const ConstantOfShape_Op &)> { + +public: + // name of the type of the operation + static const std::string Type; + +private: + using Attributes_ = StaticAttributes<ConstantOfShapeAttr, Tensor>; + template <ConstantOfShapeAttr e> + using attr = typename Attributes_::template attr<e>; + const std::shared_ptr<Attributes_> mAttributes; + +public: + /** + * @brief constructor for ConstantOfShape_op + * @param[in] value : a scalar tensor which holds the value that will + * fill the output tensor + */ + ConstantOfShape_Op(const Tensor &value = Tensor(0.f)) + : OperatorTensor(Type, {InputCategory::Data}, 1), + mAttributes(std::make_shared<Attributes_>( + attr<ConstantOfShapeAttr::Value>(value))) {} + + /** + * @brief Copy-constructor. Copy the operator attributes and its output + * tensor(s), but not its input tensors (the new operator has no input + * associated). + * @param op Operator to copy. + */ + ConstantOfShape_Op(const ConstantOfShape_Op &op) + : OperatorTensor(op), mAttributes(op.mAttributes) { + if (op.mImpl) { + SET_IMPL_MACRO(ConstantOfShape_Op, *this, op.backend()); + } else { + mImpl = nullptr; + } + } + + /** + * @brief Clone the operator using its copy-constructor. + * @see Operator::MatMul_Op + */ + std::shared_ptr<Operator> clone() const override final { + return std::make_shared<ConstantOfShape_Op>(*this); + } + + /** + * @brief Compute dimensions for the output Tensor + * @param allowDataDependency specify if the output shape of this operator + * depends on its inputs. + */ + bool forwardDims(bool allowDataDependency = false) override final; + + void setBackend(const std::string &name, + DeviceIdx_t device = 0) override final; + + inline std::shared_ptr<Attributes> attributes() const override { + return mAttributes; + } + inline Tensor &value() const noexcept { + return mAttributes->template getAttr<ConstantOfShapeAttr::Value>(); + } + + static const std::vector<std::string> getInputsName() { return {"input"}; } + static const std::vector<std::string> getOutputsName() { + return {"constant_of_shape"}; + } +}; + +// helper with C-style array instead of std::array for kernel_dims to allow +// automatic template DIM deduction +inline std::shared_ptr<Node> ConstantOfShape(const Tensor value = Tensor(0.f), + const std::string &name = "") { + return std::make_shared<Node>(std::make_shared<ConstantOfShape_Op>(value), + name); +} +} // namespace Aidge + +namespace { +template <> +const char *const EnumStrings<Aidge::ConstantOfShapeAttr>::data[] = {"Value"}; +} + +#endif // AIDGE_CORE_OPERATOR_CONSTANT_OF_SHAPE_H_ + diff --git a/python_binding/operator/pybind_ConstantOfShape.cpp b/python_binding/operator/pybind_ConstantOfShape.cpp new file mode 100644 index 000000000..b0d5ef2ef --- /dev/null +++ b/python_binding/operator/pybind_ConstantOfShape.cpp @@ -0,0 +1,44 @@ + +/******************************************************************************** + * 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 <pybind11/pybind11.h> +#include <string> +#include <vector> + +#include "aidge/data/Tensor.hpp" +#include "aidge/operator/OperatorTensor.hpp" +#include "aidge/operator/ConstantOfShape.hpp" + +namespace py = pybind11; +namespace Aidge { + +void init_ConstantOfShape(py::module &m) { + py::class_<ConstantOfShape_Op, std::shared_ptr<ConstantOfShape_Op>, OperatorTensor>( + m, "ConstantOfShapeOp", py::multiple_inheritance()) + // Here we bind the methods of the Unsqueeze_Op that wil want to access + .def("get_inputs_name", &ConstantOfShape_Op::getInputsName) + .def("get_outputs_name", &ConstantOfShape_Op::getOutputsName) + .def("value", &ConstantOfShape_Op::value); + // Here we bind the constructor of the ConstantOfShape Node. We add an argument for + // each attribute of the operator (in here we only have 'axes') and the last + // argument is the node's name. + m.def("ConstantOfShape", &ConstantOfShape, py::arg("value") = Tensor(0.f), + py::arg("name") = "", + R"mydelimiter( + Initialize a node containing an constantOfShape operator. + :param value : tensor with a given datatype that contains the value that will fill the output tensor + :type value : :py:class: Tensor + :param name : name of the node. +)mydelimiter"); +} +} // namespace Aidge + diff --git a/python_binding/pybind_core.cpp b/python_binding/pybind_core.cpp index 7c9ac168f..616a8424b 100644 --- a/python_binding/pybind_core.cpp +++ b/python_binding/pybind_core.cpp @@ -33,6 +33,7 @@ void init_ArgMax(py::module&); void init_AvgPooling(py::module&); void init_BatchNorm(py::module&); void init_Concat(py::module&); +void init_ConstantOfShape(py::module&); void init_Conv(py::module&); void init_ConvDepthWise(py::module&); void init_Div(py::module&); @@ -113,6 +114,7 @@ void init_Aidge(py::module& m) { init_Concat(m); init_Conv(m); init_ConvDepthWise(m); + init_ConstantOfShape(m); init_Div(m); init_Erf(m); init_FC(m); diff --git a/src/operator/ConstantOfShape.cpp b/src/operator/ConstantOfShape.cpp new file mode 100644 index 000000000..4c245d27d --- /dev/null +++ b/src/operator/ConstantOfShape.cpp @@ -0,0 +1,68 @@ +/******************************************************************************** + * 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 "aidge/operator/ConstantOfShape.hpp" + +#include <cstdint> +#include <fmt/format.h> +#include <memory> +#include <stdexcept> // std::runtime_error +#include <string> +#include <vector> + +#include "aidge/data/Data.hpp" +#include "aidge/data/Tensor.hpp" +#include "aidge/data/half.hpp" +#include "aidge/utils/ErrorHandling.hpp" +#include "aidge/utils/Types.h" + +namespace Aidge { + +const std::string ConstantOfShape_Op::Type = "ConstantOfShape"; + +bool ConstantOfShape_Op::forwardDims(bool allowDataDependency) { + if (!inputsAssociated()) { + return false; + } + + if (!allowDataDependency) { + Log::warn("{} : unable to forwardDims() because output dims are data " + "dependent on input#0", + type()); + return false; + } + + AIDGE_ASSERT(getInput(0)->nbDims() == 1, + "{} : Input tensor should have only 1 dimension. {}Â dimensions" + "received : {}", + __func__, getInput(0)->nbDims(), getInput(0)->dims()); + AIDGE_ASSERT(getInput(0)->dataType() == DataType::Int64, + "{} : Input tensor data type should be int64t, received : {}", + __func__, getInput(0)->nbDims(), getInput(0)->dims()); + std::vector<DimSize_t> output_dims; + output_dims.reserve(getInput(0)->size()); + for (std::size_t i = 0; i < getInput(0)->size(); ++i) { + auto temp = getInput(0)->template get<std::int64_t>(i); + output_dims.push_back(temp); + } + mOutputs[0]->resize(output_dims); + return true; +} + +void ConstantOfShape_Op::setBackend(const std::string &name, + Aidge::DeviceIdx_t device) { + SET_IMPL_MACRO(ConstantOfShape_Op, *this, name); + mOutputs[0]->setBackend(name, device); + value().setBackend(name,device); +} + +} // namespace Aidge + diff --git a/unit_tests/operator/Test_ConstantOfShape.cpp b/unit_tests/operator/Test_ConstantOfShape.cpp new file mode 100644 index 000000000..c10d97ce5 --- /dev/null +++ b/unit_tests/operator/Test_ConstantOfShape.cpp @@ -0,0 +1,85 @@ +/******************************************************************************** + * 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 <algorithm> +#include <catch2/catch_test_macros.hpp> +#include <catch2/generators/catch_generators_random.hpp> +#include <cstddef> // std::size_t +#include <cstdint> +#include <functional> +#include <memory> +#include <numeric> +#include <random> // std::mt19937, std::uniform_int_distribution +#include <system_error> +#include <vector> + +#include "aidge/data/Data.hpp" +#include "aidge/data/Tensor.hpp" +#include "aidge/filler/Filler.hpp" +#include "aidge/operator/ConstantOfShape.hpp" +#include "aidge/operator/OperatorTensor.hpp" +#include "aidge/utils/Types.h" + +namespace Aidge { +TEST_CASE("[core/operator] ConstantOfShape_Op(forwardDims)", + "[ConstantOfShape][forwardDims]") { + constexpr std::uint16_t NBTRIALS = 10; + + // Create a random number generator + auto random_seed = Catch::Generators::Detail::getSeed; + std::mt19937 gen(random_seed()); + std::uniform_int_distribution<std::size_t> input_tensor_dims_dist(1, 10); + std::uniform_int_distribution<std::size_t> input_tensor_value_dist(1, 9); + std::uniform_real_distribution<float> op_value_attr_value_dist(1, 10000); + + std::uniform_int_distribution<std::size_t> op_value_attr_type_dist( + 0, static_cast<int>(Aidge::DataType::UInt64)); + // TENSORS + std::shared_ptr<Tensor> input_T = std::make_shared<Tensor>(); + input_T->setDataType(Aidge::DataType::Int64); + input_T->setBackend("cpu"); + + SECTION("operator test") { + // Create Operator + for (int i = 0; i < NBTRIALS; ++i) { + std::shared_ptr<Node> node = + ConstantOfShape(Tensor(op_value_attr_value_dist(gen))); + auto op = + std::static_pointer_cast<ConstantOfShape_Op>(node->getOperator()); + op->associateInput(0, input_T); + + std::vector<DimSize_t> input_dims; + input_dims.push_back(input_tensor_dims_dist(gen)); + + Log::setConsoleLevel(Log::Debug); + int input_nb_elems = input_dims.at(0); + int output_nb_elems = 1; + int64_t *array_in = new int64_t[input_nb_elems]; + for (std::size_t i = 0; i < input_nb_elems; ++i) { + std::int64_t val = input_tensor_value_dist(gen); + array_in[i] = val; + output_nb_elems *= val; + } + + input_T->resize(input_dims); + op->setInput(0, input_T); + input_T->getImpl()->setRawPtr(array_in, input_nb_elems); + + REQUIRE(op->forwardDims(true)); + REQUIRE(input_T->size() == op->getOutput(0)->nbDims()); + for (DimSize_t i = 0; i < op->getOutput(0)->nbDims(); ++i) { + CHECK(array_in[i] == op->getOutput(0)->dims().at(i)); + } + } + } +} +} // namespace Aidge + -- GitLab