ML Atlas

Kurs: modele językowe od środka · Lekcja 1 z 12 · 13 min

Tokeny: jak BPE buduje słownik

W skrócie

Model nie czyta liter, tylko numery tokenów. Zobacz, jak BPE skleja najczęstsze pary w słownik i ile to kosztuje: krótsze ciągi, większa macierz osadzeń.

Rozgrzewka Dwa pytania z kursów NN i ML, na których ta lekcja się opiera. Minuta, a lekcji nie blokuje.
Z kursu ML, lekcja 7.2: Od sieci neuronowych do dużych modeli językowych Na jakim zadaniu trenuje się duży model językowy?

Pokaż odpowiedź

Poprawna odpowiedź: Na przewidywaniu następnego tokenu na podstawie wcześniejszych

LLM uczy się dawać wysokie prawdopodobieństwo faktycznemu następnemu tokenowi, a cała reszta wyrasta z tego zadania.

Z kursu NN, lekcja 4: Wyjście i strata: softmax i entropia krzyżowa Co softmax robi z logitami?

Pokaż odpowiedź

Poprawna odpowiedź: Zamienia je w liczby dodatnie, które sumują się do 1, i zachowuje ich kolejność

Potęgowanie daje liczby dodatnie, dzielenie przez sumę daje 1, a większy logit dostaje większe prawdopodobieństwo.

Zanim model językowy cokolwiek policzy, tekst zamienia się w ciąg liczb. Każda liczba to numer tokenu: kawałka tekstu ze stałego słownika. Słownik nie jest ustalony ręcznie. Buduje go algorytm BPE (od angielskiego byte-pair encoding) na tekście do nauki, czyli korpusie, a od tego, jak go zbuduje, zależy długość każdego ciągu, który model potem przetwarza, i rozmiar pierwszej macierzy modelu.

Przewidź, potem sprawdź Zdanie „pies je kość pod stołem” jest w 28 zdaniach, na których uczy się tokenizator w labie. Ma 19 liter, więc bez żadnego scalenia to 19 tokenów (spacje nie są tokenami, znak ▁ początku słowa siedzi przy pierwszej literze). Ile tokenów ma po 80 scaleniach?
Jak bardzo jesteś pewny(-a)?

Eksperyment

Ten interaktywny eksperyment działa w przeglądarce z włączonym JavaScriptem. Tekst wokół opisuje, co w nim widać. W eksperymencie: tokenizator BPE uczony na kilkudziesięciu zdaniach: im więcej scaleń, tym mniej tokenów, a nieznane słowa rozpadają się na litery.

Pokaż odpowiedź

Poprawna odpowiedź: Około ćwierci: 5, po jednym na słowo

Po 80 scaleniach każde z pięciu słów jest jednym tokenem, więc z 19 tokenów zostaje 5. To słowa z korpusu: BPE skleja najpierw pary, które w nim występują najczęściej, a te słowa składają się właśnie z takich par.

Spróbuj

  • Zostań przy „pies je kość pod stołem” i przesuwaj suwak po jednym scaleniu. Przy którym scaleniu zdanie ma pierwszy raz tyle tokenów, ile słów? To cel labu.
  • Patrz na licznik „liter na token” przy 0 i przy 80 scaleniach: o ile krótszy robi się ten sam tekst?
  • Przejdź na „psy siedzą na podłodze” przy 80 scaleniach i sprawdź, które kawałki słowa „siedzą” pochodzą z innych słów korpusu.

Pomysł w prostych słowach

BPE zaczyna od pojedynczych liter każdego słowa. Liczy, jak często każda para sąsiednich kawałków występuje w tekście do nauki, skleja najczęstszą parę w nowy token i powtarza. Każde scalenie to jeden nowy token w słowniku. Na 28 zdaniach labu słownik startowy ma 43 symbole (litery, przy czym litera na początku słowa jest osobnym symbolem), a pierwsze scalenie to „i” + „e”: ta para występuje 36 razy. Scaleń może być najwyżej 92, bo dalej żadna para nie występuje co najmniej 2 razy.

