[filter/TVM] Add tensor-filter subplugin for TVM
authorJunhwan Kim <jejudo.kim@samsung.com>
Fri, 16 Apr 2021 12:55:29 +0000 (21:55 +0900)
committerMyungJoo Ham <myungjoo.ham@samsung.com>
Thu, 29 Apr 2021 09:44:27 +0000 (18:44 +0900)
- `dlpack` and either `tvm` or `tvm_runtime` should be included
- Only supports tvm model compiled with relay (graph_executor)

Signed-off-by: Junhwan Kim <jejudo.kim@samsung.com>
ext/nnstreamer/tensor_filter/meson.build
ext/nnstreamer/tensor_filter/tensor_filter_tvm.cc [new file with mode: 0644]
meson.build
meson_options.txt

index 45f7cde..0f1b26b 100644 (file)
@@ -651,3 +651,29 @@ if lua_support_is_available
     install_dir: nnstreamer_libdir
   )
 endif
+
+if tvm_support_is_available
+  nnstreamer_filter_tvm_deps = tvm_support_deps + [glib_dep, gst_dep, nnstreamer_dep]
+
+  filter_sub_tvm_sources = ['tensor_filter_tvm.cc']
+
+  nnstreamer_filter_tvm_sources = []
+  foreach s : filter_sub_tvm_sources
+    nnstreamer_filter_tvm_sources += join_paths(meson.current_source_dir(), s)
+  endforeach
+
+  shared_library('nnstreamer_filter_tvm',
+    nnstreamer_filter_tvm_sources,
+    dependencies: nnstreamer_filter_tvm_deps,
+    install: true,
+    install_dir: filter_subplugin_install_dir
+  )
+
+  static_library('nnstreamer_filter_tvm',
+    nnstreamer_filter_tvm_sources,
+    dependencies: nnstreamer_filter_tvm_deps,
+    install: true,
+    install_dir: nnstreamer_libdir
+  )
+
+endif
diff --git a/ext/nnstreamer/tensor_filter/tensor_filter_tvm.cc b/ext/nnstreamer/tensor_filter/tensor_filter_tvm.cc
new file mode 100644 (file)
index 0000000..709ed29
--- /dev/null
@@ -0,0 +1,430 @@
+/* SPDX-License-Identifier: LGPL-2.1-only */
+/**
+ * @file    tensor_filter_tvm.cc
+ * @date    16 Apr 2021
+ * @brief   NNStreamer tensor-filter sub-plugin for Apache TVM
+ * @author  Junhwan Kim <jejudo.kim@samsung.com>
+ * @see     http://github.com/nnstreamer/nnstreamer
+ * @bug     No known bugs
+ *
+ * This is the per-NN-framework plugin (TVM) for tensor_filter.
+ *
+ * @note    Only supports tvm.contrib.graph_executor model
+ */
+
+#include <glib.h>
+#include <nnstreamer_cppplugin_api_filter.hh>
+#include <nnstreamer_log.h>
+#include <tensor_common.h>
+
+#include <dlpack/dlpack.h>
+#include <tvm/runtime/module.h>
+#include <tvm/runtime/packed_func.h>
+#include <tvm/runtime/registry.h>
+
+
+namespace nnstreamer
+{
+namespace tensorfilter_tvm
+{
+
+G_BEGIN_DECLS
+
+void init_filter_tvm (void) __attribute__ ((constructor));
+void fini_filter_tvm (void) __attribute__ ((destructor));
+
+G_END_DECLS
+
+/**
+ * @brief Class for TVM subplugin.
+ */
+class tvm_subplugin final : public tensor_filter_subplugin
+{
+  private:
+  bool empty_model;
+  gchar *model_path;
+  GstTensorsInfo inputInfo;
+  GstTensorsInfo outputInfo;
+
+  DLDevice device;
+  tvm::runtime::Module mod_factory;
+  tvm::runtime::Module gmod;
+  std::vector<DLTensor> input_tensor_list;
+  std::vector<DLTensor> output_tensor_list;
+
+  static const char *name;
+  static const accl_hw hw_list[];
+  static tvm_subplugin *registeredRepresentation;
+
+  bool parse_custom_prop (const char *custom_prop);
+  bool convert_dtype (tensor_type &nns_type, const DLDataType &dtype);
+  void cleanup () noexcept;
+
+  public:
+  static void init_filter_tvm ();
+  static void fini_filter_tvm ();
+
+  tvm_subplugin ();
+  ~tvm_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);
+};
+
+const char *tvm_subplugin::name = "tvm";
+const accl_hw tvm_subplugin::hw_list[] = { ACCL_CPU, ACCL_GPU };
+
+/**
+ * @brief Construct a new tvm subplugin::tvm subplugin object
+ */
+tvm_subplugin::tvm_subplugin ()
+    : tensor_filter_subplugin (), empty_model (true), model_path (nullptr),
+      device (DLDevice{ kDLCPU, 0 }), mod_factory (nullptr), gmod (nullptr)
+{
+  inputInfo.num_tensors = 0;
+  outputInfo.num_tensors = 0;
+}
+
+/**
+ * @brief Cleanup method for tvm subplugin
+ */
+void
+tvm_subplugin::cleanup () noexcept
+{
+  if (empty_model)
+    return;
+  g_free (model_path);
+
+  input_tensor_list.clear ();
+  output_tensor_list.clear ();
+
+  model_path = nullptr;
+  inputInfo.num_tensors = 0;
+  outputInfo.num_tensors = 0;
+  empty_model = true;
+}
+
+/**
+ * @brief Destroy the tvm subplugin::tvm subplugin object
+ */
+tvm_subplugin::~tvm_subplugin ()
+{
+  cleanup ();
+}
+
+/**
+ * @brief Method to get an empty object
+ */
+tensor_filter_subplugin &
+tvm_subplugin::getEmptyInstance ()
+{
+  return *(new tvm_subplugin ());
+}
+
+/**
+ * @brief Internal method to parse custom properties
+ * @param custom_prop Given c_str value of 'custom' property,
+ *                    which contains device info
+ */
+bool
+tvm_subplugin::parse_custom_prop (const char *custom_prop)
+{
+  gchar **options;
+  bool invalid_option = false;
+
+  if (custom_prop == nullptr) {
+    /* no custom properties */
+    return true;
+  }
+
+  options = g_strsplit (custom_prop, ",", -1);
+
+  for (guint op = 0; op < g_strv_length (options); ++op) {
+    gchar **option = g_strsplit (options[op], ":", -1);
+
+    if (g_strv_length (option) > 1) {
+      g_strstrip (option[0]);
+      g_strstrip (option[1]);
+
+      if (g_ascii_strcasecmp (option[0], "device") == 0) {
+        if (g_ascii_strcasecmp (option[1], "CPU") == 0) {
+          device = DLDevice{ kDLCPU, 0 };
+        } else if (g_ascii_strcasecmp (option[1], "GPU") == 0) {
+          device = DLDevice{ kDLGPU, 0 };
+        } else {
+          nns_loge ("Unknown device (%s).", option[1]);
+          invalid_option = true;
+        }
+      } else {
+        nns_logw ("Unknown option (%s).", options[op]);
+      }
+    }
+    if (invalid_option) {
+      g_strfreev (option);
+      break;
+    }
+    g_strfreev (option);
+  }
+  g_strfreev (options);
+  return !invalid_option;
+}
+
+/**
+ * @brief Configure tvm instance
+ */
+void
+tvm_subplugin::configure_instance (const GstTensorFilterProperties *prop)
+{
+  if (!parse_custom_prop (prop->custom_properties)) {
+    nns_loge ("Failed to parse custom property.");
+    cleanup ();
+    throw std::invalid_argument ("Failed to parse custom property.");
+  }
+
+  if (!empty_model) {
+    if (!prop->model_files[0] || prop->model_files[0][0] == '\0') {
+      std::cerr << "Model path is not given." << std::endl;
+      cleanup ();
+      throw std::invalid_argument ("Model path is not given.");
+    }
+    cleanup ();
+  }
+
+  /* read model */
+  model_path = g_strdup (prop->model_files[0]);
+  mod_factory = tvm::runtime::Module::LoadFromFile (model_path, "so");
+  gmod = mod_factory.GetFunction ("default") (device);
+
+  inputInfo.num_tensors = (int)gmod.GetFunction ("get_num_inputs") ();
+  outputInfo.num_tensors = (int)gmod.GetFunction ("get_num_outputs") ();
+
+  tvm::runtime::NDArray arr;
+  const DLTensor *dt;
+
+  /* infer in out info */
+  auto getInput = gmod.GetFunction ("get_input");
+  if (getInput == nullptr) {
+    cleanup ();
+    throw std::invalid_argument ("Packed function `get_input` not defined in model");
+  }
+  auto getOutput = gmod.GetFunction ("get_output");
+  if (getOutput == nullptr) {
+    cleanup ();
+    throw std::invalid_argument ("Packed function `get_output` not defined in model");
+  }
+
+  for (unsigned int i = 0; i < inputInfo.num_tensors; ++i) {
+    arr = getInput (i);
+    dt = arr.operator-> ();
+    input_tensor_list.push_back (*dt);
+
+    if (!convert_dtype (inputInfo.info[i].type, dt->dtype)) {
+      cleanup ();
+      throw std::invalid_argument ("Failed to convert DLPack data type");
+    }
+    std::copy (dt->shape, dt->shape + dt->ndim, inputInfo.info[i].dimension);
+    inputInfo.info[i].name = nullptr;
+  }
+
+  for (unsigned int i = 0; i < outputInfo.num_tensors; ++i) {
+    arr = getOutput (i);
+    dt = arr.operator-> ();
+    output_tensor_list.push_back (*dt);
+
+    if (!convert_dtype (outputInfo.info[i].type, dt->dtype)) {
+      cleanup ();
+      throw std::invalid_argument ("Failed to convert DLPack data type");
+    }
+    std::copy (dt->shape, dt->shape + dt->ndim, outputInfo.info[i].dimension);
+    outputInfo.info[i].name = nullptr;
+  }
+  empty_model = false;
+}
+
+/**
+ * @brief Internal method to convert DLPack data type to tensor type
+ */
+bool
+tvm_subplugin::convert_dtype (tensor_type &nns_type, const DLDataType &dtype)
+{
+  switch (dtype.code) {
+  case kDLInt:
+    switch (dtype.bits) {
+    case 64:
+      nns_type = _NNS_INT64;
+      break;
+    case 32:
+      nns_type = _NNS_INT32;
+      break;
+    case 16:
+      nns_type = _NNS_INT16;
+      break;
+    case 8:
+      nns_type = _NNS_INT8;
+      break;
+    default:
+      return false;
+    }
+    break;
+  case kDLUInt:
+    switch (dtype.bits) {
+    case 64:
+      nns_type = _NNS_UINT64;
+      break;
+    case 32:
+      nns_type = _NNS_UINT32;
+      break;
+    case 16:
+      nns_type = _NNS_UINT16;
+      break;
+    case 8:
+      nns_type = _NNS_UINT8;
+      break;
+    default:
+      return false;
+    }
+    break;
+  case kDLFloat:
+    switch (dtype.bits) {
+    case 64:
+      nns_type = _NNS_FLOAT64;
+      break;
+    case 32:
+      nns_type = _NNS_FLOAT32;
+      break;
+    default:
+      return false;
+    }
+    break;
+  default:
+    return false;
+  }
+  return true;
+}
+
+/**
+ * @brief Invoke tvm instance
+ */
+void
+tvm_subplugin::invoke (const GstTensorMemory *input, GstTensorMemory *output)
+{
+  assert (!empty_model);
+  assert (gmod.defined ());
+  assert (input != NULL && output != NULL);
+
+  unsigned int i;
+  tvm::runtime::NDArray tensor;
+
+  /* read functions */
+  tvm::runtime::PackedFunc set_input = gmod.GetFunction ("set_input");
+  if (set_input == nullptr) {
+    cleanup ();
+    throw std::runtime_error ("Packed function `set_input` not defined in model");
+  }
+  tvm::runtime::PackedFunc get_output = gmod.GetFunction ("get_output");
+  if (set_input == nullptr) {
+    cleanup ();
+    throw std::runtime_error ("Packed function `get_output` not defined in model");
+  }
+  tvm::runtime::PackedFunc run = gmod.GetFunction ("run");
+  if (set_input == nullptr) {
+    cleanup ();
+    throw std::runtime_error ("Packed function `run` not defined in model");
+  }
+
+  for (i = 0; i < inputInfo.num_tensors; ++i) {
+    input_tensor_list[i].data = input[i].data;
+    set_input (i, &input_tensor_list[i]);
+  }
+
+  run ();
+
+  for (i = 0; i < outputInfo.num_tensors; ++i) {
+    output_tensor_list[i].data = output[i].data;
+    get_output (i, &output_tensor_list[i]);
+  }
+}
+
+/**
+ * @brief Get tvm frameworks info
+ */
+void
+tvm_subplugin::getFrameworkInfo (GstTensorFilterFrameworkInfo &info)
+{
+  info.name = name;
+  info.allow_in_place = 0;
+  info.allocate_in_invoke = 0;
+  info.run_without_model = 0;
+  info.verify_model_path = 1;
+}
+
+/**
+ * @brief Get tvm model information
+ */
+int
+tvm_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;
+}
+
+/**
+ * @brief Method to handle the event
+ */
+int
+tvm_subplugin::eventHandler (event_ops ops, GstTensorFilterFrameworkEventData &data)
+{
+  return -ENOENT;
+}
+
+tvm_subplugin *tvm_subplugin::registeredRepresentation = nullptr;
+
+/**
+ * @brief Initialize the object for runtime register
+ */
+void
+tvm_subplugin::init_filter_tvm (void)
+{
+  registeredRepresentation
+      = tensor_filter_subplugin::register_subplugin<tvm_subplugin> ();
+}
+
+/**
+ * @brief Destruct the subplugin
+ */
+void
+tvm_subplugin::fini_filter_tvm (void)
+{
+  assert (registeredRepresentation != nullptr);
+  tensor_filter_subplugin::unregister_subplugin (registeredRepresentation);
+}
+
+/**
+ * @brief initializer
+ */
+void
+init_filter_tvm ()
+{
+  tvm_subplugin::init_filter_tvm ();
+}
+
+/**
+ * @brief finalizer
+ */
+void
+fini_filter_tvm ()
+{
+  tvm_subplugin::fini_filter_tvm ();
+}
+
+} // namespace tensorfilter_tvm
+} /* namespace nnstreamer */
index f362add..6af1b16 100644 (file)
@@ -174,6 +174,20 @@ if not get_option('snpe-support').disabled()
   endif
 endif
 
