CIRCT 20.0.0git
Loading...
Searching...
No Matches
CPSATSchedulers.cpp
Go to the documentation of this file.
1//===- CPSATSchedulers.cpp - Schedulers using external CPSAT solvers
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// Implementation of cp-sat programming-based schedulers using external solvers
10// via OR-Tools.
11//
12//===----------------------------------------------------------------------===//
13
15
16#include "mlir/IR/Operation.h"
17
18#include "ortools/sat/cp_model.h"
19
20#include "llvm/Support/Debug.h"
21#include "llvm/Support/Format.h"
22
23#define DEBUG_TYPE "cpsat-schedulers"
24
25using namespace circt;
26using namespace circt::scheduling;
27using namespace operations_research;
28using namespace operations_research::sat;
29
30using llvm::dbgs;
31using llvm::format;
32
33/// Solve the shared operators problem by modeling it as a Resource
34/// Constrained Project Scheduling Problem (RCPSP), which in turn is formulated
35/// as a Constraint Programming (CP) Satisfiability (SAT) problem.
36///
37/// This is a high-fidelity translation of
38/// https://github.com/google/or-tools/blob/stable/examples/python/rcpsp_sat.py
39/// but a gentler introduction (though with differing formulation) is
40/// https://python-mip.readthedocs.io/en/latest/examples.html
42 Operation *lastOp) {
43 Operation *containingOp = prob.getContainingOp();
44 if (!prob.hasOperation(lastOp))
45 return containingOp->emitError("problem does not include last operation");
46
47 CpModelBuilder cpModel;
48 auto &tasks = prob.getOperations();
49
50 DenseMap<Operation *, IntVar> taskStarts;
51 DenseMap<Operation *, IntVar> taskEnds;
52 DenseMap<Problem::OperatorType, SmallVector<IntervalVar, 4>>
53 resourcesToTaskIntervals;
54
55 // First get horizon, i.e., the time taken if all operations were executed
56 // sequentially.
57 unsigned horizon = 0;
58 for (auto *task : tasks) {
59 unsigned duration = *prob.getLatency(*prob.getLinkedOperatorType(task));
60 horizon += duration;
61 }
62
63 // Build task-interval decision variables, which effectively serve to
64 // constrain startVar and endVar to be duration apart. Then map them
65 // to the resources (operators) consumed during those intervals. Note,
66 // resources are in fact not constrained to be occupied for the whole of the
67 // task interval, but only during the first "tick". See comment below
68 // regarding cpModel.NewFixedSizeIntervalVar.
69 for (auto item : llvm::enumerate(tasks)) {
70 auto i = item.index();
71 auto *task = item.value();
72 IntVar startVar = cpModel.NewIntVar(Domain(0, horizon))
73 .WithName((Twine("start_of_task_") + Twine(i)).str());
74 IntVar endVar = cpModel.NewIntVar(Domain(0, horizon))
75 .WithName((Twine("end_of_task_") + Twine(i)).str());
76 taskStarts[task] = startVar;
77 taskEnds[task] = endVar;
78 auto resource = prob.getLinkedOperatorType(task);
79 unsigned duration = *prob.getLatency(*resource);
80 IntervalVar taskInterval =
81 cpModel.NewIntervalVar(startVar, duration, endVar)
82 .WithName((Twine("task_interval_") + Twine(i)).str());
83
84 if (prob.getLimit(*resource))
85 resourcesToTaskIntervals[resource.value()].emplace_back(taskInterval);
86 }
87
88 // Check for cycles and otherwise establish operation ordering
89 // constraints.
90 for (Operation *task : tasks) {
91 for (auto dep : prob.getDependences(task)) {
92 Operation *src = dep.getSource();
93 Operation *dst = dep.getDestination();
94 if (src == dst)
95 return containingOp->emitError() << "dependence cycle detected";
96 cpModel.AddLessOrEqual(taskEnds[src], taskStarts[dst]);
97 }
98 }
99
100 // Establish "cumulative" constraints in order to constrain maximum
101 // concurrent usage of operators.
102 for (auto resourceToTaskIntervals : resourcesToTaskIntervals) {
103 Problem::OperatorType &resource = resourceToTaskIntervals.getFirst();
104 auto capacity = prob.getLimit(resource);
105 SmallVector<IntervalVar, 4> &taskIntervals =
106 resourceToTaskIntervals.getSecond();
107 // The semantics of cumulative constraints in or-tools are such that
108 // for any integer point, the sum of the demands across all
109 // intervals containing that point does not exceed the capacity of the
110 // resource. Thus tasks, in 1-1 correspondence with their intervals, are
111 // constrained to satisfy maximum resource requirements.
112 // See https://or.stackexchange.com/a/3363 for more details.
113 CumulativeConstraint cumu = cpModel.AddCumulative(capacity.value());
114 for (const auto &item : llvm::enumerate(taskIntervals)) {
115 auto i = item.index();
116 auto taskInterval = item.value();
117 IntVar demandVar = cpModel.NewIntVar(Domain(1)).WithName(
118 (Twine("demand_") + Twine(i) + Twine("_") + Twine(resource.strref()))
119 .str());
120 // Conventional formulation for SharedOperatorsProblem;
121 // interval during which the resource is occupied has size 1.
122 IntervalVar start =
123 cpModel.NewFixedSizeIntervalVar(taskInterval.StartExpr(), 1);
124 cumu.AddDemand(start, demandVar);
125 }
126 }
127
128 cpModel.Minimize(taskEnds[lastOp]);
129
130 Model model;
131
132 int numSolutions = 0;
133 model.Add(NewFeasibleSolutionObserver([&](const CpSolverResponse &r) {
134 LLVM_DEBUG(dbgs() << "Solution " << numSolutions << '\n');
135 LLVM_DEBUG(dbgs() << "Solution status" << r.status() << '\n');
136 ++numSolutions;
137 }));
138
139 LLVM_DEBUG(dbgs() << "Starting solver\n");
140 const CpSolverResponse response = SolveCpModel(cpModel.Build(), &model);
141
142 if (response.status() == CpSolverStatus::OPTIMAL ||
143 response.status() == CpSolverStatus::FEASIBLE) {
144 for (auto *task : tasks)
145 prob.setStartTime(task, SolutionIntegerValue(response, taskStarts[task]));
146
147 return success();
148 }
149 return containingOp->emitError() << "infeasible";
150}
std::optional< unsigned > getLatency(OperatorType opr)
The latency is the number of cycles opr needs to compute its result.
Definition Problems.h:204
bool hasOperation(Operation *op)
Return true if op is part of this problem.
Definition Problems.h:170
std::optional< OperatorType > getLinkedOperatorType(Operation *op)
The linked operator type provides the runtime characteristics for op.
Definition Problems.h:196
void setStartTime(Operation *op, unsigned val)
Definition Problems.h:215
const OperationSet & getOperations()
Return the set of operations.
Definition Problems.h:172
DependenceRange getDependences(Operation *op)
Return a range object to transparently iterate over op's incoming 1) implicit def-use dependences (ba...
Definition Problems.cpp:53
Operation * getContainingOp()
Return the operation containing this problem, e.g. to emit diagnostics.
Definition Problems.h:165
mlir::StringAttr OperatorType
Operator types are distinguished by name (chosen by the client).
Definition Problems.h:98
This class models a resource-constrained scheduling problem.
Definition Problems.h:413
std::optional< unsigned > getLimit(OperatorType opr)
The limit is the maximum number of operations using opr that are allowed to start in the same time st...
Definition Problems.h:427
LogicalResult scheduleCPSAT(SharedOperatorsProblem &prob, Operation *lastOp)
Solve the acyclic problem with shared operators using constraint programming and an external SAT solv...
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.