Ladenie linuxového jadra pre AI servery: čo naozaj zaberá

Predvolené jadro Ubuntu na stacku HWE 6.8 alebo 6.11 je pre približne 80 % AI workloadov úplne postačujúce. Tento článok je o zvyšných 20 % — viacsoketový EPYC poháňajúci osem GPU, vLLM servovací stroj, ktorý pod záťažou naráža na strop vm.max_map_count, alebo inferenčný server, ktorému koncovú latenciu požierajú prechody C-stavov CPU.

Väčšina verejne dostupných rád na „ladenie Linuxu pre AI“ je recyklovaná databázová príručka z roku 2015 a nezanedbateľná časť z nich latenciu inferencie potichu zhoršuje. Nižšie nájdete menšiu, poctivú množinu zmien, ktoré na stroji triedy Kentino AI (4–8 GPU na Xeone alebo EPYC, Ubuntu 22.04 / 24.04, jadro 6.x) naozaj pomáhajú. Predpokladáme, že ovládač a CUDA stack sú nainštalované podľa L01 a L02.

Poctivá hierarchia prínosov

Skôr než siahnete na akýkoľvek sysctl, ukotvite si veľkosť výhry:

Zmena Typický prínos pri inferencii / tréningu
Umiestňovanie procesov s ohľadom na NUMA 10–30 % na viacsoketových strojoch
Governor CPU performance (namiesto powersave) 5–15 % na servovacích strojoch, nižší TTFT
Vypnutie hlbokých C-stavov Mikrosekundy dole z P99, +30–50 W v pokoji na soket
THP nastavené na madvise 1–5 % na PyTorchi, menej zadrhnutí pri vysokom obrate pamäte
vm.max_map_count, ulimit -n Zabráni pádu servovania pod záťažou
Afinita IRQ na lokálny NUMA uzol 5–15 % na sieťových cestách DataLoader / RDMA
Dimenzovanie TCP bufferov (rmem_max, wmem_max) Má význam len pre úložisko / streaming >25 GbE
Všetko ostatné 0–3 %, často len šum

Ak si z tohto článku máte odniesť jedinú vec: NUMA a governor CPU sú dve veľké výhry. Všetko ostatné je zaokrúhľovacia chyba, pokiaľ ste si to nezmerali.

Governor CPU: najlacnejších 5–15 %

Ubuntu podľa ovládača predvolene používa powersave (intel_pstate) alebo ondemand (acpi_cpufreq). Oba naťahujú takty reaktívne, čo stojí 5–15 % na TTFT inferencie a na prípravných CPU operáciách okolo doprednej passy vLLM — tokenizácia, plánovanie, vzorkovanie logitov.

Na servovacom stroji nastavte performance a zabudnite na to:

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

Na EPYC Genoa / Turin s jadrom 6.5+ nahrádza ovládač amd-pstate ovládač acpi-cpufreq. Princíp je rovnaký, nastavte scaling_governor na performance. Novší režim amd-pstate-epp udrží výkon a zároveň umožní boostovanie jednotlivých jadier; EPP nechajte na performance, nie balance_performance.

Výhrada: na tréningovom stroji pri trvalom ~100 % využití GPU sú procesory aj tak zaťažené a governor zaváži menej. Veľký prínos je na inferenčných serveroch, ktoré medzi požiadavkami zaháľajú a musia sa okamžite rozbehnúť.

NUMA: rozdiel medzi rýchlym a priemerným EPYC strojom

Jednosoketové servery Xeon alebo EPYC nemajú čo riešiť — jeden NUMA uzol, každý prístup do pamäte je lokálny. Tých 10–30 % sa skrýva v dvojsoketových strojoch.

Predvolený plánovač pokojne umiestni vLLM workera na soket 0 a nechá ho stránkovať do pamäte alokovanej na sokete 1 — každé načítanie je skok cez UPI / Infinity Fabric medzi soketmi. Pri modeli so stovkami GB váh, ktoré sa prejdú raz na každý token, je to reálny problém.

Zistenie stavu:

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

Vzor, ktorý chcete — a ktorý NCCL deteguje automaticky — je GPU N pripnuté na NUMA uzol hostiaci jeho PCIe root complex, nie na druhý soket. Pri ručne spustenom inferenčnom procese pripnite na lokálny uzol aj procesor, aj pamäť:

