[TestdataGen] Reuse NPU emul to generate reference data for tvn models
authorDongju Chae <dongju.chae@samsung.com>
Fri, 14 Feb 2020 08:06:38 +0000 (17:06 +0900)
committer채동주/On-Device Lab(SR)/Staff Engineer/삼성전자 <dongju.chae@samsung.com>
Tue, 18 Feb 2020 08:04:18 +0000 (17:04 +0900)
This commit reuses NPU emul to generate reference data for tvn models.

Signed-off-by: Dongju Chae <dongju.chae@samsung.com>
debian/rules
include/common/npubinfmt.h
packaging/npu-engine.spec
tests/apptests/example_visa.c
tools/gen-testdata/gen_npu_model.py [moved from tools/gen-testdata/model_gen.py with 85% similarity]
tools/gen-testdata/gen_ref_data.cpp [new file with mode: 0644]
tools/gen-testdata/gen_visa_prog.cpp [moved from tools/gen-testdata/testdata_gen.cpp with 73% similarity]
tools/gen-testdata/meson.build

index 0364cef..9f095b2 100755 (executable)
@@ -15,7 +15,8 @@
 
 ROOT_DIR:=$(shell pwd)
 export NPU_TRINITY_INSTALL_PREFIX=/opt/trinity
-export LC_ALL = C.UTF-8
+export PATH=$(shell printenv PATH):${NPU_TRINITY_INSTALL_PREFIX}/bin
+export LC_ALL=C.UTF-8
 
 %:
        dh $@ --parallel
@@ -46,35 +47,67 @@ override_dh_auto_test:
        ./build/tests/apptests/apptest_async_callbacks
 
        # Gen model binaries
-       # Single-layer test data
-       mkdir -p $(CURDIR)/single
+       # 1) Singl-layer models from AIP/SIM_Trinity_SIM (i.e., libnpuvision)
+       # 2) Multi-layer models from AIP/NPU_SystemService
+       # 3) MUlti-layer models from AIP/NPU_Compiler
+
+       mkdir -p $(CURDIR)/npubinfmt_v1
+       mkdir -p $(CURDIR)/npubinfmt_v2
+
+       # 1) Reuse existing testdata but need to make npu models (v1)
+       find /opt/trinity/share/npuvision/testdata/* -type d -exec basename {} \; |\
+               xargs -I{} mkdir -p $(CURDIR)/npubinfmt_v1/{}
        find /opt/trinity/share/npuvision/testdata/* -type d -exec basename {} \; |\
-                       xargs -I{} mkdir -p single/{}
+               xargs -I{} $(ROOT_DIR)/tools/gen-testdata/gen_npu_model.py -s -o $(CURDIR)/npubinfmt_v1/{} \
+               /opt/trinity/share/npuvision/testdata/{} 1
        find /opt/trinity/share/npuvision/testdata/* -type d -exec basename {} \; |\
-                       xargs -I{} $(ROOT_DIR)/tools/gen-testdata/model_gen.py -s -o single/{} \
-                       /opt/trinity/share/npuvision/testdata/{} 1
-       # Multi-layer test data
-       mkdir -p $(CURDIR)/multi
-       cd multi && $(ROOT_DIR)/build/tools/gen-testdata/gen-testdata > /dev/null
-       find $(CURDIR)/multi -name 'testcase*' -type d -exec basename {} \; | \
-               xargs -I{} ${NPU_TRINITY_INSTALL_PREFIX}/bin/encoder multi/{}/program.asm multi/{}/program.bin > /dev/null
-       find $(CURDIR)/multi -name 'testcase*_v1' -type d -exec basename {} \; |\
-               xargs -I{} $(ROOT_DIR)/tools/gen-testdata/model_gen.py multi/{} 1
-       find $(CURDIR)/multi -name 'testcase*_v2' -type d -exec basename {} \; |\
-               xargs -I{} $(ROOT_DIR)/tools/gen-testdata/model_gen.py multi/{} 2
-       # Remove intermediate files
-       find $(CURDIR)/multi -type f -name '_*' -delete
-       find $(CURDIR)/multi -type f -name 'program.*' -delete
-
-       # single/multi-layer model test
-       ./build/tests/apptests/apptest_example_visa single > /dev/null
-       ./build/tests/apptests/apptest_example_visa multi > /dev/null
+               xargs -I{} $(ROOT_DIR)/build/tools/gen-testdata/gen_ref_data -o $(CURDIR)/npubinfmt_v1/{} \
+               $(CURDIR)/npubinfmt_v1/{}/model.tvn > /dev/null
+
+       # 2) Make npu models from scratch (v1/v2)
+       $(ROOT_DIR)/build/tools/gen-testdata/gen_visa_prog $(CURDIR)/npubinfmt_v1 > /dev/null
+       find $(CURDIR)/npubinfmt_v1 -name 'testcase*' -type d -exec basename {} \; | \
+               xargs -I{} cp -r $(CURDIR)/npubinfmt_v1/{} $(CURDIR)/npubinfmt_v2/{}
+
+       find $(CURDIR)/npubinfmt_v1 -name 'testcase*' -type d -exec basename {} \; |\
+               xargs -I{} $(ROOT_DIR)/tools/gen-testdata/gen_npu_model.py -o $(CURDIR)/npubinfmt_v1/{} \
+               $(CURDIR)/npubinfmt_v1/{} 1
+       find $(CURDIR)/npubinfmt_v2 -name 'testcase*' -type d -exec basename {} \; |\
+               xargs -I{} $(ROOT_DIR)/tools/gen-testdata/gen_npu_model.py -o $(CURDIR)/npubinfmt_v2/{} \
+               $(CURDIR)/npubinfmt_v2/{} 2
+
+       find $(CURDIR)/npubinfmt_v1 -name 'testcase*' -type d -exec basename {} \; |\
+               xargs -I{} $(ROOT_DIR)/build/tools/gen-testdata/gen_ref_data -o $(CURDIR)/npubinfmt_v1/{} \
+               $(CURDIR)/npubinfmt_v1/{}/model.tvn > /dev/null
+       find $(CURDIR)/npubinfmt_v2 -name 'testcase*' -type d -exec basename {} \; |\
+               xargs -I{} $(ROOT_DIR)/build/tools/gen-testdata/gen_ref_data -o $(CURDIR)/npubinfmt_v2/{} \
+               $(CURDIR)/npubinfmt_v2/{}/model.tvn > /dev/null
+
+       # 3) Make only reference input/output data (v1 currently)
+       tar zxf $(ROOT_DIR)/tests/models/tvn_models.tar.gz
+
+       find $(CURDIR)/tvn_models/* -type d -exec basename {} \; |\
+               xargs -I{} $(ROOT_DIR)/build/tools/gen-testdata/gen_ref_data -o $(CURDIR)/tvn_models/{} \
+               $(CURDIR)/tvn_models/{}/model.tvn > /dev/null
+       find $(CURDIR)/tvn_models/* -type d -exec basename {} \; |\
+               xargs -I{} mv $(CURDIR)/tvn_models/{} $(CURDIR)/npubinfmt_v1
+
+  # Cleanup
+       rm -rf $(CURDIR)/tvn_models
+       find $(CURDIR)/npubinfmt_v1 -type f -name 'program.*' -delete
+       find $(CURDIR)/npubinfmt_v2 -type f -name 'program.*' -delete
+       find $(CURDIR)/npubinfmt_v1 -type f -name 'input_weight*' -delete
+       find $(CURDIR)/npubinfmt_v2 -type f -name 'input_weight*' -delete
+
+       # Test the models
+       ./build/tests/apptests/apptest_example_visa $(CURDIR)/npubinfmt_v1 > /dev/null
+       ./build/tests/apptests/apptest_example_visa $(CURDIR)/npubinfmt_v2 > /dev/null
 
 override_dh_auto_install:
        DESTDIR=$(CURDIR)/debian/tmp ninja -C build install
        mkdir -p debian/tmp/${NPU_TRINITY_INSTALL_PREFIX}/share/npu-engine/testdata
-       mv single debian/tmp/${NPU_TRINITY_INSTALL_PREFIX}/share/npu-engine/testdata
-       mv multi debian/tmp/${NPU_TRINITY_INSTALL_PREFIX}/share/npu-engine/testdata
+       mv npubinfmt_v1 debian/tmp/${NPU_TRINITY_INSTALL_PREFIX}/share/npu-engine/testdata
+       mv npubinfmt_v2 debian/tmp/${NPU_TRINITY_INSTALL_PREFIX}/share/npu-engine/testdata
 
 override_dh_install:
        dh_install --sourcedir=debian/tmp --list-missing
index 042d39c..9690d55 100644 (file)
@@ -28,6 +28,7 @@
 
 /* npubinfmt magiccode macros */
 #define NPUBIN_MAGICCODE (0x53524E5055000000ULL)  /* ASCII hex for 'SRNPU' */
