| Date | Description |
| 11-Oct-15 | Original posting |
Your assignment is to optimize matrix multiplication for NVIDIA's Kepler GPU. You'll measure and report observed performance on Sorken. For development, you may also use the Stampede system located at TACC, but note that, due to differences in hardware capability (3.7 on Sorken, 3.5 on TACC), performance may differ.
The assignment includes a report (5-10 pages long, including figures), which is important part of the assignment. The requirements for your report are described later in this document.
All assignments will be turned in using UCSD's GitLab and the instructions are identical for the first assignment. The turnin procedure is described below in the section "Turning in your assignment". In this section, we'll go over how to set up your repository.
You must follow these steps to get the starter code and to get set up to do assignment 2
Refer to the link below for instructions on how to obtain the starter code to get started on your assignment
A2 Git Instructions
Try to make your code go as fast as you can. In the spirit of friendly competition, post your performance results to the HW2 Piazza folder. We will post a summary of the class' results on-line.
Your grade will based on 4 factors: performance attained by your implementation; correctness, well-displayed results and comments and explanation and insight into how you realized your reported performance. In determining your grade for the assignment, explanations and insight will be emphasized somewhat more heavily than performance itself, unless there was little improvement in performance over the provided naive code.
Your performance goal is 360 Gigaflops/second on a matrix of size 1024×1024 (double precision).
To realize your performance goals, explore the optimizations in this SC '08 paper by Volkov and Demmel: Benchmarking GPUs to tune dense linear algebra. Since the implementation discussed in the paper was designed for a pre-Kepler device, the required optimizations (or the details, i.e. tuning parameters) may differ for Kepler.
n and thread block geometry.
The provided verification code, which resides in
genMatrix.cpp, will enable you to
establish correctness.
This code verifies a
matrix product that can be expressed in closed form
and depends on a particular matrix set up in the
matrix initialization routine (genMatrix()).
Specific report requirements.
Your report must present certain measurements,
but feel free to add others if they illuminate your understanding
of high performance matrix multiplication on Kepler.
For the problem sizes mentioned in part 3 (n=256, 512, 1024 and 2048),
plot the
speedup of your optimized algorithm against the provided
naive code and your own the provided (BLAS) multicore version from Assignment #1.
For each GPU data point, use the optimal blocking factor,
and report the blocking factors you used in a table,
along with the GigaFLOP rates for the naive your optimized implementation and the
multicore implementation.
When more than one blocking factor
led to the same performance (differing less than 5%), identify all
thread block factors (factors) that yielded that performance.
For n=1024, plot performance (GigaFLOPS/sec) as a function of the
thread block geometry (shape), noting the optimal value.
Is there a "sweet spot" (or spots)
that minimizes the running time?
Explain how the optimal value of the thread blocking factor relates to performance
mmpy_kernel.cu, the CUDA kernel that
implements matrix multiplication, and setGrid.cu, where you can set the block and grid configuration.
We've provided you with starter code that implements the naïve CUDA variant of matrix multiplication discussed in the Hwu and Kirk text. The naive implementation performs poorly, realizing only about 15 GFlops/second It makes all memory accesses through global memory, which is inefficient. Your implementation will run significantly faster. The provided code will report various information about your device and is available via the course's gitlab repository.
The provided Makefile sets up the appropriate flags for the compiler via an included "arch" file located in $(PUB)/Arch.
The starter code enables you to input the thread block geometry via two command line flags: -x and -y.
However, in your optimized code, these flags are not used;
you must statically compile in the geometry using make
command line parameters, i.e. for a 16 x 8 tile:
make bx=16 by=8
This comes as a consequence
of constraints on how shared memory arrays are defined.
When changing block geometry, be sure to do a make clean first.
Using these flags sets two C pre-processor macros, BLOCKDIM_X and BLOCKDIM_Y, which are included in the provide code and the Makefile.
You'll notice that the code uses the datatype _DOUBLE_ for
most floating point data types, including the matrices.
This datatype is a generic type for floating point data
and is set using the C pre-processor macro _DOUBLE_.
If you use this generic type you can conveniently
compile in either single precision or double precision floating point
via a make command line argument, which will enable you
to explore the impact, on performance, of numerical precision.
This is optional, but if you are interested,
you can compile with
single precision
by specifying the following argument on the make command line:
single=1
This setting defines the _DOUBLE_ macro in types.h
so that all
instances of _DOUBLE_ will expand to
the built-in type float. Be sure to use the generic type specifier
_DOUBLE_ wherever you need to specify the built-in floating point type
double or float (i.e. declarations or type casts).
If you don't specify single=1,
the type of _DOUBLE will be set to double precision, i.e. double. Any module that uses the generic type must include the header file types.h.
When changing floating point precision, don't forget to do a make clean before
rebuilding the code. Otherwise your code will be compiled with inconsistent floating point precision, resulting in loader errors.
To build the starter code, be sure that your environment has been set up as explained in Lab document.
Both Sorken and Stampede support interactive and non-interactive access. Work on Sorken unless the machine becomes crowded. (Stampede's interactive queues can be busy, but job turnaround is usually good.) As noted above, the devices on the two systems have different compute capabilities (CC), so performance tuning and optimizations can be different on the two machines, for example, the optimal thread block size. Sorken and other 3.7 devices provide 128K 32-bit registers per multiprocessor, as opposed to 64K for Stampede and other 3.5 CC devices. CC 3.7 devices also have 112KB of shared memory per multiprocessor, which is more than 3.5 CC devices. There are other differences as well, such as bandwidth to main memory. Consult these two documents if you are curious and wish to explain performance differences on the two machines:
Due to CC differences, your final performance results will need to be collected on Sorken.
To run interactively on either Stampede or Sorken use the handy alias set up for you when you added the provided aliases to your .bashrc. It is not possible to run GPU jobs on a login node; any attempt will result in and error message reported by the provided code.
Be courteous to other users by using a GPU node for computation. Keep a second window open to the login node so that you can edit and compile there. You can always tell when you using a login node because the host name will begin with the word "login" as in
$ hostname sorken.ucsd.edu login2.stampede.tacc.utexas.edu
When you access a compute node in Sorken, the host name will be different, e.g.
$hostname compute-0-0.local c442-804.stampede.tacc.utexas.edu
The code enables you to repeatedly execute matrix multiplication via the -r flag (See cmdLine.c, which processes command line flags and arguments).
Adjust the repetition count to limit the running times to about 5 seconds.
A value of 50 will probably be sufficient, but a suitable value depends on the matrix size, as the running time varies is O(N3).
Use the non-interactive batch queues to collect timings. Use the interactive queues (when available) to tune your code's performance.
The provided code includes timer calls, which are based on
gettimeofday().
For any given combination of N and optimization (e.g. thread block size), take at least 3 timings and report the minimum.
Note that, as discussed in class,
it is important to invoke cudaThreadSynchronize() to correctly
time the kernel launch.
Your timings on GPUs should be highly reproducible, since you have sole access to the device when running on it. However, since Sorken packs 4 GPUs into a node, and all share the same host memory, you may see noticeable variations in external memory transfer times. For this reason, it's important not to time any external memory transfers.
It is possible to
take timings on the device only.
This will ensure that inconsistent host/device transfer times
won't taint your results on the CSEClass workstations, if someone
happens to be working there as well (But note that, CUDA doesn't handle space sharing on a GPU, and results involving 2 or more users may be inconsistent).
On a non-dedicated resource, it may be helpful to use the CUDA timer.
This timer is
enabled by adding the following line
to your Makefile command line
cuda_timer=1
The effect will be to define the CUDA_TIMER macro when compiling your code.
(When using a Make command line change, don't
forget to do a make clean before
rebuilding the code. Otherwise your code won't be compiled consistently, resulting in anomalous behavior).
GPU timers are discussed in CUDA C Best Practices Guide, §8.1.2. (This documentation is for version CUDA version 7.5 but the discussion is valid for 7.0)
CUDA provides profiling tools, which are discussed in the Profiler's User's Guide. You have a choice of command line or visual interface.
If using the visual interface, be sure to follow instructions at
http://cseweb.ucsd.edu/classes/fa15/cse260-a/lab.html for how to set up the remote display in ssh.
The command line tool is called nvprof, and
may be run within a batch job.
Look at the instructions for the Command line profiler in § 3 of the
Profiler User's Guide.
To use the profiler take the following steps.
export COMPUTE_PROFILE=1 export COMPUTE_PROFILE_LOG=~/nvprof.txt export COMPUTE_PROFILE_CONFIG=~/nvprof_config.txt
~/nvprof_config.txt
gpustarttimestamp gridsize3d threadblocksize active_warps active_cycles
make clean; make debug=1 ./mmpy -n 1024 -x 32 -y 32 -r 1 -R
~/nvprof.txt
When you have completed the assignment, you must submit it. You will do this by pushing your changes to GitLab. Follow these steps (which are more or less the same as the steps from the "pushing changes to GitLab" section above:
Some of the finer points about this process:
Even though we will be substituting fresh copies for all but the required files, include all code files your worked with, in case we need to unravel a problem with your turnin. For this reason, also include your Makefile.
Your turnin must also fill in two important documents, which accompany the provided code. These documents are required and we will not report a grade for your assignment without them. A 10% penalty will be assessed in cases of missing forms, empty forms, or forms not properly filled out.
This file must have the following format.
The stem part of the file name (before the .pdf)
should begin with "A2-report" and end with your repository name, e.g.
A1-report-baden-powell.pdf.
Document your work in a well-written, two to three page report, including plots, which presents your results, analyzes them, and offers insight. Your report should present a clear evaluation of the design of your code, including bottlenecks of the implementation. If you improved performance of your code in a sequence of steps, document the process. If you like, summarize the information in a list or a table in your report. If necessary, provide pseudo code to illustrate the concepts. Be sure to thoroughly explain the results presented in your plots, relating them to how you designed the code. Discuss the design decisions you made to improve performance, including any negative results.
An important part of your report is your analysis. That is, to provide insight that explains your results. For this reason, if you are running out of time, put more effort into the writeup and your analysis rather than trying yet another performance enhancement. It can take time to collect, tabulate and plot results, as well as offer a reasonable explanation. We prefer a deeper analysis involving a few program optimizations over a shallower analysis over a larger number of optimizations. Spend more time discussing the optimizations that made a difference and less on those that made less of a difference. Negative results are also especially valued if you carefully document them, with the underlying causes, or best guesses if the causes are unclear.
Do not include full code listings in your writeup as these will be in your turnin.
Take care in how you plot your data, plotting only what's necessary to get your points across. Choose log and linear plots carefully, to take advantage of the plotting space. You must also include any plotted data in tabular form. Tabular data may be included electronically (in your turnin) as a spreadsheet or other document called, e.g. Data.xlsx, Data.txt. Be sure to provide a caption for each plot or table that describes the experiment you conducted, e.g. "strong scaling."
Cite any written works or software as appropriate in the text of the report. Citations should include any software you used that was written by someone else, or that was written by you for purposes other than this class. In such cases you must talk with us (and any code co-authors) prior to get approval.
There are 10 possible points on the assignment and your grade will depend on 4 factors: performance attained by your code, correctness, well-displayed results and comments. You must not only put comments inside your code, but every time you commit to your repository. There should be a steady stream of comments that enable us to retrace your development steps.
Your performance score is divided into two parts. You'll get the bulk of those points for reaching the slightly lower performance goal of 300 Gflops on the 1024 × 1024 problem, but to get all points on performance, you must realize 360 GFlops.
We will grade your code (on Sorken) using our own Makefile.
Our Makefile will compile your code using nvcc gcc and
and then link it with the test driver.
We will use
the same compilation flags as in the provided starter code.
To get full credit, design, style and comments (including gitlab commit comments)
count.
Don't wait until the last minute to attempt a turnin for the first time. Try out the turnin process early on so you are sure that you you are familiar with the process, and that all the required additional files are present.
You will receive 0 points for this assignment if your code does not compile, including cases of non-compilation due to merge conflicts. If you developed your code on another machine you are responsible for properly testing on bang, else you may lose significant credit, possibly all 10 points depending on the circumstances. As mentioned previously, you must ensure that you resolve all of your merge conflicts and that your code compiles and runs correctly IMMEDIATELY BEFORE you push to GitLab.
For this assignment staff will attempt intervene manually to fix problems. However, the policy for such manual intervention is as follows. If any manual intervention is required by staff you will lose at least 2 points. Included are: incorrectly naming your GitLab repository, or misspelling the words FINAL SUBMISSION in the commit message. Lastly, the only files you should be pushing to GitLab are text files: source code and ASCII test files. Do not push large binaries such as the executables or media files, as doing so could lead to problems such as losing the ability to push to GitLab. Staff will not intervene under these circumstances and you you may be unable to complete your assignment by the deadline. If you commit binaries, you are doing so at your own risk.
Questions will be to this section as the need arises.
While Nvidia posts a considerable amount of information on its web site,
the documentation is for the latest version of CUDA--v7.5--while we are
using an earlier version--v7.0. Much of the documentation is similar.
However, you may access the version 7 documents on Sorken at the following location: /usr/local/cuda-7.0/doc .
The main documents you will be interested in are the following,
which may all be accessed via NVIDIA's CUDA Toolkit Documentation web pages,
located at http://docs.nvidia.com/cuda/index.html#:
[Sun Oct 11 22:09:49 PDT 2015]