Optymalizacja wnioskowania jest krytyczną częścią generatywnych aplikacji sztucznej inteligencji wdrażanych w produkcji. Efektywne wykorzystanie LLM na dużą skalę jest wyzwaniem, a w ostatnich latach opracowano wiele technik, dzięki którym wnioskowanie jest szybsze i tańsze. Przyjrzyjmy się tym technikom w tym artykule.
Duże modele językowe (LLM) są oparte na architekturze transformatorowej wynalezionej w 2017 roku przez Vaswani et al. Architektura transformatorowa osiąga doskonałą dokładność, uczenie się w kilku ujęciach i zdolności zbliżone do ludzkich w różnych zadaniach językowych. Jednak te podstawowe modele, często składające się z dziesiątek do setek miliardów parametrów, są kosztowne do wyszkolenia i wymagają dużych zasobów podczas wnioskowania. Koszty wnioskowania rosną wraz z długimi kontekstami wejściowymi, które wymagają znacznej mocy obliczeniowej ze względu na duże dane wejściowe. Sprawia to, że wydajne wnioskowanie jest krytycznym wyzwaniem, szczególnie w zakresie zarządzania pamięcią i zasobami obliczeniowymi.

Mówiąc dokładniej, najbardziej znanymi modelami LLM są modele LLM oparte wyłącznie na dekoderze, takie jak GPT-3, GPT-4, LLaMA, Mistral, DeepSeek itp. Modele te są wstępnie trenowane w zadaniu modelowania przyczynowego, działając jako predyktory następnego słowa. Przetwarzają one sekwencję tokenów jako dane wejściowe i wytwarzają kolejne tokeny autoregresyjnie, aż do osiągnięcia warunku zatrzymania.
Wnioskowanie LLM w modelach opartych wyłącznie na dekoderach obejmuje dwie kluczowe fazy: fazę wstępnego wypełniania i fazę dekodowania. W fazie wstępnego wypełniania model przetwarza tokeny wejściowe w celu obliczenia stanów pośrednich (kluczy i wartości) do wygenerowania pierwszego nowego tokena. Faza ta, przypominająca operację macierz-macierz, jest wysoce zrównoleglona i efektywnie wykorzystuje możliwości GPU. I odwrotnie, faza dekodowania generuje tokeny, jeden po drugim, opierając się na stanach poprzednich tokenów. Ta operacja macierz-wektor jest związana z pamięcią, ponieważ transfer danych do GPU, a nie szybkość obliczeń, dyktuje przede wszystkim opóźnienia, co prowadzi do niewykorzystania mocy obliczeniowej GPU.
Optymalizacja fazy dekodowania jest centralnym punktem rozwiązywania wyzwań związanych z wnioskowaniem. Rozwiązania obejmują opracowanie wydajnych mechanizmów uwagi oraz lepsze zarządzanie kluczami i wartościami w celu zmniejszenia wąskich gardeł pamięci. W artykule przedstawiono praktyczne podejścia do poprawy wydajności wnioskowania, przy założeniu, że czytelnicy posiadają podstawową wiedzę na temat architektury transformatora i mechanizmów uwagi. Optymalizacje te mają kluczowe znaczenie dla poprawy przepustowości i zmniejszenia opóźnień w rzeczywistych wdrożeniach LLM.
Kolejna komplikacja wynika z użycia różnych tokenizerów w LLM, co wpływa na porównywalność tokenów. Tokeny, w przybliżeniu odpowiadające czterem angielskim znakom, różnią się reprezentacją w zależności od tokenizera, co sprawia, że bezpośrednie porównania przepustowości wnioskowania (np. tokenów na sekundę) są mylące. Ta zmienność podkreśla potrzebę ustandaryzowanych wskaźników oceny w celu dokładnej oceny i porównania wydajności LLM podczas wnioskowania.
Batching jest kluczową strategią zwiększania wykorzystania GPU i przepustowości w dużych modelach językowych (LLM). Przetwarzanie wielu żądań jednocześnie przy użyciu tego samego modelu pozwala rozłożyć koszt pamięci związany z wagami modelu na poszczególne żądania, dzięki czemu większe partie mogą wykorzystać większą moc obliczeniową GPU. Istnieje jednak granica wielkości partii, ponieważ zbyt duże partie mogą powodować przepełnienie pamięci ze względu na wymagania pamięciowe LLM, szczególnie związane z buforowaniem klucz-wartość (KV) (więcej na ten temat w dalszej części).

