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

Merge branch 'dev' into feature/docker_reorg

parents 020b98d0 ddb8a192
No related branches found
No related tags found
No related merge requests found
......@@ -48,6 +48,8 @@ Guide to writing FINN transformations
"""
from abc import ABC, abstractmethod
from finn.util.basic import get_num_default_workers
import multiprocessing as mp
class Transformation(ABC):
......@@ -60,3 +62,54 @@ class Transformation(ABC):
@abstractmethod
def apply(self, model):
pass
class NodeLocalTransformation(Transformation):
"""
Parent class for transformations, which can be executed locally to one node
by accessing and modifying the attributes of only that node.
This class can then automatically parallelize the transformation.
Transformations sublcassing NodeLocalTransformation must implement the
abstract method applyNodeLocal().
To control the degree of parallelization, specify the num_workers argument
in the constructor, using one of the following values:
* None: use NUM_DEFAULT_WORKERS environment variable
* 0: use all available CPU cores
* (any other int>0): set number of parallel workers
"""
def __init__(self, num_workers=None):
super().__init__()
if num_workers is None:
self._num_workers = get_num_default_workers()
else:
self._num_workers = num_workers
assert self._num_workers >= 0, "Number of workers must be nonnegative."
if self._num_workers == 0:
self._num_workers = mp.cpu_count()
@abstractmethod
def applyNodeLocal(self, node):
pass
def apply(self, model):
# Remove old nodes from the current model
old_nodes = []
for i in range(len(model.graph.node)):
old_nodes.append(model.graph.node.pop())
# Execute transformation in parallel
with mp.Pool(self._num_workers) as p:
new_nodes_and_bool = p.map(self.applyNodeLocal, old_nodes, chunksize=1)
# extract nodes and check if the transformation needs to run again
# Note: .pop() had initially reversed the node order
run_again = False
for node, run in reversed(new_nodes_and_bool):
# Reattach new nodes to old model
model.graph.node.append(node)
if run is True:
run_again = True
return (model, run_again)
......@@ -28,28 +28,30 @@
import finn.custom_op.registry as registry
import finn.util.basic as util
from finn.transformation import Transformation
from finn.transformation import NodeLocalTransformation
class Compile(Transformation):
class Compile(NodeLocalTransformation):
"""For every node: compile C++ code in node attribute "code_gen_dir_npysim"
and save path to executables in node attribute "executable_path".
All nodes in the graph must have the fpgadataflow backend attribute.
To use these executables, exec_mode must be set to "npysim" (using transformation
SetExecMode) and the model has to be executed using execute_onnx() from
finn.core.onnx_exec"""
finn.core.onnx_exec
def __init__(self):
super().__init__()
* num_workers (int or None) number of parallel workers, see documentation in
NodeLocalTransformation for more details.
"""
def apply(self, model):
for node in model.graph.node:
op_type = node.op_type
if node.domain == "finn":
backend_attribute = util.get_by_name(node.attribute, "backend")
if backend_attribute is None:
continue
def __init__(self, num_workers=None):
super().__init__(num_workers=num_workers)
def applyNodeLocal(self, node):
op_type = node.op_type
if node.domain == "finn":
backend_attribute = util.get_by_name(node.attribute, "backend")
if backend_attribute is not None:
backend_value = backend_attribute.s.decode("UTF-8")
if backend_value == "fpgadataflow":
try:
......@@ -74,4 +76,4 @@ class Compile(Transformation):
raise Exception(
"Custom op_type %s is currently not supported." % op_type
)
return (model, False)
return (node, False)
......@@ -28,50 +28,54 @@
import finn.custom_op.registry as registry
import finn.util.basic as util
from finn.transformation import Transformation
from finn.transformation import NodeLocalTransformation
class HLSSynth_IPGen(Transformation):
class HLSSynth_IPGen(NodeLocalTransformation):
"""For each node: generate IP block from code in folder
that is referenced in node attribute "code_gen_dir_ipgen"
and save path of generated project in node attribute "ipgen_path".
All nodes in the graph must have the fpgadataflow backend attribute.
This transformation calls Vivado HLS for synthesis, so it will run for
some time (several minutes)"""
some time (several minutes)
def __init__(self):
super().__init__()
* num_workers (int or None) number of parallel workers, see documentation in
NodeLocalTransformation for more details.
"""
def apply(self, model):
for node in model.graph.node:
op_type = node.op_type
if node.domain == "finn":
backend_attribute = util.get_by_name(node.attribute, "backend")
if backend_attribute is None:
continue
backend_value = backend_attribute.s.decode("UTF-8")
if backend_value == "fpgadataflow":
try:
# lookup op_type in registry of CustomOps
inst = registry.custom_op[op_type](node)
# ensure that code is generated
assert (
inst.get_nodeattr("code_gen_dir_ipgen") != ""
), """Node
attribute "code_gen_dir_ipgen" is empty. Please run
transformation CodeGen_ipgen first."""
# call the compilation function for this node
inst.ipgen_singlenode_code()
# ensure that executable path is now set
assert (
inst.get_nodeattr("ipgen_path") != ""
), """Transformation
HLSSynth_IPGen was not successful. Node attribute "ipgen_path"
is empty."""
except KeyError:
# exception if op_type is not supported
raise Exception(
"Custom op_type %s is currently not supported." % op_type
)
return (model, False)
def __init__(self, num_workers=None):
super().__init__(num_workers=num_workers)
def applyNodeLocal(self, node):
op_type = node.op_type
if node.domain == "finn":
backend_attribute = util.get_by_name(node.attribute, "backend")
if backend_attribute is None:
return (node, False)
backend_value = backend_attribute.s.decode("UTF-8")
if backend_value == "fpgadataflow":
try:
# lookup op_type in registry of CustomOps
inst = registry.custom_op[op_type](node)
# ensure that code is generated
assert (
inst.get_nodeattr("code_gen_dir_ipgen") != ""
), """Node
attribute "code_gen_dir_ipgen" is empty. Please run
transformation CodeGen_ipgen first."""
# call the compilation function for this node
inst.ipgen_singlenode_code()
# ensure that executable path is now set
assert (
inst.get_nodeattr("ipgen_path") != ""
), """Transformation
HLSSynth_IPGen was not successful. Node attribute "ipgen_path"
is empty."""
except KeyError:
# exception if op_type is not supported
raise Exception(
"Custom op_type %s is currently not supported." % op_type
)
return (node, False)
......@@ -44,6 +44,17 @@ pynq_part_map["Pynq-Z2"] = "xc7z020clg400-1"
pynq_part_map["ZCU104"] = "xczu7ev-ffvc1156-2-e"
def get_num_default_workers():
"""Return the number of workers for parallel transformations. Controllable
via the NUM_DEFAULT_WORKERS environment variable. If the env.var. is
undefined, the default value of 1 is returned.
"""
try:
return int(os.environ["NUM_DEFAULT_WORKERS"])
except KeyError:
return 1
def get_finn_root():
"Return the root directory that FINN is cloned into."
......
......@@ -112,8 +112,8 @@ def test_end2end_cnv_w1a1_streamline():
def test_end2end_cnv_w1a1_convert_to_hls_layers():
model = ModelWrapper(build_dir + "/end2end_cnv_w1a1_streamlined.onnx")
model = model.transform(to_hls.InferBinaryStreamingFCLayer())
model = model.transform(to_hls.InferQuantizedStreamingFCLayer())
model = model.transform(to_hls.InferBinaryStreamingFCLayer(mem_mode))
model = model.transform(to_hls.InferQuantizedStreamingFCLayer(mem_mode))
model = model.transform(to_hls.InferConvInpGen())
model = model.transform(to_hls.InferStreamingMaxPool())
model = model.transform(MoveReshape())
......
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