CUDA : comment NVIDIA a verrouillé toute une industrie

Cet article vous a aidé ?

Le post-mortem d’une panne n’affiche jamais « CUDA » dans le titre. Il n’en a jamais. On y lit des choses comme « pipeline d’entraînement bloqué », « nœuds GPU sous-utilisés » ou mon préféré personnel, « régression du modèle due à une dérive d’environnement ».
Mais on creuse un peu et voilà : une mise à jour de pilote, un décalage d’outillage, une reconstruction « mineure » de conteneur, et vos GPU coûteux sont soudainement d’excellents radiateurs d’appoint.

CUDA n’a pas seulement rendu les GPU utiles pour le calcul général. Il les a rendus opérationnellement fiables selon ses propres règles, puis a organisé l’écosystème pour que la plupart des charges sérieuses suivent ces règles. Voilà le verrouillage. Pas du malice. Pas de magie. Juste une stratégie produit implacablement efficace et un écosystème qui se renforce.

Ce qu’est réellement CUDA (et ce que ce n’est pas)

CUDA n’est pas « le GPU ». CUDA est la plateforme de calcul parallèle et le modèle de programmation de NVIDIA, plus les bibliothèques en espace utilisateur, la chaîne d’outils du compilateur et le runtime qui font que les GPU NVIDIA ressemblent à une cible stable.
Si vous exécutez du ML, vous interagissez principalement avec CUDA de façon indirecte : PyTorch appelle cuDNN et cuBLAS ; l’entraînement distribué appelle NCCL ; votre serveur d’inférence appelle TensorRT ; vos kernels se compilent peut‑être avec NVCC ; et le pilote est en dessous de tout ça en faisant semblant que c’est normal.

Le génie stratégique de CUDA est qu’il est simultanément :

  • Un écosystème de langage (CUDA C/C++, PTX, intrinsics device, flux de compilation).
  • Un contrat d’exécution (API driver vs runtime, gestion de contextes, modèle mémoire, streams/events).
  • Un empire de bibliothèques (cuBLAS, cuDNN, NCCL, cuFFT, TensorRT, CUTLASS, et plus).
  • Une histoire de performances (kernels optimisés par architecture, opérations fusionnées, tensor cores, capture de graphes).
  • Une histoire opérationnelle (nvidia-smi, NVML, MIG, DCGM, packaging des pilotes, conteneurs).

Le verrouillage n’exige pas la malveillance. Il exige une porte sans retour : vous y entrez parce que c’est plus rapide et plus simple, et plus tard vous découvrez que la sortie est un escalier étroit derrière un distributeur automatique.

L’abstraction clé : « écrire une fois, s’exécuter assez vite »

CUDA a donné aux développeurs un modèle stable et assez haut niveau pour le calcul GPU. Vous écrivez des kernels. Vous gérez les transferts mémoire. Vous lancez des grilles/blocs. C’est suffisamment bas niveau pour obtenir la vitesse, suffisamment haut pour livrer.
Puis NVIDIA a continué d’ajouter des fonctionnalités architecturales (tensor cores, améliorations de la mémoire unifiée, nouvelles hiérarchies de cache) tout en maintenant une histoire de compatibilité qui fonctionne généralement tant que vous ne la contestez pas.

Ce que CUDA n’est pas

  • Ce n’est pas juste un compilateur. Le compilateur est la partie visible ; les bibliothèques et le contrat du pilote sont la gravité.
  • Ce n’est pas intrinsèquement « meilleur en mathématiques ». Il est meilleur pour fournir une chaîne produit du concept à la vitesse jusqu’au déploiement.
  • Ce n’est pas un seul numéro de version. Vous avez des versions de pilote, des versions d’outillage, des versions de bibliothèques, des builds de frameworks et des images de base de conteneur. Ils négocient au runtime comme un comité avec des incitations.

Blague n°1 : Si vous pensez que votre version CUDA est « celle que pip a installée », j’ai un pont à vous vendre — compilé avec la mauvaise capacité de calcul.

Comment se produit le verrouillage : mécanismes techniques

« Verrouillage » sonne comme du théâtre d’achats. En pratique, c’est des maths d’ingénierie : coût de migration = (réécritures + perte de performance + risque opérationnel) × (nombre de workloads) ÷ (votre tolérance à la douleur).
CUDA augmente le coût de migration le long de plusieurs axes à la fois.

1) Le fossé des bibliothèques : cuDNN, cuBLAS, TensorRT, NCCL

La plupart du code en production n’appelle pas des kernels CUDA bruts. Il appelle des frameworks, qui appellent des bibliothèques du fournisseur. Ces bibliothèques sont optimisées jusqu’aux détails microarchitecturaux :
tailles de tiles pour tensor cores, schémas d’accès mémoire, épilogues fusionnés, heuristiques de sélection de kernel, même « cette forme de convolution aime tel kernel sur tel GPU ».

Des piles alternatives peuvent implémenter des équivalents, mais atteindre la parité est difficile parce que l’« API » est la partie simple ; les derniers 30 % de performance représentent des années de profilage, de chirurgie de kernels et d’attention aux cas particuliers étranges.

2) La chaîne de compilation : PTX, SASS, fatbins et compatibilité vers l’avant

CUDA compile le code device en un mélange de :

  • PTX (une ISA virtuelle, une sorte d’IR assemblage GPU).
  • SASS (vrai code machine pour une architecture GPU spécifique).
  • Fat binaries regroupant plusieurs variantes.