Tradycyjne lub statyczne wsadowanie ma ograniczenia, ponieważ żądania w ramach wsadu często generują różne liczby tokenów ukończenia, co prowadzi do różnych czasów wykonania. Powoduje to, że wszystkie żądania czekają na ukończenie najwolniejszego z nich, co może być problematyczne, gdy długości generacji znacznie się różnią. Aby temu zaradzić, opracowano zaawansowane techniki, takie jak batching w locie, w celu optymalizacji wydajności.
Batching w locie, znany również jako batching ciągły, stawia czoła wyzwaniom związanym z dynamicznym charakterem obciążeń LLM, które mogą obejmować zarówno proste odpowiedzi chatbota, jak i złożone podsumowania dokumentów lub generowanie kodu. Zadania te generują dane wyjściowe o bardzo różnych rozmiarach, co utrudnia ich równoległe i wydajne wykonywanie. W przeciwieństwie do batchingu statycznego, batching w locie pozwala serwerowi na natychmiastowe usunięcie ukończonych sekwencji z partii i rozpoczęcie przetwarzania nowych żądań, podczas gdy inne są nadal w toku. Podejście to znacząco zwiększa wykorzystanie GPU, dostosowując się do zmiennych czasów wykonywania żądań w rzeczywistych scenariuszach.
Zrównoleglanie modeli jest kluczową strategią zarządzania pamięcią i wymaganiami obliczeniowymi dużych modeli uczenia maszynowego poprzez dystrybucję ich na wiele procesorów graficznych. Podejście to pozwala na obsługę większych modeli lub partii danych wejściowych, które przekraczają pojemność pamięci pojedynczego urządzenia, co czyni je niezbędnym zarówno do uczenia, jak i wnioskowania, gdy ograniczenia pamięci są ograniczone. Istnieją różne techniki dzielenia wag modeli, w tym równoległość potoków, równoległość tensorów i równoległość sekwencji, z których każda dotyczy różnych aspektów dystrybucji modeli. W przeciwieństwie do równoległości danych, która koncentruje się na replikowaniu wag modelu na różnych urządzeniach w celu przetwarzania większych partii danych wejściowych podczas uczenia, metody te są bardziej istotne dla zmniejszenia śladów pamięciowych zarówno podczas uczenia, jak i wnioskowania.

