Optimisation du noyau Linux pour les serveurs d'IA : ce qui fait vraiment la différence

Le noyau Ubuntu par défaut sur une pile HWE 6.8 ou 6.11 convient à environ 80 % des charges de travail d'IA. Cet article s'adresse aux 20 % restants : des configurations EPYC multi-sockets gérant huit GPU, un serveur vLLM atteignant une charge élevée… vm.max_map_count un mur sous charge, ou un serveur d'inférence dont la latence de queue est absorbée par les transitions d'état C du processeur.

La plupart des conseils publics d'« optimisation Linux pour l'IA » sont des recommandations recyclées datant de 2015, et une part non négligeable d'entre elles aggrave discrètement la latence d'inférence. Voici un ensemble de modifications plus restreint et honnête qui améliorent réellement les performances sur un système K-AI de type Kentino (4 à 8 GPU sur Xeon ou EPYC, Ubuntu 22.04/24.04, noyau 6.x). Les pilotes et la pile CUDA sont supposés installés. L01 et L02.

La hiérarchie honnête des victoires

Avant toute modification de sysctl, basez-vous sur la taille du prix :

Changer Victoire typique en inférence/entraînement
Placement de processus prenant en compte NUMA 10 à 30 % sur les boîtiers multiprises
Gouverneur du processeur performance (Partir powersave) 5 à 15 % sur les boîtes de service, TTFT plus faible
Désactivation des états C profonds Microsecondes après l'arrêt du P99, +30–50 W en veille / prise
THP réglé à madvise 1 à 5 % sur PyTorch, moins de blocages en cas de forte activité
vm.max_map_count, ulimit -n Empêche le service de basculer sous la charge
Affinité IRQ avec le nœud NUMA local 5 à 15 % sur les chemins DataLoader/RDMA en réseau
Dimensionnement du tampon TCP (rmem_max, wmem_max) Concerne uniquement le stockage/streaming > 25 GbE
Tout le reste 0 à 3 %, souvent du bruit

Si vous ne retenez rien d'autre de cet article : La prise en charge de l'interface NUMA et le régulateur de processeur sont les deux principaux atouts. Tout le reste n'est que pure spéculation, à moins d'avoir effectué des mesures précises.

Régulateur de processeur : les 5 à 15 % les moins chers du marché

Ubuntu par défaut powersave (intel_pstate) ou ondemand (acpi_cpufreq) dépend du pilote. Les deux fréquences d'horloge augmentent de manière réactive, ce qui coûte 5 à 15 % sur le TTFT d'inférence et sur la préparation côté CPU autour d'une passe avant vLLM — tokenisation, planification, échantillonnage des logits.

Pour une boîte de service, définissez performance et oublier :

sudo apt install -y cpufrequtils
echo 'GOVERNOR="performance"' | sudo tee /etc/default/cpufrequtils
sudo systemctl restart cpufrequtils

# Verify
for c in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do cat $c; done | sort -u

Sur EPYC Genoa / Turin avec noyau 6.5+, le amd-pstate le conducteur remplace acpi-cpufreqMême idée, ensemble scaling_governor à performance. Le plus récent amd-pstate-epp Ce mode préserve les performances tout en autorisant l'optimisation par cœur ; laissez l'EPP à sa valeur par défaut. performance, Pas balance_performance.

Avertissement : sur un serveur d’entraînement avec une utilisation GPU soutenue d’environ 100 %, les CPU sont de toute façon sollicités et le régulateur de fréquence a moins d’importance. Le gain le plus significatif se situe au niveau des serveurs d’inférence qui restent inactifs entre les requêtes et doivent monter en charge instantanément.

NUMA : la différence entre un boîtier EPYC rapide et un boîtier EPYC médiocre

Les serveurs mono-processeur Xeon ou EPYC n'ont aucun souci à se faire : un seul nœud NUMA et tous les accès mémoire sont locaux. C'est sur les serveurs bi-processeurs que se concentrent 10 à 30 % des cas.

Le planificateur par défaut placera sans problème un processus vLLM sur le socket 0 et le fera basculer vers la mémoire allouée sur le socket 1 en cas de défaut de page ; chaque chargement correspond à un saut UPI/Infinity Fabric inter-sockets. Pour un modèle avec des centaines de Go de poids traités une fois par jeton, c'est tout à fait réaliste.

