[neurun/cpu] Add constant pad for float data type (#9159)
authorDilshodzhon Poshshoev/AI Tools Lab /SRR/Engineer/Samsung Electronics <d.poshshoev@samsung.com>
Mon, 25 Nov 2019 11:44:33 +0000 (14:44 +0300)
committer이한종/On-Device Lab(SR)/Engineer/삼성전자 <hanjoung.lee@samsung.com>
Mon, 25 Nov 2019 11:44:33 +0000 (20:44 +0900)
Add pad operation on cpu backend for float data type

Signed-off-by: Poshshoev Dilshodzhon <d.poshshoev@samsung.com>
compute/cker/include/cker/operation/Pad.h [new file with mode: 0644]
runtime/neurun/backend/cpu/KernelGenerator.cc
runtime/neurun/backend/cpu/KernelGenerator.h
runtime/neurun/backend/cpu/ShapeFixer.cc
runtime/neurun/backend/cpu/ShapeFixer.h
runtime/neurun/backend/cpu/kernel/PadLayer.cc [new file with mode: 0644]
runtime/neurun/backend/cpu/kernel/PadLayer.h [new file with mode: 0644]
tests/nnapi/nnapi_gtest.skip.armv7l-linux.cpu
tests/nnapi/nnapi_gtest.skip.x86_64-linux
tests/nnapi/specs/V1_1/pad_2D_HW_nnfw.mod.py

diff --git a/compute/cker/include/cker/operation/Pad.h b/compute/cker/include/cker/operation/Pad.h
new file mode 100644 (file)
index 0000000..6fab7c5
--- /dev/null
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __NNFW_CKER_PAD_H__
+#define __NNFW_CKER_PAD_H__
+
+#include "cker/Shape.h"
+#include "cker/Types.h"
+#include "cker/Utils.h"
+#include <stdexcept>
+#include <iostream>
+namespace nnfw
+{
+namespace cker
+{
+inline void Pad(const uint8_t *padding_data, int32_t pad_rank, const Shape &input_shape,
+                const float *input_data, const Shape &output_shape, float *output_data,
+                const float *constant_value_data)
+{
+  // Note, this is pad with mode=`CONSTANT`: it doesn't support `REFLECT` and `SYMMETRIC`
+  // TODO: come up with more subtle solution that uses subtensors like arm compute
+  // TODO: Check if it works for all layouts
+
+  using PaddingInfo = std::pair<int32_t, int32_t>;
+  /** List of padding information */
+  using PaddingList = std::vector<PaddingInfo>;
+
+  auto constant_value = constant_value_data ? *constant_value_data : 0;
+  assert(output_shape.DimensionsCount() == input_shape.DimensionsCount());
+
+  PaddingList padding_list(pad_rank);
+  for (int32_t n = 0; n < pad_rank; ++n)
+  {
+    const int32_t *from = reinterpret_cast<const int32_t *>(padding_data) + (n * 2);
+    padding_list[n] = {from[0], from[1]};
+  }
+  for (int32_t i = 0; i < pad_rank; ++i)
+  {
+    assert(output_shape.Dims(i) ==
+           input_shape.Dims(i) + padding_list[i].first + padding_list[i].second);
+  }
+  /* Use pad_rank since given input/output shapes are expanded to 4d before calling all cker
+     functions:
+     1. to prevent access violation in padding_list;
+     2. handling as 4d is slower than as 2d/3d.
+  */
+  switch (pad_rank)
+  {
+    case 0:
+    case 1:
+    {
+      const int32_t in_row_len = input_shape.Dims(0);
+      std::fill_n(output_data, padding_list[0].first, constant_value);
+      std::memcpy(output_data + padding_list[0].first, input_data, in_row_len * sizeof(float));
+      std::fill_n(output_data + padding_list[0].first + in_row_len, padding_list[0].second,
+                  constant_value);
+      break;
+    }
+    case 2: // HW
+    {
+      const int32_t in_row_len = input_shape.Dims(1);
+      const int32_t out_row_size = output_shape.Dims(1);
+
+      // prepend padding rows
+      std::fill_n(output_data, padding_list[0].first * out_row_size, constant_value);
+
+      const auto r_h_inp_lim = input_shape.Dims(0) + padding_list[0].first;
+      for (auto i = padding_list[0].first, j = 0; i < r_h_inp_lim; ++i, ++j)
+      {
+        auto out_offset = i * out_row_size;
+        const auto in_offset = j * in_row_len;
+
+        // prepend padding values
+        std::fill_n(output_data + out_offset, padding_list[1].first, constant_value);
+
+        out_offset += padding_list[1].first;
+
+        // copy a row of input data
+        memcpy(output_data + out_offset, input_data + in_offset, in_row_len * sizeof(float));
+
+        out_offset += in_row_len;
+
+        // append padding values
+        std::fill_n(output_data + out_offset, padding_list[1].second, constant_value);
+      }
+
+      // append padding rows
+      std::fill_n(output_data + r_h_inp_lim * out_row_size, padding_list[0].second * out_row_size,
+                  constant_value);
+      break;
+    }
+    case 3: // HWC
+    {
+      const int32_t in_row_len = input_shape.Dims(2);
+      const int32_t out_row_size = output_shape.Dims(2);
+      const auto plain_size = out_row_size * output_shape.Dims(1);
+
+      // prepend padding plains
+      std::fill_n(output_data, padding_list[0].first * plain_size, constant_value);
+
+      const auto r_h_inp_lim = input_shape.Dims(0) + padding_list[0].first;
+      for (auto i = padding_list[0].first, i_inp = 0; i < r_h_inp_lim; ++i, ++i_inp)
+      {
+        const auto out_w_offset = (i * output_shape.Dims(1) + 0) * output_shape.Dims(2);
+
+        // prepend padding rows
+        std::fill_n(output_data + out_w_offset, padding_list[1].first * out_row_size,
+                    constant_value);
+
+        const auto r_w_inp_lim = input_shape.Dims(1) + padding_list[1].first;
+        for (auto j = padding_list[1].first, j_inp = 0; j < r_w_inp_lim; ++j, ++j_inp)
+        {
+          auto out_offset = (i * output_shape.Dims(1) + j) * output_shape.Dims(2);
+          const auto in_offset = (i_inp * input_shape.Dims(1) + j_inp) * input_shape.Dims(2);
+
+          // prepend padding values
+          std::fill_n(output_data + out_offset, padding_list[2].first, constant_value);
+
+          out_offset += padding_list[2].first;
+
+          // copy a row of input data
+          memcpy(output_data + out_offset, input_data + in_offset, in_row_len * sizeof(float));
+
+          out_offset += in_row_len;
+
+          // append padding values
+          std::fill_n(output_data + out_offset, padding_list[2].second, constant_value);
+        }
+
+        // append padding rows
+        std::fill_n(output_data + out_w_offset + r_w_inp_lim * out_row_size,
+                    padding_list[1].second * out_row_size, constant_value);
+      }
+
+      // append padding plains
+      std::fill_n(output_data + r_h_inp_lim * plain_size, padding_list[0].second * plain_size,
+                  constant_value);
+      break;
+    }
+    case 4:
+    {
+      auto get_offset = [](const Shape &shape, int32_t n, int32_t h, int32_t w) -> int32_t {
+        return ((n * shape.Dims(1) + h) * shape.Dims(2) + w) * shape.Dims(3);
+      };
+      const int32_t in_row_len = input_shape.Dims(3);
+      const int32_t out_row_size = output_shape.Dims(3);
+      const auto plain_size = out_row_size * output_shape.Dims(2);
+      const auto parallelepiped_size = plain_size * output_shape.Dims(1);
+
+      // prepend padding parallelepipeds
+      std::fill_n(output_data, padding_list[0].first * parallelepiped_size, constant_value);
+
+      const auto r_b_inp_lim = input_shape.Dims(0) + padding_list[0].first;
+      for (auto i = padding_list[0].first, i_inp = 0; i < r_b_inp_lim; ++i, ++i_inp)
+      {
+        const auto out_h_offset = get_offset(output_shape, i, 0, 0);
+        // prepend padding plains
+        std::fill_n(output_data + out_h_offset, padding_list[1].first * plain_size, constant_value);
+
+        const auto r_h_inp_lim = input_shape.Dims(1) + padding_list[1].first;
+        for (auto j = padding_list[1].first, j_inp = 0; j < r_h_inp_lim; ++j, ++j_inp)
+        {
+          const auto out_w_offset = get_offset(output_shape, i, j, 0);
+
+          // prepend padding rows
+          std::fill_n(output_data + out_w_offset, padding_list[2].first * out_row_size,
+                      constant_value);
+
+          const auto r_w_inp_lim = input_shape.Dims(2) + padding_list[2].first;
+          for (auto k = padding_list[2].first, k_inp = 0; k < r_w_inp_lim; ++k, ++k_inp)
+          {
+            auto out_c_offset = get_offset(output_shape, i, j, k);
+            const auto in_offset = get_offset(input_shape, i_inp, j_inp, k_inp);
+
+            // prepend padding values
+            std::fill_n(output_data + out_c_offset, padding_list[3].first, constant_value);
+
+            out_c_offset += padding_list[3].first;
+
+            // copy a row of input data
+            memcpy(output_data + out_c_offset, input_data + in_offset, in_row_len * sizeof(float));
+
+            out_c_offset += in_row_len;
+
+            // append padding values
+            std::fill_n(output_data + out_c_offset, padding_list[3].second, constant_value);
+          }
+
+          // append padding rows
+          std::fill_n(output_data + out_w_offset + r_w_inp_lim * out_row_size,
+                      padding_list[2].second * out_row_size, constant_value);
+        }
+
+        // append padding plains
+        std::fill_n(output_data + out_h_offset + r_h_inp_lim * plain_size,
+                    padding_list[1].second * plain_size, constant_value);
+      }
+      // append padding parallelepipeds
+      std::fill_n(output_data + r_b_inp_lim * parallelepiped_size,
+                  padding_list[0].second * parallelepiped_size, constant_value);
+      break;
+    }
+    default:
+      throw std::runtime_error("Padding for rank > 4 NYI");
+      break;
+  }
+}
+} // namespace cker
+} // namespace nnfw
+
+#endif // __NNFW_CKER_PAD_H__
index 53de721..952c9b9 100644 (file)
@@ -34,6 +34,7 @@
 #include "kernel/SubLayer.h"
 #include "kernel/GatherLayer.h"
 #include "kernel/LogisticLayer.h"
+#include "kernel/PadLayer.h"
 
 #include <backend/Backend.h>
 #include <backend/IConfig.h>
@@ -566,6 +567,30 @@ void KernelGenerator::visit(const model::operation::Logistic &node)
   _execution_builder->append(std::move(fn));
 }
 
