From 6c970d89314092da9872ffcd8e7404e9d065be6b Mon Sep 17 00:00:00 2001 From: NAUD Maxence <maxence.naud@cea.fr> Date: Fri, 16 Feb 2024 16:11:39 +0000 Subject: [PATCH] [WIP][NF] Start Tensor changes - [Add] ``zeros()`` member function to set implpementation elements to 0 - [unit_tests][NF] Add many more cases in Test_TensorImpl.cpp - [include] update includes in Tensor.hpp --- include/aidge/backend/TensorImpl.hpp | 14 +- include/aidge/backend/cpu/data/TensorImpl.hpp | 9 + include/aidge/data/Tensor.hpp | 22 +- include/aidge/utils/ArrayHelpers.hpp | 21 ++ src/data/Tensor.cpp | 15 +- unit_tests/data/Test_TensorImpl.cpp | 228 +++++++++++++----- 6 files changed, 231 insertions(+), 78 deletions(-) diff --git a/include/aidge/backend/TensorImpl.hpp b/include/aidge/backend/TensorImpl.hpp index 8539c8e36..087966876 100644 --- a/include/aidge/backend/TensorImpl.hpp +++ b/include/aidge/backend/TensorImpl.hpp @@ -67,7 +67,7 @@ private: class TensorImpl { public: TensorImpl() = delete; - TensorImpl(const char *backend, DeviceIdx_t device, std::vector<DimSize_t> dims) : mBackend(backend), mDevice(device) + TensorImpl(const char *backend, DeviceIdx_t device, std::vector<DimSize_t> dims) : mBackend(backend), mDevice(device) { resize(dims); }; @@ -148,7 +148,7 @@ public: }; /** - * Set the size, in number of elements, that must be stored. + * @brief Set the size, in number of elements, that must be stored. */ virtual void resize(std::vector<DimSize_t> dims) { size_t product = 1; @@ -159,14 +159,20 @@ public: } /** - * Return the number of elements stored. + * @brief Return the number of elements stored. */ inline std::size_t size() const noexcept { return mNbElts; } /** - * Return the size (in bytes) of one element (scalar). + * @brief Return the size (in bytes) of one element (scalar). */ virtual std::size_t scalarSize() const noexcept = 0; + + /** + * @brief Set every element of the implementation to zero. + */ + virtual void zeros() = 0; + constexpr const char *backend() const { return mBackend; } virtual ~TensorImpl() = default; virtual bool operator==(const TensorImpl &othImpl) const = 0; diff --git a/include/aidge/backend/cpu/data/TensorImpl.hpp b/include/aidge/backend/cpu/data/TensorImpl.hpp index a1fbfa336..6f4693fa2 100644 --- a/include/aidge/backend/cpu/data/TensorImpl.hpp +++ b/include/aidge/backend/cpu/data/TensorImpl.hpp @@ -53,6 +53,15 @@ public: inline std::size_t scalarSize() const noexcept override final { return sizeof(T); } + void zeros() override final { + if (mData.empty()) { + lazyInit(); + } + for (std::size_t i = 0; i < mData.size(); ++i) { + *(mData.data() + i) = T(0); + } + } + void copy(const void *src, NbElts_t length, NbElts_t offset = 0) override final { const T* srcT = static_cast<const T *>(src); T* dstT = static_cast<T *>(rawPtr(offset)); diff --git a/include/aidge/data/Tensor.hpp b/include/aidge/data/Tensor.hpp index 978a85046..25df67980 100644 --- a/include/aidge/data/Tensor.hpp +++ b/include/aidge/data/Tensor.hpp @@ -92,7 +92,7 @@ class Tensor : public Data, newTensor.makeContiguous(); } else { - std::shared_ptr<TensorImpl> newImpl = Registrar<Tensor>::create({mImpl->backend(), mDataType})(mImpl->device().second, mDims); + std::shared_ptr<TensorImpl> newImpl = Registrar<Tensor>::create({mImpl->backend(), mDataType})(mImpl->device().second, mDims); newImpl->copy(mImpl->rawPtr(mImplOffset), mSize); newTensor.setImpl(newImpl); } @@ -454,6 +454,15 @@ class Tensor : public Data, */ bool empty() const { return mDims.empty(); } + /** + * @brief Set each element of the tensor to zero. + */ + void zeros() const { + if (mImpl) { + mImpl->zeros(); + } + } + template <typename expectedType> const expectedType& get(std::size_t idx) const { AIDGE_ASSERT(NativeType<expectedType>::type == mDataType, "wrong data type"); @@ -589,7 +598,7 @@ class Tensor : public Data, * @param flatIdx 1D contiguous index of the value considering a flatten, contiguous, tensor. * @return std::vector<DimSize_t> */ - std::vector<std::size_t> getCoord(std::size_t flatIdx) const { + std::vector<std::size_t> getCoord(const 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){ @@ -635,10 +644,11 @@ class Tensor : public Data, } /** - * Returns a sub-tensor with one or more dimension less. - * For instance, t.extract({1}) on a CHW tensor will return the HW tensor + * @brief Returns a sub-tensor with equal or lower number of dimensions. + * + * For instance, ``t.extract({1})`` on a CHW tensor will return the HW tensor * of channel #1. - * Likewise, t.extract({0, 1}) on a NCHW tensor will return the HW tensor + * Likewise, ``t.extract({0, 1})`` on a NCHW tensor will return the HW tensor * of batch #0 and channel #1. * No memory copy is performed, the returned tensor does not own the memory. * If the number of coordinates matches the number of dimensions, an empty @@ -652,7 +662,7 @@ class Tensor : public Data, Tensor extract(const std::vector<std::size_t>& coordIdx) const; /** - * Returns a sub-tensor at some coordinate and with some dimension. + * @brief Returns a sub-tensor at some coordinate and with some dimension. * * @param coordIdx First coordinates of the sub-tensor to extract * @param dims Dimensions of the sub-tensor to extract diff --git a/include/aidge/utils/ArrayHelpers.hpp b/include/aidge/utils/ArrayHelpers.hpp index b0db3ca11..bd9f2e650 100644 --- a/include/aidge/utils/ArrayHelpers.hpp +++ b/include/aidge/utils/ArrayHelpers.hpp @@ -103,11 +103,32 @@ constexpr std::array<T, N + 1> append(T t, std::array<T, N> a) { // Generic helper for initializing a Tensor template <typename T, std::size_t SIZE_0> struct Array1D { + Array1D(std::initializer_list<T> list) { + auto it = list.begin(); + for (std::size_t i = 0; i < SIZE_0; ++i, ++it) { + data[i] = *it; + } + } + Array1D(const T (&dataArray)[SIZE_0]) { + std::copy_n(&dataArray[0], SIZE_0, &data[0]); + } T data[SIZE_0]; }; template <typename T, std::size_t SIZE_0, std::size_t SIZE_1> struct Array2D { + Array2D(std::initializer_list<std::initializer_list<T>> list) { + auto it1 = list.begin(); + for (std::size_t i = 0; i < SIZE_0; ++i, ++it1) { + auto it2 = it1->begin(); + for (std::size_t j = 0; j < SIZE_1; ++j, ++it2) { + data[i][j] = *it2; + } + } + } + Array2D(const T (&dataArray)[SIZE_0][SIZE_1]) { + std::copy_n(&dataArray[0][0], SIZE_0 * SIZE_1, &data[0][0]); + } T data[SIZE_0][SIZE_1]; }; diff --git a/src/data/Tensor.cpp b/src/data/Tensor.cpp index 108541536..17ef02334 100644 --- a/src/data/Tensor.cpp +++ b/src/data/Tensor.cpp @@ -10,8 +10,13 @@ ********************************************************************************/ #include "aidge/data/Tensor.hpp" -#include "aidge/utils/Types.h" + +#include <cstddef> +#include <vector> + #include "aidge/utils/ErrorHandling.hpp" +#include "aidge/utils/Registrar.hpp" +#include "aidge/utils/Types.h" Aidge::Tensor Aidge::Tensor::extract(const std::vector<std::size_t>& coordIdx) const { AIDGE_ASSERT(isContiguous(), "Tensor must be contiguous"); @@ -46,13 +51,13 @@ void Aidge::Tensor::makeContiguous() { // Create a new storage that will be contiguous std::shared_ptr<TensorImpl> newImpl = Registrar<Tensor>::create({mImpl->backend(), mDataType})(mImpl->device().second, mDims); // Copy elements from old to new storage - size_t idx = 0; + std::size_t idx = 0; while (idx < mSize) { - const size_t storageIdx = getStorageIdx(getCoord(idx)); + const std::size_t storageIdx = getStorageIdx(getCoord(idx)); // Determine the size of the contiguous chunk - size_t copySize = 1; - while (idx + copySize < mSize && + std::size_t copySize = 1; + while (idx + copySize < mSize && getStorageIdx(getCoord(idx + copySize)) == storageIdx + copySize) { ++copySize; diff --git a/unit_tests/data/Test_TensorImpl.cpp b/unit_tests/data/Test_TensorImpl.cpp index cfcfb45e3..b35af4321 100644 --- a/unit_tests/data/Test_TensorImpl.cpp +++ b/unit_tests/data/Test_TensorImpl.cpp @@ -10,6 +10,10 @@ ********************************************************************************/ #include <array> +#include <cstddef> +#include <cstdint> //std::uint16_t +#include <random> +#include <vector> #include <catch2/catch_test_macros.hpp> @@ -19,82 +23,180 @@ using namespace Aidge; -TEST_CASE("Tensor creation") { - SECTION("from const array") { - Tensor x = Array3D<int, 2, 2, 2>{{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; - - Tensor xCopy = Array3D<int, 2, 2, 2>{{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; +TEST_CASE("[backend/cpu/data] Tensor", "[Tensor]") { + Tensor x; + + SECTION("TensorUtils, constructor from const arrays") { + // construction from different types and sizes + REQUIRE_NOTHROW(x = Array1D<int, 2>{{1, 2}}); + x.print(); + REQUIRE_NOTHROW(x = Array2D<int, 2, 2>{{{1, 2}, {3, 4}}}); + x.print(); + REQUIRE_NOTHROW(x = Array3D<std::uint8_t, 2, 2, 2>{{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}); + REQUIRE_NOTHROW(x = Array3D<int, 2, 2, 2>{{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}); + REQUIRE_NOTHROW(x = Array3D<float, 2, 2, 2>{{{{1.0f, 2.0f}, {3.0f, 4.0f}}, {{5.0f, 6.0f}, {7.0f, 8.0f}}}}); + REQUIRE_NOTHROW(x = Array3D<double, 2, 2, 2>{{{{1., 2.}, {3., 4.}}, {{5., 6.}, {7., 8.}}}}); + + REQUIRE_NOTHROW(x = Array4D<int, 2, 2, 2, 2>{{{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}, + {{{9,10}, {11,12}}, {{13,14},{15,16}}}}}); + } + x = Array3D<int, 2, 2, 2>{{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; - Tensor xFloat = - Array3D<float, 2, 2, 2>{{{{1., 2.}, {3., 4.}}, {{5., 6.}, {7., 8.}}}}; SECTION("Tensor features") { - REQUIRE(x.nbDims() == 3); - REQUIRE(x.dims()[0] == 2); - REQUIRE(x.dims()[1] == 2); - REQUIRE(x.dims()[2] == 2); - REQUIRE(x.size() == 8); + x = Array3D<int, 2, 2, 2>{{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + REQUIRE(x.nbDims() == 3); + REQUIRE(x.dims()[0] == 2); + REQUIRE(x.dims()[1] == 2); + REQUIRE(x.dims()[2] == 2); + REQUIRE(x.size() == 8); } SECTION("Access to array") { - REQUIRE(static_cast<int *>(x.getImpl()->rawPtr())[0] == 1); - REQUIRE(static_cast<int *>(x.getImpl()->rawPtr())[7] == 8); + x = Array3D<int, 2, 2, 2>{{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + REQUIRE(static_cast<int *>(x.getImpl()->rawPtr())[0] == 1); + REQUIRE(static_cast<int *>(x.getImpl()->rawPtr())[7] == 8); } SECTION("get function") { - REQUIRE(x.get<int>({0, 0, 0}) == 1); - REQUIRE(x.get<int>({0, 0, 1}) == 2); - REQUIRE(x.get<int>({0, 1, 1}) == 4); - REQUIRE(x.get<int>({1, 1, 0}) == 7); - x.set<int>({1, 1, 1}, 36); - REQUIRE(x.get<int>({1, 1, 1}) == 36); + x = Array3D<int, 2, 2, 2>{{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + REQUIRE(x.get<int>({0, 0, 0}) == 1); + REQUIRE(x.get<int>({0, 0, 1}) == 2); + REQUIRE(x.get<int>({0, 1, 1}) == 4); + REQUIRE(x.get<int>({1, 1, 0}) == 7); + x.set<int>({1, 1, 1}, 36); + REQUIRE(x.get<int>(7) == 36); + x.set<int>(7, 40); + REQUIRE(x.get<int>({1, 1, 1}) == 40); } - SECTION("Pretty printing for debug") { REQUIRE_NOTHROW(x.print()); } + SECTION("Pretty printing for debug") { + x = Array3D<int, 2, 2, 2>{{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + REQUIRE_NOTHROW(x.print()); + } SECTION("Tensor (in)equality") { - REQUIRE(x == xCopy); - REQUIRE_FALSE(x == xFloat); + Tensor xCopy = Array3D<int, 2, 2, 2>{{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + Tensor xFloat = Array3D<float, 2, 2, 2>{{{{1.0f, 2.0f}, {3.0f, 4.0f}}, {{5.0f, 6.0f}, {7.0f, 8.0f}}}}; + + REQUIRE(x == xCopy); + REQUIRE_FALSE(x == xFloat); } - } -} -TEST_CASE("Tensor methods") { - Tensor x = Array3D<int, 2, 2, 2>{{ - {{1, 2}, - {3, 4}}, - {{5, 6}, - {7, 8}} - }}; - - Tensor xCopy = Array3D<int, 2, 2, 2>{{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; - - Tensor xFloat = - Array3D<float, 2, 2, 2>{{{{1., 2.}, {3., 4.}}, {{5., 6.}, {7., 8.}}}}; - - SECTION("Tensor sharing") { - Tensor xCopyCtor(x); - REQUIRE(xCopyCtor.getImpl() == x.getImpl()); - - Tensor xEqOp = x; - REQUIRE(xEqOp.getImpl() == x.getImpl()); - - Tensor xCloned = x.clone(); - REQUIRE(xCloned.getImpl() != x.getImpl()); - REQUIRE(xCloned == x); - } - - SECTION("Tensor extract") { - Tensor y = x.extract({0, 1}); - REQUIRE(y.getImpl() == x.getImpl()); - REQUIRE(approxEq<int>(y, Array1D<int, 2>{{3, 4}})); - REQUIRE(y.isContiguous()); - - Tensor y2 = x.extract({0, 1, 1}, {2, 1, 1}); - REQUIRE(y2.getImpl() == x.getImpl()); - REQUIRE(!y2.isContiguous()); - Tensor y3 = y2.clone(); - REQUIRE(y3.isContiguous()); - REQUIRE(approxEq<int>(y3, Array3D<int, 2, 1, 1>{{{{4}}, {{8}}}})); - } + constexpr std::uint16_t NBTRIALS = 10; + + // Create a random number generator + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<std::size_t> dist(1, 10); + std::uniform_int_distribution<std::size_t> nbDims(1, 5); + + x.setDataType(DataType::Int32); + x.setBackend("cpu"); + + SECTION("Tensor sharing") { + for (std::uint16_t trial = 0; trial < NBTRIALS; ++trial) { + // create Tensor + const std::size_t nb_dims = nbDims(gen) + 1; + std::vector<std::size_t> dims(nb_dims); + for (std::size_t i = 0; i < nb_dims; ++i) { + dims[i] = dist(gen); + } + x.resize(dims); + + // copy constructor + Tensor xCopyCtor(x); + REQUIRE(xCopyCtor.getImpl() == x.getImpl()); + + // copy assignment operator + Tensor xCopyAssignmentOp = x; + REQUIRE(xCopyAssignmentOp.getImpl() == x.getImpl()); + + Tensor xCloned = x.clone(); + REQUIRE(xCloned.getImpl() != x.getImpl()); + REQUIRE(xCloned == x); + } + } + SECTION("zeros()") { + for (std::uint16_t trial = 0; trial < NBTRIALS; ++trial) { + // create Tensor + constexpr std::size_t nb_dims = 3; + const std::size_t dim0 = nbDims(gen); + const std::size_t dim1 = nbDims(gen); + const std::size_t dim2 = nbDims(gen); + const std::vector<std::size_t> dims = {dim0, dim1, dim2}; + int array0[dim0][dim1][dim2]; + for (std::size_t i = 0; i < dim0; ++i) { + for (std::size_t j = 0; j < dim1; ++j) { + for (std::size_t k = 0; k < dim2; ++k) { + array0[i][j][k] = dist(gen); + } + } + } + x.resize(dims); + + x.zeros(); + for (std::size_t i = 0; i < dim0; ++i) { + for (std::size_t j = 0; j < dim1; ++j) { + const std::size_t idx = (i * dim1 + j) * dim2; + for (std::size_t k = 0; k < dim2; ++k) { + int val = *static_cast<int*>(x.getImpl()->hostPtr(idx + k)); + if (val != 0) { + throw std::runtime_error("Value should be 0"); + } + // REQUIRE(*static_cast<int*>(x.getImpl()->hostPtr(idx + k)) == 0); + } + } + } + } + } + + SECTION("Tensor extract") { + x = Array3D<int, 2, 2, 2>{{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + Tensor y; + Tensor y0; + Tensor y1; + Tensor y2; + Tensor y3; + + for (std::uint16_t trial = 0; trial < NBTRIALS; ++trial) { + // create Tensor + const std::size_t nb_dims = 3; + const std::size_t dim0 = nbDims(gen) + 1; // dim0 >= 2 + const std::size_t dim1 = nbDims(gen) + 1; + const std::size_t dim2 = nbDims(gen) + 1; + std::vector<std::size_t> dims = {dim0, dim1, dim2}; + int array0[dim0][dim1][dim2]; + for (std::size_t i = 0; i < dim0; ++i) { + for (std::size_t j = 0; j < dim1; ++j) { + for (std::size_t k = 0; k < dim2; ++k) { + array0[i][j][k] = dist(gen); + } + } + } + x.resize(dims); + + REQUIRE(x.isContiguous()); + // extract Tensor slice from one set of coordinates + REQUIRE_NOTHROW(y0 = x.extract({})); + REQUIRE_NOTHROW(y1 = x.extract({nbDims(gen)})); + REQUIRE_NOTHROW(y2 = x.extract({nbDims(gen), nbDims(gen)})); + REQUIRE_NOTHROW(y3 = x.extract({nbDims(gen), nbDims(gen), nbDims(gen)})); + REQUIRE_THROWS(y = x.extract({0, dim0 + 1, 0})); + + REQUIRE_NOTHROW(y = x.extract({0, 1})); + REQUIRE(y.getImpl() == x.getImpl()); // shared implem + REQUIRE(!y.isContiguous()); + + Tensor yClone = y.clone(); // when copying data, they are contiguous in memory + REQUIRE(yClone.isContiguous()); + // int yTruth[2][1][1] = + REQUIRE(approxEq<int>(yClone, Array3D<int, 2, 1, 1>{{{{4}}, {{8}}}})); + + y = x.extract({0, 1}); + REQUIRE(y.getImpl() == x.getImpl()); + REQUIRE(approxEq<int>(y, Array1D<int, 2>{{3, 4}})); + REQUIRE(y.isContiguous()); + } + } } -- GitLab