Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions include/layers/TransposeLayer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#pragma once
#include <vector>

#include "layers/Layer.hpp"
#include "layers/Tensor.hpp"

namespace it_lab_ai {

class TransposeLayer : public Layer {
public:
explicit TransposeLayer(std::vector<int64_t> perm = {})
: perm_(std::move(perm)) {}

void run(const Tensor& input, Tensor& output) override;

#ifdef ENABLE_STATISTIC_WEIGHTS
Tensor get_weights() override { return Tensor(); }
#endif

static std::string get_name() { return "TransposeLayer"; }

private:
std::vector<int64_t> perm_;

void validate_perm(const Shape& input_shape) const;
};

} // namespace it_lab_ai
81 changes: 81 additions & 0 deletions src/layers/TransposeLayer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#include "layers/TransposeLayer.hpp"

#include <algorithm>
#include <numeric>

namespace it_lab_ai {

void TransposeLayer::run(const Tensor& input, Tensor& output) {
const auto& shape = input.get_shape();
const auto* input_data = input.as<float>();

if (!input_data || input_data->empty()) {
throw std::runtime_error("Input tensor is empty or invalid");
}

if (perm_.empty()) {
perm_.resize(shape.dims());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we enumerate shapes starting from the end?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted the default to be reverse permutation, but it's probably unnecessary.

std::iota(perm_.rbegin(), perm_.rend(), 0);
}

validate_perm(shape);

std::vector<size_t> new_dims;
for (const auto& axis : perm_) {
if (axis < 0 || static_cast<size_t>(axis) >= shape.dims()) {
throw std::invalid_argument("Invalid axis in permutation");
}
new_dims.push_back(shape[static_cast<size_t>(axis)]);
}
Shape new_shape(new_dims);

std::vector<float> output_data(input_data->size());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, check for other data types


std::vector<size_t> new_indices(shape.dims());
std::vector<size_t> old_indices(shape.dims());

for (size_t i = 0; i < input_data->size(); ++i) {
size_t remaining = i;
for (size_t dim = shape.dims(); dim-- > 0;) {
old_indices[dim] = remaining % shape[dim];
remaining /= shape[dim];
}

for (size_t dim = 0; dim < perm_.size(); ++dim) {
new_indices[dim] = old_indices[static_cast<size_t>(perm_[dim])];
}

size_t new_index = 0;
size_t stride = 1;
for (size_t dim = new_shape.dims(); dim-- > 0;) {
new_index += new_indices[dim] * stride;
stride *= new_shape[dim];
}

if (new_index >= output_data.size()) {
throw std::runtime_error("Index out of bounds during transposition");
}
output_data[new_index] = (*input_data)[i];
}

output = make_tensor(output_data, new_shape);
}

void TransposeLayer::validate_perm(const Shape& input_shape) const {
if (perm_.size() != input_shape.dims()) {
throw std::invalid_argument("Permutation size must match input dimensions");
}

std::vector<bool> used_axes(input_shape.dims(), false);
for (const auto& axis : perm_) {
if (axis < 0 || static_cast<size_t>(axis) >= input_shape.dims()) {
throw std::invalid_argument("Invalid axis in permutation");
}
if (used_axes[static_cast<size_t>(axis)]) {
throw std::invalid_argument("Duplicate axis in permutation");
}
used_axes[static_cast<size_t>(axis)] = true;
}
}

} // namespace it_lab_ai
149 changes: 149 additions & 0 deletions test/single_layer/test_transposelayer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#include <cstdint>
#include <iostream>
#include <vector>

#include "gtest/gtest.h"
#include "layers/Tensor.hpp"
#include "layers/TransposeLayer.hpp"

using namespace it_lab_ai;

TEST(TransposeLayerTest, EmptyTensor) {
Tensor input = make_tensor<float>({}, {0});
TransposeLayer layer;
Tensor output;

EXPECT_THROW(layer.run(input, output), std::runtime_error);
}

TEST(TransposeLayerTest, IdentityTranspose) {
Tensor input = make_tensor<float>({1, 2, 3, 4}, {2, 2});
TransposeLayer layer({0, 1});
Tensor output;

layer.run(input, output);

ASSERT_EQ(output.get_shape(), Shape({2, 2}));
EXPECT_FLOAT_EQ(output.get<float>({0, 0}), 1.0f);
EXPECT_FLOAT_EQ(output.get<float>({0, 1}), 2.0f);
EXPECT_FLOAT_EQ(output.get<float>({1, 0}), 3.0f);
EXPECT_FLOAT_EQ(output.get<float>({1, 1}), 4.0f);
}

TEST(TransposeLayerTest, VectorTranspose) {
Tensor input = make_tensor<float>({1, 2, 3, 4}, {4});
TransposeLayer layer({0});
Tensor output;

layer.run(input, output);

ASSERT_EQ(output.get_shape(), Shape({4}));
EXPECT_FLOAT_EQ(output.get<float>({0}), 1.0f);
EXPECT_FLOAT_EQ(output.get<float>({1}), 2.0f);
EXPECT_FLOAT_EQ(output.get<float>({2}), 3.0f);
EXPECT_FLOAT_EQ(output.get<float>({3}), 4.0f);
}

