add executorch subplugin
authorJunhyeong Kim <leeeryboy@gmail.com>
Thu, 6 Jun 2024 05:34:47 +0000 (14:34 +0900)
committerMyungJoo Ham <myungjoo.ham@samsung.com>
Tue, 9 Jul 2024 08:56:49 +0000 (17:56 +0900)
Signed-off-by: Junhyeong Kim <leeeryboy@gmail.com>
ext/nnstreamer/tensor_filter/meson.build
ext/nnstreamer/tensor_filter/tensor_filter_executorch.cc
meson.build

index f92137b..ea303ad 100644 (file)
@@ -818,19 +818,6 @@ if ncnn_support_is_available
   )
 endif
 
-if executorch_support_is_available
-  filter_sub_executorch_sources = ['tensor_filter_executorch.cc']
-
-  nnstreamer_filter_executorch_deps = [executorch_support_deps, glib_dep, nnstreamer_single_dep]
-
-  static_library('nnstreamer_filter_executorch',
-    filter_sub_executorch_sources,
-    dependencies: nnstreamer_filter_executorch_deps,
-    install: true,
-    install_dir: nnstreamer_libdir
-  )
-endif
-
 if onnxruntime_support_is_available
   filter_sub_onnxruntime_sources = ['tensor_filter_onnxruntime.cc']
 
index 9a191db..25f8dad 100644 (file)
@@ -4,14 +4,14 @@
  * @file    tensor_filter_executorch.cc
  * @date    26 Apr 2024
  * @brief   NNStreamer tensor-filter sub-plugin for ExecuTorch
- * @author  
+ * @author
  * @see     http://github.com/nnstreamer/nnstreamer
  * @bug     No known bugs.
  *
  * This is the executorch plugin for tensor_filter.
  *
  * @note Currently only skeleton
- * 
+ *
  **/
 
 
@@ -23,6 +23,7 @@
 
 #include <iostream>
 #include <memory>
+#include <vector>
 
 #include <executorch/extension/data_loader/file_data_loader.h>
 #include <executorch/extension/evalue_util/print_evalue.h>
 #include <executorch/runtime/platform/log.h>
 #include <executorch/runtime/platform/runtime.h>
 
