KV Cache na GPU z 16 GB pamięci: jak realnie zmieścić długi kontekst

Dlaczego kontekst 128K zawodzi na 16 GB

Page content

Model może reklamować okno kontekstowe 128K i mimo to zawiedzie przy 40K tokenach na karcie GPU 16 GB. Architektoniczny limit nigdy nie gwarantował, że wagi, cache KV, bufora obliczeniowe i kompozytor pulpitu zmieszczą się jednocześnie na Twojej karcie.

Cache KV to zazwyczaj miejsce, w którym plany długiego kontekstu zderzają się z fizycznym ograniczeniem. Rośnie wraz z każdym aktywnym tokenem i sekwencją, więc konfiguracja, która na starcie wygląda komfortowo, może gwałtownie spowolnić, zacząć używać pamięci systemowej lub zawieść podczas dużego prefillu.

Budżet pamięci cache KV na karcie GPU 16 GB

Ten przewodnik przekształca problem w budżet pamięci VRAM. Omawia on formułę na cache, reprodukowalne tabele rozmiarów od 32K do 128K oraz działające konfiguracje dla --cache-type-k i --cache-type-v w llama.cpp, stronicowanego i prefiksowego cache’ingu w vLLM oraz kontroli kontekstu w Ollama — a także eksperymentalne forki adaptacyjnego cache’ingu, które zasługują na zainteresowanie, ale nie na ślepe zaufanie. Aby uzyskać szerszy kontekst dotyczący przepustowości, opóźnienia i benchmarków stojących za tymi liczbami, zacznij od hubu wydajności LLM.

Krótka odpowiedź dla karty GPU 16 GB

Zacznij od jednej sekwencji, realistycznego maksymalnego kontekstu, Flash Attention i 8-bitowego cache’ingu KV. Zmierz tę konfigurację, zanim spróbujesz 4-bitowego cache’ingu, offloadu na CPU, wielu równoległych slotów lub eksperymentalnego forka.

Cel Rozsądna pierwsza próba na 16 GB Główne ryzyko
32K Wagi Q4 lub Q5, KV Q8, jedna sekwencja Wagi modelu zostawiają za mało miejsca w buforze
64K Mniejszy model lub agresywna kwantyzacja wag, KV Q8 Opóźnienie prefillu i przepustowość cache
128K Mały model GQA, KV Q8 lub przetestowany Q4, jedna sekwencja Sam cache może pochłonąć większość VRAM
Dwie jednoczesne sesje 64K Traktuj to jak budżet cache na ok. 128K Równoległa pojemność bywa mylona z darmową przepustowością

Moje zdanie jest proste: stabilna konfiguracja 64K jest zwykle bardziej przydatna niż nominalna konfiguracja 128K działająca na granicy awarii z powodu braku pamięci (out-of-memory). Pojemność kontekstowa to nie trofeum; to decyzja dotycząca opóźnienia, jakości i równoległości.

Co przechowuje cache KV

W trakcie autoregresyjnej generacji każda warstwa uwagi (attention) wytwarza tensory kluczy (key) i wartości (value) dla każdego przetworzonego tokenu. Runtime utrzymuje te tensory, aby kolejny token mógł „patrzeć” (attend) na wcześniejsze tokeny bez ponownego obliczania całego prefiksu.

Cache oszczędza ogromne ilości mocy obliczeniowej, ale konsumuje pamięć proporcjonalnie do liczby utrzymywanych tokenów. Dla konwencjonalnego transformera z grupowaną uwagą (grouped-query attention), przydatnym punktem odniesienia jest:

KV bytes = sequences * tokens * layers * 2 * KV heads * head dimension * bytes per value

Czynnik dwa reprezentuje klucze i wartości. Uwaga wielogłowica (Multi-head attention) używa tylu głów KV, ile głów zapytań (query), uwaga zgrupowana (grouped-query attention) używa mniejszej liczby głów KV, a uwaga wielogłowica latencjalna (multi-head latent attention) lub hybrydowe architektury rekurencyjne wymagają innych obliczeń.

Dlaczego liczba parametrów nie jest wystarczająca

Dwa modele 8B mogą mieć bardzo różne koszty cache’ingu KV. Jeden może używać 32 warstw i 8 głów KV, podczas gdy inny może używać mniejszej liczby głów KV, współdzielonych warstw KV, uwagi ze ślizgającym się oknem (sliding-window) lub skompresowanych stanów latentnych.

Liczba parametrów w dużej mierze przewiduje pamięć wag. Geometria KV wynika z architektury uwagi, więc czytaj metadane modelu, zamiast zgadywać na podstawie 8B, 27B lub rozmiaru pliku GGUF. Najjaśniejszą ilustracją tego jest to, jak daleko projektowanie uwagi wykracza poza zwykłą uwagę wielogłowicą (MHA):

  • Uwaga Wieloapytaniowa (MQA) (Multi-Query Attention) współdzieli jedną głowę K/V między wszystkimi głowami zapytań — maksymalne oszczędności pamięci cache, ale jest to najbardziej agresywny kompromis jakości i rzadko stosowana w pojedynkę w obecnych czołowych modelach.
  • Uwaga Zgrupowana (GQA) (Grouped-Query Attention) grupuje głowy zapytań w klastry, z których każdy współdzieli jedną głowę K/V — to mainstreamowy kompromis stosowany przez większość otwartych modeli gęstych (dense) i geometria, której zakłada powyższa formuła.
  • Uwaga Wielogłowica Latencjalna (MLA) (Multi-Head Latent Attention), wprowadzona w DeepSeek-V2 i wprowadzona w DeepSeek-V3 oraz Kimi K2, podejmuje zupełnie inne podejście: zamiast współdzielenia K/V między głowami, projekcjonuje klucze i wartości do skompresowanego wektora latentnego o niskim ranku i odtwarza pełnej rozdzielczości K/V na żądanie w chwili uwagi. DeepSeek zgłosił redukcję cache’ingu KV o około 93% w porównaniu z równie dużym modelem gęstym MHA, zachowując konkurencyjną jakość — czasem lepszą — niż GQA przy tym samym budżecie pamięci.

