Dekodowanie ogranicza pamięć: sufit ~59 tok/s na RTX 4090 i jak przebija go dekodowanie spekulatywne
Szybkość dekodowania jednego strumienia wyznacza przepustowość pamięci, nie FLOPS-y. Sufit przepustowości dla modelu 17 GB na 4090, matematyka dekodowania spekulatywnego i MTP, kompromis między głębokością szkicu a akceptacją i dlaczego sampler szkicu ma znaczenie.
Moja maszyna dekoduje do 82 tok/s. Na papierze jedna RTX 4090 nie przepuści tego modelu szybciej niż około 59 przebiegów na sekundę. Obie liczby są prawdziwe, a różnica między nimi to dekodowanie spekulatywne. Poniżej matematyka obu.
Sufit
Krok dekodowania w modelu dense czyta każdą wagę raz, żeby wyprodukować jeden token dla jednej sekwencji. Na każdą odczytaną wagę przypadają mniej więcej dwie operacje zmiennoprzecinkowe, mnożenie i dodawanie. GPU potrafi wykonać kilkadziesiąt razy więcej obliczeń na bajt, więc krok ogranicza tempo, w jakim wagi wypływają z VRAM:
przepustowość pamięci RTX 4090 ≈ 1008 GB/s
model 27B w Q4_K_M ≈ 17 GB wag
sufit, jeden token na przebieg ≈ 1008 / 17 ≈ 59 przebiegów/s
Obliczenia nie są nawet blisko limitu. 27 mld parametrów × 2 FLOP to 54 GFLOP na token; przy 59 tokenach na sekundę to około 3 TFLOP/s, kilka procent możliwości karty. Dekodowanie spędza czas na czekaniu na pamięć.
Przy długim kontekście do wag w każdym przebiegu dochodzi cache KV, bo atencja czyta cały cache dla każdego nowego tokenu. Przy 262K z 4-bitowym cache TBQ4 to kolejne 4,4 GB na przebieg:
wagi + pełny cache TBQ4 przy 262K ≈ 17 + 4,4 = 21,4 GB na przebieg
sufit ≈ 1008 / 21,4 ≈ 47 przebiegów/s
W FP16 cache miałby 17 GB, tyle co wagi, a sufit spadłby do około 29 przebiegów na sekundę. Mniejszy cache to optymalizacja szybkości, nie tylko pamięci. (Rozmiary są w notatce o cache KV.)
Masz więc dwie dźwignie: czytać mniej bajtów na przebieg (kwantyzacja) albo wyciągać z każdego przebiegu więcej niż jeden token.
Dekodowanie spekulatywne
Tani model szkicujący proponuje k tokenów. Duży model ocenia je wszystkie w jednym przebiegu. Ponieważ przebieg ogranicza pamięć, sprawdzenie k tokenów kosztuje mniej więcej tyle, co wygenerowanie jednego. Silnik zatrzymuje najdłuższy prefiks, z którym duży model się zgadza. Przy pierwszej niezgodności bierze własny token dużego modelu, więc każdy przebieg daje co najmniej jeden token.
Przy próbkowaniu „zgadza się” oznacza próbkowanie z odrzuceniem: token szkicu x jest przyjmowany z prawdopodobieństwem min(1, p(x) / q(x)), gdzie p to rozkład dużego modelu, a q szkicującego. Dzięki temu rozkład wyjścia jest dokładnie rozkładem dużego modelu. Spekulacja zmienia szybkość, a nie tekst, który byś dostał.
Jeśli każdy token szkicu jest przyjmowany z prawdopodobieństwem α, niezależnie od pozostałych, oczekiwana liczba tokenów na przebieg wynosi (Leviathan i in., 2023):
E[tokeny na przebieg] = (1 − α^(k+1)) / (1 − α)
| Akceptacja α | k = 1 | k = 3 | k = 5 |
|---|---|---|---|
| 0,35 | 1,35 | 1,52 | 1,54 |
| 0,5 | 1,50 | 1,88 | 1,97 |
| 0,6 | 1,60 | 2,18 | 2,38 |
| 0,7 | 1,70 | 2,53 | 2,94 |
| 0,8 | 1,80 | 2,95 | 3,69 |
| 0,9 | 1,90 | 3,44 | 4,69 |
Tabelę czytaj wierszami:
- Wysoka akceptacja nagradza głębokie szkice: przy 0,9 pięć tokenów szkicu daje 4,7 tokenu na przebieg.
- Niska akceptacja sprawia, że głębokość prawie nic nie daje: przy 0,35 przejście z trzech do pięciu tokenów szkicu dodaje 0,02 tokenu na przebieg, a każdy dodatkowy token wciąż kosztuje obliczenia i poszerza partię do weryfikacji.
Pełne przyspieszenie uwzględnia też koszt szkicowania. Jeśli krok szkicu kosztuje c przebiegu dużego modelu:
przyspieszenie = (1 − α^(k+1)) / ((1 − α) · (k·c + 1))
Dlatego tani model szkicujący jest tak samo ważny jak dobry.
MTP: szkicowanie bez modelu szkicującego
Osobny model szkicujący potrzebuje własnej pamięci VRAM i własnego cache KV. Głowice predykcji wielu tokenów (MTP) tego unikają. To małe dodatkowe warstwy, trenowane razem z modelem, które przewidują tokeny t+1, t+2… ze stanu ukrytego samego trzonu. Spopularyzował je DeepSeek-V3; Qwen3.5 i 3.6 mają je w standardzie. Krok szkicu to ułamek przebiegu, więc c jest małe.
Haczyk polega na tym, że jakość szkicu ogranicza jakość głowic. Nie podmienisz szkicującego na lepszego bez ponownego wytrenowania głowic, więc akceptacja jest cechą wybranego modelu w tym samym stopniu co silnika.
Sprawdzenie spójności na moich liczbach
82 tok/s przy suficie około 59 przebiegów na sekundę oznacza co najmniej 1,4 tokenu na przebieg, nawet gdyby każdy przebieg szedł z teoretyczną prędkością. Prawdziwe przebiegi są wolniejsze niż sufit, więc faktyczna liczba jest wyższa. Przy 1,8–2 tokenach na przebieg i trzech tokenach szkicu wzór daje akceptację około 0,47–0,54. To mieści się w zakresie 50–68%, który README forka podaje dla głowic tego modelu.
To rachunek na serwetce, a nie profilowanie, ale liczby się zgadzają. Większość pozostałego zapasu leży po stronie modelu, w ponownym wytrenowaniu głowic MTP, a nie w jądrze.
Dwie rzeczy, które wynikają z matematyki
Dopasuj głębokość do akceptacji. Tabela pokazuje, że dodatkowe tokeny szkicu się marnują, gdy akceptacja spada, a spada na trudnych fragmentach tekstu. --spec-adapt w moim forku ogranicza głębokość szkicu, gdy zmierzona akceptacja spada poniżej 35%, czyli w wierszu, w którym dodatkowe tokeny szkicu prawie nic nie dają.
Dopasuj sampler szkicu do samplera modelu docelowego. Oczekiwana akceptacja to Σ min(p(x), q(x)) po wszystkich tokenach, czyli część wspólna obu rozkładów. Zachłanny szkicujący stawia całą masę na jeden token, więc akceptacja spada do p(argmax). Przy modelu docelowym próbkującym z temperaturą 0,6 to często dużo mniej, niż dostałby szkicujący próbkujący z podobnego rozkładu. To właśnie zmienia przełączenie samplera szkicu MTP w moim forku z zachłannego na stochastyczny. Niezależnie od tego produkcyjne ustawienia samplera (temperatura 0,6, top-p 0,95, min-p 0,05) podniosły dekodowanie z 62 do 82 tok/s i zatrzymały pętle wywołań narzędzi.
Zasady, których się trzymam
- Najpierw policz sufit: przepustowość ÷ bajty na przebieg. Jeśli jesteś blisko niego, strojenie jądra Cię nie uratuje. Uratuje Cię mniej bajtów albo więcej tokenów na przebieg.
- Partie (batching) zwiększają przepustowość, nie zmniejszają opóźnienia. N sekwencji dzieli jeden odczyt wag, więc łączna liczba tokenów na sekundę serwera może się mnożyć. Strumień pojedynczego użytkownika nadal ogranicza sufit z góry.
- Głębokość szkicu dobieraj ze zmierzonej akceptacji i mierz dalej. Zmienia się z tekstem, samplerem i modelem.
- Długi kontekst wszystko pogarsza. Odczyt cache rośnie z kontekstem. Skwantyzuj cache, zanim kupisz więcej przepustowości.
Źródła: Leviathan, Kalman i Matias, Fast Inference from Transformers via Speculative Decoding (ICML 2023), wzory na akceptację i przyspieszenie. Przepustowość RTX 4090 to specyfikacja NVIDIA (GDDR6X 21 Gbps na szynie 384-bitowej). Liczby silnika i próg --spec-adapt pochodzą z mojego forka.