Supercalculateurs avec des bogues absurdes : pousser les problèmes d’échelle jusqu’à la lune

Cet article vous a aidé ?

Les pannes les plus étranges en calcul haute performance ne sont pas causées par des rayons cosmiques ou des conditions de course exotiques.
Elles proviennent d’hypothèses ordinaires qui deviennent des armes une fois que vous montez à des dizaines de milliers de cœurs,
des millions de fichiers et des réseaux conçus comme une cathédrale.

Si votre code fonctionne bien sur 64 cœurs puis se transforme en citrouille à 4 096, vous n’avez pas un « problème de performance ».
Vous avez un bogue d’échelle. Et les bogues d’échelle sont rarement glamour. Ils sont souvent ennuyeux. C’est pourquoi ils survivent.

À quoi ressemblent les bogues d’échelle dans de vrais supercalculateurs

Un bogue d’échelle survient lorsque quelque chose qui est « acceptable » à petite échelle devient mathématiquement,
physiquement ou organisationnellement impossible à grande échelle.
Pas plus lent. Impossible.

À 8 nœuds, une barrière bâclée est une erreur d’arrondi. À 8 192 nœuds, cette barrière devient une taxe que vous payez à chaque itération,
et le percepteur est une tempête de paquets. À 8 nœuds, ouvrir 10 000 fichiers au démarrage est « assez rapide ».
À 8 192 nœuds, c’est une attaque par déni de service distribuée contre votre serveur de métadonnées.

La partie délicate est que le système semble souvent « sain » de l’extérieur. Les CPU sont occupés, le travail ne plante pas, les liens réseau sont actifs,
et le stockage est « seulement » à 40 % de capacité. Pendant ce temps, votre temps réel double quand vous ajoutez des nœuds.
Ce n’est pas un mystère. C’est de la physique, de la théorie des files d’attente et l’hypothèse innocente d’un développeur sur « encore un MPI_Allreduce ».

Voici l’idée principale : les supercalculateurs échouent de la même manière que les flottes ordinaires.
Ils le font juste à une échelle absurde, avec un couplage plus serré, et des utilisateurs qui mesurent le temps en nœud-heures et en rancœurs.

Les bogues d’échelle sont souvent « absurdes » parce qu’ils sont de taille humaine

  • Chemins de contrôle O(N) qui auraient dû être O(1) deviennent un précipice. Un « gather debug info » en all-to-all se transforme en funérailles all-to-all.
  • Ressources partagées uniques (un verrou, un rang leader, une cible de métadonnées, un thread sur le nœud maître) deviennent des goulets d’étranglement.
  • Paramètres par défaut (tampons TCP, hugepages, limites de l’ordonnanceur, compte de stripes Lustre) ont été choisis pour des charges « raisonnables », pas pour votre chaos.
  • Lacunes d’observabilité transforment la performance en superstition. Sans temporisation par rang, vous vous disputez au sujet de fantômes.

Blague #1 : Un travail à 10 000 cœurs n’est qu’un travail normal avec plus d’occasions de se tromper.

Une citation à garder au mur

« L’espoir n’est pas une stratégie. » — General Gordon R. Sullivan

Si vous exploitez un HPC en production, vous pouvez être optimiste pour la science. Vous ne pouvez pas être optimiste concernant la latence de queue,
les systèmes de fichiers partagés ou la probabilité qu’un nœud sur 5 000 fasse quelque chose d’étrange aujourd’hui.

Faits et histoire qui comptent vraiment

L’histoire du supercalcul est pleine de chiffres FLOPS brillants. Les leçons opérationnelles se trouvent dans les notes de bas de page.
Quelques faits concrets et points de contexte qui changent la façon de penser l’échelle :

  1. La loi d’Amdahl (1967) n’a pas cessé d’être vraie parce que nous avons commencé à acheter des GPU. Les fractions sérielles tuent encore l’accélération à l’échelle.
  2. La loi de Gustafson (fin des années 1980) explique pourquoi la « weak scaling » peut sembler excellente même quand la « strong scaling » s’effondre. Ne les confondez pas.
  3. MPI existe depuis les années 1990 ; les schémas de communication sont bien étudiés. Beaucoup de « nouveaux » bogues d’échelle modernes sont de vieilles erreurs habillées en conteneurs.
  4. Les systèmes de fichiers parallèles comme Lustre ont popularisé la séparation métadonnées/données ; la plupart des ralentissements catastrophiques aujourd’hui sont des pathologies du chemin métadonnées, pas des limites de bande passante brute.
  5. InfiniBand et RDMA ont rendu possibles des collectives à faible latence, mais ils ont aussi facilité la saturation du tissu par des tempêtes de collectives.
  6. Les systèmes de l’ère exascale ont fortement opté pour des nœuds hétérogènes (CPU + GPU). Cela déplace les goulets : la localité PCIe/NVLink et le staging côté hôte sont des tueurs silencieux fréquents.
  7. Checkpoint/restart est devenu opérationnellement obligatoire à mesure que la taille des travaux augmentait ; on ne peut pas traiter les pannes comme des « événements rares » quand on exécute 20 000 composants pendant des heures.
  8. La mise à l’échelle des métadonnées du système de fichiers est devenue une préoccupation majeure lorsque les workflows sont passés de quelques gros fichiers à des millions de petits (pensez aux shards de features ML, aux exécutions ensemblistes et aux journaux par rang).
  9. Les ordonnanceurs (PBS, Slurm, etc.) ont transformé les supercalculateurs en systèmes de production partagés. Cela a introduit la « physique des files d’attente » : backfill, fragmentation et santé des nœuds deviennent des variables de performance.

