├── .gitignore ├── README ├── create_relation_graph.py ├── drmt.py ├── drmt_latencies.py ├── experiment_results ├── drmt_ipc_1_switch_combined.txt ├── drmt_ipc_1_switch_combined_subset.txt ├── drmt_ipc_1_switch_egress.txt ├── drmt_ipc_1_switch_egress_subset.txt ├── drmt_ipc_1_switch_ingress.txt ├── drmt_ipc_1_switch_ingress_subset.txt ├── drmt_ipc_2_switch_combined.txt ├── drmt_ipc_2_switch_combined_subset.txt ├── drmt_ipc_2_switch_egress.txt ├── drmt_ipc_2_switch_egress_subset.txt ├── drmt_ipc_2_switch_ingress.txt ├── drmt_ipc_2_switch_ingress_subset.txt ├── prmt_coarse_switch_combined.txt ├── prmt_coarse_switch_combined_subset.txt ├── prmt_coarse_switch_egress.txt ├── prmt_coarse_switch_egress_subset.txt ├── prmt_coarse_switch_ingress.txt ├── prmt_coarse_switch_ingress_subset.txt ├── prmt_fine_switch_combined.txt ├── prmt_fine_switch_combined_subset.txt ├── prmt_fine_switch_egress.txt ├── prmt_fine_switch_egress_subset.txt ├── prmt_fine_switch_ingress.txt └── prmt_fine_switch_ingress_subset.txt ├── expt_times.txt ├── figures ├── switch_combined.pdf ├── switch_combined_subset.pdf ├── switch_egress.pdf ├── switch_egress_subset.pdf ├── switch_ingress.pdf ├── switch_ingress_subset.pdf └── threads.txt ├── fine_to_coarse.py ├── graph_generator.py ├── greedy_prmt_solver.py ├── large_hw.py ├── large_hw_ipc2.py ├── printers.py ├── prmt.py ├── prmt_latencies.py ├── random_odg_generator.py ├── randomized_sieve.py ├── run_experiments.sh ├── schedule_dag.py ├── sieve_rotator.py ├── simple_dag_generator.py ├── small_hw.py ├── solution.py ├── switch_combined.py ├── switch_combined_subset.py ├── switch_egress.py ├── switch_egress_subset.py ├── switch_ingress.py └── switch_ingress_subset.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.pyc 3 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Prereqs: 2 | 1. Install Gurobi from http://www.gurobi.com 3 | 2. Install networkx from http://networkx.github.io or 4 | using the command "pip install networkx" 5 | 6 | Running the program: 7 | Usage: drmt_scheduler_full.py 8 | For instance, to run example.py, type "drmt_scheduler_full.py example" 9 | 10 | Format of input file: Look at the comments in example.py 11 | -------------------------------------------------------------------------------- /create_relation_graph.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Spyder Editor 4 | 5 | This is a temporary script file. 6 | """ 7 | 8 | import importlib as im 9 | import networkx as nx 10 | import matplotlib.pyplot as plt 11 | import time 12 | import os 13 | 14 | from random_odg_generator import odg_generator 15 | 16 | 17 | # return True if substr is a substring of srt and false otherwise. 18 | def is_substring(srt, substr): 19 | return bool(srt.find(substr) >= 0) 20 | 21 | 22 | 23 | def reduce_graph(super_source, fn): 24 | 25 | # import operation dependancy graph 26 | input_graph = im.import_module(fn, "*") 27 | 28 | # retreive nodes and edges 29 | nodes = input_graph.nodes 30 | edges = input_graph.edges 31 | 32 | new_nodes = [] 33 | new_edges = [] 34 | 35 | for node in nodes: 36 | 37 | if is_substring(node, 'condition'): 38 | 39 | input_node = node + '_' + 'input' 40 | output_node = node + '_' + 'output' 41 | 42 | cond_true = node + '_' + 'true' 43 | cond_false = node + '_' + 'false' 44 | 45 | new_nodes.append(input_node) 46 | new_nodes.append(output_node) 47 | 48 | new_nodes.append(cond_true) 49 | new_nodes.append(cond_false) 50 | 51 | cond_edge = (input_node, output_node) 52 | 53 | true_edge = (output_node, cond_true) 54 | false_edge = (output_node, cond_false) 55 | 56 | new_edges.append(cond_edge) 57 | 58 | new_edges.append(true_edge) 59 | new_edges.append(false_edge) 60 | 61 | else: 62 | 63 | new_nodes.append(node) 64 | 65 | 66 | for edge in edges: 67 | 68 | source, destination = edge 69 | 70 | source_cond = is_substring(source, 'condition') 71 | destination_cond = is_substring(destination, 'condition') 72 | 73 | if source_cond and destination_cond: 74 | 75 | true_false = edges[edge]['condition'] 76 | 77 | # True 78 | if true_false: 79 | 80 | new_source = source + '_' + 'true' 81 | new_destination = destination + '_' + 'input' 82 | 83 | new_edge = (new_source, new_destination) 84 | new_edges.append(new_edge) 85 | 86 | # False 87 | else: 88 | 89 | new_source = source + '_' + 'false' 90 | new_destination = destination + '_' + 'input' 91 | 92 | new_edge = (new_source, new_destination) 93 | new_edges.append(new_edge) 94 | 95 | 96 | 97 | elif source_cond: 98 | 99 | 100 | 101 | true_false = edges[edge]['condition'] 102 | 103 | 104 | # True 105 | if true_false: 106 | 107 | new_source = source + '_' + 'true' 108 | 109 | new_edge = (new_source, destination) 110 | new_edges.append(new_edge) 111 | 112 | # False 113 | else: 114 | 115 | new_source = source + '_' + 'false' 116 | 117 | new_edge = (new_source, destination) 118 | new_edges.append(new_edge) 119 | 120 | 121 | 122 | elif destination_cond: 123 | 124 | new_destination = destination + '_' + 'input' 125 | 126 | new_edge = (source, new_destination) 127 | new_edges.append(new_edge) 128 | 129 | else: 130 | 131 | new_edges.append(edge) 132 | 133 | 134 | new_weighted_edges = [] 135 | 136 | for edge in new_edges: 137 | source, destination = edge 138 | weighted_edge = (source, destination, 1) 139 | new_weighted_edges.append(weighted_edge) 140 | 141 | 142 | G = nx.DiGraph() 143 | 144 | G.add_nodes_from(new_nodes) 145 | G.add_weighted_edges_from(new_weighted_edges) 146 | 147 | 148 | if super_source: 149 | 150 | in_degrees = G.in_degree() 151 | 152 | zero_in_degree_nodes = [node for node in in_degrees if in_degrees[node]==0] 153 | zero_in_degree_nodes_sorted = zero_in_degree_nodes 154 | zero_in_degree_nodes_sorted.sort() 155 | 156 | super_source_node = 'super_source_node' 157 | super_source_node_edges = [] 158 | 159 | for zero_in_degree_node in zero_in_degree_nodes: 160 | zero_in_degree_weighted_edge = (super_source_node, zero_in_degree_node, 1) 161 | super_source_node_edges.append(zero_in_degree_weighted_edge) 162 | 163 | G.add_node(super_source_node) 164 | G.add_weighted_edges_from(super_source_node_edges) 165 | 166 | 167 | 168 | return G 169 | 170 | 171 | 172 | def main(fn): 173 | 174 | # Change to True to create a source to all zero degree nodes. 175 | super_source = True 176 | G = reduce_graph(super_source, fn) 177 | 178 | # nx.draw(G,pos=nx.random_layout(G),with_labels=True,node_size=1000,iterations=10000) 179 | # plt.show() 180 | 181 | nodes = G.nodes() 182 | 183 | super_sink_node = 'super_sink_node' 184 | 185 | G.add_node(super_sink_node) 186 | 187 | dep_edges = [] 188 | ancestor_to_descendant = [] 189 | 190 | # find all descendant-ancestor relations. 191 | for node_1 in nodes: 192 | for node_2 in nodes: 193 | if node_1 != node_2: 194 | if not is_substring(node_1, 'condition') and not is_substring(node_2, 'condition'): 195 | 196 | if nx.has_path(G, node_1, node_2): 197 | ancestor_to_descendant.append((node_1, node_2)) 198 | if (node_2, node_1) not in dep_edges: 199 | dep_edges.append((node_1, node_2)) 200 | if nx.has_path(G, node_2, node_1): 201 | ancestor_to_descendant.append((node_2, node_1)) 202 | if (node_2, node_1) not in dep_edges: 203 | dep_edges.append((node_1, node_2)) 204 | 205 | 206 | # for all nodes that do not have descendant-ancestor relations we look for max-flow. 207 | for node_1 in nodes: 208 | for node_2 in nodes: 209 | if not is_substring(node_1, 'condition') and not is_substring(node_2, 'condition'): 210 | if (node_1, node_2) not in dep_edges and (node_2, node_1) not in dep_edges: 211 | if node_1 != node_2 and node_1 != 'super_source_node' and node_2 != 'super_source_node': 212 | 213 | G.add_weighted_edges_from([(node_1, super_sink_node, 1),(node_2, super_sink_node, 1)]) 214 | 215 | for curr_source in nodes: 216 | if not is_substring(curr_source, 'condition'): 217 | if nx.maximum_flow_value(G, curr_source, 'super_sink_node', capacity='weight') == 2: 218 | dep_edges.append((node_1, node_2)) 219 | 220 | # make relationship between descendants 221 | for ancestor, descendant in ancestor_to_descendant: 222 | if ancestor == node_1: 223 | if (descendant, node_2) not in dep_edges and (node_2, descendant) not in dep_edges: 224 | dep_edges.append((descendant, node_2)) 225 | if ancestor == node_2: 226 | if (descendant, node_1) not in dep_edges and (node_1, descendant) not in dep_edges: 227 | dep_edges.append((descendant, node_1)) 228 | 229 | break 230 | 231 | G.remove_edges_from([(node_1, super_sink_node, 1),(node_2, super_sink_node, 1)]) 232 | 233 | 234 | RG = nx.Graph() 235 | 236 | RG.add_edges_from(dep_edges) 237 | if super_source: 238 | RG.remove_node('super_source_node') 239 | 240 | # Draw the relation graph. 241 | # print "\nDrawing the relation graph...\n" 242 | # nx.draw(RG,pos=nx.circular_layout(RG),with_labels=True,node_size=1000,iterations=10000) 243 | 244 | N = RG.number_of_nodes() 245 | print "|V| = %d" % N 246 | max_num_of_edges = (N*(N-1)) >> 1 247 | print "0.5(|V|^2 - |V|) = %d" % max_num_of_edges 248 | E = RG.number_of_edges() 249 | print "|E| = %d" % E 250 | unrelated_nodes = max_num_of_edges - E 251 | print "Number of unrelated node couples = %d" % unrelated_nodes 252 | 253 | 254 | ############################################################################### 255 | ############################################################################### 256 | 257 | if __name__ == "__main__": 258 | 259 | start_time = time.time() 260 | 261 | random_odg = False 262 | 263 | if random_odg: 264 | 265 | fn = 'test_odg.py' 266 | 267 | try: 268 | os.remove('test_odg.py') 269 | except OSError: 270 | pass 271 | 272 | print "######################### ODG data - start ########################" 273 | odg_generator(30, 'test_odg') 274 | print "######################### ODG data - end ##########################" 275 | 276 | else: 277 | 278 | # file name 279 | fn = 'switch_ingress_sched_data' 280 | 281 | 282 | print "##################### relation graph data - start ##################" 283 | main(fn) 284 | print "##################### relation graph data - end ####################" 285 | 286 | print("TIME: --- %s seconds ---" % round(time.time() - start_time, 2)) 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | -------------------------------------------------------------------------------- /drmt.py: -------------------------------------------------------------------------------- 1 | from gurobipy import * 2 | import numpy as np 3 | import collections 4 | import importlib 5 | import math 6 | from schedule_dag import ScheduleDAG 7 | from printers import * 8 | from solution import Solution 9 | from randomized_sieve import * 10 | from sieve_rotator import * 11 | from prmt import PrmtFineSolver 12 | 13 | RND_SIEVE_TIME = 30 14 | 15 | class DrmtScheduleSolver: 16 | def __init__(self, dag, input_spec, latency_spec, seed_rnd_sieve, period_duration, minute_limit): 17 | self.G = dag 18 | self.input_spec = input_spec 19 | self.latency_spec = latency_spec 20 | self.seed_rnd_sieve = seed_rnd_sieve 21 | self.period_duration = period_duration 22 | self.minute_limit = minute_limit 23 | 24 | def solve(self): 25 | """ Returns the optimal schedule 26 | 27 | Returns 28 | ------- 29 | time_of_op : dict 30 | Timeslot for each operation in the DAG 31 | ops_at_time : defaultdic 32 | List of operations in each timeslot 33 | length : int 34 | Maximum latency of optimal schedule 35 | """ 36 | init_drmt_schedule = None 37 | if (self.seed_rnd_sieve): 38 | print ('{:*^80}'.format(' Running rnd sieve ')) 39 | rnd_sch = rnd_sieve(self.input_spec, self.G, RND_SIEVE_TIME, self.period_duration) 40 | 41 | print ('{:*^80}'.format(' Running PRMT + rotator ')) 42 | psolver = PrmtFineSolver(self.G, self.input_spec, self.latency_spec, seed_greedy=True) 43 | solution = psolver.solve(solve_coarse = False) 44 | prmt_sch = sieve_rotator(solution.ops_at_time, self.period_duration, self.latency_spec.dM, self.latency_spec.dA) 45 | 46 | if ((rnd_sch == None) and (prmt_sch == None)): 47 | print ("Both heuristics returned nothing") 48 | init_drmt_schedule = None 49 | elif ((rnd_sch == None)): 50 | print ("Picking output from PRMT") 51 | print ("Latency for PRMT ", max(prmt_sch.values())) 52 | init_drmt_schedule = prmt_sch 53 | elif ((prmt_sch == None)): 54 | print ("Picking output from RND sieve") 55 | print ("Latency for RND sieve: ", max(rnd_sch.values())) 56 | init_drmt_schedule = rnd_sch 57 | else: 58 | print ("Latencies, PRMT: ", max(prmt_sch.values()), " RND sieve: ", max(rnd_sch.values())) 59 | if (max(prmt_sch.values()) < max(rnd_sch.values())): 60 | print ("Picking output from PRMT") 61 | init_drmt_schedule = prmt_sch 62 | else: 63 | print ("Picking output from RND sieve") 64 | init_drmt_schedule = rnd_sch 65 | 66 | if (init_drmt_schedule): 67 | Q_MAX = int(math.ceil((1.0 * (max(init_drmt_schedule.values()) + 1)) / self.period_duration)) 68 | else: 69 | # Set Q_MAX based on critical path 70 | cpath, cplat = self.G.critical_path() 71 | Q_MAX = int(math.ceil(1.5 * cplat / self.period_duration)) 72 | 73 | print ('{:*^80}'.format(' Running DRMT ILP solver ')) 74 | T = self.period_duration 75 | nodes = self.G.nodes() 76 | match_nodes = self.G.nodes(select='match') 77 | action_nodes = self.G.nodes(select='action') 78 | edges = self.G.edges() 79 | 80 | m = Model() 81 | m.setParam("LogToConsole", 0) 82 | 83 | # Create variables 84 | # t is the start time for each DAG node in the first scheduling period 85 | t = m.addVars(nodes, lb=0, ub=GRB.INFINITY, vtype=GRB.INTEGER, name="t") 86 | 87 | # The quotients and remainders when dividing by T (see below) 88 | # qr[v, q, r] is 1 when t[v] 89 | # leaves a quotient of q and a remainder of r, when divided by T. 90 | qr = m.addVars(list(itertools.product(nodes, range(Q_MAX), range(T))), vtype=GRB.BINARY, name="qr") 91 | 92 | # Is there any match/action from packet q in time slot r? 93 | # This is required to enforce limits on the number of packets that 94 | # can be performing matches or actions concurrently on any processor. 95 | any_match = m.addVars(list(itertools.product(range(Q_MAX), range(T))), vtype=GRB.BINARY, name = "any_match") 96 | any_action = m.addVars(list(itertools.product(range(Q_MAX), range(T))), vtype=GRB.BINARY, name = "any_action") 97 | 98 | # The length of the schedule 99 | length = m.addVar(lb=0, ub=GRB.INFINITY, vtype=GRB.INTEGER, name="length") 100 | 101 | # Set objective: minimize length of schedule 102 | m.setObjective(length, GRB.MINIMIZE) 103 | 104 | # Set constraints 105 | 106 | # The length is the maximum of all t's 107 | m.addConstrs((t[v] <= length for v in nodes), "constr_length_is_max") 108 | 109 | # Given v, qr[v, q, r] is 1 for exactly one q, r, i.e., there's a unique quotient and remainder 110 | m.addConstrs((sum(qr[v, q, r] for q in range(Q_MAX) for r in range(T)) == 1 for v in nodes),\ 111 | "constr_unique_quotient_remainder") 112 | 113 | # This is just a way to write dividend = quotient * divisor + remainder 114 | m.addConstrs((t[v] == \ 115 | sum(q * qr[v, q, r] for q in range(Q_MAX) for r in range(T)) * T + \ 116 | sum(r * qr[v, q, r] for q in range(Q_MAX) for r in range(T)) \ 117 | for v in nodes), "constr_division") 118 | 119 | # Respect dependencies in DAG 120 | m.addConstrs((t[v] - t[u] >= self.G.edge[u][v]['delay'] for (u,v) in edges),\ 121 | "constr_dag_dependencies") 122 | 123 | # Number of match units does not exceed match_unit_limit 124 | # for every time step (j) < T, check the total match unit requirements 125 | # across all nodes (v) that can be "rotated" into this time slot. 126 | m.addConstrs((sum(math.ceil((1.0 * self.G.node[v]['key_width']) / self.input_spec.match_unit_size) * qr[v, q, r]\ 127 | for v in match_nodes for q in range(Q_MAX))\ 128 | <= self.input_spec.match_unit_limit for r in range(T)),\ 129 | "constr_match_units") 130 | 131 | # The action field resource constraint (similar comments to above) 132 | m.addConstrs((sum(self.G.node[v]['num_fields'] * qr[v, q, r]\ 133 | for v in action_nodes for q in range(Q_MAX))\ 134 | <= self.input_spec.action_fields_limit for r in range(T)),\ 135 | "constr_action_fields") 136 | 137 | # Any time slot (r) can have match or action operations 138 | # from only match_proc_limit/action_proc_limit packets 139 | # We do this in two steps. 140 | 141 | # First, detect if there is any (at least one) match/action operation from packet q in time slot r 142 | # if qr[v, q, r] = 1 for any match node, then any_match[q,r] must = 1 (same for actions) 143 | # Notice that any_match[q, r] may be 1 even if all qr[v, q, r] are zero 144 | m.addConstrs((sum(qr[v, q, r] for v in match_nodes) <= (len(match_nodes) * any_match[q, r]) \ 145 | for q in range(Q_MAX)\ 146 | for r in range(T)),\ 147 | "constr_any_match1"); 148 | 149 | m.addConstrs((sum(qr[v, q, r] for v in action_nodes) <= (len(action_nodes) * any_action[q, r]) \ 150 | for q in range(Q_MAX)\ 151 | for r in range(T)),\ 152 | "constr_any_action1"); 153 | 154 | # Second, check that, for any r, the summation over q of any_match[q, r] is under proc_limits 155 | m.addConstrs((sum(any_match[q, r] for q in range(Q_MAX)) <= self.input_spec.match_proc_limit\ 156 | for r in range(T)), "constr_match_proc") 157 | m.addConstrs((sum(any_action[q, r] for q in range(Q_MAX)) <= self.input_spec.action_proc_limit\ 158 | for r in range(T)), "constr_action_proc") 159 | 160 | # Seed initial values 161 | if init_drmt_schedule: 162 | for i in nodes: 163 | t[i].start = init_drmt_schedule[i] 164 | 165 | # Solve model 166 | m.setParam('TimeLimit', self.minute_limit * 60) 167 | m.optimize() 168 | ret = m.Status 169 | 170 | if (ret == GRB.INFEASIBLE): 171 | print ('Infeasible') 172 | return None 173 | elif ((ret == GRB.TIME_LIMIT) or (ret == GRB.INTERRUPTED)): 174 | if (m.SolCount == 0): 175 | print ('Hit time limit or interrupted, no solution found yet') 176 | return None 177 | else: 178 | print ('Hit time limit or interrupted, suboptimal solution found with gap ', m.MIPGap) 179 | elif (ret == GRB.OPTIMAL): 180 | print ('Optimal solution found with gap ', m.MIPGap) 181 | else: 182 | print ('Return code is ', ret) 183 | assert(False) 184 | 185 | # Construct and return schedule 186 | self.time_of_op = {} 187 | self.ops_at_time = collections.defaultdict(list) 188 | self.length = int(length.x + 1) 189 | assert(self.length == length.x + 1) 190 | for v in nodes: 191 | tv = int(t[v].x) 192 | self.time_of_op[v] = tv 193 | self.ops_at_time[tv].append(v) 194 | 195 | # Compute periodic schedule to calculate resource usage 196 | self.compute_periodic_schedule() 197 | 198 | # Populate solution 199 | solution = Solution() 200 | solution.time_of_op = self.time_of_op 201 | solution.ops_at_time = self.ops_at_time 202 | solution.ops_on_ring = self.ops_on_ring 203 | solution.length = self.length 204 | solution.match_key_usage = self.match_key_usage 205 | solution.action_fields_usage = self.action_fields_usage 206 | solution.match_units_usage = self.match_units_usage 207 | solution.match_proc_usage = self.match_proc_usage 208 | solution.action_proc_usage = self.action_proc_usage 209 | return solution 210 | 211 | def compute_periodic_schedule(self): 212 | T = self.period_duration 213 | self.ops_on_ring = collections.defaultdict(list) 214 | self.match_key_usage = dict() 215 | self.action_fields_usage = dict() 216 | self.match_units_usage = dict() 217 | self.match_proc_set = dict() 218 | self.match_proc_usage = dict() 219 | self.action_proc_set = dict() 220 | self.action_proc_usage = dict() 221 | for t in range(T): 222 | self.match_key_usage[t] = 0 223 | self.action_fields_usage[t] = 0 224 | self.match_units_usage[t] = 0 225 | self.match_proc_set[t] = set() 226 | self.match_proc_usage[t] = 0 227 | self.action_proc_set[t] = set() 228 | self.action_proc_usage[t] = 0 229 | 230 | for v in self.G.nodes(): 231 | k = self.time_of_op[v] / T 232 | r = self.time_of_op[v] % T 233 | self.ops_on_ring[r].append('p[%d].%s' % (k,v)) 234 | if self.G.node[v]['type'] == 'match': 235 | self.match_key_usage[r] += self.G.node[v]['key_width'] 236 | self.match_units_usage[r] += math.ceil((1.0 * self.G.node[v]['key_width'])/ self.input_spec.match_unit_size) 237 | self.match_proc_set[r].add(k) 238 | self.match_proc_usage[r] = len(self.match_proc_set[r]) 239 | else: 240 | self.action_fields_usage[r] += self.G.node[v]['num_fields'] 241 | self.action_proc_set[r].add(k) 242 | self.action_proc_usage[r] = len(self.action_proc_set[r]) 243 | 244 | if __name__ == "__main__": 245 | # Cmd line args 246 | if (len(sys.argv) != 5): 247 | print ("Usage: ", sys.argv[0], "