Inspecter:

numactl --hardware                            # node count, sizes, distance matrix
nvidia-smi topo -m                            # GPU↔CPU NUMA affinity
cat /sys/class/net/<iface>/device/numa_node   # NIC NUMA affinity

Le schéma recherché (et détecté automatiquement par NCCL) consiste à associer le GPU N au nœud NUMA hébergeant son interface PCIe racine, et non à l'autre socket. Pour un processus d'inférence lancé manuellement, associez le CPU et la mémoire au nœud local.

# vLLM on a 2-socket EPYC, GPUs 0–3 on NUMA node 0
numactl --cpunodebind=0 --membind=0 \
  python -m vllm.entrypoints.openai.api_server \
  --model meta-llama/Llama-3.3-70B-Instruct --tensor-parallel-size 4

Deux boutons liés :

  • kernel.numa_balancing déplace les pages vers le processeur qui les touche. Utile pour les charges de travail générales, parfois contre-productif pour l'IA : un préremplissage parcourt les poids une seule fois, le noyau déplace les pages, la requête suivante les récupère. Laisser activé par défaut (1); si la gigue est corrélée à numa_pte_updates in /proc/vmstat, Essayez de sysctl kernel.numa_balancing=0.
  • NCCL sur prise double - ensemble NCCL_SOCKET_IFNAME à la direction NIC et NCCL_IB_HCA à la carte réseau située sur le même nœud NUMA que les GPU. Une carte réseau incorrecte divise par deux, sans avertissement, le débit entre les nœuds.

Serveur EPYC 9004/9005 à socket unique : ignorez toute cette section.

Pages transparentes géantes : madvise, Pas always

THP replie les pages de 4 Ko en pages de 2 Mo de manière opportuniste, réduisant ainsi la pression sur le TLB. PyTorch et le pilote CUDA en bénéficient légèrement. Le piège est le suivant : always En mode de manipulation de la mémoire, le noyau bloque un thread pendant 50 à 100 ms pour compacter la mémoire, ce qui compromet tout SLO de latence.

Le bon réglage est madvise: les applications qui savent ce qu'elles font appellent madvise(MADV_HUGEPAGE) sur les allocations de longue durée et obtenez du THP ; rien d'autre. Défini via /etc/default/grub:

GRUB_CMDLINE_LINUX_DEFAULT="... transparent_hugepage=madvise"

update-grub && rebootLa défragmentation devrait correspondre — defer+madvise permet à la compaction de s'effectuer en arrière-plan plutôt que dans le chemin du thread d'allocation :

echo defer+madvise | sudo tee /sys/kernel/mm/transparent_hugepage/defrag

Pour vLLM / SGLang / TensorRT-LLM en production, madvise est la valeur par défaut appropriée. PyTorch avec CUDA Unified Memory (code de recherche) bénéficie parfois de always — mesurer avant de retourner.

Pages énormes explicites de 1 Go : uniquement si vous avez mesuré

pages énormes explicites réservées au démarrage via hugetlbfs Cette méthode offre un léger gain supplémentaire pour les poids LLM très élevés en éliminant les échecs de TLB lors de l'analyse des poids. Cependant, elle présente un inconvénient : la mémoire est réservée en amont et indisponible pour le reste du système, l'application doit être conçue pour l'utiliser, et le gain est de 1 à 3 % pour la plupart des charges de travail de serveur.

GRUB_CMDLINE_LINUX_DEFAULT="... default_hugepagesz=1G hugepagesz=1G hugepages=64"

Intéressant pour une machine TensorRT-LLM où le moteur tient dans la mémoire allouée et où l'on cherche à optimiser le TTFT au maximum. Inutile pour un serveur vLLM général qui change de modèle ou pour toute machine d'entraînement. À éviter lors d'une première installation ; à réexaminer seulement si perf stat -e dTLB-load-misses indique la pression TLB et vous disposez de marge de manœuvre RAM.

vm.max_map_count et nofile: les murs que vLLM frappe à l'échelle