Cela a des conséquences opérationnelles. Si vous n’expédiez que du SASS pour une architecture, vous êtes lié à ces GPU. Si vous expédiez PTX, vous dépendez du JIT du pilote pour compiler pour le GPU installé, ce qui vous lie aux fonctionnalités du pilote et peut ajouter une latence de démarrage.

3) Le contrat du pilote : « le pilote plus récent exécute l’ancien CUDA » (en grande partie)

Le modèle de compatibilité de NVIDIA est une grande partie du verrouillage parce qu’il réduit la peur quotidienne. Vous pouvez standardiser une branche de pilote, puis autoriser une plage d’outillages CUDA dans les conteneurs.
L’industrie a appris à construire des pratiques opérationnelles autour de cela : « pilote sur l’hôte, toolkit dans le conteneur ». Ce pattern est maintenant de la mémoire musculaire.

Le hic est le mot « en grande partie ». Les cas limites deviennent vos pagers :
nouveaux GPU nécessitant de nouveaux pilotes, frameworks attendant une libcudart plus récente, NCCL nécessitant un certain comportement du pilote, et des toolchains anciennes rencontrant des kernels modernes comme un mauvais rendez-vous à l’aveugle.

4) La gravité de l’écosystème : frameworks, tutoriels, recrutement et CI

CUDA est devenu le choix par défaut en ML. Cela signifie :

  • La plupart des ingénieurs ML sont formés sur des workflows axés CUDA.
  • La plupart des bibliothèques tierces livrent d’abord des wheels CUDA.
  • La plupart des conseils de performance supposent des compteurs et profileurs NVIDIA.
  • La plupart des guides « comment faire de l’entraînement distribué » supposent NCCL.

C’est un verrouillage plus discret : le coût de reformation des personnes et de refactorisation des systèmes de build. Il n’apparaît pas dans les tableaux de benchmark, mais il apparaît dans les dates de livraison.

5) Les fonctionnalités matérielles exposées via l’outillage CUDA

Quand NVIDIA lance une nouvelle fonctionnalité matérielle, elle est généralement accompagnée d’une histoire CUDA : API, bibliothèques et support dans le profileur. Si vous voulez la fonctionnalité, vous adoptez l’outillage.
Exemples : les tensor cores (et les couches de support de bibliothèque autour), le partitionnement MIG pour la multi-tenancy, et les collectives conscientes de NVLink pour l’échelle multi-GPU.

Faits et histoire qui expliquent la dominance

Voici des points de contexte concrets que l’on oublie quand on réduit CUDA à « verrouillage fournisseur ». C’est plus intéressant que ça.

  1. CUDA lancé en 2007 et a fait paraître la programmation GPU générique comme du C, pas un bidouillage d’API graphique.
  2. Avant CUDA, GPGPU signifiait souvent abus de shaders : empaqueter du calcul dans des pixel/vertex shaders via OpenGL/DirectX, ce qui était ingénieux et misérable.
  3. cuDNN (2014) a été un tournant : les charges de deep learning ont eu une bibliothèque de kernels optimisée et soutenue par le fournisseur sur laquelle les frameworks pouvaient compter.
  4. NCCL a rendu le multi-GPU « normal » en fournissant des collectives en anneau/arbre optimisées pour les interconnexions et la topologie NVIDIA. L’entraînement distribué n’est plus réservé au HPC.
  5. Les tensor cores (ère Volta) ont déplacé le jeu de « le GPU fait des FLOPs » à « le GPU fait très bien des mathématiques matricielles en forme ML », et les bibliothèques CUDA ont appris à en tirer parti.
  6. La pile de profilage/télémétrie CUDA a mûri tôt : NVML, nvidia-smi, outils Nsight — les opérateurs avaient des leviers et de la visibilité.
  7. La dynamique universitaire et open-source s’est alignée sur NVIDIA : les premières avancées DL ont souvent été reproduites sur du matériel CUDA parce que c’était ce que les labos pouvaient obtenir et ce que les frameworks supportaient.
  8. Le pattern « pilote sur l’hôte, toolkit dans le conteneur » est devenu le standard de production de facto et a réduit la friction de déploiement, renforçant CUDA comme choix sûr.
  9. L’histoire de compatibilité ascendante de CUDA a rendu l’adoption en entreprise moins terrifiante que « recompiler tout pour chaque pilote ».

La morale : NVIDIA n’a pas seulement vendu des puces. Ils ont vendu une voie de l’idée au système opérationnel, et ils ont continué de la paver.

La pile CUDA en production : où la réalité mord

En production, CUDA est un gâteau en couches. Il est délicieux jusqu’à ce que vous le laissiez dans une voiture chaude.

Couche 0 : matériel et topologie

La performance GPU n’est pas seulement « combien de GPU ». C’est :

  • génération PCIe et largeur de voies
  • placement NUMA (quel socket CPU est relié au GPU)
  • présence et câblage NVLink/NVSwitch
  • taille et bande passante mémoire GPU
  • paramètres ECC et comportement en cas d’erreur

Couche 1 : pilote noyau + driver en espace utilisateur

Le pilote NVIDIA est la vraie plateforme. Il expose le périphérique à l’OS, fournit l’implémentation de l’API driver CUDA, et fait la médiation entre le runtime CUDA de votre conteneur et le GPU réel.
Si votre pilote est mauvais, rien d’autre n’a d’importance. Vous pouvez installer tous les toolkits connus de l’humanité et obtenir quand même « no devices found ».

Couche 2 : runtime CUDA + bibliothèques

