[Scheduling] Set up infrastructure for using OR-Tools' solvers. (#2465)

This commit is contained in:
Julian Oppermann 2022-01-18 10:55:15 +01:00 committed by GitHub
parent e389ffa605
commit 677d780169
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 235 additions and 2 deletions

View File

@ -295,6 +295,26 @@ else()
endif() endif()
endif() endif()
#-------------------------------------------------------------------------------
# OR-Tools Configuration
#-------------------------------------------------------------------------------
option(OR_TOOLS_DISABLE "Disable OR-Tools.")
if (OR_TOOLS_DISABLE)
message(STATUS "Disabling OR-Tools.")
else()
if(DEFINED OR_TOOLS_PATH)
list(APPEND CMAKE_PREFIX_PATH ${OR_TOOLS_PATH}/lib/cmake)
endif()
find_package(ortools CONFIG)
if (ortools_FOUND)
list(APPEND CMAKE_INSTALL_RPATH ${ortools_LIBDIR})
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
endif()
endif()
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
# llhd-sim Configuration # llhd-sim Configuration
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------

View File

@ -50,6 +50,11 @@ LogicalResult scheduleSimplex(SharedOperatorsProblem &prob, Operation *lastOp);
/// that do not include at least one edge with a non-zero distance. /// that do not include at least one edge with a non-zero distance.
LogicalResult scheduleSimplex(ModuloProblem &prob, Operation *lastOp); LogicalResult scheduleSimplex(ModuloProblem &prob, Operation *lastOp);
/// Solve the basic problem using linear programming and an external LP solver.
/// The objective is to minimize the start time of the given \p lastOp. Fails if
/// the dependence graph contains cycles.
LogicalResult scheduleLP(Problem &prob, Operation *lastOp);
} // namespace scheduling } // namespace scheduling
} // namespace circt } // namespace circt

View File