Réglage par défaut vm.max_map_count Sous Ubuntu, cette valeur est de 65 530, soit le nombre de mappages mémoire distincts qu'un processus peut gérer. vLLM gère un modèle complexe avec une forte concurrence, ou tout framework utilisant de nombreux mappages mémoire. mmap'd safetensors éclats, souffle au-delà de ceci et meurt avec Cannot allocate memoryLa valeur de 262 144, calculée par Elasticsearch, est le strict minimum ; pour un vLLM gérant plus de 70 milliards de requêtes avec des centaines de séquences simultanées, il est recommandé de la porter à 1 048 576. Cela ne coûte rien : il s’agit d’une limite souple, et non d’une réservation.

Réglage par défaut ulimit -n Sous Ubuntu, cette limite est de 1024. Un chiffre ridiculement bas : vLLM, Triton, les workers PyTorch DataLoader, NCCL et la couche gRPC qui les relie ouvrent chacun des centaines de descripteurs de fichiers. Une fois la limite atteinte, vous obtenez… EMFILE: too many open files et un processus qui s'installe silencieusement.

# /etc/sysctl.d/99-ai-server.conf
vm.max_map_count = 1048576

# /etc/security/limits.d/99-ai-server.conf
*    soft    nofile  1048576
*    hard    nofile  1048576

# /etc/systemd/system.conf.d/99-limits.conf
[Manager]
DefaultLimitNOFILE=1048576

sudo sysctl --system et systemctl daemon-reexecVérifiez avec ulimit -n et cat /proc/<pid>/limits.

Affinité IRQ : attribution des interruptions NIC aux processeurs locaux

Sur une carte réseau ConnectX-6/7 100 GbE supportant un flux de données ou un trafic RDMA supérieur à 10 Go/s, les interruptions doivent être appliquées aux processeurs (a) du même nœud NUMA que la carte réseau, et (b) pas aux mêmes cœurs exécutant les processus DataLoader. irqbalance Il fait un travail acceptable ; sous forte charge, ce n'est pas le cas.

La solution la plus propre est celle de Mellanox. set_irq_affinity.sh à partir de mlnx-tools:

sudo systemctl disable --now irqbalance
sudo /usr/sbin/set_irq_affinity.sh enp1s0f0                  # all NIC-local cores
sudo /usr/sbin/set_irq_affinity_cpulist.sh 4-11 enp1s0f0     # pin to specific cores

Le mauvais choix est de partir irqbalance Sur un serveur où vous avez également effectué des épingles manuelles, les deux options entrent en conflit. Choisissez-en une. Serveur avec une ou deux cartes réseau RDMA : épinglez manuellement, désactivez irqbalanceBoîtier polyvalent avec de nombreuses interfaces : laissez irqbalance avec --banirq exclure le NIC critique.

Tampons du noyau réseau : uniquement pour les chemins de stockage/flux rapides

Pour un serveur d'inférence à nœud unique communiquant avec des clients via TCP standard à des débits modérés, la valeur par défaut net.core.rmem_max / wmem_max Une taille de 208 Ko est acceptable. Pour un nœud récupérant des données d'entraînement depuis un système de fichiers NFS 100 GbE ou un stockage d'objets, ou pour un frontal vLLM derrière un équilibreur de charge à haut débit (RPS), les valeurs par défaut constituent la limite de votre produit bande passante-délai. Voici une configuration initiale pour un serveur connecté en 100 GbE :

# /etc/sysctl.d/99-ai-network.conf
net.core.rmem_max = 268435456
net.core.wmem_max = 268435456
net.ipv4.tcp_rmem = 4096 87380 268435456
net.ipv4.tcp_wmem = 4096 65536 268435456
net.core.netdev_max_backlog = 250000
net.core.somaxconn = 65535