Votre conteneur peut inclure libcudart, cuDNN, cuBLAS, NCCL, etc. Ceux-ci doivent être suffisamment compatibles avec le pilote hôte.
« Suffisamment compatible » est la partie qui transforme des mises à jour routinières en tickets d’incident.

Couche 3 : frameworks et artefacts de build

Les binaires PyTorch/TensorFlow/JAX sont construits contre des versions CUDA spécifiques et attendent souvent une plage spécifique d’ABI de bibliothèques.
Il y a ensuite des extensions CUDA personnalisées (fréquentes dans les systèmes de recommandation, l’accélération d’inférence et les prototypes de recherche passés à l’âge adulte qui ont maintenant un pager).

Couche 4 : orchestration et multi‑tenancy

Plugins de périphériques Kubernetes, partitionnement MIG, quotas GPU, MPS, planificateurs de jobs, paramètres cgroup — c’est là que « ça marche sur ma station » se fait auditer.
Plus votre flotte est grande, plus votre problème devient l’application des politiques et le contrôle de dérive.

Couche 5 : stockage et pipelines de données

En tant que personne SRE/stockage, je dirai la chose tranquille à voix haute : beaucoup de « problèmes de performance GPU » sont en réalité des problèmes de données.
Si le GPU attend les dataloaders, un stockage objet lent, des IOPS de petits fichiers ou une décompression sur le mauvais cœur CPU, CUDA n’est pas votre goulot ; c’est juste là où l’on voit le temps d’inactivité.

Une citation à garder au coin du tableau de bord du cluster :
Idée paraphrasée : « La latence est une propriété du système entier, pas d’un composant. » — Werner Vogels (idée paraphrasée)

Playbook de diagnostic rapide : quoi vérifier en premier/deuxième/troisième

Quand les charges GPU sont lentes ou échouent, ne commencez pas par réinstaller CUDA. C’est comme ça qu’on crée un second incident. Commencez par les contraintes et l’observabilité.

Premier : « Voit‑on le GPU et est‑il sain ? »

  • Vérifier la visibilité GPU : le nœud voit-il les périphériques, le pilote correct est-il chargé, pas d’orage ECC.
  • Vérifier les processus actifs : quelque chose d’autre occupe‑t‑il le GPU, le slicing MIG est‑il celui que vous pensez.
  • Vérifier les clocks/limites de puissance : y a‑t‑il un throttling thermique ou une limitation de puissance.

Deuxième : « La charge est‑elle limitée par le GPU ou par l’entrée ? »

  • Regarder l’utilisation et les proxys d’occupation SM (l’utilisation seule est trompeuse, mais c’est un signal rapide).
  • Vérifier PCIe RX/TX : si le transfert de données est massif, vous pouvez être limité par le pipeline.
  • Vérifier la saturation CPU et l’attente I/O : les dataloaders et le prétraitement dominent souvent.

Troisième : « Est‑ce un décalage de compatibilité ? »

  • Plage pilote vs toolkit : le pilote hôte prend‑il en charge le runtime CUDA du conteneur.
  • NCCL + topologie : de mauvais réglages peuvent forcer silencieusement des chemins lents.
  • Attentes de build du framework : les wheels PyTorch CUDA sont pointilleuses ; les extensions personnalisées le sont encore plus.

Quatrième : « Est‑ce un choix de kernel / régression de perf ? »

  • Les auto‑tuning cuDNN changent entre versions.
  • Les basculements TF32/FP16/BF16 modifient les chemins de calcul.
  • Les nouveaux pilotes changent parfois le comportement du JIT ou les heuristiques d’ordonnancement.

Cet ordre évite le mode d’échec classique : passer six heures à tuner des kernels pour un job bloqué sur un montage NFS lent.

Tâches pratiques : commandes, ce que signifient les sorties et la décision à prendre

Voici les commandes que vous lancez quand vous êtes d’astreinte et que quelqu’un dit « les GPU sont lents », comme si c’était un diagnostic.
Chaque tâche inclut : commande, sortie typique, ce que cela signifie, et la décision à prendre.

Task 1: Verify driver and GPU visibility

cr0x@server:~$ nvidia-smi
Tue Jan 13 10:02:14 2026
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.54.14              Driver Version: 550.54.14      CUDA Version: 12.4   |
|-----------------------------------------+----------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |         Memory-Usage | GPU-Util  Compute M. |
|                                         |                      |               MIG M. |
|=========================================+======================+======================|
|  0  NVIDIA A100-SXM4-40GB          On   | 00000000:81:00.0 Off |                    0 |
| N/A   47C    P0              165W / 400W|   1024MiB / 40960MiB  |     12%      Default |
+-----------------------------------------+----------------------+----------------------+
+---------------------------------------------------------------------------------------+
| Processes:                                                                            |
|  GPU   GI   CI        PID   Type   Process name                            GPU Memory |
|        ID   ID                                                             Usage      |
|=======================================================================================|
|    0   N/A  N/A     19342      C   python                                      1008MiB|
+---------------------------------------------------------------------------------------+

Signification : Le pilote est chargé, le GPU est visible et un processus utilise ~1GB de VRAM.

Décision : Si cela échoue (aucun périphérique), arrêtez et corrigez le pilote/le device plugin/le matériel avant de toucher aux frameworks.

Task 2: Check detailed GPU telemetry (utilization, clocks, throttling clues)

cr0x@server:~$ nvidia-smi -q -d PERFORMANCE,CLOCK,POWER,TEMPERATURE | sed -n '1,120p'
==============NVSMI LOG==============

