Inference — jak powstaje odpowiedź, token po tokenie

przez Łukasz | sie 17, 2026

W pierwszym artykule kod z aneksu pokazywał rozkład prawdopodobieństwa: pięciu kandydatów na jeden token, każdy ze swoją liczbą. Zostawiliśmy go tam bez ciągu dalszego.

Teraz pytanie, które się wtedy nasuwało: skoro model zwraca rozkład, to kto właściwie wybiera jedno słowo — i dlaczego przy tym samym pytaniu dwa razy z rzędu dostajesz inną odpowiedź.

Dwie fazy, dwie różne charakterystyki

Inference to użycie gotowego modelu. Wagi są zamrożone, model niczego się nie uczy, a przebieg dzieli się na dwie części, które warto rozróżniać, bo zachowują się zupełnie inaczej.

Faza pierwsza: przetworzenie promptu. Cały twój wsad wchodzi naraz i jest liczony równolegle. Model przepuszcza wszystkie tokeny przez wszystkie warstwy jednocześnie — to obciąża sprzęt, ale dzieje się szybko, bo nic nie musi czekać na nic innego.

Faza druga: generowanie. Tu równoległość znika. Dziesiątego tokenu nie da się wyliczyć przed dziewiątym, bo dziewiąty musi najpierw wejść z powrotem na wejście. Każde kolejne słowo to osobne przejście przez cały model.

Stąd bierze się rzecz, która zaskakuje przy pierwszym liczeniu kosztów: długa odpowiedź kosztuje czasowo znacznie więcej niż długi prompt. Możesz wrzucić modelowi dziesięć stron i dostać pierwszy token szybko. Poproś o dwie strony odpowiedzi, a poczekasz — nawet jeśli pytanie miało jedno zdanie.

To samo tłumaczy, dlaczego cenniki rozdzielają stawki za tokeny wejściowe i wyjściowe. To nie jest arbitralny podział, tylko odbicie tego, że te dwie fazy naprawdę kosztują różnie.

Kto wybiera token

Model nie wybiera. Zwraca rozkład — liczbę dla każdej pozycji w słowniku. Wyboru dokonuje osobna warstwa i to ona nazywa się samplingiem.

Najprostszy wariant bierze zawsze kandydata o najwyższym prawdopodobieństwie. Brzmi rozsądnie i daje teksty zaskakująco kiepskie: powtarzalne, monotonne, wpadające w pętle. Język, w którym za każdym razem pada najbardziej oczekiwane słowo, przestaje brzmieć jak język.

Dlatego w praktyce wybór jest losowany spośród najlepszych kandydatów — a to, jak szeroko, regulują trzy parametry.

Temperatura skaluje cały rozkład. Niska wyostrza go, więc faworyt wygrywa niemal zawsze. Wysoka spłaszcza, dając szansę kandydatom mniej oczywistym.

Top-p ogranicza pulę do najmniejszej grupy kandydatów, których łączne prawdopodobieństwo przekracza zadany próg. Pula jest zmienna: gdy model jest pewny, zostaje w niej jeden token; gdy waha się między dziesiątkami, zostaje ich wiele.

Top-k ogranicza pulę do ustalonej liczby najlepszych, niezależnie od tego, jak wygląda rozkład.

Wszystkie trzy działają na tym samym rozkładzie i zwykle stosuje się je razem.

Czego sampling nie robi

Tu jest miejsce na nieporozumienie, które kosztuje najwięcej czasu przy diagnozowaniu problemów.

Sampling nie dodaje modelowi wiedzy. Przesuwa wybór wewnątrz rozkładu, który już powstał. Jeśli model nie ma oparcia w danych, żadne ustawienie tego nie naprawi.

Temperatura zero nie daje prawdy, tylko powtarzalność. Model będzie konsekwentnie zwracał tę samą odpowiedź — również wtedy, gdy jest ona błędna. Obniżanie temperatury bywa traktowane jako środek przeciwko halucynacjom i jest to nieporozumienie: dostajesz ten sam błąd za każdym razem zamiast różnych błędów.

