diff --git a/.gitlab/ci/build.gitlab-ci.yml b/.gitlab/ci/build.gitlab-ci.yml index 73b85c8a409e675c849b9ca66557c63b5acf6359..cd56a55fa7e9cbcefba4715188fd270462e81976 100644 --- a/.gitlab/ci/build.gitlab-ci.yml +++ b/.gitlab/ci/build.gitlab-ci.yml @@ -27,6 +27,8 @@ build:ubuntu_python: - python3 -m pip install virtualenv - virtualenv venv - source venv/bin/activate + # Numpy dependancy for unit test + - python3 -m pip install numpy - export AIDGE_INSTALL=`pwd`/install - python3 -m pip install . artifacts: diff --git a/aidge_core/unit_tests/test_parameters.py b/aidge_core/unit_tests/test_parameters.py index 02c7598820d2429bc49ff9a2f02c8ee841783173..1e24276745312d4483c268156963e0efe413b46c 100644 --- a/aidge_core/unit_tests/test_parameters.py +++ b/aidge_core/unit_tests/test_parameters.py @@ -40,7 +40,7 @@ class test_parameters(unittest.TestCase): def test_matmul(self): out_channels = 8 - matmul_op = aidge_core.Matmul(out_channels).get_operator() + matmul_op = aidge_core.MatMul(out_channels).get_operator() self.assertEqual(matmul_op.get("OutChannels"), out_channels) def test_producer_1D(self): diff --git a/aidge_core/unit_tests/test_recipies.py b/aidge_core/unit_tests/test_recipies.py new file mode 100644 index 0000000000000000000000000000000000000000..754907443530f7e73d1e10ed9549d0c8eb78a011 --- /dev/null +++ b/aidge_core/unit_tests/test_recipies.py @@ -0,0 +1,78 @@ +""" +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 +""" + +import unittest +import aidge_core + +class test_recipies(unittest.TestCase): + """ + """ + def setUp(self): + pass + + def tearDown(self): + pass + + def test_remove_flatten(self): + graph_view = aidge_core.sequential([ + aidge_core.GenericOperator("Flatten", 1, 1, 1, name="Flatten0"), + aidge_core.FC(50, name='0') + ]) + old_nodes = graph_view.get_nodes() + aidge_core.remove_flatten(graph_view) + self.assertTrue(len(graph_view.get_nodes()) == len(old_nodes) - 1) + self.assertTrue("Flatten0" not in [i.name for i in graph_view.get_nodes()]) + + self.assertTrue(all([i in old_nodes for i in graph_view.get_nodes()])) + + def test_fuse_matmul_add(self): + matmul0 = aidge_core.GenericOperator("MatMul", 1, 2, 1, name="MatMul0") + add0 = aidge_core.Add(name="Add0") + matmul1 = aidge_core.GenericOperator("MatMul", 1, 2, 1, name="MatMul1") + add1 = aidge_core.Add(name="Add1") + + graph_view = aidge_core.sequential([matmul0, add0, matmul1, add1]) + + w0 = aidge_core.Producer([1, 1], name="W0") + w0.add_child(matmul0, 0, 1) + graph_view.add(w0) + + b0 = aidge_core.Producer([1], name="B0") + b0.add_child(add0, 0, 1) + graph_view.add(b0) + + w1 = aidge_core.Producer([1, 1], name="W1") + w1.add_child(matmul1, 0, 1) + graph_view.add(w1) + + b1 = aidge_core.Producer([1], name="B1") + b1.add_child(add1, 0, 1) + graph_view.add(b1) + + old_nodes = graph_view.get_nodes() + aidge_core.fuse_mul_add(graph_view) + + self.assertTrue(len(graph_view.get_nodes()) == len(old_nodes) - 2) + self.assertTrue("MatMul0" not in [i.name() for i in graph_view.get_nodes()]) + self.assertTrue("Add0" not in [i.name() for i in graph_view.get_nodes()]) + self.assertTrue("MatMul1" not in [i.name() for i in graph_view.get_nodes()]) + self.assertTrue("Add1" not in [i.name() for i in graph_view.get_nodes()]) + + self.assertTrue("W0" in [i.name() for i in graph_view.get_nodes()]) + self.assertTrue("B0" in [i.name() for i in graph_view.get_nodes()]) + self.assertTrue("W1" in [i.name() for i in graph_view.get_nodes()]) + self.assertTrue("B1" in [i.name() for i in graph_view.get_nodes()]) + # TODO : Vérifier que FC bien crée + +if __name__ == '__main__': + unittest.main() + + + diff --git a/aidge_core/unit_tests/test_tensor.py b/aidge_core/unit_tests/test_tensor.py new file mode 100644 index 0000000000000000000000000000000000000000..a214a0e354c64b515d0a7ac24d81c85e116938ca --- /dev/null +++ b/aidge_core/unit_tests/test_tensor.py @@ -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 +""" + +import unittest +import aidge_core + +from functools import reduce +import numpy as np + +class test_tensor(unittest.TestCase): + """ + """ + def setUp(self): + pass + + def tearDown(self): + pass + + def test_getcoord_getidx(self): + dims = [2,2,2] + size = reduce((lambda x, y: x*y), dims) + + np_array = np.arange(size).reshape(dims) + + t = aidge_core.Tensor(np_array) + for i in range(size): + coord = t.get_coord(i) + idx = t.get_idx(coord) + self.assertEqual(idx, i) + +if __name__ == '__main__': + unittest.main() + + + + + diff --git a/include/aidge/aidge.hpp b/include/aidge/aidge.hpp index cfda3ac7fa024f8cf80b4589d978b9b5bff5b4f0..9c0d8c0b321892d60f40d52eb2a44d4d0fec3a2c 100644 --- a/include/aidge/aidge.hpp +++ b/include/aidge/aidge.hpp @@ -33,7 +33,7 @@ #include "aidge/operator/ConvDepthWise.hpp" #include "aidge/operator/FC.hpp" #include "aidge/operator/GenericOperator.hpp" -#include "aidge/operator/Matmul.hpp" +#include "aidge/operator/MatMul.hpp" #include "aidge/operator/MaxPooling.hpp" //#include "aidge/operator/MetaOperator.hpp" #include "aidge/operator/Operator.hpp" diff --git a/include/aidge/backend/TensorImpl.hpp b/include/aidge/backend/TensorImpl.hpp index c56f66fc0b827ccccd9749b9880507dbf48c8179..dfe3d932ac68929acfd26ecf7126e07c4707bcfc 100644 --- a/include/aidge/backend/TensorImpl.hpp +++ b/include/aidge/backend/TensorImpl.hpp @@ -27,6 +27,9 @@ public: { printf("Cannot set raw pointer for backend %s\n", mBackend); }; + + virtual void* getRaw(std::size_t /*idx*/)=0; + virtual std::size_t scalarSize() const = 0; // Size of one scalar (in bytes) constexpr const char *backend() const { return mBackend; } virtual ~TensorImpl() = default; diff --git a/include/aidge/data/Tensor.hpp b/include/aidge/data/Tensor.hpp index c3a6e478f8943253a9f9b3565db2d4452a9ca133..7422a52eb171ee6dae0e14ad67c0562295fe5d8c 100644 --- a/include/aidge/data/Tensor.hpp +++ b/include/aidge/data/Tensor.hpp @@ -446,18 +446,33 @@ class Tensor : public Data, */ bool empty() const { return mDims.empty(); } - template <typename expectedType, std::array<std::size_t, 1>::size_type DIM> - constexpr expectedType &get(std::array<std::size_t, DIM> idx) { - assert(DIM == mDims.size()); - assert(mImpl); - std::size_t unfoldedIdx = 0; - for (std::size_t i = 0; i < DIM - std::size_t(1); ++i) { - unfoldedIdx = (unfoldedIdx + idx[i]) * mDims[i + 1]; - } - unfoldedIdx += idx[DIM - 1]; - return static_cast<expectedType *>(mImpl->rawPtr())[unfoldedIdx]; + template <typename expectedType> + expectedType& get(std::size_t idx){ + // TODO : add assert expected Type compatible with datatype + // TODO : add assert idx < Size + return *reinterpret_cast<expectedType *>(mImpl->getRaw(idx)); + } + + template <typename expectedType> + expectedType& get(std::vector<std::size_t> coordIdx){ + return get<expectedType>(getIdx(coordIdx)); + } + + template <typename expectedType> + void set(std::size_t idx, expectedType value){ + // TODO : add assert expected Type compatible with datatype + // TODO : add assert idx < Size + void* dataPtr = mImpl->getRaw(idx); + std::memcpy(dataPtr, &value, sizeof(expectedType)); } + template <typename expectedType> + void set(std::vector<std::size_t> coordIdx, expectedType value){ + set<expectedType>(getIdx(coordIdx), value); + } + + + std::string toString() { if (dims().empty()) { return "{}"; } std::string res; @@ -559,6 +574,42 @@ class Tensor : public Data, return mGrad; } + /** + * @brief From the the 1D index, return the coordinate of an element in the tensor. + * + * @param flatIdx 1D index of the value considering a flatten tensor. + * @return std::vector<DimSize_t> + */ + std::vector<std::size_t> getCoord(std::size_t flatIdx) const { + std::vector<std::size_t> coordIdx = std::vector<std::size_t>(mDims.size()); + std::size_t idx = flatIdx; + for (std::size_t i = mDims.size() - 1; i > 0; --i){ + coordIdx[i] = (idx % mDims[i]); + idx/=mDims[i]; + } + coordIdx[0] = idx % mDims[0]; + return coordIdx; + } + + /** + * @brief From the coordinate returns the 1D index of an element in the tensor. + * + * @param coordIdx Coordinate to an element in the tensor + * @return DimSize_t + */ + std::size_t getIdx(std::vector<std::size_t> coordIdx) const { + // std::size_t flatIdx = 0; + // std::size_t stride = 1; + std::size_t flatIdx = 0; + assert(coordIdx.size() == mDims.size() && "Coordinates does not match number of dimensions"); + std::size_t i = 0; + for(; i < mDims.size() - 1; ++i){ + assert(coordIdx[i] < mDims[i] && "Coordinates dimensions does not fit the dimensions of the tensor"); + flatIdx = (flatIdx + coordIdx[i]) * mDims[i + 1]; + } + return flatIdx + coordIdx[i]; + } + private: ///\bug not protected against overflow std::size_t computeSize() { diff --git a/include/aidge/operator/BatchNorm.hpp b/include/aidge/operator/BatchNorm.hpp index f1a6ae8f52141839f72211f23511a0607e2138b6..c95ecac92d14be6d56edd7abda6c20b011e65aba 100644 --- a/include/aidge/operator/BatchNorm.hpp +++ b/include/aidge/operator/BatchNorm.hpp @@ -97,7 +97,6 @@ public: if (!mInputs[0]->empty()) { for (std::size_t i = nbDataInputs(); i < nbInputs(); ++i) { if(mInputs[i]->size() != mInputs[0]->dims()[1]) { - assert(!mInputs[0]->hasImpl() && "Incompatible size with already implemented learnable parameter"); mInputs[i]->resize(std::array<DimSize_t, 1>({mInputs[0]->dims()[1]})); } } @@ -181,4 +180,4 @@ template <> const char *const EnumStrings<Aidge::BatchNormParam>::data[] = { "Epsilon", "Momentum" }; } -#endif //AIDGE_CORE_OPERATOR_BATCHNORM_H_ \ No newline at end of file +#endif //AIDGE_CORE_OPERATOR_BATCHNORM_H_ diff --git a/include/aidge/operator/Conv.hpp b/include/aidge/operator/Conv.hpp index e95b46ae5583df9e6b471dc4005d0d9c4636ca9b..7113adb41d051d67e22456bbac05c00aa15333ab 100644 --- a/include/aidge/operator/Conv.hpp +++ b/include/aidge/operator/Conv.hpp @@ -216,8 +216,14 @@ inline std::shared_ptr<Node> Conv( namespace { template <> -const char *const EnumStrings<Aidge::ConvParam>::data[] = {"StrideDims", "DilationDims", "InChannels", "OutChannels", - "KernelDims", "PaddingDims"}; +const char *const EnumStrings<Aidge::ConvParam>::data[] = { + "StrideDims", + "DilationDims", + "InChannels", + "OutChannels", + "KernelDims", + "PaddingDims" +}; } #endif /* AIDGE_CORE_OPERATOR_CONV_H_ */ diff --git a/include/aidge/operator/Matmul.hpp b/include/aidge/operator/MatMul.hpp similarity index 77% rename from include/aidge/operator/Matmul.hpp rename to include/aidge/operator/MatMul.hpp index 54bbcb267f346fd79a2b9e3a8aca571ed2e6ba91..9e7e7e43a52afdc93a0f3a0430ae64010abb11dc 100644 --- a/include/aidge/operator/Matmul.hpp +++ b/include/aidge/operator/MatMul.hpp @@ -27,29 +27,29 @@ #include "aidge/utils/Registrar.hpp" namespace Aidge { -enum class MatmulParam { OutChannels }; +enum class MatMulParam { OutChannels }; -class Matmul_Op : public Operator, - public Registrable<Matmul_Op, +class MatMul_Op : public Operator, + public Registrable<MatMul_Op, std::string, - std::unique_ptr<OperatorImpl>(const Matmul_Op &)>, - public Parameterizable<MatmulParam, DimSize_t> { + std::unique_ptr<OperatorImpl>(const MatMul_Op &)>, + public Parameterizable<MatMulParam, DimSize_t> { public: std::array<std::shared_ptr<Tensor>, 2> mInputs = {std::make_shared<Tensor>(), std::make_shared<Tensor>()}; const std::shared_ptr<Tensor> mOutput = std::make_shared<Tensor>(); public: - static constexpr const char* Type = "Matmul"; + static constexpr const char* Type = "MatMul"; - Matmul_Op() = delete; + MatMul_Op() = delete; - using Parameterizable_ = Parameterizable<MatmulParam, DimSize_t>; - template <MatmulParam e> using param = typename Parameterizable_::template param<e>; + using Parameterizable_ = Parameterizable<MatMulParam, DimSize_t>; + template <MatMulParam e> using param = typename Parameterizable_::template param<e>; - Matmul_Op(DimSize_t out_channels) + MatMul_Op(DimSize_t out_channels) : Operator(Type), Parameterizable_( - param<MatmulParam::OutChannels>(out_channels)) + param<MatMulParam::OutChannels>(out_channels)) { setDatatype(DataType::Float32); } @@ -58,22 +58,22 @@ public: * @brief Copy-constructor. Copy the operator parameters and its output tensor(s), but not its input tensors (the new operator has no input associated). * @param op Operator to copy. */ - Matmul_Op(const Matmul_Op& op) + MatMul_Op(const MatMul_Op& op) : Operator(Type), Parameterizable_(op), mOutput(std::make_shared<Tensor>(*op.mOutput)) { // cpy-ctor setDatatype(op.mOutput->dataType()); - mImpl = op.mImpl ? Registrar<Matmul_Op>::create(mOutput->getImpl()->backend())(*this) : nullptr; + mImpl = op.mImpl ? Registrar<MatMul_Op>::create(mOutput->getImpl()->backend())(*this) : nullptr; } /** * @brief Clone the operator using its copy-constructor. - * @see Operator::Matmul_Op + * @see Operator::MatMul_Op */ std::shared_ptr<Operator> clone() const override { - return std::make_shared<Matmul_Op>(*this); + return std::make_shared<MatMul_Op>(*this); } void associateInput(const IOIndex_t inputIdx, std::shared_ptr<Data> data) override final { @@ -85,9 +85,9 @@ public: void computeOutputDims() override final { if (!mInputs[0]->empty()) { // <in_features**, out_channels> - std::array<DimSize_t, 2> weightDims = {static_cast<DimSize_t>(mInputs[0]->size()), this->template get<MatmulParam::OutChannels>()}; + std::array<DimSize_t, 2> weightDims = {this->template get<MatMulParam::OutChannels>(), static_cast<DimSize_t>(mInputs[0]->sizeM1())}; // <out_channels, batch> - std::array<DimSize_t, 1> outputDims = {this->template get<MatmulParam::OutChannels>()}; + std::array<DimSize_t, 2> outputDims = {mInputs[0]->dims()[0], this->template get<MatMulParam::OutChannels>()}; mInputs[1]->resize(weightDims); mOutput->resize(outputDims); @@ -128,7 +128,7 @@ public: void setBackend(const std::string& name) { - mImpl = Registrar<Matmul_Op>::create(name)(*this); + mImpl = Registrar<MatMul_Op>::create(name)(*this); mOutput->setBackend(name); // FIXME: temporary workaround @@ -150,17 +150,17 @@ public: inline IOIndex_t nbOutputs() const noexcept override final { return 1; } }; -inline std::shared_ptr<Node> Matmul(DimSize_t out_channels, const std::string& name = "") { - // FIXME: properly handle default w&b initialization in every cases - auto matmul = std::make_shared<Node>(std::make_shared<Matmul_Op>(out_channels), name); - addProducer(matmul, 1, {1, out_channels}, "w"); +inline std::shared_ptr<Node> MatMul(DimSize_t out_channels, const std::string& name = "") { + // FIXME: properly handle default w initialization in every cases + auto matmul = std::make_shared<Node>(std::make_shared<MatMul_Op>(out_channels), name); + addProducer(matmul, 1, {out_channels, 1}, "w"); return matmul; } } // namespace Aidge namespace { template <> -const char *const EnumStrings<Aidge::MatmulParam>::data[] = {"OutChannels"}; +const char *const EnumStrings<Aidge::MatMulParam>::data[] = {"OutChannels"}; } #endif /* AIDGE_CORE_OPERATOR__MATMUL_H_ */ diff --git a/include/aidge/operator/MetaOperator.hpp b/include/aidge/operator/MetaOperator.hpp index 9e12b159888923cfea10dd02b7b267a46abcb3b7..327361de4fc278efbf6cc1afe4c140c7994fe61e 100644 --- a/include/aidge/operator/MetaOperator.hpp +++ b/include/aidge/operator/MetaOperator.hpp @@ -34,7 +34,7 @@ public: /** * @brief Clone the operator using its copy-constructor. - * @see Operator::Matmul_Op + * @see Operator::MatMul_Op */ std::shared_ptr<Operator> clone() const override { return std::make_shared<MetaOperator>(*this); diff --git a/include/aidge/operator/Producer.hpp b/include/aidge/operator/Producer.hpp index fbab24a0d23712b138c41e969372701fdb3d749e..681ed96892e216094f9392df01f5a10f66609638 100644 --- a/include/aidge/operator/Producer.hpp +++ b/include/aidge/operator/Producer.hpp @@ -75,6 +75,16 @@ public: assert(false && "Producer operator takes no input"); } + /** + * @brief Set the Output Tensor of the Producer operator. + * This method will create a copy of the Tensor. + * + * @param newOutput Tensor containing the values to copy + */ + void setOutputTensor(const Tensor& newOutput) { + *mOutput = newOutput; + } + void computeOutputDims() override final {} bool outputDimsForwarded() const override final {return true;} @@ -163,4 +173,4 @@ void addProducer(std::shared_ptr<Node>& otherNode, const IOIndex_t inputIdx, Dim } } // namespace Aidge -#endif /* AIDGE_CORE_OPERATOR_PRODUCER_H_ */ \ No newline at end of file +#endif /* AIDGE_CORE_OPERATOR_PRODUCER_H_ */ diff --git a/include/aidge/utils/Parameter.hpp b/include/aidge/utils/Parameter.hpp index a475576170915182e25dbaa193ca8a7a3853c0e0..2b48e833533da5b8bb4a5f4f134860e89717804a 100644 --- a/include/aidge/utils/Parameter.hpp +++ b/include/aidge/utils/Parameter.hpp @@ -145,11 +145,11 @@ public: assert(false && "parameter not found"); } - template <typename R, std::size_t SIZE = std::tuple_size<std::tuple<T...>>::value-1> + template <typename R, std::size_t SIZE = std::tuple_size<std::tuple<T...>>::value> constexpr typename std::enable_if<(SIZE > 0), R&>::type get(std::size_t i) { - if (i == SIZE) { - if (std::is_same<R, typename std::tuple_element<SIZE,std::tuple<T...>>::type>::value) { - return reinterpret_cast<R&>(std::get<SIZE>(mParams)); + if (i == SIZE-1) { + if (std::is_same<R, typename std::tuple_element<SIZE-1,std::tuple<T...>>::type>::value) { + return reinterpret_cast<R&>(std::get<SIZE-1>(mParams)); } else { assert(false && "wrong parameter type"); @@ -160,9 +160,10 @@ public: } } - template <typename R, std::size_t SIZE = std::tuple_size<std::tuple<T...>>::value-1> - constexpr typename std::enable_if<(SIZE <= 0), R&>::type get(std::size_t i) { + template <typename R, std::size_t SIZE = std::tuple_size<std::tuple<T...>>::value> + [[noreturn]] constexpr typename std::enable_if<(SIZE == 0), R&>::type get(std::size_t /*i*/) { assert(false && "parameter not found"); + exit(-1); } constexpr const std::tuple<T...>& getParams() const { diff --git a/include/aidge/utils/Recipies.hpp b/include/aidge/utils/Recipies.hpp index 4cbf8fd284bef314dbe28b19ebdae05172467bad..894e56fae2e9c2f6bcf11e4e76a433f5c8058080 100644 --- a/include/aidge/utils/Recipies.hpp +++ b/include/aidge/utils/Recipies.hpp @@ -17,11 +17,54 @@ namespace Aidge{ +// FUSE MATMUL + ADD -> FC + +/** + * @brief Merge ``MatMul`` and :cpp:function:`Aidge::Add` Node into a :cpp:function:`Aidge::FC` Node. + * + * @param nodes Strict set of Node to merge. + */ void fuseMulAdd(std::set<std::shared_ptr<Node>> nodes); +/** + * @brief Merge ``MatMul`` and :cpp:function:`Aidge::Add` Node into a :cpp:function:`Aidge::FC` Node. + * + * @param graphView Graph view to use graph matching on, in order to apply transfomrations. + */ +void fuseMulAdd(std::shared_ptr<GraphView> graphView); + + +// REMOVE FLATTEN + FC -> FC + +/** + * @brief Remove ``Flatten`` before :cpp:function:`Aidge::FC` Node. + * + * @param nodes Strict set of Node to merge. + */ void removeFlatten(std::set<std::shared_ptr<Node>> nodes); +/** + * @brief Remove ``Flatten`` before :cpp:function:`Aidge::FC` Node. + * + * @param graphView Graph view to use graph matching on, in order to apply transfomrations. + */ +void removeFlatten(std::shared_ptr<GraphView> graphView); + +// FUSE BN + FC || CONV -> FC || CONV +/** + * @brief Fuse :cpp:function:`Aidge::BatchNorm` with :cpp:function:`Aidge::Conv` or :cpp:function:`Aidge::FC` Nodes. + * Ref: https://nenadmarkus.com/p/fusing-batchnorm-and-conv/ + * + * @param nodes Strict set of Node to merge. + */ +void fuseBatchNorm(std::set<std::shared_ptr<Node>> nodes); +/** + * @brief Fuse :cpp:function:`Aidge::BatchNorm` with :cpp:function:`Aidge::Conv` or :cpp:function:`Aidge::FC` Nodes. + * Ref: https://nenadmarkus.com/p/fusing-batchnorm-and-conv/ + * + * @param graphView Graph view to use graph matching on, in order to apply transfomrations. + */ +void fuseBatchNorm(std::shared_ptr<GraphView> graphView); } - -#endif /* AIDGE_CORE_UTILS_RECIPIES_H_ */ \ No newline at end of file +#endif /* AIDGE_CORE_UTILS_RECIPIES_H_ */ diff --git a/include/aidge/utils/Registrar.hpp b/include/aidge/utils/Registrar.hpp index 98749c1349bad644dee2c1a8549559939791f71c..de543e95a16475c4443164af7be5c379d6554f8d 100644 --- a/include/aidge/utils/Registrar.hpp +++ b/include/aidge/utils/Registrar.hpp @@ -34,6 +34,7 @@ public: static std::map<Key, std::function<Func>>& registry() { #ifdef PYBIND + #define _CRT_SECURE_NO_WARNINGS if (std::getenv("AIDGE_CORE_WITH_PYBIND")){ std::string name = std::string("registrar_")+typeid(Registrable<DerivedClass, Key, Func>).name(); static auto shared_data = reinterpret_cast<std::map<Key, std::function<Func>> *>(py::get_shared_data(name)); diff --git a/include/aidge/utils/TensorUtils.hpp b/include/aidge/utils/TensorUtils.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6387619546c66922e48cf95a8a56487d4b0d0641 --- /dev/null +++ b/include/aidge/utils/TensorUtils.hpp @@ -0,0 +1,52 @@ +/******************************************************************************** + * 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_UTILS_TENSOR_UTILS_H_ +#define AIDGE_CORE_UTILS_TENSOR_UTILS_H_ +#include <cmath> // std::abs +#include "aidge/data/Tensor.hpp" + +/** + * @brief Compare two :cpp:class:`Aidge::Tensor` value wise. The comparison function is: + * + * |t1-t2| <= absolute + relative * |t2| + * + * If a tensor value is different from the other tensor return False + * If the tensor does not have the same size, return False + * If the datatype is not the same between each tensor return False + * If the templated type does not correspond to the datatype of each tensor, raise an assertion error + * + * @tparam T should correspond to the type of the tensor, define the type of the absolute and relative error + * @param t1 first :cpp:class:`Aidge::Tensor` to test + * @param t2 second :cpp:class:`Aidge::Tensor` to test + * @param relative relative difference allowed (should be betwen 0 and 1) + * @param absolute absolute error allowed (shoulmd be positive) + * @return true if both tensor are approximately equal and have the datatype, shape. Else return false + */ +template <typename T> +bool approxEq(Aidge::Tensor t1, Aidge::Tensor t2, float relative, float absolute){ + assert(t1.dataType() == t2.dataType()); + assert(t1.dataType() == NativeType<T>::type); + assert(relative >= 0); + assert(absolute >= 0 && absolute<=1); + + if (t1.size() != t2.size()){ + return false; + } + for(size_t i; i < t1.size(); ++i){ + if (static_cast<float>(std::abs(t1.get<T>(i) - t2.get<T>(i))) > (absolute + (relative * static_cast<float>(std::abs(t2.get<T>(i)))))){ + return false; + } + } + return true; +} + +#endif /* AIDGE_CORE_UTILS_TENSOR_UTILS_H_s */ diff --git a/python_binding/data/pybind_Tensor.cpp b/python_binding/data/pybind_Tensor.cpp index d6442723ecc79527e8eaa7d3e03a466c085dfa58..31470e0eb2c50b5386b64498f89419801b133d3a 100644 --- a/python_binding/data/pybind_Tensor.cpp +++ b/python_binding/data/pybind_Tensor.cpp @@ -26,10 +26,10 @@ namespace Aidge { template<typename T> void addCtor(py::class_<Tensor, - std::shared_ptr<Tensor>, - Data, + std::shared_ptr<Tensor>, + Data, Registrable<Tensor, - std::tuple<std::string, DataType>, + std::tuple<std::string, DataType>, std::unique_ptr<TensorImpl>(const Tensor&)>>& mTensor){ mTensor.def(py::init([]( py::array_t<T, py::array::c_style | py::array::forcecast> b) { /* Request a buffer descriptor from Python */ @@ -46,24 +46,27 @@ void addCtor(py::class_<Tensor, }else{ printf("Warning : Could not use aidge_cpu backend, verify you have `import aidge_cpu`\n"); } - + return newTensor; - })); + })) + .def("__setitem__", (void (Tensor::*)(std::size_t, T)) &Tensor::set) + .def("__setitem__", (void (Tensor::*)(std::vector<std::size_t>, T)) &Tensor::set) + ; } void init_Tensor(py::module& m){ py::class_<Registrable<Tensor, - std::tuple<std::string, DataType>, + std::tuple<std::string, DataType>, std::unique_ptr<TensorImpl>(const Tensor&)>, std::shared_ptr<Registrable<Tensor, - std::tuple<std::string, DataType>, + std::tuple<std::string, DataType>, std::unique_ptr<TensorImpl>(const Tensor&)>>>(m,"TensorRegistrable"); - py::class_<Tensor, std::shared_ptr<Tensor>, - Data, + py::class_<Tensor, std::shared_ptr<Tensor>, + Data, Registrable<Tensor, - std::tuple<std::string, DataType>, + std::tuple<std::string, DataType>, std::unique_ptr<TensorImpl>(const Tensor&)>> pyClassTensor (m,"Tensor", py::multiple_inheritance(), py::buffer_protocol()); @@ -74,6 +77,8 @@ void init_Tensor(py::module& m){ .def("size", &Tensor::size) .def("resize", (void (Tensor::*)(const std::vector<DimSize_t>&)) &Tensor::resize) .def("has_impl", &Tensor::hasImpl) + .def("get_coord", &Tensor::getCoord) + .def("get_idx", &Tensor::getIdx) .def_static("get_available_backends", &Tensor::getAvailableBackends) .def("__str__", [](Tensor& b) { return b.toString(); @@ -82,15 +87,27 @@ void init_Tensor(py::module& m){ return b.size(); }) .def("__getitem__", [](Tensor& b, size_t idx)-> py::object { - // TODO : Should return error if backend not compatible with get if (idx >= b.size()) throw py::index_error(); switch(b.dataType()){ case DataType::Float64: - return py::cast(static_cast<double*>(b.getImpl()->rawPtr())[idx]); + return py::cast(b.get<double>(idx)); + case DataType::Float32: + return py::cast(b.get<float>(idx)); + case DataType::Int32: + return py::cast(b.get<int>(idx)); + default: + return py::none(); + } + }) + .def("__getitem__", [](Tensor& b, std::vector<size_t> coordIdx)-> py::object { + if (b.getIdx(coordIdx) >= b.size()) throw py::index_error(); + switch(b.dataType()){ + case DataType::Float64: + return py::cast(b.get<double>(coordIdx)); case DataType::Float32: - return py::cast(static_cast<float*>(b.getImpl()->rawPtr())[idx]); + return py::cast(b.get<float>(coordIdx)); case DataType::Int32: - return py::cast(static_cast<int*>(b.getImpl()->rawPtr())[idx]); + return py::cast(b.get<int>(coordIdx)); default: return py::none(); } @@ -126,12 +143,12 @@ void init_Tensor(py::module& m){ } return py::buffer_info( - tensorImpl->rawPtr(), /* Pointer to buffer */ - tensorImpl->scalarSize(), /* Size of one scalar */ - dataFormatDescriptor, /* Python struct-style format descriptor */ - b.nbDims(), /* Number of dimensions */ - dims, /* Buffer dimensions */ - strides /* Strides (in bytes) for each index */ + tensorImpl->rawPtr(), /* Pointer to buffer */ + tensorImpl->scalarSize(), /* Size of one scalar */ + dataFormatDescriptor, /* Python struct-style format descriptor */ + b.nbDims(), /* Number of dimensions */ + dims, /* Buffer dimensions */ + strides /* Strides (in bytes) for each index */ ); }); @@ -142,6 +159,6 @@ void init_Tensor(py::module& m){ // #if SIZE_MAX != 0xFFFFFFFF addCtor<double>(pyClassTensor); // #endif - + } } diff --git a/python_binding/operator/pybind_Matmul.cpp b/python_binding/operator/pybind_Matmul.cpp index b6ae27289fabe1fe4dbeea60704a61373bc850cf..b0b3c3df6dfbd2c50969da40c2621dbbdf04178b 100644 --- a/python_binding/operator/pybind_Matmul.cpp +++ b/python_binding/operator/pybind_Matmul.cpp @@ -11,7 +11,7 @@ #include <pybind11/pybind11.h> -#include "aidge/operator/Matmul.hpp" +#include "aidge/operator/MatMul.hpp" #include "aidge/utils/Parameter.hpp" #include "aidge/backend/OperatorImpl.hpp" #include "aidge/operator/Operator.hpp" @@ -20,13 +20,13 @@ namespace py = pybind11; namespace Aidge { -void declare_Matmul(py::module &m) { - py::class_<Matmul_Op, std::shared_ptr<Matmul_Op>, Operator, PyAbstractParametrizable>(m, "Matmul_Op", py::multiple_inheritance()); +void declare_MatMul(py::module &m) { + py::class_<MatMul_Op, std::shared_ptr<MatMul_Op>, Operator, PyAbstractParametrizable>(m, "MatMul_Op", py::multiple_inheritance()); - m.def("Matmul", &Matmul, py::arg("out_channels"), py::arg("name") = ""); + m.def("MatMul", &MatMul, py::arg("out_channels"), py::arg("name") = ""); } -void init_Matmul(py::module &m) { - declare_Matmul(m); +void init_MatMul(py::module &m) { + declare_MatMul(m); } } // namespace Aidge diff --git a/python_binding/operator/pybind_Producer.cpp b/python_binding/operator/pybind_Producer.cpp index ea9880800059e8993996e67138f89419c165fc4f..4714e096fba90aff5c4289c1f87486e411c21b78 100644 --- a/python_binding/operator/pybind_Producer.cpp +++ b/python_binding/operator/pybind_Producer.cpp @@ -26,18 +26,19 @@ template <DimIdx_t DIM> void declare_Producer(py::module &m) { // m.def(("Producer_" + std::to_string(DIM)+"D").c_str(), py::overload_cast<shared_ptr<Node>&>(&Producer<DIM>), py::arg("dims"), py::arg("name")); m.def("Producer", static_cast<std::shared_ptr<Node>(*)(const std::array<DimSize_t, DIM>&, const std::string&)>(&Producer), py::arg("dims"), py::arg("name") = ""); - + } void init_Producer(py::module &m) { py::class_<Producer_Op, std::shared_ptr<Producer_Op>, Operator>( - m, - "ProducerOp", + m, + "ProducerOp", py::multiple_inheritance()) - .def("dims", &Producer_Op::dims); + .def("dims", &Producer_Op::dims) + .def("set_output_tensor", &Producer_Op::setOutputTensor); m.def("Producer", static_cast<std::shared_ptr<Node>(*)(const std::shared_ptr<Tensor>, const std::string&)>(&Producer), py::arg("tensor"), py::arg("name") = ""); - + declare_Producer<1>(m); declare_Producer<2>(m); declare_Producer<3>(m); diff --git a/python_binding/pybind_core.cpp b/python_binding/pybind_core.cpp index 78418d51a5c410cb56bb8421fd7f3dc6ec6d32db..db116d132ec8ffc504b2c0910eafc1a3da34534f 100644 --- a/python_binding/pybind_core.cpp +++ b/python_binding/pybind_core.cpp @@ -28,7 +28,7 @@ void init_ConvDepthWise(py::module&); void init_FC(py::module&); void init_GenericOperator(py::module&); void init_LeakyReLU(py::module&); -void init_Matmul(py::module&); +void init_MatMul(py::module&); void init_MaxPooling(py::module&); void init_Producer(py::module&); void init_ReLU(py::module&); @@ -46,7 +46,7 @@ void init_GRegex(py::module&); void init_Recipies(py::module&); void init_Scheduler(py::module&); - +void init_TensorUtils(py::module&); void set_python_flag(){ // Set an env variable to know if we run with ypthon or cpp @@ -75,7 +75,7 @@ void init_Aidge(py::module& m){ init_FC(m); init_GenericOperator(m); init_LeakyReLU(m); - init_Matmul(m); + init_MatMul(m); init_MaxPooling(m); init_ReLU(m); init_Softmax(m); @@ -86,6 +86,7 @@ void init_Aidge(py::module& m){ init_GRegex(m); init_Recipies(m); init_Scheduler(m); + init_TensorUtils(m); } PYBIND11_MODULE(aidge_core, m) { diff --git a/python_binding/recipies/pybind_Recipies.cpp b/python_binding/recipies/pybind_Recipies.cpp index b4147dcb4fb82dbfe9f5b4605604725c6945ece9..93c131ef7417135bfdbc657c5c809339430616ed 100644 --- a/python_binding/recipies/pybind_Recipies.cpp +++ b/python_binding/recipies/pybind_Recipies.cpp @@ -20,24 +20,51 @@ namespace py = pybind11; namespace Aidge { void init_Recipies(py::module &m) { - m.def("fuse_mul_add", &fuseMulAdd, py::arg("nodes"), R"mydelimiter( - Recipie to Fuse MatMul and Add operators into an `aidge.FC` operator. - - Parameters - ---------- + + + m.def("fuse_mul_add", static_cast<void(*)(std::shared_ptr<GraphView>)>(fuseMulAdd), py::arg("graph_view"), R"mydelimiter( + Recipie to Fuse MatMul and Add operators into an :py:class:`aidge_core.FC` operator. + + :param graph_view: Graph view on which we want to apply the recipie + :type graph_view: :py:class:`aidge_core.GraphView` + )mydelimiter"); + m.def("fuse_mul_add", static_cast<void(*)(std::set<std::shared_ptr<Node>>)>(fuseMulAdd), py::arg("nodes"), R"mydelimiter( + Recipie to Fuse MatMul and Add operators into an :py:class:`aidge_core.FC` operator. + :param nodes: The MatMul and Add nodes to fuse. - :type nodes: list of `aidge.node` + :type nodes: list of :py:class:`aidge_core.Node` + )mydelimiter"); + + m.def("remove_flatten", static_cast<void(*)(std::shared_ptr<GraphView>)>(removeFlatten), py::arg("graph_view"), R"mydelimiter( + Recipie to remove a flatten operator. + :param graph_view: Graph view on which we want to apply the recipie + :type graph_view: :py:class:`aidge_core.GraphView` )mydelimiter"); - m.def("remove_flatten", &removeFlatten, py::arg("nodes"), R"mydelimiter( + m.def("remove_flatten", static_cast<void(*)(std::set<std::shared_ptr<Node>>)>(removeFlatten), py::arg("nodes"), R"mydelimiter( Recipie to remove a flatten operator. - - Parameters - ---------- + :param nodes: The flatten operator to remove. - :type nodes: list of `aidge.node` + :type nodes: list of :py:class:`aidge_core.Node` + )mydelimiter"); + m.def("fuse_mul_add", static_cast<void(*)(std::set<std::shared_ptr<Node>>)>(fuseMulAdd), py::arg("nodes"), R"mydelimiter( + Recipie to Fuse MatMul and Add operators into an :py:class:`aidge_core.FC` operator. + :param nodes: The MatMul and Add nodes to fuse. + :type nodes: list of :py:class:`aidge_core.Node` + )mydelimiter"); + + m.def("fuse_batchnorm", static_cast<void(*)(std::shared_ptr<GraphView>)>(fuseBatchNorm), py::arg("graph_view"), R"mydelimiter( + Recipie to remove a flatten operator. + + :param graph_view: Graph view on which we want to apply the recipie + :type graph_view: :py:class:`aidge_core.GraphView` + )mydelimiter"); + m.def("fuse_batchnorm", static_cast<void(*)(std::set<std::shared_ptr<Node>>)>(fuseBatchNorm), py::arg("nodes"), R"mydelimiter( + Recipie to remove a flatten operator. + + :param nodes: The flatten operator to remove. + :type nodes: list of :py:class:`aidge_core.Node` )mydelimiter"); - } } // namespace Aidge diff --git a/python_binding/utils/pybind_TensorUtils.cpp b/python_binding/utils/pybind_TensorUtils.cpp new file mode 100644 index 0000000000000000000000000000000000000000..78825a5f3b8d45f22f76c57bd780dc7019fbc123 --- /dev/null +++ b/python_binding/utils/pybind_TensorUtils.cpp @@ -0,0 +1,57 @@ +/******************************************************************************** + * 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 <pybind11/stl.h> + +#include <string> + +#include "aidge/utils/TensorUtils.hpp" + +namespace py = pybind11; + +namespace Aidge { + +template<typename T> +void addTensorUtilsFunction(py::module &m){ + m.def("approx_eq", + & approxEq<T>, + py::arg("t1"), + py::arg("t2"), + py::arg("relative"), + py::arg("absolute"), + R"mydelimiter( + Compare two :cpp:class:`Aidge::Tensor` value wise. The comparison function is: + |t1-t2| <= absolute + relative * |t2| + + If a tensor value is different from the other tensor return False + If the tensor does not have the same size, return False + If the datatype is not the same between each tensor return False + If the templated type does not correspond to the datatype of each tensor, raise an assertion error + + :param t1: first tensor to test + :type t1: :py:class:`aidge_core.Tensor` + :param t2: second tensor to test + :type t2: :py:class:`aidge_core.Tensor` + :param relative: relative difference allowed (should be betwen 0 and 1) + :type relative: float + :param absolute: absolute error allowed (shoulmd be positive) + :type absolute: float + )mydelimiter"); +} + +void init_TensorUtils(py::module &m) { + addTensorUtilsFunction<float>(m); + addTensorUtilsFunction<double>(m); + addTensorUtilsFunction<int>(m); + addTensorUtilsFunction<long>(m); +} +} // namespace Aidge diff --git a/src/graph/GraphView.cpp b/src/graph/GraphView.cpp index bbf895285e0e00d1132eb1f46c7e67a455d705d7..03b2a9adb439eb00d0ba59a13fead4f25d617b36 100644 --- a/src/graph/GraphView.cpp +++ b/src/graph/GraphView.cpp @@ -519,17 +519,17 @@ void Aidge::GraphView::link(std::string /*name1_inID*/, printf("Not implemented yet.\n"); } -void Aidge::GraphView::insertParent(NodePtr childNode, - NodePtr newParentNode, - IOIndex_t childInputTensorIdx, - IOIndex_t newParentInputTensorIdx, +void Aidge::GraphView::insertParent(NodePtr childNode, + NodePtr newParentNode, + IOIndex_t childInputTensorIdx, + IOIndex_t newParentInputTensorIdx, IOIndex_t newParentOutputTensorIdx){ NodePtr currentParentNode = childNode->getParent(childInputTensorIdx); const IOIndex_t currentParentOutputTensorIdx = childNode->input(childInputTensorIdx).second; - // Remove child from current parent & current Parent from child + // Remove child from current parent & current Parent from child currentParentNode->removeChild(childNode, currentParentOutputTensorIdx); - // Add child + // Add child currentParentNode->addChild(newParentNode,currentParentOutputTensorIdx, newParentInputTensorIdx); newParentNode->addChild(childNode, newParentOutputTensorIdx, childInputTensorIdx); @@ -542,9 +542,8 @@ bool Aidge::GraphView::replaceWith(std::set<std::shared_ptr<Node>> newNodes) { assert(mNodes.size()>0 && "There must be at least one Node to replace"); bool replacable; - std::shared_ptr<Node> previousInputNode; - std::shared_ptr<Node> newInputNode; - std::shared_ptr<Node> previousOutputNode; + std::shared_ptr<Node> previousInputNode = (*inputNodes().begin()); + std::shared_ptr<Node> previousOutputNode = (*outputNodes().begin()); std::shared_ptr<Node> newOutputNode; auto gNew = std::make_shared<GraphView>(); @@ -552,18 +551,15 @@ bool Aidge::GraphView::replaceWith(std::set<std::shared_ptr<Node>> newNodes) { if (newNodes.empty()) { replacable = (outputNodes().size() == 1) && - (inputNodes().size() == 1) && - ((*outputNodes().begin())->nbOutputs() == 1) && - ((*inputNodes().begin())->nbInputs() == 1); - previousOutputNode = (*outputNodes().begin()); - previousInputNode = (*inputNodes().begin()); + (inputNodes().size() == 1) && + ((*outputNodes().begin())->nbOutputs() == 1) && + ((*inputNodes().begin())->nbDataInputs() == 1); newOutputNode = previousInputNode->input(0).first; } else { - replacable = ((outputNodes().size() == gNew->outputNodes().size()) && - (outputNodes().size() == 1)); - previousOutputNode = (*outputNodes().begin()); newOutputNode = (*gNew->outputNodes().begin()); - replacable = replacable && (previousOutputNode->nbOutputs() == newOutputNode->nbOutputs()); + replacable = (outputNodes().size() == gNew->outputNodes().size()) && + (outputNodes().size() == 1) && + (previousOutputNode->nbOutputs() == newOutputNode->nbOutputs()); } if (replacable) { diff --git a/src/recipies/FuseBatchNorm.cpp b/src/recipies/FuseBatchNorm.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2250bf87c66ff12b702bb2f01c7429c19ed41606 --- /dev/null +++ b/src/recipies/FuseBatchNorm.cpp @@ -0,0 +1,146 @@ +/******************************************************************************** + * 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 <set> +#include <cassert> +#include <memory> +#include <string> +#include "aidge/operator/FC.hpp" +#include "aidge/operator/BatchNorm.hpp" +#include "aidge/operator/Conv.hpp" + +#include "aidge/utils/Recipies.hpp" +#include "aidge/graph/GraphView.hpp" +#include "aidge/graph/Node.hpp" +#include "aidge/operator/Producer.hpp" +#include "aidge/operator/GenericOperator.hpp" +// Graph Regex +#include "aidge/graphmatching/GRegex.hpp" +#include "aidge/graphmatching/NodeRegex.hpp" +using namespace Aidge; + +void Aidge::fuseBatchNorm(std::set<std::shared_ptr<Node>> nodes){ + + assert(nodes.size() == 2 && "Wrong number of nodes to replace\n"); + + // Assert the nodes types are correct to be fused + std::shared_ptr<Node> conv; + std::shared_ptr<Node> batchnorm; + for (const auto& element : nodes) { + assert((element->type() == "Conv" || element->type() == "BatchNorm") && "Wrong type for the nodes to replace"); + if (element->type() == "Conv"){ + conv = element; + } + else if (element->type() == "BatchNorm") { + batchnorm = element; + } + } + // TODO : check if batchnorm is the only child of the Conv or FC + std::shared_ptr<Tensor> scale = batchnorm->input(1).first->getOperator()->getOutput(batchnorm->input(1).second); + std::shared_ptr<Tensor> shift = batchnorm->input(2).first->getOperator()->getOutput(batchnorm->input(2).second); + std::shared_ptr<Tensor> b_mean = batchnorm->input(3).first->getOperator()->getOutput(batchnorm->input(3).second); + std::shared_ptr<Tensor> b_var = batchnorm->input(4).first->getOperator()->getOutput(batchnorm->input(4).second); + + + // TODO : Find a way to remove the template + const float epsilon = std::static_pointer_cast<BatchNorm_Op<2>>(batchnorm->getOperator())->get<float>("Epsilon"); + DimSize_t convOutDims = std::static_pointer_cast<Conv_Op<2>>(conv->getOperator())->get<DimSize_t>("OutChannels"); + + + assert(scale->size() == convOutDims); + assert(shift->size() == convOutDims); + assert(b_mean->size() == convOutDims); + assert(b_var->size() == convOutDims); + assert(epsilon > 0.0); + // TODO : no no_bias attribute ? + float meanVariance = 0.0; + unsigned int count = 0; + + for (std::size_t output = 0; output < convOutDims; ++output) { + // TODO : get suppose datatype is float .. + if (b_var->get<float>(output) > 1.0e-12) { + meanVariance += b_var->get<float>(output); + ++count; + } + else { + printf("Zero-variance: %s [%lu]\n", conv->name().c_str(), output); + } + } + if (count > 0) + meanVariance /= count; + else { + printf("variance < 1e-12 for all outputs! Is the network correctly trained?\n"); + } + + const DimSize_t channelsSize = std::static_pointer_cast<Conv_Op<2>>(conv->getOperator())->get<DimSize_t>("InChannels"); + + // TODO : suppose we have Conv2D ... + const std::array<DimSize_t, 2> kernelDims = std::static_pointer_cast<Conv_Op<2>>(conv->getOperator())->get<std::array<DimSize_t, 2>>("KernelDims"); + + std::shared_ptr<Tensor> weight = conv->input(1).first->getOperator()->getOutput(conv->input(1).second); + std::shared_ptr<Tensor> bias = conv->input(2).first->getOperator()->getOutput(conv->input(2).second); + + for (std::size_t output = 0; output < convOutDims; ++output) { + // Corrected for zero-variance issue: + // "A Quantization-Friendly Separable Convolution for MobileNets" + // https://arxiv.org/pdf/1803.08607.pdf + // to help post-training quantization + const float factor = scale->get<float>(output) + / std::sqrt(epsilon + ((b_var->get<float>(output) > 1.0e-12 || count == 0) + ? b_var->get<float>(output) : meanVariance)); + // Weights adjustments + for (std::size_t channel = 0; channel < channelsSize; ++channel) { + // TODO : Suppose kerneldims = 2 + for(std::size_t k0 = 0; k0 < kernelDims[0]; ++ k0){ + for(std::size_t k1 = 0; k1 < kernelDims[1]; ++ k1){ + std::vector<DimSize_t> currentIdx = {output, channel, k0, k1}; + // TODO : suppose weights are float + float weightValue = weight->get<float>(currentIdx); + weight->set<float>(currentIdx, weightValue*factor); // Update check it update Conv weights + } + } + } + + // TODO : check if noBias==true is set, then set biasValue to 0 + float biasValue = bias->get<float>(output); + + biasValue = shift->get<float>(output) + (biasValue - b_mean->get<float>(output)) * factor; + + bias->set<float>(output, biasValue); + + } + auto g = std::make_shared<GraphView>(); + g->add(std::set<std::shared_ptr<Node>>({ + batchnorm, + batchnorm->input(1).first, + batchnorm->input(2).first, + batchnorm->input(3).first, + batchnorm->input(4).first + })); + g->replaceWith({}); + +} + +void Aidge::fuseBatchNorm(std::shared_ptr<GraphView> graphView){ + std::map<std::string,NodeRegex*> nodesRegex ; + nodesRegex["BatchNorm"] = new NodeRegex("BatchNorm"); + nodesRegex["Conv"] = new NodeRegex("Conv"); + nodesRegex["FC"] = new NodeRegex("FC"); + + + std::vector<std::string> seqRegex; + seqRegex.push_back("Conv -> BatchNorm;"); // TODO: Add (Conv | FC) + GRegex GReg(nodesRegex, seqRegex); + Match matches = GReg.match(graphView); + std::vector<std::set<std::shared_ptr<Node>>> matchNodes = matches.getMatchNodes(); + for (size_t i = 0; i < matches.getNbMatch(); ++i) { + fuseBatchNorm(matchNodes[i]); + } +} diff --git a/src/recipies/FuseMulAdd.cpp b/src/recipies/FuseMulAdd.cpp index 561d25776a28f1aad8f8c943711887ec6661a10c..1de79890f9b597c4baff7427e01d7217f9695a44 100644 --- a/src/recipies/FuseMulAdd.cpp +++ b/src/recipies/FuseMulAdd.cpp @@ -20,21 +20,18 @@ #include "aidge/graph/Node.hpp" #include "aidge/operator/Producer.hpp" #include "aidge/operator/GenericOperator.hpp" - +// Graph Regex +#include "aidge/graphmatching/GRegex.hpp" +#include "aidge/graphmatching/NodeRegex.hpp" using namespace Aidge; -/** - * @brief Merge MatMul and Add Node into FC. - * - * @param nodes Strict set of Node to merge. - */ void Aidge::fuseMulAdd(std::set<std::shared_ptr<Node>> nodes){ // Fuse Mulmat & Add into FC // Inputs : old nodes (pointers on mul & add) - + assert(nodes.size() == 2 && "Wrong number of nodes to replace\n"); // Too bad we lose information on the type after matching, how to keep the information after matching (not only for the type) ? - + // Step 0 : Assert the nodes types are correct to be fused std::shared_ptr<Node> add; std::shared_ptr<Node> matmul; @@ -53,7 +50,7 @@ void Aidge::fuseMulAdd(std::set<std::shared_ptr<Node>> nodes){ auto producer_add_bias = add->input(1); Tensor& bias_tensor = (producer_add_bias.first)->getOperator()->output(0); - // Instanciate FC + // Instanciate FC //std::shared_ptr<Node> fc = FC(dim[0], false, "Fc"); std::shared_ptr<Node> fc = std::make_shared<Node>(std::make_shared<FC_Op>(bias_tensor.dims()[0], false)); @@ -61,10 +58,12 @@ void Aidge::fuseMulAdd(std::set<std::shared_ptr<Node>> nodes){ // link weights & bias if (matmul->getParent(1)==nullptr) { matmul->getParent(0)->addChild(fc, 0, 1); + printf("MatMul out[1] == nullptr !\n"); } else { + printf("MatMul out[1] != nullptr !\n"); if (matmul->getParent(0)!=nullptr) matmul->getParent(0)->addChild(fc, 0, 0); - matmul->getParent(1)->addChild(fc, 0, 1); + matmul->input(1).first->addChild(fc, 0, 1); } (producer_add_bias.first)->addChild(fc,0,2); @@ -74,7 +73,22 @@ void Aidge::fuseMulAdd(std::set<std::shared_ptr<Node>> nodes){ // Case 2 : If not all nodes are in a graph view : only delete the nodes from the graphview // Maybe create a central mechanism to update automatically all graph views rather than each node have graphview presence memory ? auto nodeToReplace = std::make_shared<GraphView>(); - nodeToReplace->add(nodes); + nodeToReplace->add(nodes, false); nodeToReplace->replaceWith({fc}); -} \ No newline at end of file +} + +void Aidge::fuseMulAdd(std::shared_ptr<GraphView> graphView){ + + std::map<std::string,NodeRegex*> nodesRegex ; + nodesRegex["MatMul"] = new NodeRegex("MatMul"); + nodesRegex["Add"] = new NodeRegex("Add"); + std::vector<std::string> seqRegex; + seqRegex.push_back("MatMul -> Add;"); + GRegex GReg(nodesRegex, seqRegex); + Match matches = GReg.match(graphView); + std::vector<std::set<std::shared_ptr<Node>>> matchNodes = matches.getMatchNodes(); + for (size_t i = 0; i < matches.getNbMatch(); ++i) { + fuseMulAdd(matchNodes[i]); + } +} diff --git a/src/recipies/RemoveFlatten.cpp b/src/recipies/RemoveFlatten.cpp index cc3c3324e40636a1edcbc73cdc4a9dcfeec8a026..9096c107ba505f5f18993a761273552408db721b 100644 --- a/src/recipies/RemoveFlatten.cpp +++ b/src/recipies/RemoveFlatten.cpp @@ -15,10 +15,38 @@ #include "aidge/graph/GraphView.hpp" #include "aidge/utils/Recipies.hpp" +// Graph Regex +#include "aidge/graphmatching/GRegex.hpp" +#include "aidge/graphmatching/NodeRegex.hpp" + + namespace Aidge { void removeFlatten(std::set<std::shared_ptr<Node>> nodes) { + assert(nodes.size() == 2 && "Wrong number of nodes to replace\n"); + std::shared_ptr<Node> flatten; + for (const auto& element : nodes) { + assert((element->type() == "FC" || element->type() == "Flatten") && "Wrong type for the nodes to replace"); + if (element->type() == "Flatten"){ + flatten = element; + } + } auto g = std::make_shared<GraphView>(); - g->add(std::set<std::shared_ptr<Node>>({nodes})); + // TODO : avoid using replace_with and use a remove method instead + g->add(std::set<std::shared_ptr<Node>>({flatten})); g->replaceWith({}); } -} \ No newline at end of file + + void removeFlatten(std::shared_ptr<GraphView> graphView){ + std::map<std::string,NodeRegex*> nodesRegex ; + nodesRegex["Flatten"] = new NodeRegex("Flatten"); + nodesRegex["FC"] = new NodeRegex("FC"); + std::vector<std::string> seqRegex; + seqRegex.push_back("Flatten->FC;"); + GRegex GReg(nodesRegex, seqRegex); + Match matches = GReg.match(graphView); + std::vector<std::set<std::shared_ptr<Node>>> matchNodes = matches.getMatchNodes(); + for (size_t i = 0; i < matches.getNbMatch(); ++i) { + removeFlatten(matchNodes[i]); + } + } +} diff --git a/src/scheduler/Scheduler.cpp b/src/scheduler/Scheduler.cpp index dc0768d2b6f7a1dd46fc0a8523b950011f7dcf5d..4dc8eb5c84ddb25546a32a672bdc84685a6f79f0 100644 --- a/src/scheduler/Scheduler.cpp +++ b/src/scheduler/Scheduler.cpp @@ -34,6 +34,11 @@ void drawProgressBar(double progress, int barWidth, const std::string& additiona } void Aidge::SequentialScheduler::generateScheduling(bool verbose) { + // TODO: For loop on the list of node to run + // run sequencially every runnable consumers once + // TODO: handle memory allocation in scheduler + // TODO: optimize memory usage + // setup initial producers list mComputationNumber = 0; std::set<std::shared_ptr<Node>> producers; @@ -74,16 +79,16 @@ void Aidge::SequentialScheduler::generateScheduling(bool verbose) { "\n\t\tR/C:\t", (consumer->type() + "_" + std::to_string(reinterpret_cast<uintptr_t>(consumer.get()))).c_str()); for (IOIndex_t inId = 0; inId < consumer->nbInputs() - 1; ++inId) { - printf("%ld/%ld\n\t\t\t", consumer->getOperator()->getNbConsumedData(inId), + printf("%zu/%zu\n\t\t\t", consumer->getOperator()->getNbConsumedData(inId), consumer->getOperator()->getNbRequiredData(inId)); } - printf("%ld/%ld", consumer->getOperator()->getNbConsumedData(static_cast<IOIndex_t>(consumer->nbInputs()) - 1), + printf("%zu/%zu", consumer->getOperator()->getNbConsumedData(static_cast<IOIndex_t>(consumer->nbInputs()) - 1), consumer->getOperator()->getNbRequiredData(static_cast<IOIndex_t>(consumer->nbInputs()) - 1)); printf("\n\t\tP:\t"); for (IOIndex_t outId = 0; outId < consumer->nbOutputs() - 1; ++outId) { - printf("%ld\n\t\t\t", consumer->getOperator()->getNbProducedData(outId)); + printf("%zu\n\t\t\t", consumer->getOperator()->getNbProducedData(outId)); } - printf("%ld", consumer->getOperator()->getNbProducedData(static_cast<IOIndex_t>(consumer->nbOutputs()) - 1)); + printf("%zu", consumer->getOperator()->getNbProducedData(static_cast<IOIndex_t>(consumer->nbOutputs()) - 1)); printf("\n"); } bool isRunnable = true; @@ -123,13 +128,13 @@ void Aidge::SequentialScheduler::generateScheduling(bool verbose) { printf("%ld/%ld\n\t\t\t", consumer->getOperator()->getNbConsumedData(inId), consumer->getOperator()->getNbRequiredData(inId)); } - printf("%ld/%ld", consumer->getOperator()->getNbConsumedData(static_cast<IOIndex_t>(consumer->nbInputs()) - 1), + printf("%zu/%zu", consumer->getOperator()->getNbConsumedData(static_cast<IOIndex_t>(consumer->nbInputs()) - 1), consumer->getOperator()->getNbRequiredData(static_cast<IOIndex_t>(consumer->nbInputs()) - 1)); printf("\n\t\tP:\t"); for (IOIndex_t outId = 0; outId < consumer->nbOutputs() - 1; ++outId) { - printf("%ld\n\t\t\t", consumer->getOperator()->getNbProducedData(outId)); + printf("%zu\n\t\t\t", consumer->getOperator()->getNbProducedData(outId)); } - printf("%ld", consumer->getOperator()->getNbProducedData(static_cast<IOIndex_t>(consumer->nbOutputs()) - 1)); + printf("%zu", consumer->getOperator()->getNbProducedData(static_cast<IOIndex_t>(consumer->nbOutputs()) - 1)); printf("\n"); } bool isStillConsumer = false; @@ -180,35 +185,20 @@ void Aidge::SequentialScheduler::forward(bool forwardDims, bool verbose) { mScheduling.clear(); this->generateScheduling(); - - // TODO: For loop on the list of node to run - // run sequencially every runnable consumers once - // TODO: handle memory allocation in scheduler - // TODO: optimize memory usage + int cpt = 0; for (const auto& runnable : mStaticSchedule) { - bool computationOverForConsumer = true; - for (IOIndex_t parentIDi = 0; parentIDi < runnable->nbInputs(); ++parentIDi) { - if (runnable->getOperator()->getNbConsumedData(parentIDi) < - runnable->getOperator()->getNbRequiredData(parentIDi)) { - computationOverForConsumer = false; - break; - } - } - if (computationOverForConsumer) { - computationOver.insert(runnable); - } - if (verbose) printf("run: %s\n", (runnable->type() + "_" + std::to_string(reinterpret_cast<uintptr_t>(runnable.get()))).c_str()); else - drawProgressBar(static_cast<float>(computationOver.size()) / static_cast<float>(mComputationNumber), 50, + drawProgressBar(static_cast<float>(cpt) / static_cast<float>(mStaticSchedule.size()), 50, (std::string("running ") + runnable->type() + "_" + std::to_string(reinterpret_cast<uintptr_t>(runnable.get())))); const auto tStart = std::chrono::high_resolution_clock::now(); runnable->forward(); const auto tEnd = std::chrono::high_resolution_clock::now(); mScheduling.push_back(SchedulingElement(runnable, tStart, tEnd)); + cpt++; } if (!verbose) drawProgressBar(1.0, 50, " "); printf("\n"); diff --git a/unit_tests/recipies/Test_FuseMulAdd.cpp b/unit_tests/recipies/Test_FuseMulAdd.cpp new file mode 100644 index 0000000000000000000000000000000000000000..da53642055a3146c71a211ad7816f21c9b92d6cd --- /dev/null +++ b/unit_tests/recipies/Test_FuseMulAdd.cpp @@ -0,0 +1,77 @@ +/******************************************************************************** + * 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 <set> + +// #include "aidge/backend/cpu/operator/AddImpl.hpp" +// #include "aidge/backend/cpu/operator/ConvImpl.hpp" +// #include "aidge/backend/cpu/operator/FCImpl.hpp" +// #include "aidge/backend/cpu/operator/MatMulImpl.hpp" +#include "aidge/data/Tensor.hpp" +#include "aidge/graph/GraphView.hpp" +#include "aidge/operator/Add.hpp" +#include "aidge/operator/FC.hpp" +#include "aidge/operator/MatMul.hpp" +#include "aidge/operator/Producer.hpp" +#include "aidge/utils/Recipies.hpp" + +namespace Aidge { + +TEST_CASE("[cpu/recipies] FuseMulAdd", "[FuseMulAdd][recipies]") { + // generate the original GraphView + auto matmul0 = MatMul(5, "matmul0"); + auto add0 = Add<2>("add0"); + auto matmul1 = MatMul(5, "matmul1"); + auto add1 = Add<2>("add1"); + + auto b0 = Producer({5}, "B0"); + auto w0 = Producer({5, 5}, "W0"); + auto b1 = Producer({5}, "B1"); + auto w1 = Producer({5,5},"W1"); + auto input = Producer({2,5}, "input"); + + input->addChild(matmul0, 0, 0); + w0->addChild(matmul0, 0, 1); + + matmul0->addChild(add0, 0, 0); + b0->addChild(add0, 0, 1); + + add0->addChild(matmul1, 0, 0); + w1->addChild(matmul1, 0, 1); + + matmul1->addChild(add1, 0, 0); + b1->addChild(add1, 0, 1); + + auto g = std::make_shared<GraphView>(); + g->add({matmul0, add0, matmul1, add1, b0, b1}); + + // Check original graph + REQUIRE(g->getNodes() == + std::set<std::shared_ptr<Node>>({w0, matmul0, b0, add0, w1, matmul1, b1, add1})); + REQUIRE(((matmul0->getParent(0) == input) && (matmul0->getParent(1) == w0))); + REQUIRE(((add0->getParent(0) == matmul0) && (add0->getParent(1) == b0))); + REQUIRE(((matmul1->getParent(0) == add0) && (matmul1->getParent(1) == w1))); + REQUIRE(((add1->getParent(0) == matmul1) && (add1->getParent(1) == b1))); + + // Transform GraphView inplace + fuseMulAdd(g); + g->save("bonjour"); + + // Check new GraphView + std::set<std::shared_ptr<Node>> newNodes = g->getNodes(); + REQUIRE(newNodes != std::set<std::shared_ptr<Node>>({w0, matmul0, b0, add0, w1, matmul1, b1, add1})); + REQUIRE(newNodes.size() == 6); + for (const auto& node : newNodes) { + REQUIRE(((node->type() == "Producer") || (node->type() == "FC"))); + } +} +} // namespace Aidge \ No newline at end of file