Loading repository data…
Loading repository data…
takagi / repository
Cl-cuda is a library to use NVIDIA CUDA in Common Lisp programs.
Cl-cuda is a library to use NVIDIA CUDA in Common Lisp programs. It provides not only FFI binding to CUDA driver API but the kernel description language with which users can define CUDA kernel functions in S-expression. The kernel description language also provides facilities to define kernel macros and kernel symbol macros in addition to kernel functions. Cl-cuda's kernel macro and kernel symbol macro offer powerful abstraction that CUDA C itself does not have and provide enormous advantage in resource-limited GPU programming.
Kernel functions defined with the kernel description language can be launched as almost same as ordinal Common Lisp functions except that they must be launched in a CUDA context and followed with grid and block sizes. Kernel functions are compiled and loaded automatically and lazily when they are to be launched for the first time. This process is as following. First, they are compiled into a CUDA C code (.cu file) by cl-cuda. The compiled CUDA C code, then, is compiled into a CUDA kernel module (.ptx file) by NVCC - NVIDIA CUDA Compiler Driver. The obtained kernel module is automatically loaded via CUDA driver API and finally the kernel functions are launched with properly constructed arguments to be passed to CUDA device. Since this process is autonomously managed by the kernel manager, users do not need to handle it for themselves. About the kernel manager, see Kernel manager section.
Memory management is also one of the most important things in GPU programming. Cl-cuda provides memory block data structure which abstract host memory and device memory. With memory block, users do not need to manage host memory and device memory individually for themselves. It lightens their burden on memory management, prevents bugs and keeps code simple. Besides memory block that provides high level abstraction on host and device memory, cl-cuda also offers low level interfaces to handle CFFI pointers and CUDA device pointers directly. With these primitive interfaces, users can choose to gain more flexible memory control than using memory block if needed.
Cl-cuda is verified on several environments. For detail, see Verification environments section.
Following code is a part of vector addition example using cl-cuda based on CUDA SDK's "vectorAdd" sample.
You can define vec-add-kernel kernel function using defkernel macro. In the definition, aref is to refer values stored in an array. set is to store values into an array. block-dim-x, block-idx-x and thread-idx-x have their counterparts in CUDA C's built-in variables and are used to specify the array index to be operated in each CUDA thread.
Once the kernel function is defined, you can launch it as if it is an ordinal Common Lisp function except that it requires to be in a CUDA context and followed by :gird-dim and :block-dim keyword parameters which specify the dimensions of grid and block. To keep a CUDA context, you can use with-cuda macro which has responsibility on initializing CUDA and managing a CUDA context. with-memory-blocks manages memory blocks which abstract host memory area and device memory area, then sync-memory-block copies data stored in a memory block between host and device.
For the whole code, please see examples/vector-add.lisp.
(defkernel vec-add-kernel (void ((a float*) (b float*) (c float*) (n int)))
(let ((i (+ (* block-dim-x block-idx-x) thread-idx-x)))
(if (< i n)
(set (aref c i)
(+ (aref a i) (aref b i))))))
(defun main ()
(let* ((dev-id 0)
(n 1024)
(threads-per-block 256)
(blocks-per-grid (/ n threads-per-block)))
(with-cuda (dev-id)
(with-memory-blocks ((a 'float n)
(b 'float n)
(c 'float n))
(random-init a n)
(random-init b n)
(sync-memory-block a :host-to-device)
(sync-memory-block b :host-to-device)
(vec-add-kernel a b c n
:grid-dim (list blocks-per-grid 1 1)
:block-dim (list threads-per-block 1 1))
(sync-memory-block c :device-to-host)
(verify-result a b c n)))))
You can install cl-cuda via quicklisp.
> (ql:quickload :cl-cuda)
You may encounter the following error, please install CFFI explicitly (ql:quickload :cffi) before loading cl-cuda. Just once is enough.
Component CFFI-GROVEL not found
[Condition of type ASDF/FIND-SYSTEM:MISSING-COMPONENT]
Cl-cuda requires following:
Cl-cuda is verified to work in following environments:
(setf *nvcc-options* (list "-arch=sm_20" "-m32")) neededvector-add example works (didn't try the rest yet)Further information:
(setf *nvcc-options* (list "-arch=sm_20" "-m32")) neededrpmfusion instead of the ones in cuda packageHere explain some APIs commonly used.
WITH-CUDA (dev-id) &body body
Initializes CUDA and keeps a CUDA context during body. dev-id is passed to get-cuda-device function and the device handler returned is passed to create-cuda-context function to create a CUDA context in the expanded form. The results of get-cuda-device and create-cuda-context functions are bound to *cuda-device* and *cuda-context* special variables respectively. The kernel manager unloads before with-cuda exits.
SYNCHRONIZE-CONTEXT
Blocks until a CUDA context has completed all preceding requested tasks.
ALLOC-MEMORY-BLOCK type size
Allocates a memory block to hold size elements of type type and returns it. Actually, linear memory areas are allocated on both host and device memory and a memory block holds pointers to them.
FREE-MEMORY-BLOCK memory-block
Frees memory-block previously allocated by alloc-memory-block. Freeing a memory block twice should cause an error.
WITH-MEMORY-BLOCK (var type size) &body body
WITH-MEMORY-BLOCKS ({(var type size)}*) &body body
Binds var to a memory block allocated using alloc-memory-block applied to the given type and size during body. The memory block is freed using free-memory-block when with-memory-block exits. with-memory-blocks is a plural form of with-memory-block.
SYNC-MEMORY-BLOCK memory-block direction
Copies stored data between host memory and device memory for memory-block. direction is either :host-to-device or :device-to-host which specifies the direction of copying.
MEMORY-BLOCK-AREF memory-block index
Accesses memory-block's element specified by index. Note that the accessed memory area is that on host memory. Use sync-memory-block to synchronize stored data between host memory and device memory.
DEFGLOBAL name type &optional expression qualifiers
Defines a global variable. name is a symbol which is the name of the variable. type is the type of the variable. Optional expression is an expression which initializes the variable. Optional qualifiers is one of or a list of keywords: :device, :constant, :shared, :managed and :restrict, which are corresponding to CUDA C's __device__, __constant__, __shared__, __managed__ and __restrict__ variable qualifiers. If not given, :device is used.
(defglobal pi float 3.14159 :constant)
Accesses a global variable's value on device from host with automatically copying its value from/to device.
(defglobal x :device int 0)
(global-ref x) ; => 0
(setf (global-ref x) 42)
(global-ref x) ; => 42
Specifies the temporary directory in which cl-cuda generates files such as .cu file and .ptx file. The default is "/tmp/".
(setf *tmp-path* "/path/to/tmp/")
Specifies additional command line options passed to nvcc command that cl-cuda calls internally. The default is nil. If -arch=sm_XX option is not specified here, it is automatically inserted with cuDeviceComputeCapability driver API.
(setf *nvcc-options* (list "-arch=sm_20" "-m32"))
Specifies the path to nvcc command so that cl-cuda can call internally. The default is just nvcc.
(setf *nvcc-binary* "/path/to/nvcc")
Specifies whether to let cl-cuda show operational messages or not. The default is t.
(setf *show-messages* nil)
Readonly. The value is t if cl-cuda could not find CUDA SDK or at least it failed to load libcuda for some reason, otherwise nil.
*sdk-not-found* ; => nil
not documented yet.
IF test-form then-form [else-form]
if allows the execution of a form to be dependent on a single test-form. First test-form is evaluated. If the result is true, then then-form is selected; otherwise else-form is selected. Whichever form is selected is then evaluated. If else-form is not provided, does nothing when else-form is selected.
Example:
(if (= a 0)
(return 0)
(return 1))
Compiled:
if (a == 0) {
return 0;
} else {
return 1;
}
LET ({(var init-form)}*) statement*
let declares new variable bindings and set corresponding init-forms to them and execute a series of statements that use these bindings. let performs the bindings in parallel. For sequentially, use let* kernel macro instead.
Example:
(let ((i 0))
(return i))
Compiled:
{
int i = 0;
return i;
}
SYMBOL-MACROLET ({(symbol expansion)}*) statement*
symbol-macrolet establishes symbol expansion rules in the variable environment and execute a series of statements that use these rules. In cl-cuda's compilation process, the symbol macros found in a form are replaces by corresponding expansions.
Example:
(symbol-macrolet ((x 1.0))
(return x))
Compiled:
{
return 1.0;
}