From d41a8def49e45128841c099db938e1362200691a Mon Sep 17 00:00:00 2001 From: NAUD Maxence <maxence.naud@cea.fr> Date: Fri, 22 Mar 2024 14:52:49 +0000 Subject: [PATCH] [Add][Unit-Test] operator+,-,*,/ of Tensor --- unit_tests/data/Test_TensorImpl.cpp | 192 ++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 unit_tests/data/Test_TensorImpl.cpp diff --git a/unit_tests/data/Test_TensorImpl.cpp b/unit_tests/data/Test_TensorImpl.cpp new file mode 100644 index 00000000..31fbed4c --- /dev/null +++ b/unit_tests/data/Test_TensorImpl.cpp @@ -0,0 +1,192 @@ +/******************************************************************************** + * 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 <cstddef> // std::size_t +#include <cstdint> // std::uint16_t +#include <chrono> +#include <iostream> +#include <memory> +#include <numeric> // std::accumulate +#include <random> // std::random_device, std::mt19937, std::uniform_real_distribution + +#include "aidge/data/Tensor.hpp" +#include "aidge/backend/cpu/data/TensorImpl.hpp" +#include "aidge/operator/Add.hpp" +#include "aidge/backend/cpu/operator/AddImpl.hpp" + +namespace Aidge { + +TEST_CASE("Test addition of Tensors","[TensorImpl][Add]") { + constexpr std::uint16_t NBTRIALS = 10; + // Create a random number generator + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution<float> valueDist(0.1f, 1.1f); // Random float distribution between 0 and 1 + std::uniform_int_distribution<std::size_t> dimSizeDist(std::size_t(2), std::size_t(10)); + std::uniform_int_distribution<int> boolDist(0,1); + + // Create MatMul Operator + std::shared_ptr<Node> mySub = Add(2); + auto op = std::static_pointer_cast<OperatorTensor>(mySub-> getOperator()); + op->setDataType(DataType::Float32); + op->setBackend("cpu"); + + // Create 2 input Tensors + std::shared_ptr<Tensor> T0 = std::make_shared<Tensor>(); + op->associateInput(0,T0); + T0->setDataType(DataType::Float32); + T0->setBackend("cpu"); + std::shared_ptr<Tensor> T1 = std::make_shared<Tensor>(); + op -> associateInput(1,T1); + T1->setDataType(DataType::Float32); + T1->setBackend("cpu"); + + // Create results Tensor + Tensor Tres{}; + Tres.setDataType(DataType::Float32); + Tres.setBackend("cpu"); + + // To measure execution time of 'MatMul_Op::forward()' member function call + std::chrono::time_point<std::chrono::system_clock> start; + std::chrono::time_point<std::chrono::system_clock> end; + std::chrono::duration<double, std::micro> duration{}; + + std::size_t number_of_operation = 0; + + for (std::uint16_t trial = 0; trial < NBTRIALS; ++trial) { + // generate 2 random Tensors + // handle dimensions, replace some dimensions with '1' to get broadcasting + constexpr std::size_t nbDims = 4; + std::vector<std::size_t> dims; + for (std::size_t i = 0; i < nbDims; ++i) { + dims.push_back(dimSizeDist(gen)); + } + std::vector<std::size_t> dims0 = dims; + std::vector<std::size_t> dims1 = dims; + std::vector<std::size_t> dimsOut = dims; + for (std::size_t i = 0; i < nbDims; ++i) { + if (boolDist(gen)) { + dims0[i] = 1; + } + if (boolDist(gen)) { + dims1[i] = 1; + } + dimsOut[i] = (dims0[i] == 1) ? dims1[i] : dims0[i]; + } + + // create arrays and fill them with random values + float* array0 = new float[dims0[0]*dims0[1]*dims0[2]*dims0[3]]; + float* array1 = new float[dims1[0]*dims1[1]*dims1[2]*dims1[3]]; + float* result = new float[dimsOut[0]*dimsOut[1]*dimsOut[2]*dimsOut[3]]; + + for (std::size_t i = 0; i < dims0[0]*dims0[1]*dims0[2]*dims0[3]; ++i) { + array0[i] = valueDist(gen); + } + for (std::size_t i = 0; i < dims1[0]*dims1[1]*dims1[2]*dims1[3]; ++i) { + array1[i] = valueDist(gen); + } + + // compute true result + const std::size_t strides0[nbDims] = {dims0[1]*dims0[2]*dims0[3], dims0[2]*dims0[3], dims0[3], 1}; + const std::size_t strides1[nbDims] = {dims1[1]*dims1[2]*dims1[3], dims1[2]*dims1[3], dims1[3], 1}; + for (std::size_t a = 0; a < dimsOut[0]; ++a) { + for (std::size_t b = 0; b < dimsOut[1]; ++b) { + const std::size_t idx0_0 = strides0[0] * ((dims0[0] > 1) ? a : 0) + + strides0[1] * ((dims0[1] > 1) ? b : 0); + const std::size_t idx1_0 = strides1[0] * ((dims1[0] > 1) ? a : 0) + + strides1[1] * ((dims1[1] > 1) ? b : 0); + for (std::size_t c = 0; c < dimsOut[2]; ++c) { + const std::size_t idx_out = dimsOut[3] * (c + dimsOut[2] * (b + dimsOut[1] * a)); + for (std::size_t d = 0; d < dimsOut[3]; ++d) { + std::size_t idx0 = idx0_0 + + strides0[2] * ((dims0[2] > 1) ? c : 0) + + ((dims0[3] > 1) ? d : 0); + std::size_t idx1 = idx1_0 + + strides1[2] * ((dims1[2] > 1) ? c : 0) + + ((dims1[3] > 1) ? d : 0); + result[idx_out + d] = array0[idx0] + array1[idx1]; + // std::cout << "(" << idx0 << ", " << idx1 << ") -> " << array0[idx0] << " - " << array1[idx1] << " -> " << idx_out + d << std::endl; + } + } + } + } + + // conversion to Aidge::Tensors + // input0 + T0->resize(dims0); + T0->getImpl() -> setRawPtr(array0, dims0[0]*dims0[1]*dims0[2]*dims0[3]); + + // input1 + T1->resize(dims1); + T1->getImpl() -> setRawPtr(array1, dims1[0]*dims1[1]*dims1[2]*dims1[3]); + + // results + Tres.resize(dimsOut); + Tres.getImpl() -> setRawPtr(result, dimsOut[0]*dimsOut[1]*dimsOut[2]*dimsOut[3]); + + Tensor T2 = *T0 + *T1; + REQUIRE(T2 == Tres); + + // no implementation + Tensor T3(T1->dims()); + REQUIRE_THROWS(*T0 + T3); + + // // wrong backend + // static Registrar<Add_Op> registrarAddImpl_custom("custom", [](const Add_Op& op) { return std::make_unique<AddImpl_cpu>(op); } ); + // static Registrar<Tensor> registrarTensorImpl_custom_Int32({"custom", DataType::Int32}, + // [] (DeviceIdx_t device, std::vector<DimSize_t> dims) { + // return std::make_shared<TensorImpl_cpu<int>>(device, dims); + // } + // ); + // T1.setBackend("custom"); + // REQUIRE_THROWS(T0 + T1); + + // wrong datatype + Tensor T4(T1->dims()); + T4.setDataType(DataType::Float64); + REQUIRE_THROWS(*T0 + T4); + } +} + +TEST_CASE("Test substraction of Tensors","[TensorImpl][Sub]") { + Tensor T0 = Array3D<int, 2, 2, 2>{{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + Tensor T1 = Array3D<int, 2, 2, 2>{{{{7, 1}, {3, 7}}, {{54, 0}, {7, 12}}}}; + Tensor T2 = T0 - T1; + T2.print(); + REQUIRE(T2 == Tensor(Array3D<int, 2, 2, 2>{{{{-6,1},{0,-3}},{{-49,6},{0,-4}}}})); + + Tensor T3(T1.dims()); + REQUIRE_THROWS(T0 - T3); +} + +TEST_CASE("Test multiplication of Tensors","[TensorImpl][Mul]") { + Tensor T0 = Array3D<int, 2, 2, 2>{{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + Tensor T1 = Array3D<int, 2, 2, 2>{{{{7, 2}, {3, 7}}, {{5, 6}, {7, 8}}}}; + Tensor T2 = T0 * T1; + T2.print(); + REQUIRE(T2 == Tensor(Array3D<int, 2, 2, 2>{{{{7,4},{9,28}},{{25,36},{49,64}}}})); + + Tensor T3(T1.dims()); + REQUIRE_THROWS(T0 * T3); +} + +TEST_CASE("Test division of Tensors","[TensorImpl][Div]") { + Tensor T0 = Array3D<int, 2, 2, 2>{{{{7,4},{9,28}},{{25,36},{49,64}}}}; + Tensor T1 = Array3D<int, 2, 2, 2>{{{{7, 2}, {3, 7}}, {{5, 6}, {7, 8}}}}; + Tensor T2 = T0 / T1; + T2.print(); + REQUIRE(T2 == Tensor(Array3D<int, 2, 2, 2>{{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}})); + + Tensor T3(T1.dims()); + REQUIRE_THROWS(T0 / T3); +} +} // namespace Aidge -- GitLab