Zero nie znaczy też deterministycznie w każdych warunkach. Ten sam prompt przy tych samych ustawieniach potrafi dać nieco inny wynik, gdy zmieni się wersja modelu, infrastruktura albo sposób grupowania zapytań po stronie dostawcy. Determinizm dotyczy wyboru z rozkładu, nie całego stosu wykonawczego.

Jak dobierać ustawienia

Reguła jest prosta i wynika wprost z tego, do czego służy wynik.

Wynik ma być przetworzony przez maszynę — ekstrakcja danych, klasyfikacja, wybór narzędzia, generowanie struktury: ustawienia niskolosowe. Zależy ci na powtarzalności i na tym, żeby format nie odjeżdżał.

Wynik ma czytać człowiek — opisy, treści, warianty tekstu: ustawienia wyższe. Wariantywność jest tu zaletą.

I rzecz praktyczna, o której łatwo zapomnieć: wartości domyślne różnią się między dostawcami, a nawet między interfejsami tego samego modelu. Ten sam model wywołany z playgroundu i z API potrafi zachowywać się inaczej wyłącznie z tego powodu. Przy migracji między modelami warto ustawiać te parametry jawnie, zamiast polegać na tym, co przyjdzie z fabryki.

Do tego dochodzą dwa ograniczniki, które nie są samplingiem, ale należą do tej samej warstwy: sekwencje zatrzymujące oraz limit tokenów wyjściowych. Ucięta w pół zdania odpowiedź to prawie zawsze ten drugi.

Dlaczego to ma znaczenie przy agencie

Wszystko powyżej dotyczy jednego wywołania. Agent wykonuje ich wiele na jedno zadanie użytkownika i tam konsekwencje się mnożą.

Koszt i opóźnienie mnożą się przez liczbę kroków. Przy każdym obrocie pętli cały kontekst idzie do modelu od nowa, razem z historią dotychczasowych kroków. To rośnie szybciej, niż podpowiada intuicja.

Losowość na jednym kroku zmienia wszystkie następne. Inny wybór narzędzia w drugim kroku prowadzi do zupełnie innej trajektorii w piątym. Dlatego pojedynczy udany przebieg testowy nie dowodzi niczego — o czym więcej w artykule o ewaluacji trajektorii. Ewaluacja agenta wymaga powtórzeń, nie jednego sprawdzenia.

Kroki maszynowe i kroki dla człowieka warto ustawiać osobno. W jednym agencie mogą współistnieć wywołania z różnymi parametrami: niskolosowe tam, gdzie model wybiera narzędzie i buduje argumenty, wyższe tam, gdzie pisze podsumowanie dla użytkownika. Traktowanie całego agenta jednym ustawieniem to niepotrzebny kompromis.

Jeśli wynik ma być parsowany, warto wymusić strukturę zamiast liczyć na dyscyplinę modelu — o tym jest artykuł o structured output z serii o context engineeringu.

Jak to sprawdzić u siebie

Poniższy kod uruchamia to samo zapytanie po pięć razy przy dwóch ustawieniach: bez losowości i z nią.

python
from transformers import pipeline

gen = pipeline("text-generation", model="Qwen/Qwen2.5-0.5B-Instruct")
prompt = "Opisz jednym zdaniem, jak wygląda poranek w górach."

for etykieta, kwargs in [
    ("bez losowości", dict(do_sample=False)),
    ("temperatura 1.0", dict(do_sample=True, temperature=1.0, top_p=0.95)),
]:
    print(f"\n=== {etykieta} ===")
    for _ in range(5):
        wynik = gen(prompt, max_new_tokens=40, **kwargs)[0]["generated_text"]
        print("-", wynik[len(prompt):].strip()[:90])

Pierwsza piątka będzie pięcioma kopiami tego samego zdania. Druga — pięcioma różnymi zdaniami, czasem lepszymi, czasem dziwniejszymi.

Ciekawsze jest jednak drugie ćwiczenie. Zamień prompt na coś, co ma jedną poprawną odpowiedź, ale jest na granicy wiedzy modelu — jakiś szczegół, o którym mógł czytać rzadko. Uruchom pięć razy z losowością. Jeśli dostaniesz pięć różnych odpowiedzi, właśnie zobaczyłeś, jak wygląda niepewność modelu od zewnątrz, bez zaglądania w rozkład.