# 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

Dva súvisiace parametre:

  • kernel.numa_balancing migruje stránky smerom k procesoru, ktorý s nimi pracuje. Pri bežných workloadoch pomáha, pri AI je občas kontraproduktívny: prefill prejde váhy raz, jadro stránky presunie a ďalšia požiadavka si ich ťahá späť. Predvolene nechajte zapnuté (1); ak jitter koreluje s numa_pte_updates v /proc/vmstat, skúste sysctl kernel.numa_balancing=0.
  • NCCL na dvojsoketovom stroji — nastavte NCCL_SOCKET_IFNAME na manažmentovú NIC a NCCL_IB_HCA na NIC na rovnakom NUMA uzle ako GPU. Nesprávna NIC potichu zníži medziuzlovú priepustnosť na polovicu.

Jednosoketový server EPYC 9004/9005: celú túto sekciu ignorujte.

Transparent Huge Pages: madvise, nie always

THP príležitostne zlučuje 4 KB stránky do 2 MB stránok a znižuje tlak na TLB. PyTorch aj ovládač CUDA z toho mierne profitujú. Pascou je režim always pri vysokom obrate pamäte — jadro zastaví vlákno na 50–100 ms kvôli kompakcii pamäte a torpéduje akékoľvek SLO na latenciu.

Správne nastavenie je madvise: aplikácie, ktoré vedia, čo robia, zavolajú na dlhožijúce alokácie madvise(MADV_HUGEPAGE) a THP dostanú; nič iné nie. Nastavuje sa cez /etc/default/grub:

GRUB_CMDLINE_LINUX_DEFAULT="... transparent_hugepage=madvise"

update-grub && reboot. Defrag by mal sedieť — defer+madvise umožní, aby kompakcia prebiehala na pozadí, a nie v ceste alokujúceho vlákna:

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

Pre vLLM / SGLang / TensorRT-LLM v produkcii je madvise správna predvoľba. PyTorch s CUDA Unified Memory (výskumný kód) občas ťaží z always — pred prepnutím merajte.

Explicitné 1 GB hugepages: len ak ste merali

Explicitné hugepages rezervované pri štarte cez hugetlbfs prinášajú pri veľmi veľkých LLM váhach malý dodatočný zisk tým, že eliminujú výpadky TLB pri prechode váh. Háčik: pamäť sa rezervuje dopredu a je nedostupná pre zvyšok systému, aplikácia musí byť na jej využitie postavená a zisk je pri väčšine servovacích workloadov 1–3 %.

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

Oplatí sa to pri TensorRT-LLM stroji, kde sa engine zmestí do pinnutej pamäte a vy naháňate posledné percento TTFT. Neoplatí sa to pri bežnom vLLM serveri, ktorý prepína modely, ani pri tréningovom stroji. Pri prvom builde to preskočte; vráťte sa k tomu, len ak perf stat -e dTLB-load-misses ukáže tlak na TLB a máte rezervu v RAM.

vm.max_map_count and nofile: steny, na ktoré vLLM pri škálovaní naráža

Predvolený vm.max_map_count je na Ubuntu 65530 — je to počet samostatných pamäťových mapovaní, ktoré proces môže držať. vLLM servujúci veľký model pri vysokej konkurencii alebo ľubovoľný framework používajúci množstvo mmap-ovaných safetensors shardov tento limit prekročí a spadne s hláškou Cannot allocate memory. Hodnota 262144 prevzatá z Elasticsearchu je holé minimum; pre vLLM servujúci 70B+ so stovkami súbežných sekvencií choďte na 1048576. Nič to nestojí — je to mäkký limit, nie rezervácia.

Predvolený ulimit -n je na Ubuntu 1024. Komicky málo: vLLM, Triton, workeri PyTorch DataLoadera, NCCL aj gRPC vrstva medzi nimi otvárajú stovky deskriptorov každý. Keď na limit narazíte, dostanete EMFILE: too many open files a proces, ktorý sa potichu zasekne.

# /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 a systemctl daemon-reexec. Overte cez ulimit -n a cat /proc/<pid>/limits.

Afinita IRQ: pripnutie prerušení sieťovky na lokálne jadrá

