Cuda détermination des threads par bloc, blocs par grille

Je suis nouveau dans le paradigme CUDA. Ma question consiste à déterminer le nombre de threads par bloc et de blocs par grille. Est-ce qu'un peu d'art et de procès jouent dans cela? 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 serais capable de passer des matrices - de n'importe quelle taille - à une méthode de multiplication. De sorte que, chaque élément de C (comme dans C = A * B) serait calculé par un seul thread. Comment voulez-vous déterminer la threads / bloc, blocs / grille dans ce cas?

45
demandé sur dnbwise 2010-12-08 21:58:53

4 réponses

En général, vous voulez 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 influençant l'occupation sont l'utilisation de la mémoire partagée, l'utilisation du registre et la taille du bloc de thread.

Un GPU compatible CUDA a sa capacité de traitement divisée en SMs (multiprocesseurs de streaming), et le nombre de SMs dépend de la carte réelle, mais ici nous allons nous concentrer sur un seul SM pour plus de simplicité (ils se comportent tous le même). Chaque SM a un nombre fini de registres 32 bits, de mémoire partagée, un nombre maximum de blocs actifs et un nombre maximum de threads actifs. Ces chiffres dépendent du CC (compute capability) 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 thread 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 maximum de threads et de blocs qui correspondent à la capacité de calcul de votre carte. Parfois, il y a 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 votre SM avant d'atteindre la limite de fil 2048. Si vous utilisez 256 threads, vous ne pouvez adapter 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 l'utilisation de la mémoire partagée et du registre n'est 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 le SMs pour commencer, puis les blocs restants sont placés dans un pipeline. Les blocs sont déplacés dans le SMs pour le traitement dès qu'il y a suffisamment de ressources dans ce SM pour prendre le bloc. En d'autres termes, lorsque 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 monopolisera moins de ressources, mais cela dépend beaucoup du code.

En ce qui concerne les registres et la mémoire partagée, regardez cela ensuite, car cela peut limiter votre occupation. La mémoire partagée est finie pour un SM entier, alors essayez de l'utiliser dans une quantité qui permet à autant de blocs que possible de tenir encore sur un SM. La même chose vaut 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!

72
répondu underpickled 2012-10-16 19:11:13

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

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

18
répondu jmilloy 2016-10-06 11:32:29

À 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, telle que les dimensions de la matrice dans le cas de la multiplication matricielle.

Choisir le 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 le plus efficace. Il est presque toujours un multiple de 32, et au moins 64, à cause de la façon dont le matériel de planification des threads fonctionne. Un bon choix pour une première tentative est 128 ou 256.

15
répondu Heatsink 2010-12-08 19:20:54

Vous devez également considérer 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, plus de threads par bloc peut être avantageux.

Par exemple, en termes de changement de contexte, tout multiple de 32 fonctionne de la même manière. Donc, pour le cas 1D, lancer 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 à portée de main se décompose naturellement en 1 Longueur-vecteur 64, alors 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
répondu ely 2011-11-08 20:03:28