[Pure ACL NN Runtime] Add Source/Sink (#615)
author박종현/동작제어Lab(SR)/Senior Engineer/삼성전자 <jh1302.park@samsung.com>
Thu, 12 Apr 2018 04:55:06 +0000 (13:55 +0900)
committer김정현/동작제어Lab(SR)/Senior Engineer/삼성전자 <jh0822.kim@samsung.com>
Thu, 12 Apr 2018 04:55:06 +0000 (13:55 +0900)
This commit introduces the concept of Source and Sink which sets inputs
before execution, and gets outputs after execution.

Signed-off-by: Jonghyun Park <jh1302.park@samsung.com>
tools/nnapi_bindings/bindings/pure_arm_compute/src/execution.cc
tools/nnapi_bindings/bindings/pure_arm_compute/src/execution.h
tools/nnapi_bindings/bindings/pure_arm_compute/src/internal/arm_compute.cc
tools/nnapi_bindings/bindings/pure_arm_compute/src/internal/arm_compute.h
tools/nnapi_bindings/bindings/pure_arm_compute/src/internal/arm_compute/feature/View.h [new file with mode: 0644]
tools/nnapi_bindings/bindings/pure_arm_compute/src/internal/nnapi/feature/Reader.h [new file with mode: 0644]
tools/nnapi_bindings/bindings/pure_arm_compute/src/internal/nnapi/feature/Utils.h [new file with mode: 0644]
tools/nnapi_bindings/bindings/pure_arm_compute/src/internal/nnapi/feature/View.h [new file with mode: 0644]

index 9858d2e..4bb12ef 100644 (file)
@@ -3,8 +3,80 @@
 #include "compilation.h"
 #include "execution.h"
 
+#include "internal/nnapi/feature/Reader.h"
+#include "internal/nnapi/feature/View.h"
+
+#include "internal/arm_compute/feature/View.h"
+
+#include "util/feature/IndexIterator.h"
+
 #include <cassert>
 
+//
+// FeatureSource
+//
+class FeatureSource final : public Source
+{
+public:
+  FeatureSource(const nnfw::util::feature::Shape &shape, const uint8_t *base, const size_t size)
+    : _shape{shape}, _base{base}, _size{size}
+  {
+    // DO NOTHING
+  }
+
+public:
+  void push(::arm_compute::ITensor &tensor) const override
+  {
+    const ::internal::nnapi::feature::Reader<float> from{_shape, _base, _size};
+    ::internal::arm_compute::feature::View<float> into{&tensor};
+
+    ::nnfw::util::feature::iterate(_shape) << [&] (uint32_t ch, uint32_t row, uint32_t col)
+    {
+      const auto value = from.at(ch, row, col);
+      into.at(ch, row, col) = value;
+    };
+  }
+
+private:
+  const nnfw::util::feature::Shape _shape;
+  const uint8_t * const _base;
+  const size_t _size;
+};
+
+//
+// FeatureSink
+//
+class FeatureSink final : public Sink
+{
+public:
+  FeatureSink(const nnfw::util::feature::Shape &shape, uint8_t *base, const size_t size)
+    : _shape{shape}, _base{base}, _size{size}
+  {
+    // DO NOTHING
+  }
+
+public:
+  void pull(::arm_compute::ITensor &tensor) const override
+  {
+    const ::internal::arm_compute::feature::View<float> from{&tensor};
+    ::internal::nnapi::feature::View<float> into{_shape, _base, _size};
+
+    ::nnfw::util::feature::iterate(_shape) << [&] (uint32_t ch, uint32_t row, uint32_t col)
+    {
+      const auto value = from.at(ch, row, col);
+      into.at(ch, row, col) = value;
+    };
+  }
+
+private:
+  const nnfw::util::feature::Shape _shape;
+  uint8_t * const _base;
+  const size_t _size;
+};
+
+//
+// NNAPI Implementation
+//
 ResultCode
 ANeuralNetworksExecution_create(ANeuralNetworksCompilation* compilation, ANeuralNetworksExecution** execution)
 {
@@ -22,6 +94,18 @@ ANeuralNetworksExecution_setInput(ANeuralNetworksExecution* execution,
                                  int32_t index, const ANeuralNetworksOperandType* type,
                                  const void* buffer, size_t length)
 {
+  const auto &operands = execution->plan().model().operands();
+
+  // TODO Check type conflicts
+
+  // NOTE The current implemenation assumes that every input is a feature map.
+  // TODO Remove this assumption
+  const auto operand_index = execution->plan().model().inputs.at(index);
+  const auto &operand_shape = operands.at(operand_index).shape().asFeature();
+
+  execution->source<FeatureSource>(index,
+                                   operand_shape, reinterpret_cast<const uint8_t *>(buffer), length);
+
   return ANEURALNETWORKS_NO_ERROR;
 }
 
@@ -30,6 +114,18 @@ ANeuralNetworksExecution_setOutput(ANeuralNetworksExecution* execution,
                                    int32_t index, const ANeuralNetworksOperandType* type,
                                    void* buffer, size_t length)
 {
+  const auto &operands = execution->plan().model().operands();
+
+  // TODO Check type conflicts
+
+  // NOTE The current implemenation assumes that every output is a feature map.
+  // TODO Remove this assumption
+  const auto operand_index = execution->plan().model().outputs.at(index);
+  const auto &operand_shape = operands.at(operand_index).shape().asFeature();
+
+  execution->sink<FeatureSink>(index,
+                               operand_shape, reinterpret_cast<uint8_t *>(buffer), length);
+
   return ANEURALNETWORKS_NO_ERROR;
 }
 
@@ -38,6 +134,20 @@ ANeuralNetworksExecution_startCompute(ANeuralNetworksExecution* execution, ANeur
 {
   assert(execution != nullptr);
 
+  const auto &plan = execution->plan();
+  const auto &model = plan.model();
+
+  // Set input(s)
+  for (uint32_t n = 0; n < model.inputs.size(); ++n)
+  {
+    auto setter = [&] (::arm_compute::ITensor &tensor)
+    {
+      execution->source(n).push(tensor);
+    };
+
+    plan.operands().at(model.inputs.at(n)).access(setter);
+  }
+
   const auto &operations = execution->plan().operations();
 
   for (uint32_t n = 0; n < operations.size(); ++n)
@@ -45,6 +155,17 @@ ANeuralNetworksExecution_startCompute(ANeuralNetworksExecution* execution, ANeur
     operations.at(n).run();
   }
 
+  // Get output(s)
+  for (uint32_t n = 0; n < model.outputs.size(); ++n)
+  {
+    auto getter = [&] (::arm_compute::ITensor &tensor)
+    {
+      execution->sink(n).pull(tensor);
+    };
+
+    plan.operands().at(model.outputs.at(n)).access(getter);
+  }
+
   return ANEURALNETWORKS_NO_ERROR;
 }
 
index 7ac80ca..f163b8e 100644 (file)
@@ -3,13 +3,28 @@
 
 #include "internal/arm_compute.h"
 
+struct Source
+{
+  virtual ~Source() = default;
+
+  virtual void push(::arm_compute::ITensor &tensor) const = 0;
+};
+
+struct Sink
+{
+  virtual ~Sink() = default;
+
+  virtual void pull(::arm_compute::ITensor &tensor) const = 0;
+};
+
 struct ANeuralNetworksExecution
 {
 public:
   ANeuralNetworksExecution(const std::shared_ptr<const internal::arm_compute::Plan> &plan)
     : _plan{plan}
   {
-    // DO NOTHING
+    _sources.resize(_plan->model().inputs.size());
+    _sinks.resize(_plan->model().outputs.size());
   }
 
 public:
@@ -17,6 +32,32 @@ public:
 
 private:
   std::shared_ptr<const internal::arm_compute::Plan> _plan;
+
+public:
+  // TODO Use InputIndex instead of int
+  void source(int n, std::unique_ptr<Source> &&source) { _sources.at(n) = std::move(source); }
+  template<typename T, typename... Args> void source(int n, Args&&... args)
+  {
+    source(n, std::unique_ptr<T>{new T{std::forward<Args>(args)...}});
+  }
+
+public:
+  const Source &source(int n) const { return *(_sources.at(n)); }
+
+public:
+  // TODO Use OutputIndex instead of int
+  void sink(int n, std::unique_ptr<Sink> &&sink) { _sinks.at(n) = std::move(sink); }
+  template<typename T, typename... Args> void sink(int n, Args&&... args)
+  {
+    sink(n, std::unique_ptr<T>{new T{std::forward<Args>(args)...}});
+  }
+
+public:
+  const Sink &sink(int n) const { return *(_sinks.at(n)); }
+
+private:
+  std::vector<std::unique_ptr<Source>> _sources;
+  std::vector<std::unique_ptr<Sink>> _sinks;
 };
 
 #endif
index 20c9da3..5906bc5 100644 (file)
@@ -1,5 +1,7 @@
 #include "internal/arm_compute.h"
 
+#include <arm_compute/runtime/CL/CLScheduler.h>
+
 #include <cassert>
 
 namespace internal
@@ -9,6 +11,26 @@ namespace arm_compute
 namespace operand
 {
 
+void Object::access(const std::function<void (::arm_compute::ITensor &tensor)> &fn) const
+{
+  auto &queue = ::arm_compute::CLScheduler::get().queue();
+
+  _tensor->map(queue);
+  fn(*_tensor);
+  _tensor->unmap(queue);
+}
+
+} // namespace operand
+} // namepsace arm_compute
+} // namespace internal
+
+namespace internal
+{
+namespace arm_compute
+{
+namespace operand
+{
+
 Context &Context::set(const ::internal::tflite::operand::Index &id,
                       const std::shared_ptr<::arm_compute::ICLTensor> &tensor)
 {
index 7c49e0d..6221ac4 100644 (file)
@@ -26,6 +26,9 @@ public:
 
 private:
   std::shared_ptr<::arm_compute::ICLTensor> _tensor;
+
+public:
+  void access(const std::function<void (::arm_compute::ITensor &tensor)> &fn) const;
 };
 
 } // namespace operand
diff --git a/tools/nnapi_bindings/bindings/pure_arm_compute/src/internal/arm_compute/feature/View.h b/tools/nnapi_bindings/bindings/pure_arm_compute/src/internal/arm_compute/feature/View.h
new file mode 100644 (file)
index 0000000..5298c7b
--- /dev/null
@@ -0,0 +1,72 @@
+#ifndef __INTERNAL_ARM_COMPUTE_FEATURE_VIEW_H__
+#define __INTERNAL_ARM_COMPUTE_FEATURE_VIEW_H__
+
+#include "util/feature/Reader.h"
+
+#include <arm_compute/core/ITensor.h>
+
+#include <cassert>
+
+namespace internal
+{
+namespace arm_compute
+{
+namespace feature
+{
+
+template<typename T> class View;
+
+template<> class View<float> final : public nnfw::util::feature::Reader<float>
+{
+public:
+  View(::arm_compute::ITensor *tensor) : _tensor{tensor}
+  {
+    assert(tensor->info()->data_type() == ::arm_compute::DataType::F32);
+
+    // TODO Validate whether tensor is a feature map, or not
+
+    _shape.C = tensor->info()->dimension(2);
+    _shape.H = tensor->info()->dimension(1);
+    _shape.W = tensor->info()->dimension(0);
+  }
+
+public:
+  const ::nnfw::util::feature::Shape &shape(void) const { return _shape; }
+
+public:
+  float at(uint32_t ch, uint32_t row, uint32_t col) const override
+  {
+    const auto offset = feature_index_to_byte_offset(ch, row, col);
+
+    float *ptr = reinterpret_cast<float *>(_tensor->buffer() + offset);
+
+    return *ptr;
+  }
+
+public:
+  float &at(uint32_t ch, uint32_t row, uint32_t col)
+  {
+    const auto offset = feature_index_to_byte_offset(ch, row, col);
+
+    float *ptr = reinterpret_cast<float *>(_tensor->buffer() + offset);
+
+    return *ptr;
+  }
+
+private:
+  size_t feature_index_to_byte_offset(uint32_t ch, uint32_t row, uint32_t col) const
+  {
+    // ARM Compute uses CHW ordering
+    return _tensor->info()->offset_element_in_bytes(::arm_compute::Coordinates{col, row, ch});
+  }
+
+private:
+  ::nnfw::util::feature::Shape _shape;
+  ::arm_compute::ITensor *_tensor;
+};
+
+} // namespace feature
+} // namespace arm_compute
+} // namespace internal
+
+#endif // __INTERNAL_ARM_COMPUTE_FEATURE_VIEW_H__
diff --git a/tools/nnapi_bindings/bindings/pure_arm_compute/src/internal/nnapi/feature/Reader.h b/tools/nnapi_bindings/bindings/pure_arm_compute/src/internal/nnapi/feature/Reader.h
new file mode 100644 (file)
index 0000000..730e349
--- /dev/null
@@ -0,0 +1,51 @@
+#ifndef __INTERNAL_NNAPI_FEATURE_READER_H__
+#define __INTERNAL_NNAPI_FEATURE_READER_H__
+
+#include "internal/nnapi/feature/Utils.h"
+
+#include "util/feature/Reader.h"
+
+namespace internal
+{
+namespace nnapi
+{
+namespace feature
+{
+
+template<typename T> class Reader;
+
+template<> class Reader<float> final : public nnfw::util::feature::Reader<float>
+{
+public:
+  Reader(const ::nnfw::util::feature::Shape &shape, const uint8_t *ptr, size_t len)
+    : _shape{shape}, _ptr{ptr}, _len{len}
+  {
+    // DO NOTHING
+  }
+
+public:
+  const nnfw::util::feature::Shape &shape(void) const { return _shape; }
+
+public:
+  float at(uint32_t ch, uint32_t row, uint32_t col) const override
+  {
+    uint32_t index = index_of(_shape, ch, row, col);
+
+    const auto arr = reinterpret_cast<const float *>(_ptr);
+
+    return arr[index];
+  }
+
+private:
+  nnfw::util::feature::Shape _shape;
+
+private:
+  const uint8_t *_ptr;
+  const size_t _len;
+};
+
+} // namespace feature
+} // namespace nnapi
+} // namespace internal
+
+#endif // __INTERNAL_NNAPI_FEATURE_READER_H__
diff --git a/tools/nnapi_bindings/bindings/pure_arm_compute/src/internal/nnapi/feature/Utils.h b/tools/nnapi_bindings/bindings/pure_arm_compute/src/internal/nnapi/feature/Utils.h
new file mode 100644 (file)
index 0000000..6b1574d
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef __INTERNAL_NNAPI_FEATURE_UTILS_H__
+#define __INTERNAL_NNAPI_FEATURE_UTILS_H__
+
+#include "util/feature/Shape.h"
+
+namespace internal
+{
+namespace nnapi
+{
+namespace feature
+{
+
+inline uint32_t index_of(const ::nnfw::util::feature::Shape &shape,
+                         uint32_t ch, uint32_t row, uint32_t col)
+{
+  uint32_t res = 0;
+
+  // NNAPI uses NHWC ordering
+  res += row * shape.W * shape.C;
+  res += col * shape.C;
+  res += ch;
+
+  return res;
+}
+
+} // namespace feature
+} // namespace nnapi
+} // namespace internal
+
+#endif // __INTERNAL_NNAPI_FEATURE_UTILS_H__
diff --git a/tools/nnapi_bindings/bindings/pure_arm_compute/src/internal/nnapi/feature/View.h b/tools/nnapi_bindings/bindings/pure_arm_compute/src/internal/nnapi/feature/View.h
new file mode 100644 (file)
index 0000000..86be0af
--- /dev/null
@@ -0,0 +1,58 @@
+#ifndef __INTERNAL_NNAPI_FEATURE_VIEW_H__
+#define __INTERNAL_NNAPI_FEATURE_VIEW_H__
+
+#include "internal/nnapi/feature/Utils.h"
+
+#include "util/feature/Reader.h"
+
+namespace internal
+{
+namespace nnapi
+{
+namespace feature
+{
+
+template<typename T> class View final : public nnfw::util::feature::Reader<float>
+{
+public:
+  View(const ::nnfw::util::feature::Shape &shape, uint8_t *ptr, size_t len)
+    : _shape{shape}, _ptr{ptr}, _len{len}
+  {
+    // DO NOTHING
+  }
+
+public:
+  const nnfw::util::feature::Shape &shape(void) const { return _shape; }
+
+public:
+  T at(uint32_t ch, uint32_t row, uint32_t col) const override
+  {
+    uint32_t index = index_of(_shape, ch, row, col);
+
+    T *arr = reinterpret_cast<T *>(_ptr);
+
+    return arr[index];
+  }
+
+  T &at(uint32_t ch, uint32_t row, uint32_t col)
+  {
+    uint32_t index = index_of(_shape, ch, row, col);
+
+    T *arr = reinterpret_cast<T *>(_ptr);
+
+    return arr[index];
+  }
+
+private:
+  nnfw::util::feature::Shape _shape;
+
+private:
+  uint8_t *_ptr;
+  const size_t _len;
+};
+
+} // namespace feature
+} // namespace nnapi
+} // namespace internal
+
+#endif // __INTERNAL_NNAPI_FEATURE_VIEW_H__