[UnitTest/Model] Revise model and add its unittests
authorDongju Chae <dongju.chae@samsung.com>
Tue, 14 Apr 2020 08:30:03 +0000 (17:30 +0900)
committer송욱/On-Device Lab(SR)/Staff Engineer/삼성전자 <wook16.song@samsung.com>
Thu, 16 Apr 2020 08:08:54 +0000 (17:08 +0900)
This patch revises model and add its unittests

Signed-off-by: Dongju Chae <dongju.chae@samsung.com>
src/core/ne-handler.cc
src/core/ne-model.cc
src/core/ne-model.h
tests/unittests/meson.build
tests/unittests/ne_core_model_test.cc [new file with mode: 0644]

index b2bf3d6..828aadf 100644 (file)
@@ -702,9 +702,7 @@ HostHandler::setDataInfo (uint32_t modelid, tensors_data_info *in,
   if (model == nullptr)
     return -ENOENT;
 
-  model->setDataInfo (in, out);
-
-  return 0;
+  return model->setDataInfo (in, out);
 }
 
 /**
index 0fb1fc3..f7808e9 100644 (file)
 
 #include <string.h>
 
-std::atomic<uint32_t> Model::global_model_id_ (0);
+#define TAG _N71
+
+std::atomic<uint32_t> Model::global_model_id_ (1);
+
+/**
+ * @brief constructor of metadata class
+ */
+Metadata::Metadata (npubin_meta *meta)
+  : meta_ (meta)
+{
+  version_ = NPUBIN_VERSION (meta->magiccode);
+  if (version_ == 0)  /* backward-compatability */
+    version_ = 1;
+}
 
 /**
  * @brief extract metadata from model binary data
@@ -28,20 +41,124 @@ Metadata::extractMetadata (void *data)
   npubin_meta * meta_data = static_cast <npubin_meta *> (data);
 
   /** check whether it's npu binary */
-  if (!CHECK_NPUBIN (meta_data->magiccode))
+  if (!CHECK_NPUBIN (meta_data->magiccode)) {
+    logerr (TAG, "Non-compatible binary format\n");
     return nullptr;
+  }
+
+  std::unique_ptr<Metadata> meta_ptr;
+  Metadata * meta_ins = nullptr;
 
-  std::unique_ptr<Metadata> meta (nullptr);
   switch (NPUBIN_VERSION (meta_data->magiccode)) {
   case 0:
   case 1:
-    meta.reset (new Metadata_v1 (meta_data));
+    meta_ins = new Metadata_v1 (meta_data);
     break;
   case 2:
-    meta.reset (new Metadata_v2 (meta_data));
+    meta_ins = new Metadata_v2 (meta_data);
+    break;
+  default:
+    logerr (TAG, "Invalid NPU binary format version: %d\n",
+        NPUBIN_VERSION (meta_data->magiccode));
     break;
   }
