Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions docs/src/guides/use_multithreading.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Loading