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 --job-name=test_CUDA
#SBATCH --output=slurm-%J.out
#SBATCH --time=0-00:05:00
#SBATCH --partition=shared-gpu-EL7
#SBATCH --ntasks=1
#SBATCH --gres=gpu:pascal:1
#SBATCH --cpus-per-task=1
module load foss/2019a
module load CUDA
srun ./julia 600 600 -2 -2 2 2 -0.4 0.6 2 100
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);
}