Równoległość potokowa dzieli model pionowo na sekwencyjne fragmenty, z których każdy zawiera podzbiór warstw przypisanych do oddzielnego urządzenia. Przykładowo, w czterokierunkowej konfiguracji potokowej, każde urządzenie obsługuje jedną czwartą warstw modelu, przekazując dane wyjściowe do następnego urządzenia w kolejności. Chociaż znacznie zmniejsza to wymagania dotyczące pamięci na urządzenie, wprowadza nieefektywności znane jako "bąbelki potoku", w których urządzenia mogą być bezczynne, czekając na wyjścia z poprzednich warstw. Microbatching, który dzieli partie wejściowe na mniejsze partie do sekwencyjnego przetwarzania, może zmniejszyć te bąbelki, ale nie wyeliminować ich całkowicie, ponieważ czasy bezczynności utrzymują się podczas przejść do przodu i do tyłu.
Z kolei równoległość tensorowa dzieli poszczególne warstwy poziomo na mniejsze bloki obliczeniowe, które mogą być wykonywane niezależnie na różnych urządzeniach. Jest to szczególnie skuteczne w przypadku komponentów transformatora, takich jak bloki uwagi i perceptrony wielowarstwowe (MLP), gdzie na przykład różne głowice uwagi można przypisać do oddzielnych urządzeń w celu równoległego wykonywania obliczeń. Równoległość tensorowa jest jednak mniej skuteczna w przypadku operacji takich jak LayerNorm i Dropout, których nie można łatwo podzielić i które muszą być replikowane na różnych urządzeniach, co prowadzi do nadmiarowego wykorzystania pamięci do przechowywania aktywacji. Ograniczenie to podkreśla potrzebę stosowania komplementarnych podejść do optymalizacji wydajności pamięci.
Równoległość sekwencji rozwiązuje problem nieefektywności pamięci operacji takich jak LayerNorm i Dropout, dzieląc je wzdłuż wymiaru sekwencji wejściowej, wykorzystując ich niezależność między elementami sekwencji. Metoda ta zmniejsza ślad pamięciowy nadmiarowych aktywacji, co czyni ją cennym uzupełnieniem równoległości tensorowej. Te techniki równoległości nie wykluczają się wzajemnie i mogą być łączone w celu dalszej optymalizacji dużych modeli językowych (LLM). Dodatkowo, specyficzne strategie optymalizacji dla modułu uwagi mogą zwiększyć skalowalność i zmniejszyć zapotrzebowanie na pamięć na procesor graficzny, umożliwiając bardziej wydajne uczenie i wnioskowanie dla dużych modeli.
W artykule z 2017 roku *Attention Is All You Need* autorstwa Vaswani et al. wprowadzono model Transformer, którego kamieniem węgielnym jest uwaga własna. Samoobserwacja umożliwia modelowi ocenę znaczenia różnych słów w zdaniu względem siebie, zwiększając zrozumienie kontekstowe w zadaniach takich jak przetwarzanie języka naturalnego. W artykule sformalizowano samouwagę, w szczególności poprzez mechanizm skalowanej uwagi iloczynu kropkowego (SDPA), który mapuje zapytania i pary klucz-wartość na dane wyjściowe, co czyni go kluczowym elementem nowoczesnych sieci neuronowych. Oto niektóre z najważniejszych technik optymalizacji obliczeń uwagi:

Uwaga wielogłowicowa (MHA) opiera się na SDPA poprzez równoległe uruchamianie wielu operacji uwagi, z których każda ma różne projekcje zapytania, klucza i macierzy wartości. Te równoległe operacje lub "głowy" koncentrują się na różnych podprzestrzeniach reprezentacyjnych, wzbogacając zrozumienie danych wejściowych przez model. Dane wyjściowe z tych głowic są łączone i rzutowane liniowo, zachowując wydajność obliczeniową porównywalną z uwagą pojedynczej głowicy poprzez zmniejszenie wymiarowości każdej głowicy (np. dzieląc wymiar modelu przez liczbę głowic, np. 8).
Multi-query attention (MQA) optymalizuje MHA pod kątem wnioskowania poprzez współdzielenie projekcji kluczy i wartości przez wiele głowic uwagi, przy jednoczesnym zachowaniu wielu projekcji zapytań. Zmniejsza to zapotrzebowanie na przepustowość pamięci i rozmiar pamięci podręcznej klucz-wartość (KV), umożliwiając większe rozmiary partii i lepsze wykorzystanie mocy obliczeniowej. Jednak MQA może nieznacznie zmniejszyć dokładność, a modele wykorzystujące ją wymagają szkolenia lub dostrajania z włączoną MQA, aby utrzymać wydajność.
Grouped-query attention (GQA) równoważy MHA i MQA poprzez grupowanie głowic zapytań i współdzielenie projekcji klucz-wartość w każdej grupie, osiągając jakość zbliżoną do MHA z wydajnością obliczeniową bliższą MQA. Modele takie jak Llama 2 70B wykorzystują GQA, a te wyszkolone z MHA można dostosować do GQA przy minimalnym dodatkowym szkoleniu. Zarówno MQA, jak i GQA zmniejszają zapotrzebowanie na pamięć podręczną KV, choć konieczne są dalsze optymalizacje w zarządzaniu pamięcią podręczną.
FlashAttention usprawnia mechanizmy uwagi poprzez zmianę kolejności obliczeń w celu bardziej efektywnego wykorzystania hierarchii pamięci GPU. W przeciwieństwie do tradycyjnego przetwarzania warstwa po warstwie, FlashAttention łączy operacje i wykorzystuje "kafelkowanie" do jednoczesnego obliczania małych części macierzy wyjściowej, minimalizując operacje odczytu/zapisu pamięci. Ten dokładny algorytm uwzględniający operacje wejścia/wyjścia płynnie integruje się z istniejącymi modelami bez konieczności ich modyfikacji, oferując znaczne przyspieszenie dzięki optymalizacji ruchu danych.
Buforowanie KV jest krytyczną techniką optymalizacji stosowaną podczas fazy dekodowania dużych modeli językowych (LLM) w celu poprawy wydajności obliczeń samoobserwacji. W tej fazie każdy wygenerowany token zależy od tensorów klucza (K) i wartości (V) wszystkich poprzednich tokenów, w tym tych obliczonych podczas etapu wstępnego wypełniania i kolejnych kroków dekodowania. Zamiast ponownie obliczać te tensory dla każdego tokena w każdym kroku czasowym, buforowanie KV przechowuje je w pamięci GPU, dołączając nowe tensory do pamięci podręcznej w miarę ich obliczania. Zazwyczaj oddzielna pamięć podręczna KV jest utrzymywana dla każdej warstwy modelu, co znacznie zmniejsza liczbę zbędnych obliczeń i przyspiesza proces dekodowania.