Timestamp                                 : Tue Jan 13 10:02:30 2026
Driver Version                            : 550.54.14
CUDA Version                              : 12.4

Attached GPUs                             : 1
GPU 00000000:81:00.0
    Power Readings
        Power Management                  : Supported
        Power Draw                        : 165.32 W
        Power Limit                       : 400.00 W
    Clocks
        Graphics                          : 1410 MHz
        SM                                : 1410 MHz
        Memory                            : 1215 MHz
    Temperature
        GPU Current Temp                  : 47 C
        GPU T.Limit Temp                  : 93 C

Signification : Puissance et clocks semblent normales ; pas de bridage thermique.

Décision : Si les clocks sont faibles ou la consommation plafonnée à une limite basse, corrigez la politique de puissance/thermique (ou les contraintes d’instance cloud) avant d’accuser CUDA.

Task 3: Identify who is holding the GPU

cr0x@server:~$ nvidia-smi pmon -c 1
# gpu        pid  type    sm   mem   enc   dec   command
    0      19342     C     12     4     0     0   python

Signification : Un seul processus compute existe ; l’utilisation SM est faible.

Décision : Une faible occupation SM suggère un goulot d’entrée, des blocages de synchronisation ou des tailles de batch trop petites ; examinez le CPU/I/O ensuite.

Task 4: Confirm kernel module is loaded and not failing

cr0x@server:~$ lsmod | egrep 'nvidia|nouveau' | head
nvidia_uvm           1769472  0
nvidia_drm            110592  2
nvidia_modeset       1622016  1 nvidia_drm
nvidia              77168640  92 nvidia_uvm,nvidia_modeset

Signification : Les modules NVIDIA sont chargés ; aucun conflit nouveauau montré.

Décision : Si nouveau est présent, blacklistez‑le et reconstruisez l’initramfs ; les pilotes mixtes causent des bizarreries ressemblant à « CUDA instable ».

Task 5: Check recent kernel logs for GPU errors

cr0x@server:~$ sudo dmesg -T | egrep -i 'nvrm|xid|gpu has fallen|ecc' | tail -n 10
[Tue Jan 13 09:58:07 2026] NVRM: GPU 0000:81:00.0: RmInitAdapter succeeded
[Tue Jan 13 10:01:02 2026] NVRM: Xid (PCI:0000:81:00): 31, pid=19342, Ch 00000028, MMU Fault: ENGINE GRAPHICS

Signification : Les erreurs Xid indiquent des fautes au niveau GPU/pilote (MMU fault ici). Ce n’est pas « votre code PyTorch » tant que l’inverse n’est pas prouvé.

Décision : Quarantainez le nœud, videz‑le des workloads et considérez une mise à jour/downgrade du pilote ou un RMA matériel si récurrent.

Task 6: Validate container GPU access (NVIDIA container runtime)

cr0x@server:~$ docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi
Tue Jan 13 10:03:10 2026
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.54.14              Driver Version: 550.54.14      CUDA Version: 12.4   |
+---------------------------------------------------------------------------------------+

Signification : Le conteneur voit le GPU et le passthrough du pilote fonctionne.

Décision : Si cela échoue, votre problème est l’intégration runtime (device plugin, permissions, configuration du runtime conteneur), pas le framework ML.

Task 7: Check CUDA runtime libraries inside the container

cr0x@server:~$ docker run --rm --gpus all myimage:latest bash -lc "ldconfig -p | egrep 'libcudart|libcublas|libcudnn' | head -n 20"
	libcudart.so.12 (libc6,x86-64) => /usr/local/cuda/lib64/libcudart.so.12
	libcublas.so.12 (libc6,x86-64) => /usr/local/cuda/lib64/libcublas.so.12
	libcudnn.so.9 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libcudnn.so.9

Signification : Le conteneur embarque des bibliothèques CUDA spécifiques.

Décision : Si le framework attend des versions majeures différentes (fréquent avec cuDNN), épinglez les images de base et les builds du framework ensemble. Ne faites pas un « apt upgrade » qui vous lance dans la roulette ABI.

Task 8: Confirm PyTorch sees CUDA and which version it was built against

cr0x@server:~$ python3 - <<'PY'
import torch
print("torch", torch.__version__)
print("cuda available", torch.cuda.is_available())
print("torch built cuda", torch.version.cuda)
print("gpu", torch.cuda.get_device_name(0) if torch.cuda.is_available() else None)
PY
torch 2.4.0
cuda available True
torch built cuda 12.1
gpu NVIDIA A100-SXM4-40GB

Signification : PyTorch est CUDA‑activé et compilé pour CUDA 12.1, s’exécutant sur un pilote hôte qui annonce la capacité CUDA 12.4.

Décision : Si cuda available est false, stoppez : c’est soit un accès périphérique manquant, une wheel CPU‑only, soit des libs incompatibles.

Task 9: Identify GPU memory pressure and fragmentation signals

cr0x@server:~$ nvidia-smi --query-gpu=memory.total,memory.used,memory.free --format=csv
memory.total [MiB], memory.used [MiB], memory.free [MiB]
40960 MiB, 38912 MiB, 2048 MiB

Signification : Vous êtes proche de la saturation de la VRAM.

Décision : Si les jobs plantent par OOM, réduisez la taille de batch, activez le checkpointing d’activation, ou migrez vers des GPU plus grands ; ne « rajoutez pas de swap » parce que la VRAM ne fonctionne pas comme ça.