Na 100 GbE NIC ConnectX-6/7, ktorá trvale ťahá 10+ GB/s streamovania datasetu alebo RDMA prevádzky, musia prerušenia pristávať na jadrách (a) na rovnakom NUMA uzle ako NIC a (b) nie na tých istých jadrách, kde bežia workeri DataLoadera. Predvolený irqbalance to zvláda obstojne; pri ťažkej záťaži už nie.

Najčistejším riešením je Mellanox set_irq_affinity.sh z balíka 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

Chybou je nechať bežať irqbalance na stroji, kde ste zároveň pripínali ručne — budú si navzájom prekážať. Vyberte si jedno. Servovací stroj s jednou či dvoma RDMA sieťovkami: pripnite ručne a vypnite irqbalance. Univerzálny stroj s mnohými rozhraniami: nechajte irqbalance zapnutý a kritickú NIC vylúčte cez --banirq.

Sieťové buffery jadra: len pre rýchle úložisko a streamovacie cesty

Pri jednouzlovom inferenčnom serveri, ktorý komunikuje s klientmi cez obyčajné TCP pri miernych rýchlostiach, je predvolených 208 KB pre net.core.rmem_max / wmem_max úplne v poriadku. Pri uzle, ktorý ťahá tréningové dáta zo 100 GbE NFS alebo objektového úložiska, alebo pri vLLM frontende za load balancerom s vysokým RPS sú predvolené hodnoty stropom vášho súčinu šírky pásma a oneskorenia. Východiskové nastavenie pre stroj pripojený cez 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

Maximum 256 MB je pre 100 GbE v rámci racku prehnané (BDP je ~1.25 MB), ale neškodí — automatické ladenie TCP zväčšuje buffery len podľa potreby až po tento strop. Pri RDMA (RoCE / IB) na dátovej ceste tieto parametre nehrajú rolu — RDMA obchádza TCP stack jadra. Stále však platia pre manažmentovú rovinu, NFS a sťahovanie modelov.

C-stavy: latencia verzus pokojová spotreba

Na servovacom stroji citlivom na latenciu je po governore CPU najlacnejším zlepšením koncovej latencie vypnutie hlbokých C-stavov. Vstup do C6 a výstup z neho trvá desiatky mikrosekúnd; pri požiadavke, ktorá má odpovedať do 30 ms, pridáva procesor prebúdzajúci sa z C6 kvôli vzorkovaniu po tokene citeľný jitter.

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

update-grub && reboot. Overte cez cpupower idle-info — dostupné by mali byť len C0 a C1. Cena: 30–50 W pokojovej spotreby na soket, pretože jadrá nikdy neprejdú do hlbokého spánku. Na dvojsoketovom serveri s 8 GPU je to 60–100 W trvalej réžie — zanedbateľné oproti 3.5–4.5 kW trvalého odberu GPU pod záťažou.

Použite na inferenčné servery citlivé na latenciu a na robotické cesty v reálnom čase. Preskočte na tréningových strojoch (latencia tam nehrá rolu a pokojová spotreba sa za mesiace nasčíta) a pri dávkových úlohách.

Ladenie jadra súvisiace so súborovým systémom (ochutnávka L04)

Význam má hŕstka sysctlov fs.*:

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 je ten, ktorý štípe — frameworky robiace asynchrónne I/O proti mnohým shardom (DALI, načítavače váh vLLM) pri veľkých modeloch prejedia predvolených 65536. Samotná voľba súborového systému (XFS vs ZFS vs ext4), možnosti pripojenia a sémantika O_DIRECT sú témou L04.

Vypnutie nepotrebných modulov jadra

Bezhlavý servovací stroj nemá dôvod načítavať moduly pre bluetooth, zvuk, webkameru, bezdrôt, joystick ani tlačový server. Každý z nich je ďalší kód v ploche útoku a malé zdržanie pri štarte. Jednotlivo zanedbateľné; dohromady sa čistejší stroj oveľa lepšie chápe.

# /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

Štart klesne z ~30 s na ~10 s na typickom EPYC builde a lsmod sa stane čitateľným. Nulový dopad na výkon inferencie, mierne zmenšenie plochy útoku.

Dva profily: inferencia verzus tréning

Tvar ladenia je naozaj odlišný. Náčrty na priame použitie:

Inferencia (/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

Navyše na príkazovom riadku jadra: transparent_hugepage=madvise intel_idle.max_cstate=1 processor.max_cstate=1. Governor: performance. IRQ sieťovky pripnuté na NUMA-lokálne jadrá, irqbalance vypnutý.

Tréning (/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

Príkazový riadok jadra: transparent_hugepage=madvise (pripínanie C-stavov preskočte — tréning je limitovaný priepustnosťou a pokojová spotreba sa za týždne nasčíta). Governor: performance, ak máte energetický rozpočet. NCCL pripnuté na lokálny NUMA cez NCCL_IB_HCA.

Pasca „všetko som vyladil a je to pomalšie“

Najčastejším spôsobom zlyhania je kargokultový výsyp: skopírovať sto sysctlov z blogu, rebootovať, pozorovať horší výkon a netušiť, ktorý parameter vrátiť späť. Metóda, ktorá funguje:

  1. Stanovte si základňu — nccl-tests all_reduce_perf, vLLM benchmark_throughput.py, čas tréningového kroku. Čísla si zapíšte.
  2. Zmeňte jednu vec.
  3. Spustite rovnaký benchmark znova, trikrát kvôli šumu.
  4. Ponechajte, ak je medián citeľne lepší. Inak vráťte späť.
  5. Zmenu a jej dôvod zdokumentujte vo verziovacom systéme vedľa súboru so sysctlmi.

Videli sme produkčné stroje so 40 riadkami sysctl ladenia, ktoré boli dohromady o 2 % pomalšie než predvolené Ubuntu — každá jednotlivá zmena neutrálna alebo horšia, no operátor si bol istý, že „ladenie pomáha“.

Red Hat tuned 2.27 prináša explicitné profily ai-inference a ai-training a na RHEL / Rocky je to dôveryhodná skratka. Ubuntu ho nedodáva; apt install tuned funguje, ale je menej dotiahnutý. Na Ubuntu sa ručne písané drop-in súbory v /etc/sysctl.d/ a čistý príkazový riadok GRUB auditujú ľahšie a presne viete, čo je nastavené.

Čo robiť ďalej

Rozumná postupnosť ladenia jadra pre nový build Kentino AI, v poradí podľa priority:

  1. Nastavte governor CPU na performance. Overte cez cpupower frequency-info. Najväčšia výhra zadarmo.
  2. Spustite numactl --hardware a nvidia-smi topo -m. Pred akýmkoľvek pripínaním pochopte topológiu. Na dvojsoketových strojoch naplánujte, ktoré GPU patria ku ktorému NUMA uzlu.
  3. Nastavte limity vm.max_map_count a nofile. Tieto nezabraňujú pomalosti, ale zlyhaniam. Urobte ich pred prvým produkčným behom.
  4. Nastavte transparent_hugepage=madvise na príkazovom riadku jadra.
  5. Pripnite IRQ sieťovky na NUMA uzol lokálny k NIC pomocou set_irq_affinity.sh. Vypnite irqbalance, ak ste tak urobili.
  6. Len pre inferenčné stroje: vypnite hlboké C-stavy cez intel_idle.max_cstate=1 processor.max_cstate=1.
  7. Sieťové buffery ladite len vtedy, ak ste namerali úzke miesto na úložisku alebo streamovacej ceste 25/100 GbE.
  8. Explicitné 1 GB hugepages preskočte, pokiaľ nemáte benchmark preukazujúci tlak na TLB.
  9. Pred každou zmenou aj po nej benchmarkujte. Vždy len jednu vec naraz. Dokumentujte.

Krížové odkazy: L01 pre ovládač a pripnutie jadra, L02 pre CUDA / kontajnerový stack, L04 pre vrstvu súborového systému, L05 pre monitoring.

Ladenie jadra na modernom Linuxe je väčšinou jednorazová konfigurácia, nie nekonečná optimalizačná hra. Nastavte správne governor, umiestnenie NUMA a limity. Zvyšok preskočte, pokiaľ váš benchmark nehovorí inak.


Toto je súčasť Kentino Wiki, referenčnej série o AI compute, robotike a systémoch, ktoré ich spájajú. Komentáre a opravy uvítame na info@kentino.com.