Expert Cybersécurité & IAv9.0
Centres de ressources conformité
Besoin d'un accompagnement expert ?
Devis personnalisé sous 24h — audit, conformité, incident
Checklists Sécurité — Audit & Durcissement
Formats disponibles
📄 PDF 📊 Excel 🌐 Web

11 checklists professionnelles couvrant 2 200+ points de contrôle. Téléchargement gratuit, aucune inscription.

Aller au contenu Aller au quiz
Lu

Profiling avec Nsight

En route — chaque ligne compte.

~32 min
Programme complet

Profiling avec Nsight

Diagnostiquer les goulots d'étranglement d'une application CUDA avec Nsight Systems et Nsight Compute, appliquer le modèle roofline, et sécuriser le code avec cuda-gdb et compute-sanitizer.

Ch. 9/12 Avancé
Table des matières

    Pourquoi profiler avant d'optimiser

    Optimiser un kernel CUDAGPUIAProcesseur graphique parallélisant massivement les calculs matriciels ; indispensable à l'entraînement et à l'inférence des modèles de deep learning.Voir dans le glossaire sans donnéesdonnéesIAEnsemble d'informations structurées ou non utilisées pour entraîner, évaluer ou alimenter un modèle. La qualité, la quantité et la représentativité des données sont les facteurs décisifs pour les performances en apprentissage automatique.Voir dans le glossaire de profiling revient à régler un moteur les yeux bandés. L'intuition sur ce qui limite les performances — trop de registres, accès mémoire mal alignés, occupation insuffisante — se trompe plus souvent qu'elle n'a raison, y compris chez des développeurs expérimentés. La chaîne d'outilstool callingIACapacité d'un agent ou d'un LLM à invoquer des outils externes (API, calcul, recherche) pendant le raisonnement.Voir dans le glossaire NVIDIA distingue deux niveaux d'analyse qui répondent à deux questions différentes. Nsight Systems répond à « où mon application perd-elle du temps, à l'échelle du programme entier ? ». Nsight Compute répond à « pourquoi ce kernel précis est-il lent ? ». Confondre les deux, ou sauter directement à l'optimisation fine sans vue d'ensemble, est la première cause de sessions de profiling improductives : on passe des heures à réduire l'occupancy d'un kernel qui ne représente que 2 % du temps d'exécution total.

    Toujours commencer par Nsight Systems, jamais par Nsight Compute. La timeline applicative révèle souvent que le vrai problème n'est pas un kernel lent mais une synchronisation superflue, un transfert H2D redondant, ou une section CPU qui bloque le GPU en attente de données. Optimiser un kernel qui n'est pas sur le chemin critique ne change rien au temps d'exécution perçu.

    Nsight Systems : la vue d'ensemble en timeline

    Nsight Systems (nsys) capture l'exécution complète d'une application : threads CPU, appels à l'API CUDA, exécution des kernels par stream, transferts mémoire, et — si le code est instrumenté — plages NVTX personnalisées. La commande de base :

    nsys profile --trace=cuda,nvtx,osrt -o rapport ./mon_application
    nsys stats rapport.nsys-rep
    

    Le fichier .nsys-rep s'ouvre dans l'interface graphique Nsight Systems, qui affiche une timeline multi-lignes : une ligne par thread CPU, une ligne pour la file des appels API CUDA, une ligne par stream GPU avec les kernels et copies mémoire qui s'y exécutent. C'est cette vue qui permet de répondre aux questions structurantes : le GPU est-il occupé en continu, ou y a-t-il des trous ? Les streams se recouvrent-ils réellement, ou s'exécutent-ils en série faute de dépendances mal gérées ? Le temps CPU entre deux lancements de kernel dépasse-t-il le temps d'exécution du kernel lui-même ?

    Un trou dans la timeline GPU signale presque toujours l'une de ces causes : une synchronisation bloquante (cudaDeviceSynchronize, cudaStreamSynchronize) qui force le GPU à attendre le CPU, une dépendance de données non exprimée en streams multiples, ou un lancement de kernel dont la préparation côté CPU (calcul d'indices, allocation, cudaMemcpy synchrone) devient le facteur limitant.

    Instrumentez le code source avec des plages NVTX (nvtxRangePushA("assemblage_matrice") / nvtxRangePop()) autour des sections logiques. Ces plages apparaissent comme des bandes colorées dans la timeline et permettent de faire correspondre directement une portion de code à une durée mesurée, sans deviner à partir des seuls noms de kernels CUDA.

    Nsight Systems fournit aussi une vue statistique (nsys stats) qui agrège le temps total par type de kernel, par type d'appel API, et par type de transfert mémoire. C'est le point de départ pour prioriser : un kernel qui cumule 60 % du temps GPU mérite l'attentionattentionIAMécanisme par lequel un modèle pondère l'importance de chaque token du contexte lorsqu'il en traite un autre, quelle que soit la distance qui les sépare.Voir dans le glossaire de Nsight Compute ; un kernel à 1 % ne la mérite pas, même s'il « semble » mal écrit à la lecture du code.

    Nsight Compute : diagnostic kernel par kernel

    Une fois le ou les kernels critiques identifiés, ncu (Nsight Compute) permet une analyse fine d'une exécution isolée :

    ncu --set full --kernel-name mon_kernel --launch-count 1 -o rapport_kernel ./mon_application
    

    Le rapport se structure en sections. La section GPU Speed of Light donne en un coup d'œil deux pourcentages : l'utilisation du débit de calcul (compute throughput) et l'utilisation de la bande passante mémoire (memory throughput), chacun exprimé en proportion du maximum théorique de la carte. C'est la première chose à lire : si les deux pourcentages sont bas simultanément, le kernel n'est ni borné mémoire ni borné calcul — il est probablement limité par la latence (dépendances entre instructions, occupancy insuffisante) plutôt que par le débit.

    La section Occupancy compare l'occupancy théorique (fonction du nombre de registres par thread, de la mémoire partagée utilisée, et de la configuration de lancement) à l'occupancy atteinte réellement pendant l'exécution. Un écart important entre les deux indique un déséquilibre de charge entre blocs, ou une exécution trop courte pour saturer les unités de calcul.

    La section Warp State Statistics détaille les raisons pour lesquelles les warps sont en attente plutôt qu'actifs : Long Scoreboard (attente d'une donnée en mémoire globale), Barrier (attente à un __syncthreads()), Short Scoreboard (attente d'une opération mémoire partagée ou registre), MIO Throttle (unité mémoire saturée). C'est souvent l'information la plus actionnable du rapport : elle indique littéralement ce que les warps attendent le plus.

    Une occupancy théorique maximale (100 %) n'est pas une garantie de performance. Un kernel à faible occupancy mais avec un excellent recouvrement calcul/mémoire grâce à l'instruction-level parallelism peut surpasser un kernel à occupancy maximale mais mal équilibré. Ne réglez jamais uniquement sur ce chiffre : croisez-le systématiquement avec le débit mémoire et la répartition des stalls de warp.

    Le modèle roofline : borné mémoire ou borné calcul

    Le modèle roofline relie la performance atteignable (en FLOP/s) à l'intensité arithmétique d'un kernel, définie comme le nombre d'opérations flottantes effectuées par octet transféré depuis la mémoire globale :

    intensité_arithmétique = FLOPs_total / octets_transférés_total
    

    Sur un graphe où l'axe horizontal est l'intensité arithmétique (échelle logarithmique) et l'axe vertical la performance atteinte, deux plafonds définissent la région de performance possible : un plafond horizontal fixé par le débit de calcul crête de la carte (indépendant de l'intensité), et un plafond diagonal fixé par la bande passante mémoire crête (proportionnel à l'intensité, puisqu'à bande passante fixe, plus on fait de calcul par octet, plus on peut atteindre de FLOP/s). Un kernel se place sous l'un ou l'autre plafond selon son profil : s'il est proche du plafond diagonal, il est borné mémoire — augmenter la bande passante effective (coalescing, réutilisation via mémoire partagée, réduction du volume de données) l'améliore ; augmenter le débit de calcul ne sert à rien. S'il est proche du plafond horizontal, il est borné calcul — l'inverse s'applique.

    Roofline : kernels bornés mémoire vs bornés calcul TFLOP/s Intensité arithmétique (FLOP/octet) Plafond calcul (peak FLOP/s) Plafond mémoire (bande passante) Réduction (borné mémoire) GEMM dense (borné calcul)
    Deux kernels typiques placés sur le graphe roofline : une réduction, à faible intensité arithmétique, colle au plafond mémoire ; un GEMM dense, à forte intensité, colle au plafond calcul.

    Un kernel de réduction (somme d'un grand tableau) lit un octet pour effectuer une seule addition : son intensité arithmétique est proche de 0,25 FLOP/octet en simple précisionprécisionIAProportion des alertes émises par un modèle qui sont justifiées. Elle s'oppose au rappel : améliorer l'une dégrade l'autre.Voir dans le glossaire. Il colle au plafond mémoire quelle que soit la qualité du code : la seule optimisation utile est de maximiser la bande passante effective (coalescing parfait, utilisation de la mémoire partagée pour réduire les accès globaux redondants). À l'inverse, un GEMM dense bien écrit atteint des dizaines de FLOP par octet grâce au tuilage en mémoire partagée : il colle au plafond calcul, et seule une meilleure utilisation des unités de calcul (Tensor Cores, réduction de la divergence) l'améliore.

    Nsight Compute calcule directement l'intensité arithmétique et positionne le kernel sur un graphe roofline dans la section Memory Workload Analysis combinée à Compute Workload Analysis — inutile de le calculer à la main pour un diagnostic rapide, mais comprendre le principe évite de mal interpréter un pourcentage de débit mémoire élevé comme un problème alors qu'il s'agit simplement de la limite physique du kernel.

    Débogage avec cuda-gdb

    Lorsqu'un kernel produit un résultat incorrect ou plante, cuda-gdb étend GDB au contextefenêtre de contexteIAQuantité de texte qu'un modèle peut prendre en compte simultanément : question, documents fournis et historique. Au-delà, les éléments les plus anciens sortent du champ.Voir dans le glossaire GPU : points d'arrêt dans le code device, inspection des variables par thread, navigation entre warps et blocs.

    cuda-gdb ./mon_application
    (cuda-gdb) break mon_kernel
    (cuda-gdb) run
    (cuda-gdb) cuda thread
    (cuda-gdb) cuda thread (12,0,0)
    (cuda-gdb) print mon_tableau[threadIdx.x]
    (cuda-gdb) cuda kernel block thread
    

    La commande cuda thread affiche le thread courant ; on peut basculer explicitement vers un thread ou un bloc donné pour inspecter son état au moment du breakpoint. C'est particulièrement utile pour un bug qui ne se manifeste que sur certains indices (effet de bord d'un accès hors limites, division par un cas particulier).

    cuda-gdb nécessite une compilation avec -G -g (symboles de debug device, optimisations désactivées). Le comportement observé sous cuda-gdb peut différer du binaire optimisé de production : un bug de race condition peut disparaître (timing modifié) ou au contraire apparaître (l'optimiseur avait réordonné des accès d'une façon qui masquait le problème). Ne concluez jamais qu'un bug est résolu uniquement parce qu'il ne se reproduit plus sous debug — validez avec compute-sanitizer sur le binaire de release.

    compute-sanitizer : détecter les erreurs mémoire et de synchronisation

    compute-sanitizer (qui remplace l'ancien cuda-memcheck) regroupe plusieurs outils d'analyse dynamique, chacun ciblant une classe de bug :

    Outil Détecte Commande
    memcheck accès hors limites, use-after-free, fuites compute-sanitizer --tool memcheck ./app
    racecheck races sur mémoire partagée entre threads compute-sanitizer --tool racecheck ./app
    initcheck lecture de mémoire device non initialisée compute-sanitizer --tool initcheck ./app
    synccheck usage incorrect de primitives de synchronisation (__syncthreads() en branche divergente) compute-sanitizer --tool synccheck ./app

    memcheck est le plus utilisé au quotidien : un accès hors limites en mémoire globale ne provoque pas toujours un plantage immédiat — il peut corrompre silencieusement une zone mémoire voisine et ne se manifester que bien plus tard, par un résultat faux à un endroit sans lien apparent. racecheck est indispensable dès qu'un kernel utilise de la mémoire partagée sans synchronisation explicite entre écriture et lecture par des threads différents : ce type de bug est souvent non déterministe et invisible en test occasionnel, mais racecheck le détecte de façon fiable en instrumentant chaque accès.

    Le surcoût de compute-sanitizer est important — un facteur 10 à 30 selon l'outil et le kernel, racecheck étant généralement le plus coûteux. Ne l'exécutez jamais sur un workload de production ou sur un jeu de données de taille réelle : réduisez la taille du problème pour la durée de l'analyse, et intégrez-le en CI sur des cas de test représentatifs plutôt qu'en profiling de performance.

    Workflow de profiling recommandé

    Une séquence qui évite le gaspillage de temps :

    1. Nsight Systems sur l'application complète, jeu de données réaliste, pour identifier le ou les kernels/sections qui dominent le temps total.
    2. Vérifier l'absence de trous anormaux dans la timeline GPU (synchronisations superflues, transferts non recouverts).
    3. Nsight Compute ciblé sur les kernels identifiés en (1), en isolant un seul lancement (--launch-count 1) pour un rapport lisible.
    4. Lire dans l'ordre : Speed of Light, puis Memory/Compute Workload Analysis pour situer le kernel sur le roofline, puis Warp State Statistics pour la cause de stall.
    5. Appliquer une optimisation ciblée en fonction du diagnostic, puis rejouer nsys et ncu pour mesurer l'effet réel — jamais se fier à l'intuition sur le gain obtenu.
    6. Avant toute mise en production, exécuter compute-sanitizer --tool memcheck et racecheck sur les cas de test, sur un jeu de données réduit.

    Pièges courants

    Symptôme observé Cause probable Action
    Timeline GPU avec de larges trous Synchronisations CPU-GPU superflues, un seul stream utilisé Introduire des streams multiples, éviter cudaDeviceSynchronize dans la boucle chaude
    Débit mémoire élevé mais performance décevante Kernel réellement borné mémoire, optimisation calcul inutile Travailler le coalescing et la mémoire partagée, pas l'algorithme arithmétique
    Occupancy théorique 100 %, occupancy atteinte faible Déséquilibre de charge entre blocs, kernel trop court Revoir la taille de grille, fusionner des lancements courts
    Résultat faux, intermittent Race condition sur mémoire partagée ou globale compute-sanitizer --tool racecheck sur un jeu de données réduit
    Crash aléatoire loin du code fautif Corruption mémoire par accès hors limites compute-sanitizer --tool memcheck en premier réflexe, avant cuda-gdb
    Comportement différent en debug vs release Compilation -G désactive les optimisations Reproduire sous compute-sanitizer sur le binaire optimisé, pas seulement sous cuda-gdb

    Le profiling n'est pas une étape ponctuelle en fin de développement : c'est un réflexe à réactiver à chaque changement significatif de kernel, de taille de problème ou de génération de GPU cible. Un kernel borné calcul sur une architecture peut devenir borné mémoire sur la suivante si le ratio entre débit de calcul et bande passante a changé — ce qui est systématiquement le cas d'une génération de carte à l'autre.

    L'essentiel à retenir

    Ce chapitre présente la démarche de profiling CUDA à deux niveaux : Nsight Systems pour la vue d'ensemble temporelle de l'application, Nsight Compute pour l'analyse détaillée d'un kernel donné. Il explique le modèle roofline pour déterminer si un kernel est borné par la bande passante mémoire ou par la capacité de calcul, et comment orienter l'optimisation en conséquence. Il couvre également cuda-gdb pour le débogage interactif et compute-sanitizer pour détecter les erreurs mémoire, les races et les problèmes de synchronisation avant qu'ils ne deviennent des bugs de production. Une checklist de workflow et un tableau de pièges courants closent le chapitre.

    Questions fréquentes

    Faut-il toujours utiliser Nsight Compute avec --set full, ou existe-t-il des jeux de métriques plus légers ?
    Le jeu complet (--set full) collecte un grand nombre de métriques et impose plusieurs passes de rejeu du kernel, ce qui est lent sur des kernels appelés des milliers de fois. Pour un premier diagnostic, --set basic ou --set roofline suffit généralement à situer le kernel (borné mémoire, calcul, ou latence) ; on ne passe au jeu complet que pour creuser une hypothèse précise sur un kernel déjà identifié comme critique.
    Nsight Systems peut-il profiler une application multi-GPU ou distribuée sur plusieurs nœuds ?
    Oui, Nsight Systems capture nativement plusieurs GPU sur une même machine dans une seule timeline, avec une ligne par device. Pour du multi-nœud, chaque processus MPI génère son propre rapport ; NVIDIA fournit des outils complémentaires (comme les rapports agrégés via nsys avec MPI wrapping) pour corréler plusieurs rapports, mais la lecture reste par nœud dans l'interface standard.
    Un kernel a un débit mémoire à 85 % et un débit calcul à 20 % : est-il bien optimisé ?
    Cela dépend de son intensité arithmétique attendue. Si le kernel est structurellement à faible intensité (une réduction, une copie avec transformation légère), 85 % de bande passante est un excellent résultat et il est inutile de chercher à améliorer le débit calcul. Le point de référence n'est pas 100 % sur les deux axes, mais la position théorique du kernel sur le roofline compte tenu de son ratio FLOPs/octets.
    compute-sanitizer peut-il remplacer complètement cuda-gdb ?
    Non, les deux sont complémentaires. compute-sanitizer détecte des classes de bugs précises (accès mémoire invalides, races, synchronisation incorrecte) de façon automatisée, sans intervention. cuda-gdb sert à l'inspection interactive quand on a déjà localisé une zone suspecte et qu'on doit examiner l'état exact des variables, thread par thread, à un point d'exécution donné.
    Pourquoi ma timeline Nsight Systems montre-t-elle des kernels qui semblent se chevaucher dans le même stream, ce qui paraît impossible ?
    Un stream unique exécute ses opérations dans l'ordre, sans chevauchement réel. Un chevauchement apparent dans l'interface graphique vient généralement d'une confusion entre la durée affichée (qui inclut parfois le temps de lancement API) et l'exécution device réelle, ou d'un zoom insuffisant qui agrège visuellement deux événements proches. Zoomez sur la zone concernée pour vérifier l'ordre exact des événements device.
    Est-il utile de profiler avec Nsight sur un GPU différent de celui de production ?
    Cela reste informatif pour la structure générale (répartition du temps, présence de trous, comportement algorithmique) mais les métriques absolues — bande passante atteinte, occupancy, position sur le roofline — ne se transposent pas directement d'une architecture à l'autre, car les plafonds calcul et mémoire diffèrent entre générations de GPU. Le diagnostic fin doit être rejoué sur le matériel cible avant toute décision d'optimisation définitive.
    Les plages NVTX ont-elles un coût en performance si on les laisse dans le code de production ?
    Le coût est négligeable en dehors d'une session de profiling : nvtxRangePush/Pop sans outil de capture attaché se résume à un appel de fonction très léger. Il est courant de les laisser en permanence dans le code plutôt que de les ajouter et retirer à chaque session, ce qui garantit une instrumentation toujours disponible pour un futur profiling.

    Progression sauvegardée dans votre navigateur.

    Quiz de validation

    Quiz de validation

    Quiz indisponible (données invalides).

    Vos projets IA sont-ils sécurisés ? Audit LLM, conformité AI Act, red teaming — devis sous 48h.
    Devis gratuit
    Ch. 9/12 Profiling avec Nsight 75% ~32 min Mode lecture v2.7.9