Task 10: Check PCIe bandwidth and placement sanity (NUMA)

cr0x@server:~$ nvidia-smi topo -m
        GPU0    CPU Affinity    NUMA Affinity
GPU0     X     0-31            0

Signification : GPU0 est attaché au nœud NUMA 0 ; l’affinité CPU suggère quels cœurs sont locaux.

Décision : Épinglez les dataloaders et les threads CPU au nœud NUMA local quand possible ; l’accès mémoire distant peut saboter silencieusement le débit.

Task 11: Catch the classic “GPU idle because dataloader is slow”

cr0x@server:~$ mpstat -P ALL 1 3
Linux 6.5.0 (server) 	01/13/2026 	_x86_64_	(64 CPU)

10:03:58 AM  CPU   %usr  %nice   %sys %iowait  %irq  %soft  %steal  %idle
10:03:59 AM  all   8.12   0.00   2.01   18.44  0.00   0.22    0.00  71.21
10:04:00 AM  all   7.55   0.00   1.88   21.10  0.00   0.25    0.00  69.22

Signification : Un iowait élevé suggère que le CPU est bloqué sur le stockage.

Décision : Avant de toucher à CUDA, corrigez le chemin de données : cache NVMe local, readahead plus grand, moins de petits fichiers, préfetch parallèle, ou un client stockage objet plus rapide.

Task 12: Validate filesystem throughput on the node (data pipeline reality check)

cr0x@server:~$ dd if=/mnt/dataset/bigfile.bin of=/dev/null bs=64M count=16 iflag=direct,status=progress
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 1.244 s, 863 MB/s
16+0 records in
16+0 records out
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 1.24567 s, 862 MB/s

Signification : La lecture séquentielle est d’environ 860MB/s ; cela peut être correct, mais les workloads ML ont souvent besoin aussi de lectures aléatoires et d’opérations metadata.

Décision : Si c’est bas ou saccadé, votre « goulot GPU » est probablement le stockage ou le réseau. Corrigez‑ça et voyez l’utilisation monter comme par magie.

Task 13: Check network health for multi-node training (NCCL sensitivity)

cr0x@server:~$ ip -s link show dev eth0 | sed -n '1,12p'
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9000 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 52:54:00:aa:bb:cc brd ff:ff:ff:ff:ff:ff
    RX:  bytes packets errors dropped  missed   mcast
    9876543210  1234567      0       12       0       0
    TX:  bytes packets errors dropped carrier collsns
    8765432109  2345678      0        0       0       0

Signification : Des paquets RX sont dropés. Sur un cluster chargé, « 12 » peut être du bruit ou le début d’un calvaire.

Décision : Si les drops augmentent pendant l’entraînement, examinez les buffers NIC, les mismatches MTU, le contrôle de congestion et les ports de commutateur. NCCL punira les réseaux instables par des ralentissements qui ressemblent à « mauvais scaling GPU ».

Task 14: Quick NCCL debug for topology/transport mistakes

cr0x@server:~$ NCCL_DEBUG=INFO NCCL_DEBUG_SUBSYS=INIT,NET python3 - <<'PY'
import os
print("NCCL_DEBUG", os.environ.get("NCCL_DEBUG"))
print("NCCL_DEBUG_SUBSYS", os.environ.get("NCCL_DEBUG_SUBSYS"))
PY
NCCL_DEBUG INFO
NCCL_DEBUG_SUBSYS INIT,NET

Signification : Vous avez activé les logs NCCL d’init/network pour un run.

Décision : Utilisez‑les pour confirmer le transport (InfiniBand vs TCP), la sélection d’interface et la détection de topologie. S’il tombe sur TCP de façon inattendue, corrigez la configuration du fabric avant de tuner quoi que ce soit d’autre.

Task 15: Check MIG configuration (multi-tenancy surprises)

cr0x@server:~$ nvidia-smi -L
GPU 0: NVIDIA A100-SXM4-40GB (UUID: GPU-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee)
  MIG 1g.5gb Device 0: (UUID: MIG-GPU-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/1/0)
  MIG 1g.5gb Device 1: (UUID: MIG-GPU-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/2/0)

Signification : Le GPU est partitionné en instances MIG ; votre job peut ne voir qu’une tranche.

Décision : Si la performance est « mystérieusement basse », confirmez que l’ordonnancement a demandé le bon profil MIG. Ne comparez pas une tranche MIG à des chiffres full‑GPU et ne blâmez pas CUDA.

Trois mini-récits d’entreprise (anonymisés, plausibles et douloureusement familiers)

Mini-récit 1 : L’incident causé par une mauvaise hypothèse

Une entreprise de taille moyenne lançait des fine-tunes nocturnes sur une piscine de GPU. Ils avaient tout conteneurisé et se sentaient matures : dépendances Python épinglées, wheel PyTorch épinglée, image de base épinglée.
Quelqu’un a proposé une maintenance hôte « sûre » : mettre à jour le pilote NVIDIA sur toute la flotte pour correspondre au nouveau kernel. La demande de changement contenait la phrase réconfortante « les pilotes sont rétrocompatibles ».

Le déploiement a commencé correctement. Puis une classe de jobs a commencé à échouer avec des erreurs d’accès mémoire illégal. Pas tout de suite. Dix à trente minutes après le démarrage de l’entraînement, ce qui est le pire timing : assez long pour gaspiller de l’argent, assez court pour ruiner le débit.
Les ingénieurs ont chassé des fantômes dans le code du modèle, puis dans les extensions CUDA, puis dans la corruption des dataloaders. L’incident a duré une journée entière parce que les échecs étaient non déterministes et corrélés avec certains nœuds.