Les principaux modes de défaillance : calcul, réseau, stockage, ordonnanceur

1) Calcul : le rang le plus lent impose le rythme

Dans le HPC fortement couplé, la performance est dictée par la queue. Un rang subit une tempête de défauts de page, ou une socket fonctionne à une fréquence plus basse,
et tout le travail attend au prochain point de synchronisation. C’est pourquoi « l’utilisation CPU moyenne » est un mensonge que vous vous racontez pour vous rassurer.

Surveillez :

  • Erreurs de localité NUMA : mémoire allouée sur le mauvais socket, bande passante distante, stalls imprévisibles.
  • Sursouscription de threads : des valeurs par défaut OpenMP qui se multiplient à travers les ranks MPI et vident les caches CPU.
  • Régulation de fréquence : plafonds de puissance, thermal throttling, ou baisse de fréquence causée par AVX changeant le timing par nœud.

2) Réseau : les collectives se fichent de vos sentiments

La plupart des effondrements mystérieux à l’échelle sont des surcoûts de communication qui ont grandi plus vite que votre calcul.
Les collectives (Allreduce, Alltoall, Barrier) sont nécessaires ; elles sont aussi le moyen le plus simple de transformer un tissu sophistiqué en parking.

Surveillez :

  • Schémas all-to-all dans les FFT, les transposes et certaines charges ML. Ils sont sensibles à la topologie et à la congestion.
  • Ranks déséquilibrés transformant les collectives en stalls répétées.
  • MTU, crédit ou réglages de file qui ont aidé les microbenchmarks mais déstabilisé le trafic réel.

3) Stockage : la bande passante n’est rarement la première chose qui casse

Les bogues de mise à l’échelle du stockage sont souvent une contention de métadonnées déguisée en « I/O lente ».
Vous le voyez comme : les jobs bloquent au démarrage, ou « open() est lent », ou « stat() prend une éternité », ou le système de fichiers semble correct mais l’application est bloquée.

Surveillez :

  • Millions de petits fichiers au démarrage/fin du job (journaux par rang, sorties par tâche, fichiers temporaires).
  • Tempêtes de checkpoints où de nombreux ranks écrivent simultanément, saturant un sous-ensemble d’OSTs en raison d’un mauvais striping.
  • Mésappariements du caching côté client : cache trop agressif provoquant contention et invalidations, trop peu provoquant des allers-retours métadonnées.

4) Ordonnanceur et opérations : votre job est un invité dans un hôtel bondé

L’échelle n’est pas seulement à l’intérieur du job. Elle est dans le cluster.
Les politiques de l’ordonnanceur, la santé des nœuds et les services partagés (auth, DNS, registres de conteneurs, serveurs de licences) font partie de votre profil de performance.

Surveillez :

  • Placement des jobs : nœuds éparpillés sur des îlots ou des switches leaf, augmentant le nombre de sauts et la contention.
  • Bruit de fond : agents de supervision, mises à jour du noyau, logs hors de contrôle, ou un job voisin qui effectue des I/O « créatives ».
  • Fragilité du plan de contrôle : surcharge de Slurmctld, scripts prolog/epilog lents, ou retards d’authentification à grande échelle.

Méthode de diagnostic rapide (trouvez vite le goulot)

Vous ne gagnez pas de points pour diagnostiquer lentement. Quand un gros job brûle des nœud-heures, vous triagez comme un SRE :
déterminez si vous êtes lié par le calcul, le réseau ou les I/O, puis réduisez le limiteur spécifique.

Première étape : confirmez ce que « mauvais » signifie et isolez l’échelle

  1. Reproduisez à deux tailles : une qui scale « correctement » et une qui ne le fait pas (ex. 256 ranks vs 4096). Si le problème ne varie pas avec l’échelle, ce n’est pas un bogue d’échelle.
  2. Décidez votre métrique : temps par itération, temps pour checkpoint, temps jusqu’à la solution, ou efficacité du job. Choisissez-en une. N’occupez pas de vague.
  3. Vérifiez les changements de phase : démarrage, état stable de calcul, phases de communication, checkpoints I/O, finalisation. Les échecs d’échelle se cachent souvent dans des phases courtes.

Deuxième étape : déterminez l’état d’attente dominant

  1. CPU occupé mais IPC faible suggère mémoire/NUMA ou régulation vectorielle.
  2. Beaucoup de temps dans des appels MPI suggère réseau/collectives ou déséquilibre.
  3. Beaucoup de temps dans open/stat/fsync suggère des pathologies du chemin métadonnées.
  4. Beaucoup de ranks inactifs aux barrières suggère déséquilibre de charge ou un nœud lent.

Troisième étape : trouvez le schéma du « seul élément lent »

À grande échelle, un nœud malade peut brider tout un job.
Ne faites pas de moyennes. Identifiez les valeurs aberrantes par rang et par hôte.

  1. Trouvez les ranks les plus lents (temporisation applicative ou profilage MPI).
  2. Mappez les ranks aux hôtes (ordonnanceur ou mappage mpirun).
  3. Vérifiez la santé locale du nœud : erreurs mémoire, fréquence CPU, compteurs NIC, erreurs client système de fichiers.

Quatrième étape : vérifiez que les services partagés ne sont pas le goulot caché

Les timeouts DNS, la lenteur LDAP, les pulls de registre de conteneur et les vérifications de licence peuvent ressembler à des « blocages applicatifs »
quand ils sont multipliés par des milliers de nœuds. Le cluster peut être « indisponible » sans être « down ».

Blague #2 : Rien ne crée autant d’esprit d’équipe que 4 000 nœuds attendant un seul lookup DNS.

Tâches pratiques : commandes, sorties, décisions (12+)

Ce sont des tâches opérationnelles réalistes que vous pouvez exécuter pendant un incident ou une enquête de performance.
Chacune inclut : une commande, ce que signifie une sortie typique, et la décision que vous en tirez.
Ajustez les chemins et les noms de périphériques à votre environnement.

Task 1: Confirm job placement and node list (Slurm)

cr0x@server:~$ scontrol show job 842193
JobId=842193 JobName=climate_step
   UserId=ana(14021) GroupId=hpc(14000) MCS_label=N/A
   Priority=12233 Nice=0 Account=research QOS=normal
   JobState=RUNNING Reason=None Dependency=(null)
   RunTime=00:18:42 TimeLimit=02:00:00 TimeMin=N/A
   NodeList=cn[1203-1234,1301-1366]
   NumNodes=98 NumCPUs=6272 NumTasks=6272 CPUs/Task=1
   TRES=cpu=6272,mem=1500G,node=98
   MinCPUsNode=64 MinMemoryNode=15000M MinTmpDiskNode=0

Ce que cela signifie : NodeList montre si vous avez obtenu un bloc compact ou une allocation éparpillée. Des plages mixtes peuvent signifier plusieurs îlots réseau.

Décision : Si la performance est sensible à la topologie, demandez des nœuds contigus (contraintes/partitions) ou utilisez les options de l’ordonnanceur sensibles à la topologie.

Task 2: Check job efficiency and find obvious waste (Slurm accounting)

cr0x@server:~$ sacct -j 842193 --format=JobID,Elapsed,AllocCPUS,CPUTime,MaxRSS,AveCPU,State
       JobID    Elapsed  AllocCPUS    CPUTime     MaxRSS     AveCPU      State
842193        00:18:42      6272  19-11:05:24    2100Mc    00:08.2    RUNNING
842193.batch  00:18:42        64  00:19:10      320Mc     00:18.9    RUNNING

Ce que cela signifie : Une AveCPU basse par rapport à Elapsed suggère beaucoup d’attente (MPI, I/O, déséquilibre). MaxRSS aide à repérer la marge mémoire ou le risque de pagination.

Décision : Si AveCPU est bien en dessous de l’attendu, profilez le temps MPI ou I/O ; si MaxRSS est proche des limites mémoire du nœud, attendez-vous à de la pagination et à des ranks lents.

Task 3: Find per-node CPU frequency and throttling hints

cr0x@server:~$ sudo turbostat --quiet --Summary --interval 5 --num_iterations 1
Avg_MHz  Busy%  Bzy_MHz  TSC_MHz  IRQ  SMI  PkgTmp  PkgWatt  CorWatt
  1890    78.3    2415     2300  8120    0    84.0    265.4    202.1

Ce que cela signifie : Si Bzy_MHz est bas sous charge ou si PkgTmp est élevé, vous pouvez subir de la régulation. Busy% près de 100% mais MHz bas est suspect.

Décision : Si la régulation est présente, vérifiez les plafonds de puissance/problèmes thermiques ; envisagez de réduire l’impact des codes lourds AVX ou d’ajuster les politiques de gestion d’énergie.

Task 4: Spot NUMA misplacement quickly

cr0x@server:~$ numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0-31
node 0 size: 256000 MB
node 0 free: 182340 MB
node 1 cpus: 32-63
node 1 size: 256000 MB
node 1 free: 190112 MB
node distances:
node   0   1
  0:  10  21
  1:  21  10

Ce que cela signifie : La distance indique la pénalité d’accès distant. Si des processus tournent sur le nœud 0 mais allouent la mémoire sur le nœud 1, vous payez en bande passante et latence.

Décision : Fixez l’affinité des ranks/threads et de la mémoire de façon cohérente (ex. Slurm –cpu-bind, numactl, ou affinité OpenMP). Validez avec la performance par rang.

Task 5: Identify CPU runqueue pressure and iowait on a node

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

12:10:11 PM  CPU   %usr %nice %sys %iowait %irq %soft %steal %idle
12:10:12 PM  all   61.2  0.0   6.1    18.9  0.0   1.2    0.0  12.6
12:10:12 PM    0   55.0  0.0   5.0    29.0  0.0   1.0    0.0  10.0

Ce que cela signifie : Un %iowait élevé indique que le CPU est bloqué en attente d’I/O. Ce n’est pas « le disque est occupé », mais « votre processus est bloqué ».

Décision : Si iowait monte pendant les checkpoints, enquêtez sur le débit du système de fichiers et les réglages de striping ; si c’est pendant le calcul, regardez la pagination ou les appels métadonnées du système de fichiers.

Task 6: Confirm if you’re paging (a classic slow-rank generator)

cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa
12  0      0 182340  4212  81234    0    0     0    12 5200 8100 62  6 13 19
10  2   8192  10240  3900  22000  120  240  1024  2048 6100 9900 41  7  8 44

Ce que cela signifie : Des si/so non nuls (swap-in/out) sous charge sont mauvais. Même « un peu de swap » à l’échelle crée des traînards.

Décision : Réduisez l’empreinte mémoire, augmentez la demande mémoire par nœud, corrigez les fuites, ou ajustez la taille du problème. Si un seul nœud swap, suspectez un DIMM défectueux ou des limites cgroup mal configurées.

Task 7: Check MPI rank imbalance with a quick-and-dirty timing file

cr0x@server:~$ awk '{sum+=$2; if($2>max){max=$2; rmax=$1} if(min==0||$2

Ce que cela signifie : Un large écart max/min crie déséquilibre ou nœud lent. Le rang max est votre cible d’investigation.

Décision : Mappez le rang lent à un hôte ; vérifiez la santé du nœud, le placement NUMA et les erreurs NIC/stockage sur cet hôte.

Task 8: Map ranks to nodes (Slurm + mpirun style)

cr0x@server:~$ srun -j 842193 -N 1 -n 1 hostname
cn1203

Ce que cela signifie : Validez que vous pouvez cibler des nœuds spécifiques depuis l’allocation. Vous utiliserez cela pour interroger les valeurs aberrantes.

Décision : Si les ranks lents se mappent à un nœud ou à un groupe de commutateurs, vous avez probablement un problème matériel ou de topologie, pas d’algorithme.

Task 9: Check InfiniBand port counters for errors and congestion

cr0x@server:~$ ibstat
CA 'mlx5_0'
	CA type: MT4123
	Number of ports: 1
	Port 1:
		State: Active
		Physical state: LinkUp
		Rate: 200
		Base lid: 1043
		SM lid: 1
		Link layer: InfiniBand
cr0x@server:~$ perfquery -x -r 1 | egrep 'PortXmitWait|PortRcvErrors|PortXmitDiscards'
PortXmitWait....................: 000000000000a1f2
PortRcvErrors...................: 0000000000000000
PortXmitDiscards................: 0000000000000003

Ce que cela signifie : PortXmitWait suggère de la congestion (attente pour transmettre). Les discards indiquent des pertes ; pas normal en état stable.

Décision : Si les compteurs de congestion augmentent pendant les phases lentes, regardez le placement/topologie du job et les algorithmes de collective ; si erreurs/discards augmentent sur un nœud, suspectez un câble/NIC/port de switch.

Task 10: Verify link utilization and drops on Ethernet management or storage networks

cr0x@server:~$ ip -s link show dev eno1
2: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
    RX:  bytes packets errors dropped  missed   mcast
    9876543210  8123456      0     120       0  10022
    TX:  bytes packets errors dropped carrier collsns
    8765432109  7345678      0      42       0      0

Ce que cela signifie : Des paquets perdus à l’échelle peuvent apparaître comme de la « lenteur aléatoire », surtout pour les services du plan de contrôle et les homes basés sur NFS.

Décision : Si les drops augmentent, vérifiez les buffers de ring NIC, le driver/firmware et les files de commutateur ; envisagez d’isoler le trafic bruyant hors des liens de gestion partagés.

Task 11: Identify metadata hot spots on a Lustre client

cr0x@server:~$ lctl get_param -n llite.*.stats | egrep 'open|close|statfs|getattr' | head
open                                   1209341 samples [usec] 1 10 25 100 250 1000 2000 5000 10000 50000
close                                  1209340 samples [usec] 1 10 25 100 250 1000 2000 5000 10000 50000
getattr                                883201 samples [usec] 1 10 25 100 250 1000 2000 5000 10000 50000
statfs                                  12012 samples [usec] 1 10 25 100 250 1000 2000 5000 10000 50000

Ce que cela signifie : Des comptes élevés d’open/getattr suggèrent que l’application martèle les métadonnées. Les buckets de latence (si étendus) montrent si ces appels sont lents.

Décision : Si les métadonnées sont chaudes, réduisez le nombre de fichiers, utilisez une agrégation par nœud, évitez les journaux par rang, et envisagez le hashing de répertoires/striping recommandé par votre système de fichiers.

Task 12: Check OST/MDT health and saturation signals (Lustre server side)

cr0x@server:~$ lctl get_param -n obdfilter.*.kbytesavail | head
obdfilter.fs-OST0000.kbytesavail=912345678
obdfilter.fs-OST0001.kbytesavail=905123456
obdfilter.fs-OST0002.kbytesavail=887654321
cr0x@server:~$ lctl get_param -n mdt.*.md_stats | head
mdt.fs-MDT0000.md_stats:
open                      39123890
close                     39123888
getattr                   82012311
setattr                    1023311

Ce que cela signifie : Un déséquilibre de capacité peut créer des hotspots involontaires. Des ops MDT élevées se corrèlent avec « démarrage lent » et « finalisation bloquée ».

Décision : Si un OST est beaucoup plus plein, rééquilibrez ou ajustez le striping ; si les ops MDT sont extrêmes pendant les tempêtes de jobs, ajustez le comportement client et corrigez les motifs d’accès applicatifs.

Task 13: Observe per-process I/O with pidstat

cr0x@server:~$ pidstat -d -p 21344 1 3
Linux 5.15.0 (cn1203) 	01/22/2026 	_x86_64_	(64 CPU)

12:12:01 PM   UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s iodelay  Command
12:12:02 PM  14021     21344      0.00  51200.00      0.00      87  climate_step
12:12:03 PM  14021     21344      0.00  48000.00      0.00      91  climate_step

Ce que cela signifie : Un débit d’écriture élevé plus iodelay indique que le processus est bloqué sur l’I/O. Si de nombreux ranks montrent cela simultanément, c’est un goulot du système de fichiers.

Décision : Coordonnez le timing des checkpoints, augmentez le striping, réduisez la fréquence, ou utilisez des burst buffers locaux si disponibles.

Task 14: Detect “small I/O” pathology with iostat

cr0x@server:~$ iostat -dxm 1 2
Linux 5.15.0 (cn1203) 	01/22/2026 	_x86_64_	(64 CPU)

Device            r/s     w/s    rMB/s    wMB/s  avgrq-sz  avgqu-sz   await  svctm  %util
nvme0n1          0.0   3200.0     0.0     48.0     30.7       9.4     3.1    0.2   64.0

Ce que cela signifie : Beaucoup d’IOPS avec avgrq-sz faible signifie petites écritures. Await augmente quand la file s’accumule. %util montre la saturation du périphérique.

Décision : Si c’est local au nœud, corrigez le buffering et le batching ; si c’est une mount partagée, le motif s’amplifie à l’échelle—agrégez les écritures et alignez les tailles I/O.

Task 15: Find syscall hotspots (open/stat/fsync) with strace summary

cr0x@server:~$ strace -c -p 21344 -f -q -e trace=openat,statx,futex,fsync,close -t -o /tmp/strace.out
strace: Process 21344 attached
^Cstrace: Process 21344 detached
cr0x@server:~$ tail -n 8 /tmp/strace.out
% time     seconds  usecs/call     calls    errors syscall
 62.14    1.840231          92     20007      102 openat
 18.77    0.555880          71      7821        0 statx
 11.02    0.326112          40      8099        0 futex
  8.07    0.239055         310       771        0 fsync

Ce que cela signifie : Si openat/statx dominent, vous avez de l’intensité métadonnées. Si futex domine, vous pouvez avoir de la contention sur des verrous. Si fsync domine, vous payez le coût de durabilité.

Décision : Pour les métadonnées, réduisez les opérations de fichiers et utilisez moins de fichiers ; pour la contention futex, refactorez le threading ou réduisez les verrous partagés ; pour fsync, regroupez les syncs ou utilisez des points de durabilité moins fréquents.

Task 16: Check kernel and filesystem client logs for “soft” errors

cr0x@server:~$ dmesg -T | tail -n 12
[Thu Jan 22 12:06:41 2026] Lustre: llite fs-ffff8c2b3c2c8800: server not responding, reconnecting
[Thu Jan 22 12:06:43 2026] Lustre: llite fs-ffff8c2b3c2c8800: Connection restored
[Thu Jan 22 12:08:10 2026] mlx5_core 0000:41:00.0: CQE error: syndrome 0x2 vendor syndrome 0x0
[Thu Jan 22 12:08:10 2026] mlx5_core 0000:41:00.0: Dumping QP 0x1a2b

Ce que cela signifie : Les reconnexions transitoires et les erreurs CQ NIC peuvent créer des ranks lents sans défaillance dure. Ce sont les bogues « absurdes » qui ruinent les graphes d’échelle.

Décision : Si seulement un sous-ensemble de nœuds montre cela, mettez-les en drain dans l’ordonnanceur et ouvrez un ticket matériel ; si c’est généralisé, suspectez un incident du tissu/système de fichiers.

Trois mini-récits du monde de l’entreprise (anonymisés)

Mini-récit 1 : La mauvaise hypothèse (et la taxe MPI_Barrier)

Un groupe de recherche a apporté une nouvelle simulation dans un cluster HPC d’entreprise — code sérieux, science sérieuse, budget sérieux.
Les développeurs avaient validé la justesse sur une partition modeste et ont demandé « autant de nœuds que vous pouvez » pour respecter un délai.

La première exécution a bien scalé jusqu’à quelques centaines de ranks. Au-delà, la performance s’est détériorée.
Le job ne plantait pas ; il ne s’accélérait tout simplement plus. Les utilisateurs ont blâmé « le réseau ».
L’équipe réseau a blâmé « le code ». Tout le monde avait à moitié raison, ce qui est le pire genre de raison.

Le profilage a montré une fraction choquante du temps passée dans MPI_Barrier. Ce n’est pas automatiquement un crime,
mais ce n’est jamais un compliment. Le vrai coupable était une hypothèse : un bloc de « timing debug » qui rassemblait
des métriques par rang à chaque itération et sérialisait la sortie via le rang 0. C’était supposé être temporaire.
C’était devenu permanent par inertie.

À petite échelle, le coût de la barrière était masqué par le calcul. À grande échelle, la barrière est devenue un point de
synchronisation global qui a amplifié de légers déséquilibres en arrêts majeurs. Le rang 0 faisait ensuite un travail supplémentaire pour formater
et écrire des logs. Le « debug temporaire » est devenu un générateur de déséquilibre de charge.

La correction a été presque insultante de simplicité : collecter le timing toutes les N itérations, agréger hiérarchiquement (au niveau du nœud puis global),
et écrire un enregistrement structuré compact par checkpoint au lieu de par itération. Le scaling a récupéré.
Pas parce que le réseau était devenu plus rapide, mais parce que le code a arrêté d’exiger que le réseau se comporte comme par magie.

Mini-récit 2 : L’optimisation qui s’est retournée (striping pour la gloire, contention pour la réalité)

Une équipe plateforme a essayé d’aider une charge analytique qui écrivait de gros fichiers de checkpoint.
Quelqu’un a suggéré d’augmenter le striping Lustre sur de nombreux OSTs pour « obtenir plus de bande passante ».
Ça a fonctionné dans un petit benchmark. Ils l’ont déployé largement via un module qui définissait le striping par défaut
pour un répertoire de projet entier.

Puis la production est arrivée. La charge ne copiait pas un gros fichier séquentiel par job.
Elle écrivait de nombreux fichiers moyens et effectuait des opérations métadonnées fréquentes. Les étendre largement a augmenté la quantité de
coordination que le système de fichiers devait faire. Ça a aussi augmenté la probabilité qu’au moins un OST soit occupé,
transformant la latence de queue en facteur contrôlant.

Le symptôme le plus douloureux : les jobs sont devenus imprévisibles. Certains allaient bien, d’autres ramenaient.
Les utilisateurs ont fait ce que font les utilisateurs — ils ont relancé. Cela a multiplié la charge et donné l’impression que le cluster était hanté.

Le diagnostic est venu en corrélant les périodes lentes avec le déséquilibre de charge des OST et une montée des ops métadonnées.
« Plus de stripes » a augmenté le fanout cross-OST et la contention, ce qui a nui quand le motif d’accès n’était pas du gros I/O en streaming. L’optimisation a résolu un problème de labo et créé un problème de production.

Ils ont rétabli le striping par défaut, donné des recommandations sensées par charge, et ajouté un garde-fou :
les jobs utilisant le template de répertoire projet avaient un contrôle préliminaire pour valider la taille des fichiers et recommander le nombre de stripes.
Le système de fichiers n’est pas devenu plus rapide ; il est redevenu prévisible. Prévisible bat rapide dans les systèmes partagés.

Mini-récit 3 : La pratique ennuyeuse qui a sauvé la mise (drainer les nœuds défaillants)

Une charge longue a commencé à échouer de manière intermittente, mais seulement à grande échelle.
Ça ressemblait à un bug applicatif : blocages aléatoires, timeouts MPI occasionnels, et ralentissements qui disparaissaient au rerun.
Le tableau de bord santé du cluster était vert. Bien sûr qu’il l’était.

L’équipe opérations avait une habitude ennuyeuse : elle suivait les « nœuds suspects » sur la base de compteurs bas-niveau,
pas seulement des pannes dures. Un nœud qui consignait des erreurs CQ NIC récurrentes ou des reconnexions système de fichiers allait sur une liste de surveillance.
Après un seuil, il était drainé de l’ordonnanceur et testé hors ligne.

Pendant l’incident, ils ont mappé les ranks MPI les plus lents aux hôtes et trouvé un schéma :
un petit ensemble de nœuds hébergeait répétitivement des traînards. Ces nœuds n’avaient pas d’erreurs dramatiques — juste de petits avertissements fréquents.
Assez pour ralentir un rang. Assez pour bloquer des milliers.

Ils ont drainé les nœuds, relancé la charge, et le « bug applicatif » a disparu.
Le matériel a été remplacé plus tard. L’équipe science a obtenu ses résultats à temps.
La pratique n’était pas brillante. Elle était disciplinée.

Erreurs courantes : symptômes → cause racine → correction

Les échecs d’échelle se répètent parce qu’ils se ressemblent vus de loin. Voici les classiques, écrits dans le format dont vous avez vraiment besoin pendant un feu.

1) Symptom: adding nodes makes the job slower

Cause racine : Vous scalez en strong-scaling au-delà du point où la communication et la synchronisation dominent ; ou vous avez introduit un chemin global sérialisé (I/O rang 0, barrières, verrous).

Correction : Réduisez les collectives globales, superposez communication et calcul, utilisez des réductions hiérarchiques, et mesurez le temps dans les appels MPI. Arrêtez de scaler au-delà du « genou » de la courbe.

2) Symptom: job “hangs” at startup or at the end

Cause racine : Tempête métadonnées : des milliers de ranks faisant stat/open/unlink dans le même répertoire, ou concurrençant des environnements/modules Python partagés.

Correction : Placez le logiciel localement, utilisez des caches partagés par nœud, évitez la création de fichiers par rang, regroupez les sorties, et distribuez les arbres de répertoires. Traitez les métadonnées comme une ressource rare.

3) Symptom: periodic multi-minute stalls during steady-state compute

Cause racine : Rafales de checkpoints ou événements de récupération du système de fichiers en arrière-plan ; ou voisins bruyants saturant des OSTs/MDTs partagés.

Correction : Échelonnez les checkpoints, utilisez des burst buffers, ajustez le striping selon la taille des fichiers, et coordonnez des fenêtres de checkpoint cluster-wide pour les gros jobs.

4) Symptom: only some runs are slow; reruns “fix” it

Cause racine : Problèmes de queue : un nœud dégradé, erreurs de port de switch, placement déséquilibré, ou un OST plus chaud que les autres.

Correction : Identifiez les ranks/hôtes outliers ; drenez les nœuds suspects ; imposez un placement sensible à la topologie ; rééquilibrez les cibles du système de fichiers.

5) Symptom: high CPU utilization but poor progress

Cause racine : Spin-waiting, contention sur des verrous, ou busy-polling dans la pile MPI ; parfois une variable d’environnement mal définie provoque un polling agressif.

Correction : Profilez avec perf et des résumés strace ; ajustez les paramètres de progrès MPI ; réduisez le partage de verrous ; reconsidérez le pinning des threads.

6) Symptom: high iowait on compute nodes during “compute” phase

Cause racine : I/O cachée : pagination, logging, appels métadonnées, ou lecture répétée de fichiers de configuration partagés.

Correction : Éliminez le swap, tamponnez les logs, mettez les configs en cache en mémoire, et réduisez les appels système de fichiers dans la boucle chaude.

7) Symptom: network looks fine, but MPI time is huge

Cause racine : Inadéquation d’algorithme de collective, congestion due à la topologie, ou tailles de messages déclenchant des protocoles défavorables.

Correction : Utilisez le profilage MPI pour trouver l’appel ; essayez des algorithmes collectifs alternatifs (si votre MPI le permet) ; utilisez un placement sensible à la topologie ; réduisez la fréquence des all-to-all.

Listes de contrôle / plan pas à pas

Checklist A: When a big job scales badly (first hour)

  1. Obtenez deux points de données : un run « bonne échelle » et un run « mauvaise échelle » avec la même entrée et la même build.
  2. Confirmez le placement des nœuds et si l’allocation est fragmentée.
  3. Décomposition du temps : calcul vs MPI vs I/O vs « autre ». Si vous ne pouvez pas décomposer, ajoutez une instrumentation minimale.
  4. Trouvez les ranks les plus lents et mappez-les aux hôtes.
  5. Vérifiez ces hôtes pour régulation, swap, erreurs NIC, reconnexions du système de fichiers.
  6. Cherchez les tempêtes de métadonnées : comptes d’open/stat/unlink, journaux par rang, fichiers temporaires.
  7. Vérifiez les dépendances aux services partagés : DNS/LDAP, pulls de conteneurs, serveurs de licences.
  8. Faites un changement à la fois. Les bogues d’échelle adorent les variables confondantes.

Checklist B: Storage sanity for HPC workloads

  1. Mesurez le nombre de fichiers et la disposition des répertoires avant d’exécuter à grande échelle.
  2. Adaptez le nombre de stripes à la taille des fichiers et au motif d’accès (streaming vs petits I/O aléatoires).
  3. Évitez la création de fichiers par rang dans des répertoires partagés.
  4. Écrivez moins, mais plus gros fichiers ; regroupez les opérations métadonnées.
  5. Échelonnez les checkpoints ; ne laissez pas 10 000 ranks fsync en même temps sauf si vous aimez le chaos.
  6. Surveillez les ops MDT et l’utilisation des OST, pas seulement la « bande passante globale ».