+void KernelGenerator::visit(const model::operation::Pad &node)
+{
+  const auto input_index{node.getInputs().at(model::operation::Pad::Input::INPUT)};
+  const auto pad_index{node.getInputs().at(model::operation::Pad::Input::PAD)};
+  const auto output_index{node.getOutputs().at(0)};
+  assert(_ctx.at(pad_index).isConstant());
+
+  auto input = _tensor_builder->at(input_index).get();
+  auto output = _tensor_builder->at(output_index).get();
+  auto pad_rank = _ctx.at(pad_index).shape().dim(0);
+  auto pad_base = _ctx.at(pad_index).data().base();
+  const auto ofm_backend_descr = ::neurun::backend::cpu::kernel::getTensorDescriptor(
+      _ctx.at(output_index), _current_subg_layout);
+  const auto ifm_backend_descr = ::neurun::backend::cpu::kernel::getTensorDescriptor(
+      _ctx.at(input_index), _current_subg_layout);
+
+  auto fn = nnfw::cpp14::make_unique<::neurun::backend::cpu::kernel::PadLayer>();
+
+  fn->configure(input->buffer(), ifm_backend_descr, output->buffer(), ofm_backend_descr, pad_base,
+                pad_rank);
+
+  _execution_builder->append(std::move(fn));
+}
+
 } // namespace cpu
 } // namespace backend
 } // namespace neurun
