DIRECTIVES-BASED PARALLEL PROGRAMMING Jeff Larkin
1. You should have received an email from Okta, please activate your account.
2. Install SFT client on your laptop for your OS: https://help.okta.com/en/prod/Content/Topics /Adv_Server_Access/docs/client.htm
3. Open terminal and run: sft enroll --team nv- demo
4. Approve in browser
5. Back in your terminal, log in to the hackathon head node: sft ssh -L 9090:localhost:9090 raplab- hackathon
6. Open http://localhost:9090
2 WHAT’S A COMPILER DIRECTIVE?
Compiler Directives are instructions, hints, or other information given to the compiler beyond the base language source code. Examples: • GCC, unroll this loop 4 times (#pragma GCC unroll 4) • Ignore vector dependencies in this loop (#pragma ivdep) These are different from pre-processor macros, since they affect the actual code generation during compilation, can’t always be pre-processed. These are different from attributes because they are not defined by the language.
3 DIRECTIVES FOR PARALLEL PROGRAMMING
OpenMP – Established 1997
OpenMP was founded to create a unified set of compiler directives for shared-memory-parallel computers (SMP) that were becoming commonplace in the late 90s. In 2008 this was expanded to include task-based parallelism. In 2013 this was expanded again to include offloading to co-processors.
OpenACC – Established 2011
OpenACC was founded to create a unified set of compiler directives for “accelerators” that began emerging around 2010 (primarily GPUs) with potentially disjoint memories, non-smp architectures, and offloading.
Both directives support C, C++, and Fortran and require support from the compiler.
4 A BRIEF HISTORY OF OPENMP
OpenMP 2.5 OpenMP 4.5 OpenMP 1.0 Improved Offloading Unified into single OpenMP 3.1 Task Priority Basic shared- spec. memory parallelism, Tasking C/C++ and Fortran Improvements two specs. OpenMP 4.0 OpenMP 5.0 OpenMP 3.0 Target Offloading Loop Construct Teams Metadirective OpenMP 2.0 Tasking Added SIMD Base language updates Atomics Requires directive Incorporation Clarifications and Tasking Improvements Improvements
1996 1997 2000, 2002 2005 2008 2011 2013 2015 2018
5 A BRIEF HISTORY OF OPENACC
OpenACC 2.5 Reference Counting, OpenACC 1.0 Profiling Interface, OpenACC 2.7 Additional Compute on Self, Basic parallelism, Improvements from readonly, Array structured data, and User Feedback Reductions, Lots of async/wait Clarifications, Misc. semantics OpenACC 2.6 User Feedback OpenACC 3.0 Updated Base Serial Construct, OpenACC 2.0 Languages, C++ Attach/Detach Incorporation Lambdas, Zero Unstructured Data (Manual Deep Copy), modifier, Improved Lifetimes, Routines, Misc. User Feedback ORNL asks CAPS, multi-device support Cray, & PGI to unify Atomic, efforts with the help Clarifications & of NVIDIA Improvements
Nov. June Oct. Nov Nov. Nov. 2011 2011 2013 2015 2016 2018 2019
6 3 WAYS TO ACCELERATE APPLICATIONS
Applications
Compiler Programming Libraries Directives Languages
Easy to use Easy to use Most Performance Most Performance Portable code Most Flexibility
7 THE FUTURE OF PARALLEL PROGRAMMING Standard Languages | Directives | Specialized Languages
__global__ void saxpy(int n, float a, float *x, float *y) { #pragma acc data copy(x,y) { int i = blockIdx.x*blockDim.x + threadIdx.x; std::for_each_n(POL, idx(0), n, ... if (i < n) y[i] += a*x[i]; [&](Index_t i){ } y[i] += a*x[i]; std::for_each_n(POL, idx(0), n, }); [&](Index_t i){ int main(void) { ... y[i] += a*x[i]; cudaMemcpy(d_x, x, ...); }); cudaMemcpy(d_y, y, ...); do concurrent (i = 1:n) ... saxpy<<<(N+255)/256,256>>>(...); y(i) = y(i) + a*x(i) enddo } cudaMemcpy(y, d_y, ...);
Drive Base Languages to Better Augment Base Languages with Maximize Performance with Support Parallelism Directives Specialized Languages & Intrinsics
8 DIRECTIVE SYNTAX
9 DIRECTIVE SYNTAX Syntax for using compiler directives in code
C/C++ Fortran #pragma sentinel directive clauses !$sentinel directive clauses
A pragma in C/C++ gives instructions to the compiler on how to compile the code. Compilers that do not understand a particular pragma can freely ignore it.
A directive in Fortran is a specially formatted comment that likewise instructions the compiler in the compilation of the code and can be freely ignored.
The sentinel informs the compiler the directive language that will follow (acc = OpenACC, omp = OpenMP)
Directives are commands and information to the compiler for altering or interpreting the code
Clauses are specifiers or additions to directives, like function parameters.
10 INTRODUCTION TO OPENACC
11 OpenACC Directives
Manage #pragma acc data copyin(a,b) copyout(c) Incremental Data { Movement ... Single source #pragma acc parallel { Initiate #pragma acc loop gang vector Interoperable Parallel for (i = 0; i < n; ++i) { Execution c[i] = a[i] + b[i]; Performance portable ... } CPU, GPU, Manycore Optimize } Loop ... Mappings }
12 OPENACC
Incremental Single Source Low Learning Curve
▪ Rebuild the same code ▪ OpenACC is meant to ▪ Maintain existing on multiple be easy to use, and sequential code architectures easy to learn ▪ Add annotations to ▪ Compiler determines ▪ Programmer remains expose parallelism how to parallelize for in familiar C, C++, or ▪ After verifying the desired machine Fortran correctness, annotate ▪ Sequential code is ▪ No reason to learn low- more of the code maintained level details of the hardware.
13 OPENACC DIRECTIVES a directive-based parallel programming model designed for usability, performance and portability
3 OF TOP 5 HPC 18% OF INCITE AT SUMMIT PLATFORMS SUPPORTED NVIDIA GPU X86 CPU POWER CPU Sunway ARM CPU AMD GPU
OPENACC APPS OPENACC SLACK MEMBERS >200K DOWNLOADS 1724 236 200 1154 150 692 87 107 361 39 150 305 SC15 SC16 SC17 SC18 ISC19 SC19 ISC17 SC17 ISC18 SC18 ISC19 SC19
14 EXAMPLE CODE
15 LAPLACE HEAT TRANSFER Introduction to lab code - visual Very Hot Room Temp
We will observe a simple simulation of heat distributing across a metal plate.
We will apply a consistent heat to the top of the plate.
Then, we will simulate the heat distributing across the plate.
16 EXAMPLE: JACOBI ITERATION
▪ Iteratively converges to correct value (e.g. Temperature), by computing new values at each point from the average of neighboring points. ▪ Common, useful algorithm
▪ Example: Solve Laplace equation in 2D: 훁ퟐ풇(풙, 풚) = ퟎ A(i,j+1)
A(i-1,j) A(i+1,j) A(i,j) 퐴 (푖 − 1, 푗) + 퐴 푖 + 1, 푗 + 퐴 푖, 푗 − 1 + 퐴 푖, 푗 + 1 퐴 푖, 푗 = 푘 푘 푘 푘 A(i,j-1) 푘+1 4
17 JACOBI ITERATION: C CODE
while ( err > tol && iter < iter_max ) { Iterate until converged err=0.0;
Iterate across matrix for( int j = 1; j < n-1; j++) { for(int i = 1; i < m-1; i++) { elements
Anew[j][i] = 0.25 * (A[j][i+1] + A[j][i-1] + Calculate new value from A[j-1][i] + A[j+1][i]); neighbors
err = max(err, abs(Anew[j][i] - A[j][i])); } Compute max error for } convergence
for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { A[j][i] = Anew[j][i]; Swap input/output arrays } }
iter++; } 18 18 PROFILE-DRIVEN DEVELOPMENT
19 OPENACC DEVELOPMENT CYCLE ▪ Analyze your code to determine most likely places needing Analyze parallelization or optimization. ▪ Parallelize your code by starting with the most time consuming parts and check for correctness. ▪ Optimize your code to improve observed speed-up from parallelization.
Optimize Parallelize
20 PROFILING SEQUENTIAL CODE
Profile Your Code Lab Code: Laplace Heat Transfer Obtain detailed information about how the code ran. Total Runtime: 39.43 seconds
This can include information such as: ▪ Total runtime ▪ Runtime of individual routines swap 19.04s calcNext ▪ Hardware counters 21.49s
Identify the portions of code that took the longest to run. We want to focus on these “hotspots” when parallelizing.
21 OPENACC PARALLEL LOOP DIRECTIVE
22 OPENACC PARALLEL DIRECTIVE Expressing parallelism
#pragma acc parallel { gang gang When encountering the parallel directive, the compiler will generate gang gang 1 or more parallel gangs, which execute redundantly.
} gang gang
23 OPENACC PARALLEL DIRECTIVE
Expressing parallelism loop #pragma acc parallel loop { gang gang
loop loop for(int i = 0; i < N; i++) loop { gang gang // Do Something
}
loop loop This loop will be gang gang } executed redundantly on each gang 24 OPENACC PARALLEL DIRECTIVE Expressing parallelism
#pragma acc parallel { gang gang
for(int i = 0; i < N; i++) { gang gang // Do Something }
This means that each gang gang } gang will execute the entire loop 25 OPENACC PARALLEL DIRECTIVE Parallelizing a single loop C/C++ #pragma acc parallel ▪ Use a parallel directive to mark a region of { #pragma acc loop code where you want parallel execution to occur for(int i = 0; j < N; i++) a[i] = 0; ▪ This parallel region is marked by curly braces in } C/C++ or a start and end directive in Fortran
Fortran ▪ The loop directive is used to instruct the compiler to parallelize the iterations of the next !$acc parallel !$acc loop loop to run across the parallel gangs do i = 1, N a(i) = 0 end do !$acc end parallel
26 OPENACC PARALLEL DIRECTIVE Parallelizing a single loop
C/C++ ▪ This pattern is so common that you can do all of #pragma acc parallel loop this in a single line of code for(int i = 0; j < N; i++) a[i] = 0; ▪ In this example, the parallel loop directive applies to the next loop ▪ This directive both marks the region for parallel Fortran execution and distributes the iterations of the loop. !$acc parallel loop do i = 1, N ▪ When applied to a loop with a data dependency, a(i) = 0 parallel loop may produce incorrect results end do
27 OPENACC PARALLEL DIRECTIVE Expressing parallelism
#pragma acc parallel {
#pragma acc loop for(int i = 0; i < N; i++) { // Do Something } The loop directive informs the compiler } which loops to parallelize.
28 OPENACC PARALLEL LOOP DIRECTIVE Parallelizing many loops
▪ To parallelize multiple loops, each loop should be accompanied by a parallel directive #pragma acc parallel loop ▪ Each parallel loop can have different loop for(int i = 0; i < N; i++) boundaries and loop optimizations a[i] = 0; ▪ Each parallel loop can be parallelized in a #pragma acc parallel loop for(int j = 0; j < M; j++) different way b[j] = 0; ▪ This is the recommended way to parallelize multiple loops. Attempting to parallelize multiple loops within the same parallel region may give performance issues or unexpected results
29 PARALLELIZE WITH OPENACC PARALLEL LOOP
while ( err > tol && iter < iter_max ) { err=0.0;
#pragma acc parallel loop reduction(max:err) Parallelize first loop nest, for( int j = 1; j < n-1; j++) { max reduction required. for(int i = 1; i < m-1; i++) {
Anew[j][i] = 0.25 * (A[j][i+1] + A[j][i-1] + A[j-1][i] + A[j+1][i]);
err = max(err, abs(Anew[j][i] - A[j][i])); } }
#pragma acc parallel loop Parallelize second loop. for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { A[j][i] = Anew[j][i]; } } We didn’t detail how to iter++; parallelize the loops, just which } loops to parallelize. 30 30 REDUCTION CLAUSE
▪ The reduction clause takes many values and for( i = 0; i < size; i++ ) “reduces” them to a single value, such as in a for( j = 0; j < size; j++ ) sum or maximum for( k = 0; k < size; k++ ) c[i][j] += a[i][k] * b[k][j]; ▪ Each thread calculates its part ▪ The compiler will perform a final reduction to for( i = 0; i < size; i++ ) produce a single global result using the for( j = 0; j < size; j++ ) specified operation double tmp = 0.0f; #pragma parallel acc loop \ reduction(+:tmp) for( k = 0; k < size; k++ ) tmp += a[i][k] * b[k][j]; c[i][j] = tmp;
31 REDUCTION CLAUSE OPERATORS
Operator Description Example
+ Addition/Summation reduction(+:sum)
* Multiplication/Product reduction(*:product) max Maximum value reduction(max:maximum) min Minimum value reduction(min:minimum)
& Bitwise and reduction(&:val)
| Bitwise or reduction(|:val)
&& Logical and reduction(&&:val)
|| Logical or reduction(||:val)
32 BUILD AND RUN THE CODE
33 PGI COMPILER BASICS pgcc, pgc++ and pgfortran
▪ The command to compile C code is ‘pgcc’ ▪ The command to compile C++ code is ‘pgc++’ ▪ The command to compile Fortran code is ‘pgfortran’ ▪ The -fast flag instructs the compiler to optimize the code to the best of its abilities
$ pgcc –fast main.c $ pgc++ -fast main.cpp $ pgfortran –fast main.F90
34 PGI COMPILER BASICS -Minfo flag
▪ The -Minfo flag will instruct the compiler to print feedback about the compiled code ▪ -Minfo=accel will give us information about what parts of the code were accelerated via OpenACC ▪ -Minfo=opt will give information about all code optimizations ▪ -Minfo=all will give all code feedback, whether positive or negative
$ pgcc –fast –Minfo=all main.c $ pgc++ -fast -Minfo=all main.cpp $ pgfortran –fast –Minfo=all main.f90
35 PGI COMPILER BASICS -ta flag
▪ The -ta flag enables building OpenACC code for a “Target Accelerator” (TA) ▪ -ta=multicore – Build the code to run across threads on a multicore CPU ▪ -ta=tesla:managed – Build the code for an NVIDIA (Tesla) GPU and manage the data movement for me (more next week)
$ pgcc –fast –Minfo=accel –ta=tesla:managed main.c $ pgc++ -fast -Minfo=accel –ta=tesla:managed main.cpp $ pgfortran –fast –Minfo=accel –ta=tesla:managed main.f90
36 BUILDING THE CODE (MULTICORE)
$ pgcc -fast -ta=multicore -Minfo=accel laplace2d_uvm.c main: 63, Generating Multicore code 64, #pragma acc loop gang 64, Accelerator restriction: size of the GPU copy of Anew,A is unknown Generating reduction(max:error) 66, Loop is parallelizable 74, Generating Multicore code 75, #pragma acc loop gang 75, Accelerator restriction: size of the GPU copy of Anew,A is unknown 77, Loop is parallelizable
37 37 OPENACC SPEED-UP
Speed-up 3.50X
3.05X 3.00X
2.50X
2.00X
Up -
1.50X Speed
1.00X 1.00X
0.50X
0.00X SERIAL MULTICORE
PGI 18.7, NVIDIA Tesla V100, Intel i9-7900X CPU @ 3.30GHz 38 BUILDING THE CODE (GPU)
$ pgcc -fast -ta=tesla:managed -Minfo=accel laplace2d_uvm.c main: 63, Accelerator kernel generated Generating Tesla code 64, #pragma acc loop gang /* blockIdx.x */ Generating reduction(max:error) 66, #pragma acc loop vector(128) /* threadIdx.x */ 63, Generating implicit copyin(A[:]) Generating implicit copyout(Anew[:]) Generating implicit copy(error) 66, Loop is parallelizable 74, Accelerator kernel generated Generating Tesla code 75, #pragma acc loop gang /* blockIdx.x */ 77, #pragma acc loop vector(128) /* threadIdx.x */ 74, Generating implicit copyin(Anew[:]) Generating implicit copyout(A[:]) 77, Loop is parallelizable
39 39 OPENACC SPEED-UP
Speed-up 40.00X 37.14X
35.00X
30.00X
25.00X
Up -
20.00X Speed 15.00X
10.00X
5.00X 3.05X 1.00X 0.00X SERIAL MULTICORE NVIDIA TESLA V100
PGI 18.7, NVIDIA Tesla V100, Intel i9-7900X CPU @ 3.30GHz 40 CPU AND GPU MEMORIES
41 CPU + GPU Physical Diagram CPU GPU
▪ CPU memory is larger, GPU memory has more $ $ $ $ $ $
bandwidth $ $ $ $ $ $ ▪ CPU and GPU memory are usually separate, Shared Cache connected by an I/O bus (traditionally PCI-e)
$ $ $ $ $ $ $ $ ▪ Any data transferred between the CPU and GPU will be handled by the I/O Bus Shared Cache High ▪ The I/O Bus is relatively slow compared to Capacity memory bandwidth Memory IO Bus High Bandwidth ▪ The GPU cannot perform computation until the Memory data is within its memory
42 CUDA UNIFIED MEMORY
43 CUDA UNIFIED MEMORY Commonly referred to as Simplified Developer Effort “managed memory.”
Without Managed Memory With Managed Memory
CPU and GPU memories are combined into a single, shared pool
System GPU Memory Managed Memory Memory
44 CUDA MANAGED MEMORY Usefulness
▪ Handling explicit data transfers between the host and device (CPU and GPU) can be difficult ▪ The PGI compiler can utilize CUDA Managed Memory to defer data management ▪ This allows the developer to concentrate on parallelism and think about data movement as an optimization
$ pgcc –fast –acc –ta=tesla:managed –Minfo=accel main.c
$ pgfortran –fast –acc –ta=tesla:managed –Minfo=accel main.f90
45 MANAGED MEMORY Limitations
With Managed Memory ▪ The programmer will almost always be able to get better performance by manually handling data transfers ▪ Memory allocation/deallocation takes longer with managed memory ▪ Cannot transfer data asynchronously ▪ Currently only available from PGI on NVIDIA GPUs. Managed Memory
46 * Slide Courtesy of PGI 47 WE’RE USING UNIFIED MEMORY Now let’s make our code run without.
Why? ▪ Removes reliance on PGI and NVIDIA GPUs ▪ Currently the data always arrives “Just Too Late”, let’s do better
48 BASIC DATA MANAGEMENT
49 BASIC DATA MANAGEMENT Between the host and device
▪ The host is traditionally a CPU Host ▪ The device is some parallel accelerator Device ▪ When our target hardware is multicore, the host and device are the same, meaning that their memory is also the same ▪ There is no need to explicitly manage data when using a shared memory accelerator, Host such as the multicore target Memory Device Memory
50 BASIC DATA MANAGEMENT Between the host and device CPU GPU
▪ When the target hardware is a GPU data will $ $ $ $ $ $ usually need to migrate between CPU and $ $ $ $ $ $ GPU memory Shared Cache ▪ Each array used on the GPU must be allocated on the GPU $ $ $ $ $ $ $ $
▪ When data changes on the CPU or GPU the Shared Cache other must be updated High Capacity Memory IO Bus High Bandwidth Memory
51 TRY TO BUILD WITHOUT “MANAGED” Change –ta=tesla:managed to remove “managed” pgcc -ta=tesla -Minfo=accel laplace2d.c jacobi.c laplace2d.c: PGC-S-0155-Compiler failed to translate accelerator region (see -Minfo messages): Could not find allocated-variable index for symbol (laplace2d.c: 47) calcNext: 47, Accelerator kernel generated Generating Tesla code 48, #pragma acc loop gang /* blockIdx.x */ Generating reduction(max:error) 50, #pragma acc loop vector(128) /* threadIdx.x */ 48, Accelerator restriction: size of the GPU copy of Anew,A is unknown 50, Loop is parallelizable PGC-F-0704-Compilation aborted due to previous errors. (laplace2d.c) PGC/x86-64 Linux 18.7-0: compilation aborted jacobi.c:
52 DATA SHAPING
53 DATA CLAUSES copy( list ) Allocates memory on GPU and copies data from host to GPU when entering region and copies data to the host when exiting region.
Principal use: For many important data structures in your code, this is a logical default to input, modify and return the data. copyin( list ) Allocates memory on GPU and copies data from host to GPU when entering region.
Principal use: Think of this like an array that you would use as just an input to a subroutine. copyout( list ) Allocates memory on GPU and copies data to the host when exiting region.
Principal use: A result that isn’t overwriting the input data structure. create( list ) Allocates memory on GPU but does not copy.
Principal use: Temporary arrays. 54 ARRAY SHAPING
▪ Sometimes the compiler needs help understanding the shape of an array ▪ The first number is the start index of the array ▪ In C/C++, the second number is how much data is to be transferred ▪ In Fortran, the second number is the ending index copy(array[starting_index:length]) C/C++ copy(array(starting_index:ending_index)) Fortran
55 ARRAY SHAPING (CONT.) Multi-dimensional Array shaping
copy(array[0:N][0:M]) C/C++
Both of these examples copy a 2D array to the device
copy(array(1:N, 1:M)) Fortran
56 ARRAY SHAPING (CONT.) Partial Arrays
copy(array[i*N/4:N/4]) C/C++
Both of these examples copy only ¼ of the full array
copy(array(i*N/4:i*N/4+N/4)) Fortran
57 OPTIMIZED DATA MOVEMENT
while ( err > tol && iter < iter_max ) { err=0.0;
#pragma acc parallel loop reduction(max:err) copyin(A[0:n*m]) copy(Anew[0:n*m]) for( int j = 1; j < n-1; j++) { for(int i = 1; i < m-1; i++) {
Anew[j][i] = 0.25 * (A[j][i+1] + A[j][i-1] + Data clauses A[j-1][i] + A[j+1][i]); provide necessary err = max(err, abs(Anew[j][i] - A[j][i])); “shape” to the } } arrays.
#pragma acc parallel loop copyin(Anew[0:n*m]) copyout(A[0:n*m]) for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { A[j][i] = Anew[j][i]; } } iter++; }
58 TRY TO BUILD WITHOUT “MANAGED” Change –ta=tesla:managed to remove “managed” pgcc -ta=tesla -Minfo=accel laplace2d.c jacobi.c laplace2d.c: calcNext: 47, Generating copyin(A[:m*n]) Accelerator kernel generated Generating Tesla code 48, #pragma acc loop gang /* blockIdx.x */ Generating reduction(max:error) 50, #pragma acc loop vector(128) /* threadIdx.x */ 47, Generating implicit copy(error) Generating copy(Anew[:m*n]) 50, Loop is parallelizable swap: 62, Generating copyin(Anew[:m*n]) Generating copyout(A[:m*n]) Accelerator kernel generated Generating Tesla code 63, #pragma acc loop gang /* blockIdx.x */ 65, #pragma acc loop vector(128) /* threadIdx.x */ 65, Loop is parallelizable jacobi.c:
59 OPENACC SPEED-UP SLOWDOWN
Speed-up 45.00X 41.80X 40.00X
35.00X
30.00X
Up 25.00X -
20.00X Speed
15.00X
10.00X
5.00X 3.23X 1.00X 0.33X 0.00X SERIAL MULTICORE V100 V100 (DATA CLAUSES) 60 WHAT WENT WRONG?
▪ The code now has all of the information necessary to build without managed memory, but it runs much slower. ▪ Profiling tools are here to help!
61 APPLICATION PROFILE (2 STEPS)
62 APPLICATION PROFILE (2 STEPS)
Data Copies
63 RUNTIME BREAKDOWN Nearly all of our time is spent moving data to/from the GPU
Data Copy H2D Data Copy D2H CalcNext Swap
64 OPTIMIZED DATA MOVEMENT
while ( err > tol && iter < iter_max ) { err=0.0;
#pragma acc parallel loop reduction(max:err) copyin(A[0:n*m]) copy(Anew[0:n*m]) for( int j = 1; j < n-1; j++) { for(int i = 1; i < m-1; i++) {
Anew[j][i] = 0.25 * (A[j][i+1] + A[j][i-1] + Currently we’re A[j-1][i] + A[j+1][i]); copying to/from the err = max(err, abs(Anew[j][i] - A[j][i])); } GPU for each loop, } can we reuse it?
#pragma acc parallel loop copyin(Anew[0:n*m]) copyout(A[0:n*m]) for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { A[j][i] = Anew[j][i]; } } iter++; }
65 OPTIMIZE DATA MOVEMENT
66 OPENACC DATA DIRECTIVE Definition
▪ The data directive defines a lifetime #pragma acc data clauses for data on the device beyond { individual loops < Sequential and/or Parallel code > ▪ During the region data is essentially “owned by” the accelerator } ▪ Data clauses express shape and data movement for the region !$acc data clauses
< Sequential and/or Parallel code >
!$acc end data
67 STRUCTURED DATA DIRECTIVE Example
#pragma acc data copyin(a[0:N],b[0:N]) copyout(c[0:N]) Action { #pragma acc parallel loop DeallocateDeallocateExecuteAllocateAllocateCopyCopy CAB loop from from CABCBA ononfromfrom on for(int i = 0; i < N; i++){ CPUdevicedevice to to device CPU c[i] = a[i] + b[i]; } }
Host Memory Device memory A B CC’ A B C’
68 OPTIMIZED DATA MOVEMENT Copy A to/from the #pragma acc data copy(A[:n*m]) copyin(Anew[:n*m]) accelerator only when while ( err > tol && iter < iter_max ) { needed. err=0.0; Copy initial condition of #pragma acc parallel loop reduction(max:err) copyin(A[0:n*m]) copy(Anew[0:n*m]) for( int j = 1; j < n-1; j++) { Anew, but not final value for(int i = 1; i < m-1; i++) {
Anew[j][i] = 0.25 * (A[j][i+1] + A[j][i-1] + A[j-1][i] + A[j+1][i]);
err = max(err, abs(Anew[j][i] - A[j][i])); } }
#pragma acc parallel loop copyin(Anew[0:n*m]) copyout(A[0:n*m]) for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { A[j][i] = Anew[j][i]; } } iter++; } 69 REBUILD THE CODE
pgcc -fast -ta=tesla -Minfo=accel laplace2d_uvm.c main: Now data movement only 60, Generating copy(A[:m*n]) happens at our data Generating copyin(Anew[:m*n]) region. 64, Accelerator kernel generated Generating Tesla code 64, Generating reduction(max:error) 65, #pragma acc loop gang /* blockIdx.x */ 67, #pragma acc loop vector(128) /* threadIdx.x */ 67, Loop is parallelizable 75, Accelerator kernel generated Generating Tesla code 76, #pragma acc loop gang /* blockIdx.x */ 78, #pragma acc loop vector(128) /* threadIdx.x */ 78, Loop is parallelizable
70 OPENACC SPEED-UP
Speed-up 50.00X
45.00X 42.99X 41.80X 40.00X
35.00X
30.00X
Up - 25.00X
Speed 20.00X
15.00X
10.00X
5.00X 3.23X 1.00X 0.00X SERIAL MULTICORE V100 V100 (DATA) 71 WHAT WE’VE LEARNED SO FAR
▪ CUDA Unified (Managed) Memory is a powerful porting tool ▪ GPU programming without managed memory often requires data shaping ▪ Moving data at each loop is often inefficient ▪ The OpenACC Data region can decouple data movement and computation
72 DATA SYNCHRONIZATION
73 OPENACC UPDATE DIRECTIVE update: Explicitly transfers data between the host and the device Useful when you want to synchronize data in the middle of a data region Clauses: self: makes host data agree with device data device: makes device data agree with host data
#pragma acc update self(x[0:count]) #pragma acc update device(x[0:count]) C/C++ !$acc update self(x(1:end_index)) !$acc update device(x(1:end_index)) Fortran
74 OPENACC UPDATE DIRECTIVE
#pragma acc update device(A[0:N])
A A*A The data must exist on both the CPU and device CPU Memory device Memory for the update directive to work. B*B B*
#pragma acc update self(A[0:N]) 75 SYNCHRONIZE DATA WITH UPDATE
▪ Sometimes data changes on the host or int* A=(int*) malloc(N*sizeof(int) device inside a data region #pragma acc data create(A[0:N]) while( timesteps++ < numSteps ) ▪ Ending the data region and starting a new { #pragma acc parallel loop one is expensive for(int i = 0; i < N; i++){ a[i] *= 2; ▪ Instead, update the data so that the host } and device data are the same if (timestep % 100 ) { ▪ Examples: File I/O, Communication, etc. #pragma acc update self(A[0:N]) checkpointAToFile(A, N); } }
76 UNSTRUCTURED DATA DIRECTIVES
77 UNSTRUCTURED DATA DIRECTIVES Enter Data Directive
▪ Data lifetimes aren’t always neatly structured. #pragma acc enter data clauses ▪ The enter data directive handles device < Sequential and/or Parallel code > memory allocation #pragma acc exit data clauses ▪ You may use either the create or the copyin clause for memory allocation ▪ The enter data directive is not the start !$acc enter data clauses of a data region, because you may have multiple enter data directives < Sequential and/or Parallel code > !$acc exit data clauses
78 UNSTRUCTURED DATA DIRECTIVES Exit Data Directive ▪ The exit data directive handles device memory deallocation #pragma acc enter data clauses
▪ You may use either the delete or the < Sequential and/or Parallel code > copyout clause for memory deallocation #pragma acc exit data clauses ▪ You should have as many exit data for a given array as enter data
▪ These can exist in different functions !$acc enter data clauses
< Sequential and/or Parallel code >
!$acc exit data clauses
79 UNSTRUCTURED DATA CLAUSES
copyin ( list ) Allocates memory on device and copies data from host to device on enter data. copyout ( list ) Allocates memory on device and copies data back to the host on exit data. create ( list ) Allocates memory on device without data transfer on enter data. delete ( list ) Deallocates memory on device without data transfer on exit data
80 UNSTRUCTURED DATA DIRECTIVES Basic Example
#pragma acc parallel loop for(int i = 0; i < N; i++){ c[i] = a[i] + b[i]; }
81 UNSTRUCTURED DATA DIRECTIVES Basic Example
#pragma acc enter data copyin(a[0:N],b[0:N]) create(c[0:N])
#pragma acc parallel loop for(int i = 0; i < N; i++){ c[i] = a[i] + b[i]; }
#pragma acc exit data copyout(c[0:N]) delete(a,b)
82 UNSTRUCTURED VS STRUCTURED With a simple code
Unstructured Structured ▪ Can have multiple starting/ending points ▪ Must have explicit start/end points ▪ Can branch across multiple functions ▪ Must be within a single function ▪ Memory exists until explicitly deallocated ▪ Memory only exists within the data region #pragma acc enter data copyin(a[0:N],b[0:N]) \ #pragma acc data copyin(a[0:N],b[0:N]) \ create(c[0:N]) copyout(c[0:N]) { #pragma acc parallel loop #pragma acc parallel loop for(int i = 0; i < N; i++){ for(int i = 0; i < N; i++){ c[i] = a[i] + b[i]; c[i] = a[i] + b[i]; } }
#pragma acc exit data copyout(c[0:N]) \ } delete(a,b)
83 C++ STRUCTS/CLASSES With dynamic data members class vector { private: ▪ C++ Structs/Classes work the same float *arr; int n; exact way as they do in C public: vector(int size){ ▪ The main difference is that now we have n = size; to account for the implicit “this” pointer arr = new float[n]; #pragma acc enter data copyin(this) #pragma acc enter data create(arr[0:n]) } ~vector(){ #pragma acc exit data delete(arr) #pragma acc exit data delete(this) delete(arr); } };
84 C++ CLASS DATA SYNCHRONIZATION vector.accUpdateSelf(); ▪ Since data is encapsulated, the class needs to be extended to include data synchronization methods ▪ Including explicit methods for host/device synchronization may ease C++ data management CPU Memory device Memory ▪ Allows the class to be able to naturally handle synchronization, creating less code clutter vector.accUpdateDevice();
void accUpdateSelf() { #pragma acc update self(arr[0:n]) } void accUpdateDevice() { #pragma acc update device(arr[0:n]) } 85 UNSTRUCTURED DATA DIRECTIVES Branching across multiple functions int* allocate_array(int N){ int* ptr = (int *) malloc(N * sizeof(int)); ▪ In this example enter data and exit data are #pragma acc enter data create(ptr[0:N]) return ptr; in different functions } ▪ This allows the programmer to put device void deallocate_array(int* ptr){ #pragma acc exit data delete(ptr) allocation/deallocation with the matching free(ptr); host versions } int main(){ ▪ This pattern is particularly useful in C++, int* a = allocate_array(100); where structured scopes may not be #pragma acc kernels { possible. a[0] = 0; } deallocate_array(a); }
87 GANGS, WORKERS, AND VECTORS DEMYSTIFIED
88 GANGS, WORKERS, AND VECTORS DEMYSTIFIED
89 GANGS, WORKERS, AND VECTORS DEMYSTIFIED
90 GANGS, WORKERS, AND VECTORS DEMYSTIFIED
How much work 1 worker can do is limited by his speed.
A single worker can only ! move so fast.
91 GANGS, WORKERS, AND VECTORS DEMYSTIFIED
Even if we increase the size of his roller, he can only paint so fast. ! We need more workers!
92 GANGS, WORKERS, AND VECTORS DEMYSTIFIED
Multiple workers can do more work and share resources, if organized properly.
93 GANGS, WORKERS, AND VECTORS DEMYSTIFIED
By organizing our workers into groups (gangs), they can effectively work together within a floor.
Groups (gangs) on different floors can operate independently.
Since gangs operate independently, we can use as many or few as we need.
94 GANGS, WORKERS, AND VECTORS DEMYSTIFIED
Even if there’s not enough gangs for each floor, they can move to another floor when ready.
95 GANGS, WORKERS, AND VECTORS DEMYSTIFIED
Our painter is like an OpenACC worker, he can only do so much.
His roller is like a vector, he can move faster by covering Vector Workers more wall at once.
Eventually we need more Gang workers, which can be organized into gangs to get more done.
96 LOOP OPTIMIZATIONS
97 OPENACC LOOP DIRECTIVE Expressing parallelism
▪ Mark a single for loop for parallelization C/C++ ▪ Allows the programmer to give additional #pragma acc loop information and/or optimizations about the for(int i = 0; i < N; i++) loop // Do something
▪ Provides many different ways to describe the Fortran type of parallelism to apply to the loop !$acc loop ▪ Must be contained within an OpenACC do i = 1, N compute region (either a kernels or a parallel ! Do something region) to parallelize loops
98 COLLAPSE CLAUSE
▪ collapse( N ) #pragma acc parallel loop collapse(2) for( i = 0; i < size; i++ ) ▪ Combine the next N tightly nested loops for( j = 0; j < size; j++ ) double tmp = 0.0f; ▪ Can turn a multidimensional loop nest #pragma acc loop reduction(+:tmp) into a single-dimension loop for( k = 0; k < size; k++ ) tmp += a[i][k] * b[k][j]; ▪ This can be extremely useful for c[i][j] = tmp; increasing memory locality, as well as creating larger loops to expose more parallelism
99 COLLAPSE CLAUSE
collapse( 2 )
(0,0) (0,1) (0,2) (0,3)
(1,0) (1,1) (1,2) (1,3)
(2,0) (2,1) (2,2) (2,3)
(3,0) (3,1) (3,2) (3,3)
#pragma acc parallel loop collapse(2) for( i = 0; i < 4; i++ ) for( j = 0; j < 4; j++ ) array[i][j] = 0.0f; 100 COLLAPSE CLAUSE When/Why to use it
▪ A single loop might not have enough iterations to parallelize ▪ Collapsing outer loops gives more scalable (gangs) parallelism ▪ Collapsing inner loops gives more tight (vector) parallelism ▪ Collapsing all loops gives the compiler total freedom, but may cost data locality
101 COLLAPSE CLAUSE
#pragma acc data copy(A[:n*m]) copyin(Anew[:n*m]) while ( err > tol && iter < iter_max ) { err=0.0; Collapse 2 loops into one #pragma acc parallel loop reduction(max:err) collapse(2) \ for more flexibility in copyin(A[0:n*m]) copy(Anew[0:n*m]) parallelizing. for( int j = 1; j < n-1; j++) { for(int i = 1; i < m-1; i++) { Anew[j][i] = 0.25 * (A[j][i+1] + A[j][i-1] + A[j-1][i] + A[j+1][i]); err = max(err, abs(Anew[j][i] - A[j][i])); } }
#pragma acc parallel loop collapse(2) \ copyin(Anew[0:n*m]) copyout(A[0:n*m]) for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { A[j][i] = Anew[j][i]; } } iter++; } 102 OPENACC SPEED-UP
Speed-up 1.04X 1.03X 1.03X
1.03X
1.02X
1.02X Up
- 1.01X
1.01X
Speed 1.00X 1.00X
1.00X
0.99X
0.99X
0.98X NVIDIA TESLA V100 (DATA) NVIDIA TESLA V100 (COLLAPSE)
PGI 18.7, NVIDIA Tesla V100, Intel i9-7900X CPU @ 3.30GHz 103 TILE CLAUSE
▪ tile ( x , y , z, ...) ▪ Breaks multidimensional loops into “tiles” or “blocks” #pragma acc kernels loop tile(32, 32) for( i = 0; i < size; i++ ) ▪ Can increase data locality in some for( j = 0; j < size; j++ ) for( k = 0; k < size; k++ ) codes c[i][j] += a[i][k] * b[k][j]; ▪ Will be able to execute multiple “tiles” simultaneously
104 TILE CLAUSE
tile ( 2 , 2 )
(0,0) (0,1) (0,2) (0,3) (0,0) (0,1) (0,2) (0,3) #pragma acc kernels loop tile(2,2) for(int x = 0; x < 4; x++){ (1,0) (1,1) (1,2) (1,3) for(int y = 0; y < 4; y++){ array[x][y]++; (1,0) (1,1) (1,2) (1,3) } } (2,0) (2,1) (2,2) (2,3) (2,0) (2,1) (2,2) (2,3) (3,0) (3,1) (3,2) (3,3) (3,0) (3,1) (3,2) (3,3)
105 OPTIMIZED DATA MOVEMENT
#pragma acc data copy(A[:n*m]) copyin(Anew[:n*m]) while ( err > tol && iter < iter_max ) { err=0.0; Create 32x32 tiles of the #pragma acc parallel loop reduction(max:err) tile(32,32) \ loops to better exploit copyin(A[0:n*m]) copy(Anew[0:n*m]) data locality. for( int j = 1; j < n-1; j++) { for(int i = 1; i < m-1; i++) { Anew[j][i] = 0.25 * (A[j][i+1] + A[j][i-1] + A[j-1][i] + A[j+1][i]); err = max(err, abs(Anew[j][i] - A[j][i])); } }
#pragma acc parallel loop tile(32,32) \ copyin(Anew[0:n*m]) copyout(A[0:n*m]) for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { A[j][i] = Anew[j][i]; } } iter++; } 106 TILING RESULTS (V100) CPU GPU Improvement Improvement
Baseline 1.00X 1.00X The collapse clause often requires an 4x4 1.00X 0.77X exhaustive search of options. 4x8 1.00X 0.92X 8x4 1.00X 0.95X
For our example code… 8x8 1.00X 1.02X • CPU saw no benefit from tiling 8x16 1.00X 0.99X 16x8 1.00X 1.02X • GPU saw anywhere from a 23% loss 16x16 1.00X 1.01X of performance to a 10% improvement 16x32 1.00X 1.06X
32x16 1.00X 1.07X
32x32 1.00X 1.10X
107 OPENACC SPEED-UP
Speed-up 1.15X 1.13X
1.10X
1.05X
Up 1.03X -
1.00X Speed 1.00X
0.95X
0.90X NVIDIA TESLA V100 (DATA) NVIDIA TESLA V100 (COLLAPSE) NVIDIA TESLA V100 (TILE)
PGI 18.7, NVIDIA Tesla V100, Intel i9-7900X CPU @ 3.30GHz 108 GANG, WORKER, AND VECTOR CLAUSES
▪ The developer can instruct the compiler #pragma acc parallel loop gang which levels of parallelism to use on for( i = 0; i < size; i++ ) given loops by adding clauses: #pragma acc loop worker for( j = 0; j < size; j++ ) ▪ gang – Mark this loop for gang #pragma acc loop vector parallelism for( k = 0; k < size; k++ ) c[i][j] += a[i][k] * b[k][j]; ▪ worker – Mark this loop for worker parallelism #pragma acc parallel loop \ collapse(3) gang vector ▪ vector – Mark this loop for vector for( i = 0; i < size; i++ ) parallelism for( j = 0; j < size; j++ ) for( k = 0; k < size; k++ ) These can be combined on the same c[i][j] += a[i][k] * b[k][j]; loop.
109 SEQ CLAUSE
▪ The seq clause (short for sequential) will tell the compiler to run the loop sequentially #pragma acc parallel loop ▪ In the sample code, the compiler will for( i = 0; i < size; i++ ) parallelize the outer loops across the #pragma acc loop parallel threads, but each thread will run for( j = 0; j < size; j++ ) the inner-most loop sequentially #pragma acc loop seq for( k = 0; k < size; k++ ) ▪ The compiler may automatically apply c[i][j] += a[i][k] * b[k][j]; the seq clause to loops as well
110 ADJUSTING GANGS, WORKERS, AND VECTORS
The compiler will choose a number of gangs, workers, and a vector length for you, but you #pragma acc parallel num_gangs(2) \ can change it with clauses. num_workers(2) vector_length(32) { ▪ num_gangs(N) – Generate N gangs for this #pragma acc loop gang worker parallel region for(int x = 0; x < 4; x++){ #pragma acc loop vector ▪ num_workers(M) – Generate M workers for for(int y = 0; y < 32; y++){ this parallel region array[x][y]++; } ▪ vector_length(Q) – Use a vector length of } Q for this parallel region }
111 COLLAPSE CLAUSE WITH VECTOR LENGTH
#pragma acc data copy(A[:n*m]) copyin(Anew[:n*m]) while ( err > tol && iter < iter_max ) { err=0.0;
#pragma acc parallel loop reduction(max:err) collapse(2) vector_length(1024) \ copyin(A[0:n*m]) copy(Anew[0:n*m]) for( int j = 1; j < n-1; j++) { for(int i = 1; i < m-1; i++) { Anew[j][i] = 0.25 * (A[j][i+1] + A[j][i-1] + A[j-1][i] + A[j+1][i]); err = max(err, abs(Anew[j][i] - A[j][i])); } }
#pragma acc parallel loop collapse(2) vector_length(1024) \ copyin(Anew[0:n*m]) copyout(A[0:n*m]) for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { A[j][i] = Anew[j][i]; } } iter++; } 112 OPENACC SPEED-UP
Speed-up 1.15X 1.13X 1.12X
1.10X
1.05X
Up 1.03X -
1.00X Speed 1.00X
0.95X
0.90X NVIDIA TESLA V100 (DATA) NVIDIA TESLA V100 (COLLAPSE) NVIDIA TESLA V100 (TILE) NVIDIA TESLA V100 (COLLAPSE/VECTOR)
PGI 18.7, NVIDIA Tesla V100, Intel i9-7900X CPU @ 3.30GHz 113 LOOP OPTIMIZATION RULES OF THUMB
▪ It is rarely a good idea to set the number of gangs in your code, let the compiler decide. ▪ Most of the time you can effectively tune a loop nest by adjusting only the vector length. ▪ It is rare to use a worker loop. When the vector length is very short, a worker loop can increase the parallelism in your gang. ▪ When possible, the vector loop should step through your arrays ▪ Gangs should come from outer loops, vectors from inner
114 CLOSING REMARKS
115 KEY CONCEPTS In this lab we discussed… ▪ Some details that are available to use from a GPU profile ▪ Gangs, Workers, and Vectors Demystified ▪ Collapse clause ▪ Tile clause ▪ Gang/Worker/Vector clauses
116 OPENACC RESOURCES
Guides ● Talks ● Tutorials ● Videos ● Books ● Spec ● Code Samples ● Teaching Materials ● Events ● Success Stories ● Courses ● Slack ● Stack Overflow
Resources Success Stories https://www.openacc.org/resources https://www.openacc.org/success-stories FREE Compilers
Compilers and Tools Events https://www.openacc.org/tools https://www.openacc.org/events
https://www.openacc.org/community#slack INTRODUCTION TO OPENMP
118 OpenMP Worksharing
PARALLEL Directive OMP PARALLEL
Spawns a team of threads Thread Team Execution continues redundantly on all threads of the team. All threads join at the end and the master thread continues execution. Master Thread
119 OpenMP Worksharing
FOR/DO (Loop) Directive OMP PARALLEL
Divides (“workshares”) the Thread Team iterations of the next loop across the threads in the team OMP FOR How the iterations are divided is determined by a schedule.
120 CPU-Parallelism
while ( error > tol && iter < iter_max ) { error = 0.0; Create a team of threads #pragma omp parallel for reduction(max:error) and workshare this loop for( int j = 1; j < n-1; j++) { across those threads. for( int i = 1; i < m-1; i++ ) { Anew[j][i] = 0.25 * ( A[j][i+1] + A[j][i-1] + A[j-1][i] + A[j+1][i]); error = fmax( error, fabs(Anew[j][i] - A[j][i])); } } Create a team of threads #pragma omp parallel for for( int j = 1; j < n-1; j++) { and workshare this loop for( int i = 1; i < m-1; i++ ) { across those threads. A[j][i] = Anew[j][i]; } }
if(iter++ % 100 == 0) printf("%5d, %0.6f\n", iter, error); }
121 CPU-Parallelism, single region
while ( error > tol && iter < iter_max ) { error = 0.0;
#pragma omp parallel Create a team of threads { #pragma omp for reduction(max:error) for( int j = 1; j < n-1; j++) { Workshare this loop for( int i = 1; i < m-1; i++ ) { Anew[j][i] = 0.25 * ( A[j][i+1] + A[j][i-1] + A[j-1][i] + A[j+1][i]); error = fmax( error, fabs(Anew[j][i] - A[j][i])); } } Implicit barrier from
#pragma omp for above “for” will ensure for( int j = 1; j < n-1; j++) { correctness here for( int i = 1; i < m-1; i++ ) { A[j][i] = Anew[j][i]; } } } if(iter++ % 100 == 0) printf("%5d, %0.6f\n", iter, error); } 122 CPU-Parallelism, with SIMD
while ( error > tol && iter < iter_max ) { error = 0.0;
#pragma omp parallel for reduction(max:error) for( int j = 1; j < n-1; j++) { #pragma omp simd for( int i = 1; i < m-1; i++ ) { Anew[j][i] = 0.25 * ( A[j][i+1] + A[j][i-1] + A[j-1][i] + A[j+1][i]); error = fmax( error, fabs(Anew[j][i] - A[j][i])); Some compilers want a } SIMD directive to simdize } on CPUS. #pragma omp parallel for for( int j = 1; j < n-1; j++) { #pragma omp simd for( int i = 1; i < m-1; i++ ) { A[j][i] = Anew[j][i]; } }
if(iter++ % 100 == 0) printf("%5d, %0.6f\n", iter, error); } 123 Targeting the GPU
124 OpenMP Offloading
TARGET Directive Offloads execution and associated data from the CPU to the GPU • The target device owns the data, accesses by the CPU during the execution of the target region are forbidden. • Data used within the region may be implicitly or explicitly mapped to the device. • All of OpenMP is allowed within target regions, but only a subset will run well on GPUs.
125 OpenMP Data Mapping
126 Target the GPU
while ( error > tol && iter < iter_max ) { error = 0.0; Moves this region of #pragma omp target { code to the GPU and #pragma omp parallel for reduction(max:error) implicitly maps data. for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { Anew[j][i] = 0.25 * ( A[j][i+1] + A[j][i-1] + A[j-1][i] + A[j+1][i]); error = fmax( error, fabs(Anew[j][i] - A[j][i])); } }
#pragma omp parallel for for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { A[j][i] = Anew[j][i]; } } } if(iter++ % 100 == 0) printf("%5d, %0.6f\n", iter, error); }
127 Target the GPU
while ( error > tol && iter < iter_max ) { error = 0.0; Moves this region of #pragma omp target { code to the GPU and #pragma omp parallel for reduction(max:error) implicitly maps data. for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { Anew[j][i] = 0.25 * ( A[j][i+1] + A[j][i-1] + A[j-1][i] + A[j+1][i]); error = fmax( error, fabs(Anew[j][i] - A[j][i])); } }
#pragma omp parallel for for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { A[j][i] = Anew[j][i]; } } } if(iter++ % 100 == 0) printf("%5d, %0.6f\n", iter, error); }
128 Target the GPU
while ( error > tol && iter < iter_max ) { error = 0.0; #pragma omp target map(to:Anew[:n+2][:m+2]) \ Moves this region of map(tofrom:A[:n+2][:m+2],tofrom:error) code to the GPU and { explicitly maps data. #pragma omp parallel for reduction(max:error) for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { Anew[j][i] = 0.25 * ( A[j][i+1] + A[j][i-1] + A[j-1][i] + A[j+1][i]); error = fmax( error, fabs(Anew[j][i] - A[j][i])); } }
#pragma omp parallel for for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { A[j][i] = Anew[j][i]; } } } if(iter++ % 100 == 0) printf("%5d, %0.6f\n", iter, error); } 129 Target the GPU
while ( error > tol && iter < iter_max ) { error = 0.0; #pragma omp target map(to:Anew[:n+2][:m+2]) \ Moves this region of map(tofrom:A[:n+2][:m+2],tofrom:error) code to the GPU and { explicitly maps data. #pragma omp parallel for reduction(max:error) for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { Anew[j][i] = 0.25 * ( A[j][i+1] + A[j][i-1] + A[j-1][i] + A[j+1][i]); error = fmax( error, fabs(Anew[j][i] - A[j][i])); } }
#pragma omp parallel for for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { A[j][i] = Anew[j][i]; } } } if(iter++ % 100 == 0) printf("%5d, %0.6f\n", iter, error); } 130 Excessive Data Movement
A and Anew arrays are copied for each iteration.
131 OpenMP Data Offloading
TARGET DATA Directive Offloads data from the CPU to the GPU, but not execution • The target device owns the data, accesses by the CPU during the execution of contained target regions are forbidden. • Useful for sharing data between TARGET regions • NOTE: A TARGET region is a TARGET DATA region.
132 Improve Data Movement Explicitly map the data #pragma omp target data map(to:Anew) map(A) once for the whole while ( error > tol && iter < iter_max ) { simulation. error = 0.0; #pragma omp target map(error) { #pragma omp parallel for reduction(max:error) for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { Anew[j][i] = 0.25 * ( A[j][i+1] + A[j][i-1] + A[j-1][i] + A[j+1][i]); error = fmax( error, fabs(Anew[j][i] - A[j][i])); } } #pragma omp parallel for for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { A[j][i] = Anew[j][i]; } } } // OMP TARGET } 133 Improved Data Movement
A and Anew arrays are copied before and after the simulation, only “error” is copied per iteration.
134 Speed-Up
20.00
18.00
16.00
14.00
12.00
up - 10.00
Speed 8.00
6.00
4.00
2.00
0.00 XLC CLANG GCC Serial Threaded Fused SIMD Best
IBM Power9, NVIDIA Tesla V100 135 GPUs are composed of 1 or more independent parts, GPU Architecture Basics known as Streaming Multiprocessors (“SMs”) Threads are organized into threadblocks. Threads within the same theadblock run on an SM and can synchronize. Threads in different threadblocks (even if they’re on the same SM) cannot synchronize. CUDA 9 introduces cooperative groups, they may address this.
136 Teaming Up
137 OpenMP Teams TEAMS Directive To better utilize the GPU resources, use many thread teams via the OMP TEAMS TEAMS directive. • Spawns 1 or more thread teams with the same number of threads • Execution continues on the master threads of each team (redundantly) • No synchronization between teams
138 OpenMP Teams DISTRIBUTE Directive Distributes the iterations of the next loop to the master threads of the OMP TEAMS teams. • Iterations are distributed statically. OMP DISTRIBUTE • There’s no guarantees about the order teams will execute. • No guarantee that all teams will execute simultaneously • Does not generate parallelism/worksharing within the thread teams.
139 Teaming Up Explicitly maps arrays #pragma omp target data map(to:Anew) map(A) for the entire while while ( error > tol && iter < iter_max ) { loop. error = 0.0;
#pragma omp target teams distribute parallel for reduction(max:error) map(error) for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { • Spawns thread teams Anew[j][i] = 0.25 * ( A[j][i+1] + A[j][i-1] • Distributes iterations + A[j-1][i] + A[j+1][i]); error = fmax( error, fabs(Anew[j][i] - A[j][i])); to those teams } • Workshares within } those teams. #pragma omp target teams distribute parallel for for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { A[j][i] = Anew[j][i]; } }
if(iter % 100 == 0) printf("%5d, %0.6f\n", iter, error);
iter++; 140 } Speed-Up
2.50
2.00 XLC CLANG GCC
1.50
Up -
Speed 1.00
0.50
0.00
IBM Power9, NVIDIA Tesla V100 141 Increasing Parallelism
142 Increasing Parallelism
Currently both our distributed and workshared parallelism comes from the same loop. • We could collapse them together • We could move the PARALLEL to the inner loop The COLLAPSE(N) clause • Turns the next N loops into one, linearized loop. • This will give us more parallelism to distribute, if we so choose.
143 Collapse Collapse the two loops into one and then #pragma omp target teams distribute parallel for reduction(max:error) map(error) \ collapse(2) parallelize this new for( int j = 1; j < n-1; j++) loop across both teams { for( int i = 1; i < m-1; i++ ) and threads. { Anew[j][i] = 0.25 * ( A[j][i+1] + A[j][i-1] + A[j-1][i] + A[j+1][i]); error = fmax( error, fabs(Anew[j][i] - A[j][i])); } }
#pragma omp target teams distribute parallel for collapse(2) for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { A[j][i] = Anew[j][i]; } }
144 Splitting Teams & Parallel
#pragma omp target teams distribute map(error) Distribute the “j” loop for( int j = 1; j < n-1; j++) over teams. { #pragma omp parallel for reduction(max:error) for( int i = 1; i < m-1; i++ ) { Anew[j][i] = 0.25 * ( A[j][i+1] + A[j][i-1] + A[j-1][i] + A[j+1][i]); error = fmax( error, fabs(Anew[j][i] - A[j][i])); } }
#pragma omp target teams distribute for( int j = 1; j < n-1; j++) { #pragma omp parallel for Workshare the “i” loop for( int i = 1; i < m-1; i++ ) over threads. { A[j][i] = Anew[j][i]; } }
145 Speed-Up
4.00
3.50 XLC CLANG GCC 3.00
2.50
Up -
2.00 Speed 1.50
1.00
0.50
0.00
IBM Power9, NVIDIA Tesla V100 146