Pętla następnego tokena: model mentalny modeli językowych dużych rozmiarów przed zajrzeniem w agenty
Dowiedz się, jak działają tokeny, okna kontekstowe, próbkowanie oraz pętla generowania, za pomocą prostych przykładów w Pythonie uruchamianych offline, które wyjaśniają, dlaczego istnieją RAG, ReAct i LangGraph.
Inżynierowie, którzy pracują z modelami językowymi wielkich modeli za pośrednictwem frameworków takich jak LangChain, często nie potrafią stwierdzić, gdy coś się psuje, czy winny jest framework, API, którego używają, czy sam model. Rozwiązaniem jest jasny model mentalny: model językowy wielkiego modelu to funkcja przewidywania następnego tokena, która jest wywoływana wielokrotnie w pętli. Gdy ta koncepcja jest jasna, RAG to sposób na wybór elementów wprowadzanych do funkcji, ReAct to sposób na analizę wyników, a LangGraph to narzędzie do koordynacji wielu wywołań. Poniższe przykłady działają lokalnie z użyciem Pythona i jednej małej biblioteki – nie wymagają klucza API.
Generowanie to przewidywanie następnego tokena w pętli
Dla danej sekwencji tokenów model zwraca prawdopodobieństwo każdego możliwego następnego tokena. Wybierany jest jeden z nich, dodawany do sekwencji, a dłuższa sekwencja jest ponownie wprowadzana do procesu. Proces ten powtarza się, aż zostanie osiągnięty token zakończenia lub limit długości. Wyświetlanie wyników w formie strumienia polega po prostu na prezentowaniu tych tokenów jeden po drugim.
Wynikają z tego dwie konsekwencje:
- Nie ma żadnego planu. Każdy token zależy wyłącznie od tokenów poprzedzających go.
- Nie ma pamięci między wywołaniami. Każda prośba zaczyna się dokładnie od tokenów, które wysyłasz, i to jest powód istnienia generacji wzbogaconej o wydobycie informacji.
Tokeny to fragmenty podsłów, a nie słowa
Model widzi liczby całkowite, a nie tekst. Tokenizer mapuje łańcuchy znaków na ID tokenów i z powrotem. Często używane słowa zazwyczaj stają się jednym tokenem, natomiast rzadkie słowa i kod są dzielone na kilka tokenów. Praktyczna szacunkowa wartość: jeden token to około trzech czwartych słowa.
Fragment ten ładuje o200k_base, kodowanie rodziny gpt-4o, za pomocą tiktoken, koduje zdanie i wyświetla każdy ID wraz z odpowiadającym mu fragmenciem tekstu. Zauważ, że „LangGraph” staje się dwoma tokenami, a spacje na początku należą do następnego tokenu.
import tiktoken
# tokenizer of the gpt-4o model family
enc = tiktoken.get_encoding("o200k_base")
ids = enc.encode("LangGraph orchestrates agents.")
print(ids)
# [30741, 9922, 109873, 1381, 19297, 13]
for i in ids:
print(i, repr(enc.decode([i])))
# 'Lang' 'Graph' ' orchestr' 'ates' ' agents' '.'
Szkolenie i inferencja to oddzielne fazy
Szkolenie polega na dostosowywaniu wag poprzez przewidywanie kolejnych tokenów w dużych ilościach tekstu. Inferencja polega na uruchomieniu zamarzniętego modelu. Twój prompt nigdy nie zmienia wag, więc informacje, których brakuje modelowi, muszą zostać umieszczone w promptzie podczas inferencji. Właśnie to robi RAG.
Okno kontekstowe to cały świat modelu
Jedna wywołanie może przetworzyć tylko określoną liczbę tokenów, a ten limit, czyli okno kontekstowe, obejmuje zarówno prompt, jak i wygenerowany wynik. Wszystko, co znajduje się poza nim, nie istnieje w ramach tego wywołania. Rozmowa wydaje się ciągła tylko dlatego, że aplikacja za każdym razem przesyła wcześniejsze wiadomości, a RAG istnieje, ponieważ zbiory dokumentów nie mieszczą się w tym oknie, więc pobierane są jedynie kilka istotnych fragmentów.
Budżetowanie to prosta arytmetyka. Narzędzie liczy tokeny wejściowe za pomocą wspomnianego wcześniej tokenera i odejmuje je wraz z rezerwą na odpowiedź od okna o pojemności 128 000 tokenów, która jest podana dla gpt-4o-mini. Limity różnią się w zależności od modeli i wersji, dlatego sprawdź aktualną dokumentację swojego dostawcy.
def count_tokens(text: str) -> int:
return len(enc.encode(text))
prompt = "Summarize the attached design doc."
window = 128_000 # e.g. gpt-4o-mini
reserve = 1_000 # room for the answer
used = count_tokens(prompt)
print("left:", window - reserve - used)
Temperatura wpływa na sposób wyboru następnego tokena
Proces próbkowania wybiera jeden token z rozkładu modelu, a temperatura modyfikuje ten rozkład. Gdy jest bliska zera, niemal zawsze wygrywa najczęściej występujący token. Przy wartości 1,0 i wyższej mniej prawdopodobne tokeny mają realną szansę. Używaj temperatury 0 przy ekstrakcji, generowaniu strukturyzowanych wyników oraz wykorzystywaniu agentów do wywoływania narzędzi, a około 0,7–1,0 przy opracowywaniu pomysłów i burzy mózgów. Nasz przewodnik po temperaturze, top-k i top-p omawia pozostałe parametry kontrolne próbkowania.
Budowanie pętli w miniaturze
Następny przykład łączy rzeczywisty tokenizer z fałszywym modelem stworzonym na podstawie tabeli wyszukiwania. Jest mały, ale realizuje ten sam cykl co prawdziwy model językowy: kodowanie, przewidywanie, dodawanie i zatrzymanie.
Najpierw mały korpus jest tokenizowany, a następnie liczone są wszystkie sąsiednie pary identyfikatorów. Dla każdego tokena tabela przechowuje tylko jego najczęściej występującego następcę, co sprawia, że jest to przewidywacz typu „chciwy”, odpowiadający temperaturze zero.
CORPUS = (
"the agent calls the model. "
"the model returns a token. "
"the agent calls the tool. "
"the tool returns a result."
)
ids = enc.encode(CORPUS)
counts = {}
for a, b in zip(ids, ids[1:]):
counts.setdefault(a, {})
counts[a][b] = counts[a].get(b, 0) + 1
# greedy "model": token -> likeliest successor
table = {
a: max(s, key=s.get)
for a, s in counts.items()
}
Pętla koduje prompt, wyszukuje najbardziej prawdopodobnego następcę ostatniego tokena, dodaje go i wyświetla zdekodowany tekst. Brakujący następca zwraca None, co stanowi zamiennik tokenu zatrzymującego proces. Kluczowym elementem jest właśnie dodawanie – wyjście staje się wejściem.
seq = enc.encode("the agent calls")
for _ in range(3):
nxt = table.get(seq[-1])
if nxt is None: # our toy "stop token"
break
seq.append(nxt) # output becomes input
print(enc.decode(seq))
Sekwencja rośnie o jeden token na krok:
the agent calls the
the agent calls the model
the agent calls the model.
Jeden szczegół jest pouczający. W korpusie wyrażenie „the” jest następnie połączone z „model” i „tool” z równą częstotliwością, a max zachowuje to pierwsze, które napotka. Prawdziwe modele stale borykają się z takimi bliskimi wynikami, dlatego ustawienia próbkowania są istotne.
Rozpoczynanie pracy z przykładami
Zebierz fragmenty w jednym skrypcie, na przykład examples/part01_tokens.py, wraz z plikiem requirements.txt, który wymienia tiktoken. Skrypc ten wyświetla podziały tokenów, wykonuje obliczenia związane z budżetem i uruchamia prosty pętlę w ciągu około sekundy, poza siecią, gdy dane tokenizatora zostaną załadowane do pamięci podręcznej. Następnie zmień korpus i obserwuj zmiany w wynikach.
pip install -r requirements.txt
python examples/part01_tokens.py
Główne wnioski
- LLM to zastygła funkcja przekształcająca sekwencję tokenów w rozkład następnego tokena, uruchamiana w pętli sprzężenia zwrotnego.
Literatura pokrewna
- Zrozumienie agentów AI: cele, narzędzia, pamięć i pętla agenta — przystępne dla początkujących wyjaśnienie różnic między agentami AI a chatbotami, obejmujące podstawowe komponenty, pętlę decyzyjną, poziomy autonomii oraz praktyczne zastosowania w rzeczywistym świecie.
- LangGraph vs Pydantic AI: Dlaczego to model, a nie framework, decydował o dokładności — Kontrolowane badanie porównawcze przeprowadzone 160 razy pomiędzy LangGraph a Pydantic AI pokazuje identyczną dokładność wywoływania narzędzi i ujawnia, że wybór modelu oraz projekt testów mają znacznie większe znaczenie.
- Debugowanie małego GPT w PyTorch: Testy izolujące każdą awarię — Krok po kroku przeprowadzanie procesu debugowania modelu GPT na poziomie poszczególnych znaków w PyTorch, od identyfikacji tokenów i ich przesunięć po analizę gradientów, wartości NaN oraz plików checkpoint.
- Budżety tokenów vs. granice semantyczne: prawidłowe dzielenie kawałków RAG — Dowiedz się, w czym różni się tokenizacja od dzielenia na kawałki, jakie mają one znaczenie w procesie RAG oraz jak dzielić dokumenty według znaczenia, mierząc jednocześnie rozmiar za pomocą rzeczywistego tokenizatora.