Skip to content
Snippets Groups Projects
Unverified Commit d0fd81bf authored by Yaman Umuroglu's avatar Yaman Umuroglu Committed by GitHub
Browse files

Merge pull request #130 from quetric/feature/SortGraph

Feature/sort graph
parents 05140723 e5cfd570
No related branches found
No related tags found
No related merge requests found
...@@ -8,4 +8,5 @@ pre-commit ...@@ -8,4 +8,5 @@ pre-commit
pyverilator pyverilator
scipy scipy
sphinx sphinx
toposort
wget wget
...@@ -259,11 +259,10 @@ class ModelWrapper: ...@@ -259,11 +259,10 @@ class ModelWrapper:
def find_producer(self, tensor_name): def find_producer(self, tensor_name):
"""Finds and returns the node that produces the tensor with given name.""" """Finds and returns the node that produces the tensor with given name."""
ret = None
for x in self._model_proto.graph.node: for x in self._model_proto.graph.node:
if tensor_name in x.output: if tensor_name in x.output:
ret = x return x
return ret return None
def find_upstream(self, tensor_name, finder_fxn): def find_upstream(self, tensor_name, finder_fxn):
"""Follow the producer chain upstream, calling finder_fxn on each upstream """Follow the producer chain upstream, calling finder_fxn on each upstream
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
import finn.util.basic as util import finn.util.basic as util
from finn.transformation import Transformation from finn.transformation import Transformation
from toposort import toposort_flatten
class GiveUniqueNodeNames(Transformation): class GiveUniqueNodeNames(Transformation):
...@@ -104,11 +105,13 @@ class GiveUniqueParameterTensors(Transformation): ...@@ -104,11 +105,13 @@ class GiveUniqueParameterTensors(Transformation):
# first occurance # first occurance
seen_parameters += [node_input] seen_parameters += [node_input]
continue continue
new_param_name = model.make_new_valueinfo_name() new_param_name = model.make_new_valueinfo_name()
model.set_initializer(new_param_name, input_init) model.set_initializer(new_param_name, input_init)
model.set_tensor_datatype(new_param_name, model.get_tensor_datatype(node_input)) model.set_tensor_datatype(
new_param_name, model.get_tensor_datatype(node_input)
)
# point node input to new tensor # point node input to new tensor
n.input[input_idx] = new_param_name n.input[input_idx] = new_param_name
...@@ -116,6 +119,56 @@ class GiveUniqueParameterTensors(Transformation): ...@@ -116,6 +119,56 @@ class GiveUniqueParameterTensors(Transformation):
return (model, graph_modified) return (model, graph_modified)
class SortGraph(Transformation):
""" Returns the model with its node list sorted topologically.
Any ONNX graph to be executed must have a topologically sorted node list, as dictated
by the ONNX standard.
"""
# Notes on SortGraph performance:
# benchmark in tests/transformation/test_sort_graph.py
#
# The algorithm doesn't move initializers so its performance should only depend on
# the number of nodes
#
# Relative order of magnitudes for time per step:
# - Gather graph structure: base
# - Sort nodes: 0.1 of base
# - Remove and insert in order : 0.001 of base
#
# Notes:
# Remove nodes and insert them in order:
# Probably this is faster than copying initializers and more robust in general
def apply(self, model):
# Gather graph structure
graph_dependencies = {}
node_list = [
n for n in model.graph.node
] # I also need the list to remove the nodes
for node_idx, n in enumerate(node_list):
node_pred = model.find_direct_predecessors(n)
if node_pred is None:
# Will also eliminate nodes that are floating around for some reason
continue
node_dependencies = [node_list.index(pred) for pred in node_pred]
graph_dependencies[node_idx] = set(node_dependencies)
# Sort nodes
sorted_node_indexes = toposort_flatten(graph_dependencies)
# Remove nodes and insert them in order
# Can't remove nodes before if I want to use model.find_direct_predecessors()
for n in node_list:
model.graph.node.remove(n)
for new_idx, sorted_idx in enumerate(sorted_node_indexes):
model.graph.node.insert(new_idx, node_list[sorted_idx])
return model, False
class ConvertSubToAdd(Transformation): class ConvertSubToAdd(Transformation):
"""Convert subtract-a-constant nodes to add-a-constant nodes.""" """Convert subtract-a-constant nodes to add-a-constant nodes."""
......
from onnx import TensorProto, helper
import numpy as np
from finn.core.modelwrapper import ModelWrapper
from finn.transformation.general import SortGraph
from finn.transformation.infer_shapes import InferShapes
import pytest
import finn.analysis.topology as ta
def make_randomly_sorted_linear_model(num_of_nodes, seed=None):
if seed is not None:
np.random.seed(seed)
ch = 2
ifmdim = 16
input_shape = (1, ch, ifmdim, ifmdim)
top_in = helper.make_tensor_value_info("t0", TensorProto.FLOAT, input_shape)
top_out = helper.make_tensor_value_info(
"t" + str(num_of_nodes), TensorProto.FLOAT, input_shape
)
value_info = []
nodes = []
for i in range(num_of_nodes):
nodes += [
helper.make_node("Add", ["t" + str(i), "p" + str(i)], ["t" + str(i + 1)])
]
value_info += [
helper.make_tensor_value_info("p" + str(i), TensorProto.FLOAT, input_shape)
]
nodes = np.random.permutation(nodes)
modelproto = helper.make_model(
helper.make_graph(
name="test",
inputs=[top_in],
outputs=[top_out],
value_info=value_info,
nodes=nodes,
)
)
model = ModelWrapper(modelproto)
model = model.transform(InferShapes())
for i in range(num_of_nodes):
model.set_initializer(
"p" + str(i), np.random.rand(*input_shape).astype(np.float32)
)
return model
@pytest.mark.parametrize("num_of_nodes", [64])
def test_sort_linear_graph(num_of_nodes):
model = make_randomly_sorted_linear_model(num_of_nodes, seed=0)
new_model = model.transform(SortGraph())
# Test
ret = new_model.analysis(ta.nodes_topologically_sorted)
assert ret["nodes_topologically_sorted"], "Nodes are not topologically sorted."
def test_sort_nonlinear_graph():
ch = 2
ifmdim = 16
input_shape = (1, ch, ifmdim, ifmdim)
top_in = helper.make_tensor_value_info("top_in", TensorProto.FLOAT, input_shape)
top_out = helper.make_tensor_value_info("top_out", TensorProto.FLOAT, input_shape)
num_of_params = 8
value_info = []
for i in range(num_of_params):
value_info += [
helper.make_tensor_value_info("p" + str(i), TensorProto.FLOAT, input_shape)
]
modelproto = helper.make_model(
helper.make_graph(
name="test",
inputs=[top_in],
outputs=[top_out],
value_info=value_info,
nodes=[
# Not sorted nodes
helper.make_node("Mul", ["fork1", "p2"], ["t3"]),
helper.make_node("Add", ["t4", "p3"], ["t5"]),
helper.make_node("Add", ["t2", "t3"], ["t4"]),
helper.make_node("Add", ["t6", "t7"], ["t8"]),
helper.make_node("Add", ["fork3", "fork3"], ["top_out"]),
helper.make_node("Mul", ["t5", "p4"], ["fork2"]),
helper.make_node("Add", ["top_in", "p0"], ["fork1"]),
helper.make_node("Mul", ["fork1", "p1"], ["t2"]),
helper.make_node("Add", ["fork2", "p5"], ["t6"]),
helper.make_node("Add", ["fork2", "p6"], ["t7"]),
helper.make_node("Mul", ["t8", "p7"], ["fork3"]),
],
)
)
model = ModelWrapper(modelproto)
model = model.transform(InferShapes())
np.random.seed(0)
for i in range(num_of_params):
model.set_initializer(
"p" + str(i), np.random.rand(*input_shape).astype(np.float32)
)
new_model = model.transform(SortGraph())
# Test
ret = new_model.analysis(ta.nodes_topologically_sorted)
assert ret["nodes_topologically_sorted"], "Nodes are not topologically sorted."
if __name__ == "__main__":
import time
sizes = [10, 50, 100, 500, 1000]
times = []
reps = 10
print("SortGraph performance test:")
print("Test sizes", sizes)
print("Repetitions per size:", reps)
for sz in sizes:
acc_time = 0
print(" Testing size ", sz)
for i in range(reps):
# it should take the same time even with the sorted one
# but better new model each time as it is a more general approach
model = make_randomly_sorted_linear_model(sz) # new model as seed is None
bef = time.time()
new_model = model.transform(SortGraph(), make_deepcopy=False)
acc_time += time.time() - bef
times += [acc_time / reps]
# print csv
print("\nnum_of_nodes, seconds")
for sz, tm in zip(sizes, times):
print("{:12d}, {:6.4e}".format(sz, tm))
# plot
# import matplotlib.pyplot as plt
# plt.plot(sizes,times,"--o")
# plt.grid(True)
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