Inferensoptimalisering er en kritisk del av generative AI-applikasjoner som brukes i produksjon. Det er en utfordring å bruke LLM-er effektivt i stor skala, og i løpet av de siste årene er det utviklet mange teknikker for å gjøre inferens raskere og billigere. La oss gå gjennom disse teknikkene i denne artikkelen.
Store språkmodeller (LLM-er) er alle basert på transformatorarkitekturen, som ble oppfunnet i 2017 av Vaswani et al. Transformatorarkitekturen oppnår overlegen nøyaktighet, læring med få innlæringsmomenter og nesten menneskelige evner på tvers av ulike språkoppgaver. Disse grunnmodellene, som ofte består av titalls til hundrevis av milliarder parametere, er imidlertid kostbare å trene opp og ressurskrevende under inferens. Inferansekostnadene eskalerer med lange inputkontekster, som krever betydelig prosessorkraft på grunn av store inputdata. Dette gjør effektiv inferens til en kritisk utfordring, særlig når det gjelder å håndtere minne- og databehandlingsressurser.

Mer spesifikt er de mest kjente LLM-ene rene dekoder-LLM-er, som GPT-3, GPT-4, LLaMA, Mistral, DeepSeek osv. Disse modellene er forhåndstrenet på en kausal modelleringsoppgave, og fungerer som prediktorer for neste ord. De behandler en sekvens av tokens som input og produserer følgende tokens autoregressivt til en stoppbetingelse er nådd.
LLM-inferens i rene dekodermodeller involverer to hovedfaser: prefill-fasen og dekoderfasen. I prefill-fasen behandler modellen input-tokens for å beregne mellomliggende tilstander (nøkler og verdier) for å generere det første nye tokenet. Denne fasen, som ligner en matrise-matrise-operasjon, er svært parallellisert og utnytter GPU-kapasiteten på en effektiv måte. I avkodingsfasen genereres derimot tokens, én om gangen, basert på tidligere tokens' tilstander. Denne matrise-vektor-operasjonen er minnebundet, ettersom det først og fremst er dataoverføringen til GPU-en, og ikke beregningshastigheten, som bestemmer ventetiden, noe som fører til underutnyttelse av GPU-ens regnekraft.
Optimalisering av avkodingsfasen er et sentralt punkt når det gjelder å løse inferensutfordringer. Løsningene inkluderer utvikling av effektive oppmerksomhetsmekanismer og bedre håndtering av nøkler og verdier for å redusere flaskehalser i minnet. Innlegget tar for seg praktiske metoder for å forbedre inferensytelsen, forutsatt at leserne har en grunnleggende forståelse av transformatorarkitekturen og oppmerksomhetsmekanismene. Disse optimaliseringene er avgjørende for å forbedre gjennomstrømningen og redusere ventetiden i virkelige LLM-distribusjoner.
En annen komplikasjon er bruken av ulike tokenizer på tvers av LLM-er, noe som påvirker sammenlignbarheten av tokener. Tokens, som tilsvarer omtrent fire engelske tegn, varierer i representasjon avhengig av tokenizer, noe som gjør direkte sammenligninger av inferensgjennomstrømning (f.eks. tokens per sekund) misvisende. Denne variasjonen understreker behovet for standardiserte evalueringsmålinger for å kunne vurdere og sammenligne LLM-ytelsen under inferens på en nøyaktig måte.
Batching er en viktig strategi for å forbedre GPU-utnyttelsen og gjennomstrømningen i store språkmodeller (LLM-er). Ved å behandle flere forespørsler samtidig ved hjelp av samme modell, fordeler batching minnekostnaden for modellvekter på tvers av forespørsler, slik at større batcher kan utnytte mer GPU-beregningskraft. Det er imidlertid en grense for batchstørrelsen, ettersom for store batcher kan føre til minneoverløp på grunn av minnekravene til LLM-er, spesielt i forbindelse med key-value (KV)-bufring (mer om dette senere).