+# tvm
+tvm_dep = dependency('', required: false)
+dlpack_dep = dependency('', required: false)
+if not get_option('tvm-support').disabled()
+  tvm_dep = dependency('tvm', required: false)
+  dlpack_dep = dependency('dlpack', required: false)
+  if not tvm_dep.found()
+    tvm_dep = cxx.find_library('tvm', required: get_option('tvm-support'))
+  endif
+  if not dlpack_dep.found()
+    dlpack_dep = cxx.find_library('dlpack', required: get_option('tvm-support'))
+  endif
+endif
+
 # tensorrt
 nvinfer_dep = dependency('', required: false)
 nvparsers_dep = dependency('', required: false)
@@ -306,6 +320,9 @@ features = {
   },
   'mqtt-support': {
     'extra_deps': [ pahomqttc_dep ]
+  },
+  'tvm-support': {
+    'extra_deps': [ tvm_dep, dlpack_dep ]
   }
 }
 
index dcefe46..70b68f5 100644 (file)
@@ -19,6 +19,7 @@ option('tensorrt-support', type: 'feature', value: 'auto')
 option('grpc-support', type: 'feature', value: 'auto')
 option('lua-support', type: 'feature', value: 'auto')
 option('mqtt-support', type: 'feature', value: 'auto')
+option('tvm-support', type: 'feature', value: 'auto')
 
 # booleans & other options
 option('enable-test', type: 'boolean', value: true)