Token. Chwila. Token. Chwila. Lokalny model wypluwa tekst jak stara drukarka igłowa, literka po literce, a ty gapisz się w kursor, jakby to on dyktował tempo twojego życia. I prawie każdy jęk o prędkości sprowadza się do jednego: „mam ładne 4 tokeny na sekundę i cierpliwość mnicha". I człowiek zaczyna kombinować — może da się to jakoś oszukać?

Bo pod spodem dzieje się rzecz z pozoru absurdalnie rozrzutna. Żeby dołożyć jeden token, model o siedmiu czy siedemdziesięciu miliardach parametrów musi przepchnąć całe swoje wnętrze — pełny przebieg naprzód — po czym dokłada jedną literkę i zaczyna od nowa. Cały ten kolos rozgrzewa się do jednego słowa, potem znowu, i znowu. Trochę jak odpalać silnik ciężarówki za każdym razem, gdy chcesz przejechać metr.

Skąd w ogóle ten pomysł

W 2022 roku trójka badaczy z Google’a — Leviathan, Kalman i Matias — zadała pytanie, które brzmi jak sabotaż: a co, jeśli większość kolejnych tokenów jest tak oczywista, że nie trzeba do nich całego dużego modelu? Że „w dniu dzisiejszym" po słowie „w" i „dniu" da się zgadnąć czymś dużo tańszym? Opisali to w pracy Fast Inference from Transformers via Speculative Decoding i pokazali coś, co brzmi zbyt pięknie: 2–3 razy szybsza generacja na modelach klasy T5-XXL, bez douczania czegokolwiek i bez zmiany rozkładu wyjściowego — czyli model gada dokładnie to samo, co gadałby normalnie, tylko szybciej.1 Papier poszedł na ICML 2023 jako prezentacja ustna, więc nie była to notka na marginesie.

Dwa miesiące później niezależnie tę samą ideę opisała ekipa z DeepMind — Chen, Borgeaud, Irving, Lespiau, Sifre i Jumper — pod nazwą speculative sampling, z lekko innym schematem matematycznym i przyspieszeniem 2–2,5x na Chinchilli (70B) w środowisku rozproszonym.2 Dwa zespoły, ten sam pomysł, ta sama gwarancja: przyspieszamy, jakości nie ruszamy. Kiedy dwie niezależne grupy trafiają w to samo, to zwykle znak, że pod spodem siedzi coś prawdziwego.

Jak to działa krok po kroku

Dobra, ale jak to działa naprawdę? Bierzesz dwa modele. Jeden duży — ten, którego odpowiedzi chcesz — nazwijmy go docelowym (target). I jeden mały, szybki — szkicownik (draft) — najlepiej tej samej rodziny, żeby „myślał" podobnie. I teraz taniec w trzech krokach:

  1. Szkicownik zgaduje. Mały model generuje autoregresywnie kilka kolejnych tokenów — powiedzmy K sztuk. Robi to szybko, bo jest mały. To są propozycje, brudnopis.
  2. Duży weryfikuje — jednym przebiegiem. I tu jest cały myk: duży model dostaje te K proponowanych tokenów naraz i sprawdza je w jednym przebiegu naprzód, wsadowo. Nie K osobnych przejazdów ciężarówką — jeden.
  3. Akceptuj albo popraw. Tam, gdzie duży model zgadza się ze szkicownikiem, tokeny wpadają za darmo. Przy pierwszym niezgodnym — odrzucamy resztę, duży dokłada swój token i cała zabawa rusza od nowa.

Jeśli szkicownik trafił w pięć tokenów z rzędu, to policzyłeś je jednym przebiegiem dużego modelu zamiast pięciu. Jeśli spudłował od razu — koszt jest praktycznie taki, jak przy zwykłym dekodowaniu. Downside prawie żaden, upside spory. Ta sama logika weryfikacji „licz wsad, nie sekwencję" siedzi zresztą u podstaw tego, czemu KV-cache w ogóle się opłaca — rozgryzaliśmy to tutaj.

Czemu to nie psuje jakości

W tym miejscu każdy rozsądny człowiek zapala czerwoną lampkę. „Mały model zgaduje za dużego? To brzmi jak destylacja na skróty, na pewno wychodzi gorsza jakość." Otóż nie — i to jest najładniejsza część całej sztuczki.

To nie jest kwantyzacja ani destylacja. Wag dużego modelu nikt nie dotyka, jego dokładność zostaje nietknięta. Sekret siedzi w tym, jak akceptowane są tokeny — używa się zmodyfikowanego rejection sampling (próbkowania odrzucającego). Token zaproponowany przez szkicownik przyjmujemy z prawdopodobieństwem min(1, p_target(x) / p_draft(x)) — czyli: jeśli duży model uważa dany token za co najmniej tak samo prawdopodobny jak szkicownik, bierzemy go w ciemno. Jeśli mniej — akceptujemy proporcjonalnie, a przy odrzuceniu dobieramy nowy token z rozkładu resztkowego (residual), tak dobranego, żeby wszystko się matematycznie spięło.1

Efekt? Łączny rozkład tokenów na wyjściu jest identyczny z tym, co dałoby zwykłe, niespekulacyjne próbkowanie samego dużego modelu. Nie „prawie taki sam", nie „w granicach błędu" — dokładnie taki sam, z dowodem.

Dlaczego to ważne dla lokalnego LLM

To jest ten rzadki obiad, za który nikt nie płaci. Speculative decoding nie jest kompromisem „szybciej za cenę jakości", jak kwantyzacja czy przycinanie kontekstu. Rozkład wyjściowy zostaje matematycznie nienaruszony — dostajesz te same odpowiedzi co z gołego dużego modelu, tylko prędzej. Jedyne, czym płacisz, to trochę VRAM-u na drugi, mały model w pamięci i odrobina narzutu na weryfikację.

Skąd się bierze przyspieszenie

Skoro nic nie tniemy, to gdzie ten darmowy obiad się chowa? W wąskim gardle, o którym lokalny LLM-owiec i tak wie w kościach: inferencja jednego tokena naraz jest ograniczona pamięcią (bandwidth-bound), nie liczeniem. Karta większość czasu nie liczy, tylko czeka, aż wagi modelu przemaszerują z VRAM-u do rdzeni. Przepchnięcie przez ten sam kanał jednego tokena a pięciu naraz kosztuje prawie tyle samo — bo i tak głównie czekasz na pamięć, a nie na arytmetykę.

Stąd cały zysk: policzenie K+1 tokenów w jednym wsadowym przebiegu jest znacznie tańsze niż K+1 osobnych, sekwencyjnych kroków. Im więcej propozycji szkicownika duży model akceptuje, tym mocniej wygrywasz.3 I tu ważne zastrzeżenie, bo to nie jest magiczne pokrętło „więcej = szybciej": przyspieszenie nie rośnie liniowo z liczbą draftowanych tokenów. Wszystko wisi na współczynniku akceptacji — jak często szkicownik trafia. Każąc mu zgadywać dwadzieścia tokenów naprzód przy kiepskiej trafności, generujesz górę brudnopisu, który duży model i tak wyrzuci do kosza. Bywa, że wtedy jest wolniej niż bez całej zabawy.

Zrób to sam w llama.cpp

Teoria teorią, ale llama.cpp (repo ggml-org/llama.cpp) obsługuje to od dawna — i w llama-server, i w osobnym narzędziu llama-speculative-simple. Cały wjazd na klasyczny wariant to jedna flaga: --spec-draft-model (krócej -md), która wskazuje plik GGUF modelu szkicowego.4 Bierzesz duży model, dokładasz mały z tej samej rodziny i lecisz:

llama-server -m qwen3-14b-q4_k_m.gguf \
  -md qwen3-0.6b-q4_k_m.gguf \
  --spec-draft-n-max 4 \
  -ngl all -c 8192 -fa on

--spec-draft-n-max N (domyślnie 3) ustawia maksymalną liczbę tokenów szkicowanych na krok, a --spec-draft-n-min N (domyślnie 0) dolną granicę.4 Progi akceptacji stroisz przez --spec-draft-p-min (domyślnie 0.00) i --spec-draft-p-split (domyślnie 0.10). Do reszty flag llama-server-ngl, -c, -fa i spółka — wracaliśmy osobno tutaj.

Strategię wybiera --spec-type, i tu robi się ciekawie, bo klasyczny draft model to dopiero początek:

--spec-typeCo robiPotrzebny osobny model draft?
draft-simplezwykły mały model szkicowytak (GGUF przez -md)
draft-eagle3jedna warstwa czytająca stany ukryte dużego modelutak (lekka głowica EAGLE-3)
draft-dflashgeneruje cały blok szkicu jednym przebiegiem (block diffusion)tak
ngram-simplezgaduje z historii wygenerowanego tekstunie
ngram-cachewariant n-gramowy z podręczną pamięciąnie

Warianty n-gramowe to osobny gatunek: nie potrzebują żadnego drugiego modelu, tylko zgadują kolejne tokeny na podstawie tego, co już się w tekście pojawiło (świetne, gdy model dużo cytuje sam siebie albo klepie strukturę). Mechanizm weryfikacji jest ten sam — po prostu brudnopis powstaje inaczej.3 A na drugim biegunie EAGLE-3 (draft-eagle3) to lekka, jednotransformerowa głowica, która podgląda stany ukryte dużego modelu i przez to trafia częściej niż samodzielny mały model tej samej wagi. Uruchomienie z dokumentacji wygląda tak:

llama-server -m Qwen3-4B.gguf \
  -md Qwen3-4B-eagle3.gguf \
  --spec-type draft-eagle3

Jedna pułapka na koniec, żebyś nie stracił wieczora na kopiowanie starych poradników: dawne flagi --draft, --draft-max, --draft-min zostały usunięte. Aktualna składnia to --spec-draft-n-max i pochodne --spec-*.4 Jak przepiszesz przykład sprzed roku i llama-server prycha, że nie zna flagi — to nie ty zwariowałeś, to repo poszło naprzód.

Najładniejsze w tym wszystkim jest to, że speculative decoding nie jest żadnym oszustwem na jakości — jest raczej jak dobry sekretarz. Ktoś szybki i tani pisze pod dyktando pierwszy szkic, przewidując, dokąd zmierza zdanie, a szef tylko przelatuje wzrokiem i skreśla to, co się nie zgadza. Reszta zostaje, jak stała. Szef odpowiada za każde słowo tak samo jak wtedy, gdy pisał sam — tyle że nie musi już maczać pióra w kałamarzu przy każdej literce.

I to jest ten moment, w którym twoja drukarka igłowa spod biurka nagle łapie zryw — te same słowa, ten sam rozkład, ta sama karta. Tylko kursor przestaje w końcu dyktować ci tempo.

Przypisy · źródła


  1. Leviathan, Kalman, Matias, Fast Inference from Transformers via Speculative Decoding, ICML 2023 (oral) — definicja metody, gwarancja identycznego rozkładu wyjściowego przez zmodyfikowany rejection sampling, przyspieszenie 2–3x na T5-XXL. arxiv.org/abs/2211.17192↩︎ ↩︎

  2. Chen, Borgeaud, Irving, Lespiau, Sifre, Jumper (DeepMind), Accelerating Large Language Model Decoding with Speculative Sampling — niezależne sformułowanie, przyspieszenie 2–2,5x na Chinchilli 70B w środowisku rozproszonym. arxiv.org/abs/2302.01318↩︎

  3. llama.cpp, mechanizm weryfikacji wsadowej i lista strategii (draft-eagle3, draft-dflash, warianty ngram-*), przykład uruchomienia z EAGLE-3, docs/speculative.md↩︎ ↩︎

  4. llama.cpp, dokładne nazwy i domyślne wartości flag --spec-draft-model/-md, --spec-type, --spec-draft-n-max/n-min, --spec-draft-p-min/p-split oraz informacja o usunięciu dawnych --draft/--draft-max/--draft-min, tools/server/README.md↩︎ ↩︎ ↩︎