Zapotrzebowanie na pamięć w przypadku obliczeń LLM na procesorach graficznych wynika przede wszystkim z dwóch elementów: wag modeli i pamięci podręcznej KV. Wagi modelu, które składają się z parametrów modelu, zajmują znaczną ilość pamięci; na przykład model o 7 miliardach parametrów, taki jak Llama 2 7B w 16-bitowej precyzji, wymaga około 14 GB. Z drugiej strony pamięć podręczna KV przechowuje tensory własnej uwagi, aby uniknąć ponownych obliczeń, a jej rozmiar zależy od takich czynników, jak liczba warstw, głowice uwagi, wymiary głowic i precyzja. Dla każdego tokena rozmiar pamięci podręcznej jest obliczany jako 2 * num_layers * (num_heads * dim_head) * precision_in_bytes, gdzie współczynnik 2 uwzględnia zarówno macierze K, jak i V. W przypadku partii danych wejściowych całkowity rozmiar pamięci podręcznej KV skaluje się wraz z rozmiarem partii i długością sekwencji, potencjalnie osiągając znaczne rozmiary, takie jak ~ 2 GB dla modelu Llama 2 7B o długości sekwencji 4096 i rozmiarze partii 1.
Efektywne zarządzanie pamięcią podręczną KV stanowi wyzwanie ze względu na jej liniowy wzrost wraz z rozmiarem partii i długością sekwencji, co może ograniczać przepustowość i komplikować obsługę danych wejściowych o długim kontekście. Powszechna nieefektywność wynika ze statycznego nadmiarowego przydzielania pamięci, w którym pamięć jest zarezerwowana dla maksymalnej obsługiwanej długości sekwencji (np. 2048 tokenów), niezależnie od rzeczywistego rozmiaru danych wejściowych. Prowadzi to do znacznego marnotrawstwa pamięci lub fragmentacji, ponieważ znaczna część zarezerwowanej przestrzeni często pozostaje niewykorzystana przez cały czas trwania żądania, wiążąc cenne zasoby pamięci GPU.
Aby zaradzić tym nieefektywnościom, algorytm PagedAttention wprowadza nowatorskie podejście inspirowane stronicowaniem systemu operacyjnego. Dzieli on pamięć podręczną KV na bloki o stałym rozmiarze, z których każdy reprezentuje określoną liczbę tokenów, które mogą być przechowywane w pamięci w sposób nieciągłości. Tabela bloków śledzi te bloki, pobierając je w razie potrzeby podczas obliczeń uwagi. Gdy generowane są nowe tokeny, dodatkowe bloki są przydzielane dynamicznie. Metoda ta minimalizuje marnotrawstwo pamięci, eliminując potrzebę ciągłej alokacji i nadmiernej alokacji, umożliwiając większe rozmiary partii i poprawiając przepustowość, co czyni ją znaczącym postępem w zarządzaniu pamięcią podręczną KV dla LLM.
W tej sekcji omawiamy różne techniki optymalizacji dużych modeli językowych (LLM) w celu zmniejszenia ich zużycia pamięci i zwiększenia wydajności na procesorach graficznych. Kluczowe metody obejmują kwantyzację, rzadkość i destylację, a każda z nich ukierunkowana jest na inne aspekty wydajności modelu. Techniki te modyfikują wagi modeli, wykorzystują akcelerację sprzętową GPU i przenoszą wiedzę do mniejszych modeli, umożliwiając uruchamianie większych modeli na ograniczonym sprzęcie przy zachowaniu wydajności. Metody te mogą pogorszyć dokładność modelu, więc powinny być stosowane ostrożnie.
Kwantyzacja zmniejsza precyzję wag i aktywacji modelu, zwykle z 32 lub 16 bitów do 8 lub mniej bitów, umożliwiając modelom zajmowanie mniejszej ilości pamięci i wydajniejsze przesyłanie danych. Podczas gdy kwantyzacja wag jest prosta ze względu na ich stały charakter po treningu, kwantyzacja aktywacji jest bardziej złożona ze względu na wartości odstające, które rozszerzają ich zakres dynamiczny. Techniki takie jak LLM.int8() rozwiązują ten problem poprzez selektywne stosowanie wyższej precyzji do niektórych aktywacji lub poprzez ponowne wykorzystanie dynamicznego zakresu kwantyzowanych wag dla aktywacji, chociaż procesory graficzne mogą wymagać konwersji wag z powrotem do wyższej precyzji dla operacji.
Rzadkość polega na przycinaniu wartości modelu bliskich zeru, tworząc rzadkie macierze, które wymagają mniej pamięci. Układy GPU obsługują strukturalną rzadkość, taką jak reprezentowanie dwóch z każdych czterech wartości jako zera, co przyspiesza obliczenia. Połączenie rzadkości z kwantyzacją może jeszcze bardziej zwiększyć szybkość wykonywania obliczeń. Badania nad optymalnymi reprezentacjami rzadkich macierzy LLM są kontynuowane, co wskazuje na obiecującą drogę do poprawy szybkości wnioskowania.
Destylacja przenosi wiedzę z większego modelu "nauczyciela" do mniejszego modelu "ucznia", kompresując rozmiar przy jednoczesnym zachowaniu wydajności. Na przykład DistilBERT osiąga 40% redukcję rozmiaru i 60% wzrost szybkości w porównaniu do BERT, zachowując 97% swoich możliwości. Destylacja może obejmować naśladowanie wyników nauczyciela lub wykorzystywanie danych generowanych przez nauczyciela do szkolenia, z metodami takimi jak "Destylacja krok po kroku!" zawierającymi racjonalne przesłanki do skutecznego uczenia się. Jednak restrykcyjne licencje na wiele zaawansowanych LLM ograniczają dostępność odpowiednich modeli nauczycieli do destylacji.
Wnioskowanie spekulatywne, znane również jako próbkowanie spekulatywne lub generowanie wspomagane, to metoda zrównoleglania wykonywania autoregresyjnych dużych modeli językowych (LLM), takich jak modele w stylu GPT, które zazwyczaj generują tekst token po tokenie. W standardowym wykonaniu każdy token zależy od wszystkich wcześniejszych tokenów dla kontekstu, co uniemożliwia równoległe generowanie, ponieważ n-ty token musi zostać wygenerowany przed (n+1) trzecim. Wnioskowanie spekulatywne rozwiązuje ten problem, wykorzystując "tańszy" model wstępny do przewidywania wielu przyszłych tokenów jednocześnie, które są następnie weryfikowane lub odrzucane równolegle przez główny model, umożliwiając szybsze generowanie tekstu.
Proces ten polega na wygenerowaniu szkicu kontynuacji kilku tokenów przy użyciu mniej zasobochłonnej metody, a następnie równoległej weryfikacji przez główny model przy użyciu szkicu jako kontekstu spekulacyjnego. Jeśli model weryfikacyjny pasuje do wersji roboczej tokenów, są one akceptowane; w przeciwnym razie niedopasowane tokeny i kolejne są odrzucane, a proces powtarza się z nową wersją roboczą. Wersje robocze tokenów mogą być generowane przy użyciu różnych podejść, takich jak trenowanie wielu modeli, dostrajanie wielu głowic na wstępnie wytrenowanym modelu w celu przewidywania przyszłych tokenów lub stosowanie mniejszego modelu wersji roboczej wraz z większym, bardziej wydajnym modelem weryfikacji, z których każdy ma swoje własne kompromisy.
Wnioskowanie zdezagregowane to technika, w której zadania obliczeniowe są podzielone na różne urządzenia w celu optymalizacji wydajności, kosztów i wykorzystania zasobów. W szczególności oddziela ona fazy wstępnego wypełniania i dekodowania. Dzięki dezagregacji tych faz, każda z nich może zostać przypisana do sprzętu najlepiej dostosowanego do jej wymagań obliczeniowych, poprawiając wydajność i skalowalność.