La mauvaise hypothèse n’était pas que la rétrocompatibilité existe. C’était de supposer que c’est une garantie universelle pour toutes les combinaisons : branche de pilote, microcode GPU, framework et extensions personnalisées.
Une extension utilisait la compilation JIT via PTX et dépendait d’un comportement qui a changé subtilement avec la mise à jour du pilote. Le JIT a généré un code différent, et sous une forme d’entrée particulière il a déclenché un cas limite.

La correction fut ennuyeuse : épingler la branche de pilote pour ce cluster, reconstruire l’extension avec des cibles arch explicites (fatbin incluant SASS pour les GPU déployés), et ajouter un job canari qui exécutait des formes représentatives pendant une heure avant de promouvoir le pilote.
La leçon fut plus tranchante : « compatible » n’est pas la même chose qu’« identique ». En terre GPU, seul « identique » est le mot que respecte la file d’incidents.

Mini-récit 2 : L’optimisation qui s’est retournée contre eux

Une autre organisation avait un pipeline d’entraînement distribué qui ne scalait pas au‑delà de quatre GPU. Quelqu’un a repéré un coupable familier : les transferts host→device. Ils ont activé un préfetch agressif et augmenté le nombre de workers du dataloader.
Le premier benchmark s’est amélioré. On a fêté. Puis la production a commencé à manquer des SLA, et pas seulement l’entraînement — d’autres services sur les mêmes nœuds sont devenus plus lents aussi.

L’optimisation s’est retournée contre eux parce qu’elle a déplacé le goulot vers des ressources partagées. Plus de workers de dataloader signifiait plus de pression CPU, plus de churn du page cache, et plus de tempêtes de metadata contre le stockage partagé.
Les GPU affichaient une utilisation plus élevée pendant un moment, mais la latence en queue du cluster est devenue horrible. Certains jobs étaient rapides ; d’autres se bloquaient quand le backend stockage était chaud. Les retries amplifiaient la charge, bien sûr.

Le problème racine n’était pas « prefetch mauvais ». C’était prefetch sans isolation des ressources et sans mesurer le système entier.
Ils avaient optimisé pour le débit d’un job unique et accidentellement fait un DoS sur leur propre stockage, ce qui est une manière classique d’apprendre que les IOPS sont une chose réelle et pas une suggestion.

La correction a impliqué de plafonner le nombre de workers, d’ajouter des throttles I/O par job, d’utiliser un cache NVMe local pour les shards chauds, et d’introduire un pipeline qui prétraitait et empaquetait les petits fichiers en blobs contigus plus grands.
L’utilisation GPU a légèrement diminué, mais le débit global et la prévisibilité du système se sont améliorés — ce qui, en production, est le but.

Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise

Une entreprise exécutant de l’inférence avait une règle : chaque service GPU doit exposer un endpoint de diagnostic retournant la version du pilote, les périphériques visibles et les versions des bibliothèques CUDA chargées en processus.
Personne n’aimait cette règle. Elle ressemblait à de la paperasserie en JSON. Elle était appliquée quand même.

Puis une reconstruction d’image est arrivée avec une nouvelle version majeure de cuDNN. Ça fonctionnait en staging (nœud unique, charge légère) et échouait en production (SKU GPU différent, plus haute concurrence). Les erreurs ressemblaient à un générique « CUDNN_STATUS_INTERNAL_ERROR ».
L’ingénieur d’astreinte n’a pas eu à deviner. Il a frappé l’endpoint, vu la discordance de bibliothèques, et l’a corrélée avec la vague de déploiement en quelques minutes.

Le rollback a été rapide parce que les images étaient immuables et versionnées, et parce qu’ils disposaient d’un tag baseline connu‑bon toujours disponible.

Le postmortem n’était pas glamour : meilleurs tests d’intégration contre plusieurs SKU GPU, et une politique exigeant une porte de compatibilité pour les changements majeurs de cuDNN.

Cette règle — l’endpoint de diagnostic ennuyeux — a transformé un incident potentiel de plusieurs heures en un rollback contrôlé. La fiabilité est souvent l’art d’être peu romanesque.

Blague n°2 : « On va juste hotfixer l’image CUDA en production » est l’équivalent GPU de « je vais juste jongler avec des tronçonneuses pour gagner du temps ».

Erreurs courantes : symptômes → cause racine → réparation

1) Symptom: torch.cuda.is_available() is false in containers

Cause racine : Runtime du conteneur non configuré pour les GPU, device plugin manquant, ou exécution d’un build framework uniquement CPU.

Réparation : Validez avec une image de base CUDA connue + nvidia-smi dans le conteneur ; assurez-vous du runtime correct ; installez des wheels CUDA‑enabled.

2) Symptom: “CUDA driver version is insufficient for CUDA runtime version”

Cause racine : Le conteneur embarque un runtime CUDA plus récent que ce que le pilote hôte supporte.

Réparation : Mettez à jour le pilote hôte ou rétrogradez le toolkit/framework du conteneur ; standardisez une matrice de compatibilité par cluster.

3) Symptom: Random illegal memory access / Xid errors under load

Cause racine : Bug du pilote, matériel instable, surchauffe/problèmes d’alimentation, ou un cas limite JIT/PTX déclenché par des kernels spécifiques.

