Calcul sur GPU

Le problème exposé dans ce sujet a été résolu.

Bonjour,

Je m’essaye à CUDA et au calcul sur GPU. J’avoue avoir un peu de mal encore avec ces histoires de blocs et de grille. Je me suis donc mis à faire l’exercice suivant : pour chaque élément d’une matrice, calculer la moyenne des voisins (en prenant en compte l’élément) et sortir donc la matrice des moyennes, c’est une sorte de flou je crois.

Pour le moment voici mon code :

#include <cuda.h>
#include <math.h>
#include <stdio.h>

#define MATRIX_ROWS (4)
#define MATRIX_COLS (4)

#define CU_THREADS_PER_BLOCK_X    (16)
#define CU_THREADS_PER_BLOCK_Y    (16)


__global__ void kernel_blur_matrix(float *input, float *output, int rows, int cols)
{
    int tid = (threadIdx.x + threadIdx.y * blockDim.x) + (blockDim.x * blockDim.y) * blockIdx.x;
    int size = rows * cols;

    if (tid < size)
    {
        // Compute mean
        int count = 1;
        float mean = input[tid];

        if ((tid - rows - 1) > 0 && (tid - rows - 1) < size) 
        {
            mean += input[tid - rows - 1];
            count++;
        }

        if ((tid - rows) > 0 && (tid - rows) < size)
        {
            mean += input[tid - rows];
            count++;
        }

        if ((tid - rows + 1) > 0 && (tid - rows + 1) < size) 
        {
            mean += input[tid - rows + 1];
            count++;
        }

        if ((tid - 1) > 0 && (tid - 1) < size) 
        {
            mean += input[tid - 1];
            count++;
        }

        if ((tid + 1) > 0 && (tid + 1) < size) 
        {
            mean += input[tid + 1];
            count++;
        }

        if ((tid + rows - 1) > 0 && (tid + rows - 1) < size) 
        {
            mean += input[tid + rows - 1];
            count++;
        }

        if ((tid + rows) > 0 && (tid + rows) < size) 
        {
            mean += input[tid + rows];
            count++;
        }

        if ((tid + rows + 1) > 0 && (tid + rows + 1) < size) 
        {
            mean += input[tid + rows + 1];
            count++;
        }

        mean /= count;
        output[tid] = mean;
    }
}

int main()
{
    // Host initializations
    unsigned int size = MATRIX_COLS * MATRIX_ROWS * sizeof(float);
    float *input = (float*)malloc(size);
    float *output = (float*)malloc(size);

    // a faire directement sur GPU
    for (int i = 0; i < MATRIX_COLS * MATRIX_ROWS; ++i) 
    {
        input[i] = i;
    }

    // Device initializations
    float *d_input, *d_output;
    cudaMalloc(&d_input, size);
    cudaMalloc(&d_output, size);

    cudaMemcpy(d_input, input, size, cudaMemcpyHostToDevice);

    dim3 grid_dim(ceil((float)(MATRIX_COLS * MATRIX_ROWS) / (CU_THREADS_PER_BLOCK_X * CU_THREADS_PER_BLOCK_Y)), 1, 1);
    dim3 block_dim(CU_THREADS_PER_BLOCK_X, CU_THREADS_PER_BLOCK_Y, 1);

    kernel_blur_matrix<<<grid_dim, block_dim>>>(d_input, d_output, MATRIX_ROWS, MATRIX_COLS);

    cudaDeviceSynchronize();
    cudaMemcpy(output, d_output, size, cudaMemcpyDeviceToHost);

    for (int i = 0; i < MATRIX_ROWS; ++i)
    {
        for (int j = 0; j < MATRIX_COLS; ++j) 
            printf("%8.2f  ", input[j + i * MATRIX_ROWS]);
        printf("\n");
    }

    printf("\n\n");

    for (int i = 0; i < MATRIX_ROWS; ++i)
    {
        for (int j = 0; j < MATRIX_COLS; ++j) 
            printf("%8.2f  ", output[j + i * MATRIX_ROWS]);
        printf("\n");
    }    
    
    cudaFree(d_input);
    cudaFree(d_output);

    free(input);
    free(output);

    return 0;
}

