[nnc] Implement Gather operation in interpreter backend (#2681)
authorСергей Баранников/AI Tools Lab /SRR/Engineer/삼성전자 <s.barannikov@samsung.com>
Fri, 14 Dec 2018 13:45:41 +0000 (16:45 +0300)
committerEfimov Alexander/AI Tools Lab/./Samsung Electronics <a.efimov@samsung.com>
Fri, 14 Dec 2018 13:45:41 +0000 (16:45 +0300)
Add implementation of Gather operation to interpreter backend.

Signed-off-by: Sergei Barannikov <s.barannikov@samsung.com>
contrib/nnc/core/modelIR/TensorVariant.cpp
contrib/nnc/include/core/modelIR/Tensor.h
contrib/nnc/include/core/modelIR/TensorVariant.h
contrib/nnc/passes/caffe_frontend/caffe_op_creator.cpp
contrib/nnc/passes/interpreter/Interpreter.cpp
contrib/nnc/passes/interpreter/ops/Bias.h
contrib/nnc/passes/interpreter/ops/Gather.cpp [new file with mode: 0644]
contrib/nnc/passes/interpreter/ops/Gather.h [new file with mode: 0644]
contrib/nnc/passes/interpreter/ops/Scale.cpp
contrib/nnc/passes/soft_backend/code_snippets/cpp_gather.def
contrib/nnc/passes/soft_backend/code_snippets/cpp_operations.def

index 45f3696..94c9c18 100644 (file)
@@ -56,11 +56,15 @@ TensorVariant::TensorVariant(const TensorVariant& t_old,
   }
 }
 