@ -1,21 +1,39 @@
set(LLVM_OPTIONAL_SOURCES set(LLVM_OPTIONAL_SOURCES
ASAPScheduler.cpp ASAPScheduler.cpp
LPSchedulers.cpp
Problems.cpp Problems.cpp
SimplexSchedulers.cpp SimplexSchedulers.cpp
TestPasses.cpp TestPasses.cpp
) )
add_circt_library(CIRCTScheduling set(SCHEDULING_SOURCES
ASAPScheduler.cpp ASAPScheduler.cpp
Problems.cpp Problems.cpp
SimplexSchedulers.cpp SimplexSchedulers.cpp
)
LINK_LIBS PUBLIC set(SCHEDULING_LIBS
MLIRIR MLIRIR
MLIRStandard MLIRStandard
MLIRSupport MLIRSupport
) )
if(ortools_FOUND)
option(SCHEDULING_OR_TOOLS "Enable schedulers relying on an external solver from OR-Tools" ON)
endif()
if(SCHEDULING_OR_TOOLS)
list(APPEND SCHEDULING_SOURCES LPSchedulers.cpp)
list(APPEND SCHEDULING_LIBS ortools::ortools)
endif()
add_circt_library(CIRCTScheduling
${SCHEDULING_SOURCES}
LINK_LIBS PUBLIC
${SCHEDULING_LIBS}
)
add_circt_library(CIRCTSchedulingTestPasses add_circt_library(CIRCTSchedulingTestPasses
TestPasses.cpp TestPasses.cpp
@ -23,3 +41,7 @@ add_circt_library(CIRCTSchedulingTestPasses
CIRCTScheduling CIRCTScheduling
MLIRPass MLIRPass
) )
if(SCHEDULING_OR_TOOLS)
target_compile_definitions(obj.CIRCTSchedulingTestPasses PRIVATE SCHEDULING_OR_TOOLS)
endif()

View File

@ -0,0 +1,77 @@
//===- LPSchedulers.cpp - Schedulers using external LP solvers ------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Implementation of linear programming-based schedulers using external solvers
// via OR-Tools.
//
//===----------------------------------------------------------------------===//
#include "circt/Scheduling/Algorithms.h"
#include "mlir/IR/Operation.h"
#include "ortools/linear_solver/linear_solver.h"
using namespace circt;
using namespace circt::scheduling;
using namespace operations_research;
LogicalResult scheduling::scheduleLP(Problem &prob, Operation *lastOp) {
Operation *containingOp = prob.getContainingOp();
MPSolver::OptimizationProblemType problemType;
if (!MPSolver::ParseSolverType("GLOP_LINEAR_PROGRAMMING", &problemType) ||
!MPSolver::SupportsProblemType(problemType))
return containingOp->emitError("Solver is unvailable");
MPSolver solver("Problem", problemType);
double infinity = solver.infinity();
// Create start time variables.
DenseMap<Operation *, MPVariable *> vars;
unsigned i = 0;
for (auto *op : prob.getOperations()) {
vars[op] = solver.MakeNumVar(0, infinity, (Twine("t_") + Twine(i)).str());
++i;
}
// The objective is to minimize the start time of the last operation.
MPObjective *objective = solver.MutableObjective();
objective->SetCoefficient(vars[lastOp], 1);
objective->SetMinimization();
// Construct a linear constraint for each dependence.
for (auto *op : prob.getOperations())
for (auto dep : prob.getDependences(op)) {
Operation *src = dep.getSource();
Operation *dst = dep.getDestination();
if (src == dst)
return containingOp->emitError() << "dependence cycle detected";
// t_src + t.linkedOperatorType.latency <= t_dst
// <=> 1 * t_src + -1 * t_dst <= -latency
unsigned latency = *prob.getLatency(*prob.getLinkedOperatorType(src));
MPConstraint *constraint =
solver.MakeRowConstraint(-infinity, -((double)latency));
constraint->SetCoefficient(vars[src], 1);
constraint->SetCoefficient(vars[dst], -1);
}
// Invoke solver. The LP is infeasible if the scheduling problem contained
// dependence cycles. Otherwise, we expect the result to be optimal.
MPSolver::ResultStatus result = solver.Solve();
if (result == MPSolver::INFEASIBLE)
return containingOp->emitError() << "dependence cycle detected";
assert(result == MPSolver::OPTIMAL);
// Retrieve start times.
for (auto *op : prob.getOperations())
prob.setStartTime(op, std::round(vars[op]->solution_value()));
return success();
}

View File

@ -497,6 +497,55 @@ void TestSimplexSchedulerPass::runOnFunction() {
llvm_unreachable("Unsupported scheduling problem"); llvm_unreachable("Unsupported scheduling problem");
} }
//===----------------------------------------------------------------------===//
// LPScheduler
//===----------------------------------------------------------------------===//
#ifdef SCHEDULING_OR_TOOLS
namespace {
struct TestLPSchedulerPass
: public PassWrapper<TestLPSchedulerPass, FunctionPass> {
TestLPSchedulerPass() = default;
TestLPSchedulerPass(const TestLPSchedulerPass &) {}
Option<std::string> problemToTest{*this, "with", llvm::cl::init("Problem")};
void runOnFunction() override;
StringRef getArgument() const override { return "test-lp-scheduler"; }
StringRef getDescription() const override {
return "Emit an LP scheduler's solution as attributes";
}
};
} // anonymous namespace
void TestLPSchedulerPass::runOnFunction() {
auto func = getFunction();
Operation *lastOp = func.getBlocks().front().getTerminator();
OpBuilder builder(func.getContext());
if (problemToTest == "Problem") {
auto prob = Problem::get(func);
constructProblem(prob, func);
assert(succeeded(prob.check()));
if (failed(scheduleLP(prob, lastOp))) {
func->emitError("scheduling failed");
return signalPassFailure();
}
if (failed(prob.verify())) {
func->emitError("schedule verification failed");
return signalPassFailure();
}
emitSchedule(prob, "lpStartTime", builder);
return;
}
llvm_unreachable("Unsupported scheduling problem");
}
#endif // SCHEDULING_OR_TOOLS
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
// Pass registration // Pass registration
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
@ -525,6 +574,11 @@ void registerSchedulingTestPasses() {
mlir::registerPass([]() -> std::unique_ptr<::mlir::Pass> { mlir::registerPass([]() -> std::unique_ptr<::mlir::Pass> {
return std::make_unique<TestSimplexSchedulerPass>(); return std::make_unique<TestSimplexSchedulerPass>();
}); });
#ifdef SCHEDULING_OR_TOOLS
mlir::registerPass([]() -> std::unique_ptr<::mlir::Pass> {
return std::make_unique<TestLPSchedulerPass>();
});
#endif
} }
} // namespace test } // namespace test
} // namespace circt } // namespace circt

