quand l'appel à la fonction cudaDeviceSynchronize
est-il vraiment nécessaire?.
D'après ce que je comprends de la documentation CUDA, les noyaux CUDA sont asynchrones, il semble donc que nous devrions appeler cudaDeviceSynchronize
après chaque lancement du noyau. Cependant, j'ai essayé le même code (entraînement des réseaux de neurones) avec et sans cudaDeviceSynchronize
, sauf un avant la mesure du temps. J'ai trouvé que j'obtiens le même résultat mais avec une vitesse entre 7-12x (selon les tailles de matrice).
Donc, la question est de savoir s'il existe des raisons d'utiliser cudaDeviceSynchronize
en dehors de la mesure du temps.
Par exemple:
Est-il nécessaire avant de recopier les données du GPU vers l'hôte avec cudaMemcpy
?
Si je fais des multiplications matricielles comme
C = A * B
D = C * F
dois-je mettre cudaDeviceSynchronize
entre les deux?
D'après mon expérience, il semble que non.
Pourquoi cudaDeviceSynchronize
ralentit-il tant le programme?
Bien que les lancements du noyau CUDA soient asynchrones, toutes les tâches liées au GPU placées dans un flux (qui est le comportement par défaut) sont exécutées séquentiellement.
Ainsi, par exemple,
kernel1<<<X,Y>>>(...); // kernel start execution, CPU continues to next statement
kernel2<<<X,Y>>>(...); // kernel is placed in queue and will start after kernel1 finishes, CPU continues to next statement
cudaMemcpy(...); // CPU blocks until memory is copied, memory copy starts only after kernel2 finishes
Donc dans votre exemple, il n'y a pas besoin de cudaDeviceSynchronize
. Cependant, il peut être utile pour le débogage de détecter lequel de votre noyau a provoqué une erreur (le cas échéant).
cudaDeviceSynchronize
peut provoquer un certain ralentissement, mais 7-12x semble trop. Peut-être y a-t-il un problème avec la mesure du temps, ou peut-être que les noyaux sont vraiment rapides, et la surcharge de synchronisation explicite est énorme par rapport au temps de calcul réel.
Une situation où l'utilisation de cudaDeviceSynchronize()
est appropriée serait lorsque vous avez plusieurs cudaStream
en cours d'exécution et que vous souhaitez qu'ils échangent des informations. Un cas réel de ceci est le tempérage parallèle dans les simulations quantiques de Monte Carlo. Dans ce cas, nous voudrions nous assurer que chaque flux a fini d'exécuter un ensemble d'instructions et a obtenu des résultats avant de commencer à se transmettre des messages, ou nous finirions par transmettre des informations inutiles. La raison pour laquelle cette commande ralentit tellement le programme est que cudaDeviceSynchronize()
force le programme à attendre la fin de toutes les commandes précédemment émises dans tous les flux de l'appareil avant de continuer (à partir du Guide de programmation CUDA C). Comme vous l'avez dit, l'exécution du noyau est normalement asynchrone, donc pendant que le périphérique GPU exécute votre noyau, le CPU peut continuer à travailler sur d'autres commandes, émettre plus d'instructions sur le périphérique, etc., au lieu d'attendre. Cependant, lorsque vous utilisez cette commande de synchronisation, le processeur est à la place obligé de rester inactif jusqu'à ce que tout le travail GPU soit terminé avant de faire quoi que ce soit d'autre. Ce comportement est utile lors du débogage, car vous pouvez avoir une erreur de segmentation se produisant à des moments apparemment "aléatoires" en raison de l'exécution asynchrone du code de périphérique (que ce soit dans un flux ou plusieurs). cudaDeviceSynchronize()
forcera le programme à s'assurer que les noyaux/memcpys du ou des flux sont complets avant de continuer, ce qui peut faciliter la recherche de l'emplacement des accès illégaux (car l'échec apparaîtra pendant la synchronisation).
Lorsque vous souhaitez que votre GPU commence à traiter certaines données, vous effectuez généralement une invocation kernale. Lorsque vous le faites, votre appareil (le GPU) commencera à faire tout ce que vous lui avez dit de faire. Cependant, contrairement à un programme séquentiel normal sur votre hôte (le CPU) continuera d'exécuter les lignes de code suivantes dans votre programme. cudaDeviceSynchronize fait attendre à l'hôte (le CPU) jusqu'à ce que le périphérique (le GPU) ait fini d'exécuter TOUS les threads que vous avez démarrés, et ainsi votre programme continuera comme s'il s'agissait d'un programme séquentiel normal.
Dans les petits programmes simples, vous utilisez généralement cudaDeviceSynchronize, lorsque vous utilisez le GPU pour effectuer des calculs, pour éviter les décalages de synchronisation entre le processeur demandant le résultat et le GPU finalisant le calcul. L'utilisation de cudaDeviceSynchronize facilite beaucoup le codage de votre programme, mais il y a un inconvénient majeur: votre CPU est inactif tout le temps, tandis que le GPU effectue le calcul. Par conséquent, dans le calcul haute performance, vous vous efforcez souvent de faire effectuer des calculs par votre processeur en attendant la fin du GPU.