The Portland Group
Oakridge Top Right
HPCwire

Since 1986 - Covering the Fastest Computers
in the World and the People Who Run Them

Language Flags

Visit additional Tabor Communication Publications

Enterprise Tech
Datanami
HPCwire Japan

Compilers and More: A GPU and Accelerator Programming Model


Okay, maybe the title should be "Languages and More," but I promise to talk about compilers further on below.

In recent columns, I've discussed parallel programming and programming GPUs and accelerators in particular. In May, I predicted that accelerator-based systems would dominate high performance computing, and suggested that an evolutionary approach to migrating applications from CPUs to accelerators was possible and appropriate. In September, I discussed in more detail the specifics of GPU hardware architecture, pointing out its strengths for high performance computing (lots of parallelism), as well as its weaknesses (limited to specific parallelism domains). In October, I showed what it takes to start porting a CPU program to a GPU, exposing some of the complexities of the interactions between the host and the GPU. The specific examples use NVIDIA's very popular CUDA language, but I discuss OpenCL briefly as well (which should be about ready for public discussion about the time you read this). In my most recent column, I showed the details of optimizing a simple matmul kernel for a GPU, including testing various ways to organize it and vary the parallelism parameters.

If you read these, or are familiar with current approaches to programming accelerators, you are either discomforted by the complexities, or excited at the levels of control you can get. The low-level programming model in CUDA and OpenCL certainly has its place, though it's not for the faint of heart. So, to go back to the first of those columns, can we come up with a different model of GPU and accelerator programming, one that retains most of the advantages of CUDA or OpenCL, but without requiring complete program rewrites, that can be applied to different target accelerators, and that retains the potential to develop and test in a more accessible environment? In short, a model that allows HPC programmers to focus on domain science instead of on computer science?

Architectural Model

Let's start by looking at the features of the architecture that we want to use to advantage. Current GPUs are specific implementations of a programming model that works well for graphics problems. They support two levels of parallelism: an outer fully-parallel doall loop level, and an inner synchronous (SIMD or vector) loop level. Each level can be multidimensional (2 or 3 dimensions), but the domain must be strictly rectangular. The synchronous level may not be fully implemented with SIMD or vector operations, so explicit synchronization is supported (and required) across this level. No synchronization is supported between parallel threads across the doall level.

For those familiar with memory models, current GPUs implement a particularly weak model. In particular, they don't support memory coherence between threads, unless those threads are parallel only at the synchronous level and the memory operations are separated by an explicit barrier. Otherwise, if one thread updates a memory location and another reads the same location, or two threads store a value to the same location, the hardware does not guarantee the results. You can't say it gets the wrong answers, because such programs are defined as being in error. There is a software-managed cache on a GPU, and there are some hardware caches that can be used as well, but only in certain situations (and limited to read-only data).

The most significant characteristic is that the memory on the GPU or accelerator is separate from the host memory. The host can't simply read or write to the accelerator memory because it's not mapped into the virtual memory space of the host. Similarly, the accelerator can't simply read or write to host memory; the host memory doesn't support the bandwidth necessary for the accelerator, not to mention the need to support the virtual memory map on the accelerator.

The chips support parallelism on the order of hundreds of threads (today), but effective programs need parallelism on the order of thousands. This provides enough slack parallelism to tolerate long latency memory operations by thread switching, or multithreading, an idea pioneered by the venerable Denelcor HEP almost 30 years ago.

In summary, today's GPUs look like an attached processor with its separate memory, that supports a multidimensional rectangular domain of parallelism, including doall and synchronous parallelism. We'd like a programming model that simplifies most of the hardware details, but gives experts finer levels of control. We probably can't hide the distinction between the two levels of parallelism, but we'd like to avoid requiring the programmer to insert explicit synchronization as much as possible. It's easy to map doall parallelism onto SIMD parallelism, but not the other way around, so we'd like to encourage programmers to program in a doall style when possible and appropriate. We probably can't completely hide the distinction between host memory and accelerator memory, but the details of transferring data should be handled automatically.

But our programming model shouldn't focus on the details of today's GPUs as the ultimate accelerator architecture. One can envision accelerators with mostly (or only) synchronous parallelism (like the Clearspeed CSX700 accelerator processor), or with mostly doall parallelism (like the Tilera TILE64 chip). Future accelerators may share physical and/or virtual memory with the host, and may support a stronger memory model with richer synchronization methods. Software and hardware cache architectures are likely to change rapidly. A robust programming model should express parallelism broadly enough that compilers and tools can map an application onto future generations of accelerators as well as it does onto today's GPUs. In fact, a successful model should be able to map applications onto a multicore X64 processor, where the SSE instructions implement the synchronous parallelism, and the doall parallelism is mapped across cores. From the available details, this model would even map well onto Intel's proposed Larrabee chip. There will be work to tune the performance for each architecture, both in the tools and even at the application level, but the parallelism model needs to be reasonably portable.

