22 #include <gurobi_c++.h>
63 std::vector<GRBVar> makeGRBVars(GRBModel& model,
int count,
char type) {
64 std::unique_ptr<GRBVar[]> storage(model.addVars(count, type));
65 return {storage.get(), storage.get() + count};
69 auto& nodeClasses()
const {
return node_classes; }
70 const auto& mrrg()
const {
return *m_mrrg; }
79 [](std::shared_ptr<CGRA> cgra,
int timelimit,
const ConfigStore& args) {
80 return std::make_unique<ILPMapper>(cgra, timelimit, args);
83 "Fully encode the mapping problem in an ILP.\n"
84 "Initially presented in 'An Architecture-Agnostic Integer Linear Programming Approach to CGRA Mapping' (DAC 2018)",
86 {
"enable_topological_order_costing_by_max", 0,
""},
87 {
"enable_topological_order_costing_by_sum", 0,
""},
88 {
"enable_topological_order_mode", 0,
""},
89 {
"lazy_constraint_add_mode", 0,
""},
90 {
"max_threads", 1,
""},
91 {
"mip_gap", 0.2,
"Relative threshold on the difference between the primal and dual cost functions to consider a solution optimal"},
92 {
"per_value_topological_order_variables", 0,
""},
94 {
"solution_limit", 1,
"Maximum number of solutions that will be considered before giving up and taking the lowest cost one found. Larger values generally lead to more optimal solutions."},
95 {
"model_dump_filename",
"",
""},
96 {
"model_IIS_dump_filename",
"",
""},
100 {
"enable_lazy_constraint_.*",
"boolean, controls whether the constraint group that follows is added lazily"},
108 , mipgap(args.getReal(
"mip_gap"))
109 , solnlimit(args.getInt(
"solution_limit"))
112 if (args.
getBoolOr(
"enable_lazy_constraint_" + type_and_name.second,
false)) {
123 std::cout <<
"[INFO] Mapping DFG Onto CGRA Architecture..." << std::endl;
124 std::cout <<
"[INFO] ILP Mapper Config: " <<
flags <<
'\n';
131 {
"check_mux_exclusivity",
"error" },
134 if (flags.getInt(
"verbosity") > 0) {
135 std::cout <<
"[INFO] Inserted " << (muxExInsertionResult.transformed_mrrg.size() - mrrg.size())
136 <<
" nodes for mux exclusivity invariant\n";
140 Mapping mapping_result(cgra, II, opgraph);
144 mapper_status = GurobiMap(*opgraph, II, &mapping_result, muxExInsertionResult.transformed_mrrg, fix_port);
146 throwExceptionButWrapIfGurobiException(std::current_exception());
149 switch (mapper_status) {
158 std::cout <<
"[ERROR] CGRA Mapping Results in Unlisted Status" << std::endl;
159 std::cout <<
"[INFO] Please Report this Bug to Xander Chin(xan@ece.utoronto.ca)" << std::endl;
176 GRBEnv env = GRBEnv(
true);
177 env.set(GRB_IntParam_OutputFlag,
flags.
getInt(
"verbosity") > 0);
179 env.set(GRB_DoubleParam_MIPGap,
mipgap);
183 GRBModel model = GRBModel(env);
184 model.set(GRB_IntParam_Seed,
flags.
getInt(
"seed"));
185 model.set(GRB_IntParam_Threads,
flags.
getInt(
"max_threads"));
191 const int num_dfg_vals = opgraph.
valNodes().size();
192 const int num_dfg_ops = opgraph.
opNodes().size();
193 const int num_mrrg_r = mrrg_info.nodeClasses().routing_nodes.size();
194 const int num_mrrg_f = mrrg_info.nodeClasses().function_nodes.size();
196 const auto R = makeGRBVars(model, num_dfg_vals * num_mrrg_r, GRB_BINARY);
197 const auto F = makeGRBVars(model, num_dfg_ops * num_mrrg_f, GRB_BINARY);
199 std::map<OpGraph::ValDescriptor, std::map<MRRG::NodeDescriptor, GRBVar>> R_var_map;
200 std::map<OpGraph::ValDescriptor, std::map<MRRG::NodeDescriptor, std::vector<GRBVar>>> S_var_map;
201 std::map<OpGraph::ValDescriptor, std::map<MRRG::NodeDescriptor, GRBVar>> T_var_map;
202 std::map<OpGraph::OpDescriptor, std::map<MRRG::NodeDescriptor, GRBVar>> F_var_map;
205 std::map<OpGraph::ValDescriptor, std::map<MRRG::NodeDescriptor, std::map<MRRG::NodeDescriptor, GRBVar>>> edge_used_var_map;
208 int constr_count = 0;
210 for (
auto& val : opgraph.
valNodes()) {
211 const auto num_val_fanouts = (std::ptrdiff_t)opgraph.
outputOps(val).size();
213 for (
auto& r : mrrg_info.nodeClasses().routing_nodes) {
216 R_var_map[val][r] = R[j * num_mrrg_r + i];
218 auto& s_vars_for_val_and_node = S_var_map[val][r];
219 if (num_val_fanouts > 1) {
220 s_vars_for_val_and_node = makeGRBVars(model, num_val_fanouts, GRB_BINARY);
221 for (
int k = 0; k < num_val_fanouts; k++) {
222 auto& fanout_s_var = s_vars_for_val_and_node.at(k);
224 model.addConstr((R_var_map.at(val).at(r)) >= fanout_s_var,
"sub_val" +
std::to_string(constr_count++));
227 s_vars_for_val_and_node = {R_var_map.at(val).at(r)};
235 for (
auto& op : opgraph.
opNodes()) {
237 for (
auto& f : mrrg_info.nodeClasses().function_nodes) {
238 F_var_map[op][f] = F[p * num_mrrg_f + q];
248 std::vector<GRBVar> all_T_vars;
249 std::map<MRRG::NodeDescriptor, GRBVar> T_var_by_node;
250 for (
const auto& val_and_node_to_var_map : R_var_map) {
251 for (
const auto& node_and_var : val_and_node_to_var_map.second) {
252 auto& val = val_and_node_to_var_map.first;
253 auto& r = node_and_var.first;
254 const auto& tvar = [&]() {
255 if (not
flags.
getBool(
"per_value_topological_order_variables")) {
256 const auto search_result = T_var_by_node.find(r);
257 if (search_result ==
end(T_var_by_node)) {
258 auto res = std::unique_ptr<GRBVar[]>(model.addVars(1, GRB_INTEGER))[0];
259 T_var_by_node.emplace(r, res);
260 res.set(GRB_StringAttr_VarName,
"T_" + R_var_map.at(val).at(r).get(GRB_StringAttr_VarName));
261 all_T_vars.emplace_back(res);
264 return search_result->second;
267 auto res = std::unique_ptr<GRBVar[]>(model.addVars(1, GRB_INTEGER))[0];
268 res.set(GRB_StringAttr_VarName,
"T_" + R_var_map.at(val).at(r).get(GRB_StringAttr_VarName));
269 all_T_vars.emplace_back(res);
273 T_var_map[val][r] = tvar;
277 for (
auto& val : opgraph.
valNodes()) {
278 for (
auto &r: mrrg_info.nodeClasses().routing_nodes) {
279 for (
auto &mrrg_fanout : r->fanout) {
283 const auto& rvar = R_var_map.at(val).at(r);
284 const auto& rfanoutvar = R_var_map.at(val).at(mrrg_fanout);
285 const auto RvalString = rvar.get(GRB_StringAttr_VarName) +
"_to_" + rfanoutvar.get(GRB_StringAttr_VarName);
286 auto evar = std::unique_ptr<GRBVar[]>(model.addVars(1, GRB_BINARY))[0];
287 evar.set(GRB_StringAttr_VarName,
"Edge_used_" + RvalString);
288 edge_used_var_map[val][r][mrrg_fanout] = evar;
293 const auto dot_product_of_S_and_F_vars_with_latency_for_opgraph_path = [&](
const auto& path) {
295 for (
const auto& edge : path) {
296 for (
const auto& r : mrrg_info.nodeClasses().routing_nodes) {
299 for (
const auto& f : mrrg_info.nodeClasses().function_nodes) {
307 GRBLinExpr objective;
308 for (
const auto& r : R) {
312 if (
flags.
getBool(
"enable_topological_order_costing_by_max") &&
flags.
getBool(
"enable_topological_order_costing_by_sum")) {
313 throw cgrame_mapper_error(
"can't set both enable_topological_order_costing_by_max and enable_topological_order_costing_by_sum");
316 if (
flags.
getBool(
"enable_topological_order_costing_by_max")) {
317 auto max_tvar = std::unique_ptr<GRBVar[]>(model.addVars(1, GRB_INTEGER))[0];
318 model.addGenConstrMax(max_tvar, all_T_vars.data(), all_T_vars.size(), 0,
"max_order_var");
319 objective += max_tvar;
322 if (
flags.
getBool(
"enable_topological_order_costing_by_sum")) {
323 for (
const auto& t : all_T_vars) {
328 model.setObjective(objective, GRB_MINIMIZE);
330 ConstraintSet<ConstraintGroup> constraint_set;
331 constraint_set.setVerbosity(
flags.
getInt(
"verbosity"));
335 auto result = params.makeReturnValue();
337 for (
int i = 0; i < num_mrrg_r; i++) {
338 GRBLinExpr constraint;
339 long num_node_usages = 0;
341 for (
int j = 0; j < num_dfg_vals; j++) {
342 auto& r_val = R[j * num_mrrg_r + i];
344 num_node_usages += params.inIncrementalMode() ? std::lround(params.callback->getSolution(r_val)) : 0;
347 if (not params.inIncrementalMode() or not ( num_node_usages <= 1 )) {
348 result.constraints.emplace_back(makeNewStandardConstraintID(i), constraint <= 1);
357 auto result = params.makeReturnValue();
359 for (
int p = 0; p < num_mrrg_f; p++) {
360 GRBLinExpr constraint;
361 auto coeffs = std::make_unique<double[]>(num_dfg_ops);
362 auto vars = std::make_unique<GRBVar[]>(num_dfg_ops);
364 for (
int q = 0; q < num_dfg_ops; q++) {
366 vars[q] = F[q * num_mrrg_f + p];
369 constraint.addTerms(coeffs.get(), vars.get(), num_dfg_ops);
371 result.constraints.emplace_back(makeNewStandardConstraintID(p), constraint <= 1);
379 auto result = params.makeReturnValue();
381 for (
int q = 0; q < num_dfg_ops; q++) {
382 GRBLinExpr constraint;
383 auto coeffs = std::make_unique<double[]>(num_mrrg_f);
384 auto vars = std::make_unique<GRBVar[]>(num_mrrg_f);
386 for (
int p = 0; p < num_mrrg_f; p++) {
388 vars[p] = F[q * num_mrrg_f + p];
391 constraint.addTerms(coeffs.get(), vars.get(), num_mrrg_f);
393 result.constraints.emplace_back(makeNewStandardConstraintID(q), constraint == 1);
401 auto result = params.makeReturnValue();
403 for (
auto &val: opgraph.
valNodes()) {
404 for (
auto &r: mrrg_info.nodeClasses().routing_nodes) {
405 int val_fanouts = val->output.size();
406 for (
int i = 0; i < val_fanouts; i++) {
407 GRBLinExpr sum_of_fanouts;
408 long num_fanouts_used = 0.0;
409 for (
auto &mrrg_fanout : r->fanout) {
411 const auto& s_val = S_var_map.at(val).at(mrrg_fanout).at(i);
412 sum_of_fanouts += s_val;
413 num_fanouts_used += params.inIncrementalMode() ? std::lround(params.callback->getSolution(s_val)) : 0;
415 auto &op = val->output[i];
416 for (
auto& fanin : mrrg_fanout->fanin) {
418 const auto& f_val = F_var_map.at(op).at(mrrg_fanout);
419 sum_of_fanouts += f_val;
420 num_fanouts_used += params.inIncrementalMode() ? std::lround(params.callback->getSolution(f_val)) : 0;
425 make_and_throw<cgrame_mapper_error>([&](
auto&& s) {
431 const auto& src_s_val = S_var_map.at(val).at(r).at(i);
432 const auto constraint_is_obeyed = [&]() {
433 return num_fanouts_used >= std::lround(params.callback->getSolution(src_s_val));
436 if (not params.inIncrementalMode() || not constraint_is_obeyed()) {
437 result.constraints.emplace_back(makeNewTupleConstraintID(val, r), sum_of_fanouts >= src_s_val);
448 auto result = params.makeReturnValue();
450 for (
auto &val: opgraph.
valNodes()) {
451 for (
auto &r: mrrg_info.nodeClasses().routing_nodes) {
452 GRBLinExpr sum_of_fanins;
453 long num_fanin_used = 0;
454 int fanin_count = r->fanin.size();
455 if (fanin_count > 1) {
456 for (
auto &fanin : r->fanin) {
458 throw make_from_stream<cgrame_mapper_error>([&](
auto&& s) {
459 s <<
"node has more than one fanin, and one of these fanin is not a routing node";
463 if (fanin->fanout.size() != 1) {
464 throw make_from_stream<cgrame_mapper_error>([&](
auto&& s) {
465 s <<
"Mux Exclusivity Constraint Invariant is violated\n";
466 s <<
"Candidate MUX node is: " << mrrg.
getNodeRef(r) <<
'\n';
467 for (
const auto& r_fanin : mrrg.
fanin(r)) {
468 s << mrrg.getNodeRef(r_fanin) <<
"->" << mrrg.getNodeRef(r) <<
'\n';
471 s <<
"Problem fanin is: " << mrrg.
getNodeRef(fanin) <<
'\n';
472 for (
const auto& fanin_fanout : mrrg.
fanout(fanin)) {
473 s << mrrg.getNodeRef(fanin) <<
"->" << mrrg.getNodeRef(fanin_fanout) <<
'\n';
478 auto& fanin_r_val = R_var_map.at(val).at(fanin);
479 sum_of_fanins += fanin_r_val;
480 num_fanin_used += params.inIncrementalMode() ? std::lround(params.callback->getSolution(fanin_r_val)) : 0.0;
483 auto& src_r_val = R_var_map.at(val).at(r);
484 const auto& constraint_is_obeyed = [&]() {
485 return num_fanin_used == std::lround(params.callback->getSolution(src_r_val));
488 if (not params.inIncrementalMode() or not constraint_is_obeyed()) {
489 auto cid = makeNewTupleConstraintID(val, r);
490 result.constraints.emplace_back(std::move(cid), sum_of_fanins == src_r_val);
502 auto result = params.makeReturnValue();
504 for (
auto &op: opgraph.
opNodes()) {
505 for (
auto &f: mrrg_info.nodeClasses().function_nodes) {
508 if (f->fanout.size() != 1) {
509 throw make_from_stream<cgrame_mapper_error>([&](
auto&& s) {
510 s <<
"FU does not have exactly one fanout. " << mrrg.
getNodeRef(f).name <<
" has " << mrrg.
fanout(f).size();
513 for (
auto& r : f->fanout) {
514 int val_fanouts = val->
output.size();
515 for (
int i = 0; i < val_fanouts; i++) {
516 result.constraints.emplace_back(makeNewTupleConstraintID(op, f), (F_var_map.at(op).at(f)) == S_var_map.at(val).at(r).at(i));
531 auto result = params.makeReturnValue();
533 for (
const auto& val : opgraph.
valNodes()) {
534 const auto& val_fanout = opgraph.
outputOps(val);
537 std::map<OpGraph::OpDescriptor, std::vector<int>> outputs_to_op;
538 for (
int i = 0; i < val_fanout.size(); ++i) {
539 const auto op = val_fanout.at(i);
540 outputs_to_op[op].push_back(i);
544 for (
const auto& op_and_output_list : outputs_to_op) {
545 const auto& op = op_and_output_list.first;
546 const auto& output_list = op_and_output_list.second;
547 if (output_list.size() == 1)
continue;
549 for (
const auto& f : mrrg_info.nodeClasses().function_nodes) {
551 for (
const auto& fanin_mrrg : mrrg.
fanin(f)) {
553 GRBLinExpr sum_of_s_at_input;
554 for (
const auto i : output_list) {
555 sum_of_s_at_input += S_var_map.at(val).at(fanin_mrrg).at(i);
557 result.constraints.emplace_back(
558 makeNewTupleConstraintID(op, val, f, fanin_mrrg),
559 sum_of_s_at_input <= 1
571 auto result = params.makeReturnValue();
575 for (
auto &op: opgraph.
opNodes()) {
577 for (
auto& node : fix_port) {
578 if (op->getName() == node.first) {
580 fixNode = node.second;
583 for (
auto &f: mrrg_info.nodeClasses().function_nodes) {
585 if(f->getHierarchyQualifiedName().find(fixNode) == fixNode.npos || !f->canMapOp(op)) {
588 result.constraints.emplace_back(makeNewTupleConstraintID(op, f), (F_var_map.at(op).at(f)) == 0);
590 }
else if (!f->canMapOp(op)) {
591 result.constraints.emplace_back(makeNewTupleConstraintID(op, f), (F_var_map.at(op).at(f)) == 0);
609 auto result = params.makeReturnValue();
611 for (
const auto& path_pair : trails_to_balance.noncyclic_trail_pairs) {
612 auto sum1 = dot_product_of_S_and_F_vars_with_latency_for_opgraph_path(path_pair.first);
613 auto sum2 = dot_product_of_S_and_F_vars_with_latency_for_opgraph_path(path_pair.second);
614 result.constraints.emplace_back(makeNewTupleConstraintID(&path_pair), sum1 == sum2);
622 auto result = params.makeReturnValue();
624 for (
const auto& path : trails_to_balance.cyclic_trails) {
625 auto sum = dot_product_of_S_and_F_vars_with_latency_for_opgraph_path(path);
626 result.constraints.emplace_back(makeNewTupleConstraintID(&path), sum == mrrg.
initiationInterval());
634 auto result = params.makeReturnValue();
636 if (params.inIncrementalMode()) {
637 std::cerr <<
"Cannot add edge use constraints lazily, as they are general constraints" << std::endl;
638 params.callback->abort();
641 int edge_used_constr_count = 0;
642 for (
auto& val : opgraph.
valNodes()) {
643 for (
auto &r: mrrg_info.nodeClasses().routing_nodes) {
644 for (
auto &mrrg_fanout : r->fanout) {
648 const auto& rvar = R_var_map.at(val).at(r);
649 const auto& rfanoutvar = R_var_map.at(val).at(mrrg_fanout);
650 const auto& evar = edge_used_var_map.at(val).at(r).at(mrrg_fanout);
653 std::array<GRBVar, 2> r_values { rvar, rfanoutvar };
654 model.addGenConstrAnd(
656 r_values.data(), r_values.size(),
664 std::cout <<
"actually added " << edge_used_constr_count <<
" edge_used constraints\n";
671 auto result = params.makeReturnValue();
673 if (params.inIncrementalMode()) {
674 std::cerr <<
"Cannot add topological constraints lazily, as they are general constraints" << std::endl;
675 params.callback->abort();
678 int ordering_constr_count = 0;
679 for (
const auto& val_and_node_to_var_map : R_var_map) {
680 for (
const auto& node_and_var : val_and_node_to_var_map.second) {
681 auto& r = node_and_var.first;
682 auto& val = val_and_node_to_var_map.first;
683 for (
auto& mrrg_fanout : r->fanout) {
687 model.addGenConstrIndicator(
688 edge_used_var_map.at(val).at(r).at(mrrg_fanout),
true,
689 T_var_map.at(val).at(mrrg_fanout) - T_var_map.at(val).at(r), GRB_GREATER_EQUAL, 1,
697 std::cout <<
"actually added " << ordering_constr_count <<
" ordering constraints\n";
704 auto result = params.makeReturnValue();
706 for (
auto &val: opgraph.
valNodes()) {
707 for (
auto &r: mrrg_info.nodeClasses().routing_nodes) {
708 if (r->supported_operand_tags.empty())
continue;
709 for (
int k = 0; k < (ptrdiff_t)val->output.size(); k++) {
711 result.constraints.emplace_back(makeNewTupleConstraintID(val, r, k), S_var_map.at(val).at(r).at(k) == 0);
719 const auto& all_cgroups = constraint_set.getAllConstraintGroupsRegistered();
722 if (e == ConstraintGroup::MuxExclusivity) {
733 const auto non_lazy_cgroups =
filter_collection(filtered_cgroups, [&](
auto&& e) {
734 return lazy_constraints.find(e) ==
end(lazy_constraints);
737 const auto lazy_add_mode = [&]() {
switch (flags.getInt(
"lazy_constraint_add_mode")) {
738 default:
return ConstraintAddMode::LazyViaCallback;
739 case 1:
return ConstraintAddMode::LazyToModel1;
740 case 2:
return ConstraintAddMode::LazyToModel2;
741 case 3:
return ConstraintAddMode::LazyToModel3;
744 auto constraint_add_conf = ConstraintAddConf::make<ConstraintGroup>({
745 { lazy_add_mode, lazy_constraints },
746 { ConstraintAddMode::NotLazyToModel, non_lazy_cgroups },
749 if (flags.getInt(
"verbosity") > 0) {
750 std::cout <<
"Adding constraints to model...\n";
752 auto constraint_cache = constraint_set.addConstraintsToModel(ConstraintCache{}, constraint_add_conf, model);
754 auto callback = makeOnMIPSolutionFunctorGRBCallback([&](
auto& cb_obj) {
755 if (flags.getInt(
"verbosity") > 0) {
756 std::cout <<
"CGRAME_main_ILP_callback is adding constraints...\n";
758 constraint_cache = constraint_set.addConstraintsViaCallback(std::move(constraint_cache), constraint_add_conf, cb_obj);
761 if (constraint_add_conf.willRequireLazyModelAttribute()) {
762 model.set(GRB_IntParam_LazyConstraints, 1);
766 model.setCallback(&callback);
768 {
const auto model_dump_filename = flags.getString(
"model_dump_filename");
769 if (not model_dump_filename.empty()) {
770 const auto model_dump_filename_with_extension = model_dump_filename
771 + (model_dump_filename.find(
'.') == model_dump_filename.npos ?
".lp" :
"");
772 std::cout <<
"[INFO] Writing model to " << model_dump_filename_with_extension << std::endl;
773 model.write(model_dump_filename_with_extension);
774 std::cout <<
"[INFO] Done writing model to " << model_dump_filename_with_extension << std::endl;
780 mapping_result->solve_time_in_seconds = model.get(GRB_DoubleAttr_Runtime);
782 int status = model.get(GRB_IntAttr_Status);
785 {
const auto model_IIS_dump_filename = flags.getString(
"model_IIS_dump_filename");
786 if (not model_IIS_dump_filename.empty()) {
787 const auto iis_filename_with_extension = model_IIS_dump_filename
788 + (model_IIS_dump_filename.find(
'.') == model_IIS_dump_filename.npos ?
".ilp" :
"");
789 std::cout <<
"[INFO] Computing IIS, will dump to " << iis_filename_with_extension << std::endl;
791 std::cout <<
"[INFO] Writing IIS to " << iis_filename_with_extension << std::endl;
792 model.write(iis_filename_with_extension);
793 std::cout <<
"[INFO] Done writing IIS to " << iis_filename_with_extension << std::endl;
798 case GRB_OPTIMAL: [[fallthrough]];
799 case GRB_SUBOPTIMAL: [[fallthrough]];
800 case GRB_SOLUTION_LIMIT:
801 if (flags.getInt(
"verbosity") > 0) {
802 std::cout <<
"FinalILPObjective: " << model.get(GRB_DoubleAttr_ObjVal) <<
'\n';
805 for (
auto& val : opgraph.valNodes()) {
806 for (
auto& r : mrrg_info.nodeClasses().routing_nodes) {
807 if (std::llround(R_var_map.at(val).at(r).get(GRB_DoubleAttr_X))) {
808 mapping_result->mapMRRGNode(val, r);
813 for (
auto& op : opgraph.opNodes()) {
814 for (
auto& f : mrrg_info.nodeClasses().function_nodes) {
815 if (std::llround(F_var_map.at(op).at(f).get(GRB_DoubleAttr_X))) {
816 mapping_result->mapMRRGNode(op, f);