Strona główna / Artykuły

Szkolenie małego GPT na indyjskich baśniach ludowych: tokenizer, maska i strata

Od zera stworzony GPT z 9 milionami parametrów: dlaczego tokenizer GPT-2 zawodzi przy słowach z baśni ludowych, jak działają BPE i maska przyczynowa oraz jak jedna partia danych daje 4 096 strat tokenów następnych.

Wydawca: REACTAPP.TOP16 min czytania3644 słów

Źródła: Building LLM, Part 1 — Training: Predicting the Next Token

Niewielki GPT wyszkolony na indyjskich opowieściach ludowych

Każdy, kto już budował model typu Vision Transformer, zna w przybliżeniu 70 procent składników modelu językowego. Embeddingi, mechanizm uwagi, struktura resztkowa oraz pętla treningowa są takie same w obu typach modeli. Pozostaje różnica, a ten przewodnik omawia ją poprzez stworzenie małego GPT na podstawie zasad fundamentalnych i jego trening od zera przy użyciu karty graficznej w laptopie. Korpus tekstu to indyjskie opowieści ludowe: historie z serii Jataka, bajki w stylu Panchatantra oraz opowieści z Pendżabu i Bengalu.

Realny problem pojawia się już na wczesnym etapie. Podstawowy tokenizer GPT-2 dzieli słowa takie jak Brahman i Rajah na fragmenty, ponieważ nigdy ich nie spotkał, dlatego zamiast tego trenuje się tokenizer na korpusie danych. Na końcu mamy model z 9 milionów parametrów, który przeszedł od losowego szumu do wytrenowanych wag, a każdy może odtworzyć te wyniki, uruchamiając proces treningu trwający około dziesięciu minut.

Dlaczego najpierw pojawiła się percepcja wzroku

Patrzenie na obrazy przed tekstem może wydawać się niewłaściwym porządkiem, skoro chatboty pracują z słowami. Jednak transformator nie różnicuje się pod względem tego, czy jego wejściem są fragmenty zdjęć, czy fragmenty zdań. Głowy uwagi, LayerNorm, połączenia pomijające, struktury MLP oraz pętla optymalizacji pozostają niezmienne. Lepszym pytaniem jest to, gdzie ten wspólny mechanizm jest najłatwiejszy do nauczenia, a obrazy wygrywają z czterech powodów.

  • Tokener to po prostu fragment obrazka, ustalona siatka wycięta z zdjęcia, więc nie ma potrzeby tworzenia słownika, tokenizera ani rozwiązywania problemu nieznanych słów.
  • Nie ma masek przyczynowo-skutkowych. ViT widzi cały obraz jednocześnie, więc zwykła uwaga ma pierwszeństwo przed dodatkowymi regułami języka zakazującymi patrzenia w przyszłość.
  • Behawior modelu jest widoczny – fragmenty obrazka, mapy uwagi oraz obszary, na które skupia się model, można narysować, a piksele wydają się bardziej namacalne w porównaniu z tekstem.
  • Model trenuje od zera na jednej karcie graficznej w ciągu kilku minut (na danych CIFAR-10), więc każda liczba to wynik jego własnego obliczenia, a nie coś przyjętego bez krytyki.

Wynikiem jest to, że pełny model typu transformer już istnieje po stworzeniu ViT, tylko jest dostosowany do przetwarzania obrazów. Model językowy dodaje jedynie mechanizm przekształcania tekstu w tokeny (zestawienie z tablicą, a nie obliczenie), maskę przyczynowo-skutkową oraz funkcję predykcji następnego tokena. Trudna, abstrakcyjna część – sam język – staje się znacznie łatwiejsza do zrozumienia, gdy maszyna już jest z nim zapoznana.

Szkolenie to prosta gra: zgadnij następny token

Podczas szkolenia wszystko, co robi model językowy, sprowadza się do odczytywania dotychczasowych tokenów i przewidywania tego, który nastąpi później.

Rysunek ilustruje przewidywanie następnego tokena na podstawie poprzednich tokenów. Podaj mu początek zdania „W pewnej wiosce żył biedny bramin”, a otrzymasz prawdopodobieństwo dla każdego z możliwych następnych tokenów. Przecinek, przecinka lub słowa takie jak who czy named są wszystkie gramatycznie dopuszczalne. Nikt nie musi nic komentować – prawidłową odpowiedzią jest ten token, który następował w tekście. Dlatego sam tekst surowy może nauczyć model językowy, stanowiąc cały sygnał edukacyjny. Wszystkie inne komponenty służą jedynie do wspierania tego jednego przewidywania, a ViT już dostarcza większości z nich.