View File

@ -0,0 +1,50 @@
// REQUIRES: or-tools
// RUN: circt-opt %s -test-lp-scheduler=with=Problem -allow-unregistered-dialect | FileCheck %s -check-prefix=LP
// LP-LABEL: unit_latencies
func @unit_latencies(%a1 : i32, %a2 : i32, %a3 : i32, %a4 : i32) -> i32 {
%0 = arith.addi %a1, %a2 : i32
%1 = arith.addi %0, %a3 : i32
%2:3 = "more.results"(%0, %1) : (i32, i32) -> (i32, i32, i32)
%3 = arith.addi %a4, %2#1 : i32
%4 = arith.addi %2#0, %2#2 : i32
%5 = arith.addi %3, %3 : i32
%6 = "more.operands"(%3, %4, %5) : (i32, i32, i32) -> i32
// LP: return
// LP-SAME: lpStartTime = 6
return %6 : i32
}
// LP-LABEL: arbitrary_latencies
func @arbitrary_latencies(%v : complex<f32>) -> f32 attributes {
operatortypes = [
{ name = "extr", latency = 0 },
{ name = "add", latency = 3 },
{ name = "mult", latency = 6 },
{ name = "sqrt", latency = 10 }
] } {
%0 = "complex.re"(%v) { opr = "extr" } : (complex<f32>) -> f32
%1 = "complex.im"(%v) { opr = "extr" } : (complex<f32>) -> f32
%2 = arith.mulf %0, %0 { opr = "mult" } : f32
%3 = arith.mulf %1, %1 { opr = "mult" } : f32
%4 = arith.addf %2, %3 { opr = "add" } : f32
%5 = "math.sqrt"(%4) { opr = "sqrt" } : (f32) -> f32
// LP: return
// LP-SAME: lpStartTime = 19
return %5 : f32
}
// LP-LABEL: auxiliary_dependences
func @auxiliary_dependences() attributes { auxdeps = [
[0,1], [0,2], [2,3], [3,4], [3,6], [4,5], [5,6]
] } {
%0 = arith.constant 0 : i32
%1 = arith.constant 1 : i32
%2 = arith.constant 2 : i32
%3 = arith.constant 3 : i32
%4 = arith.constant 4 : i32
%5 = arith.constant 5 : i32
// LP: return
// LP-SAME: lpStartTime = 5
return { problemStartTime = 6 }
}

View File

@ -70,6 +70,10 @@ if config.verilator_path != "":
if config.esi_capnp != "": if config.esi_capnp != "":
config.available_features.add('capnp') config.available_features.add('capnp')
# Enable tests for schedulers relying on an external solver from OR-Tools.
if config.scheduling_or_tools != "":
config.available_features.add('or-tools')
# Add llhd-sim if it is built. # Add llhd-sim if it is built.
if config.llhd_sim_enabled: if config.llhd_sim_enabled:
config.available_features.add('llhd-sim') config.available_features.add('llhd-sim')

View File

@ -37,6 +37,7 @@ config.circt_tools_dir = "@CIRCT_TOOLS_DIR@"
config.circt_shlib_dir = "@LLVM_LIBRARY_OUTPUT_INTDIR@" config.circt_shlib_dir = "@LLVM_LIBRARY_OUTPUT_INTDIR@"
config.verilator_path = "@VERILATOR_PATH@" config.verilator_path = "@VERILATOR_PATH@"
config.esi_capnp = "@ESI_CAPNP@" config.esi_capnp = "@ESI_CAPNP@"
config.scheduling_or_tools = "@SCHEDULING_OR_TOOLS@"
config.llhd_sim_enabled = @CIRCT_LLHD_SIM_ENABLED@ config.llhd_sim_enabled = @CIRCT_LLHD_SIM_ENABLED@
# Support substitution of the tools_dir with user parameters. This is # Support substitution of the tools_dir with user parameters. This is