summaryrefslogtreecommitdiff
path: root/compiler/luci/export
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/luci/export')
-rw-r--r--compiler/luci/export/CMakeLists.txt29
-rw-r--r--compiler/luci/export/README.md3
-rw-r--r--compiler/luci/export/include/luci/CircleExporter.h64
-rw-r--r--compiler/luci/export/src/Check.h35
-rw-r--r--compiler/luci/export/src/CircleExporter.cpp64
-rw-r--r--compiler/luci/export/src/CircleExporterImpl.cpp266
-rw-r--r--compiler/luci/export/src/CircleExporterImpl.h82
-rw-r--r--compiler/luci/export/src/CircleExporterUtils.cpp169
-rw-r--r--compiler/luci/export/src/CircleExporterUtils.h51
-rw-r--r--compiler/luci/export/src/CircleOperationExporter.cpp643
-rw-r--r--compiler/luci/export/src/CircleOperationExporter.h37
-rw-r--r--compiler/luci/export/src/CircleTensorExporter.cpp264
-rw-r--r--compiler/luci/export/src/CircleTensorExporter.h44
-rw-r--r--compiler/luci/export/src/Optimize.cpp48
-rw-r--r--compiler/luci/export/src/Optimize.h33
-rw-r--r--compiler/luci/export/src/ProgressReporter.cpp84
-rw-r--r--compiler/luci/export/src/ProgressReporter.h53
-rw-r--r--compiler/luci/export/src/SerializedData.h95
18 files changed, 2064 insertions, 0 deletions
diff --git a/compiler/luci/export/CMakeLists.txt b/compiler/luci/export/CMakeLists.txt
new file mode 100644
index 000000000..e32eca366
--- /dev/null
+++ b/compiler/luci/export/CMakeLists.txt
@@ -0,0 +1,29 @@
+file(GLOB_RECURSE SOURCES "src/*.cpp")
+# TODO enable tests
+#file(GLOB_RECURSE TESTS "src/*.test.cpp")
+#list(REMOVE_ITEM SOURCES ${TESTS})
+
+add_library(luci_export SHARED ${SOURCES})
+target_include_directories(luci_export PRIVATE src)
+target_include_directories(luci_export PUBLIC include)
+target_link_libraries(luci_export PRIVATE luci_lang)
+target_link_libraries(luci_export PRIVATE luci_service)
+target_link_libraries(luci_export PRIVATE luci_pass)
+target_link_libraries(luci_export PRIVATE mio_circle)
+target_link_libraries(luci_export PRIVATE luci_log)
+target_link_libraries(luci_export PRIVATE luci_logex)
+target_link_libraries(luci_export PRIVATE nncc_common)
+target_link_libraries(luci_export PRIVATE locop)
+target_link_libraries(luci_export PRIVATE oops)
+install(TARGETS luci_export DESTINATION lib)
+
+#if(NOT ENABLE_TEST)
+# return()
+#endif(NOT ENABLE_TEST)
+#
+#nnas_find_package(GTest REQUIRED)
+#
+#GTest_AddTest(luci_export_test ${TESTS})
+#target_include_directories(luci_export_test PRIVATE src)
+#target_link_libraries(luci_export_test luci_export)
+#target_link_libraries(luci_export_test oops)
diff --git a/compiler/luci/export/README.md b/compiler/luci/export/README.md
new file mode 100644
index 000000000..12b190a2f
--- /dev/null
+++ b/compiler/luci/export/README.md
@@ -0,0 +1,3 @@
+# luci-export
+
+_luci-export_ provides exporting _loco_ graph of Circle IR to Circle model file
diff --git a/compiler/luci/export/include/luci/CircleExporter.h b/compiler/luci/export/include/luci/CircleExporter.h
new file mode 100644
index 000000000..0584c623c
--- /dev/null
+++ b/compiler/luci/export/include/luci/CircleExporter.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2020 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 __LUCI_CIRCLEEXPORTER_H__
+#define __LUCI_CIRCLEEXPORTER_H__
+
+#include <luci/IR/Module.h>
+
+#include <loco.h>
+
+#include <memory>
+
+namespace luci
+{
+
+class CircleExporter
+{
+public:
+ // This contract class describes the interaction between a exporter and its client.
+ struct Contract
+ {
+ public:
+ virtual ~Contract() = default;
+
+ public: // Client -> Exporter
+ // Input Graph (to be exported)
+ // Exporter expects a loco graph that consists of Circle nodes
+ virtual loco::Graph *graph(void) const = 0;
+
+ // Input Module (to be exported)
+ // Exporter expects a luci module that consists of loco graphs
+ // TODO make this pure virtual
+ virtual luci::Module *module(void) const;
+
+ public: // Exporter -> Client
+ // Exporter calls store for export data
+ // Notice: Please DO NOT STORE ptr and size when implementing this in Client
+ virtual bool store(const char *ptr, const size_t size) const = 0;
+ };
+
+public:
+ explicit CircleExporter();
+
+public:
+ // invoke(...) returns false on failure.
+ bool invoke(Contract *) const;
+};
+
+} // namespace luci
+
+#endif // __LUCI_CIRCLEEXPORTER_H__
diff --git a/compiler/luci/export/src/Check.h b/compiler/luci/export/src/Check.h
new file mode 100644
index 000000000..e05ec904a
--- /dev/null
+++ b/compiler/luci/export/src/Check.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2020 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 __CHECK_H__
+#define __CHECK_H__
+
+#include <stdexcept>
+#include <cassert>
+#include <iostream>
+
+// TODO Add macro for Release version
+
+#define LUCI_ASSERT(condition, msg) \
+ { \
+ if (!(condition)) \
+ { \
+ std::cerr << "[assert failed] " << (msg) << ". " << std::endl; \
+ assert((condition)); \
+ } \
+ }
+
+#endif // __CHECK_H__
diff --git a/compiler/luci/export/src/CircleExporter.cpp b/compiler/luci/export/src/CircleExporter.cpp
new file mode 100644
index 000000000..125df7802
--- /dev/null
+++ b/compiler/luci/export/src/CircleExporter.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2020 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 "luci/CircleExporter.h"
+#include "luci/IR/Module.h"
+#include "CircleExporterImpl.h"
+
+#include <oops/InternalExn.h>
+
+#include <fstream>
+#include <memory>
+
+namespace luci
+{
+
+// TODO remove this
+Module *CircleExporter::Contract::module(void) const { return nullptr; }
+
+CircleExporter::CircleExporter()
+{
+ // NOTHING TO DO
+}
+
+bool CircleExporter::invoke(Contract *contract) const
+{
+ auto module = contract->module();
+ if (module != nullptr)
+ {
+ CircleExporterImpl impl(module);
+
+ const char *ptr = impl.getBufferPointer();
+ const size_t size = impl.getBufferSize();
+
+ // we just send one time
+ return contract->store(ptr, size);
+ }
+
+ auto graph = contract->graph();
+ if (graph == nullptr)
+ return false;
+
+ CircleExporterImpl impl(graph);
+
+ const char *ptr = impl.getBufferPointer();
+ const size_t size = impl.getBufferSize();
+
+ // we just send one time
+ return contract->store(ptr, size);
+}
+
+} // namespace luci
diff --git a/compiler/luci/export/src/CircleExporterImpl.cpp b/compiler/luci/export/src/CircleExporterImpl.cpp
new file mode 100644
index 000000000..81109ee62
--- /dev/null
+++ b/compiler/luci/export/src/CircleExporterImpl.cpp
@@ -0,0 +1,266 @@
+/*
+ * Copyright (c) 2020 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 "CircleExporterImpl.h"
+#include "Optimize.h"
+#include "CircleTensorExporter.h"
+#include "CircleOperationExporter.h"
+#include "CircleExporterUtils.h"
+
+#include <oops/InternalExn.h>
+#include <mio/circle/schema_generated.h>
+#include <flatbuffers/flatbuffers.h>
+
+#include <cassert>
+#include <unordered_map>
+#include <string>
+#include <stdexcept>
+
+namespace
+{
+
+luci::CircleInput *input_node(loco::Graph *g, const loco::GraphInputIndex &index)
+{
+ for (uint32_t n = 0; n < g->nodes()->size(); ++n)
+ {
+ if (auto pull = dynamic_cast<luci::CircleInput *>(g->nodes()->at(n)))
+ {
+ if (pull->indexed() && pull->index() == index)
+ {
+ return pull;
+ }
+ }
+ }
+ return nullptr;
+}
+
+luci::CircleOutput *output_node(loco::Graph *g, const loco::GraphOutputIndex &index)
+{
+ for (uint32_t n = 0; n < g->nodes()->size(); ++n)
+ {
+ if (auto push = dynamic_cast<luci::CircleOutput *>(g->nodes()->at(n)))
+ {
+ if (push->indexed() && push->index() == index)
+ {
+ return push;
+ }
+ }
+ }
+ return nullptr;
+}
+
+void registerGraphInputTensors(loco::Graph *graph, luci::SubGraphContext &ctx)
+{
+ for (uint32_t n = 0; n < graph->inputs()->size(); ++n)
+ {
+ auto node = input_node(graph, n);
+ assert(node != nullptr);
+ ctx._inputs.push_back(luci::get_tensor_index(node));
+ }
+}
+
+void registerGraphOutputTensors(loco::Graph *graph, luci::SubGraphContext &ctx)
+{
+ for (uint32_t n = 0; n < graph->outputs()->size(); ++n)
+ {
+ auto push = output_node(graph, n);
+ assert(push != nullptr);
+ auto node = push->from();
+ assert(node != nullptr);
+ ctx._outputs.push_back(luci::get_tensor_index(node));
+ }
+}
+
+} // namespace
+
+namespace
+{
+
+using namespace circle;
+using namespace flatbuffers;
+
+Offset<Vector<Offset<OperatorCode>>>
+encodeOperatorCodes(FlatBufferBuilder &builder, std::unordered_map<luci::OpCode, uint32_t> &opcodes,
+ std::unordered_map<luci::OpCode, std::string> &custom_opcodes)
+{
+ std::vector<Offset<OperatorCode>> operator_codes_vec(opcodes.size());
+ for (auto it : opcodes)
+ {
+ uint32_t idx = it.second;
+ if (it.first.opcode != BuiltinOperator_CUSTOM)
+ {
+ operator_codes_vec[idx] = CreateOperatorCode(builder, it.first.opcode);
+ }
+ else // custom op
+ {
+ auto opCode = it.first;
+ auto custom_code = custom_opcodes.find(opCode);
+ if (custom_code == custom_opcodes.end())
+ INTERNAL_EXN("Cannot find code for customop even though opcode is BuiltinOperator_CUSTOM");
+
+ operator_codes_vec[idx] =
+ CreateOperatorCode(builder, it.first.opcode, builder.CreateString(custom_code->second));
+ }
+ }
+ return builder.CreateVector(operator_codes_vec);
+}
+
+} // namespace
+
+namespace luci
+{
+
+using namespace circle;
+using namespace flatbuffers;
+
+CircleExporterImpl::CircleExporterImpl(loco::Graph *graph) { exportGraph(graph); }
+CircleExporterImpl::CircleExporterImpl(Module *module) { exportModule(module); }
+
+::flatbuffers::Offset<::circle::SubGraph>
+CircleExporterImpl::exportSubgraph(SerializedGraphData &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 df = gd._data_format;
+ auto subgraph = CreateSubGraph(_builder, tensors, inputs, outputs, operators, df);
+ return subgraph;
+}
+
+void CircleExporterImpl::exportGraph(loco::Graph *graph)
+{
+ // do graph optimization
+ optimize(graph);
+
+ _builder.Clear();
+
+ SerializedModelData md;
+ SerializedGraphData gd;
+
+ // This version is taken from comment in fbs
+ constexpr uint32_t version = 0;
+
+ // TODO set this value properly
+ gd._data_format = circle::DataFormat::DataFormat_CHANNELS_LAST;
+
+ // prepare model data
+ prepareModelData(_builder, md);
+
+ // parse graph into SerializedModelData structure
+ exportOpDefinedTensors(graph, _builder, md, gd);
+
+ // NOTE Invoke these register functions only after each node is annotated with its tensor_index
+ registerGraphInputTensors(graph, gd);
+ registerGraphOutputTensors(graph, gd);
+
+ exportNodes(graph, _builder, md, gd);
+
+ // encode operator codes
+ auto operator_codes =
+ encodeOperatorCodes(_builder, md._operator_codes, md._custom_operator_codes);
+
+ // Subgraphs
+ Offset<SubGraph> subgraph = exportSubgraph(gd);
+ auto subgraphs = _builder.CreateVector(std::vector<Offset<SubGraph>>{subgraph});
+
+ // Description
+ std::string description_str = "nnpackage";
+ auto description = _builder.CreateString(description_str);
+
+ // create array of buffers
+ auto buffers = _builder.CreateVector(md._buffers);
+
+ // empty metadata
+ std::vector<int> 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);
+}
+
+void CircleExporterImpl::exportModule(Module *module)
+{
+ assert(module->size() > 0);
+ // do graph optimization
+
+ SerializedModelData md;
+
+ _builder.Clear();
+
+ std::vector<flatbuffers::Offset<circle::SubGraph>> subgraph_vec;
+
+ for (size_t g = 0; g < module->size(); ++g)
+ {
+ auto graph = module->graph(g);
+
+ optimize(graph);
+
+ SerializedGraphData gd;
+
+ // TODO set this value properly
+ gd._data_format = circle::DataFormat::DataFormat_CHANNELS_LAST;
+
+ // parse graph into SerializedModelData structure
+ exportOpDefinedTensors(graph, _builder, md, gd);
+
+ // NOTE Invoke these register functions only after each node is annotated with its tensor_index
+ registerGraphInputTensors(graph, gd);
+ registerGraphOutputTensors(graph, gd);
+
+ exportNodes(graph, _builder, md, gd);
+
+ // Subgraphs
+ Offset<SubGraph> subgraph = exportSubgraph(gd);
+ subgraph_vec.push_back(subgraph);
+ }
+
+ auto subgraphs = _builder.CreateVector(std::vector<Offset<SubGraph>>{subgraph_vec});
+
+ // encode operator codes
+ auto operator_codes =
+ encodeOperatorCodes(_builder, md._operator_codes, md._custom_operator_codes);
+
+ // Description
+ std::string description_str = "nnpackage";
+ auto description = _builder.CreateString(description_str);
+
+ // create array of buffers
+ auto buffers = _builder.CreateVector(md._buffers);
+
+ // empty metadata
+ std::vector<int> metadata_buffer_vec;
+ auto metadata_buffer = _builder.CreateVector(metadata_buffer_vec);
+
+ // This version is taken from comment in fbs
+ constexpr uint32_t version = 0;
+
+ // Model
+ auto model_offset = CreateModel(_builder, version, operator_codes, subgraphs, description,
+ buffers, metadata_buffer);
+ FinishModelBuffer(_builder, model_offset);
+}
+
+const char *CircleExporterImpl::getBufferPointer() const
+{
+ return reinterpret_cast<const char *>(_builder.GetBufferPointer());
+}
+
+size_t CircleExporterImpl::getBufferSize() const { return _builder.GetSize(); }
+
+} // namespace luci
diff --git a/compiler/luci/export/src/CircleExporterImpl.h b/compiler/luci/export/src/CircleExporterImpl.h
new file mode 100644
index 000000000..e5d5b5a00
--- /dev/null
+++ b/compiler/luci/export/src/CircleExporterImpl.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2020 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 __CIRCLE_EXPORTER_IMPL_H__
+#define __CIRCLE_EXPORTER_IMPL_H__
+
+#include "luci/CircleExporter.h"
+#include "luci/IR/Module.h"
+
+#include "SerializedData.h"
+
+#include "SerializedData.h"
+
+#include <mio/circle/schema_generated.h>
+
+#include <loco.h>
+
+namespace luci
+{
+
+/**
+ * internal implementation of interface exporter class
+ */
+class CircleExporterImpl
+{
+public:
+ CircleExporterImpl() = delete;
+ ~CircleExporterImpl() = default;
+
+ explicit CircleExporterImpl(loco::Graph *graph);
+ explicit CircleExporterImpl(Module *module);
+
+ /**
+ * @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 SerializedGraphData
+ * @param gd information about serializer parts of model
+ * @return offset in buffer corresponding to serialized subgraph
+ */
+ flatbuffers::Offset<circle::SubGraph> exportSubgraph(SerializedGraphData &gd);
+
+ /**
+ * @brief root function that writes graph into internal buffer
+ * @param graph
+ */
+ void exportGraph(loco::Graph *graph);
+
+ /**
+ * @brief root function that writes Module into internal buffer
+ * @param module
+ */
+ void exportModule(Module *module);
+
+private:
+ flatbuffers::FlatBufferBuilder _builder;
+};
+
+} // namespace luci
+
+#endif // __CIRCLE_EXPORTER_IMPL_H__
diff --git a/compiler/luci/export/src/CircleExporterUtils.cpp b/compiler/luci/export/src/CircleExporterUtils.cpp
new file mode 100644
index 000000000..1272facb2
--- /dev/null
+++ b/compiler/luci/export/src/CircleExporterUtils.cpp
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2020 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 "CircleExporterUtils.h"
+
+#include <oops/InternalExn.h>
+
+#include <cassert>
+#include <memory>
+
+namespace luci
+{
+
+circle::ActivationFunctionType to_circle_actfunc(luci::FusedActFunc func)
+{
+ switch (func)
+ {
+ case luci::FusedActFunc::NONE:
+ return circle::ActivationFunctionType_NONE;
+ case luci::FusedActFunc::RELU:
+ return circle::ActivationFunctionType_RELU;
+ case luci::FusedActFunc::RELU_N1_TO_1:
+ return circle::ActivationFunctionType_RELU_N1_TO_1;
+ case luci::FusedActFunc::RELU6:
+ return circle::ActivationFunctionType_RELU6;
+ default:
+ INTERNAL_EXN_V("trying to convert unsupported luci::FusedActFunc", oops::to_uint32(func));
+ }
+}
+
+circle::TensorType to_circle_tensortype(loco::DataType type)
+{
+ switch (type)
+ {
+ case loco::DataType::U8:
+ return circle::TensorType_UINT8;
+
+ case loco::DataType::S8:
+ return circle::TensorType_INT8;
+ case loco::DataType::S16:
+ return circle::TensorType_INT16;
+ case loco::DataType::S32:
+ return circle::TensorType_INT32;
+ case loco::DataType::S64:
+ return circle::TensorType_INT64;
+
+ case loco::DataType::FLOAT16:
+ return circle::TensorType_FLOAT16;
+ case loco::DataType::FLOAT32:
+ return circle::TensorType_FLOAT32;
+
+ case loco::DataType::BOOL:
+ return circle::TensorType_BOOL;
+
+ default:
+ INTERNAL_EXN_V("failed to convert unsupported loco::DataType", oops::to_uint32(type));
+ }
+}
+
+} // namespace luci
+
+namespace luci
+{
+
+uint32_t SerializedModelData::registerBuiltinOpcode(circle::BuiltinOperator builtin_code)
+{
+ auto it = _operator_codes.find(OpCode{builtin_code});
+ if (it != _operator_codes.end())
+ {
+ return it->second;
+ }
+ auto idx = static_cast<uint32_t>(_operator_codes.size());
+ _operator_codes.emplace(OpCode{builtin_code}, idx);
+ return idx;
+}
+
+uint32_t SerializedModelData::registerCustomOpcode(const std::string &custom_op)
+{
+ circle::BuiltinOperator custom_code = circle::BuiltinOperator_CUSTOM;
+ auto idx = registerBuiltinOpcode(custom_code);
+ _custom_operator_codes.emplace(OpCode{custom_code}, custom_op);
+ return idx;
+}
+
+circle::Padding getOpPadding(const loco::Padding2D *pad, const loco::Stride<2> *stride,
+ const ShapeDescription &ifm, const ShapeDescription &ofm)
+{
+ // VALID padding
+ if (pad->top() == 0 && pad->bottom() == 0 && pad->left() == 0 && pad->right() == 0)
+ return circle::Padding_VALID;
+
+ // SAME padding
+ //
+ // For same padding, by definition, following equation should hold:
+ // O = floor((I - 1) / S) + 1
+ // where input size I, output size O, stride S
+ //
+ // NOTE input and output 'feature' map are shape of NHWC
+ bool same_padding_criterion_1 =
+ (static_cast<uint32_t>(ofm._dims[1]) == (ifm._dims[1] - 1) / stride->vertical() + 1) &&
+ (static_cast<uint32_t>(ofm._dims[2]) == (ifm._dims[2] - 1) / stride->horizontal() + 1);
+
+ // For same padding, rear padding is same or bigger than front padding by at most 1
+ bool same_padding_criterion_2 =
+ (pad->top() <= pad->bottom()) && (pad->bottom() <= pad->top() + 1) &&
+ (pad->left() <= pad->right()) && (pad->right() <= pad->left() + 1);
+
+ if (same_padding_criterion_1 && same_padding_criterion_2)
+ return circle::Padding_SAME;
+
+ INTERNAL_EXN("Unsupported padding criteria");
+}
+
+circle::Padding getOpPadding(const luci::Padding pad)
+{
+ if (pad == luci::Padding::VALID)
+ return circle::Padding_VALID;
+ if (pad == luci::Padding::SAME)
+ return circle::Padding_SAME;
+
+ INTERNAL_EXN_V("Unsupported luci::Padding", oops::to_uint32(pad));
+}
+
+namespace
+{
+
+class CircleTensorIndexAnnotation final : public loco::NodeAnnotation
+{
+public:
+ CircleTensorIndexAnnotation(const CircleTensorIndex &index) : _index{index}
+ {
+ // DO NOTHING
+ }
+
+public:
+ const CircleTensorIndex &index(void) const { return _index; }
+
+private:
+ CircleTensorIndex _index;
+};
+
+} // namespace
+
+void set_tensor_index(loco::Node *node, const CircleTensorIndex &tensor_id)
+{
+ assert(node->annot<CircleTensorIndexAnnotation>() == nullptr);
+ node->annot(std::make_unique<CircleTensorIndexAnnotation>(tensor_id));
+}
+
+CircleTensorIndex get_tensor_index(loco::Node *node)
+{
+ assert(node->annot<CircleTensorIndexAnnotation>() != nullptr);
+ return node->annot<CircleTensorIndexAnnotation>()->index();
+}
+
+} // namespace luci
diff --git a/compiler/luci/export/src/CircleExporterUtils.h b/compiler/luci/export/src/CircleExporterUtils.h
new file mode 100644
index 000000000..6b970fd3c
--- /dev/null
+++ b/compiler/luci/export/src/CircleExporterUtils.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2020 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 __CIRCLE_EXPORTER_UTILS_H__
+#define __CIRCLE_EXPORTER_UTILS_H__
+
+#include "SerializedData.h"
+
+#include <luci/IR/CircleNodes.h>
+#include <luci/Service/ShapeDescription.h>
+
+#include <loco.h>
+
+#include <mio/circle/schema_generated.h>
+
+namespace luci
+{
+
+circle::ActivationFunctionType to_circle_actfunc(luci::FusedActFunc func);
+circle::TensorType to_circle_tensortype(loco::DataType type);
+
+} // namespace luci
+
+namespace luci
+{
+
+circle::Padding getOpPadding(const loco::Padding2D *pad, const loco::Stride<2> *stride,
+ const ShapeDescription &ifm, const ShapeDescription &ofm);
+circle::Padding getOpPadding(const luci::Padding pad);
+
+using CircleTensorIndex = int32_t;
+
+void set_tensor_index(loco::Node *node, const CircleTensorIndex &tensor_id);
+CircleTensorIndex get_tensor_index(loco::Node *node);
+
+} // namespace luci
+
+#endif // __CIRCLE_EXPORTER_UTILS_H__
diff --git a/compiler/luci/export/src/CircleOperationExporter.cpp b/compiler/luci/export/src/CircleOperationExporter.cpp
new file mode 100644
index 000000000..ad9c7fd4b
--- /dev/null
+++ b/compiler/luci/export/src/CircleOperationExporter.cpp
@@ -0,0 +1,643 @@
+/*
+ * Copyright (c) 2020 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 "CircleOperationExporter.h"
+#include "CircleExporterUtils.h"
+#include "Check.h"
+
+#include <luci/IR/CircleNode.h>
+#include <luci/IR/CircleNodes.h>
+#include <luci/IR/CircleNodeVisitor.h>
+#include <luci/Service/CircleShapeInference.h>
+
+#include <loco/IR/CanonicalNodeVisitor.h>
+#include <oops/InternalExn.h>
+
+#include <flatbuffers/flexbuffers.h>
+
+using namespace flatbuffers;
+using namespace circle;
+
+namespace
+{
+
+using namespace luci;
+
+class OperationExporter final : public luci::CircleNodeMutableVisitor<void>,
+ public loco::CanonicalNodeMutableVisitor<void>
+{
+public:
+ OperationExporter(FlatBufferBuilder &fbb, SerializedModelData &m, SerializedGraphData &g)
+ : builder{fbb}, md{m}, gd{g}
+ {
+ // DO NOTHING
+ }
+
+public:
+ void visit(luci::CircleAbs *) final;
+ void visit(luci::CircleAdd *) final;
+ void visit(luci::CircleArgMax *) final;
+ void visit(luci::CircleAveragePool2D *) final;
+ void visit(luci::CircleBatchToSpaceND *) final;
+ void visit(luci::CircleConcatenation *) final;
+ void visit(luci::CircleConst *) final{/* skip, everything is done in exportOpDefinedTensors */};
+ void visit(luci::CircleConv2D *) final;
+ void visit(luci::CircleCos *) final;
+ void visit(luci::CircleDepthwiseConv2D *) final;
+ void visit(luci::CircleDiv *) final;
+ void visit(luci::CircleExp *) final;
+ void visit(luci::CircleEqual *) final;
+ void visit(luci::CircleFullyConnected *) final;
+ void visit(luci::CircleLogicalNot *) final;
+ void visit(luci::CircleLogicalOr *) final;
+ void visit(luci::CircleMaximum *) final;
+ void visit(luci::CircleMaxPool2D *) final;
+ void visit(luci::CircleMean *) final;
+ void visit(luci::CircleMul *) final;
+ void visit(luci::CirclePack *) final;
+ void visit(luci::CirclePad *) final;
+ void visit(luci::CircleRelu *) final;
+ void visit(luci::CircleRelu6 *) final;
+ void visit(luci::CircleReshape *) final;
+ void visit(luci::CircleRsqrt *) final;
+ void visit(luci::CircleSoftmax *) final;
+ void visit(luci::CircleSqrt *) final;
+ void visit(luci::CircleSquaredDifference *) final;
+ void visit(luci::CircleSub *) final;
+ // TODO CircleTanh
+ void visit(luci::CircleTranspose *) final;
+ void visit(luci::CircleTransposeConv *) final;
+ // Circle only
+ void visit(luci::CircleInstanceNorm *) final;
+ // Virtual
+ void visit(luci::CircleInput *) final {}
+ void visit(luci::CircleOutput *) final {}
+
+private:
+ /**
+ * @brief Exports CircleMaxPool2D or CircleAveragePool2D
+ *
+ * @note CirclePool2D should be one of CircleMaxPool2D or CircleAveragePool2D
+ */
+ template <class CirclePool2D>
+ void export_pool_2d(CirclePool2D *node, circle::BuiltinOperator builtin_op);
+
+private:
+ FlatBufferBuilder &builder;
+ SerializedModelData &md;
+ SerializedGraphData &gd;
+};
+
+template <class CirclePool2D>
+void OperationExporter::export_pool_2d(CirclePool2D *node, circle::BuiltinOperator builtin_op)
+{
+ LUCI_ASSERT(builtin_op == circle::BuiltinOperator_MAX_POOL_2D ||
+ builtin_op == circle::BuiltinOperator_AVERAGE_POOL_2D,
+ "Should be MaxPool or AvgPool");
+ LUCI_ASSERT(node->padding() != luci::Padding::UNDEFINED, "Padding is not set");
+
+ uint32_t op_idx = md.registerBuiltinOpcode(builtin_op);
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->value())};
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
+ auto inputs = builder.CreateVector(inputs_vec);
+ auto outputs = builder.CreateVector(outputs_vec);
+
+ circle::Padding padding = getOpPadding(node->padding());
+
+ auto options = CreatePool2DOptions(builder, padding, node->stride()->w(), node->stride()->h(),
+ node->filter()->w(), node->filter()->h(),
+ to_circle_actfunc(node->fusedActivationFunction()));
+ auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
+ circle::BuiltinOptions_Pool2DOptions, options.Union());
+ gd._operators.push_back(op_offset);
+}
+
+void OperationExporter::visit(luci::CircleAbs *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_ABS);
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->x())};
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
+ auto inputs = builder.CreateVector(inputs_vec);
+ auto outputs = builder.CreateVector(outputs_vec);
+ auto options = CreateAbsOptions(builder);
+ auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
+ circle::BuiltinOptions_AbsOptions, options.Union());
+ gd._operators.push_back(op_offset);
+}
+
+void OperationExporter::visit(luci::CircleAdd *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_ADD);
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())};
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
+ auto inputs = builder.CreateVector(inputs_vec);
+ auto outputs = builder.CreateVector(outputs_vec);
+ auto options = CreateAddOptions(builder, to_circle_actfunc(node->fusedActivationFunction()));
+ auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
+ circle::BuiltinOptions_AddOptions, options.Union());
+ gd._operators.push_back(op_offset);
+}
+
+void OperationExporter::visit(luci::CircleArgMax *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_ARG_MAX);
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->input()),
+ get_tensor_index(node->dimension())};
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
+ auto inputs = builder.CreateVector(inputs_vec);
+ auto outputs = builder.CreateVector(outputs_vec);
+ auto options = CreateArgMaxOptions(builder, to_circle_tensortype(node->output_type()));
+ auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
+ circle::BuiltinOptions_ArgMaxOptions, options.Union());
+ gd._operators.push_back(op_offset);
+}
+
+void OperationExporter::visit(luci::CircleAveragePool2D *node)
+{
+ export_pool_2d<luci::CircleAveragePool2D>(node, circle::BuiltinOperator_AVERAGE_POOL_2D);
+}
+
+void OperationExporter::visit(luci::CircleConcatenation *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_CONCATENATION);
+ std::vector<int32_t> inputs_vec;
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
+
+ for (uint32_t i = 0; i < node->numValues(); ++i)
+ inputs_vec.push_back(get_tensor_index(node->values(i)));
+
+ auto inputs = builder.CreateVector(inputs_vec);
+ auto outputs = builder.CreateVector(outputs_vec);
+ auto options = CreateConcatenationOptions(builder, node->axis(),
+ to_circle_actfunc(node->fusedActivationFunction()));
+ auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
+ circle::BuiltinOptions_ConcatenationOptions, options.Union());
+ gd._operators.push_back(op_offset);
+}
+
+void OperationExporter::visit(luci::CircleBatchToSpaceND *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_BATCH_TO_SPACE_ND);
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->input()),
+ get_tensor_index(node->block_shape()),
+ get_tensor_index(node->crops())};
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
+
+ auto inputs = builder.CreateVector(inputs_vec);
+ auto outputs = builder.CreateVector(outputs_vec);
+ auto options = CreateBatchToSpaceNDOptions(builder);
+ auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
+ circle::BuiltinOptions_BatchToSpaceNDOptions, options.Union());
+ gd._operators.push_back(op_offset);
+}
+
+void OperationExporter::visit(luci::CircleConv2D *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_CONV_2D);
+
+ // Make input, output and options for operator
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->input()), get_tensor_index(node->filter()),
+ get_tensor_index(node->bias())};
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
+ auto inputs = builder.CreateVector(inputs_vec);
+ auto outputs = builder.CreateVector(outputs_vec);
+ circle::Padding padding = getOpPadding(node->padding());
+ auto options = CreateConv2DOptions(builder, padding, node->stride()->w(), node->stride()->h(),
+ to_circle_actfunc(node->fusedActivationFunction()));
+
+ // Make CONV_2D operator
+ auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
+ circle::BuiltinOptions_Conv2DOptions, options.Union());
+ gd._operators.push_back(op_offset);
+}
+
+void OperationExporter::visit(luci::CircleCos *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_COS);
+
+ // Make input, output and options for operator
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->x())};
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
+ auto inputs = builder.CreateVector(inputs_vec);
+ auto outputs = builder.CreateVector(outputs_vec);
+ auto options = CreateCosOptions(builder);
+
+ // Make COS operator
+ auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
+ circle::BuiltinOptions_CosOptions, options.Union());
+ gd._operators.push_back(op_offset);
+}
+
+void OperationExporter::visit(luci::CircleDepthwiseConv2D *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_DEPTHWISE_CONV_2D);
+
+ // Make input, output and options for operator
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->input()), get_tensor_index(node->filter()),
+ get_tensor_index(node->bias())};
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
+ auto inputs = builder.CreateVector(inputs_vec);
+ auto outputs = builder.CreateVector(outputs_vec);
+ circle::Padding padding = getOpPadding(node->padding());
+ auto options = CreateDepthwiseConv2DOptions(builder, padding, node->stride()->w(),
+ node->stride()->h(), node->depthMultiplier(),
+ to_circle_actfunc(node->fusedActivationFunction()));
+
+ // Make DEPTHWISE_CONV_2D operator
+ auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
+ circle::BuiltinOptions_DepthwiseConv2DOptions, options.Union());
+ gd._operators.push_back(op_offset);
+}
+
+void OperationExporter::visit(luci::CircleDiv *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_DIV);
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())};
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
+ auto inputs = builder.CreateVector(inputs_vec);
+ auto outputs = builder.CreateVector(outputs_vec);
+ auto options = CreateDivOptions(builder, to_circle_actfunc(node->fusedActivationFunction()));
+ auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
+ circle::BuiltinOptions_DivOptions, options.Union());
+ gd._operators.push_back(op_offset);
+}
+
+void OperationExporter::visit(luci::CircleExp *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_EXP);
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->x())};
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
+ auto inputs = builder.CreateVector(inputs_vec);
+ auto outputs = builder.CreateVector(outputs_vec);
+ auto options = CreateAbsOptions(builder);
+ auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
+ circle::BuiltinOptions_ExpOptions, options.Union());
+ gd._operators.push_back(op_offset);
+}
+
+void OperationExporter::visit(luci::CircleFullyConnected *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_FULLY_CONNECTED);
+
+ // Make input, output and options for operator
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->input()),
+ get_tensor_index(node->weights()),
+ get_tensor_index(node->bias())};
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
+ auto inputs = builder.CreateVector(inputs_vec);
+ auto outputs = builder.CreateVector(outputs_vec);
+ auto options =
+ CreateFullyConnectedOptions(builder, to_circle_actfunc(node->fusedActivationFunction()));
+
+ // Make FULLY_CONNECTED operator
+ auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
+ circle::BuiltinOptions_FullyConnectedOptions, options.Union());
+ gd._operators.push_back(op_offset);
+}
+
+void OperationExporter::visit(luci::CircleLogicalNot *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_LOGICAL_NOT);
+
+ // Make input, output and options for operator
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->x())};
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
+ auto inputs = builder.CreateVector(inputs_vec);
+ auto outputs = builder.CreateVector(outputs_vec);
+ auto options = CreateLogicalNotOptions(builder);
+
+ // Make LOGICAL_NOT operator
+ auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
+ circle::BuiltinOptions_LogicalNotOptions, options.Union());
+ gd._operators.push_back(op_offset);
+}
+
+void OperationExporter::visit(luci::CircleLogicalOr *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_LOGICAL_OR);
+
+ // Make input, output and options for operator
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())};
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
+ auto inputs = builder.CreateVector(inputs_vec);
+ auto outputs = builder.CreateVector(outputs_vec);
+ auto options = CreateLogicalOrOptions(builder);
+
+ // Make LOGICAL_OR operator
+ auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
+ circle::BuiltinOptions_LogicalOrOptions, options.Union());
+ gd._operators.push_back(op_offset);
+}
+
+void OperationExporter::visit(luci::CircleMaximum *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_MAXIMUM);
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())};
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
+ auto inputs = builder.CreateVector(inputs_vec);
+ auto outputs = builder.CreateVector(outputs_vec);
+ auto options = CreateMaximumMinimumOptions(builder);
+ auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
+ circle::BuiltinOptions_MaximumMinimumOptions, options.Union());
+ gd._operators.push_back(op_offset);
+}
+
+void OperationExporter::visit(luci::CircleMaxPool2D *node)
+{
+ export_pool_2d<luci::CircleMaxPool2D>(node, circle::BuiltinOperator_MAX_POOL_2D);
+}
+
+void OperationExporter::visit(luci::CircleMean *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_MEAN);
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->input()),
+ get_tensor_index(node->reduction_indices())};
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
+ auto inputs = builder.CreateVector(inputs_vec);
+ auto outputs = builder.CreateVector(outputs_vec);
+ auto options = CreateReducerOptions(builder, node->keep_dims());
+ auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
+ circle::BuiltinOptions_ReducerOptions, options.Union());
+ gd._operators.push_back(op_offset);
+}
+
+void OperationExporter::visit(luci::CircleMul *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_MUL);
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())};
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
+ auto inputs = builder.CreateVector(inputs_vec);
+ auto outputs = builder.CreateVector(outputs_vec);
+ auto options = CreateMulOptions(builder, to_circle_actfunc(node->fusedActivationFunction()));
+ auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
+ circle::BuiltinOptions_MulOptions, options.Union());
+ gd._operators.push_back(op_offset);
+}
+
+void OperationExporter::visit(luci::CirclePack *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_PACK);
+ std::vector<int32_t> inputs_vec;
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
+
+ for (uint32_t i = 0; i < node->values_count(); ++i)
+ inputs_vec.push_back(get_tensor_index(node->values(i)));
+
+ auto inputs = builder.CreateVector(inputs_vec);
+ auto outputs = builder.CreateVector(outputs_vec);
+ auto options = CreatePackOptions(builder, node->values_count(), node->axis());
+ auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
+ circle::BuiltinOptions_PackOptions, options.Union());
+ gd._operators.push_back(op_offset);
+}
+
+void OperationExporter::visit(luci::CirclePad *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_PAD);
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->input()),
+ get_tensor_index(node->paddings())};
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
+ auto inputs = builder.CreateVector(inputs_vec);
+ auto outputs = builder.CreateVector(outputs_vec);
+ auto options = CreatePadOptions(builder);
+ auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
+ circle::BuiltinOptions_PadOptions, options.Union());
+ gd._operators.push_back(op_offset);
+}
+
+void OperationExporter::visit(luci::CircleRelu *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_RELU);
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->features())};
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(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 OperationExporter::visit(luci::CircleRelu6 *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_RELU6);
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->features())};
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(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 OperationExporter::visit(luci::CircleReshape *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_RESHAPE);
+
+ // Create inputs and outputs.
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->tensor()),
+ get_tensor_index(node->shape())};
+ std::vector<int32_t> outputs_vec{get_tensor_index(node)};
+ auto inputs = builder.CreateVector(inputs_vec);
+ auto outputs = builder.CreateVector(outputs_vec);
+
+ // Create options.
+ auto new_shape = builder.CreateVector<int32_t>(
+ node->newShape()->rank(), [node](size_t i) { return node->newShape()->dim(i); });
+ auto options = CreateReshapeOptions(builder, new_shape);
+
+ // Create the operator.
+ auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
+ circle::BuiltinOptions_ReshapeOptions, options.Union());
+ gd._operators.push_back(op_offset);
+}
+
+void OperationExporter::visit(luci::CircleRsqrt *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_RSQRT);
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->x())};
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(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 OperationExporter::visit(luci::CircleSoftmax *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_SOFTMAX);
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->logits())};
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
+ auto inputs = builder.CreateVector(inputs_vec);
+ auto outputs = builder.CreateVector(outputs_vec);
+ auto options = CreateSoftmaxOptions(builder, node->beta());
+ auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
+ circle::BuiltinOptions_SoftmaxOptions, options.Union());
+ gd._operators.push_back(op_offset);
+}
+
+void OperationExporter::visit(luci::CircleSqrt *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_SQRT);
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->x())};
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(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 OperationExporter::visit(luci::CircleSquaredDifference *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_SQUARED_DIFFERENCE);
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())};
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
+ auto inputs = builder.CreateVector(inputs_vec);
+ auto outputs = builder.CreateVector(outputs_vec);
+ auto options = CreateSquaredDifferenceOptions(builder);
+ auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
+ circle::BuiltinOptions_SquaredDifferenceOptions, options.Union());
+ gd._operators.push_back(op_offset);
+}
+
+void OperationExporter::visit(luci::CircleSub *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_SUB);
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())};
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
+ auto inputs = builder.CreateVector(inputs_vec);
+ auto outputs = builder.CreateVector(outputs_vec);
+ auto options = CreateSubOptions(builder, to_circle_actfunc(node->fusedActivationFunction()));
+ auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
+ circle::BuiltinOptions_SubOptions, options.Union());
+ gd._operators.push_back(op_offset);
+}
+
+// TODO CircleTanh
+
+void OperationExporter::visit(luci::CircleTranspose *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_TRANSPOSE);
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->arg(0)), get_tensor_index(node->arg(1))};
+ std::vector<int32_t> outputs_vec{get_tensor_index(node)};
+
+ auto inputs = builder.CreateVector(inputs_vec);
+ auto outputs = builder.CreateVector(outputs_vec);
+ auto options = CreateTransposeOptions(builder);
+
+ auto op_offset =
+ CreateOperator(builder, op_idx, inputs, outputs,
+ circle::BuiltinOptions::BuiltinOptions_TransposeOptions, options.Union());
+ gd._operators.push_back(op_offset);
+}
+
+void OperationExporter::visit(luci::CircleTransposeConv *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_TRANSPOSE_CONV);
+
+ // Make input, output and options for operator
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->inputSizes()),
+ get_tensor_index(node->filter()),
+ get_tensor_index(node->outBackprop())};
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
+ auto inputs = builder.CreateVector(inputs_vec);
+ auto outputs = builder.CreateVector(outputs_vec);
+ circle::Padding padding = getOpPadding(node->padding());
+ auto options =
+ CreateTransposeConvOptions(builder, padding, node->stride()->w(), node->stride()->h());
+
+ // Make TRANSPOSE_CONV operator
+ auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
+ circle::BuiltinOptions_TransposeConvOptions, options.Union());
+ gd._operators.push_back(op_offset);
+}
+
+void OperationExporter::visit(luci::CircleInstanceNorm *node)
+{
+ uint32_t op_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_INSTANCE_NORM);
+ std::vector<int32_t> inputs_vec{get_tensor_index(node->input()), get_tensor_index(node->gamma()),
+ get_tensor_index(node->beta())};
+ std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
+ auto inputs = builder.CreateVector(inputs_vec);
+ auto outputs = builder.CreateVector(outputs_vec);
+ auto options = CreateInstanceNormOptions(builder, node->epsilon(),
+ to_circle_actfunc(node->fusedActivationFunction()));
+ auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
+ circle::BuiltinOptions_InstanceNormOptions, options.Union());
+ gd._operators.push_back(op_offset);
+}
+
+void OperationExporter::visit(luci::CircleEqual *node)
+{
+ uint32_t opcode_idx = md.registerBuiltinOpcode(circle::BuiltinOperator_EQUAL);
+ std::vector<int32_t> inputs{get_tensor_index(node->x()), get_tensor_index(node->y())};
+ std::vector<int32_t> outputs{get_tensor_index(node)};
+
+ auto fb_inputs = builder.CreateVector(inputs);
+ auto fb_outputs = builder.CreateVector(outputs);
+
+ auto options = CreateEqualOptions(builder);
+
+ auto op_offset = CreateOperator(builder, opcode_idx, fb_inputs, fb_outputs,
+ circle::BuiltinOptions_EqualOptions, options.Union());
+
+ gd._operators.push_back(op_offset);
+}
+
+void exportNode(loco::Node *node, flatbuffers::FlatBufferBuilder &builder, SerializedModelData &md,
+ SerializedGraphData &gd)
+{
+ // TODO Use explicit tagging to prevent possible mistake
+ auto isNoOp = [](loco::Node *node) {
+ // If there is only one input and the TensorIndex for the input is same
+ // as the TensorIndex of the output then this node is just a dummy node
+ if (node->arity() == 1)
+ {
+ assert(node->arg(0) != nullptr);
+ return get_tensor_index(node) == get_tensor_index(node->arg(0));
+ }
+ return false;
+ };
+
+ if (isNoOp(node))
+ {
+ // Skip if a given node is marked as NoOp (op with no effect) before
+ return;
+ }
+
+ if (auto circle_node = dynamic_cast<luci::CircleNode *>(node))
+ {
+ OperationExporter exporter{builder, md, gd};
+ circle_node->accept(&exporter);
+ }
+ else
+ {
+ INTERNAL_EXN("Node with unsupported dialect found");
+ }
+}
+
+} // namespace
+
+namespace luci
+{
+
+void exportNodes(loco::Graph *g, FlatBufferBuilder &builder, SerializedModelData &md,
+ SerializedGraphData &gd)
+{
+ for (auto node : loco::postorder_traversal(loco::output_nodes(g)))
+ {
+ exportNode(node, builder, md, gd);
+ }
+}
+
+} // namespace luci
diff --git a/compiler/luci/export/src/CircleOperationExporter.h b/compiler/luci/export/src/CircleOperationExporter.h
new file mode 100644
index 000000000..de6abfc54
--- /dev/null
+++ b/compiler/luci/export/src/CircleOperationExporter.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2020 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 __CIRCLE_OPERATION_EXPORTER_H__
+#define __CIRCLE_OPERATION_EXPORTER_H__
+
+#include "CircleExporterUtils.h"
+
+#include <loco/IR/Graph.h>
+
+namespace luci
+{
+
+/**
+ * @brief create Operators corresponding to model nodes
+ * @param nodes container with nodes
+ * @param gd information about serializer parts of model
+ */
+void exportNodes(loco::Graph *g, flatbuffers::FlatBufferBuilder &builder, SerializedModelData &md,
+ SerializedGraphData &gd);
+
+} // namespace luci
+
+#endif // __CIRCLE_OPERATION_EXPORTER_H__
diff --git a/compiler/luci/export/src/CircleTensorExporter.cpp b/compiler/luci/export/src/CircleTensorExporter.cpp
new file mode 100644
index 000000000..ef9b9d7d9
--- /dev/null
+++ b/compiler/luci/export/src/CircleTensorExporter.cpp
@@ -0,0 +1,264 @@
+/*
+ * Copyright (c) 2020 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 "CircleTensorExporter.h"
+
+#include <luci/IR/CircleNodes.h>
+#include <luci/IR/CircleNodeVisitor.h>
+#include <luci/Service/CircleTypeInference.h>
+#include <luci/Service/CircleShapeInference.h>
+#include <luci/Log.h>
+
+#include <loco/IR/Algorithm.h>
+#include <loco/IR/CanonicalNode.h>
+#include <loco/IR/CanonicalNodeVisitor.h>
+#include <loco/IR/DataTypeTraits.h>
+#include <oops/InternalExn.h>
+
+using namespace circle;
+using namespace flatbuffers;
+
+namespace
+{
+
+using namespace luci;
+
+class CircleTensoInfo
+{
+public:
+ CircleTensoInfo() = default;
+
+public:
+ void name(const std::string &name) { _name = name; }
+ const std::string &name(void) const { return _name; }
+
+public:
+ const circle::TensorType &dtype(void) const { return _dtype; }
+ void dtype(const circle::TensorType &dtype) { _dtype = dtype; }
+
+ const ShapeDescription &shape(void) const { return _shape; }
+ void shape(const ShapeDescription &shape) { _shape = shape; }
+
+public:
+ luci::CircleConst *content(void) const { return _content; }
+ void content(luci::CircleConst *c) { _content = c; }
+
+ luci::CircleQuantParam *quantparam(void) const { return _quantparam; }
+ void quantparam(luci::CircleQuantParam *qp) { _quantparam = qp; }
+
+private:
+ std::string _name;
+
+ circle::TensorType _dtype;
+ ShapeDescription _shape;
+
+ luci::CircleConst *_content = nullptr;
+ luci::CircleQuantParam *_quantparam = nullptr;
+};
+
+using CircleTensorContext = std::vector<CircleTensoInfo>;
+
+struct NoOpDetector final : public luci::CircleNodeMutableVisitor<bool>
+{
+ // Input is Virtual but does produce a Tensor
+ // Output is Virtual that does not produce any Tensor
+ bool visit(luci::CircleOutput *) final { return true; }
+
+ // Return false by default
+ bool visit(luci::CircleNode *) final { return false; }
+};
+
+void allocateCircleTensor(CircleNode *node, CircleTensorContext &ctx)
+{
+ LOGGER(l);
+
+ auto isNoOp = [](loco::Node *node) {
+ if (auto circle_node = dynamic_cast<luci::CircleNode *>(node))
+ {
+ NoOpDetector d;
+ return circle_node->accept(&d);
+ }
+ return false;
+ };
+
+ if (isNoOp(node))
+ {
+ set_tensor_index(node, get_tensor_index(node->arg(0)));
+ return;
+ }
+
+ auto tensor_index = static_cast<CircleTensorIndex>(ctx.size());
+ // TODO Use Graph-level metadata for Input & Output
+ // auto tensor_name = "t_" + std::to_string(tensor_index);
+ std::string tensor_name = node->name();
+ if (tensor_name.empty())
+ tensor_name = "t_" + std::to_string(tensor_index);
+ INFO(l) << "[luci] Tensor for " << tensor_name << ": " << tensor_index << std::endl;
+
+ CircleTensoInfo tensor_info;
+
+ tensor_info.name(tensor_name);
+ tensor_info.dtype(TypeInference::get(node));
+ tensor_info.shape(ShapeInference::get(node));
+
+ tensor_info.content(dynamic_cast<luci::CircleConst *>(node));
+ tensor_info.quantparam(node->quantparam());
+
+ set_tensor_index(node, tensor_index);
+
+ ctx.emplace_back(tensor_info);
+}
+
+} // namespace
+
+namespace
+{
+
+flatbuffers::Offset<Vector<int32_t>> encodeShape(FlatBufferBuilder &builder,
+ const ShapeDescription &shape)
+{
+ assert(shape._rank_known && "unknown number of dimensions is not supported");
+ return builder.CreateVector(shape._dims);
+}
+
+flatbuffers::Offset<circle::Buffer> encodeOpBuffer(FlatBufferBuilder &builder)
+{
+ return CreateBuffer(builder);
+}
+
+template <typename NodeT>
+flatbuffers::Offset<circle::Buffer> encodeOpBuffer(FlatBufferBuilder &builder, NodeT *)
+{
+ return CreateBuffer(builder);
+}
+
+template <loco::DataType DT>
+flatbuffers::Offset<circle::Buffer> encodeOpBufferByDType(FlatBufferBuilder &builder,
+ luci::CircleConst *c)
+{
+ using NativeType = typename loco::DataTypeImpl<DT>::Type;
+
+ std::vector<NativeType> raw_data;
+ const uint32_t size = c->size<DT>();
+ raw_data.reserve(size);
+ for (uint32_t i = 0; i < size; ++i)
+ {
+ raw_data.push_back(c->at<DT>(i));
+ }
+ const size_t raw_size = size * sizeof(NativeType);
+ auto array_offset = builder.CreateVector(reinterpret_cast<uint8_t *>(raw_data.data()), raw_size);
+ return CreateBuffer(builder, array_offset);
+}
+
+template <>
+flatbuffers::Offset<circle::Buffer> encodeOpBuffer(FlatBufferBuilder &builder, luci::CircleConst *c)
+{
+ // TODO use switch
+ if (c->dtype() == loco::DataType::FLOAT32)
+ {
+ return encodeOpBufferByDType<loco::DataType::FLOAT32>(builder, c);
+ }
+ else if (c->dtype() == loco::DataType::S32)
+ {
+ return encodeOpBufferByDType<loco::DataType::S32>(builder, c);
+ }
+ else if (c->dtype() == loco::DataType::U8)
+ {
+ return encodeOpBufferByDType<loco::DataType::U8>(builder, c);
+ }
+
+ INTERNAL_EXN_V("Unsupported datatype", oops::to_uint32(c->dtype()));
+}
+
+flatbuffers::Offset<circle::QuantizationParameters>
+encodeQuantizationParameters(FlatBufferBuilder &builder, luci::CircleQuantParam *quantparam)
+{
+ if (quantparam == nullptr)
+ return 0;
+
+ flatbuffers::Offset<flatbuffers::Vector<float>> min;
+ flatbuffers::Offset<flatbuffers::Vector<float>> max;
+ flatbuffers::Offset<flatbuffers::Vector<float>> scale;
+ flatbuffers::Offset<flatbuffers::Vector<int64_t>> zero_point;
+ if (quantparam->min.size() && quantparam->max.size())
+ {
+ min = builder.CreateVector(quantparam->min);
+ max = builder.CreateVector(quantparam->max);
+ }
+ if (quantparam->scale.size() && quantparam->zerop.size())
+ {
+ scale = builder.CreateVector(quantparam->scale);
+ zero_point = builder.CreateVector(quantparam->zerop);
+ }
+ return circle::CreateQuantizationParameters(builder, min, max, scale, zero_point);
+}
+
+void exportOpDefinedTensor(const CircleTensoInfo &info, FlatBufferBuilder &builder,
+ SerializedModelData &md, SerializedGraphData &gd)
+{
+ // Create and register output tensor shape
+ auto shape_offset = encodeShape(builder, info.shape());
+
+ // encode and register output tensor buffer
+ auto buffer =
+ info.content() == nullptr ? encodeOpBuffer(builder) : encodeOpBuffer(builder, info.content());
+
+ auto quantparam = encodeQuantizationParameters(builder, info.quantparam());
+
+ auto buffer_id = static_cast<uint32_t>(md._buffers.size());
+ md._buffers.push_back(buffer);
+
+ auto name_offset = builder.CreateString(info.name());
+ auto tensor_offset = CreateTensor(builder, shape_offset, info.dtype(), buffer_id, name_offset,
+ quantparam, /*is_variable*/ false);
+ gd._tensors.push_back(tensor_offset);
+}
+
+} // namespace
+
+namespace luci
+{
+
+void prepareModelData(FlatBufferBuilder &builder, SerializedModelData &md)
+{
+ // add one empty buffer
+ // note: this follows TFLite
+ // note: there's a comment in tflite fbs file
+ // - 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.
+ auto buffer = encodeOpBuffer(builder);
+ md._buffers.push_back(buffer);
+}
+
+void exportOpDefinedTensors(loco::Graph *g, FlatBufferBuilder &builder, SerializedModelData &md,
+ SerializedGraphData &gd)
+{
+ CircleTensorContext tensor_ctx;
+
+ for (auto node : loco::postorder_traversal(loco::output_nodes(g)))
+ {
+ CircleNode *circle_node = dynamic_cast<luci::CircleNode *>(node);
+ allocateCircleTensor(circle_node, tensor_ctx);
+ }
+
+ for (const auto &tensor_info : tensor_ctx)
+ {
+ exportOpDefinedTensor(tensor_info, builder, md, gd);
+ }
+}
+
+} // namespace luci
diff --git a/compiler/luci/export/src/CircleTensorExporter.h b/compiler/luci/export/src/CircleTensorExporter.h
new file mode 100644
index 000000000..f9d6107b4
--- /dev/null
+++ b/compiler/luci/export/src/CircleTensorExporter.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2020 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 __CIRCLE_TENSOR_EXPORTER_H__
+#define __CIRCLE_TENSOR_EXPORTER_H__
+
+#include "CircleExporterUtils.h"
+
+#include <loco/IR/Graph.h>
+
+#include <flatbuffers/flatbuffers.h>
+
+namespace luci
+{
+
+/**
+ * @brief one time preparation for SerializedModelData
+ */
+void prepareModelData(flatbuffers::FlatBufferBuilder &builder, SerializedModelData &md);
+
+/**
+ * @brief create Tensors corresponding to results of all nodes in graph
+ * @param computational graph
+ * @param gd information about serialized parts of model
+ */
+void exportOpDefinedTensors(loco::Graph *g, flatbuffers::FlatBufferBuilder &builder,
+ SerializedModelData &md, SerializedGraphData &gd);
+
+} // namespace luci
+
+#endif // __CIRCLE_TENSOR_EXPORTER_H__
diff --git a/compiler/luci/export/src/Optimize.cpp b/compiler/luci/export/src/Optimize.cpp
new file mode 100644
index 000000000..6fa50b564
--- /dev/null
+++ b/compiler/luci/export/src/Optimize.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020 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 "Optimize.h"
+#include "ProgressReporter.h"
+
+#include <luci/Pass/ShapeInferencePass.h>
+#include <luci/Pass/TypeInferencePass.h>
+
+#include <logo/Phase.h>
+
+#include <memory>
+
+namespace luci
+{
+
+void optimize(loco::Graph *g)
+{
+ logo::Phase phase;
+ {
+ // prepare type and shape before optimization
+ phase.emplace_back(std::make_unique<TypeInferencePass>());
+ phase.emplace_back(std::make_unique<ShapeInferencePass>());
+
+ // TODO add more optimization passes (with a knob)
+ }
+
+ logo::PhaseRunner<logo::PhaseStrategy::Restart> phase_runner{g};
+
+ ProgressReporter prog(g, logo::PhaseStrategy::Restart);
+ phase_runner.attach(&prog);
+ phase_runner.run(phase);
+}
+
+} // namespace luci
diff --git a/compiler/luci/export/src/Optimize.h b/compiler/luci/export/src/Optimize.h
new file mode 100644
index 000000000..c3af7a04c
--- /dev/null
+++ b/compiler/luci/export/src/Optimize.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2020 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 __OPTIMIZE_H__
+#define __OPTIMIZE_H__
+
+#include <loco.h>
+
+namespace luci
+{
+
+/**
+ * @brief Run passes of graph transformations
+ *
+ */
+void optimize(loco::Graph *);
+
+} // namespace luci
+
+#endif // __OPTIMIZE_H__
diff --git a/compiler/luci/export/src/ProgressReporter.cpp b/compiler/luci/export/src/ProgressReporter.cpp
new file mode 100644
index 000000000..ac9c3d9a8
--- /dev/null
+++ b/compiler/luci/export/src/ProgressReporter.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2020 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 "ProgressReporter.h"
+
+#include "luci/Log.h"
+#include "luci/LogHelper.h"
+
+#include <logo/Phase.h>
+#include <logo/Pass.h>
+
+#include <cassert>
+
+namespace
+{
+
+char to_char(bool b) { return b ? 'Y' : 'N'; }
+
+const char *to_str(logo::PhaseStrategy s)
+{
+ switch (s)
+ {
+ case logo::PhaseStrategy::Saturate:
+ return "Saturate";
+ case logo::PhaseStrategy::Restart:
+ return "Restart";
+ }
+ assert(false);
+ return "";
+}
+
+} // namespace
+
+namespace luci
+{
+
+void ProgressReporter::notify(const logo::PhaseEventInfo<logo::PhaseEvent::PhaseBegin> *)
+{
+ LOGGER(prime);
+
+ INFO(prime) << "==============================================================";
+ INFO(prime) << "luci::PhaseRunner<" << to_str(strategy()) << ">";
+ INFO(prime) << "Initial graph";
+ INFO(prime) << fmt(graph());
+}
+
+void ProgressReporter::notify(const logo::PhaseEventInfo<logo::PhaseEvent::PhaseEnd> *)
+{
+ LOGGER(prime);
+
+ INFO(prime) << "luci::PhaseRunner<" << to_str(strategy()) << "> - done";
+}
+
+void ProgressReporter::notify(const logo::PhaseEventInfo<logo::PhaseEvent::PassBegin> *info)
+{
+ LOGGER(prime);
+
+ INFO(prime) << "--------------------------------------------------------------";
+ INFO(prime) << "Before " << logo::pass_name(info->pass());
+}
+
+void ProgressReporter::notify(const logo::PhaseEventInfo<logo::PhaseEvent::PassEnd> *info)
+{
+ LOGGER(prime);
+
+ INFO(prime) << "After " << logo::pass_name(info->pass())
+ << " (changed: " << to_char(info->changed()) << ")";
+ INFO(prime) << fmt(graph());
+}
+
+} // namespace luci
diff --git a/compiler/luci/export/src/ProgressReporter.h b/compiler/luci/export/src/ProgressReporter.h
new file mode 100644
index 000000000..e91f42592
--- /dev/null
+++ b/compiler/luci/export/src/ProgressReporter.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2020 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 __PROGRESSREPORTER_H__
+#define __PROGRESSREPORTER_H__
+
+#include <logo/Phase.h>
+
+#include <loco.h>
+
+namespace luci
+{
+
+class ProgressReporter : public logo::PhaseEventListener
+{
+public:
+ ProgressReporter(loco::Graph *graph, logo::PhaseStrategy strategy)
+ : _graph{graph}, _strategy{strategy}
+ {
+ // DO NOTHING
+ }
+
+public:
+ void notify(const logo::PhaseEventInfo<logo::PhaseEvent::PhaseBegin> *) override;
+ void notify(const logo::PhaseEventInfo<logo::PhaseEvent::PhaseEnd> *) override;
+ void notify(const logo::PhaseEventInfo<logo::PhaseEvent::PassBegin> *) override;
+ void notify(const logo::PhaseEventInfo<logo::PhaseEvent::PassEnd> *) override;
+
+public:
+ loco::Graph *graph(void) const { return _graph; }
+ logo::PhaseStrategy strategy(void) const { return _strategy; }
+
+private:
+ loco::Graph *_graph;
+ logo::PhaseStrategy _strategy;
+};
+
+} // namespace luci
+
+#endif // __PROGRESSREPORTER_H__
diff --git a/compiler/luci/export/src/SerializedData.h b/compiler/luci/export/src/SerializedData.h
new file mode 100644
index 000000000..84249653c
--- /dev/null
+++ b/compiler/luci/export/src/SerializedData.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2020 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 __SERIALIZED_DATA_H__
+#define __SERIALIZED_DATA_H__
+
+#include <mio/circle/schema_generated.h>
+
+#include <vector>
+
+#include <unordered_map>
+
+namespace luci
+{
+
+struct OpCode
+{
+ circle::BuiltinOperator opcode;
+
+ bool operator==(const OpCode &rhs) const { return opcode == rhs.opcode; }
+};
+
+} // namespace luci
+
+namespace std
+{
+
+template <> struct hash<luci::OpCode>
+{
+ size_t operator()(const luci::OpCode &x) const { return hash<int>()(x.opcode); }
+};
+
+} // namespace std
+
+namespace luci
+{
+
+/**
+ * @breif Record the information of T/F Lite SubGraph and its mapping to loco
+ */
+struct SubGraphContext
+{
+ /// @brief SubGraph input tensor id
+ std::vector<int32_t> _inputs;
+ /// @brief SubGraph output tensor id
+ std::vector<int32_t> _outputs;
+ /// @DataFormat for SubGraph
+ circle::DataFormat _data_format{circle::DataFormat::DataFormat_CHANNELS_LAST};
+};
+
+// Prerequisites for circle::Model object creation
+struct SerializedModelData final
+{
+ SerializedModelData() = default;
+ SerializedModelData(const SerializedModelData &) = delete;
+
+ std::unordered_map<OpCode, uint32_t> _operator_codes;
+ std::unordered_map<OpCode, std::string> _custom_operator_codes;
+ std::vector<flatbuffers::Offset<circle::Buffer>> _buffers;
+
+ /**
+ * @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(circle::BuiltinOperator builtin_code);
+ uint32_t registerCustomOpcode(const std::string &custom_op);
+};
+
+// Prerequisites for circle::Model object creation
+struct SerializedGraphData final : public SubGraphContext
+{
+ SerializedGraphData() = default;
+ SerializedGraphData(const SerializedModelData &) = delete;
+
+ std::vector<flatbuffers::Offset<circle::Operator>> _operators;
+ std::vector<flatbuffers::Offset<circle::Tensor>> _tensors;
+};
+
+} // namespace luci
+
+#endif // __SERIALIZED_DATA_H__