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

Merge branch 'feature/verify_custom_op_construct' into dev

parents 830b2dca 7cbe7391
No related branches found
No related tags found
No related merge requests found
import finn.custom_op.registry as registry
def verify_nodes(model):
"""Checks if custom ops in graph are correctly built, with all attributes
and inputs. Returns {node op_type : info_messages}
*info_messages is list of strings about the result of the verification"""
verification_dict = {}
for node in model.graph.node:
if node.domain == "finn":
op_type = node.op_type
inst = registry.custom_op[op_type](node)
verification_dict[op_type] = inst.verify_node()
return verification_dict
......@@ -78,3 +78,10 @@ class CustomOp(ABC):
"""Execute this CustomOp instance, given the execution context and
ONNX graph."""
pass
@abstractmethod
def verify_node(self):
"""Verifies that all attributes the node needs are there and
that particular attributes are set correctly. Also checks if
the number of inputs is equal to the expected number"""
pass
......@@ -40,7 +40,11 @@ class HLSCustomOp(CustomOp):
self.code_gen_dict = {}
def get_nodeattr_types(self):
return {"code_gen_dir": ("s", False, ""), "executable_path": ("s", False, "")}
return {
"backend": ("s", True, "fpgadataflow"),
"code_gen_dir": ("s", False, ""),
"executable_path": ("s", False, ""),
}
def code_generation(self, model):
node = self.onnx_node
......
......@@ -14,6 +14,9 @@ class StreamingFCLayer_Batch(HLSCustomOp):
def get_nodeattr_types(self):
my_attrs = {
# "backend": ("s", True, "fpgadataflow"),
# "code_gen_dir": ("s", True, ""),
# "executable_path": ("s", True, ""),
"PE": ("i", True, 0),
"SIMD": ("i", True, 0),
"MW": ("i", True, 0),
......@@ -57,6 +60,90 @@ class StreamingFCLayer_Batch(HLSCustomOp):
def infer_node_datatype(self, model):
pass
def verify_node(self):
info_messages = []
# verify number of attributes
num_of_attr = 14
if len(self.onnx_node.attribute) == num_of_attr:
info_messages.append("The number of attributes is correct")
else:
info_messages.append(
"""The number of attributes is incorrect,
{} should have {} attributes""".format(
self.onnx_node.op_type, num_of_attr
)
)
# verify that "domain" is set to "finn"
domain_value = self.onnx_node.domain
if domain_value == "finn":
info_messages.append("Attribute domain is set correctly")
else:
info_messages.append('Attribute domain should be set to "finn"')
# verify that "backend" is set to "fpgadataflow"
backend_value = self.get_nodeattr("backend")
if backend_value == "fpgadataflow":
info_messages.append("Attribute backend is set correctly")
else:
info_messages.append('Attribute backend should be set to "fpgadataflow"')
# verify that all necessary attributes exist
try:
self.get_nodeattr("code_gen_dir")
self.get_nodeattr("executable_path")
self.get_nodeattr("resType")
self.get_nodeattr("MW")
self.get_nodeattr("MH")
self.get_nodeattr("SIMD")
self.get_nodeattr("PE")
self.get_nodeattr("inputDataType")
self.get_nodeattr("weightDataType")
self.get_nodeattr("outputDataType")
self.get_nodeattr("ActVal")
self.get_nodeattr("binaryXnorMode")
self.get_nodeattr("noActivation")
info_messages.append("All necessary attributes exist")
except Exception:
info_messages.append(
"""The necessary attributes do not exist.
StreamingFCLayer_Batch needs the following attributes:
code_gen_dir, executable_path, resType, MW, MH, SIMD, PE,
inputDataType, weightDataType, outputDataType, ActVal,
binaryXnorMode, noActivation"""
)
# verify the number of inputs depending on noActivation value
# check noActivation value to determine the number of inputs
no_act = self.get_nodeattr("noActivation")
if no_act == 1:
if len(self.onnx_node.input) == 2:
info_messages.append("The number of inputs is correct")
else:
info_messages.append(
"""StreamingFCLayer_Batch needs in no
activation mode 2 inputs (data input and weights)"""
)
elif no_act == 0:
if len(self.onnx_node.input) == 3:
info_messages.append("The number of inputs is correct")
else:
info_messages.append(
"""StreamingFCLayer_Batch needs 3 inputs
(data input and weights and threshold values)"""
)
else:
info_messages.append(
"""noActivation attribute contains {} should
be 0 or 1""".format(
no_act
)
)
return info_messages
def get_input_datatype(self):
return DataType[self.get_nodeattr("inputDataType")]
......
......@@ -4,6 +4,9 @@ from finn.custom_op.fpgadataflow import HLSCustomOp
class StreamingMaxPool_Batch(HLSCustomOp):
def get_nodeattr_types(self):
my_attrs = {
# "backend": ("s", True, "fpgadataflow"),
# "code_gen_dir": ("s", True, ""),
# "executable_path": ("s", True, ""),
"ImgDim": ("i", True, 0),
"PoolDim": ("i", True, 0),
"NumChannels": ("i", True, 0),
......@@ -17,6 +20,58 @@ class StreamingMaxPool_Batch(HLSCustomOp):
def infer_node_datatype(self, model):
pass
def verify_node(self):
info_messages = []
# verify number of attributes
num_of_attr = 6
if len(self.onnx_node.attribute) == num_of_attr:
info_messages.append("The number of attributes is correct")
else:
info_messages.append(
"""The number of attributes is incorrect,
{} should have {} attributes""".format(
self.onnx_node.op_type, num_of_attr
)
)
# verify that "domain" is set to "finn"
domain_value = self.onnx_node.domain
if domain_value == "finn":
info_messages.append("Attribute domain is set correctly")
else:
info_messages.append('Attribute domain should be set to "finn"')
# verify that "backend" is set to "fpgadataflow"
backend_value = self.get_nodeattr("backend")
if backend_value == "fpgadataflow":
info_messages.append("Attribute backend is set correctly")
else:
info_messages.append('Attribute backend should be set to "fpgadataflow"')
# verify that all necessary attributes exist
try:
self.get_nodeattr("code_gen_dir")
self.get_nodeattr("executable_path")
self.get_nodeattr("ImgDim")
self.get_nodeattr("PoolDim")
self.get_nodeattr("NumChannels")
info_messages.append("All necessary attributes exist")
except Exception:
info_messages.append(
"""The necessary attributes do not exist.
StreamingMaxPool_Batch needs the following attributes:
code_gen_dir, executable_path, ImgDim, PoolDim, NumChannels"""
)
# verify the number of inputs
if len(self.onnx_node.input) == 1:
info_messages.append("The number of inputs is correct")
else:
info_messages.append("""StreamingMaxPool_Batch needs 1 data input""")
return info_messages
def global_includes(self):
self.code_gen_dict["$GLOBALS$"] = ['#include "maxpool.h"']
......
......@@ -85,3 +85,49 @@ class MultiThreshold(CustomOp):
output = multithreshold(v, thresholds, out_scale, out_bias)
# setting context according to output
context[node.output[0]] = output
def verify_node(self):
info_messages = []
# verify number of attributes
num_of_attr = 3
if len(self.onnx_node.attribute) == num_of_attr:
info_messages.append("The number of attributes is correct")
else:
info_messages.append(
"""The number of attributes is incorrect,
{} should have {} attributes""".format(
self.onnx_node.op_type, num_of_attr
)
)
# verify that "domain" is set to "finn"
domain_value = self.onnx_node.domain
if domain_value == "finn":
info_messages.append("Attribute domain is set correctly")
else:
info_messages.append('Attribute domain should be set to "finn"')
# verify that all necessary attributes exist
try:
self.get_nodeattr("out_scale")
self.get_nodeattr("out_bias")
self.get_nodeattr("out_dtype")
info_messages.append("All necessary attributes exist")
except Exception:
info_messages.append(
"""The necessary attributes do not exist.
MultiThreshold needs the following attributes:
out_scale, out_bias, out_dtype"""
)
# verify the number of inputs
if len(self.onnx_node.input) == 2:
info_messages.append("The number of inputs is correct")
else:
info_messages.append(
"""MultiThreshold needs 2 inputs
(data input and threshold values)"""
)
return info_messages
......@@ -57,3 +57,36 @@ class XnorPopcountMatMul(CustomOp):
output = xnorpopcountmatmul(inp0, inp1)
# set context according to output name
context[node.output[0]] = output
def verify_node(self):
info_messages = []
# verify number of attributes
num_of_attr = 0
if len(self.onnx_node.attribute) == num_of_attr:
info_messages.append("The number of attributes is correct")
else:
info_messages.append(
"""The number of attributes is incorrect,
{} should have {} attributes""".format(
self.onnx_node.op_type, num_of_attr
)
)
# verify that "domain" is set to "finn"
domain_value = self.onnx_node.domain
if domain_value == "finn":
info_messages.append("Attribute domain is set correctly")
else:
info_messages.append('Attribute domain should be set to "finn"')
# verify that all necessary attributes exist
info_messages.append("XnorPopcountMatMul should not have any attributes")
# verify the number of inputs
if len(self.onnx_node.input) == 2:
info_messages.append("The number of inputs is correct")
else:
info_messages.append("XnorPopcountMatMul needs 2 data inputs")
return info_messages
from onnx import TensorProto, helper
from finn.analysis.verify_custom_nodes import verify_nodes
from finn.core.modelwrapper import ModelWrapper
def check_two_dict_for_equality(dict1, dict2):
for key in dict1:
assert key in dict2, "Key: {} is not in both dictionaries".format(key)
assert (
dict1[key] == dict2[key]
), """Values for key {} are not the same
in both dictionaries""".format(
key
)
return True
def test_verify_custom_nodes():
inp = helper.make_tensor_value_info("inp", TensorProto.FLOAT, [1, 13, 64])
outp = helper.make_tensor_value_info("outp", TensorProto.FLOAT, [1, 1, 64])
# MultiThreshold
m_node = helper.make_node(
"MultiThreshold",
["xnor_out", "threshs"],
["outp"],
domain="finn",
out_scale=2.0,
out_bias=-1.0,
out_dtype="",
)
# XnorPopcountMatMul
xnor_node = helper.make_node(
"XnorPopcountMatMul",
["fclayer_out0", "fclayer_out1"],
["xnor_out"],
domain="finn",
)
# StreamingMaxPool_Batch
MaxPool_batch_node = helper.make_node(
"StreamingMaxPool_Batch",
["inp"],
["max_out"],
domain="finn",
backend="fpgadataflow",
code_gen_dir="",
executable_path="",
ImgDim=4,
PoolDim=2,
NumChannels=2,
)
# StreamingFCLayer_Batch - no activation
FCLayer0_node = helper.make_node(
"StreamingFCLayer_Batch",
["max_out", "weights"],
["fclayer_out0"],
domain="finn",
backend="fpgadataflow",
code_gen_dir="",
executable_path="",
resType="ap_resource_lut()",
MW=8,
MH=8,
SIMD=4,
PE=4,
inputDataType="<FINN DataType>",
weightDataType="<FINN DataType>",
outputDataType="<FINN DataType>",
ActVal=0,
binaryXnorMode=1,
noActivation=1,
)
# StreamingFCLayer_Batch - with activation
FCLayer1_node = helper.make_node(
"StreamingFCLayer_Batch",
["fclayer_out0", "weights", "threshs"],
["fclayer_out1"],
domain="finn",
backend="fpgadataflow",
code_gen_dir="",
executable_path="",
resType="ap_resource_lut()",
MW=8,
MH=8,
SIMD=4,
PE=4,
inputDataType="<FINN DataType>",
weightDataType="<FINN DataType>",
outputDataType="<FINN DataType>",
ActVal=0,
binaryXnorMode=1,
noActivation=0,
)
graph = helper.make_graph(
nodes=[MaxPool_batch_node, FCLayer0_node, FCLayer1_node, xnor_node, m_node],
name="custom_op_graph",
inputs=[inp],
outputs=[outp],
value_info=[
helper.make_tensor_value_info("max_out", TensorProto.FLOAT, [1, 13, 64]),
helper.make_tensor_value_info("weights", TensorProto.FLOAT, [64, 32, 416]),
helper.make_tensor_value_info("threshs", TensorProto.FLOAT, [32, 32, 16]),
helper.make_tensor_value_info("xnor_out", TensorProto.FLOAT, [1, 32, 32]),
helper.make_tensor_value_info(
"fclayer_out0", TensorProto.FLOAT, [1, 32, 32]
),
helper.make_tensor_value_info(
"fclayer_out1", TensorProto.FLOAT, [32, 64, 512]
),
],
)
model = helper.make_model(graph, producer_name="custom-op-model")
model = ModelWrapper(model)
produced = model.analysis(verify_nodes)
expected = {
"StreamingMaxPool_Batch": [
"The number of attributes is correct",
"Attribute domain is set correctly",
"Attribute backend is set correctly",
"All necessary attributes exist",
"The number of inputs is correct",
],
"StreamingFCLayer_Batch": [
"The number of attributes is correct",
"Attribute domain is set correctly",
"Attribute backend is set correctly",
"All necessary attributes exist",
"The number of inputs is correct",
],
"XnorPopcountMatMul": [
"The number of attributes is correct",
"Attribute domain is set correctly",
"XnorPopcountMatMul should not have any attributes",
"The number of inputs is correct",
],
"MultiThreshold": [
"The number of attributes is correct",
"Attribute domain is set correctly",
"All necessary attributes exist",
"The number of inputs is correct",
],
}
assert check_two_dict_for_equality(
produced, expected
), """The produced output of
the verification analysis pass is not equal to the expected one"""
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