Praktyczną konsekwencją jest to, że „model 27B GQA” i „model 27B MLA” mogą mieć odziały cache’ingu KV różniące się o rząd wielkości dla tej samej długości kontekstu. Nie zakładaj, że powyższa formuła ma zastosowanie do modelu, który w swojej dokumentacji opisuje się jako używający uwagi latentnej, stanów w stylu DeltaNet lub warstw ze ślizgającym się oknem — najpierw sprawdź sekcję architektury karty modelu.

W Ollama, ollama show MODEL --verbose ujawnia metadane modelu, w tym liczbę warstw, głów uwagi, głów KV i długość kontekstu, jeśli format je dostarcza. W llama.cpp, output ładowarki modelu wypisywany przy starcie zwykle zawiera równoważne metadane GGUF i faktyczną alokację cache’ingu runtime’u.

Limit modelu, Alokovany kontekst i Użyty kontekst

Są to trzy osobne liczby. Limit modelu to maksimum wspierane przez jego trening i kodowanie pozycyjne, alokowany kontekst to to, co runtime zarezerwuje lub dopuści, a użyty kontekst to tokeny obecnie utrzymywane dla sekwencji.

Podnoszenie flagi silnika nie może bezpiecznie rozszerzyć modelu poza jego wspierany schemat pozycji. Skalowanie RoPE może rozszerzyć niektóre architektury, ale jest to eksperyment z jakością modelu, a nie optymalizacja pamięci KV.

Tabela rozmiaru cache’ingu KV: budżety kontekstu 32K, 64K i 128K

Rozważmy reprezentacyjny model GQA z 32 warstwami, ośmioma głowami KV i wymiarem głowy (head dimension) 128. Wymiary te generują 65 536 elementów kluczy i wartości na token przed pomnożeniem przez rozmiar przechowywania każdego elementu.

Tabela używa binarnych GiB i fizycznych rozmiarów bloków powszechnie kojarzonych z f16, q8_0 i q4_0 w llama.cpp. Jest to obliczenie bazowe, a nie obietnica dotycząca całkowitej pamięci procesu; wyrównanie (alignment), metadane, warstwy hybrydowe i przestrzenie robocze backendu dodają narzut.

Typ cache Przybl. bajty na przechowywaną wartość Kontekst 32K Kontekst 64K Kontekst 128K
F16 2.0000 4.00 GiB 8.00 GiB 16.00 GiB
Q8_0 1.0625 2.13 GiB 4.25 GiB 8.50 GiB
Q4_0 0.5625 1.13 GiB 2.25 GiB 4.50 GiB
Q8_0 K plus Q4_0 V Mieszany 1.63 GiB 3.25 GiB 6.50 GiB

Teraz podwoj liczbę warstw do 64, pozostawiając inne wymiary bez zmian. Cache FP16 staje się 8 GiB przy 32K, 16 GiB przy 64K i 32 GiB przy 128K, co demonstruje, dlaczego jedna rekomendacja kontekstu nie może pokryć każdego modelu.

Rzeczywiste równanie dla 16 GB

Praktyczny budżet jest szerszy niż formuła KV:

użyteczne VRAM = całkowite VRAM - rezerwa pulpitu i sterownika

budżet KV = użyteczne VRAM
          - wagi modelu rezydujące na GPU
          - bufora grafu i aktywacji
          - przestrzeń robocza runtime
          - stan dekodowania spekulatywnego
          - margines bezpieczeństwa

Na karcie 16 GB podłączonej do wyświetlacza, nie planuj wokół założenia, że całe 16 GiB jest dostępne. Zarezerwuj co najmniej kilkaset MiB na pulpit i sterownik, a następnie zostaw inny margines na bufory zależne od obciążenia; 1,0 do 1,5 GiB całkowitego zapasu jest rozsądnym początkowym założeniem, ale to Twoje logi są autorytetem.

Załóżmy, że model GGUF zajmuje 10,8 GiB na GPU, a narzut runtime osiąga szczyt na poziomie 1,2 GiB. Po odjęciu marginesu bezpieczeństwa 1 GiB, dla KV pozostaje tylko około 3 GiB, więc reprezentacyjny model mieści około 45K tokenów z Q8_0 lub 87K z Q4_0, przed narzutem specyficznym dla silnika.

To nie oznacza automatycznie, że Q4_0 jest właściwym wyborem. Jeśli dokładność długiego kontekstu spada w Twoim obciążeniu, mniejszy lub bardziej agresywnie skwantyzowany model z cache’em Q8_0 może być lepszy niż większe wagi połączone z kruchym cache’em. Zmierzone punkty odniesienia dla dokładnie tej arytmetyki znajdują się w tabelach benchmarków llama.cpp dla 16 GB VRAM, gdzie VRAM per model jest rejestrowane przy kontekście 19K, 32K i 64K. Dla szerszego przeglądu, które rozmiary modeli i poziomy kwantyzacji dobrze zachowują się w Ollama na tej samej klasie karty, zobacz Porównanie wydajności LLM w Ollama na GPU 16GB VRAM.

