Actual source code: ex67.c
1: static char help[] = "Tests VecCUDAGetArray() and friends along with OpenMP target offload\n\n";
3: #include <petscvec.h>
4: #include <petscdevice.h>
6: int main(int argc, char **argv)
7: {
8: const PetscInt n = 16;
9: PetscScalar *a;
10: const PetscScalar *ar;
11: Vec x;
12: PetscDeviceContext dctx;
14: PetscFunctionBeginUser;
15: PetscCall(PetscInitialize(&argc, &argv, NULL, help));
16: PetscCall(PetscDeviceContextGetCurrentContext(&dctx));
18: PetscCall(VecCreate(PETSC_COMM_WORLD, &x));
19: PetscCall(VecSetSizes(x, PETSC_DECIDE, n));
20: PetscCall(VecSetType(x, VECCUDA));
22: // A PETSc write queued on the current-context stream; the first OpenMP kernel overwrites the
23: // same buffer, so the two race (write-after-write) unless PETSc's stream is synchronized first.
24: PetscCall(VecSet(x, 100.0));
26: /*
27: This test interleaves PETSc Vec operations with OpenMP target offload to exercise their
28: stream interaction. PETSc queues work on the current device context's stream, and the
29: VecCUDAGetArray*() calls return without synchronizing; the omp target regions run on a
30: separate, runtime-managed stream with no implicit ordering relative to PETSc's. So whenever
31: an OpenMP kernel touches a buffer a preceding PETSc operation is still writing on PETSc's
32: stream (e.g. VecSet() or VecScale() here), the current device context must be synchronized
33: first, or the two streams race. The omp target regions have no nowait, so they block the
34: host on return, which orders the subsequent PETSc calls after the kernel.
35: */
37: // Write access: overwrite with x[i] = i + 1.
38: PetscCall(VecCUDAGetArrayWrite(x, &a));
39: PetscCall(PetscDeviceContextSynchronize(dctx));
40: #pragma omp target teams distribute parallel for is_device_ptr(a)
41: for (PetscInt i = 0; i < n; i++) a[i] = i + 1.0;
42: PetscCall(VecCUDARestoreArrayWrite(x, &a));
44: // A PETSc operation the next OpenMP kernel depends on: double every entry, x[i] = 2(i + 1).
45: PetscCall(VecScale(x, 2.0));
47: // Read-write access: add to what VecScale() produced, so sync PETSc's stream first, x[i] = 2(i + 1) + 3.
48: PetscCall(VecCUDAGetArray(x, &a));
49: PetscCall(PetscDeviceContextSynchronize(dctx));
50: #pragma omp target teams distribute parallel for is_device_ptr(a)
51: for (PetscInt i = 0; i < n; i++) a[i] = a[i] + 3.0;
52: PetscCall(VecCUDARestoreArray(x, &a));
54: // A PETSc operation consuming the OpenMP result; the synchronous target above ordered it, x[i] = 2(i + 1) + 4.
55: PetscCall(VecShift(x, 1.0));
57: // Read-only access round-trip.
58: PetscCall(VecCUDAGetArrayRead(x, &ar));
59: PetscCall(VecCUDARestoreArrayRead(x, &ar));
61: PetscCall(PetscObjectSetName((PetscObject)x, "x"));
62: PetscCall(VecView(x, PETSC_VIEWER_STDOUT_WORLD));
64: PetscCall(VecDestroy(&x));
65: PetscCall(PetscFinalize());
66: return 0;
67: }
69: /*TEST
71: test:
72: requires: cuda defined(PETSC_HAVE_OPENMP_TARGET_OFFLOAD_CC)
73: output_file: output/ex67_1.out
75: TEST*/