Trefon.
← Wszystkie notatki 8 min

Tokenizery: BPE, modele na poziomie bajtów i ile kosztuje token

Jak BPE buduje słownik ze scaleń, dlaczego BPE na poziomie bajtów nigdy nie zawodzi na nieznanym tekście, ile rozmiar słownika kosztuje w parametrach, a ile oszczędza na długości sekwencji, dlaczego polski i kod kosztują więcej tokenów niż angielska proza i na jakim etapie są modele na poziomie bajtów bez tokenizera.

Jacek Trefon
  • deep dive
  • tokenizer
  • BPE
  • poziom bajtów
Kod ↗

W LLM wszystko liczy się w tokenach: długość kontekstu, rozmiar cache KV, cena, szybkość. Ale „token” nie jest stałą jednostką. Jest tym, co za token uznał tokenizer, a ta decyzja ma swoje koszty na każdym kolejnym etapie.

BPE: słownik zbudowany ze scaleń

Kodowanie par bajtów (byte-pair encoding) w wersji, którą Sennrich i in. (2016) zaadaptowali do tłumaczenia neuronowego, zaczyna od pojedynczych symboli i raz po raz scala najczęstszą parę sąsiednich symboli w nowy symbol, aż słownik osiągnie docelowy rozmiar. Częste słowa stają się pojedynczymi tokenami. Rzadkie słowa są składane z mniejszych kawałków. Kodowanie nowego tekstu odtwarza wyuczone scalenia po kolei.

BPE na poziomie bajtów (GPT-2, Radford i in., 2019) zaczyna scalenia od 256 możliwych bajtów zamiast od znaków. Da się zakodować każdy ciąg, czy to tekst w dowolnym języku, emoji, czy binarne śmieci, bo w najgorszym razie kodowanie wraca do surowych bajtów. Nie ma „nieznanego tokenu”. Większość obecnych otwartych modeli używa jakiegoś wariantu BPE na poziomie bajtów. Druga popularna rodzina to SentencePiece z modelami BPE albo Unigram (Kudo, 2018).

Rozmiar słownika to kompromis

Większy słownik oznacza:

  • Mniej tokenów na tekst. Każdy token obejmuje więcej znaków, więc ten sam dokument to krótsza sekwencja: mniejszy cache KV, mniej pracy dla atencji i więcej tekstu w oknie kontekstu.
  • Więcej parametrów na obu końcach. Zarówno macierz embeddingów, jak i projekcja wyjściowa mają rozmiar V × d. Przy słowniku o rozmiarze 152 064 i wymiarze ukrytym 4096 to 623 mln parametrów na macierz; przy wymiarze ukrytym 5120 to 779 mln. Jeśli te dwie macierze nie są współdzielone (tied), model płaci tę cenę dwa razy. W małym modelu to duża część całego budżetu.
  • Mniej treningu na token. Rzadkie tokeny model widzi rzadziej, więc ich embeddingi są uczone na mniejszej ilości danych.

Dlatego słowniki urosły z około 32K tokenów we wczesnych modelach Llama do dzisiejszych 128K–152K: dłuższe konteksty i tekst wielojęzyczny sprawiły, że droższą stroną stała się długość sekwencji.

Nie każdy tekst kosztuje tyle samo

Tokenizer wytrenowany głównie na angielskim pakuje angielski wydajnie, a wszystko inne słabiej. Dobrym przykładem jest polski. Znaki diakrytyczne zajmują w UTF-8 po dwa bajty: „Zażółć gęślą jaźń” ma 17 znaków, ale 26 bajtów, podczas gdy „The quick brown fox” ma 19 znaków i 19 bajtów. Polska fleksja tworzy też wiele form każdego słowa, więc mniej z nich staje się pojedynczymi tokenami. Ta sama treść kosztuje więcej tokenów, a to oznacza więcej pieniędzy, większy cache KV i krótszy efektywny kontekst niż ten sam tekst po angielsku.

Kod ma własne dziwactwa. Wcięcia, długie identyfikatory i składnia pełna symboli tokenizują się nierówno, a to, jak tokenizer traktuje ciągi spacji, mocno zmienia liczbę tokenów w kodzie. Gdy planujesz budżet kontekstu dla agenta, który pracuje na kodzie, mierz tokeny na swoich prawdziwych plikach, a nie na prozie.

Modele bez tokenizera

Modele na poziomie bajtów rezygnują z wyuczonego słownika i pracują na surowych bajtach:

  • ByT5 (Xue i in., 2022): bajty od początku do końca, przez co sekwencje są dużo dłuższe.
  • MegaByte (Yu i in., 2023): grupuje bajty we fragmenty (patches) o stałym rozmiarze.
  • Byte Latent Transformer (Pagnoni i in., 2024): dobiera rozmiar fragmentów dynamicznie, zależnie od tego, jak przewidywalny jest następny bajt. Trudne miejsca dostają więcej obliczeń, łatwe mniej.

Zaletą jest odporność: brak artefaktów tokenizacji, jednakowe traktowanie każdego języka i brak słownika, który trzeba doczepiać do modelu. Ceną jest długość sekwencji i praca inżynierska potrzebna, żeby podział na fragmenty był wydajny. Napisałem blt, wydajny tokenizer na poziomie bajtów w Ruście. To w tej warstwie dużo wydajności zyskuje się albo traci, zanim model w ogóle zobaczy wejście.

Zasady, których się trzymam

  1. Mierz tokeny na własnych danych, zanim dobierzesz rozmiar kontekstu albo oszacujesz koszt. Proporcje zmierzone na angielskiej prozie nie przenoszą się na polski ani na kod.
  2. Nigdy nie mieszaj tokenizerów między treningiem a serwowaniem. Model jest zdefiniowany tylko razem ze swoim słownikiem.
  3. Wliczaj embeddingi, gdy porównujesz małe modele. Duży słownik może stanowić jedną trzecią parametrów małego modelu.
  4. Pilnuj krawędzi w wywołaniach narzędzi. Błędy tokenizacji najpierw pojawiają się w białych znakach i tokenach specjalnych wokół JSON-a.

Źródła: Sennrich, Haddow i Birch, Neural Machine Translation of Rare Words with Subword Units (2016). Radford i in., Language Models are Unsupervised Multitask Learners (GPT-2, 2019). Kudo, Subword Regularization (2018). Xue i in., ByT5 (2022). Yu i in., MegaByte (2023). Pagnoni i in., Byte Latent Transformer (2024). Liczby bajtów policzono dla przytoczonych ciągów.

Masz podobny problem?

Opisz swój problem

30-minutowa rozmowa techniczna z inżynierem, który to buduje. Bez działu sprzedaży, bez slajdów.