Oblicz budżet cache’ingu KV dla swojego modelu

Poniższy fragment kodu Python szacuje konwencjonalny, pełno-uwagowy cache GQA. Zamień geometrię na wartości z konfiguracji modelu lub metadanych GGUF.

def kv_gib(tokens, layers, kv_heads, head_dim, bytes_per_value, sequences=1):
    total = (
        sequences
        * tokens
        * layers
        * 2
        * kv_heads
        * head_dim
        * bytes_per_value
    )
    return total / (1024 ** 3)


model = {
    "layers": 32,
    "kv_heads": 8,
    "head_dim": 128,
}

types = {
    "f16": 2.0,
    "q8_0": 34 / 32,
    "q4_0": 18 / 32,
}

for tokens in (32768, 65536, 131072):
    row = {
        name: round(kv_gib(tokens=tokens, bytes_per_value=size, **model), 2)
        for name, size in types.items()
    }
    print(tokens, row)

Proporcje Q8_0 i Q4_0 zawierają proste metadane blokowe, dlatego są nieznacznie większe niż dokładnie jeden bajt i pół bajta na wartość. Raport startowy runtime’u pozostaje bardziej dokładny, ponieważ zna specyficzne dla modelu układy cache’ingu.

Kiedy ta formuła jest błędna: Architektury hybrydowe i ze ślizgającym się oknem

Nie wymuszaj architektur hybrydowych do konwencjonalnego równania GQA. Warstwy ze ślizgającym się oknem utrzymują tylko niedawne okno, współdzielone warstwy KV zmniejszają duplikację, warstwy rekurencyjne mogą przenosić stan o stałym rozmiarze, a uwaga wielogłowica latentna przechowuje skompresowaną reprezentację, a nie tensory K/V per głowa — przypadkiem MLA powyżej jest najbardziej dramatycznym przykładem.

Współczesne silniki coraz bardziej zarządzają tymi mieszanymi układami jawnie. Używaj formuły, aby wyjaśnić dominujące składniki, a następnie potwierdź alokację raportowaną przez dokładny build silnika i backend, który planujesz wdrożyć.

llama.cpp: Bezpośrednia kontrola precyzji K i V

llama.cpp udostępnia osobne opcje --cache-type-k i --cache-type-v w swoim aktualnym parserze argumentów. To najbardziej przydatny interfejs lokalnej inferencji, gdy potrzebujesz handlować precyzją cache’ingu za pojemność kontekstową, zamiast akceptować jedną globalną presetę. Jeśli najpierw potrzebujesz otaczającej instalacji i konfiguracji serwisowania, przewodnik llama.cpp obejmuje llama-cli, llama-server i kluczowe flagi VRAM.

Konservatywna konfiguracja 64K dla jednego użytkownika wygląda tak:

./llama-server \
  --model /models/model.gguf \
  --n-gpu-layers 999 \
  --ctx-size 65536 \
  --parallel 1 \
  --flash-attn on \
  --cache-type-k q8_0 \
  --cache-type-v q8_0 \
  --batch-size 1024 \
  --ubatch-size 256

Składnia flag i wsparcie backendu zmieniają się szybko, więc wykonaj llama-server --help dla zainstalowanego buildu. Ważniejsze jest, aby inspect log startowy: powinien pokazać zamierzony kontekst, typy cache, offload GPU i alokowane bufory K i V.

Jakie typy cache’ingu llama.cpp wypróbować

Zacznij od Q8_0 dla obu K i V. Osiąga to około połowy pamięci KV w stosunku do F16, a niezależne testy perplexity na modelach 20B i większych (Qwen3.6-27B, Nemotron-30B) pokazują, że łączna różnica jakościowa w stosunku do F16 mieści się w szumie pomiarowym — to znacznie mniej dramatyczna stawka niż przejście bezpośrednio do Q4_0, które te same testy wykazały jako powodujące załamanie prędkości dekodowania i dokładności przy długim kontekście na mniejszych modelach.

Jeśli Q8_0 nie mieści się, przetestuj klucze Q8_0 z wartościami Q4_0, zanim skwantyzujesz obie strony do Q4_0. Ten porządek ma poparcie badawcze, a nie tylko legendy: kontrolowane badania alokacji bitów na checkpointach Llama, Phi-4, Qwen3 i Mistral wykazały, że tensory kluczy są konsekwentnie dwa do dziesięciu razy bardziej wrażliwe na błąd kwantyzacji niż tensory wartości, i że nadanie kluczyom większego budżetu bitów (na przykład klucze 4-bitowe z wartościami 2-bitowymi) odzyskuje do 94–98% dokładności pełnej precyzji — podczas gdy odwrócony podział (klucze 2-bitowe, wartości 4-bitowe) może stracić 30 punktów procentowych w zadaniach takich jak GSM8K. Klucze determinują, z którymi wcześniejszymi tokenami uwaga faktycznie się zgadza, więc ich ochrona w pierwszej kolejności jest architektonicznie słusznym wyborem, a nie tylko bezpieczniejszym brzmieniem.

Konfiguracja Pamięć Ryzyko jakości Rekomendacja
F16 K i V Najwyższa Najniższe Bazowe, jeśli się mieści
Q8_0 K i V Około połowy F16 Niskie, ale nie zerowe Domyślny punkt startowy na 16 GB
Q8_0 K, Q4_0 V Między Q8 a Q4 Umiarkowane Przydatny drugi krok
Q4_0 K i V Około ćwiartki F16 Najwyższe Zweryfikuj na docelowej głębokości

