diff --git a/compute/cker/include/cker/Types.h b/compute/cker/include/cker/Types.h index 1d3120fa88c..35a5366d214 100644 --- a/compute/cker/include/cker/Types.h +++ b/compute/cker/include/cker/Types.h @@ -397,6 +397,12 @@ struct LeakyReluParams float alpha; }; +struct PadParams +{ + int32_t data[8]; + int32_t rank; +}; + enum class Order { kColMajor, diff --git a/compute/cker/include/cker/train/operation/Pad.h b/compute/cker/include/cker/train/operation/Pad.h new file mode 100644 index 00000000000..f3f4a9353f4 --- /dev/null +++ b/compute/cker/include/cker/train/operation/Pad.h @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2024 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __NNFW_CKER_TRAIN_OPERATION_PAD_H__ +#define __NNFW_CKER_TRAIN_OPERATION_PAD_H__ + +#include "cker/operation/Pad.h" + +namespace nnfw +{ +namespace cker +{ +namespace train +{ + +/* + * input_data will be transformed by PAD operation with padding options(such as constant C) to + * output_data + * + * input_data -> output_data + * [0,1] -> [C,C,C,C] + * [2,3] -> [C,0,1,C] + * -> [C,2,3,C] + * -> [C,C,C,C] + */ +/* + * input_data(backward_output_data) will be transformed by backward of PAD operation (Depad) with + * padding options to output_data(backward_input_data) + * + * input_data(backward_output_data) -> output_data(backward_input_data) + * [C,C,C,C] -> [0,1] + * [C,0,1,C] -> [2,3] + * [C,2,3,C] -> + * [C,C,C,C] -> + */ +template +inline void Depad(const int32_t *padding_data, int32_t pad_rank, const Shape &input_shape, + const T *input_data, const Shape &output_shape, T *output_data) +{ + using PaddingInfo = std::pair; + using PaddingList = std::vector; + + assert(output_shape.DimensionsCount() == input_shape.DimensionsCount()); + assert(output_shape.DimensionsCount() == pad_rank); + + PaddingList padding_list(pad_rank); + for (int32_t n = 0; n < pad_rank; ++n) + { + const int32_t *from = padding_data + (n * 2); + assert(from[0] >= 0 && from[1] >= 0); + padding_list[n] = {from[0], from[1]}; + } + for (int32_t i = 0; i < pad_rank; ++i) + { + assert(output_shape.Dims(i) == + input_shape.Dims(i) - padding_list[i].first - padding_list[i].second); + } + + // logical axis: row -> col -> plain -> cube + switch (pad_rank) + { + case 0: + case 1: + { + const int32_t out_row_len = output_shape.Dims(0); + const int32_t padding_left = padding_list[0].first; + std::memcpy(output_data, input_data + padding_left, out_row_len * sizeof(T)); + break; + } + case 2: // HW + { + const int32_t out_col_len = output_shape.Dims(0); + const int32_t out_row_len = output_shape.Dims(1); + const int32_t in_row_len = input_shape.Dims(1); + const int32_t padding_top = padding_list[0].first; + const int32_t padding_left = padding_list[1].first; + for (auto i = 0; i < out_col_len; ++i) + { + const auto in_offset = (i + padding_top) * in_row_len + padding_left; + const auto out_offset = i * out_row_len; + // copy a row of input data to output data + std::memcpy(output_data + out_offset, input_data + in_offset, out_row_len * sizeof(T)); + } + break; + } + case 3: // HWC + { + const int32_t out_plain_len = output_shape.Dims(0); + const int32_t out_col_len = output_shape.Dims(1); + const int32_t out_row_len = output_shape.Dims(2); + const int32_t out_plain_size = out_col_len * out_row_len; + const int32_t in_col_len = input_shape.Dims(1); + const int32_t in_row_len = input_shape.Dims(2); + const int32_t in_plain_size = in_col_len * in_row_len; + const int32_t padding_depth = padding_list[0].first; + const int32_t padding_top = padding_list[1].first; + const int32_t padding_left = padding_list[2].first; + for (auto d = 0; d < out_plain_len; ++d) + { + for (auto h = 0; h < out_col_len; ++h) + { + const auto in_offset = + (d + padding_depth) * in_plain_size + (h + padding_top) * in_row_len + (padding_left); + const auto out_offset = (d * out_plain_size) + (h * out_row_len); + // copy a row of input data to output data + std::memcpy(output_data + out_offset, input_data + in_offset, out_row_len * sizeof(T)); + } + } + break; + } + case 4: // NHWC + { + const int32_t out_cube_len = output_shape.Dims(0); + const int32_t out_plain_len = output_shape.Dims(1); + const int32_t out_col_len = output_shape.Dims(2); + const int32_t out_row_len = output_shape.Dims(3); + const int32_t out_plain_size = out_col_len * out_row_len; + const int32_t out_cube_size = out_plain_len * out_plain_size; + const int32_t in_plain_len = input_shape.Dims(1); + const int32_t in_col_len = input_shape.Dims(2); + const int32_t in_row_len = input_shape.Dims(3); + const int32_t in_plain_size = in_col_len * in_row_len; + const int32_t in_cube_size = in_plain_len * in_plain_size; + const int32_t padding_cube = padding_list[0].first; + const int32_t padding_depth = padding_list[1].first; + const int32_t padding_top = padding_list[2].first; + const int32_t padding_left = padding_list[3].first; + for (auto c = 0; c < out_cube_len; ++c) + { + for (auto d = 0; d < out_plain_len; ++d) + { + for (auto h = 0; h < out_col_len; ++h) + { + const auto in_offset = (c + padding_cube) * in_cube_size + + (d + padding_depth) * in_plain_size + + (h + padding_top) * in_row_len + (padding_left); + const auto out_offset = (c * out_cube_size) + (d * out_plain_size) + (h * out_row_len); + // copy a row of input data to output data + std::memcpy(output_data + out_offset, input_data + in_offset, out_row_len * sizeof(T)); + } + } + } + break; + } + default: + throw std::runtime_error("Padding for rank > 4 NYI"); + break; + } +} + +} // namespace train +} // namespace cker +} // namespace nnfw + +#endif // __NNFW_CKER_TRAIN_OPERATION_PAD_H__ diff --git a/compute/cker/src/train/Pad.test.cc b/compute/cker/src/train/Pad.test.cc new file mode 100644 index 00000000000..269534f12fa --- /dev/null +++ b/compute/cker/src/train/Pad.test.cc @@ -0,0 +1,318 @@ +/* + * Copyright (c) 2024 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include +#include + +namespace +{ +using namespace nnfw::cker; + +template class PadOpVerifier +{ +private: + const PadParams _op_params; + const Shape _in_shape; + const Shape _out_shape; + T _constant_value; + +public: + PadOpVerifier(const PadParams &op_params, const Shape &in_shape, const Shape &out_shape, + T constant_value) + : _op_params(op_params), _in_shape(in_shape), _out_shape(out_shape), + _constant_value(constant_value) + { + // DO NOTHING + } + +public: + void verifyForward(const std::vector input, const std::vector expected_output, + bool expect_eq = true) + { + assert(input.size() == _in_shape.FlatSize()); + assert(expected_output.size() == _out_shape.FlatSize()); + + std::vector cacluated_output(_out_shape.FlatSize()); + nnfw::cker::Pad(_op_params.data, _op_params.rank, _in_shape, input.data(), _out_shape, + cacluated_output.data(), &_constant_value); + + if (expect_eq) + EXPECT_EQ(expected_output, cacluated_output); + else + EXPECT_NE(expected_output, cacluated_output); + } + + void verifyBackward(const std::vector backward_output, + const std::vector expected_backward_input, bool expect_eq = true) + { + assert(backward_output.size() == _out_shape.FlatSize()); + assert(expected_backward_input.size() == _in_shape.FlatSize()); + + std::vector backward_input(_in_shape.FlatSize()); + nnfw::cker::train::Depad(_op_params.data, _op_params.rank, _out_shape, backward_output.data(), + _in_shape, backward_input.data()); + + if (expect_eq) + EXPECT_EQ(expected_backward_input, backward_input); + else + EXPECT_NE(expected_backward_input, backward_input); + } +}; + +} // namespace + +TEST(CKer_Operation, Pad) +{ + // NOTE: + // Since the pad operation is an operation that only copies memory + // (precisely where to copy memory), each input and output of forward/backward + // can be used as each other's output and input. + + // Pad rank 1 + { + nnfw::cker::PadParams op_param; + { + op_param.rank = 1; + op_param.data[0] = 1; + op_param.data[1] = 1; + } + float constant_value = 3.f; + + nnfw::cker::Shape in = {1}; + nnfw::cker::Shape out = {3}; + + PadOpVerifier verifier(op_param, in, out, constant_value); + + std::vector input = {1.f}; + std::vector expected_output = {3.f, 1.f, 3.f}; + verifier.verifyForward(input, expected_output); + verifier.verifyBackward(expected_output, input); + } + { + nnfw::cker::PadParams op_param; + { + op_param.rank = 1; + op_param.data[0] = 2; + op_param.data[1] = 2; + } + float constant_value = 1.f; + + nnfw::cker::Shape in = {3}; + nnfw::cker::Shape out = {7}; + + PadOpVerifier verifier(op_param, in, out, constant_value); + + std::vector input = {2.f, 3.f, 4.f}; + std::vector expected_output = {1.f, 1.f, 2.f, 3.f, 4.f, 1.f, 1.f}; + verifier.verifyForward(input, expected_output); + verifier.verifyBackward(expected_output, input); + } + + // Pad rank 2: HW + { + nnfw::cker::PadParams op_param; + { + op_param.rank = 2; + op_param.data[0] = 1; + op_param.data[1] = 1; + op_param.data[2] = 1; + op_param.data[3] = 1; + } + float constant_value = 3.f; + + nnfw::cker::Shape in = {1, 1}; + nnfw::cker::Shape out = {3, 3}; + + PadOpVerifier verifier(op_param, in, out, constant_value); + + float init_value = 1.f; + std::vector input = {init_value}; + std::vector expected_output(3 * 3, constant_value); + expected_output[expected_output.size() / 2] = init_value; + verifier.verifyForward(input, expected_output); + verifier.verifyBackward(expected_output, input); + } + { + nnfw::cker::PadParams op_param; + { + op_param.rank = 2; + op_param.data[0] = 3; + op_param.data[1] = 3; + op_param.data[2] = 3; + op_param.data[3] = 3; + } + float constant_value = 1.f; + + nnfw::cker::Shape in = {3, 3}; + nnfw::cker::Shape out = {9, 9}; + + PadOpVerifier verifier(op_param, in, out, constant_value); + + float init_value = 1.f; + std::vector input(3 * 3, init_value); + std::vector expected_output(9 * 9, constant_value); + for (auto i = -1; i <= 1; ++i) + { + for (auto j = -1; j <= 1; ++j) + { + size_t ind = (9 * i) + (expected_output.size() / 2 + j); + expected_output[ind] = init_value; + } + } + verifier.verifyForward(input, expected_output); + verifier.verifyBackward(expected_output, input); + } + + // Pad rank 3: HWC + { + nnfw::cker::PadParams op_param; + { + op_param.rank = 3; + op_param.data[0] = 1; + op_param.data[1] = 1; + op_param.data[2] = 1; + op_param.data[3] = 1; + op_param.data[4] = 1; + op_param.data[5] = 1; + } + float constant_value = 3.f; + + nnfw::cker::Shape in = {1, 1, 1}; + nnfw::cker::Shape out = {3, 3, 3}; + + PadOpVerifier verifier(op_param, in, out, constant_value); + + float init_value = 1.f; + std::vector input = {init_value}; + std::vector expected_output(3 * 3 * 3, constant_value); + expected_output[expected_output.size() / 2] = init_value; + verifier.verifyForward(input, expected_output); + verifier.verifyBackward(expected_output, input); + } + { + nnfw::cker::PadParams op_param; + { + op_param.rank = 3; + op_param.data[0] = 5; + op_param.data[1] = 5; + op_param.data[2] = 5; + op_param.data[3] = 5; + op_param.data[4] = 5; + op_param.data[5] = 5; + } + float constant_value = 7.f; + + nnfw::cker::Shape in = {3, 3, 3}; + nnfw::cker::Shape out = {13, 13, 13}; + + PadOpVerifier verifier(op_param, in, out, constant_value); + + float init_value = 5.f; + std::vector input(3 * 3 * 3, init_value); + std::vector expected_output(13 * 13 * 13, constant_value); + for (auto i = -1; i <= 1; ++i) + { + for (auto j = -1; j <= 1; ++j) + { + for (auto k = -1; k <= 1; ++k) + { + size_t ind = (13 * 13 * i) + (13 * j) + (expected_output.size() / 2 + k); + expected_output[ind] = init_value; + } + } + } + verifier.verifyForward(input, expected_output); + verifier.verifyBackward(expected_output, input); + } + + // Pad rank 4 + { + nnfw::cker::PadParams op_param; + { + op_param.rank = 4; + op_param.data[0] = 1; + op_param.data[1] = 1; + op_param.data[2] = 1; + op_param.data[3] = 1; + op_param.data[4] = 1; + op_param.data[5] = 1; + op_param.data[6] = 1; + op_param.data[7] = 1; + } + float constant_value = 3.f; + + nnfw::cker::Shape in = {1, 1, 1, 1}; + nnfw::cker::Shape out = {3, 3, 3, 3}; + + PadOpVerifier verifier(op_param, in, out, constant_value); + + float init_value = 1.f; + std::vector input = {init_value}; + std::vector expected_output(3 * 3 * 3 * 3, constant_value); + expected_output[expected_output.size() / 2] = init_value; + verifier.verifyForward(input, expected_output); + verifier.verifyBackward(expected_output, input); + } + { + nnfw::cker::PadParams op_param; + { + op_param.rank = 4; + op_param.data[0] = 7; + op_param.data[1] = 7; + op_param.data[2] = 7; + op_param.data[3] = 7; + op_param.data[4] = 7; + op_param.data[5] = 7; + op_param.data[6] = 7; + op_param.data[7] = 7; + } + float constant_value = 9.f; + + nnfw::cker::Shape in = {5, 5, 5, 5}; + nnfw::cker::Shape out = {19, 19, 19, 19}; + + PadOpVerifier verifier(op_param, in, out, constant_value); + + float init_value = 2.f; + std::vector input(5 * 5 * 5 * 5, init_value); + std::vector expected_output(19 * 19 * 19 * 19, constant_value); + for (auto i = -2; i <= 2; ++i) + { + for (auto j = -2; j <= 2; ++j) + { + for (auto k = -2; k <= 2; ++k) + { + for (auto l = -2; l <= 2; ++l) + { + size_t ind = + (19 * 19 * 19 * i) + (19 * 19 * j) + (19 * k) + (expected_output.size() / 2 + l); + expected_output[ind] = init_value; + } + } + } + } + verifier.verifyForward(input, expected_output); + verifier.verifyBackward(expected_output, input); + } + + // TODO: Add tests for more complex padding options +} + +// TODO: neg_Pad