diff --git a/docs/src/guides/use_multithreading.md b/docs/src/guides/use_multithreading.md index 61eccf775..cc5d61026 100644 --- a/docs/src/guides/use_multithreading.md +++ b/docs/src/guides/use_multithreading.md @@ -78,3 +78,113 @@ Even if you have a large number of nodes, using more threads than you have physical cores is likely to lead to bad performance. For example, on a quad-core laptop, using `--threads 52`, is likely to lead to bad performance, even if you have a graph with 52 nodes. + +## Threading with Gurobi + +Gurobi's `Gurobi.Env` objects are not thread-safe. This means that you cannot +use the `optimizer = () -> Gurobi.Optimizer(env)` syntax if you are also using +multithreading. (You can use `optimizer = Gurobi.Optimizer` because this creates +a new environment for each node, but some license types have a limit on the +number of concurrent licenses that can be created.) + +As a work-around, use the following function to partition the nodes into `N` +disjoint sets and use a shared lock across the nodes within a group so that at +most one subproblem within each group can be solved at any one time. +```julia +function shard_gurobi_licenses!(model::SDDP.PolicyGraph, groups...) + for group in groups + env, lock = Gurobi.Env(), ReentrantLock() + for node in group + set_optimizer(model[node].subproblem, () -> Gurobi.Optimizer(env)) + set_silent(model[node].subproblem) + model[node].lock = lock + end + end + return +end +``` +For example, if you have two licenses you may want to group the nodes into "odd" +and "even": +```julia +julia> shard_gurobi_licenses!(model, 1:2:52, 2:2:52) +``` +or, if you have three licenses, you may want to split the nodes into "start", +"middle", and "end" sets: +```julia +julia> shard_gurobi_licenses!(model, 1:17, 18:35, 36:52) +``` + +Here's a full example: +```julia +julia> using SDDP, Gurobi + +julia> function shard_gurobi_licenses!(model::SDDP.PolicyGraph, groups...) + for group in groups + env, lock = Gurobi.Env(), ReentrantLock() + for node in group + set_optimizer(model[node].subproblem, () -> Gurobi.Optimizer(env)) + set_silent(model[node].subproblem) + model[node].lock = lock + end + end + return + end +shard_gurobi_licenses! (generic function with 1 method) + +julia> model = SDDP.LinearPolicyGraph(; + stages = 52, + lower_bound = 0.0, + ) do sp, t + @variable(sp, 0 <= x <= 100, SDDP.State, initial_value = 0) + @variable(sp, 0 <= u_production <= 200) + @variable(sp, u_overtime >= 0) + @constraint(sp, demand, x.in - x.out + u_production + u_overtime == 0) + Ω = [100.0, 300.0] + SDDP.parameterize(ω -> set_normalized_rhs(demand, ω), sp, Ω) + @stageobjective(sp, 100 * u_production + 300 * u_overtime + 50 * x.out) + end; + +julia> shard_gurobi_licenses!(model, 1:2:52, 2:2:52) + +julia> SDDP.train(model; parallel_scheme = SDDP.Threaded()) +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-26 +------------------------------------------------------------------- +problem + nodes : 52 + state variables : 1 + scenarios : 4.50360e+15 + existing cuts : false +options + solver : Threaded() + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [5, 5] + AffExpr in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [4, 4] + VariableRef in MOI.LessThan{Float64} : [2, 3] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 3e+02] + bounds range [1e+02, 2e+02] + rhs range [1e+02, 3e+02] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 1.440000e+06 1.383984e+06 2.383709e-02 309 3 + 22 1.607500e+06 1.432500e+06 1.177620e+00 13832 2 + 62 1.405000e+06 1.432500e+06 2.469422e+00 30472 3 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 2.469422e+00 +total solves : 30472 +best bound : 1.432500e+06 +simulation ci : 1.430250e+06 ± 3.113414e+04 +numeric issues : 0 +------------------------------------------------------------------- +``` + +!!! tip + If you need help with Gurobi licenses and multithreading, please open a + GitHub issue.