Jedna uwaga warta wczuwania: „niskie ryzyko jakościowe” na agregowanych benchmarkach nie oznacza zerowego ryzyka na poziomie tokenu. Kontrolowany test, który utrzymał Flash Attention stałe i zmienił tylko precyzję KV przy dekodowaniu chciwym (deterministycznym), wykazał, że cache Q8_0 zmienił dokładny wygenerowany tekst na dużej większości promptów, a Q4_0 zmienił go na praktycznie wszystkich — raz gdy jeden token się „przekręci”, reszta kontynuacji może się rozjechać. Perplexity i wyniki zadań downstream mogą wyglądać dobrze w średniej, podczas gdy pojedyncze wyjścia wciąż różnią się od bazy F16. Jeśli Twoja aplikacja wymaga reprodukowalności bajt-po-bajcie (testy regresyjne, zbuforowane odpowiedzi, agenci deterministyczni), traktuj każdą kwantyzację KV jako zmianę zachowawczą, a nie tylko optymalizację pamięci, i waliduj na własnym zestawie stałych promptów.

S skwantyzowany cache V może wymagać Flash Attention lub kompatybilnej ścieżki backendu. Serwer, który cicho cofa się do innego typu, unieważnia eksperyment, dlatego logi startowe są ważniejsze niż skopiowane linie poleceń.

Kontekst, Sloty równoległe i Zjednoczony Cache

--ctx-size opisuje pojemność silnika, a nie gwarancję, że każdy slot równoległy otrzymuje tyle tokenów niezależnie. Zarządzanie cache’em ewoluowało w llama.cpp, w tym zachowanie unified-cache, więc przetestuj dokładny build, zamiast polegać na starszej regule, która po prostu dzieli kontekst przez liczbę slotów.

Równanie pojemności wciąż przetrwa zmiany implementacji: jednoczesne unikalne tokeny potrzebują gdzieś przechowywania. Jeśli dwie sesje agenta mogą osiągnąć każde 48K, budżetuj na blisko 96K tokenów w ruchu, chyba że obciążenie współdzieli prefiksy lub toleruje ewikcję i ponowne obliczanie.

Rozmiar paczki nie zmniejsza przechowywanego KV

--batch-size i --ubatch-size wpływają na przetwarzanie promptu i pamięć tymczasową. Zmniejszanie ich może ocalić duży prefill przed skokiem pamięci aktywacji, ale nie zmienia trwałych bajtów wymaganych dla każdego utrzymywanego tokenu.

Ta rozróżnienie wyjaśnia powszechny wzorzec awarii: model startuje i puste żądanie działa, ale prompt 60K zawodzi podczas ingesji. Zmniejsz mikro-paczkę (micro-batch), aby zdiagnozować chwilowy szczyt; zmniejsz kontekst, precyzję cache’a, równoległość lub rezydencję wag, aby zmienić trwałą pojemność.

vLLM: Stronicowana pojemność to wciąż pojemność

vLLM podchodzi do problemu jako silnik serwowania. Profiluie dostępną pamięć, rezerwuje pulę cache’ingu KV i alokuje cache w blokach, aby równoległe sekwencje nie wymagały każda jednej dużej ciągłej regiony. Jeśli decydujesz, czy w ogóle przejść na vLLM, przewodnik migracji z Ollama na vLLM obejmuje sygnały obciążenia; tutaj pytanie brzmi wyłącznie o to, ile cache’a może trzymać pula, a szybki start vLLM obejmuje instalację i ogólne flagi serwowania poza dźwigniami pojemności poniżej.

PagedAttention redukuje fragmentację i marnotrawstwo wokół zmiennych długości sekwencji — alokacja stronicowana usuwa fragmentację, a nie koszt przechowywania per token, więc jedno unikalne żądanie 128K wciąż potrzebuje wystarczająco bloków dla swojego stanu KV.

Oficjalny przewodnik vLLM oszczędzania pamięci zaleca limitowanie max_model_len i max_num_seqs, gdy pamięć jest na wyczerpanie, i zauważa, że grafy CUDA konsumują dodatkową pamięć GPU. Na karcie 16 GB obie ustawienia powinny być świadome, a nie odziedziczone z maksymalnej konfiguracji modelu.

Zogniskowany serwer pojedynczej sekwencji może zacząć tutaj:

vllm serve MODEL_ID \
  --max-model-len 65536 \
  --max-num-seqs 1 \
  --gpu-memory-utilization 0.90 \
  --kv-cache-dtype fp8 \
  --enable-prefix-caching

Nie każda karta GPU 16 GB, model, metoda kwantyzacji ani backend uwagi wspiera ten dokładny zestaw. Traktuj to jako kształt konfiguracji: ograniczaj długość i równoległość, rezerwuj zapas, wybierz wspierany typ cache’a i waliduj raport inicjalizacji.

Cache KV FP8 w vLLM

Aktualna dokumentacja vLLM o skwantyzowanym cache’ingu KV wspiera formaty cache FP8 na kompatybilnych ścieżkach CUDA i ROCm. FP8 approximately poływa surowe przechowywanie cache’a w stosunku do BF16 lub FP16 i może dlatego zwiększać pojemność tokenów lub równoległość.

Skalowanie ma znaczenie. Dokumentacja rozróżnia domyślne skale, obliczanie rozgrzewki i kalibrację na zbiorze danych, i zaleca kalibrację opartą na zbiorze danych dla najwyższej dokładności; proste ustawienie FP8 ze skalą 1.0 jest wygodne, ale nie jest automatycznie najbardziej niezawodnym wyborem jakości.

