C'est une question sur la façon de déterminer la taille de la grille, des blocs et des threads CUDA. Ceci est une question supplémentaire à celle postée ici:
https://stackoverflow.com/a/5643838/1292251
Après ce lien, la réponse de talonmies contient un extrait de code (voir ci-dessous). Je ne comprends pas le commentaire "valeur généralement choisie par le réglage et les contraintes matérielles".
Je n'ai pas trouvé d'explication ou de clarification qui explique cela dans la documentation CUDA. En résumé, ma question est de savoir comment déterminer la taille de bloc optimale (= nombre de threads) à l'aide du code suivant:
const int n = 128 * 1024;
int blocksize = 512; // value usually chosen by tuning and hardware constraints
int nblocks = n / nthreads; // value determine by block size and total work
madd<<<nblocks,blocksize>>>mAdd(A,B,C,n);
BTW, j'ai commencé ma question avec le lien ci-dessus, car il répond en partie à ma première question. Si ce n’est pas un bon moyen de poser des questions sur Stack Overflow, veuillez vous excuser ou me conseiller.
Il y a deux parties à cette réponse (je l'ai écrite). Une partie est facile à quantifier, l'autre est plus empirique.
C'est la partie facile à quantifier. L’annexe F du guide de programmation CUDA actuel contient un certain nombre de limites définitives qui limitent le nombre de threads par bloc pouvant être générés par le lancement d’un noyau. Si vous dépassez ces valeurs, votre noyau ne fonctionnera jamais. Ils peuvent être résumés comme suit:
Si vous restez dans ces limites, tout noyau que vous pourrez compiler avec succès se lancera sans erreur.
C'est la partie empirique. Le nombre de threads par bloc que vous choisissez dans les contraintes matérielles décrites ci-dessus peut affecter les performances du code exécuté sur le matériel. Le comportement de chaque code sera différent et le seul moyen de le quantifier est de procéder à un benchmarking et à un profilage soigneux. Mais encore une fois, très sommairement résumé:
Le deuxième point est un sujet énorme que je doute que quiconque tente d’aborder dans une seule réponse à StackOverflow. Des personnes écrivant des thèses de doctorat sur l'analyse quantitative d'aspects du problème (voir cette présentation de Vasily Volkov de UC Berkley et cet article de Henry Wong de l'Université de Toronto pour des exemples de la complexité de la question).
Au niveau de l’entrée, vous devez surtout savoir que la taille de bloc que vous choisissez (dans la plage des tailles de bloc légales définies par les contraintes ci-dessus) peut avoir un impact sur la vitesse d'exécution du code, mais cela dépend du matériel. vous avez et le code que vous utilisez. En comparant les résultats, vous constaterez probablement que la plupart des codes non triviaux ont un "bonbon" dans la plage des 128 à 512 threads par bloc, mais vous aurez besoin d’une analyse de votre part pour le localiser. La bonne nouvelle est que, comme vous travaillez par multiples de la taille de la chaîne, l'espace de recherche est très limité et la meilleure configuration pour un morceau de code donné relativement facile à trouver.
Les réponses ci-dessus montrent comment la taille du bloc peut avoir un impact sur les performances et suggèrent une heuristique commune pour son choix basée sur la maximisation de l'occupation. Sans vouloir fournir le critère the pour choisir la taille du bloc, il convient de noter que CUDA 6.5 (maintenant dans la version Release Candidate) inclut plusieurs nouvelles fonctions d'exécution facilitant les calculs d'occupation et la configuration de lancement, voir
Astuce CUDA Pro: l’API d’occupation simplifie la configuration du lancement
Une des fonctions utiles est cudaOccupancyMaxPotentialBlockSize
, qui calcule de manière heuristique une taille de bloc atteignant l’occupation maximale. Les valeurs fournies par cette fonction pourraient ensuite être utilisées comme point de départ d’une optimisation manuelle des paramètres de lancement. Voici un petit exemple.
#include <stdio.h>
/************************/
/* TEST KERNEL FUNCTION */
/************************/
__global__ void MyKernel(int *a, int *b, int *c, int N)
{
int idx = threadIdx.x + blockIdx.x * blockDim.x;
if (idx < N) { c[idx] = a[idx] + b[idx]; }
}
/********/
/* MAIN */
/********/
void main()
{
const int N = 1000000;
int blockSize; // The launch configurator returned block size
int minGridSize; // The minimum grid size needed to achieve the maximum occupancy for a full device launch
int gridSize; // The actual grid size needed, based on input size
int* h_vec1 = (int*) malloc(N*sizeof(int));
int* h_vec2 = (int*) malloc(N*sizeof(int));
int* h_vec3 = (int*) malloc(N*sizeof(int));
int* h_vec4 = (int*) malloc(N*sizeof(int));
int* d_vec1; cudaMalloc((void**)&d_vec1, N*sizeof(int));
int* d_vec2; cudaMalloc((void**)&d_vec2, N*sizeof(int));
int* d_vec3; cudaMalloc((void**)&d_vec3, N*sizeof(int));
for (int i=0; i<N; i++) {
h_vec1[i] = 10;
h_vec2[i] = 20;
h_vec4[i] = h_vec1[i] + h_vec2[i];
}
cudaMemcpy(d_vec1, h_vec1, N*sizeof(int), cudaMemcpyHostToDevice);
cudaMemcpy(d_vec2, h_vec2, N*sizeof(int), cudaMemcpyHostToDevice);
float time;
cudaEvent_t start, stop;
cudaEventCreate(&start);
cudaEventCreate(&stop);
cudaEventRecord(start, 0);
cudaOccupancyMaxPotentialBlockSize(&minGridSize, &blockSize, MyKernel, 0, N);
// Round up according to array size
gridSize = (N + blockSize - 1) / blockSize;
cudaEventRecord(stop, 0);
cudaEventSynchronize(stop);
cudaEventElapsedTime(&time, start, stop);
printf("Occupancy calculator elapsed time: %3.3f ms \n", time);
cudaEventRecord(start, 0);
MyKernel<<<gridSize, blockSize>>>(d_vec1, d_vec2, d_vec3, N);
cudaEventRecord(stop, 0);
cudaEventSynchronize(stop);
cudaEventElapsedTime(&time, start, stop);
printf("Kernel elapsed time: %3.3f ms \n", time);
printf("Blocksize %i\n", blockSize);
cudaMemcpy(h_vec3, d_vec3, N*sizeof(int), cudaMemcpyDeviceToHost);
for (int i=0; i<N; i++) {
if (h_vec3[i] != h_vec4[i]) { printf("Error at i = %i! Host = %i; Device = %i\n", i, h_vec4[i], h_vec3[i]); return; };
}
printf("Test passed\n");
}
[~ # ~] éditer [~ # ~]
Le cudaOccupancyMaxPotentialBlockSize
est défini dans le cuda_runtime.h
fichier et est défini comme suit:
template<class T>
__inline__ __Host__ CUDART_DEVICE cudaError_t cudaOccupancyMaxPotentialBlockSize(
int *minGridSize,
int *blockSize,
T func,
size_t dynamicSMemSize = 0,
int blockSizeLimit = 0)
{
return cudaOccupancyMaxPotentialBlockSizeVariableSMem(minGridSize, blockSize, func, __cudaOccupancyB2DHelper(dynamicSMemSize), blockSizeLimit);
}
La signification des paramètres est la suivante
minGridSize = Suggested min grid size to achieve a full machine launch.
blockSize = Suggested block size to achieve maximum occupancy.
func = Kernel function.
dynamicSMemSize = Size of dynamically allocated shared memory. Of course, it is known at runtime before any kernel launch. The size of the statically allocated shared memory is not needed as it is inferred by the properties of func.
blockSizeLimit = Maximum size for each block. In the case of 1D kernels, it can coincide with the number of input elements.
Notez qu'à partir de CUDA 6.5, il est nécessaire de calculer ses propres dimensions de bloc 2D/3D à partir de la taille de bloc 1D suggérée par l'API.
Notez également que l’API du pilote CUDA contient des API équivalentes du point de vue fonctionnel pour le calcul du taux d’occupation. Il est donc possible d’utiliser cuOccupancyMaxPotentialBlockSize
dans le code de l’API du pilote de la même manière que pour l’API d’exécution. au dessus de.
La taille de bloc est généralement sélectionnée pour maximiser "l'occupation". Recherchez CUDA Occupancy pour plus d'informations. En particulier, voir le tableur CUDA Occupancy Calculator.