LCOV - code coverage report
Current view: top level - nntrainer/compiler - tflite_opnode.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 75.4 % 134 101
Test Date: 2025-12-14 20:38:17 Functions: 83.3 % 18 15

            Line data    Source code
       1              : // SPDX-License-Identifier: Apache-2.0
       2              : /**
       3              :  * Copyright (C) 2021 Jihoon Lee <jhoon.it.lee@samsung.com>
       4              :  *
       5              :  * @file tflite_opnode.cpp
       6              :  * @date 28 April 2021
       7              :  * @brief contains tflite opnode which has information to convert to tflite file
       8              :  * @see https://github.com/nnstreamer/nntrainer
       9              :  * @author Jihoon Lee <jhoon.it.lee@samsung.com>
      10              :  * @author Donghak Park <donghak.park@samsung.com>
      11              :  * @bug No known bugs except for NYI items
      12              :  */
      13              : 
      14              : #include <tflite_opnode.h>
      15              : 
      16              : #include <layer_context.h>
      17              : #include <layer_node.h>
      18              : #include <memory.h>
      19              : namespace nntrainer {
      20              : 
      21           22 : TfOpNode::TfOpNode() :
      22              :   inputs(),
      23              :   outputs(),
      24              :   weights(),
      25              :   weight_transform(nullptr),
      26           22 :   is_input(false),
      27           22 :   is_output(false),
      28           22 :   is_virtual(false),
      29           22 :   is_trainable(true),
      30           22 :   is_to_be_removed(false),
      31           22 :   need_reorder_weight(false),
      32              :   node_owned_variable(),
      33              :   /// @todo distinguish between uninitialized and ADD operator.
      34           22 :   op_type(tflite::BuiltinOperator_ADD),
      35              :   builtin_ops(),
      36           22 :   builtin_option_type(tflite::BuiltinOptions_NONE){};
      37              : 
      38           22 : void TfOpNode::setLayerNode(const LayerNode &layer) {
      39           22 :   is_input = layer.getNumInputConnections() == 0;
      40           22 :   is_output = layer.getNumOutputConnections() == 0;
      41              :   /// @todo support more loss layers
      42           22 :   static const std::set<std::string> loss_type = {"mse", "cross"};
      43              :   /** set to graph output node if output connection of the node includes loss
      44              :    *layer string
      45              :    *  @note this is workaround because it cannot be guaranteed that a loss layer
      46              :    *always has a loss type in its name.
      47              :    *
      48              :    *  There are two ways to pass `GraphRepresentation` parameters to `serialize`
      49              :    *method.
      50              :    *
      51              :    *  1. with loss layer at the end of the graph
      52              :    *  2. without loss layer but last node has loss layer output connection
      53              :    *
      54              :    *  Loss layer of the first case is removed by `LossRealizer` and the previous
      55              :    *layer of the loss layer is set as the output node. And, the below logic is
      56              :    *for the second case.
      57              :    **/
      58              :   /// assume that loss layers have single output
      59           22 :   if (layer.getNumOutputConnections() == 1) {
      60           63 :     for (auto &loss : loss_type) {
      61           42 :       if (layer.getOutputConnections()[0].find(loss) != std::string::npos) {
      62            4 :         is_output = true;
      63              :       }
      64              :     }
      65              :   }
      66              :   /// @todo support more virtual nodes
      67           22 :   is_virtual = layer.getType() == "multiout";
      68              : 
      69           22 :   auto &context = layer.getRunContext();
      70           44 :   auto create_variables = [](auto tensor_getter, unsigned size) {
      71              :     Variables v;
      72           44 :     v.reserve(size);
      73           82 :     for (unsigned i = 0; i < size; ++i) {
      74           38 :       v.push_back(tensor_getter(i));
      75              :     }
      76           44 :     return v;
      77            0 :   };
      78              : 
      79              :   /**
      80              :    * Q1) Why convert from NCHW to NHWC?
      81              :    * A1) the tflite uses NHWC format; nntrainer uses NCHW
      82              :    *
      83              :    * Q2) Why are only output tensors reshaped?
      84              :    * A2) the tflite needs only one tensor between nodes. Therefore,
      85              :    * basically, outputs are used for tflite tensors
      86              :    **/
      87           22 :   auto create_variables_with_NCHW_to_NHWC = [](auto tensor_getter,
      88              :                                                unsigned size) {
      89              :     Variables v;
      90           22 :     v.reserve(size);
      91           44 :     for (unsigned i = 0; i < size; ++i) {
      92           22 :       Tensor *tensor = const_cast<Tensor *>(tensor_getter(i));
      93           44 :       tensor->reshape(TensorDim{tensor->batch(), tensor->height(),
      94           22 :                                 tensor->width(), tensor->channel()});
      95           22 :       v.push_back(tensor);
      96              :     }
      97           22 :     return v;
      98            0 :   };
      99              : 
     100           44 :   inputs = create_variables(
     101           22 :     [&context](unsigned idx) { return &context.getInput(idx); },
     102           22 :     context.getNumInputs());
     103           44 :   outputs = create_variables_with_NCHW_to_NHWC(
     104           22 :     [&context](unsigned idx) { return &context.getOutput(idx); },
     105           22 :     context.getNumOutputs());
     106           44 :   weights = create_variables(
     107           16 :     [&context](unsigned idx) {
     108           16 :       auto &t = context.getWeight(idx);
     109           16 :       NNTR_THROW_IF(t.empty() || !t.isAllocated(), std::invalid_argument)
     110              :         << "every weight tensor must be allocated";
     111           16 :       return &t;
     112              :     },
     113           22 :     context.getNumWeights());
     114              : 
     115           22 :   if (context.getNumWeights() == 0) {
     116           14 :     is_trainable = false;
     117              :   }
     118           22 : }
     119              : 
     120            8 : void TfOpNode::setWeightTransformFn(TransformFn fn) { weight_transform = fn; }
     121              : 
     122            5 : void TfOpNode::setInputTransformFn(TransformFn fn) { input_transform = fn; }
     123              : 
     124            0 : void TfOpNode::setWeights(Variables weights_, bool weight_transpose) {
     125              :   unsigned int cnt = 0;
     126              : 
     127            0 :   for (auto &w : weights_) {
     128            0 :     if (cnt >= weights.size())
     129              :       break;
     130              : 
     131            0 :     const unsigned int unit = w->batch();
     132            0 :     const unsigned int channel = w->channel();
     133            0 :     const unsigned int height = w->height();
     134            0 :     const unsigned int width = w->width();
     135            0 :     auto weight_data = weights.at(cnt)->getData();
     136              : 
     137              :     auto *ptr = const_cast<float *>(weight_data);
     138            0 :     memcpy(&ptr[0], &w->getData()[0],
     139            0 :            sizeof(float) * (unit * channel * height * width));
     140            0 :     cnt++;
     141              :   }
     142              : 
     143            0 :   auto weight_transform_fn = [](std::vector<const Tensor *> &weights) {
     144              :     std::vector<Tensor> new_weights;
     145            0 :     new_weights.reserve(weights.size());
     146            0 :     if (weights.size() != 0) {
     147            0 :       new_weights.push_back(weights[0]->transpose("1:2:0"));
     148              :     }
     149            0 :     return new_weights;
     150            0 :   };
     151              : 
     152            0 :   auto transform_if = [this](TransformFn &fn, Variables &v) {
     153            0 :     if (fn) {
     154              :       auto result = fn(v);
     155            0 :       v.resize(result.size());
     156            0 :       node_owned_variable.insert(node_owned_variable.end(), result.begin(),
     157              :                                  result.end());
     158              :       std::transform(node_owned_variable.end() - result.size(),
     159            0 :                      node_owned_variable.end(), v.begin(),
     160              :                      [](Tensor &t) { return &t; });
     161            0 :     }
     162            0 :   };
     163              : 
     164            0 :   if (weight_transpose == true) {
     165            0 :     setWeightTransformFn(weight_transform_fn);
     166            0 :     transform_if(weight_transform, weights);
     167              :   }
     168            0 : }
     169              : 
     170            6 : void TfOpNode::weightReorder(unsigned int node_count) {
     171              : 
     172            6 :   if (need_reorder_weight == true) {
     173              : 
     174            4 :     auto previous_input_shape = input_nodes[0]->getInputs()[0];
     175              : 
     176            4 :     const unsigned int unit = outputs[0]->height();
     177            4 :     const unsigned int channel = previous_input_shape->channel();
     178            4 :     const unsigned int height = previous_input_shape->height();
     179            4 :     const unsigned int width = previous_input_shape->width();
     180              : 
     181            4 :     auto weight_data = weights[0]->getData();
     182              :     auto *ptr = const_cast<float *>(weight_data);
     183              : 
     184            4 :     std::vector<float> old_value_list(unit * channel * height * width);
     185            4 :     memcpy(&old_value_list[0], &ptr[0],
     186              :            sizeof(float) * (unit * channel * height * width));
     187              : 
     188           14 :     for (unsigned int h = 0; h < height; h++) {
     189           62 :       for (unsigned int w = 0; w < width; w++) {
     190          643 :         for (unsigned int c = 0; c < channel; c++) {
     191              : 
     192          591 :           unsigned int now_position = h * (width * channel) + w * channel + c;
     193          591 :           unsigned int next_position = c * (height * width) + h * width + w;
     194              : 
     195          591 :           memcpy(&ptr[now_position * unit],
     196          591 :                  &old_value_list[next_position * unit], sizeof(float) * unit);
     197              :         }
     198              :       }
     199              :     }
     200            4 :   }
     201              : 
     202            6 :   auto weight_transform_fn = [](std::vector<const Tensor *> &weights) {
     203              :     std::vector<Tensor> new_weights;
     204            6 :     new_weights.reserve(weights.size());
     205           18 :     new_weights.push_back(weights[0]->transpose("0:2:1"));
     206            6 :     new_weights.push_back(*weights[1]);
     207            6 :     return new_weights;
     208            0 :   };
     209              : 
     210            6 :   setWeightTransformFn(weight_transform_fn);
     211              : 
     212            6 :   auto transform_if = [this](TransformFn &fn, Variables &v) {
     213            6 :     if (fn) {
     214              :       auto result = fn(v);
     215            6 :       v.resize(result.size());
     216            6 :       node_owned_variable.insert(node_owned_variable.end(), result.begin(),
     217              :                                  result.end());
     218              :       std::transform(node_owned_variable.end() - result.size(),
     219            6 :                      node_owned_variable.end(), v.begin(),
     220              :                      [](Tensor &t) { return &t; });
     221            6 :     }
     222            6 :   };
     223              : 
     224            6 :   transform_if(weight_transform, weights);
     225            6 : }
     226              : 
     227           22 : void TfOpNode::finalize() {
     228           44 :   auto transform_if = [this](TransformFn &fn, Variables &v) {
     229           44 :     if (fn) {
     230              :       auto result = fn(v);
     231            7 :       v.resize(result.size());
     232              :       /// basically, result.size() == v.size() except InputLayer because a
     233              :       /// Transpose operator is added for converting nchw to nhwc
     234              :       /// @todo comment out below codes. TfOpNode needs to have LayerNode
     235              :       /// pointer
     236              :       // NNTR_THROW_IF(dynamic_cast<InputLayer>(layer_ptr->getLayer()) ==
     237              :       // nullptr && result.size() != v.size(), std::invalid_argument)
     238              :       //   << "result size must match with given variable size";
     239            7 :       node_owned_variable.insert(node_owned_variable.end(), result.begin(),
     240              :                                  result.end());
     241              :       std::transform(node_owned_variable.end() - result.size(),
     242            7 :                      node_owned_variable.end(), v.begin(),
     243              :                      [](Tensor &t) { return &t; });
     244            7 :     }
     245           44 :   };
     246              : 
     247           22 :   transform_if(weight_transform, weights);
     248           22 :   transform_if(input_transform, inputs);
     249           22 : }
     250              : 
     251           22 : flatbuffers::Offset<void> TfOpNode::getBuiltinOps() const {
     252           22 :   switch (op_type) {
     253           22 :   case tflite::BuiltinOperator_ADD:
     254              :   case tflite::BuiltinOperator_AVERAGE_POOL_2D:
     255              :   case tflite::BuiltinOperator_CONV_2D:
     256              :   case tflite::BuiltinOperator_FULLY_CONNECTED:
     257              :   case tflite::BuiltinOperator_RELU:
     258              :   case tflite::BuiltinOperator_RESHAPE:
     259              :   case tflite::BuiltinOperator_SOFTMAX:
     260              :   case tflite::BuiltinOperator_TRANSPOSE:
     261              :   case tflite::BuiltinOperator_MUL:
     262              : 
     263           22 :     return builtin_ops;
     264            0 :   default:
     265            0 :     throw std::runtime_error{"Unsupported operator"};
     266              :   }
     267              : }
     268              : 
     269           22 : void TfOpNode::setBuiltinOptions(
     270              :   tflite::BuiltinOptions builtin_option_type_,
     271              :   const flatbuffers::Offset<void> &builtin_ops_) {
     272           22 :   builtin_ops = builtin_ops_;
     273           22 :   builtin_option_type = builtin_option_type_;
     274           22 : }
     275              : 
     276              : } // namespace nntrainer
        

Generated by: LCOV version 2.0-1