diff --git a/include/aidge/operator/ConstantOfShape.hpp b/include/aidge/operator/ConstantOfShape.hpp new file mode 100644 index 0000000000000000000000000000000000000000..1f62f6a62ff6fc0f37721e25967506aa12ac9704 --- /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/include/aidge/recipes/Recipes.hpp b/include/aidge/recipes/Recipes.hpp index c42b285dacb6c59c5fa30388c268f1680152a5e0..aea39ded3e5f2547f6f47fbc5aa27d5f1ee4821f 100644 --- a/include/aidge/recipes/Recipes.hpp +++ b/include/aidge/recipes/Recipes.hpp @@ -50,6 +50,13 @@ void matMulToFC(std::shared_ptr<GraphView> graphView); */ size_t removeNode(std::shared_ptr<GraphView> graphView, const std::string& type, bool incProducers = false); +/** + * @brief Fuses constant => Generic | constantOfShape and transforms it into a Producer + * @param graph Graph to manipulate + * @return size_t Number of replacement + */ +size_t removeConstantOfShape(std::shared_ptr<GraphView> graph_view); + /** * @brief Remove ``Dropout`` Node. * diff --git a/python_binding/operator/pybind_ConstantOfShape.cpp b/python_binding/operator/pybind_ConstantOfShape.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b0d5ef2ef78380422ca1a137608f5289fa519aed --- /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 7c9ac168f3f8cd0e6bc09c45aec6041a0bd9faa3..616a8424b1f9df7e52a8af485b1bb82235f66a2f 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/python_binding/recipes/pybind_Recipes.cpp b/python_binding/recipes/pybind_Recipes.cpp index b68dfd035921a1dce4d12b9071a8df194e2ffdd5..a23b54e6f02f832fbc70482329966445f723b573 100644 --- a/python_binding/recipes/pybind_Recipes.cpp +++ b/python_binding/recipes/pybind_Recipes.cpp @@ -15,6 +15,7 @@ #include <cstddef> #include <string> +#include "aidge/graph/GraphView.hpp" #include "aidge/recipes/Recipes.hpp" #include "aidge/utils/Types.h" @@ -78,12 +79,12 @@ void init_Recipes(py::module &m) :type graph_view: :py:class:`aidge_core.GraphView` )mydelimiter"); - // m.def("remove_flatten", static_cast<void(*)(std::set<std::shared_ptr<Node>>)>(removeFlatten), py::arg("nodes"), R"mydelimiter( - // Recipe to remove a flatten operator. + m.def("remove_constantOfShape", static_cast<size_t(*)(std::shared_ptr<GraphView>)>(removeConstantOfShape), py::arg("graph_view"), R"mydelimiter( + Fuses constant => Generic | constantOfShape and transforms it into a Producer - // :param nodes: The flatten operator to remove. - // :type nodes: list of :py:class:`aidge_core.Node` - // )mydelimiter"); + :param graph_view: Graph view on which we want to apply the recipe. + :type graph_view: :py:class:`aidge_core.GraphView` + )mydelimiter"); m.def("fuse_batchnorm", static_cast<void(*)(std::shared_ptr<GraphView>)>(fuseBatchNorm), py::arg("graph_view"), R"mydelimiter( Recipe to remove a flatten operator. diff --git a/src/operator/ConstantOfShape.cpp b/src/operator/ConstantOfShape.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4c245d27d1c4b5c9865d298ff7b8647a9ba5ec0d --- /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/src/recipes/removeConstantOfShape.cpp b/src/recipes/removeConstantOfShape.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5e84f7b494815ecb5a8937bb6f76ba1de80ad3f9 --- /dev/null +++ b/src/recipes/removeConstantOfShape.cpp @@ -0,0 +1,128 @@ +/******************************************************************************** + * 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/recipes/Recipes.hpp" + +#include <algorithm> +#include <cassert> +#include <cstddef> +#include <cstdint> +#include <cstdio> +#include <functional> +#include <memory> +#include <numeric> +#include <set> +#include <stdexcept> +#include <string> + +#include "aidge/data/Data.hpp" +#include "aidge/data/Tensor.hpp" +#include "aidge/filler/Filler.hpp" +#include "aidge/graph/GraphView.hpp" +#include "aidge/graph/Matching.hpp" +#include "aidge/graph/Node.hpp" +#include "aidge/operator/ConstantOfShape.hpp" +#include "aidge/operator/GenericOperator.hpp" +#include "aidge/operator/Producer.hpp" +#include "aidge/utils/ErrorHandling.hpp" +#include "aidge/utils/Types.h" + +// Graph Regex +#include "aidge/graphRegex/GraphRegex.hpp" + +namespace Aidge { + +size_t removeConstantOfShape(std::shared_ptr<GraphView> graph_view) { + const auto matches = + SinglePassGraphMatching(graph_view).match("Producer->ConstantOfShape"); + + size_t nbReplaced = 0; + for (const auto &match : matches) { + const auto prod_node = match.graph->rootNode(); + const auto prod_op = + std::static_pointer_cast<Producer_Op>(prod_node->getOperator()); + + const NodePtr constantofshape_node = + prod_node->getOrderedChildren().at(0).at(0); + + const auto constantofshape_op = + std::static_pointer_cast<ConstantOfShape_Op>( + constantofshape_node->getOperator()); + + if (prod_op->getOutput(0)->nbDims() != 1) { + Log::debug("{} : Producer output dimension number is {} != 1 and {} " + "input has to have 1 dim, skipping match.", + __func__, prod_op->getOutput(0)->nbDims(), + ConstantOfShape_Op::Type); + continue; + } + if (!prod_op->constant()) { + Log::debug("{} : Producer is not constant, skipping match.", __func__); + continue; + } + if (prod_op->getOutput(0)->dataType() != DataType::Int64) { + AIDGE_THROW_OR_ABORT( + std::runtime_error, + "{} : Producer output dtype is {} != int64 and {} " + "input type is restricted to int64_t, this is an error." + "Fix your network. skipping match.", + __func__, prod_op->getOutput(0)->dataType(), + ConstantOfShape_Op::Type); + continue; + } + + auto graph_to_replace = std::make_shared<GraphView>(); + auto new_graph = std::make_shared<GraphView>(); + graph_to_replace->add(constantofshape_node); + if (prod_node->getChildren().size() == 1) { + graph_to_replace->add(prod_node); + } else { + Log::debug("{} : Producer node has multiple children, only" + "replacing the {} node.", + __func__, ConstantOfShape_Op::Type); + } + + prod_node->forward(); + std::shared_ptr<Tensor> prod_output = prod_op->getOutput(0); + std::vector<DimSize_t> new_input_dims; + new_input_dims.reserve(prod_output->dims()[0]); + for (DimSize_t i = 0; i < prod_output->size(); ++i) { + new_input_dims.push_back(prod_output->get<int64_t>(i)); + } + + auto new_input = std::make_shared<Tensor>(new_input_dims); + new_input->setBackend(prod_op->backend() == "" ? "cpu" + : prod_op->backend()); + new_input->setDataType(constantofshape_op->value().dataType()); + for (std::size_t i = 0; i < new_input->size(); ++i) { + new_input->getImpl()->copy( + constantofshape_op->value().getImpl()->rawPtr(), 1, i); + } + auto new_prod = + Producer(new_input, prod_node->name() + "_constant_of_shape", true); + new_graph->add(new_prod); + + const auto success = GraphView::replace(graph_to_replace, new_graph); + if (!success) { + Log::warn("Could not replace Producer({})->ConstantOfShape({}) with" + "Producer", + prod_node->name(), constantofshape_node->name()); + } else { + ++nbReplaced; + } + } + + Log::info("Replaced {} (out of {}) matching Producer->ConstantOfShape with " + "Producers", + nbReplaced, matches.size()); + return nbReplaced; +} +} // namespace Aidge + diff --git a/unit_tests/operator/Test_ConstantOfShape.cpp b/unit_tests/operator/Test_ConstantOfShape.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c10d97ce5fb774e051e75f051772e1cbcd41dbea --- /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 + diff --git a/unit_tests/recipes/Test_removeConstantOfShape.cpp b/unit_tests/recipes/Test_removeConstantOfShape.cpp new file mode 100644 index 0000000000000000000000000000000000000000..247149a0fdb1087f14ac17d125659d677ccfb506 --- /dev/null +++ b/unit_tests/recipes/Test_removeConstantOfShape.cpp @@ -0,0 +1,50 @@ +/******************************************************************************** + * 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/graph/GraphView.hpp" +#include "aidge/operator/Identity.hpp" +#include "aidge/recipes/Recipes.hpp" + +#include <cstddef> +#include <cstdint> +#include <memory> +#include <vector> + +#include <catch2/catch_test_macros.hpp> + +#include "aidge/graph/OpArgs.hpp" +#include "aidge/operator/Add.hpp" +#include "aidge/operator/ConstantOfShape.hpp" +#include "aidge/operator/Conv.hpp" +#include "aidge/operator/MatMul.hpp" +#include "aidge/operator/Producer.hpp" +#include "aidge/operator/ReLU.hpp" +#include "aidge/utils/ArrayHelpers.hpp" +#include "aidge/utils/Types.h" + +using namespace Aidge; + +TEST_CASE("[cpu/recipies] removeConstantOfShape", + "[ConstantOfShape][removeConstantOfShape][recipies]") { + auto input_T = std::make_shared<Tensor>(Array1D<int64_t, 4>({1, 1, 3, 3})); + + auto model = std::make_shared<GraphView>(); + SECTION("Sequential model") { + model = Sequential({Producer(input_T, "prod_0", true), + ConstantOfShape(3, "constantOfShape_0"), + Conv(1, 1, {3, 3}, "Conv_0"), ReLU("ReLU_1")}); + model->save("test_removeConstantOfShape_model_before_1"); + CHECK(removeConstantOfShape(model) == 1); + CHECK(model->forwardDims()); + model->save("test_removeConstantOfShape_model_after_1"); + } +} +