-  return meta;
+
+  if (meta_ins != nullptr && !meta_ins->checkSanity ()) {
+    delete meta_ins;
+    meta_ins = nullptr;
+  }
+
+  meta_ptr.reset (meta_ins);
+  return meta_ptr;
+}
+
+/** @brief constructor of npubinfmt v1 */
+Metadata_v1::Metadata_v1 (npubin_meta *meta)
+  : Metadata (meta)
+{
+  /** set dummy input/output dimensions as metadata v1 does not have dimension info. */
+  for (uint32_t idx = 0; idx < MAX_TENSORS; idx++) {
+    input_dims[idx][0] = getInputTensorSize (idx, DATA_LAYOUT_SRNPU);
+    output_dims[idx][0] = getOutputTensorSize (idx, DATA_LAYOUT_SRNPU);
+    for (uint32_t rank_idx = 1; rank_idx < MAX_RANK; rank_idx++) {
+      input_dims[idx][rank_idx] = 1;
+      output_dims[idx][rank_idx] = 1;
+    }
+  }
+}
+
+/** @brief sanity check for npubinfmt v1 */
+bool
+Metadata_v1::checkSanity () const
+{
+  if (getVersion () != 1)
+    return false;
+
+  return getSize () == getMetaSize () + getProgramSize () + getWeightSize ();
+}
+
+/** @brief constructor of npubinfmt v2 */
+Metadata_v2::Metadata_v2 (npubin_meta *meta)
+  : Metadata (meta)
+{
+}
+
+/** @brief sanity check for npubinfmt v2 */
+bool
+Metadata_v2::checkSanity () const
+{
+  if (getVersion () != 2)
+    return false;
+  if (getInputNum () > MAX_TENSORS)
+    return false;
+  if (getOutputNum () > MAX_TENSORS)
+    return false;
+
+  return getSize () == getMetaSize () + getProgramSize () + getWeightSize ();
+}
+
+/** @brief calculate tensor size depending on specified layout */
+uint32_t
+Metadata_v2::getInputTensorSize (uint32_t idx, data_layout layout) const
+{
+  assert (idx < getInputNum ());
+
+  const uint32_t *dims = getInputDims (idx);
+  uint32_t elem_size = getInputElemSize (idx);
+  uint32_t tensor_size = elem_size;
+
+  for (uint32_t rank_idx = 0; rank_idx < MAX_RANK; rank_idx++)
+    tensor_size *= dims[rank_idx];
+
+  /** special handling for TRIV */
+  if (layout == DATA_LAYOUT_SRNPU && dims[3] != 3 &&
+      dims[3] % DATA_GRANULARITY != 0) {
+    tensor_size *= (1 + dims[3] / DATA_GRANULARITY);
+  }
+
+  return tensor_size;
+}
+
+/** @brief calculate tensor size depending on specified layout */
+uint32_t
+Metadata_v2::getOutputTensorSize (uint32_t idx, data_layout layout) const
+{
+  assert (idx < getOutputNum ());
+
+  const uint32_t *dims = getOutputDims (idx);
+  uint32_t elem_size = getOutputElemSize (idx);
+  uint32_t tensor_size = elem_size;
+
+  for (uint32_t rank_idx = 0; rank_idx < MAX_RANK; rank_idx++)
+    tensor_size *= dims[rank_idx];
+
+  /** special handling for TRIV */
+  if (layout == DATA_LAYOUT_SRNPU && dims[3] != 3 &&
+      dims[3] % DATA_GRANULARITY != 0) {
+    tensor_size *= (1 + dims[3] / DATA_GRANULARITY);
+  }
+
+  return tensor_size;
 }
 
 /** @brief constructor of model class */
@@ -66,12 +183,20 @@ Model::Model (const HWmemImpl* impl)
  * @brief set data info of input/output tensors
  * @param[in] in input tensors' data info
  * @param[in] out output tensors' data info
+ * @return 0 if no error, otherwise a negative errno
  */