Prefiksowy cache to optymalizacja ponownego użycia

Automatyczny cache prefiksowy pozwala nowemu żądaniu ponownie wykorzystać bloki KV dla identycznego zbuforowanego prefiksu. Jest świetny dla powtarzających się zapytań do tego samego długiego dokumentu, współdzielonych systemowych promptów i wielokołowych rozmów, ponieważ unika ponownego obliczania pasującego prefillu.

Nie powoduje, że unikalne długie żądanie staje się mniejsze, i nie przyspiesza generowania nowych tokenów. Dokumentacja vLLM o cache’ingu prefiksowym wyraźnie ogranicza korzyść do pracy prefillu z przedzielanym prefiksem.

Wykorzystanie pamięci GPU nie jest darmową pamięcią

Podnoszenie --gpu-memory-utilization daje vLLM większy cel rezerwacji, ale nie tworzy VRAM. Pchanie go zbyt blisko 1.0 może zostawić niewystarczająco miejsca na wyświetlacz, inny proces, zmieniające się szczyty aktywacji lub alokacje nie-PyTorch.

Zacznij około 0.88 do 0.92 na dedykowanym GPU 16 GB, inspect profil i zwiększaj tylko, jeśli obciążenie pozostaje stabilne. Jeśli inicjalizacja się powiedzie, ale prawdziwe prompty zawiodą, zmniejsz paczkowane tokeny, równoległość sekwencji, przechwytywanie grafu CUDA lub maksymalny kontekst, zanim założysz, że alokator jest zepsuty.

Ollama: Łatwiejsze kontrole, mniej szczegółowa diagnostyka

Ollama celowo zapewnia mniejszą powierzchnię operacyjną. Jego aktualna dokumentacja długości kontekstu domyślnie ustawia GPU poniżej 24 GiB na kontekst 4K, zaleca co najmniej 64K dla obciążeń agencji i kodowania i ostrzega, że większy kontekst konsumuje więcej pamięci.

Ustaw domyślną wartość całego serwera i potwierdź załadowany model tak:

OLLAMA_CONTEXT_LENGTH=65536 ollama serve

ollama ps

Możesz też ustawić num_ctx per żądanie lub model. ollama ps jest ważne, ponieważ jego kolumny PROCESSOR i CONTEXT ujawniają, czy model pozostał całkowicie na GPU i czy żądany kontekst został faktycznie alokowany. Miej na uwadze, że zachowanie harmonogramu za tymi liczbami zmieniło się między wersjami Ollama; moje porównanie alokacji pamięci w Ollama v0.12.1 pokazuje, że nowy harmonogram pcha niektóre modele dalej na CPU na karcie 16 GB, więc przypnij (pin) wersję, którą zmierzyłeś.

Skwantyzowany cache KV w Ollama

Ollama udostępnia OLLAMA_KV_CACHE_TYPE z wyborami f16, q8_0 i q4_0 w swoim aktualnym FAQ. Kwantyzowany KV wymaga Flash Attention, którego Ollama używa automatycznie na wspieranych backendach lub który może być żądany z OLLAMA_FLASH_ATTENTION=1.

Długokontekstowa usługa 16 GB może więc być uruchomiona jako:

OLLAMA_CONTEXT_LENGTH=65536 \
OLLAMA_FLASH_ATTENTION=1 \
OLLAMA_KV_CACHE_TYPE=q8_0 \
OLLAMA_NUM_PARALLEL=1 \
ollama serve

Q8_0 to zalecana alternatywa dla F16 w Ollama. FAQ ostrzega, że Q4_0 może spowodować bardziej zauważalną utratę jakości, szczególnie przy wyższym kontekście, więc powinno być to zmierzone zaplecze, a nie automatyczny preset 16 GB.

Równoległość Ollama mnoży budżet kontekstu

Ollama dokumentuje szczególnie jasną regułę: wymagana pamięć skaluje się z OLLAMA_NUM_PARALLEL * OLLAMA_CONTEXT_LENGTH. Cztery równoległe żądania przy ustawieniu 32K mogą implikować agregowaną alokację kontekstu 128K dla tego modelu.

Dla osobistej agencji na 16 GB, trzymaj OLLAMA_NUM_PARALLEL=1, dopóki jedna długa sesja nie będzie stabilna. Kolejkowanie drugiego żądania jest zwykle lepsze niż pchanie pierwszego modelu częściowo na CPU i spowalnianie obu żądań. Mechanika kolejkowania, 503 i odładowywania modeli stojąca za tym wyborem jest udokumentowana w jak Ollama obsługuje żądania równoległe.

CPU Offload: Ważna droga ewakuacyjna z ceną

Przenoszenie niektórych warstw modelu lub stanu KV do pamięci systemowej może przekształcić awarię alokacji w działający proces. Umieszcza również przepustowość PCIe i opóźnienie pamięci hosta w ścieżce dekodowania, gdzie każdy wygenerowany token może ponosić ten koszt. Ścieżka i dowody generacyjne, kiedy PCIe faktycznie gryzie, znajdują się w Wydajność LLM i Liny PCIe.

Offload może być rozsądny dla okazjonalnej pracy batch, ale rzadko jest najlepszym domyślnym wyborem dla interaktywnego agenta kodującego. Najpierw porównaj mniejszą kwantyzację wag, KV Q8, zmniejszoną równoległość i realistyczny limit kontekstu; użyj offloadu, gdy pojemność jest ważniejsza niż opóźnienie.

