Line data Source code
1 : // SPDX-License-Identifier: Apache-2.0
2 : /**
3 : * Copyright (C) 2021 Parichay Kapoor <pk.kapoor@samsung.com>
4 : *
5 : * @file layer_node.cpp
6 : * @date 1 April 2021
7 : * @see https://github.com/nnstreamer/nntrainer
8 : * @author Parichay Kapoor <pk.kapoor@samsung.com>
9 : * @author Debadri Samaddar <s.debadri@samsung.com>
10 : * @bug No known bugs except for NYI items
11 : * @brief This is the layer node for network graph
12 : */
13 :
14 : #include "layer_context.h"
15 : #include <algorithm>
16 : #include <cmath>
17 : #include <iterator>
18 : #include <stdexcept>
19 : #include <utility>
20 :
21 : #include <activation_layer.h>
22 : #include <base_properties.h>
23 : #include <bn_layer.h>
24 : #include <common_properties.h>
25 : #include <connection.h>
26 : #include <context.h>
27 : #include <engine.h>
28 : #include <layer_node.h>
29 : #include <nntrainer_error.h>
30 : #include <nntrainer_log.h>
31 : #include <node_exporter.h>
32 : #include <profiler.h>
33 : #include <time_dist.h>
34 : #include <tracer.h>
35 : #include <util_func.h>
36 :
37 : #ifdef ENABLE_OPENCL
38 : #include <cl_context.h>
39 : #endif
40 :
41 : namespace nntrainer {
42 :
43 : #ifdef PROFILE
44 : static constexpr const char *FORWARD_SUFFIX = ":forward";
45 : static constexpr const char *CALC_DERIV_SUFFIX = ":calcDeriv";
46 : static constexpr const char *CALC_GRAD_SUFFIX = ":calcGrad";
47 : #endif
48 :
49 : namespace props {
50 :
51 : /**
52 : * @brief Flatten property, true if needs flatten layer afterwards
53 : */
54 6191 : class Flatten : public Property<bool> {
55 : public:
56 : Flatten() : Property<bool>() {} /**< has default value of 0 */
57 : static constexpr const char *key = "flatten"; /**< unique key to access */
58 : using prop_tag = bool_prop_tag; /**< property type */
59 : };
60 :
61 : /**
62 : * @brief Distribute property, true if it distribute across layer
63 : *
64 : */
65 6191 : class Distribute : public Property<bool> {
66 : public:
67 6191 : Distribute() : Property<bool>() {}
68 : static constexpr const char *key = "distribute";
69 : using prop_tag = bool_prop_tag;
70 : };
71 :
72 : /**
73 : * @brief Loss property, this defines loss specification of layer
74 : *
75 : */
76 : class Loss : public Property<float> {
77 :
78 : public:
79 : /**
80 : * @brief Construct a new loss object with a default value 0.0
81 : *
82 : */
83 6191 : Loss(float value = 0.0) : nntrainer::Property<float>(value) {}
84 : static constexpr const char *key = "loss"; /**< unique key to access */
85 : using prop_tag = float_prop_tag; /**< property type */
86 :
87 : /**
88 : * @brief LossSpec validator
89 : * @todo detect when loss becomes Nan is useful. But it will need dedicated
90 : * throw
91 : * @param v float to validate
92 : * @retval true if is valid number
93 : * @retval false if it is nan
94 : */
95 34147 : bool isValid(const float &v) const override {
96 34147 : if (std::isnan(v)) {
97 10 : ml_logw("loss value is NAN");
98 : }
99 :
100 34147 : return true;
101 : }
102 : };
103 :
104 : /**
105 : * @brief Input shape property which saves a single tensor shape
106 : * (practically, std::array<InputShape> is used)
107 : *
108 : */
109 5075 : class InputShape : public GenericShape {
110 :
111 : public:
112 : static constexpr const char *key = "input_shape"; /**< unique key to access */
113 : using prop_tag = dimension_prop_tag; /**< property type */
114 : };
115 :
116 : /**
117 : * @brief properties for shared from
118 : *
119 : */
120 12382 : class SharedFrom : public Name {
121 : public:
122 : static constexpr const char *key = "shared_from"; /**< unique key to access */
123 : using prop_tag = str_prop_tag; /**< property type */
124 : };
125 :
126 : } // namespace props
127 :
128 : /**
129 : * @brief Destroy the Layer Node object
130 : *
131 : */
132 24763 : LayerNode::~LayerNode() = default;
133 :
134 : /**
135 : * @brief get the compute engine property from property string vector
136 : * : default is CPU
137 : * @return LayerComputeEngine Enum : CPU, GPU, QNN
138 : *
139 : */
140 : ml::train::LayerComputeEngine
141 0 : getComputeEngine(const std::vector<std::string> &props) {
142 0 : for (auto &prop : props) {
143 : std::string key, value;
144 0 : int status = nntrainer::getKeyValue(prop, key, value);
145 0 : if (nntrainer::istrequal(key, "engine")) {
146 : constexpr const auto data =
147 : std::data(props::ComputeEngineTypeInfo::EnumList);
148 0 : for (unsigned int i = 0;
149 0 : i < props::ComputeEngineTypeInfo::EnumList.size(); ++i) {
150 0 : if (nntrainer::istrequal(value.c_str(),
151 0 : props::ComputeEngineTypeInfo::EnumStr[i])) {
152 0 : return data[i];
153 : }
154 : }
155 : }
156 : }
157 :
158 : return ml::train::LayerComputeEngine::CPU;
159 : }
160 :
161 : /**
162 : * @brief Layer factory creator with constructor
163 : */
164 : std::unique_ptr<LayerNode>
165 91 : createLayerNode(const ml::train::LayerType &type,
166 : const std::vector<std::string> &properties) {
167 91 : auto &eg = nntrainer::Engine::Global();
168 180 : return createLayerNode(eg.createLayerObject(type, properties), properties);
169 : }
170 :
171 : /**
172 : * @brief Layer factory creator with constructor
173 : */
174 : std::unique_ptr<LayerNode>
175 1771 : createLayerNode(const std::string &type,
176 : const std::vector<std::string> &properties) {
177 1771 : auto &eg = nntrainer::Engine::Global();
178 3534 : return createLayerNode(eg.createLayerObject(type, properties), properties);
179 : }
180 :
181 : /**
182 : * @brief Layer factory creator with constructor
183 : */
184 : std::unique_ptr<LayerNode>
185 6190 : createLayerNode(std::unique_ptr<nntrainer::Layer> &&layer,
186 : const std::vector<std::string> &properties) {
187 6190 : auto lnode = std::make_unique<LayerNode>(std::move(layer));
188 :
189 6190 : lnode->setProperty(properties);
190 :
191 6188 : return lnode;
192 : }
193 :
194 6191 : LayerNode::LayerNode(std::unique_ptr<nntrainer::Layer> &&l) :
195 : layer(std::move(l)),
196 6191 : inplace_type(InPlaceType::NONE),
197 6191 : needs_calc_derivative(false),
198 6191 : needs_calc_gradient(false),
199 :
200 : output_connections(),
201 : run_context(nullptr),
202 : layer_node_props(new PropsType(
203 12382 : props::Name(), props::Distribute(), props::Trainable(), {}, {},
204 18573 : props::SharedFrom(), props::ClipGradByGlobalNorm(), props::Packed(),
205 18573 : props::WeightDtype(), props::LossScaleForMixed(), props::ComputeEngine())),
206 : layer_node_props_realization(
207 6191 : new RealizationPropsType(props::Flatten(), props::Activation())),
208 12382 : loss(new props::Loss()),
209 : exec_order({0, 0, 0, 0}),
210 6191 : needs_restore_data(false),
211 12382 : data_type({TensorDim::DataType::FP32, TensorDim::DataType::FP32}) {
212 12382 : if (layer && layer->getType() == TimeDistLayer::type) {
213 0 : std::get<props::Distribute>(*layer_node_props).set(true);
214 : }
215 6191 : }
216 :
217 23226 : void LayerNode::setProperty(const std::vector<std::string> &properties) {
218 23226 : auto left_properties = loadProperties(properties, *layer_node_props);
219 : left_properties =
220 23217 : loadProperties(left_properties, *layer_node_props_realization);
221 23215 : layer->setProperty(left_properties);
222 :
223 23208 : if (getType() == ActivationLayer::type) {
224 : auto &act_prop = std::get<props::Activation>(*layer_node_props_realization);
225 2692 : if (!act_prop.empty()) {
226 10536 : layer->setProperty({"activation=" + to_string(act_prop)});
227 : }
228 : }
229 23217 : }
230 :
231 0 : std::string LayerNode::getProperty(const std::string &key) {
232 0 : if (layer_node_props) {
233 : std::string result = find_in_tuple(*layer_node_props, key);
234 0 : if (!result.empty()) {
235 0 : return result;
236 : }
237 : }
238 :
239 0 : if (layer_node_props_realization) {
240 : std::string result = find_in_tuple(*layer_node_props_realization, key);
241 0 : if (!result.empty()) {
242 0 : return result;
243 : }
244 : }
245 :
246 0 : return layer->getProperty(key);
247 : }
248 :
249 4 : void LayerNode::setWeights(const std::vector<float *> weights) {
250 5 : NNTR_THROW_IF(!run_context, std::runtime_error)
251 : << __func__ << " layer needs to be finalized first!";
252 :
253 4 : NNTR_THROW_IF(getNumWeights() != weights.size(), std::runtime_error)
254 : << __func__ << " Number of Weights dismatch!";
255 :
256 : // Needs Deep copy
257 6 : for (unsigned int idx = 0; idx < getNumWeights(); ++idx) {
258 4 : Tensor &w = getWeight(idx);
259 4 : std::copy(weights[idx], weights[idx] + w.size(), w.getData());
260 : }
261 2 : }
262 :
263 8603 : const unsigned LayerNode::getInputConnectionIndex(unsigned nth) const {
264 : auto &input_conns =
265 : std::get<std::vector<props::InputConnection>>(*layer_node_props);
266 8603 : return input_conns.at(nth).get().getIndex();
267 : }
268 :
269 14103 : const std::string &LayerNode::getInputConnectionName(unsigned nth) const {
270 : auto &input_conns =
271 : std::get<std::vector<props::InputConnection>>(*layer_node_props);
272 14103 : return input_conns.at(nth).get().getName();
273 : }
274 :
275 391 : void LayerNode::setInputConnectionIndex(unsigned nth, unsigned index) {
276 : auto &input_conns =
277 : std::get<std::vector<props::InputConnection>>(*layer_node_props);
278 391 : input_conns.at(nth).get().getIndex() = index;
279 391 : }
280 :
281 397 : void LayerNode::setInputConnectionName(unsigned nth, const std::string &name) {
282 : auto &input_conns =
283 : std::get<std::vector<props::InputConnection>>(*layer_node_props);
284 397 : input_conns.at(nth).get().getName() = name;
285 397 : }
286 :
287 7786 : const Connection *LayerNode::getOutputConnection(unsigned nth) const {
288 7786 : return output_connections.at(nth).get();
289 : }
290 :
291 4519 : void LayerNode::setOutputConnection(unsigned nth, const std::string &name,
292 : unsigned index) {
293 4519 : if (nth >= output_connections.size()) {
294 4505 : output_connections.resize(nth + 1);
295 : }
296 :
297 : auto &con = output_connections[nth];
298 : // Should be override connection for the batch normalization realizer
299 : // NNTR_THROW_IF(con, std::invalid_argument)
300 : // << "cannot override connection, this slot is reserved for "
301 : // << con->toString();
302 :
303 9038 : con = std::make_unique<Connection>(name, index);
304 4519 : }
305 :
306 0 : void LayerNode::setComputeEngine(
307 : const ml::train::LayerComputeEngine &compute_engine) {
308 : // setting compute_engine of LayerNode
309 : // can be reused later to propagate this info
310 0 : this->compute_engine = compute_engine;
311 0 : }
312 :
313 209940 : const std::string LayerNode::getName() const {
314 : auto &name = std::get<props::Name>(*layer_node_props);
315 209940 : return name.empty() ? "" : name.get();
316 : }
317 :
318 16 : std::ostream &operator<<(std::ostream &out, const LayerNode &l) {
319 :
320 : auto &input_connections =
321 : std::get<std::vector<props::InputConnection>>(*l.layer_node_props);
322 :
323 64 : out << "[" << l.getName() << '/' << l.getType() << "]\n";
324 16 : auto print_vector = [&out](const auto &cons, const std::string &title) {
325 32 : out << title << "[" << cons.size() << "] ";
326 16 : for (auto &con : cons) {
327 0 : out << con.toString() << ' ';
328 : }
329 16 : out << '\n';
330 16 : };
331 :
332 16 : auto print_vector_2 = [&out](const auto &cons, const std::string &title) {
333 32 : out << title << "[" << cons.size() << "] ";
334 16 : for (auto &con : cons) {
335 0 : out << con->toString() << ' ';
336 : }
337 16 : out << '\n';
338 16 : };
339 :
340 32 : print_vector(
341 32 : std::vector<Connection>(input_connections.begin(), input_connections.end()),
342 : " input_connections");
343 16 : print_vector_2(l.output_connections, "output_connections");
344 16 : return out;
345 : }
346 :
347 3949 : ActivationType LayerNode::getActivationType() const {
348 : auto &act_prop = std::get<props::Activation>(*layer_node_props_realization);
349 3949 : if (act_prop.empty()) {
350 : return ActivationType::ACT_NONE;
351 : }
352 :
353 482 : return act_prop;
354 : }
355 :
356 27685 : unsigned int LayerNode::getNumInputConnections() const {
357 : auto &input_conns =
358 : std::get<std::vector<props::InputConnection>>(*layer_node_props);
359 27685 : return input_conns.size();
360 : }
361 :
362 8659 : unsigned int LayerNode::getNumOutputConnections() const {
363 8659 : return output_connections.size();
364 : }
365 :
366 19254 : const std::vector<std::string> LayerNode::getInputLayers() const {
367 : auto &input_connections =
368 : std::get<std::vector<props::InputConnection>>(*layer_node_props);
369 : std::vector<std::string> names;
370 19254 : names.reserve(input_connections.size());
371 19254 : std::transform(
372 : input_connections.begin(), input_connections.end(),
373 : std::back_inserter(names),
374 : [](const Connection &con) -> const auto & { return con.getName(); });
375 19254 : return names;
376 0 : }
377 :
378 9494 : const std::vector<std::string> LayerNode::getOutputLayers() const {
379 : std::vector<std::string> names;
380 9494 : names.reserve(output_connections.size());
381 :
382 18650 : for (auto &conn : output_connections) {
383 9156 : if (conn == nullptr) {
384 32 : ml_logw("intermediate output is empty for layer: %s", getName().c_str());
385 16 : continue;
386 : }
387 9140 : names.push_back(conn->getName());
388 : }
389 9494 : return names;
390 0 : }
391 :
392 3742 : ActivationType LayerNode::getActivationToBeRealized() const {
393 3742 : if (getType() == ActivationLayer::type)
394 : return ActivationType::ACT_NONE;
395 : else
396 3742 : return getActivationType();
397 : }
398 :
399 117652 : const std::string LayerNode::getType() const { return getLayer()->getType(); }
400 :
401 87746 : bool LayerNode::getTrainable() const {
402 87746 : if (run_context)
403 : /**
404 : * if a layer does not contain any weights, it will be treated as a
405 : * non-trainable layer.
406 : */
407 110356 : return std::get<props::Trainable>(*layer_node_props) &&
408 110356 : (run_context->getNumWeights() > 0);
409 : else
410 32253 : return std::get<props::Trainable>(*layer_node_props);
411 : }
412 :
413 4060 : bool LayerNode::getFlatten() const {
414 : auto &flatten = std::get<props::Flatten>(*layer_node_props_realization);
415 4060 : if (flatten.empty()) {
416 : return false;
417 : }
418 2 : return flatten.get();
419 : }
420 :
421 9347 : std::string LayerNode::getSharedFrom() const {
422 : auto &shared_from = std::get<props::SharedFrom>(*layer_node_props);
423 9347 : return shared_from.empty() ? "" : shared_from.get();
424 : }
425 :
426 79308 : bool LayerNode::getDistribute() const {
427 : auto &distribute = std::get<props::Distribute>(*layer_node_props);
428 79308 : if (distribute.empty()) {
429 : return false;
430 : }
431 11 : return distribute.get();
432 : }
433 :
434 177637 : const nntrainer::Layer *LayerNode::getLayer() const {
435 177637 : if (run_context && getDistribute())
436 5 : return static_cast<TimeDistLayer *>(layer.get())->getDistLayer();
437 : else
438 177632 : return layer.get();
439 : }
440 :
441 3066 : nntrainer::Layer *LayerNode::getLayer() {
442 3066 : if (run_context && getDistribute())
443 2 : return static_cast<TimeDistLayer *>(layer.get())->getDistLayer();
444 : else
445 3064 : return layer.get();
446 : }
447 :
448 459 : void LayerNode::setOutputLayers(const std::vector<std::string> &layers) {
449 : output_connections.clear();
450 459 : output_connections.reserve(layers.size());
451 459 : std::transform(
452 : layers.begin(), layers.end(), std::back_inserter(output_connections),
453 458 : [](const std::string &id) { return std::make_unique<Connection>(id); });
454 459 : }
455 :
456 7172 : bool LayerNode::hasInputShapeProperty() const {
457 : auto &input_shapes =
458 : std::get<std::vector<props::InputShape>>(*layer_node_props);
459 :
460 7172 : return !input_shapes.empty() &&
461 : std::all_of(input_shapes.begin(), input_shapes.end(),
462 7172 : [](const auto &input) { return !input.empty(); });
463 : }
464 :
465 2882 : const std::vector<TensorDim> LayerNode::getInputDimensions() const {
466 2883 : NNTR_THROW_IF(!run_context, std::runtime_error)
467 : << __func__ << " layer needs to be finalized first!";
468 2881 : auto sz = run_context->getNumInputs();
469 : std::vector<TensorDim> dims;
470 2881 : dims.reserve(sz);
471 :
472 6320 : for (auto i = 0u; i < sz; ++i) {
473 6878 : dims.push_back(run_context->getInput(i).getDim());
474 : }
475 :
476 2881 : return dims;
477 0 : }
478 :
479 2586 : const std::vector<TensorDim> LayerNode::getOutputDimensions() const {
480 2587 : NNTR_THROW_IF(!run_context, std::runtime_error)
481 : << __func__ << " layer needs to be finalized first!";
482 2585 : auto sz = run_context->getNumOutputs();
483 : std::vector<TensorDim> dims;
484 2585 : dims.reserve(sz);
485 :
486 5626 : for (auto i = 0u; i < sz; ++i) {
487 6082 : dims.push_back(run_context->getOutput(i).getDim());
488 : }
489 :
490 2585 : return dims;
491 0 : }
492 :
493 2542 : void LayerNode::exportTo(Exporter &exporter,
494 : const ml::train::ExportMethods &method) const {
495 2542 : exporter.saveResult(*layer_node_props, method, this);
496 2542 : layer->exportTo(exporter, method);
497 2542 : }
498 :
499 1 : void LayerNode::read(std::ifstream &file, bool opt_var,
500 : ml::train::ExecutionMode mode, bool fsu,
501 : size_t start_offset, bool read_from_offset, int file_fd) {
502 2 : NNTR_THROW_IF(!run_context, std::runtime_error)
503 : << __func__ << " layer needs to be finalized first!";
504 0 : getLayer()->read(file, *run_context, opt_var, mode,
505 0 : (getTrainable() && mode == ml::train::ExecutionMode::TRAIN),
506 : getWeightDataType(), fsu, start_offset, read_from_offset,
507 : file_fd);
508 0 : }
509 :
510 0 : void LayerNode::read(ReadSource src, bool opt_var,
511 : ml::train::ExecutionMode mode, bool fsu,
512 : size_t start_offset, bool read_from_offset) {
513 0 : NNTR_THROW_IF(!run_context, std::runtime_error)
514 : << __func__ << " layer needs to be finalized first!";
515 0 : getLayer()->read(src, *run_context, opt_var, mode,
516 0 : (getTrainable() && mode == ml::train::ExecutionMode::TRAIN),
517 : getWeightDataType(), fsu, start_offset, read_from_offset);
518 0 : }
519 :
520 3135 : void LayerNode::save(std::ofstream &file, bool opt_var,
521 : ml::train::ExecutionMode mode) const {
522 3136 : NNTR_THROW_IF(!run_context, std::runtime_error)
523 : << __func__ << " layer needs to be finalized first!";
524 6268 : getLayer()->save(file, *run_context, opt_var, mode,
525 3134 : (getTrainable() && mode == ml::train::ExecutionMode::TRAIN),
526 : getWeightDataType());
527 3134 : }
528 :
529 93 : void LayerNode::clearOptVar() {
530 94 : NNTR_THROW_IF(!run_context, std::runtime_error)
531 : << __func__ << " layer needs to be finalized first!";
532 148 : for (unsigned int i = 0; i < run_context->getNumWeights(); ++i) {
533 56 : if (run_context->isGradientLastAccess(i) && getTrainable()) {
534 : /// @note read optimizer variables
535 150 : for (unsigned int j = 0; j < run_context->getNumWeightOptVar(i); ++j) {
536 96 : run_context->getWeightOptVar(i, j).initialize();
537 : }
538 : }
539 : }
540 92 : }
541 :
542 : /**
543 : * @brief Finalize creating the layer node
544 : */
545 4575 : InitLayerContext LayerNode::finalize(const std::vector<TensorDim> &input_dims,
546 : std::array<std::string, 3> tensor_type,
547 : ml::train::ExecutionMode mode) {
548 : // auto get_tensor_datatype = [](const std::string ty) -> TensorDim::DataType
549 : // { return from_string(ty);
550 : // };
551 :
552 4575 : if (run_context)
553 : throw std::runtime_error(
554 1 : "Trying to finalizing a layer which is already finalized in layer: " +
555 3 : getName());
556 :
557 : std::vector<TensorDim> actual_input_dims;
558 : auto &prop_dims = std::get<std::vector<props::InputShape>>(*layer_node_props);
559 : auto &prop_in_layers =
560 : std::get<std::vector<props::InputConnection>>(*layer_node_props);
561 :
562 : /** prepare input dimensions */
563 4574 : if (!input_dims.empty()) {
564 3435 : actual_input_dims = input_dims;
565 3435 : if (hasInputShapeProperty()) {
566 : std::vector<TensorDim> actual_prop_dims(prop_dims.begin(),
567 0 : prop_dims.end());
568 : /// if prop_dims exist, check if it's same with given input_dims
569 0 : NNTR_THROW_IF(input_dims != actual_prop_dims, std::invalid_argument)
570 : << "calculated input dimension is different from given input_shape "
571 : "property";
572 0 : for (auto &d : actual_prop_dims) {
573 0 : d.setDataType(
574 : str_converter<enum_class_prop_tag, nntrainer::TensorDataTypeInfo>::
575 : from_string(tensor_type[2]));
576 0 : d.setFormat(
577 : str_converter<enum_class_prop_tag, nntrainer::TensorFormatInfo>::
578 : from_string(tensor_type[0]));
579 : }
580 0 : }
581 : } else {
582 1141 : NNTR_THROW_IF(!hasInputShapeProperty(), std::invalid_argument)
583 : << "if input dims not given, input shapes must be given by the user as "
584 : "property";
585 : /// arguably, below check can go away
586 1137 : NNTR_THROW_IF((prop_dims.size() != prop_in_layers.size()) &&
587 : (prop_dims.size() != 1 || !prop_in_layers.empty()),
588 : std::invalid_argument)
589 : << "input shapes must be one if connection is not given but given "
590 : "dimesions size of: "
591 : << prop_dims.size();
592 : actual_input_dims =
593 1137 : std::vector<TensorDim>(prop_dims.begin(), prop_dims.end());
594 2330 : for (auto &d : actual_input_dims) {
595 : /// Input Tensor type of input layer needs to be float.
596 2386 : d.setDataType(
597 : str_converter<enum_class_prop_tag,
598 : nntrainer::TensorDataTypeInfo>::from_string("FP32"));
599 1193 : d.setFormat(
600 : str_converter<enum_class_prop_tag, nntrainer::TensorFormatInfo>::
601 : from_string(tensor_type[0]));
602 : }
603 : }
604 :
605 4572 : NNTR_THROW_IF(actual_input_dims.size() < getNumInputConnections(),
606 : std::invalid_argument)
607 : << "number of input dimensions must be equal or larger "
608 0 : << "than number of input connections, node name: " << getName()
609 : << " num input dims: " << input_dims.size()
610 0 : << " num connections: " << getNumInputConnections();
611 :
612 : /** manipulate layers if required */
613 9144 : if (getType() != TimeDistLayer::type && getDistribute()) {
614 2 : std::unique_ptr<TimeDistLayer> dlayer(new TimeDistLayer());
615 : NNTR_THROW_IF(!dlayer, std::invalid_argument)
616 : << "Error creating time distribution layer";
617 : dlayer->setDistLayer(std::move(layer));
618 : layer = std::move(dlayer);
619 : }
620 :
621 9144 : const auto &scope = getSharedFrom().empty() ? getName() : getSharedFrom();
622 : float max_norm = 0.0;
623 : float loss_scale = 1.0;
624 4572 : if (!std::get<props::ClipGradByGlobalNorm>(*layer_node_props).empty())
625 16 : max_norm = std::get<props::ClipGradByGlobalNorm>(*layer_node_props).get();
626 :
627 4572 : if (!std::get<props::LossScaleForMixed>(*layer_node_props).empty())
628 4113 : loss_scale = std::get<props::LossScaleForMixed>(*layer_node_props).get();
629 :
630 4572 : if (!std::get<props::ComputeEngine>(*layer_node_props).empty()) {
631 0 : compute_engine = std::get<props::ComputeEngine>(*layer_node_props).get();
632 : }
633 :
634 4572 : if (!std::get<props::Packed>(*layer_node_props).empty()) {
635 4572 : bool isPacked = std::get<props::Packed>(*layer_node_props);
636 4572 : if (!isPacked) {
637 : // set weight type = activation type
638 : tensor_type[1] = tensor_type[2];
639 : }
640 : }
641 :
642 4572 : if (!std::get<props::WeightDtype>(*layer_node_props).empty()) {
643 : // set weight type = tensor_data_type
644 8 : tensor_type[1] = to_string(std::get<props::WeightDtype>(*layer_node_props));
645 : }
646 :
647 : std::vector<bool> out_info;
648 4572 : out_info.reserve(output_connections.size());
649 : std::transform(output_connections.begin(), output_connections.end(),
650 : std::back_inserter(out_info), [](auto &con) { return !!con; });
651 :
652 4572 : if (requireLabel() && out_info.empty()) {
653 : /// as we are using output Grad to save label, add fake out info if it's
654 : /// label. This should be substituted to the proper label management
655 630 : out_info.push_back(true);
656 : }
657 :
658 : auto context = InitLayerContext(
659 : actual_input_dims, out_info, getInPlaceType() != InPlaceType::NONE,
660 13724 : getName(), scope, max_norm, tensor_type, loss_scale, mode, compute_engine);
661 :
662 4571 : layer->finalize(context);
663 :
664 : #ifdef ENABLE_TEST
665 9132 : init_context = std::make_unique<InitLayerContext>(context);
666 : #endif // ENABLE_TEST
667 :
668 : #ifdef PROFILE
669 : auto profile_name = [this](const char *suffix) {
670 : return getName() + suffix + "(" + getType() + ")";
671 : };
672 : #endif
673 :
674 : PROFILE_TIME_REGISTER_EVENT(forward_event_key, profile_name(FORWARD_SUFFIX));
675 : PROFILE_TIME_REGISTER_EVENT(calc_deriv_event_key,
676 : profile_name(CALC_DERIV_SUFFIX));
677 : PROFILE_TIME_REGISTER_EVENT(calc_grad_event_key,
678 : profile_name(CALC_GRAD_SUFFIX));
679 :
680 4566 : return context;
681 4579 : }
682 :
683 : /**
684 : * @brief Refinalize creating the layer node
685 : */
686 : InitLayerContext
687 28 : LayerNode::refinalize(const std::vector<TensorDim> &input_dims) {
688 : std::vector<TensorDim> actual_input_dims;
689 : auto &prop_dims = std::get<std::vector<props::InputShape>>(*layer_node_props);
690 : auto &prop_in_layers =
691 : std::get<std::vector<props::InputConnection>>(*layer_node_props);
692 :
693 : /** prepare input dimensions */
694 28 : if (!input_dims.empty()) {
695 27 : actual_input_dims = input_dims;
696 27 : if (hasInputShapeProperty()) {
697 : std::vector<TensorDim> actual_prop_dims(prop_dims.begin(),
698 0 : prop_dims.end());
699 : /// if prop_dims exist, check if it's same with given input_dims
700 0 : NNTR_THROW_IF(input_dims != actual_prop_dims, std::invalid_argument)
701 : << "calculated input dimension is different from given input_shape "
702 : "property";
703 0 : }
704 : } else {
705 1 : NNTR_THROW_IF(!hasInputShapeProperty(), std::invalid_argument)
706 : << "if input dims not given, input shapes must be given by the user as "
707 : "property";
708 : /// arguably, below check can go away
709 1 : NNTR_THROW_IF((prop_dims.size() != prop_in_layers.size()) &&
710 : (prop_dims.size() != 1 || !prop_in_layers.empty()),
711 : std::invalid_argument)
712 : << "input shapes must be one if connection is not given but given "
713 : "dimesions size of: "
714 : << prop_dims.size();
715 : actual_input_dims =
716 1 : std::vector<TensorDim>(prop_dims.begin(), prop_dims.end());
717 : }
718 :
719 28 : NNTR_THROW_IF(actual_input_dims.size() < getNumInputConnections(),
720 : std::invalid_argument)
721 : << "number of input dimensions must be equal or larger "
722 0 : << "than number of input connections, node name: " << getName()
723 : << " num input dims: " << input_dims.size()
724 0 : << " num connections: " << getNumInputConnections();
725 :
726 : /** manipulate layers if required */
727 56 : if (getType() != TimeDistLayer::type && getDistribute()) {
728 0 : std::unique_ptr<TimeDistLayer> dlayer(new TimeDistLayer());
729 : NNTR_THROW_IF(!dlayer, std::invalid_argument)
730 : << "Error creating time distribution layer";
731 : dlayer->setDistLayer(std::move(layer));
732 : layer = std::move(dlayer);
733 : }
734 :
735 56 : const auto &scope = getSharedFrom().empty() ? getName() : getSharedFrom();
736 : float max_norm = 0.0;
737 28 : if (!std::get<props::ClipGradByGlobalNorm>(*layer_node_props).empty())
738 0 : max_norm = std::get<props::ClipGradByGlobalNorm>(*layer_node_props).get();
739 :
740 : std::vector<bool> out_info;
741 28 : out_info.reserve(output_connections.size());
742 : std::transform(output_connections.begin(), output_connections.end(),
743 : std::back_inserter(out_info), [](auto &con) { return !!con; });
744 :
745 28 : if (requireLabel() && out_info.empty()) {
746 : /// as we are using output Grad to save label, add fake out info if it's
747 : /// label. This should be substituted to the proper label management
748 1 : out_info.push_back(true);
749 : }
750 :
751 : auto context = InitLayerContext(actual_input_dims, out_info,
752 : getInPlaceType() != InPlaceType::NONE,
753 56 : getName(), scope, max_norm);
754 :
755 28 : layer->finalize(context);
756 :
757 : #ifdef ENABLE_TEST
758 56 : init_context = std::make_unique<InitLayerContext>(context);
759 : #endif // ENABLE_TEST
760 :
761 : #ifdef PROFILE
762 : auto profile_name = [this](const char *suffix) {
763 : return getName() + suffix + "(" + getType() + ")";
764 : };
765 : #endif
766 :
767 : PROFILE_TIME_REGISTER_EVENT(forward_event_key, profile_name(FORWARD_SUFFIX));
768 : PROFILE_TIME_REGISTER_EVENT(calc_deriv_event_key,
769 : profile_name(CALC_DERIV_SUFFIX));
770 : PROFILE_TIME_REGISTER_EVENT(calc_grad_event_key,
771 : profile_name(CALC_GRAD_SUFFIX));
772 :
773 28 : return context;
774 84 : }
775 :
776 : /**
777 : * @brief Forward Propagation of a layer
778 : */
779 27303 : void LayerNode::forwarding(bool training) {
780 27303 : loss->set(run_context->getRegularizationLoss());
781 :
782 : PROFILE_TIME_START(forward_event_key);
783 27303 : if (reStoreData()) {
784 0 : if (getInPlaceType() == InPlaceType::NONE) {
785 0 : for (unsigned int i = 0; i < run_context->getNumOutputs(); ++i) {
786 0 : run_context->getOutput(i).setValue(0);
787 0 : if (!run_context->getOutputGradUnsafe(i).isValid())
788 0 : run_context->getOutputGradUnsafe(i).setValue(0);
789 : }
790 0 : for (unsigned int i = 0; i < run_context->getNumWeights(); ++i) {
791 0 : if (run_context->weightHasGradient(i)) {
792 0 : run_context->getWeightGrad(i).setValue(0);
793 : }
794 : }
795 : }
796 : }
797 :
798 27303 : layer->forwarding(*run_context, training);
799 : reStoreData(false);
800 : PROFILE_TIME_END(forward_event_key);
801 54606 : TRACE_MEMORY() << getName() + ": F";
802 54606 : TRACE_TIME() << getName() + ": F";
803 :
804 : #ifdef DEBUG
805 : if (!run_context->validate(getNumInputConnections() == 0, !requireLabel()))
806 : throw std::runtime_error("Running forwarding() layer " + getName() +
807 : " invalidated the context.");
808 : #endif
809 :
810 : /** add loss only for loss layers */
811 27303 : if (requireLabel())
812 6844 : loss->set(*loss + run_context->getLoss());
813 27303 : }
814 :
815 : /**
816 : * @brief Incremental forward Propagation of a layer
817 : */
818 0 : void LayerNode::incremental_forwarding(unsigned int from, unsigned int to,
819 : bool training) {
820 0 : loss->set(run_context->getRegularizationLoss());
821 : PROFILE_TIME_START(forward_event_key);
822 : // std::cerr << getType() << "\n";
823 0 : layer->incremental_forwarding(*run_context, from, to, training);
824 : PROFILE_TIME_END(forward_event_key);
825 0 : TRACE_MEMORY() << getName() + ": F";
826 0 : TRACE_TIME() << getName() + ": F";
827 :
828 : #ifdef DEBUG
829 : if (!run_context->validate(getNumInputConnections() == 0, !requireLabel()))
830 : throw std::runtime_error("Running forwarding() layer " + getName() +
831 : " invalidated the context.");
832 : #endif
833 :
834 : /** add loss only for loss layers */
835 0 : if (requireLabel())
836 0 : loss->set(*loss + run_context->getLoss());
837 0 : }
838 :
839 : /**
840 : * @brief calc the derivative to be passed to the previous layer
841 : */
842 9774 : void LayerNode::calcDerivative() {
843 : PROFILE_TIME_START(calc_deriv_event_key);
844 : PROFILE_MEM_ANNOTATE("CalcDerivative: " + getName());
845 9774 : layer->calcDerivative(*run_context);
846 : PROFILE_TIME_END(calc_deriv_event_key);
847 19548 : TRACE_MEMORY() << getName() + ": CD";
848 19548 : TRACE_TIME() << getName() + ": CD";
849 :
850 : #ifdef DEBUG
851 : if (!run_context->validate(getNumInputConnections() == 0, !requireLabel()))
852 : throw std::runtime_error("Running calcDerivative() layer " + getName() +
853 : " invalidated the context.");
854 : #endif
855 9774 : }
856 :
857 : /**
858 : * @brief Calculate the derivative of a layer
859 : */
860 7467 : void LayerNode::calcGradient() {
861 : PROFILE_TIME_START(calc_grad_event_key);
862 7467 : if (needs_calc_gradient) {
863 : PROFILE_MEM_ANNOTATE("CalcGradient: " + getName());
864 7458 : layer->calcGradient(*run_context);
865 14916 : TRACE_MEMORY() << getName() + ": CG";
866 14916 : TRACE_TIME() << getName() + ": CG";
867 : }
868 : PROFILE_TIME_END(calc_grad_event_key);
869 :
870 : #ifdef DEBUG
871 : if (!run_context->validate(getNumInputConnections() == 0, !requireLabel()))
872 : throw std::runtime_error("Running calcGradient() layer " + getName() +
873 : " invalidated the context.");
874 : #endif
875 7467 : }
876 :
877 : /**
878 : * @brief Set the batch for the layer
879 : */
880 3063 : void LayerNode::setBatch(unsigned int batch) {
881 3064 : NNTR_THROW_IF(!run_context, std::invalid_argument)
882 : << " setting batch not supported before initialization";
883 :
884 3062 : getLayer()->setBatch(*run_context, batch);
885 3062 : }
886 :
887 0 : void LayerNode::updateTensorsByInputDimensions(
888 : std::vector<TensorDim> input_dimensions) {
889 0 : NNTR_THROW_IF(!run_context, std::invalid_argument)
890 : << " update tensors not supported before initialization";
891 :
892 0 : getLayer()->updateTensorsByInputDimensions(*run_context, input_dimensions);
893 0 : }
894 :
895 : /**
896 : * @brief If the current layer can support in-place
897 : */
898 2468 : bool LayerNode::supportInPlace() const {
899 : ///@note below is a quick fix, we need to have a guard that this shouldn't
900 : /// be
901 : /// query until realizeProps has been finalized ( which means we will need
902 : /// another end point to fixate this property )
903 2468 : if (getDistribute()) {
904 : return false;
905 : }
906 2468 : return layer->supportInPlace();
907 : }
908 :
909 : /**
910 : * @brief Get the inplace direction for the layer
911 : */
912 2390 : InPlaceDirection LayerNode::getInPlaceDirection() const {
913 2390 : return layer->getInPlaceDirection();
914 : };
915 :
916 : /**
917 : * @brief Initialize the in-place settings of the layer
918 : * @return InPlaceType
919 : */
920 2565 : InPlaceType LayerNode::initializeInPlace() {
921 2565 : inplace_type = layer->initializeInPlace();
922 2565 : return inplace_type;
923 : }
924 :
925 : /**
926 : * @brief check if this layer requires label to be passed
927 : */
928 49618 : bool LayerNode::requireLabel() const { return getLayer()->requireLabel(); }
929 :
930 : /**
931 : * @brief get loss for the layer
932 : * @return loss of the layer
933 : */
934 37151 : float LayerNode::getLoss() const { return *loss; }
935 :
936 4437 : void LayerNode::configureRunContext(const std::vector<Weight *> &weights,
937 : const std::vector<Var_Grad *> &inputs,
938 : const std::vector<Var_Grad *> &outputs,
939 : const std::vector<Var_Grad *> &tensors,
940 : float loss_scale,
941 : std::shared_ptr<ContextData> ct_data) {
942 8874 : run_context = std::make_unique<RunLayerContext>(
943 4437 : getName(), getTrainable(), 0.0f, getInPlaceType() != InPlaceType::NONE,
944 4437 : loss_scale, ct_data, false, weights, inputs, outputs, tensors);
945 4437 : }
946 :
947 : /**
948 : * @brief Print Options when printing layer info
949 : */
950 : typedef enum {
951 : // clang-format off
952 : PRINT_INST_INFO = (1 << 0), /**< Option to print type & instance address info */
953 : PRINT_SHAPE_INFO = (1 << 1), /**< Option to print shape information, invalid before initiation*/
954 : PRINT_PROP = (1 << 2), /**< Option to print properties */
955 : PRINT_PROP_META = (1 << 3), /**< Option to print properties that describe meta info
956 : e.g) layer activation type for non-activation layer. */
957 : PRINT_WEIGHTS = (1 << 4), /**< Option to print weights */
958 : PRINT_METRIC = (1 << 5) /**< Option to print metrics (currently loss only) */
959 : // clang-format on
960 : } PrintOption;
961 :
962 28 : void LayerNode::printPreset(std::ostream &out, PrintPreset preset) {
963 : unsigned int flags = 0;
964 :
965 28 : switch (preset) {
966 4 : case PrintPreset::PRINT_ALL:
967 : flags = PRINT_WEIGHTS | PRINT_METRIC;
968 : /// fall through intended
969 4 : case PrintPreset::PRINT_SUMMARY_META:
970 4 : flags |= PRINT_PROP_META;
971 : /// fall through intended
972 4 : case PrintPreset::PRINT_SUMMARY:
973 4 : flags |= PRINT_INST_INFO | PRINT_SHAPE_INFO | PRINT_PROP | PRINT_PROP_META;
974 : break;
975 : case PrintPreset::PRINT_NONE:
976 : return;
977 0 : default:
978 0 : throw ::std::invalid_argument("undefined preset given");
979 : }
980 4 : print(out, flags);
981 : }
982 :
983 476 : void LayerNode::remapIdentifiers(std::function<void(std::string &)> remap_fn) {
984 1 : NNTR_THROW_IF(isFinalized(), std::invalid_argument)
985 : << "cannot remap identifiers after finalized";
986 : auto &name = std::get<props::Name>(*layer_node_props);
987 475 : if (!name.empty()) {
988 475 : remap_fn(name.get());
989 : }
990 :
991 : auto &shared_from = std::get<props::SharedFrom>(*layer_node_props);
992 475 : if (!shared_from.empty()) {
993 274 : remap_fn(shared_from.get());
994 : }
995 :
996 : /** remap connections without touching index */
997 475 : remapConnections(
998 1081 : [&remap_fn](std::string &name, unsigned &_) { remap_fn(name); });
999 475 : }
1000 :
1001 19241 : void LayerNode::remapConnections(
1002 : std::function<void(std::string &, unsigned &)> remap_fn) {
1003 2 : NNTR_THROW_IF(isFinalized(), std::invalid_argument)
1004 : << "cannot remap identifiers after finalized";
1005 :
1006 : auto &input_conns =
1007 : std::get<std::vector<props::InputConnection>>(*layer_node_props);
1008 :
1009 40271 : for (auto &input_layer : input_conns) {
1010 21032 : auto &name = input_layer.get().getName();
1011 21032 : auto &idx = input_layer.get().getIndex();
1012 : remap_fn(name, idx);
1013 : }
1014 :
1015 19239 : for (auto &output_layer : output_connections) {
1016 0 : if (output_layer == nullptr) {
1017 0 : continue;
1018 : }
1019 :
1020 : auto &name = output_layer->getName();
1021 : auto &idx = output_layer->getIndex();
1022 : remap_fn(name, idx);
1023 : }
1024 19239 : }
1025 :
1026 385 : std::unique_ptr<LayerNode> LayerNode::cloneConfiguration() {
1027 1 : NNTR_THROW_IF(isFinalized(), std::invalid_argument)
1028 : << "It is prohibited to clone configuration";
1029 384 : Exporter e;
1030 384 : exportTo(e, ml::train::ExportMethods::METHOD_STRINGVECTOR);
1031 384 : e.saveResult(*layer_node_props_realization,
1032 : ml::train::ExportMethods::METHOD_STRINGVECTOR, this);
1033 384 : auto props = e.getResult<ml::train::ExportMethods::METHOD_STRINGVECTOR>();
1034 :
1035 : std::vector<std::string> key_val_props;
1036 384 : key_val_props.reserve(props->size());
1037 5004 : for (auto &entry : *props) {
1038 9240 : key_val_props.push_back(entry.first + "=" + entry.second);
1039 : }
1040 :
1041 768 : return createLayerNode(getType(), key_val_props);
1042 384 : }
1043 :
1044 4 : void LayerNode::printShapeInfo(std::ostream &out) {
1045 8 : for (unsigned int idx = 0; idx < getNumInputs(); ++idx) {
1046 4 : out << "input " << run_context->getInput(idx).getDim();
1047 : }
1048 6 : for (unsigned int idx = 0; idx < getNumWeights(); idx++) {
1049 2 : out << "weight " << run_context->getWeight(idx).getDim();
1050 : }
1051 8 : for (unsigned int idx = 0; idx < getNumOutputs(); ++idx) {
1052 4 : out << "output " << run_context->getOutput(idx).getDim();
1053 : }
1054 4 : }
1055 :
1056 4 : void LayerNode::printMetric(std::ostream &out) {
1057 8 : out << "Layer loss value: " << getLoss() << "\n";
1058 4 : }
1059 :
1060 4 : void LayerNode::print(std::ostream &out, unsigned int flags) {
1061 : /** @todo properly move print to LayerNode */
1062 4 : if (flags & PRINT_INST_INFO) {
1063 4 : out << "===================";
1064 8 : if (getName().empty())
1065 0 : printInstance(out, this);
1066 : else
1067 8 : out << "<" << getName() << ">" << std::endl;
1068 :
1069 8 : out << "Layer Type: " << getType() << std::endl;
1070 : }
1071 :
1072 4 : if (flags & PRINT_SHAPE_INFO) {
1073 4 : if (run_context) {
1074 : out << "======shape information: " << std::endl;
1075 4 : printShapeInfo(out);
1076 : }
1077 : }
1078 :
1079 4 : if (flags & PRINT_PROP_META) {
1080 : out << "======meta properties: " << std::endl;
1081 : /** @todo print local and layer properties with node_exporter */
1082 4 : nntrainer::Exporter e;
1083 4 : getLayer()->exportTo(e, ml::train::ExportMethods::METHOD_STRINGVECTOR);
1084 : auto prop_meta =
1085 4 : e.getResult<ml::train::ExportMethods::METHOD_STRINGVECTOR>();
1086 4 : if (prop_meta != nullptr) {
1087 15 : for (unsigned int i = 0; i < prop_meta->size(); ++i) {
1088 12 : out << (*prop_meta)[i].first << ": " << (*prop_meta)[i].second << "\n";
1089 : }
1090 : }
1091 4 : }
1092 :
1093 4 : if (flags & PRINT_PROP) {
1094 : out << "======properties: " << std::endl;
1095 : /** @todo print local and layer properties with node_exporter */
1096 : }
1097 :
1098 4 : if (flags & PRINT_WEIGHTS) {
1099 4 : if (run_context) {
1100 : out << "======weights: " << std::endl;
1101 6 : for (unsigned int idx = 0; idx < getNumWeights(); idx++) {
1102 2 : out << run_context->getWeight(idx);
1103 : }
1104 : }
1105 : }
1106 :
1107 4 : if (flags & PRINT_METRIC) {
1108 : out << "======metrics: " << std::endl;
1109 4 : printMetric(out);
1110 : }
1111 4 : };
1112 : }; // namespace nntrainer
|