[vivante] Refactor the updated vivante code
authorYongjoo Ahn <yongjoo1.ahn@samsung.com>
Tue, 10 Jun 2025 05:24:43 +0000 (14:24 +0900)
committerYongjoo Ahn <yongjoo1.ahn@samsung.com>
Thu, 12 Jun 2025 06:00:49 +0000 (15:00 +0900)
- Refactor and fix memory leaks

Signed-off-by: Yongjoo Ahn <yongjoo1.ahn@samsung.com>
src/hal-backend-ml-vivante.cc

index 4fb56f4c2b4012ac9e61998e506217e3cbf845e2..951a4a7157801030bd8988d6d43e8d5701a3e044 100644 (file)
@@ -1,7 +1,5 @@
 /* SPDX-License-Identifier: Apache-2.0 */
 
-#include <stdexcept>
-
 #include <dlfcn.h>
 #include <glib.h>
 #include <json-glib/json-glib.h>
 #include "hal-backend-ml-util.h"
 
 
-/* Helper to convert VSI type string from JSON to vsi_nn_type_e */
-static vsi_nn_type_e
-vivante_vsi_type_from_string (const gchar * vsi_type_str)
-{
-  if (!vsi_type_str) return VSI_NN_TYPE_NONE;
-
-  /** @todo Add 4-bit types when it's available */
+/**
+ * @brief Private handle for the Vivante instance.
+ */
+typedef struct _vivante_handle_s {
+  char *model_path; /* .nb file path */
+  char *so_path; /* .so file path (for .so based model loading) */
+  char *json_path; /* .json file path (for JSON based model loading) */
+  gboolean use_json_for_graph;
+  gboolean has_post_process; /** @deprecated Do not use it. */
 
-  if (g_ascii_strcasecmp (vsi_type_str, "VSI_NN_TYPE_INT8") == 0) return VSI_NN_TYPE_INT8;
-  if (g_ascii_strcasecmp (vsi_type_str, "VSI_NN_TYPE_INT16") == 0) return VSI_NN_TYPE_INT16;
-  if (g_ascii_strcasecmp (vsi_type_str, "VSI_NN_TYPE_INT32") == 0) return VSI_NN_TYPE_INT32;
-  if (g_ascii_strcasecmp (vsi_type_str, "VSI_NN_TYPE_INT64") == 0) return VSI_NN_TYPE_INT64;
+  GstTensorsInfo inputInfo;
+  GstTensorsInfo outputInfo;
 
-  if (g_ascii_strcasecmp (vsi_type_str, "VSI_NN_TYPE_UINT8") == 0) return VSI_NN_TYPE_UINT8;
-  if (g_ascii_strcasecmp (vsi_type_str, "VSI_NN_TYPE_UINT16") == 0) return VSI_NN_TYPE_UINT16;
-  if (g_ascii_strcasecmp (vsi_type_str, "VSI_NN_TYPE_UINT32") == 0) return VSI_NN_TYPE_UINT32;
-  if (g_ascii_strcasecmp (vsi_type_str, "VSI_NN_TYPE_UINT64") == 0) return VSI_NN_TYPE_UINT64;
+  vsi_nn_graph_t *graph;
 
-  if (g_ascii_strcasecmp (vsi_type_str, "VSI_NN_TYPE_FLOAT16") == 0) return VSI_NN_TYPE_FLOAT16;
-  if (g_ascii_strcasecmp (vsi_type_str, "VSI_NN_TYPE_FLOAT32") == 0) return VSI_NN_TYPE_FLOAT32;
-  if (g_ascii_strcasecmp (vsi_type_str, "VSI_NN_TYPE_FLOAT64") == 0) return VSI_NN_TYPE_FLOAT64;
-  if (g_ascii_strcasecmp (vsi_type_str, "VSI_NN_TYPE_BFLOAT16") == 0) return VSI_NN_TYPE_BFLOAT16;
+  /* Handles for JSON based model loading */
+  vsi_nn_context_t ctx;
 
-  if (g_ascii_strcasecmp (vsi_type_str, "VSI_NN_TYPE_BOOL8") == 0) return VSI_NN_TYPE_BOOL8;
+  /* Handles for .so based model loading */
+  void *dl_handle; /* dlopened model so */
+  vsi_nn_graph_t *(*model_specific_vnn_CreateNeuralNetwork) (const char *);
+  void (*model_specific_vnn_ReleaseNeuralNetwork) (vsi_nn_graph_t *);
+  vsi_status (*model_specific_vnn_PostProcessNeuralNetwork) (vsi_nn_graph_t *); /** @deprecated */
+} vivante_handle_s;
 
-  g_warning ("[vivante backend] Unknown VSI tensor type string from JSON: %s", vsi_type_str);
+/* ===================================================================
+ * Forward Declarations of Static Helper Functions
+ * ===================================================================
+ */
+static void _json_release_neural_network (vivante_handle_s *self);
+static int _json_create_neural_network (vivante_handle_s *self);
+static int _so_create_neural_network (vivante_handle_s *self);
+
+/* ===================================================================
+ * Type Conversion Helpers
+ * ===================================================================
+ */
+/** @brief Converts VSI type string from JSON to vsi_nn_type_e enum. */
+static vsi_nn_type_e
+vivante_vsi_type_from_string (const gchar *vsi_type_str)
+{
+  if (!vsi_type_str)
+    return VSI_NN_TYPE_NONE;
+
+  /** @todo Add 4-bit types when available */
+  if (g_ascii_strcasecmp (vsi_type_str, "VSI_NN_TYPE_INT8") == 0)
+    return VSI_NN_TYPE_INT8;
+  if (g_ascii_strcasecmp (vsi_type_str, "VSI_NN_TYPE_UINT8") == 0)
+    return VSI_NN_TYPE_UINT8;
+  if (g_ascii_strcasecmp (vsi_type_str, "VSI_NN_TYPE_INT16") == 0)
+    return VSI_NN_TYPE_INT16;
+  if (g_ascii_strcasecmp (vsi_type_str, "VSI_NN_TYPE_UINT16") == 0)
+    return VSI_NN_TYPE_UINT16;
+  if (g_ascii_strcasecmp (vsi_type_str, "VSI_NN_TYPE_INT32") == 0)
+    return VSI_NN_TYPE_INT32;
+  if (g_ascii_strcasecmp (vsi_type_str, "VSI_NN_TYPE_UINT32") == 0)
+    return VSI_NN_TYPE_UINT32;
+  if (g_ascii_strcasecmp (vsi_type_str, "VSI_NN_TYPE_INT64") == 0)
+    return VSI_NN_TYPE_INT64;
+  if (g_ascii_strcasecmp (vsi_type_str, "VSI_NN_TYPE_UINT64") == 0)
+    return VSI_NN_TYPE_UINT64;
+  if (g_ascii_strcasecmp (vsi_type_str, "VSI_NN_TYPE_FLOAT16") == 0)
+    return VSI_NN_TYPE_FLOAT16;
+  if (g_ascii_strcasecmp (vsi_type_str, "VSI_NN_TYPE_FLOAT32") == 0)
+    return VSI_NN_TYPE_FLOAT32;
+  if (g_ascii_strcasecmp (vsi_type_str, "VSI_NN_TYPE_FLOAT64") == 0)
+    return VSI_NN_TYPE_FLOAT64;
+  if (g_ascii_strcasecmp (vsi_type_str, "VSI_NN_TYPE_BFLOAT16") == 0)
+    return VSI_NN_TYPE_BFLOAT16;
+  if (g_ascii_strcasecmp (vsi_type_str, "VSI_NN_TYPE_BOOL8") == 0)
+    return VSI_NN_TYPE_BOOL8;
+
+  g_warning ("[vivante] Unknown VSI tensor type string from JSON: %s", vsi_type_str);
   return VSI_NN_TYPE_NONE;
 }
 
-/* Helper to convert VSI quantization type string from JSON to vsi_nn_qnt_type_e */
+/** @brief Converts VSI quantization type string from JSON to vsi_nn_qnt_type_e enum. */
 static vsi_nn_qnt_type_e
-vivante_qnt_type_from_string (const gchar * qnt_str)
+vivante_qnt_type_from_string (const gchar *qnt_str)
 {
-  if (!qnt_str) return VSI_NN_QNT_TYPE_NONE;
-  if (g_ascii_strcasecmp (qnt_str, "VSI_NN_QNT_TYPE_NONE") == 0) return VSI_NN_QNT_TYPE_NONE;
-  if (g_ascii_strcasecmp (qnt_str, "VSI_NN_QNT_TYPE_DFP") == 0) return VSI_NN_QNT_TYPE_DFP;
-  if (g_ascii_strcasecmp (qnt_str, "VSI_NN_QNT_TYPE_AFFINE_ASYMMETRIC") == 0) return VSI_NN_QNT_TYPE_AFFINE_ASYMMETRIC;
-  if (g_ascii_strcasecmp (qnt_str, "VSI_NN_QNT_TYPE_AFFINE_PERCHANNEL_SYMMETRIC") == 0) return VSI_NN_QNT_TYPE_AFFINE_PERCHANNEL_SYMMETRIC;
-  if (g_ascii_strcasecmp (qnt_str, "VSI_NN_QNT_TYPE_AFFINE_SYMMETRIC") == 0) return VSI_NN_QNT_TYPE_AFFINE_SYMMETRIC;
-
-  /** @todo Add VSI_NN_QNT_TYPE_AFFINE_PERCHANNEL_ASYMMETRIC when it's available */
-
-  g_warning ("[vivante backend] Unknown VSI quantization type string from JSON: %s", qnt_str);
+  if (!qnt_str)
+    return VSI_NN_QNT_TYPE_NONE;
+  if (g_ascii_strcasecmp (qnt_str, "VSI_NN_QNT_TYPE_NONE") == 0)
+    return VSI_NN_QNT_TYPE_NONE;
+  if (g_ascii_strcasecmp (qnt_str, "VSI_NN_QNT_TYPE_DFP") == 0)
+    return VSI_NN_QNT_TYPE_DFP;
+  if (g_ascii_strcasecmp (qnt_str, "VSI_NN_QNT_TYPE_AFFINE_ASYMMETRIC") == 0)
+    return VSI_NN_QNT_TYPE_AFFINE_ASYMMETRIC;
+  if (g_ascii_strcasecmp (qnt_str, "VSI_NN_QNT_TYPE_AFFINE_PERCHANNEL_SYMMETRIC") == 0)
+    return VSI_NN_QNT_TYPE_AFFINE_PERCHANNEL_SYMMETRIC;
+  if (g_ascii_strcasecmp (qnt_str, "VSI_NN_QNT_TYPE_AFFINE_SYMMETRIC") == 0)
+    return VSI_NN_QNT_TYPE_AFFINE_SYMMETRIC;
+
+  /** @todo Add VSI_NN_QNT_TYPE_AFFINE_PERCHANNEL_ASYMMETRIC when available */
+  g_warning ("[vivante] Unknown VSI quantization type string from JSON: %s", qnt_str);
   return VSI_NN_QNT_TYPE_NONE;
 }
 
+/** @brief Converts vsi_nn_type_e to the framework's tensor_type. */
 static tensor_type
-convert_tensortype (vsi_nn_type_e tensor_type)
+convert_to_tensor_type (vsi_nn_type_e vsi_type)
 {
-  switch (tensor_type) {
+  switch (vsi_type) {
     case VSI_NN_TYPE_BOOL8:
     case VSI_NN_TYPE_INT8:
       return _NNS_INT8;
@@ -93,334 +144,297 @@ convert_tensortype (vsi_nn_type_e tensor_type)
     case VSI_NN_TYPE_FLOAT64:
       return _NNS_FLOAT64;
     default:
-      break;
+      g_warning ("[vivante] Unsupported vsi_nn_type_e: %d", vsi_type);
+      return _NNS_END;
   }
-  return _NNS_END;
 }
 
-typedef struct _vivante_handle_s {
-  char *model_path; /* .nb file path */
-  char *so_path;    /* .so file path (for .so based model loading) */
-  char *json_path;  /* .json file path (for JSON based model loading) */
-  gboolean using_json_for_graph_setup;
-
-  GstTensorsInfo inputInfo;
-  GstTensorsInfo outputInfo;
-
-  vsi_nn_graph_t *graph;
-
-  /* handles for json based model loading */
-  vsi_nn_context_t ctx;
-
-  /* handles for .so based model loading */
-  void *handle; /* dlopened model so */
-  vsi_nn_graph_t *(*result_vnn_CreateNeuralNetwork) (const char *);
-  void (*result_vnn_ReleaseNeuralNetwork) (vsi_nn_graph_t *);
-  vsi_status (*postProcessFunc) (vsi_nn_graph_t *);
-  int postProcess; /** @todo Add postProcess custom prop */
-} vivante_handle_s;
-
+/* ===================================================================
+ * JSON Parsing and Graph Creation Helpers
+ * ===================================================================
+ */
+/** @brief Parses tensor attributes from a JSON object into a vsi_nn_tensor_attr_t struct. */
 static int
-ml_vivante_init (void **backend_private)
+_helper_parse_tensor_attributes (JsonObject *tensor_obj, vsi_nn_tensor_attr_t *vsi_attr)
 {
-  vivante_handle_s *vivante = g_new0 (vivante_handle_s, 1);
+  memset (vsi_attr, 0, sizeof (vsi_nn_tensor_attr_t));
+  vsi_attr->vtl = FALSE;
+  vsi_attr->is_const = FALSE;
+
+  // Size and dim_num
+  JsonArray *size_array = json_object_get_array_member (tensor_obj, "size");
+  if (!size_array) {
+    g_critical ("[vivante] Tensor in JSON missing 'size' array.");
+    return HAL_ML_ERROR_INVALID_PARAMETER;
+  }
+  vsi_attr->dim_num = json_array_get_length (size_array);
+  if (vsi_attr->dim_num == 0 || vsi_attr->dim_num > VSI_NN_MAX_DIM_NUM) {
+    g_critical ("[vivante] Invalid tensor 'dim_num': %u", vsi_attr->dim_num);
+    return HAL_ML_ERROR_INVALID_PARAMETER;
+  }
+  for (guint i = 0; i < vsi_attr->dim_num; ++i) {
+    vsi_attr->size[i] = json_array_get_int_element (size_array, i);
+  }
 
-  gst_tensors_info_init (&vivante->inputInfo);
-  gst_tensors_info_init (&vivante->outputInfo);
+  // Dtype object
+  JsonObject *dtype_obj = json_object_get_object_member (tensor_obj, "dtype");
+  if (!dtype_obj) {
+    g_critical ("[vivante] Tensor in JSON missing 'dtype' object.");
+    return HAL_ML_ERROR_INVALID_PARAMETER;
+  }
+
+  // Required: vx_type
+  const gchar *vx_type_str = json_object_get_string_member (dtype_obj, "vx_type");
+  if (!vx_type_str) {
+    g_critical ("[vivante] 'dtype' missing required 'vx_type' key.");
+    return HAL_ML_ERROR_INVALID_PARAMETER;
+  }
+  vsi_attr->dtype.vx_type = vivante_vsi_type_from_string (vx_type_str);
 
-  vivante->using_json_for_graph_setup = TRUE;
+  // Optional fields with defaults
+  vsi_attr->dtype.qnt_type
+      = vivante_qnt_type_from_string (json_object_get_string_member_with_default (
+          dtype_obj, "qnt_type", "VSI_NN_QNT_TYPE_NONE"));
+  vsi_attr->dtype.fl = json_object_get_int_member_with_default (dtype_obj, "fl", 0);
+  vsi_attr->dtype.zero_point
+      = json_object_get_int_member_with_default (dtype_obj, "zero_point", 0);
+  vsi_attr->dtype.scale
+      = json_object_get_double_member_with_default (dtype_obj, "scale", 0.0f);
+
+  /** @todo Parse scales, scale_dim, etc. for PERCHANNEL quantization */
 
-  *backend_private = vivante;
   return HAL_ML_ERROR_NONE;
 }
 
+/** @brief Creates and sets up the neural network graph using a JSON definition file. */
 static int
 _json_create_neural_network (vivante_handle_s *self)
 {
+  g_autofree gchar *json_string = NULL;
+  g_autoptr (GError) err = NULL;
+  g_autoptr (JsonParser) parser = NULL;
+  JsonNode *root_node = NULL;
+  JsonObject *root_obj = NULL;
+  JsonArray *input_array = NULL, *output_array = NULL;
+  guint input_tensors_num = 0, output_tensors_num = 0;
+  vsi_nn_node_t *node = NULL;
+
   self->ctx = vsi_nn_CreateContext ();
-  if (self->ctx == NULL) {
-    g_critical ("[vivante backend] Failed to create context");
+  if (!self->ctx) {
+    g_critical ("[vivante] Failed to create VSI context.");
     return HAL_ML_ERROR_RUNTIME_ERROR;
   }
 
-  // 1. Get string from the json file
-  g_autofree gchar *json_string = NULL;
-  if (!g_file_get_contents (self->json_path, &json_string, NULL, NULL)) {
-    g_critical ("[vivante backend] Failed to read JSON file %s", self->json_path);
+  if (!g_file_get_contents (self->json_path, &json_string, NULL, &err)) {
+    g_critical ("[vivante] Failed to read JSON file '%s': %s", self->json_path,
+        err ? err->message : "Unknown error");
     return HAL_ML_ERROR_IO_ERROR;
   }
 
-  // 2. Parse json string
-  g_autoptr (GError) err = NULL;
-  g_autoptr (JsonParser) parser = json_parser_new ();
-  if (!parser) {
-    g_critical ("[vivante backend] Failed to create JsonParser. OOM?");
-    return HAL_ML_ERROR_OUT_OF_MEMORY;
-  }
-
+  parser = json_parser_new ();
   if (!json_parser_load_from_data (parser, json_string, -1, &err)) {
-    g_critical ("[vivante backend] Failed to parse json. error: %s", err ? err->message : "Unknown error");
+    g_critical ("[vivante] Failed to parse JSON: %s", err ? err->message : "Unknown error");
     return HAL_ML_ERROR_INVALID_PARAMETER;
   }
 
-  // 3. Get the root node and the object
-  JsonNode *root;
-  JsonObject *root_obj;
-  root = json_parser_get_root (parser);
-  if (!root) {
-    g_critical ("[vivante backend] Failed to get root node");
-    return HAL_ML_ERROR_RUNTIME_ERROR;
-  }
-
-  if (!JSON_NODE_HOLDS_OBJECT (root)) {
-    g_critical ("[vivante backend] Root node is not an object");
+  root_node = json_parser_get_root (parser);
+  if (!root_node || !JSON_NODE_HOLDS_OBJECT (root_node)) {
+    g_critical ("[vivante] JSON root is not a valid object.");
     return HAL_ML_ERROR_INVALID_PARAMETER;
   }
+  root_obj = json_node_get_object (root_node);
 
-  root_obj = json_node_get_object (root);
-  if (!root_obj) {
-    g_critical ("[vivante backend] Failed to get object from root node");
-    return HAL_ML_ERROR_RUNTIME_ERROR;
-  }
-
-  // 4. Get the number of inputs and outputs
-  guint input_tensors_num = 0U;
-  guint output_tensors_num = 0U;
-
-  if (!json_object_has_member (root_obj, "input_tensors")) {
-    g_critical ("[vivante backend] Missing necessary key 'input_tensors' in JSON");;
-    return HAL_ML_ERROR_INVALID_PARAMETER;
-  }
-
-  JsonArray *input_array = json_object_get_array_member (root_obj, "input_tensors");
-  if (!input_array) {
-    g_critical ("[vivante backend] Failed to get array from 'input_tensors'");
+  input_array = json_object_get_array_member (root_obj, "input_tensors");
+  output_array = json_object_get_array_member (root_obj, "output_tensors");
+  if (!input_array || !output_array) {
+    g_critical ("[vivante] JSON must contain 'input_tensors' and 'output_tensors' arrays.");
     return HAL_ML_ERROR_INVALID_PARAMETER;
   }
 
   input_tensors_num = json_array_get_length (input_array);
-
-  if (!json_object_has_member (root_obj, "output_tensors")) {
-    g_critical ("[vivante backend] Missing necessary array 'output_tensors' in JSON");;
-    return HAL_ML_ERROR_INVALID_PARAMETER;
-  }
-
-  JsonArray *output_array = json_object_get_array_member (root_obj, "output_tensors");
-  if (!output_array) {
-    g_critical ("[vivante backend] Failed to get array from 'output_tensors'");
-    return HAL_ML_ERROR_INVALID_PARAMETER;
-  }
-
   output_tensors_num = json_array_get_length (output_array);
 
-  // 5. Set up graphs
-  vsi_nn_graph_t *graph = vsi_nn_CreateGraph (self->ctx, input_tensors_num + output_tensors_num + output_tensors_num, 1 /* NBG node */);
-  if (graph == NULL) {
-    g_critical ("[vivante backend] Failed to create graph");
-    return HAL_ML_ERROR_RUNTIME_ERROR;
-  }
+  const guint node_num = 1U; /* single NBG node */
+  const guint const_tensors_num = 0U; /** @todo support this */
+  const guint normal_tensors_num = input_tensors_num + output_tensors_num;
+  const guint virtual_tensors_num = output_tensors_num;
 
-  if (!vsi_nn_SetGraphInputs (graph, NULL, input_tensors_num)) {
-    g_critical ("[vivante backend] Failed to set graph inputs");
-    return HAL_ML_ERROR_RUNTIME_ERROR;
+  self->graph = vsi_nn_CreateGraph (self->ctx,
+      normal_tensors_num + virtual_tensors_num + const_tensors_num, node_num);
+  if (!self->graph) {
+    g_critical ("[vivante] Failed to create VSI graph.");
+    goto cleanup;
   }
-  if (!vsi_nn_SetGraphOutputs (graph, NULL, output_tensors_num)) {
-    g_critical ("[vivante backend] Failed to set graph outputs");
-    return HAL_ML_ERROR_RUNTIME_ERROR;
+
+  if (!vsi_nn_SetGraphInputs (self->graph, NULL, input_tensors_num)
+      || !vsi_nn_SetGraphOutputs (self->graph, NULL, output_tensors_num)) {
+    g_critical ("[vivante] Failed to set graph inputs/outputs.");
+    goto cleanup;
   }
 
-  // 6. Set a single vsi_nn_node
-  /** @todo Assuming there is only 1 node for each nb model. Find any exceptions. */
-  vsi_nn_node_t *node = NULL;
-  node = vsi_nn_AddNode(graph, VSI_NN_OP_NBG, input_tensors_num, output_tensors_num, NULL);
+  node = vsi_nn_AddNode (
+      self->graph, VSI_NN_OP_NBG, input_tensors_num, output_tensors_num, NULL);
   if (!node) {
-    g_critical ("[vivante backend] Failed to add node");
-    return HAL_ML_ERROR_RUNTIME_ERROR;
+    g_critical ("[vivante] Failed to add NBG node to graph.");
+    goto cleanup;
   }
-
   node->uid = 0;
   node->nn_param.nbg.type = VSI_NN_NBG_FILE;
   node->nn_param.nbg.url = self->model_path;
 
-  auto _parse_tensor_attributes_from_json = [&] (JsonObject *tensor_obj, vsi_nn_tensor_attr_t *vsi_attr) -> int {
-    memset (vsi_attr, 0, sizeof (vsi_nn_tensor_attr_t));
-    if (json_object_has_member (tensor_obj, "id")) {
-      // do something?
-    }
-
-    // initialize some boolean attributes
-    vsi_attr->vtl = FALSE;
-    vsi_attr->is_const = FALSE;
-    vsi_attr->is_created_from_handle = FALSE;
-    vsi_attr->is_handle_malloc_by_ovxlib = FALSE;
-
-    // parse size and dim_num
-    if (!json_object_has_member (tensor_obj, "size")) {
-    }
-    JsonArray *size_array = json_object_get_array_member (tensor_obj, "size");
-    if (!size_array) {
-      g_critical ("[vivante backend] Failed to get array from 'size' in JSON");
-      return HAL_ML_ERROR_INVALID_PARAMETER;
-    }
-
-    vsi_attr->dim_num = json_array_get_length (size_array);
-    if (vsi_attr->dim_num == 0U || vsi_attr->dim_num > VSI_NN_MAX_DIM_NUM) {
-      g_critical ("[vivante backend] Invalid value for 'size' in JSON");
-      return HAL_ML_ERROR_INVALID_PARAMETER;
-    }
-
-    for (guint i = 0; i < vsi_attr->dim_num; ++i) {
-      gint64 dim = json_array_get_int_element (size_array, i);
-      if (dim <= 0) {
-        g_critical ("[vivante backend] Invalid value for 'dim' in JSON");
-        return HAL_ML_ERROR_INVALID_PARAMETER;
-      }
-
-      vsi_attr->size[i] = (vsi_size_t) dim;
-    }
-
-    // parse dtype
-    if (!json_object_has_member (tensor_obj, "dtype")) {
-      g_critical ("[vivante backend] Missing necessary key 'dtype' in JSON");
-      return HAL_ML_ERROR_INVALID_PARAMETER;
-    }
-
-    JsonObject *dtype_obj = json_object_get_object_member (tensor_obj, "dtype");
-
-    // parse dtype.fmt
-    vsi_attr->dtype.fmt = VSI_NN_DIM_FMT_NCHW;
-    if (!json_object_has_member (dtype_obj, "fmt")) {
-      g_warning ("[vivante backend] Missing key 'fmt' in 'dtype' in JSON. Default: 'VSI_NN_DIM_FMT_NCHW'");
-    } else {
-      // @todo parse dtype.fmt
-    }
-
-    // parse dtype.vx_type
-    if (!json_object_has_member (dtype_obj, "vx_type")) {
-      g_critical ("[vivante backend] Missing necessary key 'vx_type' in 'dtype' in JSON");
-      return HAL_ML_ERROR_INVALID_PARAMETER;
-    } else {
-      vsi_attr->dtype.vx_type = vivante_vsi_type_from_string (json_object_get_string_member (dtype_obj, "vx_type"));
-    }
-
-    // parse dtype.qnt_type
-    vsi_attr->dtype.qnt_type = VSI_NN_QNT_TYPE_NONE;
-    if (!json_object_has_member (dtype_obj, "qnt_type")) {
-      g_warning ("[vivante backend] Missing key 'qnt_type' in 'dtype' in JSON. Default: 'VSI_NN_QNT_TYPE_NONE'");
-    } else {
-      vsi_attr->dtype.qnt_type = vivante_qnt_type_from_string (json_object_get_string_member (dtype_obj, "qnt_type"));
-    }
-
-    // parse dtype.fl for dynamic fixed point
-    vsi_attr->dtype.fl = 0;
-    if (!json_object_has_member (dtype_obj, "fl")) {
-      g_warning ("[vivante backend] Missing key 'fl' in 'dtype' in JSON. Default: 0");
-    } else {
-      vsi_attr->dtype.fl = (int8_t) json_object_get_int_member (dtype_obj, "fl");
-    }
-
-    // parse dtype.zero_point for affine asymmetric
-    vsi_attr->dtype.zero_point = 0;
-    if (!json_object_has_member (dtype_obj, "zero_point")) {
-      g_warning ("[vivante backend] Missing key 'zero_point' in 'dtype' in JSON. Default: 0");
-    } else {
-      vsi_attr->dtype.zero_point = (int32_t) json_object_get_int_member (dtype_obj, "zero_point");
-    }
-
-    // parse dtype.scale for affine asymmetric
-    vsi_attr->dtype.scale = 0.0f;
-    if (!json_object_has_member (dtype_obj, "scale")) {
-      g_warning ("[vivante backend] Missing key 'scale' in 'dtype' in JSON. Default: 0.0f");
-    } else {
-      vsi_attr->dtype.scale = (float) json_object_get_double_member (dtype_obj, "scale");
-    }
-
-    /** @todo parse scales, scale_dim, channel_dim, zero_points, zero_points_dim for AFFINE_PERCHANNEL_SYMMETRIC */
-
-    return HAL_ML_ERROR_NONE;
-  };
-
-  // 7. Set up input tensors
+  // Set up input tensors
   for (guint i = 0; i < input_tensors_num; ++i) {
     vsi_nn_tensor_attr_t tensor_attr;
 
     // parse attr data from json
     JsonObject *tensor_obj = json_array_get_object_element (input_array, i);
-    if (_parse_tensor_attributes_from_json (tensor_obj, &tensor_attr) != HAL_ML_ERROR_NONE) {
-      g_critical ("[vivante backend] Failed to parse tensor attributes from JSON");
-      return HAL_ML_ERROR_INVALID_PARAMETER;
+    if (_helper_parse_tensor_attributes (tensor_obj, &tensor_attr) != HAL_ML_ERROR_NONE) {
+      g_critical ("[vivante] Failed to parse tensor attributes from JSON");
+      goto cleanup;
     }
 
     // Add the tensor to the graph
-    vsi_nn_tensor_id_t vsi_input_id = vsi_nn_AddTensor (graph, VSI_NN_TENSOR_ID_AUTO, &tensor_attr, NULL);
+    vsi_nn_tensor_id_t vsi_input_id
+        = vsi_nn_AddTensor (self->graph, VSI_NN_TENSOR_ID_AUTO, &tensor_attr, NULL);
     if (vsi_input_id == VSI_NN_TENSOR_ID_NA) {
-      g_critical ("[vivante backend] Failed to add input tensor #%u", i);
-      return HAL_ML_ERROR_RUNTIME_ERROR;
+      g_critical ("[vivante] Failed to add input tensor #%u", i);
+      goto cleanup;
     }
 
-    g_info ("[vivante backend] Added input tensor #%u with id %u", i, vsi_input_id);
+    g_info ("[vivante] Added input tensor #%u with id %u", i, vsi_input_id);
     node->input.tensors[i] = vsi_input_id;
-    graph->input.tensors[i] = vsi_input_id;
+    self->graph->input.tensors[i] = vsi_input_id;
   }
 
   for (guint i = 0; i < input_tensors_num; ++i) {
-    g_info ("[vivante backend] print Input tensor #%u:", graph->input.tensors[i]);
-    vsi_nn_tensor_t *tensor = vsi_nn_GetTensor (graph, graph->input.tensors[i]);
-    vsi_nn_PrintTensor (tensor, graph->input.tensors[i]);
+    g_info ("[vivante] print Input tensor #%u:", self->graph->input.tensors[i]);
+    vsi_nn_tensor_t *tensor
+        = vsi_nn_GetTensor (self->graph, self->graph->input.tensors[i]);
+    vsi_nn_PrintTensor (tensor, self->graph->input.tensors[i]);
   }
 
-  // 8. Set up output tensors
+  // Set up output tensors
   for (guint i = 0; i < output_tensors_num; ++i) {
     vsi_nn_tensor_attr_t tensor_attr;
 
     // parse attr data from json
     JsonObject *tensor_obj = json_array_get_object_element (output_array, i);
-    if (_parse_tensor_attributes_from_json (tensor_obj, &tensor_attr) != HAL_ML_ERROR_NONE) {
-      g_critical ("[vivante backend] Failed to parse tensor attributes from JSON");
-      return HAL_ML_ERROR_INVALID_PARAMETER;
+    if (_helper_parse_tensor_attributes (tensor_obj, &tensor_attr) != HAL_ML_ERROR_NONE) {
+      g_critical ("[vivante] Failed to parse tensor attributes from JSON");
+      goto cleanup;
     }
 
     // Add the tensor to the graph
-    vsi_nn_tensor_id_t vsi_output_id = vsi_nn_AddTensor (graph, VSI_NN_TENSOR_ID_AUTO, &tensor_attr, NULL);
+    vsi_nn_tensor_id_t vsi_output_id
+        = vsi_nn_AddTensor (self->graph, VSI_NN_TENSOR_ID_AUTO, &tensor_attr, NULL);
     if (vsi_output_id == VSI_NN_TENSOR_ID_NA) {
-      g_critical ("[vivante backend] Failed to add output tensor #%u", i);
-      return HAL_ML_ERROR_RUNTIME_ERROR;
+      g_critical ("[vivante] Failed to add output tensor #%u", i);
+      goto cleanup;
     }
 
-    g_info ("[vivante backend] Added output tensor #%u with id %u", i, vsi_output_id);
+    g_info ("[vivante] Added output tensor #%u with id %u", i, vsi_output_id);
     node->output.tensors[i] = vsi_output_id;
-    graph->output.tensors[i] = vsi_output_id;
+    self->graph->output.tensors[i] = vsi_output_id;
   }
 
   for (guint i = 0; i < output_tensors_num; ++i) {
-    g_info ("[vivante backend] print output tensor #%u:", graph->output.tensors[i]);
-    vsi_nn_tensor_t *tensor = vsi_nn_GetTensor (graph, graph->output.tensors[i]);
-    vsi_nn_PrintTensor (tensor, graph->output.tensors[i]);
+    g_info ("[vivante] print output tensor #%u:", self->graph->output.tensors[i]);
+    vsi_nn_tensor_t *tensor
+        = vsi_nn_GetTensor (self->graph, self->graph->output.tensors[i]);
+    vsi_nn_PrintTensor (tensor, self->graph->output.tensors[i]);
   }
 
-  // 9. Setup graph
-  if (vsi_nn_SetupGraph (graph, FALSE) != VSI_SUCCESS) {
-    g_critical ("[vivante backend] Failed to setup graph");
-    return HAL_ML_ERROR_RUNTIME_ERROR;
+  // setup graph
+  if (vsi_nn_SetupGraph (self->graph, FALSE) != VSI_SUCCESS) {
+    g_critical ("[vivante] Failed to setup VSI graph.");
+    goto cleanup;
   }
 
-  self->graph = graph;
-
   return HAL_ML_ERROR_NONE;
+
+cleanup:
+  _json_release_neural_network (self);
+  return HAL_ML_ERROR_RUNTIME_ERROR;
 }
 
-static int
+/** @brief Releases all resources associated with a JSON-based graph. */
+static void
 _json_release_neural_network (vivante_handle_s *self)
 {
+  if (self->graph) {
+    vsi_nn_ReleaseGraph (&self->graph);
+    self->graph = NULL;
+  }
   if (self->ctx) {
     vsi_nn_ReleaseContext (&self->ctx);
     self->ctx = NULL;
   }
+}
 
-  if (self->graph) {
-    vsi_nn_ReleaseGraph (&self->graph);
-    self->graph = NULL;
+/* ===================================================================
+ * Shared Library (.so) Loading Helpers
+ * ===================================================================
+ */
+
+/** @brief Creates the neural network from a pre-compiled shared library. */
+static int
+_so_create_neural_network (vivante_handle_s *self)
+{
+  self->dl_handle = dlopen (self->so_path, RTLD_NOW);
+  if (!self->dl_handle) {
+    g_critical ("[vivante] Failed to load shared library '%s': %s",
+        self->so_path, dlerror ());
+    return HAL_ML_ERROR_RUNTIME_ERROR;
+  }
+
+  self->model_specific_vnn_CreateNeuralNetwork = (vsi_nn_graph_t * (*) (const char *) )
+      dlsym (self->dl_handle, "vnn_CreateNeuralNetwork");
+  self->model_specific_vnn_ReleaseNeuralNetwork
+      = (void (*) (vsi_nn_graph_t *)) dlsym (self->dl_handle, "vnn_ReleaseNeuralNetwork");
+
+  if (!self->model_specific_vnn_CreateNeuralNetwork
+      || !self->model_specific_vnn_ReleaseNeuralNetwork) {
+    g_critical ("[vivante] Could not find required symbols in '%s'", self->so_path);
+    dlclose (self->dl_handle);
+    self->dl_handle = NULL;
+    return HAL_ML_ERROR_RUNTIME_ERROR;
+  }
+
+  if (self->has_post_process) {
+    self->model_specific_vnn_PostProcessNeuralNetwork = (vsi_status (*) (
+        vsi_nn_graph_t *)) dlsym (self->dl_handle, "vnn_PostProcessNeuralNetwork");
+    if (!self->model_specific_vnn_PostProcessNeuralNetwork) {
+      g_warning ("[vivante] 'postProcess' was requested, but symbol 'vnn_PostProcessNeuralNetwork' not found.");
+      self->has_post_process = FALSE;
+    }
+  }
+
+  self->graph = self->model_specific_vnn_CreateNeuralNetwork (self->model_path);
+  if (!self->graph) {
+    g_critical ("[vivante] vnn_CreateNeuralNetwork failed for model '%s'", self->model_path);
+    return HAL_ML_ERROR_RUNTIME_ERROR;
   }
+
+  return HAL_ML_ERROR_NONE;
+}
+
+/* ===================================================================
+ * Main HAL Implementation Functions
+ * ===================================================================
+ */
+static int
+ml_vivante_init (void **backend_private)
+{
+  vivante_handle_s *vivante = g_new0 (vivante_handle_s, 1);
+
+  gst_tensors_info_init (&vivante->inputInfo);
+  gst_tensors_info_init (&vivante->outputInfo);
+
+  vivante->use_json_for_graph = TRUE;
+  vivante->has_post_process = FALSE;
+
+  *backend_private = vivante;
+  return HAL_ML_ERROR_NONE;
 }
 
 static int
@@ -429,18 +443,19 @@ ml_vivante_deinit (void *backend_private)
   vivante_handle_s *vivante = (vivante_handle_s *) backend_private;
 
   if (!vivante) {
-    g_critical ("[vivante backend] invalid backend_private");
+    g_critical ("[vivante] invalid backend_private");
     return HAL_ML_ERROR_INVALID_PARAMETER;
   }
 
-  if (vivante->using_json_for_graph_setup) {
+  if (vivante->use_json_for_graph) {
     _json_release_neural_network (vivante);
   } else {
-    if (vivante->graph)
-      vivante->result_vnn_ReleaseNeuralNetwork (vivante->graph);
-
-    if (vivante->handle)
-      dlclose (vivante->handle);
+    if (vivante->graph && vivante->model_specific_vnn_ReleaseNeuralNetwork) {
+      vivante->model_specific_vnn_ReleaseNeuralNetwork (vivante->graph);
+    }
+    if (vivante->dl_handle) {
+      dlclose (vivante->dl_handle);
+    }
   }
 
   gst_tensors_info_free (&vivante->inputInfo);
@@ -460,11 +475,17 @@ ml_vivante_configure_instance (void *backend_private, const void *prop_)
   const GstTensorFilterProperties *prop = (const GstTensorFilterProperties *) prop_;
   vivante_handle_s *vivante = (vivante_handle_s *) backend_private;
 
-  if (!vivante) {
-    g_critical ("[vivante backend] invalid backend_private");
+  if (!vivante || !prop) {
+    g_critical ("[vivante] invalid backend_private");
     return HAL_ML_ERROR_INVALID_PARAMETER;
   }
 
+  vivante->model_path = g_strdup (prop->model_files[0]);
+  // Default loading strategy: if more than one model file is given, assume .so loading.
+  if (prop->num_models > 1) {
+    vivante->use_json_for_graph = FALSE;
+  }
+
   /* Parse custom properties */
   if (prop->custom_properties) {
     gchar **options = g_strsplit (prop->custom_properties, ",", -1);
@@ -477,9 +498,9 @@ ml_vivante_configure_instance (void *backend_private, const void *prop_)
         g_strstrip (option[1]);
 
         if (g_ascii_strcasecmp (option[0], "json") == 0) {
-          vivante->using_json_for_graph_setup = TRUE;
+          vivante->use_json_for_graph = TRUE;
           vivante->json_path = g_strdup (option[1]);
-          g_info ("[vivante backend] Using JSON for graph setup with JSON file: %s", option[1]);
+          g_info ("[vivante] Using JSON for graph setup: %s", vivante->json_path);
         } else {
           g_warning ("Unknown option (%s).", options[op]);
         }
@@ -491,58 +512,34 @@ ml_vivante_configure_instance (void *backend_private, const void *prop_)
     g_strfreev (options);
   }
 
-  vivante->model_path = g_strdup (prop->model_files[0]);
-  if (prop->num_models > 1) {
-    vivante->using_json_for_graph_setup = FALSE;
-  }
-
-
-  /* do model loading */
-  if (vivante->using_json_for_graph_setup) {
-    /* .json based initializing */
-    int error = _json_create_neural_network (vivante);
-    if (error != HAL_ML_ERROR_NONE) {
-      g_critical ("[vivante backend] Failed to create neural network from JSON");
-      _json_release_neural_network (vivante);
-      return error;
+  /* Load model based on the determined strategy JSON vs so */
+  if (vivante->use_json_for_graph) {
+    if (!vivante->json_path || !g_file_test (vivante->json_path, G_FILE_TEST_IS_REGULAR)) {
+      g_critical ("[vivante] JSON loading was selected, but no JSON path was provided via 'json:' custom property.");
+      return HAL_ML_ERROR_INVALID_PARAMETER;
+    }
+    if (_json_create_neural_network (vivante) != HAL_ML_ERROR_NONE) {
+      g_critical ("[vivante] Failed to create VSI graph.");
     }
-    g_info ("[vivante backend] Model loaded successfully from JSON!");
   } else {
-    /* setup .so */
-    vivante->so_path = g_strdup (prop->model_files[1]);
-
-    vivante->handle = dlopen (vivante->so_path, RTLD_NOW);
-    if (!vivante->handle) {
-      g_critical ("Failed to load shared library: %s", vivante->so_path);
-      return HAL_ML_ERROR_RUNTIME_ERROR;
+    if (prop->num_models <= 1) {
+      g_critical ("[vivante] .so loading requires a second model file (the .so path).");
+      return HAL_ML_ERROR_INVALID_PARAMETER;
     }
-
-    /* get symbols */
-    vivante->result_vnn_ReleaseNeuralNetwork = (void (*) (vsi_nn_graph_t *)) dlsym (
-        vivante->handle, "vnn_ReleaseNeuralNetwork");
-    vivante->result_vnn_CreateNeuralNetwork = (vsi_nn_graph_t * (*) (const char *) )
-        dlsym (vivante->handle, "vnn_CreateNeuralNetwork");
-
-    if (vivante->postProcess) {
-      vivante->postProcessFunc = (vsi_status (*) (vsi_nn_graph_t *)) dlsym (
-          vivante->handle, "vnn_PostProcessNeuralNetwork");
+    vivante->so_path = g_strdup (prop->model_files[1]);
+    if (_so_create_neural_network (vivante) != HAL_ML_ERROR_NONE) {
+      g_critical ("[vivante] Failed to create VSI graph.");
     }
-
-    /* .so based initializing */
-    vivante->graph = vivante->result_vnn_CreateNeuralNetwork (vivante->model_path);
   }
 
   /* setting input and output tensors info */
-  gst_tensors_info_init (&vivante->inputInfo);
-  gst_tensors_info_init (&vivante->outputInfo);
-
   vivante->inputInfo.num_tensors = vivante->graph->input.num;
   for (unsigned int i = 0; i < vivante->graph->input.num; i++) {
     vsi_nn_tensor_t *i_tensor
         = vsi_nn_GetTensor (vivante->graph, vivante->graph->input.tensors[i]);
     GstTensorInfo *info = gst_tensors_info_get_nth_info (&vivante->inputInfo, i);
 
-    info->type = convert_tensortype (i_tensor->attr.dtype.vx_type);
+    info->type = convert_to_tensor_type (i_tensor->attr.dtype.vx_type);
     info->name = g_strdup_printf ("%i", vivante->graph->input.tensors[i]);
     for (unsigned int j = 0; j < i_tensor->attr.dim_num; ++j) {
       info->dimension[j] = i_tensor->attr.size[j];
@@ -555,7 +552,7 @@ ml_vivante_configure_instance (void *backend_private, const void *prop_)
         = vsi_nn_GetTensor (vivante->graph, vivante->graph->output.tensors[i]);
     GstTensorInfo *info = gst_tensors_info_get_nth_info (&vivante->outputInfo, i);
 
-    info->type = convert_tensortype (o_tensor->attr.dtype.vx_type);
+    info->type = convert_to_tensor_type (o_tensor->attr.dtype.vx_type);
     info->name = g_strdup_printf ("%i", vivante->graph->output.tensors[i]);
     for (unsigned int j = 0; j < o_tensor->attr.dim_num; ++j) {
       info->dimension[j] = o_tensor->attr.size[j];
@@ -573,7 +570,7 @@ ml_vivante_invoke (void *backend_private, const void *input_, void *output_)
   vivante_handle_s *vivante = (vivante_handle_s *) backend_private;
 
   if (!vivante) {
-    g_critical ("[vivante backend] invalid backend_private");
+    g_critical ("[vivante] invalid backend_private");
     return HAL_ML_ERROR_INVALID_PARAMETER;
   }
 
@@ -581,18 +578,18 @@ ml_vivante_invoke (void *backend_private, const void *input_, void *output_)
     vsi_nn_tensor_t *tensor
         = vsi_nn_GetTensor (vivante->graph, vivante->graph->input.tensors[i]);
     if (vsi_nn_CopyDataToTensor (vivante->graph, tensor, (uint8_t *) input[i].data) != VSI_SUCCESS) {
-      g_critical ("[vivante backend] Failed to copy data to tensor");
+      g_critical ("[vivante] Failed to copy data to tensor");
       return HAL_ML_ERROR_RUNTIME_ERROR;
     }
   }
 
   if (vsi_nn_RunGraph (vivante->graph) != VSI_SUCCESS) {
-    g_critical ("[vivante backend] Failed to run graph");
+    g_critical ("[vivante] Failed to run graph");
     return HAL_ML_ERROR_RUNTIME_ERROR;
   }
 
-  if (vivante->postProcess)
-    vivante->postProcessFunc (vivante->graph);
+  if (vivante->has_post_process)
+    vivante->model_specific_vnn_PostProcessNeuralNetwork (vivante->graph);
 
   for (unsigned int i = 0; i < vivante->graph->output.num; i++) {
     vsi_nn_tensor_t *out_tensor
@@ -619,26 +616,20 @@ ml_vivante_get_framework_info (void *backend_private, void *fw_info)
 }
 
 static int
-ml_vivante_get_model_info (void *backend_private, int ops_, void *in_info_, void *out_info_)
+ml_vivante_get_model_info (void *backend_private, int ops, void *in_info, void *out_info)
 {
-  int ops = (model_info_ops) ops_;
-  GstTensorsInfo *in_info = (GstTensorsInfo *) in_info_;
-  GstTensorsInfo *out_info = (GstTensorsInfo *) out_info_;
   vivante_handle_s *vivante = (vivante_handle_s *) backend_private;
-
-  if (!vivante) {
-    g_critical ("[vivante backend] invalid backend_private");
+  if (!vivante)
     return HAL_ML_ERROR_INVALID_PARAMETER;
-  }
 
-  gst_tensors_info_copy (in_info, &vivante->inputInfo);
-  gst_tensors_info_copy (out_info, &vivante->outputInfo);
+  gst_tensors_info_copy ((GstTensorsInfo *) in_info, &vivante->inputInfo);
+  gst_tensors_info_copy ((GstTensorsInfo *) out_info, &vivante->outputInfo);
 
   return HAL_ML_ERROR_NONE;
 }
 
 static int
-ml_vivante_event_handler (void *backend_private, int ops_, void *data_)
+ml_vivante_event_handler (void *backend_private, int ops, void *data)
 {
   return HAL_ML_ERROR_NOT_SUPPORTED;
 }