Réparation : Vérifiez dmesg pour les Xids ; mettez les nœuds en quarantaine ; testez avec épinglage de pilote ; reconstruisez les extensions personnalisées avec cibles arch explicites ; envisagez diagnostics matériels/RMA.

4) Symptom: GPU utilization low but CPU and iowait high

Cause racine : Goulot du pipeline d’entrée : latence stockage, trop de petits fichiers, overhead de décompression, CPU sous-dimensionné, mauvais placement NUMA.

Réparation : Profilez le dataloader ; empaquetez les datasets ; utilisez un cache local ; épinglez les threads aux cœurs NUMA‑locaux ; réduisez l’overhead par échantillon.

5) Symptom: Multi-GPU scaling collapses past 2–4 GPUs

Cause racine : NCCL retombe sur TCP, mauvaise connaissance de la topologie, NIC saturée, ou variables d’environnement incorrectes choisissant la mauvaise interface.

Réparation : Activez le debug NCCL ; vérifiez le transport ; corrigez la sélection NIC ; validez le MTU ; assurez-vous que les algorithmes collectifs correspondent à la topologie ; évitez l’oversubscription.

6) Symptom: Performance regression after upgrading cuDNN/cuBLAS

Cause racine : Différentes heuristiques de sélection de kernels, changements d’auto‑tuning, ou chemins rapides désactivés à cause de différences de forme/layout.

Réparation : Relancez des benchmarks avec des formes représentatives ; verrouillez les réglages d’autotune ; épinglez les versions de bibliothèques connues‑bonnes pour les workloads critiques ; envisagez une sélection explicite de kernels quand disponible.

7) Symptom: “Works on A100, slow on L4 (or vice versa)”

Cause racine : Différences d’architecture : génération de tensor cores, taille/bande passante mémoire, comportement des clocks, précisions supportées.

Réparation : Compilez avec les cibles arch correctes ; adaptez tailles de batch et modes de précision par SKU ; conservez des baselines de performance par classe de GPU.

8) Symptom: Inference latency spikes periodically

Cause racine : Fragmentation mémoire GPU, compilation JIT à la première requête, compactages de fond, ou jobs concurrents volant du temps GPU.

Réparation : Chauffez les kernels ; préconstruisez des engines (TensorRT) quand applicable ; réservez des pools mémoire ; imposez l’isolation GPU (MIG/MPS/quotas) ; évitez la co‑tenancy pour les chemins sensibles à la latence.

Listes de contrôle / plan pas-à-pas

Checklist : exécuter CUDA en production sans détester sa vie

  1. Standardisez les branches de pilotes par cluster (pas par nœud) et gatez les changements avec des canaris.
  2. Choisissez une baseline conteneur (OS + famille d’outillage CUDA) et traitez‑la comme une plateforme, pas une suggestion.
  3. Épinglez les builds de frameworks (PyTorch/TF/JAX) à la plateforme ; évitez « latest » sauf si vous aimez les surprises.
  4. Versionnez et figez les extensions CUDA avec des builds arch explicites (fatbins) quand possible.
  5. Exposez des diagnostics runtime : version pilote, périphériques visibles, libs CUDA chargées et info de build.
  6. Séparez les environnements de performance : les nœuds de bench ne doivent pas être partagés avec des voisins bruyants.
  7. Faites des pipelines de données un SLO de première classe : suivez iowait, débit de lecture, opérations metadata, taux de hit du cache.
  8. Gardez des baselines par SKU GPU pour débit et latence ; ne comparez pas des pommes à des tranches MIG.
  9. Décidez votre modèle de multi‑tenancy : MIG, MPS, exclusive mode, ou « pas du tout ». Puis appliquez‑le.
  10. Exercez le rollback : images immuables, tags connus‑bons, et un chemin de downgrade testé pour les pilotes.

Étapes : migrer hors de CUDA (ou au moins en réduire le risque)

  1. Inventoriez ce qui est vraiment spécifique à CUDA : kernels personnalisés, engines TensorRT, hypothèses NCCL, wheels uniquement CUDA.
  2. Mesurez la « performance indispensable » : définissez les deltas acceptables ; 5 % peut être acceptable pour l’entraînement, catastrophique pour les marges d’inférence.
  3. Commencez par les périphéries : prétraitement, inférence CPU, ou flux d’export ONNX sont plus faciles à porter que les ops CUDA personnalisés.
  4. Séparez la correction de la vitesse : obtenez la parité fonctionnelle d’abord, puis tunez.
  5. Construisez un harnais de double exécution : mêmes entrées, comparez sorties/statistiques, détectez la dérive ; ne vous fiez pas au « a l’air bon ».
  6. Planifiez les lacunes opérationnelles : télémétrie, outils de debug, rapport d’erreurs kernel, maturité du packaging.
  7. Gardez CUDA comme fallback jusqu’à ce que l’alternative ait passé plusieurs cycles de release sous trafic réel.

Étapes : mettre à jour pilotes/outils en toute sécurité

  1. Choisissez la cible de compatibilité : quels frameworks et conteneurs doivent fonctionner sur le nouveau pilote.
  2. Construisez une suite canari : jobs représentatifs, incluant extensions personnalisées et runs distribués.
  3. Mettez à jour un petit pool de nœuds et laissez tourner les canaris pendant des heures, pas des minutes.
  4. Surveillez les erreurs Xid et les régressions subtiles : débit, latence tail, schémas d’utilisation mémoire.
  5. Promouvez progressivement avec des triggers de rollback automatiques pour les taux d’échec et les baisses de performance.
  6. Documentez la nouvelle matrice bénie et appliquez‑la dans le CI.