Première chose, ça ne fonctionne pas. Par exemple, sur cette entrée :

    0.00      1.00      2.00      3.00  
    4.00      5.00      6.00      7.00  
    8.00      9.00     10.00     11.00  
   12.00     13.00     14.00     15.00

Le programme me sort:

    2.60      3.60      4.00      5.00  
    5.29      5.62      6.00      7.00  
    8.00      9.00     10.00     10.38  
   10.71     11.00     12.00     12.40

La dernière ligne devrait être :

   10.50     11.00     12.00     12.50

Les résultats sont proches mais incorrects. Est-ce du à une la gestion des flottants ? Ça m’étonnerai mais bon.

Deuxième chose, j’ai l’intuition que je peux remplacer ma suite de if par deux boucles for imbriqués, mais après avoir essayé pendant deux heures, j’ai abandonné. Est-ce possible ?

Merci pour votre aide. :)

Deuxième chose, j’ai l’intuition que je peux remplacer ma suite de if par deux boucles for imbriqués, mais après avoir essayé pendant deux heures, j’ai abandonné. Est-ce possible ?

J’ai pas testé ou réfléchi très fort, mais en l’état j’essaierais quelque chose comme:

for (r = -rows; r <= rows; r += rows) {
  for (c = -1; c <= 1; ++c)
    t[tid + r + c]...
  }
}

Au sujet du fait que ton code n’est pas correct: je n’arrive pas à me convaincre que tes tests sur les bornes sont corrects, à cause du fait que tu projettes un tableau 2D vers un tableau 1D. Si on veut itérer sur les voisins de t[a][b], on itère sur un espace (di,dj) tel que 0 <= a+di < dima et 0 <= b+dj < dimb. Toi tu projettes (a+di,b+dj) dans un espace 1D, et tu testes sur les bornes du tableau sont correctes en 1D. C’est peut-être bon et un truc classique (je ne me suis jamais posé la question), mais mon intuition sans avoir trop réfléchi serait que c’est faux, il y a des cas où tu "débordes" sur une mauvaise ligne/colonne qui est toujours dans le tableau (donc passe ton teste de bornes 1D). }

J’ai pas testé ou réfléchi très fort, mais en l’état j’essaierais quelque chose comme:

for (r = -rows; r <= rows; r += rows) {
  for (c = -1; c <= 1; ++c)
    t[tid + r + c]...
  }
}

gasche

C’est à peu près ce que j’avais essayé de faire mais je rencontre toujours le même problème, il y a des éléments que je prend deux fois en compte. Si on prend par exemple tid = 3 et rows = 2:

r = -2
c = 1
=> tid + r + c = 2

r = 0
c = -1
=> tid + r + c = 2

Je t’avoue que je n’ai pas trop compris ce que tu veux dire par "passer mes tests de bornes en 1D" :-°

Voici la façon naturelle de faire ce que tu fais pour un programme C (sans GPU et compagnie):