W kodzie ta idea zajmuje dwa wiersze: najpierw uruchamia się model, a następnie jego wyniki normalizuje się do rozkładu nad kolejnym tokenem.

logits, _ = model(ids)                     # ids = the tokens so far, shape (1, T)
probs = F.softmax(logits[0, -1], dim=-1)   # one probability per vocabulary token

Pozostała część opisuje, w jaki sposób budowany i szkolony jest model, aby te prawdopodobieństwa były dobre.

Dlaczego ważny jest tokenizer

ViT dzieli obraz na fragmenty, natomiast model językowy dzieli tekst na tokeny. Fragment obliczany jest na podstawie wartości pikseli, natomiast token tekstu pobierany jest z ustalonej słownicy. Ktoś musi najpierw stworzyć tę słownikową bazę, a osoba ta decyduje, które zadania model będzie traktował jako łatwe.

Pierwsza próba wykorzystała r50k_base – gotowy tokenizer GPT-2, którego słownictwo pochodziło mniej więcej z tekstów internetowych zachodnich z 2019 roku. Następny rysunek pokazuje, jak radzi sobie on z słowami z tych opowieści.

Brahman dzieli się na Brah i man, Rajah na Raj i ah, a Panchatantra rozpada się na pięć części. Żadna z tych form nie jest trudna do zrozumienia. Słowo Rajah występuje 709 razy w korpusie, jednak narzędzie do tokenizacji nie widziało takich słów wystarczająco często we własnych danych treningowych, by przypisać im po jednym tokenie. Fragmentacja wydłuża sekwencje i zmusza model do ponownego składania Brahman z Brah oraz man, przy czym man jest również współdzielone z woman i human. Przy zaledwie milionie tokenów danych treningowych stanowi to poważną przeszkodę.

Rozwiązaniem jest tokenizer wyszkolony na tych opowieściach, wykorzystujący kodowanie par bajtów (BPE). GPT używa tego samego algorytmu, tylko dostosowanego do lepiej dopasowanego korpusu. Materiałem źródłowym są dwanaście zbiorów indyjskich baśni z domeny publicznej z Project Gutenberg, w tym dwa księgi Jataka, dwa tomy indyjskich bajek, Old Deccan Days, Folk-Tales of Bengal, The Talking Thrush, Tales of the Punjab, Twenty-Two Goblins, dwa zbiory Santal oraz Tales of the Sun from Southern India, co łącznie stanowi około 809 000 słów.

Wykres przedstawia proces łączenia par bajtów używany do szkolenia tokenizera. BPE zaczyna od 256 surowych wartości bajtów, dzięki czemu nic nie może być nieznane, a następnie wielokrotnie łączy najczęściej występujące sąsiednie pary w jeden nowy token. Po 7 935 łączeniach w oparciu o te opowieści słownictwo liczy 8 192 pozycji i obejmuje całe jednostki takie jak Rajah, Brahman oraz jackal.

Jedna decyzja projektowa wymaga jasnego stwierdzenia: tokenizer GPT-2 został całkowicie zastąpiony, a nie rozszerzony.

Rysunek pokazuje spersonalizowaną leksykę składającą się z 8 192 tokenów. Dodawanie tokenów do istniejących 50 257 w r50k jest rozsądnym rozwiązaniem, gdy chce się zachować umiejętności językowe modelu w języku angielskim, jak to robią projekty takie jak OpenHathi. Inna sytuacja występuje przy trenowaniu od zera na małym, specjalistycznym korpusie – wtedy leksyka r50k oparta na zachodnich stronach internetowych zajmuje tylko miejsce, a teksty dotyczące tych tematów obejmują zaledwie 37 procent tej leksyki. Nowa leksyka licząca 8 000 elementów jest mniejsza, wykorzystywana w 96,6 procentach przypadków i dostosowana do tych tekstów, więc jej zastąpienie jest najlepszym rozwiązaniem.