Tradisjonell eller statisk batching har begrensninger fordi forespørsler i en batch ofte genererer ulikt antall fullføringstokener, noe som fører til varierende kjøretider. Dette fører til at alle forespørsler må vente på at den tregeste skal fullføres, noe som kan være problematisk når genereringslengden varierer betydelig. For å løse dette problemet er det utviklet avanserte teknikker som "in-flight batching" for å optimalisere ytelsen.
In-flight batching, også kjent som kontinuerlig batching, løser utfordringene knyttet til den dynamiske naturen til LLM-arbeidsbelastninger, som kan variere fra enkle chatbot-svar til komplekse dokumentsammendrag eller kodegenerering. Disse oppgavene produserer data av svært ulik størrelse, noe som gjør det vanskelig å batche og utføre forespørsler effektivt i parallell. I motsetning til statisk batching kan serveren ved hjelp av "in-flight"-batching kaste ut fullførte sekvenser fra batchen umiddelbart og begynne å behandle nye forespørsler mens andre fortsatt pågår. Denne tilnærmingen øker GPU-utnyttelsen betydelig ved å tilpasse seg de varierende kjøretidene for forespørsler i virkelige scenarier.
Modellparallellisering er en viktig strategi for å håndtere minne- og beregningskravene til store maskinlæringsmodeller ved å distribuere dem over flere GPU-er. Denne tilnærmingen gjør det mulig å håndtere større modeller eller inndatapakker som overskrider minnekapasiteten til en enkelt enhet, noe som gjør den viktig for både opplæring og inferens når minnebegrensningene er stramme. Det finnes ulike teknikker for å dele opp modellvekter, blant annet rørledningsparallellisme, tensorparallellisme og sekvensparallellisme, som alle tar for seg ulike aspekter ved modelldistribusjon. I motsetning til dataparallellisme, som fokuserer på å replikere modellvekter på tvers av enheter for å behandle større inndataposter under trening, er disse metodene mer relevante for å redusere minneavtrykket under både trening og inferens.

Rørledningsparallellisme deler modellen vertikalt inn i sekvensielle biter, der hver bit inneholder et undersett av lag som er tilordnet en separat enhet. I et fireveis pipeline-oppsett håndterer for eksempel hver enhet en fjerdedel av modellens lag, og sender utdataene videre til neste enhet i rekkefølge. Selv om dette reduserer minnekravene per enhet betydelig, fører det til ineffektivitet, kjent som "pipelinebobler", der enheter kan gå på tomgang mens de venter på utdata fra tidligere lag. Microbatching, som deler inndatapakker i mindre delpakker for sekvensiell prosessering, kan redusere disse boblene, men ikke eliminere dem helt, ettersom inaktiviteten vedvarer under forover- og bakoverpasseringer.
Tensorparallellisme, derimot, deler opp individuelle lag horisontalt i mindre beregningsblokker som kan kjøres uavhengig av hverandre på tvers av enheter. Dette er spesielt effektivt for transformatorkomponenter som oppmerksomhetsblokker og flerlagsperceptroner (MLP), der for eksempel ulike oppmerksomhetshoder kan tilordnes separate enheter for parallellberegning. Tensorparallellisme er imidlertid mindre effektivt for operasjoner som LayerNorm og Dropout, som ikke enkelt kan deles og må replikeres på tvers av enheter, noe som fører til overflødig minnebruk for lagring av aktiveringer. Denne begrensningen understreker behovet for komplementære tilnærminger for å optimalisere minneeffektiviteten.
Sekvensparallellisme løser problemet med ineffektivitet i minnet for operasjoner som LayerNorm og Dropout ved å dele dem opp langs inndatasekvensdimensjonen og utnytte deres uavhengighet på tvers av sekvenselementer. Denne metoden reduserer minneavtrykket fra overflødige aktiveringer, noe som gjør den til et verdifullt supplement til tensorparallellisme. Disse parallelliseringsteknikkene utelukker ikke hverandre, og de kan kombineres for å optimalisere store språkmodeller (LLM) ytterligere. I tillegg kan spesifikke optimaliseringsstrategier for oppmerksomhetsmodulen forbedre skalerbarheten og redusere minnekravene per GPU, noe som muliggjør mer effektiv trening og inferens for store modeller.
I artikkelen *Attention Is All You Need* av Vaswani et al. fra 2017 introduserte de Transformer-modellen, med selvoppmerksomhet som hjørnestein. Selvoppmerksomhet gjør det mulig for modellen å vurdere relevansen av ulike ord i en setning i forhold til hverandre, noe som forbedrer den kontekstuelle forståelsen for oppgaver som naturlig språkbehandling. Artikkelen formaliserte selvoppmerksomhet, særlig gjennom SDPA-mekanismen (scaled dot-product attention), som tilordner spørring og nøkkelverdipar til en utdata, noe som gjør den til en sentral komponent i moderne nevrale nettverk. Her er noen av de viktigste teknikkene for å optimalisere oppmerksomhetsberegninger:

Multi-head attention (MHA) bygger på SDPA ved å kjøre flere oppmerksomhetsoperasjoner parallelt, hver med forskjellige projeksjoner av spørrings-, nøkkel- og verdimatriser. Disse parallelle operasjonene, eller "hodene", fokuserer på ulike representasjonsområder, noe som beriker modellens forståelse av inndataene. Utdataene fra disse hodene kobles sammen og projiseres lineært, slik at beregningseffektiviteten opprettholdes på samme nivå som ved oppmerksomhet med ett hode, ved at dimensjonaliteten til hvert hode reduseres (f.eks. ved å dele modelldimensjonen på antall hoder, f.eks. 8).
Multi-query attention (MQA) optimaliserer MHA for inferens ved å dele nøkkel- og verdiprojeksjoner på tvers av flere oppmerksomhetshoder, samtidig som flere spørringsprojeksjoner beholdes. Dette reduserer kravene til minnebåndbredde og størrelsen på nøkkelverdibufferen (KV), noe som muliggjør større batchstørrelser og bedre utnyttelse av databehandlingen. MQA kan imidlertid redusere nøyaktigheten noe, og modeller som utnytter det, krever trening eller finjustering med MQA aktivert for å opprettholde ytelsen.
Gruppert spørringsoppmerksomhet (GQA) balanserer MHA og MQA ved å gruppere spørringshoder og dele nøkkelverdiprojeksjoner i hver gruppe, noe som gir tilnærmet MHA-kvalitet med en beregningseffektivitet som ligger nærmere MQA. Modeller som Llama 2 70B bruker GQA, og de som er opplært med MHA, kan tilpasses til GQA med minimal ekstra opplæring. Både MQA og GQA reduserer behovet for KV-cacheminne, men det er fortsatt behov for ytterligere optimalisering av cache-styringen.
FlashAttention forbedrer oppmerksomhetsmekanismene ved å omorganisere beregningene slik at GPU-minnehierarkiene utnyttes mer effektivt. I motsetning til tradisjonell lag-for-lag-prosessering, slår FlashAttention sammen operasjoner og bruker "tiling" for å beregne små deler av utmatrisen samtidig, noe som minimerer lese-/skriveoperasjoner i minnet. Denne I/O-bevisste, nøyaktige oppmerksomhetsalgoritmen integreres sømløst i eksisterende modeller uten endringer, og gir betydelige hastighetsøkninger ved å optimalisere databevegelser.
KV-caching er en viktig optimaliseringsteknikk som brukes i avkodingsfasen i store språkmodeller (LLM-er) for å effektivisere beregningene av selvoppmerksomhet. I denne fasen er hvert genererte token avhengig av nøkkel- (K) og verdi- (V) tensorer for alle tidligere tokens, inkludert de som er beregnet under prefill-fasen og påfølgende avkodingstrinn. I stedet for å beregne disse tensorene på nytt for hvert token ved hvert tidstrinn, lagrer KV-bufferen dem i GPU-minnet og legger til nye tensorer i hurtigbufferen etter hvert som de beregnes. Vanligvis opprettholdes en separat KV-cache for hvert lag i modellen, noe som reduserer overflødige beregninger betydelig og gjør dekodingsprosessen raskere.

Minnekravene for LLM-er på GPU-er er hovedsakelig drevet av to komponenter: modellvekter og KV-cachen. Modellvektene, som består av modellens parametere, opptar mye minne; for eksempel krever en modell med 7 milliarder parametere som Llama 2 7B med 16-bits presisjon omtrent 14 GB. KV-cachen lagrer derimot selvoppmerksomhetstensorer for å unngå nye beregninger, og størrelsen bestemmes av faktorer som antall lag, oppmerksomhetshoder, hodedimensjoner og presisjon. For hvert token beregnes størrelsen på hurtigbufferen som 2 * num_layers * (num_heads * dim_head) * precision_in_bytes, der faktoren 2 tar hensyn til både K- og V-matriser. For en batch med inndata skalerer den totale KV-cachestørrelsen med batchstørrelse og sekvenslengde, og kan potensielt nå betydelige størrelser, for eksempel ~2 GB for en Llama 2 7B-modell med en sekvenslengde på 4096 og batchstørrelse på 1.
Det er utfordrende å administrere KV-cachen effektivt fordi den vokser lineært med batchstørrelsen og sekvenslengden, noe som kan begrense gjennomstrømningen og gjøre det vanskelig å håndtere inndata med lange kontekster. En vanlig ineffektivitet skyldes statisk overreservering, der minne reserveres for den maksimale sekvenslengden som støttes (f.eks. 2048 tokens), uavhengig av den faktiske inndatastørrelsen. Dette fører til betydelig sløsing med minne eller fragmentering, ettersom mye av den reserverte plassen ofte forblir ubrukt gjennom hele forespørselens levetid, noe som binder opp verdifulle GPU-minneressurser.
For å løse disse problemene introduserer PagedAttention-algoritmen en ny tilnærming som er inspirert av operativsystemets personsøking. Den deler KV-hurtigbufferen inn i blokker av fast størrelse, som hver representerer et bestemt antall tokens, og som kan lagres usammenhengende i minnet. En blokktabell sporer disse blokkene, og henter dem etter behov under oppmerksomhetsberegninger. Etter hvert som nye tokens genereres, allokeres flere blokker dynamisk. Denne metoden minimerer sløsing med minne ved å eliminere behovet for sammenhengende allokering og overtildeling, noe som muliggjør større batchstørrelser og bedre gjennomstrømning, og er dermed et betydelig fremskritt i håndteringen av KV-cacheminnet for LLM-er.
I denne delen diskuterer vi ulike teknikker for optimalisering av store språkmodeller (LLM-er) for å redusere minneforbruket og forbedre ytelsen på GPU-er. De viktigste metodene er kvantisering, sparsomhet og destillasjon, som hver for seg retter seg mot ulike aspekter av modelleffektivitet. Disse teknikkene endrer modellvekter, utnytter GPU-maskinvareakselerasjon og overfører kunnskap til mindre modeller, slik at større modeller kan kjøres på begrenset maskinvare samtidig som ytelsen opprettholdes. Disse metodene kan forringe modellens nøyaktighet, så de bør brukes med forsiktighet.
Kvantisering reduserer presisjonen til en modells vekter og aktiveringer, vanligvis fra 32 eller 16 bits til 8 eller færre bits, slik at modellene kan bruke mindre minne og overføre data mer effektivt. Mens kvantisering av vekter er grei på grunn av deres faste natur etter trening, er kvantisering av aktiveringer mer kompleks på grunn av ekstremverdier som utvider deres dynamiske område. Teknikker som LLM.int8 () løser dette ved selektivt å bruke høyere presisjon på visse aktiveringer, eller ved å gjenbruke det dynamiske området for kvantiserte vekter for aktiveringer, selv om GPU-er kan kreve å konvertere vekter tilbake til høyere presisjon for operasjoner.
Sparsomhet innebærer at modellverdier nær null fjernes, noe som skaper tynne matriser som krever mindre minne. GPU-er støtter strukturert sparsomhet, for eksempel ved å representere to av fire verdier som nuller, noe som gjør beregningene raskere. Ved å kombinere sparsomhet med kvantisering kan man øke kjøringshastigheten ytterligere. Forskningen fortsetter å utforske optimale, tynne representasjoner for LLM-er, noe som indikerer en lovende mulighet til å forbedre inferenshastigheten.
Destillasjon overfører kunnskap fra en større "lærer"-modell til en mindre "elev"-modell, noe som komprimerer størrelsen samtidig som ytelsen bevares. DistilBERT oppnår for eksempel en størrelsesreduksjon på 40 % og en hastighetsøkning på 60 % sammenlignet med BERT, samtidig som 97 % av kapasiteten beholdes. Destillasjon kan innebære å etterligne lærerens resultater eller bruke lærergenererte data til opplæring, med metoder som "Distilling Step by Step!", som inneholder begrunnelser for effektiv læring. Restriktive lisenser på mange avanserte LLM-er begrenser imidlertid tilgjengeligheten av egnede lærermodeller for destillasjon.
Spekulativ inferens, også kjent som spekulativ sampling eller assistert generering, er en metode for å parallellisere kjøringen av autoregressive store språkmodeller (LLM-er) som GPT-modeller, som vanligvis genererer tekst token for token. Ved standard kjøring er hvert token avhengig av alle tidligere tokens for kontekst, noe som gjør parallell generering umulig ettersom det n-te tokenet må genereres før det (n+1)-te. Spekulativ inferens løser dette ved å bruke en "billigere" utkastmodell til å forutsi flere fremtidige tokener samtidig, som deretter verifiseres eller avvises parallelt av hovedmodellen, noe som gjør det mulig å generere tekst raskere.
Prosessen innebærer at det genereres et utkast til en fortsettelse av flere tokens ved hjelp av en mindre ressurskrevende metode, etterfulgt av parallell verifisering av hovedmodellen ved hjelp av utkastet som spekulativ kontekst. Hvis verifiseringsmodellen samsvarer med utkastet til tokens, blir de akseptert. I motsatt fall forkastes tokens som ikke samsvarer, og påfølgende tokens, og prosessen gjentas med et nytt utkast. Utkast til tokens kan genereres ved hjelp av ulike tilnærminger, for eksempel ved å trene opp flere modeller, finjustere flere hoder på en forhåndstrenet modell for å forutsi fremtidige tokens, eller bruke en mindre utkastmodell sammen med en større, mer kapabel verifiseringsmodell, hver med sine egne avveininger.
Disaggregert inferens er en teknikk der beregningsoppgavene deles opp på ulike maskinvarer for å optimalisere ytelse, kostnader og ressursbruk. Nærmere bestemt skilles fasene prefilling og dekoding fra hverandre. Ved å dele opp disse fasene kan hver av dem tilordnes den maskinvaren som er best egnet for beregningsoppgavene, noe som forbedrer effektiviteten og skalerbarheten.

