JuliaParallel / JuliaParallel/MPI.jl

Distributed.jl with MPI.jl: execution time increases with each iteration, after MPI.jl > 0.19.2

Open
#930 7 comments 0 reactions 0 assignees View on GitHub
Dominant language
Julia
Stars
439
Forks
127
Avg merge
19h 6m
Merged PRs (30d)
17

Description

Hi, first of of all, let me acknowledge what a fantastic package this is. I have successfully used MPI.jl for embarassingly parallel groups of non-embarassingly parallel jobs, for up to 8,320 cpus and 5 hours. I wrote the non-embarassingly parallel code with Distributed and DistributedArrays, once it worked to my satisfaction, I scaled it all up using MPI.jl to 100s then 1000s of cores.

This has been working fine over the past few years, but I noticed that of late, I was getting odd behaviour *without* code changes in my package, which leads to every 1000 iterations of my code, taking increasingly large amounts of time.

Each single iteration can be parallelised, after which I need to do some stuff *before* the next iteration so my main code block looks like this
```julia
for isample = 1:nsamples
@sync for (ipid, pid) in enumerate(pids)
@async r[ipid] = remotecall_fetch(largecalc, pid, results, data, isample)
end
# do stuff
end
```
Where the `results` and `data` are a `DArray` the `localpart`s of which lie on `pid`. With an MWE, I have nailed it (I think) to changes with `MPI.jl` after the breaking 0.20.0 release. I am not fussed about the environment variables changing at all, I think `MPIPreferences` makes it all much simpler after 0.20.0 on `MPI.jl.` Here is the MWE using only `Distributed.jl`, contrasted with using `Distributed.jl` with `MPI.jl` and `MPIClusterManagers.jl` (alas, my cluster only allows MPI transport), which can be run on a desktop (Mac M2 2023, 24 GB RAM, 8 cores, julia-1.12.4, homebrew mpirun (Open MPI) 5.0.9):

Module which initialises DArrays in a function, then defines a function to do some work with them, called within another function that has parallelisation per iteration, that writes the result to a file:
```julia
module TestMPISlowdown

using Distributed, DistributedArrays

function init_darrays(pids, nvars)
results_ = Array{Future, 1}(undef, length(pids))
data_ = Array{Future, 1}(undef, length(pids))
@sync for (idx, pid) in enumerate(pids)
results_[idx] = @spawnat pid [[0.]]
data_[idx] = @spawnat pid [rand(nvars)]
end
DArray(results_), DArray(data_)
end

function largecalc(results, data, n)
s = (data)'data
results[1] = results[1]*(n-1) + s
results[1] /= n
end

function largecalc(results::DArray, data::DArray, n::Integer)
largecalc(localpart(results)[1], localpart(data)[1], n)
end

function doiters(nsamples, pids, results::DArray, data::DArray, fname::String, str::String="")
r = zeros(length(pids))
tlong = time()
for isample = 1:nsamples
@sync for (ipid, pid) in enumerate(pids)
@async r[ipid] = remotecall_fetch(largecalc, pid, results, data, isample)
end
if mod(isample-1, 1000) == 0
dt = time() - tlong
tlong = time()
if isample == 1
fp = open(fname, "w")
write(fp, str*"\n")
else
fp = open(fname, "a")
end
write(fp, "$isample\t$dt\n")
flush(fp); close(fp)
end
end
end

end # Module
```
Here is the script `useTestMPISlowdown_MPI.jl` to call this module using `mpiexec`
```julia
using MPIClusterManagers, Distributed
import MPI
MPI.Init()
rank = MPI.Comm_rank(MPI.COMM_WORLD)
sz = MPI.Comm_size(MPI.COMM_WORLD)
if rank == 0
@info "size is $sz"
end
manager = MPIClusterManagers.start_main_loop(MPI_TRANSPORT_ALL)
@info "there are $(nworkers()) workers"
@everywhere using Distributed
@everywhere push!(LOAD_PATH,pwd())
@everywhere using TestMPISlowdown
## some settings
nsamples = 20001 # serial iterations of parallel work
nvars = 2^25 # dimension of random variable x to play with
vstring = "$(pkgversion(MPI))" # MPI.jl version
fname = "testMPI_$vstring.txt" # file to write output in
## do the work and write timings
pids = workers()
r, d = TestMPISlowdown.init_darrays(pids, nvars); # return DArrays
TestMPISlowdown.doiters(nsamples, pids, r, d, fname, "MPI.jl v"*vstring)
## remove workers
MPIClusterManagers.stop_main_loop(manager)
rmprocs(workers())
exit()
```
In `zsh` if I now do
`mpiexec -np 6 julia useTestMPISlowdown_MPI.jl`
Then with MPI.jl vs 0.20.32 I get this increase of time per 1000 iterations, consistent with what I see in my code on the HPC cluster.
```
MPI.jl v0.20.23
1 0.15433502197265625
1001 22.368630170822144
2001 22.45356798171997
3001 22.8455069065094
4001 23.444272994995117
5001 23.500241994857788
6001 23.8661470413208
7001 24.7068829536438
8001 25.006561040878296
9001 26.50983691215515
10001 26.9981791973114
```
With plain Distributed.jl we can still run the `##some settings` and `## do the work and write timings` part of the calling code above after doing
```julia
cd(@__DIR__)
using Distributed
addprocs(5)
@everywhere push!(LOAD_PATH,pwd())
@everywhere using TestMPISlowdown
```
to get (for 20_001 samples now, just to check), no increase in time per 1000 iterations:
```
Plain Distributed.jl
1 0.07457900047302246
1001 20.80730700492859
2001 20.711559057235718
3001 20.714218139648438
.
.
10001 21.25517702102661
.
.
17001 20.81500005722046
18001 20.852991104125977
19001 20.885419845581055
20001 20.884173154830933
```
This closely matches `MPI.jl` after switching back to `v0.19.2` and doing `mpiexec -np 6 julia useTestMPISlowdown_MPI.jl`
```
MPI.jl v0.19.2
1 0.14057302474975586
1001 21.441424131393433
2001 21.38768696784973
3001 21.708193063735962
4001 21.66358494758606
5001 21.376691102981567
6001 21.391920804977417
7001 21.380391120910645
8001 21.381767988204956
9001 21.376633882522583
10001 21.417943000793457
```
I can see similar behaviour on the cluster as well, this time with intel-mpi/2021.10.0 . Time blows up with `MPI.jl v0.20.23`, but not `0.19.2`. On my Mac, switching over to the `jll` didn't help either.

Any ideas as to what might be going on? Many thanks.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.