diff --git a/app/Graph/build.cpp b/app/Graph/build.cpp index c4ee7fe9..b09838c6 100644 --- a/app/Graph/build.cpp +++ b/app/Graph/build.cpp @@ -101,7 +101,8 @@ void build_graph_linear(it_lab_ai::Graph& graph, it_lab_ai::Tensor& input, << '\n'; } auto pool_layer = - std::make_shared(shape, pooltype); + LayerFactory::createPoolingLayer(pooltype, shape, options); + layers.push_back(pool_layer); layerpostop.push_back(false); if (comments) std::cout << "PoolingLayer added to layers." << '\n'; @@ -408,8 +409,8 @@ ParseResult parse_json_model(RuntimeOptions options, << '\n'; } } else if (layer_type == "GlobalAveragePool") { - auto pool_layer = std::make_shared( - it_lab_ai::Shape({0, 0}), "average"); + auto pool_layer = LayerFactory::createPoolingLayer( + "average", it_lab_ai::Shape({0, 0}), options); layer = pool_layer; if (comments) { std::cout << "GlobalAveragePool layer added (will use input spatial " @@ -470,30 +471,9 @@ ParseResult parse_json_model(RuntimeOptions options, } } - auto pool_layer = - std::make_shared(shape, pooltype); - - try { - if (strides[0] != 2 || strides[1] != 2) { - pool_layer->setStrides(strides[0], strides[1]); - } - - if (pads[0] != 0 || pads[1] != 0 || pads[2] != 0 || pads[3] != 0) { - pool_layer->setPads(pads[0], pads[1], pads[2], pads[3]); - } + auto pool_layer = LayerFactory::createPoolingLayer( + pooltype, shape, options, strides, pads, dilations, ceil_mode); - if (dilations[0] != 1 || dilations[1] != 1) { - pool_layer->setDilations(dilations[0], dilations[1]); - } - - pool_layer->setCeilMode(ceil_mode); - - } catch (const std::exception& e) { - if (comments) { - std::cout << "Warning: Some pooling parameters not supported: " - << e.what() << '\n'; - } - } layer = pool_layer; } else if (layer_type.find("Flatten") != std::string::npos) { int axis = 1; diff --git a/app/Graph/build.hpp b/app/Graph/build.hpp index 0ddb32e4..5dbd3a30 100644 --- a/app/Graph/build.hpp +++ b/app/Graph/build.hpp @@ -34,6 +34,7 @@ #include "layers/TransposeLayer.hpp" #include "layers_oneDNN/ConvLayer.hpp" #include "layers_oneDNN/EWLayer.hpp" +#include "layers_oneDNN/PoolingLayer.hpp" extern std::unordered_map model_paths; @@ -99,6 +100,19 @@ class LayerFactory { return std::make_shared(step, pads, dilations, kernel, bias, group, useLegacyImpl); } + + static std::shared_ptr createPoolingLayer( + const std::string& PoolType, const Shape& shape, + const RuntimeOptions& options, const Shape& strides = {2, 2}, + const Shape& pads = {0, 0, 0, 0}, const Shape& dilations = {1, 1}, + bool ceil_mode = false) { + if (options.backend == Backend::kOneDnn) { + return std::make_shared( + shape, strides, pads, dilations, ceil_mode, PoolType); + } + return std::make_shared(shape, strides, pads, dilations, + ceil_mode, PoolType); + } }; } // namespace it_lab_ai diff --git a/include/layers/Shape.hpp b/include/layers/Shape.hpp index e8e4b11e..22c7d4ac 100644 --- a/include/layers/Shape.hpp +++ b/include/layers/Shape.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -39,6 +40,16 @@ class Shape { } [[nodiscard]] size_t dims() const noexcept { return dims_.size(); } [[nodiscard]] size_t get_index(const std::vector& coords) const; + [[nodiscard]] std::string to_string() const { + std::stringstream ss; + ss << "("; + for (size_t i = 0; i < dims_.size(); ++i) { + if (i > 0) ss << ", "; + ss << dims_[i]; + } + ss << ")"; + return ss.str(); + } bool operator==(const Shape& other) const { if (dims_.size() != other.dims_.size()) return false; for (size_t i = 0; i < dims_.size(); ++i) { diff --git a/include/layers_oneDNN/PoolingLayer.hpp b/include/layers_oneDNN/PoolingLayer.hpp new file mode 100644 index 00000000..123d58ca --- /dev/null +++ b/include/layers_oneDNN/PoolingLayer.hpp @@ -0,0 +1,87 @@ +#pragma once + +#include +#include +#include +#include + +#include "layers/Layer.hpp" + +namespace it_lab_ai { + +class PoolingLayerOneDnn : public Layer { + public: + explicit PoolingLayerOneDnn(const Shape& pooling_shape, + const Shape& strides = {2, 2}, + const Shape& pads = {0, 0, 0, 0}, + const Shape& dilations = {1, 1}, + bool ceil_mode = false, + std::string pooling_type = "average") + : Layer(kPooling), + poolingShape_(pooling_shape), + strides_(strides), + pads_(pads), + dilations_(dilations), + ceil_mode_(ceil_mode), + poolingType_(std::move(pooling_type)), + engine_(std::make_unique(dnnl::engine::kind::cpu, 0)), + stream_(std::make_unique(*engine_)) {} + + void run(const std::vector& input, + std::vector& output) override; + + void setStrides(size_t h, size_t w) { + strides_ = {h, w}; + initialized_ = false; + } + + void setPads(size_t top, size_t bottom, size_t left, size_t right) { + pads_ = {top, bottom, left, right}; + initialized_ = false; + } + + void setDilations(size_t h, size_t w) { + dilations_ = {h, w}; + initialized_ = false; + } + + void setCeilMode(bool ceil_mode) { + ceil_mode_ = ceil_mode; + initialized_ = false; + } + +#ifdef ENABLE_STATISTIC_WEIGHTS + Tensor get_weights() override { + std::vector v = {0}; + Tensor a = make_tensor(v); + return a; + } +#endif + + private: + void initialize_onednn(const Shape& shape, Type data_type); + [[nodiscard]] dnnl::algorithm get_PoolType() const; + static void validate_input(const std::vector& input); + [[nodiscard]] static dnnl::memory::data_type get_dnnl_data_type(Type type); + [[nodiscard]] Shape calculate_output_shape(const Shape& input_shape) const; + + Shape poolingShape_; + Shape strides_; + Shape pads_; + Shape dilations_; + bool ceil_mode_; + std::string poolingType_; + + bool initialized_ = false; + Shape last_shape_; + Type last_type_; + + std::unique_ptr engine_; + std::unique_ptr stream_; + std::unique_ptr pool_prim_; + dnnl::memory::desc src_memory_desc_; + dnnl::memory::desc dst_memory_desc_; + Shape output_shape_; +}; + +} // namespace it_lab_ai \ No newline at end of file diff --git a/src/layers_oneDNN/ConvLayer.cpp b/src/layers_oneDNN/ConvLayer.cpp index 8e3bc113..51f4cd8e 100644 --- a/src/layers_oneDNN/ConvLayer.cpp +++ b/src/layers_oneDNN/ConvLayer.cpp @@ -369,8 +369,9 @@ void ConvLayerOneDnn::initialize_special_conv(const Shape& input_shape, if (has_bias) { bias_md = dnnl::memory::desc( {static_cast(bias_.get_shape()[0])}, dt, - dnnl::memory::format_tag::any); + dnnl::memory::format_tag::a); } + dnnl::convolution_forward::primitive_desc conv_pd = has_bias ? dnnl::convolution_forward::primitive_desc( *engine_, dnnl::prop_kind::forward_inference, @@ -391,20 +392,21 @@ void ConvLayerOneDnn::initialize_special_conv(const Shape& input_shape, if (data_type == Type::kFloat) { const std::vector& kernel_data = *kernel_.as(); + size_t kh = k_shape[0]; size_t kw = k_shape[1]; size_t kic = k_shape[2]; size_t koc = k_shape[3]; std::vector reordered(koc * kic * kh * kw); - size_t idx = 0; - for (size_t oc = 0; oc < koc; oc++) { - for (size_t ic = 0; ic < kic; ic++) { - for (size_t h = 0; h < kh; h++) { - for (size_t w = 0; w < kw; w++) { + for (size_t oc = 0; oc < koc; ++oc) { + for (size_t ic = 0; ic < kic; ++ic) { + for (size_t h = 0; h < kh; ++h) { + for (size_t w = 0; w < kw; ++w) { size_t src_idx = ((h * kw + w) * kic + ic) * koc + oc; - reordered[idx++] = kernel_data[src_idx]; + size_t dst_idx = ((oc * kic + ic) * kh + h) * kw + w; + reordered[dst_idx] = kernel_data[src_idx]; } } } @@ -421,14 +423,13 @@ void ConvLayerOneDnn::initialize_special_conv(const Shape& input_shape, size_t koc = k_shape[3]; std::vector reordered(koc * kic * kh * kw); - size_t idx = 0; - - for (size_t oc = 0; oc < koc; oc++) { - for (size_t ic = 0; ic < kic; ic++) { - for (size_t h = 0; h < kh; h++) { - for (size_t w = 0; w < kw; w++) { + for (size_t oc = 0; oc < koc; ++oc) { + for (size_t ic = 0; ic < kic; ++ic) { + for (size_t h = 0; h < kh; ++h) { + for (size_t w = 0; w < kw; ++w) { size_t src_idx = ((h * kw + w) * kic + ic) * koc + oc; - reordered[idx++] = static_cast(kernel_data_int[src_idx]); + size_t dst_idx = ((oc * kic + ic) * kh + h) * kw + w; + reordered[dst_idx] = static_cast(kernel_data_int[src_idx]); } } } @@ -438,6 +439,22 @@ void ConvLayerOneDnn::initialize_special_conv(const Shape& input_shape, reordered.size() * sizeof(float)); } + if (has_bias) { + if (data_type == Type::kFloat) { + const std::vector& bias_data = *bias_.as(); + std::memcpy(bias_memory_.get_data_handle(), bias_data.data(), + bias_data.size() * sizeof(float)); + } else if (data_type == Type::kInt) { + const std::vector& bias_data_int = *bias_.as(); + std::vector float_bias(bias_data_int.size()); + std::transform(bias_data_int.begin(), bias_data_int.end(), + float_bias.begin(), + [](int val) { return static_cast(val); }); + std::memcpy(bias_memory_.get_data_handle(), float_bias.data(), + float_bias.size() * sizeof(float)); + } + } + conv_prim_ = std::make_unique(conv_pd); initialized_ = true; @@ -494,8 +511,8 @@ void ConvLayerOneDnn::run_special_conv(const std::vector& input, Shape output_shape = get_output_shape(input_shape); if (data_type == Type::kFloat) { - std::vector output_data(dst_memory_.get_desc().get_size() / - sizeof(float)); + size_t output_size = dst_memory_.get_desc().get_size() / sizeof(float); + std::vector output_data(output_size); std::memcpy(output_data.data(), dst_memory_.get_data_handle(), output_data.size() * sizeof(float)); output[0] = make_tensor(output_data, output_shape); diff --git a/src/layers_oneDNN/PoolingLayer.cpp b/src/layers_oneDNN/PoolingLayer.cpp new file mode 100644 index 00000000..088e2a78 --- /dev/null +++ b/src/layers_oneDNN/PoolingLayer.cpp @@ -0,0 +1,237 @@ +#include "layers_oneDNN/PoolingLayer.hpp" + +#include +#include + +namespace it_lab_ai { + +void PoolingLayerOneDnn::run(const std::vector& input, + std::vector& output) { + validate_input(input); + + const Tensor& in = input[0]; + Type type = in.get_type(); + + bool need_reinit = + !initialized_ || last_type_ != type || last_shape_ != in.get_shape(); + + if (need_reinit) { + initialize_onednn(in.get_shape(), type); + } + output.resize(1); + + if (type == Type::kFloat) { + const auto& src = *in.as(); + std::vector dst(output_shape_.count()); + + dnnl::memory src_mem(src_memory_desc_, *engine_, + const_cast(src.data())); + dnnl::memory dst_mem(dst_memory_desc_, *engine_, dst.data()); + + pool_prim_->execute(*stream_, + {{DNNL_ARG_SRC, src_mem}, {DNNL_ARG_DST, dst_mem}}); + + stream_->wait(); + output[0] = make_tensor(dst, output_shape_); + } else if (type == Type::kInt) { + const auto& src = *in.as(); + std::vector dst(output_shape_.count()); + + dnnl::memory src_mem(src_memory_desc_, *engine_, + const_cast(src.data())); + dnnl::memory dst_mem(dst_memory_desc_, *engine_, dst.data()); + + pool_prim_->execute(*stream_, + {{DNNL_ARG_SRC, src_mem}, {DNNL_ARG_DST, dst_mem}}); + + stream_->wait(); + output[0] = make_tensor(dst, output_shape_); + } +} + +void PoolingLayerOneDnn::validate_input(const std::vector& input) { + if (input.size() != 1) { + throw std::runtime_error( + "PoolingLayerOneDnn: Expected exactly 1 input tensor"); + } + + const auto& shape = input[0].get_shape(); + if (shape.dims() < 2) { + throw std::runtime_error( + "PoolingLayerOneDnn: Input must have at least 2 dimensions"); + } +} + +Shape PoolingLayerOneDnn::calculate_output_shape( + const Shape& input_shape) const { + Shape output_shape = input_shape; + + if (poolingShape_[0] == 0 && poolingShape_[1] == 0) { + for (size_t i = 0; i < std::min(static_cast(2), input_shape.dims()); + ++i) { + output_shape[i] = input_shape[i]; + } + for (size_t i = 2; i < input_shape.dims(); ++i) { + output_shape[i] = 1; + } + return output_shape; + } + + size_t spatial_dims = poolingShape_.dims(); + + for (size_t i = 0; i < spatial_dims; ++i) { + size_t input_idx = input_shape.dims() - spatial_dims + i; + size_t input_size = input_shape[input_idx]; + size_t kernel_size = poolingShape_[i]; + size_t stride = strides_[i]; + + size_t pad_front = pads_[i]; + size_t pad_back = pads_[i + spatial_dims]; + size_t dilation = dilations_[i]; + + size_t effective_kernel_size = (kernel_size - 1) * dilation + 1; + + size_t output_size; + if (ceil_mode_) { + output_size = static_cast(std::ceil( + static_cast(input_size + pad_front + pad_back - + effective_kernel_size) / + static_cast(stride))) + + 1; + } else { + output_size = static_cast(std::floor( + static_cast(input_size + pad_front + pad_back - + effective_kernel_size) / + static_cast(stride))) + + 1; + } + + output_shape[input_idx] = output_size; + } + + return output_shape; +} + +void PoolingLayerOneDnn::initialize_onednn(const Shape& shape, Type data_type) { + output_shape_ = calculate_output_shape(shape); + + std::vector src_dims; + std::vector dst_dims; + + if (shape.dims() == 4) { + for (size_t i = 0; i < 4; ++i) { + src_dims.push_back(static_cast(shape.at(i))); + } + for (size_t i = 0; i < 4; ++i) { + dst_dims.push_back(static_cast(output_shape_.at(i))); + } + } else if (shape.dims() == 3) { + src_dims.push_back(1); + dst_dims.push_back(1); + for (size_t i = 0; i < 3; ++i) { + src_dims.push_back(static_cast(shape.at(i))); + dst_dims.push_back(static_cast(output_shape_.at(i))); + } + } else if (shape.dims() == 2) { + src_dims = {1, 1, static_cast(shape[0]), + static_cast(shape[1])}; + dst_dims = {1, 1, static_cast(output_shape_[0]), + static_cast(output_shape_[1])}; + } else { + throw std::runtime_error("Unsupported shape dimensions for pooling: " + + std::to_string(shape.dims())); + } + + auto dnnl_type = get_dnnl_data_type(data_type); + + src_memory_desc_ = + dnnl::memory::desc(src_dims, dnnl_type, dnnl::memory::format_tag::nchw); + dst_memory_desc_ = + dnnl::memory::desc(dst_dims, dnnl_type, dnnl::memory::format_tag::nchw); + + dnnl::memory::dims strides = {static_cast(strides_[0]), + static_cast(strides_[1])}; + + dnnl::memory::dims kernel; + bool is_global_pool = (poolingShape_[0] == 0 && poolingShape_[1] == 0); + + if (is_global_pool) { + kernel = {static_cast(src_dims[2]), + static_cast(src_dims[3])}; + strides = {1, 1}; + } else { + kernel = {static_cast(poolingShape_[0]), + static_cast(poolingShape_[1])}; + } + + dnnl::memory::dims dilations = { + static_cast(dilations_[0] - 1), + static_cast(dilations_[1] - 1)}; + + dnnl::memory::dims padding_l = {static_cast(pads_[0]), + static_cast(pads_[2])}; + + dnnl::memory::dims padding_r = {static_cast(pads_[1]), + static_cast(pads_[3])}; + + if (ceil_mode_ && !is_global_pool) { + for (size_t i = 0; i < 2; ++i) { + auto input_size = static_cast(src_dims[2 + i]); + auto kernel_size = static_cast(kernel[i]); + auto stride = static_cast(strides[i]); + size_t dilation = static_cast(dilations[i]) + 1; + auto pad_front = static_cast(padding_l[i]); + auto pad_back = static_cast(padding_r[i]); + size_t effective_kernel = (kernel_size - 1) * dilation + 1; + auto output_size = static_cast(dst_dims[2 + i]); + size_t needed_pad_back = (output_size - 1) * stride + effective_kernel - + input_size - pad_front; + + if (needed_pad_back > pad_back) { + padding_r[i] = static_cast(needed_pad_back); + } + } + } + + try { + dnnl::pooling_forward::primitive_desc pool_pd( + *engine_, dnnl::prop_kind::forward_inference, get_PoolType(), + src_memory_desc_, dst_memory_desc_, strides, kernel, dilations, + padding_l, padding_r); + + pool_prim_ = std::make_unique(pool_pd); + } catch (const dnnl::error& e) { + std::cerr << "Error creating pooling primitive: " << e.what() << '\n'; + throw std::runtime_error("Failed to create pooling primitive: " + + std::string(e.what())); + } + + last_shape_ = shape; + last_type_ = data_type; + initialized_ = true; +} + +dnnl::memory::data_type PoolingLayerOneDnn::get_dnnl_data_type(Type type) { + switch (type) { + case Type::kFloat: + return dnnl::memory::data_type::f32; + case Type::kInt: + return dnnl::memory::data_type::s32; + default: + throw std::runtime_error("Unsupported data type for oneDNN"); + } +} + +dnnl::algorithm PoolingLayerOneDnn::get_PoolType() const { + if (poolingType_ == "average" || poolingType_ == "Average") { + return dnnl::algorithm::pooling_avg_include_padding; + } + if (poolingType_ == "max" || poolingType_ == "Max") { + return dnnl::algorithm::pooling_max; + } + + throw std::invalid_argument("Unsupported pooling type for oneDNN: " + + poolingType_); +} + +} // namespace it_lab_ai \ No newline at end of file diff --git a/test/single_layer/test_convlayer_onednn.cpp b/test/single_layer_onednn_version/test_convlayer_onednn.cpp similarity index 78% rename from test/single_layer/test_convlayer_onednn.cpp rename to test/single_layer_onednn_version/test_convlayer_onednn.cpp index b54e9692..495203c5 100644 --- a/test/single_layer/test_convlayer_onednn.cpp +++ b/test/single_layer_onednn_version/test_convlayer_onednn.cpp @@ -563,3 +563,155 @@ TEST(convlayer_onednn, int_output_processing) { EXPECT_EQ(val, 9); } } + +TEST(convlayer_onednn, int_bias_processing_special_conv) { + std::vector kernel_data(3 * 3 * 1 * 2, 1); + std::vector bias_data = {5, -3}; + + Tensor kernel = make_tensor(kernel_data, Shape({3, 3, 1, 2})); + Tensor bias = make_tensor(bias_data, Shape({2})); + + ConvLayerOneDnn layer(1, 0, 1, kernel, bias, 1, true); + + std::vector input_data(1 * 1 * 4 * 4, 2); + Tensor input = make_tensor(input_data, Shape({1, 1, 4, 4})); + Tensor output; + std::vector in{input}; + std::vector out{output}; + + EXPECT_NO_THROW(layer.run(in, out)); + + EXPECT_EQ(out[0].get_type(), Type::kInt); + + auto output_vals = *out[0].as(); + EXPECT_EQ(output_vals.size(), 8); + for (size_t i = 0; i < 4; ++i) { + EXPECT_EQ(output_vals[i], 23); + } + for (size_t i = 4; i < 8; ++i) { + EXPECT_EQ(output_vals[i], 15); + } +} + +TEST(convlayer_onednn, float_bias_processing_special_conv) { + std::vector kernel_data(3 * 3 * 1 * 2, 1.0f); + std::vector bias_data = {2.5f, -1.5f}; + + Tensor kernel = make_tensor(kernel_data, Shape({3, 3, 1, 2})); + Tensor bias = make_tensor(bias_data, Shape({2})); + + ConvLayerOneDnn layer(1, 0, 1, kernel, bias, 1, true); + + std::vector input_data(1 * 1 * 4 * 4, 1.5f); + Tensor input = make_tensor(input_data, Shape({1, 1, 4, 4})); + Tensor output; + std::vector in{input}; + std::vector out{output}; + + EXPECT_NO_THROW(layer.run(in, out)); + + EXPECT_EQ(out[0].get_type(), Type::kFloat); + + auto output_vals = *out[0].as(); + EXPECT_EQ(output_vals.size(), 8); + for (size_t i = 0; i < 4; ++i) { + EXPECT_NEAR(output_vals[i], 13.5f + 2.5f, 1e-5f); + } + for (size_t i = 4; i < 8; ++i) { + EXPECT_NEAR(output_vals[i], 13.5f - 1.5f, 1e-5f); + } +} + +TEST(convlayer_onednn, empty_bias_special_conv) { + std::vector kernel_data(3 * 3 * 1 * 2, 1.0f); + + Tensor kernel = make_tensor(kernel_data, Shape({3, 3, 1, 2})); + Tensor bias = make_tensor({}, Shape({0})); + + ConvLayerOneDnn layer(1, 0, 1, kernel, bias, 1, true); + + std::vector input_data(1 * 1 * 4 * 4, 2.0f); + Tensor input = make_tensor(input_data, Shape({1, 1, 4, 4})); + Tensor output; + std::vector in{input}; + std::vector out{output}; + + EXPECT_NO_THROW(layer.run(in, out)); +} + +TEST(convlayer_onednn, negative_bias_values) { + std::vector kernel_data(3 * 3 * 1 * 1, 1.0f); + std::vector bias_data = {-10.0f}; + + Tensor kernel = make_tensor(kernel_data, Shape({3, 3, 1, 1})); + Tensor bias = make_tensor(bias_data, Shape({1})); + + ConvLayerOneDnn layer(1, 0, 1, kernel, bias, 1, true); + + std::vector input_data(1 * 1 * 4 * 4, 1.0f); + Tensor input = make_tensor(input_data, Shape({1, 1, 4, 4})); + Tensor output; + std::vector in{input}; + std::vector out{output}; + + EXPECT_NO_THROW(layer.run(in, out)); + + auto output_vals = *out[0].as(); + EXPECT_NEAR(output_vals[0], -1.0f, 1e-5f); +} + +TEST(convlayer_onednn, large_bias_values_int) { + std::vector kernel_data(3 * 3 * 1 * 1, 1); + std::vector bias_data = {1000}; + + Tensor kernel = make_tensor(kernel_data, Shape({3, 3, 1, 1})); + Tensor bias = make_tensor(bias_data, Shape({1})); + + ConvLayerOneDnn layer(1, 0, 1, kernel, bias, 1, true); + + std::vector input_data(1 * 1 * 4 * 4, 1); + Tensor input = make_tensor(input_data, Shape({1, 1, 4, 4})); + Tensor output; + std::vector in{input}; + std::vector out{output}; + + EXPECT_NO_THROW(layer.run(in, out)); + + auto output_vals = *out[0].as(); + EXPECT_EQ(output_vals[0], 1009); +} + +TEST(convlayer_onednn, bias_with_different_conv_modes) { + std::vector kernel_data(3 * 3 * 1 * 2, 1.0f); + std::vector bias_data = {5.0f, 10.0f}; + + Tensor kernel = make_tensor(kernel_data, Shape({2, 1, 3, 3})); + Tensor bias = make_tensor(bias_data, Shape({2})); + + { + ConvLayerOneDnn layer_normal(1, 0, 1, kernel, bias, 1, false); + + std::vector input_data(1 * 1 * 4 * 4, 2.0f); + Tensor input = make_tensor(input_data, Shape({1, 1, 4, 4})); + Tensor output_normal; + std::vector in{input}; + std::vector out{output_normal}; + + EXPECT_NO_THROW(layer_normal.run(in, out)); + } + + { + std::vector kernel_data_hwio(3 * 3 * 1 * 2, 1.0f); + Tensor kernel_hwio = make_tensor(kernel_data_hwio, Shape({3, 3, 1, 2})); + + ConvLayerOneDnn layer_special(1, 0, 1, kernel_hwio, bias, 1, true); + + std::vector input_data(1 * 1 * 4 * 4, 2.0f); + Tensor input = make_tensor(input_data, Shape({1, 1, 4, 4})); + Tensor output_special; + std::vector in{input}; + std::vector out{output_special}; + + EXPECT_NO_THROW(layer_special.run(in, out)); + } +} diff --git a/test/single_layer/test_ewlayer_onednn.cpp b/test/single_layer_onednn_version/test_ewlayer_onednn.cpp similarity index 100% rename from test/single_layer/test_ewlayer_onednn.cpp rename to test/single_layer_onednn_version/test_ewlayer_onednn.cpp diff --git a/test/single_layer_onednn_version/test_poolinglayer_onednn.cpp b/test/single_layer_onednn_version/test_poolinglayer_onednn.cpp new file mode 100644 index 00000000..45c4195e --- /dev/null +++ b/test/single_layer_onednn_version/test_poolinglayer_onednn.cpp @@ -0,0 +1,478 @@ +#include +#include +#include +#include + +#include "gtest/gtest.h" +#include "layers/PoolingLayer.hpp" +#include "layers_oneDNN/PoolingLayer.hpp" + +using namespace it_lab_ai; + +TEST(poolinglayer_onednn, max_pooling_basic_float) { + PoolingLayerOneDnn layer({2, 2}, {2, 2}, {0, 0, 0, 0}, {1, 1}, false, "max"); + + std::vector input_data = {1.0F, 2.0F, 3.0F, 4.0F}; + Tensor input = make_tensor(input_data, Shape({1, 1, 2, 2})); + Tensor output; + + std::vector in{input}; + std::vector out{output}; + layer.run(in, out); + + auto output_data = *out[0].as(); + std::vector expected = {4.0F}; + + ASSERT_EQ(output_data.size(), expected.size()); + for (size_t i = 0; i < output_data.size(); i++) { + EXPECT_NEAR(output_data[i], expected[i], 1e-5); + } +} + +TEST(poolinglayer_onednn, max_pooling_basic_int) { + PoolingLayerOneDnn layer({2, 2}, {2, 2}, {0, 0, 0, 0}, {1, 1}, false, "max"); + + std::vector input_data = {1, 2, 3, 4}; + Tensor input = make_tensor(input_data, Shape({1, 1, 2, 2})); + Tensor output; + + std::vector in{input}; + std::vector out{output}; + layer.run(in, out); + + auto output_data = *out[0].as(); + std::vector expected = {4}; + + ASSERT_EQ(output_data.size(), expected.size()); + for (size_t i = 0; i < output_data.size(); i++) { + EXPECT_EQ(output_data[i], expected[i]); + } +} + +TEST(poolinglayer_onednn, average_pooling_basic_float) { + PoolingLayerOneDnn layer({2, 2}, {2, 2}, {0, 0, 0, 0}, {1, 1}, false, + "average"); + + std::vector input_data = {1.0F, 2.0F, 3.0F, 4.0F}; + Tensor input = make_tensor(input_data, Shape({1, 1, 2, 2})); + Tensor output; + + std::vector in{input}; + std::vector out{output}; + layer.run(in, out); + + auto output_data = *out[0].as(); + std::vector expected = {2.5F}; + + ASSERT_EQ(output_data.size(), expected.size()); + for (size_t i = 0; i < output_data.size(); i++) { + EXPECT_NEAR(output_data[i], expected[i], 1e-5); + } +} + +TEST(poolinglayer_onednn, max_pooling_multichannel) { + PoolingLayerOneDnn layer({2, 2}, {2, 2}, {0, 0, 0, 0}, {1, 1}, false, "max"); + + std::vector input_data(2 * 4 * 4); + for (size_t i = 0; i < input_data.size(); i++) { + input_data[i] = static_cast(i); + } + + Tensor input = make_tensor(input_data, Shape({1, 2, 4, 4})); + Tensor output; + + std::vector in{input}; + std::vector out{output}; + layer.run(in, out); + + auto output_data = *out[0].as(); + Shape output_shape = out[0].get_shape(); + + EXPECT_EQ(output_shape, Shape({1, 2, 2, 2})); + + EXPECT_NEAR(output_data[0], 5.0F, 1e-5); + EXPECT_NEAR(output_data[1], 7.0F, 1e-5); + EXPECT_NEAR(output_data[2], 13.0F, 1e-5); + EXPECT_NEAR(output_data[3], 15.0F, 1e-5); +} + +TEST(poolinglayer_onednn, max_pooling_with_padding) { + PoolingLayerOneDnn layer({3, 3}, {1, 1}, {1, 1, 1, 1}, {1, 1}, false, "max"); + + std::vector input_data = {1.0F, 2.0F, 3.0F, 4.0F}; + Tensor input = make_tensor(input_data, Shape({1, 1, 2, 2})); + Tensor output; + + std::vector in{input}; + std::vector out{output}; + layer.run(in, out); + + auto output_data = *out[0].as(); + Shape output_shape = out[0].get_shape(); + EXPECT_EQ(output_shape, Shape({1, 1, 2, 2})); +} + +TEST(poolinglayer_onednn, average_pooling_global) { + PoolingLayerOneDnn layer({0, 0}, {2, 2}, {0, 0, 0, 0}, {1, 1}, false, + "average"); + + std::vector input_data = {1.0F, 2.0F, 3.0F, 4.0F, 5.0F, + 6.0F, 7.0F, 8.0F, 9.0F}; + Tensor input = make_tensor(input_data, Shape({1, 1, 3, 3})); + Tensor output; + + std::vector in{input}; + std::vector out{output}; + layer.run(in, out); + + auto output_data = *out[0].as(); + Shape output_shape = out[0].get_shape(); + + EXPECT_EQ(output_shape, Shape({1, 1, 1, 1})); + + float expected = + (1.0F + 2.0F + 3.0F + 4.0F + 5.0F + 6.0F + 7.0F + 8.0F + 9.0F) / 9.0F; + EXPECT_NEAR(output_data[0], expected, 1e-5); +} + +TEST(poolinglayer_onednn, stride_greater_than_kernel) { + PoolingLayerOneDnn layer({2, 2}, {3, 3}, {0, 0, 0, 0}, {1, 1}, false, "max"); + + std::vector input_data(25); + for (size_t i = 0; i < 25; i++) { + input_data[i] = static_cast(i); + } + + Tensor input = make_tensor(input_data, Shape({1, 1, 5, 5})); + Tensor output; + + std::vector in{input}; + std::vector out{output}; + layer.run(in, out); + + auto output_data = *out[0].as(); + Shape output_shape = out[0].get_shape(); + + EXPECT_EQ(output_shape, Shape({1, 1, 2, 2})); +} + +TEST(poolinglayer_onednn, dilation) { + PoolingLayerOneDnn layer({2, 2}, {1, 1}, {0, 0, 0, 0}, {2, 2}, false, "max"); + + std::vector input_data(25); + for (size_t i = 0; i < 25; i++) { + input_data[i] = static_cast(i); + } + + Tensor input = make_tensor(input_data, Shape({1, 1, 5, 5})); + Tensor output; + + std::vector in{input}; + std::vector out{output}; + layer.run(in, out); + + auto output_data = *out[0].as(); + Shape output_shape = out[0].get_shape(); + EXPECT_EQ(output_shape, Shape({1, 1, 3, 3})); +} + +TEST(poolinglayer_onednn, compare_with_naive_max_pooling) { + PoolingLayerOneDnn onednn_layer({2, 2}, {2, 2}, {0, 0, 0, 0}, {1, 1}, false, + "max"); + PoolingLayer naive_layer({2, 2}, {2, 2}, {0, 0, 0, 0}, {1, 1}, false, "max"); + + std::vector input_data(16); + for (size_t i = 0; i < 16; i++) { + input_data[i] = static_cast(i); + } + + Tensor input_tensor = make_tensor(input_data, Shape({1, 1, 4, 4})); + + Tensor onednn_output; + std::vector onednn_in{input_tensor}; + std::vector onednn_out{onednn_output}; + onednn_layer.run(onednn_in, onednn_out); + auto onednn_result = *onednn_out[0].as(); + + Tensor naive_output; + std::vector naive_in{input_tensor}; + std::vector naive_out{naive_output}; + naive_layer.run(naive_in, naive_out); + auto naive_result = *naive_out[0].as(); + + ASSERT_EQ(onednn_result.size(), naive_result.size()); + for (size_t i = 0; i < onednn_result.size(); i++) { + EXPECT_NEAR(onednn_result[i], naive_result[i], 1e-5); + } +} + +TEST(poolinglayer_onednn, invalid_input_tensors) { + PoolingLayerOneDnn layer({2, 2}, {2, 2}, {0, 0, 0, 0}, {1, 1}, false, "max"); + + Tensor input1 = make_tensor({1.0F, 2.0F}); + Tensor input2 = make_tensor({3.0F, 4.0F}); + Tensor output; + + std::vector in{input1, input2}; + std::vector out{output}; + + EXPECT_THROW({ layer.run(in, out); }, std::runtime_error); +} + +TEST(poolinglayer_onednn, invalid_input_dimensions) { + PoolingLayerOneDnn layer({2, 2}, {2, 2}, {0, 0, 0, 0}, {1, 1}, false, "max"); + + Tensor input = make_tensor({1.0F}); + Tensor output; + + std::vector in{input}; + std::vector out{output}; + + EXPECT_THROW({ layer.run(in, out); }, std::runtime_error); +} + +TEST(poolinglayer_onednn, reinitialization_different_types) { + PoolingLayerOneDnn layer({2, 2}, {2, 2}, {0, 0, 0, 0}, {1, 1}, false, "max"); + + { + Tensor input = + make_tensor({1.0F, 2.0F, 3.0F, 4.0F}, Shape({1, 1, 2, 2})); + Tensor output; + std::vector in{input}; + std::vector out{output}; + + EXPECT_NO_THROW(layer.run(in, out)); + auto result = *out[0].as(); + EXPECT_EQ(result.size(), 1); + } + + { + Tensor input = make_tensor({1, 2, 3, 4}, Shape({1, 1, 2, 2})); + Tensor output; + std::vector in{input}; + std::vector out{output}; + + EXPECT_NO_THROW(layer.run(in, out)); + auto result = *out[0].as(); + EXPECT_EQ(result.size(), 1); + } + + { + Tensor input = + make_tensor({5.0F, 6.0F, 7.0F, 8.0F}, Shape({1, 1, 2, 2})); + Tensor output; + std::vector in{input}; + std::vector out{output}; + + EXPECT_NO_THROW(layer.run(in, out)); + auto result = *out[0].as(); + EXPECT_EQ(result.size(), 1); + } +} + +TEST(poolinglayer_onednn, different_shapes_same_layer) { + PoolingLayerOneDnn layer({2, 2}, {2, 2}, {0, 0, 0, 0}, {1, 1}, false, "max"); + + { + Tensor input = + make_tensor({1.0F, 2.0F, 3.0F, 4.0F}, Shape({1, 1, 2, 2})); + Tensor output; + std::vector in{input}; + std::vector out{output}; + + layer.run(in, out); + auto result = *out[0].as(); + EXPECT_EQ(result.size(), 1); + } + + { + std::vector input_data(16); + for (size_t i = 0; i < 16; i++) input_data[i] = static_cast(i); + + Tensor input = make_tensor(input_data, Shape({1, 1, 4, 4})); + Tensor output; + std::vector in{input}; + std::vector out{output}; + + EXPECT_NO_THROW(layer.run(in, out)); + auto result = *out[0].as(); + EXPECT_EQ(result.size(), 4); + } + + { + Tensor input = + make_tensor({5.0F, 6.0F, 7.0F, 8.0F}, Shape({1, 1, 2, 2})); + Tensor output; + std::vector in{input}; + std::vector out{output}; + + EXPECT_NO_THROW(layer.run(in, out)); + auto result = *out[0].as(); + EXPECT_EQ(result.size(), 1); + } +} + +TEST(poolinglayer_onednn, set_parameters_after_creation) { + PoolingLayerOneDnn layer({2, 2}, {2, 2}, {0, 0, 0, 0}, {1, 1}, false, "max"); + + { + Tensor input = + make_tensor({1.0F, 2.0F, 3.0F, 4.0F}, Shape({1, 1, 2, 2})); + Tensor output; + std::vector in{input}; + std::vector out{output}; + layer.run(in, out); + } + + layer.setStrides(1, 1); + layer.setPads(1, 1, 1, 1); + layer.setCeilMode(true); + + { + Tensor input = make_tensor( + {1.0F, 2.0F, 3.0F, 4.0F, 5.0F, 6.0F, 7.0F, 8.0F, 9.0F}, + Shape({1, 1, 3, 3})); + Tensor output; + std::vector in{input}; + std::vector out{output}; + + EXPECT_NO_THROW(layer.run(in, out)); + } +} + +TEST(poolinglayer_onednn, edge_cases) { + { + PoolingLayerOneDnn layer({10, 10}, {1, 1}, {0, 0, 0, 0}, {1, 1}, false, + "max"); + + std::vector input_data(100); + for (size_t i = 0; i < 100; i++) input_data[i] = static_cast(i); + + Tensor input = make_tensor(input_data, Shape({1, 1, 10, 10})); + Tensor output; + std::vector in{input}; + std::vector out{output}; + + EXPECT_NO_THROW(layer.run(in, out)); + auto result = *out[0].as(); + EXPECT_EQ(result.size(), 1); + } + + { + PoolingLayerOneDnn layer({2, 2}, {2, 2}, {0, 0, 0, 0}, {1, 1}, false, + "max"); + + std::vector input_data(8); + for (size_t i = 0; i < 8; i++) input_data[i] = static_cast(i); + + Tensor input = make_tensor(input_data, Shape({2, 1, 2, 2})); + Tensor output; + std::vector in{input}; + std::vector out{output}; + + EXPECT_NO_THROW(layer.run(in, out)); + auto result = *out[0].as(); + Shape output_shape = out[0].get_shape(); + + EXPECT_EQ(output_shape, Shape({2, 1, 1, 1})); + EXPECT_EQ(result.size(), 2); + } +} + +TEST(poolinglayer_onednn, different_input_dimensions) { + { + PoolingLayerOneDnn layer({2, 2}, {2, 2}, {0, 0, 0, 0}, {1, 1}, false, + "max"); + + std::vector input_data(1 * 3 * 4 * 4); + for (size_t i = 0; i < input_data.size(); i++) { + input_data[i] = static_cast(i); + } + + Tensor input = make_tensor(input_data, Shape({1, 3, 4, 4})); + Tensor output; + std::vector in{input}; + std::vector out{output}; + + EXPECT_NO_THROW(layer.run(in, out)); + auto output_shape = out[0].get_shape(); + EXPECT_EQ(output_shape, Shape({1, 3, 2, 2})); + } + + { + PoolingLayerOneDnn layer({2, 2}, {2, 2}, {0, 0, 0, 0}, {1, 1}, false, + "max"); + + std::vector input_data(3 * 4 * 4); + for (size_t i = 0; i < input_data.size(); i++) { + input_data[i] = static_cast(i); + } + + Tensor input = make_tensor(input_data, Shape({3, 4, 4})); + Tensor output; + std::vector in{input}; + std::vector out{output}; + + EXPECT_NO_THROW(layer.run(in, out)); + auto output_shape = out[0].get_shape(); + EXPECT_EQ(output_shape, Shape({3, 2, 2})); + } + + { + PoolingLayerOneDnn layer({2, 2}, {2, 2}, {0, 0, 0, 0}, {1, 1}, false, + "average"); + + std::vector input_data(4 * 4); + for (size_t i = 0; i < input_data.size(); i++) { + input_data[i] = static_cast(i); + } + + Tensor input = make_tensor(input_data, Shape({4, 4})); + Tensor output; + std::vector in{input}; + std::vector out{output}; + + EXPECT_NO_THROW(layer.run(in, out)); + auto output_shape = out[0].get_shape(); + EXPECT_EQ(output_shape, Shape({2, 2})); + } +} + +TEST(poolinglayer_onednn, invalid_dimensions) { + PoolingLayerOneDnn layer({2, 2}, {2, 2}, {0, 0, 0, 0}, {1, 1}, false, "max"); + { + std::vector input_data(2 * 3 * 4 * 5 * 6); + Tensor input = make_tensor(input_data, Shape({2, 3, 4, 5, 6})); + Tensor output; + std::vector in{input}; + std::vector out{output}; + + EXPECT_THROW(layer.run(in, out), std::runtime_error); + } + + { + std::vector input_data(1); + Tensor input = make_tensor(input_data, Shape({1})); + Tensor output; + std::vector in{input}; + std::vector out{output}; + + EXPECT_THROW(layer.run(in, out), std::runtime_error); + } +} + +TEST(poolinglayer_onednn, ceil_mode_with_padding_adjustment) { + { + PoolingLayerOneDnn layer({3, 3}, {2, 2}, {0, 0, 0, 0}, {1, 1}, true, "max"); + + std::vector input_data(1 * 1 * 5 * 5); + Tensor input = make_tensor(input_data, Shape({1, 1, 5, 5})); + Tensor output; + std::vector in{input}; + std::vector out{output}; + + layer.run(in, out); + auto output_shape = out[0].get_shape(); + EXPECT_EQ(output_shape, Shape({1, 1, 2, 2})); + } +}