From f1959d5471e459036a1c723c058c44e801188566 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Ivan=20Vagin/AI=20Tools=20Lab=20/SRR/Engineer/=EC=82=BC?= =?utf8?q?=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Fri, 14 Jun 2019 07:43:16 +0300 Subject: [PATCH] [loco exporter] Implement loco exporter v0.1 (#3737) This commit adds minimal implementation that is able to export a single node network that includes one MaxPool2D op. Signed-off-by: Pavel Iliutchenko Signed-off-by: Efimov Alexander Signed-off-by: Sergei Barannikov --- contrib/loco-exporter/CMakeLists.txt | 42 ++ contrib/loco-exporter/include/LocoExporter.h | 62 ++ contrib/loco-exporter/schema/schema.fbs | 794 ++++++++++++++++++++++++ contrib/loco-exporter/src/Exporter.test.cpp | 174 ++++++ contrib/loco-exporter/src/LocoExporter.cpp | 23 +- contrib/loco-exporter/src/LocoExporterImpl.cpp | 100 +++ contrib/loco-exporter/src/LocoExporterImpl.h | 70 +++ contrib/loco-exporter/src/LocoExporterUtils.cpp | 44 ++ contrib/loco-exporter/src/LocoExporterUtils.h | 95 +++ contrib/loco-exporter/src/OperationExporter.cpp | 191 ++++++ contrib/loco-exporter/src/OperationExporter.h | 36 ++ contrib/loco-exporter/src/TensorExporter.cpp | 167 +++++ contrib/loco-exporter/src/TensorExporter.h | 38 ++ contrib/loco-exporter/src/TypeInference.cpp | 235 +++++++ contrib/loco-exporter/src/TypeInference.h | 55 ++ 15 files changed, 2123 insertions(+), 3 deletions(-) create mode 100644 contrib/loco-exporter/schema/schema.fbs create mode 100644 contrib/loco-exporter/src/Exporter.test.cpp create mode 100644 contrib/loco-exporter/src/LocoExporterImpl.cpp create mode 100644 contrib/loco-exporter/src/LocoExporterImpl.h create mode 100644 contrib/loco-exporter/src/LocoExporterUtils.cpp create mode 100644 contrib/loco-exporter/src/LocoExporterUtils.h create mode 100644 contrib/loco-exporter/src/OperationExporter.cpp create mode 100644 contrib/loco-exporter/src/OperationExporter.h create mode 100644 contrib/loco-exporter/src/TensorExporter.cpp create mode 100644 contrib/loco-exporter/src/TensorExporter.h create mode 100644 contrib/loco-exporter/src/TypeInference.cpp create mode 100644 contrib/loco-exporter/src/TypeInference.h diff --git a/contrib/loco-exporter/CMakeLists.txt b/contrib/loco-exporter/CMakeLists.txt index cd7c03b..cfa91cf 100644 --- a/contrib/loco-exporter/CMakeLists.txt +++ b/contrib/loco-exporter/CMakeLists.txt @@ -1,5 +1,47 @@ +################### +# SCHEMA # +################### +nncc_find_package(FlatBuffers QUIET) + +if (NOT FlatBuffers_FOUND) + message(WARNING "FlatBuffers not found, loco exporter will not be built") + return() +endif () + +FlatBuffers_Target(loco_exporter_flatbuffers + OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated/schema" + SCHEMA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/schema" + SCHEMA_FILES schema.fbs) + +################### +# LOCO EXPORTER # +################### file(GLOB_RECURSE SOURCES "src/*.cpp") +file(GLOB_RECURSE TESTS "src/*.test.cpp") +list(REMOVE_ITEM SOURCES ${TESTS}) add_library(loco_exporter SHARED ${SOURCES}) target_include_directories(loco_exporter PUBLIC include) +target_include_directories(loco_exporter PRIVATE src) +target_link_libraries(loco_exporter PUBLIC loco_exporter_flatbuffers) target_link_libraries(loco_exporter PUBLIC loco) +target_link_libraries(loco_exporter PRIVATE stdex) +# Let's apply nncc common compile options +# +# NOTE This will enable strict compilation (warnings as error). +# Please refer to the top-level CMakeLists.txt for details +target_link_libraries(loco_exporter PRIVATE nncc_common) + +if (NOT ENABLE_TEST) + return() +endif (NOT ENABLE_TEST) + +# Google Test is mandatory for internal testing +nncc_find_package(GTest REQUIRED) + +GTest_AddTest(loco_exporter_test ${TESTS}) +target_include_directories(loco_exporter_test PRIVATE src) +target_link_libraries(loco_exporter_test stdex) +target_link_libraries(loco_exporter_test loco_exporter) + +add_test(loco_exporter_test loco_exporter_test) diff --git a/contrib/loco-exporter/include/LocoExporter.h b/contrib/loco-exporter/include/LocoExporter.h index 8e82c36..07c3613 100644 --- a/contrib/loco-exporter/include/LocoExporter.h +++ b/contrib/loco-exporter/include/LocoExporter.h @@ -24,17 +24,79 @@ namespace loco_exporter { +class ExporterImpl; + +/** + * Container for passing serialized buffer without copying it + * Appropriate for range based loops + */ +class BufferView +{ +public: + BufferView(const char *ptr, size_t size) : _ptr(ptr), _size(size) {} + + const char *begin() const { return _ptr; } + const char *end() const { return _ptr + _size; } + size_t size() const { return _size; } +private: + const char *_ptr; + size_t _size; +}; + +/** + * Usage of this class: + * + * loco::Graph *g = acquireGraph(); + * + * Exporter e(g); + * e.dumpToFile("model.tflite"); + * + * simplified version: + * + * Exporter(g).dumpToFile("model.tflite"); + * + * If dumping into file is not an option + * it is possible to gather raw buffer data for further processing: + * + * std::vector other_buffer; + * + * Exporter e(g); + * BufferView buf = e.getBuffer(); + * + * // note that `e` should be preserved, `buf` contains pointer to exporter internal buffer + * + * for (char byte: buf) + * { + * other_buffer.push_back(byte); + * } + * + * e.clear(); // memory could be freed if buffer is not needed anymore + * + */ class Exporter { public: explicit Exporter(loco::Graph *graph); ~Exporter(); + /** + * Clears internal state, free memory + */ + void clear(); + /** @brief write to a file * @param path path to file where to write data * @throws any file related exceptions */ void dumpToFile(const char *path) const; + + /** + * @return buffer data, ownership is not transferred + */ + BufferView getBuffer() const; + +private: + std::unique_ptr _impl; }; } // namespace exporter diff --git a/contrib/loco-exporter/schema/schema.fbs b/contrib/loco-exporter/schema/schema.fbs new file mode 100644 index 0000000..980f13b --- /dev/null +++ b/contrib/loco-exporter/schema/schema.fbs @@ -0,0 +1,794 @@ +// Copyright 2017 The TensorFlow Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Revision History +// Version 0: Initial version. +// Version 1: Add subgraphs to schema. +// Version 2: Rename operators to conform to NN API. +// Version 3: Move buffer data from Model.Subgraph.Tensors to Model.Buffers. + +namespace tflite; + +// This corresponds to the version. +file_identifier "TFL3"; +// File extension of any written files. +file_extension "tflite"; + +// IMPORTANT: All new members of tables, enums and unions must be added at the +// end to ensure backwards compatibility. + +// The type of data stored in a tensor. +enum TensorType : byte { + FLOAT32 = 0, + FLOAT16 = 1, + INT32 = 2, + UINT8 = 3, + INT64 = 4, + STRING = 5, + BOOL = 6, + INT16 = 7, + COMPLEX64 = 8, + INT8 = 9, +} + +// Custom quantization parameters for experimenting with new quantization +// techniques. +table CustomQuantization { + custom:[ubyte] (force_align: 16); +} + +// Represents a specific quantization technique's parameters. +union QuantizationDetails { + CustomQuantization, +} + +// Parameters for converting a quantized tensor back to float. +table QuantizationParameters { + // These four parameters are the asymmetric linear quantization parameters. + // Given a quantized value q, the corresponding float value f should be: + // f = scale * (q - zero_point) + // For other quantization types, the QuantizationDetails below is used. + min:[float]; // For importing back into tensorflow. + max:[float]; // For importing back into tensorflow. + scale:[float]; // For dequantizing the tensor's values. + zero_point:[long]; + + // If this is not none, the quantization parameters above are ignored and the + // value of the QuantizationDetails union below should be used. + details:QuantizationDetails; +} + +table Tensor { + // The tensor shape. The meaning of each entry is operator-specific but + // builtin ops use: [batch size, height, width, number of channels] (That's + // Tensorflow's NHWC). + shape:[int]; + type:TensorType; + // An index that refers to the buffers table at the root of the model. Or, + // if there is no data buffer associated (i.e. intermediate results), then + // this is 0 (which refers to an always existent empty buffer). + // + // The data_buffer itself is an opaque container, with the assumption that the + // target device is little-endian. In addition, all builtin operators assume + // the memory is ordered such that if `shape` is [4, 3, 2], then index + // [i, j, k] maps to data_buffer[i*3*2 + j*2 + k]. + buffer:uint; + name:string; // For debugging and importing back into tensorflow. + quantization:QuantizationParameters; // Optional. + + is_variable:bool = false; +} + +// A list of builtin operators. Builtin operators are slightly faster than custom +// ones, but not by much. Moreover, while custom operators accept an opaque +// object containing configuration parameters, builtins have a predetermined +// set of acceptable options. +enum BuiltinOperator : byte { + ADD = 0, + AVERAGE_POOL_2D = 1, + CONCATENATION = 2, + CONV_2D = 3, + DEPTHWISE_CONV_2D = 4, + // DEPTH_TO_SPACE = 5, + DEQUANTIZE = 6, + EMBEDDING_LOOKUP = 7, + FLOOR = 8, + FULLY_CONNECTED = 9, + HASHTABLE_LOOKUP = 10, + L2_NORMALIZATION = 11, + L2_POOL_2D = 12, + LOCAL_RESPONSE_NORMALIZATION = 13, + LOGISTIC = 14, + LSH_PROJECTION = 15, + LSTM = 16, + MAX_POOL_2D = 17, + MUL = 18, + RELU = 19, + // NOTE(aselle): RELU_N1_TO_1 used to be called RELU1, but it was renamed + // since different model developers use RELU1 in different ways. Never + // create another op called RELU1. + RELU_N1_TO_1 = 20, + RELU6 = 21, + RESHAPE = 22, + RESIZE_BILINEAR = 23, + RNN = 24, + SOFTMAX = 25, + SPACE_TO_DEPTH = 26, + SVDF = 27, + TANH = 28, + // TODO(aselle): Consider rename to CONCATENATE_EMBEDDINGS + CONCAT_EMBEDDINGS = 29, + SKIP_GRAM = 30, + CALL = 31, + CUSTOM = 32, + EMBEDDING_LOOKUP_SPARSE = 33, + PAD = 34, + UNIDIRECTIONAL_SEQUENCE_RNN = 35, + GATHER = 36, + BATCH_TO_SPACE_ND = 37, + SPACE_TO_BATCH_ND = 38, + TRANSPOSE = 39, + MEAN = 40, + SUB = 41, + DIV = 42, + SQUEEZE = 43, + UNIDIRECTIONAL_SEQUENCE_LSTM = 44, + STRIDED_SLICE = 45, + BIDIRECTIONAL_SEQUENCE_RNN = 46, + EXP = 47, + TOPK_V2 = 48, + SPLIT = 49, + LOG_SOFTMAX = 50, + // DELEGATE is a special op type for the operations which are delegated to + // other backends. + // WARNING: Experimental interface, subject to change + DELEGATE = 51, + BIDIRECTIONAL_SEQUENCE_LSTM = 52, + CAST = 53, + PRELU = 54, + MAXIMUM = 55, + ARG_MAX = 56, + MINIMUM = 57, + LESS = 58, + NEG = 59, + PADV2 = 60, + GREATER = 61, + GREATER_EQUAL = 62, + LESS_EQUAL = 63, + SELECT = 64, + SLICE = 65, + SIN = 66, + TRANSPOSE_CONV = 67, + SPARSE_TO_DENSE = 68, + TILE = 69, + EXPAND_DIMS = 70, + EQUAL = 71, + NOT_EQUAL = 72, + LOG = 73, + SUM = 74, + SQRT = 75, + RSQRT = 76, + SHAPE = 77, + POW = 78, + ARG_MIN = 79, + FAKE_QUANT = 80, + REDUCE_PROD = 81, + REDUCE_MAX = 82, + PACK = 83, + LOGICAL_OR = 84, + ONE_HOT = 85, + LOGICAL_AND = 86, + LOGICAL_NOT = 87, + UNPACK = 88, + REDUCE_MIN = 89, + FLOOR_DIV = 90, + REDUCE_ANY = 91, + SQUARE = 92, + ZEROS_LIKE = 93, + FILL = 94, + FLOOR_MOD = 95, + RANGE = 96, + RESIZE_NEAREST_NEIGHBOR = 97, + LEAKY_RELU = 98, + SQUARED_DIFFERENCE = 99, + MIRROR_PAD = 100, + ABS = 101, + SPLIT_V = 102, +} + +// Options for the builtin operators. +union BuiltinOptions { + Conv2DOptions, + DepthwiseConv2DOptions, + ConcatEmbeddingsOptions, + LSHProjectionOptions, + Pool2DOptions, + SVDFOptions, + RNNOptions, + FullyConnectedOptions, + SoftmaxOptions, + ConcatenationOptions, + AddOptions, + L2NormOptions, + LocalResponseNormalizationOptions, + LSTMOptions, + ResizeBilinearOptions, + CallOptions, + ReshapeOptions, + SkipGramOptions, + SpaceToDepthOptions, + EmbeddingLookupSparseOptions, + MulOptions, + PadOptions, + GatherOptions, + BatchToSpaceNDOptions, + SpaceToBatchNDOptions, + TransposeOptions, + ReducerOptions, + SubOptions, + DivOptions, + SqueezeOptions, + SequenceRNNOptions, + StridedSliceOptions, + ExpOptions, + TopKV2Options, + SplitOptions, + LogSoftmaxOptions, + CastOptions, + DequantizeOptions, + MaximumMinimumOptions, + ArgMaxOptions, + LessOptions, + NegOptions, + PadV2Options, + GreaterOptions, + GreaterEqualOptions, + LessEqualOptions, + SelectOptions, + SliceOptions, + TransposeConvOptions, + SparseToDenseOptions, + TileOptions, + ExpandDimsOptions, + EqualOptions, + NotEqualOptions, + ShapeOptions, + PowOptions, + ArgMinOptions, + FakeQuantOptions, + PackOptions, + LogicalOrOptions, + OneHotOptions, + LogicalAndOptions, + LogicalNotOptions, + UnpackOptions, + FloorDivOptions, + SquareOptions, + ZerosLikeOptions, + FillOptions, + BidirectionalSequenceLSTMOptions, + BidirectionalSequenceRNNOptions, + UnidirectionalSequenceLSTMOptions, + FloorModOptions, + RangeOptions, + ResizeNearestNeighborOptions, + LeakyReluOptions, + SquaredDifferenceOptions, + MirrorPadOptions, + AbsOptions, + SplitVOptions, +} + +enum Padding : byte { SAME, VALID } + +enum ActivationFunctionType : byte { + NONE = 0, + RELU = 1, + RELU_N1_TO_1 = 2, + RELU6 = 3, + TANH = 4, + SIGN_BIT = 5, +} + +table Conv2DOptions { + padding:Padding; + stride_w:int; + stride_h:int; + fused_activation_function:ActivationFunctionType; + dilation_w_factor:int = 1; + dilation_h_factor:int = 1; +} + +table Pool2DOptions { + padding:Padding; + stride_w:int; + stride_h:int; + filter_width:int; + filter_height:int; + fused_activation_function:ActivationFunctionType; +} + +table DepthwiseConv2DOptions { + // Parameters for DepthwiseConv version 1 or above. + padding:Padding; + stride_w:int; + stride_h:int; + depth_multiplier:int; + fused_activation_function:ActivationFunctionType; + // Parameters for DepthwiseConv version 2 or above. + dilation_w_factor:int = 1; + dilation_h_factor:int = 1; +} + +table ConcatEmbeddingsOptions { + num_channels:int; + num_columns_per_channel:[int]; + embedding_dim_per_channel:[int]; // This could be inferred from parameters. +} + +enum LSHProjectionType: byte { + UNKNOWN = 0, + SPARSE = 1, + DENSE = 2, +} + +table LSHProjectionOptions { + type: LSHProjectionType; +} + +table SVDFOptions { + rank:int; + fused_activation_function:ActivationFunctionType; +} + +// An implementation of TensorFlow RNNCell. +table RNNOptions { + fused_activation_function:ActivationFunctionType; +} + +// An implementation of TensorFlow dynamic_rnn with RNNCell. +table SequenceRNNOptions { + time_major:bool; + fused_activation_function:ActivationFunctionType; +} + +// An implementation of TensorFlow bidrectional_dynamic_rnn with RNNCell. +table BidirectionalSequenceRNNOptions { + time_major:bool; + fused_activation_function:ActivationFunctionType; + merge_outputs: bool; +} + +enum FullyConnectedOptionsWeightsFormat: byte { + DEFAULT = 0, + SHUFFLED4x16INT8 = 1, +} + +// An implementation of TensorFlow fully_connected (a.k.a Dense) layer. +table FullyConnectedOptions { + // Parameters for FullyConnected version 1 or above. + fused_activation_function:ActivationFunctionType; + + // Parameters for FullyConnected version 2 or above. + weights_format:FullyConnectedOptionsWeightsFormat = DEFAULT; +} + +table SoftmaxOptions { + beta: float; +} + +// An implementation of TensorFlow concat. +table ConcatenationOptions { + axis:int; + fused_activation_function:ActivationFunctionType; +} + +table AddOptions { + fused_activation_function:ActivationFunctionType; +} + +table MulOptions { + fused_activation_function:ActivationFunctionType; +} + +table L2NormOptions { + fused_activation_function:ActivationFunctionType; +} + +table LocalResponseNormalizationOptions { + radius:int; + bias:float; + alpha:float; + beta:float; +} + +enum LSTMKernelType : byte { + // Full LSTM kernel which supports peephole and projection. + FULL = 0, + // Basic LSTM kernels. Equivalent to TensorFlow BasicLSTMCell. + BASIC = 1, +} + +// An implementation of TensorFlow LSTMCell and CoupledInputForgetGateLSTMCell +table LSTMOptions { + // Parameters for LSTM version 1 or above. + fused_activation_function:ActivationFunctionType; + cell_clip: float; // Optional, 0.0 means no clipping + proj_clip: float; // Optional, 0.0 means no clipping + + // Parameters for LSTM version 2 or above. + // Basic kernel is only supported in version 2 or above. + kernel_type: LSTMKernelType = FULL; +} + +// An implementation of TensorFlow dynamic_rnn with LSTMCell. +table UnidirectionalSequenceLSTMOptions { + fused_activation_function:ActivationFunctionType; + cell_clip: float; // Optional, 0.0 means no clipping + proj_clip: float; // Optional, 0.0 means no clipping + + // If true then first dimension is sequence, otherwise batch. + time_major:bool; +} + +table BidirectionalSequenceLSTMOptions { + fused_activation_function:ActivationFunctionType; + cell_clip: float; // Optional, 0.0 means no clipping + proj_clip: float; // Optional, 0.0 means no clipping + + // If true, store the outputs of both directions into the first output. + merge_outputs: bool; +} + +table ResizeBilinearOptions { + new_height: int (deprecated); + new_width: int (deprecated); + align_corners: bool; +} + +table ResizeNearestNeighborOptions { + align_corners: bool; +} + +// A call operation options +table CallOptions { + // The subgraph index that needs to be called. + subgraph:uint; +} + +table PadOptions { +} + +table PadV2Options { +} + +table ReshapeOptions { + new_shape:[int]; +} + +table SpaceToBatchNDOptions { +} + +table BatchToSpaceNDOptions { +} + +table SkipGramOptions { + ngram_size: int; + max_skip_size: int; + include_all_ngrams: bool; +} + +table SpaceToDepthOptions { + block_size: int; +} + +table SubOptions { + fused_activation_function:ActivationFunctionType; +} + +table DivOptions { + fused_activation_function:ActivationFunctionType; +} + +table TopKV2Options { +} + +enum CombinerType : byte { + SUM = 0, + MEAN = 1, + SQRTN = 2, +} + +table EmbeddingLookupSparseOptions { + combiner:CombinerType; +} + +table GatherOptions { + axis: int; +} + +table TransposeOptions { +} + +table ExpOptions { +} + +table ReducerOptions { + keep_dims: bool; +} + +table SqueezeOptions { + squeeze_dims:[int]; +} + +table SplitOptions { + num_splits: int; +} + +table SplitVOptions { + num_splits: int; +} + +table StridedSliceOptions { + begin_mask: int; + end_mask: int; + ellipsis_mask: int; + new_axis_mask: int; + shrink_axis_mask: int; +} + +table LogSoftmaxOptions { +} + +table CastOptions { + in_data_type: TensorType; + out_data_type: TensorType; +} + +table DequantizeOptions { +} + +table MaximumMinimumOptions { +} + +table TileOptions { +} + +table ArgMaxOptions { + output_type : TensorType; +} + +table ArgMinOptions { + output_type : TensorType; +} + +table GreaterOptions { +} + +table GreaterEqualOptions { +} + +table LessOptions { +} + +table LessEqualOptions { +} + +table NegOptions { +} + +table SelectOptions { +} + +table SliceOptions { +} + +table TransposeConvOptions { + padding:Padding; + stride_w:int; + stride_h:int; +} + +table ExpandDimsOptions { +} + +table SparseToDenseOptions { + validate_indices:bool; +} + +table EqualOptions { +} + +table NotEqualOptions { +} + +table ShapeOptions { + // Optional output type of the operation (int32 or int64). Defaults to int32. + out_type : TensorType; +} + +table PowOptions { +} + +table FakeQuantOptions { + // Parameters supported by version 1: + min:float; + max:float; + num_bits:int; + + // Parameters supported by version 2: + narrow_range:bool; +} + +table PackOptions { + values_count:int; + axis:int; +} + +table LogicalOrOptions { +} + +table OneHotOptions { + axis:int; +} + +table AbsOptions { +} + + +table LogicalAndOptions { +} + +table LogicalNotOptions { +} + +table UnpackOptions { + num:int; + axis:int; +} + +table FloorDivOptions { +} + +table SquareOptions { +} + +table ZerosLikeOptions { +} + +table FillOptions { +} + +table FloorModOptions { +} + +table RangeOptions { +} + +table LeakyReluOptions { + alpha:float; +} + +table SquaredDifferenceOptions { +} + +enum MirrorPadMode : byte { + // Doesn't include borders. + REFLECT = 0, + // Includes borders. + SYMMETRIC = 1, +} + +table MirrorPadOptions { + mode:MirrorPadMode; +} + +// An OperatorCode can be an enum value (BuiltinOperator) if the operator is a +// builtin, or a string if the operator is custom. +table OperatorCode { + builtin_code:BuiltinOperator; + custom_code:string; + + // The version of the operator. The version need to be bumped whenever new + // parameters are introduced into an op. + version:int = 1; +} + +enum CustomOptionsFormat : byte { + FLEXBUFFERS = 0, +} + +// An operator takes tensors as inputs and outputs. The type of operation being +// performed is determined by an index into the list of valid OperatorCodes, +// while the specifics of each operations is configured using builtin_options +// or custom_options. +table Operator { + // Index into the operator_codes array. Using an integer here avoids + // complicate map lookups. + opcode_index:uint; + + // Optional input and output tensors are indicated by -1. + inputs:[int]; + outputs:[int]; + + builtin_options:BuiltinOptions; + custom_options:[ubyte]; + custom_options_format:CustomOptionsFormat; + + // A list of booleans indicating the input tensors which are being mutated by + // this operator.(e.g. used by RNN and LSTM). + // For example, if the "inputs" array refers to 5 tensors and the second and + // fifth are mutable variables, then this list will contain + // [false, true, false, false, true]. + // + // If the list is empty, no variable is mutated in this operator. + // The list either has the same length as `inputs`, or is empty. + mutating_variable_inputs:[bool]; +} + +// The root type, defining a subgraph, which typically represents an entire +// model. +table SubGraph { + // A list of all tensors used in this subgraph. + tensors:[Tensor]; + + // Indices of the tensors that are inputs into this subgraph. Note this is + // the list of non-static tensors that feed into the subgraph for inference. + inputs:[int]; + + // Indices of the tensors that are outputs out of this subgraph. Note this is + // the list of output tensors that are considered the product of the + // subgraph's inference. + outputs:[int]; + + // All operators, in execution order. + operators:[Operator]; + + // Name of this subgraph (used for debugging). + name:string; +} + +// Table of raw data buffers (used for constant tensors). Referenced by tensors +// by index. The generous alignment accommodates mmap-friendly data structures. +table Buffer { + data:[ubyte] (force_align: 16); +} + +table Model { + // Version of the schema. + version:uint; + + // A list of all operator codes used in this model. This is + // kept in order because operators carry an index into this + // vector. + operator_codes:[OperatorCode]; + + // All the subgraphs of the model. The 0th is assumed to be the main + // model. + subgraphs:[SubGraph]; + + // A description of the model. + description:string; + + // Buffers of the model. + // Note the 0th entry of this array must be an empty buffer (sentinel). + // This is a convention so that tensors without a buffer can provide 0 as + // their buffer. + buffers:[Buffer]; + + // Metadata about the model. Indirects into the existings buffers list. + metadata_buffer:[int]; +} + +root_type Model; diff --git a/contrib/loco-exporter/src/Exporter.test.cpp b/contrib/loco-exporter/src/Exporter.test.cpp new file mode 100644 index 0000000..bc0d083 --- /dev/null +++ b/contrib/loco-exporter/src/Exporter.test.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "LocoExporter.h" +#include "loco/IR/PermutingCodec.h" + +#include +#include + +using stdex::make_unique; + +class TestLocoExporterModels : public ::testing::Test +{ +public: + template uint32_t setSampleShape(T *op) + { + const uint32_t n = 1; + const uint32_t h = 100; + const uint32_t w = 100; + const uint32_t c = 3; + op->rank(4); + op->dim(0).set(n); + op->dim(1).set(c); + op->dim(2).set(h); + op->dim(3).set(w); + return n * h * w * c; + } + + loco::Pull *pullLayer() + { + loco::Pull *pull = _graph.nodes()->create(); + _graph.inputs()->create()->node(pull); + pull->dtype(loco::DataType::FLOAT32); + setSampleShape(pull); + return pull; + } + + loco::ConstGen *constLayer() + { + loco::ConstGen *cst = _graph.nodes()->create(); + cst->dtype(loco::DataType::FLOAT32); + + const auto size = setSampleShape(cst); + cst->size(size); + // fill cst layer with some data + for (uint32_t i = 0; i < size; ++i) + cst->at(i) = i; + return cst; + } + + loco::Push *pushLayer(loco::Node *input) + { + loco::Push *push = _graph.nodes()->create(); + _graph.outputs()->create()->node(push); + push->from(input); + return push; + } + + loco::ReLU *reluLayer(loco::Node *input) + { + loco::ReLU *relu = _graph.nodes()->create(); + relu->input(input); + return relu; + } + + loco::MaxPool2D *validPoolLayer(loco::Node *input_fm) + { + loco::MaxPool2D *max_pool = _graph.nodes()->create(); + auto &window = *max_pool->window(); + window.vertical(2); + window.horizontal(3); + auto &strides = *max_pool->stride(); + strides.vertical(4); + strides.horizontal(5); + max_pool->ifm(input_fm); + return max_pool; + } + + loco::FeatureEncode *featureEncodeLayer(loco::Node *input) + { + loco::FeatureEncode *encode_layer = _graph.nodes()->create(); + auto encoder = make_unique>(); + (*encoder->perm())[loco::FeatureAxis::Count] = 0; + (*encoder->perm())[loco::FeatureAxis::Depth] = 1; + (*encoder->perm())[loco::FeatureAxis::Height] = 2; + (*encoder->perm())[loco::FeatureAxis::Width] = 3; + encode_layer->encoder(std::move(encoder)); + encode_layer->input(input); + return encode_layer; + } + + loco::FeatureDecode *featureDecodeLayer(loco::Node *input) + { + loco::FeatureDecode *decode_layer = _graph.nodes()->create(); + auto decoder = make_unique>(); + (*decoder->perm())[loco::FeatureAxis::Count] = 0; + (*decoder->perm())[loco::FeatureAxis::Depth] = 1; + (*decoder->perm())[loco::FeatureAxis::Height] = 2; + (*decoder->perm())[loco::FeatureAxis::Width] = 3; + decode_layer->decoder(std::move(decoder)); + decode_layer->input(input); + return decode_layer; + } + + loco::Graph &getGraph() { return _graph; } + +private: + loco::Graph _graph; +}; + +TEST_F(TestLocoExporterModels, MaxPool2D) +{ + // Create graph with one input, one output and single maxpool operation + loco::Pull *pull = pullLayer(); + loco::FeatureEncode *encode = featureEncodeLayer(pull); + loco::MaxPool2D *max_pool = validPoolLayer(encode); + loco::FeatureDecode *decode = featureDecodeLayer(max_pool); + loco::Push *push = pushLayer(decode); + (void)push; + + loco_exporter::Exporter e(&getGraph()); + e.dumpToFile("maxpool2d.tflite"); + + ASSERT_TRUE(true); +} + +TEST_F(TestLocoExporterModels, Const) +{ + // Create graph with single constGen operation that goes straight to output of net + loco::ConstGen *cst = constLayer(); + loco::Push *push = pushLayer(cst); + (void)push; + + loco_exporter::Exporter e(&getGraph()); + e.dumpToFile("const.tflite"); + + ASSERT_TRUE(true); +} + +TEST_F(TestLocoExporterModels, PoolWithActivation) +{ + // Create graph with one input, one output and single maxpool operation with relu + loco::Pull *pull = pullLayer(); + loco::FeatureEncode *encode = featureEncodeLayer(pull); + loco::MaxPool2D *max_pool = validPoolLayer(encode); + loco::FeatureDecode *decode = featureDecodeLayer(max_pool); + loco::ReLU *relu = reluLayer(decode); + loco::Push *push = pushLayer(relu); + (void)push; + + loco_exporter::Exporter e(&getGraph()); + e.dumpToFile("maxpool_activation.tflite"); + + ASSERT_TRUE(true); +} + +int main(int argc, char **argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/contrib/loco-exporter/src/LocoExporter.cpp b/contrib/loco-exporter/src/LocoExporter.cpp index 8681719..ce62275 100644 --- a/contrib/loco-exporter/src/LocoExporter.cpp +++ b/contrib/loco-exporter/src/LocoExporter.cpp @@ -16,21 +16,38 @@ #include "LocoExporter.h" -#include +#include "LocoExporterImpl.h" + +#include "stdex/Memory.h" + +#include namespace loco_exporter { -Exporter::Exporter(loco::Graph *graph) +Exporter::Exporter(loco::Graph *graph) : _impl(stdex::make_unique(graph)) { // NOTHING TO DO } Exporter::~Exporter() = default; +void Exporter::clear() { _impl.reset(nullptr); } + void Exporter::dumpToFile(const char *path) const { - throw std::runtime_error{"Not implemented, yet"}; + const char *ptr = _impl->getBufferPointer(); + const size_t size = _impl->getBufferSize(); + assert(ptr && "graph is not serialized for some reason"); + std::ofstream file(path); + file.write(ptr, size); +} + +BufferView Exporter::getBuffer() const +{ + const char *ptr = _impl->getBufferPointer(); + const size_t size = _impl->getBufferSize(); + return {ptr, size}; } } // namespace loco_exporter diff --git a/contrib/loco-exporter/src/LocoExporterImpl.cpp b/contrib/loco-exporter/src/LocoExporterImpl.cpp new file mode 100644 index 0000000..6daf6f3 --- /dev/null +++ b/contrib/loco-exporter/src/LocoExporterImpl.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "LocoExporterImpl.h" + +#include "TensorExporter.h" +#include "OperationExporter.h" +#include "LocoExporterUtils.h" + +#include +#include + +namespace loco_exporter +{ +using namespace tflite; +using namespace flatbuffers; + +ExporterImpl::ExporterImpl(loco::Graph *graph) { exportGraph(graph); } + +Offset>> +encodeOperatorCodes(FlatBufferBuilder &builder, std::unordered_map &opcodes) +{ + std::vector> operator_codes_vec(opcodes.size()); + for (auto it : opcodes) + { + uint32_t idx = it.second; + operator_codes_vec[idx] = CreateOperatorCode(builder, it.first.opcode); + } + return builder.CreateVector(operator_codes_vec); +} + +flatbuffers::Offset ExporterImpl::exportSubgraph(SerializedModelData &gd) +{ + auto tensors = _builder.CreateVector(gd._tensors); + auto inputs = _builder.CreateVector(gd._inputs); + auto outputs = _builder.CreateVector(gd._outputs); + auto operators = _builder.CreateVector(gd._operators); + auto subgraph = CreateSubGraph(_builder, tensors, inputs, outputs, operators); + return subgraph; +} + +void ExporterImpl::exportGraph(loco::Graph *graph) +{ + _builder.Clear(); + + SerializedModelData gd; + + // This version is taken from comment in fbs + constexpr uint32_t version = 3; + + // parse graph into SerializedModelData structure + exportOpDefinedTensors(graph->nodes(), _builder, gd); + + exportNodes(graph->nodes(), _builder, gd); + + // excode operator codes + auto operator_codes = encodeOperatorCodes(_builder, gd._operator_codes); + + // Subgraphs + Offset subgraph = exportSubgraph(gd); + auto subgraphs = _builder.CreateVector(std::vector>{subgraph}); + + // Description + std::string description_str = "nnpackage"; + auto description = _builder.CreateString(description_str); + + // create array of buffers + auto buffers = _builder.CreateVector(gd._buffers); + + // empty metadata + std::vector metadata_buffer_vec; + auto metadata_buffer = _builder.CreateVector(metadata_buffer_vec); + + // Model + auto model_offset = CreateModel(_builder, version, operator_codes, subgraphs, description, + buffers, metadata_buffer); + FinishModelBuffer(_builder, model_offset); +} + +const char *ExporterImpl::getBufferPointer() const +{ + return reinterpret_cast(_builder.GetBufferPointer()); +} + +size_t ExporterImpl::getBufferSize() const { return _builder.GetSize(); } + +} // namespace loco_exporter diff --git a/contrib/loco-exporter/src/LocoExporterImpl.h b/contrib/loco-exporter/src/LocoExporterImpl.h new file mode 100644 index 0000000..fe9c273 --- /dev/null +++ b/contrib/loco-exporter/src/LocoExporterImpl.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __LOCO_EXPORTER_IMPL_H__ +#define __LOCO_EXPORTER_IMPL_H__ + +#include "schema_generated.h" + +#include "loco.h" + +namespace loco_exporter +{ + +struct SerializedModelData; + +/** + * internal implementation of interface exporter class + */ +class ExporterImpl +{ +public: + ExporterImpl() = delete; + ~ExporterImpl() = default; + + explicit ExporterImpl(loco::Graph *graph); + + /** + * @return pointer to buffer with serialized graph + */ + const char *getBufferPointer() const; + + /** + * @return size of buffer with serialized graph + */ + size_t getBufferSize() const; + +private: + /** + * @brief create Subgraph using data stored in SerializedModelData + * @param gd information about serializer parts of model + * @return offset in buffer corresponding to serialized subgraph + */ + flatbuffers::Offset exportSubgraph(SerializedModelData &gd); + + /** + * @brief root function that writes graph into internal buffer + * @param graph + */ + void exportGraph(loco::Graph *graph); + +private: + flatbuffers::FlatBufferBuilder _builder; +}; + +} // namespace loco_exporter + +#endif //_LOCO_EXPORTER_IMPL_H__ diff --git a/contrib/loco-exporter/src/LocoExporterUtils.cpp b/contrib/loco-exporter/src/LocoExporterUtils.cpp new file mode 100644 index 0000000..0a07a84 --- /dev/null +++ b/contrib/loco-exporter/src/LocoExporterUtils.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "LocoExporterUtils.h" + +namespace loco_exporter +{ + +uint32_t SerializedModelData::registerBuiltinOpcode(tflite::BuiltinOperator builtin_code) +{ + auto it = _operator_codes.find(OpCode{builtin_code}); + if (it != _operator_codes.end()) + { + return it->second; + } + auto idx = static_cast(_operator_codes.size()); + _operator_codes.emplace(OpCode{builtin_code}, idx); + return idx; +} + +tflite::Padding getOpPadding(loco::MaxPool2D *node) +{ + auto pad = node->pad(); + if (pad->top() == 0 && pad->bottom() == 0 && pad->left() == 0 && pad->right() == 0) + return tflite::Padding_VALID; + // fixme no way to distinguish same padding from cusom here + // need more information from loco IR, for example shape + return tflite::Padding_SAME; +} + +} // namepsace loco_exporter diff --git a/contrib/loco-exporter/src/LocoExporterUtils.h b/contrib/loco-exporter/src/LocoExporterUtils.h new file mode 100644 index 0000000..91b0031 --- /dev/null +++ b/contrib/loco-exporter/src/LocoExporterUtils.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __LOCO_EXPORTER_UTILS_H__ +#define __LOCO_EXPORTER_UTILS_H__ + +#include "schema_generated.h" +#include "loco/IR/Nodes.h" + +#include "loco/IR/PermutingCodec.h" + +#include + +namespace loco_exporter +{ + +struct OpCode +{ + tflite::BuiltinOperator opcode; + + bool operator==(const OpCode &rhs) const { return opcode == rhs.opcode; } +}; + +} // namespace loco_exporter + +namespace std +{ + +template <> struct hash +{ + size_t operator()(const loco_exporter::OpCode &x) const { return hash()(x.opcode); } +}; + +} // namespace std + +namespace loco_exporter +{ + +struct ShapeDescription +{ + std::vector _dims; + bool _rank_known; +}; + +// Prerequisites for tflite::Model object creation +struct SerializedModelData final +{ + SerializedModelData() = default; + SerializedModelData(const SerializedModelData &) = delete; + + std::unordered_map _operator_codes; + std::vector> _operators; + std::vector> _tensors; + std::vector> _buffers; + std::vector _inputs; + std::vector _outputs; + std::unordered_map _node_to_tensor_id; + + // Data for type and shape inference + std::unordered_map _node_to_type; + std::unordered_map _node_to_shape; + + /** + * @brief if opcode is not registered in table of opcodes add it + * @param builtin_code + * @return idx of opcode in table of opcodes (see schema) + */ + uint32_t registerBuiltinOpcode(tflite::BuiltinOperator builtin_code); +}; + +template inline bool isIdentity(PermDescr *descr) +{ + loco::Permutation *perm = descr->perm(); + return perm->axis(loco::FeatureAxis::Count) == 0 && perm->axis(loco::FeatureAxis::Depth) == 3 && + perm->axis(loco::FeatureAxis::Height) == 1 && perm->axis(loco::FeatureAxis::Width) == 2; +} + +tflite::Padding getOpPadding(loco::MaxPool2D *node); + +} // namespace loco_exporter + +#endif // __LOCO_EXPORTER_UTILS_H__ diff --git a/contrib/loco-exporter/src/OperationExporter.cpp b/contrib/loco-exporter/src/OperationExporter.cpp new file mode 100644 index 0000000..9790703 --- /dev/null +++ b/contrib/loco-exporter/src/OperationExporter.cpp @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "OperationExporter.h" +#include "LocoExporterUtils.h" + +namespace loco_exporter +{ + +using namespace flatbuffers; +using namespace tflite; + +namespace +{ + +void exportRelu(loco::ReLU *node, FlatBufferBuilder &builder, SerializedModelData &gd) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_RELU); + std::vector inputs_vec{gd._node_to_tensor_id[node->input()]}; + std::vector outputs_vec{gd._node_to_tensor_id[static_cast(node)]}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs); + gd._operators.push_back(op_offset); +} + +void exportMaxPool2D(loco::MaxPool2D *node, FlatBufferBuilder &builder, SerializedModelData &gd) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_MAX_POOL_2D); + std::vector inputs_vec{gd._node_to_tensor_id[node->ifm()]}; + std::vector outputs_vec{gd._node_to_tensor_id[static_cast(node)]}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + tflite::Padding padding = getOpPadding(node); + auto options = CreatePool2DOptions(builder, padding, node->stride()->horizontal(), + node->stride()->vertical(), node->window()->horizontal(), + node->window()->vertical()); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_Pool2DOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void exportPermutation(loco::Node *node, FlatBufferBuilder &builder, + loco::Permutation *perm, bool inverted, + SerializedModelData &gd) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_TRANSPOSE); + + auto options = CreateTransposeOptions(builder); + + // Create constant tensor with perm vector + auto perm_vec_shape_offset = builder.CreateVector(std::vector{4}); + + constexpr int perm_vec_size = 4; + std::vector perm_vec_data(perm_vec_size); + + using loco::FeatureAxis; + + if (!inverted) + { + perm_vec_data[0] = perm->axis(FeatureAxis::Count); + perm_vec_data[1] = perm->axis(FeatureAxis::Height); + perm_vec_data[2] = perm->axis(FeatureAxis::Width); + perm_vec_data[3] = perm->axis(FeatureAxis::Depth); + } + else + { + perm_vec_data[perm->axis(FeatureAxis::Count)] = 0; + perm_vec_data[perm->axis(FeatureAxis::Height)] = 1; + perm_vec_data[perm->axis(FeatureAxis::Width)] = 2; + perm_vec_data[perm->axis(FeatureAxis::Depth)] = 3; + } + + constexpr size_t raw_perm_vec_size = perm_vec_size * sizeof(int32_t); + + auto perm_vec_offset = + builder.CreateVector(reinterpret_cast(perm_vec_data.data()), raw_perm_vec_size); + + auto perm_buffer_offset = CreateBuffer(builder, perm_vec_offset); + + const auto perm_buffer_id = static_cast(gd._buffers.size()); + + gd._buffers.push_back(perm_buffer_offset); + + auto perm_tensor_offset = + CreateTensor(builder, perm_vec_shape_offset, TensorType_INT32, perm_buffer_id); + + auto perm_tensor_id = static_cast(gd._tensors.size()); + gd._tensors.push_back(perm_tensor_offset); + + // Create permutation node + + std::vector inputs_vec{gd._node_to_tensor_id[node->arg(0)], perm_tensor_id}; + std::vector outputs_vec{gd._node_to_tensor_id[node]}; + + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + + constexpr auto options_type = tflite::BuiltinOptions::BuiltinOptions_TransposeOptions; + + auto transpose_offset = + CreateOperator(builder, op_idx, inputs, outputs, options_type, options.Union()); + gd._operators.push_back(transpose_offset); +} + +void exportFeatureEncode(loco::FeatureEncode *node, FlatBufferBuilder &builder, + SerializedModelData &gd) +{ + auto perm_descr = dynamic_cast *>(node->encoder()); + if (isIdentity(perm_descr)) + return; + // This encode is not an identity, need to insert permutation + + auto *perm = perm_descr->perm(); + exportPermutation(node, builder, perm, /*inverted*/ false, gd); +} + +void exportFeatureDecode(loco::FeatureDecode *node, FlatBufferBuilder &builder, + SerializedModelData &gd) +{ + auto perm_descr = dynamic_cast *>(node->decoder()); + if (isIdentity(perm_descr)) + return; + // This encode is not an identity, need to insert permutation + + auto *perm = perm_descr->perm(); + exportPermutation(node, builder, perm, /*inverted*/ true, gd); +} + +void exportNode(loco::Node *node, flatbuffers::FlatBufferBuilder &builder, + SerializedModelData &data) +{ + if (auto *relu = dynamic_cast(node)) + { + exportRelu(relu, builder, data); + } + else if (dynamic_cast(node)) + { + data._inputs.push_back(data._node_to_tensor_id[node]); + } + else if (dynamic_cast(node)) + { + data._outputs.push_back(data._node_to_tensor_id[node->arg(0)]); + } + else if (auto *encode = dynamic_cast(node)) + { + exportFeatureEncode(encode, builder, data); + } + else if (auto *decode = dynamic_cast(node)) + { + exportFeatureDecode(decode, builder, data); + } + else if (dynamic_cast(node)) + { + // skip, everything is done in exportOpDefinedTensors + } + else if (auto *pool = dynamic_cast(node)) + { + exportMaxPool2D(pool, builder, data); + } + else + { + assert(false && "unsupported node found"); + } +} + +} // namespace + +void exportNodes(loco::Graph::NodeContext *nodes, FlatBufferBuilder &builder, + SerializedModelData &gd) +{ + for (uint32_t node_id = 0; node_id < nodes->size(); node_id++) + { + exportNode(nodes->at(node_id), builder, gd); + } +} + +} // namespace loco_exporter diff --git a/contrib/loco-exporter/src/OperationExporter.h b/contrib/loco-exporter/src/OperationExporter.h new file mode 100644 index 0000000..a260f75 --- /dev/null +++ b/contrib/loco-exporter/src/OperationExporter.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __LOCO_EXPORTER_OPERATION_EXPORTER_H__ +#define __LOCO_EXPORTER_OPERATION_EXPORTER_H__ + +#include "LocoExporterUtils.h" +#include "loco/IR/Graph.h" + +namespace loco_exporter +{ + +/** + * @brief create Operators corresponding to model nodes + * @param nodes container with nodes + * @param gd information about serializer parts of model + */ +void exportNodes(loco::Graph::NodeContext *nodes, flatbuffers::FlatBufferBuilder &builder, + SerializedModelData &gd); + +} // namespace loco_exporter + +#endif //__LOCO_EXPORTER_OPERATION_EXPORTER_H__ diff --git a/contrib/loco-exporter/src/TensorExporter.cpp b/contrib/loco-exporter/src/TensorExporter.cpp new file mode 100644 index 0000000..d8cf4eb --- /dev/null +++ b/contrib/loco-exporter/src/TensorExporter.cpp @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TensorExporter.h" +#include "TypeInference.h" + +#include "loco/IR/Algorithm.h" + +namespace loco_exporter +{ +using namespace tflite; +using namespace flatbuffers; + +namespace +{ + +flatbuffers::Offset> encodeShape(FlatBufferBuilder &builder, + const ShapeDescription &shape) +{ + assert(shape._rank_known && "unknown number of dimensions is not supported"); + return builder.CreateVector(shape._dims); +} + +template +flatbuffers::Offset encodeOpBuffer(FlatBufferBuilder &builder, NodeT *) +{ + return CreateBuffer(builder); +} + +template <> +flatbuffers::Offset encodeOpBuffer(FlatBufferBuilder &builder, loco::ConstGen *c) +{ + assert(c->dtype() == loco::DataType::FLOAT32); + std::vector raw_data; + const uint32_t size = c->size(); + raw_data.reserve(size); + for (uint32_t i = 0; i < size; ++i) + { + raw_data.push_back(c->at(i)); + } + const size_t raw_size = size * sizeof(float); + auto array_offset = builder.CreateVector(reinterpret_cast(raw_data.data()), raw_size); + return CreateBuffer(builder, array_offset); +} + +} // namespace + +template +void exportOpDefinedTensor(NodeT *node, FlatBufferBuilder &builder, SerializedModelData &gd) +{ + // Create and register output tensor shape + ShapeDescription shape_description = getOpResultShape(node, gd); + gd._node_to_shape[node] = shape_description; + auto shape_offset = encodeShape(builder, shape_description); + + // encode and register output tensor type + auto tensor_type = getOpResultType(node, gd); + gd._node_to_type[node] = tensor_type; + + // encode and register output tensor buffer + auto buffer = encodeOpBuffer(builder, node); + auto buffer_id = static_cast(gd._buffers.size()); + gd._buffers.push_back(buffer); + + // encode and register tensor itself using attributes from previous steps + auto tensor_id = static_cast(gd._tensors.size()); + std::string name = "t_" + std::to_string(tensor_id); + auto name_offset = builder.CreateString(name); + auto tensor_offset = CreateTensor(builder, shape_offset, tensor_type, buffer_id, name_offset, + /*quantization*/ 0, /*is_variable*/ false); + gd._node_to_tensor_id[node] = tensor_id; + gd._tensors.push_back(tensor_offset); +} + +void exportOpDefinedTensors(loco::Graph::NodeContext *nodes, FlatBufferBuilder &builder, + SerializedModelData &gd) +{ + // find entrances of graph + std::vector roots; + for (uint32_t node_id = 0; node_id < nodes->size(); ++node_id) + { + loco::Node *node = nodes->at(node_id); + if (dynamic_cast(node)) + { + roots.push_back(node); + } + } + + // Operations should be traversed in RPO because during processing of current operation + // we need to know all attributes of previous operations, + // like shape, type,tensor id related with previous operation + auto sequence = loco::postorder_traversal(roots); + for (loco::Node *node : sequence) + { + if (auto *pull = dynamic_cast(node)) + { + // Create tensor for input node + exportOpDefinedTensor(pull, builder, gd); + } + else if (dynamic_cast(node)) + { + // Do nothing for exit node + } + else if (auto *cst = dynamic_cast(node)) + { + // Create tensor filled with constant data + exportOpDefinedTensor(cst, builder, gd); + } + else if (auto *encode = dynamic_cast(node)) + { + // If encode node is identity - do nothing, otherwise create tensor to store transposed data + auto *perm_descr = + dynamic_cast *>(encode->encoder()); + assert(perm_descr && "unsupporter encoder"); + if (isIdentity(perm_descr)) + { + gd._node_to_tensor_id[node] = gd._node_to_tensor_id[node->arg(0)]; + } + else + { + exportOpDefinedTensor(encode, builder, gd); + } + } + else if (auto *decode = dynamic_cast(node)) + { + // If decode node is identity - do nothing, otherwise create tensor to store transposed data + auto *perm_descr = + dynamic_cast *>(decode->decoder()); + assert(perm_descr && "unsupporter decoder"); + if (isIdentity(perm_descr)) + { + gd._node_to_tensor_id[node] = gd._node_to_tensor_id[node->arg(0)]; + } + else + { + exportOpDefinedTensor(decode, builder, gd); + } + } + else if (auto *pool = dynamic_cast(node)) + { + exportOpDefinedTensor(pool, builder, gd); + } + else if (auto *relu = dynamic_cast(node)) + { + exportOpDefinedTensor(relu, builder, gd); + } + else + { + assert(false && "unsupported node type"); + } + } +} + +} // namespace loco_exporter diff --git a/contrib/loco-exporter/src/TensorExporter.h b/contrib/loco-exporter/src/TensorExporter.h new file mode 100644 index 0000000..8ee7dbe --- /dev/null +++ b/contrib/loco-exporter/src/TensorExporter.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __LOCO_EXPORTER_TENSOR_EXPORTER_H__ +#define __LOCO_EXPORTER_TENSOR_EXPORTER_H__ + +#include "LocoExporterUtils.h" + +#include "loco/IR/Graph.h" + +#include "flatbuffers/flatbuffers.h" + +namespace loco_exporter +{ + +/** + * @brief create Tensors corresponding to results of all nodes in graph + * @param nodes list of nodes in computational graph + * @param gd information about serialized parts of model + */ +void exportOpDefinedTensors(loco::Graph::NodeContext *nodes, + flatbuffers::FlatBufferBuilder &builder, SerializedModelData &gd); +} + +#endif //__LOCO_EXPORTER_TENSOR_EXPORTER_H__ diff --git a/contrib/loco-exporter/src/TypeInference.cpp b/contrib/loco-exporter/src/TypeInference.cpp new file mode 100644 index 0000000..dbc3488 --- /dev/null +++ b/contrib/loco-exporter/src/TypeInference.cpp @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TypeInference.h" + +#include "schema_generated.h" + +#include + +namespace loco_exporter +{ + +namespace +{ + +tflite::TensorType translateLocoTypeToTFLite(loco::DataType dtype) +{ + switch (dtype) + { + case loco::DataType::U8: + return tflite::TensorType_UINT8; + // case loco::DataType::U16: unsupported + // case loco::DataType::U32: unsupported + // case loco::DataType::U64: unsupported + case loco::DataType::S8: + return tflite::TensorType_INT8; + case loco::DataType::S16: + return tflite::TensorType_INT16; + case loco::DataType::S32: + return tflite::TensorType_INT32; + case loco::DataType::S64: + return tflite::TensorType_INT64; + case loco::DataType::FLOAT16: + return tflite::TensorType_FLOAT16; + case loco::DataType::FLOAT32: + return tflite::TensorType_FLOAT32; + // case loco::DataType::FLOAT64: unsupported + default: + assert(false && "unsupported data type"); + } +} + +template ::value, int>::type> +T ceil_div(T dividend, T divisor) +{ + assert(dividend > 0 && divisor > 0 && "this implementations is for positive numbers only"); + return (dividend + divisor - 1) / divisor; +} + +} // namespace + +tflite::TensorType getOpResultType(loco::ConstGen *node, SerializedModelData &) +{ + return translateLocoTypeToTFLite(node->dtype()); +} + +tflite::TensorType getOpResultType(loco::Pull *node, SerializedModelData &) +{ + return translateLocoTypeToTFLite(node->dtype()); +} + +tflite::TensorType getOpResultType(loco::ReLU *node, SerializedModelData &gd) +{ + return gd._node_to_type[node->input()]; +} + +tflite::TensorType getOpResultType(loco::MaxPool2D *node, SerializedModelData &gd) +{ + return gd._node_to_type[node->ifm()]; +} + +tflite::TensorType getOpResultType(loco::FeatureEncode *node, SerializedModelData &gd) +{ + return gd._node_to_type[node->input()]; +} + +tflite::TensorType getOpResultType(loco::FeatureDecode *node, SerializedModelData &gd) +{ + return gd._node_to_type[node->input()]; +} + +int32_t decodeShapeDimension(const loco::Dimension &dim) +{ + if (!dim.known()) + return -1; + return dim.value(); +} + +loco::Dimension encodeShapeDimension(const int32_t &value) +{ + if (value == -1) + return loco::Dimension(); + return {static_cast(value)}; +} + +ShapeDescription getOpResultShape(loco::Pull *node, SerializedModelData &) +{ + ShapeDescription shape; + shape._rank_known = true; + shape._dims.reserve(node->rank()); + for (uint32_t i = 0; i < node->rank(); ++i) + { + shape._dims.push_back(decodeShapeDimension(node->dim(i))); + } + return shape; +} + +ShapeDescription getOpResultShape(loco::ConstGen *node, SerializedModelData &) +{ + ShapeDescription shape; + shape._rank_known = true; + shape._dims.reserve(node->rank()); + for (uint32_t i = 0; i < node->rank(); ++i) + { + shape._dims.push_back(decodeShapeDimension(node->dim(i))); + } + return shape; +} + +ShapeDescription getOpResultShape(loco::MaxPool2D *node, SerializedModelData &gd) +{ + loco::Node *pred = node->ifm(); + const ShapeDescription &pred_shape = gd._node_to_shape[pred]; + if (!pred_shape._rank_known) + { + // return unknown shape + return {}; + } + ShapeDescription shape; + shape._rank_known = true; + shape._dims.resize(4); + shape._dims[0] = pred_shape._dims[0]; + shape._dims[3] = pred_shape._dims[3]; + tflite::Padding padding = getOpPadding(node); + switch (padding) + { + case tflite::Padding_SAME: + { + auto height = static_cast(pred_shape._dims[1]); + auto width = static_cast(pred_shape._dims[2]); + + int32_t proposed_res_height = ceil_div(height, node->stride()->vertical()); + int32_t proposed_res_width = ceil_div(width, node->stride()->horizontal()); + + shape._dims[1] = pred_shape._dims[1] == -1 ? -1 : proposed_res_height; + shape._dims[2] = pred_shape._dims[2] == -1 ? -1 : proposed_res_width; + break; + } + case tflite::Padding_VALID: + { + auto padded_h = static_cast(pred_shape._dims[1] - (node->window()->vertical() - 1)); + auto padded_w = static_cast(pred_shape._dims[2] - (node->window()->horizontal() - 1)); + + int32_t proposed_height = ceil_div(padded_h, node->stride()->vertical()); + int32_t proposed_width = ceil_div(padded_w, node->stride()->horizontal()); + + shape._dims[1] = pred_shape._dims[1] == -1 ? -1 : proposed_height; + shape._dims[2] = pred_shape._dims[2] == -1 ? -1 : proposed_width; + break; + } + default: + assert(false && "unknown padding type"); + } + return shape; +} + +ShapeDescription getOpResultShape(loco::ReLU *node, SerializedModelData &gd) +{ + return gd._node_to_shape[node->input()]; +} + +ShapeDescription getOpResultShape(loco::FeatureEncode *node, SerializedModelData &gd) +{ + const ShapeDescription &pred_shape = gd._node_to_shape[node->input()]; + if (!pred_shape._rank_known) + { + // return unknown shape + return {}; + } + ShapeDescription shape; + shape._rank_known = true; + loco::TensorShape tensor_shape; + uint32_t num_dims = pred_shape._dims.size(); + tensor_shape.rank(num_dims); + for (uint32_t i = 0; i < num_dims; ++i) + { + tensor_shape.dim(i) = encodeShapeDimension(pred_shape._dims[i]); + } + loco::FeatureShape feature_shape = node->encoder()->shape(tensor_shape); + shape._dims.resize(4); + shape._dims[0] = decodeShapeDimension(feature_shape.count()); + shape._dims[1] = decodeShapeDimension(feature_shape.height()); + shape._dims[2] = decodeShapeDimension(feature_shape.width()); + shape._dims[3] = decodeShapeDimension(feature_shape.depth()); + return shape; +} + +ShapeDescription getOpResultShape(loco::FeatureDecode *node, SerializedModelData &gd) +{ + const ShapeDescription &pred_shape = gd._node_to_shape[node->input()]; + if (!pred_shape._rank_known) + { + // return unknown shape + return {}; + } + ShapeDescription shape; + shape._rank_known = true; + loco::FeatureShape feature_shape; + feature_shape.count() = encodeShapeDimension(pred_shape._dims[0]); + feature_shape.height() = encodeShapeDimension(pred_shape._dims[1]); + feature_shape.width() = encodeShapeDimension(pred_shape._dims[2]); + feature_shape.depth() = encodeShapeDimension(pred_shape._dims[3]); + loco::TensorShape tensor_shape = node->decoder()->shape(feature_shape); + shape._dims.resize(4); + for (uint32_t i = 0; i < 4; ++i) + { + shape._dims[i] = decodeShapeDimension(tensor_shape.dim(i)); + } + return shape; +} + +} // namespace loco_exporter diff --git a/contrib/loco-exporter/src/TypeInference.h b/contrib/loco-exporter/src/TypeInference.h new file mode 100644 index 0000000..1b705d9 --- /dev/null +++ b/contrib/loco-exporter/src/TypeInference.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "LocoExporterUtils.h" +#include "loco/IR/Nodes.h" + +#ifndef __LOCO_EXPORTER_TYPEINFERENCE_H__ +#define __LOCO_EXPORTER_TYPEINFERENCE_H__ + +namespace loco_exporter +{ + +// Tensor type inference functions + +tflite::TensorType getOpResultType(loco::ConstGen *node, SerializedModelData &); + +tflite::TensorType getOpResultType(loco::Pull *node, SerializedModelData &); + +tflite::TensorType getOpResultType(loco::ReLU *node, SerializedModelData &gd); + +tflite::TensorType getOpResultType(loco::MaxPool2D *node, SerializedModelData &gd); + +tflite::TensorType getOpResultType(loco::FeatureEncode *node, SerializedModelData &gd); + +tflite::TensorType getOpResultType(loco::FeatureDecode *node, SerializedModelData &gd); + +// Shape inference functions + +ShapeDescription getOpResultShape(loco::Pull *node, SerializedModelData &); + +ShapeDescription getOpResultShape(loco::ConstGen *node, SerializedModelData &); + +ShapeDescription getOpResultShape(loco::MaxPool2D *node, SerializedModelData &gd); + +ShapeDescription getOpResultShape(loco::ReLU *node, SerializedModelData &gd); + +ShapeDescription getOpResultShape(loco::FeatureEncode *node, SerializedModelData &gd); + +ShapeDescription getOpResultShape(loco::FeatureDecode *node, SerializedModelData &gd); +} + +#endif //__LOCO_EXPORTER_TYPEINFERENCE_H__ -- 2.7.4