Trenowanie tokenizera wymaga zaledwie kilku linijek kodu przy użyciu biblioteki HuggingFace tokenizers. Skrypt odczytuje każdą książkę w formacie surowym, usuwa nagłówki i stopki Project Gutenberg oraz trenuje model BPE.

import glob
from tokenizers import Tokenizer, models, trainers, pre_tokenizers
from llm_transformer.prep_tales import strip_gutenberg, RAW_DIR

# read every raw .txt book and strip its Project Gutenberg header/footer,
# leaving just the story text - one big string per book
tales_texts = [strip_gutenberg(open(p, encoding="utf-8", errors="ignore").read())
               for p in sorted(glob.glob(f"{RAW_DIR}/*.txt"))]   # 12 books, ~809k words
tok = Tokenizer(models.BPE())
tok.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False)   # start from bytes
trainer = trainers.BpeTrainer(vocab_size=8192, special_tokens=["<|endoftext|>"],
                              initial_alphabet=pre_tokenizers.ByteLevel.alphabet())
tok.train_from_iterator(tales_texts, trainer)   # learn the 7,935 merges
tok.save("tales_bpe.json")

Plik wyjściowy w formacie .json to gotowy tokenizer, stała lista połączeń. Nie jest ponownie szkoleny. Jest ładowany i używany do konwertowania tekstu na identyfikatory oraz z powrotem na tekst. Jedna wywołanie funkcji .encode() łączy surowy tekst z siecią neuronową.

tok = Tokenizer.from_file("tales_bpe.json")   # reload the trained tokenizer
ids = tok.encode("Once upon a time").ids      # -> [412, 987, 15, 733]   (integers)
idx = torch.tensor(ids).unsqueeze(0)          # (1, 4) — a batch of one sequence

vocab_size = tok.get_vocab_size()             # 8192 - sets the NUMBER OF ROWS below

Tylko te liczby całkowite docierają do sieci, a oryginalne słowa już dla niej nie istnieją. Każdy identyfikator to po prostu pozycja w tabeli, a vocab_size określa, ile wpisów rezerwuje warstwa embeddingowa.

Od identyfikatorów do wektorów

Gdy tekst staje się listą identyfikatorów, każdy z nich przekształca się w wektor. To właśnie tutaj model LLM jest prostszy od ViT.

Rysunek łączy indeks tokenizatora z wektorem embeddingowym. W modelu ViT mała mnożenie macierzy oblicza token każdego fragmentu na podstawie pikseli. W modelu LLM token to dyskretny identyfikator, więc wektor to po prostu numer wiersza z nauczonej tabeli – zwykłe wyszukiwanie. Token 789 odwołuje się do wiersza 789, który stanowi wektor o wymiarze 256, a dodanie embeddingu pozycyjnego, podobnie jak w ViT, zamyka etap przetwarzania wejścia.

w kodzie warstwa nn.Embedding stanowi tę tabelę, a jej wywołanie służy do wyszukiwania poprzez indeksowanie, a nie mnożenie. Jej .weight to tensor o wymiarach 8192 na 256, który może być uczyony i zawiera 2,1 miliona wag trenowanych metodą backpropagation; dana wiersz zmienia się jedynie w tych krokach, gdy występuje w nim odpowiedni token.

self.token_embed = nn.Embedding(vocab_size, n_embed)   # the 8,192 x 256 table
# .weight IS a learnable (8192, 256) parameter tensor — 2.1M weights, trained by backprop;
# each id's row is nudged only on steps where that token appears in the batch
...
tok_emb = self.token_embed(idx)     # idx (B, T)  ->  vectors (B, T, 256), one row per id

Wszystko, co znajduje się dalej, czyli mechanizm uwagi, strumień resztkowy oraz bloki MLP, jest identyczne w Vision Transformerze, z jedną wyjątkiem.

Maska przyczynowa

ViT umożliwia każdemu patchowi skupienie się na wszystkich pozostałych. Model językowy nie może tego robić, ponieważ jego zadaniem jest odgadnięcie tego, co nastąpi dalej. Gdyby pozycja t widziała pozycję t+1, po prostu skopiowałaby odpowiedź. Rozwiązaniem jest zasłanianie mechanizmu uwagi, tak aby każdy token widział tylko siebie oraz wcześniejsze tokeny.

