web-dev-qa-db-fra.com

CUDA déterminant les threads par bloc, blocs par grille

Je suis nouveau dans le paradigme CUDA. Ma question est de déterminer le nombre de threads par bloc et de blocs par grille. Y a-t-il un peu d'art et d'épreuve? Ce que j'ai trouvé, c'est que de nombreux exemples ont un nombre apparemment arbitraire choisi pour ces choses.

J'envisage un problème où je pourrais passer des matrices - de n'importe quelle taille - à une méthode de multiplication. Ainsi, chaque élément de C (comme dans C = A * B) serait calculé par un seul thread. Comment détermineriez-vous les threads/bloc, blocs/grille dans ce cas?

54
dnbwise

En général, vous souhaitez dimensionner vos blocs/grille pour correspondre à vos données et maximiser simultanément l'occupation, c'est-à-dire le nombre de threads actifs en même temps. Les principaux facteurs qui influencent l'occupation sont l'utilisation de la mémoire partagée, l'utilisation des registres et la taille des blocs de threads.

Un GPU compatible CUDA a sa capacité de traitement divisée en SM (multiprocesseurs en streaming), et le nombre de SM dépend de la carte réelle, mais ici nous nous concentrerons sur un seul SM pour plus de simplicité (ils se comportent tous de la même manière). Chaque SM a un nombre fini de registres 32 bits, une mémoire partagée, un nombre maximum de blocs actifs ET un nombre maximum de threads actifs. Ces chiffres dépendent du CC (capacité de calcul) de votre GPU et se trouvent au milieu de l'article Wikipedia http://en.wikipedia.org/wiki/CUDA .

Tout d'abord, la taille de votre bloc de threads doit toujours être un multiple de 32, car les noyaux émettent des instructions dans les warps (32 threads). Par exemple, si vous avez une taille de bloc de 50 threads, le GPU émettra toujours des commandes à 64 threads et vous les gaspillerez.

Deuxièmement, avant de vous soucier de la mémoire partagée et des registres, essayez de dimensionner vos blocs en fonction du nombre maximal de threads et de blocs qui correspondent à la capacité de calcul de votre carte. Parfois, il existe plusieurs façons de le faire ... par exemple, une carte CC 3.0 chaque SM peut avoir 16 blocs actifs et 2048 threads actifs. Cela signifie que si vous avez 128 threads par bloc, vous pouvez insérer 16 blocs dans votre SM avant d'atteindre la limite de 2048 threads. Si vous utilisez 256 threads, vous ne pouvez en installer que 8, mais vous utilisez toujours tous les threads disponibles et aurez toujours une occupation complète. Cependant, l'utilisation de 64 threads par bloc n'utilisera que 1024 threads lorsque la limite de 16 blocs est atteinte, donc seulement 50% d'occupation. Si la mémoire partagée et l'utilisation des registres ne sont pas un goulot d'étranglement, cela devrait être votre principale préoccupation (autre que vos dimensions de données).

Sur le sujet de votre grille ... les blocs de votre grille sont répartis sur les SM pour commencer, puis les blocs restants sont placés dans un pipeline. Les blocs sont déplacés dans les SM pour traitement dès qu'il y a suffisamment de ressources dans ce SM pour prendre le bloc. En d'autres termes, au fur et à mesure que les blocs se terminent dans un SM, de nouveaux sont déplacés. Vous pouvez faire valoir que le fait d'avoir des blocs plus petits (128 au lieu de 256 dans l'exemple précédent) peut se terminer plus rapidement car un bloc particulièrement lent monopolise moins de ressources, mais cela dépend beaucoup du code.

En ce qui concerne les registres et la mémoire partagée, regardez-le ensuite, car cela pourrait limiter votre occupation. La mémoire partagée est limitée pour un SM entier, essayez donc de l'utiliser en quantité permettant à autant de blocs que possible de tenir sur un SM. Il en va de même pour l'utilisation du registre. Encore une fois, ces chiffres dépendent de la capacité de calcul et peuvent être trouvés tabulés sur la page wikipedia. Bonne chance!

87
underpickled

http://developer.download.nvidia.com/compute/cuda/CUDA_Occupancy_calculator.xls

Le calculateur d'occupation CUDA vous permet de calculer l'occupation multiprocesseur d'un GPU par un noyau CUDA donné. L'occupation multiprocesseur est le rapport entre les chaînes actives et le nombre maximal de chaînes prises en charge sur un multiprocesseur du GPU. Chaque multiprocesseur sur le périphérique possède un ensemble de N registres disponibles pour une utilisation par les threads de programme CUDA. Ces registres sont une ressource partagée qui est allouée entre les blocs de threads s'exécutant sur un multiprocesseur. Le compilateur CUDA tente de minimiser l'utilisation des registres pour maximiser le nombre de blocs de threads pouvant être actifs simultanément sur la machine. Si un programme essaie de lancer un noyau pour lequel les registres utilisés par thread fois la taille du bloc de threads est supérieur à N, le lancement échouera ...

19
jmilloy

À de rares exceptions près, vous devez utiliser un nombre constant de threads par bloc. Le nombre de blocs par grille est alors déterminé par la taille du problème, comme les dimensions de la matrice dans le cas de la multiplication matricielle.

Le choix du nombre de threads par bloc est très compliqué. La plupart des algorithmes CUDA admettent un large éventail de possibilités, et le choix est basé sur ce qui rend le noyau plus efficace. Il s'agit presque toujours d'un multiple de 32 et d'au moins 64, en raison du fonctionnement du matériel de planification des threads. Un bon choix pour une première tentative est 128 ou 256.

15
Heatsink

Vous devez également prendre en compte la mémoire partagée car les threads du même bloc peuvent accéder à la même mémoire partagée. Si vous concevez quelque chose qui nécessite beaucoup de mémoire partagée, alors plus de threads par bloc peuvent être avantageux.

Par exemple, en termes de changement de contexte, n'importe quel multiple de 32 fonctionne de la même manière. Donc pour le cas 1D, le lancement de 1 bloc avec 64 threads ou 2 blocs avec 32 threads chacun ne fait aucune différence pour les accès à la mémoire globale. Cependant, si le problème en cause se décompose naturellement en 1 vecteur de longueur 64, la première option sera meilleure (moins de surcharge de mémoire, chaque thread peut accéder à la même mémoire partagée) que la seconde.

3
ely