TEST(TransposeLayerTest, InvalidPermutationSize) {
Tensor input = make_tensor<float>({1, 2, 3, 4}, {2, 2});
TransposeLayer layer({0});
Tensor output;

EXPECT_THROW(layer.run(input, output), std::invalid_argument);
}

TEST(TransposeLayerTest, DuplicateAxes) {
Tensor input = make_tensor<float>({1, 2, 3, 4}, {2, 2});
TransposeLayer layer({0, 0});
Tensor output;

EXPECT_THROW(layer.run(input, output), std::invalid_argument);
}

TEST(TransposeLayerTest, NegativeAxis) {
Tensor input = make_tensor<float>({1, 2, 3, 4}, {2, 2});
TransposeLayer layer({0, -1});
Tensor output;

EXPECT_THROW(layer.run(input, output), std::invalid_argument);
}

TEST(TransposeLayerTest, LargeAxis) {
Tensor input = make_tensor<float>({1, 2, 3, 4}, {2, 2});
TransposeLayer layer({0, 2});
Tensor output;

EXPECT_THROW(layer.run(input, output), std::invalid_argument);
}

TEST(TransposeLayerTest, 4DTensorTranspose) {
std::vector<float> data(16);
std::iota(data.begin(), data.end(), 1.0f);
Tensor input = make_tensor<float>(data, {2, 2, 2, 2});
TransposeLayer layer({3, 1, 0, 2});
Tensor output;

layer.run(input, output);

ASSERT_EQ(output.get_shape(), Shape({2, 2, 2, 2}));
EXPECT_FLOAT_EQ(output.get<float>({0, 0, 0, 0}), 1.0f);
EXPECT_FLOAT_EQ(output.get<float>({1, 0, 0, 0}), 2.0f);
EXPECT_FLOAT_EQ(output.get<float>({0, 1, 0, 0}), 5.0f);
EXPECT_FLOAT_EQ(output.get<float>({1, 1, 0, 0}), 6.0f);
EXPECT_FLOAT_EQ(output.get<float>({0, 0, 0, 1}), 3.0f);
EXPECT_FLOAT_EQ(output.get<float>({1, 0, 0, 1}), 4.0f);
EXPECT_FLOAT_EQ(output.get<float>({0, 1, 0, 1}), 7.0f);
EXPECT_FLOAT_EQ(output.get<float>({1, 1, 0, 1}), 8.0f);
EXPECT_FLOAT_EQ(output.get<float>({0, 0, 1, 0}), 9.0f);
EXPECT_FLOAT_EQ(output.get<float>({1, 0, 1, 0}), 10.0f);
EXPECT_FLOAT_EQ(output.get<float>({0, 1, 1, 0}), 13.0f);
EXPECT_FLOAT_EQ(output.get<float>({1, 1, 1, 0}), 14.0f);
EXPECT_FLOAT_EQ(output.get<float>({0, 0, 1, 1}), 11.0f);
EXPECT_FLOAT_EQ(output.get<float>({1, 0, 1, 1}), 12.0f);
EXPECT_FLOAT_EQ(output.get<float>({0, 1, 1, 1}), 15.0f);
EXPECT_FLOAT_EQ(output.get<float>({1, 1, 1, 1}), 16.0f);
}

TEST(TransposeLayerTest, DefaultPermutation) {
Tensor input = make_tensor<float>({1, 2, 3, 4, 5, 6}, {2, 3});
TransposeLayer layer;
Tensor output;

layer.run(input, output);

ASSERT_EQ(output.get_shape(), Shape({3, 2}));
EXPECT_FLOAT_EQ(output.get<float>({0, 0}), 1.0f);
EXPECT_FLOAT_EQ(output.get<float>({1, 0}), 2.0f);
EXPECT_FLOAT_EQ(output.get<float>({2, 0}), 3.0f);
EXPECT_FLOAT_EQ(output.get<float>({0, 1}), 4.0f);
EXPECT_FLOAT_EQ(output.get<float>({1, 1}), 5.0f);
EXPECT_FLOAT_EQ(output.get<float>({2, 1}), 6.0f);
}

TEST(TransposeLayerTest, MatrixTranspose) {
Tensor input = make_tensor<float>({1, 2, 3, 4}, {2, 2});
TransposeLayer layer({1, 0});
Tensor output;

layer.run(input, output);

ASSERT_EQ(output.get_shape(), Shape({2, 2}));
EXPECT_FLOAT_EQ(output.get<float>({0, 0}), 1.0f);
EXPECT_FLOAT_EQ(output.get<float>({0, 1}), 3.0f);
EXPECT_FLOAT_EQ(output.get<float>({1, 0}), 2.0f);
EXPECT_FLOAT_EQ(output.get<float>({1, 1}), 4.0f);
}

TEST(TransposeLayerTest, 3DTensor) {
std::vector<float> data(24);
std::iota(data.begin(), data.end(), 1.0f);
Tensor input = make_tensor<float>(data, {2, 3, 4});
TransposeLayer layer({2, 0, 1});
Tensor output;

layer.run(input, output);

ASSERT_EQ(output.get_shape(), Shape({4, 2, 3}));
EXPECT_FLOAT_EQ(output.get<float>({0, 0, 0}), 1.0f);
EXPECT_FLOAT_EQ(output.get<float>({3, 1, 2}), 24.0f);
}
Loading