Error with HiGHS independent parallel solves

A parallel example using HiGHS was recently published here : Parallelism · JuMP

However, when I change the model used to something that requires several iterations instead of just presolve it prints a bunch of Iteration total error ${} + ${} + ${} + ${} = ${} != ${} messages.

MWE : I’ve found this instance to be fairly reliable on my system, but potentially the seed may influence your results. Typically the larger N is, the more messages are printed.

Julia Version 1.8.5
Commit 17cfb8e65ea (2023-01-08 06:45 UTC)
Platform Info:
OS: macOS (x86_64-apple-darwin21.4.0)

HiGHS v1.5.0
JuMP v1.9.0

# Multi-threading the right way from https://jump.dev/JuMP.jl/stable/tutorials/algorithms/parallelism/#With-multi-threading
using JuMP
using HiGHS

solutions = Pair{Int,Float64}[]
my_lock = Threads.ReentrantLock()

# Copy of multi-objective knapsack problem from https://jump.dev/JuMP.jl/stable/tutorials/linear/multi_objective_knapsack/
# With changed input data and simplified objective to remove dependence on MultiObjectiveAlgorithms
function generate_model()
    profit = [79, 91, 79, 66, 72, 83, 65, 93, 59, 94, 82, 55, 74, 68, 57, 62, 62]
    desire = [99, 62, 95, 77, 55, 66, 77, 88, 59, 61, 86, 68, 50, 82, 51, 89, 56]
    weight = [85, 53, 72, 56, 75, 51, 74, 62, 61, 54, 93, 56, 52, 53, 66, 80, 64]
    capacity = 900
    N = length(profit)

    model = Model(HiGHS.Optimizer)
    @variable(model, x[1:N], Bin)
    @constraint(model, sum(weight[i] * x[i] for i in 1:N) <= capacity)
    @expression(model, profit_expr, sum(profit[i] * x[i] for i in 1:N))
    @expression(model, desire_expr, sum(desire[i] * x[i] for i in 1:N))
    @objective(model, Max, profit_expr + desire_expr)

    set_silent(model)
    optimize!(model)
    solution_summary(model)

    return argmax(i -> objective_value(model; result = i), 1:result_count(model))
end

Threads.@threads for i in 1:Threads.nthreads()
    @show Threads.threadid()

    res = generate_model()

    Threads.lock(my_lock) do
        push!(solutions, i => res)
    end
end

@show solutions

The only mention I can find of HiGHS being thread-safe is this docstring : Docstrings · HiGHS_jll.jl

I guess the question is : is HiGHS thread-safe? Is this the intended behaviour? Is there are problem with the JuMP doc examples or my MWE?

1 Like

Hi @Naunet, welcome to the forum!

This is an interesting find. I’ll admit, I didn’t test larger problems when I wrote the tutorial.

I can reproduce, and it looks like some part of HiGHS in fact isn’t thread-safe when it internally uses multiple threads. The quick fix is to limit HiGHS to one thread:

set_attribute(model, "threads", 1)

With that, I get:

julia> using JuMP

julia> import HiGHS

julia> solutions = Pair{Int,Float64}[]
Pair{Int64, Float64}[]

julia> my_lock = Threads.ReentrantLock()
ReentrantLock(nothing, Base.GenericCondition{Base.Threads.SpinLock}(Base.InvasiveLinkedList{Task}(nothing, nothing), Base.Threads.SpinLock(0)), 0)

julia> function generate_model()
           profit = [79, 91, 79, 66, 72, 83, 65, 93, 59, 94, 82, 55, 74, 68, 57, 62, 62]
           desire = [99, 62, 95, 77, 55, 66, 77, 88, 59, 61, 86, 68, 50, 82, 51, 89, 56]
           weight = [85, 53, 72, 56, 75, 51, 74, 62, 61, 54, 93, 56, 52, 53, 66, 80, 64]
           capacity = 900
           N = length(profit)
           model = Model(HiGHS.Optimizer)
           set_attribute(model, "threads", 1)
           @variable(model, x[1:N], Bin)
           @constraint(model, sum(weight[i] * x[i] for i in 1:N) <= capacity)
           @expression(model, profit_expr, sum(profit[i] * x[i] for i in 1:N))
           @expression(model, desire_expr, sum(desire[i] * x[i] for i in 1:N))
           @objective(model, Max, profit_expr + desire_expr)
           set_silent(model)
           optimize!(model)
           solution_summary(model)
           return argmax(i -> objective_value(model; result = i), 1:result_count(model))
       end
generate_model (generic function with 1 method)

julia> Threads.@threads for i in 1:Threads.nthreads()
           res = generate_model()
           Threads.lock(my_lock) do
               push!(solutions, i => res)
           end
       end

julia> @show solutions
solutions = [4 => 1.0, 2 => 1.0, 3 => 1.0, 1 => 1.0]
4-element Vector{Pair{Int64, Float64}}:
 4 => 1.0
 2 => 1.0
 3 => 1.0
 1 => 1.0

I’ll update the tutorial with this fix, and I’ll talk to the HiGHS developers with your finding.