Hoe cAdvisor mijn lokale LLM vier keer zo traag maakte
Mijn lokale codeermodel draait op een RTX 3060 met MoE expert-offload: de vaak gebruikte experts staan in VRAM, de rest wordt via de page cache uit het modelbestand gelezen. Op een dag zakte de decode van 27,5 naar 6,6 tokens per seconde. De prefill bleef gelijk.
De vingerafdruk
Die asymmetrie is het belangrijkste aanknopingspunt. Prefill is GPU-werk en merkt niets van geheugendruk. Decode leest bij elke token experts uit de page cache. Zakt alleen de decode in, dan zit het probleem in het geheugen, niet in de GPU of het model.
Oorzaak 1: --disable_metrics overschrijft
cAdvisor draaide met --disable_metrics=tcp,udp,percpu,process. Wat ik niet wist: die vlag vervangt de standaardlijst in plaats van iets toe te voegen. De standaard schakelt onder meer referenced_memory uit. Met mijn korte lijst stond die dus weer aan.
referenced_memory wist elke tien seconden via /proc/<pid>/clear_refs de referentiebits van alle pagina's van elk proces, en leest daarna smaps. Voor een proces met 25 GiB gemapt modelbestand betekent dat een page-table-walk over miljoenen pagina's. Erger nog: door het wissen van die bits ziet de kernel de modelpagina's als koud en gooit hij ze uit de page cache. Precies de pagina's die de decode nodig heeft.
| Voor | Na | |
|---|---|---|
| cAdvisor CPU | 50 % | 0 % |
| swap-in | ~4.500 pagina's/s | 0 |
| iowait | 22–55 % | 0 % |
De fix: de volledige standaardlijst plus percpu, en --docker_only, --housekeeping_interval=30s en een geheugenlimiet.
Oorzaak 2: net te weinig RAM
Na de fix haalde de decode 13 tokens per seconde, nog steeds de helft. Ik had de VM die week verkleind van 48 naar 40 GiB. Met de volledige werklast erbij (mediastack, webinterface, containers) bleef er te weinig over voor de page cache, en experts kwamen van schijf.
Terug naar 48 GiB, en de ondergrens vastgelegd: onder ~36 GiB gaat de kernel het modelbestand evicten en stort de snelheid in. Dat is de echte grens, niet het moment van een OOM.
Meetlessen
- Vergelijk alleen bij gelijke contextlengte. Decode zakt met een langere KV-cache. Mijn eerste "voor"-meting stond op 14.600 tokens, de baseline op 4.400.
- Pas op voor cache-hits. De server hergebruikt de prompt-cache; een herhaalde prompt meet niets. Zet een unieke nonce in elke testprompt.
- Meet nooit op een korte prompt. Op 40 tokens meet je vaste overhead, niet de doorvoer.
docker statslaat gelockt geheugen niet zien. Kijk naarMlockedin/proc/meminfo.