Obserwuj klif, a nie średnią. Serwer może szybko dekodować przy 8K, a potem gwałtownie spowolnić po tym, jak część zestawu roboczego wyleje się, więc benchmarkuj przy 32K, 64K i zamierzonym maksimum, zamiast raportować tylko stawkę tokenów w pustym kontekście.

Cache’y KV ze ślizgającym się oknem i adaptacyjne

Uwaga ze ślizgającym się oknem zmienia budżet, utrzymując tylko niedawne okno dla wybranych warstw. Modele hybrydowe mogą łączyć te warstwy z okresową globalną uwagą lub stanem rekurencyjnym, czyniąc płaskie obliczenie pełnego kontekstu znaczącym przeceną lub błędnym umieszczeniem pamięci.

Optymalizacja jest częścią architektury modelu, a nie generycznym przełącznikiem, które można zastosować bez konsekwencji. Silnik musi poprawnie rozumieć wzorzec warstw, reguły ewikcji, pozycje i wszelkie globalne tokeny.

Czego próbuje poprawić adaptacyjny KV

Eksperymentalne forki idą dalej, wybierając precyzję lub układ cache’a per warstwa i głębokość kontekstu. Cel jest przyciągający: zachowaj wyższą precyzję tam, gdzie to ważne, kompresuj mniej wrażliwe warstwy i zmieniaj miksturę przed tym, jak presja VRAM spowoduje twarde wylanie — to samo znalezienie o wyższej wrażliwości kluczy nad wartościami opisane powyżej jest dokładnie tym typem sygnału, który adaptacyjny alokator chciałby wykorzystywać automatycznie, zamiast zostawiać go ręcznemu strojeniu --cache-type-k/--cache-type-v.

Jeden downstreamowy projekt z sierpnia 2026, llama.cpp-adaptive-turboquant, raportuje automatyczny selektor dla kilku trybów adaptacyjnych warstw i publikuje testy na dużej głębokości na RTX 5080 16 GB. Te liczby to wyniki zgłaszane przez autora z wyspecjalizowanego forka, a nie dowód na to, że upstream llama.cpp zachowuje się tak samo.

Dlaczego wciąż jest eksperymentalny

Fork łączy niestandardowe typy cache’a, jądra CUDA, ścieżki specyficzne dla modelu i ograniczenia toolchainu. To znacznie więcej kodu do zaufania niż przełączenie przechowywania cache’a upstream z F16 na Q8_0.

Używaj takiego forka tylko, gdy upstream nie może spełnić prawdziwego wymagania i możesz odtworzyć jakość, stabilność i prędkość na swoim modelu. Zarejestruj commit i wersję CUDA, ponieważ wynik przywiązany tylko do nazwy projektu nie jest odtwarzalny.

Sprawiedliwy test adaptacyjnego cache’ingu

Porównaj fork z bazą upstream Q8_0 z tym samym GGUF, promptem, sploterem, głębokością kontekstu i długością wyjścia. Zmierz VRAM przy starcie, szczytowe VRAM prefillu, prędkość przetwarzania promptu, prędkość dekodowania i zadanie jakościowe, które faktycznie wymaga dowodów z najstarszej części kontekstu.

Nie akceptuj udanej alokacji jako pełnego rezultatu. Cache może mieścić 128K i nadal tracić wczesne fakty, korumpować wyjście późno w sekwencji lub dekodować zbyt wolno, aby było użyteczne.

Wykonana procedura strojenia 16 GB: Jedna zmienna na raz

Najszybsza droga do stabilnej konfiguracji to zmiana jednej wymiaru pamięci na raz. Losowe zmienianie typu cache, rozmiaru paczki, offloadu warstw, równoległości i kontekstu jednocześnie produkuje działające polecenie bez wyjaśnienia.

flowchart TD A["Krok 1: ładuj przy 8K kontekst, jedna sekwencja, zapisz VRAM po rozgrzaniu"] --> B{"Wagi + runtime poniżej ok. 14.5 GiB?"} B -- "nie" --> C["Wybierz mniejszą kwantyzację lub model, powtórz Krok 1"] C --> A B -- "tak" --> D["Krok 2: baza jakości KV F16/BF16, zapisz wyjścia zadań"] D --> E["Krok 3: przełącz na KV Q8_0 / FP8 z Flash Attention"] E --> F["Krok 4: podnoś kontekst stopniowo - 32K, 64K, 96K, 128K"] F --> G{"Awaria podczas prefillu?"} G -- "tak" --> H["Krok 5: zmniejsz rozmiar batch / ubatch"] H --> F G -- "nie" --> I{"Awaria tylko przy równoległych żądaniach?"} I -- "tak" --> J["Krok 5: zmniejsz sloty równoległe / max-num-seqs"] J --> F I -- "nie" --> K["Dopiero teraz: mieszany Q8/Q4, pełne Q4, offload, adaptacyjny fork"]

Krok 1: Ustanow dolną granicę wag

Załaduj model przy 8K kontekst, jedna sekwencja i zamierzony offload GPU. Zarejestruj VRAM procesu po rozgrzaniu i zweryfikuj, że żadne warstwy nieprzewidywalnie nie przeniosły się na CPU.

Jeśli wagi i runtime już konsumują więcej niż około 14,5 do 15 GiB, długi kontekst nie ma zdrowego marginesu. Wybierz mniejszą kwantyzację wag lub model, zanim będziesz stroić cache.

