[Semantics] InitContext NumOutput is hint
[platform/core/ml/nntrainer.git] / nntrainer / layers / concat_layer.cpp
1 // SPDX-License-Identifier: Apache-2.0
2 /**
3  * Copyright (C) 2020 Jijoong Moon <jijoong.moon@samsung.com>
4  *
5  * @file   concat_layer.cpp
6  * @date   27 Oct 2020
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
11  *
12  * @todo merge concat and split layer to a common implementation
13  */
14
15 #include <concat_layer.h>
16 #include <cstring>
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>
23
24 namespace nntrainer {
25 ConcatLayer::ConcatLayer() : Layer(), leading_helper_dim(1) {}
26
27 static constexpr size_t SINGLE_INOUT_IDX = 0;
28
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();
39
40   /**
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]
46    */
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);
50
51   for (unsigned int idx = 1; idx < input_dims.size(); ++idx) {
52     const TensorDim &dim = input_dims[idx];
53
54     for (unsigned int i = 0; i < ml::train::TensorDim::getNumDim(); ++i) {
55       if (i == concat_dimension)
56         continue;
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");
61     }
62     concat_dim_val += dim[concat_dimension];
63   }
64
65   TensorDim output_dim = input_dim_0;
66   output_dim.setTensorDim(concat_dimension, concat_dim_val);
67
68   context.setOutputDimensions({output_dim});
69
70   /**
71    * Setup output_reshape_helper to which output will be reshaped in forwarding
72    * to facilitate easier processing.
73    *
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.
77    */
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);
84   }
85
86   output_reshape_helper.height(output_dim.getTensorDim(concat_dimension));
87
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));
92   }
93
94   /**
95    * Setup input_reshape_helper to which inputs will be reshaped in forwarding
96    * to facilitate easier processing.
97    */
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));
103   }
104
105   setBatch(input_dims[SINGLE_INOUT_IDX].batch());
106 }
107
108 void ConcatLayer::forwarding(RunLayerContext &context, bool training) {
109   /**
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
112    */
113   Tensor &output = context.getOutput(SINGLE_INOUT_IDX);
114
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();
119
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];
124     input.reshape(irh);
125
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);
137       }
138     }
139
140     input.reshape(in_dim);
141     output_height_offset += irh.height();
142   }
143
144   output.reshape(out_dim);
145 }
146
147 void ConcatLayer::calcDerivative(RunLayerContext &context) {
148   /**
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
151    */
152   Tensor &output = context.getIncomingDerivative(SINGLE_INOUT_IDX);
153
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();
158
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];
163     input.reshape(irh);
164
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);
176       }
177     }
178
179     input.reshape(in_dim);
180     output_height_offset += irh.height();
181   }
182
183   output.reshape(out_dim);
184 }
185
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());
191 }
192
193 void ConcatLayer::exportTo(Exporter &exporter,
194                            const ExportMethods &method) const {
195   Layer::exportTo(exporter, method);
196   exporter.saveResult(concat_props, method, this);
197 }
198
199 } /* namespace nntrainer */