Maska na rysunku ma kształt trójkąta dolnego. Przed zastosowaniem funkcji softmax wszystkie przyszłe pozycje otrzymują wartość ujemnej nieskończoności, która po przekształceniu przez softmax staje się wagą równą dokładnie zero, dzięki czemu zablokowane tokeny nic nie dodają. Ten trójkąt stanowi jedyną różnicę architektoniczną pomiędzy mechanizmem uwagi w ViT a w modelach LLM. Iloczyny skalarnych, skalowanie przez pierwiastek kwadratowy wielkości modułu, funkcja softmax oraz suma ważona wartości pozostają bez zmian.

W kodzie jest to jedna dodatkowa linia nad zwykłą uwagą. Tworzy się raz macierz trójkątną dolną składającą się z jedynek, oceny oblicza się jak zwykle, a pozycje ustawione na zero wypełnia się ujemną nieskończonością przed zastosowaniem funkcji softmax.

# tril = torch.tril(torch.ones(T, T))  — a lower-triangular matrix of 1s, made once
attn = q @ k.transpose(-2, -1) / math.sqrt(head_size)          # scores, (B, T, T)
attn = attn.masked_fill(self.tril[:T, :T] == 0, float('-inf')) # blank out the future
attn = F.softmax(attn, dim=-1)                                  # -inf -> weight 0
out  = attn @ v

Jedna przechodnia trenuje każdą długość kontekstu

Maska jednowierszowa ukrywa coś, co wymaga bliższego przyjrzenia się. Ludzie mają tendencję do wyobrażania sobie modelu językowego działającego w taki sposób, jakby generował tekst: dodając token do rosnącego kontekstu i ponownie obliczając uwagę na każdym kroku. To prawda podczas inferencji, ale nie podczas treningu. Podczas treningu zdanie jest w pełni znane, więc Q, K i V dla wszystkich tokenów T są obliczane razem, tworząc całą macierz uwagi T na T w jednym kroku.

Ta pojedyncza przechodnia jest możliwa tylko dzięki masce przyczynowej. Rozważmy wiersz t zamaskowanej macierzy: wszystkie wartości po prawej stronie diagonali to ujemna nieskończoność, więc po zastosowaniu funkcji softmax stają się zerem. Obliczenie out = attn @ V daje zatem wierszowi t sumę ważoną tylko pierwszych t wektorów wartości. Dzięki temu każda długość kontekstu jest obsługiwana jednocześnie.

Rysunek przedstawia różne iteracje oraz kształt maski podczas treningu. Przeczytaj go wiersz po wierszu:

  • Wiersz 1 bierze pod uwagę tylko token 1, więc jego wynik przewiduje token 2 na podstawie kontekstu składającego się z jednego tokena.
  • Wiersz 3 bierze pod uwagę tokeny od 1 do 3 i przewiduje token 4 na podstawie kontekstu składającego się z trzech tokenów.
  • Wiersz 6 bierze pod uwagę wszystkie sześć tokenów i przewiduje token 7 na podstawie pełnego kontekstu.

Oto inny punkt widzenia. Widoczny kontekst każdego wiersza tworzy macierz trójkątną dolną, która z każdym razem powiększa się o jeden element – 1 na 1 dla pierwszego tokena, potem 2 na 2 i 3 na 3. Są to wplecione bloki w lewym górnym rogu dużej macierzy, narysowane na pomarańczowo; dzięki temu jedna iteracja 6 na 6 obejmuje wszystkie mniejsze sekwencje, a pasek na dole je odsłania. Wszystko to jest generowane w ramach jednego attn @ V, a w Pythonie nie ma żadnych pętli przeglądających pozycje. To tłumaczy efektywność szkolenia modeli typu transformer: sekwencja składająca się z T tokenów odpowiada T przykładom do szkolenia, przy czym konteksty o długości od 1 do T są oceniane równolegle. Ta sama maska trójkątna, która dostarcza te przykłady, zapobiega również temu, by jakakolwiek pozycja patrzyła w przyszłość podczas generowania, które odbywa się wiersz po wierszu, a koszt każdego kroku jest niski dzięki przechowywaniu w pamięci wcześniejszych kluczy i wartości (pamięć KV).

Liczby rzeczywiste za pomocą uwagi

Do tej pory każda komórka oznaczała jedynie obecność lub brak obecności. Po otwarciu opcji „mnożenie” widoczne są rzeczywiste liczby dla tych samych trzech prób, pochodzące z bloku 0, głowy 0 wytrenowanego modelu na zdaniu „Once there lived a clever jackal”.