index 2753aee..880cc76 100644 (file)
@@ -56,6 +56,7 @@ public:
   void visit(const model::operation::Gather &) override;
   void visit(const model::operation::Custom &node) override;
   void visit(const model::operation::Logistic &) override;
+  void visit(const model::operation::Pad &);
 
 private:
   const neurun::model::Operands &_ctx;
index 9f483ce..a125ed6 100644 (file)
@@ -68,8 +68,6 @@ void ShapeFixer::visit(const model::operation::Concat &) { /* DO NOTHING */}
 
 void ShapeFixer::visit(const model::operation::FullyConnected &) { /* DO NOTHING */}
 
-void ShapeFixer::visit(const model::operation::Mul &) { throw std::runtime_error("NYI"); }
-
 void ShapeFixer::visit(const model::operation::Reshape &) { /* DO NOTHING */}
 
 void ShapeFixer::visit(const model::operation::Squeeze &) { /* DO NOTHING */}
@@ -86,7 +84,7 @@ void ShapeFixer::visit(const model::operation::Add &node)
   // Quantization : not supported
   if (_ctx.at(lhs_index).typeInfo().type() == model::DataType::QUANT8_ASYMM)
   {
-    throw std::runtime_error{"NYI"};
+    throw std::runtime_error{"ShapeFixer: NYI for quantized Add"};
   }
   // Broadcast
   if (!(_ctx.at(lhs_index).shape() == _ctx.at(rhs_index).shape()))