+#define NPUBIN_VERSION_MAX (2)
 #define NPUBIN_VERSION(magiccode) ((magiccode) & 0xFFULL)
 #define CHECK_NPUBIN(magiccode) (((magiccode) & ~0xFFFFFFULL) == NPUBIN_MAGICCODE)
 
index 4117000..ceff246 100644 (file)
@@ -1,6 +1,6 @@
 %define   neexampledir %{_libdir}/npu-engine/bin
-%define   testdatadir_single_raw   %{_datadir}/npuvision/testdata
-%define   testdatadir_out   %{_datadir}/npu-engine/testdata
+%define   testdatadir_npuvision %{_datadir}/npuvision/testdata
+%define   testdatadir_out %{_datadir}/npu-engine/testdata
 
 Name:          npu-engine
 Summary:       NPU Engine
@@ -88,34 +88,64 @@ DESTDIR=%{buildroot} ninja install -C build %{?_smp_mflags}
 %ifarch aarch64 x86_64
 
 # Install Test Data
-mkdir -p %{buildroot}%{testdatadir_out}/single
-# single layer
-find %{testdatadir_single_raw} -name 'core*' -type d -exec basename {} .asm \; | \
-    xargs -I{} mkdir -p %{buildroot}%{testdatadir_out}/single/{}
-find %{testdatadir_single_raw} -name 'core*' -type d -exec basename {} .asm \; | \
-    xargs -I{} ./tools/gen-testdata/model_gen.py -s -o %{buildroot}%{testdatadir_out}/single/{} \
-    %{testdatadir_single_raw}/{} 1
-
-# multi layer
-mkdir -p multi-intermediate
-pushd multi-intermediate
-find %{_builddir} -name gen-testdata -type f -exec {} \; > /dev/null
-find . -name 'testcase*' -type d -exec basename {} \; | \
-    xargs -I{} %{_bindir}/encoder {}/program.asm {}/program.bin > /dev/null
-find . -name 'testcase*' -type d -exec basename {} \; | \
-    xargs -I{} mkdir -p %{buildroot}%{testdatadir_out}/multi/{}
-popd
-find ./multi-intermediate -name 'testcase*_v1' -type d -exec basename {} \; | \
-    xargs -I{} ./tools/gen-testdata/model_gen.py -o %{buildroot}%{testdatadir_out}/multi/{} \
-    ./multi-intermediate/{} 1
-find ./multi-intermediate -name 'testcase*_v2' -type d -exec basename {} \; | \
-    xargs -I{} ./tools/gen-testdata/model_gen.py -o %{buildroot}%{testdatadir_out}/multi/{} \
-    ./multi-intermediate/{} 2
-
-# clean up
-find %{buildroot} -name gen-testdata -delete
-find %{buildroot} -name model_gen.py -delete
-find %{buildroot} -name multi.tar.gz -delete
+
+# Gen model binaries
+# 1) Singl-layer models from AIP/SIM_Trinity_SIM (i.e., libnpuvision)
+# 2) Multi-layer models from AIP/NPU_SystemService
+# 3) MUlti-layer models from AIP/NPU_Compiler
+
+mkdir -p npubinfmt_v1
+mkdir -p npubinfmt_v2
+
+# 1) Reuse existing testdata but need to make npu models (v1)
+find %{testdatadir_npuvision} -name 'core*' -type d -exec basename {} \; |\
+  xargs -I{} mkdir -p npubinfmt_v1/{}
+
+find %{testdatadir_npuvision} -name 'core*' -type d -exec basename {} \; |\
+  xargs -I{} ./tools/gen-testdata/gen_npu_model.py -s -o npubinfmt_v1/{} \
+  %{testdatadir_npuvision}/{} 1
+
+find %{testdatadir_npuvision} -name 'core*' -type d -exec basename {} \; |\
+  xargs -I{} ./build/tools/gen-testdata/gen_ref_data  -o npubinfmt_v1/{} \
+  npubinfmt_v1/{}/model.tvn > /dev/null
+
+# 2) Make npu models from scratch (v1/v2)
+./build/tools/gen-testdata/gen_visa_prog npubinfmt_v1 > /dev/null
+find npubinfmt_v1 -name 'testcase*' -type d -exec basename {} \; |\
+  xargs -I{} cp -r npubinfmt_v1/{} npubinfmt_v2/{}
+
+find npubinfmt_v1 -name 'testcase*' -type d -exec basename {} \; |\
+  xargs -I{} ./tools/gen-testdata/gen_npu_model.py -o npubinfmt_v1/{} npubinfmt_v1/{} 1
+find npubinfmt_v2 -name 'testcase*' -type d -exec basename {} \; |\
+  xargs -I{} ./tools/gen-testdata/gen_npu_model.py -o npubinfmt_v2/{} npubinfmt_v2/{} 2
+
+find npubinfmt_v1 -name 'testcase*' -type d -exec basename {} \; |\
+  xargs -I{} ./build/tools/gen-testdata/gen_ref_data -o npubinfmt_v1/{} \
+  npubinfmt_v1/{}/model.tvn > /dev/null
+
+find npubinfmt_v2 -name 'testcase*' -type d -exec basename {} \; |\
+  xargs -I{} ./build/tools/gen-testdata/gen_ref_data -o npubinfmt_v2/{} \
+  npubinfmt_v2/{}/model.tvn > /dev/null
+
+# 3) Make only reference input/output data (v1)
+tar zxf tests/models/tvn_models.tar.gz
+find tvn_models/* -type d -exec basename {} \; | xargs -I{} mkdir -p npubinfmt_v1/{}
+find tvn_models/* -type d -exec basename {} \; |\
+  xargs -I{} ./build/tools/gen-testdata/gen_ref_data -o npubinfmt_v1/{} \
+  tvn_models/{}/model.tvn > /dev/null
+find tvn_models/* -type d -exec basename {} \; |\
+  xargs -I{} mv tvn_models/{}/model.tvn npubinfmt_v1/{}/
+
+# Cleanup
+rm -rf tvn_models
+find npubinfmt_v1 -type f -name 'program.*' -delete
+find npubinfmt_v2 -type f -name 'program.*' -delete
+find npubinfmt_v1 -type f -name 'input_weight*' -delete
+find npubinfmt_v2 -type f -name 'input_weight*' -delete
+
+mkdir -p %{buildroot}%{testdatadir_out}
+mv npubinfmt_v1 %{buildroot}%{testdatadir_out}
+mv npubinfmt_v2 %{buildroot}%{testdatadir_out}
 
 %endif
 
@@ -141,10 +171,10 @@ find %{buildroot} -name multi.tar.gz -delete
   %endif
   done
 
-  # Run example_visa
+  # Run apptests using actual model files.
   %ifarch x86_64
-  ./apptests/apptest_example_visa %{buildroot}%{testdatadir_out}/single > /dev/null
-  ./apptests/apptest_example_visa %{buildroot}%{testdatadir_out}/multi > /dev/null
+  ./apptests/apptest_example_visa %{buildroot}%{testdatadir_out}/npubinfmt_v1 > /dev/null
+  ./apptests/apptest_example_visa %{buildroot}%{testdatadir_out}/npubinfmt_v2 > /dev/null
   %endif
 
   popd
@@ -210,8 +240,8 @@ This package provides test data for verfication of NPU Engine.
 Note that the npu-example package (especailly, the application tests) requires this package.
 %files testdata
 %%defattr(-,root,root,-)
-%{_datadir}/npu-engine/testdata/single/*
-%{_datadir}/npu-engine/testdata/multi/*
+%{_datadir}/npu-engine/testdata/npubinfmt_v1/*
+%{_datadir}/npu-engine/testdata/npubinfmt_v2/*
 %endif
 
 %package unittest-coverage
index e804945..c96bb73 100644 (file)
@@ -23,6 +23,7 @@
 #include <ne_test_utils.h>
 
 #define MAX_FILE_LEN 256
+#define NPU_MODEL_NAME "model.tvn"
 
 /** @brief compare output tensor with the golden data */
 static int
