Introducing SWIG
An excellent software development tool that connects modules written in C and C++ to a wide variety of high-level programming languages is SWIG which supports Perl, PHP, Python, Tcl, Java, C#, Common Lisp, Octave, R and many more (see www.swig.org/compat.html#SupportedLanguages for more languages.
Here are some links to get you started for three common languages. Check out the web for your favorite if not listed below:
- Java: Use SWIG or the JNI (Java Native Interface). One example project to get you going is JCublas, which makes the CUBLAS library discussed in Part 8 available to Java applications.
- Perl: SWIG is a good place to start as well as the Wikipedia page, although CPAN is the canonical PERL repository.
- Python: An overview of extending Python with C (and hence CUDA) is a good place to start, as is SWIG. A working Python example (that operates on NumPy arrays) is pystream and the related Project GPUlib.
The following is a simple Python example, contributed by a colleague at NVIDIA, which demonstrates the simplicity and speed of calling a CUDA kernel from Python. This example actually implements a useful method for financial applications -- namely matrix exponentiation. Unfortunately, the reasoning behind why such a method is useful is beyond the scope of this article. See the discussion starting on page 19 in the paper at http://arxiv.org/pdf/0710.1606 for more information. Be forewarned, this paper is quite dense.
In the spirit of this article, this example module makes efficient use of the GPU. The reason it performs so well is because this module lets Python programmers call SGEMM, a high flop per data item level-3 BLAS routine in the NVIDIA CUBLAS library. It also demonstrates that it is possible to map variables -- in this case an array -- very efficiently between Python and CUDA.
The full listing for the Python code exponentiationTest.py is:
#! /usr/bin/env python import copy import numpy import FastMatrixExp # Read input matrix using a user defined function a = myInputReader() b = copy.copy(a) steps = 100 # Matrix exponentiation using CPU SGEMM for i in range(steps): a = numpy.dot(a,a) # Matrix exponentiation using CUBLAS SGEMM FastMatrixExp.matrixMulLoop([steps,b]) numpy.testing.assert_array_almost_equal(a, b, decimal = 6) print 'Error = %f' % numpy.linalg.norm(a-b)
Within the exponentiationTest.py, a custom module is imported with the line:
import FastMatrixExp
The reader is required to define its own Python method to input a matrix into variable a, which is then duplicated in variable b for purposes of comparing the speed and accuracy of the CPU and GPU:
# Read input matrix using a user defined function a = myInputReader() b = copy.copy(a)
Matrix a is then raised to the power specified in the variable steps (specifically 100) on the host processor with this code snippet:
steps = 100 # Matrix exponentiation using CPU SGEMM for i in range(steps): a = numpy.dot(a,a)
After which the SGEMM routine from the CUBLAS library is called from Python and utilized on the GPU to perform the matrix exponentiation with the following:
# Matrix exponentiation using CUBLAS SGEMM FastMatrixExp.matrixMulLoop([steps,b])
Both the GPU and CPU generated results are then checked to see if they are equal within a reasonable tolerance via a numpy comparision as seen below. (Numpy is an excellent numerical Python package that has matrix operations.
numpy.testing.assert_array_almost_equal(a, b, decimal = 6) print 'Error = %f' % numpy.linalg.norm(a-b)
The following is the SWIG interface code:
%module FastMatrixExp %header %{ #include <oldnumeric.h> #include <cublas.h> %} %include exception.i /* Matrix multiplication loop for fast matrix exponentiation. */ %typemap(python,in) (int steps, float *u, int n) { $1 = PyInt_AsLong(PyList_GetItem($input,0)); $2 = (float *)(((PyArrayObject *)PyList_GetItem($input,1))->data); $3 = ((PyArrayObject *)PyList_GetItem($input,1))->dimensions[0]; } extern void matrixMulLoop(int steps, float *u, int n); %{ void matrixMulLoop(int steps, float *u, int n) { int i; float *ud; cublasStatus status; /* Allocate memory and copy u to the device. */ status = cublasAlloc(n*n, sizeof(float), (void **)&ud); status = cublasSetMatrix(n, n, sizeof(float), (void *)u,n, (void *)ud, n); /* Do "steps" updates. */ for(i=0; i<steps; i++) cublasSgemm('n','n',n,n,n,1.0f,ud,n,ud,n,0.0f,ud,n); /* Copy u back to the host and free device memory. */ status = cublasGetMatrix(n, n, sizeof(float), (void *)ud,n, (void *)u, n); status = cublasFree((void *)ud); } %} %init %{ import_array(); cublasStatus status; status = cublasInit(); %}
The module name, FastMatrixExp
, is defined in the first line of CUBLAS.i:
#module FastMatrixExp
The iterated calls to cublasSgemm
occur in the following C subroutine, which is defined between the %{
and %}
for SWIG:
%{ void matrixMulLoop(int steps, float *u, int n) { int i; float *ud; cublasStatus status; /* Allocate memory and copy u to the device. */ status = cublasAlloc(n*n, sizeof(float), (void **)&ud); status = cublasSetMatrix(n, n, sizeof(float), (void *)u,n, (void *)ud, n); /* Do "steps" updates. */ for(i=0; i<steps; i++) cublasSgemm('n','n',n,n,n,1.0f,ud,n,ud,n,0.0f,ud,n); /* Copy u back to the host and free device memory. */ status = cublasGetMatrix(n, n, sizeof(float), (void *)ud,n, (void *)u, n); status = cublasFree((void *)ud); } %}
To gain a greater understanding of the remaining parts of the SWIG file, I recommend consulting the SWIG documentation. You can also find out more about SWIG in David Beazley's article SWIG and Automated C/C++ Scripting Extensions, and Daniel Blezek's article Rapid Prototyping with SWIG.
For more advanced numerical packages that combine Python and CUDA, checkout pystream or GPUlib (which can be downloaded after submitting an email request).
- CUDA, Supercomputing for the Masses: Part 11
- CUDA, Supercomputing for the Masses: Part 10
- CUDA, Supercomputing for the Masses: Part 9
- CUDA, Supercomputing for the Masses: Part 8
- CUDA, Supercomputing for the Masses: Part 7
- CUDA, Supercomputing for the Masses: Part 6
- CUDA, Supercomputing for the Masses: Part 5
- CUDA, Supercomputing for the Masses: Part 4
- CUDA, Supercomputing for the Masses: Part 3
- CUDA, Supercomputing for the Masses: Part 2
- CUDA, Supercomputing for the Masses: Part 1
Rob Farber is a senior scientist at Pacific Northwest National Laboratory. He has worked in massively parallel computing at several national laboratories and as co-founder of several startups. He can be reached at [email protected].