-void
+int
 Model::setDataInfo (const tensors_data_info *in, const tensors_data_info *out)
 {
+  if (in == nullptr || out == nullptr) {
+    logerr (TAG, "invalid arguments provided\n");
+    return -EINVAL;
+  }
+
   in_ = *in;
   out_ = *out;
+
+  return 0;
 }
 
 /**
@@ -82,6 +207,9 @@ Model::setDataInfo (const tensors_data_info *in, const tensors_data_info *out)
 int
 Model::setMetadata (void *data)
 {
+  if (data == nullptr)
+    return -EINVAL;
+
   this->meta_ = Metadata::extractMetadata (data);
   if (this->meta_.get() == nullptr)
     return -EINVAL;
@@ -90,6 +218,34 @@ Model::setMetadata (void *data)
 }
 
 /**
+ * @brief get the number of input tensors
+ */
+uint32_t
+Model::getInputTensorNum () const
+{
+  if (meta_.get() == nullptr) {
+    logerr (TAG, "Invalid metadata\n");
+    return 0;
+  }
+
+  return meta_->getInputNum ();
+}
+
+/**
+ * @brief get the number of input tensors
+ */
+uint32_t
+Model::getOutputTensorNum () const
+{
+  if (meta_.get() == nullptr) {
+    logerr (TAG, "Invalid metadata\n");
+    return 0;
+  }
+
+  return meta_->getOutputNum ();
+}
+
+/**
  * @brief get the size of input tensor
  * @param[in] idx input tensor index
  * @return the size of input tensor
@@ -97,6 +253,16 @@ Model::setMetadata (void *data)
 uint32_t
 Model::getInputTensorSize (uint32_t idx) const
 {
+  if (meta_.get() == nullptr) {
+    logerr (TAG, "Invalid metadata\n");
+    return 0;
+  }
+
+  if (in_.num_info <= idx) {
+    logerr (TAG, "Input tensor info mismatch. Do setNPU_dataInfo() first properly\n");
+    return 0;
+  }
+
   data_layout layout = in_.info[idx].layout;
   return meta_->getInputTensorSize (idx, layout);
 }
@@ -109,6 +275,16 @@ Model::getInputTensorSize (uint32_t idx) const
 uint32_t
 Model::getOutputTensorSize (uint32_t idx) const
 {
+  if (meta_.get() == nullptr) {
+    logerr (TAG, "Invalid metadata\n");
+    return 0;
+  }
+
+  if (out_.num_info <= idx) {
+    logerr (TAG, "Output tensor info mismatch. Do setNPU_dataInfo() first properly\n");
+    return 0;
+  }
+
   data_layout layout = out_.info[idx].layout;
   return meta_->getOutputTensorSize (idx, layout);
 }
@@ -120,8 +296,16 @@ Model::getOutputTensorSize (uint32_t idx) const
  */
 const tensor_data_info *
 Model::getInputDataInfo (uint32_t idx) const {
-  if (in_.num_info <= idx)
+  if (meta_.get() == nullptr) {
+    logerr (TAG, "Invalid metadata\n");
+    return nullptr;
+  }
+
+  if (in_.num_info <= idx) {
+    logerr (TAG, "Input tensor info mismatch. Do setNPU_dataInfo() first properly\n");
     return nullptr;
+  }
+
   return &in_.info[idx];
 }
 