Programming Model

How should we implement an accelerator-targeted programming model? Three options immediately come to mind: library, language, or directives. If you look at the array of parallel programming choices (all intended to make parallel programming easy), they span all three options.

Library-based solutions are attractive for many such problems; they are easy to port and can be independent of processor or compiler vendor. The MPI communication library for large system communication is one well-known example. It's often easier to create (and modify) a standard for a library than for a language.

Language-based solutions expose the semantics in the language, allowing compilers or other tools to analyze and optimize the program. Co-Array Fortran, which is (currently) part of the next (allegedly minor) revision of the Fortran standard, exposes MPI-like parallelism and communication in the language, similar in some respects to Unified Parallel C (UPC). A compiler for Co-Array Fortran might be able to discover that a data copy from one image (thread) to another in a loop could be vectorized, given the appropriate support in the communication layer; such analysis in an MPI program is left entirely to the programmer. However, languages are expensive to implement, typically change quite slowly, and mistakes are hard to remedy once the standard is written.

A directive-based approach has some of the advantages of language-based solutions, in that directives expose the semantics to the compiler and other tools, allowing intelligent analysis and optimization. Such an approach also allows a program to be developed and tested on platforms that don't support the directives, since the base language is unchanged. OpenMP is a widely available, successful parallel programming model based on directives to describe the parallel regions of the program.

Getting good performance on today's accelerators depends on selecting a region that has enough work to amortize the overhead of moving data between the host and accelerator. This is one instance of the more general problem of selecting a region that has enough compute intensity to amortize the data traffic across the memory hierarchy, be it separate memory or multilevel caches. Some day, we may trust compilers to make this determination automatically, but not yet. So let me propose a model that borrows strategies from OpenMP, since I'm the PGI representative to the OpenMP group. I'll propose directives in C and Fortran programs to delineate the regions of the program (loops) that should be accelerated (compiled for the GPU or other accelerator). Since the architecture model uses regular rectangular domains, I'll propose using parallel loops as the primitive parallel operation.

The keys to tuning are minimizing and perhaps optimizing the data traffic between the host and accelerator, and selecting a schedule for the parallelism. In many cases, a compiler can analyze the nested loops and determine the input and output data sets, so it can manage the data traffic automatically. However, we should never trust automatic analysis to solve all our performance problems, so we'll need directives or clauses to modify or augment the analysis.

As for scheduling, we saw in my previous column that there can be many possible schedules for even the simplest of parallel loops. Recent academic research in this area depends on doing more or less what I did by hand: generating many versions of the program and running each of them, then choosing the best one from the bunch; see Shane Ryoo's PhD dissertation (University of Illinois, 2008), and joint work from Professors Ramanujam and Sadayappan (Louisiana State University and The Ohio State University) as good examples. Such an approach is valid for research, or when searching for a good algorithm for a highly tuned library, but inappropriate for a compiler. Instead, we will depend on the compiler to determine a reasonably good schedule (as we do when we use automatic parallelization and vectorization today), again with directives or clauses to modify or augment the decisions.

It's important that a programmer be able to control any compiler optimization decision here; the difference between good and bad performance is quite dramatic, and at least in the immediate future, any compiler decision will be made with only partial information. However, to support this requires that the compiler tell the programmer what decisions it has made, and hopefully why, so the programmer knows whether it's appropriate to step in and make a change.

So let me propose two directives. The first delineates an accelerator region, with optional clauses to control the data movement between host and accelerator memory. Borrowing liberally from OpenMP, I'll propose a #pragma acc prefix for C directives, and !$acc prefix in Fortran. In C, I'll describe an acceleration region as:

    #pragma acc region
    {
       /* loops to be accelerated go here */
    }
Fortran doesn't have structured blocks (yet), so we'll use region and end region directives:
   !$acc region
       ! loops to be accelerated go here
   !$acc end region
Compare these to the OpenMP parallel regions. I propose optional clauses to tell the compiler what data needs to be copied into the region, from host to accelerator, what data needs to be copied out, and what data is local to the region; local data corresponds roughly to OpenMP private data. Compiler analysis is often able to determine the in/out/local data automatically.

The second directive is used to describe the mapping of parallel loops onto the hardware parallelism, what I called the schedule earlier. This corresponds roughly to the OpenMP loop directive, which describes the work-sharing pattern of parallel loops. It's probably easiest to explain with a familiar example; in my most recent column, I showed several versions of matmul in CUDA with different schedules. The first (and simplest) version would be written (in Fortran) using these directives as:

   !$acc region
      !$acc do parallel
      do j = 1, m
        do k = 1, p
          !$acc do parallel, vector(32)
          do i = 1, n
            a(i,j) = a(i,j) + b(i,k)*c(k,j)
          enddo
        enddo
      enddo
   !$acc end region
