1 // SPDX-License-Identifier: Apache-2.0
3 * Copyright (C) 2020 Jijoong Moon <jijoong.moon@samsung.com>
5 * @file concat_layer.cpp
7 * @see https://github.com/nnstreamer/nntrainer
8 * @author Jijoong Moon <jijoong.moon@samsung.com>
9 * @bug No known bugs except for NYI items
10 * @brief This is Concat Layer Class for Neural Network
12 * @todo merge concat and split layer to a common implementation
15 #include <concat_layer.h>
17 #include <layer_context.h>
18 #include <nntrainer_error.h>
19 #include <nntrainer_log.h>
20 #include <node_exporter.h>
21 #include <tensor_dim.h>
22 #include <util_func.h>
25 ConcatLayer::ConcatLayer() : Layer(), leading_helper_dim(1) {}
27 static constexpr size_t SINGLE_INOUT_IDX = 0;
29 void ConcatLayer::finalize(InitLayerContext &context) {
30 auto &concat_dimension_prop = std::get<props::ConcatDimension>(concat_props);
31 /** for backward compatibility, default concat dimension will be channel */
32 /// @todo this is hacky way to force concat dimension to width if channel
33 /// dimension is taken, this is because recurrent realizer, return sequence
34 /// exploits concat layer but have no control over where to stack/axis
35 unsigned int concat_dimension =
36 context.getInputDimensions().front().channel() > 1 ? 3 : 1;
37 if (!concat_dimension_prop.empty())
38 concat_dimension = concat_dimension_prop.get();
41 * The concat is only done along the axis dimension.
42 * For example, consider 2 inputs a, b with dimensions [b,c,h,w] each
43 * 1. concat_dimension = 1, output_dim = [b,c_a+c_b,h,w]
44 * 2. concat_dimension = 2, output_dim = [b,c,h_a+h_b,w]
45 * 3. concat_dimension = 3, output_dim = [b,c,h,w_a+w_b]
47 auto const &input_dims = context.getInputDimensions();
48 const TensorDim &input_dim_0 = input_dims[SINGLE_INOUT_IDX];
49 unsigned int concat_dim_val = input_dim_0.getTensorDim(concat_dimension);
51 for (unsigned int idx = 1; idx < input_dims.size(); ++idx) {
52 const TensorDim &dim = input_dims[idx];
54 for (unsigned int i = 0; i < ml::train::TensorDim::getNumDim(); ++i) {
55 if (i == concat_dimension)
57 if (input_dim_0[i] != dim[i])
58 throw std::runtime_error("Error: concat layer requires same "
59 "shape from all input layers along "
60 "non-concat dimension");
62 concat_dim_val += dim[concat_dimension];
65 TensorDim output_dim = input_dim_0;
66 output_dim.setTensorDim(concat_dimension, concat_dim_val);
68 context.setOutputDimensions({output_dim});
71 * Setup output_reshape_helper to which output will be reshaped in forwarding
72 * to facilitate easier processing.
74 * The helper shape consolidates all the dimensions before the axis
75 * together and all the dimensions after the axis to faciliate
76 * easier splitting of the data.
78 leading_helper_dim = 1;
79 output_reshape_helper.channel(1);
80 output_reshape_helper.height(1);
81 output_reshape_helper.width(1);
82 for (unsigned int idx = 1; idx < concat_dimension; ++idx) {
83 leading_helper_dim *= output_dim.getTensorDim(idx);
86 output_reshape_helper.height(output_dim.getTensorDim(concat_dimension));
88 for (unsigned int idx = concat_dimension + 1;
89 idx < ml::train::TensorDim::getNumDim(); ++idx) {
90 output_reshape_helper.width(output_reshape_helper.width() *
91 output_dim.getTensorDim(idx));
95 * Setup input_reshape_helper to which inputs will be reshaped in forwarding
96 * to facilitate easier processing.
98 input_reshape_helper.resize(input_dims.size());
99 for (unsigned int idx = 0; idx < input_reshape_helper.size(); idx++) {
100 input_reshape_helper[idx] = output_reshape_helper;
101 input_reshape_helper[idx].height(
102 input_dims[idx].getTensorDim(concat_dimension));
105 setBatch(input_dims[SINGLE_INOUT_IDX].batch());
108 void ConcatLayer::forwarding(RunLayerContext &context, bool training) {
110 * @todo avoid copy by creating input here as a shared_tensor of the output
111 * here and then this layer can be in_place as well
113 Tensor &output = context.getOutput(SINGLE_INOUT_IDX);
115 const TensorDim out_dim = output.getDim();
116 output.reshape(output_reshape_helper);
117 unsigned int output_height_offset = 0;
118 unsigned int data_copy_size = output_reshape_helper.width();
120 for (unsigned int idx = 0; idx < context.getNumInputs(); idx++) {
121 Tensor &input = context.getInput(idx);
122 const TensorDim in_dim = input.getDim();
123 auto const &irh = input_reshape_helper[idx];
126 /** loop over the dimensions before the concat dimension */
127 for (unsigned int batch = 0; batch < output.batch(); batch++) {
128 /** loop over the concat dimension itself */
129 for (unsigned int count = 0; count < irh.height(); count++) {
130 Tensor dest_tensor = Tensor::Map(
131 output.getAddress(batch, 0, output_height_offset + count, 0),
132 data_copy_size * sizeof(float), {1, 1, 1, data_copy_size});
133 const Tensor source_tensor = Tensor::Map(
134 input.getAddress(batch, 0, count, 0), data_copy_size * sizeof(float),
135 {1, 1, 1, data_copy_size});
136 dest_tensor.copy(source_tensor);
140 input.reshape(in_dim);
141 output_height_offset += irh.height();
144 output.reshape(out_dim);
147 void ConcatLayer::calcDerivative(RunLayerContext &context) {
149 * @todo avoid copy by creating input here as a shared_tensor of the output
150 * here and then this layer can be in_place as well
152 Tensor &output = context.getIncomingDerivative(SINGLE_INOUT_IDX);
154 const TensorDim out_dim = output.getDim();
155 output.reshape(output_reshape_helper);
156 unsigned int output_height_offset = 0;
157 unsigned int data_copy_size = output_reshape_helper.width();
159 for (unsigned int idx = 0; idx < context.getNumInputs(); idx++) {
160 Tensor &input = context.getOutgoingDerivative(idx);
161 const TensorDim in_dim = input.getDim();
162 auto const &irh = input_reshape_helper[idx];
165 /** loop over the dimensions before the concat dimension */
166 for (unsigned int batch = 0; batch < output.batch(); batch++) {
167 /** loop over the concat dimension itself */
168 for (unsigned int count = 0; count < irh.height(); count++) {
169 const Tensor source_tensor = Tensor::Map(
170 output.getAddress(batch, 0, output_height_offset + count, 0),
171 data_copy_size * sizeof(float), {1, 1, 1, data_copy_size});
172 Tensor dest_tensor = Tensor::Map(input.getAddress(batch, 0, count, 0),
173 data_copy_size * sizeof(float),
174 {1, 1, 1, data_copy_size});
175 dest_tensor.copy(source_tensor);
179 input.reshape(in_dim);
180 output_height_offset += irh.height();
183 output.reshape(out_dim);
186 void ConcatLayer::setProperty(const std::vector<std::string> &values) {
187 auto remain_props = loadProperties(values, concat_props);
188 NNTR_THROW_IF(!remain_props.empty(), std::invalid_argument)
189 << "[ConcatLayer] Unknown Layer Properties count " +
190 std::to_string(values.size());
193 void ConcatLayer::exportTo(Exporter &exporter,
194 const ExportMethods &method) const {
195 Layer::exportTo(exporter, method);
196 exporter.saveResult(concat_props, method, this);
199 } /* namespace nntrainer */