Częste słowa szybko stają się jednym tokenem, rzadkie zostają w kawałkach dłużej. W zdaniu z labu ostatnie w całość skleja się „stołem”, które w korpusie występuje tylko 2 razy. Słowo, którego w nauce nie było, tokenizator złoży ze znanych kawałków, w skrajnym razie z pojedynczych liter, ale tylko z liter, które wystąpiły w korpusie. W labie „ż” i „ó” w korpusie nie występują, więc w zdaniu „żółw pływa w akwarium” tokenizator z labu nie miałby dla nich numeru. Dlatego BPE może zaczynać od bajtów zamiast liter: słownik startowy to wtedy 256 możliwych bajtów, każdy znak zapisuje się jednym albo kilkoma bajtami i żaden tekst nie wypada poza słownik [2]. Sam algorytm pochodzi z kompresji danych; jako słownik podsłów zastosowano go w tłumaczeniu maszynowym, żeby rozwiązać problem słów spoza słownika [1].

Dlaczego to działa

Słownik ma dwie strony kosztu. Po 80 scaleniach cały korpus labu to 195 tokenów zamiast 620, czyli ciągi są ponad 3 razy krótsze. To wynik na tekście, na którym tokenizator się uczył: trzy zdania labu spoza korpusu mają po 80 scaleniach 27 tokenów na 55 liter, czyli ok. 2 litery na token. Model, który czyta krótszy ciąg, robi mniej kroków przy generowaniu i mieści więcej tekstu w tym samym oknie kontekstu, czyli w liczbie tokenów, które widzi naraz. Za to każdy token słownika dostaje w modelu własny wektor osadzenia: przy wektorach po 16 liczb macierz osadzeń ma 688 liczb przy 43 symbolach i 1968 liczb przy 123 tokenach. Większy słownik to krótsze ciągi i większa macierz. Rozmiar słownika wybiera się jako kompromis między tymi kosztami.

Ten sam mechanizm ma skutek, który widać w prawdziwych tokenizatorach. Słownik uczy się na tekście do nauki, więc język, którego było w nim mało, kroi się na drobniejsze kawałki. Ten sam tekst przetłumaczony na różne języki może mieć w jednym tokenizatorze nawet 15 razy więcej tokenów w jednym języku niż w innym [3]. Liczby dla polskiego zależą od konkretnego tokenizatora; lab ich nie liczy, bo nie wczytujemy prawdziwych słowników.

Częsty błąd

Prawda czy fałsz: „Model dostaje litery słowa »parapecie«, więc widzi każde jego »r«.” Fałsz dla modelu na tokenach BPE takich jak w labie. Po 80 scaleniach „parapecie” to 1 token, czyli dla modelu jeden numer, a dalej jeden wiersz macierzy osadzeń. Litery wewnątrz tokenu nie są osobnym wejściem: o pisowni tokenu model wie tylko tyle, ile wywnioskował z tekstu w treningu. Model, który czyta pojedyncze litery, dostaje każdą osobno, ale w labie płaci za to ponad 3 razy dłuższymi ciągami: 620 tokenów korpusu zamiast 195.

Quiz

Trzy pytania na sprawdzenie się. Wybierz odpowiedź albo rozwiń odpowiedź pod pytaniem. Z klawiatury: strzałki wybierają, Enter albo spacja zatwierdza.

1. Ile tokenów ma zdanie „dzieci lubią koty i psy” po 40 scaleniach?
Pokaż odpowiedź

Poprawna odpowiedź: 5 tokenów

Po 40 scaleniach każde słowo tego zdania jest już jednym tokenem; po 20 scaleniach było ich jeszcze 10, bez scaleń 19.

2. Słownik startowy labu ma 43 symbole, a każdy token dostaje wektor osadzenia o 16 liczbach. Ile liczb ma macierz osadzeń po 80 scaleniach?
Pokaż odpowiedź

Poprawna odpowiedź: 1968 liczb

