diff --git a/app/Graph/CMakeLists.txt b/app/Graph/CMakeLists.txt index 66612643a..218e511bf 100644 --- a/app/Graph/CMakeLists.txt +++ b/app/Graph/CMakeLists.txt @@ -11,6 +11,7 @@ target_link_libraries(BuildGraph PUBLIC ${OpenCV_LIBS}) target_link_libraries(BuildGraph PUBLIC reader_lib) target_link_libraries(BuildGraph PUBLIC TBB_unified) target_link_libraries(BuildGraph PUBLIC layers_lib) +target_link_libraries(BuildGraph PUBLIC layers_fused_lib) target_link_libraries(BuildGraph PUBLIC layers_oneDNN_lib) target_link_libraries(BuildGraph PUBLIC gtest_main) @@ -25,6 +26,7 @@ target_link_libraries(ACC BuildGraph) add_executable(onnx_subgraphs onnx_subgraphs.cpp) target_link_libraries(onnx_subgraphs BuildGraph) target_link_libraries(onnx_subgraphs OpenMP::OpenMP_CXX) +target_link_libraries(onnx_subgraphs ${OpenCV_LIBS}) target_link_libraries(onnx_subgraphs graphT_lib) file(DOWNLOAD diff --git a/app/Graph/build.cpp b/app/Graph/build.cpp index 381e8fcd5..e17c56e12 100644 --- a/app/Graph/build.cpp +++ b/app/Graph/build.cpp @@ -13,7 +13,7 @@ std::unordered_map model_paths = { void build_graph_linear(it_lab_ai::Graph& graph, it_lab_ai::Tensor& input, it_lab_ai::Tensor& output, RuntimeOptions options, - bool comments) { + bool comments, bool enable_postops) { if (comments) { for (size_t i = 0; i < input.get_shape().dims(); i++) { std::cout << input.get_shape()[i] << ' '; @@ -75,7 +75,7 @@ void build_graph_linear(it_lab_ai::Graph& graph, it_lab_ai::Tensor& input, if (layer_type.find("relu") != std::string::npos) { auto ew_layer = LayerFactory::createEwLayer("relu", options); layers.push_back(ew_layer); - layerpostop.push_back(true); + layerpostop.push_back(enable_postops); if (comments) { std::cout << "Element wise (relu) added to layers" << '\n'; } diff --git a/app/Graph/build.hpp b/app/Graph/build.hpp index 5628e9d19..5d98c4bc8 100644 --- a/app/Graph/build.hpp +++ b/app/Graph/build.hpp @@ -61,7 +61,8 @@ void build_graph(it_lab_ai::Graph& graph, it_lab_ai::Tensor& input, it_lab_ai::RuntimeOptions options, bool comments); void build_graph_linear(it_lab_ai::Graph& graph, it_lab_ai::Tensor& input, it_lab_ai::Tensor& output, - it_lab_ai::RuntimeOptions options, bool comments); + it_lab_ai::RuntimeOptions options, bool comments, + bool enable_postops = true); std::unordered_map load_class_names( const std::string& filename); diff --git a/app/Graph/onnx_subgraphs.cpp b/app/Graph/onnx_subgraphs.cpp index 1647206f5..d666195db 100644 --- a/app/Graph/onnx_subgraphs.cpp +++ b/app/Graph/onnx_subgraphs.cpp @@ -1,26 +1,85 @@ #include #include #include +#include #include +#include #include #include #include "build.hpp" #include "graph_transformations/graph_transformations.hpp" +#include "layers_fused/ConvRelu.hpp" #include "perf/benchmarking.hpp" using namespace it_lab_ai; +void alexnet_inf_careless(Graph& graph, const RuntimeOptions& options, + Tensor& input, Tensor& output) { + auto* o = new Tensor(output); + auto* i = new Tensor(input); + graph.inference(options); + if (graph.getLayersCount() == 0) { + throw std::runtime_error("No layers"); + } + graph.setOutput(graph.getLayerFromID(graph.getLayersCount() - 1), *o); + graph.setInput(graph.getLayerFromID(0), *i); +} + +void alexnet_comparison() { + std::vector counts = {979, 1134, 1031, 1009, 981, + 891, 957, 1027, 973, 1008}; + size_t sum = std::accumulate(counts.begin(), counts.end(), size_t{0}); + int count_pic = static_cast(sum) + 10; + std::vector res(count_pic * 28 * 28, 1.0F); + Tensor input; + Shape sh1({1, 5, 5, 3}); + std::vector vec; + vec.reserve(75); + for (int i = 0; i < 75; ++i) { + vec.push_back(3); + } + Tensor output = make_tensor(vec, sh1); + + Shape sh({static_cast(count_pic), 1, 28, 28}); + Tensor t = make_tensor(res, sh); + input = t; + + RuntimeOptions options; + Graph graph; + Graph graph2; + build_graph_linear(graph, input, output, options, true, false); + Graph subgraph; + std::shared_ptr layer_0 = std::make_shared(); + std::shared_ptr layer_1 = std::make_shared("relu"); + subgraph.setInput(layer_0, input); + subgraph.makeConnection(layer_0, layer_1); + std::shared_ptr layer_to = std::make_shared( + std::dynamic_pointer_cast(layer_0)); + changed_subgraphs(graph, subgraph, layer_to, graph2, input, options); + Tensor input_c = input; + Tensor output_c = output; + auto time1 = elapsed_time_avg( + 4, alexnet_inf_careless, graph, options, input_c, output_c); + print_time_stats(graph); + auto time2 = elapsed_time_avg( + 4, alexnet_inf_careless, graph2, options, input_c, output_c); + print_time_stats(graph2); + std::cout << time1 << " for unchanged graph\n"; + std::cout << time2 << " for convrelu graph\n"; +} + int main() { int type = 2; Tensor input = make_tensor(std::vector({0})); RuntimeOptions options; + alexnet_comparison(); if (type == 0) { Graph graph1; build_graph(graph1, input, input, MODEL_PATH_DENSENET_ONNX, options, false); Graph subgraph; - Tensor scale = make_tensor(std::vector({1.0})); + Tensor scale = make_tensor(std::vector({1.0F})); std::shared_ptr layer_0 = std::make_shared(scale, scale, scale, scale); std::shared_ptr layer_1 = std::make_shared("relu"); diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index c9ac0850c..f280d2f30 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -5,16 +5,17 @@ file(GLOB_RECURSE graphT_headers graph_transformations/*.h graph_transformations set(GRAPHT_HEADERS "${graphT_headers}" PARENT_SCOPE) file(GLOB_RECURSE layers_headers layers/*.h layers/*.hpp) -set(LAYERS_HEADERS "${layers_headers}" PARENT_SCOPE) +file(GLOB_RECURSE parallel_headers parallel/*.h parallel/*.hpp) +set(LAYERS_HEADERS "${layers_headers}" "${parallel_headers}" PARENT_SCOPE) file(GLOB_RECURSE layers_oneDNN_headers layers_oneDNN/*.h layers_oneDNN/*.hpp) set(LAYERS_ONEDNN_HEADERS "${layers_oneDNN_headers}" PARENT_SCOPE) +file(GLOB_RECURSE layers_fused_headers layers_fused/*.h layers_fused/*.hpp) +set(LAYERS_FUSED_HEADERS "${layers_fused_headers}" PARENT_SCOPE) + file(GLOB_RECURSE perf_headers perf/*.h perf/*.hpp) set(PERF_HEADERS "${perf_headers}" PARENT_SCOPE) file(GLOB_RECURSE reader_headers Weights_Reader/*.h Weights_Reader/*.hpp) set(READER_HEADERS "${reader_headers}" PARENT_SCOPE) - -file(GLOB_RECURSE parallel_headers parallel/*.h parallel/*.hpp) -set(LAYERS_HEADERS "${parallel_headers}" PARENT_SCOPE) diff --git a/include/graph/graph.hpp b/include/graph/graph.hpp index 4743d9968..3e95a4fb1 100644 --- a/include/graph/graph.hpp +++ b/include/graph/graph.hpp @@ -31,7 +31,8 @@ static std::unordered_map label_map = { {kReshape, "Reshape"}, {kSoftmax, "Softmax"}, {kReduce, "Reduce"}, - {kBatchNormalization, "Normalization"}}; + {kBatchNormalization, "Normalization"}, + {kConvRelu, "ConvRelu"}}; struct LayerTimeStats { std::string layer_name; diff --git a/include/graph_transformations/graph_transformations.hpp b/include/graph_transformations/graph_transformations.hpp index f0adf6fdd..f1dd2ea81 100644 --- a/include/graph_transformations/graph_transformations.hpp +++ b/include/graph_transformations/graph_transformations.hpp @@ -2,8 +2,10 @@ #include #include "graph/graph.hpp" +#include "layers/ConvLayer.hpp" #include "layers/EWLayer.hpp" #include "layers/Layer.hpp" +#include "layers_fused/ConvRelu.hpp" namespace it_lab_ai { diff --git a/include/layers/ConvLayer.hpp b/include/layers/ConvLayer.hpp index 6087c5e4c..b053ba3ba 100644 --- a/include/layers/ConvLayer.hpp +++ b/include/layers/ConvLayer.hpp @@ -58,6 +58,19 @@ class ConvolutionalLayer : public Layer { dilations_ = dilations; useLegacyImpl_ = useLegacyImpl; } + + [[nodiscard]] std::vector getNumericParams() const { + std::vector res = {stride_, pads_, dilations_, group_}; + return res; + } + + [[nodiscard]] std::vector> getTensorParams() { + std::vector> res = {kernel_, bias_}; + return res; + } + + [[nodiscard]] bool getLegacyImplBool() const { return useLegacyImpl_; } + void run(const std::vector& input, std::vector& output) override; void run(const std::vector& input, std::vector& output, diff --git a/include/layers/Layer.hpp b/include/layers/Layer.hpp index 0139912f8..706d419f0 100644 --- a/include/layers/Layer.hpp +++ b/include/layers/Layer.hpp @@ -34,7 +34,8 @@ enum LayerType : uint8_t { kReshape, kSoftmax, kMatmul, - kBatchNormalization + kBatchNormalization, + kConvRelu }; enum ImplType : uint8_t { kDefault, kTBB, kSTL }; diff --git a/include/layers/Tensor.hpp b/include/layers/Tensor.hpp index 783d0c9fb..c39c7562e 100644 --- a/include/layers/Tensor.hpp +++ b/include/layers/Tensor.hpp @@ -170,7 +170,7 @@ std::vector* Tensor::as() { } template -const std::vector* Tensor::as() const { +[[nodiscard]] const std::vector* Tensor::as() const { if (GetTypeEnum() != type_) { throw std::invalid_argument("Template type doesn't fit this Tensor"); } diff --git a/include/layers_fused/ConvRelu.hpp b/include/layers_fused/ConvRelu.hpp new file mode 100644 index 000000000..e2c815303 --- /dev/null +++ b/include/layers_fused/ConvRelu.hpp @@ -0,0 +1,431 @@ +#pragma once + +#include +#include +#include + +#include "layers/ConvLayer.hpp" +#include "layers/Layer.hpp" +#include "layers/Tensor.hpp" + +namespace it_lab_ai { + +template +void relu(Tensor& t) { + Shape sh = t.get_shape(); + for (size_t i = 0; i < sh.count(); i++) { + if ((*t.as())[i] < 0) { + (*t.as())[i] = 0; + } + } +} + +class ConvReluLayer : public Layer { + private: + size_t stride_; + size_t pads_; + size_t dilations_; + std::shared_ptr kernel_; + std::shared_ptr bias_; + size_t group_; + bool useLegacyImpl_; + + public: + ConvReluLayer() : Layer(kConvRelu), kernel_(nullptr), bias_(nullptr) { + stride_ = 0; + pads_ = 0; + dilations_ = 0; + } + ConvReluLayer(size_t step, size_t pads, size_t dilations, + const Tensor& kernel, const Tensor& bias = Tensor(), + size_t group = 1, bool useLegacyImpl = false) + : Layer(kConvRelu), + kernel_(std::make_shared(kernel)), + bias_(std::make_shared(bias)) { + stride_ = step; + pads_ = pads; + group_ = group; + dilations_ = dilations; + useLegacyImpl_ = useLegacyImpl; + } + ConvReluLayer(size_t step, size_t pads, size_t dilations, + std::shared_ptr kernel, + std::shared_ptr bias = std::make_shared(), + size_t group = 1, bool useLegacyImpl = false) + : Layer(kConvRelu), kernel_(std::move(kernel)), bias_(std::move(bias)) { + stride_ = step; + pads_ = pads; + group_ = group; + dilations_ = dilations; + useLegacyImpl_ = useLegacyImpl; + } + explicit ConvReluLayer(const std::shared_ptr& conv) + : Layer(kConvRelu) { + auto numerics = conv->getNumericParams(); + auto tensors = conv->getTensorParams(); + stride_ = numerics[0]; + pads_ = numerics[1]; + dilations_ = numerics[2]; + group_ = numerics[3]; + kernel_ = tensors[0]; + bias_ = tensors[1]; + useLegacyImpl_ = conv->getLegacyImplBool(); + } + void run(const std::vector& input, + std::vector& output) override; + void run(const std::vector& input, std::vector& output, + const RuntimeOptions& options) override; +#ifdef ENABLE_STATISTIC_WEIGHTS + Tensor get_weights() override { return *kernel_; } +#endif +}; + +// NCHW -> NCHW only +template +void Conv4DRelu(const Tensor& input, const Tensor& kernel_, const Tensor& bias_, + Tensor& output, size_t stride_, size_t pads_, size_t group_, + size_t dilations_, ParBackend backend = ParBackend::kSeq) { + size_t batch_size = input.get_shape()[0]; + size_t in_channels = input.get_shape()[1]; + size_t in_height = input.get_shape()[2]; + size_t in_width = input.get_shape()[3]; + + size_t out_channels = kernel_.get_shape()[0]; + size_t kernel_in_channels = kernel_.get_shape()[1]; + size_t kernel_height = kernel_.get_shape()[2]; + size_t kernel_width = kernel_.get_shape()[3]; + + if (group_ > 1) { + if (in_channels % group_ != 0 || out_channels % group_ != 0) { + throw std::runtime_error("Channels must be divisible by group"); + } + if (kernel_in_channels != in_channels / group_) { + throw std::runtime_error( + "Kernel input channels don't match group configuration"); + } + } + + size_t out_height = ComputeConvOutputDim(in_height, kernel_height, stride_, + pads_, dilations_); + size_t out_width = + ComputeConvOutputDim(in_width, kernel_width, stride_, pads_, dilations_); + + std::vector>>> padded_input( + batch_size, + std::vector>>( + in_height + 2 * pads_, + std::vector>( + in_width + 2 * pads_, std::vector(in_channels, 0)))); + + parallel::Options options; + options.backend = backend; + + parallel::parallel_for( + batch_size, + [&](size_t b) { + for (size_t h = 0; h < in_height; ++h) { + for (size_t w = 0; w < in_width; ++w) { + for (size_t c = 0; c < in_channels; ++c) { + padded_input[b][h + pads_][w + pads_][c] = + input.get({b, c, h, w}); + } + } + } + }, + options); + + size_t dilated_kernel_height = (kernel_height - 1) * dilations_ + 1; + size_t dilated_kernel_width = (kernel_width - 1) * dilations_ + 1; + + std::vector>>> dil_kernel( + out_channels, std::vector>>( + kernel_in_channels, + std::vector>( + dilated_kernel_height, + std::vector(dilated_kernel_width, 0)))); + + parallel::parallel_for( + out_channels, + [&](size_t oc) { + for (size_t ic = 0; ic < kernel_in_channels; ++ic) { + for (size_t kh = 0; kh < kernel_height; ++kh) { + for (size_t kw = 0; kw < kernel_width; ++kw) { + dil_kernel[oc][ic][kh * dilations_][kw * dilations_] = + kernel_.get({oc, ic, kh, kw}); + } + } + } + }, + options); + + std::vector>>> output_tensor( + batch_size, + std::vector>>( + out_channels, std::vector>( + out_height, std::vector(out_width, 0)))); + + size_t total_work = batch_size * out_channels; + parallel::parallel_for( + total_work, + [&](size_t idx) { + size_t b = idx / out_channels; + size_t oc = idx % out_channels; + + for (size_t oh = 0; oh < out_height; ++oh) { + for (size_t ow = 0; ow < out_width; ++ow) { + ValueType value = 0; + size_t h_start = oh * stride_; + size_t w_start = ow * stride_; + + size_t group = (group_ > 1) ? oc / (out_channels / group_) : 0; + size_t group_start_channel = group * (in_channels / group_); + size_t group_end_channel = (group + 1) * (in_channels / group_); + + for (size_t ic = group_start_channel; ic < group_end_channel; + ++ic) { + size_t kernel_ic = ic - group_start_channel; + + for (size_t kh = 0; kh < dilated_kernel_height; ++kh) { + for (size_t kw = 0; kw < dilated_kernel_width; ++kw) { + size_t h_index = h_start + kh; + size_t w_index = w_start + kw; + + if (h_index < padded_input[b].size() && + w_index < padded_input[b][h_index].size()) { + value += padded_input[b][h_index][w_index][ic] * + dil_kernel[oc][kernel_ic][kh][kw]; + } + } + } + } + + if (!bias_.empty() && oc < bias_.get_shape()[0]) { + value += bias_.get({oc}); + } + + output_tensor[b][oc][oh][ow] = value > 0 ? value : 0; + } + } + }, + options); + + Shape output_shape({batch_size, out_channels, out_height, out_width}); + std::vector flat_output(batch_size * out_channels * out_height * + out_width); + + parallel::parallel_for( + batch_size, + [&](size_t b) { + size_t base_idx = b * out_channels * out_height * out_width; + for (size_t oc = 0; oc < out_channels; ++oc) { + for (size_t h = 0; h < out_height; ++h) { + for (size_t w = 0; w < out_width; ++w) { + flat_output[base_idx++] = output_tensor[b][oc][h][w]; + } + } + } + }, + options); + + output = make_tensor(flat_output, output_shape); +} + +template +void DepthwiseConv4DRelu(const Tensor& input, const Tensor& kernel_, + const Tensor& bias_, Tensor& output, size_t stride_, + size_t pads_, size_t dilations_, + ParBackend backend = ParBackend::kSeq) { + size_t batch_size = input.get_shape()[0]; + size_t channels = input.get_shape()[1]; + size_t in_height = input.get_shape()[2]; + size_t in_width = input.get_shape()[3]; + + size_t kernel_out_channels = kernel_.get_shape()[0]; + size_t kernel_in_channels = kernel_.get_shape()[1]; + size_t kernel_height = kernel_.get_shape()[2]; + size_t kernel_width = kernel_.get_shape()[3]; + + if (kernel_out_channels != channels || kernel_in_channels != 1) { + throw std::runtime_error("Invalid kernel shape for depthwise convolution"); + } + + size_t out_height = ComputeConvOutputDim(in_height, kernel_height, stride_, + pads_, dilations_); + size_t out_width = + ComputeConvOutputDim(in_width, kernel_width, stride_, pads_, dilations_); + + Tensor output_tensor(Shape({batch_size, channels, out_height, out_width}), + input.get_type()); + + parallel::Options options; + options.backend = backend; + + size_t total_work = batch_size * channels; + + parallel::parallel_for( + total_work, + [&](size_t idx) { + size_t b = idx / channels; + size_t c = idx % channels; + + for (size_t oh = 0; oh < out_height; ++oh) { + for (size_t ow = 0; ow < out_width; ++ow) { + ValueType sum = 0; + + for (size_t kh = 0; kh < kernel_height; ++kh) { + for (size_t kw = 0; kw < kernel_width; ++kw) { + size_t ih = oh * stride_ + kh * dilations_; + size_t iw = ow * stride_ + kw * dilations_; + + if (ih >= pads_ && iw >= pads_ && (ih - pads_) < in_height && + (iw - pads_) < in_width) { + auto input_val = + input.get({b, c, ih - pads_, iw - pads_}); + auto kernel_val = kernel_.get({c, 0, kh, kw}); + sum += input_val * kernel_val; + } + } + } + + if (!bias_.empty() && c < bias_.get_shape()[0]) { + sum += bias_.get({c}); + } + + output_tensor.set({b, c, oh, ow}, sum > 0 ? sum : 0); + } + } + }, + options); + + output = output_tensor; +} + +// NCHW -> NCHW only (Legacy version) +template +void Conv4D_LegacyRelu(const Tensor& input, const Tensor& kernel_, + const Tensor& bias_, Tensor& output, size_t stride_, + size_t pads_, size_t dilations_, + ParBackend backend = ParBackend::kSeq) { + size_t batch_size = input.get_shape()[0]; + size_t in_height = input.get_shape()[2]; + size_t in_width = input.get_shape()[3]; + size_t in_channels = input.get_shape()[1]; + + size_t kernel_height = kernel_.get_shape()[0]; + size_t kernel_width = kernel_.get_shape()[1]; + size_t kernel_in_channels = kernel_.get_shape()[2]; + size_t kernel_out_channels = kernel_.get_shape()[3]; + + parallel::Options options; + options.backend = backend; + + std::vector>>> padded_input( + batch_size, + std::vector>>( + in_height + 2 * pads_, + std::vector>( + in_width + 2 * pads_, std::vector(in_channels, 0)))); + + parallel::parallel_for( + batch_size, + [&](size_t b) { + for (size_t h = 0; h < in_height; ++h) { + for (size_t w = 0; w < in_width; ++w) { + for (size_t c = 0; c < in_channels; ++c) { + padded_input[b][h + pads_][w + pads_][c] = + input.get({b, c, h, w}); + } + } + } + }, + options); + + size_t dilated_kernel_height = kernel_height * dilations_ + 1 - dilations_; + size_t dilated_kernel_width = kernel_width * dilations_ + 1 - dilations_; + + std::vector>>> dil_kernel( + dilated_kernel_height, + std::vector>>( + dilated_kernel_width, + std::vector>( + kernel_in_channels, + std::vector(kernel_out_channels, 0)))); + + parallel::parallel_for( + kernel_out_channels, + [&](size_t b) { + for (size_t h = 0; h < kernel_height; ++h) { + for (size_t w = 0; w < kernel_width; ++w) { + for (size_t c = 0; c < kernel_in_channels; ++c) { + dil_kernel[h * dilations_][w * dilations_][c][b] = + kernel_.get({h, w, c, b}); + } + } + } + }, + options); + + size_t out_height = ComputeConvOutputDim(in_height, kernel_height, stride_, + pads_, dilations_); + size_t out_width = + ComputeConvOutputDim(in_width, kernel_width, stride_, pads_, dilations_); + + std::vector>>> output_tensor( + batch_size, std::vector>>( + kernel_out_channels, + std::vector>( + out_height, std::vector(out_width, 0)))); + + size_t total_work = batch_size * kernel_out_channels; + + parallel::parallel_for( + total_work, + [&](size_t idx) { + size_t b = idx / kernel_out_channels; + size_t c = idx % kernel_out_channels; + + for (size_t i = 0; i < out_height; i += stride_) { + for (size_t j = 0; j < out_width; j += stride_) { + ValueType value = 0; + for (size_t ic = 0; ic < in_channels; ++ic) { + for (size_t h = 0; h < dilated_kernel_height; ++h) { + for (size_t w = 0; w < dilated_kernel_width; ++w) { + value += padded_input[b][i + h][j + w][ic] * + dil_kernel[h][w][ic][c]; + } + } + } + if (!bias_.empty()) { + output_tensor[b][c][i][j] = + value + (*bias_.as())[c] > 0 + ? (value + (*bias_.as())[c]) + : 0; + } else { + output_tensor[b][c][i][j] = value > 0 ? value : 0; + } + } + } + }, + options); + + Shape sh({batch_size, kernel_out_channels, out_height, out_width}); + std::vector one_d_vector(batch_size * out_height * out_width * + kernel_out_channels); + + parallel::parallel_for( + batch_size, + [&](size_t i) { + size_t base_idx = i * kernel_out_channels * out_height * out_width; + for (size_t l = 0; l < kernel_out_channels; ++l) { + for (size_t j = 0; j < out_height; ++j) { + for (size_t k = 0; k < out_width; ++k) { + one_d_vector[base_idx++] = output_tensor[i][l][j][k]; + } + } + } + }, + options); + + output = make_tensor(one_d_vector, sh); +} +} // namespace it_lab_ai diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ab526f5b8..01e4acb04 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,5 +2,6 @@ add_subdirectory(graph) add_subdirectory(graph_transformations) add_subdirectory(perf) add_subdirectory(layers) +add_subdirectory(layers_fused) add_subdirectory(layers_oneDNN) add_subdirectory(Weights_Reader) diff --git a/src/graph_transformations/graph_transformations.cpp b/src/graph_transformations/graph_transformations.cpp index 6d86d5f8f..8d688f441 100644 --- a/src/graph_transformations/graph_transformations.cpp +++ b/src/graph_transformations/graph_transformations.cpp @@ -160,7 +160,15 @@ void changed_subgraphs(const Graph& graph, const Graph& subgraph_from, sub_used[i] = false; continue; } - std::shared_ptr layer = layer_based_shared_copy(layer_to, options); + std::shared_ptr layer; + if (layer_to->getName() == kConvRelu && + graph.getLayerFromID(subs_c[i][0])->getName() == kConvolution) { + layer = std::static_pointer_cast(std::make_shared( + std::dynamic_pointer_cast( + graph.getLayerFromID(subs_c[i][0])))); // convrelu case + } else { + layer = layer_based_shared_copy(layer_to, options); + } std::vector is_root_special(roots.size(), false); roots_inps_final.clear(); leaves_outs_final.clear(); diff --git a/src/layers_fused/CMakeLists.txt b/src/layers_fused/CMakeLists.txt new file mode 100644 index 000000000..d66b72bfc --- /dev/null +++ b/src/layers_fused/CMakeLists.txt @@ -0,0 +1,8 @@ +file(GLOB_RECURSE layers_fused_src *.cpp) +add_library(layers_fused_lib STATIC "${LAYERS_FUSED_HEADERS}" "${layers_fused_src}") + +target_link_libraries(layers_fused_lib PUBLIC layers_lib) +target_link_libraries(layers_fused_lib PUBLIC TBB_unified) +target_link_libraries(layers_fused_lib PUBLIC OpenMP::OpenMP_CXX) +target_link_libraries(layers_fused_lib PUBLIC dnnl) +target_link_libraries(layers_fused_lib PUBLIC Kokkos_imported) diff --git a/src/layers_fused/ConvRelu.cpp b/src/layers_fused/ConvRelu.cpp new file mode 100644 index 000000000..14009b9f5 --- /dev/null +++ b/src/layers_fused/ConvRelu.cpp @@ -0,0 +1,174 @@ +#include "layers_fused/ConvRelu.hpp" + +#include "layers/ConvLayer.hpp" + +namespace it_lab_ai { + +void ConvReluLayer::run(const std::vector& input, + std::vector& output) { + RuntimeOptions default_options; + run(input, output, default_options); +} + +void ConvReluLayer::run(const std::vector& input, + std::vector& output, + const RuntimeOptions& options) { + if (kernel_ == nullptr || bias_ == nullptr) { + throw std::runtime_error("ConvolutionalLayer: no weights or bias"); + } + if (input.size() != 1) { + throw std::runtime_error("ConvolutionalLayer: Input tensors not 1"); + } + if (input[0].get_shape().dims() != 4) { + throw std::out_of_range("input must be 4-dimensional"); + } + + ParBackend backend = options.par_backend; + + if (group_ > 1) { + if (group_ == input[0].get_shape()[1] && + group_ == kernel_->get_shape()[0]) { + switch (input[0].get_type()) { + case Type::kFloat: + DepthwiseConv4DRelu(input[0], *kernel_, *bias_, output[0], + stride_, pads_, dilations_, backend); + break; + case Type::kInt: + DepthwiseConv4DRelu(input[0], *kernel_, *bias_, output[0], + stride_, pads_, dilations_, backend); + break; + default: + throw std::runtime_error( + "Unsupported type for depthwise convolution"); + } + return; + } + } + + switch (input[0].get_type()) { + case Type::kInt: { + if (kernel_->get_shape().dims() == 2) { + if (dilations_ > 0) { + dilations_--; + } + ConvImpl used_impl( + stride_, pads_, dilations_, + static_cast( + input[0].get_shape()[input[0].get_shape().dims() - 1]), + static_cast( + input[0].get_shape()[input[0].get_shape().dims() - 2]), + static_cast( + input[0].get_shape()[input[0].get_shape().dims() - 3]), + input[0].get_shape()[input[0].get_shape().dims() - 1] * + input[0].get_shape()[input[0].get_shape().dims() - 2], + bias_->empty() ? std::vector() : *bias_->as()); + auto sizeforshape = static_cast( + ((static_cast( + input[0].get_shape()[input[0].get_shape().dims() - 1]) - + 1 - + static_cast( + (1 + kernel_->get_shape()[kernel_->get_shape().dims() - 1]) * + dilations_ + + kernel_->get_shape()[kernel_->get_shape().dims() - 1] - 1)) / + static_cast(stride_)) + + 1); + + Shape sh({1, 3, sizeforshape, sizeforshape}); + output[0] = make_tensor( + used_impl.run( + *input[0].as(), + static_cast( + input[0].get_shape()[input[0].get_shape().dims() - 1]) + + 2 * static_cast(pads_), + static_cast( + input[0].get_shape()[input[0].get_shape().dims() - 2]) + + 2 * static_cast(pads_), + *kernel_->as(), + kernel_->get_shape()[kernel_->get_shape().dims() - 1], + (1 + kernel_->get_shape()[kernel_->get_shape().dims() - 1]) * + dilations_ + + kernel_->get_shape()[kernel_->get_shape().dims() - 1], + static_cast( + ((1 + + kernel_->get_shape()[kernel_->get_shape().dims() - 1]) * + dilations_ + + kernel_->get_shape()[kernel_->get_shape().dims() - 1] - + 1) / + 2)), + sh); + relu(output[0]); + } else { + Conv4DRelu(input[0], *kernel_, *bias_, output[0], stride_, pads_, + group_, dilations_, backend); + } + break; + } + case Type::kFloat: { + if (kernel_->get_shape().dims() == 2) { + if (dilations_ > 0) { + dilations_--; + } + ConvImpl used_impl( + stride_, pads_, dilations_, + static_cast( + input[0].get_shape()[input[0].get_shape().dims() - 1]), + static_cast( + input[0].get_shape()[input[0].get_shape().dims() - 2]), + static_cast( + input[0].get_shape()[input[0].get_shape().dims() - 3]), + input[0].get_shape()[input[0].get_shape().dims() - 1] * + input[0].get_shape()[input[0].get_shape().dims() - 2], + bias_->empty() ? std::vector() : *bias_->as()); + auto sizeforshape = static_cast( + ((static_cast( + input[0].get_shape()[input[0].get_shape().dims() - 1]) - + 1 - + static_cast( + (1 + kernel_->get_shape()[kernel_->get_shape().dims() - 1]) * + dilations_ + + kernel_->get_shape()[kernel_->get_shape().dims() - 1] - 1)) / + static_cast(stride_)) + + 1); + + Shape sh({1, 3, sizeforshape, sizeforshape}); + output[0] = make_tensor( + used_impl.run( + *input[0].as(), + static_cast( + input[0].get_shape()[input[0].get_shape().dims() - 1]) + + 2 * static_cast(pads_), + static_cast( + input[0].get_shape()[input[0].get_shape().dims() - 2]) + + 2 * static_cast(pads_), + *kernel_->as(), + kernel_->get_shape()[kernel_->get_shape().dims() - 1], + (1 + kernel_->get_shape()[kernel_->get_shape().dims() - 1]) * + dilations_ + + kernel_->get_shape()[kernel_->get_shape().dims() - 1], + static_cast( + ((1 + + kernel_->get_shape()[kernel_->get_shape().dims() - 1]) * + dilations_ + + kernel_->get_shape()[kernel_->get_shape().dims() - 1] - + 1) / + 2)), + sh); + relu(output[0]); + } else { + if (useLegacyImpl_) { + Conv4D_LegacyRelu(input[0], *kernel_, *bias_, output[0], + stride_, pads_, dilations_, backend); + } else { + Conv4DRelu(input[0], *kernel_, *bias_, output[0], stride_, + pads_, group_, dilations_, backend); + } + } + break; + } + default: { + throw std::runtime_error("Unsupported tensor type"); + } + } +} + +} // namespace it_lab_ai diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c6e776981..142773ef3 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -3,7 +3,7 @@ file(GLOB_RECURSE TEST_SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) add_executable(run_test ${TEST_SRC_FILES}) target_link_libraries(run_test PUBLIC OpenMP::OpenMP_CXX) -target_link_libraries(run_test PUBLIC perf_lib layers_lib layers_oneDNN_lib) +target_link_libraries(run_test PUBLIC perf_lib layers_lib layers_oneDNN_lib layers_fused_lib) target_link_libraries(run_test PUBLIC gtest) target_link_libraries(run_test PUBLIC ReadLib) target_link_libraries(run_test PUBLIC reader_lib) diff --git a/test/single_layer/test_convlayer.cpp b/test/single_layer/test_convlayer.cpp index f4c2cbe48..42320a102 100644 --- a/test/single_layer/test_convlayer.cpp +++ b/test/single_layer/test_convlayer.cpp @@ -7,6 +7,26 @@ using namespace it_lab_ai; class ConvTestFixture : public BaseTestFixture {}; +TEST(ConvolutionalLayerTest, Getters) { + int step = 2; + std::vector kernelvec = {1, 0, 1, 0, 1, 0, 1, 0, 1}; + Shape sh2({3, 3}); + Tensor kernel = make_tensor(kernelvec, sh2); + ConvolutionalLayer layer(step, 0, 1, kernel); + std::vector vec = {1, 2, 3, 4}; + Tensor input1 = make_tensor(vec, {4}); + Tensor input2 = make_tensor(vec, {2, 2}); + std::vector in{input1, input2}; + std::vector output{input1}; + + bool is_legacy = layer.getLegacyImplBool(); + std::vector nums = layer.getNumericParams(); + auto tens = layer.getTensorParams(); + EXPECT_EQ(is_legacy, false); + EXPECT_FALSE(nums.empty()); + EXPECT_FALSE(tens.empty()); +} + TEST(ConvolutionalLayerTest, IncompatibleInput) { int step = 2; std::vector kernelvec = {1, 0, 1, 0, 1, 0, 1, 0, 1}; diff --git a/test/single_layer_fused/test_convrelu.cpp b/test/single_layer_fused/test_convrelu.cpp new file mode 100644 index 000000000..1090a499c --- /dev/null +++ b/test/single_layer_fused/test_convrelu.cpp @@ -0,0 +1,722 @@ +#include + +#include "layers/EWLayer.hpp" +#include "layers_fused/ConvRelu.hpp" + +using namespace it_lab_ai; + +TEST(ConvReluLayerTest, CopyFromConv) { + int step = 2; + std::vector kernelvec = {1, 0, 1, 0, 1, 0, 1, 0, 1}; + Shape sh2({3, 3}); + Tensor kernel = make_tensor(kernelvec, sh2); + std::shared_ptr layer = + std::make_shared(step, 0, 1, kernel); + std::vector vec = {1, 2, 3, 4}; + Tensor input1 = make_tensor(vec, {4}); + Tensor input2 = make_tensor(vec, {2, 2}); + std::vector in{input1, input2}; + std::vector output{input1}; + EXPECT_NO_THROW(ConvReluLayer layer2(layer)); +} + +TEST(ConvReluLayerTest, IncompatibleInput) { + int step = 2; + std::vector kernelvec = {1, 0, 1, 0, 1, 0, 1, 0, 1}; + Shape sh2({3, 3}); + Tensor kernel = make_tensor(kernelvec, sh2); + ConvReluLayer layer(step, 0, 1, kernel); + std::vector vec = {1, 2, 3, 4}; + Tensor input1 = make_tensor(vec, {4}); + Tensor input2 = make_tensor(vec, {2, 2}); + std::vector in{input1, input2}; + std::vector output{input1}; + EXPECT_THROW(layer.run(in, output), std::runtime_error); +} + +TEST(ConvReluLayerTest, FStep2) { + std::vector image; + image.reserve(75); + for (int i = 0; i < 75; ++i) { + image.push_back(1); + } + Shape sh1({1, 3, 5, 5}); + Tensor input = make_tensor(image, sh1); + int step = 2; + std::vector kernelvec; + kernelvec.reserve(3 * 3 * 3 * 3); + for (int i = 0; i < 81; ++i) { + kernelvec.push_back((i % 9) % 2 == 0 ? 1.0f : 0.0f); + } + Shape sh2({3, 3, 3, 3}); + Tensor kernel = make_tensor(kernelvec, sh2); + size_t out_height = (5 + 2 * 0 - 1 * (3 - 1) - 1) / 2 + 1; + size_t out_width = (5 + 2 * 0 - 1 * (3 - 1) - 1) / 2 + 1; + size_t expected_size = 1 * 3 * out_height * out_width; + std::vector expected_output(expected_size, 15.0f); + Shape output_shape({1, 3, out_height, out_width}); + std::vector output_vec(expected_size, 0.0f); + Tensor output = make_tensor(output_vec, output_shape); + ConvReluLayer layer(step, 0, 1, kernel); + std::vector in{input}; + std::vector out{output}; + layer.run(in, out); + std::vector tmp = *out[0].as(); + ASSERT_EQ(tmp.size(), expected_output.size()); + for (size_t i = 0; i < tmp.size(); ++i) { + ASSERT_FLOAT_EQ(tmp[i], expected_output[i]); + } +} +TEST(ConvReluLayerTest, FStep1) { + std::vector image; + image.reserve(75); + for (int i = 0; i < 75; ++i) { + image.push_back(1); + } + Shape sh1({1, 3, 5, 5}); + Tensor input = make_tensor(image, sh1); + int step = 1; + std::vector kernelvec; + kernelvec.reserve(3 * 3 * 3 * 3); + for (int i = 0; i < 81; ++i) { + kernelvec.push_back((i % 9) % 2 == 0 ? 1.0f : 0.0f); + } + Shape sh2({3, 3, 3, 3}); + Tensor kernel = make_tensor(kernelvec, sh2); + size_t out_height = (5 + 2 * 0 - 1 * (3 - 1) - 1) / 1 + 1; + size_t out_width = (5 + 2 * 0 - 1 * (3 - 1) - 1) / 1 + 1; + size_t expected_size = 1 * 3 * out_height * out_width; + std::vector expected_output(expected_size, 15.0f); + Shape output_shape({1, 3, out_height, out_width}); + std::vector output_vec(expected_size, 0.0f); + Tensor output = make_tensor(output_vec, output_shape); + ConvReluLayer layer(step, 0, 1, kernel); + std::vector in{input}; + std::vector out{output}; + layer.run(in, out); + std::vector tmp = *out[0].as(); + ASSERT_EQ(tmp.size(), expected_output.size()); + for (size_t i = 0; i < tmp.size(); ++i) { + ASSERT_FLOAT_EQ(tmp[i], expected_output[i]); + } +} +TEST(ConvReluLayerTest, IntStep2) { + std::vector image; + image.reserve(75); + for (int i = 0; i < 75; ++i) { + image.push_back(1); + } + Shape sh({2, 2}); + std::vector vec = {1, 2, 3, 4}; + Shape sh1({1, 3, 5, 5}); + Tensor input = make_tensor(image, sh1); + Tensor output = make_tensor(vec, sh); + int step = 2; + std::vector kernelvec = {1, 0, 1, 0, 1, 0, 1, 0, 1}; + std::vector expected_output(12, 5); + Shape sh2({3, 3}); + Tensor kernel = make_tensor(kernelvec, sh2); + ConvReluLayer layer(step, 0, 1, kernel); + std::vector in{input}; + std::vector out{output}; + layer.run(in, out); + std::vector tmp = *out[0].as(); + ASSERT_EQ(tmp.size(), expected_output.size()); + for (size_t i = 0; i < tmp.size(); ++i) { + ASSERT_EQ(tmp[i], expected_output[i]); + } +} +TEST(ConvReluLayerTest, IntStep1) { + std::vector image; + image.reserve(75); + for (int i = 0; i < 75; ++i) { + image.push_back(1); + } + Shape sh({2, 2}); + std::vector vec = {1, 2, 3, 4}; + Shape sh1({1, 3, 5, 5}); + Tensor input = make_tensor(image, sh1); + Tensor output = make_tensor(vec, sh); + int step = 1; + std::vector kernelvec = {1, 0, 1, 0, 1, 0, 1, 0, 1}; + std::vector expected_output(27, 5); + Shape sh2({3, 3}); + Tensor kernel = make_tensor(kernelvec, sh2); + ConvReluLayer layer(step, 0, 1, kernel); + std::vector in{input}; + std::vector out{output}; + layer.run(in, out); + std::vector tmp = *out[0].as(); + ASSERT_EQ(tmp.size(), expected_output.size()); + for (size_t i = 0; i < tmp.size(); ++i) { + ASSERT_EQ(tmp[i], expected_output[i]); + } +} +TEST(ConvReluLayerTest, FloatWithBias) { + std::vector image(75, 1.0f); + Shape input_shape({1, 3, 5, 5}); + Tensor input = make_tensor(image, input_shape); + std::vector kernelvec; + kernelvec.reserve(3 * 3 * 3 * 3); + for (int i = 0; i < 81; ++i) { + kernelvec.push_back((i % 9) % 2 == 0 ? 1.0f : 0.0f); + } + Shape kernel_shape({3, 3, 3, 3}); + Tensor kernel = make_tensor(kernelvec, kernel_shape); + std::vector biasvec = {0.5f, 0.5f, 0.5f}; + Tensor bias = make_tensor(biasvec, Shape({3})); + size_t out_height = 3; + size_t out_width = 3; + size_t expected_size = 1 * 3 * out_height * out_width; + Shape output_shape({1, 3, out_height, out_width}); + std::vector output_vec(expected_size, 0.0f); + Tensor output = make_tensor(output_vec, output_shape); + std::vector expected_output(expected_size, 15.5f); + ConvReluLayer layer(1, 0, 1, kernel, bias); + std::vector in{input}; + std::vector out{output}; + layer.run(in, out); + std::vector tmp = *out[0].as(); + ASSERT_EQ(tmp.size(), expected_output.size()); + for (size_t i = 0; i < tmp.size(); ++i) { + ASSERT_FLOAT_EQ(tmp[i], expected_output[i]); + } +} +TEST(ConvReluLayerTest, InvalidInputShapeDims) { + std::vector image(15, 1.0f); + Shape invalid_shape({1, 3, 5}); + Tensor input = make_tensor(image, invalid_shape); + + std::vector kernelvec = {1, 0, 1, 0, 1, 0, 1, 0, 1}; + Shape kernel_shape({3, 3}); + Tensor kernel = make_tensor(kernelvec, kernel_shape); + + Shape output_shape({1, 3, 3, 3}); + std::vector output_vec(27, 0.0f); + Tensor output = make_tensor(output_vec, output_shape); + + ConvReluLayer layer(1, 0, 1, kernel); + + std::vector in{input}; + std::vector out{output}; + + EXPECT_THROW(layer.run(in, out), std::out_of_range); +} +TEST(ConvReluLayerTest, Conv4DKern) { + std::vector image; + image.reserve(75); + for (int i = 0; i < 75; ++i) { + image.push_back(1); + } + Shape sh1({1, 3, 5, 5}); + Tensor input = make_tensor(image, sh1); + int step = 1; + std::vector kernelvec; + kernelvec.reserve(54); + for (int i = 0; i < 54; ++i) { + kernelvec.push_back(1); + } + Shape sh2({2, 3, 3, 3}); + Tensor kernel = make_tensor(kernelvec, sh2); + size_t out_height = (5 + 2 * 1 - 1 * (3 - 1) - 1) / 1 + 1; + size_t out_width = (5 + 2 * 1 - 1 * (3 - 1) - 1) / 1 + 1; + size_t expected_size = 1 * 2 * out_height * out_width; + std::vector expected_output(expected_size, 9); + Shape output_shape({1, 2, out_height, out_width}); + std::vector output_vec(expected_size, 0.0f); + Tensor output = make_tensor(output_vec, output_shape); + ConvReluLayer layer(step, 1, 1, kernel); + std::vector in{input}; + std::vector out{output}; + layer.run(in, out); + std::vector tmp = *out[0].as(); + ASSERT_EQ(tmp.size(), expected_output.size()); +} +TEST(ConvReluLayerTest, Conv4DKern_int) { + std::vector image; + image.reserve(784); + for (int i = 0; i < 784; ++i) { + image.push_back(1); + } + Shape sh1({1, 1, 28, 28}); + Tensor input = make_tensor(image, sh1); + + int step = 1; + std::vector kernelvec; + kernelvec.reserve(400); + for (int i = 0; i < 400; ++i) { + kernelvec.push_back(1); + } + Shape sh2({16, 1, 5, 5}); + Tensor kernel = make_tensor(kernelvec, sh2); + size_t out_height = (28 + 2 * 0 - 1 * (5 - 1) - 1) / 1 + 1; + size_t out_width = (28 + 2 * 0 - 1 * (5 - 1) - 1) / 1 + 1; + size_t expected_size = 1 * 16 * out_height * out_width; + std::vector expected_output(expected_size, 25); + Shape output_shape({1, 16, out_height, out_width}); + std::vector output_vec(expected_size, 0); + Tensor output = make_tensor(output_vec, output_shape); + ConvReluLayer layer(step, 0, 1, kernel); + std::vector in{input}; + std::vector out{output}; + layer.run(in, out); + + std::vector tmp = *out[0].as(); + ASSERT_EQ(tmp.size(), expected_output.size()); + for (size_t i = 0; i < tmp.size(); ++i) { + ASSERT_EQ(tmp[i], expected_output[i]); + } +} +TEST(ConvReluLayerTest, Conv4DKern_int_36) { + std::vector image; + image.reserve(16 * 784); + for (int i = 0; i < 16 * 784; ++i) { + image.push_back(1); + } + Shape sh1({1, 16, 28, 28}); + Tensor input = make_tensor(image, sh1); + int step = 1; + std::vector kernelvec; + kernelvec.reserve(5 * 5 * 16 * 36); + for (int i = 0; i < 5 * 5 * 16 * 36; ++i) { + kernelvec.push_back(1); + } + Shape sh2({36, 16, 5, 5}); + Tensor kernel = make_tensor(kernelvec, sh2); + size_t pads = (kernel.get_shape()[2] - 1) / 2; + size_t out_height = (28 + 2 * pads - 1 * (5 - 1) - 1) / 1 + 1; + size_t out_width = (28 + 2 * pads - 1 * (5 - 1) - 1) / 1 + 1; + size_t expected_size = 1 * 36 * out_height * out_width; + std::vector expected_output(expected_size, 5 * 5 * 16); + Shape output_shape({1, 36, out_height, out_width}); + std::vector output_vec(expected_size, 0); + Tensor output = make_tensor(output_vec, output_shape); + ConvReluLayer layer(step, pads, 1, kernel); + std::vector in{input}; + std::vector out{output}; + layer.run(in, out); + std::vector tmp = *out[0].as(); + ASSERT_EQ(tmp.size(), expected_output.size()); +} + +TEST(ConvReluLayerTest, DepthwiseIntegration) { + std::vector image(32, 1.0f); + Shape input_shape({1, 2, 4, 4}); + Tensor input = make_tensor(image, input_shape); + + std::vector kernelvec(18, 1.0f); + Shape kernel_shape({2, 1, 3, 3}); + Tensor kernel = make_tensor(kernelvec, kernel_shape); + Tensor bias; + + size_t out_height = (4 + 2 * 1 - 1 * (3 - 1) - 1) / 1 + 1; + size_t out_width = (4 + 2 * 1 - 1 * (3 - 1) - 1) / 1 + 1; + Shape output_shape({1, 2, out_height, out_width}); + std::vector output_vec(32, 0.0f); + Tensor output = make_tensor(output_vec, output_shape); + + ConvReluLayer layer(1, 1, 1, kernel, bias, 2); + std::vector in{input}; + std::vector out{output}; + + EXPECT_NO_THROW(layer.run(in, out)); + + std::vector result = *out[0].as(); + ASSERT_EQ(result.size(), 32); +} + +TEST(ConvReluLayerTest, DepthwiseViaConvolutionalLayer) { + std::vector image(32, -1.0f); + Shape input_shape({1, 2, 4, 4}); + Tensor input = make_tensor(image, input_shape); + + std::vector kernelvec(18, 1.0f); + Shape kernel_shape({2, 1, 3, 3}); + Tensor kernel = make_tensor(kernelvec, kernel_shape); + Tensor bias; + + Shape output_shape({1, 2, 2, 2}); + std::vector output_vec(8, 0.0f); + Tensor output = make_tensor(output_vec, output_shape); + + ConvReluLayer layer(1, 0, 1, kernel, bias, 2); + std::vector in{input}; + std::vector out{output}; + layer.run(in, out); + + std::vector result = *out[0].as(); + + float expected_value = 0.0f; + for (size_t i = 0; i < result.size(); ++i) { + ASSERT_NEAR(result[i], expected_value, 1e-5f); + } +} + +TEST(ConvReluLayerTest, Conv4DLegacyViaConvolutionalLayer) { + std::vector image(48, 1.0f); + Shape input_shape({1, 3, 4, 4}); + Tensor input = make_tensor(image, input_shape); + + std::vector kernelvec(54, 1.0f); + Shape kernel_shape({3, 3, 3, 2}); + Tensor kernel = make_tensor(kernelvec, kernel_shape); + Tensor bias; + + size_t out_height = (4 + 2 * 0 - 1 * (3 - 1) - 1) / 1 + 1; + size_t out_width = (4 + 2 * 0 - 1 * (3 - 1) - 1) / 1 + 1; + Shape output_shape({1, 2, out_height, out_width}); + std::vector output_vec(8, 0.0f); + Tensor output = make_tensor(output_vec, output_shape); + + ConvReluLayer layer(1, 0, 1, kernel, bias, 1, true); + std::vector in{input}; + std::vector out{output}; + + layer.run(in, out); + + std::vector result = *out[0].as(); + + ASSERT_EQ(result.size(), 8); + float expected_value = 27.0f; + for (size_t i = 0; i < result.size(); ++i) { + ASSERT_NEAR(result[i], expected_value, 1e-5f); + } +} + +TEST(ConvReluLayerTest, DepthwiseConv4DIntPathCoverage) { + std::vector image = {1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16}; + Shape input_shape({1, 2, 2, 4}); + Tensor input = make_tensor(image, input_shape); + + std::vector kernelvec = {1, 1, 1, 1, 2, 2, 2, 2}; + Shape kernel_shape({2, 1, 2, 2}); + Tensor kernel = make_tensor(kernelvec, kernel_shape); + + std::vector biasvec = {10, 20}; + Tensor bias = make_tensor(biasvec, Shape({2})); + + size_t out_height = (2 + 2 * 0 - 1 * (2 - 1) - 1) / 1 + 1; + size_t out_width = (4 + 2 * 0 - 1 * (2 - 1) - 1) / 1 + 1; + Shape output_shape({1, 2, out_height, out_width}); + std::vector output_vec(6, 0); + Tensor output = make_tensor(output_vec, output_shape); + + ConvReluLayer layer(1, 0, 1, kernel, bias, 2); + std::vector in{input}; + std::vector out{output}; + + EXPECT_NO_THROW(layer.run(in, out)); + + std::vector result = *out[0].as(); + EXPECT_FALSE(result.empty()); +} + +TEST(ConvReluLayerTest, DepthwiseConv4DFloatPathCoverage) { + std::vector image = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f}; + Shape input_shape({1, 2, 2, 2}); + Tensor input = make_tensor(image, input_shape); + + std::vector kernelvec = {1.0f, 1.0f, 1.0f, 1.0f, + 0.5f, 0.5f, 0.5f, 0.5f}; + Shape kernel_shape({2, 1, 2, 2}); + Tensor kernel = make_tensor(kernelvec, kernel_shape); + + std::vector biasvec = {0.1f, 0.2f}; + Tensor bias = make_tensor(biasvec, Shape({2})); + + size_t out_height = (2 + 2 * 0 - 1 * (2 - 1) - 1) / 1 + 1; + size_t out_width = (2 + 2 * 0 - 1 * (2 - 1) - 1) / 1 + 1; + Shape output_shape({1, 2, out_height, out_width}); + std::vector output_vec(2, 0.0f); + Tensor output = make_tensor(output_vec, output_shape); + + ConvReluLayer layer(1, 0, 1, kernel, bias, 2); + std::vector in{input}; + std::vector out{output}; + + EXPECT_NO_THROW(layer.run(in, out)); + + std::vector result = *out[0].as(); + EXPECT_FALSE(result.empty()); +} + +TEST(ConvReluLayerTest, DepthwiseConv4DNoBiasIntPathCoverage) { + std::vector image = {1, 2, 3, 4, 5, 6, 7, 8}; + Shape input_shape({1, 2, 2, 2}); + Tensor input = make_tensor(image, input_shape); + + std::vector kernelvec = {1, 1, 1, 1, 2, 2, 2, 2}; + Shape kernel_shape({2, 1, 2, 2}); + Tensor kernel = make_tensor(kernelvec, kernel_shape); + Tensor bias; + + size_t out_height = (2 + 2 * 0 - 1 * (2 - 1) - 1) / 1 + 1; + size_t out_width = (2 + 2 * 0 - 1 * (2 - 1) - 1) / 1 + 1; + Shape output_shape({1, 2, out_height, out_width}); + std::vector output_vec(2, 0); + Tensor output = make_tensor(output_vec, output_shape); + + ConvReluLayer layer(1, 0, 1, kernel, bias, 2); + std::vector in{input}; + std::vector out{output}; + + EXPECT_NO_THROW(layer.run(in, out)); + + std::vector result = *out[0].as(); + EXPECT_FALSE(result.empty()); +} + +TEST(ConvReluLayerTest, DepthwiseConv4DNoBiasFloatPathCoverage) { + std::vector image = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f}; + Shape input_shape({1, 2, 2, 2}); + Tensor input = make_tensor(image, input_shape); + + std::vector kernelvec = {1.0f, 1.0f, 1.0f, 1.0f, + 0.5f, 0.5f, 0.5f, 0.5f}; + Shape kernel_shape({2, 1, 2, 2}); + Tensor kernel = make_tensor(kernelvec, kernel_shape); + Tensor bias; + + size_t out_height = (2 + 2 * 0 - 1 * (2 - 1) - 1) / 1 + 1; + size_t out_width = (2 + 2 * 0 - 1 * (2 - 1) - 1) / 1 + 1; + Shape output_shape({1, 2, out_height, out_width}); + std::vector output_vec(2, 0.0f); + Tensor output = make_tensor(output_vec, output_shape); + + ConvReluLayer layer(1, 0, 1, kernel, bias, 2); + std::vector in{input}; + std::vector out{output}; + + EXPECT_NO_THROW(layer.run(in, out)); + + std::vector result = *out[0].as(); + EXPECT_FALSE(result.empty()); +} + +TEST(ConvReluLayerTest, ConvImplInt2DKernel) { + std::vector image(75, -1); + Shape input_shape({1, 3, 5, 5}); + Tensor input = make_tensor(image, input_shape); + + std::vector kernelvec = {1, 0, 1, 0, 1, 0, 1, 0, 1}; + Shape kernel_shape({3, 3}); + Tensor kernel = make_tensor(kernelvec, kernel_shape); + + std::vector output_vec(27, 0); + Tensor output = make_tensor(output_vec, Shape({1, 3, 3, 3})); + + ConvReluLayer layer(1, 0, 1, kernel); + std::vector in{input}; + std::vector out{output}; + layer.run(in, out); + std::vector result = *out[0].as(); + ASSERT_EQ(result.size(), 27); + for (size_t i = 0; i < result.size(); ++i) { + ASSERT_EQ(result[i], 0); + } +} +TEST(ConvReluLayerTest, ConvImplInt2DKernelBasic) { + std::vector image(75, 1); + Shape input_shape({1, 3, 5, 5}); + Tensor input = make_tensor(image, input_shape); + + std::vector kernelvec = {1, 0, 1, 0, 1, 0, 1, 0, 1}; + Shape kernel_shape({3, 3}); + Tensor kernel = make_tensor(kernelvec, kernel_shape); + + std::vector output_vec(27, 0); + Tensor output = make_tensor(output_vec, Shape({1, 3, 3, 3})); + + ConvReluLayer layer(1, 0, 1, kernel); + std::vector in{input}; + std::vector out{output}; + + layer.run(in, out); + + std::vector result = *out[0].as(); + + ASSERT_EQ(result.size(), 27); + for (size_t i = 0; i < result.size(); ++i) { + ASSERT_EQ(result[i], 5); + } +} + +TEST(ConvReluLayerTest, ConvImplInt2DKernelWithStride) { + std::vector image(75, 1); + Shape input_shape({1, 3, 5, 5}); + Tensor input = make_tensor(image, input_shape); + + std::vector kernelvec = {1, 0, 1, 0, 1, 0, 1, 0, 1}; + Shape kernel_shape({3, 3}); + Tensor kernel = make_tensor(kernelvec, kernel_shape); + + std::vector output_vec(12, 0); + Tensor output = make_tensor(output_vec, Shape({1, 3, 2, 2})); + + ConvReluLayer layer(2, 0, 1, kernel); + std::vector in{input}; + std::vector out{output}; + + layer.run(in, out); + + std::vector result = *out[0].as(); + + ASSERT_EQ(result.size(), 12); + for (size_t i = 0; i < result.size(); ++i) { + ASSERT_EQ(result[i], 5); + } +} + +TEST(ConvReluLayerTest, ConvImplInt2DKernelWithBias) { + std::vector image(75, 1); + Shape input_shape({1, 3, 5, 5}); + Tensor input = make_tensor(image, input_shape); + + std::vector kernelvec = {1, 0, 1, 0, 1, 0, 1, 0, 1}; + Shape kernel_shape({3, 3}); + Tensor kernel = make_tensor(kernelvec, kernel_shape); + + std::vector biasvec = {1, 1, 1}; + Tensor bias = make_tensor(biasvec, Shape({3})); + std::vector output_vec(27, 0); + Tensor output = make_tensor(output_vec, Shape({1, 3, 3, 3})); + + ConvReluLayer layer(1, 0, 1, kernel, bias); + std::vector in{input}; + std::vector out{output}; + + layer.run(in, out); + + std::vector result = *out[0].as(); + + ASSERT_EQ(result.size(), 27); + for (size_t i = 0; i < result.size(); ++i) { + ASSERT_EQ(result[i], 6); + } +} + +TEST(ConvReluLayerTest, ConvImplInt2DKernelSmallInput) { + std::vector image(27, 2); + Shape input_shape({1, 3, 3, 3}); + Tensor input = make_tensor(image, input_shape); + + std::vector kernelvec = {1, 1, 1, 1, 1, 1, 1, 1, 1}; + Shape kernel_shape({3, 3}); + Tensor kernel = make_tensor(kernelvec, kernel_shape); + std::vector output_vec(3, 0); + Tensor output = make_tensor(output_vec, Shape({1, 3, 1, 1})); + + ConvReluLayer layer(1, 0, 1, kernel); + std::vector in{input}; + std::vector out{output}; + + layer.run(in, out); + + std::vector result = *out[0].as(); + + ASSERT_EQ(result.size(), 3); + for (size_t i = 0; i < result.size(); ++i) { + ASSERT_EQ(result[i], 18); + } +} + +TEST(ConvReluLayerTest, ConvImplInt2DKernelComplexPattern) { + std::vector image = {1, 2, 1, 2, 3, 4, 3, 4, 1, 2, 1, 2, 3, 4, 3, 4, + + 2, 3, 2, 3, 4, 5, 4, 5, 2, 3, 2, 3, 4, 5, 4, 5, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + Shape input_shape({1, 3, 4, 4}); + Tensor input = make_tensor(image, input_shape); + + std::vector kernelvec = {1, 1, 1, 1, 1, 1, 1, 1, 1}; + Shape kernel_shape({3, 3}); + Tensor kernel = make_tensor(kernelvec, kernel_shape); + + std::vector output_vec(12, 0); + Tensor output = make_tensor(output_vec, Shape({1, 3, 2, 2})); + + ConvReluLayer layer(1, 0, 1, kernel); + std::vector in{input}; + std::vector out{output}; + + layer.run(in, out); + + std::vector result = *out[0].as(); + + ASSERT_EQ(result.size(), 12); + for (size_t i = 0; i < result.size(); ++i) { + ASSERT_GT(result[i], 0); + } +} + +TEST(ConvReluLayerTest, Float2DKernelPathCoverage) { + std::vector image = {1.0f, 2.0f, 3.0f, 4.0f}; + Shape input_shape({1, 1, 2, 2}); + Tensor input = make_tensor(image, input_shape); + + std::vector kernelvec = {1.0f, 0.0f, 1.0f, 0.0f}; + Shape kernel_shape({2, 2}); + Tensor kernel = make_tensor(kernelvec, kernel_shape); + + std::vector output_vec(1, 0.0f); + Tensor output = make_tensor(output_vec, Shape({1, 1, 1, 1})); + + ConvReluLayer layer(1, 0, 0, kernel); + std::vector in{input}; + std::vector out{output}; + + EXPECT_THROW(layer.run(in, out), std::exception); +} + +TEST(ConvReluLayerTest, Float4DKernelWorking) { + std::vector image = {1.0f, 2.0f, 3.0f, 4.0f}; + Shape input_shape({1, 1, 2, 2}); + Tensor input = make_tensor(image, input_shape); + + std::vector kernelvec = {1.0f, 0.0f, 1.0f, 0.0f}; + Shape kernel_shape({1, 1, 2, 2}); + Tensor kernel = make_tensor(kernelvec, kernel_shape); + + std::vector output_vec(1, 0.0f); + Tensor output = make_tensor(output_vec, Shape({1, 1, 1, 1})); + + ConvReluLayer layer(1, 0, 0, kernel); + std::vector in{input}; + std::vector out{output}; + + EXPECT_NO_THROW(layer.run(in, out)); + + std::vector result = *out[0].as(); + ASSERT_EQ(result.size(), 4); +} + +TEST(ConvReluLayerTest, Conv4DWithParallelDefaultFallback) { + RuntimeOptions options; + options.backend = Backend::kNaive; + + std::vector image(48, 1.0f); + Shape input_shape({1, 3, 4, 4}); + Tensor input = make_tensor(image, input_shape); + + std::vector kernelvec(54, 1.0f); + Shape kernel_shape({2, 3, 3, 3}); + Tensor kernel = make_tensor(kernelvec, kernel_shape); + + Shape output_shape({1, 2, 2, 2}); + std::vector output_vec(8, 0.0f); + Tensor output = make_tensor(output_vec, output_shape); + + ConvReluLayer layer(1, 0, 1, kernel); + std::vector in{input}; + std::vector out{output}; + layer.run(in, out, options); + + std::vector result = *out[0].as(); + + float expected_value = 27.0f; + for (size_t i = 0; i < result.size(); ++i) { + ASSERT_NEAR(result[i], expected_value, 1e-5f); + } +}