@@ -108,7 +106,7 @@ void ShapeFixer::visit(const model::operation::Sub &node)
   // Quantization : not supported
   if (_ctx.at(lhs_index).typeInfo().type() == model::DataType::QUANT8_ASYMM)
   {
-    throw std::runtime_error{"NYI"};
+    throw std::runtime_error{"ShapeFixer: NYI for quantized Sub"};
   }
   // Broadcast
   if (!(_ctx.at(lhs_index).shape() == _ctx.at(rhs_index).shape()))
@@ -124,6 +122,18 @@ void ShapeFixer::visit(const model::operation::Custom &) { /* DO NOTHING */}
 
 void ShapeFixer::visit(const model::operation::Logistic &) { /* DO NOTHING */}
 
+void ShapeFixer::visit(const model::operation::Pad &node)
+{
+  // TODO: empty this method when quantization is supported
+  const auto lhs_index{node.getInputs().at(model::operation::Sub::Input::LHS)};
+
+  // Quantization : not supported
+  if (_ctx.at(lhs_index).typeInfo().type() == model::DataType::QUANT8_ASYMM)
+  {
+    throw std::runtime_error{"ShapeFixer: NYI for quantized Pad"};
+  }
+}
+
 } // namespace cpu
 } // namespace backend
 } // namespace neurun
index d52b76a..81c8415 100644 (file)
@@ -44,7 +44,6 @@ public:
   void visit(const model::operation::AvgPool2D &) override;
   void visit(const model::operation::Concat &) override;
   void visit(const model::operation::FullyConnected &) override;
-  void visit(const model::operation::Mul &) override;
   void visit(const model::operation::Reshape &) override;
   void visit(const model::operation::Squeeze &) override;
   void visit(const model::operation::Softmax &) override;
@@ -54,6 +53,7 @@ public:
   void visit(const model::operation::Permute &) override;
   void visit(const model::operation::Custom &) override;
   void visit(const model::operation::Logistic &) override;
+  void visit(const model::operation::Pad &);
 
 private:
   const neurun::model::Operands &_ctx;