To jest, nawiasem mówiąc, tania metoda wykrywania halucynacji: zapytaj kilka razy i sprawdź, czy odpowiedzi są zgodne. Nie daje pewności, ale rozbieżność jest sygnałem, że model zgaduje.

Co dalej

Wiemy już, jak powstaje odpowiedź. Wiemy też, że rozkład prawdopodobieństwa istnieje zawsze — również wtedy, gdy w danych nie było żadnej odpowiedzi. Czas zebrać to w całość i zająć się zjawiskiem, które wynika z tego bezpośrednio, a bywa traktowane jak usterka.

O tym jest artykuł siódmy: Halucynacje nie są błędem.

Agent kończy, ale zadania nie wykonał

Agent kończy, ale zadania nie wykonał

Objawy Agent odpowiada: „Przygotowałem i wysłałem potwierdzenie do klienta." Potwierdzenie nie zostało wysłane. Albo: „Zaktualizowałem wszystkie rekordy" — zaktualizował trzy z dwunastu. Albo: „Nie znalazłem żadnych zamówień" — bo narzędzie zwróciło błąd, którego nikt...

Agent zrobił coś, o co nikt nie prosił

Agent zrobił coś, o co nikt nie prosił

Objawy Operacja, której nikt nie zlecił. Dane wysłane pod adres, którego nie ma w żadnej konfiguracji. Rekord zmieniony poza zakresem zadania. Odpowiedź, z której wynika, że agent dostał instrukcje od kogoś innego niż ty. Cecha wspólna: technicznie wszystko zadziałało...

Agent zrobił to samo dwa razy

Agent zrobił to samo dwa razy

Objawy Ten syndrom różni się od pozostałych jedną rzeczą: objaw widzi klient, nie ty. Trzy identyczne wiadomości w skrzynce. Dwa zamówienia zamiast jednego. Podwójne obciążenie. Duplikaty rekordów, które ktoś zauważa tydzień później. W twoim dzienniku wszystko wygląda...

Agent gubi wątek w długim zadaniu

Agent gubi wątek w długim zadaniu

Objawy Pierwsze kroki idą wzorowo. Po kilkunastu agent zaczyna się rozjeżdżać. Przestaje przestrzegać reguły z promptu systemowego, której trzymał się na początku. Zmienia format odpowiedzi w połowie zadania. Wraca do czegoś, co już ustalił, i ustala to inaczej....

Agent podaje dane, których nie ma

Agent podaje dane, których nie ma

Objawy Numer zamówienia w idealnym formacie, którego nie ma w bazie. Kwota, której w dokumencie nie ma. Nazwa pola API, które nigdy nie istniało. Cytat z regulaminu, brzmiący dokładnie jak reszta regulaminu i w nim nieobecny. Cecha wspólna wszystkich tych przypadków:...

Działał wczoraj, dziś nie działa

Działał wczoraj, dziś nie działa

Objawy Nic się nie wywala. Nie ma wyjątków, nie ma timeoutów, dziennik wygląda tak samo jak zawsze. Po prostu odpowiedzi są gorsze. Postacie, w jakich to się objawia: Jakość. Agent zaczyna pomijać kroki, które wcześniej wykonywał, albo odpowiada ogólniej. Format....

Agent wybiera złe narzędzie

Agent wybiera złe narzędzie

Objawy Agent odpowiada nie na to pytanie. Pobiera listę zamówień, gdy pytano o jedno konkretne. Odpowiada z pamięci, choć miał sprawdzić w bazie. Albo sięga po właściwe narzędzie i wpisuje w argumenty coś, czego nie da się użyć. Sygnał, który odróżnia ten syndrom od...

Agent kręci się w kółko

Agent kręci się w kółko

Objawy Agent wykonuje kolejne obroty pętli, nie zbliżając się do zakończenia zadania. W dzienniku widać jedną z trzech postaci. Powtórzenie. To samo narzędzie, te same argumenty, raz za razem — czasem dziesiątki razy pod rząd. Oscylacja. Agent wywołuje na przemian dwa...