CUDA

Description

CUDA (Compute Unified Device Architecture) is a parallel computing platform and programming model developed by NVIDIA for processing on GPUs (Graphics Processing Units). It enables significant acceleration for scientific applications, machine learning, and image processing.

Available Versions

  • cuda/12.4

  • cuda/12.9 (default)

Note

CUDA versions are compatible with specific drivers. Check the documentation of the applications you plan to use to choose the appropriate version.

Environment Setup

When loading the CUDA module, several environment variables are defined:

module load cuda/12.9

# Important directories
echo $CUDA_HOME      # Installation directory
echo $CUDA_PATH      # Same as CUDA_HOME
echo $CUDA_ROOT      # CUDA root

# PATH and LD_LIBRARY_PATH are automatically configured
echo $PATH
echo $LD_LIBRARY_PATH

Jobs using GPU must be submitted to the special gpu partition:

#SBATCH --partition=gpu
#SBATCH --gres=gpu:N    # N = number of GPUs desired (1-4)

Compiling CUDA Code

The nvcc compiler is used to compile CUDA code.

compile_cuda.sh
#!/bin/bash
#SBATCH -J compile_cuda
#SBATCH -p gpu
#SBATCH --gres=gpu:1
#SBATCH -N 1
#SBATCH -n 1
#SBATCH -t 00:10:00
#SBATCH --mem=20G

export INPUT="vetor_add.cu"
export OUTPUT="vetor_add"

module load cuda/12.9

job-nanny nvcc -o vetor_add vetor_add.cu

Example CUDA Code

vetor_add.cu
#include <stdio.h>
#include <cuda_runtime.h>

// CUDA Kernel
__global__ void add_vectors(float *a, float *b, float *c, int n) {
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    if (i < n) {
        c[i] = a[i] + b[i];
    }
}

int main() {
    int n = 1000000;
    size_t size = n * sizeof(float);

    // Allocate host memory
    float *h_a = (float*)malloc(size);
    float *h_b = (float*)malloc(size);
    float *h_c = (float*)malloc(size);

    // Initialize data
    for (int i = 0; i < n; i++) {
        h_a[i] = i * 1.0f;
        h_b[i] = i * 2.0f;
    }

    // Allocate device memory
    float *d_a, *d_b, *d_c;
    cudaMalloc(&d_a, size);
    cudaMalloc(&d_b, size);
    cudaMalloc(&d_c, size);

    // Copy data to device
    cudaMemcpy(d_a, h_a, size, cudaMemcpyHostToDevice);
    cudaMemcpy(d_b, h_b, size, cudaMemcpyHostToDevice);

    // Configure and launch kernel
    int threads = 256;
    int blocks = (n + threads - 1) / threads;

    add_vectors<<<blocks, threads>>>(d_a, d_b, d_c, n);
    cudaDeviceSynchronize();

    // Copy result back
    cudaMemcpy(h_c, d_c, size, cudaMemcpyDeviceToHost);

    // Verify result
    printf("c[0] = %f\n", h_c[0]);
    printf("c[%d] = %f\n", n-1, h_c[n-1]);

    // Free memory
    cudaFree(d_a);
    cudaFree(d_b);
    cudaFree(d_c);
    free(h_a);
    free(h_b);
    free(h_c);

    return 0;
}

CUDA Job Submission

submit_cuda.sh
#!/bin/bash
#SBATCH -J cuda_job
#SBATCH -p gpu
#SBATCH --gres=gpu:1
#SBATCH -N 1
#SBATCH -n 1
#SBATCH -t 00:30:00
#SBATCH --mem=40G

export INPUT="vetor_add"
export OUTPUT="cuda_output.txt"

module load cuda/12.9

job-nanny ./vetor_add > cuda_output.txt

Multi-GPU

submit_cuda_multi.sh
#!/bin/bash
#SBATCH -J cuda_multi
#SBATCH -p gpu
#SBATCH --gres=gpu:4
#SBATCH -N 1
#SBATCH -n 4
#SBATCH -t 02:00:00
#SBATCH --mem=32G

export INPUT="programa_multi_gpu"
export OUTPUT="multi_gpu_output/"

module load cuda/12.9

# The program must manage multiple GPUs
job-nanny ./programa_multi_gpu

GPU Information

During execution, you can obtain information about the GPUs:

# In the submission script
nvidia-smi --query-gpu=name,memory.total,memory.free --format=csv

# Check GPUs available for the job
echo "CUDA_VISIBLE_DEVICES=$CUDA_VISIBLE_DEVICES"

# Simple test
deviceQuery

Compiling for Multiple Architectures

# Compile for multiple architectures
nvcc -arch=sm_80 -code=sm_80,sm_86,sm_89 programa.cu -o programa

# Or use the current architecture
nvcc -arch=native programa.cu -o programa

Using CUDA with Python (CuPy)

submit_cupy.sh
#!/bin/bash
#SBATCH -J cupy
#SBATCH -p gpu
#SBATCH --gres=gpu:1
#SBATCH -N 1
#SBATCH -n 1
#SBATCH -t 01:00:00
#SBATCH --mem=8G

export INPUT="cupy_script.py"
export OUTPUT="cupy_output/"

module load cuda/12.9
module load miniconda/24.4.0-libmamba

source activate cupy_env  # Environment with cupy installed

job-nanny python cupy_script.py

Example with CuPy

cupy_script.py
import cupy as cp
import numpy as np
import time

# Create arrays on the GPU
size = 10000
a = cp.random.randn(size, size, dtype=cp.float32)
b = cp.random.randn(size, size, dtype=cp.float32)

# GPU operation
start = time.time()
c = cp.matmul(a, b)
cp.cuda.Stream.null.synchronize()
gpu_time = time.time() - start

print(f"Matrix multiplication {size}x{size} on GPU: {gpu_time:.3f}s")

# CPU comparison
a_cpu = cp.asnumpy(a)
b_cpu = cp.asnumpy(b)

start = time.time()
c_cpu = np.matmul(a_cpu, b_cpu)
cpu_time = time.time() - start

print(f"Multiplication on CPU: {cpu_time:.3f}s")
print(f"Speedup: {cpu_time/gpu_time:.2f}x")

Debugging and Profiling

# Use cuda-gdb for debugging
cuda-gdb ./programa

# Profiling with nvprof (older versions)
nvprof ./programa

# Profiling with Nsight Systems (newer versions)
nsys profile ./programa

# Profiling with Nsight Compute
ncu ./programa

References

See also