Vous déployez un conteneur qui « fonctionne définitivement sur mon portable », vous le lancez sur un hôte doté d’un GPU, et votre modèle bascule sur le CPU comme s’il faisait pénitence.
Les logs indiquent « no CUDA-capable device detected », nvidia-smi est absent, ou PyTorch vous informe poliment que CUDA n’est pas disponible.
Pendant ce temps, le GPU est là, inactif, coûteux, et facturé à l’heure.
Les échecs de GPU en conteneur sont rarement mystiques. Il s’agit presque toujours d’un petit ensemble de désaccords : pilote vs. toolkit, runtime vs. configuration Docker,
nœuds de périphériques vs. permissions, ou un ordonnanceur et une couche de sécurité qui font exactement ce que vous avez demandé — juste pas ce que vous vouliez.
Un modèle mental pratique : ce que « GPU dans un conteneur » signifie réellement
Les conteneurs ne virtualisent pas le matériel. Ils isolent les processus via des namespaces et contrôlent l’accès aux ressources via les cgroups.
Votre GPU reste le GPU de l’hôte. Le pilote du noyau vit toujours sur l’hôte. Les nœuds de périphérique dans /dev proviennent toujours de l’hôte.
Le conteneur est fondamentalement un processus avec une vue différente du système de fichiers, du réseau et de l’espace PID.
Pour les GPU NVIDIA, la chaîne critique ressemble à ceci :
- Pilote hôte : le(s) module(s) du noyau et les bibliothèques user-space du pilote sur l’hôte. Si cela est cassé, aucune magie de conteneur ne vous sauvera.
- Nœuds de périphérique :
/dev/nvidia0,/dev/nvidiactl,/dev/nvidia-uvm, etc. Sans eux, l’espace utilisateur ne peut pas parler au pilote. - Intégration du runtime conteneur : quelque chose doit monter les périphériques appropriés et injecter les bibliothèques nécessaires dans le conteneur. C’est le rôle du NVIDIA Container Toolkit.
- Stack CUDA en espace utilisateur : votre image de conteneur peut inclure les bibliothèques CUDA, cuDNN et les frameworks (PyTorch, TensorFlow). Ils doivent être compatibles avec le pilote de l’hôte.
- Permissions et politique : Docker rootless, SELinux/AppArmor, politique d’appareils cgroup, et les contextes de sécurité Kubernetes peuvent bloquer l’accès même quand tout le reste est correct.
Si un lien manque, vous aurez les symptômes habituels : CUDA indisponible, libcuda.so introuvable, nvidia-smi en échec,
ou des applications qui basculent silencieusement sur le CPU.
Une vérité opérationnelle : le chemin GPU n’est pas « configurer et oublier ». C’est « configurer, puis figer les versions, puis surveiller les dérives ».
Mises à jour de pilote, mises à jour du noyau, upgrades Docker, et changements de mode cgroup sont les quatre cavaliers.
Faits intéressants et courte histoire qui expliquent le bazar actuel
- Fait 1 : Les premières approches « GPU dans les conteneurs » utilisaient des flags fragiles
--deviceet des bibliothèques montées à la main, parce que le runtime ne disposait pas d’un hook GPU standardisé. - Fait 2 : L’histoire des conteneurs NVIDIA s’est mûrie lorsque l’écosystème est passé vers des hooks runtime (OCI) qui peuvent injecter des mounts/périphériques au démarrage du conteneur.
- Fait 3 : Les bibliothèques CUDA en espace utilisateur peuvent vivre dans le conteneur, mais le pilote du noyau ne peut pas ; le pilote doit correspondre au noyau hôte et est géré intrinsèquement par l’hôte.
- Fait 4 : La « version CUDA » que vous voyez dans
nvidia-smin’est pas la même chose que l’outil CUDA installé dans votre conteneur ; elle reflète la capacité du pilote, pas le contenu de votre image. - Fait 5 : Le passage de cgroup v1 à cgroup v2 a modifié la façon dont l’accès et la délégation des périphériques se comportent, et cela a cassé des configurations GPU fonctionnelles de façon subtile lors des mises à jour.
- Fait 6 : L’ordonnancement GPU dans Kubernetes n’est devenu courant qu’après la standardisation des device plugins pour annoncer et allouer des GPU ; avant cela, c’était le Far West des pods privilégiés.
- Fait 7 : MIG (Multi-Instance GPU) a introduit une nouvelle unité d’allocation — des tranches de GPU — ce qui a rendu la question « quel GPU mon conteneur a-t-il obtenu ? » non triviale.
- Fait 8 : Les « conteneurs rootless » sont excellents pour la sécurité, mais les GPU ne se prêtent pas naturellement au rootless car les permissions des nœuds de périphériques sont un mur dur, pas une suggestion.
Playbook de diagnostic rapide (vérifiez ceci en premier)
Quand la production brûle, vous n’avez pas besoin d’un doctorat en packaging NVIDIA. Vous avez besoin d’une courte séquence qui réduit rapidement le domaine de défaillance.
Commencez par l’hôte, puis le runtime, puis l’image.
Première étape : prouver que le GPU de l’hôte fonctionne (pas encore de conteneurs)
- Exécutez
nvidia-smisur l’hôte. Si ça échoue, arrêtez. Corrigez d’abord la situation pilote/noyau de l’hôte. - Confirmez l’existence des nœuds de périphérique :
ls -l /dev/nvidia*. S’ils manquent, le pilote n’est pas chargé ou udev n’a pas créé les nœuds. - Vérifiez l’état des modules du noyau :
lsmod | grep nvidiaetdmesgpour des erreurs GPU/pilote.
Deuxième étape : prouver que Docker peut brancher le GPU dans un conteneur
- Exécutez une image CUDA minimale avec
--gpus allet lanceznvidia-smià l’intérieur. Si cela échoue, c’est le runtime/toolkit, pas votre application. - Inspectez la configuration runtime de Docker : assurez-vous que le hook runtime NVIDIA est installé et sélectionné quand c’est nécessaire.
Troisième étape : prouver que votre image applicative est compatible
- À l’intérieur de votre conteneur applicatif, vérifiez la visibilité de
libcuda.soet la compilation CUDA du framework. - Validez la compatibilité pilote/toolkit : pilote trop ancien pour le CUDA du conteneur, ou conteneur qui attend des bibliothèques absentes.
- Vérifiez permissions/politique : mode rootless, SELinux/AppArmor, et restrictions d’appareils cgroup.
Le goulot d’étranglement est généralement découvert à l’étape 4 ou 5. Si vous devinez encore après cela, vous déboguez probablement trois problèmes à la fois.
Ne le faites pas. Réduisez la portée jusqu’à ce qu’une seule chose échoue à la fois.
Tâches pratiques : commandes, sorties attendues et décisions
Voici les tâches que j’exécute réellement quand un conteneur GPU ne se comporte pas. Chacune inclut : la commande, ce que signifie la sortie, et quelle décision prendre.
Exécutez-les dans l’ordre si vous voulez une recherche binaire propre à travers la pile.
Tâche 1 : Vérification de santé de l’hôte avec nvidia-smi
cr0x@server:~$ nvidia-smi
Thu Jan 3 10:14:22 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 A10 On | 00000000:17:00.0 Off | Off |
| 0% 39C P0 62W / 150W | 512MiB / 23028MiB | 3% Default |
+-------------------------------+----------------------+----------------------+
+-----------------------------------------------------------------------------+
Signification : Le pilote est chargé et communique avec le GPU. La « CUDA Version » indique ce que le pilote supporte, pas ce que votre conteneur embarque.
Décision : Si ceci échoue, corrigez d’abord l’hôte : installation du pilote, headers du noyau, Secure Boot, reconstruction DKMS, ou problèmes matériels.
Tâche 2 : Vérifier l’existence des nœuds de périphérique
cr0x@server:~$ ls -l /dev/nvidia*
crw-rw-rw- 1 root root 195, 0 Jan 3 10:10 /dev/nvidia0
crw-rw-rw- 1 root root 195, 255 Jan 3 10:10 /dev/nvidiactl
crw-rw-rw- 1 root root 235, 0 Jan 3 10:10 /dev/nvidia-uvm
crw-rw-rw- 1 root root 235, 1 Jan 3 10:10 /dev/nvidia-uvm-tools
Signification : Les devices caractères existent ; l’espace utilisateur peut parler au pilote du noyau.
Décision : S’ils manquent, chargez les modules (modprobe nvidia) et investiguez pourquoi udev n’a pas créé les nœuds (ou pourquoi le pilote a échoué).
Tâche 3 : Vérifier que les modules du noyau sont chargés
cr0x@server:~$ lsmod | grep -E 'nvidia|nouveau'
nvidia_uvm 1556480 0
nvidia_drm 94208 2
nvidia_modeset 1564672 1 nvidia_drm
nvidia 62480384 80 nvidia_uvm,nvidia_modeset
Signification : Modules propriétaires NVIDIA chargés ; aucun nouveau affiché, ce qui est généralement bon pour les nœuds de calcul CUDA.
Décision : Si nouveau est chargé sur un hôte de calcul, attendez-vous à des problèmes. Mettez-le sur liste noire et reconstruisez initramfs selon la politique de votre OS.
Tâche 4 : Chercher des erreurs du pilote dans dmesg
cr0x@server:~$ dmesg -T | tail -n 12
[Thu Jan 3 10:10:05 2026] nvidia: loading out-of-tree module taints kernel.
[Thu Jan 3 10:10:05 2026] nvidia: module license 'NVIDIA' taints kernel.
[Thu Jan 3 10:10:06 2026] nvidia-nvlink: Nvlink Core is being initialized, major device number 510
[Thu Jan 3 10:10:06 2026] nvidia 0000:17:00.0: enabling device (0000 -> 0003)
[Thu Jan 3 10:10:07 2026] nvidia_uvm: Loaded the UVM driver, major device number 235
Signification : Messages normaux de chargement de module. Cherchez des « failed » ; « tainted » n’est pas le problème ; « RmInitAdapter failed » l’est.
Décision : Si vous voyez des échecs d’initialisation, arrêtez de chasser Docker. Corrigez d’abord le pilote/noyau/matériel.
Tâche 5 : Confirmer que le NVIDIA Container Toolkit est installé (hôte)
cr0x@server:~$ dpkg -l | grep -E 'nvidia-container-toolkit|nvidia-container-runtime'
ii nvidia-container-toolkit 1.15.0-1 amd64 NVIDIA Container toolkit
ii nvidia-container-runtime 3.14.0-1 amd64 NVIDIA container runtime
Signification : Les paquets toolkit/runtime sont présents (style Debian/Ubuntu). Sur RPM utilisez rpm -qa.
Décision : S’ils sont absents, installez-les. S’ils sont présents mais anciens, mettez-les à jour — l’activation GPU n’est pas un composant « configurez et n’y touchez plus ».
Tâche 6 : Vérifier que Docker voit le runtime
cr0x@server:~$ docker info | sed -n '/Runtimes/,+3p'
Runtimes: io.containerd.runc.v2 nvidia runc
Default Runtime: runc
Signification : Docker connaît le runtime nvidia. Le runtime par défaut reste runc, ce qui est correct si vous utilisez --gpus.
Décision : Si le runtime nvidia est absent, le toolkit n’est pas branché dans Docker (ou Docker nécessite un redémarrage).
Tâche 7 : Test minimal de conteneur avec GPU
cr0x@server:~$ docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi
Thu Jan 3 10:16:01 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 |
|===============================+======================+======================|
| 0 NVIDIA A10 On | 00000000:17:00.0 Off | Off |
+-------------------------------+----------------------+----------------------+
Signification : Le runtime a injecté avec succès les périphériques GPU et les bibliothèques du pilote ; le conteneur peut interroger le GPU.
Décision : Si cela échoue, votre image d’application est hors sujet. Corrigez le toolkit/runtime, la politique, ou les permissions des périphériques.
Tâche 8 : Inspecter quels périphériques GPU ont été injectés
cr0x@server:~$ docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 bash -lc 'ls -l /dev/nvidia*'
crw-rw-rw- 1 root root 195, 0 Jan 3 10:16 /dev/nvidia0
crw-rw-rw- 1 root root 195, 255 Jan 3 10:16 /dev/nvidiactl
crw-rw-rw- 1 root root 235, 0 Jan 3 10:16 /dev/nvidia-uvm
crw-rw-rw- 1 root root 235, 1 Jan 3 10:16 /dev/nvidia-uvm-tools
Signification : Le conteneur voit le même type de périphériques caractères que l’hôte.
Décision : Si les périphériques ne sont pas présents dans le conteneur, vous n’utilisez pas réellement le chemin runtime GPU (--gpus manquant, runtime mal configuré, ou politique bloquante).
Tâche 9 : Valider les bibliothèques du pilote injectées (libcuda)
cr0x@server:~$ docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 bash -lc 'ldconfig -p | grep -E "libcuda\.so|libnvidia-ml\.so" | head'
libcuda.so.1 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libcuda.so.1
libnvidia-ml.so.1 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libnvidia-ml.so.1
Signification : Le conteneur peut résoudre les bibliothèques clés côté pilote.
Décision : Si libcuda.so est absent à l’intérieur du conteneur, l’injection du toolkit est cassée ou vous êtes sur une image de base non standard qui perturbe le runtime.
Tâche 10 : Vérifier ce que Docker pense que votre conteneur a demandé
cr0x@server:~$ docker inspect --format '{{json .HostConfig.DeviceRequests}}' gpu-test
[{"Driver":"","Count":-1,"DeviceIDs":null,"Capabilities":[["gpu"]],"Options":{}}]
Signification : Le conteneur a demandé des GPU via l’interface moderne de device request (--gpus all affiche Count:-1).
Décision : Si ceci est vide pour une charge de travail que vous pensiez GPU-activée, vous avez trouvé le bug : votre spec run/compose n’a pas demandé de GPU.
Tâche 11 : Diagnostiquer cgroup v2 et la délégation
cr0x@server:~$ stat -fc %T /sys/fs/cgroup/
cgroup2fs
Signification : Vous êtes sur cgroup v2. La plupart des distributions modernes le sont. Certaines anciennes configurations GPU supposent des comportements v1.
Décision : Si l’accès GPU est instable après une mise à jour OS, revérifiez la version du NVIDIA Container Toolkit et votre pile runtime sur cgroup v2.
Tâche 12 : Vérifier le statut d’AppArmor (bloqueur silencieux courant)
cr0x@server:~$ aa-status | sed -n '1,8p'
apparmor module is loaded.
54 profiles are loaded.
51 profiles are in enforce mode.
docker-default
/usr/bin/man
/usr/sbin/sshd
Signification : AppArmor est actif. Le profil Docker par défaut est généralement acceptable, mais des profils personnalisés peuvent bloquer l’accès aux périphériques.
Décision : Si un profil durci est en place, confirmez qu’il autorise l’accès requis aux devices /dev/nvidia* ou testez avec un profil connu bon.
Tâche 13 : Vérifier le mode SELinux (si applicable)
cr0x@server:~$ getenforce
Enforcing
Signification : SELinux est en mode enforcing. Ce n’est pas un problème en soi ; c’est un problème de politique s’il est mal configuré.
Décision : Si le GPU fonctionne en permissive mais pas en enforcing, vous devez corriger les labels/politiques SELinux pour les nœuds de périphériques et le runtime.
Tâche 14 : Réalité Docker rootless
cr0x@server:~$ docker info | grep -E 'Rootless|Security Options'
Rootless: true
Security Options:
seccomp
rootless
Signification : Vous exécutez Docker en rootless. C’est excellent pour la sécurité, mais l’accès aux GPU est souvent bloqué car l’utilisateur ne peut pas ouvrir les nœuds de périphériques.
Décision : Si le rootless est requis, planifiez une approche supportée (souvent : n’utilisez pas GPU via rootless sur des hôtes partagés sauf si vous contrôlez attentivement les permissions des périphériques).
Tâche 15 : Vérifier dans votre conteneur applicatif que le framework voit CUDA
cr0x@server:~$ docker run --rm --gpus all myapp:latest bash -lc 'python -c "import torch; print(torch.__version__); print(torch.cuda.is_available()); print(torch.version.cuda)"'
2.2.1
True
12.1
Signification : Votre build de framework supporte CUDA et peut l’initialiser.
Décision : Si is_available() est faux mais que nvidia-smi fonctionne, suspectez des libs CUDA manquantes dans l’image, une wheel CPU-only, ou un glibc incompatible.
Tâche 16 : Attraper le classique mismatch « pilote trop ancien »
cr0x@server:~$ docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 bash -lc 'cuda-samples-deviceQuery 2>/dev/null || true'
bash: cuda-samples-deviceQuery: command not found
Signification : Les images de base n’incluent pas les samples ; c’est normal. Le point est de se rappeler que « image CUDA » n’implique pas « toolkit et outils installés ».
Décision : Si vous avez besoin d’outils de compilation, utilisez une image devel. N’installez pas de toolchains de build dans les images runtime à moins d’aimer les CI lentes et les CVE surprises.
Pourquoi ça échoue : les grands modes d’échec en production
1) Le pilote hôte est cassé (et vous déboguez Docker quand même)
Les conteneurs sont un bouc émissaire commode. Mais si le pilote hôte ne peut pas initialiser le GPU, vous n’êtes pas « à un flag près » du succès.
Causes courantes : mise à jour du noyau sans reconstruire les modules DKMS, Secure Boot bloquant les modules non signés, ou une mise à jour partielle du pilote qui a laissé user-space et kernel-space en désaccord.
Le signal : nvidia-smi échoue sur l’hôte. Tout le reste est du bruit.
2) Le NVIDIA Container Toolkit n’est pas installé ou pas branché dans Docker
Docker a besoin d’un hook OCI qui ajoute les nœuds de périphérique, monte les bibliothèques du pilote, et définit des variables d’environnement pour les capacités.
Sans le toolkit, --gpus all ne fait soit rien soit échoue bruyamment, selon les versions.
Si vous utilisez directement containerd, le point d’intégration change. Si vous utilisez Kubernetes, le device plugin GPU est une autre couche où les choses peuvent mal tourner.
Même idée, plus de pièces en mouvement.
3) Vous avez demandé « GPU » dans votre tête, pas dans votre spec
On penserait que cela ne peut pas arriver. Ça arrive tout le temps. Fichiers Compose sans la bonne section, charts Helm qui ne définissent pas les ressources,
ou un job CI qui lance docker run mais sans --gpus.
La pire version est la bascule silencieuse vers le CPU. Votre service reste « healthy », la latence augmente, les coûts montent, et personne ne remarque jusqu’à la revue trimestrielle des performances.
Blague 1 : Un GPU qui n’est pas alloué à votre conteneur n’est qu’un radiateur très cher — sauf qu’il ne chauffe même pas vos mains parce qu’il est inactif.
4) Mismatch capacité pilote vs. toolkit CUDA du conteneur
Le pilote définit quelles fonctionnalités runtime CUDA peuvent être supportées. Le conteneur définit quelles bibliothèques CUDA en espace utilisateur votre app lie.
Si vous expédiez un conteneur construit contre CUDA 12.x et que vous le déployez sur un hôte dont le pilote ne supporte qu’un CUDA plus ancien, l’initialisation peut échouer.
En pratique, les stacks modernes sont plus tolérants qu’avant, mais « tolérant » n’est pas un plan.
Le plan correct est : figer les versions de pilote sur l’hôte et figer les tags des images de base CUDA dans vos builds.
Traitez la compatibilité comme un contrat d’interface, pas comme une impression.
5) Conteneurs rootless et permissions des nœuds de périphérique
Les nœuds de périphérique sont protégés par les permissions Unix et parfois par des couches de sécurité supplémentaires.
Docker rootless exécute des conteneurs comme un utilisateur non-root, ce qui est excellent jusqu’au moment où il faut accéder à /dev/nvidia0 et consorts.
On peut parfois résoudre ça en ajustant la propriété de groupe (par ex. video), des règles udev, ou en utilisant un helper privilégié.
Mais soyez honnête : sur des systèmes multi-tenant, GPU + rootless est souvent un combat de politique déguisé en technique.
6) La politique de sécurité bloque l’accès GPU (SELinux/AppArmor/seccomp)
Les systèmes de sécurité n’« cassent » pas les choses. Ils appliquent des règles. Si vous n’avez pas déclaré les périphériques GPU comme autorisés, l’accès est refusé.
Cela se manifeste par des erreurs de permission ou des frameworks qui échouent à initialiser avec des messages cryptiques.
En entreprise, la solution n’est souvent pas « désactiver SELinux », mais « écrire la politique qui autorise l’accès minimal requis aux périphériques ».
Désactiver les contrôles de sécurité parce que votre job ML est en retard, c’est comment on obtient un autre type d’incident.
7) Kubernetes : allocation GPU et mismatch du device plugin
Dans Kubernetes, le runtime conteneur est une couche, mais l’allocation en est une autre. Si le device plugin NVIDIA n’est pas installé,
l’ordonnanceur ne peut pas assigner de GPU et votre pod ne sera soit pas planifié soit fonctionnera sans accès GPU.
Aussi : les ressources GPU dans Kubernetes sont typiquement demandées via des limits. Si vous oubliez la limite, vous n’obtenez pas de GPU.
C’est cohérent, prévisible, et pourtant surprenant pour quelqu’un chaque semaine.
8) MIG et « il voit un GPU, mais pas celui que vous attendiez »
Avec MIG, le « GPU » que vous recevez peut être une tranche. Les bibliothèques et outils montreront des identifiants différents.
Les opérateurs confondent cela avec « le GPU a disparu ». Non : l’unité d’allocation a changé.
9) Bizarreries du système de fichiers et des chemins de bibliothèques (images scratch, distroless, OS minimal)
Certaines images minimales n’ont pas ldconfig, n’ont pas les répertoires de bibliothèques standards, ou utilisent des layouts de linker dynamique non standards.
L’injection NVIDIA fonctionne en montant les libs du pilote dans des chemins attendus. Si votre image est trop « astucieuse », elle devient incompatible avec cette approche.
Soyez pragmatique : si vous souhaitez un minimalisme maximal, payez la taxe d’intégration. Sinon, choisissez une image de base qui se comporte comme une distribution Linux normale.
10) Échecs de performance : ça « marche », mais c’est lent
Un conteneur GPU fonctionnel peut toujours être un échec en production s’il est lent. Coupables fréquents :
CPU trop sollicité, topologie PCIe et NUMA mal alignées, limites de mémoire du conteneur provoquant du swapping, ou goulots d’entrée/sortie alimentant mal le GPU.
Une utilisation GPU à 5% avec CPU élevé et beaucoup d’iowait n’est pas un problème GPU. C’est un problème de pipeline de données.
idée paraphrasée — John Ousterhout : la fiabilité vient de la simplicité ; la complexité est l’endroit où les bugs et les pannes aiment se cacher.
Erreurs courantes : symptômes → cause → correction
« CUDA n’est pas disponible » dans PyTorch/TensorFlow, alors que le GPU de l’hôte est OK
Symptôme : torch.cuda.is_available() retourne false ; pas d’erreur évidente.
Cause : Build du framework CPU-only dans le conteneur, ou bibliothèques CUDA en espace utilisateur manquantes.
Correction : Installez la wheel/conda activée CUDA, et assurez-vous que votre image de base inclut les runtime CUDA compatibles. Validez avec la Tâche 15.
nvidia-smi marche sur l’hôte, échoue dans le conteneur : « command not found »
Symptôme : Le conteneur ne peut pas exécuter nvidia-smi.
Cause : Votre image n’inclut pas nvidia-smi (c’est une partie des utilitaires user-space NVIDIA), même si le passthrough GPU est correct.
Correction : Utilisez les images de base nvidia/cuda pour déboguer, ou installez nvidia-utils-* dans votre image si vous en avez vraiment besoin (souvent ce n’est pas nécessaire).
nvidia-smi échoue dans le conteneur : « Failed to initialize NVML »
Symptôme : Erreurs NVML à l’intérieur du conteneur.
Cause : libnvidia-ml.so manquante/mal montée, libs pilotes non appariées, ou problème de permission/politique.
Correction : Validez l’injection du toolkit (Tâches 5–9). Confirmez la présence des nœuds de périphérique dans le conteneur (Tâche 8). Vérifiez SELinux/AppArmor (Tâches 12–13).
Le conteneur tourne, mais aucun GPU n’est visible
Symptôme : Le framework voit zéro device ; /dev/nvidia* absent.
Cause : Vous n’avez pas demandé de GPU (--gpus manquant) ou la spec Compose/K8s manque la demande GPU.
Correction : Ajoutez --gpus all (ou des devices spécifiques) et vérifiez avec la Tâche 10. Dans Kubernetes, définissez correctement les limites de ressource GPU.
Fonctionne en root, échoue en non-root
Symptôme : Root peut exécuter CUDA ; non-root reçoit permission denied.
Cause : Permissions/groupes des nœuds de périphérique qui n’autorisent pas l’accès, particulièrement avec Docker rootless.
Correction : Reconsidérez la propriété des nœuds de périphérique (règles udev), l’appartenance aux groupes, ou évitez rootless pour les charges GPU sur cet hôte (Tâche 14).
Après une mise à jour OS, les conteneurs GPU ont cessé de fonctionner
Symptôme : Tout allait bien, puis « soudainement » cassé.
Cause : Mise à jour du noyau ayant cassé la build DKMS NVIDIA, ou changement de mode cgroup, ou mise à jour Docker/containerd sans toolkit compatible.
Correction : Re-validez le pilote hôte (Tâches 1–4), les paquets toolkit (Tâche 5), les runtimes Docker (Tâche 6), et le mode cgroup (Tâche 11).
Boîte multi-GPU : le conteneur voit le mauvais GPU
Symptôme : Le job tourne sur le GPU 0, alors que vous vouliez le GPU 2 ; ou plusieurs jobs se percutent.
Cause : Aucune sélection explicite de device ; dépendance à l’ordre implicite ; absence d’ordonnanceur.
Correction : Utilisez --gpus '"device=2"' ou un ordonnanceur avec allocation correcte. Pour MIG, validez les IDs de tranche et la logique d’allocation.
Ça marche, mais les performances sont catastrophiques
Symptôme : Faible utilisation GPU, temps réel élevé.
Cause : Goulots d’entraînement de données, débit de stockage, affinité CPU/cores, désalignement NUMA, ou tailles de batch trop petites qui sous-utilisent le GPU.
Correction : Profilez bout en bout : vérifiez l’utilisation CPU, l’iowait, et le débit de stockage. Ne « optimisez » pas le GPU tant que vous n’avez pas prouvé que le GPU est le goulot.
Trois mini-histoires d’entreprise venues du terrain
Incident causé par une mauvaise supposition : « La version CUDA dans nvidia-smi est celle du conteneur »
Une entreprise de taille moyenne a déployé un nouveau conteneur d’inférence construit contre un runtime CUDA plus récent. L’équipe a vérifié les nœuds GPU et a vu
nvidia-smi reportant une version CUDA récente. Ils ont supposé que pilote+runtime étaient alignés et ont poussé le déploiement.
Le service n’a pas planté. Il a dégradé. Le framework a discrètement décidé que CUDA n’était pas utilisable et est repassé au CPU. La latence a grimpé, l’autoscaling s’est déclenché,
et le cluster s’est agrandi comme s’il avait découvert un buffet à volonté.
L’ingénieur on-call a fait la bonne chose : il a arrêté de regarder les logs applicatifs et a exécuté un test minimal CUDA en conteneur. Il a échoué avec un mismatch de capacité pilote.
La « CUDA Version » dans nvidia-smi était la capacité du pilote, pas une promesse que l’espace utilisateur de leur conteneur s’initialiserait correctement.
La correction a été ennuyeuse : figer le pilote hôte sur une version compatible avec le runtime CUDA du conteneur, et ajouter un job de préflight qui exécute
nvidia-smi à l’intérieur de l’image de production exacte. Ils ont aussi ajouté une alerte quand l’utilisation GPU chute sous un seuil tandis que le CPU monte — un signal clair de bascule silencieuse.
Optimisation qui s’est retournée contre eux : réduire l’image jusqu’à casser l’injection GPU
Une autre société avait un mandat de sécurité pour réduire les images conteneur. Quelqu’un a voulu bien faire : il est passé d’une base distro standard
à une approche minimaliste/distroless, a supprimé le cache du linker dynamique, et a arraché des répertoires « inutiles ».
L’image était plus petite, le scanner de vulnérabilités était content, et la démo fonctionnait en CI. Puis la production a commencé à échouer seulement sur certains nœuds.
La même image tournait sur un pool GPU et échouait sur un autre. C’est le type d’incohérence qui fait dire des gros mots dans les tickets.
La cause racine n’était pas « NVIDIA est capricieux ». C’était une hypothèse subtile sur la façon dont le runtime injecte les bibliothèques du pilote et attend la disposition du filesystem du conteneur.
L’image minimale n’avait pas les chemins de recherche de bibliothèques conventionnels, donc les bibliothèques injectées existaient mais n’étaient pas trouvées par le linker dynamique.
Ils ont fait marche arrière vers une image de base conventionnelle pour les charges GPU, puis appliqué une approche de durcissement mesurée :
builds multi-étapes, garder les images runtime légères mais pas hostiles, et vérifier avec un test d’intégration qui exécute une vraie initialisation CUDA.
Blague 2 : Rien ne dit « sécurité » comme une image si minimale qu’elle ne trouve pas ses propres bibliothèques.
Pratique ennuyeuse mais correcte qui a sauvé la mise : nœud golden + versions figées + tests canary
Une entreprise régulée exécutait des workloads GPU sur un cluster dédié. Ils avaient deux règles bureaucratiques qui semblaient excessives :
(1) Les nœuds GPU sont construits à partir d’une image golden avec noyau figé, pilote NVIDIA figé, et toolkit figé.
(2) Chaque changement passe par un pool canary qui exécute des tests synthétiques GPU toutes les heures.
Un jour, une mise à jour du dépôt OS a introduit un patch de noyau qui était inoffensif pour le calcul général mais a déclenché un souci de rebuild DKMS dans leur environnement.
Le pool canary a allumé ses alertes en une heure : nvidia-smi a échoué sur les nouveaux nœuds ; le test conteneur synthétique a aussi échoué.
Parce que le cluster utilisait des versions figées, le rayon d’impact a été contenu. Aucun nœud production n’a dérivé automatiquement.
L’équipe a bloqué la mise à jour, corrigé le pipeline de build, reconstruit l’image golden, et l’a promue après que les canaries soient passés.
La morale n’est pas « ne jamais mettre à jour ». C’est « mettre à jour avec intention ». Les stacks GPU sont un problème à trois corps : noyau, pilote, runtime conteneur.
Vous voulez un processus qui empêche la physique de devenir de l’astrologie.
Renforcement et fiabilité : rendre les conteneurs GPU ennuyeux
Figez les versions comme si vous le pensiez vraiment
La stack GPU a plusieurs axes de versions : noyau, pilote, container toolkit, runtime CUDA, framework, et parfois NCCL.
Si vous les laissez flotter indépendamment, vous finirez par créer une combinaison incompatible.
Faites ceci :
- Figez les versions du pilote NVIDIA hôte par pool de nœuds.
- Figez les tags des images de base CUDA (n’utilisez pas
latestsauf si vous aimez ne pas dormir). - Figez les versions des frameworks et enregistrez quelle variante CUDA vous avez installée.
- Mettez à jour en bundle dans un déploiement contrôlé.
Faites de la disponibilité GPU un signal SLO explicite
« Le service est up » n’est pas la même chose que « le service utilise le GPU ». Pour l’inférence, la bascule CPU silencieuse est une bombe à coût et à latence.
Pour l’entraînement, c’est une bombe d’agenda.
Opérationnellement : émettez des métriques pour l’utilisation GPU, la mémoire GPU, et un binaire « CUDA initialisé avec succès » au démarrage du processus.
Alertez sur un mismatch (CPU élevé, GPU faible) pour les services dépendants du GPU.
Séparez les « images debug » des « images production »
Vous voulez des images petites en production. Vous voulez aussi des outils pour déboguer à 03:00. Ne confondez pas les objectifs.
Gardez une variante debug qui inclut bash, procps, peut-être pciutils, et la possibilité d’exécuter nvidia-smi ou un petit test CUDA.
Les images de production peuvent rester légères.
Faites attention à l’élévation de privilèges
Le passthrough GPU ne nécessite pas --privileged dans la plupart des configurations. Si vous l’utilisez « parce que ça règle les problèmes », vous camouflez probablement
une configuration runtime manquante ou une règle de politique absente. Les conteneurs privilégiés augmentent la surface d’attaque et compliquent les revues de conformité.
N’ignorez pas NUMA et la topologie PCIe
Une fois le GPU visible, les problèmes de performance se tracent souvent à la topologie : les threads CPU du conteneur sont planifiés sur un nœud NUMA éloigné du GPU,
ou la carte réseau et le GPU sont sur des sockets différents causant du trafic cross-socket.
Les conteneurs ne corrigent pas automatiquement un mauvais placement. Votre ordonnanceur et la configuration du nœud le font.
Listes de contrôle / plans pas à pas
Pas à pas : activer le support GPU sur un hôte Docker neuf (NVIDIA)
-
Installer et valider le pilote hôte.
Exécutez la Tâche 1. Sinvidia-smiéchoue, n’allez pas plus loin. -
Confirmer les nœuds de périphérique.
Exécutez la Tâche 2. Des nœuds manquants signifient que le pilote n’est pas correctement chargé. -
Installer le NVIDIA Container Toolkit.
Validez avec la Tâche 5. -
Redémarrer Docker et confirmer les runtimes.
Validez avec la Tâche 6. -
Exécuter un test conteneur GPU minimal.
La Tâche 7 doit réussir. -
Bloquer les versions.
Enregistrez noyau, pilote, toolkit, version Docker. Figez-les pour le pool de nœuds.
Pas à pas : déboguer une charge de production qui échoue
- Confirmer la santé du GPU hôte (Tâche 1). Si c’est cassé, stoppez.
- Confirmer le chemin runtime avec une image CUDA connue bonne (Tâche 7).
- Confirmer périphériques et bibliothèques dans le conteneur (Tâches 8–9).
- Confirmer que la charge a réellement demandé un GPU (Tâche 10).
- Vérifier les couches de politique (Tâches 12–14), surtout après des changements de durcissement.
- Vérifier la stack applicative (Tâche 15) pour des builds CPU-only ou dépendances manquantes.
- Ce n’est qu’ensuite que vous traquez la performance : CPU, I/O, taille de batch, placement NUMA.
Checklist opérationnelle : prévenir les régressions
- Garder un pool de nœuds GPU canary. Exécuter des tests synthétiques GPU toutes les heures.
- Alerter sur « GPU attendu mais inutilisé » : GPU faible + CPU élevé pour les services tagués GPU.
- Figer et déployer pilote/toolkit/noyau en bundle.
- Suivre les changements de mode cgroup comme un breaking change.
- Maintenir un conteneur debug capable d’exécuter rapidement des validations GPU de base.
- Documenter les patterns Compose/Helm exacts qui demandent des GPU ; interdire les configurations ad-hoc copiées-collées.
FAQ
1) Pourquoi nvidia-smi fonctionne sur l’hôte mais pas dans le conteneur ?
Habituellement parce que le runtime conteneur n’a pas injecté les devices/bibliothèques (toolkit manquant ou --gpus manquant),
ou une politique de sécurité bloque l’accès aux périphériques. Validez avec les Tâches 6–9 et 12–13.
2) Dois-je installer le pilote NVIDIA à l’intérieur du conteneur ?
Non. Le pilote du noyau doit être sur l’hôte. Le conteneur transporte typiquement les bibliothèques CUDA en espace utilisateur (runtime/toolkit/framework),
tandis que le NVIDIA Container Toolkit injecte les bibliothèques du pilote hôte nécessaires pour parler au pilote du noyau.
3) Que signifie réellement la « CUDA Version » dans nvidia-smi ?
Elle indique la capacité CUDA maximale supportée par le pilote installé. Elle ne vous dit pas quel toolkit CUDA est dans votre image conteneur.
Considérez-la comme « le pilote parle jusqu’à ce dialecte CUDA », pas comme « le toolkit est installé ».
4) Dois-je définir le runtime par défaut de Docker sur nvidia ?
Dans la plupart des configurations modernes, non. Utilisez --gpus et gardez le runtime par défaut comme runc.
Mettre nvidia par défaut peut surprendre les workloads non-GPU et compliquer le débogage.
5) Pourquoi mon framework signale CUDA indisponible alors que nvidia-smi fonctionne ?
Parce que nvidia-smi prouve seulement que NVML peut parler au pilote. Les frameworks ont aussi besoin des bibliothèques runtime CUDA correctes,
d’un build de framework compatible (pas CPU-only), et parfois d’un glibc compatible. La Tâche 15 est votre test de vérité rapide.
6) Puis-je utiliser des GPU avec Docker rootless ?
Parfois, mais attendez-vous à des frictions. Le problème central est la permission d’ouvrir les nœuds de périphérique /dev/nvidia*.
Si vous ne pouvez pas garantir les permissions et l’alignement des politiques, rootless + GPU devient un hazard de fiabilité.
7) Mon conteneur voit le GPU, mais les performances sont terribles. Docker ralentit-il ?
Le surcoût Docker est rarement le principal coupable pour le calcul GPU. Les problèmes de performance viennent généralement de goulots CPU/I/O,
de placement NUMA, de petites tailles de batch, ou d’un pipeline de données affamé. Prouvez que le GPU est le facteur limitant avant d’« optimiser » CUDA.
8) Quelle est la manière la plus sûre de donner l’accès GPU à un conteneur ?
Demandez seulement les GPU dont vous avez besoin (pas all par défaut), évitez --privileged, et reposez-vous sur le chemin d’injection runtime NVIDIA.
Combinez cela avec un pinning serré des images et l’isolation des pools de nœuds pour les workloads GPU.
9) Comment empêcher la bascule silencieuse vers le CPU ?
Ajoutez des checks de démarrage qui échouent rapidement si l’initialisation CUDA échoue, exposez une métrique indiquant l’utilisation GPU, et alertez sur les mismatches CPU/GPU.
« Healthy mais faux » est un piège classique de fiabilité.
Conclusion : prochaines étapes pratiques
Les problèmes GPU en conteneur semblent chaotiques parce qu’il y a plusieurs couches, et chaque couche échoue différemment.
La solution est de la discipline : validez l’hôte, validez l’injection runtime, puis validez l’image applicative.
Ne sautez pas d’étapes. Ne déboguez pas au feeling.
Prochaines étapes que vous pouvez faire aujourd’hui :
- Exécutez le playbook de diagnostic rapide sur un hôte cassé et un hôte connu bon ; comparez les sorties.
- Ajoutez un test smoke CI qui exécute
nvidia-smi(ou l’initialisation CUDA du framework) à l’intérieur de votre image de production. - Figez les versions pilote/toolkit/images CUDA et déployez-les en bundle via un pool canary.
- Construisez une alerte « GPU attendu mais inutilisé » pour détecter la bascule CPU silencieuse avant que la finance ne le remarque.