diff --git a/runtime/neurun/backend/cpu/kernel/PadLayer.cc b/runtime/neurun/backend/cpu/kernel/PadLayer.cc
new file mode 100644 (file)
index 0000000..aeee736
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * 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 "PadLayer.h"
+
+#include "OperationUtils.h"
+
+#include <cker/operation/Pad.h>
+
+namespace neurun
+{
+namespace backend
+{
+namespace cpu
+{
+namespace kernel
+{
+
+PadLayer::PadLayer()
+    : _inputData(), _outputData(), _inputDescr(), _outputDescr(), _padData(), _padRank(),
+      _constantValueData(), _inputType(OperandType::FLOAT32)
+{
+  // DO NOTHING
+}
+
+void PadLayer::padFloat32()
+{
+  nnfw::cker::Pad(_padData, _padRank, convertTensorDescriptorToCkerShape(_inputDescr), _inputData.f,
+                  convertTensorDescriptorToCkerShape(_outputDescr), _outputData.f,
+                  _constantValueData.f);
+}
+void PadLayer::padQuant8() { throw std::runtime_error("Quantized Pad isn't supported NYI"); }
+
+void PadLayer::configure(uint8_t *inputData, const TensorDescriptor inputDescr, uint8_t *outputData,
+                         const TensorDescriptor outputDescr, const uint8_t *padData,
+                         int32_t padRank, uint8_t *constantValueData)
+{
+  _inputData.u8 = inputData;
+  _inputDescr = inputDescr;
+  _inputType = inputDescr.type;
+  _outputData.u8 = outputData;
+  _outputDescr = outputDescr;
+  _padData = padData;
+  _padRank = padRank;
+  _constantValueData.u8 = constantValueData;
+}
+
+void PadLayer::run()
+{
+  if (_inputType == OperandType::FLOAT32)
+  {
+    padFloat32();
+  }
+  else if (_inputType == OperandType::QUANT8_ASYMM)
+  {
+    padQuant8();
+  }
+}
+
+} // namespace kernel
+} // namespace cpu
+} // namespace backend
+} // namespace neurun
diff --git a/runtime/neurun/backend/cpu/kernel/PadLayer.h b/runtime/neurun/backend/cpu/kernel/PadLayer.h
new file mode 100644 (file)
index 0000000..f3d4119
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __NEURUN_BACKEND_CPU_KERNEL_PADLAYER_H__
+#define __NEURUN_BACKEND_CPU_KERNEL_PADLAYER_H__
+
+#include <exec/IFunction.h>
+
+#include "OperationUtils.h"
+
+namespace neurun
+{
+namespace backend
+{
+namespace cpu
+{
+namespace kernel
+{
+
+// Note, this is pad with mode=`CONSTANT`: it doesn't support `REFLECT` and `SYMMETRIC`
+class PadLayer : public ::neurun::exec::IFunction
+{
+public:
+  PadLayer();
+
+public:
+  void padFloat32();
+
+  void padQuant8();
+
+  void configure(uint8_t *inputData, const TensorDescriptor inputDescr, uint8_t *outputData,
+                 const TensorDescriptor outputDescr, const uint8_t *padData, int32_t padRank,
+                 uint8_t *constantValueData = nullptr);
+
+  void run();
+  void runSync()
+  {
+    // this abstract method is used just for profiling and called for
+    // backend::acl_common::AclFunction
+    run();
+  }
+
+private:
+  DataPtr _inputData;
+  DataPtr _outputData;
+
+  TensorDescriptor _inputDescr;
+  TensorDescriptor _outputDescr;
+
+  const uint8_t *_padData;
+  int32_t _padRank;
+  DataPtr _constantValueData;
+
+  OperandType _inputType;
+};
+
+} // namespace kernel
+} // namespace cpu
+} // namespace backend
+} // namespace neurun
+
+#endif // __NEURUN_BACKEND_CPU_KERNEL_PADLAYER_H__
index ac5f130..f4d1139 100644 (file)
@@ -50,7 +50,7 @@ GeneratedTests.resize_bilinear*
 GeneratedTests.rnn*
 GeneratedTests.rsqrt*
 GeneratedTests.mean*
-GeneratedTests.pad*
+GeneratedTests.pad_quant8_nnfw
 GeneratedTests.space_to_depth*
 GeneratedTests.sqrt_ex*
 GeneratedTests.squared_difference_ex*
index d990298..301ad9c 100644 (file)
@@ -51,7 +51,7 @@ GeneratedTests.resize_bilinear*
 GeneratedTests.rnn*
 GeneratedTests.rsqrt*
 GeneratedTests.mean*
-GeneratedTests.pad*
+GeneratedTests.pad_quant8_nnfw
 GeneratedTests.space_to_depth*
 GeneratedTests.sqrt_ex*
 GeneratedTests.squared_difference_ex*
index d21a4f1..8c8e64a 100644 (file)
@@ -1,19 +1,20 @@
 # model
 model = Model()
-i1 = Input("op1", "TENSOR_FLOAT32", "{2, 2}")
+i1 = Input("op1", "TENSOR_FLOAT32", "{3, 2}")
 i2 = Parameter("op2", "TENSOR_INT32", "{2, 2}", [1, 2, 2, 1])
-i3 = Output("op3", "TENSOR_FLOAT32", "{5, 5}")
+i3 = Output("op3", "TENSOR_FLOAT32", "{6, 5}")
 model = model.Operation("PAD", i1, i2).To(i3)
 
 # Example 1. Input in operand 0,
 input0 = {i1: # input 0
-          [1.0, 2.0,
-           3.0, 4.0,]}
+          [1.0, 2.0, 3.0,
+           4.0, 5.0, 6.0,]}
 
 output0 = {i3: # output 0
            [0.0, 0.0, 0.0, 0.0, 0.0,
            0.0, 0.0, 1.0, 2.0, 0.0,
             0.0, 0.0, 3.0, 4.0, 0.0,
+            0.0, 0.0, 5.0, 6.0, 0.0,
             0.0, 0.0, 0.0, 0.0, 0.0,
            0.0, 0.0, 0.0, 0.0, 0.0]}