From d8b08118ff86e63ae15de9f3511e1b7cb6bc906a Mon Sep 17 00:00:00 2001 From: MikeMuradov Date: Sat, 26 Jul 2025 18:35:51 +0300 Subject: [PATCH 1/6] first_try_reduceSumLayer --- include/layers/ReduceSumLayer.hpp | 31 ++++ include/layers/Shape.hpp | 9 ++ src/layers/ReduceSumLayer.cpp | 182 ++++++++++++++++++++++ test/single_layer/test_reducesumlayer.cpp | 112 +++++++++++++ 4 files changed, 334 insertions(+) create mode 100644 include/layers/ReduceSumLayer.hpp create mode 100644 src/layers/ReduceSumLayer.cpp create mode 100644 test/single_layer/test_reducesumlayer.cpp diff --git a/include/layers/ReduceSumLayer.hpp b/include/layers/ReduceSumLayer.hpp new file mode 100644 index 00000000..0cd41740 --- /dev/null +++ b/include/layers/ReduceSumLayer.hpp @@ -0,0 +1,31 @@ +#pragma once +#include + +#include "layers/Layer.hpp" +#include "layers/Tensor.hpp" + +namespace itlab_2023 { + +class ReduceSumLayer : public Layer { + public: + explicit ReduceSumLayer(int64_t keepdims = 0); + + void run(const Tensor& input, Tensor& output) override; + void run(const Tensor& input, const Tensor& axes, Tensor& output); + + static std::string get_name() { return "ReduceSumLayer"; } + + private: + int64_t keepdims_; + + void normalize_axes(const Shape& input_shape, + std::vector& axes) const; + Shape calculate_output_shape(const Shape& input_shape, + const std::vector& axes) const; + + template + void compute(const Tensor& input, const Shape& output_shape, + const std::vector& axes, Tensor& output) const; +}; + +} // namespace itlab_2023 \ No newline at end of file diff --git a/include/layers/Shape.hpp b/include/layers/Shape.hpp index 693a1512..d69063b6 100644 --- a/include/layers/Shape.hpp +++ b/include/layers/Shape.hpp @@ -39,6 +39,15 @@ class Shape { } size_t dims() const noexcept { return dims_.size(); } size_t get_index(const std::vector& coords) const; + bool operator==(const Shape& other) const { + if (dims_.size() != other.dims_.size()) return false; + for (size_t i = 0; i < dims_.size(); ++i) { + if (dims_[i] != other.dims_[i]) return false; + } + return true; + } + + bool operator!=(const Shape& other) const { return !(*this == other); } friend std::ostream& operator<<(std::ostream& os, const Shape& shape); private: diff --git a/src/layers/ReduceSumLayer.cpp b/src/layers/ReduceSumLayer.cpp new file mode 100644 index 00000000..d6d83210 --- /dev/null +++ b/src/layers/ReduceSumLayer.cpp @@ -0,0 +1,182 @@ +#include "layers/ReduceSumLayer.hpp" + +#include +#include + +namespace itlab_2023 { + +ReduceSumLayer::ReduceSumLayer(int64_t keepdims) : keepdims_(keepdims) {} + +void ReduceSumLayer::normalize_axes(const Shape& input_shape, + std::vector& axes) const { + const int64_t rank = static_cast(input_shape.dims()); + + if (rank == 0) { + if (!axes.empty()) { + throw std::runtime_error("ReduceSum: Axis specified for scalar input"); + } + return; + } + + if (axes.empty()) { + axes.resize(rank); + std::iota(axes.begin(), axes.end(), 0); + return; + } + + for (auto& axis : axes) { + if (axis < -rank || axis >= rank) { + throw std::runtime_error("ReduceSum: Axis out of range"); + } + if (axis < 0) axis += rank; + } + + std::sort(axes.begin(), axes.end()); + axes.erase(std::unique(axes.begin(), axes.end()), axes.end()); +} + +Shape ReduceSumLayer::calculate_output_shape( + const Shape& input_shape, const std::vector& axes) const { + if (input_shape.dims() == 0) { + return Shape({}); + } + + std::vector new_dims; + + if (keepdims_) { + for (size_t i = 0; i < input_shape.dims(); ++i) { + new_dims.push_back(input_shape[i]); + } + + for (int64_t axis : axes) { + if (axis >= 0 && axis < static_cast(new_dims.size())) { + new_dims[axis] = 1; + } + } + } else { + for (size_t i = 0; i < input_shape.dims(); ++i) { + if (std::find(axes.begin(), axes.end(), static_cast(i)) == + axes.end()) { + new_dims.push_back(input_shape[i]); + } + } + if (new_dims.empty()) { + new_dims.push_back(1); + } + } + + return Shape(new_dims); +} + +template +void ReduceSumLayer::compute(const Tensor& input, const Shape& output_shape, + const std::vector& axes, + Tensor& output) const { + const auto& input_data = *input.as(); + std::vector output_data(output_shape.count(), 0); + + const auto& input_shape = input.get_shape(); + const size_t input_rank = input_shape.dims(); + + std::vector is_reduced(input_rank, false); + for (int64_t axis : axes) { + is_reduced[axis] = true; + } + + std::vector input_strides(input_rank, 1); + for (size_t i = input_rank - 1; i > 0; --i) { + input_strides[i - 1] = input_strides[i] * input_shape[i]; + } + + for (size_t out_idx = 0; out_idx < output_data.size(); ++out_idx) { + size_t remaining = out_idx; + size_t input_idx = 0; + + for (size_t out_dim = 0; out_dim < output_shape.dims(); ++out_dim) { + size_t out_coord = remaining % output_shape[out_dim]; + remaining /= output_shape[out_dim]; + + size_t input_dim = 0; + for (size_t i = 0; i < input_rank; ++i) { + if (!is_reduced[i]) { + if (input_dim == out_dim) { + input_idx += out_coord * input_strides[i]; + break; + } + input_dim++; + } + } + } + + T sum = 0; + size_t reduced_count = 1; + for (int64_t axis : axes) { + reduced_count *= input_shape[axis]; + } + + for (size_t offset = 0; offset < reduced_count; ++offset) { + size_t current_idx = input_idx; + size_t temp = offset; + + for (int64_t axis : axes) { + size_t axis_size = input_shape[axis]; + size_t coord = temp % axis_size; + temp /= axis_size; + current_idx += coord * input_strides[axis]; + } + + sum += input_data[current_idx]; + } + + output_data[out_idx] = sum; + } + + output = make_tensor(output_data, output_shape); +} + +template void ReduceSumLayer::compute(const Tensor&, const Shape&, + const std::vector&, + Tensor&) const; +template void ReduceSumLayer::compute(const Tensor&, const Shape&, + const std::vector&, + Tensor&) const; + +void ReduceSumLayer::run(const Tensor& input, Tensor& output) { + run(input, Tensor(), output); +} + +void ReduceSumLayer::run(const Tensor& input, const Tensor& axes, + Tensor& output) { + if (input.get_shape().count() == 0) { + output = make_tensor({0.0f}, {}); + return; + } + + std::vector axes_indices; + if (axes.get_shape().dims() > 0) { + if (axes.get_type() == Type::kInt) { + auto axes_data = axes.as(); + axes_indices.assign(axes_data->begin(), axes_data->end()); + } else { + throw std::runtime_error("ReduceSum: Axes tensor must be of type int"); + } + } + + normalize_axes(input.get_shape(), axes_indices); + Shape output_shape = calculate_output_shape(input.get_shape(), axes_indices); + + switch (input.get_type()) { + case Type::kFloat: + compute(input, output_shape, axes_indices, output); + break; + case Type::kInt: + compute(input, output_shape, axes_indices, output); + break; + default: + throw std::runtime_error( + "ReduceSum: Unsupported input tensor type. Only float and int are " + "supported"); + } +} + +} // namespace itlab_2023 \ No newline at end of file diff --git a/test/single_layer/test_reducesumlayer.cpp b/test/single_layer/test_reducesumlayer.cpp new file mode 100644 index 00000000..f0a6285f --- /dev/null +++ b/test/single_layer/test_reducesumlayer.cpp @@ -0,0 +1,112 @@ +#include + +#include "layers/ReduceSumLayer.hpp" +#include "layers/Tensor.hpp" + +namespace itlab_2023 { + +TEST(ReduceSumLayer, DefaultConstructor) { + ASSERT_NO_THROW(ReduceSumLayer layer); +} + +TEST(ReduceSumLayer, SumAllAxesKeepDims) { + ReduceSumLayer layer(1); + Tensor input = make_tensor({1.0f, 2.0f, 3.0f, 4.0f}, {2, 2}); + Tensor output; + + layer.run(input, output); + + EXPECT_EQ(output.get_shape(), Shape({1, 1})); + EXPECT_FLOAT_EQ(output.get({0, 0}), 10.0f); +} + +TEST(ReduceSumLayer, SumAlongAxis0) { + ReduceSumLayer layer(0); + Tensor input = make_tensor({1.0f, 2.0f, 3.0f, 4.0f}, {2, 2}); + Tensor axes = make_tensor({0}); + Tensor output; + + layer.run(input, axes, output); + + EXPECT_EQ(output.get_shape(), Shape({2})); + EXPECT_FLOAT_EQ(output.get({0}), 4.0f); + EXPECT_FLOAT_EQ(output.get({1}), 6.0f); +} + +TEST(ReduceSumLayer, SumAlongAxis1KeepDims) { + ReduceSumLayer layer(1); + Tensor input = make_tensor({1.0f, 2.0f, 3.0f, 4.0f}, {2, 2}); + Tensor axes = make_tensor({1}); + Tensor output; + + layer.run(input, axes, output); + + EXPECT_EQ(output.get_shape(), Shape({2, 1})); + EXPECT_FLOAT_EQ(output.get({0, 0}), 3.0f); + EXPECT_FLOAT_EQ(output.get({1, 0}), 7.0f); +} + +TEST(ReduceSumLayer, NegativeAxis) { + ReduceSumLayer layer(0); + Tensor input = make_tensor({1.0f, 2.0f, 3.0f, 4.0f}, {2, 2}); + Tensor axes = make_tensor({-1}); + Tensor output; + + layer.run(input, axes, output); + + EXPECT_EQ(output.get_shape(), Shape({2})); + EXPECT_FLOAT_EQ(output.get({0}), 3.0f); + EXPECT_FLOAT_EQ(output.get({1}), 7.0f); +} + +TEST(ReduceSumLayer, InvalidAxisThrows) { + ReduceSumLayer layer; + Tensor input = make_tensor({1.0f, 2.0f}, {2}); + Tensor axes = make_tensor({2}); + + Tensor output; + ASSERT_THROW(layer.run(input, axes, output), std::runtime_error); +} + +TEST(ReduceSumLayer, IntTensorSupport) { + ReduceSumLayer layer(0); + Tensor input = make_tensor({1, 2, 3, 4}, {2, 2}); + Tensor axes = make_tensor({0}); + Tensor output; + + layer.run(input, axes, output); + + EXPECT_EQ(output.get_shape(), Shape({2})); + EXPECT_EQ(output.get({0}), 4); + EXPECT_EQ(output.get({1}), 6); +} + +TEST(ReduceSumLayer, 3DTensorReduction) { + ReduceSumLayer layer(1); + Tensor input = make_tensor( + {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f}, {2, 2, 2}); + Tensor axes = make_tensor({1}); + Tensor output; + + layer.run(input, axes, output); + + EXPECT_EQ(output.get_shape(), Shape({2, 1, 2})); + EXPECT_FLOAT_EQ(output.get({0, 0, 0}), 4.0f); + EXPECT_FLOAT_EQ(output.get({0, 0, 1}), 6.0f); + EXPECT_FLOAT_EQ(output.get({1, 0, 0}), 12.0f); + EXPECT_FLOAT_EQ(output.get({1, 0, 1}), 14.0f); +} + +TEST(ReduceSumLayer, Resnet) { + ReduceSumLayer layer(1); + Tensor input = make_tensor({1, 2, 64, 64, 64}, {5}); + Tensor axes = make_tensor({1}); + Tensor output; + + layer.run(input, axes, output); + + EXPECT_EQ(output.get_shape(), Shape({1})); + EXPECT_EQ(output.get({0}), 195); +} + +} // namespace itlab_2023 \ No newline at end of file From ebeaa106ed9f9263a774fd4e3a91ddf54ea73618 Mon Sep 17 00:00:00 2001 From: MikeMuradov Date: Fri, 1 Aug 2025 16:50:25 +0300 Subject: [PATCH 2/6] fix realization --- src/layers/ReduceSumLayer.cpp | 109 ++++++++++++---------- test/single_layer/test_reducesumlayer.cpp | 75 ++++++++++----- 2 files changed, 115 insertions(+), 69 deletions(-) diff --git a/src/layers/ReduceSumLayer.cpp b/src/layers/ReduceSumLayer.cpp index d6d83210..49adec67 100644 --- a/src/layers/ReduceSumLayer.cpp +++ b/src/layers/ReduceSumLayer.cpp @@ -20,15 +20,15 @@ void ReduceSumLayer::normalize_axes(const Shape& input_shape, if (axes.empty()) { axes.resize(rank); - std::iota(axes.begin(), axes.end(), 0); + std::iota(axes.begin(), axes.end(), 1); return; } for (auto& axis : axes) { - if (axis < -rank || axis >= rank) { - throw std::runtime_error("ReduceSum: Axis out of range"); + if (axis < 1 || axis > rank) { + throw std::runtime_error( + "ReduceSum: Axis out of range. Use 1-based indexing"); } - if (axis < 0) axis += rank; } std::sort(axes.begin(), axes.end()); @@ -44,19 +44,19 @@ Shape ReduceSumLayer::calculate_output_shape( std::vector new_dims; if (keepdims_) { + new_dims.resize(input_shape.dims(), 1); for (size_t i = 0; i < input_shape.dims(); ++i) { - new_dims.push_back(input_shape[i]); - } - - for (int64_t axis : axes) { - if (axis >= 0 && axis < static_cast(new_dims.size())) { - new_dims[axis] = 1; + bool is_axis = std::find(axes.begin(), axes.end(), + static_cast(i + 1)) != axes.end(); + if (!is_axis) { + new_dims[i] = input_shape[i]; } } } else { for (size_t i = 0; i < input_shape.dims(); ++i) { - if (std::find(axes.begin(), axes.end(), static_cast(i)) == - axes.end()) { + bool is_axis = std::find(axes.begin(), axes.end(), + static_cast(i + 1)) != axes.end(); + if (!is_axis) { new_dims.push_back(input_shape[i]); } } @@ -78,57 +78,70 @@ void ReduceSumLayer::compute(const Tensor& input, const Shape& output_shape, const auto& input_shape = input.get_shape(); const size_t input_rank = input_shape.dims(); - std::vector is_reduced(input_rank, false); - for (int64_t axis : axes) { - is_reduced[axis] = true; + std::vector reduced_axes; + for (auto axis : axes) { + reduced_axes.push_back(static_cast(axis - 1)); } - std::vector input_strides(input_rank, 1); + std::vector strides(input_rank, 1); for (size_t i = input_rank - 1; i > 0; --i) { - input_strides[i - 1] = input_strides[i] * input_shape[i]; + strides[i - 1] = strides[i] * input_shape[i]; } - for (size_t out_idx = 0; out_idx < output_data.size(); ++out_idx) { - size_t remaining = out_idx; - size_t input_idx = 0; + std::vector axis_mapping; + for (size_t i = 0; i < input_rank; ++i) { + if (std::find(reduced_axes.begin(), reduced_axes.end(), i) == + reduced_axes.end()) { + axis_mapping.push_back(i); + } + } - for (size_t out_dim = 0; out_dim < output_shape.dims(); ++out_dim) { - size_t out_coord = remaining % output_shape[out_dim]; - remaining /= output_shape[out_dim]; + std::vector out_strides(output_shape.dims(), 1); + for (size_t i = output_shape.dims() - 1; i > 0; --i) { + out_strides[i - 1] = out_strides[i] * output_shape[i]; + } - size_t input_dim = 0; - for (size_t i = 0; i < input_rank; ++i) { - if (!is_reduced[i]) { - if (input_dim == out_dim) { - input_idx += out_coord * input_strides[i]; - break; - } - input_dim++; - } + std::vector in_coords(input_rank, 0); + for (size_t in_idx = 0; in_idx < input_data.size(); ++in_idx) { + std::vector out_coords; + for (size_t i = 0; i < input_rank; ++i) { + if (std::find(reduced_axes.begin(), reduced_axes.end(), i) == + reduced_axes.end()) { + out_coords.push_back(in_coords[i]); } } - T sum = 0; - size_t reduced_count = 1; - for (int64_t axis : axes) { - reduced_count *= input_shape[axis]; + size_t out_idx = 0; + for (size_t i = 0; i < out_coords.size(); ++i) { + out_idx += out_coords[i] * out_strides[i]; } - for (size_t offset = 0; offset < reduced_count; ++offset) { - size_t current_idx = input_idx; - size_t temp = offset; - - for (int64_t axis : axes) { - size_t axis_size = input_shape[axis]; - size_t coord = temp % axis_size; - temp /= axis_size; - current_idx += coord * input_strides[axis]; + if (keepdims_) { + std::vector full_out_coords; + size_t out_pos = 0; + for (size_t i = 0; i < input_rank; ++i) { + if (std::find(reduced_axes.begin(), reduced_axes.end(), i) != + reduced_axes.end()) { + full_out_coords.push_back(0); + } else { + full_out_coords.push_back(out_coords[out_pos++]); + } + } + out_idx = 0; + for (size_t i = 0; i < full_out_coords.size(); ++i) { + out_idx += full_out_coords[i] * out_strides[i]; } - - sum += input_data[current_idx]; } - output_data[out_idx] = sum; + output_data[out_idx] += input_data[in_idx]; + + for (size_t i = input_rank - 1;; --i) { + ++in_coords[i]; + if (in_coords[i] < input_shape[i] || i == 0) { + break; + } + in_coords[i] = 0; + } } output = make_tensor(output_data, output_shape); diff --git a/test/single_layer/test_reducesumlayer.cpp b/test/single_layer/test_reducesumlayer.cpp index f0a6285f..589c8c28 100644 --- a/test/single_layer/test_reducesumlayer.cpp +++ b/test/single_layer/test_reducesumlayer.cpp @@ -23,7 +23,7 @@ TEST(ReduceSumLayer, SumAllAxesKeepDims) { TEST(ReduceSumLayer, SumAlongAxis0) { ReduceSumLayer layer(0); Tensor input = make_tensor({1.0f, 2.0f, 3.0f, 4.0f}, {2, 2}); - Tensor axes = make_tensor({0}); + Tensor axes = make_tensor({1}); Tensor output; layer.run(input, axes, output); @@ -36,7 +36,7 @@ TEST(ReduceSumLayer, SumAlongAxis0) { TEST(ReduceSumLayer, SumAlongAxis1KeepDims) { ReduceSumLayer layer(1); Tensor input = make_tensor({1.0f, 2.0f, 3.0f, 4.0f}, {2, 2}); - Tensor axes = make_tensor({1}); + Tensor axes = make_tensor({2}); Tensor output; layer.run(input, axes, output); @@ -46,23 +46,10 @@ TEST(ReduceSumLayer, SumAlongAxis1KeepDims) { EXPECT_FLOAT_EQ(output.get({1, 0}), 7.0f); } -TEST(ReduceSumLayer, NegativeAxis) { - ReduceSumLayer layer(0); - Tensor input = make_tensor({1.0f, 2.0f, 3.0f, 4.0f}, {2, 2}); - Tensor axes = make_tensor({-1}); - Tensor output; - - layer.run(input, axes, output); - - EXPECT_EQ(output.get_shape(), Shape({2})); - EXPECT_FLOAT_EQ(output.get({0}), 3.0f); - EXPECT_FLOAT_EQ(output.get({1}), 7.0f); -} - TEST(ReduceSumLayer, InvalidAxisThrows) { ReduceSumLayer layer; Tensor input = make_tensor({1.0f, 2.0f}, {2}); - Tensor axes = make_tensor({2}); + Tensor axes = make_tensor({3}); Tensor output; ASSERT_THROW(layer.run(input, axes, output), std::runtime_error); @@ -71,7 +58,7 @@ TEST(ReduceSumLayer, InvalidAxisThrows) { TEST(ReduceSumLayer, IntTensorSupport) { ReduceSumLayer layer(0); Tensor input = make_tensor({1, 2, 3, 4}, {2, 2}); - Tensor axes = make_tensor({0}); + Tensor axes = make_tensor({1}); Tensor output; layer.run(input, axes, output); @@ -83,9 +70,23 @@ TEST(ReduceSumLayer, IntTensorSupport) { TEST(ReduceSumLayer, 3DTensorReduction) { ReduceSumLayer layer(1); - Tensor input = make_tensor( - {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f}, {2, 2, 2}); - Tensor axes = make_tensor({1}); + Tensor input = make_tensor({1, 2, 3, 4, 5, 6, 7, 8}, {2, 2, 2}); + Tensor axes = make_tensor({3}); + Tensor output; + + layer.run(input, axes, output); + + EXPECT_EQ(output.get_shape(), Shape({2, 2, 1})); + EXPECT_FLOAT_EQ(output.get({0, 0, 0}), 3.0f); + EXPECT_FLOAT_EQ(output.get({0, 1, 0}), 7.0f); + EXPECT_FLOAT_EQ(output.get({1, 0, 0}), 11.0f); + EXPECT_FLOAT_EQ(output.get({1, 1, 0}), 15.0f); +} + +TEST(ReduceSumLayer, 3DReductionAxis2) { + ReduceSumLayer layer(1); + Tensor input = make_tensor({1, 2, 3, 4, 5, 6, 7, 8}, {2, 2, 2}); + Tensor axes = make_tensor({2}); Tensor output; layer.run(input, axes, output); @@ -97,8 +98,40 @@ TEST(ReduceSumLayer, 3DTensorReduction) { EXPECT_FLOAT_EQ(output.get({1, 0, 1}), 14.0f); } -TEST(ReduceSumLayer, Resnet) { +TEST(ReduceSumLayer, 3DReductionAxis10) { ReduceSumLayer layer(1); + Tensor input = make_tensor( + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, {2, 2, 2, 2}); + + Tensor axes = make_tensor({1}); + Tensor output; + + layer.run(input, axes, output); + + EXPECT_EQ(output.get_shape(), Shape({1, 2, 2, 2})); + EXPECT_FLOAT_EQ(output.get({0, 0, 0, 0}), 1 + 9); + EXPECT_FLOAT_EQ(output.get({0, 0, 0, 1}), 2 + 10); + EXPECT_FLOAT_EQ(output.get({0, 0, 1, 0}), 3 + 11); + EXPECT_FLOAT_EQ(output.get({0, 0, 1, 1}), 4 + 12); + EXPECT_FLOAT_EQ(output.get({0, 1, 0, 0}), 5 + 13); + EXPECT_FLOAT_EQ(output.get({0, 1, 0, 1}), 6 + 14); + EXPECT_FLOAT_EQ(output.get({0, 1, 1, 0}), 7 + 15); + EXPECT_FLOAT_EQ(output.get({0, 1, 1, 1}), 8 + 16); +} + +TEST(ReduceSumLayer, 3DFullReduction) { + ReduceSumLayer layer(1); + Tensor input = make_tensor({1, 2, 3, 4, 5, 6, 7, 8}, {2, 2, 2}); + + Tensor output; + layer.run(input, output); + + EXPECT_EQ(output.get_shape(), Shape({1, 1, 1})); + EXPECT_FLOAT_EQ(output.get({0, 0, 0}), 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8); +} + +TEST(ReduceSumLayer, Resnet) { + ReduceSumLayer layer(0); Tensor input = make_tensor({1, 2, 64, 64, 64}, {5}); Tensor axes = make_tensor({1}); Tensor output; From 7f4e68db51b2c6c2164413a96bc8b429973fbc50 Mon Sep 17 00:00:00 2001 From: MikeMuradov Date: Mon, 4 Aug 2025 20:18:18 +0300 Subject: [PATCH 3/6] add mean, mult, max, min, fix 1->0, fix tidy, fix 2-dim reduce, add negative axes, fix i check --- .../{ReduceSumLayer.hpp => ReduceLayer.hpp} | 23 +- src/layers/ReduceLayer.cpp | 213 +++++++++++++ src/layers/ReduceSumLayer.cpp | 195 ------------ test/single_layer/test_reducelayer.cpp | 280 ++++++++++++++++++ test/single_layer/test_reducesumlayer.cpp | 145 --------- 5 files changed, 509 insertions(+), 347 deletions(-) rename include/layers/{ReduceSumLayer.hpp => ReduceLayer.hpp} (57%) create mode 100644 src/layers/ReduceLayer.cpp delete mode 100644 src/layers/ReduceSumLayer.cpp create mode 100644 test/single_layer/test_reducelayer.cpp delete mode 100644 test/single_layer/test_reducesumlayer.cpp diff --git a/include/layers/ReduceSumLayer.hpp b/include/layers/ReduceLayer.hpp similarity index 57% rename from include/layers/ReduceSumLayer.hpp rename to include/layers/ReduceLayer.hpp index 0cd41740..e6dd5ab9 100644 --- a/include/layers/ReduceSumLayer.hpp +++ b/include/layers/ReduceLayer.hpp @@ -6,20 +6,29 @@ namespace itlab_2023 { -class ReduceSumLayer : public Layer { +class ReduceLayer : public Layer { public: - explicit ReduceSumLayer(int64_t keepdims = 0); - + enum class Operation { + kSum, + kMean, + kMult, + kMax, + kMin + }; + + ReduceLayer(Operation op, int64_t keepdims = 0); + explicit ReduceLayer(int64_t keepdims = 0) + : ReduceLayer(Operation::kSum, keepdims) {} void run(const Tensor& input, Tensor& output) override; void run(const Tensor& input, const Tensor& axes, Tensor& output); - static std::string get_name() { return "ReduceSumLayer"; } + static std::string get_name() { return "ReduceLayer"; } private: + Operation op_; int64_t keepdims_; - - void normalize_axes(const Shape& input_shape, - std::vector& axes) const; + static void normalize_axes(const Shape& input_shape, + std::vector& axes); Shape calculate_output_shape(const Shape& input_shape, const std::vector& axes) const; diff --git a/src/layers/ReduceLayer.cpp b/src/layers/ReduceLayer.cpp new file mode 100644 index 00000000..e067feac --- /dev/null +++ b/src/layers/ReduceLayer.cpp @@ -0,0 +1,213 @@ +#include "layers/ReduceLayer.hpp" + +#include +#include +#include + +namespace itlab_2023 { + +ReduceLayer::ReduceLayer(Operation op, int64_t keepdims) + : op_(op), keepdims_(keepdims) {} + +void ReduceLayer::normalize_axes(const Shape& input_shape, + std::vector& axes) { + const auto rank = static_cast(input_shape.dims()); + + if (rank == 0) { + if (!axes.empty()) { + throw std::runtime_error("ReduceLayer: Axis specified for scalar input"); + } + return; + } + + if (axes.empty()) { + axes.resize(rank); + std::iota(axes.begin(), axes.end(), 0); + return; + } + + for (auto& axis : axes) { + if (axis < -rank || axis >= rank) { + throw std::runtime_error( + "ReduceLayer: Axis out of range. Valid range is [-" + + std::to_string(rank) + ", " + std::to_string(rank - 1) + "]"); + } + + if (axis < 0) { + axis += rank; + } + } + + std::sort(axes.begin(), axes.end()); + axes.erase(std::unique(axes.begin(), axes.end()), axes.end()); +} + +Shape ReduceLayer::calculate_output_shape( + const Shape& input_shape, const std::vector& axes) const { + if (input_shape.dims() == 0) { + return Shape({}); + } + + std::vector new_dims; + + if (keepdims_) { + new_dims.resize(input_shape.dims(), 1); + for (int64_t i = 0; i < static_cast(input_shape.dims()); ++i) { + bool is_axis = std::find(axes.begin(), axes.end(), i) != axes.end(); + if (!is_axis) { + new_dims[i] = input_shape[i]; + } + } + } else { + for (int64_t i = 0; i < static_cast(input_shape.dims()); ++i) { + bool is_axis = std::find(axes.begin(), axes.end(), i) != axes.end(); + if (!is_axis) { + new_dims.push_back(input_shape[i]); + } + } + if (new_dims.empty()) { + new_dims.push_back(1); + } + } + + return Shape(new_dims); +} + +template +void ReduceLayer::compute(const Tensor& input, const Shape& output_shape, + const std::vector& axes, + Tensor& output) const { + const auto& input_data = *input.as(); + std::vector output_data(output_shape.count()); + std::vector counts(output_shape.count(), 0); + + switch (op_) { + case Operation::kSum: + case Operation::kMean: + std::fill(output_data.begin(), output_data.end(), T(0)); + break; + case Operation::kMult: + std::fill(output_data.begin(), output_data.end(), T(1)); + break; + case Operation::kMax: + std::fill(output_data.begin(), output_data.end(), + std::numeric_limits::lowest()); + break; + case Operation::kMin: + std::fill(output_data.begin(), output_data.end(), + std::numeric_limits::max()); + break; + } + + const auto& input_shape = input.get_shape(); + const int64_t input_rank = static_cast(input_shape.dims()); + + std::vector in_coords(input_rank, 0); + for (size_t in_idx = 0; in_idx < input_data.size(); ++in_idx) { + std::vector out_coords; + if (keepdims_) { + out_coords.resize(input_rank, 0); + for (int64_t i = 0; i < input_rank; ++i) { + if (std::find(axes.begin(), axes.end(), i) == axes.end()) { + out_coords[i] = in_coords[i]; + } + } + } else { + for (int64_t i = 0; i < input_rank; ++i) { + if (std::find(axes.begin(), axes.end(), i) == axes.end()) { + out_coords.push_back(in_coords[i]); + } + } + } + + size_t out_idx = 0; + size_t stride = 1; + for (size_t i = out_coords.size(); i-- > 0;) { + out_idx += out_coords[i] * stride; + stride *= output_shape[i]; + } + + switch (op_) { + case Operation::kSum: + case Operation::kMean: + output_data[out_idx] += input_data[in_idx]; + counts[out_idx]++; + break; + case Operation::kMult: + output_data[out_idx] *= input_data[in_idx]; + break; + case Operation::kMax: + if (input_data[in_idx] > output_data[out_idx]) { + output_data[out_idx] = input_data[in_idx]; + } + break; + case Operation::kMin: + if (input_data[in_idx] < output_data[out_idx]) { + output_data[out_idx] = input_data[in_idx]; + } + break; + } + + for (int64_t i = input_rank; i-- > 0;) { + ++in_coords[i]; + if (in_coords[i] < input_shape[i]) break; + in_coords[i] = 0; + } + } + + if (op_ == Operation::kMean) { + for (size_t i = 0; i < output_data.size(); ++i) { + if (counts[i] != 0) { + output_data[i] /= static_cast(counts[i]); + } + } + } + + output = make_tensor(output_data, output_shape); +} + +template void ReduceLayer::compute(const Tensor&, const Shape&, + const std::vector&, + Tensor&) const; +template void ReduceLayer::compute(const Tensor&, const Shape&, + const std::vector&, + Tensor&) const; + +void ReduceLayer::run(const Tensor& input, Tensor& output) { + run(input, Tensor(), output); +} + +void ReduceLayer::run(const Tensor& input, const Tensor& axes, Tensor& output) { + if (input.get_shape().count() == 0) { + output = make_tensor({0.0F}, {}); + return; + } + + std::vector axes_indices; + if (axes.get_shape().dims() > 0) { + if (axes.get_type() == Type::kInt) { + const auto* axes_data = axes.as(); + axes_indices.assign(axes_data->begin(), axes_data->end()); + } else { + throw std::runtime_error("ReduceLayer: Axes tensor must be of type int"); + } + } + + normalize_axes(input.get_shape(), axes_indices); + Shape output_shape = calculate_output_shape(input.get_shape(), axes_indices); + + switch (input.get_type()) { + case Type::kFloat: + compute(input, output_shape, axes_indices, output); + break; + case Type::kInt: + compute(input, output_shape, axes_indices, output); + break; + default: + throw std::runtime_error( + "ReduceLayer: Unsupported input tensor type. Only float and int are " + "supported"); + } +} + +} // namespace itlab_2023 \ No newline at end of file diff --git a/src/layers/ReduceSumLayer.cpp b/src/layers/ReduceSumLayer.cpp deleted file mode 100644 index 49adec67..00000000 --- a/src/layers/ReduceSumLayer.cpp +++ /dev/null @@ -1,195 +0,0 @@ -#include "layers/ReduceSumLayer.hpp" - -#include -#include - -namespace itlab_2023 { - -ReduceSumLayer::ReduceSumLayer(int64_t keepdims) : keepdims_(keepdims) {} - -void ReduceSumLayer::normalize_axes(const Shape& input_shape, - std::vector& axes) const { - const int64_t rank = static_cast(input_shape.dims()); - - if (rank == 0) { - if (!axes.empty()) { - throw std::runtime_error("ReduceSum: Axis specified for scalar input"); - } - return; - } - - if (axes.empty()) { - axes.resize(rank); - std::iota(axes.begin(), axes.end(), 1); - return; - } - - for (auto& axis : axes) { - if (axis < 1 || axis > rank) { - throw std::runtime_error( - "ReduceSum: Axis out of range. Use 1-based indexing"); - } - } - - std::sort(axes.begin(), axes.end()); - axes.erase(std::unique(axes.begin(), axes.end()), axes.end()); -} - -Shape ReduceSumLayer::calculate_output_shape( - const Shape& input_shape, const std::vector& axes) const { - if (input_shape.dims() == 0) { - return Shape({}); - } - - std::vector new_dims; - - if (keepdims_) { - new_dims.resize(input_shape.dims(), 1); - for (size_t i = 0; i < input_shape.dims(); ++i) { - bool is_axis = std::find(axes.begin(), axes.end(), - static_cast(i + 1)) != axes.end(); - if (!is_axis) { - new_dims[i] = input_shape[i]; - } - } - } else { - for (size_t i = 0; i < input_shape.dims(); ++i) { - bool is_axis = std::find(axes.begin(), axes.end(), - static_cast(i + 1)) != axes.end(); - if (!is_axis) { - new_dims.push_back(input_shape[i]); - } - } - if (new_dims.empty()) { - new_dims.push_back(1); - } - } - - return Shape(new_dims); -} - -template -void ReduceSumLayer::compute(const Tensor& input, const Shape& output_shape, - const std::vector& axes, - Tensor& output) const { - const auto& input_data = *input.as(); - std::vector output_data(output_shape.count(), 0); - - const auto& input_shape = input.get_shape(); - const size_t input_rank = input_shape.dims(); - - std::vector reduced_axes; - for (auto axis : axes) { - reduced_axes.push_back(static_cast(axis - 1)); - } - - std::vector strides(input_rank, 1); - for (size_t i = input_rank - 1; i > 0; --i) { - strides[i - 1] = strides[i] * input_shape[i]; - } - - std::vector axis_mapping; - for (size_t i = 0; i < input_rank; ++i) { - if (std::find(reduced_axes.begin(), reduced_axes.end(), i) == - reduced_axes.end()) { - axis_mapping.push_back(i); - } - } - - std::vector out_strides(output_shape.dims(), 1); - for (size_t i = output_shape.dims() - 1; i > 0; --i) { - out_strides[i - 1] = out_strides[i] * output_shape[i]; - } - - std::vector in_coords(input_rank, 0); - for (size_t in_idx = 0; in_idx < input_data.size(); ++in_idx) { - std::vector out_coords; - for (size_t i = 0; i < input_rank; ++i) { - if (std::find(reduced_axes.begin(), reduced_axes.end(), i) == - reduced_axes.end()) { - out_coords.push_back(in_coords[i]); - } - } - - size_t out_idx = 0; - for (size_t i = 0; i < out_coords.size(); ++i) { - out_idx += out_coords[i] * out_strides[i]; - } - - if (keepdims_) { - std::vector full_out_coords; - size_t out_pos = 0; - for (size_t i = 0; i < input_rank; ++i) { - if (std::find(reduced_axes.begin(), reduced_axes.end(), i) != - reduced_axes.end()) { - full_out_coords.push_back(0); - } else { - full_out_coords.push_back(out_coords[out_pos++]); - } - } - out_idx = 0; - for (size_t i = 0; i < full_out_coords.size(); ++i) { - out_idx += full_out_coords[i] * out_strides[i]; - } - } - - output_data[out_idx] += input_data[in_idx]; - - for (size_t i = input_rank - 1;; --i) { - ++in_coords[i]; - if (in_coords[i] < input_shape[i] || i == 0) { - break; - } - in_coords[i] = 0; - } - } - - output = make_tensor(output_data, output_shape); -} - -template void ReduceSumLayer::compute(const Tensor&, const Shape&, - const std::vector&, - Tensor&) const; -template void ReduceSumLayer::compute(const Tensor&, const Shape&, - const std::vector&, - Tensor&) const; - -void ReduceSumLayer::run(const Tensor& input, Tensor& output) { - run(input, Tensor(), output); -} - -void ReduceSumLayer::run(const Tensor& input, const Tensor& axes, - Tensor& output) { - if (input.get_shape().count() == 0) { - output = make_tensor({0.0f}, {}); - return; - } - - std::vector axes_indices; - if (axes.get_shape().dims() > 0) { - if (axes.get_type() == Type::kInt) { - auto axes_data = axes.as(); - axes_indices.assign(axes_data->begin(), axes_data->end()); - } else { - throw std::runtime_error("ReduceSum: Axes tensor must be of type int"); - } - } - - normalize_axes(input.get_shape(), axes_indices); - Shape output_shape = calculate_output_shape(input.get_shape(), axes_indices); - - switch (input.get_type()) { - case Type::kFloat: - compute(input, output_shape, axes_indices, output); - break; - case Type::kInt: - compute(input, output_shape, axes_indices, output); - break; - default: - throw std::runtime_error( - "ReduceSum: Unsupported input tensor type. Only float and int are " - "supported"); - } -} - -} // namespace itlab_2023 \ No newline at end of file diff --git a/test/single_layer/test_reducelayer.cpp b/test/single_layer/test_reducelayer.cpp new file mode 100644 index 00000000..344d0289 --- /dev/null +++ b/test/single_layer/test_reducelayer.cpp @@ -0,0 +1,280 @@ +#include + +#include "layers/ReduceLayer.hpp" +#include "layers/Tensor.hpp" + +namespace itlab_2023 { + +TEST(ReduceLayer, DefaultConstructor) { + ASSERT_NO_THROW(ReduceLayer layer); +} + +TEST(ReduceLayer, SumAllAxesKeepDims) { + ReduceLayer layer(1); + Tensor input = make_tensor({1.0f, 2.0f, 3.0f, 4.0f}, {2, 2}); + Tensor output; + + layer.run(input, output); + + EXPECT_EQ(output.get_shape(), Shape({1, 1})); + EXPECT_FLOAT_EQ(output.get({0, 0}), 10.0f); +} + +TEST(ReduceLayer, SumAlongAxis0) { + ReduceLayer layer(0); + Tensor input = make_tensor({1.0f, 2.0f, 3.0f, 4.0f}, {2, 2}); + Tensor axes = make_tensor({0}); + Tensor output; + + layer.run(input, axes, output); + + EXPECT_EQ(output.get_shape(), Shape({2})); + EXPECT_FLOAT_EQ(output.get({0}), 4.0f); + EXPECT_FLOAT_EQ(output.get({1}), 6.0f); +} + +TEST(ReduceLayer, SumAlongAxis1KeepDims) { + ReduceLayer layer(1); + Tensor input = make_tensor({1.0f, 2.0f, 3.0f, 4.0f}, {2, 2}); + Tensor axes = make_tensor({1}); + Tensor output; + + layer.run(input, axes, output); + + EXPECT_EQ(output.get_shape(), Shape({2, 1})); + EXPECT_FLOAT_EQ(output.get({0, 0}), 3.0f); + EXPECT_FLOAT_EQ(output.get({1, 0}), 7.0f); +} + +TEST(ReduceLayer, InvalidAxisThrows) { + ReduceLayer layer; + Tensor input = make_tensor({1.0f, 2.0f}, {2}); + Tensor axes = make_tensor({2}); + + Tensor output; + ASSERT_THROW(layer.run(input, axes, output), std::runtime_error); +} + +TEST(ReduceLayer, IntTensorSupport) { + ReduceLayer layer(0); + Tensor input = make_tensor({1, 2, 3, 4}, {2, 2}); + Tensor axes = make_tensor({0}); + Tensor output; + + layer.run(input, axes, output); + + EXPECT_EQ(output.get_shape(), Shape({2})); + EXPECT_EQ(output.get({0}), 4); + EXPECT_EQ(output.get({1}), 6); +} + +TEST(ReduceLayer, 3DTensorReduction) { + ReduceLayer layer(1); + Tensor input = make_tensor({1, 2, 3, 4, 5, 6, 7, 8}, {2, 2, 2}); + Tensor axes = make_tensor({2}); + Tensor output; + + layer.run(input, axes, output); + + EXPECT_EQ(output.get_shape(), Shape({2, 2, 1})); + EXPECT_FLOAT_EQ(output.get({0, 0, 0}), 3.0f); + EXPECT_FLOAT_EQ(output.get({0, 1, 0}), 7.0f); + EXPECT_FLOAT_EQ(output.get({1, 0, 0}), 11.0f); + EXPECT_FLOAT_EQ(output.get({1, 1, 0}), 15.0f); +} + +TEST(ReduceLayer, 3DReductionAxis2) { + ReduceLayer layer(1); + Tensor input = make_tensor({1, 2, 3, 4, 5, 6, 7, 8}, {2, 2, 2}); + Tensor axes = make_tensor({1}); + Tensor output; + + layer.run(input, axes, output); + + EXPECT_EQ(output.get_shape(), Shape({2, 1, 2})); + EXPECT_FLOAT_EQ(output.get({0, 0, 0}), 4.0f); + EXPECT_FLOAT_EQ(output.get({0, 0, 1}), 6.0f); + EXPECT_FLOAT_EQ(output.get({1, 0, 0}), 12.0f); + EXPECT_FLOAT_EQ(output.get({1, 0, 1}), 14.0f); +} + +TEST(ReduceLayer, 3DReductionAxis10) { + ReduceLayer layer(1); + Tensor input = make_tensor( + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, {2, 2, 2, 2}); + + Tensor axes = make_tensor({0}); + Tensor output; + + layer.run(input, axes, output); + + EXPECT_EQ(output.get_shape(), Shape({1, 2, 2, 2})); + EXPECT_FLOAT_EQ(output.get({0, 0, 0, 0}), 1 + 9); + EXPECT_FLOAT_EQ(output.get({0, 0, 0, 1}), 2 + 10); + EXPECT_FLOAT_EQ(output.get({0, 0, 1, 0}), 3 + 11); + EXPECT_FLOAT_EQ(output.get({0, 0, 1, 1}), 4 + 12); + EXPECT_FLOAT_EQ(output.get({0, 1, 0, 0}), 5 + 13); + EXPECT_FLOAT_EQ(output.get({0, 1, 0, 1}), 6 + 14); + EXPECT_FLOAT_EQ(output.get({0, 1, 1, 0}), 7 + 15); + EXPECT_FLOAT_EQ(output.get({0, 1, 1, 1}), 8 + 16); +} + +TEST(ReduceLayer, 3DFullReduction) { + ReduceLayer layer(1); + Tensor input = make_tensor({1, 2, 3, 4, 5, 6, 7, 8}, {2, 2, 2}); + + Tensor output; + layer.run(input, output); + + EXPECT_EQ(output.get_shape(), Shape({1, 1, 1})); + EXPECT_FLOAT_EQ(output.get({0, 0, 0}), 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8); +} + +TEST(ReduceLayer, Resnet) { + ReduceLayer layer(1); + Tensor input = make_tensor( + {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, + 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f, 17.0f, 18.0f, + 19.0f, 20.0f, 21.0f, 22.0f, 23.0f, 24.0f, 25.0f, 26.0f, 27.0f, + 28.0f, 29.0f, 30.0f, 31.0f, 32.0f, 33.0f, 34.0f, 35.0f, 36.0f, + 37.0f, 38.0f, 39.0f, 40.0f, 41.0f, 42.0f, 43.0f, 44.0f, 45.0f, + 46.0f, 47.0f, 48.0f, 49.0f, 50.0f, 51.0f, 52.0f, 53.0f, 54.0f}, + {1, 2, 3, 3, 3}); + + Tensor axes = make_tensor({1}); + Tensor output; + + layer.run(input, axes, output); + + EXPECT_EQ(output.get_shape(), + Shape({1, 1, 3, 3, 3})); + EXPECT_FLOAT_EQ(output.get({0, 0, 0, 0, 0}), + 1.0f + 28.0f); + EXPECT_FLOAT_EQ(output.get({0, 0, 2, 2, 2}), + 27.0f + 54.0f); +} + +TEST(ReduceLayer, NegativeAxisBasic) { + ReduceLayer layer(0); + Tensor input = make_tensor({1.0f, 2.0f, 3.0f, 4.0f}, {2, 2}); + Tensor axes = make_tensor({-1}); + Tensor output; + + layer.run(input, axes, output); + + EXPECT_EQ(output.get_shape(), Shape({2})); + EXPECT_FLOAT_EQ(output.get({0}), 3.0f); + EXPECT_FLOAT_EQ(output.get({1}), 7.0f); +} + +TEST(ReduceLayer, NegativeAxis3DTensor) { + ReduceLayer layer(1); + Tensor input = make_tensor({1, 2, 3, 4, 5, 6, 7, 8}, {2, 2, 2}); + Tensor axes = make_tensor({-2}); + Tensor output; + + layer.run(input, axes, output); + + EXPECT_EQ(output.get_shape(), Shape({2, 1, 2})); + EXPECT_FLOAT_EQ(output.get({0, 0, 0}), 4.0f); + EXPECT_FLOAT_EQ(output.get({0, 0, 1}), 6.0f); + EXPECT_FLOAT_EQ(output.get({1, 0, 0}), 12.0f); + EXPECT_FLOAT_EQ(output.get({1, 0, 1}), 14.0f); +} + +TEST(ReduceLayer, ReduceMean) { + ReduceLayer layer(ReduceLayer::Operation::kMean, 1); + Tensor input = make_tensor({1.0f, 2.0f, 3.0f, 4.0f}, {2, 2}); + Tensor output; + Tensor axes = make_tensor({0}); + + layer.run(input, axes, output); + + EXPECT_EQ(output.get_shape(), Shape({1, 2})); + EXPECT_FLOAT_EQ(output.get({0, 0}), 2.0f); +} + +TEST(ReduceLayer, ReduceMeanResnet) { + ReduceLayer layer(ReduceLayer::Operation::kMean, 1); + Tensor input = make_tensor({1.0f, 2.0f, 3.0f, 4.0f}, {2, 2}); + Tensor output; + Tensor axes = make_tensor({0}); + + layer.run(input, axes, output); + + EXPECT_EQ(output.get_shape(), Shape({1, 2})); + EXPECT_FLOAT_EQ(output.get({0, 0}), 2.0f); +} + +TEST(ReduceLayer, MultAlongAxis0) { + ReduceLayer layer(ReduceLayer::Operation::kMult, 0); + Tensor input = make_tensor({1.0f, 2.0f, 3.0f, 4.0f}, {2, 2}); + Tensor axes = make_tensor({0}); + Tensor output; + + layer.run(input, axes, output); + + EXPECT_EQ(output.get_shape(), Shape({2})); + EXPECT_FLOAT_EQ(output.get({0}), 3.0f); + EXPECT_FLOAT_EQ(output.get({1}), 8.0f); +} + +TEST(ReduceLayer, MaxAlongAxis1KeepDims) { + ReduceLayer layer(ReduceLayer::Operation::kMax, 1); + Tensor input = make_tensor({1.0f, 2.0f, 3.0f, 4.0f}, {2, 2}); + Tensor axes = make_tensor({1}); + Tensor output; + + layer.run(input, axes, output); + + EXPECT_EQ(output.get_shape(), Shape({2, 1})); + EXPECT_FLOAT_EQ(output.get({0, 0}), 2.0f); + EXPECT_FLOAT_EQ(output.get({1, 0}), 4.0f); +} + +TEST(ReduceLayer, Min3DTensorReduction) { + ReduceLayer layer(ReduceLayer::Operation::kMin, 1); + Tensor input = make_tensor({1, 2, 3, 4, 5, 6, 7, 8}, {2, 2, 2}); + Tensor axes = make_tensor({2}); + Tensor output; + + layer.run(input, axes, output); + + EXPECT_EQ(output.get_shape(), Shape({2, 2, 1})); + EXPECT_FLOAT_EQ(output.get({0, 0, 0}), 1.0f); + EXPECT_FLOAT_EQ(output.get({0, 1, 0}), 3.0f); + EXPECT_FLOAT_EQ(output.get({1, 0, 0}), 5.0f); + EXPECT_FLOAT_EQ(output.get({1, 1, 0}), 7.0f); +} + +TEST(ReduceLayer, ResnetReduceMean) { + ReduceLayer layer(ReduceLayer::Operation::kMean, 1); + Tensor input = make_tensor( + {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, + 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f, 17.0f, 18.0f, + 19.0f, 20.0f, 21.0f, 22.0f, 23.0f, 24.0f, 25.0f, 26.0f, 27.0f}, + {1, 1, 3, 3, 3}); + + Tensor axes = make_tensor({2, 3}); + Tensor output; + + layer.run(input, axes, output); + + EXPECT_EQ(output.get_shape(), Shape({1, 1, 1, 1, 3})); + EXPECT_FLOAT_EQ( + output.get({0, 0, 0, 0, 0}), + (1.0f + 4.0f + 7.0f + 10.0f + 13.0f + 16.0f + 19.0f + 22.0f + 25.0f) / + 9.0f); + + EXPECT_FLOAT_EQ( + output.get({0, 0, 0, 0, 1}), + (2.0f + 5.0f + 8.0f + 11.0f + 14.0f + 17.0f + 20.0f + 23.0f + 26.0f) / + 9.0f); + + EXPECT_FLOAT_EQ( + output.get({0, 0, 0, 0, 2}), + (3.0f + 6.0f + 9.0f + 12.0f + 15.0f + 18.0f + 21.0f + 24.0f + 27.0f) / + 9.0f); +} + +} // namespace itlab_2023 \ No newline at end of file diff --git a/test/single_layer/test_reducesumlayer.cpp b/test/single_layer/test_reducesumlayer.cpp deleted file mode 100644 index 589c8c28..00000000 --- a/test/single_layer/test_reducesumlayer.cpp +++ /dev/null @@ -1,145 +0,0 @@ -#include - -#include "layers/ReduceSumLayer.hpp" -#include "layers/Tensor.hpp" - -namespace itlab_2023 { - -TEST(ReduceSumLayer, DefaultConstructor) { - ASSERT_NO_THROW(ReduceSumLayer layer); -} - -TEST(ReduceSumLayer, SumAllAxesKeepDims) { - ReduceSumLayer layer(1); - Tensor input = make_tensor({1.0f, 2.0f, 3.0f, 4.0f}, {2, 2}); - Tensor output; - - layer.run(input, output); - - EXPECT_EQ(output.get_shape(), Shape({1, 1})); - EXPECT_FLOAT_EQ(output.get({0, 0}), 10.0f); -} - -TEST(ReduceSumLayer, SumAlongAxis0) { - ReduceSumLayer layer(0); - Tensor input = make_tensor({1.0f, 2.0f, 3.0f, 4.0f}, {2, 2}); - Tensor axes = make_tensor({1}); - Tensor output; - - layer.run(input, axes, output); - - EXPECT_EQ(output.get_shape(), Shape({2})); - EXPECT_FLOAT_EQ(output.get({0}), 4.0f); - EXPECT_FLOAT_EQ(output.get({1}), 6.0f); -} - -TEST(ReduceSumLayer, SumAlongAxis1KeepDims) { - ReduceSumLayer layer(1); - Tensor input = make_tensor({1.0f, 2.0f, 3.0f, 4.0f}, {2, 2}); - Tensor axes = make_tensor({2}); - Tensor output; - - layer.run(input, axes, output); - - EXPECT_EQ(output.get_shape(), Shape({2, 1})); - EXPECT_FLOAT_EQ(output.get({0, 0}), 3.0f); - EXPECT_FLOAT_EQ(output.get({1, 0}), 7.0f); -} - -TEST(ReduceSumLayer, InvalidAxisThrows) { - ReduceSumLayer layer; - Tensor input = make_tensor({1.0f, 2.0f}, {2}); - Tensor axes = make_tensor({3}); - - Tensor output; - ASSERT_THROW(layer.run(input, axes, output), std::runtime_error); -} - -TEST(ReduceSumLayer, IntTensorSupport) { - ReduceSumLayer layer(0); - Tensor input = make_tensor({1, 2, 3, 4}, {2, 2}); - Tensor axes = make_tensor({1}); - Tensor output; - - layer.run(input, axes, output); - - EXPECT_EQ(output.get_shape(), Shape({2})); - EXPECT_EQ(output.get({0}), 4); - EXPECT_EQ(output.get({1}), 6); -} - -TEST(ReduceSumLayer, 3DTensorReduction) { - ReduceSumLayer layer(1); - Tensor input = make_tensor({1, 2, 3, 4, 5, 6, 7, 8}, {2, 2, 2}); - Tensor axes = make_tensor({3}); - Tensor output; - - layer.run(input, axes, output); - - EXPECT_EQ(output.get_shape(), Shape({2, 2, 1})); - EXPECT_FLOAT_EQ(output.get({0, 0, 0}), 3.0f); - EXPECT_FLOAT_EQ(output.get({0, 1, 0}), 7.0f); - EXPECT_FLOAT_EQ(output.get({1, 0, 0}), 11.0f); - EXPECT_FLOAT_EQ(output.get({1, 1, 0}), 15.0f); -} - -TEST(ReduceSumLayer, 3DReductionAxis2) { - ReduceSumLayer layer(1); - Tensor input = make_tensor({1, 2, 3, 4, 5, 6, 7, 8}, {2, 2, 2}); - Tensor axes = make_tensor({2}); - Tensor output; - - layer.run(input, axes, output); - - EXPECT_EQ(output.get_shape(), Shape({2, 1, 2})); - EXPECT_FLOAT_EQ(output.get({0, 0, 0}), 4.0f); - EXPECT_FLOAT_EQ(output.get({0, 0, 1}), 6.0f); - EXPECT_FLOAT_EQ(output.get({1, 0, 0}), 12.0f); - EXPECT_FLOAT_EQ(output.get({1, 0, 1}), 14.0f); -} - -TEST(ReduceSumLayer, 3DReductionAxis10) { - ReduceSumLayer layer(1); - Tensor input = make_tensor( - {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, {2, 2, 2, 2}); - - Tensor axes = make_tensor({1}); - Tensor output; - - layer.run(input, axes, output); - - EXPECT_EQ(output.get_shape(), Shape({1, 2, 2, 2})); - EXPECT_FLOAT_EQ(output.get({0, 0, 0, 0}), 1 + 9); - EXPECT_FLOAT_EQ(output.get({0, 0, 0, 1}), 2 + 10); - EXPECT_FLOAT_EQ(output.get({0, 0, 1, 0}), 3 + 11); - EXPECT_FLOAT_EQ(output.get({0, 0, 1, 1}), 4 + 12); - EXPECT_FLOAT_EQ(output.get({0, 1, 0, 0}), 5 + 13); - EXPECT_FLOAT_EQ(output.get({0, 1, 0, 1}), 6 + 14); - EXPECT_FLOAT_EQ(output.get({0, 1, 1, 0}), 7 + 15); - EXPECT_FLOAT_EQ(output.get({0, 1, 1, 1}), 8 + 16); -} - -TEST(ReduceSumLayer, 3DFullReduction) { - ReduceSumLayer layer(1); - Tensor input = make_tensor({1, 2, 3, 4, 5, 6, 7, 8}, {2, 2, 2}); - - Tensor output; - layer.run(input, output); - - EXPECT_EQ(output.get_shape(), Shape({1, 1, 1})); - EXPECT_FLOAT_EQ(output.get({0, 0, 0}), 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8); -} - -TEST(ReduceSumLayer, Resnet) { - ReduceSumLayer layer(0); - Tensor input = make_tensor({1, 2, 64, 64, 64}, {5}); - Tensor axes = make_tensor({1}); - Tensor output; - - layer.run(input, axes, output); - - EXPECT_EQ(output.get_shape(), Shape({1})); - EXPECT_EQ(output.get({0}), 195); -} - -} // namespace itlab_2023 \ No newline at end of file From 9ded69d656414b13ba545debabfe752ccbf96f41 Mon Sep 17 00:00:00 2001 From: MikeMuradov Date: Mon, 4 Aug 2025 20:29:10 +0300 Subject: [PATCH 4/6] fix confs fix namespace --- include/layers/ReduceLayer.hpp | 14 ++++---------- include/layers/Shape.hpp | 10 ---------- src/layers/ReduceLayer.cpp | 4 ++-- test/single_layer/test_reducelayer.cpp | 17 ++++++----------- 4 files changed, 12 insertions(+), 33 deletions(-) diff --git a/include/layers/ReduceLayer.hpp b/include/layers/ReduceLayer.hpp index e6dd5ab9..0e20cee3 100644 --- a/include/layers/ReduceLayer.hpp +++ b/include/layers/ReduceLayer.hpp @@ -4,17 +4,11 @@ #include "layers/Layer.hpp" #include "layers/Tensor.hpp" -namespace itlab_2023 { +namespace it_lab_ai { class ReduceLayer : public Layer { public: - enum class Operation { - kSum, - kMean, - kMult, - kMax, - kMin - }; + enum class Operation { kSum, kMean, kMult, kMax, kMin }; ReduceLayer(Operation op, int64_t keepdims = 0); explicit ReduceLayer(int64_t keepdims = 0) @@ -28,7 +22,7 @@ class ReduceLayer : public Layer { Operation op_; int64_t keepdims_; static void normalize_axes(const Shape& input_shape, - std::vector& axes); + std::vector& axes); Shape calculate_output_shape(const Shape& input_shape, const std::vector& axes) const; @@ -37,4 +31,4 @@ class ReduceLayer : public Layer { const std::vector& axes, Tensor& output) const; }; -} // namespace itlab_2023 \ No newline at end of file +} // namespace it_lab_ai \ No newline at end of file diff --git a/include/layers/Shape.hpp b/include/layers/Shape.hpp index 6fc36cd3..d6d1fad7 100644 --- a/include/layers/Shape.hpp +++ b/include/layers/Shape.hpp @@ -49,16 +49,6 @@ class Shape { bool operator!=(const Shape& other) const { return !(*this == other); } friend std::ostream& operator<<(std::ostream& os, const Shape& shape); - bool operator==(const Shape& other) const noexcept { - if (dims_.size() != other.dims_.size()) { - return false; - } - return std::equal(dims_.begin(), dims_.end(), other.dims_.begin()); - } - - bool operator!=(const Shape& other) const noexcept { - return !(*this == other); - } private: std::vector dims_; diff --git a/src/layers/ReduceLayer.cpp b/src/layers/ReduceLayer.cpp index e067feac..77d833ac 100644 --- a/src/layers/ReduceLayer.cpp +++ b/src/layers/ReduceLayer.cpp @@ -4,7 +4,7 @@ #include #include -namespace itlab_2023 { +namespace it_lab_ai { ReduceLayer::ReduceLayer(Operation op, int64_t keepdims) : op_(op), keepdims_(keepdims) {} @@ -210,4 +210,4 @@ void ReduceLayer::run(const Tensor& input, const Tensor& axes, Tensor& output) { } } -} // namespace itlab_2023 \ No newline at end of file +} // namespace it_lab_ai \ No newline at end of file diff --git a/test/single_layer/test_reducelayer.cpp b/test/single_layer/test_reducelayer.cpp index 344d0289..4fbf048c 100644 --- a/test/single_layer/test_reducelayer.cpp +++ b/test/single_layer/test_reducelayer.cpp @@ -3,11 +3,9 @@ #include "layers/ReduceLayer.hpp" #include "layers/Tensor.hpp" -namespace itlab_2023 { +namespace it_lab_ai { -TEST(ReduceLayer, DefaultConstructor) { - ASSERT_NO_THROW(ReduceLayer layer); -} +TEST(ReduceLayer, DefaultConstructor) { ASSERT_NO_THROW(ReduceLayer layer); } TEST(ReduceLayer, SumAllAxesKeepDims) { ReduceLayer layer(1); @@ -146,12 +144,9 @@ TEST(ReduceLayer, Resnet) { layer.run(input, axes, output); - EXPECT_EQ(output.get_shape(), - Shape({1, 1, 3, 3, 3})); - EXPECT_FLOAT_EQ(output.get({0, 0, 0, 0, 0}), - 1.0f + 28.0f); - EXPECT_FLOAT_EQ(output.get({0, 0, 2, 2, 2}), - 27.0f + 54.0f); + EXPECT_EQ(output.get_shape(), Shape({1, 1, 3, 3, 3})); + EXPECT_FLOAT_EQ(output.get({0, 0, 0, 0, 0}), 1.0f + 28.0f); + EXPECT_FLOAT_EQ(output.get({0, 0, 2, 2, 2}), 27.0f + 54.0f); } TEST(ReduceLayer, NegativeAxisBasic) { @@ -277,4 +272,4 @@ TEST(ReduceLayer, ResnetReduceMean) { 9.0f); } -} // namespace itlab_2023 \ No newline at end of file +} // namespace it_lab_ai \ No newline at end of file From dc1647bf37f5ab4c2bbe1f4dbc697b3f480ce23c Mon Sep 17 00:00:00 2001 From: MikeMuradov Date: Mon, 4 Aug 2025 21:59:47 +0300 Subject: [PATCH 5/6] tidy --- include/layers/ReduceLayer.hpp | 2 +- src/layers/ReduceLayer.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/layers/ReduceLayer.hpp b/include/layers/ReduceLayer.hpp index 0e20cee3..fdafaa77 100644 --- a/include/layers/ReduceLayer.hpp +++ b/include/layers/ReduceLayer.hpp @@ -8,7 +8,7 @@ namespace it_lab_ai { class ReduceLayer : public Layer { public: - enum class Operation { kSum, kMean, kMult, kMax, kMin }; + enum class Operation { kSum, kMean, kMult, kMax, kMin } uint8_t; ReduceLayer(Operation op, int64_t keepdims = 0); explicit ReduceLayer(int64_t keepdims = 0) diff --git a/src/layers/ReduceLayer.cpp b/src/layers/ReduceLayer.cpp index 77d833ac..0ed989a5 100644 --- a/src/layers/ReduceLayer.cpp +++ b/src/layers/ReduceLayer.cpp @@ -100,7 +100,7 @@ void ReduceLayer::compute(const Tensor& input, const Shape& output_shape, } const auto& input_shape = input.get_shape(); - const int64_t input_rank = static_cast(input_shape.dims()); + const auto input_rank = static_cast(input_shape.dims()); std::vector in_coords(input_rank, 0); for (size_t in_idx = 0; in_idx < input_data.size(); ++in_idx) { From 8dd56aa28ddf5e6a9d76b9c4f845fdd794c1ecd9 Mon Sep 17 00:00:00 2001 From: MikeMuradov Date: Mon, 4 Aug 2025 22:20:19 +0300 Subject: [PATCH 6/6] tidy --- include/layers/ReduceLayer.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/layers/ReduceLayer.hpp b/include/layers/ReduceLayer.hpp index fdafaa77..e2400600 100644 --- a/include/layers/ReduceLayer.hpp +++ b/include/layers/ReduceLayer.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include "layers/Layer.hpp" @@ -8,7 +9,7 @@ namespace it_lab_ai { class ReduceLayer : public Layer { public: - enum class Operation { kSum, kMean, kMult, kMax, kMin } uint8_t; + enum class Operation : uint8_t { kSum, kMean, kMult, kMax, kMin }; ReduceLayer(Operation op, int64_t keepdims = 0); explicit ReduceLayer(int64_t keepdims = 0)