Actual source code: ex67f.F90

  1: !
  2: ! Description: Tests VecCUDAGetArray() and friends along with OpenMP target offload.
  3: ! It is the Fortran version of ex67.c.
  4: !
  5: #include <petsc/finclude/petscvec.h>
  6: program main
  7:   use petscvec
  8:   implicit none

 10:   PetscInt, parameter :: n = 16
 11:   PetscScalar, parameter :: one = 1.0, two = 2.0, three = 3.0, hundred = 100.0
 12:   PetscScalar, pointer, dimension(:) :: a
 13:   PetscInt :: i
 14:   PetscErrorCode :: ierr
 15:   Vec :: x
 16:   PetscDeviceContext :: dctx

 18:   PetscCallA(PetscInitialize(ierr))
 19:   PetscCallA(PetscDeviceContextGetCurrentContext(dctx, ierr))

 21:   PetscCallA(VecCreate(PETSC_COMM_WORLD, x, ierr))
 22:   PetscCallA(VecSetSizes(x, PETSC_DECIDE, n, ierr))
 23:   PetscCallA(VecSetType(x, 'cuda', ierr))

 25: !  A PETSc write queued on the current-context stream; the first OpenMP kernel overwrites the
 26: !  same buffer, so the two race (write-after-write) unless PETSc's stream is synchronized first.

 28:   PetscCallA(VecSet(x, hundred, ierr))

 30: !  This test interleaves PETSc Vec operations with OpenMP target offload to exercise their
 31: !  stream interaction. PETSc queues work on the current device context's stream, and the
 32: !  VecCUDAGetArray*() calls return without synchronizing; the omp target regions run on a
 33: !  separate, runtime-managed stream with no implicit ordering relative to PETSc's. So whenever
 34: !  an OpenMP kernel touches a buffer a preceding PETSc operation is still writing on PETSc's
 35: !  stream (e.g. VecSet() or VecScale() here), the current device context must be synchronized first,
 36: !  or the two streams race. The omp target regions have no nowait, so they block the host on
 37: !  return, which orders the subsequent PETSc calls after the kernel.

 39: !  Write access: overwrite with x(i) = i.

 41:   PetscCallA(VecCUDAGetArrayWrite(x, a, ierr))
 42:   PetscCallA(PetscDeviceContextSynchronize(dctx, ierr))
 43:   !$omp target teams distribute parallel do is_device_ptr(a)
 44:   do i = 1, n
 45:     a(i) = i
 46:   end do
 47:   !$omp end target teams distribute parallel do
 48:   PetscCallA(VecCUDARestoreArrayWrite(x, a, ierr))

 50: !  A PETSc operation the next OpenMP kernel depends on: double every entry, x(i) = 2*i.

 52:   PetscCallA(VecScale(x, two, ierr))

 54: !  Read-write access: add to what VecScale() produced, so sync PETSc's stream first, x(i) = 2*i + 3.

 56:   PetscCallA(VecCUDAGetArray(x, a, ierr))
 57:   PetscCallA(PetscDeviceContextSynchronize(dctx, ierr))
 58:   !$omp target teams distribute parallel do is_device_ptr(a)
 59:   do i = 1, n
 60:     a(i) = a(i) + three
 61:   end do
 62:   !$omp end target teams distribute parallel do
 63:   PetscCallA(VecCUDARestoreArray(x, a, ierr))

 65: !  A PETSc operation consuming the OpenMP result; the synchronous target above ordered it, x(i) = 2*i + 4.

 67:   PetscCallA(VecShift(x, one, ierr))

 69: !  Read-only access round-trip.

 71:   PetscCallA(VecCUDAGetArrayRead(x, a, ierr))
 72:   PetscCallA(VecCUDARestoreArrayRead(x, a, ierr))

 74:   PetscCallA(PetscObjectSetName(x, 'x', ierr))
 75:   PetscCallA(VecView(x, PETSC_VIEWER_STDOUT_WORLD, ierr))

 77:   PetscCallA(VecDestroy(x, ierr))
 78:   PetscCallA(PetscFinalize(ierr))
 79: end

 81: !
 82: !/*TEST
 83: !
 84: ! test:
 85: !    requires: cuda defined(PETSC_HAVE_OPENMP_TARGET_OFFLOAD_FC)
 86: !    output_file: output/ex67_1.out
 87: !
 88: !TEST*/