vLLM innenfra: slik gjør paged attention GPU-minnet til blokker på 16 tokens
Bryter ned hvordan vLLM holder GPU-en mettet: KV-cachen deles i blokker på 16 tokens, og nye forespørsler kan skyves inn mellom hvert steg.
Kjører du en lokal modell bak et API, er det sjelden vektene som er flaskehalsen. Det er minnet KV-cachen spiser, og hvor lenge GPU-en står tom mens den venter. Aleksa Gordić publiserte 29. august 2025 en gjennomgang av nettopp den mekanikken i vLLM, forankret i commit 42172ad fra 9. august 2025.
Kjernen er KV-cache-håndtereren. I stedet for å reservere ett sammenhengende minneområde per forespørsel, holder vLLM en pool av ledige blokker i en free_block_queue, ofte hundretusener av dem avhengig av VRAM og blokkstørrelse. Hver blokk rommer som standard 16 tokens, og størrelsen i byte er 2 for nøkkel og verdi, ganget med blokkstørrelsen, antall KV-hoder, hodestørrelsen og antall byte per dtype. Blokkene er indeksstrukturen som binder tokens til de ferdig beregnede KV-verdiene sine.
Hvor mange blokker som får plass, gjettes ikke. Under oppstart kjører hver worker et dummy forward-pass, tar et minnesnapshot av GPU-en og regner ut antallet ut fra gpu_memory_utilization, altså andelen av totalt VRAM du tillater at motoren bruker. Med mindre du gir --enforce-eager, spilles det også inn CUDA-grafer for hver oppvarmingsbatch, slik at hele sekvensen av GPU-arbeid kan spilles av som en ferdig graf senere og kutte overheaden ved å starte hver enkelt kjerne.
Selve kjøringen er en løkke over step(), og hvert steg har tre faser:
- Planlegg hvilke forespørsler som skal kjøre i steget, som dekode eller chunked prefill.
- Kjør forward-passet og sample tokens.
- Etterbehandl: legg de samplede token-IDene på hver Request, detokeniser og sjekk stoppbetingelsene. Er en forespørsel ferdig, leveres blokkene tilbake til free_block_queue.
Forskjellen mellom det offline eksempelet i innlegget og en ekte server ligger i om nye forespørsler kan skyves inn underveis. Den synkrone motoren behandler bare promptene den ble matet ved start, mens den asynkrone vurderer både nye og gamle etter hvert steg. Det er dette som er continuous batching. Prefill er et forward-pass over alle prompt-tokens og som regel compute-bound, dekode er det ikke, og derfor kan planleggeren blande dem i samme steg.
For deg som selvhoster er det to knapper som betyr mer enn de fleste andre: blokkstørrelsen, som avgjør hvor mye cache som går til spille på halvfylte blokker, og gpu_memory_utilization, som avgjør hvor mange samtidige forespørsler du får plass til før planleggeren begynner å køe. Gjennomgangen er pinnet til en commit fra august 2025 og dekker V1-motoren, så flagg og standardverdier kan ha flyttet seg siden. Mekanikken har ikke det.
Bakgrunn
Innlegget er det første i en serie, bygget etter omvendt pyramide: motor og motorkjerne først, deretter avanserte funksjoner som chunked prefill, prefix caching, guided og spekulativ dekoding, så oppskalering til flere GPU-er og noder, serveringslaget og til slutt benchmarking og auto-tuning. Gordić dekker V1-motoren; V0 er avviklet, men han beskriver den som nyttig for å forstå hvordan prosjektet har utviklet seg.
KI-kuratert — innholdet er generert av KI-agenter basert på originalkilden.