Krok 2: Zmierz KV F16 lub BF16 jako bazę jakości

Uruchom najmniejszy kontekst, który wspiera Twój test i zachowaj domyślny cache wysokiej precyzji. Zapisz wyjścia z zadań odzyskiwania, edycji kodu, selekcji narzędzi i długich instrukcji.

Ta baza powie Ci, czy późniejsze błędy pochodzą z kwantyzacji cache’a. Bez niej, problem szablonu czatu lub słaby model łatwo mogą zostać obwinione za KV Q4.

Krok 3: Przejdź na Q8 lub FP8

Włącz Flash Attention, gdzie wymagane, wybierz Q8_0 w llama.cpp lub Ollama, lub wspierany tryb FP8 w vLLM. Powtórz te same prompty na tych samych głębokościach tokenów i potwierdź, że log pokazuje zamierzony typ cache’a.

Dla wielu wdrożeń 16 GB, to przydatny punkt zatrzymania. Osiąga to około podwojenia surowej pojemności KV, nie czyniąc kompresji cache’a najbardziej agresywną kwantyzacją w stacku.

Krok 4: Podnoś kontekst stopniowo

Testuj 32K, 64K, 96K i 128K, zamiast skakać bezpośrednio do reklamowanego maksimum. Na każdym etapie zarejestruj tokeny na sekundę przetwarzania promptu, tokeny na sekundę dekodowania, szczytowe VRAM i czy dowody blisko początku mogą nadal być odzyskane.

Dekodowanie długiego kontekstu często spowalnia nawet po tym, jak pamięć się mieści, ponieważ uwaga czyta więcej zbuforowanego stanu. Pojemność i wydajność to osie oddzielone.

Krok 5: Stroij pamięć przejściową

Jeśli awaria następuje podczas prefillu, a nie inicjalizacji, zmniejsz mikro-paczkę lub maksymalną liczbę paczkowanych tokenów. Jeśli awaria następuje tylko przy jednoczesnych żądaniach, zmniejsz równoległość sekwencji lub sloty równoległe.

Dopiero po zrozumieniu tych kontroli, spróbuj mieszanych cache’y Q8/Q4, pełnego cache’a Q4, offloadu na CPU lub forka adaptacyjnego. Zachowaj uruchomienie upstream Q8 jako bazę porównawczą. Jeśli później dodasz dekodowanie spekulatywne lub MTP, pamiętaj, że jego bufory szkiców to kolejny wiersz w równaniu budżetu, a nie darmowa prędkość — przewodnik o dekodowaniu spekulatywnym obejmuje mechanikę i ich koszt VRAM, a mój benchmark Qwen 3.6 27B i 35B MTP vs Standard pokazuje dokładnie, ile kontekstu dodatkowy stan głowy MTP może kosztować na karcie 16 GB.

Co zapisywać w benchmarku długiego kontekstu

Pojedyncza liczba tokens/s ukrywa dokładnie ten problem, który ten artykuł próbuje rozwiązać. Testowanie długiego kontekstu powinno zachować wystarczająco szczegółów, aby inny operator mógł odtworzyć granicę pamięci.

Pole Dlaczego to ważne
GPU i użyteczne VRAM Użycie wyświetlacza i inne procesy zmieniają budżet
Wersja silnika lub commit Zachowanie cache’a i flagi ewoluują szybko
Wersja sterownika, CUDA, ROCm lub Vulkan Determinuje zachowanie backendu i jądra
Dokładny model i kwantyzacja wag Definiuje rezydencję wag i architekturę
Typy cache K i V Definiuje rozmiar trwałego cache’a i ryzyko jakości
Pojemność kontekstu i głębokość promptu Alokacja to nie to samo co faktyczna głębokość
Równoległe sekwencje Mnoży lub współdzieli popyt na cache
Batch i mikro-batch Wpływa na szczyty prefillu i prędkość
Prędkość przetwarzania promptu Ujawnia użyteczność długiego prefillu
Prędkość dekodowania na każdej głębokości Ujawnia spowolnienie z powodu przepustowości cache
Szczytowe VRAM i offload na CPU Rozróżnia dopasowanie od wylania
Wynik jakości długiego kontekstu Wykrywa kompresję lub awarie pozycji

Używaj próbkowania nvidia-smi lub równoważnego narzędzia vendor podczas prefillu i dekodowania. Raport alokacji silnika jest konieczny, ale szczytowa pamięć urządzenia podczas prawdziwego promptu to liczba, która decyduje o stabilności.

Powszechne błędy cache’a KV na GPU 16 GB

Traktowanie wsparcia 128K jako obietnicy sprzętu

Pole kontekstu w konfiguracji modelu to architektoniczny sufit. Mówi nic o pamięci pozostawionej po załadowaniu określonej kwantyzacji na określonym silniku.

Oblicz cache i zweryfikuj runtime. Kontekst w rozmiarze marketingowym bez budżetu VRAM to po prostu odłożone OOM do pierwszego poważnego promptu.

Kwantyzowanie wag, ale zapomnienie o KV

GGUF 4-bitowe zmniejsza wagi modelu, a nie cache KV F16. Przy długim kontekście cache może wymazać całą oszczędność i ostatecznie przekroczyć odział wag.

Raportuj obie kwantyzacje. Model Q4_K_M, KV Q8_0 ma znaczenie; model 4-bit jest niekompletny.

Zakładanie, że Paged Attention kompresuje tokeny

Stronicowanie poprawia zachowanie alokacji i współdzielenia. Nie zmienia precyzji tensorów ani nie usuwa stanu KV wymaganej przez jedną unikalną sekwencję.

