Strona główna / Rozdział 3 · Jak model pisze i ile to kosztuje
Ostatnia zmiana · 6 min czytania
Pętla generowania i KV cache
Generowanie to pętla: jeden przebieg modelu daje jeden token, który trafia na koniec wejścia. KV cache trzyma klucze i wartości attention wszystkich wcześniejszych tokenów, więc każdy krok liczy tylko nowy token, a ceną jest pamięć GPU, która ogranicza długość kontekstu i liczbę równoległych rozmów.
Po ludzkuPiszesz list i przy każdym nowym słowie zerkasz do notatek z tego, co już napisałeś. Nie czytasz listu od nowa, ale notatki przeglądasz za każdym razem. KV cache to te notatki. Leżą na biurku, czyli w pamięci karty graficznej, i im dłuższy list, tym więcej miejsca zajmują.
Przejdź krok po kroku z cache’em i bez. Porównaj, ile tokenów liczy każdy krok
Co jest w KV cache’u i czemu to działa
- W każdej warstwie attention token ma trzy rzuty swojego stanu: zapytanie Q, klucz K i wartość V (temat „Attention”). Nowy token porównuje swoje Q z kluczami wszystkich wcześniejszych i bierze ważoną sumę ich wartości. KV cache to K i V wszystkich dotychczasowych tokenów, osobno dla każdej warstwy i głowy KV.
- Q nie trafia do cache’u. Zapytanie tokena jest potrzebne tylko w kroku, w którym ten token jest ostatni. Późniejsze tokeny czytają już tylko jego K i V.
- Cache można trzymać dzięki masce przyczynowej: K i V tokena i zależą tylko od tokenów 0…i, więc nowe tokeny ich nie zmieniają. To memoizacja. Bez niej krok n liczyłby od nowa wszystkie n tokenów, więc wygenerowanie N tokenów kosztowałoby pracę projekcji i MLP rosnącą z N² zamiast z N (a koszt attention z N³ zamiast z N²).
- Działa to też w drugą stronę: K i V tokena zależą od wszystkich tokenów przed nim i od jego pozycji (RoPE). Zmiana jednego tokena unieważnia cache wszystkiego za nim. Dlatego dostawca może ponownie użyć cache’u tylko dla identycznego prefiksu, a agent historię tylko dopisuje (temat „Prompt caching”).
Ile pamięci zajmuje KV cache
- Na token: 2 (K i V) × warstwy × głowy KV × wymiar głowy × bajty. Llama 3.1 70B w FP16: 2 × 80 × 8 × 128 × 2 B = 327 680 B, ok. 0,33 MB. Pełne 128 tys. tokenów to 43 GB na jedną rozmowę, przy 141 GB samych wag.
- Kształt cache’u ustala architektura. GQA daje jedną parę K, V na grupę głów zapytań (MQA to skrajność: jedna para na warstwę). Llama 3 70B ma 8 głów KV na 64 głowy Q. Bez GQA cache 128 tys. tokenów zająłby 344 GB. MLA z DeepSeek zapisuje jeden skompresowany wektor na warstwę: w DeepSeek-V3 to 61 × 576 × 2 B ≈ 70 KB na token, prawie 5 razy mniej niż Llama 70B, choć model ma 671 mld parametrów.
- Okno przesuwne: część warstw widzi tylko ostatnie W tokenów, więc ich cache nie rośnie ponad W. Gemma 3 ma pięć takich warstw (W = 1024) na jedną pełną, co przy 128 tys. tokenów daje ok. 17% cache’u tego samego modelu z samymi pełnymi warstwami. Modele hybrydowe zastępują część warstw attention warstwami SSM o stałym rozmiarze stanu.
- Przy serwowaniu gotowego modelu da się jeszcze zmienić precyzję: cache w FP8 zajmuje połowę pamięci. To przybliżenie, więc sprawdź jakość na własnych ewaluacjach.
Wybierz model, kontekst i liczbę rozmów. Zobacz, ile kart H100 zajmuje sam cache
Wzór: 2 × warstwy × głowy KV × wymiar głowy × bajty × tokeny. MLA zapisuje zamiast K i V jeden wektor 576 liczb na warstwę, więc bez mnożenia przez 2. W Gemmie 3 co szósta warstwa jest pełna, a pozostałe trzymają najwyżej 1024 ostatnie tokeny. Bez wag modelu i narzutów serwera. Suwak kończy się na 128 tys. tokenów, najdłuższym kontekście tych modeli.
Prefill i decode
- Prefill przetwarza cały prompt w jednym przebiegu. Mnoży wagi przez macierz wszystkich tokenów naraz, więc każdy przeczytany bajt wag służy setkom tokenów i karta liczy pełną mocą. Tu powstaje cache promptu. Prefill wyznacza czas do pierwszego tokena (TTFT), a attention rośnie z kwadratem długości, więc bardzo długi prompt wydłuża TTFT szybciej niż liniowo.
- Decode daje jeden token na rozmowę na krok i mnoży te same wagi przez jeden wektor. W FP16 to ok. 1 operacja na bajt wag na każdą rozmowę w batchu, a H100 potrzebuje ok. 300, żeby wąskim gardłem stało się liczenie (temat „Czemu GPU się nudzi”). Czas kroku ≈ (wagi + cache wszystkich rozmów w batchu) / przepustowość pamięci.
- Wagi czyta się raz na cały batch, cache każdej rozmowy osobno. Dlatego serwery sklejają wiele rozmów w jeden krok (temat „Continuous batching”), długi kontekst jednej rozmowy spowalnia cały batch, a token wyjściowy kosztuje u dostawców kilka razy więcej niż wejściowy.
Zwiększ kontekst i liczbę rozmów w batchu. Zobacz, kiedy czytanie cache’u zaczyna dominować nad czytaniem wag
Model poglądowy jak w temacie „Czemu GPU się nudzi”: 8 mld parametrów w FP16 (16 GB wag, cache 128 KB na token), jedna karta H100 z 80 GB pamięci i 3,35 TB/s. Czas kroku = (wagi + cache wszystkich rozmów) / przepustowość. To górna granica prędkości, bez narzutów.
Cache na serwerze
- PagedAttention (vLLM) dzieli cache na bloki po kilkanaście tokenów, jak strony pamięci wirtualnej. Serwer nie rezerwuje miejsca na maksymalną długość, a wspólny prefiks wielu rozmów trzyma raz. Prompt caching to te same bloki zachowane po requeście i odszukane po hashu prefiksu.
- Gdy pamięci brakuje, serwer wstrzymuje część rozmów i albo zwalnia ich cache i później liczy go od nowa, albo zrzuca go do RAM-u lub na dysk i wczytuje z powrotem. Oba sposoby kosztują czas, ale nie zmieniają wyniku.
- Wyrzucanie części tokenów z cache’u (StreamingLLM, H2O: zostają pierwsze, ostatnie i te, które dostają najwięcej attention) oszczędza pamięć, ale jest przybliżeniem. Model traci dostęp do wyrzuconego tekstu, więc jakość na długich zadaniach spada.
Sprawdź się
Czym jest KV cache i czym różni się prefill od decode?
Generowanie to pętla: jeden przebieg modelu daje jeden token. Attention potrzebuje kluczy i wartości wszystkich wcześniejszych tokenów, a dzięki masce przyczynowej one się nie zmieniają, więc liczy się je raz i trzyma w pamięci GPU. Prefill liczy cały prompt równolegle, jest ograniczony obliczeniami i wyznacza czas do pierwszego tokena. Decode idzie token po tokenie i czeka na pamięć, bo każdy krok czyta wagi i cały cache. Cache to ok. 0,33 MB na token dla 70B z GQA, więc to on ogranicza kontekst i batch. Zmniejszają go GQA, MLA, okno przesuwne i FP8.
In English
Generation is a loop: one forward pass yields one token. Attention needs the keys and values of all earlier tokens, and thanks to the causal mask they never change, so they are computed once and kept in GPU memory. Prefill processes the whole prompt in parallel, is compute-bound and sets time to first token. Decode goes token by token and is memory-bound, because every step reads the weights and the whole cache. The cache is about 0.33 MB per token for a 70B model with GQA, so it is what caps context length and batch size. GQA, MLA, sliding windows and FP8 shrink it.
Pytania pogłębiające (4)
- Od czego zależy czas do pierwszego tokena, a od czego prędkość pisania?
- Czas do pierwszego tokena zależy od kolejki i od tego, ile promptu trzeba policzyć poza trafionym cache’em, bo to prefill. Prędkość pisania zależy od rozmiaru wag, długości kontekstu, liczby rozmów w batchu i przepustowości pamięci, bo to decode.
- Czemu długi kontekst spowalnia pisanie, skoro każdy krok liczy jeden token?
- Bo każdy krok czyta cały cache. Dla modelu 70B przy 128 tys. tokenów to 43 GB na krok, prawie jedna trzecia tego, co ważą wagi, a decode jest ograniczony przepustowością pamięci.
- vLLM pada z braku pamięci przy długich kontekstach. Co robisz?
- Policz budżet: wagi plus cache na token × maksymalny kontekst × liczba równoległych sekwencji. Potem ogranicz maksymalną długość i liczbę sekwencji, włącz cache w FP8 i współdzielenie prefiksów, a gdy to za mało, podziel model na więcej kart. Tensor parallelism dzieli cache GQA po głowach KV, więc najwyżej na tyle kart, ile jest głów KV (8 w Llama 3.1 70B). Cache MLA jest powielany na każdej karcie, dlatego modele w stylu DeepSeek używają zamiast tego data-parallel attention.
- Jak zmniejszyć KV cache?
- Przy projektowaniu modelu: GQA lub MQA, MLA, okno przesuwne, warstwy SSM. Przy serwowaniu: FP8, współdzielenie prefiksów, zrzut do RAM-u, wyrzucanie tokenów. Wyniku nie zmieniają tylko współdzielenie i zrzut.