Skip to content
Snippets Groups Projects
Commit 6056da63 authored by shingjan's avatar shingjan
Browse files

emit structure almost finished

parent 3c63fb81
No related branches found
No related tags found
No related merge requests found
import sys
import numpy as np
import os
from operators import *
from ir import *
class Node(object):
def __init__(self, name, shape, dtype):
self._name = name
self._shape = shape if shape else {}
self._dtype = dtype
def __str__(self):
return "Node: " + self._name + " with shape: " + str(self._shape) + " and data type " + str(self._dtype)
__repr__ = __str__
class GraphBuilder(object):
def __init__(self, model, shape, dtype, opset):
self._nodes = {}
self._params = {}
self._renames = {}
self._num_input = 0
self._num_param = 0
self._dtype = dtype
self._graph = model.graph
self._opset = opset
self._shape = shape if shape else self._build_shape()
self.program_str = ""
self.dfg = DFG()
################################################
# Aux functions for graph building
################################################
def _build_shape(self):
shape = {}
for input in self._graph.input:
# get type of input tensor
tensor_type = input.type.tensor_type
# check if it has a shape:
if (tensor_type.HasField("shape")):
shape[input.name] = tensor_type.shape
return shape
def _parse_array(self, tensor_proto):
try:
from onnx.numpy_helper import to_array
except ImportError as e:
raise ImportError(
"Unable to import onnx which is required {}".format(e))
np_array = to_array(tensor_proto).reshape(tuple(tensor_proto.dims))
return np_array
def _parse_value_proto(self, value_proto):
"""Parse ValueProto or raw str."""
try:
name = value_proto.name
except AttributeError:
name = value_proto
return name
def _parse_dtype(self, value_proto, dtype):
"""Parse dtype."""
try:
from onnx.mapping import TENSOR_TYPE_TO_NP_TYPE
return TENSOR_TYPE_TO_NP_TYPE[value_proto.type.tensor_type.elem_type].name
except AttributeError:
return dtype
################################################
# Emit functions for code generation
################################################
def dump_weights(self):
for init_tensor in self._graph.initializer:
print(init_tensor)
def build_graph(self):
# parse init tensors
for init_tensor in self._graph.initializer:
if not init_tensor.name.strip():
raise ValueError("Tensor's name is required.")
self._params[init_tensor.name] = self._parse_array(init_tensor)
self._nodes[init_tensor.name] = Node(init_tensor.name,
self._params[init_tensor.name].shape,
self._params[init_tensor.name].dtype)
# parse inputs
# from onnx v0.2, GraphProto.input has type ValueInfoProto,
# and the name is 'i.name'
for i in self._graph.input:
i_name = self._parse_value_proto(i)
d_type = self._parse_dtype(i, 'float32')
if i_name in self._params:
# i is a param instead of input
self._num_param += 1
self._params[i_name] = self._params.pop(i_name)
self._nodes[i_name] = Node(i_name,
self._params[i_name].shape,
self._params[i_name].dtype)
else:
self._num_input += 1
if i_name in self._shape:
tshape = self._shape[i_name]
else:
raise ValueError("Must provide an input shape for `{0}`.".format(i_name))
if isinstance(self._dtype, dict):
dtype = self._dtype[i_name] if i_name in self._dtype else d_type
else:
dtype = d_type
self._nodes[i_name] = Node(i_name,
tshape,
dtype)
for node in self._graph.node:
op_name = node.op_type
print("###############################")
print(op_name)
#print("attribute: " + str(node.attribute))
#print("input: " + str(node.input))
#print("output: " + str(node.output))
#print(self._nodes)
def traverse_graph(self, cur_node, visited):
if cur_node in visited:
return
if dfg.predVisited(cur_node, visited):
visited_nodes[cur_node.layer_name] = True
self.program_str += cur_node.codegen()
for output_node in cur_node.outputs:
self.codegenNode(dfg, output_node, visited)
def emit_graph(self):
self.build_graph()
visited_nodes = {}
self.traverse_graph(self.dfg.root, visited)
def emit_header(self):
headers = "\n#include <stdio.h> \n"
headers += "#include <stdlib.h> \n"
headers += "#include <unistd.h> \n"
headers += "#include <fcntl.h> \n"
headers += "#include <sys/types.h> \n"
headers += "#include <sys/stat.h> \n"
headers += "#include <string.h> \n"
headers += "#include \"../../tensor_runtime/include/tensor_runtime.h\" \n"
headers += "#include \"../include/utils.h\" \n\n"
main_func = "int main(){ \n\n"
initialization = "llvm_hpvm_initTensorRt(0); \n\n"
self.program_str += headers
self.program_str += main_func
self.program_str += initialization
def emit_footer(self, test_data):
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
self.program_str += end_main
def emit_batch_loop(self, x_test):
N = x_test.shape[0]
C = x_test.shape[1]
H = x_test.shape[2]
W = x_test.shape[3]
loop_str = ""
loop_str += "\nstartMemTracking(); \n\n"
loop_str += "int test_input_size = " + str(N) + "; \n"
loop_str += "int batch_size = " + str(N) + "; \n"
loop_str += "int batch_count = test_input_size / batch_size; \n"
loop_str += "float final_accuracy = 0.0; \n\n"
loop_str += "for(int i = 0; i < batch_count; i++){ \n\n"
loop_str += "int start = i * batch_size; \n"
loop_str += "int end = (i + 1) * batch_size; \n"
loop_str += "\nvoid* input = readInputBatch(input_path.c_str(),0,start,end,"
loop_str += str(C) + "," + str(H) + "," + str(W) + "); \n\n"
self.program_str += loop_str
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
output_var = self.output_map[last_node.layer_name]
accuracy_call = "\nfloat accuracy = computeAccuracy3(labels, " + 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"
end_loop_str += "final_accuracy = final_accuracy / batch_count; \n"
end_loop_str += "dumpFinalAccuracy(final_accuracy); \n\n"
self.program_str += end_loop_str
def emit_program(self, src_dir):
f = open(src_dir + "/src.cc", "w+")
f.write(self.program_str)
f.close()
'''
Compile is a top level function to compile an onnx model into C/C++
program with HPVM intrinsics
'''
def codegen(self, weights_dir, test_data, test_labels):
if os.path.exists(weights_dir):
raise ValueError("Weight dir existed. Compilation interrupted!")
os.mkdir(weights_dir)
self.emit_header()
self.emit_weights(weights_dir)
self.emit_batch_loop(test_data)
self.emit_graph()
self.emit_batch_loop_end()
self.emit_footer(test_data)
self.emit_program(weights_dir)
class DFG:
class DFG(object):
root_set = False;
......@@ -87,10 +87,9 @@ class DFG:
visited_nodes = {}
# Starting traversal at the DFG root node
self.traverseNode(self.root_node, visited_nodes)
## This should be the place where partial evaluation happens
class NodeFactory:
def build(self, layer):
## This should be the place where partial evaluation happens
def emitNode(self, layer):
if layer.op_type == "Conv":
pass
elif layer.op_type == "Tanh":
......@@ -107,10 +106,11 @@ class NodeFactory:
pass
elif layer.op_type == "Identity":
pass
else
else:
raise ValueError("Unsupported operator type!")
class DFGNode:
class DFGNode(object):
def add_output(self, output_node):
self.outputs.append(output_node)
def add_input(self, input_node):
......
......@@ -11,7 +11,7 @@ from onnx import numpy_helper, version_converter
from onnx_frontend.util import convert_to_hpvm
#model = onnx.load('../models/mnist/mnist.onnx')
model = onnx.load('../models/alexnet/alexnet.onnx')
model = onnx.load('../models/keras/alexnet.onnx')
test_data_dir = '../models/mnist/test_data_set_0'
# print('The model before conversion:\n{}'.format(model))
......
......@@ -2,6 +2,8 @@ import sys
import numpy as np
import os
from operators import *
class Node(object):
def __init__(self, name, shape, dtype):
self._name = name
......@@ -12,7 +14,6 @@ class Node(object):
__repr__ = __str__
class GraphBuilder(object):
def __init__(self, model, shape, dtype, opset):
self._nodes = {}
self._params = {}
......@@ -23,8 +24,11 @@ class GraphBuilder(object):
self._graph = model.graph
self._opset = opset
self._shape = shape if shape else self._build_shape()
def dump_weights(self):
print(self._graph.initializer)
def build_cfg(self):
def build_graph(self):
# parse init tensors
for init_tensor in self._graph.initializer:
if not init_tensor.name.strip():
......@@ -110,4 +114,4 @@ class GraphBuilder(object):
if os.path.exists(weights_dir):
raise ValueError("Weight dir existed. Compilation interrupted!")
os.mkdir(weights_dir)
self.build_cfg()
\ No newline at end of file
self.build_graph()
\ No newline at end of file
import sys
import numpy as np
import os
from .graph_builder import GraphBuilder
from graph_builder import GraphBuilder
def check_model(onnx_model):
try:
......@@ -59,4 +59,5 @@ def convert_to_hpvm(model,
opset = 1 # default opset version set to 1 if not specified
print("opset version: ", opset)
gb = GraphBuilder(model, shape, dtype, opset)
gb.build_cfg()
\ No newline at end of file
gb.dump_weights()
gb.build_graph()
\ No newline at end of file
from .ir import DFGNode, ActivationNode, LogicalOpNode
from ir import DFGNode, ActivationNode, LogicalOpNode
class AddNode(DFGNode):
def __init__(self, layer):
DFGNode.__init__(self, layer)
def codegen(self):
pass
input_vars = self.getMultipleInputNames(cur_node)
inst_str = "void* " + out_var_name1 + " = "
inst_str += "tensorAdd(" + input_vars[0] + ", " + input_vars[1] + "); \n"
return inst_str
class MatMulNode(DFGNode):
def __init__(self, layer):
DFGNode.__init__(self, layer)
def codegen(self):
pass
class SoftMaxNode(DFGNode):
def __init__(self, layer):
DFGNode.__init__(self, layer)
def codegen(self):
pass
class Conv2DNode(DFGNode):
def __init__(self, layer):
DFGNode.__init__(self, layer)
......@@ -20,7 +32,39 @@ class Conv2DNode(DFGNode):
print("\t", self.strides)
print("\tPadding = ", self.padding)
def codegen(self):
pass
input_var_name = self.getSingleInputName(cur_node)
weights = cur_node.weights
strides = cur_node.strides
padding = 0
if cur_node.padding.strip() == "valid":
padding = 0
else:
padding = cur_node.padding
padding = int((weights.shape[0] - 1) / 2)
prev_padding = self.getPrevLayerPadding(cur_node)
if prev_padding != None:
# FIXME: currently only supporting symmetric padding
padding = prev_padding[0][0]
inst_str = "void* " + out_var_name1 + " = "
inst_str += "tensorConvolution(" + input_var_name + ", "
inst_str += cur_node.layer_name + "_w, "
inst_str += str(padding) + ", "
inst_str += str(padding) + ", "
inst_str += str(strides[0]) + ", "
inst_str += str(strides[1]) + ", "
inst_str += "1, "
if layer_type == "DepthwiseConv2D":
C = weights.shape[2]
inst_str += str(C) + "); \n"
else:
inst_str += "1); \n"
if strides[0] > 1 and cur_node.padding.strip() == "same":
print ("!ERROR: Same Padding not supported for Conv with Stride > 1")
print ("Use: ZeroPadding2D(padding=(" + str(padding) + "," + str(padding) + "));\n");
sys.exit(0)
return inst_str
class DepthwiseConv2DNode(DFGNode):
def __init__(self, layer):
DFGNode.__init__(self, layer)
......@@ -63,7 +107,7 @@ class DepthwiseConv2DNode(DFGNode):
print ("!ERROR: Same Padding not supported for Conv with Stride > 1")
print ("Use: ZeroPadding2D(padding=(" + str(padding) + "," + str(padding) + "));\n");
sys.exit(0)
return += inst_str
return inst_str
class DenseNode(DFGNode):
def __init__(self, layer):
......@@ -88,7 +132,11 @@ class MaxPool2DNode(DFGNode):
print("\t pool_size = ", self.pool_size)
print("\t strides = ", self.strides)
def codegen(self):
pass
inst_str = "void* " + out_var_name1 + " = "
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"
class AveragePooling2DNode(DFGNode):
def __init__(self, layer):
DFGNode.__init__(self, layer)
......@@ -97,7 +145,12 @@ class AveragePooling2DNode(DFGNode):
print("\t pool_size = ", self.pool_size)
print("\t strides = ", self.strides)
def codegen(self):
pass
inst_str = "void* " + out_var_name1 + " = "
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"
return insr_str
class ZeroPadding2DNode(DFGNode):
def __init__(self, layer):
DFGNode.__init__(self, layer)
......@@ -106,6 +159,7 @@ class ZeroPadding2DNode(DFGNode):
print ("padding = ", self.padding)
def codegen(self):
pass
class BatchNormalizationNode(DFGNode):
def __init__(self, layer):
DFGNode.__init__(self, layer)
......
import sys
import numpy as np
import os
from .graph_builder import GraphBuilder
def check_model(onnx_model):
try:
from onnx import checker, onnx_cpp2py_export
if hasattr(checker, 'check_model'):
# try use onnx's own model checker before converting any model
try:
checker.check_model(onnx_model)
print("onnx model is checked valid.")
except onnx_cpp2py_export.checker.ValidationError as e:
import warnings
# the checker is a bit violent about errors, so simply print warnings here
warnings.warn(str(e))
except ImportError as e:
raise ImportError("Unable to import onnx.checker which is required {}".format(e))
def convert_to_hpvm(model,
shape=None,
dtype="float32",
opset=None):
"""Converting an onnx model to equivalent HPVM IR
ONNX graphs are represented as Python Protobuf objects.
The companion parameters will be handled automatically.
However, the input names from onnx graph is vague, mixing inputs and
network weights/bias such as "1", "2"...
For convenience, we rename the `real` input names to "input_0",
"input_1"... And renaming parameters to "param_0", "param_1"...
Parameters
----------
model : protobuf object
ONNX ModelProto after ONNX v1.1.0
shape : dict of str to tuple, optional
The input shape to the graph
dtype : str or dict of str to str
The input types to the graph
opset : int, optional
Override to autodetected opset.
This can be helpful for some testing.
Returns
-------
"""
check_model(model)
graph = model.graph
if opset is None:
try:
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)
gb = GraphBuilder(model, shape, dtype, opset)
gb.build_cfg()
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment