RDMA en pratique + conception de la liaison montante du cluster
Les articles précédents de la série N ont plaidé pour le RDMA (N02) et détaillé les choix de topologie (N04, N05). Celui-ci est la partie pratique : installer les pilotes, prouver que le chemin fonctionne, activer GPUDirect, valider NCCL, puis prendre de la hauteur pour réfléchir à la manière dont l'ensemble du cluster se raccorde au monde extérieur.
Nous partons d'Ubuntu 22.04 ou 24.04, de cartes réseau Mellanox/NVIDIA ConnectX-5 ou ConnectX-6, et soit d'InfiniBand HDR/NDR, soit de RoCEv2 sur un réseau Ethernet sans perte. Les commandes sont exactement celles que nous saisissons sur les bancs de test Kentino avant l'expédition d'un cluster Kentino AI à 4 nœuds.
Pilotes : MLNX_OFED ou rdma-core amont ?
| Option | Ce que vous obtenez | Quand la choisir |
|---|---|---|
| MLNX_OFED (désormais NVIDIA DOCA-OFED) | Pack de pilotes testé par NVIDIA, GPUDirect peermem, perftest, mlxconfig | Clusters IA en production avec ConnectX-6/7 et GPUDirect |
rdma-core amont + mlx5 intégré au noyau
|
Ce que livre Ubuntu, sans dépôt supplémentaire | Machines de laboratoire, mono-nœud, sans GPUDirect ni outillage firmware |
Pour tout ce qui transporte du trafic NCCL en production, installez MLNX_OFED. Le mlx5 amont fonctionne, mais vous perdez mlxconfig, le perftest fourni et — surtout — un nvidia-peermem côté noyau testé contre le même arbre OFED.
Une installation propre sur Ubuntu 22.04 avec le noyau 5.15.x :
tar xf MLNX_OFED_LINUX-*.tgz && cd MLNX_OFED_LINUX-*
sudo ./mlnxofedinstall --add-kernel-support --with-nvmf --force
sudo /etc/init.d/openibd restart && sudo systemctl enable openibd
Le drapeau --add-kernel-support est déterminant. Omettez-le sur un noyau absent de la matrice OFED et la compilation DKMS échoue en silence : vous vous retrouvez à faire tourner le mlx5 d'origine sans le savoir. Vérifiez la pile en espace utilisateur avec dpkg -l | grep -E 'libibverbs|rdma-core|mlnx-ofed'.
Monter le lien et confirmer que la carte voit le réseau
Trois commandes vous disent tout ce qui compte :
sudo mst start && mst status # outils firmware
ibstat # état du port, largeur, vitesse
ibv_devinfo -v # GID, max_qp, MTU, révision matérielle
Un port en bonne santé affiche State: Active, Physical state: LinkUp, le Rate: attendu (par exemple 200) et la bonne valeur de Link layer: (InfiniBand ou Ethernet). Les deux champs que l'on oublie le plus souvent :
-
Link layer: InfiniBandcontreEthernet. Une ConnectX bimode bascule avecmlxconfig -d /dev/mst/mt4125_pciconf0 set LINK_TYPE_P1=2(1=IB, 2=Ethernet). Redémarrage obligatoire. -
Le
Rate:correspond à la vitesse attendue. Un port NDR à 200 Gb/s remonté à 100 Gb/s est la panne silencieuse la plus fréquente : câble défectueux, longueur de DAC inadaptée ou port de commutateur bridé. Vérifiez-le avant tout benchmark.
En RoCE, confirmez aussi que la v2 est sélectionnée (la v1 utilise l'ethertype 0x8915, la v2 passe en UDP/4791 et c'est ce qu'emploie toute pile moderne) : sudo cma_roce_mode -d mlx5_0 -p 1 -m 2.
Gestionnaire de sous-réseau (InfiniBand uniquement)
InfiniBand n'est pas de l'Ethernet : rien ne circule sur le réseau tant qu'un gestionnaire de sous-réseau (SM) n'a pas attribué les LID. Sur un réseau de laboratoire, lancez sudo apt install opensm && sudo systemctl enable --now opensm sur un seul nœud. En production, utilisez le SM embarqué du commutateur. Deux SM logiciels en concurrence, c'est un après-midi de débogage dont personne n'a besoin. RoCE n'a pas de SM : le routage est l'affaire du réseau Ethernet, raison pour laquelle la configuration RoCE porte surtout sur la QoS des commutateurs, pas sur l'hôte.
Prouver que le RDMA fonctionne vraiment : perftest
Avant toute exécution NCCL ou applicative, validez le lien physique avec perftest. Deux nœuds, le serveur d'abord :
# serveur (nœud A) # client (nœud B)
ib_send_bw -d mlx5_0 -F --report_gbits -D 10
ib_send_bw -d mlx5_0 -F --report_gbits -D 10 10.10.1.1
Valeurs attendues sur un réseau sain :
| Lien |
ib_send_bw (gros messages) |
ib_send_lat (2 octets) |
|---|---|---|
| 100 Gb/s EDR / 100 GbE RoCE | 95–98 Gb/s | 1.0–1.5 µs |
| 200 Gb/s HDR / 200 GbE RoCE | 188–197 Gb/s | 0.9–1.3 µs |
| 400 Gb/s NDR | 370–395 Gb/s | 0.8–1.1 µs |
Si vous êtes 20 % en dessous de ces chiffres, ne vous lancez pas dans l'optimisation de NCCL. C'est le réseau qui ne va pas. Vérifiez dans cet ordre : (1) le MTU, (2) le PFC en RoCE, (3) le couple câble/transceiver, (4) la génération PCIe et le nombre de lignes de la carte réseau, (5) le placement NUMA. Une latence inférieure à la microseconde est atteignable en NDR dans un même rack ; au-delà de 5 µs sur un réseau RoCE à commutateur unique, quelque chose est cassé.
GPUDirect RDMA : activer le chemin DMA
Tout l'intérêt du RDMA dans un cluster IA, c'est que la carte réseau lise et écrive directement dans la mémoire du GPU, en contournant l'hôte. Cela nécessite nvidia-peermem (ou, sur les noyaux récents, DMA-BUF — NVIDIA recommande désormais DMA-BUF lorsque le noyau le prend en charge, mais la plupart des piles de production livrent encore peermem).
sudo modprobe nvidia-peermem
lsmod | grep nvidia_peermem
echo nvidia-peermem | sudo tee /etc/modules-load.d/nvidia-peermem.conf
Si le module refuse de se charger, c'est que le noyau n'a pas été construit avec une API de mémoire pair RDMA compatible OFED. L'ordre d'installation compte : OFED d'abord, puis le pilote NVIDIA, puis nvidia-peermem — peermem se compile contre les en-têtes OFED au moment de l'installation.
Validez le chemin de bout en bout avec une écriture RDMA de GPU à GPU :
# serveur # client
ib_write_bw -d mlx5_0 --use_cuda=0 -F --report_gbits -D 10
ib_write_bw -d mlx5_0 --use_cuda=0 -F --report_gbits -D 10 10.10.1.1
--use_cuda=0 enregistre la mémoire CUDA du GPU 0 comme tampon RDMA. Si le résultat se situe à quelques pour cent du cas mémoire hôte, GPUDirect fonctionne. S'il est 5× plus lent, le trafic transite par la mémoire hôte — généralement un problème de chargement de peermem ou une topologie PCIe où la carte réseau et le GPU se trouvent sur des nœuds NUMA opposés.
MTU et PFC pour RoCE (c'est là que les clusters RoCE se jouent)
RoCEv2 sur un réseau Ethernet sans perte exige trois éléments qui doivent fonctionner ensemble :
- Un grand MTU de bout en bout. Réglez 9000 sur chaque carte réseau, chaque port de commutateur et chaque saut de routeur. RoCE retient le plus grand MTU de type IB qui tient dans le MTU Ethernet : 9000 côté Ethernet donne un MTU RoCE de 4096 octets, ce qui est exactement ce que vous voulez.
- Le PFC sur la priorité RDMA. Une pause au niveau liaison qui empêche les pertes sur une classe de trafic dédiée. Pratique standard : RDMA sur la priorité 3, tout le reste sur la priorité 0.
- Le marquage ECN sur les commutateurs et les cartes réseau. L'ECN est le signal de congestion de long terme ; le PFC est le frein d'urgence immédiat. L'ECN fait le travail la plupart du temps ; le PFC ne se déclenche que lorsque l'ECN ne suit plus.
Côté hôte, avec mlnx_qos :
sudo mlnx_qos -i enp1s0f0 --pfc 0,0,0,1,0,0,0,0 # PFC sur prio 3
sudo mlnx_qos -i enp1s0f0 --trust dscp
echo 106 | sudo tee /sys/class/infiniband/mlx5_0/tc/1/traffic_class
La valeur DSCP (26) et la priorité PFC (3) doivent concorder à chaque saut. Le réseau de commutation doit refléter cette configuration : PFC activé sur la priorité 3, marquage ECN sur ces files, configuration de tampons sans perte et headroom par port dimensionné pour le BDP du lien le plus long.
Acheter un commutateur chez un fournisseur proposant des modèles RoCE documentés (NVIDIA Spectrum, Arista, Cisco Nexus 9000) vous fait gagner une semaine. Configurer le PFC à la main sur un whitebox Broadcom générique est faisable, mais c'est un projet à part entière. Nous l'avons fait. Nous ne le recommandons pas.
DCQCN (Data Center Quantized Congestion Notification) est la boucle de régulation qui relie PFC et ECN : l'ECN marque les paquets lorsqu'une file se remplit, le récepteur renvoie un CNP, l'émetteur ralentit, puis remonte en débit quand la file se vide. Le PFC est le filet de sécurité quand DCQCN ne réagit pas assez vite. Sur les firmwares ConnectX-6/7 récents, il est actif par défaut, et de 4 à 16 nœuds les réglages d'usine conviennent. Le réglage fin (alpha, débit cible, seuils en octets et en temps) est réservé à ceux qui opèrent à des échelles où 0.5 % sur l'allreduce vaut deux semaines de travail.
NCCL : les variables qui comptent
NCCL est la couche qui exploite le RDMA pour PyTorch, JAX, DeepSpeed, vLLM en parallélisme de tenseurs et consorts. Elle détecte la configuration automatiquement, et le plus souvent correctement. Quatre variables d'environnement reviennent dans tous les scripts de lancement en production :
| Variable | Son rôle | Quand la définir |
|---|---|---|
NCCL_IB_DISABLE |
1 force les sockets TCP au lieu d'IB/RoCE |
Débogage uniquement |
NCCL_SOCKET_IFNAME |
Interface utilisée pour l'amorçage NCCL (pas pour le plan de données) | Toujours — pointez vers la carte d'administration pour que l'amorçage n'aille pas s'installer sur le réseau RDMA |
NCCL_IB_HCA |
La ou les HCA que NCCL utilise pour le plan de données | Nœuds multi-cartes — l'explicite vaut mieux que l'automatique |
NCCL_NET_GDR_LEVEL |
Jusqu'où pousser l'usage de GPUDirect RDMA selon la topologie PCIe |
PIX/PHB quand GPU et cartes réseau partagent un commutateur PCIe ou un nœud NUMA |
Un lancement fonctionnel sur un cluster de 4 nœuds à 4 GPU chacun :
export NCCL_SOCKET_IFNAME=eno1 # réseau d'administration 1 GbE
export NCCL_IB_HCA=mlx5_0,mlx5_1 # les deux cartes RDMA
export NCCL_IB_GID_INDEX=3 # GID RoCE v2
export NCCL_NET_GDR_LEVEL=PHB
export NCCL_DEBUG=INFO # une seule fois, puis repasser à WARN
mpirun -np 16 -N 4 --hostfile hosts -x NCCL_SOCKET_IFNAME \
-x NCCL_IB_HCA -x NCCL_IB_GID_INDEX -x NCCL_NET_GDR_LEVEL \
-x NCCL_DEBUG ./build/all_reduce_perf -b 8 -e 8G -f 2 -g 1
La sortie de NCCL_DEBUG=INFO est à lire impérativement lors de la première exécution. Elle indique quel transport NCCL a retenu (Channel ... via NET/IB/0 GDR) pour chaque rang et chaque canal. Si vous voyez via NET/Socket quelque part, le chemin RDMA n'est pas utilisé : vous n'avez pas testé ce que vous croyez avoir testé.
Valider avec nccl-tests
nccl-tests valide toute la pile — pilote, OFED, peermem, NCCL, réseau — de bout en bout. Le chiffre qui compte est la bande passante de bus (busbw), pas la bande passante algorithmique (algbw). La bande passante de bus normalise la taille de l'anneau ou de l'arbre et c'est elle que vous comparez au débit nominal de la carte réseau.
| Taille du cluster | busbw allreduce attendue (gros messages) |
|---|---|
| 1 nœud, 4 GPU via NVLink | 200–400 GB/s |
| 1 nœud, 8 GPU via NVLink | 250–500 GB/s |
| 2 nœuds, 4 GPU chacun, RDMA 200 GbE | 20–24 GB/s |
| 4 nœuds, 4 GPU chacun, RDMA 200 GbE + GDR | 20–22 GB/s |
L'allreduce inter-nœuds plafonne à peu près au débit de la carte divisé par deux (l'allreduce envoie et reçoit chaque octet une fois par rang). 200 Gb/s ≈ 25 GB/s de plafond ; 22 GB/s mesurés, c'est sain. Si le chiffre est divisé par 5 en passant de 1 à 2 nœuds, le RDMA n'est pas utilisé pour le saut inter-nœuds. Relisez la sortie de NCCL_DEBUG=INFO.
Prenons du recul : la conception du lien montant du cluster
Tout ce qui précède concerne le plan de données — le réseau que les GPU utilisent pour dialoguer entre eux. L'autre moitié d'un cluster utile, c'est le lien montant : la façon dont ce réseau se raccorde au monde extérieur. On se trompe de lien montant en permanence.
Un cluster d'entraînement Kentino AI à 4 nœuds entretient trois relations externes :
- Le réseau d'entreprise / le WAN — registres de modèles, stockage de jeux de données (S3, NFS, MinIO), Git, registres de conteneurs, télémétrie.
- Les postes de travail des développeurs — ingénieurs qui se connectent en SSH, lancent des jobs, récupèrent des points de contrôle, exécutent Jupyter.
- Les autres clusters — un deuxième cluster d'entraînement, un cluster d'inférence, un cluster de CI/évaluation.
Le calcul de bande passante
Si 4 nœuds font de l'allreduce à environ 22 GB/s chacun, le réseau interne déplace à peu près 700 Gb/s de trafic est-ouest agrégé. Le lien montant n'a pas besoin d'égaler ce chiffre. Il doit correspondre au débit d'ingestion des données :
- 4 nœuds × 4 GPU × environ 1 GB/s par GPU (modèle image/vidéo) = 16 GB/s ≈ 128 Gb/s de lecture soutenue depuis le stockage objet.
- Le pré-entraînement d'un LLM sur du texte tokenisé est bien plus léger — 1 à 4 Gb/s, car les tokens sont denses et un lot occupe le GPU longtemps.
- Un fine-tuning qui recharge fréquemment des points de contrôle culmine à 40 Gb/s pendant quelques secondes, puis retombe à presque zéro.
| Charge de travail | Ingestion soutenue | Pointe | Lien montant |
|---|---|---|---|
| Pré-entraînement LLM (texte tokenisé) | 1–4 Gb/s | 20 Gb/s | 25 GbE |
| Entraînement de modèles image / vidéo | 50–150 Gb/s | 200 Gb/s | 2× 100 GbE en LAG ou 1× 200 GbE |
| Fine-tuning / RLHF avec rotation de points de contrôle | 5–20 Gb/s | 50 Gb/s | 25–100 GbE |
| Cluster d'inférence derrière un répartiteur de charge | 5–50 Gb/s | dépend du modèle | 25–100 GbE |
Erreur courante : dépenser 40 k€ dans un lien montant à 400 GbE parce que le réseau interne est à 400 GbE. Mauvaise cible. La bonne cible, c'est le débit de lecture du jeu de données. Nous avons construit des clusters de 4 nœuds avec un lien montant de 25 GbE qui ont tourné à plein régime pendant des semaines sur des données de tokens LLM.
Bande passante agrégée contre bande passante allouée
Un nœud équipé de quatre cartes 100 GbE dispose d'une capacité physique agrégée de 400 Gb/s. Cette capacité est allouée par flux, elle n'est pas mutualisée. Une seule connexion TCP entre deux adresses IP emprunte une seule carte — 100 Gb/s au maximum. L'ECMP et le hachage par flux répartissent des flux différents sur les quatre cartes.
-
L'allreduce génère de nombreux flux en parallèle — un par canal et par pair. NCCL répartit nativement la charge sur plusieurs cartes (il suffit de lister les deux dans
NCCL_IB_HCA). Pour NCCL, 4× 100 GbE équivaut en pratique à 400 Gb/s. - Un flux de données unique (une requête HTTP GET tirant un shard de 1 TB) est un seul flux. 4× 100 GbE lui donnent 100 Gb/s, pas 400. Pour exploiter l'agrégat, le chargeur de données doit ouvrir des flux parallèles — ce que DALI, WebDataset et Streaming de MosaicML font tous par conception.
Séparation des réseaux
- RoCE 100/200/400 GbE ou InfiniBand HDR/NDR
- NCCL, lectures de jeux de données, écritures de points de contrôle
- Sans perte, PFC/ECN, commutateurs dédiés
- Stérile — aucun trafic non IA
- 1 GbE ou 10 GbE
- SSH, Prometheus, NTP, syslog, IPMI/BMC
- Commutateurs bon marché, L2/L3 standard, pas de QoS particulière
- Toujours disponible, même si le réseau de données est tombé
Déployez toujours deux réseaux. Le plan d'administration ne doit jamais dépendre du plan de données — il vous faut un accès SSH quand le réseau RDMA est en panne.
Le plan d'administration n'est pas une option. Quand le réseau de données tombe — transceiver défectueux, PFC mal configuré, commutateur planté — il vous faut un chemin SSH indépendant du réseau en panne. Déboguer une tempête RoCE par le lien qui est précisément en train de saturer est le genre d'erreur que l'on ne commet qu'une fois. L'IPMI/BMC et le NTP vivent ici aussi ; une dérive d'horloge reste invisible jusqu'au jour où votre framework distribué se met à produire des gradients faux.
BGP unnumbered pour un Clos routé
Au-delà d'environ 8 nœuds, un leaf-spine en L2 devient pénible : limites du spanning tree, montée en charge des tables MAC, tempêtes de broadcast, pas de multi-chemin natif. La réponse moderne est un Clos routé en L3 : chaque lien leaf-spine est unnumbered, BGP porte les routes et l'ECMP répartit les flux sur les spines.
BGP unnumbered établit ses sessions sur les adresses IPv6 link-local attribuées automatiquement par le noyau, ce qui vous évite entièrement la gestion des /31 par lien. Un leaf exécutant FRRouting sous Linux ressemble à peu près à ceci :
router bgp 65001
neighbor swp1 interface remote-as external
neighbor swp2 interface remote-as external
address-family ipv4 unicast
network 10.1.1.0/24
redistribute connected
Chaque leaf et chaque spine constitue son propre AS, l'eBGP annonce les loopbacks, l'ECMP entre spines est gratuit. La RFC 7938 documente le schéma ; Cumulus/NVIDIA, Arista, Cisco et Juniper prennent tous en charge le BGP unnumbered aujourd'hui. À 4 nœuds, un commutateur unique suffit. À 8–16 nœuds avec deux spines, le Clos routé commence à être rentable. Au-delà de 16 nœuds, c'est la seule réponse sensée.
Se raccorder aux autres clusters
Ne placez pas un cluster d'entraînement et un cluster d'inférence sur le même réseau RDMA. L'entraînement, ce sont d'énormes allreduce en rafales ; l'inférence, un régime stable de petits messages ; les exigences de QoS divergent et un entraînement mal élevé affamera le chemin d'inférence. Deux réseaux distincts se rejoignant sur un routeur L3 en IP classique, voilà la bonne réponse. Le trafic de contrôle entre clusters — files de jobs, acheminement des logs, transfert d'artefacts — emprunte le plan d'administration ou un lien Ethernet inter-clusters dédié. Un fichier de poids 70B pèse environ 140 GB ; un point de contrôle, le double. À 25 GbE, cela prend environ 90 secondes ; à 100 GbE, environ 22 secondes. Prévoyez large.
La suite
Un déroulé raisonnable pour mettre en service le RDMA sur un cluster neuf :
-
Câblez, puis vérifiez d'abord la couche liaison. Lancez
ibstatsur chaque nœud. Même débit, même MTU, même couche liaison. Réglez la couche physique avant de toucher au logiciel. -
Installez MLNX_OFED, pas seulement
rdma-core, si GPUDirect ou NCCL fait partie du périmètre. Alignez la version d'OFED sur le noyau en cours d'exécution. -
Lancez
perftestentre chaque paire de nœuds avant de toucher aux frameworks. Des valeurs à 5 % du théorique = tout va bien. En dessous = arrêtez et corrigez. -
Chargez
nvidia-peermemet prouvez-le avecib_write_bw --use_cuda=0. Le résultat doit égaler le cas mémoire hôte. - Configurez PFC, ECN et DCQCN si vous êtes en RoCE. En IB cette étape n'existe pas ; c'est la moitié de la raison pour laquelle certains choisissent IB.
-
Exécutez l'allreduce de
nccl-testssur 2 nœuds, puis 4, puis 8. Inspectez la sortie deNCCL_DEBUG=INFOà la première exécution de chaque taille de cluster. Confirmez queNET/IB ... GDRapparaît bien. - Séparez les réseaux. L'administration sur du 1/10 GbE bon marché. Les données sur le réseau RDMA. Ne mélangez pas.
- Dimensionnez le lien montant sur votre débit d'ingestion de données, pas sur la vitesse du réseau interne. La plupart des clusters ont besoin de bien moins de bande passante externe qu'ils n'en ont.
- Au-delà de 8 nœuds, prévoyez un Clos routé en BGP unnumbered. En dessous de 8, un commutateur unique fait l'affaire.
Les articles suivants de la série N traitent de la dissection de la latence (N06) et de la complexité du routage (N07) — c'est là que se logent le réglage de DCQCN et les pathologies du hachage ECMP. La série K prend le relais avec l'entraînement distribué (K02), les clusters d'inférence (K03) et le stockage (K04) — tous partent du principe que la pile RDMA décrite ici fonctionne déjà.
Cet article fait partie du Kentino Wiki, une série de référence sur le calcul IA et les systèmes qui le relient. Vos corrections sont les bienvenues à info@kentino.com.