FAQ

1) Is CUDA “the reason” NVIDIA dominates AI?

C’est une raison majeure. CUDA a rendu les GPU programmables à grande échelle, puis l’écosystème de bibliothèques (cuDNN, cuBLAS, NCCL, TensorRT) a rendu la performance et le déploiement répétables.
Le matériel compte, mais le logiciel transforme le matériel en standard industriel.

2) What exactly is the lock-in: code, tooling, or operations?

Les trois. Le verrouillage par le code se produit via les kernels CUDA et les extensions. Le verrouillage outil se produit via des profileurs et débuggeurs adaptés au modèle NVIDIA.
Le verrouillage opérationnel se produit via les patterns pilote/toolkit et une dépendance profonde aux bibliothèques NVIDIA pour la performance et la stabilité.

3) Can I avoid lock-in by writing in PyTorch and never touching CUDA directly?

Vous réduisez le verrouillage au niveau du code source, mais pas au niveau de la plateforme. Vos wheels de framework, le backend distribué (souvent NCCL) et les kernels critiques pour la performance vous lient toujours à l’écosystème CUDA.

4) Why is “driver on host, toolkit in container” such a big deal?

Parce que cela vous permet de standardiser les pilotes au niveau de la flotte tout en autorisant des toolkits par application.
C’est pragmatique : les pilotes sont plus difficiles à mettre à jour en toute sécurité que les conteneurs. Ce pattern a fait ressembler les déploiements GPU à une infrastructure normale.

5) What’s the most common production failure mode with CUDA?

La dérive de versions : une reconstruction de conteneur modifie les bibliothèques CUDA ou les builds de framework, et subitement la combinaison pilote/hôte n’est plus compatible.
La deuxième plus fréquente est « GPU lent » qui est en fait stockage, CPU ou réseau.

6) How do I know if I’m compute-bound or input-bound?

Commencez par l’utilisation GPU et la consommation de puissance. Si l’utilisation est faible et l’iowait CPU élevé, vous êtes input‑bound.
Si la puissance GPU est élevée et l’utilisation stable, vous êtes plutôt compute‑bound. Ensuite, optimisez kernels, modes de précision et tailles de batch.

7) Does MIG reduce lock-in?

MIG réduit la contention des ressources et améliore la prévisibilité de la multi‑tenancy sur le matériel NVIDIA. Il ne réduit pas le verrouillage CUDA ; il augmente votre dépendance aux outils opérationnels et à l’intégration d’ordonnancement de NVIDIA.
Il reste pertinent quand le mix de workload exige de l’isolation.

8) Why do CUDA upgrades sometimes change performance even when nothing crashes?

Les mises à jour de bibliothèques changent les heuristiques de sélection de kernels et peuvent activer/désactiver des chemins rapides.
Les mises à jour de pilotes peuvent modifier la compilation JIT et l’ordonnancement. Votre modèle n’a pas changé, mais le code qu’il exécute peut l’avoir fait.

9) Is it realistic to migrate CUDA workloads to another GPU stack?

Parfois. Si vous dépendez surtout d’ops mainstream des frameworks, la migration consiste davantage à valider la correction et la performance.
Si vous avez des extensions CUDA personnalisées, des engines TensorRT et un comportement NCCL fortement optimisé, la migration devient un programme sur plusieurs trimestres avec des risques réels.

10) What should I standardize first: drivers, toolkits, or frameworks?

Les pilotes d’abord au niveau du cluster, puis les baselines conteneur (toolkits + OS), puis les frameworks.
Standardiser les frameworks sans contrôler le pilote/la plateforme est la voie qui mène à « ça marche sur certains nœuds ».

Conclusion : étapes pratiques suivantes

CUDA a verrouillé l’industrie de la même manière qu’une bonne infrastructure verrouille une entreprise : en étant la chose qui fonctionne de manière fiable à l’échelle, puis en accumulant des outils, des habitudes et des hypothèses de performance autour d’elle.
Si vous gérez des systèmes de production, vous ne combattez pas cela par idéologie. Vous le combattez par la clarté.

  1. Notez votre matrice de compatibilité bénie : branche pilote, image de base, versions de frameworks, versions majeures NCCL/cuDNN.
  2. Implémentez le playbook de diagnostic rapide comme runbook et automatisez les cinq premières vérifications (visibilité GPU, erreurs, utilisation, iowait, drops réseau).
  3. Ajoutez des workloads canaris qui tournent assez longtemps pour attraper les comportements « n’échoue que sous charge », surtout pour JIT/PTX et extensions personnalisées.
  4. Cessez d’appeler les problèmes de données « problèmes GPU » : suivez les métriques stockage et prétraitement à côté des métriques GPU sur le même tableau de bord.
  5. Si vous voulez de l’optionnalité, commencez par isoler le code spécifique CUDA et construire des tests de double exécution. L’optionnalité s’ingénie, elle ne s’invoque pas.

CUDA est une plateforme. Traitez‑la comme telle : versionnez‑la, testez‑la, gatez‑la et observez‑la. Le verrouillage devient gérable quand vous arrêtez de prétendre que ce n’est qu’une bibliothèque qu’on peut mettre à jour au hasard un vendredi.

← Précédent
P-cores vs E-cores : CPU hybrides expliqués sans marketing
Suivant →
E-mail : enregistrements DNS mixtes — la faute de frappe qui tue la délivrabilité (et la correction)

Laisser un commentaire