Każde scalenie dokłada jeden token: 43 + 80 = 123 tokeny, a 123 · 16 = 1968. 688 to macierz bez scaleń, 1280 to tylko 80 nowych wierszy.

3. Słowo „parapecie” po 80 scaleniach jest jednym tokenem. Co dostaje model na wejściu?
Pokaż odpowiedź

Poprawna odpowiedź: Jeden numer tokenu, czyli jeden wiersz macierzy osadzeń

Tokenizator zamienia „parapecie” na jeden numer. Litery tego tokenu nie trafiają do modelu jako osobne wejścia.

Najważniejsze wnioski

  • Model dostaje numery tokenów, a nie litery; słownik tokenów buduje BPE na tekście do nauki.
  • BPE zaczyna od liter, skleja najczęstszą parę sąsiednich kawałków i powtarza; każde scalenie to jeden nowy token.
  • Większy słownik daje krótsze ciągi, ale większą macierz osadzeń: rozmiar słownika to kompromis.
  • Tekst rzadki w danych do nauki, także inny język, kroi się na więcej tokenów.

Źródła (sprawdzone 26.10.2026)

  1. arxiv.org/abs/1508.07909: BPE z kompresji danych przeniesione na słownik podsłów w tłumaczeniu maszynowym; rozdział 3.2: znacznik końca słowa, liczba scaleń jako jedyny hiperparametr (Sennrich, Haddow, Birch). (sprawdzone 26.10.2026)
  2. arxiv.org/abs/1909.03341: BPE na bajtach: słownik startowy to 256 bajtów, więc nie ma tokenów spoza słownika (Wang, Cho, Gu, abstrakt). (sprawdzone 26.10.2026)
  3. arxiv.org/abs/2305.15425: Ten sam tekst w różnych językach daje w tym samym tokenizatorze bardzo różną liczbę tokenów (Petrov i in., NeurIPS 2023, abstrakt). (sprawdzone 26.10.2026)

Głębiej: wzór, kod, praktyka

Matematyka

Słownik startowy V₀ to litery korpusu. Krok BPE: dla każdej pary sąsiednich symboli (a, b) policz n(a, b) = Σ_słowa f(słowo) · (liczba wystąpień pary w słowie), gdzie f to częstość słowa, i dodaj do słownika ab dla pary z największym n.

  • Po k scaleniach słownik ma |V| = |V₀| + k tokenów.
  • Macierz osadzeń ma |V| · d liczb, gdzie d to długość wektora tokenu.
  • Długość zakodowanego tekstu L(k) nigdy nie rośnie z k: scalenie zamienia dwa symbole na jeden albo niczego nie zmienia.
  • W praktyce liczbę scaleń, czyli rozmiar słownika, wybiera się z góry: to jedyny hiperparametr algorytmu [1]. Lab kończy, gdy żadna para nie występuje co najmniej 2 razy: taki token skróciłby tylko jedno słowo i nic by nie uogólniał.
  • Znak ▁ na początku słowa to wariant zapisu granicy słowa; w artykule o BPE granicę oznacza znacznik na końcu słowa [1].
W kodzie (Python)

BPE na 28 zdaniach labu w czystym Pythonie i numpy: słownik startowy, pierwsze scalenie, cel labu i koszt słownika.

import numpy as np
from collections import Counter

corpus = [
    "kot siedzi na parapecie", "kot je rybę na kanapie", "pies siedzi na kanapie", "pies je kość pod stołem",
    "koty lubią spać na parapecie", "psy lubią biegać po parku", "mały kot i mały pies śpią razem", "dzieci lubią koty i psy",
    "kot śpi na kanapie a pies na podłodze", "dzieci biegają po parku z psem", "mały pies je kość a kot je rybę", "koty i psy śpią razem na kanapie",
    "pies biega po parku a kot siedzi na parapecie", "koty lubią rybę a psy lubią kość", "dzieci karmią koty i psy", "mały kot śpi na podłodze",
    "pies śpi pod stołem", "dzieci lubią biegać z psem po parku", "kot patrzy na rybę", "mały pies patrzy na kota",
    "ptak siedzi na drzewie", "ptaki śpiewają na drzewie", "kot patrzy na ptaki", "babcia gotuje obiad dla dzieci",
    "dzieci jedzą obiad przy stole", "babcia karmi ptaki w ogrodzie", "pies biega po ogrodzie", "babcia lubi ptaki",
]