Prefilling er beregningskrevende, og det kreves betydelige matrisemultiplikasjoner for å behandle hele inndataprompten og produsere KV-cacher. Denne fasen drar nytte av maskinvare med høy ytelse, som GPU-er eller TPU-er, som utmerker seg med parallelle beregninger. Siden prefilling er en engangsoppgave per inferensforespørsel, kan den overføres til en sentralisert, kraftig beregningsnode som er optimalisert for slike arbeidsbelastninger. Dette oppsettet muliggjør raskere behandling av store forespørsler og reduserer belastningen på mindre kapable enheter, noe som gjør det ideelt for skybaserte miljøer eller datasentermiljøer der maskinvare med høy gjennomstrømning er tilgjengelig.
Dekoding, derimot, er minnebundet og innebærer iterativ generering av tokener, noe som i stor grad baserer seg på tilgang til KV-cachene. Den krever mindre regnekraft, men trenger rask minnetilgang, noe som gjør den egnet for mindre kraftig, minneoptimalisert maskinvare som CPU-er eller edge-enheter. Ved å flytte avkodingen til separat maskinvare - potensielt nærmere sluttbrukeren, for eksempel lokale servere eller edge-enheter - reduserer disaggregert inferens ventetid og krav til nettverksbåndbredde. Denne separasjonen muliggjør fleksibel distribusjon, der prefilling kjører på avanserte skyservere og dekoding skjer på lokale enheter eller edge-enheter, noe som optimaliserer ressursallokeringen og muliggjør effektiv skalering for applikasjoner som sanntids chatbots eller interaktive AI-systemer.
Mange teknikker for inferensoptimalisering har nylig blitt utviklet for å forbedre ytelsen til LLM-er.
Implementering av disse teknikkene krever en dyp forståelse av LLM-arkitekturen og maskinvaren du bruker, så det er generelt enklere å bruke en eksisterende inferensmotor som allerede har implementert disse teknikkene, for eksempel vLLM, TensorRT-LLM, LMDeploy osv. Vi har faktisk implementert disse teknikkene i vår egen inferensmotor hos NLP Cloud, og vi har skrevet et blogginnlegg om inferensmotorer hvis du ønsker å distribuere dine egne modeller: du kan lese den her.
Hvis du ikke kan eller vil distribuere dine egne LLM-er selv, kan du bruke NLP Cloud og utnytte raske generative AI-modeller i stor skala i produksjonen. Prøv rask inferens på NLP Cloud nå!
Hvis du har spørsmål om inferensmotorer generelt, ikke nøl med å spørre oss, det er alltid en glede å gi deg råd!
Julien
Teknisk direktør i NLP Cloud