-
Notifications
You must be signed in to change notification settings - Fork 1
/
Placer.py
323 lines (256 loc) · 12.2 KB
/
Placer.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# This file is part of GenMap and released under the MIT License, see LICENSE.
# Author: Takuya Kojima
import networkx as nx
import random
import math
from operator import mul
from multiprocessing import Pool
import multiprocessing as multi
class Placer():
DATA_FLOW = {"left-right": lambda pos: Placer.__to_horizontal(pos),\
"right-left": lambda pos: Placer.__to_horizontal(pos, False),\
"bottom-top": lambda pos: Placer.__to_vertical(pos),\
"top-bottom": lambda pos: Placer.__to_vertical(pos, False),\
"horizontal": lambda pos: Placer.__to_horizontal(pos, \
bool(random.randint(0, 1))),\
"vertical": lambda pos: Placer.__to_vertical(pos, \
bool(random.randint(0, 1))),\
"any": lambda pos: Placer.__to_horizontal(pos, \
bool(random.randint(0, 1))) \
if bool(random.randint(0, 1)) \
else Placer.__to_vertical(pos, \
bool(random.randint(0, 1)))}
ORIGIN_COORDS = {"left-right": [(0.0, 0.5)],
"right-left": [(1.0, 0.5)],
"bottom-top": [(0.5, 0.0)],
"top-bottom": [(0.5, 1.0)],
"horizontal": [(0.0, 0.5), (1.0, 0.5)],
"vertical": [(0.5, 0.0), (0.5, 1.0)],
"any": [(0.0, 0.5), (1.0, 0.5), (0.5, 0.0), (0.5, 1.0)]
}
def __init__(self, method, dir, iterations = 50, randomness = "Full"):
""" Initializes this class
Args:
method (str) : initial mapping method
available methods are follows:
1. graphviz (default)
2. tsort
3. random
dir (str) : data flow direction
available values corresponds to the keys of the dict "DATA_FLOW"
iterations (int): maximum iteration number for generating a node position.
Default = 50
randomness (str): randomness of rounding.
if it is "Full", then the positions are rounded fully randomly.
if is is "Partial", then partilly randomly.
"""
self.__iterations = iterations
self.__method = method
self.__dir = dir
if not randomness in ["Full", "Partial"]:
raise ValueError("Invalid randomness type: " + randomness)
else:
self.__randomness = randomness
@staticmethod
def __to_vertical(pos, bottom2top = True):
# make sink nodes upper side
if bottom2top:
pos = {v: (x, 1 - y) for v, (x, y) in pos.items()}
# randomly flip x position
if random.randint(0, 1) == 0:
pos = {v: (1 - x, y) for v, (x, y) in pos.items()}
return pos
@staticmethod
def __to_horizontal(pos, left2right = True):
if left2right:
# rotate by + 90 deg
pos = {v: (1 - y, x) for v, (x, y) in pos.items()}
else:
# rotate by -90 deg
pos = {v: (y, 1 - x) for v, (x, y) in pos.items()}
# randomly flip y position
if random.randint(0, 1) == 0:
pos = {v: (x, 1 - y) for v, (x, y) in pos.items()}
return pos
def generate_init_mappings(self, dag, width, height, count, proc_num=multi.cpu_count()):
"""Returns multiple initial mappings.
Args:
dag (networkx DiGraph): data-flow-graph
width (int): PE array width
height (int): PE array height
count (int): try count to generate mappings
Optional:
proc_num (int): the number of process
Default is equal to cpu count
Returns:
list: a list of mappings
"""
if self.__method == "graphviz":
mt_args = [(dag, random.randint(1, width), height) for i in range(count)]
p = Pool(proc_num)
results = p.map(self.mt_wrapper, mt_args)
p.close()
init_mapping = []
init_hashable_mapping = set() # for checking dupulication
for mapping in results:
# remove invalid results
if not mapping is None:
if not mapping.values() in init_hashable_mapping:
init_mapping.append(mapping)
init_hashable_mapping.add(mapping.values())
return init_mapping
elif self.__method == "tsort":
return self.make_random_mappings(dag, width, height, count, 1)
else:
return self.make_random_mappings(dag, width, height, count, 0)
def mt_wrapper(self, args):
return self.make_graphviz_mapping(*args)
def make_graphviz_mapping(self, dag, width, height):
""" Makes nodes position on the PE array.
Args:
dag (networkx DiGraph): data-flow-graph
width (int): PE array width
height (int): PE array height
Returns:
Dictionary: keys are operation label, values are mapped positions of them.
In case of failure, returns None
"""
# validate input dag
if nx.is_directed_acyclic_graph(dag) == False:
raise ValueError("Input data-flow-graph is not DAG")
# check dag size
node_num = len(dag.nodes())
if node_num > width * height:
return None
# enumerate possible rectangles
rect_pattern = [(w, h) for w in range(1, width + 1) for h in range(1, height + 1) if w * h >= node_num]
# graph layout by dot's algorithm
pos = nx.nx_pydot.graphviz_layout(dag, prog="dot")
# normalize coordinates
max_x = max([x for (x, y) in pos.values()])
max_y = max([y for (x, y) in pos.values()])
pos = {v: (x / max_x, y / max_y) for v, (x, y) in pos.items()}
# adjust for the data flow direction
pos = self.DATA_FLOW[self.__dir](pos)
# choose a rectangle pattern
(map_width, map_height) = rect_pattern[random.randint(0, len(rect_pattern) - 1)]
# calculate actual coordinates
pos = {v: ((map_width - 1) * x, (map_height - 1) * y) for v, (x, y) in pos.items()}
# try to rounding the conrdinates
best_mapping_lest = len(pos)
for i in range(self.__iterations):
mapping = {v: self.__coord_rouding((x, y)) for v, (x, y) in pos.items()}
# check duplication
duplicated_node_num = len(list(mapping.values())) - len(set(mapping.values()))
if duplicated_node_num == 0:
# check dependency
# if self.__if_keep_dependency(dag, mapping):
# break
break
elif duplicated_node_num < best_mapping_lest:
best_mapping = mapping
best_mapping_lest = duplicated_node_num
else:
# fail to rouding
# get duplicated nodes
duplicated_nodes = {coord: [v for v in best_mapping.keys() if best_mapping[v] == coord] \
for coord in set(best_mapping.values()) \
if list(best_mapping.values()).count(coord) > 1}
# fix one of nodes which are mapped to same coord
for coord in duplicated_nodes:
duplicated_nodes[coord].pop(\
random.randint(0, len(duplicated_nodes[coord]) - 1))
# sort in order of lest node count
duplicated_nodes = dict(sorted(duplicated_nodes.items(), key=lambda x: - len(x[1])))
# get free coordinates
free_coords = [(x, y) for x in range(map_width) for y in range(map_height)\
if not (x, y) in best_mapping.values()]
for coord, nodes in duplicated_nodes.items():
for v in nodes:
dists = [math.sqrt((x - coord[0]) ** 2 + (y - coord[1]) ** 2) \
for (x, y) in free_coords]
nearest_pos = free_coords[dists.index(min(dists))]
free_coords.remove(nearest_pos)
best_mapping[v] = nearest_pos
return best_mapping
return mapping
def make_random_mappings(self, dag, width, height, size, sort_prob = 0.5):
""" Generate random mappings
Args:
dag (networkx DiGraph): data-flow-graph
width (int): PE array width
height (int): PE array height
size (int): The number of mappings to be generated
Option:
sort_prob (float): topological sort probability.
Returns:
list: generated mappings
"""
# validate input dag
if nx.is_directed_acyclic_graph(dag) == False:
raise ValueError("Input data-flow-graph is not DAG")
# check dag size
node_num = len(dag.nodes())
if node_num > width * height:
return None
# enumerate possible rectangles
rect_pattern = [(w, h) for w in range(1, width + 1) for h in range(1, height + 1) if w * h >= node_num]
rtn_list = []
for i in range(size):
if random.random() < sort_prob:
topological_sort_enable = True
else:
topological_sort_enable = False
(map_width, map_height) = rect_pattern[random.randint(0, len(rect_pattern) - 1)]
positions = random.sample([(x, y) for x in range(map_width) for y in range(map_height)], node_num)
if topological_sort_enable:
norm_origin = random.choice(self.ORIGIN_COORDS[self.__dir])
origin = tuple(map(mul, norm_origin, \
(map_width - 1, map_height - 1)))
positions = sorted(positions, key=lambda x: \
(x[0] - origin[0])**2 + (x[1] - origin[1]) ** 2)
rtn_list.append({k: v for k, v in zip(list(nx.topological_sort(dag)), positions)})
else:
rtn_list.append({k: v for k, v in zip(dag.nodes(), positions)})
return rtn_list
@staticmethod
def __if_keep_dependency(dag, mapping):
"""Check dependency between operations.
Args:
dag (networkx digraph): data-flow-graph to be mapped
mapping (dict): operation mapping
keys: operation labels
values: PE coordinates where the nodes are mapped
"""
valid = True
for u, v in dag.edges():
if mapping[u][1] > mapping[v][1]:
valid = False
break
return valid
def __coord_rouding(self, coord):
""" Round a float value coordinate to a int value coordinate.
Args:
coord: a list-like coordinate
Return:
a tuple: rounded coordinate
"""
if self.__randomness == "Full":
# Either ceil or floor is used randomly
x_ceil = random.randint(0, 1) == 0
y_ceil = random.randint(0, 1) == 0
elif self.__randomness == "Partial":
# extract after the decimal points
x_dec = coord[0] - int(coord[0])
y_dec = coord[0] - int(coord[0])
# decide ceil or floor depending on the decimal
x_ceil = random.random() < x_dec
y_ceil = random.random() < y_dec
if x_ceil and y_ceil:
return (math.ceil(coord[0]), math.ceil(coord[1]))
elif x_ceil and not y_ceil:
return (math.ceil(coord[0]), math.floor(coord[1]))
elif not x_ceil and y_ceil:
return (math.floor(coord[0]), math.ceil(coord[1]))
else:
return (math.floor(coord[0]), math.floor(coord[1]))