Une capacité maximale de 256 Mo est surdimensionnée pour une infrastructure 100 GbE en rack (BDP d'environ 1.25 Mo), mais sans conséquence : l'ajustement automatique TCP n'augmente la taille des tampons qu'en fonction des besoins, jusqu'à la limite. Sur le chemin de données RDMA (RoCE/IB), ces paramètres sont inopérants, car RDMA contourne la pile TCP du noyau. Ils restent cependant importants pour le plan de gestion, NFS et le téléchargement de modèles.

États C : latence vs consommation en veille

Pour un serveur sensible à la latence, la solution la plus économique pour réduire la latence de queue après la limitation de la fréquence du processeur consiste à désactiver les états C profonds. L'entrée/sortie de l'état C6 se situe dans la dizaine de microsecondes ; pour une requête qui devrait répondre en 30 ms, le fait que le processeur sorte de l'état C6 pour gérer l'échantillonnage post-jeton introduit une gigue perceptible.

GRUB_CMDLINE_LINUX_DEFAULT="... intel_idle.max_cstate=1 processor.max_cstate=1"

update-grub && rebootVérifiez avec cpupower idle-info — Seuls C0 et C1 devraient être disponibles. Coût : 30 à 50 W de puissance en veille par prise En effet, les cœurs ne passent jamais en mode veille profonde. Sur un serveur biprocesseur à 8 GPU, la consommation permanente de 60 à 100 W est négligeable comparée à la consommation soutenue du GPU en charge, qui s'élève à 3.5 à 4.5 kW.

À utiliser sur les serveurs d'inférence sensibles à la latence et les trajectoires robotiques en temps réel. À éviter sur les boîtiers d'entraînement (la latence est négligeable, la consommation en veille s'accumule sur plusieurs mois) et les tâches par lots.

Optimisation du noyau liée au système de fichiers (aperçu de L04)

Une poignée de fs.* Les paramètres système sont importants :

fs.aio-max-nr = 1048576              # default 65536 too low for vLLM weight loaders
fs.inotify.max_user_watches = 524288 # tooling that watches checkpoint dirs

fs.aio-max-nr C'est là que le bât blesse : les frameworks effectuant des E/S asynchrones sur de nombreux fragments (DALI, chargeurs de poids vLLM) épuisent la limite par défaut de 65 536 octets sur les modèles volumineux. Le choix du système de fichiers (XFS, ZFS ou ext4), les options de montage, et O_DIRECT la sémantique vit dans L04.

Désactivation des modules du noyau inutiles

Un serveur sans interface graphique n'a aucune raison d'intégrer des modules pour le Bluetooth, le son, la webcam, le Wi-Fi, le joystick ou le serveur d'impression. Chacun de ces modules augmente la surface d'attaque et induit un léger délai au démarrage. Pris individuellement, ces modules sont insignifiants ; collectivement, un serveur plus épuré est plus facile à appréhender.

# /etc/modprobe.d/blacklist-ai-server.conf
blacklist bluetooth
blacklist btusb
blacklist snd_hda_intel
blacklist uvcvideo
blacklist joydev

# Plus
sudo systemctl disable --now bluetooth.service cups.service avahi-daemon.service \
                              ModemManager.service whoopsie.service apport.service

Le temps de démarrage passe d'environ 30 secondes à environ 10 secondes sur une configuration EPYC typique et lsmod devient lisible. Aucun impact sur les performances d'inférence, réduction modeste de la surface d'attaque.

Deux profils : inférence vs entraînement

La courbe d'accordage est véritablement différente. Croquis d'insertion :

Inférence (/etc/sysctl.d/99-ai-inference.conf)

vm.max_map_count = 1048576
vm.swappiness = 1
vm.overcommit_memory = 1
kernel.numa_balancing = 1
fs.aio-max-nr = 1048576
net.core.rmem_max = 268435456
net.core.wmem_max = 268435456
net.ipv4.tcp_rmem = 4096 87380 268435456
net.ipv4.tcp_wmem = 4096 65536 268435456
net.core.netdev_max_backlog = 250000
net.core.somaxconn = 65535

De plus, sur la ligne de commande du noyau : transparent_hugepage=madvise intel_idle.max_cstate=1 processor.max_cstate=1Gouverneur : performance. Les interruptions NIC sont associées aux cœurs NUMA locaux, irqbalance de.

Entraînement (/etc/sysctl.d/99-ai-training.conf)

vm.max_map_count = 1048576
vm.swappiness = 1
vm.overcommit_memory = 1
kernel.numa_balancing = 0       # page migration mid-epoch is just churn
fs.aio-max-nr = 1048576
fs.inotify.max_user_watches = 524288
net.core.rmem_max = 536870912
net.core.wmem_max = 536870912
net.ipv4.tcp_rmem = 4096 131072 536870912
net.ipv4.tcp_wmem = 4096 131072 536870912

Ligne de commande du noyau : transparent_hugepage=madvise (Ignorer l'épinglage de l'état C — la formation est limitée par le débit, la consommation d'énergie en veille s'accumule sur plusieurs semaines). Gouverneur : performance si vous avez le budget énergétique nécessaire. NCCL connecté au réseau NUMA local via NCCL_IB_HCA.

Le piège du « J'ai tout réglé et ça a ralenti »

Le problème le plus fréquent est la copie aveugle de centaines de sysctls trouvés sur un blog : on redémarre, on constate une baisse des performances, sans savoir quel paramètre modifier. Méthode qui fonctionne :

  1. Établir une base de référence — nccl-tests all_reduce_perf, vLLM benchmark_throughput.py, durée de l'étape d'entraînement. Notez les chiffres.
  2. Changez une chose.
  3. Relancez le même test de performance, trois fois, pour détecter le bruit.
  4. Conserver si la médiane est sensiblement meilleure. Rétablir la version précédente dans le cas contraire.
  5. Documentez la modification et sa raison dans le système de contrôle de version, à côté du fichier sysctl.

Nous avons vu des machines de production avec 40 lignes de réglage sysctl qui étaient collectivement 2 % plus lentes que les paramètres par défaut d'Ubuntu — chaque modification individuelle étant neutre ou pire, mais l'opérateur était certain que « le réglage est utile ».

Chapeau rouge tuned 2.27 navires explicites ai-inference et ai-training Il s'agit d'un raccourci fiable pour les profils sur RHEL / Rocky. Ubuntu ne le fournit pas ; apt install tuned Fonctionne, mais est moins abouti. Sur Ubuntu, des modules complémentaires faits à la main dans /etc/sysctl.d/ et une ligne de commande GRUB propre est plus facile à auditer et vous savez exactement ce qui est configuré.

Que faire ensuite

Séquence raisonnable d'optimisation du noyau pour une nouvelle installation de Kentino K-AI, par ordre de priorité :

  1. Réglez le gouverneur du processeur sur performance. Vérifier avec cpupower frequency-info. Le plus gros gain sans frais.
  2. Courir numactl --hardware et nvidia-smi topo -m. Avant toute configuration, il est essentiel de bien comprendre la topologie. Sur les serveurs à deux sockets, il convient de planifier l'affectation des GPU à chaque nœud NUMA.
  3. complet » vm.max_map_count et nofile limites. Ces mesures préviennent les pannes, pas les ralentissements. Mettez-les en œuvre avant le premier cycle de production.
  4. complet » transparent_hugepage=madvise sur la ligne de commande du noyau.
  5. IRQ de la carte réseau (brocher) au nœud NUMA local de la carte réseau avec set_irq_affinity.sh. Désactiver irqbalance si vous l'avez fait.
  6. Pour les boîtes d'inférence uniquement : désactiver les états C profonds via intel_idle.max_cstate=1 processor.max_cstate=1.
  7. Réglage des tampons réseau uniquement si vous avez mesuré un goulot d'étranglement sur un chemin de stockage ou de diffusion en continu 25/100 GbE.
  8. Passer à la section « 1 Go de contenu explicite » (heavypages) à moins que vous ne disposiez d'un point de référence indiquant la pression TLB.
  9. Établir des points de référence avant et après chaque changement. Une chose à la fois. Document.

Références croisées : L01 pour l'épinglage des pilotes et du noyau, L02 pour la pile CUDA / conteneur, L04 pour la couche système de fichiers, L05 pour surveillance.

Le réglage du noyau sur les systèmes Linux modernes se fait principalement une fois pour toutes, et non par une optimisation continue. Il suffit de bien configurer le gouverneur de fréquence, l'emplacement des unités NUMA et les limites. Le reste n'a pas d'importance, sauf si vos tests de performance indiquent le contraire.


Ceci fait partie du Kentino Wiki, une série de référence sur l'intelligence artificielle, la robotique et les systèmes qui les connectent. Commentaires et corrections bienvenus. info@kentino.com.