Używaj alokacji stronicowej, aby efektywnie serwować zmienne obciążenia. Używaj precyzji cache, architektury modelu, limitów kontekstu i limitów równoległości, aby kontrolować pojemność.

Zakładanie, że cache prefiksowy pomaga każdemu długiemu promptowi

Cache prefiksowy oszczędza powtarzające się obliczenia prefillu, gdy żądania współdzielą dokładny prefiks. Jednorazowy zrzut repozytorium 100K nie otrzymuje magicznej zniżki pamięci tylko dlatego, że cache prefiksowy jest włączony.

To jest optymalizacja obciążenia, a nie substytut równania budżetu. Mierz stopień trafności (hit rate) i presję zbuforowanego cache’a w serwisowaniu wieloużytkownikowym.

Używanie KV Q4 bez testu jakości

Cache niskobitowe może zawieść subtelnie. Model wciąż pisze płynny tekst, ale uwaga na odległych dowodach, dokładnych nazwach, argumentach narzędzi lub zależnościach kodu może się pogorszyć — i jak badania o rozbieżności tokenów powyżej pokazują, nawet „bezpieczne” ustawienie Q8_0 nie jest gwarantowane, że odtworzy dokładne wyjście F16 przy dekodowaniu deterministycznym, tylko zachować dokładność w agregacie.

Testuj zadanie docelowe na docelowej głębokości. Krótkie benchmarki czatu są prawie bezużyteczne dla walidacji cache’a długiego kontekstu.

Zostawianie równoległości na Auto

Silnik może wybrać równoległość, która jest rozsądna dla przepustowości, ale niemożliwa dla Twojego celu długiego kontekstu. Na 16 GB, jedna głęboka sekwencja i kilka krótkich sekwencji to fundamentalnie różne obciążenia.

Ustaw limit jawnie, a następnie podnoś go ze zmierzonym ruchem. W przeciwnym razie drugie żądanie może przekształcić stabilną konfigurację 64K w zaskoczenie alokacji lub opóźnienia.

Zalecane profile 16 GB

Te profile to punkty startowe, a nie uniwersalne presety. Model z nietypową geometrią KV — w szczególności projekt MLA lub hybrydowy ze ślizgającym się oknem — może być znacznie tańszy lub droższy niż konwencjonalny przykład GQA.

Interaktywny agent kodujący

Użyj jednej sekwencji, kontekstu 48K do 64K, cache Q8, Flash Attention i pełnej rezydencji wag na GPU, jeśli to możliwe. Ten profil faworyzuje przewidywalne opóźnienie i dobrą precyzję cache’a ponad imponujące, ale rzadko użyteczne maksimum.

Włącz ponowne użycie prefiksu, jeśli silnik to wspiera, ponieważ tury kodowania często współdzielą duży prefiks repozytorium lub rozmowy. Nadal kompaktuj wyjście narzędzi i stare transkrypcje; inżynieria cache’a nie czyni nieistotnych tokenów wartościowymi.

Analiza długich dokumentów

Użyj mniejszego modelu z pojemnością 64K do 128K, cache Q8 lub skalibrowanego FP8 i cache’ingu z powtarzanym prefiksem, gdy wiele pytań celuje w ten sam dokument. Mierz czas do pierwszego tokenu, ponieważ prefill może dominować nawet, gdy dekodowanie pozostaje akceptowalne.

Jeśli zostanie zadane tylko jedno pytanie, odzyskiwanie (retrieval) lub podsumowanie kawałkami może być szybsze i bardziej niezawodne niż zmuszanie całego korpusu przez kartę 16 GB. Długi kontekst to narzędzie, a nie substytut architektury informacji.

Mały serwer wieloużytkownikowy

Ogranicz kontekst per żądanie i całkowitą liczbę aktywnych sekwencji, zamiast reklamować maksimum modelu każdemu klientowi. Alokacja stronicowa vLLM jest tu użyteczna, podczas gdy Ollama i llama.cpp również wymagają wyraźnej uwagi do agregowanych tokenów w ruchu.

Preferuj kolejkowanie nad niekontrolowanym wylaniem. Wolniejsza polityka przyjęcia jest mniej szkodliwa niż to, że każde żądanie nagle przekracza PCIe podczas dekodowania.

Ostateczna rekomendacja dla długiego kontekstu na 16 GB

Dla długiego kontekstu na 16 GB, KV Q8 i jedna aktywna sekwencja to właściwa baza. Ujawniają one prawdziwy limit, nie czyniąc jednocześnie jakości cache’a niskobitowego, alokacji równoległej i opóźnienia offloadu zawieszającymi się jednocześnie.

Obliczaj z geometrii uwagi, odejmij wagi i narzut runtime, a następnie potwierdź wynik w logach silnika i pomiarach szczytowej pamięci. Jeśli 128K wciąż się nie mieści, mniejszy model jest często najczystszą optymalizacją; jeśli mieści się, ale pełza, zmniejszenie kontekstu jest często uczciwe.

Uwaga stronicowa, cache prefiksowy, ślizgające się okna i precyzja adaptacyjna rozwiązują użyteczne, ale różne problemy. Wygrywającym zestawieniem jest to, które pozostaje na GPU, poprawnie odzyskuje stare dowody i podtrzymuje akceptowalną prędkość dekodowania na głębokości kontekstu, którą faktycznie używasz.

Referencje

Subskrybuj

Otrzymuj nowe wpisy o systemach, infrastrukturze i inżynierii AI.