~/textes/gpu-passthrough-host-reboot
Le jeu qui redémarrait l'hyperviseur
La VM de jeu d'un ami forçait un redémarrage à froid de son hôte Proxmox dès qu'un jeu se lançait, sans rien dans les journaux. La cause se trouvait sous chaque couche qu'on n'arrêtait pas de modifier : un transitoire de puissance au lancement du jeu qui déclenchait une faute PCIe et réinitialisait la machine.
Un invité n'est pas censé pouvoir redémarrer son hyperviseur. C'est tout le principe : la VM vit dans une boîte, et le pire qu'elle puisse faire, c'est de planter elle-même. Alors quand un ami m'a dit que sa VM de jeu sous Arch redémarrait tout l'hôte Proxmox à chaque lancement de jeu, ma première lecture, c'était qu'il avait mal diagnostiqué. L'hôte plantait probablement pour ses propres raisons et le jeu était une coïncidence.
Ça l'était pas. Tu lances le jeu, et une fraction de seconde plus tard la machine physique au complet fait un cycle d'alimentation. Pas la VM. La boîte. Les sessions SSH vers l'hôte tombaient, les ventilateurs ralentissaient, et elle revenait à froid une minute plus tard comme si quelqu'un avait arraché le cordon. La VM atteignait à travers l'hyperviseur et tirait sur la prise du matériel en dessous. Ça devrait pas être possible.
La configuration qui rendait ça bizarre
La carte est une Tesla P100. Ce détail compte plus qu'il en a l'air. C'est une carte de calcul pour centre de données, génération Pascal, aucune sortie d'affichage. Tu peux pas brancher un moniteur dessus. Donc c'était pas un passthrough de GPU de jeu normal où la VM pilote un écran directement. La P100 était passée en passthrough via VFIO comme pur périphérique de calcul et de rendu, la VM faisait tourner un affichage virtuel QXL pour le bureau, et les jeux étaient diffusés sur le réseau. Un GPU sans image, qui rendait des trames qui n'ont jamais existé autrement que comme un flux vidéo.
C'est une bécane assez bizarre pour qu'aucun message de forum y corresponde. La forme du problème, c'était à moi de la trouver.
Capture les preuves avant de pouvoir les perdre
Le premier vrai obstacle, c'est que l'hôte mourait en silence. Pas de panique du noyau, pas d'oops, pas de trace de pile. Le journal s'arrêtait au milieu d'une ligne et l'entrée suivante était le prochain démarrage. Peu importe ce qui se passait, c'était assez rapide et assez bas pour que le noyau n'ait jamais le temps d'écrire quoi que ce soit avant que le courant disparaisse.
Tu peux pas déboguer une machine qui efface ses propres derniers mots. Avant de toucher à autre chose, j'ai configuré netconsole pour expédier les messages du noyau hors de la boîte par UDP vers une autre machine en temps réel. Tout ce que le noyau imprimait dans ses derniers instants se retrouverait déjà dans un journal ailleurs avant que l'hôte meure.
# Ship kernel messages off-box over UDP before the host can die with them.
# 6666 is the listener port on the collector machine.
modprobe netconsole \
netconsole="6666@HOST_IP/eth0,6666@COLLECTOR_IP/COLLECTOR_MAC"
# On the collector, just listen:
# nc -u -l 6666 | tee host-kernel.logPas glorieux, mais tout le reste en dépendait. Avec netconsole en marche, le lancement suivant m'a donné quelque chose à lire : une faute PCIe sur le bus où vivait la P100, journalisée juste avant que les lumières s'éteignent. Pas une erreur logicielle. Une faute au niveau matériel sur le lien, qui escaladait vers quelque chose que la plateforme décidait de gérer par une réinitialisation.
Regarde sous la couche que tu peux modifier
Quand un invité peut redémarrer son hôte, arrête de regarder l'invité. La frontière entre la VM et l'hyperviseur est imposée par le matériel, donc un évènement qui la traverse se produit sous le logiciel que tu n'arrêtes pas de modifier. Le bogue est dans la couche PCIe, le micrologiciel ou le périphérique, pas dans les options de lancement du jeu.
L'échelle des faux correctifs
Une faute PCIe sur une carte en passthrough a une liste bien connue de suspects. Je l'ai descendue. J'ai désactivé AER avec pci=noaer. Coupé ASPM avec pcie_aspm=off. Désactivé le Downstream Port Containment. Désactivé la signalisation SERR sur le pont. Mis un plafond de puissance sur la carte. Lancé memtest toute la nuit au cas où la RAM de l'hôte serait marginale.
pci=noaer pcie_aspm=off ... # each one "helped" for a whileCertains rendaient le plantage moins fréquent. Aucun le faisait arrêter. Et chaque fois que l'intervalle s'allongeait, il y avait une tentation de déclarer ça réglé et de partir. Cette tentation, c'est la partie dangereuse. Un bogue qui prend maintenant dix lancements à reproduire au lieu d'un, c'est pas réglé. C'est caché.
À un moment, j'ai dû le dire clairement : on avait peut-être supprimé le symptôme sans toucher à la cause. Désactiver AER fait pas arrêter les fautes. Ça fait arrêter le noyau de les rapporter. Si la faute sous-jacente continuait de se déclencher et que j'avais juste bâillonné le messager, j'avais rendu le système plus silencieux et tout aussi cassé, avec le problème supplémentaire que je le verrais pas venir. Dire ça à voix haute, c'est ce qui a gardé l'enquête en vie au lieu d'expédier un placebo et d'attendre le prochain mystère.
Ce qui se passait vraiment
L'indice était dans le timing. La faute se déclenchait pas au repos et se déclenchait pas sous charge stable. Elle se déclenchait à la transition : l'instant exact où un jeu démarrait et faisait passer le GPU de presque au repos à plein régime.
La P100 monte ses fréquences agressivement sous charge. Sur un saut de charge soudain, la carte fait grimper les fréquences et la consommation presque instantanément, et ce pic transitoire suffisait à déclencher une faute PCIe sur cette plateforme précise. La carte demandait plus de courant plus vite que le slot ou le lien pouvait le fournir proprement, et la faute que ça produisait était assez grave pour que la réponse de la plateforme soit une réinitialisation.
Chaque faux correctif était en aval de ça. AER, ASPM, DPC, SERR : tous contrôlent comment le système rapporte et réagit aux fautes. Aucun touchait à la chose qui causait la faute, c'est-à-dire le transitoire de fréquence et de puissance au début de la charge. C'est pour ça que chacun aidait un peu et qu'aucun aidait assez. J'avais réglé le système d'alarme pendant que la faute réelle restait intacte.
Le correctif, c'était d'arrêter le transitoire. Figer les fréquences du GPU à plat pour qu'il n'y ait pas de montée soudaine à dépasser, fixées au démarrage avec nvidia-smi et persistées comme service systemd pour survivre aux redémarrages.
[Unit]
Description=Pin Tesla P100 clocks to stop load-transition power faults
After=multi-user.target
[Service]
Type=oneshot
# Persistence mode on, then lock the GPU graphics clock flat.
ExecStart=/usr/bin/nvidia-smi -pm 1
ExecStart=/usr/bin/nvidia-smi -lgc 1189,1189
RemainAfterExit=yes
[Install]
WantedBy=multi-user.targetFige les fréquences à plat et la carte ne monte plus au début de la charge, le transitoire disparaît, et la faute n'a plus rien pour la déclencher. Le plantage a complètement arrêté. Pas moins souvent. Il a arrêté.
Les yaks secondaires
Le plantage de l'hôte était la manchette, mais rendre la bécane pleinement fonctionnelle a voulu dire régler quelques autres affaires que les plantages avaient causées ou cachées.
L'hôte de diffusion était Sunshine, et l'encodage NVENC refusait de fonctionner, lançant cudaErrorNoKernelImageForDevice depuis le kernel de conversion de couleurs. Cette erreur est précise et exacte : le binaire n'a aucune image de code compilée pour la capacité de calcul du GPU. CUDA 13 a laissé tomber le support de Pascal, qui est sm_60, donc une compilation standard n'avait aucune image sm_60 à exécuter. Le correctif, c'était de compiler Sunshine depuis les sources contre CUDA 12.9, le dernier toolkit qui émet encore du code Pascal.
# CUDA 13 dropped Pascal (sm_60). Build against 12.9, which still emits it.
cmake -DCMAKE_CUDA_ARCHITECTURES=60 -DCUDA_TOOLKIT_ROOT_DIR=/opt/cuda-12.9 ..
make -j"$(nproc)"Ensuite je l'ai épinglé avec IgnorePkg dans pacman.conf. Le mode de défaillance d'une mise à jour de routine qui ramène silencieusement CUDA 13, c'est NVENC qui recasse des semaines plus tard avec la même erreur cryptique et aucun lien évident avec quoi que ce soit qui a changé.
Deux autres affaires sont tombées après ça. Les redémarrages antérieurs de l'hôte avaient corrompu un préfixe Proton, et la casse qui en résultait ressemblait à un problème d'options de lancement de jeu. C'est exactement ce que tu obtiens quand un plantage sans rapport endommage un état et que le dommage refait surface ailleurs complètement. Effacer et reconstruire le préfixe a réglé ça. Et le lanceur next-gen de Fallout 4 refusait de démarrer parce qu'il appelait une API Windows, RtlGetDeviceFamilyInfoEnum, que seul Proton Experimental avait implémentée à l'époque. Le correctif, c'était de pointer ce titre vers le bon runtime Proton.
Ce que ça donne au total
Quand un invité peut redémarrer son hôte, le bogue est sous le logiciel que tu n'arrêtes pas de tâter. Aucune quantité de modification d'options de lancement ou de bascule de drapeaux de gestion d'erreurs PCIe ne va l'atteindre. Ce qui a marché, c'est la séquence : capturer les preuves avant que la machine puisse les détruire, refuser d'appeler un symptôme supprimé un correctif, suivre le timing de la faute jusqu'à la couche qui en est vraiment responsable.
Trouver la vraie cause racine a pas juste arrêté un plantage. Ça a aussi expliqué le préfixe Proton corrompu, et ça a retransformé une machine que mon ami avait à moitié abandonnée en une machine en laquelle il a confiance.
Le verrouillage des fréquences, c'est une seule ligne nvidia-smi. Arriver au point où cette ligne était le bon move évident a pris tout le reste.