--- /dev/null
+/*
+ * 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 "Convert.h"
+#include "GraphBuilder.h"
+#include "GraphBuilderContext.h"
+
+#include <loco.h>
+#include <loco/IR/PermutingCodec.h>
+#include <stdex/Memory.h>
+
+#include <tensorflow/core/framework/graph.pb.h>
+
+#include <cassert>
+#include <stdexcept>
+
+namespace
+{
+using namespace moco::tf;
+
+class IFMUpdate final : public GraphUpdate
+{
+public:
+ IFMUpdate(loco::FeatureEncode *ifm_enc, const std::string &ifm_name)
+ : _ifm_enc(ifm_enc), _ifm_name(ifm_name)
+ {
+ }
+
+ void input(const SymbolTable *) const override;
+
+private:
+ loco::FeatureEncode *_ifm_enc;
+ const std::string _ifm_name;
+};
+
+class KernelUpdate final : public GraphUpdate
+{
+public:
+ KernelUpdate(loco::FilterEncode *ker_enc, const std::string &ker_name)
+ : _ker_enc(ker_enc), _ker_name(ker_name)
+ {
+ }
+
+ void input(const SymbolTable *) const override;
+
+private:
+ loco::FilterEncode *_ker_enc;
+ const std::string _ker_name;
+};
+
+void IFMUpdate::input(const SymbolTable *node_table) const
+{
+ loco::Node *ifm_node = node_table->node(_ifm_name);
+ _ifm_enc->input(ifm_node);
+}
+
+void KernelUpdate::input(const SymbolTable *node_table) const
+{
+ loco::Node *ker_node = node_table->node(_ker_name);
+ _ker_enc->input(ker_node);
+}
+
+} // namespace
+
+namespace moco
+{
+namespace tf
+{
+
+/**
+ * @brief GraphBuilder for Conv2D node
+ */
+class Conv2DGraphBuilder final : public GraphBuilder
+{
+public:
+ bool validate(const tensorflow::NodeDef &) const override;
+ void build(const tensorflow::NodeDef &, GraphBuilderContext *) const override;
+};
+
+bool Conv2DGraphBuilder::validate(const tensorflow::NodeDef &node) const
+{
+ assert(node.input_size() == 2);
+
+ // note: even though "data_format" is not entered when a model is written,
+ // TF seems to generate "data_format" field into a pb file
+ static std::string attrs[] = {"T", "data_format", "dilations", "padding", "strides"};
+
+ for (auto attr : attrs)
+ if (!node.attr().count(attr))
+ return false;
+
+ return true;
+}
+
+void Conv2DGraphBuilder::build(const tensorflow::NodeDef &node, GraphBuilderContext *context) const
+{
+ assert(context != nullptr);
+
+ loco::Graph *graph = context->graph();
+ SymbolTable *nodes = context->nodes();
+ UpdateQueue *updates = context->updates();
+
+ // name of loco nodes
+ std::string conv2d_name = node.name();
+
+ // tensorflow data_format, e.g., NHWC, NCHW, etc.
+ auto data_layout = get_data_layout(node, "data_format");
+
+ auto feature_enc = graph->nodes()->create<loco::FeatureEncode>();
+ {
+ auto enc = stdex::make_unique<loco::PermutingEncoder<loco::Domain::Feature>>();
+
+ if (data_layout == DataLayout::NHWC)
+ {
+ enc->perm()->axis(loco::FeatureAxis::Count) = 0;
+ enc->perm()->axis(loco::FeatureAxis::Height) = 1;
+ enc->perm()->axis(loco::FeatureAxis::Width) = 2;
+ enc->perm()->axis(loco::FeatureAxis::Depth) = 3;
+ }
+ else if (data_layout == DataLayout::NCHW)
+ {
+ enc->perm()->axis(loco::FeatureAxis::Count) = 0;
+ enc->perm()->axis(loco::FeatureAxis::Depth) = 1;
+ enc->perm()->axis(loco::FeatureAxis::Height) = 2;
+ enc->perm()->axis(loco::FeatureAxis::Width) = 3;
+ }
+ else
+ throw std::runtime_error("Not yet supported");
+
+ feature_enc->encoder(std::move(enc));
+ }
+
+ auto filter_enc = graph->nodes()->create<loco::FilterEncode>();
+ {
+ auto enc = stdex::make_unique<loco::PermutingEncoder<loco::Domain::Filter>>();
+
+ // In Tensorflow, conv2d filter is a 4-D tensor of following shape:
+ // [filter_height, filter_width, in_channels, out_channels] -> HWIO (HWCN)
+
+ enc->perm()->axis(loco::FilterAxis::Height) = 0;
+ enc->perm()->axis(loco::FilterAxis::Width) = 1;
+ enc->perm()->axis(loco::FilterAxis::Depth) = 2;
+ enc->perm()->axis(loco::FilterAxis::Count) = 3;
+
+ filter_enc->encoder(std::move(enc));
+ }
+
+ auto conv2d = graph->nodes()->create<loco::Conv2D>();
+ {
+ // let's convert attrs:
+ // Tensorflow attr : T, data_format, dilations, padding, strides
+ // to loco attr: not defined, TBD, TBD, TBD, stride
+
+ // tf strides -> loco stride
+ auto tf_strides = get_list_attr(node, "strides");
+ auto stride = conv2d->stride();
+
+ if (data_layout == DataLayout::NHWC)
+ {
+ stride->vertical(tf_strides.i(1));
+ stride->horizontal(tf_strides.i(2));
+ }
+ else if (data_layout == DataLayout::NCHW)
+ {
+ stride->vertical(tf_strides.i(2));
+ stride->horizontal(tf_strides.i(3));
+ }
+
+ // TODO Consider "SAME" padding
+ assert(get_string_attr(node, "padding") == "VALID");
+ }
+
+ auto feature_dec = graph->nodes()->create<loco::FeatureDecode>();
+ {
+ auto dec = stdex::make_unique<loco::PermutingDecoder<loco::Domain::Feature>>();
+
+ if (data_layout == DataLayout::NHWC)
+ {
+ dec->perm()->axis(loco::FeatureAxis::Count) = 0;
+ dec->perm()->axis(loco::FeatureAxis::Height) = 1;
+ dec->perm()->axis(loco::FeatureAxis::Width) = 2;
+ dec->perm()->axis(loco::FeatureAxis::Depth) = 3;
+ }
+ else if (data_layout == DataLayout::NCHW)
+ {
+ dec->perm()->axis(loco::FeatureAxis::Count) = 0;
+ dec->perm()->axis(loco::FeatureAxis::Depth) = 1;
+ dec->perm()->axis(loco::FeatureAxis::Height) = 2;
+ dec->perm()->axis(loco::FeatureAxis::Width) = 3;
+ }
+ else
+ throw std::runtime_error("Not supported data layout");
+
+ feature_dec->decoder(std::move(dec));
+ }
+
+ // link nodes
+ conv2d->ifm(feature_enc);
+ conv2d->ker(filter_enc);
+ feature_dec->input(conv2d);
+
+ // To set the input node of encode_node with conv2d_name
+ nodes->enroll(conv2d_name, feature_dec);
+
+ // Record ifm inputs to featureEncode_node
+ auto ifm_update = stdex::make_unique<IFMUpdate>(feature_enc, node.input(0));
+ auto ker_update = stdex::make_unique<KernelUpdate>(filter_enc, node.input(1));
+
+ updates->enroll(std::move(ifm_update));
+ updates->enroll(std::move(ker_update));
+}
+
+} // namespace tf
+} // namespace moco
+
+#include "GraphBuilderRegistry.h"
+
+REGISTER_OP_BUILDER(Conv2D, Conv2DGraphBuilder)
--- /dev/null
+/*
+ * 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 "TestHelper.h"
+
+#include <moco/tf/Frontend.h>
+
+#include <loco.h>
+#include <loco/IR/TensorShape.h>
+#include <loco/IR/FeatureShape.h>
+
+#include <gtest/gtest.h>
+
+#include <tensorflow/core/framework/graph.pb.h>
+
+#include <cstring>
+#include <memory>
+
+using namespace moco::tf::test;
+
+namespace
+{
+// clang-format off
+const char *conv2d_01_pbtxtdata = STRING_CONTENT(
+node {
+ name: "ifm"
+ op: "Const"
+ attr { key: "dtype" value { type: DT_FLOAT } }
+ attr {
+ key: "value"
+ value {
+ tensor {
+ dtype: DT_FLOAT
+ tensor_shape {
+ dim { size: 1 }
+ dim { size: 4 }
+ dim { size: 4 }
+ dim { size: 3 }
+ }
+ float_val: 1.1
+ }
+ }
+ }
+}
+node {
+ name: "ker"
+ op: "Const"
+ attr { key: "dtype" value { type: DT_FLOAT } }
+ attr {
+ key: "value"
+ value {
+ tensor {
+ dtype: DT_FLOAT
+ tensor_shape {
+ dim { size: 2 }
+ dim { size: 2 }
+ dim { size: 3 }
+ dim { size: 100 }
+ }
+ float_val: 1.1
+ }
+ }
+ }
+}
+node {
+ name: "conv2d"
+ op: "Conv2D"
+ input: "ifm"
+ input: "ker"
+ attr { key: "T" value { type: DT_FLOAT } }
+ attr { key: "data_format" value { s: "NHWC" } }
+ attr { key: "dilations" value { list { i: 1 i: 1 i: 1 i: 1 } } }
+ attr { key: "padding" value { s: "VALID" } }
+ attr { key: "strides" value { list { i: 1 i: 2 i: 3 i: 1 } } }
+ attr { key: "use_cudnn_on_gpu" value { b: false } }
+}
+);
+// clang-format on
+} // namespace
+
+TEST(TensorFlowFrontend, Conv2D_01)
+{
+ moco::tf::Frontend frontend;
+ moco::tf::ModelSignature signature;
+
+ imemstream mempb(conv2d_01_pbtxtdata, std::strlen(conv2d_01_pbtxtdata));
+
+ signature.add_output("conv2d");
+
+ std::unique_ptr<loco::Graph> graph =
+ frontend.load(signature, &mempb, moco::tf::Frontend::FileType::Text);
+
+ // test 1.
+ // loco node : ConstGen - FeatureEncode -- Conv2D - FeatureDecode - Push
+ // ConstGen - FilterEncode /
+ loco::Graph::NodeContext *loco_nodes = graph->nodes();
+
+ loco::Graph::InputContext *loco_inputs = graph->inputs();
+ ASSERT_EQ(loco_inputs->size(), 0);
+ ASSERT_EQ(loco_nodes->size(), 7);
+
+ int idx = 0;
+
+ loco::ConstGen *ifm = dynamic_cast<loco::ConstGen *>(loco_nodes->at(idx++));
+ loco::ConstGen *ker = dynamic_cast<loco::ConstGen *>(loco_nodes->at(idx++));
+
+ loco::FeatureEncode *ifm_enc = dynamic_cast<loco::FeatureEncode *>(loco_nodes->at(idx++));
+ loco::FilterEncode *ker_enc = dynamic_cast<loco::FilterEncode *>(loco_nodes->at(idx++));
+
+ loco::Conv2D *conv2d = dynamic_cast<loco::Conv2D *>(loco_nodes->at(idx++));
+ loco::FeatureDecode *dec = dynamic_cast<loco::FeatureDecode *>(loco_nodes->at(idx++));
+
+ loco::Push *push = dynamic_cast<loco::Push *>(loco_nodes->at(idx++));
+
+ ASSERT_NE(ifm, nullptr);
+ ASSERT_NE(ifm_enc, nullptr);
+ ASSERT_NE(ker, nullptr);
+ ASSERT_NE(ker_enc, nullptr);
+ ASSERT_NE(conv2d, nullptr);
+ ASSERT_NE(dec, nullptr);
+ ASSERT_NE(push, nullptr);
+
+ // check their connection is all OK
+ ASSERT_TRUE(ifm_enc->input() == ifm);
+ ASSERT_TRUE(ker_enc->input() == ker);
+ ASSERT_TRUE(conv2d->ifm() == ifm_enc);
+ ASSERT_TRUE(conv2d->ker() == ker_enc);
+ ASSERT_TRUE(dec->input() == conv2d);
+ ASSERT_TRUE(push->from() == dec);
+
+ // test 2.
+ // attrs inside Conv2D
+ {
+ // stride
+ ASSERT_EQ(conv2d->stride()->vertical(), 2);
+ ASSERT_EQ(conv2d->stride()->horizontal(), 3);
+
+ // TODO add padding test
+ }
+
+ // test 3.
+ // attrs inside FeatureEncoder
+ {
+ auto ifm_encoder = ifm_enc->encoder();
+ ASSERT_TRUE(ifm_encoder != nullptr);
+
+ loco::TensorShape tensor_shape;
+ tensor_shape.rank(4);
+ tensor_shape.dim(0) = 1; // COUNT
+ tensor_shape.dim(1) = 720; // HEIGHT
+ tensor_shape.dim(2) = 1280; // WIDTH
+ tensor_shape.dim(3) = 3; // DEPTH
+
+ // Get the feature shape corresponding to a given image
+ auto feature_shape = ifm_encoder->shape(tensor_shape);
+
+ ASSERT_EQ(feature_shape.count().value(), 1);
+ ASSERT_EQ(feature_shape.height().value(), 720);
+ ASSERT_EQ(feature_shape.width().value(), 1280);
+ ASSERT_EQ(feature_shape.depth().value(), 3);
+ }
+
+ // test 4.
+ // attrs inside FilterEncoder
+ {
+ auto ker_encoder = ker_enc->encoder();
+ ASSERT_TRUE(ker_encoder != nullptr);
+
+ loco::TensorShape tensor_shape;
+ tensor_shape.rank(4);
+ tensor_shape.dim(0) = 2; // H
+ tensor_shape.dim(1) = 4; // W
+ tensor_shape.dim(2) = 3; // I (C)
+ tensor_shape.dim(3) = 7; // O (N)
+
+ // Get the feature shape corresponding to a given image
+ auto ker_shape = ker_encoder->shape(tensor_shape);
+
+ ASSERT_EQ(ker_shape.height().value(), 2);
+ ASSERT_EQ(ker_shape.width().value(), 4);
+ ASSERT_EQ(ker_shape.depth().value(), 3);
+ ASSERT_EQ(ker_shape.count().value(), 7);
+ }
+
+ // test 5
+ // attrs inside FeatureDecoder
+ {
+ auto decoder = dec->decoder();
+ ASSERT_TRUE(decoder != nullptr);
+
+ loco::FeatureShape feature_shape;
+ feature_shape.count() = 1;
+ feature_shape.height() = 720;
+ feature_shape.width() = 1280;
+ feature_shape.depth() = 3;
+
+ // Get the tensor shape corresponding to a given image
+ auto tensor_shape = decoder->shape(feature_shape);
+
+ ASSERT_EQ(tensor_shape.rank(), 4);
+ ASSERT_EQ(tensor_shape.dim(0).value(), 1); // COUNT
+ ASSERT_EQ(tensor_shape.dim(1).value(), 720); // HEIGHT
+ ASSERT_EQ(tensor_shape.dim(2).value(), 1280); // WIDTH
+ ASSERT_EQ(tensor_shape.dim(3).value(), 3); // DEPTH
+ }
+}