-char *TensorVariant::at(const Index &idx) const
-{
+char* TensorVariant::at(const Index& idx) const {
   return _data.get() + getOffset(idx) * _elementSize;
 }
 
+char* TensorVariant::atOffset(int32_t offset) const {
+  assert(offset >= 0 && offset < getShape().numElements());
+  return _data.get() + offset * _elementSize;
+}
+
 size_t TensorVariant::getOffset(const Index &idx) const {
   assert(idx.rank() == getShape().rank());
   std::size_t offset = 0;
index 6098911..68a3b75 100644 (file)
@@ -35,12 +35,20 @@ class Tensor final{
   explicit Tensor(const TensorVariant &t) : _proxy(t), _shape(t.getShape()) {
   }
 
-  T at(const Index &id) const {
-    return *reinterpret_cast<T *>(this->_proxy.at(id));
+  T at(const Indexid) const {
+    return *reinterpret_cast<T*>(this->_proxy.at(id));
   }
 
-  T &at(const Index &id) {
-    return *reinterpret_cast<T *>(this->_proxy.at(id));
+  T& at(const Index& id) {
+    return *reinterpret_cast<T*>(this->_proxy.at(id));
+  }
+
+  T atOffset(int32_t offset) const {
+    return *reinterpret_cast<T*>(this->_proxy.atOffset(offset));
+  }
+
+  T& atOffset(int32_t offset) {
+    return *reinterpret_cast<T*>(this->_proxy.atOffset(offset));
   }
 
   ExternalRegion<T> getRegion(const Index& idx) {
index 07495d0..0ccd826 100644 (file)
@@ -50,7 +50,8 @@ public:
   }
   virtual ~TensorVariant() = default;
 
-  char *at(const Index &idx) const;
+  char* at(const Index& idx) const;
+  char* atOffset(int32_t offset) const;
   size_t getOffset(const Index &idx) const;
 
   virtual const Shape &getShape() const { return _shape; }
index 6657ec9..35e7208 100644 (file)
@@ -548,7 +548,6 @@ CaffeOpCreator::convertEmbed(const caffe::LayerParameter& layer,
                              const std::vector<mir::IODescriptor>& inputs) {
   const auto& params = layer.embed_param();
   auto data = createOp<ops::ConstantOp>(layer.name() + ".weights", *convertBlob(layer.blobs(0)));
-  // FIXME Indices in Caffe have floating type, while in ModelIR they are integral.
   auto result = createOp<ops::GatherOp>(layer.name(), data->getOutput(0), inputs[0], 0);
 
   // Add the bias, if any.
index 6811411..8db2fb6 100644 (file)
 
 #include "passes/interpreter/Interpreter.h"
 
-#include "core/modelIR/operations/FullyConnectedOp.h"
-#include "core/modelIR/operations/GemmOp.h"
-#include "core/modelIR/operations/SoftmaxOp.h"
+#include "core/modelIR/operations/BatchNormOp.h"
+#include "core/modelIR/operations/BiasAddOp.h"
 #include "core/modelIR/operations/CappedReluOp.h"
-#include "core/modelIR/operations/DepthwiseConv2DOp.h"
+#include "core/modelIR/operations/ConcatOp.h"
 #include "core/modelIR/operations/ConstantOp.h"
 #include "core/modelIR/operations/Conv2DOp.h"
 #include "core/modelIR/operations/Deconv2DOp.h"
+#include "core/modelIR/operations/DepthwiseConv2DOp.h"
+#include "core/modelIR/operations/DropoutOp.h"
+#include "core/modelIR/operations/ElementwiseOp.h"
+#include "core/modelIR/operations/EluOp.h"
+#include "core/modelIR/operations/FullyConnectedOp.h"
+#include "core/modelIR/operations/GatherOp.h"
+#include "core/modelIR/operations/GemmOp.h"
+#include "core/modelIR/operations/PadOp.h"
 #include "core/modelIR/operations/PoolOp.h"
-#include "core/modelIR/operations/VariableOp.h"
-#include "core/modelIR/operations/ReluOp.h"
 #include "core/modelIR/operations/ReduceFOp.h"
+#include "core/modelIR/operations/ReluOp.h"
 #include "core/modelIR/operations/ResizeOp.h"
-#include "core/modelIR/operations/EluOp.h"
-#include "core/modelIR/operations/ConcatOp.h"
-#include "core/modelIR/operations/BiasAddOp.h"
-#include "core/modelIR/operations/BatchNormOp.h"
 #include "core/modelIR/operations/ScaleOp.h"
-#include "core/modelIR/operations/DropoutOp.h"
-#include "core/modelIR/operations/TanhOp.h"
-#include "core/modelIR/operations/ElementwiseOp.h"
+#include "core/modelIR/operations/SoftmaxOp.h"
 #include "core/modelIR/operations/SqueezeOp.h"
-#include "core/modelIR/operations/PadOp.h"
+#include "core/modelIR/operations/TanhOp.h"
 #include "core/modelIR/operations/TransposeOp.h"
+#include "core/modelIR/operations/VariableOp.h"
 
+#include "ops/BatchNorm.h"
 #include "ops/Bias.h"
 #include "ops/Concat.h"
 #include "ops/conv_2D.h"
 #include "ops/DeConv2D.h"
 #include "ops/Depthwise_conv_2D.h"
+#include "ops/Dropout.h"
 #include "ops/FullyConnected.h"
+#include "ops/Gather.h"
 #include "ops/Gemm.h"
+#include "ops/Pad.h"
 #include "ops/Pool.h"
 #include "ops/Reshape.h"
-#include "ops/Softmax.h"
 #include "ops/Scale.h"
+#include "ops/Softmax.h"
 #include "ops/Transpose.h"
-#include "ops/Dropout.h"
-#include "ops/BatchNorm.h"
-#include "ops/Pad.h"
 
 namespace nnc {
 
@@ -368,7 +370,12 @@ void NNInterpreter::visit(ops::TransposeOp& op) {
 }
 
 void NNInterpreter::visit(ops::GatherOp& op) {
-  assert(false && "Not yet imlemented");
+  mapByName(&op);
+  auto data_descr = op.getPrevNodes()[0];
+  auto indices_descr = op.getPrevNodes()[1];
+  const auto& data = var(data_descr.op->getId())[data_descr.index];
+  const auto& indices = var(indices_descr.op->getId())[indices_descr.index];
+  var(op.getId()) = Gather(data, indices, op)();
 }
 
 } // namespace nnc
index 46bbc4e..408c97e 100644 (file)
@@ -37,7 +37,7 @@ public:
   std::vector<mir::TensorVariant> operator()() override
   {
     return Fill<float>(_outputShape, [this](const mir::Index &idx) {
-      return _input.at(idx) + _weights.at({idx.at(idx.rank() - 1)});
+      return _input.at(idx) + _weights.atOffset({idx.at(idx.rank() - 1)});
     })();
   }
 
diff --git a/contrib/nnc/passes/interpreter/ops/Gather.cpp b/contrib/nnc/passes/interpreter/ops/Gather.cpp
new file mode 100644 (file)
index 0000000..3d3cda8
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2018 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 "Gather.h"
+
+namespace nnc {
+
+using namespace mir;
+
+Gather::Gather(const TensorVariant& data,
+               const TensorVariant& indices,
+               const ops::GatherOp& op)
+    : _data(data), _indices(indices), _op(op) {}
+
+std::vector<TensorVariant> Gather::operator()() {
+  const auto& data_shape = _data.getShape();
+  const auto& indices_shape = _indices.getShape();
+  const auto& output_shape = _op.getOutputShape(0);
+  auto res = allocate_tensor(_op.getOutputShape(0));
+  Tensor<float> data(_data);
+  Tensor<float> indices(_indices);
+  Tensor<float> output(res);
+
+  int32_t axis = _op.getAxis();
+  if (axis < 0)
+    axis += data_shape.rank();
+  assert(axis >= 0 && axis < data_shape.rank());
+  int32_t axis_size = data_shape.dim(axis);
+  int32_t num_indices = indices_shape.numElements();
+
+  int32_t outer_size = 1;
+  for (int32_t i = 0; i < axis; ++i)
+    outer_size *= data_shape.dim(i);
+
+  int32_t inner_size = 1;
+  for (int32_t i = axis + 1; i < data_shape.rank(); ++i)
+    inner_size *= data_shape.dim(i);
+
+  for (int32_t outer = 0; outer < outer_size; ++outer) {
+    for (int32_t i = 0; i < num_indices; ++i) {
+      auto index = static_cast<int32_t>(indices.atOffset(i));
+      assert(index >= 0 && index < axis_size);
+      for (int32_t inner = 0; inner < inner_size; inner++) {
+        output.atOffset((outer * num_indices + i) * inner_size + inner) =
+            data.atOffset((outer * axis_size + index) * inner_size + inner);
+      }
+    }
+  }
+
+  return {res};
+}
+
+} // namespace nnc
diff --git a/contrib/nnc/passes/interpreter/ops/Gather.h b/contrib/nnc/passes/interpreter/ops/Gather.h
new file mode 100644 (file)
index 0000000..9096135
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018 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 _NNC_CORE_BACKEND_INTERPRETER_GATHER_
+#define _NNC_CORE_BACKEND_INTERPRETER_GATHER_
+
+#include "OperationImpl.h"
+#include "core/modelIR/operations/GatherOp.h"
+
+namespace nnc {
+
+class Gather : public OperationImpl<float> {
+public:
+  Gather(const mir::TensorVariant& data,
+         const mir::TensorVariant& indices,
+         const mir::ops::GatherOp& op);
+
+  std::vector<mir::TensorVariant> operator()() override;
+
+private:
+  const mir::Tensor<float> _data;
+  const mir::Tensor<float> _indices;
+  const mir::ops::GatherOp& _op;
+};
+
+}
+
+#endif //_NNC_CORE_BACKEND_INTERPRETER_GATHER_
index 32fe358..b2fdeb6 100644 (file)
@@ -26,7 +26,7 @@ std::vector<mir::TensorVariant> Scale::operator()()
   //For now handles only most common case with scale applied by last dimension
   mir::Tensor<float> weightsAccessor(_op.getWeights());
   return Fill<float>(_input.getShape(), [this, weightsAccessor](const mir::Index &idx) {
-    return _input.at(idx) * weightsAccessor.at({idx.at(idx.rank() - 1)});
+    return _input.at(idx) * weightsAccessor.atOffset({idx.at(idx.rank() - 1)});
   })();
 }
 
index 2a8c86f..bb4a55f 100644 (file)
@@ -36,11 +36,12 @@ inline void Gather(const GatherParams& op_params,
 
   for (int outer = 0; outer < outer_size; ++outer) {
     for (int i = 0; i < coords_count; ++i) {
-      TFLITE_DCHECK_GE(coords_data[i], 0);
-      TFLITE_DCHECK_LT(coords_data[i], axis_size);
+      int coord = static_cast<int>(coords_data[i]);
+      TFLITE_DCHECK_GE(coord, 0);
+      TFLITE_DCHECK_LT(coord, axis_size);
       std::memcpy(
           output_data + (outer * coords_count + i) * inner_size,
-          input_data + (outer * axis_size + coords_data[i]) * inner_size,
+          input_data + (outer * axis_size + coord) * inner_size,
           sizeof(T) * inner_size);
     }
   }
index c705d9f..2766ac5 100644 (file)
@@ -618,6 +618,6 @@ void gather(Tensor &out, const char *params, const Tensor &data, const Tensor &i
   // pointer to float.
   Gather(gather_params,
          shapeToRuntimeShape(data.getShape()), data.getData(),
-         shapeToRuntimeShape(indices.getShape()), reinterpret_cast<const int32*>(indices.getData()),
+         shapeToRuntimeShape(indices.getShape()), indices.getData(),
          shapeToRuntimeShape(out.getShape()), out.getData());
 }