@@ -144,8 +145,8 @@ run_inference_each (npudev_h dev, const char *base_path, const char *target)
 
   /** 1: setup model (not dmabuf) */
   memset (model_path, '\x00', MAX_FILE_LEN);
-  snprintf (model_path, MAX_FILE_LEN, "%s/%s/npu_model.bin",
-      base_path, target);
+  snprintf (model_path, MAX_FILE_LEN, "%s/%s/%s",
+      base_path, target, NPU_MODEL_NAME);
   if ((err = get_model_meta (model_path, &meta)) != 0) {
     fprintf (stderr, "Fail to get the metadata of %s\n", model_path);
     return err;
similarity index 85%
rename from tools/gen-testdata/model_gen.py
rename to tools/gen-testdata/gen_npu_model.py
index 80d8550..39a80d6 100755 (executable)
@@ -1,12 +1,12 @@
 #!/usr/bin/env python
 
 ##
-# @file model_gen.py
-# @brief generate NPU model binary with existing VISA binary and necessary data
+# @file gen_npu_model.py
+# @brief generate NPU model binary with VISA prog binary and weight data
 # @author Dongju Chae <dongju.chae@samsung.com>
 #
 # Usage:
-#   $ python model_gen.py [testdata directory]
+#   $ python gen_npu_model.py [testdata directory]
 ##
 
 import argparse
@@ -20,6 +20,7 @@ import re
 META_SIZE=4096
 SIZE_ALIGN=4096
 SIGNATURE='SRNPU'
+NPU_MODEL_NAME='model.tvn'
 
 MAX_TENSORS=16
 MAX_RANK=4
@@ -27,12 +28,10 @@ ELEM_SIZE=1
 
 ## @brief class for NPU tensor
 class Tensor:
-  def __init__ (self, asm, dma_en, data):
+  def __init__ (self, asm, dma_en):
     opcode = asm["opcode"]
     self.asm = asm
     self.dma_en = dma_en
-    self.data = data
-    self.size = len(data)
     self.dims = []
     self.elem_size = ELEM_SIZE   # do not consider other types yet
 
@@ -72,6 +71,12 @@ class Tensor:
     self.emod_y = asm[dma_en + "_emod_y"]
     self.emod_z = asm[dma_en + "_emod_z"]
 
+  def size (self):
+    size = self.elem_size
+    for dim in self.dims:
+      size *= dim
+    return size
+
 ## @brief class for NPU weight
 class Weight:
   def __init__ (self, asm, data):
@@ -120,7 +125,7 @@ class NPUModel:
     self.path_prog_asm = self.dir + "/program.asm"
     self.path_prog_binary = self.dir + "/program.bin"
 
-    self.path_model = self.outdir + "/npu_model.bin"
+    self.path_model = self.outdir + "/" + NPU_MODEL_NAME
 
     self.size_prog_binary = os.path.getsize(self.path_prog_binary)
     self.size_all_weights = 0
@@ -143,12 +148,12 @@ class NPUModel:
     max_size = 0
 
     for tensor in self.input_tensors:
-      if max_size < tensor.eaddr + tensor.size:
-        max_size = tensor.eaddr + tensor.size
+      if max_size < tensor.eaddr + tensor.size():
+        max_size = tensor.eaddr + tensor.size()
 
     for tensor in self.output_tensors:
-      if max_size < tensor.eaddr + tensor.size:
-        max_size = tensor.eaddr + tensor.size
+      if max_size < tensor.eaddr + tensor.size():
+        max_size = tensor.eaddr + tensor.size()
 
     return max_size
 
@@ -165,7 +170,7 @@ class NPUModel:
   ## @brief get binary data; return empty string if it does not exist
   def get_bin_data (self, name, idx):
     if self.single_mode:
-      path = self.dir + "/" + name[1:] + ".bin"
+      path = self.dir + "/" + name + ".bin"
     else:
       path = self.dir + "/" + name + "_" + str(idx) + ".bin"
     return open(path).read() if os.path.isfile(path) else ""
@@ -176,7 +181,6 @@ class NPUModel:
     self.fill_meta()
     self.fill_program()
     self.fill_weight()
-    self.fill_tensors()
     self.finalize()
 
   ## @brief parse program assembly to retreive necessary info.
@@ -195,22 +199,16 @@ class NPUModel:
       # Note that this model generator supports only HWA's layer operations, not a generic model.
       # So, each layer has 2 input tensors and 1 output tensor at the most.
       if asm["dma_in0_en"] == 1:
-        data = self.get_bin_data ("_input_fmap", idx)
-        if data:
-          tensor = Tensor(asm, "in0", data)
-          self.input_tensors.append(tensor)
+        tensor = Tensor(asm, "in0")
+        self.input_tensors.append(tensor)
       if asm["dma_in1_en"] == 1:
-        data = self.get_bin_data ("_input_esum", idx)
-        if data:
-          tensor = Tensor(asm, "in1", data)
-          self.input_tensors.append(tensor)
+        tensor = Tensor(asm, "in1")
+        self.input_tensors.append(tensor)
       if asm["dma_out_en"] == 1:
-        data = self.get_bin_data ("_output_fmap", idx)
-        if data:
-          tensor = Tensor(asm, "out", data)
-          self.output_tensors.append(tensor)
+        tensor = Tensor(asm, "out")
+        self.output_tensors.append(tensor)
       if asm["dma_wgt_en"] == 1:
-        data = self.get_bin_data ("_input_weight", idx)
+        data = self.get_bin_data ("input_weight", idx)
         if data:
           weight = Weight(asm, data)
           self.size_all_weights += weight.size
@@ -250,12 +248,12 @@ class NPUModel:
       # input size (first layer) including esum (all layers)
       input_tensors_size = 0
       for tensor in self.input_tensors:
-        input_tensors_size += tensor.size
+        input_tensors_size += tensor.size()
       self.file_model.write(struct.pack('<Q', input_tensors_size))
       # output offset (last lasyer)
       self.file_model.write(struct.pack('<Q', self.output_tensors[-1].eaddr))
       # output size (last layer)
-      self.file_model.write(struct.pack('<Q', self.output_tensors[-1].size))
+      self.file_model.write(struct.pack('<Q', self.output_tensors[-1].size()))
     else:
       # fill dummy data for backward campatibility
       self.file_model.write(struct.pack('<Q', 0))
@@ -351,28 +349,6 @@ class NPUModel:
     for weight in self.input_weights:
       self.file_model.write(weight.data)
 
-  ## @brief fill the content of tensors
-  def fill_tensors (self):
-    if self.version == 1:
-      # input tensors including esum
-      with open(self.outdir + "/input_fmap.bin", "wb") as file_tensor:
-        for tensor in self.input_tensors:
-          file_tensor.seek(tensor.eaddr - self.input_tensors[0].eaddr)
-          file_tensor.write(tensor.data)
-      # output tensors of the last layer
-      with open(self.outdir + "/output_fmap.bin", "wb") as file_tensor:
-        file_tensor.write(self.output_tensors[-1].data)
-    elif self.version == 2:
-      # input tensors including esum
-      for idx, tensor in enumerate(self.input_tensors):
-        with open(self.outdir + "/input_fmap_" + str(idx) + ".bin", "wb") as file_tensor:
-          file_tensor.write(tensor.data)
-      # output tensors of the last layer
-      with open(self.outdir + "/output_fmap_0.bin", "wb") as file_tensor:
-        file_tensor.write(self.output_tensors[-1].data)
-    else:
-      raise Exception("Invalid version")
-
   ## @brief close all files
   def finalize (self):
     self.file_model.close()
@@ -391,10 +367,10 @@ if __name__ == '__main__':
     help='A path of directory where the test data are located')
   parser.add_argument('version', metavar='VER', type=int, nargs=1,
     help='A version of NPU model binary (only support 1 or 2)')
