Skip to content
Snippets Groups Projects
Commit be3a0418 authored by Yaman Umuroglu's avatar Yaman Umuroglu
Browse files

[Refactor] use correct attribute accessors for custom op codegen

parent 6907c12d
No related branches found
No related tags found
No related merge requests found
from abc import abstractmethod from abc import abstractmethod
import os import os
from finn.custom_op import CustomOp from finn.custom_op import CustomOp
import finn.core.utils as util
class HLSCustomOp(CustomOp): class HLSCustomOp(CustomOp):
...@@ -37,10 +36,6 @@ class HLSCustomOp(CustomOp): ...@@ -37,10 +36,6 @@ class HLSCustomOp(CustomOp):
""" """
self.code_gen_dict = {} self.code_gen_dict = {}
self.tmp_dir = ""
self.code_gen_dir = util.get_by_name(onnx_node.attribute, "code_gen_dir")
self.executable_path = ""
def get_nodeattr_types(self): def get_nodeattr_types(self):
return {"code_gen_dir": ("s", False, ""), "executable_path": ("s", False, "")} return {"code_gen_dir": ("s", False, ""), "executable_path": ("s", False, "")}
...@@ -62,8 +57,8 @@ class HLSCustomOp(CustomOp): ...@@ -62,8 +57,8 @@ class HLSCustomOp(CustomOp):
# transform list into long string separated by '\n' # transform list into long string separated by '\n'
code_gen_line = "\n".join(self.code_gen_dict[key]) code_gen_line = "\n".join(self.code_gen_dict[key])
template = template.replace(key, code_gen_line) template = template.replace(key, code_gen_line)
code_gen_dir = self.get_nodeattr("code_gen_dir")
f = open(os.path.join(self.tmp_dir, "execute_{}.cpp".format(node.op_type)), "w") f = open(os.path.join(code_gen_dir, "execute_{}.cpp".format(node.op_type)), "w")
f.write(template) f.write(template)
f.close() f.close()
......
import os import os
import subprocess import subprocess
import tempfile as tmp
import numpy as np import numpy as np
...@@ -151,7 +150,6 @@ class StreamingFCLayer_Batch(HLSCustomOp): ...@@ -151,7 +150,6 @@ class StreamingFCLayer_Batch(HLSCustomOp):
return ret return ret
def generate_weights(self, model): def generate_weights(self, model):
weights = model.get_initializer(self.onnx_node.input[1]) weights = model.get_initializer(self.onnx_node.input[1])
# convert weights into hlslib-compatible format # convert weights into hlslib-compatible format
weight_tensor = self.get_hls_compatible_weight_tensor(weights) weight_tensor = self.get_hls_compatible_weight_tensor(weights)
...@@ -164,7 +162,8 @@ class StreamingFCLayer_Batch(HLSCustomOp): ...@@ -164,7 +162,8 @@ class StreamingFCLayer_Batch(HLSCustomOp):
weight_tensor, export_wdt, "weights", True, True weight_tensor, export_wdt, "weights", True, True
) )
# write weights into params.h # write weights into params.h
f_weights = open("{}/params.h".format(self.tmp_dir), "w") code_gen_dir = self.get_nodeattr("code_gen_dir")
f_weights = open("{}/params.h".format(code_gen_dir), "w")
if export_wdt.bitwidth() != 1: if export_wdt.bitwidth() != 1:
f_weights.write( f_weights.write(
...@@ -200,7 +199,8 @@ class StreamingFCLayer_Batch(HLSCustomOp): ...@@ -200,7 +199,8 @@ class StreamingFCLayer_Batch(HLSCustomOp):
threshold_tensor, tdt, "thresholds", False, True threshold_tensor, tdt, "thresholds", False, True
) )
# write thresholds into thresh.h # write thresholds into thresh.h
f_thresh = open("{}/thresh.h".format(self.tmp_dir), "w") code_gen_dir = self.get_nodeattr("code_gen_dir")
f_thresh = open("{}/thresh.h".format(code_gen_dir), "w")
tdt_hls = tdt.get_hls_datatype_str() tdt_hls = tdt.get_hls_datatype_str()
odt_hls = self.get_output_datatype().get_hls_datatype_str() odt_hls = self.get_output_datatype().get_hls_datatype_str()
f_thresh.write( f_thresh.write(
...@@ -218,13 +218,8 @@ class StreamingFCLayer_Batch(HLSCustomOp): ...@@ -218,13 +218,8 @@ class StreamingFCLayer_Batch(HLSCustomOp):
def execute_node(self, context, graph): def execute_node(self, context, graph):
node = self.onnx_node node = self.onnx_node
# make temporary directory for generated files # TODO ensure codegen dir exists
self.tmp_dir = tmp.mkdtemp(prefix=str(node.op_type) + "_") code_gen_dir = self.get_nodeattr("code_gen_dir")
# create empty list for temporary files to enable the option
# to delete the files after the execution
temp_files = []
# create a npy file fore each input of the node (in_ind is input index) # create a npy file fore each input of the node (in_ind is input index)
in_ind = 0 in_ind = 0
for inputs in node.input: for inputs in node.input:
...@@ -239,48 +234,25 @@ class StreamingFCLayer_Batch(HLSCustomOp): ...@@ -239,48 +234,25 @@ class StreamingFCLayer_Batch(HLSCustomOp):
if self.get_input_datatype() == DataType.BIPOLAR: if self.get_input_datatype() == DataType.BIPOLAR:
# store bipolar activations as binary # store bipolar activations as binary
np.save( np.save(
os.path.join(self.tmp_dir, "input_{}.npy".format(in_ind)), os.path.join(code_gen_dir, "input_{}.npy".format(in_ind)),
(context[inputs] + 1) / 2, (context[inputs] + 1) / 2,
) )
else: else:
np.save( np.save(
os.path.join(self.tmp_dir, "input_{}.npy".format(in_ind)), os.path.join(code_gen_dir, "input_{}.npy".format(in_ind)),
context[inputs], context[inputs],
) )
temp_files.append("{}/input_{}.npy".format(self.tmp_dir, in_ind))
elif in_ind > 2: elif in_ind > 2:
raise Exception("Unexpected input found for StreamingFCLayer") raise Exception("Unexpected input found for StreamingFCLayer")
in_ind += 1 in_ind += 1
# execute precompiled executable
temp_files.append("{}/params.h".format(self.tmp_dir)) executable_path = self.get_nodeattr("executable_path")
temp_files.append("{}/thresh.h".format(self.tmp_dir)) # TODO sanity check executable
process_execute = subprocess.Popen(executable_path, stdout=subprocess.PIPE)
# code generation
self.code_generation(context)
# c++ compilation and execution flow
temp_files.append("{}/execute_{}.cpp".format(self.tmp_dir, node.op_type))
bash_compile = """g++ -o {}/execute_{} {}/execute_{}.cpp
/workspace/cnpy/cnpy.cpp -I/workspace/finn/src/finn/data/cpp -I/workspace/cnpy/
-I/workspace/finn-hlslib -I/workspace/vivado-hlslib
--std=c++11 -lz""".format(
self.tmp_dir, node.op_type, self.tmp_dir, node.op_type
)
process_compile = subprocess.Popen(bash_compile.split(), stdout=subprocess.PIPE)
process_compile.communicate()
bash_execute = "{}/execute_{}".format(self.tmp_dir, node.op_type)
process_execute = subprocess.Popen(bash_execute.split(), stdout=subprocess.PIPE)
process_execute.communicate() process_execute.communicate()
temp_files.append("{}/execute_{}".format(self.tmp_dir, node.op_type))
temp_files.append("{}/output.npy".format(self.tmp_dir))
# load output npy file # load output npy file
output = np.load("{}/output.npy".format(self.tmp_dir)) output = np.load("{}/output.npy".format(code_gen_dir))
context[node.output[0]] = output context[node.output[0]] = output
# deleting temporary files
# for temp_file in temp_files:
# os.remove(temp_file)
def global_includes(self): def global_includes(self):
self.code_gen_dict["$GLOBALS$"] = ['#include "weights.hpp"'] self.code_gen_dict["$GLOBALS$"] = ['#include "weights.hpp"']
...@@ -309,13 +281,14 @@ class StreamingFCLayer_Batch(HLSCustomOp): ...@@ -309,13 +281,14 @@ class StreamingFCLayer_Batch(HLSCustomOp):
] ]
def read_npy_data(self): def read_npy_data(self):
code_gen_dir = self.get_nodeattr("code_gen_dir")
dtype = self.get_input_datatype() dtype = self.get_input_datatype()
elem_bits = dtype.bitwidth() elem_bits = dtype.bitwidth()
packed_bits = self.get_instream_width() packed_bits = self.get_instream_width()
packed_hls_type = "ap_uint<%d>" % packed_bits packed_hls_type = "ap_uint<%d>" % packed_bits
elem_hls_type = dtype.get_hls_datatype_str() elem_hls_type = dtype.get_hls_datatype_str()
npy_type = "float" npy_type = "float"
npy_in = "%s/input_0.npy" % self.tmp_dir npy_in = "%s/input_0.npy" % code_gen_dir
self.code_gen_dict["$READNPYDATA$"] = [] self.code_gen_dict["$READNPYDATA$"] = []
self.code_gen_dict["$READNPYDATA$"].append( self.code_gen_dict["$READNPYDATA$"].append(
'npy2apintstream<%s, %s, %d, %s>("%s", in0);' 'npy2apintstream<%s, %s, %d, %s>("%s", in0);'
...@@ -352,13 +325,14 @@ class StreamingFCLayer_Batch(HLSCustomOp): ...@@ -352,13 +325,14 @@ class StreamingFCLayer_Batch(HLSCustomOp):
] ]
def dataoutstrm(self): def dataoutstrm(self):
code_gen_dir = self.get_nodeattr("code_gen_dir")
dtype = self.get_output_datatype() dtype = self.get_output_datatype()
elem_bits = dtype.bitwidth() elem_bits = dtype.bitwidth()
packed_bits = self.get_outstream_width() packed_bits = self.get_outstream_width()
packed_hls_type = "ap_uint<%d>" % packed_bits packed_hls_type = "ap_uint<%d>" % packed_bits
elem_hls_type = dtype.get_hls_datatype_str() elem_hls_type = dtype.get_hls_datatype_str()
npy_type = "float" npy_type = "float"
npy_out = "%s/output.npy" % self.tmp_dir npy_out = "%s/output.npy" % code_gen_dir
nf = int(self.get_nodeattr("MH") / self.get_nodeattr("PE")) nf = int(self.get_nodeattr("MH") / self.get_nodeattr("PE"))
shape = (1, nf, self.get_nodeattr("PE")) shape = (1, nf, self.get_nodeattr("PE"))
shape_cpp_str = str(shape).replace("(", "{").replace(")", "}") shape_cpp_str = str(shape).replace("(", "{").replace(")", "}")
......
import os import os
import subprocess import subprocess
import tempfile as tmp
import numpy as np import numpy as np
...@@ -25,48 +24,26 @@ class StreamingMaxPool_Batch(HLSCustomOp): ...@@ -25,48 +24,26 @@ class StreamingMaxPool_Batch(HLSCustomOp):
def execute_node(self, context, graph): def execute_node(self, context, graph):
node = self.onnx_node node = self.onnx_node
# make temporary directory for generated files code_gen_dir = self.get_nodeattr("code_gen_dir")
self.tmp_dir = tmp.mkdtemp(prefix=str(node.op_type) + "_")
# create empty list for temporary files to enable the option
# to delete the files after the execution
temp_files = []
# create a npy file fore each input of the node (in_ind is input index) # create a npy file fore each input of the node (in_ind is input index)
in_ind = 0 in_ind = 0
for inputs in node.input: for inputs in node.input:
np.save( if in_ind == 0:
os.path.join(self.tmp_dir, "input_{}.npy".format(in_ind)), np.save(
context[inputs], os.path.join(code_gen_dir, "input_{}.npy".format(in_ind)),
) context[inputs],
temp_files.append("{}/input_{}.npy".format(self.tmp_dir, in_ind)) )
else:
raise Exception("Unexpected input found for StreamingMaxPool_Batch")
in_ind += 1 in_ind += 1
# execute precompiled executable
# code generation executable_path = self.get_nodeattr("executable_path")
self.code_generation() # TODO sanity check executable
process_execute = subprocess.Popen(executable_path, stdout=subprocess.PIPE)
# c++ compilation and execution flow
temp_files.append("{}/execute_{}.cpp".format(self.tmp_dir, node.op_type))
bash_compile = """g++ -o {}/execute_{} {}/execute_{}.cpp
/workspace/cnpy/cnpy.cpp -I/workspace/cnpy/
-I/workspace/finn-hlslib -I/workspace/vivado-hlslib
--std=c++11 -lz""".format(
self.tmp_dir, node.op_type, self.tmp_dir, node.op_type
)
process_compile = subprocess.Popen(bash_compile.split(), stdout=subprocess.PIPE)
process_compile.communicate()
bash_execute = "{}/execute_{}".format(self.tmp_dir, node.op_type)
process_execute = subprocess.Popen(bash_execute.split(), stdout=subprocess.PIPE)
process_execute.communicate() process_execute.communicate()
temp_files.append("{}/execute_{}".format(self.tmp_dir, node.op_type))
temp_files.append("{}/output.npy".format(self.tmp_dir))
# load output npy file # load output npy file
output = np.load("{}/output.npy".format(self.tmp_dir)) output = np.load("{}/output.npy".format(code_gen_dir))
context[node.output[0]] = output context[node.output[0]] = output
# deleting temporary files
# for temp_file in temp_files:
# os.remove(temp_file)
def global_includes(self): def global_includes(self):
self.code_gen_dict["$GLOBALS$"] = ['#include "maxpool.h"'] self.code_gen_dict["$GLOBALS$"] = ['#include "maxpool.h"']
...@@ -85,13 +62,14 @@ class StreamingMaxPool_Batch(HLSCustomOp): ...@@ -85,13 +62,14 @@ class StreamingMaxPool_Batch(HLSCustomOp):
def read_npy_data(self): def read_npy_data(self):
node = self.onnx_node node = self.onnx_node
code_gen_dir = self.get_nodeattr("code_gen_dir")
# c++ code to read out an npy file # c++ code to read out an npy file
# and put it in hls::stream in the correct order # and put it in hls::stream in the correct order
self.code_gen_dict["$READNPYDATA$"] = [] self.code_gen_dict["$READNPYDATA$"] = []
input_ind = 0 input_ind = 0
input_file_names = [] input_file_names = []
for inputs in node.input: for inputs in node.input:
input_file_names.append("{}/input_{}.npy".format(self.tmp_dir, input_ind)) input_file_names.append("{}/input_{}.npy".format(code_gen_dir, input_ind))
input_ind += 1 input_ind += 1
input_ind = 0 input_ind = 0
...@@ -183,11 +161,12 @@ class StreamingMaxPool_Batch(HLSCustomOp): ...@@ -183,11 +161,12 @@ class StreamingMaxPool_Batch(HLSCustomOp):
self.code_gen_dict["$DATAOUTSTREAM$"].append("}") self.code_gen_dict["$DATAOUTSTREAM$"].append("}")
def save_as_npy(self): def save_as_npy(self):
numReps = 2 code_gen_dir = self.get_nodeattr("code_gen_dir")
numReps = 1
self.code_gen_dict["$SAVEASCNPY$"] = [ self.code_gen_dict["$SAVEASCNPY$"] = [
"""cnpy::npy_save("{}/output.npy",&output_data_vector[0], """cnpy::npy_save("{}/output.npy",&output_data_vector[0],
{{{},{},{}}},"w");""".format( {{{},{},{}}},"w");""".format(
self.tmp_dir, code_gen_dir,
numReps, numReps,
self.get_nodeattr("NumChannels"), self.get_nodeattr("NumChannels"),
int(self.get_nodeattr("ImgDim") / self.get_nodeattr("PoolDim")), int(self.get_nodeattr("ImgDim") / self.get_nodeattr("PoolDim")),
......
...@@ -6,55 +6,21 @@ from finn.core.utils import get_by_name ...@@ -6,55 +6,21 @@ from finn.core.utils import get_by_name
from finn.transformation import Transformation from finn.transformation import Transformation
def code_gen_transformation(node, model): def _codegen_single_node(node, model):
"""Call custom implementation to generate code for single custom node """Call custom implementation to generate code for single custom node
and create folder that contains all the generated files""" and create folder that contains all the generated files"""
op_type = node.op_type op_type = node.op_type
try: try:
# lookup op_type in registry of CustomOps # lookup op_type in registry of CustomOps
inst = registry.custom_op[op_type](node) inst = registry.custom_op[op_type](node)
# get the path of the code generation directory # get the path of the code generation directory
code_gen_dir = inst.code_gen_dir code_gen_dir = inst.get_nodeattr("code_gen_dir")
code_gen_dir = code_gen_dir.s.decode("UTF-8") # ensure that there is a directory
if code_gen_dir == "" or not os.path.isdir(code_gen_dir):
# parameter is empty code_gen_dir = tmp.mkdtemp(prefix="code_gen_" + str(node.op_type) + "_")
if not code_gen_dir: inst.set_nodeattr("code_gen_dir", code_gen_dir)
tmp_dir = tmp.mkdtemp(prefix="code_gen_" + str(node.op_type) + "_") # ensure that there is generated code inside the dir
inst.tmp_dir = tmp_dir inst.code_generation(model)
inst.code_generation(model)
# check if directory exists
if os.path.isdir(tmp_dir):
if len(os.listdir(tmp_dir)) == 0:
raise Exception("Code was not generated!")
else:
inst.code_gen_dir = tmp_dir
model.set_attribute(node, "code_gen_dir", tmp_dir)
else:
raise Exception("Code was not generated!")
# there is already a code gen directory
else:
# check directory for files
if os.path.isdir(code_gen_dir):
if len(os.listdir(code_gen_dir)) == 0:
os.rmdir(code_gen_dir)
tmp_dir = tmp.mkdtemp(prefix="code_gen_" + str(node.op_type) + "_")
inst.tmp_dir = tmp_dir
inst.code_generation(model)
if os.path.isdir(tmp_dir):
if len(os.listdir(tmp_dir)) == 0:
raise Exception("Code was not generated!")
else:
inst.code_gen_dir = tmp_dir
model.set_attribute(node, "code_gen_dir", tmp_dir)
else:
raise Exception("Code was not generated!")
# else: attribute is correctly set
else:
inst.code_gen_dir = tmp_dir
model.set_attribute(node, "code_gen_dir", tmp_dir)
except KeyError: except KeyError:
# exception if op_type is not supported # exception if op_type is not supported
raise Exception("Custom op_type %s is currently not supported." % op_type) raise Exception("Custom op_type %s is currently not supported." % op_type)
...@@ -69,5 +35,5 @@ class CodeGen(Transformation): ...@@ -69,5 +35,5 @@ class CodeGen(Transformation):
backend_attribute = get_by_name(node.attribute, "backend") backend_attribute = get_by_name(node.attribute, "backend")
backend_value = backend_attribute.s.decode("UTF-8") backend_value = backend_attribute.s.decode("UTF-8")
if backend_value == "fpgadataflow": if backend_value == "fpgadataflow":
code_gen_transformation(node, model) _codegen_single_node(node, model)
return (model, False) return (model, False)
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