Appearance
MPI
These recipes use mpih_object from fundal_mpih_object (build FUNDAL with MPI=1 or an mpi FoBiS mode) and run on 2 ranks. They print from rank 0 only.
One device per rank
Initialize MPI and give each rank of a node its own device.
fortran
call mpih%initialize(do_mpi_init=.true., do_device_init=.true.)fortran
call MPI_COMM_RANK(local_comm, local_rank, ierr) ! local_comm: the ranks of this node (set by do_device_init)
mine = [mpih%myrank, local_rank, mydev] ! dev_init chose mydev = mod(local_rank, devs_number)
allocate(table(3,0:mpih%procs_number-1))
call MPI_GATHER(mine, 3, MPI_INTEGER, table, 3, MPI_INTEGER, 0, MPI_COMM_WORLD, ierr)
if (mpih%myrank == 0) then
do p=0, mpih%procs_number - 1
print '(A,I0,A,I0,A,I0)', 'rank ', table(1,p), ': local rank ', table(2,p), ', device ', table(3,p)
enddo
endiftext
$ mpirun --oversubscribe -np 2 mpi devices
rank 0: local rank 0, device 0
rank 1: local rank 1, device 0The documentation runs on the host, which is one device: both ranks get device 0. On a node with two GPUs the ranks get devices 0 and 1, mod(local_rank, devs_number).
WARNING
local_comm is set only by initialize(do_device_init=.true.). To forbid the host fallback on every rank, pass require_device=.true. to initialize: a rank without a device then stops.
A global sum of device results
Reduce on the device of each rank, then across the ranks with MPI.
fortran
local_sum = 0._R8P
!$acc parallel loop reduction(+:local_sum) DEVICEVAR(a_dev)
!$omp OMPLOOP reduction(+:local_sum) DEVICEPTR(a_dev)
do i=1, n
a_dev(i) = real(offset + i, R8P) ! the global numbers 1, 2, ..., n * procs
local_sum = local_sum + a_dev(i)
enddo
call MPI_ALLREDUCE(local_sum, global_sum, 1, MPI_DOUBLE_PRECISION, MPI_SUM, MPI_COMM_WORLD, ierr)text
$ mpirun --oversubscribe -np 2 mpi allreduce
global sum: 20100.0
equal to m (m+1) / 2, m = n procs: TExchange a halo through host buffers
Copy the boundary cells from the device, exchange them, copy the received values into the ghost cells (from tutorial chapter 6):
fortran
call dev_memcpy_from_device(dst=edge(1:1), src=t_dev(first:first)) ! device -> host buffer
call dev_memcpy_from_device(dst=edge(2:2), src=t_dev(last:last))
call MPI_SENDRECV(edge(2), 1, MPI_DOUBLE_PRECISION, right, 0, ghost(1), 1, MPI_DOUBLE_PRECISION, left, 0, &
MPI_COMM_WORLD, MPI_STATUS_IGNORE, ierr)
call MPI_SENDRECV(edge(1), 1, MPI_DOUBLE_PRECISION, left, 1, ghost(2), 1, MPI_DOUBLE_PRECISION, right, 1, &
MPI_COMM_WORLD, MPI_STATUS_IGNORE, ierr)
if (left /= MPI_PROC_NULL) call dev_memcpy_to_device(dst=t_dev(first-1:first-1), src=ghost(1:1)) ! host -> device
if (right /= MPI_PROC_NULL) call dev_memcpy_to_device(dst=t_dev(last+1:last+1), src=ghost(2:2))WARNING
MPI_SENDRECV (or non-blocking calls) avoids the deadlock of two ranks that both call a blocking MPI_SEND first. Passing device pointers directly to MPI requires a GPU-aware MPI library.