double window_average(double t[][], int xlen, int ylen, int x0, int y0) {
  double total = 0;
  int count = 0;
  for (int dx = -1; dx <= 1; ++dx) {
    for (int dy = -1; dy <= 1; ++dy) {
      int x = x0 + dx;
      int y = y0 + dy;
      if (0 <= x && x < xlen && 0 <= y && y < ylen) {
        total += t[x][y];
        ++count;
      }
  }
  return (total / count);
}

Ici les tests de sortie de bornes du tableau sont simples et clairs. Toi tu travailles avec une représentation moche où au lieu d’avoir t[x][y] tu as un tableau à une seule dimension et tu utilises t[x*ylen + y]. Au lieu de tester si 0 <= x < xlen && 0 <= y < ylen, tu testes si 0 <= x*ylen + y < xlen*ylen + ylen, et je pense que les deux ne sont pas équivalents.

+1 -0

Salut !

J’avais regardé pour faire des tableaux à deux dimensions sur CUDA et la documentation le déconseille, ils recommandent de mapper vers des tableaux à une dimension.

J’ai donc repris mon problème de 0 en pensant à ce que tu m’avais dit plus tôt, à projeter les matrices d’un espace à l’autre. Et en fait c’est là qu’était ma solution aha ! J’utilise toujours des tableaux à une dimension, sauf que je les projette à deux dimensions sur le système de blocs et de grille de CUDA. Ce qui fait que lorsque je calcule sur GPU, j’ai bien deux dimensions ! Voici mon code, fonctionnel (certainement pas optimisé, je n’en suis pas encore là, mais déjà beaucoup plus rapide que sur CPU) :

#include <cuda.h>
#include <math.h>
#include <stdio.h>

#define MATRIX_ROWS (500)
#define MATRIX_COLS (500)

#define CU_THREADS_PER_BLOCK_X    (16)
#define CU_THREADS_PER_BLOCK_Y    (16)


__global__ void kernel_blur_matrix(float *input, float *output, int rows, int cols)
{
    int tidx = blockDim.x * blockIdx.x + threadIdx.x;
    int tidy = blockDim.y * blockIdx.y + threadIdx.y;

    if (tidx < cols && tidy < rows)
    {
        int count = 0;
        float mean = 0.0f;

        for (int i = -1; i <= 1; ++i)
        {
            for (int j = -1; j <= 1; ++j)
            {
                int pos_x = tidx + i;
                int pos_y = tidy + j;
             
                if (pos_x  < 0 || pos_x >= cols || pos_y < 0 || pos_y >= rows)
                    continue;
                
                mean += input[pos_y * cols + pos_x];
                ++count;
            }
        }

        mean /= count;
        output[tidy * cols + tidx] = mean;
    }
}

int main()
{
    // Host initializations
    unsigned int size = MATRIX_COLS * MATRIX_ROWS * sizeof(float);
    float *input = (float*)malloc(size);
    float *output = (float*)malloc(size);

    for (int i = 0; i < MATRIX_COLS * MATRIX_ROWS; ++i) 
    {
        input[i] = i;
    }

    // Device initializations
    float *d_input, *d_output;
    cudaMalloc(&d_input, size);
    cudaMalloc(&d_output, size);

    cudaMemcpy(d_input, input, size, cudaMemcpyHostToDevice);

    int block_x = ceil((1 + MATRIX_ROWS - MATRIX_ROWS % CU_THREADS_PER_BLOCK_X) / (float)CU_THREADS_PER_BLOCK_X);
    int block_y = ceil((1 + MATRIX_COLS - MATRIX_COLS % CU_THREADS_PER_BLOCK_Y) / (float)CU_THREADS_PER_BLOCK_Y);
    dim3 grid_dim(block_x, block_y, 1);
    dim3 block_dim(CU_THREADS_PER_BLOCK_X, CU_THREADS_PER_BLOCK_Y, 1);

    printf("Griddim(%d, %d), Blockdim(%d, %d)\n", grid_dim.x, grid_dim.y, block_dim.x, block_dim.y);

    kernel_blur_matrix<<<grid_dim, block_dim>>>(d_input, d_output, MATRIX_ROWS, MATRIX_COLS);

    cudaDeviceSynchronize();
    cudaMemcpy(output, d_output, size, cudaMemcpyDeviceToHost);

    // for (int i = 0; i < MATRIX_ROWS; ++i)
    // {
    //     for (int j = 0; j < MATRIX_COLS; ++j) 
    //         printf("%8.2f  ", input[j + i * MATRIX_COLS]);
    //     printf("\n");
    // }

    // printf("\n\n");

    // for (int i = 0; i < MATRIX_ROWS; ++i)
    // {
    //     for (int j = 0; j < MATRIX_COLS; ++j) 
    //         printf("%8.2f  ", output[j + i * MATRIX_COLS]);
    //     printf("\n");
    // }    
    
    cudaFree(d_input);
    cudaFree(d_output);

    free(input);
    free(output);

    return 0;
}

Merci encore pour ton aide précieuse !

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte