Astuce CUDA : Boostez les performances avec l’accès mémoire vectorisé
Sources: https://developer.nvidia.com/blog/cuda-pro-tip-increase-performance-with-vectorized-memory-access, developer.nvidia.com
TL;DR
- Les accès mémoire vectorisés en CUDA utilisent des chargements et des stockages de largeur 64 bits ou 128 bits pour traiter plusieurs valeurs en une seule opération, améliorant l’utilisation de la bande passante.
- L’emploi de types de données vectoriels (int2, int4, float2, float4) permet ces chargements larges via de simples cast et des vérifications d’alignement.
- Les charges vectorisées peuvent réduire considérablement le comptage d’instructions (environ 2x pour deux éléments traités; environ 4x pour quatre éléments) et diminuer la latence dans les kernels limités par la bande passante.
- L’alignement est essentiel : les données doivent être alignées sur la taille du type vectoriel; les décalages mal alignés annulent les avantages et peuvent nécessiter des chemins scalaires.
- Évaluez les gains face à l’augmentation potentielle de la pression sur les registres et à la réduction du parallélisme; si un kernel est déjà limité par les registres, les charges scalaires peuvent être préférables. Pour de nombreux kernels, les charges vectorisées constituent une optimisation fondamentale pour une plus grande largeur de bande.
Contexte et arrière-plan
De nombreux kernels CUDA sont limités par la bande passante, et l’augmentation du rapport flops/bande passante sur les nouveaux GPU entraîne davantage de kernels dominés par les transferts de données. Dans ce cadre, augmenter le débit mémoire peut apporter des améliorations de performance significatives sans modifier les algorithmes sous-jacents. Le Pro Tip CUDA explique comment exploiter les chargements et stockages vectorisés en CUDA C++ afin d’augmenter l’utilisation de la bande passante tout en réduisant le nombre total d’instructions exécutées. La discussion porte sur un kernel simple de copie mémoire et sur la manière dont ses performances évoluent lors du passage du scalaire au vectoriel. Comme décrit, le SASS d’un kernel de copie scalaire utilise des chargements et stockages de 32 bits (LDG.E et STG.E) depuis la mémoire globale. Les variantes vectorielles passent à des chargements de 64 bits (LDG.E.64/STG.E.64) ou 128 bits (LDG.E.128/STG.E.128), permettant au processeur/GPU de lire ou écrire deux ou quatre valeurs en une instruction. Ces instructions plus larges réduisent le nombre total d’instructions, peuvent réduire la latence et améliorer l’utilisation de la bande passante. Pour vérifier le code généré, on peut examiner le SASS ou utiliser des outils comme cuobjdump fournis avec le CUDA Toolkit. L’idée centrale est que des opérations de lecture plus larges transmettent plus de données par instruction, ce qui aide le throughput dans de nombreux cas. CUDA Pro Tip: Increase Performance with Vectorized Memory Access.
Quoi de neuf
Le billet présente plusieurs approches pratiques pour adopter les charges vectorielles dans les kernels existants avec des modifications minimales :
- Traiter davantage de données par itération : l’itération gère deux éléments (N/2) lorsqu’on utilise des types vectoriels.
- Cast des pointeurs vers les types vectoriels : reinterpret_cast(d_in) ou casts similaires permettent de lire/écrire plusieurs valeurs en une seule opération. En C, (int2*(d_in)) produit le même effet. Le déférencement du pointeur résultant génère l’accès vectoriel.
- Assurer l’alignement : la mémoire du device est automatiquement alignée sur la taille du type, mais les décalages doivent aussi respecter l’alignement. Par exemple, reinterpret_cast(d_in+1) est invalide car le décalage n’est pas aligné; des décalages sûrs incluent reinterpret_cast(d_in+2).
- Structures de taille puissance de deux : les structures dont la taille est un nombre de bytes puissance de deux s’alignent proprement; les tailles non puissances de deux peuvent introduire du padding et du désalignement.
- Utiliser vector4 : une version vector4 utilise LDG.E.128/STG.E.128 et peut réduire encore le nombre d’instructions, parfois jusqu’à un facteur de quatre dans des conditions idéales.
- Considérations de lancement : comme chaque itération traite plus de données, on peut lancer moitié moins de threads que pour l’approche scalaire. Cela peut améliorer le débit global lorsque la bande passante est le goulet d’étranglement. Les compromis importants : les chargements vectorisés augmentent la pression sur les registres et peuvent réduire le parallélisme. Si un kernel est déjà limité par les registres ou a un faible parallélisme, les charges scalaires peuvent être préférables. Cependant, lorsque la bande passante est le principal goulot d’étranglement et que le kernel tolère l’impact sur les registres, les charges vectorisées offrent une voie claire vers un débit plus élevé.
Pourquoi cela compte (impact pour les développeurs/entreprises)
Les développeurs CUDA qui visent à optimiser la bande passante mémoire peuvent obtenir un throughput plus élevé lorsque la mémoire est le facteur limitant. En réduisant le nombre d’instructions grâce à des accès vecteurs, les kernels peuvent s’approcher du débit mémoire maximal, en traitant plus de données par unité de temps. Cela est particulièrement pertinent pour les applications traitant de gros flux de données, du traitement d’images, du traitement du signal ou des simulations à grande échelle qui dépendent fortement du mouvement des données. Pour les entreprises, cela se traduit par une utilisation plus efficace des GPUs : des projets dépendant fortement de la bande passante mémoire peuvent obtenir de meilleures performances sans modifier en profondeur les algorithmes numériques, bien que cela vienne avec des compromis sur l’occupation et le parallélisme. Dans l’ensemble, l’approche offre un moyen pragmatique d’extraire des performances supplémentaires des bases de code CUDA existantes.
Détails techniques ou Mise en œuvre
Voici une vue consolidée de la façon d’implémenter l’accès mémoire vectorisé dans un kernel CUDA, avec les considérations qui guident le choix entre scalaires, vector2 et vector4 :
- Concepts de vectorisation :
- Utilisez les types vectoriels définis dans les en-têtes CUDA C++ : int2, int4, float2, float4, etc. Cast des pointeurs ordinaires vers ces types permet de lire/écrire plusieurs valeurs en une seule instruction.
- L’alignement est essentiel : les données doivent être alignées sur la taille du type vectoriel. Les décalages doivent préserver l’alignement; les désalignements ou les tailles non puissance de deux peuvent invalider la vectorisation.
- Étapes d’implémentation :
- Choisir la largeur de traitement : deux éléments par itération (vector2) ou quatre éléments (vector4).
- Cast des pointeurs d’entrée/sortie vers le type vectoriel correspondant (par exemple, reinterpret_cast(d_in)).
- Mettre à jour la boucle pour traiter N/2 ou N/4 itérations, et gérer les éléments restants si N n’est pas divisible par la largeur.
- Configuration du lancement : si vous traitez deux éléments par itération, vous pouvez réduire de moitié le nombre de threads par rapport au kernel scalaire.
- Vérifier l’alignement de tous les décalages utilisés dans le kernel. Si l’alignement ne peut être garanti, prévoir une voie scalaire pour les régions non alignées.
- Vérifier les compatibilités entre les types et les tailles pour s’assurer que la taille est une puissance de deux pour la vectorisation efficace.
- Résultats attendus :
- Les loads vectorisés réduisent le nombre total d’instructions, réduisent la latence et améliorent l’utilisation de la largeur de bande.
- Une approche à deux éléments par itération offre typiquement une réduction d’environ 2x du nombre d’instructions par rapport au chargement scalaire; une approche à quatre éléments par itération peut réduire jusqu’à environ 4x dans des conditions optimales.
- Les gains dépendent des caractéristiques du kernel (pression sur les registres et parallélisme).
- Précautions pratiques :
- Les loads vectorisés augmentent la pression sur les registres et peuvent diminuer l’occupation globale. Si le kernel est déjà limité par les registres ou a faible parallélisme, les loads scalaires peuvent être préférables.
- Si l’alignement ne peut être garanti (par ex., tailles non puissances de deux ou strides non alignés), les chemins vectorisés risquent de ne pas apporter d’avantages.
- Aligner les motifs d’accès sur les largeurs vectorielles aide à maximiser les gains.
Tableau rapide des approches
| Approche | Données par itération | Notes |---|---|---| | Copie scalaire | 1 élément | Ligne de base ; loads/stores scalaires. |Vectorisé (deux éléments) | 2 éléments | loads/stores de 64 bits; moins d’instructions. |Vectorisé (quatre éléments) | 4 éléments | loads/stores de 128 bits; réduction possible jusqu’à ~4x. |
Points clés à retenir
- L’accès mémoire vectorisé est une optimisation clé du CUDA pour les kernels limités par la bande passante.
- Les loads vectorisés peuvent augmenter le throughput et réduire la latence tout en augmentant l’utilisation de la bande passante.
- L’alignement et le fait que la taille soit une puissance de deux sont cruciaux pour obtenir les gains.
- Évaluez les compromis sur la pression des registres et l’occupation ; pour certains kernels, les loads scalaires restent préférables.
- Commencez par des changements modestes et mesurez l’impact sur le throughput et l’occupation avant d’adopter largement la vectorisation.
FAQ
-
Qu’est-ce que l’accès mémoire vectorisé dans CUDA ?
L’utilisation d’opérations mémoire plus larges (64 bits ou 128 bits) pour lire/écrire plusieurs valeurs en une seule instruction, rendue possible par les types vectoriels tels que int2, int4, float2 et float4. [CUDA Pro Tip: Increase Performance with Vectorized Memory Access](https://developer.nvidia.com/blog/cuda-pro-tip-increase-performance-with-vectorized-memory-access).
-
uand faut-il envisager la vectorisation des accès mémoire ?
Quand les kernels sont limités par la bande passante et peuvent tolérer une plus grande pression sur les registres ou un parallélisme légèrement réduit ; les loads vectorisés améliorent l’utilisation de la bande passante et réduisent le nombre d’instructions.
-
uelles sont les exigences d’alignement pour les loads vectorisés ?
Les données doivent être alignées sur la taille du type vectoriel; les décalages doivent préserver cet alignement. Des données mal alignées ou des tailles non puissances de deux peuvent annuler les bénéfices.
-
uels sont les inconvénients potentiels de la vectorisation ?
Oui : elle peut augmenter la pression sur les registres et réduire l’occupation, ce qui peut nuire si le kernel est déjà limité par ces facteurs.
-
Comment vérifier les bénéfices des loads vectorisés ?
Comparez les variantes scalaire et vectorisée du kernel en termes de comptage d’instructions, latence et utilisation de la bande passante, et utilisez des outils pour examiner le code généré si nécessaire. Le billet de NVIDIA donne la méthodologie et la justification.
Références
- CUDA Pro Tip: Increase Performance with Vectorized Memory Access. https://developer.nvidia.com/blog/cuda-pro-tip-increase-performance-with-vectorized-memory-access
More news
NVIDIA HGX B200 réduit l’intensité des émissions de carbone incorporé
Le HGX B200 de NVIDIA abaisse l’intensité des émissions de carbone incorporé de 24% par rapport au HGX H100, tout en offrant de meilleures performances IA et une efficacité énergétique accrue. Cet article résume les données PCF et les nouveautés matérielles.
Prévoir les phénomènes météorologiques extrêmes en quelques minutes sans superordinateur : Huge Ensembles (HENS)
NVIDIA et le Lawrence Berkeley National Laboratory présentent Huge Ensembles (HENS), un outil IA open source qui prévoit des événements météorologiques rares et à fort impact sur 27 000 années de données, avec des options open source ou prêtes à l’emploi.
Comment réduire les goulots d’étranglement KV Cache avec NVIDIA Dynamo
NVIDIA Dynamo déporte le KV Cache depuis la mémoire GPU vers un stockage économique, permettant des contextes plus longs, une meilleure concurrence et des coûts d’inférence réduits pour les grands modèles et les charges AI génératives.
Le Playbook des Grands Maîtres Kaggle: 7 Techniques de Modélisation pour Données Tabulaires
Analyse approfondie de sept techniques éprouvées par les Grands Maîtres Kaggle pour résoudre rapidement des ensembles de données tabulaires à l’aide d’une accélération GPU, des baselines divers à l’assemblage et à la pseudo-étiquetage.
NVIDIA RAPIDS 25.08 Ajoute un Nouveau Profiler pour cuML, Améliorations du moteur GPU Polars et Support d’Algorithmes Étendu
RAPIDS 25.08 introduit deux profils pour cuml.accel (fonctionnel et ligne), l’exécuteur streaming par défaut du moteur Polars GPU, un support de types et chaînes étendu, Spectral Embedding dans cuML et des accélérations zéro-code pour plusieurs estimateurs.
Décodage spéculatif pour réduire la latence de l’inférence IA : EAGLE-3, MTP et approche Draft-Target
Analyse détaillée du décodage spéculatif pour l’inférence IA, incluant les méthodes draft-target et EAGLE-3, leur réduction de latence et les déploiements via TensorRT.