Wypełnianie wstępne jest pracochłonne obliczeniowo, wymagając znacznych mnożeń macierzy w celu przetworzenia całego monitu wejściowego i utworzenia pamięci podręcznej KV. Faza ta korzysta z wysokowydajnego sprzętu, takiego jak procesory graficzne lub jednostki TPU, które doskonale radzą sobie z obliczeniami równoległymi. Ponieważ wstępne wypełnianie jest zadaniem jednorazowym dla każdego żądania wnioskowania, można je odciążyć do scentralizowanego, potężnego węzła obliczeniowego zoptymalizowanego pod kątem takich obciążeń. Taka konfiguracja pozwala na szybsze przetwarzanie dużych zapytań i zmniejsza obciążenie mniej wydajnych urządzeń, dzięki czemu idealnie nadaje się do środowisk opartych na chmurze lub centrach danych, w których dostępny jest sprzęt o wysokiej przepustowości.
Z kolei dekodowanie jest związane z pamięcią i obejmuje iteracyjne generowanie tokenów, opierając się w dużej mierze na dostępie do pamięci podręcznych KV. Wymaga mniejszej mocy obliczeniowej, ale potrzebuje szybkiego dostępu do pamięci, dzięki czemu nadaje się do mniej wydajnego, zoptymalizowanego pod kątem pamięci sprzętu, takiego jak procesory lub urządzenia brzegowe. Przenosząc dekodowanie do oddzielnego sprzętu - potencjalnie bliżej użytkownika końcowego, takiego jak serwery lokalne lub urządzenia brzegowe - zdezagregowane wnioskowanie zmniejsza opóźnienia i zapotrzebowanie na przepustowość sieci. Separacja ta umożliwia elastyczne wdrażanie, w którym wstępne wypełnianie działa na wysokiej klasy serwerach w chmurze, a dekodowanie odbywa się na urządzeniach lokalnych lub brzegowych, optymalizując alokację zasobów i umożliwiając wydajne skalowanie dla aplikacji takich jak chatboty w czasie rzeczywistym lub interaktywne systemy sztucznej inteligencji.
Ostatnio opracowano wiele technik optymalizacji wnioskowania w celu poprawy wydajności LLM.
Wdrożenie tych technik wymaga dogłębnego zrozumienia architektury LLM i używanego sprzętu, więc generalnie łatwiej jest użyć istniejącego silnika wnioskowania, który już zaimplementował te techniki, takiego jak vLLM, TensorRT-LLM, LMDeploy itp. Zaimplementowaliśmy te techniki w naszym własnym silniku wnioskowania w NLP Cloud i napisaliśmy post na blogu o silnikach wnioskowania, jeśli chcesz wdrożyć własne modele: można przeczytać tutaj.
Jeśli nie możesz lub nie chcesz samodzielnie wdrażać własnych LLM, możesz skorzystać z NLP Cloud i wykorzystać szybkie generatywne modele AI na dużą skalę w produkcji. Wypróbuj szybkie wnioskowanie w NLP Cloud już teraz!
Jeśli masz pytania dotyczące silników wnioskowania w ogóle, nie wahaj się nas zapytać, zawsze chętnie doradzimy!
Julien
CTO w NLP Cloud