-  parser.add_argument('--debug', '-d', action='store_true', dest='debug_mode', default=False,
-    help='Turn on the debug mode (optional)', required=False)
   parser.add_argument('--single', '-s', action='store_true', dest='single_mode', default=False,
     help='Turn on the single layer mode (optional)', required=False)
+  parser.add_argument('--debug', '-d', action='store_true', dest='debug_mode', default=False,
+    help='Turn on the debug mode (optional)', required=False)
   parser.add_argument('--outdir', '-o', dest='outdir', type=str,
     help='A path of directory where the generated model would be placed (optional)', required=False)
   args = parser.parse_args()
diff --git a/tools/gen-testdata/gen_ref_data.cpp b/tools/gen-testdata/gen_ref_data.cpp
new file mode 100644 (file)
index 0000000..75e40b1
--- /dev/null
@@ -0,0 +1,260 @@
+/**
+ * Proprietary
+ * Copyright (C) 2019 Samsung Electronics
+ * Copyright (C) 2019 Dongju Chae <dongju.chae@samsung.com>
+ */
+/**
+ * @file gen_ref_data.cpp
+ * @date 13 Feb 2020
+ * @brief generate the reference input/output/weight data for NPU models
+ * @author Dongju Chae <dongju.chae@samsung.com>
+ * @note This requires the libnpuvision package to be compiled.
+ */
+
+#include <iostream>
+#include <fstream>
+#include <unistd.h>
+#include <string.h>
+
+#include <NPUemul.h>
+
+using namespace std;
+
+/** @brief a wrapper for TrinityCore/DataGen */
+class RefDataGen: public NPUCoreEmul {
+  public:
+    /** @brief constructor */
+    RefDataGen(const char *_output_dir): output_dir(_output_dir) {
+      model = buffer = NULL;
+    }
+    /** @brief destructor */
+    ~RefDataGen();
+    /** @brief load and verify the model binary */
+    int loadModel(const string target_path);
+    /** @brief emit function to generate reference data for the model */
+    int emit();
+
+  protected:
+    /** @brief dump input or output data */
+    void dumpData(const string name, const char *data, uint32_t size);
+    /** @brief generate random input data */
+    void genRandomInput();
+    /** @brief generate golden output data */
+    void genGoldenOutput();
+
+  private:
+    const string output_dir;
+    char *model;
+    char *buffer;
+    npubin_meta meta;
+};
+
+/** @brief destructor */
+RefDataGen::~RefDataGen()
+{
+  if (model)
+    delete model;
+  if (buffer)
+    delete buffer;
+}
+
+/** @brief load and verify the model binary */
+int RefDataGen::loadModel(const string target_path)
+{
+  ifstream ifs(target_path, ios::binary);
+  size_t model_size;
+
+  if (!ifs.is_open()) {
+    cerr << "Cannot find the target model " << target_path << endl;
+    return -1;
+  }
+
+  ifs.seekg (0, ios::end);
+  model_size = ifs.tellg();
+  if (model_size <= 0) {
+    cerr << "Invalid model binary" << endl;
+    goto exit_error;
+  }
+  ifs.seekg (0, ios::beg);
+
+  this->model = new char[model_size];
+  ifs.read(this->model, model_size);
+
+  memcpy(&this->meta, this->model, NPUBIN_META_SIZE);
+  if (this->meta.size != model_size) {
+    cerr << "The model size is not matched" << endl;
+    goto exit_error;
+  }
+
+  if (this->meta.buffer_size == 0) {
+    cerr << "The buffer size should be larger than 0" << endl;
+    goto exit_error;
+  }
+
+  if (!CHECK_NPUBIN(this->meta.magiccode)) {
+    cerr << "This model binary is not compatible to our design" << endl;
+    cerr << "Please refer to ./include/common/npubinfmt.h" << endl;
+    goto exit_error;
+  }
+
+  if (NPUBIN_VERSION(this->meta.magiccode) > NPUBIN_VERSION_MAX) {
+    cerr << "Unsupported npubinfmt version" << endl;
+    goto exit_error;
+  }
+
+  this->buffer = new char[this->meta.buffer_size];
+
+  ifs.close();
+  return 0;
+
+exit_error:
+  if (this->model)
+    delete this->model;
+  ifs.close();
+  return -1;
+}
+
+/** @brief dump input or output data */
+void RefDataGen::dumpData(const string name, const char *data, uint32_t size)
+{
+  ofstream ofs(output_dir + '/' + name, ios::binary);
+
+  if (!ofs.is_open()) {
+    cerr << "Cannot create input data" << endl;
+    return;
+  }
+
+  ofs.write(data, size);
+  ofs.close();
+}
+
+/** @brief generate random input data */
+void RefDataGen::genRandomInput()
+{
+  srand (time(NULL));
+
+  switch (NPUBIN_VERSION(this->meta.magiccode)) {
+    case 0: /* regarded as version 1 */
+    case 1:
+      for (uint64_t i = 0; i < this->meta.input_size; i++)
+        this->buffer[this->meta.input_offset + i] = (uint8_t) rand();
+      dumpData("input_fmap.bin",
+          this->buffer + this->meta.input_offset, this->meta.input_size);
+      break;
+    case 2:
+      for (uint32_t i = 0; i < this->meta.input_num; i++) {
+        /* it may not be the exact size; but enough for testing */
+        uint32_t input_size = this->meta.input_elem_size[i];
+
+        for (uint32_t j = 0; j < MAX_RANK; j++)
+          input_size *= this->meta.input_dims[i][j];
+
+        for (uint32_t j = 0; j < input_size; j++)
+          this->buffer[this->meta.input_offsets[i] + j] = (uint8_t) rand();
+
+        dumpData("input_fmap_" + to_string(i) + ".bin",
+            this->buffer + this->meta.input_offsets[i], input_size);
+      }
+      break;
+    default:
+      abort (); /* already checked */
+  }
+}
+
+/** @brief generate golden output data */
+void RefDataGen::genGoldenOutput()
+{
+  switch (NPUBIN_VERSION(this->meta.magiccode)) {
+    case 0: /* regarded as version 1 */
+    case 1:
+      dumpData("output_fmap.bin",
+          this->buffer + this->meta.output_offset, this->meta.output_size);
+      break;
+    case 2:
+      for (uint32_t i = 0; i < this->meta.output_num; i++) {
+        /* it may not be the exact size; but enough for testing */
+        uint32_t output_size = this->meta.output_elem_size[i];
+
+        for (uint32_t j = 0; j < MAX_RANK; j++)
+          output_size *= this->meta.output_dims[i][j];
+
+        dumpData("output_fmap_" + to_string(i) + ".bin",
+            this->buffer + this->meta.output_offsets[i], output_size);
+      }
+      break;
+    default:
+      abort (); /* already checked */
+  }
+}
+
+/** @brief emit function to generate reference data for the model */
+int RefDataGen::emit()
+{
+  genRandomInput();
+  int ret;
+
+  ret = run(this->model, this->buffer);
+  if (ret == 0)
+    genGoldenOutput();
+
+  return ret;
+}
+
+static void print_usage(const char *name)
+{
+  cerr << "Usage: " << name << " [options] binary" << endl;
+  cerr << "Options: " << endl;
+  cerr << "  -o <arg> \t Specify output directory path" << endl;
+}
+
+static char default_output_dir[] = ".";
+
+/** @brief main function */
+int main(int argc, char **argv)
+{
+  char *target_path = NULL;
+  char *output_dir = NULL;
+  int i, c;
+
+  /* parse option arguments */
+  opterr = 0;
+  while ((c = getopt (argc, argv, "o:")) != -1) {
+    switch (c)
+    {
+      case 'o':
+        output_dir = optarg;
+        break;
+      case '?':
+        if (optopt == 'o')
+          cerr << "Option -o requires an argument" << endl;
+        else
+          cerr << "Unknown option" << endl;
+        cerr << endl;
+
+        print_usage(argv[0]);
+        return -1;
+      default:
+        abort(); /* impossible */
+    }
+  }
+
+  if (output_dir == NULL)
+    output_dir = default_output_dir;
+
+  /* parse non-option arguments */
+  for (i = optind; i < argc; i++)
+    target_path = argv[i];
+
+  if (target_path == NULL) {
+    print_usage(argv[0]);
+    return -1;
+  }
+
+  /* generate reference data */
+  RefDataGen gen(output_dir);
+
+  if (gen.loadModel(target_path) != 0)
+    return -1;
+
+  return gen.emit();
+}
similarity index 73%
rename from tools/gen-testdata/testdata_gen.cpp
rename to tools/gen-testdata/gen_visa_prog.cpp
index 4b1f004..6d26808 100644 (file)
@@ -4,9 +4,9 @@
  * Copyright (C) 2019 Dongju Chae <dongju.chae@samsung.com>
  */
 /**
- * @file testdata_gen.cpp
+ * @file gen_visa_prog.cpp
  * @date 07 Nov 2019
- * @brief geneate the testdata for multi-layer models
+ * @brief generate VISA program binary for multi-layer NPU models
  * @author Dongju Chae <dongju.chae@samsung.com>
  * @note This requires the libnpuvision package to be compiled.
  */
@@ -16,8 +16,8 @@
 #include <cstdlib>
 #include <ctime>
 #include <vector>
+
 #include <TrinityCore.h>
-#include <DataGen.h>
 
 #define INPUT_ADDR_START    0x1000
 #define OUTPUT_ADDR_START 0x100000
@@ -40,18 +40,16 @@ static int avgp_rshamts[17] = {0,0,0,1,1,0,2,0,2,3,0,0,3,0,0,0,3};
 static int avgp_mults[17] = {0,0,1073741824,1431655765,1073741824,0,1431655765,0,1073741824,1908874354,0,0,1431655765,0,0,0,1073741824};
 
 /** @brief a wrapper for TrinityCore/DataGen */
-class TestdataGen: public TrinityCore<64>, public DataGen {
+class ProgGen: public TrinityCore<64> {
   public:
     /** @brief constructor */
-    TestdataGen(const char* _dir, uint32_t _max_ops, uint32_t _version)
-      : dir(_dir), version(_version) {
-      ops.reserve(_max_ops);
+    ProgGen(const string _dir): dir(_dir) {
       addr_in = addr_wgt = 0;
       addr_out = OUTPUT_ADDR_START;
       addr_out_prev = INPUT_ADDR_START + get_random_offset(); /* any value is fine */
     }
     /** @brief emit function to generate the testdata for multi-layer models */
-    void emit();
+    int emit();
     /** @brief append an trinity operation to the operation list */
     void append(TRINITY_CORE_PARA_OP& op) { ops.push_back(op); }
     /** @brief convert the operation to VISA assembly codes */
@@ -62,7 +60,6 @@ class TestdataGen: public TrinityCore<64>, public DataGen {
 
   private:
     const string dir;
-    uint32_t version;
     uint32_t addr_in, addr_wgt;
     uint32_t addr_out, addr_out_prev;
     vector<TRINITY_CORE_PARA_OP> ops;
@@ -70,17 +67,13 @@ class TestdataGen: public TrinityCore<64>, public DataGen {
 };
 
 /** @brief get random offset aligned to 0x1000 */
-uint32_t TestdataGen::get_random_offset()
+uint32_t ProgGen::get_random_offset()
 {
-  /** consider non-contiguous tensors */
-  if (version == 2)
-    return (std::rand() % 9 + 1) * 0x1000;
-  else
-    return 0;
+  return (std::rand() % 9 + 1) * 0x1000;
 }
 
 /** @brief convert the operation to VISA assembly codes */
-void TestdataGen::write_asm_file(size_t idx, TRINITY_FMAP_PARA& in,
+void ProgGen::write_asm_file(size_t idx, TRINITY_FMAP_PARA& in,
     TRINITY_FMAP_PARA& out, WGT_PARA& weight)
 {
   TRINITY_CORE_PARA_OP& op = ops[idx];
@@ -191,19 +184,15 @@ void TestdataGen::write_asm_file(size_t idx, TRINITY_FMAP_PARA& in,
 }
 
 /** @brief emit function to generate the testdata for multi-layer models */
-void TestdataGen::emit()
+int ProgGen::emit()
 {
-  TRINITY_FMAP_PARA para_fmap_out_prev;
-  bool remove_pad = true;
-  bool depth_first = true;
-  bool bin_file_only = true;
   const string mkdir_cmd = "mkdir -p " + dir;
-  int ret = 0;
+  const string encode_cmd = "encoder " + dir + "/program.asm " + dir + "/program.bin";
 
-  ret = system(mkdir_cmd.c_str());
-  if (ret != 0) {
-    cerr << "Fail to create a directory, " << mkdir_cmd << ", where the test data would be placed" << endl;
-    TR_ASSERT(false);
+  if (system(mkdir_cmd.c_str()) != 0) {
+    cerr << "Fail to create a directory, " << mkdir_cmd
+         << ", where the result would be placed" << endl;
+    return -1;
   }
 
   ofs_asm.open(dir + "/program.asm");
@@ -213,96 +202,45 @@ void TestdataGen::emit()
     TRINITY_FMAP_PARA para_fmap_out;
     WGT_PARA para_weight;
 
-    /** parse the op and set its variables */
-    if (!calc_tensor_data_size(ops[idx], para_fmap_in, para_weight, para_fmap_out)) {
-      cerr << "Fail to parse a trinity op" << endl;
-      TR_ASSERT(false);
-    }
-
-    /** compare dimensions of this layer's input and the previous layer's output */
-    if (idx != 0 &&
-        !(para_fmap_in.height == para_fmap_out_prev.height &&
-          para_fmap_in.width == para_fmap_out_prev.width &&
-          para_fmap_in.depth == para_fmap_out_prev.depth)) {
-      cerr << "Mismatch detected for dimensions between layers" << endl;
-      TR_ASSERT(false);
-    }
-    para_fmap_out_prev = para_fmap_out;
-
-    WGT_BIN_DATA wmod = WBIN_NORMAL;
-
-    /** input (== in0) fmap (random generation for the first layer) */
-    SIZE3D size3d_in;
-    size3d_in.depth = para_fmap_in.depth;
-    size3d_in.height = para_fmap_in.height;
-    size3d_in.width = para_fmap_in.width;
-
-    TR_FMAP trinity_fmap_in;
-    trinity_fmap_in.alloc(size3d_in);
-    if (idx == 0) {
-      trinity_fmap_in.gen_rand(9, 0);
-      trinity_fmap_in.write_data_file(dir, "_input_fmap_" + to_string(idx),
-          remove_pad, depth_first, bin_file_only);
-    } else {
-      Point1D<int8_t, 8> data_mem;
-      data_mem.clean();
-      TR_ASSERT(read_data_file(dir, "_output_fmap_" + to_string(idx-1), data_mem));
-      trinity_fmap_in.set_data(data_mem);
+    /** parse the operation and assign its parameters */
+    if (!calc_tensor_data_size(ops[idx],
+          para_fmap_in, para_weight, para_fmap_out)) {
+      cerr << "Fail to parse a trinity operation" << endl;
+      ofs_asm.close();
+      return -1;
     }
 
-    /** output fmap (SW emulation) */
-    SIZE3D size3d_out;
-    size3d_out.depth = para_fmap_out.depth;
-    size3d_out.height = para_fmap_out.height;
-    size3d_out.width = para_fmap_out.width;
-
-    TR_FMAP trinity_fmap_out;
-    trinity_fmap_out.alloc(size3d_out);
-
-    /** esum (== in1) fmap (if exist) */
-    TR_FMAP trinity_esum_in;
-    if (ops[idx].info.OPCODE == TRINITY_OPCODE_ESUM || ops[idx].info.CNV_ESUM_EN) {
-      if (ops[idx].info.OPCODE == TRINITY_OPCODE_ESUM) {
-        trinity_esum_in.alloc(size3d_in);
-      } else {
-        trinity_esum_in.alloc(size3d_out);
-      }
-      trinity_esum_in.gen_rand(9, 0);
-      trinity_esum_in.write_data_file(dir, "_input_esum_" + to_string(idx),
-          remove_pad, depth_first, bin_file_only);
-    }
-
-    /** input weight (random generation) */
-    TrinityWgt trinity_weight;
+    /** generate weight data */
     if (ops[idx].info.OPCODE < TRINITY_OPCODE_MAXP) {
+      TrinityWgt trinity_weight;
+
       trinity_weight.alloc(para_weight);
       trinity_weight.gen_rand();
-      trinity_weight.write_data_file(dir, "_input_weight_" + to_string(idx),
-          wmod, bin_file_only);
+      trinity_weight.write_data_file(dir, "input_weight_" + to_string(idx),
+          WBIN_NORMAL, true);
     }
 
-    /** run emulation */
-    do_main_operation(ops[idx],
-        trinity_fmap_in,
-        trinity_esum_in,
-        trinity_weight,
-        trinity_fmap_out);
-
-    trinity_fmap_out.write_data_file(dir, "_output_fmap_" + to_string(idx),
-        remove_pad, depth_first, bin_file_only);
-
     write_asm_file(idx, para_fmap_in, para_fmap_out, para_weight);
   }
 
   ofs_asm << "SAW" << endl;
   ofs_asm << "NOP" << endl;
   ofs_asm.close();
+
+  if (system(encode_cmd.c_str()) != 0) {
+    cerr << "Fail to encode visa program, " << encode_cmd << endl;
+    return -1;
+  }
+
+  return 0;
 }
 
 /** @brief generate testdata for testcase1 (CONV/CONV) */
-void gen_testdata_testcase1(const char* dir, uint32_t version)
+int gen_prog_testcase1(const string base, const string name)
 {
-  TestdataGen gen(dir, 2, version);
+  ProgGen gen(base + '/' + name);
+
+  cout << "testcase1: CONV/CONV" << endl;
 
   /** 1) 3x3 CONV */
   TRINITY_CORE_PARA_OP conv1;
@@ -349,13 +287,15 @@ void gen_testdata_testcase1(const char* dir, uint32_t version)
 
   gen.append(conv2);
 
-  gen.emit();
+  return gen.emit();
 }
 
 /** @brief generate testdata for testcase2 (CONV/ESUM/MAXP) */
-void gen_testdata_testcase2(const char* dir, uint32_t version)
+int gen_prog_testcase2(const string base, const string name)
 {
-  TestdataGen gen(dir, 3, version);
+  ProgGen gen(base + '/' + name);
+
+  cout << "testcase2: CONV/ESUM/MAXP" << endl;
 
   /** 1) 3x3 CONV */
   TRINITY_CORE_PARA_OP conv;
@@ -416,13 +356,15 @@ void gen_testdata_testcase2(const char* dir, uint32_t version)
 
   gen.append(maxp);
 
-  gen.emit();
+  return gen.emit();
 }
 
 /** @brief generate testdata for testcase3 (CONVE/ReLU/AVGP) */
-void gen_testdata_testcase3(const char* dir, uint32_t version)
+int gen_prog_testcase3(const string base, const string name)
 {
-  TestdataGen gen(dir, 3, version);
+  ProgGen gen(base + '/' + name);
+
+  cout << "testcase3: CONVE/ReLU/AVGP" << endl;
 
   /** 1) 3x3 CONVE */
   TRINITY_CORE_PARA_OP conve;
@@ -491,27 +433,24 @@ void gen_testdata_testcase3(const char* dir, uint32_t version)
 
   gen.append(avgp);
 
-  gen.emit();
+  return gen.emit();
 }
 
 /** @brief main function */
-int main()
+int main(int argc, char **argv)
 {
-  std::srand(time(NULL));
-
-  cout << "testcase1: CONV/CONV" << endl;
-  gen_testdata_testcase1("testcase1_v1", 1);
-  gen_testdata_testcase1("testcase1_v2", 2);
+  if (argc != 2) {
+    cerr << "Usage: " << argv[0] << " result_dir" << endl;
+    return -1;
+  }
 
-  cout << "testcase2: CONV/ESUM/MAXP" << endl;
-  gen_testdata_testcase2("testcase2_v1", 1);
-  gen_testdata_testcase2("testcase2_v2", 2);
+  std::srand(time(NULL));
 
-  cout << "testcase3: CONVE/ReLU/AVGP" << endl;
-  gen_testdata_testcase3("testcase3_v1", 1);
-  gen_testdata_testcase3("testcase3_v2", 2);
+  TR_ASSERT(gen_prog_testcase1(argv[1], "testcase1") == 0);
+  TR_ASSERT(gen_prog_testcase2(argv[1], "testcase2") == 0);
+  TR_ASSERT(gen_prog_testcase3(argv[1], "testcase3") == 0);
 
-  /** @todo add more test cases for multi-layer models */
+  /** @todo add more multi-layer models */
 
   return 0;
 }
index 8086925..ad1efe3 100644 (file)
@@ -1,6 +1,15 @@
 # Build tools
 
-gen_testdata_tool = executable('gen-testdata',
-    'testdata_gen.cpp',
-    dependencies : dependency('libnpuvision'),
+libnpuvision_dep = dependency('libnpuvision')
+
+gen_visa_asm = executable('gen_visa_prog',
+    'gen_visa_prog.cpp',
+    include_directories: ne_common_inc,
+    dependencies : [libnpuvision_dep],
+    install : false)
+
+gen_ref_data = executable('gen_ref_data',
+    'gen_ref_data.cpp',
+    include_directories: ne_common_inc,
+    dependencies : [libnpuvision_dep, ne_core_npu_emul_dep],
     install : false)