@@ -132,8 +316,16 @@ Model::getInputDataInfo (uint32_t idx) const {
  */
 const tensor_data_info *
 Model::getOutputDataInfo (uint32_t idx) const {
-  if (out_.num_info <= idx)
+  if (meta_.get() == nullptr) {
+    logerr (TAG, "Invalid metadata\n");
     return nullptr;
+  }
+
+  if (out_.num_info <= idx) {
+    logerr (TAG, "Output tensor info mismatch. Do setNPU_dataInfo() first properly\n");
+    return nullptr;
+  }
+
   return &out_.info[idx];
 }
 
index 63d3723..09b1fd3 100644 (file)
@@ -25,6 +25,8 @@
 #include <atomic>
 #include <mutex>
 
+#include <assert.h>
+
 /** this assumes uint8 zero quantization */
 #define DEFAULT_ZERO_POINT  (127)
 #define DEFAULT_SCALE       (1.0)
@@ -39,9 +41,11 @@ class Metadata {
   public:
     static std::unique_ptr<Metadata> extractMetadata (void *data);
 
-    Metadata (npubin_meta *meta) : meta_(meta), version_(0) {}
+    Metadata (npubin_meta *meta);
     virtual ~Metadata () {};
 
+    virtual bool checkSanity () const = 0;
+
     virtual uint32_t getInputNum () const = 0;
     virtual uint32_t getOutputNum () const = 0;
 
@@ -69,6 +73,8 @@ class Metadata {
     uint64_t getBufferSize () const { return meta_->buffer_size; }
     uint32_t getMetaSize () const { return NPUBIN_META_TOTAL_SIZE(meta_->magiccode); }
 
+    int getVersion () const { return version_; }
+
   protected:
     npubin_meta *meta_;   /**< user-exposed metadata structure */
     int version_;         /**< metadata version */
@@ -77,17 +83,9 @@ class Metadata {
 /** @brief metadata version 1: support only a single pair of input/ouput tensors */
 class Metadata_v1 : public Metadata {
   public:
-    Metadata_v1 (npubin_meta *meta) : Metadata (meta) {
-      /** set dummy input/output dimensions as metadata v1 does not have dimension info. */
-      for (uint32_t idx = 0; idx < MAX_TENSORS; idx++) {
-        input_dims[idx][0] = getInputTensorSize (idx, DATA_LAYOUT_SRNPU);
-        output_dims[idx][0] = getOutputTensorSize (idx, DATA_LAYOUT_SRNPU);
-        for (uint32_t rank_idx = 1; rank_idx < MAX_RANK; rank_idx++) {
-          input_dims[idx][rank_idx] = 1;
-          output_dims[idx][rank_idx] = 1;
-        }
-      }
-    }
+    Metadata_v1 (npubin_meta *meta);
+
+    bool checkSanity () const;
 
     uint32_t getInputNum () const override { return 1; }
     uint32_t getOutputNum () const override { return 1; }
@@ -138,72 +136,56 @@ class Metadata_v1 : public Metadata {
 /** @brief metadata version 2: support multiple input/output tensors */
 class Metadata_v2 : public Metadata {
   public:
-    Metadata_v2 (npubin_meta *meta) : Metadata (meta) {}
+    Metadata_v2 (npubin_meta *meta);
+
+    bool checkSanity () const;
 
     uint32_t getInputNum () const override { return meta_->input_num; }
     uint32_t getOutputNum () const override { return meta_->output_num; }
 
     uint32_t getInputOffset (uint32_t idx) const override {
+      assert (idx < getInputNum ());
       return meta_->input_offsets[idx];
     }
     uint32_t getOutputOffset (uint32_t idx) const override {
+      assert (idx < getOutputNum ());
       return meta_->output_offsets[idx];
     }
-    uint32_t getInputTensorSize (uint32_t idx, data_layout layout) const override {
-      const uint32_t *dims = getInputDims (idx);
-      uint32_t elem_size = getInputElemSize (idx);
-      uint32_t tensor_size = elem_size;
-
-      for (uint32_t rank_idx = 0; rank_idx < MAX_RANK; rank_idx++)
-        tensor_size *= dims[rank_idx];
-
-      /** special handling for TRIV */
-      if (layout == DATA_LAYOUT_SRNPU && dims[3] != 3 &&
-          dims[3] % DATA_GRANULARITY != 0) {
-        tensor_size *= (1 + dims[3] / DATA_GRANULARITY);
-      }
-
-      return tensor_size;
-    }
-    uint32_t getOutputTensorSize (uint32_t idx, data_layout layout) const override {
-      const uint32_t *dims = getOutputDims (idx);
-      uint32_t elem_size = getOutputElemSize (idx);
-      uint32_t tensor_size = elem_size;
 
-      for (uint32_t rank_idx = 0; rank_idx < MAX_RANK; rank_idx++)
-        tensor_size *= dims[rank_idx];
+    uint32_t getInputTensorSize (uint32_t idx, data_layout layout) const override;
+    uint32_t getOutputTensorSize (uint32_t idx, data_layout layout) const override;
 
-      /** special handling for TRIV */
-      if (layout == DATA_LAYOUT_SRNPU && dims[3] != 3 &&
-          dims[3] % DATA_GRANULARITY != 0) {
-        tensor_size *= (1 + dims[3] / DATA_GRANULARITY);
-      }
-
-      return tensor_size;
-    }
     uint32_t getInputElemSize (uint32_t idx) const override {
+      assert (idx < getInputNum ());
       return meta_->input_elem_size[idx];
     }
     uint32_t getOutputElemSize (uint32_t idx) const override {
+      assert (idx < getOutputNum ());
       return meta_->output_elem_size[idx];
     }
     const uint32_t* getInputDims (uint32_t idx) const override {
+      assert (idx < getInputNum ());
       return meta_->input_dims[idx];
     }
     const uint32_t* getOutputDims (uint32_t idx) const override {
+      assert (idx < getOutputNum ());
       return meta_->output_dims[idx];
     }
 
     uint32_t getInputQuantZero (uint32_t idx) const override {
+      assert (idx < getInputNum ());
       return meta_->input_quant_z[idx];
     }
     float getInputQuantScale (uint32_t idx) const override {
+      assert (idx < getInputNum ());
       return meta_->input_quant_s[idx];
     }
     uint32_t getOutputQuantZero (uint32_t idx) const override {
+      assert (idx < getOutputNum ());
       return meta_->output_quant_z[idx];
     }
     float getOutputQuantScale (uint32_t idx) const override {
+      assert (idx < getOutputNum ());
       return meta_->output_quant_s[idx];
     }
 };
@@ -213,12 +195,16 @@ class Model : public HWmem {
   public:
     Model (const HWmemImpl* impl);
 
-    void setDataInfo (const tensors_data_info *in, const tensors_data_info *out);
     void setConstraint (const npuConstraint& constraint) { constraint_ = constraint; }
+    int setDataInfo (const tensors_data_info *in, const tensors_data_info *out);
     int setMetadata (void *data);
     const Metadata *getMetadata () const { return meta_.get(); }
 
     uint32_t getID () const { return model_id_; }
+
+    uint32_t getInputTensorNum () const;
+    uint32_t getOutputTensorNum () const;
+
     uint32_t getInputTensorSize (uint32_t idx) const;
     uint32_t getOutputTensorSize (uint32_t idx) const;
 
index 5aff034..17ad5ee 100644 (file)
@@ -32,6 +32,15 @@ if gtest_dep.found()
   )
   test('unittest_ne_core_hwmem', unittest_ne_core_hwmem)
 
+  unittest_ne_core_model = executable('unittest_ne_core_model',
+    ['ne_core_model_test.cc'],
+    dependencies: [gtest_dep, ne_core_dep],
+    install : true,
+    install_rpath : ne_libdir,
+    install_dir : join_paths(ne_bindir, 'unittests')
+  )
+  test('unittest_ne_core_model', unittest_ne_core_model)
+
 #  unittest_ne_core_handler = executable('unittest_ne_core_handler',
 #    ['ne_core_handler_test.cpp'],
 #    include_directories: ne_host_inc,
diff --git a/tests/unittests/ne_core_model_test.cc b/tests/unittests/ne_core_model_test.cc
new file mode 100644 (file)
index 0000000..e6389da
--- /dev/null
@@ -0,0 +1,315 @@
+/**
+ * Proprietary
+ * Copyright (C) 2020 Samsung Electronics
+ * Copyright (C) 2020 Dongju Chae <dongju.chae@samsung.com>
+ */
+/**
+ * @file ne_core_model_test.cc
+ * @date 14 Apr 2020
+ * @brief Test functionality of model
+ * @author Dongju Chae <dongju.chae@samsung.com>
+ * @bug No known bugs except for NYI items
+ */
+
+#include <ne-model.h>
+
+#include "ne_unittest_utils.h"
+
+/**
+ * @brief check set/get primitives of model
+ */
+TEST (ne_core_model_test, model_primitives)
+{
+  std::unique_ptr <Model> model (new Model (new HWmemDevice));
+
+  EXPECT_GT (model->getID (), 0);
+
+  /** make dummy metadata */
+  npubin_meta data;
+  data.magiccode = NPUBIN_MAGICCODE | 0x1;  /* v1 */
+  data.size = 4096;
+  data.buffer_size = 4096;
+  data.input_size = 2048;
+  data.input_offset = 0;
+  data.output_size = 2048;
+  data.output_offset = 2048;
+  data.program_size = 0;
+  data.weight_size = 0;
+
+  EXPECT_EQ (model->setMetadata (&data), 0);
+  EXPECT_NE (model->getMetadata (), nullptr);
+
+  EXPECT_EQ (model->getInputTensorNum (), 1); /* v1 has one input/output tensors */
+  EXPECT_EQ (model->getOutputTensorNum (), 1);
+
+  EXPECT_EQ (model->getInputTensorSize (0), 2048);
+  EXPECT_EQ (model->getOutputTensorSize (0), 2048);
+
+  /** check default values */
+  npuConstraint constraint = model->getConstraint ();
+  EXPECT_EQ (constraint.timeout_ms, DEFAULT_TIMEOUT);
+  EXPECT_EQ (constraint.priority, DEFAULT_PRIORITY);
+
+  const tensor_data_info * in = model->getInputDataInfo (0);
+  EXPECT_NE (in, nullptr);
+  EXPECT_EQ (in->layout, DATA_LAYOUT_SRNPU);
+  EXPECT_EQ (in->type, DATA_TYPE_SRNPU);
+
+  const tensor_data_info * out = model->getOutputDataInfo (0);
+  EXPECT_NE (out, nullptr);
+  EXPECT_EQ (out->layout, DATA_LAYOUT_SRNPU);
+  EXPECT_EQ (out->type, DATA_TYPE_SRNPU);
+
+  /** user can specify constraint & info */
+  constraint.timeout_ms = 100;
+  constraint.priority = NPU_PRIORITY_HIGH;
+  model->setConstraint (constraint);
+  EXPECT_EQ (constraint.timeout_ms, 100);
+  EXPECT_EQ (constraint.priority, NPU_PRIORITY_HIGH);
+
+  tensors_data_info in_info;
+  in_info.num_info = 2;
+  in_info.info[1].layout = DATA_LAYOUT_NHWC;
+  in_info.info[1].type = DATA_TYPE_QSYMM16;
+
+  tensors_data_info out_info;
+  out_info.num_info = 3;
+  out_info.info[2].layout = DATA_LAYOUT_NCHW;
+  out_info.info[2].type = DATA_TYPE_QASYMM8;
+
+  model->setDataInfo (&in_info, &out_info);
+
+  in = model->getInputDataInfo (1);
+  EXPECT_NE (in, nullptr);
+  EXPECT_EQ (in->layout, DATA_LAYOUT_NHWC);
+  EXPECT_EQ (in->type, DATA_TYPE_QSYMM16);
+
+  out = model->getOutputDataInfo (2);
+  EXPECT_NE (out, nullptr);
+  EXPECT_EQ (out->layout, DATA_LAYOUT_NCHW);
+  EXPECT_EQ (out->type, DATA_TYPE_QASYMM8);
+}
+
+/**
+ * @brief check set/get primitives of model with error handling
+ */
+TEST (ne_core_model_test, model_primitives_invalid_args_n)
+{
+  std::unique_ptr <Model> model (new Model (new HWmemDevice));
+
+  /** invalid arguments */
+  EXPECT_NE (model->setMetadata (nullptr), 0);
+
+  tensors_data_info info;
+  EXPECT_NE (model->setDataInfo (nullptr, nullptr), 0);
+  EXPECT_NE (model->setDataInfo (&info, nullptr), 0);
+  EXPECT_NE (model->setDataInfo (nullptr, &info), 0);
+
+  /** out-of-range */
+  info.num_info = 2;
+  EXPECT_EQ (model->setDataInfo (&info, &info), 0);
+  EXPECT_EQ (model->getInputDataInfo (3), nullptr);
+  EXPECT_EQ (model->getOutputDataInfo (3), nullptr);
+  EXPECT_EQ (model->getInputTensorSize (3), 0);
+  EXPECT_EQ (model->getOutputTensorSize (3), 0);
+}
+
+/**
+ * @brief check set/get primitives of model with error handling
+ */
+TEST (ne_core_model_test, model_primitives_no_metadata_n)
+{
+  std::unique_ptr <Model> model (new Model (new HWmemDevice));
+
+  /** access info without setting metadata */
+  EXPECT_EQ (model->getMetadata (), nullptr);
+  EXPECT_EQ (model->getInputTensorSize (0), 0);
+  EXPECT_EQ (model->getOutputTensorSize (0), 0);
+  EXPECT_EQ (model->getInputDataInfo (0), nullptr);
+  EXPECT_EQ (model->getOutputDataInfo (0), nullptr);
+}
+
+/**
+ * @brief check setMetadata with invalid contents
+ */
+TEST (ne_core_model_test, model_set_metadata_n)
+{
+  std::unique_ptr <Model> model (new Model (new HWmemDevice));
+
+  /** start with the valid metadata */
+  npubin_meta data;
+  data.magiccode = NPUBIN_MAGICCODE | 0x1;  /* v1 */
+  data.size = 8192;
+  data.program_size = 2048;
+  data.weight_size = 2048;
+  EXPECT_EQ (model->setMetadata (&data), 0);
+
+  /** invalid magiccode */
+  data.magiccode = 0;
+  EXPECT_NE (model->setMetadata (&data), 0);
+
+  /** unsupported version */
+  data.magiccode = NPUBIN_MAGICCODE | 0x9;  /* v9 ?? */
+  EXPECT_NE (model->setMetadata (&data), 0);
+
+  /** invalid number of tensors */
+  data.magiccode = NPUBIN_MAGICCODE | 0x2;  /* v2 */
+  data.input_num = MAX_TENSORS + 1;
+  EXPECT_NE (model->setMetadata (&data), 0);
+  data.output_num = MAX_TENSORS + 1;
+  EXPECT_NE (model->setMetadata (&data), 0);
+
+  /** size mismatch */
+  data.input_num = 1;
+  data.output_num = 1;
+  data.size = NPUBIN_META_SIZE / 2;
+  EXPECT_NE (model->setMetadata (&data), 0); /* at least metadata size */
+  data.size = NPUBIN_META_SIZE;
+  data.program_size = NPUBIN_META_SIZE;
+  data.weight_size = NPUBIN_META_SIZE;
+  EXPECT_NE (model->setMetadata (&data), 0); /* size overflow */
+
+  /** TODO add more */
+}
+
+/**
+ * @brief check set/get primitives of metadata v1
+ */
+TEST (ne_core_model_test, metadata_primitives_v1)
+{
+  std::unique_ptr <Model> model (new Model (new HWmemDevice));
+
+  /** make dummy metadata v1 */
+  npubin_meta data;
+
+  data.magiccode = NPUBIN_MAGICCODE | 0x1;  /* v1 */
+  data.size = 8192;
+  data.buffer_size = 4096;
+  data.program_size = 2048;
+  data.weight_size = 2048;
+  data.input_offset = 1024;
+  data.input_size = 1024;
+  data.output_offset = 2048;
+  data.output_size = 2048;
+
+  EXPECT_EQ (model->setMetadata (&data), 0);
+
+  const Metadata * meta = model->getMetadata ();
+  EXPECT_NE (meta, nullptr);
+
+  EXPECT_EQ (meta->getVersion (), 1);
+  EXPECT_EQ (meta->getSize (), 8192);
+  EXPECT_EQ (meta->getBufferSize (), 4096);
+  EXPECT_EQ (meta->getProgramSize (), 2048);
+  EXPECT_EQ (meta->getWeightSize (), 2048);
+  EXPECT_EQ (meta->getMetaSize (), 4096);
+
+  EXPECT_EQ (meta->getInputNum (), 1);
+  EXPECT_EQ (meta->getOutputNum (), 1);
+  EXPECT_EQ (meta->getInputOffset (0), 1024);
+  EXPECT_EQ (meta->getInputTensorSize (0, DATA_LAYOUT_SRNPU), 1024);
+  EXPECT_EQ (meta->getOutputOffset (0), 2048);
+  EXPECT_EQ (meta->getOutputTensorSize (0, DATA_LAYOUT_SRNPU), 2048);
+
+  /** return default values for unsupported info */
+  EXPECT_EQ (meta->getInputElemSize (0), 1);
+  EXPECT_EQ (meta->getOutputElemSize (0), 1);
+  EXPECT_EQ (meta->getInputDims (0)[0], meta->getInputTensorSize (0, DATA_LAYOUT_SRNPU));
+  EXPECT_EQ (meta->getOutputDims (0)[0], meta->getOutputTensorSize (0, DATA_LAYOUT_SRNPU));
+  EXPECT_EQ (meta->getInputQuantZero (0), DEFAULT_ZERO_POINT);
+  EXPECT_EQ (meta->getOutputQuantZero (0), DEFAULT_ZERO_POINT);
+  EXPECT_EQ (meta->getInputQuantScale (0), DEFAULT_SCALE);
+  EXPECT_EQ (meta->getOutputQuantScale (0), DEFAULT_SCALE);
+}
+
+/**
+ * @brief check set/get primitives of metadata v2
+ */
+TEST (ne_core_model_test, metadata_primitives_v2)
+{
+  std::unique_ptr <Model> model (new Model (new HWmemDevice));
+
+  /** make dummy metadata v2 */
+  npubin_meta data;
+
+  data.magiccode = NPUBIN_MAGICCODE | 0x2;  /* v2 */
+  data.size = 8192;
+  data.buffer_size = 4096;
+  data.program_size = 2048;
+  data.weight_size = 2048;
+
+  uint32_t input_num = 3;
+  uint32_t output_num = 4;
+
+  data.input_num = input_num;
+  for (uint32_t i = 0; i < data.input_num; i++) {
+    data.input_offsets[i] = 100 * (i + 1);
+    data.input_elem_size[i] = 1 * (i + 1);
+    for (uint32_t j = 0; j < MAX_RANK; j++)
+      data.input_dims[i][j] = 2 * (i + 1);
+    data.input_quant_z[i] = 3 * (i + 1);
+    data.input_quant_s[i] = 4 * (i + 1);
+  }
+
+  data.output_num = output_num;
+  for (uint32_t i = 0; i < data.output_num; i++) {
+    data.output_offsets[i] = 100 * (i + 1);
+    data.output_elem_size[i] = 1 * (i + 1);
+    for (uint32_t j = 0; j < MAX_RANK; j++)
+      data.output_dims[i][j] = 2 * (i + 1);
+    data.output_quant_z[i] = 3 * (i + 1);
+    data.output_quant_s[i] = 4 * (i + 1);
+  }
+
+  EXPECT_EQ (model->setMetadata (&data), 0);
+
+  const Metadata * meta = model->getMetadata ();
+  EXPECT_NE (meta, nullptr);
+
+  EXPECT_EQ (meta->getVersion (), 2);
+  EXPECT_EQ (meta->getBufferSize(), 4096);
+  EXPECT_EQ (meta->getProgramSize(), 2048);
+  EXPECT_EQ (meta->getWeightSize(), 2048);
+
+  EXPECT_EQ (meta->getInputNum (), input_num);
+  for (uint32_t i = 0; i < meta->getInputNum (); i++) {
+    EXPECT_EQ (meta->getInputOffset (i), 100 * (i + 1));
+    EXPECT_EQ (meta->getInputElemSize (i), 1 * (i + 1));
+    for (uint32_t j = 0; j < MAX_RANK; j++)
+      EXPECT_EQ (meta->getInputDims(i)[j], 2 * (i + 1));
+    EXPECT_EQ (meta->getInputQuantZero (i), 3 * (i + 1));
+    EXPECT_EQ (meta->getInputQuantScale (i), 4 * (i + 1));
+  }
+
+  EXPECT_EQ (meta->getOutputNum (), output_num);
+  for (uint32_t i = 0; i < meta->getOutputNum (); i++) {
+    EXPECT_EQ (meta->getOutputOffset (i), 100 * (i + 1));
+    EXPECT_EQ (meta->getOutputElemSize (i), 1 * (i + 1));
+    for (uint32_t j = 0; j < MAX_RANK; j++)
+      EXPECT_EQ (meta->getOutputDims(i)[j], 2 * (i + 1));
+    EXPECT_EQ (meta->getOutputQuantZero (i), 3 * (i + 1));
+    EXPECT_EQ (meta->getOutputQuantScale (i), 4 * (i + 1));
+  }
+}
+
+/**
+ * @brief model alloc without driver api
+ */
+TEST (ne_core_model_test, model_alloc_no_drv_api_n)
+{
+  std::unique_ptr <Model> model (new Model (new HWmemDevice));
+
+  EXPECT_EQ (model->getDriverAPI (), nullptr);
+  EXPECT_NE (model->alloc (4096), 0);
+}
+
+/** TODO v3 */
+
+/**
+ * @brief main function for unit test
+ */
+int
+main (int argc, char **argv)
+{
+  return start_gtest (argc, argv);
+}