The loop directives do two things: the first is to tell the compiler about loop-level parallelism, augmenting its analysis. The second is to tell the compiler how to schedule or map the loop-level parallelism onto the hardware. In this loop, both the i and j loops exhibit doall parallelism, but we want to map the stride-1 i loop onto the synchronous (vector) parallelism in strips of size 32, using doall parallelism between the strips. We expect compilers to issue a warning message if a programmer inserts a do parallel directive on a loop that compiler analysis shows is in fact not parallel. Compare this code for clarity with the actual CUDA kernel.

This isn't intended to be a user guide, tutorial, even a formal proposal, but I hope to convince you that a directive-based approach is feasible in the short term, and can address many of the problems programmers will face when porting large applications for use on host+GPU platforms in particular, and host+accelerators in general.

This model does use reasonably sophisticated compiler analysis, but nothing that hasn't been implemented in commercial parallelizing compilers for many years. In this example, the compiler must take the following steps:

  • Determine what data is input to the region; for this loop, the input data is a(1:n,1:m), b(1:n,1:p), c(1:p,1:m), and the loop limits.
  • Determine what data is output to the region; this is simply a(1:n,1:m).
  • Determine what data is local to the region, which is empty (except perhaps for the loop counters). Classical data flow and array region analysis solves all three of these problems.
  • Determine which loops can run in parallel, augmented by information in the directives. For this loop, the j and i loops are completely parallel; the k loop requires a sum reduction, which is less efficient but could still be parallelized.
  • Determine the loop schedule; in this example, the schedule is specified by the directives. Without the loop directives, the compiler would have to search among the possible schedules and select a best one; note to academics: this is still a fertile area for continued research.
  • Generate code for the accelerator. For the most part, this is a classical compiler problem, and well known methods apply. On a target like the NVIDIA GPU, optimizing for the software-managed cache adds some complexity, but such problems have been addressed on past machines as well.
  • Generate host code to move data to the accelerator, launch the accelerator kernel(s), and move results back from the accelerator.

Final Words

Will adoption and use of directives such as these make GPUs more generally applicable? These directives may make GPUs more accessible, but there are still serious limitations to the parallelism GPUs support. The restrictions include rectangular domains, two levels of parallelism, limited synchronization, and a weak memory model (in the formal sense). This makes it unlikely that anyone will be porting unstructured mesh code or dynamic pointer-chasing data structures to a GPU anytime soon.

Can this programming model be adapted to make parallel programming easy in general? I've argued that parallel programming is difficult, and always will be, regardless of the programming model, and I'm not backing down. To reiterate, this directive model is intended to make accelerator programming accessible, so programmers can focus on algorithms and performance, not on syntax and other trivialities.

This proposed style of parallel programming isn't universal, but it does address a significant segment of the parallel community. The model is portable, across GPUs, across accelerators, even to multicore CPUs, though we need to develop the compilers. Moreover, it's nicely incremental; you can use these directives to accelerate parts of your program without having to undertake a whole rewrite, and, as with OpenMP, you can still build and test your application on the host by ignoring the directives altogether.

-----

Michael Wolfe has developed compilers for over 30 years in both academia and industry, and is now a senior compiler engineer at The Portland Group, Inc. (www.pgroup.com), a wholly-owned subsidiary of STMicroelectronics, Inc. The opinions stated here are those of the author, and do not represent opinions of The Portland Group, Inc. or STMicroelectronics, Inc.

-----

UPDATE: This article was original published during our SC08 coverage on November 20. Due to recent developments in heterogeneous compilers by PGI and CAPS Enterprise, as well as the ratification of the OpenCL standard, we felt it was worth another look. -- Editor

Most Read Features

Most Read Around the Web

Most Read This Just In

Most Read Blogs


Sponsored Whitepapers

Breaking I/O Bottlenecks

10/30/2013 | Cray, DDN, Mellanox, NetApp, ScaleMP, Supermicro, Xyratex | Creating data is easy… the challenge is getting it to the right place to make use of it. This paper discusses fresh solutions that can directly increase I/O efficiency, and the applications of these solutions to current, and new technology infrastructures.

A New Ultra-Dense Hyper-Scale x86 Server Design

10/01/2013 | IBM | A new trend is developing in the HPC space that is also affecting enterprise computing productivity with the arrival of “ultra-dense” hyper-scale servers.

Sponsored Multimedia

Xyratex, presents ClusterStor at the Vendor Showdown at ISC13

Ken Claffey, SVP and General Manager at Xyratex, presents ClusterStor at the Vendor Showdown at ISC13 in Leipzig, Germany.

HPCwire Live! Atlanta's Big Data Kick Off Week Meets HPC

Join HPCwire Editor Nicole Hemsoth and Dr. David Bader from Georgia Tech as they take center stage on opening night at Atlanta's first Big Data Kick Off Week, filmed in front of a live audience. Nicole and David look at the evolution of HPC, today's big data challenges, discuss real world solutions, and reveal their predictions. Exactly what does the future holds for HPC?

Xyratex

HPC Job Bank


Featured Events


HPCwire Events