Exemples complets simples de programmation sur GPU avec CUDA

Compilation :

nvcc programme.cu -std=c++11 -o programme

Il faut en plus charger le module CUDA

Exemple de script de soumission de tâche GPU :

#!/bin/sh
#SBATCH -J testcuda
#SBATCH -o testcuda.o%j
#SBATCH --ntasks=1
#SBATCH -p shared-gpu
#SBATCH -t 00:10:00
#SBATCH --gres=gpu:pascal:1

echo $SLURM_NODELIST

srun ./testcuda

Addition de deux vecteurs

#include "stdio.h"
#include <iostream>
#include <vector>
#include <cuda_runtime.h>
 
// Handle CUDA error messages
inline void CUDA_HandleError(cudaError_t err, const char *file, int line)
{
  if (err != cudaSuccess)
  {
    std::cout << cudaGetErrorString(err) << " in " << file << " at line " << line << std::endl;
    exit(EXIT_FAILURE);
  }
}
#define CUDA_HANDLE_ERROR( err ) (CUDA_HandleError( err, __FILE__, __LINE__ ))
 
// fonction kernel s'executant sur le device
__global__ void VecAdd(float* A, float* B, float* C, int N)
{
  int i = blockDim.x * blockIdx.x + threadIdx.x;
  if (i < N)
    C[i] = A[i] + B[i];
}
 
// affichage d'un vecteur
void printVec(std::vector<float> &vec){
    for(auto e : vec) std::cout << e << ", ";
    std::cout << std::endl;
}
 
// code host
int main()
{
  int N = 10;
  size_t size = N * sizeof(float);
 
  // allocation de la memoire sur le host
  std::vector<float> h_A(N, 1.0);
  std::vector<float> h_B(N, 2.0);
  std::vector<float> h_C(N, -1.0);
 
  std::cout << "h_C avant execution kernel : " << std::endl;
  printVec(h_C);
 
  // allocation de la memoire sur le device
  float* d_A;
  CUDA_HANDLE_ERROR( cudaMalloc(&d_A, size) );
  float* d_B;
  cudaMalloc(&d_B, size);
  float* d_C;
  cudaMalloc(&d_C, size);
 
  // poie des vecteur h_A et h_B sur les espaces memoire du device d_A et d_B
  cudaMemcpy(d_A, h_A.data(), size, cudaMemcpyHostToDevice);
  cudaMemcpy(d_B, h_B.data(), size, cudaMemcpyHostToDevice);
 
  // preparation de la configuration d'execution
  int threadsPerBlock = 256;
  int blocksPerGrid = (N + threadsPerBlock - 1) / threadsPerBlock;
  // execution du kernel
  VecAdd<<<blocksPerGrid, threadsPerBlock>>>(d_A, d_B, d_C, N);
  // attendre que le kernel se termine et recuperer la derniere erreur
  CUDA_HANDLE_ERROR( cudaPeekAtLastError() );
  CUDA_HANDLE_ERROR( cudaDeviceSynchronize() );
 
  // copie des donnees du device vers le host (d_C vers h_C)
  cudaMemcpy(h_C.data(), d_C, size, cudaMemcpyDeviceToHost);
 
  std::cout << "h_C apres execution kernel : " << std::endl;
  printVec(h_C);
 
  // liberation de la memoire sur le device
  cudaFree(d_A);
  cudaFree(d_B);
  cudaFree(d_C);
}

Addition d'une constante sur une matrice

#include "stdio.h"
#include <iostream>
#include "Array2D.hpp"
#include <cuda_runtime.h>
 
// fonction s'executant sur le device
// le prefixe __device__ signifie que cette fonction ne peut etre
// appelee que depuis une fonction s'executant sur le device
__device__ int dostuff(int b){
    return b*b;
}
 
// code kernel s'executant sur le device
// le prefixe __global__ signifie que la fonction peut etre appelee depuis le host
__global__ void matadd(int* A, int2 size, int a )
{
    int2 pos = make_int2(blockDim.x * blockIdx.x + threadIdx.x, blockDim.y * blockIdx.y + threadIdx.y);
    if (pos.x < size.x && pos.y < size.y)
        A[pos.y*size.x+pos.x] += dostuff(a);
}
 
// affichage d'un Array2D
void printArray(Array2D<int>& a){
    for(int y=0; y<a.sizeY(); y++){
        for(int x=0; x<a.sizeX(); x++){
            std::cout << a(x, y) << ", ";
        }
        std::cout << std::endl;
    }
}
 
// Host code
int main(int argc, char** argv)
{
    int2 size = make_int2(30, 30);
 
    // allocation de la memoire sur le host
    Array2D<int> h_A(size.x, size.y, 1);
 
    std::cout << "before : " << std::endl;
    printArray(h_A);
    std::cout << "====================" << std::endl;
 
    // allocation de la memoire sur le device
    int* d_A;
    cudaMalloc(&d_A, size.x*size.y*sizeof(int));
    // copie des donnees du host vers le device
    cudaMemcpy(d_A, h_A.data(), size.x*size.y*sizeof(int), cudaMemcpyHostToDevice);
 
    // preparation de la configuration d'execution
    dim3 dimBlock(16, 16);
    dim3 dimGrid((size.x + dimBlock.x - 1) / dimBlock.x, (size.y + dimBlock.y - 1) / dimBlock.y);
    std::cout << "dimGrid : " << dimGrid.x << ", " << dimGrid.y << std::endl;
    // execution du kernel
    matadd<<<dimGrid, dimBlock>>>(d_A, size, 3);
 
    // copie des donnees du device vers le host
    cudaMemcpy(h_A.data(), d_A, size.x*size.y*sizeof(int), cudaMemcpyDeviceToHost);
 
    std::cout << "after : " << std::endl;
    printArray(h_A);
    std::cout << "====================" << std::endl;
 
    // liberation de la memoire du device
    cudaFree(d_A);
}