Rysunek przedstawia obliczenia Q, K i V dla poszczególnych prób. W obrębie jednego wiersza wagi uwagi są dodatnie i sumują się do 1, więc wynik stanowi wypukłą mieszankę wektorów wartości widocznych, co jest średnią ważoną.

  • W próbie 1 widoczny jest jeden token o wadze 1,00, więc out1 = V1. Pierwsza pozycja kopiuje swoją własną wartość, ponieważ nie ma jeszcze nic do połączenia.
  • W próbie 3 wynik powstaje przez połączenie trzech wartości przy użyciu wag około 0,20 dla pierwszego tokena, 0,06 dla drugiego i 0,74 dla trzeciego. Najbardziej opiera się na swoim własnym tokenie lived, ale nadal uwzględnia jedną piątą wartości z Once, więc jest to prawdziwa mieszanka.
  • W szóstej rundzie wagi są rozkładane między wszystkie sześć wartości, przy czym po 0,26 przypada na there i jackal. Ten połączony wektor 32-wymiarowy podsumowuje cały prefiks dotyczący głowy, a kolejne warstwy przekształcają go w prognozę.
  • To obejmuje wszystko, co robi mechanizm uwagi: wyniki z zapytań i kluczy określają wagi, a te z kolei łączą wartości. Maskowanie gwarantuje, że ta mieszanka nigdy nie wykracza poza bieżący token, dzięki czemu każdy wiersz stanowi sprawiedliwe podsumowanie wszystkiego, co zostało do tej pory przeczytane. Prawdziwe głowy mają 32 wymiary wartości, a model posiada osiem takich głów w każdej z sześciu warstw. Paski pokazują pierwsze 8 wymiarów pojedynczej głowy, chociaż ten sam mechanizm obowiązuje we wszystkich przypadkach.

    T predictions, T losses

    Przewidywanie następnego tokena daje korzyść, której brakowało modelowi ViT. ViT dokonywał pojedynczego przewidywania na obraz – etykiety klasy. Maska przyczynowa umożliwia modelowi językowemu jednoczesne odgadnięcie następnego tokena we wszystkich pozycjach, dzięki czemu sekwencja dostarcza tylu sygnałów do uczenia się, ile ma tokenów.

    Na rysunku przedstawiono średnią wartości T przewidywań i strat dla wejścia składającego się z T tokenów w każdym przebiegu. Każda pozycja otrzymuje stratę entropii krzyżowej, która mierzy stopień zaskoczenia modelu rzeczywistym następnym tokenem. Niektóre przypadki są proste, np. a po słowie lived, przy wartości zaledwie 0,58. Inne są trudniejsze, np. przymiotnik clever, przy wartości 7,17, ponieważ mogło pasować wiele innych słów. Średnia z tych sześciu wynosi 3,59, a propagacja wsteczna obniża tę wartość, wykorzystując ten sam mechanizm reguły łańcuchowej co model ViT.

    W kodzie wszystkie straty T pochodzą z jednego wywołania cross_entropy. Siatka pozycji (B, T) jest spłaszczana do jednej długiej listy, a każdy element jest porównywany ze swoim prawdziwym następnym tokenem.

    logits = self.lm_head(x)                       # (B, T, vocab)
    B, T, C = logits.shape
    loss = F.cross_entropy(logits.reshape(B*T, C), # every position ...
                           targets.reshape(B*T))   # ... vs the token that actually followed
    

    Każda strata to klasifikacja słownictwa

    Ważne jest, aby dokładnie wiedzieć, co porównuje cross_entropy, ponieważ zarówno przewidywanie, jak i cel to wektory, a nie pojedyncze słowa. W każdej pozycji lm_head wytwarza wektor logitów składający się z 8 192 elementów – po jednej ocenie na każdy token słownictwa – a funkcja softmax przekształca go w rozkład prawdopodobieństw dla wszystkich tych tokenów. Celem jest wektor one-hot o tej samej długości, zawierający 1 przy słowie, które faktycznie wystąpiło, oraz 0 we wszystkich pozostałych miejscach. Przewidywanie następnego tokena stanowi zatem problem klasifikacji, w którym słowa ze słownictwa pełnią rolę klas.

    Rysunek porównuje logity w modelu LLM i ViT. Wyjaśnia on jeden subtelny aspekt: paski są uporządkowane według prawdopodobieństwa wyłącznie dla ułatwienia odczytu, podczas gdy rzeczywisty wynik to wektor o ustalonej strukturze, w którym pozycja i zawsze odpowiada identyfikatorowi tokena i. Słowo „son” pozostaje na indeksie 498 przy każdym przetwarzaniu, zarówno podczas treningu, jak i inferencji, a funkcja softmax zmienia jedynie wysokość tych pozycji, nigdy ich kolejność. To wyjaśnia, dlaczego jedyne 1 w celu one-hot znajduje się na indeksie 498, a nie na pierwszej pozycji: tokenizer przydzielił każdemu tokenowi jego identyfikator jako pozycję.

    To właśnie jest głowa klasyfikatora ViT, która stosuje funkcję softmax do ocen klas i ocenia je za pomocą entropii krzyżowej w porównaniu z etykietą typu one-hot. Różnią się dwie rzeczy: liczba klas (8 192 tokenów słownika w porównaniu z około 10 etykiet obrazów) oraz częstotliwość występowania (we wszystkich pozycjach T w porównaniu z raz na obraz). W przypadku celu typu one-hot entropia krzyżowa sprowadza się również do ujemnego logarytmu prawdopodobieństwa przypisanego odpowiedniemu słowu, dlatego strata dla każdej pozycji przypomina wynik zaskoczenia. Słowo „son” o prawdopodobieństwie 9,8 procent kosztuje 2,32, co jest dużo wyższe, niż model silnie przewidywał, że będzie kosztować, natomiast prawdziwe zaskoczenie powinno wiązać się z znacznie wyższą wartością.

    Jak budowana jest partia danych

    Operacja reshape(B*T) wskazuje po prostu, że partia składa się z B sekwencji, z których każda wnosi straty dla T pozycji. Skąd więc pochodzą te B sekwencji i czy wszystkie mają tę samą długość?

    Ludzie często przeoczają jeden szczegół: sekwencje używane do wstępnego szkolenia nie są uzupełniane i wszystkie mają dokładnie tę samą długość kontekstu. Korpus jest łączony w jeden długi strumień tokenów, z tokenem <|endoftext|> pomiędzy dokumentami, a okna o stałej długości są wycinane w losowych pozycjach. Wszystkie okna pochodzą z tego samego ciągłego strumienia, więc żadne nie może być krótkie, a tokenów uzupełniających nie jest potrzebnych.

    Wykres przedstawia ścieżkę od korpusu treningowego przez partie danych aż do wartości straty. Jedynym specjalnym tokenem w tym strumieniu jest <|endoftext|> (EOT), który pełni rolę separatora zamiast elementu wypełniającego i wskazuje, gdzie kończy się jedna baśń, a zaczyna się następna. Okno analityczne może przekroczyć taką granicę i zawierać element EOT w swoim środku, i właśnie w ten sposób model dowiaduje się, że historia się zakończyła, a rozpoczęła się nowa. Elementy wypełniające pojawiają się później, podczas drobnej kalibracji modelu, gdy długość przykładów się różni, a pozycje wypełnione są maskowane w celu wykluczenia ich z obliczeń straty – jest to zupełnie inna sytuacja niż podczas intensywnego treningu wstępnego.

    Strata jest drugą kwestią, w której LLM odbiega od ViT, a ta różnica jest pożądana. ViT wytwarzał jedną prognozę i jedną stratę na obraz. Sekwencja składająca się z 128 tokenów generuje tu 128 strat, które są średnionane, by uzyskać stratę danego przykładu (3,78 dla przykładu 1 na rysunku), a 32 straty z poszczególnych przykładów w partiach są średnionane, by otrzymać stratę partii wynoszącą 3,92, którą minimalizuje algorytm backpropagation. Dzięki tym dwóm etapom średniania jedno kroku obniża średnią 32 razy 128, czyli 4 096 strat dla kolejnego tokena – co daje znacznie większą ilość informacji na krok niż jedna strata na obraz.

    Obserwowanie spadku straty

    Połącz tę stratę z optymalizatorem i powtórz proces. To właśnie jest rzeczywiste treningowanie na korpusie opowieści.

    Wykres przedstawia stratę podczas treningu oraz stratę walidacyjną na przestrzeni epok. Trening rozpoczyna się od wartości 9,2, co odpowiada ln(8192) – stratie wynikającej z przypadkowego zgadywania w słowniku składającym się z 8 192 tokenów, a najlepsza strata walidacyjna osiąga wartość 4,73. Krzywe pozostają blisko siebie, z odległością około 1,0, co oznacza, że przy wąskim zakresie problemu model potrafi generalizować zamiast jedynie zapamiętywać. Sam model jest niewielki.

    Rysunek łączy proces treningu z liczbą wag, które można dostosować. Dzięki 9 milionom parametrów model trenuje się w około dziesięć minut na GPU laptopa o pojemności 6 GB. Nieco ponad połowa z nich znajduje się w sześciu blokach typu transformer, a reszta należy do tabeli embeddingów oraz modułu wyjściowego. Trening polega wyłącznie na wielokrotnym dostosowywaniu tych 9 milionów wartości, dzięki czemu prawdopodobieństwa następnego tokena są kierowane w stronę tego, który rzeczywiście pojawił się później.

    W kodzie pętla jest taka, jakiej można się spodziewać, i ma taką samą strukturę jak w ViT: pobiera się losowy kontekst oraz okna następnych tokenów, wykonuje się przepływ danych w kierunku wyjścia, aby uzyskać jedną wartość straty średniej, ustawia się gradienty na zero, przeprowadza się propagację wsteczną i wykonuje krok.

    for step in range(train_steps):
        xb, yb = get_batch(train_data)     # random (context, next-token) windows
        _, loss = model(xb, yb)            # forward: T predictions -> one mean loss
        opt.zero_grad(); loss.backward()   # backprop the loss into all 9M parameters
        opt.step()                         # nudge them downhill
    

    Pełny skrypt zawiera harmonogram zmian szybkości uczenia się oparty na kosinusie oraz mechanizm oceny.

    Co nowego i co pozostało bez zmian

    W porównaniu z Vision Transformer w LLM jest niewiele nowości: embeddingi tokenów działające jako tablice wyszukiwania, maska przyczynowo-skutkowa oraz strata związana z następnym tokenem w każdej pozycji, która klasyfikuje na podstawie całego słownika. Wyróżnia się również praktyczna lekcja – tokenizer należy do modelu, więc jego dostosowanie do konkretnego dziedziny sprawia, że się to opłaca. Mechanizmy takie jak uwaga, embeddingi pozycyjne, strumień resztkowy, LayerNorm, bloki MLP oraz pełny cykl backpropagation i optymalizator pozostają bez zmian.

    Liczby rzeczywiste ujawniły wewnętrzne mechanizmy: jak maska przyczynowa pozwala na jedną transmisję danych na przód, aby ocenić wszystkie długości kontekstu jednocześnie, co wytwarza attn @ V (każdy rząd wyników łączy wektory wartości dostępne dla danego tokena), jak partie treningowe powstają poprzez pakowanie zamiast wypełniania dodatkowymi danymi, oraz jak strata jednej pozycji, będąca klasyfikacją w obrębie słownika, łączy się z pozostałymi w jedną średnią.

    Wagi zostały ustalone, ale wytrenowany model nic nie robi, dopóki nie zostanie uruchomiony. Następnie następuje proces inferencji: pętla generowania, cache KV, który zapewnia szybkość działania, oraz to, co mały model pisze po otrzymaniu początku historii.

    Literatura pokrewna

  • Od przewidywania następnego tokena do wykonywania instrukcji: drobna optymalizacja modelu GPT — Jak przeszkolony model GPT uczy się wykonywać polecenia: formatowanie zapisów, zmiana etykiet, maski wypełniania, pętla treningowa, ograniczenia oceny oraz krok w stronę preferencji DPO.
  • Distylacja z miękkimi etykietami dla agenta SQL o pojemności 0,8 miliarda parametrów: kiedy słabszy nauczyciel wygrywa — Metoda distylacji z miękkimi etykietami typu top-k na zamrożonych danych pomogła agentowi SQL o pojemności 0,8 miliarda parametrów rozwiązać 55 z 220 zadań, przy czym słabszy nauczyciel z tej samej rodziny okazał się skuteczniejszy od silniejszego.