-// static uint8_t method_allocator_pool[4 * 1024U * 1024U]; // 4 MB
-
-// DEFINE_string(
-//     model_path,
-//     "model.pte",
-//     "Model serialized in flatbuffer format.");
+static uint8_t method_allocator_pool[4 * 1024U * 1024U]; // 4 MB
 
 using namespace torch::executor;
 using torch::executor::util::FileDataLoader;
 
 namespace nnstreamer
 {
-namespace tensorfilter_executorch
+namespace tensor_filter_executorch
 {
 
-G_BEGIN_DECLS
-
+extern "C" {
 void init_filter_executorch (void) __attribute__ ((constructor));
 void fini_filter_executorch (void) __attribute__ ((destructor));
-
-G_END_DECLS
+}
 
 /**
- * @brief Class for executorch subplugin (skeleton)
+ * @brief tensor-filter-subplugin concrete class for ExecuTorch
  */
 
-class executorch_subplugin final: public tensor_filter_subplugin
+class executorch_subplugin final : public tensor_filter_subplugin
 {
-    public:
-    static void init_filter_executorch (); /**< Dynamic library contstructor helper */
-    static void fini_filter_executorch (); /**< Dynamic library desctructor helper */
+  private:
+  static executorch_subplugin *registeredRepresentation;
+  static const GstTensorFilterFrameworkInfo framework_info;
+
+  bool configured;
+  char *model_path; /**< The model *.pte file */
+  void cleanup (); /**< cleanup function */
+  GstTensorsInfo inputInfo; /**< Input tensors metadata */
+  GstTensorsInfo outputInfo; /**< Output tensors metadata */
 
-    executorch_subplugin ();
-    ~executorch_subplugin ();
+  /** executorch method*/
+  std::unique_ptr<Result<Method>> method;
+  const char *method_name;
 
-    /**< Implementations of ncnn tensor_filter_subplugin */
-    tensor_filter_subplugin &getEmptyInstance ();
-    void configure_instance (const GstTensorFilterProperties *prop);
-    void invoke (const GstTensorMemory *input, GstTensorMemory *output);
-    void getFrameworkInfo (GstTensorFilterFrameworkInfo &info);
-    int getModelInfo (model_info_ops ops, GstTensorsInfo &in_info, GstTensorsInfo &out_info);
-    int eventHandler (event_ops ops, GstTensorFilterFrameworkEventData &data);
+  public:
+  static void init_filter_executorch ();
+  static void fini_filter_executorch ();
 
-    private:
-    bool empty_model; /**< Empty (not initialized) model flag */
+  executorch_subplugin ();
+  ~executorch_subplugin ();
+
+  tensor_filter_subplugin &getEmptyInstance ();
+  void configure_instance (const GstTensorFilterProperties *prop);
+  void invoke (const GstTensorMemory *input, GstTensorMemory *output);
+  void getFrameworkInfo (GstTensorFilterFrameworkInfo &info);
+  int getModelInfo (model_info_ops ops, GstTensorsInfo &in_info, GstTensorsInfo &out_info);
+  int eventHandler (event_ops ops, GstTensorFilterFrameworkEventData &data);
 };
 
+/**
+ * @brief Describe framework information.
+ */
+const GstTensorFilterFrameworkInfo executorch_subplugin::framework_info = { .name = "executorch",
+  .allow_in_place = FALSE,
+  .allocate_in_invoke = FALSE,
+  .run_without_model = FALSE,
+  .verify_model_path = TRUE,
+  .hw_list = (const accl_hw[]){ ACCL_CPU },
+  .num_hw = 1,
+  .accl_auto = ACCL_CPU,
+  .accl_default = ACCL_CPU,
+  .statistics = nullptr };
+
+/**
+ * @brief Constructor for executorch subplugin.
+ */
+executorch_subplugin::executorch_subplugin ()
+    : tensor_filter_subplugin (), configured (false), model_path (nullptr),
+      method_name (nullptr)
+{
+  gst_tensors_info_init (std::addressof (inputInfo));
+  gst_tensors_info_init (std::addressof (outputInfo));
+}
+
+/**
+ * @brief Destructor for executorch subplugin.
+ */
+executorch_subplugin::~executorch_subplugin ()
+{
+  cleanup ();
+}
+
+/**
+ * @brief Method to get empty object.
+ */
+tensor_filter_subplugin &
+executorch_subplugin::getEmptyInstance ()
+{
+  return *(new executorch_subplugin ());
+}
+
+/**
+ * @brief Method to cleanup executorch subplugin.
+ */
+void
+executorch_subplugin::cleanup ()
+{
+  g_free (model_path);
+  model_path = nullptr;
+
+  if (!configured)
+    return;
+
+  gst_tensors_info_free (std::addressof (inputInfo));
+  gst_tensors_info_free (std::addressof (outputInfo));
+
+  method_name = nullptr;
+  configured = false;
+}
+
+/**
+ * @brief Method to prepare/configure ExecuTorch instance.
+ */
+void
+executorch_subplugin::configure_instance (const GstTensorFilterProperties *prop)
+{
+  /* Already configured */
+  if (configured)
+    cleanup ();
+
+  try {
+    /* Load network (.pte file) */
+    if (!g_file_test (prop->model_files[0], G_FILE_TEST_IS_REGULAR)) {
+      const std::string err_msg
+          = "Given file " + (std::string) prop->model_files[0] + " is not valid";
+      throw std::invalid_argument (err_msg);
+    }
+
+    model_path = g_strdup (prop->model_files[0]);
+
+    // Create a loader to get the data of the program file.
+    Result<FileDataLoader> loader = FileDataLoader::from (model_path);
+    ET_CHECK_MSG (loader.ok (), "FileDataLoader::from() failed: 0x%" PRIx32,
+        (uint32_t) loader.error ());
+
+    // Parse the program file.
+    Result<Program> program = Program::load (&loader.get ());
+    if (!program.ok ()) {
+      ET_LOG (Error, "Failed to parse model file %s", model_path);
+      return;
+    }
+    ET_LOG (Info, "Model file %s is loaded.", model_path);
+
+    // Use the first method in the program.
+    const auto method_name_result = program->get_method_name (0);
+    ET_CHECK_MSG (method_name_result.ok (), "Program has no methods");
+    method_name = *method_name_result;
+    ET_LOG (Info, "Using method %s", method_name);
+
+    // MethodMeta describes the memory requirements of the method.
+    Result<MethodMeta> method_meta = program->method_meta (method_name);
+    ET_CHECK_MSG (method_meta.ok (), "Failed to get method_meta for %s: 0x%" PRIx32,
+        method_name, (uint32_t) method_meta.error ());
+
+    MemoryAllocator method_allocator{ MemoryAllocator (
+        sizeof (method_allocator_pool), method_allocator_pool) };
+
+    std::vector<std::unique_ptr<uint8_t[]>> planned_buffers; // Owns the memory
+    std::vector<Span<uint8_t>> planned_spans; // Passed to the allocator
+    size_t num_memory_planned_buffers = method_meta->num_memory_planned_buffers ();
+    for (size_t id = 0; id < num_memory_planned_buffers; ++id) {
+      // .get() will always succeed because id < num_memory_planned_buffers.
+      size_t buffer_size = static_cast<size_t> (
+          method_meta->memory_planned_buffer_size (id).get ());
+      ET_LOG (Info, "Setting up planned buffer %zu, size %zu.", id, buffer_size);
+      planned_buffers.push_back (std::make_unique<uint8_t[]> (buffer_size));
+      planned_spans.push_back ({ planned_buffers.back ().get (), buffer_size });
+    }
+    HierarchicalAllocator planned_memory ({ planned_spans.data (), planned_spans.size () });
+
+    // Assemble all of the allocators into the MemoryManager that the Executor
+    // will use.
+    MemoryManager memory_manager (&method_allocator, &planned_memory);
+
+    //
+    // Load the method from the program, using the provided allocators. Running
+    // the method can mutate the memory-planned buffers, so the method should
+    // only be used by a single thread at at time, but it can be reused.
+    //
+
+    method = std::make_unique<Result<Method>> (
+        program->load_method (method_name, &memory_manager));
+    ET_CHECK_MSG (method->ok (), "Loading of method %s failed with status 0x%" PRIx32,
+        method_name, (uint32_t) method->error ());
+    ET_LOG (Info, "Method loaded.");
+
+    configured = true;
+  } catch (const std::exception &e) {
+    cleanup ();
+    /* throw exception upward */
+    throw;
+  }
+}
+
+/**
+ * @brief Method to execute the model.
+ */
+void
+executorch_subplugin::invoke (const GstTensorMemory *input, GstTensorMemory *output)
+{
+  if (!input)
+    throw std::runtime_error ("Invalid input buffer, it is NULL.");
+  if (!output)
+    throw std::runtime_error ("Invalid output buffer, it is NULL.");
+
+  if (!method->ok ()) {
+    throw std::runtime_error ("Method is not properly initialized.");
+  }
+
+  Method &method_ref = method->get ();
+
+  // Set inputs
+  for (unsigned int i = 0; i < inputInfo.num_tensors; i++) {
+    GstTensorInfo *info = gst_tensors_info_get_nth_info (std::addressof (inputInfo), i);
+    EValue input_value ((const char *) input[i].data, info->type);
+    Error set_input_error = method_ref.set_input (input_value, i);
+    assert (set_input_error == Error::Ok);
+  }
+
+  // Execute the method
+  Error status = method_ref.execute ();
+  ET_CHECK_MSG (status == Error::Ok, "Execution of method %s failed with status 0x%" PRIx32,
+      method_name, (uint32_t) status);
+  ET_LOG (Info, "Model executed successfully.");
+
+  // Get outputs
+  for (unsigned int i = 0; i < outputInfo.num_tensors; i++) {
+    auto output_value = method_ref.get_output (i);
+    Error set_output_error
+        = method_ref.set_output_data_ptr (output[i].data, sizeof (output[i].data), i);
+    assert (set_output_error == Error::Ok);
+  }
+}
+
+/**
+ * @brief Method to get the information of ExecuTorch subplugin.
+ */
+void
+executorch_subplugin::getFrameworkInfo (GstTensorFilterFrameworkInfo &info)
+{
+  info = framework_info;
+}
+
+/**
+ * @brief Method to get the model information.
+ */
+int
+executorch_subplugin::getModelInfo (
+    model_info_ops ops, GstTensorsInfo &in_info, GstTensorsInfo &out_info)
+{
+  if (ops == GET_IN_OUT_INFO) {
+    gst_tensors_info_copy (std::addressof (in_info), std::addressof (inputInfo));
+    gst_tensors_info_copy (std::addressof (out_info), std::addressof (outputInfo));
+    return 0;
+  }
+
+  return -ENOENT;
 }
-}
\ No newline at end of file
+
+/**
+ * @brief Method to handle events.
+ */
+int
+executorch_subplugin::eventHandler (event_ops ops, GstTensorFilterFrameworkEventData &data)
+{
+  UNUSED (ops);
+  UNUSED (data);
+
+  return -ENOENT;
+}
+
+executorch_subplugin *executorch_subplugin::registeredRepresentation = nullptr;
+
+/** @brief Initialize this object for tensor_filter subplugin runtime register */
+void
+executorch_subplugin::init_filter_executorch (void)
+{
+  registeredRepresentation
+      = tensor_filter_subplugin::register_subplugin<executorch_subplugin> ();
+}
+
+/** @brief Destruct the subplugin */
+void
+executorch_subplugin::fini_filter_executorch (void)
+{
+  assert (registeredRepresentation != nullptr);
+  tensor_filter_subplugin::unregister_subplugin (registeredRepresentation);
+}
+
+/**
+ * @brief Register the sub-plugin for ExecuTorch.
+ */
+void
+init_filter_executorch ()
+{
+  executorch_subplugin::init_filter_executorch ();
+}
+
+/**
+ * @brief Destruct the sub-plugin for ExecuTorch.
+ */
+void
+fini_filter_executorch ()
+{
+  executorch_subplugin::fini_filter_executorch ();
+}
+
+
+} // namespace tensor_filter_executorch
+} // namespace nnstreamer
\ No newline at end of file
index 15afda4..548558b 100644 (file)
@@ -405,18 +405,6 @@ if not get_option('ncnn-support').disabled()
   endif
 endif
 
-# executorch
-executorch_dep = dependency('', required: false)
-if not get_option('executorch-support').disabled()
-  executorch_dep = dependency('executorch', required: false)
-  if (not executorch_dep.found())
-    executorch_lib = cxx.find_library('executorch', required: false)
-    if (executorch_lib.found() and cxx.check_header('executorch/runtime/platform/runtime.h'))
-      executorch_dep = declare_dependency(dependencies: [ executorch_lib ])
-    endif
-  endif
-endif
-
 # datarepo requires json-glib-1.0 in the name of json_glib_dep
 json_glib_dep = dependency('json-glib-1.0', required: false)
 if get_option('datarepo-support').enabled() and not json_glib_dep.found()
@@ -538,7 +526,7 @@ features = {
     'project_args': { 'ENABLE_NCNN' : 1 }
   },
   'executorch-support': {
-    'extra_deps': [ executorch_dep ],
+    'target': 'executorch',
     'project_args': { 'ENABLE_EXECUTORCH' : 1 }
   },
   'datarepo-support': {