Ladění linuxového jádra pro AI servery: co skutečně zabírá
Výchozí jádro Ubuntu na stacku HWE 6.8 nebo 6.11 je pro zhruba 80 % AI workloadů dostačující. Tento článek je pro zbývajících 20 % — vícesoketový EPYC pohánějící osm GPU, vLLM serving stroj, který pod zátěží narazí na limit vm.max_map_count, nebo inference server, jemuž tail latency ukusují přechody mezi C-stavy CPU.
Většina veřejných rad na „ladění Linuxu pro AI“ je recyklované databázové doporučení z roku 2015 a nemalá část z nich inference latenci tiše zhoršuje. Níže je menší, poctivá množina změn, které na stroji třídy Kentino AI (4–8 GPU na Xeonu nebo EPYC, Ubuntu 22.04 / 24.04, jádro 6.x) skutečně pomáhají. Předpokládáme, že ovladač i CUDA stack jsou nainstalované podle L01 a L02.
Poctivá hierarchie přínosů
Než sáhnete na jakýkoli sysctl, ujasněte si, o jak velkou výhru jde:
| Změna | Typický přínos pro inferenci / trénink |
|---|---|
| NUMA-aware umístění procesů | 10–30 % na vícesoketových strojích |
Governor CPU performance (z powersave) |
5–15 % na serving strojích, nižší TTFT |
| Vypnutí hlubokých C-stavů | Mikrosekundy dolů z P99, +30–50 W v idle / soket |
THP nastavené na madvise
|
1–5 % na PyTorchi, méně stallů při fluktuaci paměti |
vm.max_map_count, ulimit -n
|
Zabrání pádu serving vrstvy pod zátěží |
| IRQ afinita na lokální NUMA uzel | 5–15 % na síťových cestách DataLoaderu / RDMA |
Dimenzování TCP bufferů (rmem_max, wmem_max) |
Důležité jen pro >25GbE úložiště / streaming |
| Vše ostatní | 0–3 %, často jen šum |
Pokud si z článku odnesete jedinou věc: Dva velké přínosy jsou NUMA a governor CPU. Všechno ostatní je zaokrouhlovací chyba, dokud si to nezměříte.
Governor CPU: nejlevnějších 5–15 %
Ubuntu má ve výchozím stavu powersave (intel_pstate) nebo ondemand (acpi_cpufreq) podle ovladače. Oba zvyšují takty reaktivně, což stojí 5–15 % na TTFT inference a na přípravě na straně CPU kolem forward passu vLLM — tokenizace, plánování, sampling logitů.
Na serving stroji nastavte performance a zapomeňte 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 jádrem 6.5+ nahrazuje ovladač amd-pstate ovladač acpi-cpufreq. Stejný princip, nastavte scaling_governor na performance. Novější režim amd-pstate-epp udržuje výkon a zároveň umožňuje boostování jednotlivých jader; EPP nechte na performance, ne na balance_performance.
Výhrada: na trénovacím stroji s trvalým ~100% vytížením GPU jsou procesory zatížené tak jako tak a na governoru tolik nezáleží. Velká výhra je u inference serverů, které mezi požadavky zahálejí a musí naskočit okamžitě.
NUMA: rozdíl mezi rychlým a průměrným EPYC strojem
U jednosoketových serverů s Xeonem nebo EPYC není co řešit — jeden NUMA uzel, každý přístup do paměti je lokální. Těch 10–30 % se skrývá u dvousoketových strojů.
Výchozí plánovač klidně umístí vLLM worker na soket 0 a nechá ho page-faultovat do paměti alokované na soketu 1 — každé čtení je skok přes UPI / Infinity Fabric mezi sokety. U modelu se stovkami GB vah, které se procházejí jednou na token, je to znát.
Zkontrolujte:
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
Vzorec, který chcete — a který NCCL detekuje automaticky — je GPU N připnuté k NUMA uzlu hostujícímu jeho PCIe root complex, ne k druhému soketu. U ručně spuštěného inference procesu připněte k lokálnímu uzlu CPU i 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 související parametry:
-
kernel.numa_balancingmigruje stránky blíž k CPU, které s nimi pracuje. U běžných workloadů pomáhá, u AI je občas kontraproduktivní: prefill projde váhy jednou, jádro stránky přemigruje a další požadavek si je stáhne zpět. Ve výchozím stavu nechte zapnuté (1); pokud jitter koreluje snuma_pte_updatesv/proc/vmstat, zkustesysctl kernel.numa_balancing=0. -
NCCL na dvousoketu — nastavte
NCCL_SOCKET_IFNAMEna management NIC aNCCL_IB_HCAna NIC na stejném NUMA uzlu jako GPU. Špatná NIC tiše sníží propustnost mezi uzly na polovinu.
Jednosoketový server EPYC 9004/9005: celou tuto sekci ignorujte.
Transparent Huge Pages: madvise, ne always
THP oportunisticky slučuje 4KB stránky do 2MB stránek a snižuje tak tlak na TLB. PyTorch i ovladač CUDA z toho mírně profitují. Past je režim always při vysoké fluktuaci paměti — jádro zastaví vlákno na 50–100 ms kvůli kompakci paměti a torpéduje tím jakékoli latenční SLO.
Správné nastavení je madvise: aplikace, které vědí, co dělají, zavolají nad dlouhožijícími alokacemi madvise(MADV_HUGEPAGE) a THP dostanou; nic jiného ne. Nastavíte přes /etc/default/grub:
GRUB_CMDLINE_LINUX_DEFAULT="... transparent_hugepage=madvise"
update-grub && reboot. Defrag by měl odpovídat — defer+madvise nechá kompakci běžet na pozadí, nikoli v cestě alokujícího vlákna:
echo defer+madvise | sudo tee /sys/kernel/mm/transparent_hugepage/defrag
Pro vLLM / SGLang / TensorRT-LLM v produkci je madvise správná výchozí volba. PyTorch s CUDA Unified Memory (výzkumný kód) občas těží z always — před přepnutím si to změřte.
Explicitní 1GB hugepages: jen pokud jste měřili
Explicitní hugepages rezervované při bootu přes hugetlbfs přinesou u velmi velkých vah LLM malý přínos navíc tím, že odstraní výpadky TLB při procházení vah. Háček: paměť se rezervuje předem a je pro zbytek systému nedostupná, aplikace s nimi musí umět pracovat a u většiny serving workloadů je zisk 1–3 %.
GRUB_CMDLINE_LINUX_DEFAULT="... default_hugepagesz=1G hugepagesz=1G hugepages=64"
Vyplatí se u TensorRT-LLM stroje, kde se engine vejde do pinned paměti a Vy honíte poslední procento TTFT. Nevyplatí se u běžného vLLM serveru, který přepíná modely, ani u trénovacího stroje. Při prvním buildu je přeskočte; vraťte se k nim, jen když perf stat -e dTLB-load-misses ukáže tlak na TLB a máte rezervu RAM.
vm.max_map_count a nofile: zdi, do kterých vLLM při škálování naráží
Výchozí vm.max_map_count je na Ubuntu 65530 — počet různých mapování paměti, která proces může držet. vLLM obsluhující velký model s vysokou souběžností, nebo jakýkoli framework s množstvím safetensors shardů načítaných přes mmap, tuto hranici překročí a spadne s Cannot allocate memory. Hodnota 262144 převzatá z Elasticsearche je naprosté minimum; pro vLLM obsluhující 70B+ se stovkami souběžných sekvencí jděte na 1048576. Nic to nestojí — jde o soft limit, ne o rezervaci.
Výchozí ulimit -n je na Ubuntu 1024. Komicky málo: vLLM, Triton, workeři PyTorch DataLoaderu, NCCL a gRPC vrstva mezi nimi otevírají každý stovky FD. Jakmile narazíte na limit, dostanete EMFILE: too many open files a proces, který se tiše 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. Ověřte přes ulimit -n a cat /proc/<pid>/limits.
IRQ afinita: připnutí přerušení NIC na lokální CPU
Na 100GbE NIC ConnectX-6/7, která drží 10+ GB/s streamování datasetu nebo RDMA provozu, musí přerušení dopadat na CPU (a) na stejném NUMA uzlu jako NIC a (b) ne na stejná jádra, kde běží workeři DataLoaderu. Výchozí irqbalance si vede obstojně; pod velkou zátěží už ne.
Nejčistší řešení je skript set_irq_affinity.sh od Mellanoxu z balíčku 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
Chyba je nechat běžet irqbalance na stroji, kde jste zároveň pinovali ručně — perou se spolu. Vyberte si jedno. Serving stroj s jednou nebo dvěma RDMA NIC: pinujte ručně a vypněte irqbalance. Univerzální stroj s mnoha rozhraními: nechte irqbalance zapnutý s --banirq pro vyloučení kritické NIC.
Síťové buffery jádra: jen pro rychlé úložiště / streaming
U jednouzlového inference serveru, který komunikuje s klienty přes běžné TCP při mírných rychlostech, výchozí net.core.rmem_max / wmem_max o velikosti 208 KB stačí. U uzlu, který tahá trénovací data ze 100GbE NFS nebo object storu, nebo u vLLM frontendu za load balancerem s vysokým RPS jsou výchozí hodnoty stropem Vašeho bandwidth-delay produktu. Výchozí sada pro stroj připojený na 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 pro 100 GbE v rámci racku přehnané (BDP je ~1.25 MB), ale neškodí — automatické ladění TCP zvětšuje buffery jen podle potřeby až do stropu. Na RDMA (RoCE / IB) na datové cestě tyto parametry nehrají roli — RDMA obchází TCP stack jádra. Pro management rovinu, NFS a stahování modelů ale platí dál.
C-stavy: latence versus klidová spotřeba
U latenčně citlivého serving stroje je po governoru CPU nejlevnějším zlepšením tail latency vypnutí hlubokých C-stavů. Vstup do C6 a výstup z něj trvá desítky mikrosekund; u požadavku, který má odpovědět za 30 ms, přidá CPU probouzející se z C6 kvůli samplingu po tokenu znatelný jitter.
GRUB_CMDLINE_LINUX_DEFAULT="... intel_idle.max_cstate=1 processor.max_cstate=1"
update-grub && reboot. Ověřte přes cpupower idle-info — k dispozici by měly být jen C0 a C1. Cena: 30–50 W klidové spotřeby na soket, protože jádra nikdy nepřejdou do hlubokého spánku. Na dvousoketovém serveru s osmi GPU jde o 60–100 W trvalé režie — zanedbatelné proti 3.5–4.5 kW trvalého odběru GPU pod zátěží.
Používejte u latenčně citlivých inference serverů a real-time robotických cest. Přeskočte u trénovacích strojů (latence nehraje roli, klidová spotřeba se za měsíce nasčítá) a u dávkových úloh.
Ladění jádra kolem souborového systému (náhled na L04)
Důležitých je několik sysctlů 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, který opravdu zabolí — frameworky provádějící asynchronní I/O proti mnoha shardům (DALI, načítání vah vLLM) u velkých modelů výchozích 65536 vyčerpají. Samotná volba souborového systému (XFS vs ZFS vs ext4), parametry připojení a sémantika O_DIRECT jsou tématem L04.
Vypnutí nepotřebných modulů jádra
Bezhlavý serving stroj nemá důvod načítat moduly pro bluetooth, zvuk, webkameru, wi-fi, joystick nebo tiskový server. Každý z nich je další kód v útočné ploše a malé zdržení při bootu. Jednotlivě triviální; dohromady se o čistším stroji lépe přemýšlí.
# /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
Boot se u typického EPYC buildu zkrátí z ~30 s na ~10 s a výstup lsmod se dá číst. Nulový dopad na výkon inference, mírné zmenšení útočné plochy.
Dva profily: inference versus trénink
Tvar ladění se skutečně liší. Náčrty k přímému použití:
Inference (/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
Navíc na příkazový řádek jádra: transparent_hugepage=madvise intel_idle.max_cstate=1 processor.max_cstate=1. Governor: performance. IRQ NIC připnutá na NUMA-lokální jádra, irqbalance vypnutý.
Trénink (/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
Příkazový řádek jádra: transparent_hugepage=madvise (vynechte pinování C-stavů — trénink je omezený propustností a klidová spotřeba se za týdny nasčítá). Governor: performance, pokud na to máte energetický rozpočet. NCCL připnuté na lokální NUMA přes NCCL_IB_HCA.
Past „všechno jsem vyladil a je to pomalejší“
Nejčastější způsob selhání je cargo-cult výsyp: zkopírovat sto sysctlů z blogu, restartovat, naměřit horší výkon a netušit, který parametr vrátit zpět. Postup, který funguje:
- Změřte výchozí stav —
nccl-tests all_reduce_perf, vLLMbenchmark_throughput.py, čas trénovacího kroku. Čísla si zapište. - Změňte jednu věc.
- Spusťte stejný benchmark znovu, třikrát kvůli šumu.
- Nechte, pokud je medián znatelně lepší. Jinak vraťte zpět.
- Změnu i její důvod zdokumentujte ve verzovacím systému vedle souboru sysctl.
Viděli jsme produkční stroje se 40 řádky sysctl ladění, které byly dohromady o 2 % pomalejší než výchozí Ubuntu — každá jednotlivá změna neutrální nebo horší, ale operátor byl přesvědčený, že „to ladění pomáhá“.
Nástroj tuned 2.27 od Red Hatu přináší hotové profily ai-inference a ai-training a na RHEL / Rocky je to seriózní zkratka. Ubuntu jej nedodává; apt install tuned funguje, ale je méně dotažený. Na Ubuntu se ručně psané drop-in soubory v /etc/sysctl.d/ a čistý příkazový řádek GRUBu snáze auditují a přesně víte, co je nastavené.
Co dělat dál
Rozumná posloupnost ladění jádra pro čerstvý build Kentino AI, podle priority:
-
Nastavte governor CPU na
performance. Ověřte přescpupower frequency-info. Největší výhra zdarma. -
Spusťte
numactl --hardwareanvidia-smi topo -m. Než cokoli připnete, pochopte topologii. U dvousoketových strojů naplánujte, které GPU patří ke kterému NUMA uzlu. -
Nastavte
vm.max_map_countanofilelimity. Ty zabraňují selháním, ne pomalosti. Udělejte je před prvním produkčním během. -
Nastavte
transparent_hugepage=madvisena příkazovém řádku jádra. -
Připněte IRQ NIC k lokálnímu NUMA uzlu dané NIC pomocí
set_irq_affinity.sh. Vypněteirqbalance, pokud jste tak učinili. -
Pouze pro inference stroje: vypněte hluboké C-stavy přes
intel_idle.max_cstate=1 processor.max_cstate=1. - Vyladěte síťové buffery jen pokud jste naměřili úzké hrdlo na 25/100GbE cestě k úložišti nebo streamingu.
- Přeskočte explicitní 1GB hugepages, pokud nemáte benchmark ukazující tlak na TLB.
- Před každou změnou i po ní měřte. Vždy jednu věc. Dokumentujte.
Křížové odkazy: L01 k ovladači a pinování jádra, L02 ke stacku CUDA / kontejnerů, L04 k vrstvě souborového systému, L05 k monitoringu.
Ladění jádra na moderním Linuxu je většinou jednorázová konfigurace, ne nekonečná hra na optimalizaci. Nastavte správně governor, umístění NUMA a limity. Zbytek přeskočte, pokud Váš benchmark neříká jinak.
Toto je součást Kentino Wiki, referenční série o AI výpočtech, robotice a systémech, které je propojují. Komentáře a opravy uvítáme na info@kentino.com.