Checklist C: Network and MPI sanity

  1. Mesurez le temps dans les appels MPI (pas seulement le runtime total).
  2. Identifiez les hotspots de collectives (Allreduce, Alltoall) et leur fréquence.
  3. Vérifiez les compteurs du tissu pour congestion et erreurs ; isolez par nœuds ou systémique.
  4. Assurez-vous que le pinning des ranks/threads est correct ; les erreurs NUMA peuvent se déguiser en problèmes réseau.
  5. Utilisez un placement sensible à la topologie pour les gros jobs ; évitez d’étendre sur des îlots sauf nécessité.

Checklist D: Boring operations that prevent “mystery slowness”

  1. Drainer les nœuds avec des erreurs correctables récurrentes ou des avertissements NIC, pas seulement des pannes dures.
  2. Gardez le firmware et les drivers cohérents sur la flotte ; l’hétérogénéité engendre des heisenbugs.
  3. Définissez et appliquez des valeurs par défaut sensées pour les modules d’environnement (nombre de threads, pinning, libs I/O).
  4. Exécutez régulièrement des microbenchmarks automatisés pour réseau et système de fichiers afin d’établir des bases.

FAQ

1) Why does performance get worse when I add nodes?

Parce que vous payez des coûts de coordination croissants (communication, synchronisation, contention métadonnées) qui dépassent vos gains de calcul.
Le strong scaling a une limite ; trouvez-la et arrêtez de faire semblant qu’elle n’existe pas.

2) How do I know if I’m network-bound or just imbalanced?

Si le temps MPI est élevé et que quelques ranks sont constamment plus lents, c’est souvent un déséquilibre ou un nœud lent.
Si tous les ranks passent un temps similaire dans les collectives et que les compteurs du tissu montrent de la congestion, c’est le réseau/topologie.

3) Is the parallel filesystem “slow,” or is my application doing something silly?

Comptez les opérations métadonnées et les petits I/O. Si vous créez des millions de fichiers, stattez les mêmes chemins de façon répétée,
ou écrivez de petits morceaux avec fsync, vous ferez paraître n’importe quel système de fichiers lent.

4) What’s the fastest way to detect a metadata storm?

Cherchez des comptes massifs de open/stat/getattr et des plaintes d’utilisateurs sur des délais de démarrage/finalisation.
Sur Lustre, les stats client et mdt md_stats sont votre système d’alerte précoce.

5) Why do MPI collectives become a cliff at scale?

Beaucoup de collectives ont des coûts qui croissent avec le nombre de ranks, la taille des messages et la topologie.
Elles sont aussi sensibles à la latence de queue : un participant lent ralentit toute l’opération.

6) Should we always increase Lustre striping to get more bandwidth?

Non. Étendre le striping peut aider pour du I/O séquentiel large, mais peut se retourner contre vous pour de nombreux fichiers moyens, des motifs d’accès mixtes,
ou quand cela amplifie la contention et la latence de queue. Mesurez et adaptez au workload.

7) What’s the most common “silly bug” you see in HPC applications?

Journaux par rang et fichiers temporaires par rang dans un répertoire partagé, surtout au démarrage et à l’arrêt.
C’est l’équivalent opérationnel de tout le monde essayant de sortir par une seule porte.

8) How do you deal with “only fails at scale” issues?

Traitez-le comme un incident SRE : reproduisez à deux tailles, isolez les phases, identifiez les ranks/hôtes outliers, et corrélez avec les compteurs système.
Puis supprimez une variable à la fois jusqu’à ce que le précipice disparaisse.

9) What’s more important: peak performance or predictability?

Dans les systèmes de production partagés, la prévisibilité gagne. Un job légèrement plus lent mais stable économise plus de nœud-heures qu’un job « rapide »
qui explose occasionnellement en retries et resubmissions.

Conclusion : prochaines étapes pratiques

Les supercalculateurs n’échouent pas de manière exotique. Ils échouent comme des versions à grande échelle des erreurs quotidiennes :
un chemin sérialisé, une tempête d’opérations système de fichiers minuscules, un nœud malade qui traîne une collective, une fonctionnalité de debug « temporaire »
devenue taxe permanente.

Prochaines étapes qui font vraiment avancer les choses :

  1. Instrumentez votre application avec des temporisations par phase et par rang pour voir le déséquilibre et l’attente.
  2. Adoptez la méthode de diagnostic rapide et entraînez-vous les jours sans incident, quand vous pouvez réfléchir.
  3. Corrigez les motifs de fichiers avant de corriger les systèmes de fichiers : moins de fichiers, moins de stats, moins de fsync, agrégation plus intelligente.
  4. Rendez explicites la topologie et le placement pour les gros jobs ; ne laissez pas cela au hasard de l’ordonnanceur.
  5. Drainer agressivement les nœuds suspects ; une NIC défaillante peut transformer « scaling » en « souffrance ».

Faire monter les problèmes à la lune est impressionnant. Les résoudre avec une correction ennuyeuse est la façon de garder les lumières allumées.

← Précédent
Le mur thermique : comment la physique a mis fin à l’histoire préférée du marketing
Suivant →
Ray tracing : ce qu’il apporte vraiment au-delà de jolies captures d’écran

Laisser un commentaire