def letters(word):  # "kot" -> ["▁k", "o", "t"]: the first letter carries the word-start mark
    return ["▁" + word[0]] + list(word[1:])

def merge(syms, a, b):
    out, i = [], 0
    while i < len(syms):
        if i + 1 < len(syms) and syms[i] == a and syms[i + 1] == b:
            out.append(a + b); i += 2
        else:
            out.append(syms[i]); i += 1
    return out

def train(sentences):
    freq = Counter(w for s in sentences for w in s.split())
    vocab = [(letters(w), f) for w, f in freq.items()]
    merges = []
    while True:
        pairs = Counter()
        for syms, f in vocab:
            for a, b in zip(syms, syms[1:]):
                pairs[(a, b)] += f
        (a, b), n = max(pairs.items(), key=lambda kv: kv[1])  # ties: the pair met first
        if n < 2:
            return merges
        merges.append((a, b, n))
        vocab = [(merge(syms, a, b), f) for syms, f in vocab]

def encode(sentence, merges, k):
    out = []
    for w in sentence.split():
        syms = letters(w)
        for a, b, _ in merges[:k]:
            syms = merge(syms, a, b)
        out += syms
    return out

merges = train(corpus)
start = sorted({t for s in corpus for t in encode(s, [], 0)})
a, b, n = merges[0]
print(f"start vocabulary: {len(start)} symbols; merges possible: {len(merges)}, first: {a} + {b} ({n}x)")

s = "pies je kość pod stołem"
fewest = next(k for k in range(len(merges) + 1) if len(encode(s, merges, k)) <= len(s.split()))
print(f"'{s}': {len(encode(s, merges, 0))} tokens at 0 merges, {len(encode(s, merges, 80))} at 80, fewest for <= words: {fewest}")

for k in [0, 80]:
    tokens = sum(len(encode(x, merges, k)) for x in corpus)
    E = np.zeros((len(start) + k, 16))  # one 16-number embedding per token of the vocabulary
    print(f"{k} merges: corpus {tokens} tokens, vocabulary {len(start) + k}, embedding matrix {E.size} numbers")

Wynik

start vocabulary: 43 symbols; merges possible: 92, first: i + e (36x)
'pies je kość pod stołem': 19 tokens at 0 merges, 5 at 80, fewest for <= words: 68
0 merges: corpus 620 tokens, vocabulary 43, embedding matrix 688 numbers
80 merges: corpus 195 tokens, vocabulary 123, embedding matrix 1968 numbers

Wynik jest ten sam co w labie, bo kod robi te same kroki w tej samej kolejności, łącznie z remisami. Uruchomione w numpy 2.1.3.

Dla praktyka
  • Licz długość tekstu, okno kontekstu i koszt w tokenach konkretnego tokenizatora, nie w słowach ani znakach: ten sam tekst w innym tokenizatorze ma inną długość.
  • Tokenizator jest częścią modelu: model uczony z jednym słownikiem nie zadziała z innym, bo numery tokenów znaczą co innego.
  • Zanim wybierzesz model do tekstów po polsku, policz tokeny na próbce własnych tekstów w jego tokenizatorze; ten sam tekst w różnych językach daje bardzo różną liczbę tokenów [3].
  • Gdy zadanie dotyczy pojedynczych liter (liczenie, odwracanie słowa), pamiętaj, że model dostaje tokeny, a nie litery: rozpisz słowo na litery w poleceniu albo daj mu narzędzie.

Więcej w Atlasie

Hasła encyklopedii o pojęciach z tej lekcji: