Jest jedna optymalizacja, która potrafi ściąć rachunek za API o dwie trzecie, nie wymaga zmiany modelu ani frameworka i mieści się w jednej decyzji o kolejności tekstu. Jest też jedna rzecz, która ją unieważnia — i prawie każdy popełnia ten błąd przynajmniej raz.
W poprzednim artykule dwukrotnie natrafiliśmy na to samo marnotrawstwo: cała historia rozmowy przechodzi przez prefill przy każdym zapytaniu, bo pamięć podręczna została zwolniona zaraz po poprzedniej odpowiedzi. Przy agencie wysyłającym ten sam prompt systemowy trzydzieści razy pod rząd jest to marnotrawstwo trudne do zaakceptowania.
Okazuje się, że da się go uniknąć. Pod jednym warunkiem.
Skąd w ogóle bierze się możliwość
Wróćmy do mechanizmu z czwartego artykułu. Dla każdego tokenu w kontekście model liczy klucz i wartość, po czym zapisuje je do pamięci podręcznej.
Kluczowa własność brzmiała: te wektory się nie zmieniają. Klucz policzony dla trzeciego tokenu jest identyczny w chwili generowania tokenu czwartego, czterechsetnego i czterotysięcznego. Nic, co pojawi się później, nie ma na niego wpływu.
Teraz idziemy o krok dalej. Skoro klucz dla trzeciego tokenu nie zależy od niczego, co przyjdzie po nim — to nie zależy też od tego, w którym zapytaniu ten token się pojawił. Wyślij jutro ten sam prompt systemowy, a model policzy dla niego dokładnie te same liczby.
I tu pojawia się pytanie, które zadał sobie każdy dostawca: skoro wynik będzie identyczny, po co go liczyć drugi raz?
Na czym polega cache promptu
Dostawca zachowuje obliczoną pamięć podręczną dla początkowego fragmentu kontekstu i przy kolejnym zapytaniu, które zaczyna się dokładnie tak samo, podstawia gotowca zamiast liczyć od nowa.
Prefill dla tej części po prostu nie następuje. Model zaczyna pracę od pierwszego tokenu, który różni się od tego, co już zna.
Oszczędność jest podwójna i warto rozdzielić te dwie rzeczy, bo mylą się nawzajem.
Czas. Prefill dziesięciotysięcznego promptu systemowego trwa na karcie serwerowej ułamek sekundy, ale przy trzydziestu krokach pętli agenta te ułamki się sumują. Odpada cały ten składnik czasu do pierwszego tokena.
Pieniądze. Tokeny odczytane z pamięci podręcznej są rozliczane po stawce obniżonej — zwykle to ułamek ceny zwykłego tokenu wejściowego. To jest ta obniżka, która robi różnicę na fakturze.
Warunek, który wszystko psuje
Cały mechanizm opiera się na jednym założeniu i ono jest bezlitosne.
Dopasowanie musi być dokładne i musi zaczynać się od początku kontekstu.
Nie „podobny prompt”. Nie „ten sam system prompt gdzieś w środku”. Dokładnie ten sam ciąg tokenów, od pierwszego, bez żadnej różnicy.
Powód jest mechaniczny, nie administracyjny. Klucz dla tokenu numer pięćset zależy od wszystkich tokenów przed nim. Zmień jeden znak na pozycji trzeciej, a wszystkie następne klucze będą inne — cały zapisany stan staje się bezużyteczny.
To jest dokładnie ta sama zależność, którą opisywałeś przy mechanizmie uwagi: reprezentacja każdej pozycji powstaje z uwzględnieniem wszystkich wcześniejszych. Tu ta własność wraca jako ograniczenie praktyczne.
Pięć sposobów, żeby to zepsuć
Teraz rzecz najbardziej praktyczna w całym tym torze. Oto błędy, które unieważniają cache, uszeregowane od najczęstszego.
Znacznik czasu na początku. Wstawienie bieżącej daty i godziny w pierwszym zdaniu instrukcji systemowej. Wygląda niewinnie i jest najkosztowniejszą linijką w całym projekcie — przy każdym wywołaniu początek kontekstu jest inny, więc cache nigdy nie trafia.
Identyfikator sesji albo użytkownika w nagłówku promptu. Ten sam mechanizm. Jeśli musi tam być, ma być na końcu.
Losowa kolejność narzędzi. Definicje narzędzi są częścią kontekstu. Jeśli framework serializuje je ze słownika bez gwarancji kolejności, przy każdym uruchomieniu wychodzi inny ciąg tokenów.
Dynamiczne przykłady na górze. Wstawianie do instrukcji systemowej przykładów dobieranych do zapytania. Sensowne merytorycznie, zabójcze dla cache, jeśli siedzą przed resztą.
Drobna zmiana w instrukcji między wdrożeniami. Poprawienie literówki w prompcie systemowym unieważnia cache dla wszystkich sesji. Nie jest to powód, żeby literówek nie poprawiać — jest to powód, żeby nie robić tego dwadzieścia razy dziennie.
Reguła, którą warto zapamiętać
Wszystkie powyższe błędy sprowadzają się do jednej zasady, którą da się zapisać w czterech słowach:
Stałe na początku, zmienne na końcu.
Instrukcja systemowa, definicje narzędzi, dokumenty referencyjne i przykłady — wszystko, co się nie zmienia między wywołaniami — idzie na sam początek, w niezmiennej kolejności. Zapytanie użytkownika, znacznik czasu, identyfikatory i wszystko, co zmienne — na koniec.
Ta jedna decyzja o kolejności potrafi być różnicą między rachunkiem trzykrotnie wyższym a trzykrotnie niższym. Trzykrotnie przy krótkiej pętli, a przy długiej znacznie więcej. Nie wymaga zmiany modelu, frameworka ani architektury. Wymaga przestawienia bloków tekstu.
Dlaczego agent jest idealnym przypadkiem
Zwykła rozmowa z czatem korzysta z cache umiarkowanie, bo każda tura dokłada treść i unieważnia coraz mniejszą część.
Agent jest inny. Przy każdym kroku pętli wysyła ten sam prompt systemowy i te same definicje narzędzi, a zmienia się tylko narastająca historia kroków na końcu. Struktura kontekstu jest dokładnie taka, jakiej cache potrzebuje: gruby niezmienny początek, cienki zmienny ogon.
Przy agencie wykonującym trzydzieści kroków oznacza to, że dwadzieścia dziewięć razy nie płacisz pełnej stawki za coś, co i tak się nie zmieniło.
Jeśli optymalizujesz koszt agenta i nie sprawdziłeś, czy cache trafia — prawdopodobnie zaczynasz od złego końca. To zwykle największa pojedyncza pozycja do odzyskania.
Co nie jest cache’em promptu
Warto to odgraniczyć, bo nazwy są mylące i dwa różne mechanizmy bywają mylone.
Cache semantyczny działa na zewnątrz modelu: zapamiętuje całe odpowiedzi i zwraca je, gdy przychodzi pytanie o podobnym znaczeniu. Model w ogóle się nie uruchamia. To narzędzie o innym przeznaczeniu i innym profilu ryzyka — przy zbyt luźnym progu podobieństwa potrafi zwrócić jednemu użytkownikowi odpowiedź wygenerowaną na danych innego.
Cache promptu działa wewnątrz modelu i nie zmienia odpowiedzi. Pomija wyłącznie przeliczanie tego, co i tak wyszłoby identycznie. To ważne rozróżnienie: jedno jest optymalizacją, drugie jest skrótem z konsekwencjami dla poprawności.
Czego nie widzisz
Trzy rzeczy warte świadomości.
Cache wygasa. Zachowany stan zajmuje pamięć, a pamięci jest skończona ilość. Dostawcy trzymają go zwykle od kilku minut do kilkudziesięciu. Agent uruchamiany raz na godzinę nie skorzysta z niego nigdy.
Cache bywa przypisany do maszyny. Twoja prośba trafia do puli maszyn przez router. Jeśli druga prośba trafi na inną maszynę, zapisany stan jest dla niej niedostępny. To jedna z przesłanek, którymi kieruje się router przy wyborze — i powód, dla którego trafienia w cache bywają mniej powtarzalne, niż by wynikało z samej treści promptu.
Odpowiedź API mówi, czy trafiłeś. Większość dostawców zwraca liczbę tokenów odczytanych z pamięci podręcznej osobno od tokenów przeliczonych. To jedyny sposób, żeby sprawdzić, czy twoja optymalizacja działa — zgadywanie po rachunku jest zbyt wolne.
Co dalej
Doszliśmy do miejsca, w którym mechanizm da się już przełożyć na decyzje. Wiesz, skąd bierze się kolejka, dlaczego limity liczą się w tokenach i jak nie zmarnować cache’u.
Zostaje ostatnia rzecz w tym torze: pieniądze. Nie jako lista stawek, bo te zmieniają się co kwartał, tylko jako sposób czytania cennika — czym różni się dostawca tani od dostawcy szybkiego, dlaczego jedna liczba nie opisuje żadnego z nich i za co właściwie płacisz, skoro nie za model.
Następny artykuł zamyka tor drugi.
Kalkulator trafień w cache webflux · maszyneria modelu





















