From f2fdb01d050650b4074ed2869e311ea8021ab9aa Mon Sep 17 00:00:00 2001
From: shingjan <yjshi03@gmail.com>
Date: Sun, 7 Jun 2020 00:55:21 -0500
Subject: [PATCH] add instruction for running onnx frontend

---
 hpvm/projects/onnx/frontend/README.md        |  10 ++
 hpvm/projects/onnx/frontend/config.py        |   2 +-
 hpvm/projects/onnx/frontend/graph_codegen.py | 119 -------------------
 hpvm/projects/onnx/frontend/hpvm_codegen.py  |  10 +-
 hpvm/projects/onnx/frontend/main.py          |   2 +-
 hpvm/projects/onnx/frontend/utils.py         |  16 +--
 6 files changed, 25 insertions(+), 134 deletions(-)
 create mode 100644 hpvm/projects/onnx/frontend/README.md

diff --git a/hpvm/projects/onnx/frontend/README.md b/hpvm/projects/onnx/frontend/README.md
new file mode 100644
index 0000000000..69ffe81f5a
--- /dev/null
+++ b/hpvm/projects/onnx/frontend/README.md
@@ -0,0 +1,10 @@
+### How to Run
+```
+python main.py
+```
+Set all your config, e.g. onnx model location, input size and emit directory for generated source code, in **config.py**.
+
+### Resources
+1. [ONNX overview](https://github.com/onnx/onnx/blob/master/docs/IR.md)
+2. [ONNX operator specs](https://github.com/onnx/onnx/blob/master/docs/Operators.md)
+3. [Conversion between models - available adapters](https://github.com/onnx/onnx/blob/master/onnx/version_converter.py#L21)
\ No newline at end of file
diff --git a/hpvm/projects/onnx/frontend/config.py b/hpvm/projects/onnx/frontend/config.py
index c8053b8d98..55ae0cebf4 100644
--- a/hpvm/projects/onnx/frontend/config.py
+++ b/hpvm/projects/onnx/frontend/config.py
@@ -1,6 +1,6 @@
 model_name = "alexnet"
 input_size = [1,2,3,4]
 onnx_file_dir = "../models/keras/alexnet.onnx"
-opset_version_default = 11
+opset_version_default = 10
 src_emit_dir = "./test_src"
 
diff --git a/hpvm/projects/onnx/frontend/graph_codegen.py b/hpvm/projects/onnx/frontend/graph_codegen.py
index f47abfefab..df02685386 100644
--- a/hpvm/projects/onnx/frontend/graph_codegen.py
+++ b/hpvm/projects/onnx/frontend/graph_codegen.py
@@ -25,118 +25,9 @@ class GraphCodeGen(object):
         self.var_cnt = self.var_cnt + 1
         return "var_" + str(self.var_cnt)
 
-    def emit_node_call(self, cur_node):
-        inst_str = ""
-        # check if all inputs of this node is mapped
-        for i in cur_node.input:
-            self.tensors[i].get_mapped_name() 
-        # set var name for output node
-        if len(cur_node.output) > 1:
-            raise ValueError("Output number for a single layer larger than 1!")
-        if cur_node.op_type in skip_layer:
-            mapped_output_name = self.get_last_var()
-        else:
-            mapped_output_name = self.get_new_var()
-        output_name = cur_node.output[0]
-        self.tensors[output_name].set_mapped_name(mapped_output_name)
-        #print(cur_node)
-        if cur_node.op_type == "Conv":# or cur_node.op_type == "DepthwiseConv":
-          input_var_name = self.tensors[cur_node.input[0]].get_mapped_name()
-          weight = cur_node.input[1]
-          strides = list()
-          padding = 0
-          for attr in cur_node.attribute:
-            if attr.name == "pads":
-                padding = attr.ints[0]
-            elif attr.name == "strides":
-                for stride in attr.ints:
-                    strides.append(stride)
-
-          inst_str += "void* " + mapped_output_name + " = "
-          inst_str += "tensorConvolution(" + input_var_name + ", "
-          inst_str += self.tensors[cur_node.input[1]].get_mapped_name() + ", "
-          inst_str += str(padding) + ", "
-          inst_str += str(padding) + ", "
-          inst_str += str(strides[0]) + ", "
-          inst_str += str(strides[1]) + ", "
-          inst_str += "1, 1); \n"
-
-          # check if has bias add (Optional)
-          # in ONNX it is only in Conv
-          # in Keras bias add could exist in Dense
-          if len(cur_node.input) == 3:
-            mapped_output_name2 = self.get_new_var()
-            inst_str += "void* " + mapped_output_name2 + " = "
-            inst_str += "tensorAdd(" + mapped_output_name + ", "
-            inst_str += self.tensors[cur_node.input[2]].get_mapped_name() + ""
-            inst_str += "); \n"
-            self.tensors[output_name].set_mapped_name(mapped_output_name2)
-        elif cur_node.op_type == "MaxPool" or cur_node.op_type == "AveragePool":  
-          input_var_name = self.tensors[cur_node.input[0]].get_mapped_name()
-          strides = list()
-          pool_size = list()
-          for attr in cur_node.attribute:
-            if attr.name == "kernel_shape":
-                for pool in attr.ints:
-                    pool_size.append(pool)
-            elif attr.name == "strides":
-                for stride in attr.ints:
-                    strides.append(stride)
-          # FIXME: Non-same padding is *NOT* currently supported
-          padding = 0
-          pool_type = 0
-          if cur_node.op_type == "MaxPool":
-            pool_type = "0"   
-          elif cur_node.op_type == "AveragePool":
-            pool_type = "1"
-          # tensorPooling(input, pool_type, pool_h, pool_w, v_pad, h_pad, v_stride, h_stride)
-          inst_str += "void* " + mapped_output_name + " = "
-          inst_str += "tensorPooling(" + input_var_name + "," + pool_type + "," + str(pool_size[0]) + "," + str(pool_size[1]) 
-          inst_str +=  "," + str(padding) + "," + str(padding) + "," + str(strides[0]) + "," + str(strides[1])
-          inst_str += "); \n"
-          # self.program_str += inst_str
-        elif cur_node.op_type == "MatMul":
-            left_input = self.tensors[cur_node.input[0]].get_mapped_name()
-            right_input = self.tensors[cur_node.input[1]].get_mapped_name()
-            inst_str += "void* " + mapped_output_name + " = "
-            inst_str += "tensorGemmGPU(" + left_input + ", " + right_input + "); \n"
-        elif cur_node.op_type == "Add":
-            left_input = self.tensors[cur_node.input[0]].get_mapped_name()
-            right_input = self.tensors[cur_node.input[1]].get_mapped_name()
-            inst_str += "void* " + mapped_output_name + " = "
-            inst_str += "tensorAdd(" + left_input + ", " + right_input + "); \n"
-        elif cur_node.op_type == "Softmax":
-            mapped_input_name = self.tensors[cur_node.input[0]].get_mapped_name()
-            inst_str += "void* " + mapped_output_name + " = "
-            inst_str += "tensorSoftmax(" + mapped_input_name + "); \n"
-        elif cur_node.op_type == "Relu":
-            mapped_input_name = self.tensors[cur_node.input[0]].get_mapped_name()
-            inst_str += "void* " + mapped_output_name + " = "
-            inst_str += "tensorRelu(" + mapped_input_name + "); \n"
-        elif cur_node.op_type == "BatchNormalization":
-          mapped_input_name = self.tensors[cur_node.input[0]].get_mapped_name()
-          epsilon = ""
-          for attr in cur_node.attribute:
-            if attr.name == "epsilon":
-                epsilon = str(attr.f)
-          inst_str += "void* " + mapped_output_name + " = "
-          inst_str += "tensorBatchNorm(" + mapped_input_name + ", "
-          inst_str += self.tensors[cur_node.input[1]].get_mapped_name() + ", "
-          inst_str += self.tensors[cur_node.input[2]].get_mapped_name() + ", "
-          inst_str += self.tensors[cur_node.input[3]].get_mapped_name() + ", "
-          inst_str += self.tensors[cur_node.input[4]].get_mapped_name() + ", "
-          inst_str += str(epsilon)
-          inst_str += "); \n"
-        elif cur_node.op_type in skip_layer:
-            pass
-        else:
-            raise ValueError("Not supported op type:" + cur_node.op_type + "! \n")
-        return inst_str
-
     ################################################
     # CodeGen functions
     ################################################
-
     def emit_weights(self):
         weights_str = ""
         weights_str += "std::string dir_prefix = std::string(\"" + str(self.weights_dir) + "\");\n"
@@ -178,10 +69,6 @@ class GraphCodeGen(object):
             self.tensors[cur_node.output[0]].set_mapped_name(mapped_output_name)
             self.program_str += node.codegen(self.tensors)
 
-    def emit_graph2(self):
-        for node in self.graph.node:
-            self.program_str += self.emit_node_call(node)
-
     def emit_header(self):
         headers = "\n#include <stdio.h> \n"
         headers += "#include <stdlib.h> \n"
@@ -201,10 +88,6 @@ class GraphCodeGen(object):
         self.program_str += initialization
 
     def emit_footer(self, test_data=None):
-        #if test_data is not None and self.dfg.last_node is not None:
-            #last_node = self.dfg.last_node
-            #output_var = self.output_map[last_node.layer_name]
-
         destructors = "\nllvm_hpvm_cleanupTensorRt(); \n"
         end_main = "\nreturn 0; \n\n}\n"
         self.program_str += destructors
@@ -237,12 +120,10 @@ class GraphCodeGen(object):
     def emit_batch_loop_end(self):
         end_loop_str = ""
         end_loop_str += "\nuint32_t* labels = readLabelsBatch3(labels_path.c_str(),start,end); \n"
-        #last_node = self.dfg.last_node
         mapped_output_var = self.tensors[self.graph.output[0].name].get_mapped_name()
         accuracy_call = "\nfloat accuracy = computeAccuracy3(labels, " + \
             mapped_output_var + "); \n"
         end_loop_str += accuracy_call
-        #end_loop_str += "float accuracy = computeAccuracy2(labels, batch_size, var_60); "
         end_loop_str += "final_accuracy += accuracy; \n"
         end_loop_str += "freeBatchMemory(); \n "
         end_loop_str += "\n}\n\n"
diff --git a/hpvm/projects/onnx/frontend/hpvm_codegen.py b/hpvm/projects/onnx/frontend/hpvm_codegen.py
index 48cfaec36b..2446e0747f 100644
--- a/hpvm/projects/onnx/frontend/hpvm_codegen.py
+++ b/hpvm/projects/onnx/frontend/hpvm_codegen.py
@@ -1,18 +1,16 @@
 from tensor import WeightTensor
-
-skip_layer = ["Identity", "Flatten", "Pad"]
+from utils import skip_layer
 
 class HpvmCodeGen:
-    def __init__(self, DFG, weights_dir, test_data=None, test_labels=None):
+    def __init__(self, DFG, weights_dir, test_data_shape=None):
         self.program_str = ""
         self.graph = DFG.graph
         self.tensors = DFG.tensors
         self.nodes = DFG.nodes
         self.var_cnt = 0
         self.weights_dir = weights_dir
-        self.test_data = test_data
-        self.test_labels = test_labels
-        self.filter_names = dict()  # essentially tensors
+        self.test_data_shape = test_data_shape
+        self.filter_names = dict()  # essentially weight tensors
         for tensor in self.tensors.values():
             if isinstance(tensor, WeightTensor):
                 self.filter_names[tensor.get_mapped_name()] = 1
diff --git a/hpvm/projects/onnx/frontend/main.py b/hpvm/projects/onnx/frontend/main.py
index 6014569acd..b1dba8d21f 100644
--- a/hpvm/projects/onnx/frontend/main.py
+++ b/hpvm/projects/onnx/frontend/main.py
@@ -6,7 +6,7 @@ import glob
 
 def check_version(model, new_version):
     try:
-        opset = model.opset_import[0].version + 1 if model.opset_import else 1
+        opset = model.opset_import[0].version if model.opset_import else 1
     except AttributeError:
         opset = 1  # default opset version set to 1 if not specified
     print("opset version: ", opset)
diff --git a/hpvm/projects/onnx/frontend/utils.py b/hpvm/projects/onnx/frontend/utils.py
index fa0d9de28f..2a43557958 100644
--- a/hpvm/projects/onnx/frontend/utils.py
+++ b/hpvm/projects/onnx/frontend/utils.py
@@ -90,18 +90,20 @@ def dumpConvWeights(file_name, weights):
     print ("*DumpConvWeights")
     print("-min_val = ", np.amin(weights))
     print("-max_val = ", np.amax(weights))
-
-    N = weights.shape[0]
-    C = weights.shape[1]
-    H = weights.shape[2]
-    W = weights.shape[3]
-
+    weights_keras = np.einsum('NCWH->WHCN', weights)
+    print ("Convert shape for conv weights: " + str(weights_keras.shape))
+    N = weights_keras.shape[3]
+    C = weights_keras.shape[2]
+    H = weights_keras.shape[1]
+    W = weights_keras.shape[0]
     f = open(file_name, "wb")
     for i in range(N):
         for j in range(C):
             for k in range(H):
                 for l in range(W):
-                    f.write(weights[i][j][k][l])
+                    # FIXME: Legacy code from Keras frontend 
+                    # should actually interchange k with l
+                    f.write(weights_keras[k][l][j][i])
 
     f.close()
 
-- 
GitLab