[TFLITE/RELOAD] Revise tflite sub-plugin to support model reloading
authorDongju Chae <dongju.chae@samsung.com>
Tue, 10 Dec 2019 11:34:15 +0000 (20:34 +0900)
committerMyungJoo Ham <myungjoo.ham@samsung.com>
Fri, 13 Dec 2019 04:40:19 +0000 (13:40 +0900)
This commit revises tflite sub-plugin to support model reloading.

It implements new interpreter class with its own mutex, which wraps
existing codes. Also, it's designed to support interpreter swapping
to hide the overhead of model reloading.

Signed-off-by: Dongju Chae <dongju.chae@samsung.com>
ext/nnstreamer/tensor_filter/tensor_filter_tensorflow_lite.c
ext/nnstreamer/tensor_filter/tensor_filter_tensorflow_lite_core.cc
ext/nnstreamer/tensor_filter/tensor_filter_tensorflow_lite_core.h
gst/nnstreamer/nnstreamer_plugin_api_filter.h

index 7d0c339..9a034bf 100644 (file)
@@ -69,17 +69,18 @@ tflite_loadModelFile (const GstTensorFilterProperties * prop,
     void **private_data)
 {
   tflite_data *tf;
+  int err;
 
   if (*private_data != NULL) {
     /** @todo : Check the integrity of filter->data and filter->model_files, nnfw */
     tf = *private_data;
-    if (g_strcmp0 (prop->model_files[0],
-            tflite_core_getModelPath (tf->tflite_private_data)) != 0) {
-      tflite_close (prop, private_data);
-    } else {
+
+    if (tflite_core_compareModelPath (tf->tflite_private_data, prop->model_files[0]))
       return 1;
-    }
+
+    tflite_close (prop, private_data);
   }
+
   tf = *private_data = g_new0 (tflite_data, 1);
   if (tf == NULL) {
     g_printerr ("Failed to allocate memory for filter subplugin.");
@@ -87,16 +88,26 @@ tflite_loadModelFile (const GstTensorFilterProperties * prop,
   }
 
   tf->tflite_private_data = tflite_core_new (prop->model_files[0], prop->accl_str);
-  if (tf->tflite_private_data) {
-    if (tflite_core_init (tf->tflite_private_data)) {
-      g_printerr ("failed to initialize the object: Tensorflow-lite");
-      return -2;
-    }
-    return 0;
-  } else {
+  if (tf->tflite_private_data == NULL) {
     g_printerr ("failed to create the object: Tensorflow-lite");
-    return -1;
+    err = -1;
+    goto err_free;
   }
+
+  if (tflite_core_init (tf->tflite_private_data) != 0) {
+    g_printerr ("failed to initialize the object: Tensorflow-lite");
+    tflite_core_delete (tf->tflite_private_data);
+    err = -2;
+    goto err_free;
+  }
+
+  return 0;
+
+err_free:
+  g_free (*private_data);
+  *private_data = NULL;
+
+  return err;
 }
 
 /**
@@ -182,6 +193,25 @@ tflite_setInputDim (const GstTensorFilterProperties * prop, void **private_data,
   return tflite_core_setInputDim (tf->tflite_private_data, in_info, out_info);
 }
 
+/**
+ * @brief The optional callback for GstTensorFilterFramework
+ * @param prop property of tensor_filter instance
+ * @param private_data : tensorflow lite plugin's private data
+ * @return 0 if OK. non-zero if error.
+ */
+static int
+tflite_reloadModel (const GstTensorFilterProperties * prop, void **private_data)
+{
+  tflite_data *tf;
+  tf = *private_data;
+  g_assert (*private_data);
+
+  if (prop->num_models != 1)
+    return -1;
+
+  return tflite_core_reloadModel (tf->tflite_private_data, prop->model_files[0]);
+}
+
 static gchar filter_subplugin_tensorflow_lite[] = "tensorflow-lite";
 
 static GstTensorFilterFramework NNS_support_tensorflow_lite = {
@@ -192,6 +222,7 @@ static GstTensorFilterFramework NNS_support_tensorflow_lite = {
   .getInputDimension = tflite_getInputDim,
   .getOutputDimension = tflite_getOutputDim,
   .setInputDimension = tflite_setInputDim,
+  .reloadModel = tflite_reloadModel,
   .open = tflite_open,
   .close = tflite_close,
 };
index 6c0041b..735a26e 100644 (file)
   "(?<!!)" ACCL_NEON_STR ")?"
 
 /**
- * @brief      TFLiteCore creator
- * @param      _model_path     : the logical path to '{model_name}.tflite' file
- * @param      accelerators  : the accelerators property set for this subplugin
- * @note       the model of _model_path will be loaded simultaneously
- * @return     Nothing
+ * @brief TFLiteInterpreter constructor
  */
-TFLiteCore::TFLiteCore (const char * _model_path, const char * accelerators)
+TFLiteInterpreter::TFLiteInterpreter ()
 {
-  g_assert (_model_path != NULL);
-  model_path = g_strdup (_model_path);
   interpreter = nullptr;
   model = nullptr;
+#ifdef ENABLE_TFLITE_NNAPI_DELEGATE
+  nnfw_delegate = nullptr;
+#endif
+  model_path = nullptr;
 
-  setAccelerator (accelerators);
-  g_warning ("nnapi = %d, accl = %s", use_nnapi, get_accl_hw_str(accelerator));
+  g_mutex_init (&mutex);
 
   gst_tensors_info_init (&inputTensorMeta);
   gst_tensors_info_init (&outputTensorMeta);
 }
 
 /**
- * @brief      TFLiteCore Destructor
- * @return     Nothing
+ * @brief TFLiteInterpreter desctructor
  */
-TFLiteCore::~TFLiteCore ()
+TFLiteInterpreter::~TFLiteInterpreter ()
 {
+  g_mutex_clear (&mutex);
+  g_free (model_path);
+
   gst_tensors_info_free (&inputTensorMeta);
   gst_tensors_info_free (&outputTensorMeta);
 }
 
-void TFLiteCore::setAccelerator (const char * accelerators)
+/**
+ * @brief Internal implementation of TFLiteCore's invoke()
+ */
+int
+TFLiteInterpreter::invoke (const GstTensorMemory * input,
+    GstTensorMemory * output, bool use_nnapi)
 {
-  GRegex * nnapi_elem;
-  GMatchInfo * match_info;
+#if (DBG)
+  gint64 start_time = g_get_real_time ();
+#endif
 
-  if (accelerators == NULL) {
-    goto use_nnapi_ini;
-  }
+  std::vector <int> tensors_idx;
+  int tensor_idx;
+  TfLiteTensor *tensor_ptr;
+  TfLiteStatus status;
 
-  /* If set by user, get the precise accelerator */
-  use_nnapi = (bool) g_regex_match_simple (REGEX_ACCL_NNAPI, accelerators,
-      G_REGEX_CASELESS, G_REGEX_MATCH_NOTEMPTY);
-  if (use_nnapi == TRUE) {
-    /** Default to auto mode */
-    accelerator = ACCL_AUTO;
-    nnapi_elem = g_regex_new (REGEX_ACCL_NNAPI_ELEM, G_REGEX_CASELESS,
-        G_REGEX_MATCH_NOTEMPTY, NULL);
+  for (int i = 0; i < outputTensorMeta.num_tensors; ++i) {
+    tensor_idx = interpreter->outputs ()[i];
+    tensor_ptr = interpreter->tensor (tensor_idx);
 
-    /** Now match each provided element and get specific accelerator */
-    if (g_regex_match (nnapi_elem, accelerators, G_REGEX_MATCH_NOTEMPTY,
-          &match_info)) {
+    g_assert (tensor_ptr->bytes == output[i].size);
+    tensor_ptr->data.raw = (char *) output[i].data;
+    tensors_idx.push_back (tensor_idx);
+  }
 
-      while (g_match_info_matches (match_info)) {
-        gchar *word = g_match_info_fetch (match_info, 0);
-        accelerator = get_accl_hw_type (word);
-        g_free (word);
-        break;
-      }
-    }
-    g_match_info_free (match_info);
-    g_regex_unref (nnapi_elem);
-  } else  {
-    goto use_nnapi_ini;
+  for (int i = 0; i < inputTensorMeta.num_tensors; ++i) {
+    tensor_idx = interpreter->inputs ()[i];
+    tensor_ptr = interpreter->tensor (tensor_idx);
+
+    g_assert (tensor_ptr->bytes == input[i].size);
+    tensor_ptr->data.raw = (char *) input[i].data;
+    tensors_idx.push_back (tensor_idx);
   }
 
-  return;
+#ifdef ENABLE_TFLITE_NNAPI_DELEGATE
+  if (use_nnapi)
+    status = nnfw_delegate->Invoke (interpreter.get());
+  else
+#endif
+    status = interpreter->Invoke ();
 
-use_nnapi_ini:
-  use_nnapi = nnsconf_get_custom_value_bool ("tensorflowlite", "enable_nnapi",
-      FALSE);
-  if (use_nnapi == FALSE) {
-    accelerator = ACCL_NONE;
-  } else {
-    accelerator = ACCL_AUTO;
+  /** if it is not `nullptr`, tensorflow makes `free()` the memory itself. */
+  int tensorSize = tensors_idx.size ();
+  for (int i = 0; i < tensorSize; ++i) {
+    interpreter->tensor (tensors_idx[i])->data.raw = nullptr;
   }
-}
 
-/**
- * @brief      initialize the object with tflite model
- * @return 0 if OK. non-zero if error.
- *        -1 if the model is not loaded.
- *        -2 if the initialization of input tensor is failed.
- *        -3 if the initialization of output tensor is failed.
- */
-int
-TFLiteCore::init ()
-{
-  if (loadModel ()) {
-    g_critical ("Failed to load model\n");
+#if (DBG)
+  gint64 stop_time = g_get_real_time ();
+  g_message ("Invoke() is finished: %" G_GINT64_FORMAT,
+      (stop_time - start_time));
+#endif
+
+  if (status != kTfLiteOk) {
+    g_critical ("Failed to invoke");
     return -1;
   }
-  if (setInputTensorProp ()) {
-    g_critical ("Failed to initialize input tensor\n");
-    return -2;
-  }
-  if (setOutputTensorProp ()) {
-    g_critical ("Failed to initialize output tensor\n");
-    return -3;
-  }
-  return 0;
-}
 
-/**
- * @brief      get the model path
- * @return the model path.
- */
-const char *
-TFLiteCore::getModelPath ()
-{
-  return model_path;
+  return 0;
 }
 
 /**
- * @brief      load the tflite model
- * @note       the model will be loaded
+ * @brief Internal implementation of TFLiteCore's loadModel()
  * @return 0 if OK. non-zero if error.
  */
 int
-TFLiteCore::loadModel ()
+TFLiteInterpreter::loadModel (bool use_nnapi)
 {
 #if (DBG)
   gint64 start_time = g_get_real_time ();
 #endif
 
+  if (!g_file_test (model_path, G_FILE_TEST_IS_REGULAR)) {
+    g_critical ("the file of model_path (%s) is not valid (not regular)\n", model_path);
+    return -1;
+  }
+  model = tflite::FlatBufferModel::BuildFromFile (model_path);
+  if (!model) {
+    g_critical ("Failed to mmap model\n");
+    return -1;
+  }
+  /* If got any trouble at model, active below code. It'll be help to analyze. */
+  /* model->error_reporter (); */
+
+  interpreter = nullptr;
+
+  tflite::ops::builtin::BuiltinOpResolver resolver;
+  tflite::InterpreterBuilder (*model, resolver) (&interpreter);
   if (!interpreter) {
-    if (!g_file_test (model_path, G_FILE_TEST_IS_REGULAR)) {
-      g_critical ("the file of model_path (%s) is not valid (not regular)\n", model_path);
-      return -1;
-    }
-    model =
-        std::unique_ptr <tflite::FlatBufferModel>
-        (tflite::FlatBufferModel::BuildFromFile (model_path));
-    if (!model) {
-      g_critical ("Failed to mmap model\n");
-      return -1;
-    }
-    /* If got any trouble at model, active below code. It'll be help to analyze. */
-    /* model->error_reporter (); */
-
-    tflite::ops::builtin::BuiltinOpResolver resolver;
-    tflite::InterpreterBuilder (*model, resolver) (&interpreter);
-    if (!interpreter) {
-      g_critical ("Failed to construct interpreter\n");
-      return -2;
-    }
+    g_critical ("Failed to construct interpreter\n");
+    return -2;
+  }
 
-    interpreter->UseNNAPI(use_nnapi);
+  interpreter->UseNNAPI (use_nnapi);
 
 #ifdef ENABLE_TFLITE_NNAPI_DELEGATE
-    if (use_nnapi) {
-      nnfw_delegate.reset (new ::nnfw::tflite::NNAPIDelegate);
-      if (nnfw_delegate->BuildGraph (interpreter.get()) != kTfLiteOk) {
-        g_critical ("Fail to BuildGraph");
-        return -3;
-      }
+  if (use_nnapi) {
+    nnfw_delegate.reset (new ::nnfw::tflite::NNAPIDelegate);
+    if (nnfw_delegate->BuildGraph (interpreter) != kTfLiteOk) {
+      g_critical ("Fail to BuildGraph");
+      return -3;
     }
+  }
 #endif
 
-    /** set allocation type to dynamic for in/out tensors */
-    int tensor_idx;
+  /** set allocation type to dynamic for in/out tensors */
+  int tensor_idx;
 
-    int tensorSize = interpreter->inputs ().size ();
-    for (int i = 0; i < tensorSize; ++i) {
-      tensor_idx = interpreter->inputs ()[i];
-      interpreter->tensor (tensor_idx)->allocation_type = kTfLiteDynamic;
-    }
+  int tensorSize = interpreter->inputs ().size ();
+  for (int i = 0; i < tensorSize; ++i) {
+    tensor_idx = interpreter->inputs ()[i];
+    interpreter->tensor (tensor_idx)->allocation_type = kTfLiteDynamic;
+  }
 
-    tensorSize = interpreter->outputs ().size ();
-    for (int i = 0; i < tensorSize; ++i) {
-      tensor_idx = interpreter->outputs ()[i];
-      interpreter->tensor (tensor_idx)->allocation_type = kTfLiteDynamic;
-    }
+  tensorSize = interpreter->outputs ().size ();
+  for (int i = 0; i < tensorSize; ++i) {
+    tensor_idx = interpreter->outputs ()[i];
+    interpreter->tensor (tensor_idx)->allocation_type = kTfLiteDynamic;
+  }
 
-    if (interpreter->AllocateTensors () != kTfLiteOk) {
-      g_critical ("Failed to allocate tensors\n");
-      return -2;
-    }
+  if (interpreter->AllocateTensors () != kTfLiteOk) {
+    g_critical ("Failed to allocate tensors\n");
+    return -2;
   }
 #if (DBG)
   gint64 stop_time = g_get_real_time ();
@@ -248,7 +223,7 @@ TFLiteCore::loadModel ()
  * @return the enum of defined _NNS_TYPE
  */
 tensor_type
-TFLiteCore::getTensorType (TfLiteType tfType)
+TFLiteInterpreter::getTensorType (TfLiteType tfType)
 {
   switch (tfType) {
     case kTfLiteFloat32:
@@ -271,13 +246,38 @@ TFLiteCore::getTensorType (TfLiteType tfType)
 }
 
 /**
+ * @brief      return the Dimension of Tensor.
+ * @param tensor_idx   : the real index of model of the tensor
+ * @param[out] dim     : the array of the tensor
+ * @return 0 if OK. non-zero if error.
+ * @note assume that the interpreter lock was already held.
+ */
+int
+TFLiteInterpreter::getTensorDim (int tensor_idx, tensor_dim dim)
+{
+  TfLiteIntArray *tensor_dims = interpreter->tensor (tensor_idx)->dims;
+  int len = tensor_dims->size;
+  g_assert (len <= NNS_TENSOR_RANK_LIMIT);
+
+  /* the order of dimension is reversed at CAPS negotiation */
+  std::reverse_copy (tensor_dims->data, tensor_dims->data + len, dim);
+
+  /* fill the remnants with 1 */
+  for (int i = len; i < NNS_TENSOR_RANK_LIMIT; ++i) {
+    dim[i] = 1;
+  }
+
+  return 0;
+}
+
+/**
  * @brief extract and store the information of given tensor list
  * @param tensor_idx_list list of index of tensors in tflite interpreter
  * @param[out] tensorMeta tensors to set the info into
  * @return 0 if OK. non-zero if error.
  */
 int
-TFLiteCore::setTensorProp (const std::vector<int> &tensor_idx_list,
+TFLiteInterpreter::setTensorProp (const std::vector<int> &tensor_idx_list,
     GstTensorsInfo * tensorMeta)
 {
   tensorMeta->num_tensors = tensor_idx_list.size ();
@@ -306,7 +306,7 @@ TFLiteCore::setTensorProp (const std::vector<int> &tensor_idx_list,
  * @return 0 if OK. non-zero if error.
  */
 int
-TFLiteCore::setInputTensorProp ()
+TFLiteInterpreter::setInputTensorProp ()
 {
   return setTensorProp (interpreter->inputs (), &inputTensorMeta);
 }
@@ -316,72 +316,22 @@ TFLiteCore::setInputTensorProp ()
  * @return 0 if OK. non-zero if error.
  */
 int
-TFLiteCore::setOutputTensorProp ()
+TFLiteInterpreter::setOutputTensorProp ()
 {
   return setTensorProp (interpreter->outputs (), &outputTensorMeta);
 }
 
 /**
- * @brief      return the Dimension of Tensor.
- * @param tensor_idx   : the real index of model of the tensor
- * @param[out] dim     : the array of the tensor
- * @return 0 if OK. non-zero if error.
- */
-int
-TFLiteCore::getTensorDim (int tensor_idx, tensor_dim dim)
-{
-  TfLiteIntArray *tensor_dims = interpreter->tensor (tensor_idx)->dims;
-  int len = tensor_dims->size;
-  g_assert (len <= NNS_TENSOR_RANK_LIMIT);
-
-  /* the order of dimension is reversed at CAPS negotiation */
-  std::reverse_copy (tensor_dims->data, tensor_dims->data + len, dim);
-
-  /* fill the remnants with 1 */
-  for (int i = len; i < NNS_TENSOR_RANK_LIMIT; ++i) {
-    dim[i] = 1;
-  }
-
-  return 0;
-}
-
-/**
- * @brief      return the Dimension of Input Tensor.
- * @param[out] info Structure for tensor info.
- * @todo return whole array rather than index 0
- * @return 0 if OK. non-zero if error.
- */
-int
-TFLiteCore::getInputTensorDim (GstTensorsInfo * info)
-{
-  gst_tensors_info_copy (info, &inputTensorMeta);
-  return 0;
-}
-
-/**
- * @brief      return the Dimension of Tensor.
- * @param[out] info Structure for tensor info.
- * @todo return whole array rather than index 0
- * @return 0 if OK. non-zero if error.
- */
-int
-TFLiteCore::getOutputTensorDim (GstTensorsInfo * info)
-{
-  gst_tensors_info_copy (info, &outputTensorMeta);
-  return 0;
-}
-
-/**
  * @brief set the Dimension for Input Tensor.
  * @param info Structure for input tensor info.
  * @return 0 if OK. non-zero if error.
  * @note rank can be changed dependent on the model
  */
 int
-TFLiteCore::setInputTensorDim (const GstTensorsInfo * info)
+TFLiteInterpreter::setInputTensorsInfo (const GstTensorsInfo * info)
 {
   TfLiteStatus status;
-  const std::vector<int> &input_idx_list = interpreter->inputs ();
+  const std::vector<int> &input_idx_list = interpreter->inputs();
 
   /** Cannot change the number of inputs */
   if (info->num_tensors != input_idx_list.size ())
@@ -416,7 +366,7 @@ TFLiteCore::setInputTensorDim (const GstTensorsInfo * info)
         dims[idx] = tensor_info->dimension[rank - idx - 1];
       }
 
-      status = interpreter->ResizeInputTensor(input_idx_list[tensor_idx], dims);
+      status = interpreter->ResizeInputTensor (input_idx_list[tensor_idx], dims);
       if (status != kTfLiteOk)
         continue;
 
@@ -426,10 +376,9 @@ TFLiteCore::setInputTensorDim (const GstTensorsInfo * info)
     /** return error when none of the ranks worked */
     if (status != kTfLiteOk)
       return -EINVAL;
-
   }
 
-  status = interpreter->AllocateTensors();
+  status = interpreter->AllocateTensors ();
   if (status != kTfLiteOk)
     return -EINVAL;
 
@@ -437,69 +386,320 @@ TFLiteCore::setInputTensorDim (const GstTensorsInfo * info)
 }
 
 /**
- * @brief      run the model with the input.
- * @param[in] input : The array of input tensors
- * @param[out]  output : The array of output tensors
- * @return 0 if OK. non-zero if error.
+ * @brief update the model path
  */
-int
-TFLiteCore::invoke (const GstTensorMemory * input, GstTensorMemory * output)
+void
+TFLiteInterpreter::setModelPath (const char *_model_path)
 {
-#if (DBG)
-  gint64 start_time = g_get_real_time ();
+  if (_model_path) {
+    g_free (model_path);
+    model_path = g_strdup (_model_path);
+  }
+}
+
+/**
+ * @brief Move the ownership of interpreter internal members
+ */
+void
+TFLiteInterpreter::moveInternals (TFLiteInterpreter& interp)
+{
+  interpreter = std::move (interp.interpreter);
+  model = std::move (interp.model);
+#ifdef ENABLE_TFLITE_NNAPI_DELEGATE
+  nnfw_delegate = std::move (interp.nnfw_delegate);
 #endif
+  setModelPath (interp.getModelPath ());
+}
 
-  std::vector <int> tensors_idx;
-  int tensor_idx;
-  TfLiteTensor *tensor_ptr;
-  TfLiteStatus status;
+/**
+ * @brief      TFLiteCore creator
+ * @param      _model_path     : the logical path to '{model_name}.tflite' file
+ * @param      accelerators  : the accelerators property set for this subplugin
+ * @note       the model of _model_path will be loaded simultaneously
+ * @return     Nothing
+ */
+TFLiteCore::TFLiteCore (const char * _model_path, const char * accelerators)
+{
+  g_assert (_model_path != NULL);
 
-  for (int i = 0; i < outputTensorMeta.num_tensors; ++i) {
-    tensor_idx = interpreter->outputs ()[i];
-    tensor_ptr = interpreter->tensor (tensor_idx);
+  interpreter.setModelPath (_model_path);
 
-    g_assert (tensor_ptr->bytes == output[i].size);
-    tensor_ptr->data.raw = (char *) output[i].data;
-    tensors_idx.push_back (tensor_idx);
-  }
+  setAccelerator (accelerators);
 
-  for (int i = 0; i < inputTensorMeta.num_tensors; ++i) {
-    tensor_idx = interpreter->inputs ()[i];
-    tensor_ptr = interpreter->tensor (tensor_idx);
+#if (DBG)
+  g_message ("nnapi = %d, accl = %s", use_nnapi, get_accl_hw_str(accelerator));
+#endif
+}
 
-    g_assert (tensor_ptr->bytes == input[i].size);
-    tensor_ptr->data.raw = (char *) input[i].data;
-    tensors_idx.push_back (tensor_idx);
+void TFLiteCore::setAccelerator (const char * accelerators)
+{
+  GRegex * nnapi_elem;
+  GMatchInfo * match_info;
+
+  if (accelerators == NULL) {
+    goto use_nnapi_ini;
   }
 
-#ifdef ENABLE_TFLITE_NNAPI_DELEGATE
-  if (use_nnapi)
-    status = nnfw_delegate->Invoke (interpreter.get());
-  else
-#endif
-    status = interpreter->Invoke ();
+  /* If set by user, get the precise accelerator */
+  use_nnapi = (bool) g_regex_match_simple (REGEX_ACCL_NNAPI, accelerators,
+      G_REGEX_CASELESS, G_REGEX_MATCH_NOTEMPTY);
+  if (use_nnapi == TRUE) {
+    /** Default to auto mode */
+    accelerator = ACCL_AUTO;
+    nnapi_elem = g_regex_new (REGEX_ACCL_NNAPI_ELEM, G_REGEX_CASELESS,
+        G_REGEX_MATCH_NOTEMPTY, NULL);
 
-  /** if it is not `nullptr`, tensorflow makes `free()` the memory itself. */
-  int tensorSize = tensors_idx.size ();
-  for (int i = 0; i < tensorSize; ++i) {
-    interpreter->tensor (tensors_idx[i])->data.raw = nullptr;
+    /** Now match each provided element and get specific accelerator */
+    if (g_regex_match (nnapi_elem, accelerators, G_REGEX_MATCH_NOTEMPTY,
+          &match_info)) {
+
+      while (g_match_info_matches (match_info)) {
+        gchar *word = g_match_info_fetch (match_info, 0);
+        accelerator = get_accl_hw_type (word);
+        g_free (word);
+        break;
+      }
+    }
+    g_match_info_free (match_info);
+    g_regex_unref (nnapi_elem);
+  } else  {
+    goto use_nnapi_ini;
   }
 
-#if (DBG)
-  gint64 stop_time = g_get_real_time ();
-  g_message ("Invoke() is finished: %" G_GINT64_FORMAT,
-      (stop_time - start_time));
-#endif
+  return;
 
-  if (status != kTfLiteOk) {
-    g_critical ("Failed to invoke");
+use_nnapi_ini:
+  use_nnapi = nnsconf_get_custom_value_bool ("tensorflowlite", "enable_nnapi",
+      FALSE);
+  if (use_nnapi == FALSE) {
+    accelerator = ACCL_NONE;
+  } else {
+    accelerator = ACCL_AUTO;
+  }
+}
+
+/**
+ * @brief      initialize the object with tflite model
+ * @return 0 if OK. non-zero if error.
+ *        -1 if the model is not loaded.
+ *        -2 if the initialization of input tensor is failed.
+ *        -3 if the initialization of output tensor is failed.
+ */
+int
+TFLiteCore::init ()
+{
+  if (loadModel ()) {
+    g_critical ("Failed to load model\n");
     return -1;
   }
+  if (setInputTensorProp ()) {
+    g_critical ("Failed to initialize input tensor\n");
+    return -2;
+  }
+  if (setOutputTensorProp ()) {
+    g_critical ("Failed to initialize output tensor\n");
+    return -3;
+  }
+  return 0;
+}
+
+/**
+ * @brief      compare the model path
+ * @return TRUE if tflite core has the same model path
+ */
+gboolean
+TFLiteCore::compareModelPath (const char *model_path)
+{
+  gboolean is_same;
+
+  interpreter.lock ();
+  is_same = (g_strcmp0 (model_path, interpreter.getModelPath ()) == 0);
+  interpreter.unlock ();
+
+  return is_same;
+}
+
+/**
+ * @brief      load the tflite model
+ * @note       the model will be loaded
+ * @return 0 if OK. non-zero if error.
+ */
+int
+TFLiteCore::loadModel ()
+{
+  int err;
+
+  interpreter.lock ();
+  err = interpreter.loadModel (use_nnapi);
+  interpreter.unlock ();
+
+  return err;
+}
+
+/**
+ * @brief extract and store the information of input tensors
+ * @return 0 if OK. non-zero if error.
+ */
+int
+TFLiteCore::setInputTensorProp ()
+{
+  int err;
+
+  interpreter.lock ();
+  err = interpreter.setInputTensorProp ();
+  interpreter.unlock ();
+
+  return err;
+}
+
+/**
+ * @brief extract and store the information of output tensors
+ * @return 0 if OK. non-zero if error.
+ */
+int
+TFLiteCore::setOutputTensorProp ()
+{
+  int err;
+
+  interpreter.lock ();
+  err = interpreter.setOutputTensorProp ();
+  interpreter.unlock ();
+
+  return err;
+}
+
+/**
+ * @brief      return the Dimension of Input Tensor.
+ * @param[out] info Structure for tensor info.
+ * @todo return whole array rather than index 0
+ * @return 0 if OK. non-zero if error.
+ */
+int
+TFLiteCore::getInputTensorDim (GstTensorsInfo * info)
+{
+  interpreter.lock ();
+  gst_tensors_info_copy (info, interpreter.getInputTensorsInfo());
+  interpreter.unlock ();
+
+  return 0;
+}
+
+/**
+ * @brief      return the Dimension of Tensor.
+ * @param[out] info Structure for tensor info.
+ * @todo return whole array rather than index 0
+ * @return 0 if OK. non-zero if error.
+ */
+int
+TFLiteCore::getOutputTensorDim (GstTensorsInfo * info)
+{
+  interpreter.lock ();
+  gst_tensors_info_copy (info, interpreter.getOutputTensorsInfo());
+  interpreter.unlock ();
 
   return 0;
 }
 
 /**
+ * @brief set the Dimension for Input Tensor.
+ * @param info Structure for input tensor info.
+ * @return 0 if OK. non-zero if error.
+ * @note rank can be changed dependent on the model
+ */
+int
+TFLiteCore::setInputTensorDim (const GstTensorsInfo * info)
+{
+  int err;
+
+  interpreter.lock ();
+  err = interpreter.setInputTensorsInfo (info);
+  interpreter.unlock ();
+
+  return err;
+}
+
+/**
+ * @brief      reload a model
+ * @param      tflite  : the class object
+ * @param[in] model_path : the path of model file
+ * @return 0 if OK. non-zero if error.
+ * @note reloadModel() is asynchronously called with other callbacks. But, it requires
+ *       extra memory size enough to temporarily hold both models during this function.
+ */
+int
+TFLiteCore::reloadModel (const char * _model_path)
+{
+  int err;
+
+  interpreter_sub.lock ();
+  interpreter_sub.setModelPath (_model_path);
+
+  /**
+   * load a model into sub interpreter. This loading overhead is indenendent
+   * with main one's activities.
+   */
+  err = interpreter_sub.loadModel (use_nnapi);
+  if (err != 0) {
+    g_critical ("Failed to load model %s\n", _model_path);
+    goto out_unlock;
+  }
+  err = interpreter_sub.setInputTensorProp ();
+  if (err != 0) {
+    g_critical ("Failed to initialize input tensor\n");
+    goto out_unlock;
+  }
+  err = interpreter_sub.setOutputTensorProp ();
+  if (err != 0) {
+    g_critical ("Failed to initialize output tensor\n");
+    goto out_unlock;
+  }
+
+  /* Also, we need to check input/output tensors have the same info */
+  if (!gst_tensors_info_is_equal (
+        interpreter.getInputTensorsInfo (),
+        interpreter_sub.getInputTensorsInfo ()) ||
+      !gst_tensors_info_is_equal (
+        interpreter.getOutputTensorsInfo (),
+        interpreter_sub.getOutputTensorsInfo ())) {
+    g_critical ("The model has unmatched tensors info\n");
+    err = -EINVAL;
+    goto out_unlock;
+  }
+
+  /**
+   * Everything is ready. let's move the model in sub interpreter to main one.
+   * But, it needs to wait if main interpreter is busy (e.g., invoke()).
+   */
+  interpreter.lock ();
+  interpreter.moveInternals (interpreter_sub);
+  /* after this, all callbacks will handle operations for the reloaded model */
+  interpreter.unlock ();
+
+out_unlock:
+  interpreter_sub.unlock ();
+
+  return err;
+}
+
+/**
+ * @brief      run the model with the input.
+ * @param[in] input : The array of input tensors
+ * @param[out]  output : The array of output tensors
+ * @return 0 if OK. non-zero if error.
+ */
+int
+TFLiteCore::invoke (const GstTensorMemory * input, GstTensorMemory * output)
+{
+  int err;
+
+  interpreter.lock ();
+  err = interpreter.invoke (input, output, use_nnapi);
+  interpreter.unlock ();
+
+  return err;
+}
+
+/**
  * @brief      call the creator of TFLiteCore class.
  * @param      _model_path     : the logical path to '{model_name}.tffile' file
  * @param accelerators : the accelerators property set for this subplugin
@@ -536,15 +736,15 @@ tflite_core_init (void * tflite)
 }
 
 /**
- * @brief      get the model path
+ * @brief      compare the model path
  * @param      tflite  : the class object
- * @return the model path.
+ * @return TRUE if tflite core has the same model path
  */
-const char *
-tflite_core_getModelPath (void * tflite)
+gboolean
+tflite_core_compareModelPath (void * tflite, const char * model_path)
 {
   TFLiteCore *c = (TFLiteCore *) tflite;
-  return c->getModelPath ();
+  return c->compareModelPath (model_path);
 }
 
 /**
@@ -629,6 +829,19 @@ tflite_core_setInputDim (void * tflite, const GstTensorsInfo * in_info,
 }
 
 /**
+ * @brief      reload a model
+ * @param      tflite  : the class object
+ * @param[in] model_path : the path of model file
+ * @return 0 if OK. non-zero if error.
+ */
+int
+tflite_core_reloadModel (void * tflite, const char * model_path)
+{
+  TFLiteCore *c = (TFLiteCore *) tflite;
+  return c->reloadModel (model_path);
+}
+
+/**
  * @brief      invoke the model
  * @param      tflite  : the class object
  * @param[in] input : The array of input tensors
index 44ec838..1ac9d6b 100644 (file)
 #endif
 
 /**
- * @brief      ring cache structure
+ * @brief Wrapper class for TFLite Interpreter to support model switching
  */
-class TFLiteCore
+class TFLiteInterpreter
 {
 public:
-  TFLiteCore (const char *_model_path, const char *accelerators);
-  ~TFLiteCore ();
+  TFLiteInterpreter ();
+  ~TFLiteInterpreter ();
+
+  int invoke (const GstTensorMemory * input, GstTensorMemory * output, bool use_nnapi);
+  int loadModel (bool use_nnapi);
+  void moveInternals (TFLiteInterpreter& interp);
 
-  int init ();
-  int loadModel ();
-  const char* getModelPath ();
   int setInputTensorProp ();
   int setOutputTensorProp ();
-  int getInputTensorDim (GstTensorsInfo * info);
-  int getOutputTensorDim (GstTensorsInfo * info);
-  int setInputTensorDim (const GstTensorsInfo * info);
-  int invoke (const GstTensorMemory * input, GstTensorMemory * output);
+  int setInputTensorsInfo (const GstTensorsInfo * info);
 
-private:
+  void setModelPath (const char *model_path);
+  /** @brief get current model path */
+  const char *getModelPath () { return model_path; }
 
-  char *model_path;
-  bool use_nnapi;
-  accl_hw accelerator;
+  /** @brief return input tensor meta */
+  const GstTensorsInfo* getInputTensorsInfo () { return &inputTensorMeta; }
+  /** @brief return output tensor meta */
+  const GstTensorsInfo* getOutputTensorsInfo () { return &outputTensorMeta; }
 
-  GstTensorsInfo inputTensorMeta;  /**< The tensor info of input tensors */
-  GstTensorsInfo outputTensorMeta;  /**< The tensor info of output tensors */
+  /** @brief lock this interpreter */
+  void lock () { g_mutex_lock (&mutex); }
+  /** @brief unlock this interpreter */
+  void unlock () { g_mutex_unlock (&mutex); }
+
+private:
+  GMutex mutex;
+  char *model_path;
 
   std::unique_ptr <tflite::Interpreter> interpreter;
   std::unique_ptr <tflite::FlatBufferModel> model;
-
 #ifdef ENABLE_TFLITE_NNAPI_DELEGATE
   std::unique_ptr <nnfw::tflite::NNAPIDelegate> nnfw_delegate;
 #endif
 
+  GstTensorsInfo inputTensorMeta;  /**< The tensor info of input tensors */
+  GstTensorsInfo outputTensorMeta;  /**< The tensor info of output tensors */
+
   tensor_type getTensorType (TfLiteType tfType);
   int getTensorDim (int tensor_idx, tensor_dim dim);
   int setTensorProp (const std::vector<int> &tensor_idx_list,
       GstTensorsInfo * tensorMeta);
+};
+
+/**
+ * @brief      ring cache structure
+ */
+class TFLiteCore
+{
+public:
+  TFLiteCore (const char *_model_path, const char *accelerators);
+
+  int init ();
+  int loadModel ();
+  gboolean compareModelPath (const char *model_path);
+  int setInputTensorProp ();
+  int setOutputTensorProp ();
+  int getInputTensorDim (GstTensorsInfo * info);
+  int getOutputTensorDim (GstTensorsInfo * info);
+  int setInputTensorDim (const GstTensorsInfo * info);
+  int reloadModel (const char * model_path);
+  int invoke (const GstTensorMemory * input, GstTensorMemory * output);
+
+private:
+  bool use_nnapi;
+  accl_hw accelerator;
+
+  TFLiteInterpreter interpreter;
+  TFLiteInterpreter interpreter_sub;
 
   void setAccelerator (const char * accelerators);
 };
@@ -90,11 +126,12 @@ extern "C"
   void *tflite_core_new (const char *_model_path, const char *accelerators);
   void tflite_core_delete (void * tflite);
   int tflite_core_init (void * tflite);
-  const char *tflite_core_getModelPath (void * tflite);
+  gboolean tflite_core_compareModelPath (void * tflite, const char * model_path);
   int tflite_core_getInputDim (void * tflite, GstTensorsInfo * info);
   int tflite_core_getOutputDim (void * tflite, GstTensorsInfo * info);
   int tflite_core_setInputDim (void * tflite, const GstTensorsInfo * in_info,
       GstTensorsInfo * out_info);
+  int tflite_core_reloadModel (void * tflite, const char * model_path);
   int tflite_core_invoke (void * tflite, const GstTensorMemory * input,
       GstTensorMemory * output);
 
index 2d3637e..ae7e3bd 100644 (file)
@@ -204,8 +204,7 @@ typedef struct _GstTensorFilterFramework
        */
 
   int (*reloadModel) (const GstTensorFilterProperties * prop, void **private_data);
-      /**< Optional. tensor_filter.c will call it when a model property is newly configured.
-       * @todo add more detail comments.
+      /**< Optional. tensor_filter.c will call it when a model property is newly configured. Also, 'is-updatable' property of the framework should be TRUE. This function reloads a new model specified in the 'prop' argument. Note that it requires extra memory size enough to temporarily hold both old and new models during this function to hide the reload overhead.
        *
        * @param[in] prop read-only property values
        * @param[in/out] private_data A subplugin may save its internal private data here. The subplugin is responsible for alloc/free of this pointer. Normally, close() frees private_data and set NULL.