<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>KV-Cache — Inferownia</title><link>https://inferownia.pl/tags/kv-cache/</link><description>Inferownia — polski hub wiedzy o lokalnym AI: uruchamianie modeli językowych na własnym sprzęcie, kwantyzacja, inferencja, małe modele, fine-tuning i RAG.</description><language>pl-PL</language><copyright>&#169; 2026 Inferownia</copyright><lastBuildDate>Sat, 18 Jul 2026 00:00:00 +0200</lastBuildDate><atom:link href="https://inferownia.pl/tags/kv-cache/index.xml" rel="self" type="application/rss+xml"/><item><title>llama.cpp: flagi, które naprawdę musisz znać</title><link>https://inferownia.pl/poradniki/flagi-llama-cpp/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0200</pubDate><category>inferencja</category><guid>https://inferownia.pl/poradniki/flagi-llama-cpp/</guid><description>Wpisujesz llama-cli --help i dostajesz ścianą flag prosto w twarz. Rozbieramy te, które realnie decydują, czy model wejdzie na twój sprzęt i jak szybko będzie gadał.</description><content:encoded><![CDATA[<p>Wpisałeś kiedyś <code>llama-cli --help</code> i przez chwilę pomyślałeś, że terminal się zawiesił?
Zjechało z ekranu jakieś sto flag, jedna pod drugą, każda z krótkim opisem po angielsku,
a człowiek siedzi jak przed instrukcją do mebli z drugiej ręki — niby wszystko po kolei,
a i tak nie wiadomo, od czego zacząć. Łatwo wtedy zamknąć to okno i wrócić do klikania
w Ollamie, bo Ollama „po prostu działa&quot;.</p>
<p>I działa — do momentu, w którym chcesz coś, czego domyślnie nie dostajesz. Więcej kontekstu.
Model wciśnięty w kartę, która teoretycznie jest za mała. Serwer na całą sieć domową, bez
wysyłania czegokolwiek na zewnątrz. Wtedy schodzisz piętro niżej, do <code>llama.cpp</code> — silnika
w czystym C/C++, na którym pół tego lokalnego światka stoi (Ollama, LM Studio, KoboldCpp —
wszystkie mają go gdzieś pod maską). A tam rządzą flagi. I dobra wiadomość jest taka:
z tej setki na co dzień dotykasz może dziesięciu.</p>
<h2 id="zanim-ruszysz-dwie-binarki-nie-jedna">Zanim ruszysz: dwie binarki, nie jedna</h2>
<p>Kiedyś było <code>main</code>. Dziś, po sprzątaniu w repo, masz dwa najważniejsze narzędzia:
<code>llama-cli</code> (rozmowa w terminalu, testy, skrypty) i <code>llama-server</code> (serwer z web UI i API
kompatybilnym z OpenAI). Flagi w dużej mierze się pokrywają, więc to, co niżej, działa
w obu — chyba że zaznaczę inaczej.</p>
<p>Model podajesz jednym parametrem, <code>-m</code> (od <em>model</em>). Reszta to strojenie:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">llama-cli -m qwen2.5-7b-instruct-q4_k_m.gguf -cnv
</span></span></code></pre></div><p><code>-cnv</code> (od <em>conversation</em>) włącza tryb czatu, zamiast jednorazowego dokończenia promptu.
Do tego <code>-sys &quot;jesteś zwięzłym asystentem&quot;</code> ustawia prompt systemowy, a <code>-p &quot;...&quot;</code> podaje
zwykły prompt. Tyle wystarczy, żeby cokolwiek gadało. Tyle że domyślne ustawienia rzadko
są skrojone pod twoją konkretną kartę — więc przechodzimy do sedna.</p>
<h2 id="żeby-model-wszedł-do-karty">Żeby model wszedł do karty</h2>
<p>Tu toczy się cała gra, bo VRAM (pamięć karty) to waluta, której nigdy nie masz dość.
Cztery flagi, które robią 90% roboty:</p>
<ul>
<li><strong><code>-ngl N</code></strong> (<em>gpu-layers</em>) — ile warstw modelu wrzucić na GPU. To najważniejsza flaga
w całym zestawie. Przyjmuje liczbę albo słowo: <code>auto</code> (domyślnie — <code>llama.cpp</code> sam dobiera,
ile się zmieści) oraz <code>all</code> (wrzuć wszystko, co się da). Chcesz maksimum na karcie?
<code>-ngl all</code>. Za mało VRAM-u na cały model? Podaj mniejszą liczbę, np. <code>-ngl 20</code> — reszta
poleci na CPU. Mieszany offload jest wolniejszy, ale często to jedyny sposób, żeby
większy model w ogóle ruszył.</li>
<li><strong><code>-c N</code></strong> (<em>ctx-size</em>) — okno kontekstu w tokenach. Domyślnie <code>llama.cpp</code> bierze rozsądną
wartość, ale to właśnie kontekst potrafi po cichu zjeść ci pamięć. Podwoisz <code>-c</code> z 4096 na
8192 i nagle brakuje pół giga VRAM-u, którego przed chwilą było w sam raz. <code>-c 0</code> mówi
„weź maksimum z modelu&quot; — kuszące, ale sprawdź wcześniej, czy masz na to pamięć.</li>
<li><strong><code>-fa on</code></strong> (<em>flash attention</em>) — zoptymalizowana uwaga: mniej pamięci na KV-cache
(bufor, w którym model trzyma przetworzony kontekst) i zwykle ciut szybciej. Flaga
przyjmuje <code>on</code>/<code>off</code>/<code>auto</code>, a domyślnie stoi na <code>auto</code> (to <code>llama.cpp</code> decyduje). Chcesz
mieć pewność, że działa na nowszej karcie — wal <code>-fa on</code>.</li>
<li><strong><code>-ctk</code> / <code>-ctv</code></strong> (<em>cache-type-k / -v</em>) — kwantyzacja samego KV-cache, np. <code>q8_0</code>
albo <code>q4_0</code>. Przy długim kontekście to on, nie wagi, robi się największym żłobem na pamięć.
Ściśnięcie go do <code>q8_0</code> odzyskuje sporo VRAM-u niemal bez odczuwalnej straty. Najlepiej
razem z <code>-fa on</code>.</li>
</ul>
<div class="callout">
  <span class="callout__label">Dlaczego to ważne dla lokalnego LLM</span>
<p>Kolejność ratowania, gdy „nie wchodzi&quot;: najpierw <code>-fa on</code>, potem <code>-ctk q8_0 -ctv q8_0</code>,
dopiero na końcu tnij <code>-c</code> albo schodź z <code>-ngl</code>. Pierwsze dwa kosztują cię grosze na
jakości, a potrafią odzyskać na tyle VRAM-u, że model klasy 7B–8B wejdzie na kartę
12 GB z długim kontekstem, zamiast dławić się na starcie.</p>
</div>
<h2 id="żeby-było-szybciej">Żeby było szybciej</h2>
<p>Zmieściło się? To teraz o prędkość:</p>
<ul>
<li><strong><code>-t N</code></strong> (<em>threads</em>) — ile wątków CPU. Ustaw na liczbę <strong>fizycznych</strong> rdzeni, nie
logicznych. Więcej nie znaczy szybciej — powyżej liczby rdzeni zaczynasz sobie tylko
przeszkadzać. Liczy się głównie wtedy, gdy część modelu (albo całość) siedzi na CPU.</li>
<li><strong><code>-b</code> / <code>-ub</code></strong> (<em>batch-size / ubatch-size</em>) — jak duże porcje tokenów przetwarza model
naraz przy „czytaniu&quot; promptu. Domyślne wartości są sensowne; ruszaj je, dopiero jak
świadomie walczysz o przepustowość przy długich promptach.</li>
<li><strong><code>--mlock</code></strong> — przypnij model w RAM, żeby system nie wyrzucił go do swapu w najmniej
odpowiednim momencie. <strong><code>--no-mmap</code></strong> ładuje cały plik do pamięci z góry (przydatne na
dziwnych dyskach sieciowych). Na co dzień rzadko potrzebne, ale warto wiedzieć, że są.</li>
<li><strong><code>-ts</code> / <code>-sm</code></strong> (<em>tensor-split / split-mode</em>) — masz dwie karty? <code>-ts 3,1</code> rozłoży
model w proporcji 3:1, a <code>-sm</code> decyduje, jak dokładnie. Temat na osobny wpis, ale niech
ci mignie, że wielo-GPU to nie czarna magia.</li>
</ul>
<h2 id="żeby-generacja-miała-ręce-i-nogi">Żeby generacja miała ręce i nogi</h2>
<p>Te flagi nie dotykają pamięci — sterują tym, <em>jak</em> model dobiera słowa:</p>
<table>
	<thead>
			<tr>
					<th>Flaga</th>
					<th>Co robi</th>
					<th>Bezpieczny start</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>--temp</code></td>
					<td>„temperatura&quot; — im wyżej, tym bardziej kreatywnie (i chaotycznie)</td>
					<td><code>0.7</code></td>
			</tr>
			<tr>
					<td><code>--top-p</code></td>
					<td>odcina mało prawdopodobny ogon słów (nucleus)</td>
					<td><code>0.9</code></td>
			</tr>
			<tr>
					<td><code>--min-p</code></td>
					<td>nowsza, często lepsza alternatywa dla top-p</td>
					<td><code>0.05</code></td>
			</tr>
			<tr>
					<td><code>--repeat-penalty</code></td>
					<td>kara za powtórki, gdy model się zacina w kółko</td>
					<td><code>1.1</code></td>
			</tr>
			<tr>
					<td><code>-n N</code></td>
					<td>ile tokenów wygenerować (<code>-1</code> = bez limitu)</td>
					<td><code>-1</code></td>
			</tr>
	</tbody>
</table>
<p>Jak model gada od rzeczy — zbij <code>--temp</code>. Jak mieli w kółko to samo — podnieś
<code>--repeat-penalty</code> o włos. Bez fanatyzmu, to pokrętła, nie wyrocznia.</p>
<h2 id="serwer-czyli-twój-prywatny-endpoint">Serwer, czyli twój prywatny endpoint</h2>
<p>Najlepszy patent na co dzień: odpal <code>llama-server</code> raz i gadaj z modelem z przeglądarki
albo z kodu, przez API w formacie OpenAI — wszystko lokalnie, offline.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">llama-server -m qwen2.5-7b-instruct-q4_k_m.gguf <span class="se">\
</span></span></span><span class="line"><span class="cl">  -ngl all -c <span class="m">8192</span> -fa on <span class="se">\
</span></span></span><span class="line"><span class="cl">  --host 0.0.0.0 --port <span class="m">8080</span>
</span></span></code></pre></div><p>Wchodzisz na <code>http://localhost:8080</code> i masz web UI od strzału. <code>--host 0.0.0.0</code> wystawia
serwer na całą sieć domową (laptop w kuchni gada z kartą w gabinecie — klasyk domowego setupu).
<code>-np N</code> (<em>parallel</em>) obsłuży kilka rozmów naraz, a <code>-cb</code> (<em>continuous batching</em>) sensownie
je poupycha. <code>--jinja</code> każe użyć szablonu czatu wbudowanego w model — przydaje się, gdy
odpowiedzi wyglądają, jakby model nie wiedział, że rozmawia.</p>
<h2 id="zrób-to-sam">Zrób to sam</h2>
<p>Masz kartę 8 GB i model 7B w Q4? Zacznij od tego i patrz na zużycie VRAM:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">llama-cli -m qwen2.5-7b-instruct-q4_k_m.gguf -ngl all -c <span class="m">4096</span> -fa on -cnv
</span></span></code></pre></div><p>Nie wchodzi? Dorzuć ściśnięty cache i przytnij okno:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">llama-cli -m qwen2.5-7b-instruct-q4_k_m.gguf <span class="se">\
</span></span></span><span class="line"><span class="cl">  -ngl all -c <span class="m">4096</span> -fa on -ctk q8_0 -ctv q8_0 -cnv
</span></span></code></pre></div><p>Modele w formacie GGUF ściągniesz z Hugging Face (szukaj wariantów <code>Q4_K_M</code> — dobry
kompromis rozmiar/jakość). Kod, aktualne flagi i instrukcje budowania są w oficjalnym
repo: <a href="https://github.com/ggml-org/llama.cpp">github.com/ggml-org/llama.cpp</a>. A pełną,
zawsze aktualną listę wywołasz tym, od czego zaczęliśmy — <code>llama-cli --help</code>. Tylko teraz
ta ściana nie będzie już ścianą, a tablicą z pokrętłami, z których wiesz, które przekręcić.</p>
<blockquote>
<p>Wartości domyślne w <code>llama.cpp</code> bywają dobre, ale zmieniają się z wersją. Jak coś
zaczyna działać dziwnie po aktualizacji — najpierw zajrzyj, czy flaga, na której
polegasz, nie dostała nowego domyślnego zachowania.</p>
</blockquote>
<p>Bo cała zabawa w lokalnym AI sprowadza się właśnie do tego jednego uczucia: siedzisz przed
własnym sprzętem, przekręcasz pokrętło o jeden klik i patrzysz, jak model, który „się nie
mieścił&quot;, nagle mruczy pod biurkiem jak trzeba. Nikt ci nie dyktuje, ile masz mu dać
kontekstu ani na czym go odpalić. Ty, karta i garść flag.</p>
]]></content:encoded></item><item><title>KV-cache: dlaczego długi kontekst zjada VRAM</title><link>https://inferownia.pl/poradniki/kv-cache-vram/</link><pubDate>Tue, 14 Jul 2026 00:00:00 +0200</pubDate><category>inferencja</category><guid>https://inferownia.pl/poradniki/kv-cache-vram/</guid><description>Wagi się załadowały, model wszedł na kartę z zapasem — a po dłuższej rozmowie znów wyskakuje out of memory. Winny nie jest model. Winny jest bufor, który puchnie z każdym tokenem.</description><content:encoded><![CDATA[<p>Znasz ten moment, kiedy model ładnie wszedł na kartę, zostało ci jeszcze ze dwa giga
zapasu, odpalasz dłuższą rozmowę albo wklejasz wielki plik do streszczenia — i po
kilkuset tokenach terminal wita cię tym samym, co zawsze: <code>CUDA out of memory</code>. Cała
na biało. I człowiek stoi zdziwiony, bo przecież model się mieścił. Wagi się nie
rozrosły. Karta nie zmalała. To co, u licha, zeżarło resztę pamięci między jednym
tokenem a drugim?</p>
<p>To wręcz podręcznikowy scenariusz — ktoś odpala 7B na karcie 8 GB, chwali się, że
działa, a dwa dni później wraca z płaczem, że „przy dłuższym kontekście się sypie&quot;.
I zawsze pada to samo pytanie w komentarzach: a ustawiłeś sobie cache? No właśnie.
Bo obok wag, które raz się ładują i grzecznie leżą, siedzi drugi żłob na VRAM —
taki, który rośnie z każdym słowem, jakie model przeczyta albo napisze. Nazywa się
KV-cache i jest głównym podejrzanym w prawie każdej sprawie „mieściło się, a potem
przestało&quot;.</p>
<h2 id="skąd-w-ogóle-bierze-się-ten-cache">Skąd w ogóle bierze się ten cache</h2>
<p>Cofnijmy się o krok. Model generuje tekst token po tokenie — jeden na raz, a każdy
kolejny patrzy wstecz na całą dotychczasową historię. Żeby policzyć uwagę (attention)
dla nowego tokenu, potrzebuje wektorów <strong>K</strong> (key) i <strong>V</strong> (value) dla wszystkiego,
co było wcześniej. I teraz klucz do zrozumienia: te wektory się nie zmieniają. K i V
dla trzeciego tokenu są takie same, kiedy generujesz token dziesiąty, jak wtedy,
gdy generowałeś czwarty.</p>
<p>Skoro się nie zmieniają — po co je liczyć od nowa przy każdym kroku? No i nie liczymy.
KV-cache to dokładnie ten bufor, w którym model odkłada raz wyliczone wektory K i V
dla każdego przetworzonego tokenu, w każdej warstwie, żeby przy generowaniu następnego
nie przemielać całej historii od zera.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> To sprytny handel: dokładasz pamięci,
żeby oszczędzić obliczenia. Bez niego każdy nowy token oznaczałby przeliczanie całego
kontekstu w kółko — decode zamieniłby się w mękę.</p>
<p>Cache jest więc dobry. Problem w tym, że za oszczędność obliczeń płacisz pamięcią.
A ta pamięć nie jest stała.</p>
<h2 id="dlaczego-on-rośnie-a-wagi-nie">Dlaczego on rośnie, a wagi nie</h2>
<p>Tu jest sedno całej sprawy, więc rozłóżmy to powoli. Wagi modelu to wartość stała.
Ładujesz plik GGUF raz, siada na karcie i tyle — czy generujesz jeden token, czy
dziesięć tysięcy, wagi zajmują dokładnie tyle samo. KV-cache zachowuje się odwrotnie:
puchnie z każdym tokenem, jaki wpadnie w okno kontekstu.<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>Ile dokładnie puchnie? Mechanizm da się złapać jednym wzorem, który krąży po całej
scenie:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">rozmiar_KV = 2 × n_layer × n_kv_heads × head_dim × długość_kontekstu × bajty_na_element
</span></span></code></pre></div><p>Rozbierzmy to na palcach. Dwójka na przedzie to osobno tensor K i osobno tensor V —
trzymasz dwa, stąd współczynnik.<sup id="fnref2:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> <code>n_layer</code> to liczba warstw transformera; cache
odkłada się w każdej z osobna. <code>n_kv_heads</code> to liczba głów KV, <code>head_dim</code> to wymiar
jednej głowy. A <code>długość_kontekstu</code> — no i tu jest pies pogrzebany: to jedyny człon,
który rośnie w trakcie. Reszta to stałe architektury danego modelu.</p>
<p>Zauważ, co z tego wynika: rozmiar cache rośnie <strong>liniowo</strong> z długością kontekstu.
Podwoisz kontekst — podwoisz cache. I liniowo z liczbą warstw oraz głów KV. Ani razu
w tym wzorze nie pada rozmiar wag.<sup id="fnref3:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> To dwa niezależne budżety pamięci.</p>
<div class="callout">
  <span class="callout__label">Dlaczego to ważne dla lokalnego LLM</span>
<p>Częsty mit z forów: „KV-cache rośnie kwadratowo z kontekstem&quot;. Nie. <strong>Sam bufor rośnie
liniowo</strong> — podwajasz tokeny, podwajasz cache. Kwadratowy jest koszt <em>obliczeniowy</em>
samej operacji attention w naiwnej implementacji (i pamięć pośrednia, którą ona
zżera po drodze) — i to właśnie ten kwadratowy narzut łagodzi flash attention. Pamięć
samego cache to prosta linia w górę.<sup id="fnref4:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
</div>
<p>Skoro cache rośnie z kontekstem, a wagi stoją w miejscu, to przy dostatecznie długim
oknie następuje przecięcie: KV-cache zaczyna dominować zużycie VRAM, a przy naprawdę
długim kontekście potrafi przerosnąć rozmiar samych wag.<sup id="fnref5:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> I stąd twoje <code>out of memory</code> w połowie rozmowy — nie model spuchł, tylko jego pamięć krótkotrwała napęczniała
do rozmiarów, których karta nie udźwignęła. Jeśli chcesz policzyć ten budżet z wyprzedzeniem,
zamiast zgadywać, sam wzór na VRAM rozkładaliśmy krok po kroku <a href="/poradniki/ile-vram-na-model/">tutaj</a>.</p>
<h2 id="jak-go-ścisnąć--trzy-dźwignie">Jak go ścisnąć — trzy dźwignie</h2>
<p>Dobra wiadomość: skoro wiesz, z czego składa się wzór, wiesz też, gdzie przyłożyć.
Masz trzy dźwignie, każda ciągnie za inny człon.</p>
<h3 id="kwantyzacja-cache--najprostszy-zysk">Kwantyzacja cache — najprostszy zysk</h3>
<p>Spójrz na ostatni człon wzoru: <code>bajty_na_element</code>. Domyślnie cache K i V w llama.cpp
siedzą w <code>f16</code>, czyli po 2 bajty na element<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> — a czym <code>f16</code> różni się od <code>bf16</code>, tłumaczymy <a href="/poradniki/bf16-f16-f32/">tutaj</a>. A nikt nie powiedział, że musi
tak zostać. <code>llama.cpp</code> daje dwie flagi — <code>-ctk</code> (<code>--cache-type-k</code>) i <code>-ctv</code>
(<code>--cache-type-v</code>) — którymi ustawiasz typ danych osobno dla cache K i cache V.
Dozwolone wartości to <code>f32</code>, <code>f16</code>, <code>bf16</code>, <code>q8_0</code>, <code>q4_0</code>, <code>q4_1</code>, <code>iq4_nl</code>, <code>q5_0</code>,
<code>q5_1</code>; domyślnie oba stoją na <code>f16</code>.<sup id="fnref1:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<p>Matematyka jest tu boleśnie prosta. <code>q8_0</code> to 1 bajt na element zamiast dwóch — czyli
z grubsza <strong>dwa razy mniej</strong> pamięci na cache. <code>q4_0</code> to pół bajta — jakieś <strong>cztery
razy mniej</strong> niż <code>f16</code>.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> Nie podam ci, ile to konkretnie megabajtów, bo to zależy
od <code>n_layer</code>, <code>n_kv_heads</code>, <code>head_dim</code> i długości kontekstu twojego modelu — kto rzuca
sztywnym „zaoszczędzisz 3 GB&quot;, ten zgaduje. Ale proporcja trzyma się zawsze: ścinasz
typ, ścinasz cache w tym samym stosunku.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">llama-server -m model-q4_k_m.gguf -c <span class="m">8192</span> -fa on -ctk q8_0 -ctv q8_0
</span></span></code></pre></div><p>I tu ważne rozróżnienie, na którym potyka się pół forum: <strong>to nie jest to samo, co
kwantyzacja wag</strong>. Wagi kwantyzujesz przy pakowaniu GGUF-a (te wszystkie <code>Q4_K_M</code>
i spółka — nazewnictwo rozbieraliśmy <a href="/poradniki/nazwy-kwantyzacji-gguf/">tutaj</a>).
Cache kwantyzujesz flagami runtime, w locie. To dwa niezależne mechanizmy. Ściśnięcie
wag do 4 bitów ani o bajt nie zmniejszy KV-cache, i odwrotnie. Ktoś odpala model
w <code>Q4_K_M</code> i dziwi się, że kontekst dalej zjada pamięć — no bo cache dalej siedzi
w <code>f16</code>, dopóki mu tego ręcznie nie zmienisz.</p>
<h3 id="flash-attention--i-warunek-wstępny">Flash attention — i warunek wstępny</h3>
<p>Druga dźwignia to <code>-fa</code> (<code>--flash-attn</code>). Flaga przyjmuje <code>on</code>, <code>off</code> albo <code>auto</code>,
domyślnie stoi na <code>auto</code>.<sup id="fnref2:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> Co robi flash attention? To nie żadne przybliżenie
uwagi — i tu kolejna pułapka do ominięcia. FlashAttention (Dao i in., NeurIPS 2022)
to algorytm <strong>dokładny</strong> (exact): liczy dokładnie to samo, co naiwna uwaga, wynik
liczbowy jest identyczny.<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> Sztuczka jest gdzie indziej — jest „IO-aware&quot;, czyli
ogranicza liczbę odczytów i zapisów między wolną pamięcią HBM karty a szybką pamięcią
on-chip SRAM. Efekt: szybciej i z mniejszym narzutem pamięci pomocniczej niż naiwna
implementacja uwagi.</p>
<p>Ale flash attention ma tu jeszcze jedną, praktyczną rolę — jest <strong>warunkiem wstępnym</strong>
kwantyzacji cache V. W llama.cpp nie da się skwantyzować cache V bez włączonego flash
attention; implementacja zwyczajnie odmawia startu, rzucając runtime error w rodzaju
<code>quantized V cache was requested, but this requires Flash Attention</code>.<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> Czyli jeśli chcesz <code>-ctv q8_0</code>, musisz najpierw dać <code>-fa on</code>.
Bez tego dostaniesz błąd, nie oszczędność.</p>
<h3 id="gqa--dźwignia-której-nie-przełączysz">GQA — dźwignia, której nie przełączysz</h3>
<p>Trzeci człon wzoru to <code>n_kv_heads</code>. Im mniej głów KV, tym mniejszy cache — wprost,
liniowo. I dokładnie na to celuje <strong>Grouped-Query Attention</strong> (GQA, Ainslie i in.,
EMNLP 2023).<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> Idea: zamiast tylu głów KV, ile jest głów zapytań (pełna uwaga
wielogłowowa, MHA), albo tylko jednej wspólnej (multi-query attention, MQA), bierzesz
liczbę pośrednią. Kilka głów zapytań dzieli jedną głowę KV. Skoro <code>n_kv_heads</code> w naszym
wzorze spada — spada i cache, przy jakości bliskiej pełnemu MHA i szybkości bliskiej
MQA.<sup id="fnref1:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup></p>
<p>Co ciekawe, model GQA nie powstaje od zera. Bierze się istniejący checkpoint MHA
i „doucza&quot; (uptraining) przy jakichś 5% oryginalnego budżetu treningu — tanio, bez
budowania nowej architektury od podstaw.<sup id="fnref2:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup></p>
<p>Jest tylko jeden haczyk, o którym musisz pamiętać: <strong>GQA to nie przełącznik</strong>.
To cecha architektury zapieczona w modelu podczas treningu. Nie ma flagi CLI, która
„włączy GQA&quot; na dowolnym modelu. Możesz co najwyżej <em>wybrać</em> model, który już ma
GQA — a większość nowszych 7B/8B ma — i taki z natury nosi mniejszy cache przy tej
samej długości kontekstu co stary model z pełnym MHA. To kryterium przy pobieraniu,
nie pokrętło w terminalu.</p>
<h2 id="zrób-to-sam-kolejność-ratowania-pamięci">Zrób to sam: kolejność ratowania pamięci</h2>
<p>No dobra, dość teorii — masz <code>out of memory</code> i chcesz uratować kontekst. Kolejność
ma znaczenie, bo pierwsze ruchy kosztują cię grosze na jakości, a ostatnie bolą.</p>
<ol>
<li><strong>Najpierw <code>-fa on</code>.</strong> Ścina narzut pamięci samej operacji uwagi i — co równie
ważne — odblokowuje kwantyzację cache V. To krok zero, nie negocjujemy.<sup id="fnref1:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup></li>
<li><strong>Potem skwantyzuj cache: <code>-ctk q8_0 -ctv q8_0</code>.</strong> <code>q8_0</code> to bezpieczny wybór —
połowa pamięci cache, strata jakości ledwo wyczuwalna. Dopiero gdy VRAM dalej
piszczy, schodź do <code>-ctk q4_0 -ctv q4_0</code> — ćwiartka pamięci, ale za to już płacisz
odczuwalniej jakością.<sup id="fnref1:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></li>
<li><strong>Wybierając model, celuj w te z GQA.</strong> Tego nie zrobisz flagą po fakcie — to
decyzja przy pobieraniu. Model z GQA startuje z mniejszym cache, zanim jeszcze
dotkniesz jakiegokolwiek przełącznika.<sup id="fnref3:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup></li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># krok 1 + 2 razem — tak wygląda ratunek w praktyce</span>
</span></span><span class="line"><span class="cl">llama-server -m model-q4_k_m.gguf -c <span class="m">16384</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -fa on -ctk q8_0 -ctv q8_0
</span></span></code></pre></div><p>Dopiero kiedy to wszystko wyczerpiesz, a i tak nie wchodzi — wtedy tnij samą długość
kontekstu (<code>-c</code>) albo schodź z warstw na GPU (<code>-ngl</code>). Ale to już ostatnia deska
ratunku, bo pierwsze odbiera modelowi pamięć, a drugie prędkość. Sam zestaw flag i to, co która
robi, przeszliśmy po kolei w <a href="/poradniki/flagi-llama-cpp/">osobnym wpisie o flagach llama.cpp</a>.</p>
<p>Pomyśl o tym tak. Wagi modelu to biblioteka — stoi w regale, zajmuje swoje półki
i tyle, nieważne, ile razy do niej zajrzysz. KV-cache to biurko, na którym rozkładasz
wszystko, co akurat czytasz. Im dłużej pracujesz nad jednym tematem, tym więcej kartek
ląduje na blacie — i w pewnym momencie nie ma gdzie postawić kubka, choć regał ani
drgnął. Kwantyzacja cache to składanie tych kartek na pół, flash attention to
sprzątanie w locie, GQA to od razu mniejszy stos notatek na tę samą robotę.</p>
<p>Karta się nie skurczyła. To po prostu twoja rozmowa urosła — a teraz już wiesz,
który człon wzoru za to odpowiada i za które pokrętło pociągnąć, zanim znów zobaczysz
to <code>out of memory</code>.</p>
<h2 id="przypisy--źródła">Przypisy · źródła</h2>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Mechanizm KV-cache: bufor par klucz-wartość liczonych raz na token i warstwę, o rozmiarze <code>2 × n_layer × n_kv_heads × head_dim × długość_kontekstu × bajty_na_element</code> — rośnie liniowo z kontekstem, niezależnie od rozmiaru wag; kwadratowy jest jedynie koszt obliczeniowy naiwnej uwagi. Wyprowadzenie mechanizmu spójne z definicją flash attention w FlashAttention (Dao i in.), <a href="https://arxiv.org/abs/2205.14135">arXiv:2205.14135</a>.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref3:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref4:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref5:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>llama.cpp, definicje flag <code>-ctk</code>/<code>--cache-type-k</code>, <code>-ctv</code>/<code>--cache-type-v</code> (dozwolone typy: <code>f32</code>, <code>f16</code>, <code>bf16</code>, <code>q8_0</code>, <code>q4_0</code>, <code>q4_1</code>, <code>iq4_nl</code>, <code>q5_0</code>, <code>q5_1</code>; domyślnie <code>f16</code>) oraz <code>-fa</code>/<code>--flash-attn</code> (<code>on</code>/<code>off</code>/<code>auto</code>, domyślnie <code>auto</code>), <a href="https://github.com/ggml-org/llama.cpp/blob/master/common/arg.cpp">common/arg.cpp</a>. Te same parametry w <a href="https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md">dokumentacji serwera</a>.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><code>q8_0</code> = 1 bajt na element (ok. 2× mniej niż <code>f16</code>), <code>q4_0</code> = pół bajta (ok. 4× mniej niż <code>f16</code>); dokładne MB zależą od <code>n_layer</code>, <code>n_kv_heads</code>, <code>head_dim</code> i długości kontekstu konkretnego modelu. Typy zdefiniowane w llama.cpp, <a href="https://github.com/ggml-org/llama.cpp/blob/master/common/arg.cpp">common/arg.cpp</a>.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness (Dao, Fu, Ermon, Rudra, Ré, NeurIPS 2022) — dokładny, IO-aware algorytm uwagi redukujący odczyty/zapisy między HBM a SRAM GPU; nie jest aproksymacją uwagi, <a href="https://arxiv.org/abs/2205.14135">arXiv:2205.14135</a>.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Zależność wymuszona w kodzie llama.cpp: przy wyłączonym flash attention skwantyzowany cache V rzuca runtime error <code>quantized V cache was requested, but this requires Flash Attention</code> — sprawdzenie w konstruktorze kontekstu, <a href="https://github.com/ggml-org/llama.cpp/blob/master/src/llama-context.cpp">src/llama-context.cpp</a>.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints (Ainslie i in., EMNLP 2023) — grouped-query attention jako interpolacja między MHA a MQA, redukcja liczby głów KV, uptraining z ok. 5% oryginalnego budżetu treningu, <a href="https://arxiv.org/abs/2305.13245">arXiv:2305.13245</a>.&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref3:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Ile VRAM-u potrzebuje model? Liczymy na palcach</title><link>https://inferownia.pl/poradniki/ile-vram-na-model/</link><pubDate>Sun, 12 Jul 2026 00:00:00 +0200</pubDate><category>inferencja</category><guid>https://inferownia.pl/poradniki/ile-vram-na-model/</guid><description>Nowy model, ładna karta modelu, a na dole jedno zdanie: 16 GB VRAM. Ty masz te swoje 8. Zamiast wróżyć z fusów, policzmy na palcach, ile naprawdę zważy — i jak zejść niżej.</description><content:encoded><![CDATA[<p><code>Wymagania: 16 GB VRAM</code>. Zjeżdżasz na dół karty modelu, widzisz to jedno zdanie, zerkasz na
swoją kartę do gier z jej ośmioma gigabajtami — z czego połowę i tak zżera przeglądarka — i
serce ci lekko siada. A bywa gorzej: czasem nawet tej jednej liczby nie ma, jest za to tabelka
z dwudziestoma wariantami pliku, każdy waży inaczej, i dalej nie wiesz, czy w ogóle podchodzić,
czy zaraz przywita cię „CUDA out of memory&quot;, cała na biało. To codzienność — połowa pytań „will it fit?&quot; to ludzie, którzy kupili
GPU do gier, a teraz próbują wcisnąć na nie model wielkości małego miasta.</p>
<p>Dobra wiadomość: nie musisz zgadywać. VRAM da się policzyć na palcach, z grubsza, w głowie,
zanim cokolwiek ściągniesz. Nie do grama — do grama policzy ci <code>llama.cpp</code> przy starcie —
ale na tyle dokładnie, żeby wiedzieć, czy w ogóle podchodzić. O to cała sztuka:
zamiast wróżyć z rozmiaru pliku, umieć powiedzieć „7B w Q4 to jakieś pięć giga plus
zapas, wejdzie&quot; — i mieć rację.</p>
<h2 id="wzór-na-palcach">Wzór na palcach</h2>
<p>Cały fundament mieści się w jednej linijce:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">rozmiar wag (GB) ≈ liczba parametrów (mld) × bity na wagę / 8
</span></span></code></pre></div><p>I tyle. Bierzesz, ile model ma miliardów parametrów, mnożysz przez to, ile bitów zajmuje
jedna waga w danym formacie, dzielisz przez osiem (bo bajt ma osiem bitów) i masz gramaturę
samych wag w gigabajtach. Model 8B w kwantyzacji Q4, gdzie na wagę schodzi jakieś 4,5 bita?
<code>8 × 4,5 / 8 ≈ 4,5 GB</code>. I to nie jest teoretyczna wróżba — realny plik <code>Q4_K_M</code> dla
Llamy 3.1 8B waży 4,92 GB.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Zgadza się co do joty, a właściwie co do paru setek megabajtów.</p>
<div class="callout">
  <span class="callout__label">Dlaczego to ważne dla lokalnego LLM</span>
<p>To „bity na wagę&quot; (po angielsku <em>bits-per-weight</em>, bpw) to sedno całej zabawy z kwantyzacją.
Surowy model trenuje się zwykle w 16 bitach na wagę. Kwantyzacja to nic innego jak zapisanie
tych samych wag ciaśniej — w 8, 5, 4, a nawet 2 bitach — już po treningu, bez ruszania
architektury. Nie myl tego z trenowaniem od zera w niskiej precyzji; to dwie różne bajki.
Jak działają nazwy w stylu <code>Q4_K_M</code> i skąd te dziwne litery, rozgryzaliśmy osobno
<a href="/poradniki/nazwy-kwantyzacji-gguf/">tutaj</a>.</p>
</div>
<h2 id="skąd-biorą-się-ułamkowe-bity">Skąd biorą się ułamkowe bity</h2>
<p>Tu pierwsza pułapka, na którą łapie się każdy początkujący: „Q4 to przecież cztery bity,
nie?&quot;. No właśnie nie do końca. Gdyby świat był prosty, <code>Q4_0</code> (stary, prościutki typ)
brałby równe 4,5 bita na wagę — bo pakuje 32 wagi w blok i dokłada do nich trochę metadanych
o skali.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> Ale nowsze formaty, tak zwane K-quanty (<code>Q2_K</code> do <code>Q6_K</code>, z dopiskami <code>_S</code>,
<code>_M</code>, <code>_L</code>), są sprytniejsze: mieszają precyzję między różnymi warstwami modelu. Te tensory,
od których jakość naprawdę zależy, dostają więcej bitów, reszcie się skąpi. Efekt? Liczba
bitów na wagę wychodzi ułamkowa.</p>
<p>Konkretnie, dla Llamy 3.1 8B według oficjalnej tabeli <code>llama.cpp</code>:<sup id="fnref1:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<table>
	<thead>
			<tr>
					<th>Format</th>
					<th>Bity na wagę</th>
					<th>Rozmiar (tabela llama.cpp)</th>
					<th>Realny plik GGUF</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>Q4_K_M</code></td>
					<td>4,8944</td>
					<td>4,58 GiB</td>
					<td>4,92 GB</td>
			</tr>
			<tr>
					<td><code>Q5_K_M</code></td>
					<td>5,7036</td>
					<td>5,33 GiB</td>
					<td>5,73 GB</td>
			</tr>
			<tr>
					<td><code>Q6_K</code></td>
					<td>~6,5</td>
					<td>—</td>
					<td>6,60 GB</td>
			</tr>
			<tr>
					<td><code>Q8_0</code></td>
					<td>~8,50</td>
					<td>—</td>
					<td>8,54 GB</td>
			</tr>
			<tr>
					<td><code>F16</code></td>
					<td>16,0005</td>
					<td>14,96 GiB</td>
					<td>—</td>
			</tr>
	</tbody>
</table>
<p>Widzisz tę drobną rozbieżność między kolumnami? Jedna to GiB (liczone po 1024), druga GB
(po 1000), plus K-quant miesza precyzję tensorów — stąd <code>Q4_K_M</code> ma prawie 4,9 bita, a nie
równe 4,5.<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Do liczenia na palcach nic to nie zmienia: bierzesz „mniej więcej 4,5–5
bita dla Q4&quot; i tak trafiasz w wynik z dokładnością do zapasu, który i tak musisz zostawić.
Jest jeszcze cała rodzina IQ-quantów (<code>IQ2_XXS</code>, <code>IQ3_S</code> i spółka), które schodzą poniżej
trzech bitów, ważąc jakość specjalną macierzą istotności — ale to temat na osobny wieczór.<sup id="fnref2:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<h2 id="ile-zważy-ten-twój-model">Ile zważy „ten twój&quot; model</h2>
<p>Przełóżmy wzór na cztery rozmiary, które realnie krążą po lokalnym światku. To są same wagi,
bez narzutu — o narzucie za chwilę:</p>
<table>
	<thead>
			<tr>
					<th>Model</th>
					<th>Q4_K_M</th>
					<th>Q5_K_M</th>
					<th>Q8_0</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Llama 3.1 <strong>8B</strong><sup id="fnref2:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></td>
					<td>4,92 GB</td>
					<td>5,73 GB</td>
					<td>8,54 GB</td>
			</tr>
			<tr>
					<td>Mistral Nemo <strong>12B</strong><sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></td>
					<td>7,48 GB</td>
					<td>8,73 GB</td>
					<td>13,02 GB</td>
			</tr>
			<tr>
					<td>Qwen2.5 <strong>14B</strong><sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup></td>
					<td>8,99 GB</td>
					<td>10,51 GB</td>
					<td>15,70 GB</td>
			</tr>
	</tbody>
</table>
<p>Popatrz na to przez chwilę, bo tu widać całą matematykę życia lokalnego LLM-owca. Masz kartę
8 GB? Ósemka w Q4 wchodzi na luzie, w Q5 już z zadyszką, a Q8 nawet nie podchodź. Dwunastka
w Q4 to 7,5 GB samych wag — teoretycznie „się mieści&quot;, tylko że na sam narzut nie zostaje już
nic. A czternastka w Q4 to prawie dziewięć giga — twoje 8 GB kończy się, zanim się zaczęło.
Dlatego „te swoje 8 GB&quot; znika tak szybko: liczysz gramaturę wag, cieszysz się, że wchodzi,
a potem uruchamiasz i okazuje się, że o czymś zapomniałeś.</p>
<h2 id="czego-zapominasz-kv-cache-i-bufory">Czego zapominasz: KV-cache i bufory</h2>
<p>O to zapominasz. Rozmiar pliku GGUF to tylko wagi. Do tego dochodzi KV-cache — pamięć, w której
model trzyma przetworzone klucze i wartości dla każdego tokena, który model już wygenerował lub
wczytał z promptu. I to jest ten cichy złodziej VRAM-u, bo rośnie <strong>liniowo z długością
kontekstu</strong>. Im dłuższa rozmowa, tym większy żłób na pamięć.</p>
<p>Ile dokładnie? Też się liczy na palcach. Na jeden token przypada:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">KV na token = 2 (klucz + wartość) × liczba_warstw × n_head_kv × head_dim × bajty_na_element
</span></span></code></pre></div><p>Dla Llamy 3.1 8B te liczby to: 32 warstwy, <code>n_head_kv</code> = 8, <code>head_dim</code> = 128, a KV-cache
domyślnie leci w <code>f16</code>, czyli 2 bajty na element<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> (co to za <code>f16</code> i czemu nie <code>bf16</code> — <a href="/poradniki/bf16-f16-f32/">tu</a>). Wrzuć to do wzoru:
<code>2 × 32 × 8 × 128 × 2 bajty = 128 KiB</code> na token. Przy kontekście 8192 tokenów robi się z tego
równiutki 1 GiB — tyle dokłada sam cache do tego, co już zjadły wagi. Rozbieramy ten mechanizm
dokładniej <a href="/poradniki/kv-cache-vram/">tutaj</a>, ale zasada do zapamiętania jest prosta: dłuższy
kontekst = więcej VRAM-u, po prostu.</p>
<p>Zwróć uwagę na <code>n_head_kv</code> = 8 — pełna liczba głów uwagi w tym modelu jest większa, ale dzięki
GQA (<em>grouped-query attention</em>, mechanizm współdzielenia głów klucza/wartości) do cache liczy się
tylko ta zredukowana ósemka. Bez GQA, na starej architekturze, ten narzut potrafił być
wielokrotnie większy. Do tego dochodzą jeszcze bufory obliczeniowe samego procesu inferencji.
Dlatego świętą zasadą jest: <strong>zostaw 1–2 GB zapasu ponad rozmiar wag</strong>. Kto liczy VRAM tylko
z gramatury pliku GGUF, ten regularnie ląduje na „out of memory&quot; przy trzecim akapicie rozmowy.</p>
<h2 id="jak-zejść-niżej-gdy-nie-wchodzi">Jak zejść niżej, gdy nie wchodzi</h2>
<p>Powiedzmy, że policzyłeś i wychodzi za dużo. Masz trzy dźwignie, w tej właśnie kolejności.</p>
<p>Raz — <strong>kwantyzacja niżej</strong>. To najprostszy ruch: zamiast Q8 bierzesz Q5, zamiast Q5 bierzesz
Q4. Czternastka w Q8 to 15,7 GB, a w Q4 już tylko 8,99 GB<sup id="fnref1:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> — prawie połowa mniej, a
jakość spada zaskakująco mało, bo K-quanty oszczędzają mądrze. Q4_K_M to nie bez powodu
domyślny wybór połowy internetu.</p>
<p>Dwa — <strong>kwantyzacja KV-cache</strong>. Skoro cache potrafi dołożyć giga przy dłuższym kontekście, to
jego też można ścisnąć. <code>llama-server</code> ma na to flagi <code>-ctk</code> / <code>--cache-type-k</code> i <code>-ctv</code> /
<code>--cache-type-v</code>, osobno dla kluczy i wartości; domyślnie oba stoją na <code>f16</code>, ale przyjmują też
<code>q8_0</code>, <code>q4_0</code> i całą listę innych typów.<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> Ściśnięcie cache z <code>f16</code> do <code>q8_0</code> to znów
zejście z jego rozmiaru mniej więcej o połowę.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">llama-server -m qwen2.5-14b-instruct-q4_k_m.gguf <span class="se">\
</span></span></span><span class="line"><span class="cl">  -ngl all -c <span class="m">8192</span> -fa on <span class="se">\
</span></span></span><span class="line"><span class="cl">  -ctk q8_0 -ctv q8_0
</span></span></code></pre></div><p>Widzisz to <code>-fa on</code>? To nie ozdoba. Flaga <code>-fa</code> / <code>--flash-attn</code> (przyjmuje <code>on</code>/<code>off</code>/<code>auto</code>,
domyślnie <code>auto</code>) włącza Flash Attention — a kwantyzacja KV-cache do typów poniżej <code>f16</code>
praktycznie <strong>wymaga</strong> włączonej Flash Attention.<sup id="fnref1:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> Ustawisz <code>-ctk q4_0</code> bez <code>-fa on</code> i
w najlepszym razie nic nie zyskasz, w najgorszym nie ruszy. Więc: najpierw Flash Attention,
potem duś cache.</p>
<p>Trzy — <strong>przytnij kontekst albo zrzuć część warstw na CPU</strong>. To ostateczność, bo krótszy
kontekst to mniejsza pamięć modelu, a offload na CPU (flaga <code>-ngl</code> z liczbą mniejszą niż
„wszystko&quot;) spowalnia generację. Ale gdy 12B w Q4 nie chce wejść na 8 GB nawet po ściśnięciu
cache — czasem to jedyny sposób, żeby w ogóle ruszyło. Cała ta chirurgia flagami to osobny
rozdział, który rozpisaliśmy <a href="/poradniki/flagi-llama-cpp/">tu</a>.</p>
<h2 id="zrób-to-sam-rachunek-w-trzy-sekundy">Zrób to sam: rachunek w trzy sekundy</h2>
<p>Następnym razem, gdy staniesz przed nowym modelem, przelicz go w głowie, zanim klikniesz
pobieranie:</p>
<ol>
<li><strong>Wagi</strong>: <code>parametry × bity / 8</code>. Ósemka w Q4 → <code>8 × 4,5 / 8 ≈ 4,5 GB</code>. Albo zerknij na
realny plik na Hugging Face i weź gotową liczbę.</li>
<li><strong>Cache</strong>: przy zwykłym kontekście (4–8k) dorzuć z grubsza 0,5–1 GB dla modelu klasy 7–14B
z GQA. Planujesz 32k albo więcej? Licz się z kilkoma gigabajtami i od razu myśl o <code>-fa on</code>
plus <code>-ctk q8_0</code>.</li>
<li><strong>Zapas</strong>: dorzuć 1–2 GB na bufory i oddech.</li>
</ol>
<p>Suma większa niż twój VRAM? Schodzisz z kwantyzacją albo duszisz cache — w tej kolejności.
Suma mniejsza? Ściągaj śmiało, wejdzie.</p>
<p>Bo cała sztuka z VRAM-em nie polega na wróżeniu, tylko na tym jednym prostym mnożeniu na
palcach — parametry razy bity, przez osiem, plus zapas. Kiedy raz je poczujesz, folder <code>models</code>
przestaje być loterią, a zaczyna być tym, czym powinien: półką, na której z góry wiesz, co się
zmieści, a co poleży, aż dokupisz kartę „na później&quot;. A dokupisz. Wszyscy dokupujemy.</p>
<h2 id="przypisy--źródła">Przypisy · źródła</h2>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Realne rozmiary plików GGUF dla Meta-Llama-3.1-8B-Instruct (<code>Q4_K_M</code> = 4,92 GB, <code>Q5_K_M</code> = 5,73 GB, <code>Q6_K</code> = 6,60 GB, <code>Q8_0</code> = 8,54 GB), <a href="https://huggingface.co/bartowski/Meta-Llama-3.1-8B-Instruct-GGUF">bartowski/Meta-Llama-3.1-8B-Instruct-GGUF</a>.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>llama.cpp, tabela typów kwantyzacji z wartościami bits-per-weight i rozmiarami dla Llama 3.1 8B (<code>Q4_K_M</code> = 4,8944 bpw / 4,58 GiB, <code>Q5_K_M</code> = 5,7036 bpw / 5,33 GiB, <code>F16</code> = 16,0005 bpw / 14,96 GiB; <code>Q4_0</code> = 4,5 bpw), <a href="https://github.com/ggml-org/llama.cpp/blob/master/tools/quantize/README.md">tools/quantize/README.md</a>.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Realne rozmiary plików GGUF dla Mistral-Nemo-Instruct-2407 (12B): <code>Q4_K_M</code> = 7,48 GB, <code>Q5_K_M</code> = 8,73 GB, <code>Q8_0</code> = 13,02 GB, <a href="https://huggingface.co/bartowski/Mistral-Nemo-Instruct-2407-GGUF">bartowski/Mistral-Nemo-Instruct-2407-GGUF</a>.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Realne rozmiary plików GGUF dla Qwen2.5-14B-Instruct: <code>Q4_K_M</code> = 8,99 GB, <code>Q5_K_M</code> = 10,51 GB, <code>Q8_0</code> = 15,70 GB, <a href="https://huggingface.co/bartowski/Qwen2.5-14B-Instruct-GGUF">bartowski/Qwen2.5-14B-Instruct-GGUF</a>.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>llama.cpp, parametry KV-cache dla Llama 3 8B (<code>n_layer</code> = 32, <code>n_head_kv</code> = 8, <code>head_dim</code> = 128, <code>n_embd_k_gqa</code>/<code>v_gqa</code> = 1024) i potwierdzenie domyślnego typu cache <code>f16</code>, <a href="https://github.com/ggml-org/llama.cpp/discussions/7949">GitHub Discussion #7949 — KV Cache Dimension</a>.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>llama.cpp, opis flag <code>llama-server</code>: <code>-ctk</code>/<code>--cache-type-k</code>, <code>-ctv</code>/<code>--cache-type-v</code> (domyślnie <code>f16</code>, dozwolone m.in. <code>q8_0</code>, <code>q4_0</code>) oraz <code>-fa</code>/<code>--flash-attn</code> (<code>on</code>/<code>off</code>/<code>auto</code>, domyślnie <code>auto</code>); kwantyzacja KV-cache poniżej <code>f16</code> wymaga Flash Attention, <a href="https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md">tools/server/README.md</a>.&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>FlashAttention: uwaga, która nie zjada pamięci</title><link>https://inferownia.pl/naukowy/flashattention/</link><pubDate>Sat, 11 Jul 2026 00:00:00 +0200</pubDate><author>Tri Dao</author><author>Daniel Y. Fu</author><author>Stefano Ermon</author><author>Atri Rudra</author><author>Christopher Ré</author><category>inferencja</category><guid>https://inferownia.pl/naukowy/flashattention/</guid><description>Wrzucasz jedną flagę, -fa, i model nagle mieści dłuższy kontekst na tej samej karcie. Za tym skrótem stoi praca, która nie przybliża uwagi ani jej nie tnie — tylko przestawia kolejność liczenia tak, żeby wielka macierz N×N nigdy nie musiała wylądować w wolnej pamięci.</description><content:encoded><![CDATA[<p>Znasz to <code>-fa on</code>, które wklepujesz przy odpalaniu <code>llama.cpp</code> prawie z automatu, jak
człowiek zapinający pas w aucie — nie zastanawiasz się, po prostu wiesz, że tak trzeba?
To jeden z tych patentów, który powtarza się jak refren:
„nie wchodzi na kartę? dorzuć flash attention&quot;. Wrzucasz, kontekst się rozciąga, VRAM-u
ubywa wolniej, i jedziesz dalej, nawet nie pytając, co ta flaga właściwie robi pod maską.</p>
<p>A pod maską siedzi jedna z ładniejszych prac ostatnich lat — <em>FlashAttention</em>, Tri Dao
i spółka ze Stanfordu, maj 2022.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Ładna nie dlatego, że wymyśliła nowy rodzaj uwagi.
Wręcz przeciwnie — wynik matematyczny wychodzi <strong>co do liczby taki sam</strong> jak w zwykłej
uwadze. Cały myk polega na czymś, co brzmi jak herezja dla kogoś, kto liczył złożoność
na studiach: żeby przyspieszyć obliczenia, zamiast liczyć mniej, autorzy kazali karcie
<strong>mniej biegać po pamięci</strong>. I to załatwiło sprawę.</p>
<h2 id="skąd-w-ogóle-ten-głód-pamięci">Skąd w ogóle ten głód pamięci</h2>
<p>Najpierw ustalmy, czego uwaga potrzebuje, bo tu leży cały problem. Uwaga (ta z „Attention
Is All You Need&quot;, z 2017 — inna praca, inni ludzie, nie mylić) dla sekwencji o długości <code>N</code>
tokenów porównuje każdy token z każdym. Każdy z każdym to macierz <code>N × N</code>. Masz 4 tysiące
tokenów kontekstu? To 16 milionów liczb w jednej tablicy. Osiem tysięcy? Już 64 miliony.
Pamięć rośnie z kwadratem długości — podwajasz kontekst, płacisz czterokrotnie.<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>I teraz najważniejsze, co ludzie przeoczają: nie chodzi o to, że tych mnożeń jest dużo.
GPU uwielbia mnożyć, od tego jest. Problem w tym, <strong>gdzie</strong> ta wielka macierz ląduje.
Karta ma dwa rodzaje pamięci. Jest HBM — ta duża, te twoje 8, 12 czy 24 giga, wolna jak
poranny ruch na obwodnicy. I jest SRAM — malutka pamięć tuż przy rdzeniach obliczeniowych,
błyskawiczna, ale mieści tyle co nic. Zwykła uwaga liczy całą macierz <code>N × N</code>, zapisuje ją
w HBM, potem odczytuje z powrotem, żeby policzyć softmax, znowu zapisuje, znowu czyta…
i to bieganie tam i z powrotem — nie samo mnożenie — jest tym, co zżera czas i pamięć.</p>
<h2 id="io-aware-czyli-licz-tam-gdzie-blisko">„IO-aware&quot;, czyli licz tam, gdzie blisko</h2>
<p>Tu wchodzi pomysł, od którego cała praca dostała podtytuł: <em>IO-awareness</em>, świadomość
operacji wejścia-wyjścia.<sup id="fnref2:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Brzmi korporacyjnie, a znaczy rzecz prostą jak drut:
przestań optymalizować liczbę mnożeń, zacznij optymalizować liczbę <strong>podróży do wolnej
pamięci</strong>. Bo to one są wąskim gardłem.</p>
<p>Jak to zrobić, skoro macierz <code>N × N</code> z definicji jest ogromna i nie zmieści się w tej
malutkiej szybkiej SRAM? No właśnie — nie zmieści się <strong>w całości</strong>. Ale w kawałkach już
tak. I to jest sedno.</p>
<ul>
<li><strong>Tiling (kafelkowanie).</strong> Zamiast liczyć naraz całą macierz, tniesz <code>Q</code>, <code>K</code> i <code>V</code>
na bloki i przerabiasz je kafelek po kafelku. Każdy kafelek jest na tyle mały, że wjeżdża
do SRAM, tam się go przemiela, wypluwa wynik i bierze następny. Wielka macierz <code>N × N</code>
jako całość <strong>nigdy nie powstaje</strong> w wolnej pamięci — istnieje tylko przelotnie, we
fragmentach, w tej szybkiej pamięci przy rdzeniach.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></li>
<li><strong>Online softmax.</strong> Tu jest haczyk, bo softmax klasycznie potrzebuje całego wiersza
naraz — musi znormalizować po wszystkich elementach. FlashAttention liczy go „w locie&quot;,
z bieżącą normalizacją: przerabia kolejne bloki i na bieżąco koryguje wynik, tak jakby
sumował rachunek pozycja po pozycji, poprawiając napiwek za każdym razem, gdy dojdzie
nowa pozycja. Efekt końcowy identyczny, a pełnego wiersza nigdy nie trzeba trzymać.<sup id="fnref1:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></li>
<li><strong>Rekomputacja.</strong> Przy uczeniu (backward pass) normalnie trzymasz w pamięci wszystkie
pośrednie wyniki, żeby policzyć gradienty. FlashAttention mówi: po co je trzymać, skoro
taniej je <strong>policzyć jeszcze raz</strong> z tego, co już mamy w szybkiej pamięci? Dokłada trochę
mnożenia, żeby oszczędzić na pamięci i na tym przeklętym bieganiu do HBM. I na tym
akurat wychodzi na plus.<sup id="fnref3:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></li>
</ul>
<p>Zbierz to razem i dostajesz uwagę, w której zużycie pamięci rośnie <strong>liniowo</strong> z długością
sekwencji, <code>O(N)</code>, zamiast kwadratowo, <code>O(N²)</code>.<sup id="fnref4:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Ta sama matematyka, ta sama odpowiedź
co do ostatniej cyfry — tylko poukładana tak sprytnie, że wielka tablica pośrednia nigdy
nie musi się zmaterializować tam, gdzie jest wolno.</p>
<div class="callout">
  <span class="callout__label">Dlaczego to ważne dla lokalnego LLM</span>
<p>Dla ciebie, odpalającego model w domu, sedno jest takie: FlashAttention <strong>nie jest</strong>
przybliżeniem. To nie sparse attention, nie low-rank, nie żadna sztuczka, która coś obcina
i modli się, żeby jakość nie spadła. Dostajesz dokładnie ten sam wynik co przy zwykłej
uwadze — po prostu policzony taniej i mniejszym kosztem VRAM-u. To rzadki przypadek obiadu,
za który naprawdę nikt nie płaci. I dlatego w nowszych buildach <code>llama.cpp</code> z backendem CUDA
ta flaga po cichu bywa włączona domyślnie.</p>
</div>
<h2 id="czy-to-na-pewno-działało-liczby-z-pracy">Czy to na pewno działało? Liczby z pracy</h2>
<p>Bo można sobie opowiadać o eleganckim algorytmie do rana, ale papier bez liczb to wróżenie
z fusów. Autorzy zmierzyli i pokazali czarno na białym.<sup id="fnref5:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<table>
	<thead>
			<tr>
					<th>Model / zadanie</th>
					<th>Długość sekwencji</th>
					<th>Efekt</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>BERT-large</td>
					<td>512</td>
					<td>15% szybciej end-to-end</td>
			</tr>
			<tr>
					<td>GPT-2</td>
					<td>1K</td>
					<td>3× szybciej</td>
			</tr>
			<tr>
					<td>Long-Range Arena</td>
					<td>1K–4K</td>
					<td>2,4× szybciej</td>
			</tr>
	</tbody>
</table>
<p>I najciekawsze na koniec — nie samo „szybciej&quot;, ale „w ogóle się dało&quot;. Skoro pamięć
przestała rosnąć kwadratowo, można było pchać modele na kontekst, na którym wcześniejsze
Transformery po prostu się poddawały. Na Path-X (sekwencja 16 tysięcy) FlashAttention
wyciągnął 61,4% dokładności, a na Path-256 (64 tysiące!) — 63,1%.<sup id="fnref6:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> To zadania, na
których wcześniej model radził sobie <strong>nie lepiej niż rzut monetą</strong>. Dłuższy kontekst,
który dziś traktujesz jak coś oczywistego, częściowo wziął się właśnie stąd.</p>
<h2 id="zrób-to-sam--i-nie-pomyl-tego-z-kv-cache">Zrób to sam — i nie pomyl tego z KV-cache</h2>
<p>Nie musisz nic implementować, cały ten mechanizm masz w jednej fladze. W <code>llama.cpp</code>
to <code>-fa</code>, <code>--flash-attn</code>, przyjmuje <code>on</code>/<code>off</code>/<code>auto</code>, domyślnie stoi na <code>auto</code>.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">llama-cli -m qwen2.5-7b-instruct-q4_k_m.gguf -ngl all -c <span class="m">8192</span> -fa on -cnv
</span></span></code></pre></div><p>Jedna rzecz, którą warto tu dorzucić, bo ludzie się na niej wykładają: w <code>llama.cpp</code>
flash attention jest <strong>twardym wymogiem</strong> dla kwantyzacji KV-cache. Chcesz ścisnąć cache
do <code>q8_0</code>, żeby odzyskać VRAM przy długim kontekście? Bez <code>-fa on</code> się nie da — kwantyzacja
V-cache wymaga flash attention wprost, kod ucina to jednym komunikatem błędu.<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>
Więc te dwie sztuczki chodzą w parze:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">llama-cli -m qwen2.5-7b-instruct-q4_k_m.gguf <span class="se">\
</span></span></span><span class="line"><span class="cl">  -ngl all -c <span class="m">8192</span> -fa on -ctk q8_0 -ctv q8_0 -cnv
</span></span></code></pre></div><p>I tu ostrzeżenie, żebyś nie wpadł w częstą pułapkę pojęciową. FlashAttention <strong>to nie
jest</strong> KV-cache — to dwie różne rzeczy, które akurat obie oszczędzają pamięć. KV-cache
dotyczy tego, że model <strong>przechowuje</strong> przeliczony kontekst między kolejnymi tokenami
generacji (rozgryzaliśmy to bliżej <a href="/poradniki/kv-cache-vram/">tutaj</a>). FlashAttention
dotyczy tego, <strong>jak</strong> liczysz uwagę w jednym przejściu — kolejności obliczeń, nie
przechowywania stanu. Oszczędność bierze się wyłącznie z tilingu i rekomputacji, a nie
z jakiejkolwiek utraty precyzji — nikt tu niczego nie kwantyzuje ani nie zaokrągla.</p>
<p>Dwa zastrzeżenia od siebie, żeby cię to potem nie zaskoczyło. Raz — wsparcie nie jest
uniwersalne: na backendzie CUDA jest jak znalazł, ale na Vulkanie czy SYCL dla części kart
flaga bywała kapryśna, więc nie traktuj jej jak działającej wszędzie i zawsze (więcej o tym,
czym różnią się <a href="/aktualnosci/backendy-llama-cpp/">backendy</a>). Dwa — jak zwykle w
<code>llama.cpp</code>, resztę flag, które chodzą obok tej, <a href="/poradniki/flagi-llama-cpp/">ogarnialiśmy osobno</a>.</p>
<p>Historia nie skończyła się w 2022. Rok później przyszedł <em>FlashAttention-2</em> — już samego
Tri Dao — który poprzestawiał podział pracy między wątkami GPU i wycisnął jeszcze z grubsza
dwukrotne przyspieszenie względem jedynki, dobijając do 50–73% teoretycznego maksimum
kart A100.<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> Kod jednego i drugiego (plus wersji trzeciej) siedzi w oficjalnym repo
<code>Dao-AILab/flash-attention</code>, na licencji BSD-3.<sup id="fnref2:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<p>Ale najfajniejsze w tej całej historii jest to, jak przewrotnie brzmi jej morał. Przez lata
uczono nas, że żeby program był szybszy, trzeba go zmusić do liczenia mniej. A tu przyszli
ludzie, którzy kazali karcie <strong>policzyć część rzeczy dwa razy</strong> — i wyszło szybciej. Bo
okazało się, że najdroższa rzecz na GPU to nie mnożenie liczb, tylko chodzenie po nie na
drugi koniec pamięci. Trochę jak z tym kolegą, który zamiast nosić zakupy po jednej siatce
z auta pod blok, woli obładować się wszystkim naraz i przejść ten dystans <strong>raz</strong>. Nie jest
leniwy. Po prostu wie, że najgorszy jest sam spacer tam i z powrotem.</p>
<h2 id="przypisy--źródła">Przypisy · źródła</h2>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Tri Dao, Daniel Y. Fu, Stefano Ermon, Atri Rudra, Christopher Ré, <em>FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness</em>, arXiv:2205.14135 (v1 z 27.05.2022) — złożoność <code>O(N)</code> zamiast <code>O(N²)</code>, tiling, rekomputacja, dokładna (nie przybliżona) uwaga, wyniki 15% / 3× / 2,4× oraz Path-X 61,4% i Path-256 63,1%: <a href="https://arxiv.org/abs/2205.14135">arxiv.org/abs/2205.14135</a>.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref3:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref4:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref5:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref6:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Oficjalna implementacja referencyjna, licencja BSD-3-Clause — tiling, online softmax, brak materializacji macierzy <code>N × N</code>, kod FlashAttention 1/2/3: <a href="https://github.com/Dao-AILab/flash-attention">github.com/Dao-AILab/flash-attention</a>.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>llama.cpp (ggml-org), opis flagi <code>-fa, --flash-attn [on|off|auto]</code> (default: auto): <a href="https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md">tools/server/README.md</a>.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>llama.cpp (ggml-org), twardy wymóg flash attention dla kwantyzacji V-cache — komunikat błędu „V cache quantization requires flash_attn&quot;: <a href="https://github.com/ggml-org/llama.cpp/blob/master/src/llama-context.cpp#L3564">src/llama-context.cpp#L3564</a>.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Tri Dao, <em>FlashAttention-2: Faster Attention with Better Parallelism and Work Partitioning</em>, arXiv:2307.08691 (17.07.2023) — osobna praca, ok. 2× przyspieszenie względem FlashAttention-1 i 50–73% teoretycznego FLOPs/s na A100: <a href="https://arxiv.org/abs/2307.08691">arxiv.org/abs/2307.08691</a>.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Mamba: co potrafią modele przestrzeni stanów</title><link>https://inferownia.pl/naukowy/mamba-ssm/</link><pubDate>Tue, 07 Jul 2026 00:00:00 +0200</pubDate><author>Albert Gu</author><author>Tri Dao</author><category>inferencja</category><guid>https://inferownia.pl/naukowy/mamba-ssm/</guid><description>Transformer płaci za każdy token kwadratem, a jego KV-cache puchnie z długością rozmowy jak balon. Mamba mówi: da się liniowo i przy stałym stanie. Rozkładamy na części, co potrafią selektywne modele przestrzeni stanów.</description><content:encoded><![CDATA[<p>Jest taki sposób czytania, który brzmi jak kara: przy każdym kolejnym słowie wracasz na
początek i przebiegasz wzrokiem wszystko, co już przeczytałeś. Zdanie dalej — znowu od zera.
Brzmi absurdalnie, a mniej więcej tak właśnie zwykły transformer obchodzi się z długim
tekstem. Im dłuższy kontekst wrzucasz, tym więcej pamięci schodzi, i to nie po
równo, tylko coraz szybciej. Wśród ludzi, co odpalają to w domu, wraca to jak natrętny wątek: „mam 24 GB, czemu przy
32k kontekstu i tak leci OOM?&quot;. Odpowiedź brzmi zawsze tak samo — bo attention, ta sama
uwaga, która zrobiła z transformerów króla, ma pewien brzydki nawyk. Za każdy token płaci
kwadratem.</p>
<p>I przez lata żyliśmy z tym jak z hałaśliwym sąsiadem: da się, tylko trzeba akceptować.
Aż w grudniu 2023 Albert Gu i Tri Dao wrzucili na arXiv pracę o wdzięcznej nazwie
<em>Mamba</em>, w której powiedzieli coś w stylu „a gdyby tak w ogóle wyrzucić attention?&quot;.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>
Nie ograniczyć, nie zoptymalizować — wyrzucić. Cały mechanizm uwagi, a przy okazji i bloki
MLP, i zastąpić je czymś, co skaluje się liniowo, a przy generowaniu trzyma stan o stałym
rozmiarze. Brzmi jak sprzedażowa bajka. A jednak papier ma liczby.</p>
<h2 id="skąd-w-ogóle-ten-kwadrat-w-attention">Skąd w ogóle ten kwadrat w attention</h2>
<p>Zanim zachwycimy się Mambą, warto wiedzieć, od czego ona ucieka. Uwaga w transformerze
działa tak, że każdy token patrzy na każdy inny token w sekwencji. Masz 1000 tokenów?
To milion par do policzenia. Masz 10 000? Sto milionów. Rośnie z kwadratem długości —
stąd to słynne O(n²), które przy długim kontekście robi się głównym żłobem na pamięć i czas.</p>
<p>Do tego dochodzi druga rzecz, boleśnie znajoma każdemu, kto odpala model lokalnie:
KV-cache. Żeby przy generowaniu nie liczyć uwagi od zera dla każdego nowego tokenu,
transformer trzyma w pamięci klucze i wartości wszystkich dotychczasowych tokenów. I ten
bufor rośnie <strong>liniowo z długością rozmowy</strong> — im dłużej gadasz, tym więcej VRAM-u zjada
sam cache, niezależnie od wag modelu. Rozgryzaliśmy ten mechanizm bliżej
<a href="/poradniki/kv-cache-vram/">tutaj</a>, ale sedno jest proste: transformer ma pamięć, która
puchnie. Zawsze puchnie.</p>
<p>Mamba wychodzi z zupełnie innej rodziny — z <strong>modeli przestrzeni stanów</strong> (SSM, <em>state
space models</em>).<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> To pomysł stary jak teoria sterowania: zamiast patrzeć na wszystko
naraz, przesuwasz się przez sekwencję krok po kroku i utrzymujesz jeden <strong>stan</strong> — wektor
o stałym rozmiarze, który streszcza wszystko, co do tej pory widziałeś. Nowy token wpada,
aktualizuje stan, idziesz dalej. Trochę jak RNN, tylko policzony sprytniej.</p>
<h2 id="dlaczego-wcześniejsze-ssm-nie-zabiły-transformera">Dlaczego wcześniejsze SSM nie zabiły transformera</h2>
<p>Bo miały jedną wadę, przez którą były głupsze niż attention. Klasyczny SSM — jak S4,
poprzednik Mamby od tych samych autorów — ma stałe parametry. Te same macierze przetwarzają
każdy token identycznie, bez względu na to, co ten token właściwie mówi. To pozwala policzyć
całość jako jeden wielki splot (konwolucję), błyskawicznie i równolegle — ale odbiera
modelowi zdolność, którą attention ma za darmo: <strong>rozumowanie oparte na treści</strong>.</p>
<p>Wyobraź sobie zdanie, w którym ważne jest jedno słowo gdzieś na początku, a reszta to
wypełniacz. Attention po prostu skupi się na tym jednym słowie. Stary SSM nie umie — traktuje
wypełniacz tak samo poważnie jak sedno, bo nie potrafi wybrać. Nie umie powiedzieć „to
zapamiętaj, a tamto olej&quot;.</p>
<h2 id="na-czym-polega-selekcja-cały-myk-mamby">Na czym polega „selekcja&quot;, cały myk Mamby</h2>
<p>I tu wchodzi jedna zmiana, która robi całą różnicę — <strong>selekcja</strong>. Gu i Dao uzależnili
parametry SSM od wejścia. Krok dyskretyzacji Δ oraz macierze B i C przestają być stałe
i stają się <strong>funkcjami aktualnego tokenu</strong>.<sup id="fnref2:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Innymi słowy: model przy każdym kroku
sam decyduje, ile z nowej informacji wpuścić do stanu, a ile ze starego stanu zapomnieć —
w zależności od tego, co właśnie czyta.</p>
<p>To jest ten moment „aha&quot;. Selektywny SSM potrafi zrobić to, czego stary nie umiał:
przepuścić ważny token dalej wzdłuż sekwencji, a nieważny wygasić. Dostaje content-based
reasoning, którym attention się chwalił — tylko bez płacenia kwadratem.</p>
<div class="callout">
  <span class="callout__label">Dlaczego to ważne dla lokalnego LLM</span>
<p>Dla ciebie, odpalającego model pod biurkiem, sedno jest takie: Mamba przy generowaniu
utrzymuje stan rekurencyjny o <strong>stałym rozmiarze</strong>. Nie ma analogu KV-cache, który rośnie
z długością rozmowy. Piąta godzina konwersacji zjada tyle samo pamięci co pierwsza minuta.
Uwaga tylko — to nie znaczy „brak stanu&quot;. Stan wciąż jest, ukryty wektor sobie siedzi
i pracuje; on po prostu nie puchnie z kontekstem. To różnica między plecakiem o stałej
pojemności a workiem, do którego dorzucasz kamień za każdym tokenem.</p>
</div>
<h2 id="cena-selekcji-znika-droga-na-skróty">Cena selekcji: znika droga na skróty</h2>
<p>Tyle że za wszystko się płaci. Uzależnienie parametrów od wejścia ma paskudny efekt
uboczny: <strong>zabija formę splotową</strong>. Skoro macierze zmieniają się z każdym tokenem, nie
policzysz już całości jednym szybkim splotem, jak w S4. Musisz wrócić do trybu
rekurencyjnego — krok po kroku — a to na GPU brzmi jak przepis na wolny, sekwencyjny
dramat.</p>
<p>Autorzy rozwiązali to algorytmem, który nazwali <strong>selective scan</strong>, i zaprojektowali go
„świadomie sprzętowo&quot; (<em>hardware-aware</em>), czerpiąc wprost z pomysłów, które znasz z
<a href="/naukowy/flashattention/">FlashAttention</a>.<sup id="fnref3:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Cały trik siedzi w tym, gdzie na GPU
lądują dane. Karta graficzna ma pamięć szybką i małą (SRAM) oraz wolną i dużą (HBM) —
a wąskim gardłem prawie zawsze jest przepychanie danych między nimi, nie samo liczenie.</p>
<p>Selective scan robi trzy rzeczy, żeby to obejść:<sup id="fnref4:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<ul>
<li><strong>Nie materializuje</strong> rozdmuchanego stanu w wolnej pamięci HBM — dyskretyzację i rekurencję
liczy w szybkim SRAM.</li>
<li><strong>Zlepia operacje w jeden kernel</strong> (kernel fusion), zamiast latać z wynikami tam i z powrotem.</li>
<li>Przy propagacji wstecznej <strong>przelicza stany od nowa</strong>, zamiast trzymać je w pamięci
(recomputation) — bo na nowoczesnym GPU policzyć jest taniej niż odczytać.</li>
</ul>
<p>Efekt? Model, który w teorii jest rekurencyjny i „powinien&quot; być wolny, w praktyce śmiga.
To ta sama filozofia, co przy flash attention: nie zmieniaj matematyki, zmień to, jak dane
wędrują przez poziomy pamięci karty.</p>
<h2 id="co-z-tego-wychodzi-w-liczbach">Co z tego wychodzi w liczbach</h2>
<p>Papier nie owija w bawełnę. Kilka twardych wyników:<sup id="fnref5:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<table>
	<thead>
			<tr>
					<th>Cecha</th>
					<th>Transformer</th>
					<th>Mamba</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Skalowanie z długością sekwencji</td>
					<td>O(n²)</td>
					<td>O(n) — liniowe</td>
			</tr>
			<tr>
					<td>Stan przy generowaniu</td>
					<td>KV-cache rośnie z kontekstem</td>
					<td>stały rozmiar</td>
			</tr>
			<tr>
					<td>Przepustowość generowania</td>
					<td>punkt odniesienia</td>
					<td><strong>5× wyższa</strong></td>
			</tr>
			<tr>
					<td>Jakość (language modeling)</td>
					<td>model 2× większy dorównuje</td>
					<td>Mamba-3B dorównuje</td>
			</tr>
	</tbody>
</table>
<p>To „5× higher throughput&quot; to nie chochlik — Mamba generuje tokeny pięć razy szybciej niż
transformer podobnej klasy, właśnie dzięki temu, że nie musi za każdym krokiem przemielać
rosnącego cache&rsquo;u.<sup id="fnref6:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> A jakościowo Mamba-3B dorównuje transformerom <strong>dwukrotnie
większym</strong> i bije te swojej wielkości — zarówno w pretreningu, jak i w ewaluacji downstream.</p>
<p>I jedna rzecz, która dla długiego kontekstu jest najsmaczniejsza: wydajność na realnych
danych <strong>poprawia się aż do sekwencji rzędu milionów tokenów</strong>.<sup id="fnref7:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Nie „nie psuje się&quot; —
poprawia. Tam, gdzie transformer się dusi, Mamba dopiero się rozkręca. Do tego papier
pokazuje wyniki state-of-the-art nie tylko dla języka, ale i dla audio oraz genomiki —
jako ogólny backbone sekwencyjny, nie wąska sztuczka do jednej modalności.</p>
<h2 id="zrób-to-sam-a-przynajmniej-dotknij">Zrób to sam (a przynajmniej dotknij)</h2>
<p>To nie jest zamknięty papier z ładnym wykresem. Kod i checkpointy leżą w oficjalnym repo
<code>state-spaces/mamba</code> — z gotowymi modelami od 130M do 2.8B parametrów, trenowanymi między
innymi na 300 miliardach tokenów (wariant SlimPajama na 600 mld).<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> Model z abstraktu
opisywany jako „Mamba-3B&quot; to na Hugging Face ten sam checkpoint co <code>mamba-2.8b</code> — nazewnictwo
się rozjeżdża, liczba parametrów ta sama.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></p>
<p>Jak chcesz zajrzeć pod maskę bloku Mamba, warto znać trzy pokrętła z implementacji:<sup id="fnref1:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># kluczowe hiperparametry modułu Mamba (Mamba-1)</span>
</span></span><span class="line"><span class="cl"><span class="nv">d_state</span> <span class="o">=</span> <span class="m">16</span>   <span class="c1"># rozmiar stanu SSM — ile &#34;pamięci&#34; trzyma ukryty wektor</span>
</span></span><span class="line"><span class="cl"><span class="nv">d_conv</span>  <span class="o">=</span> <span class="m">4</span>    <span class="c1"># szerokość lokalnej konwolucji przed SSM</span>
</span></span><span class="line"><span class="cl"><span class="nv">expand</span>  <span class="o">=</span> <span class="m">2</span>    <span class="c1"># o ile blok rozszerza wymiar wewnętrzny</span>
</span></span></code></pre></div><p><code>d_state</code> to wielkość tego stałego stanu, o którym cała gadka — domyślnie 16 dla Mamby-1.
<code>d_conv</code> to szerokość krótkiej lokalnej konwolucji (Mamba wciąż lubi lokalny kontekst
tuż-tuż), a <code>expand</code> mówi, o ile blok pompuje wymiar wewnętrzny. Trzy liczby, a definiują
charakter całej architektury.</p>
<p>Jedno zastrzeżenie na koniec, żeby nie wpaść w hype: Mamba <strong>nie</strong> wysyła transformerów na
emeryturę. Papier pokazuje przewagę na modelowaniu języka, audio i genomice — nie ogłasza,
że uwaga jest bezużyteczna wszędzie. Późniejsze prace tych samych autorów (Mamba-2 i tak
zwane <em>structured state space duality</em>) pokazują wręcz, że SSM i attention są matematycznie
spokrewnione bliżej, niż się na pierwszy rzut oka wydaje — to nie dwa wrogie plemiona,
raczej dwie gałęzie tego samego drzewa.</p>
<p>I może właśnie o to chodzi. Przez kilka lat myśleliśmy, że długi kontekst to podatek, który
płaci się kwadratem i pęczniejącym cache&rsquo;em — po prostu prawo natury lokalnego LLM-owania.
Mamba przyszła i pokazała, że to była tylko jedna droga przez las, nie jedyna. Ten sam
worek, do którego dorzucałeś kamień za każdym tokenem, nagle okazał się plecakiem o stałej
wadze — i można iść dalej, aż po horyzont milionów tokenów, bez zaciskania zębów przy
liczniku VRAM-u.</p>
<h2 id="przypisy--źródła">Przypisy · źródła</h2>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Albert Gu, Tri Dao, <em>Mamba: Linear-Time Sequence Modeling with Selective State Spaces</em>, arXiv:2312.00752 (v1: 1 grudnia 2023, v2: 31 maja 2024) — <a href="https://arxiv.org/abs/2312.00752">arxiv.org/abs/2312.00752</a>. Abstrakt i sekcja 3 (hardware-aware selective scan): brak attention/MLP, selekcja parametrów Δ/B/C jako funkcji wejścia, liniowe skalowanie, 5× throughput, poprawa do sekwencji milionowej długości, wyniki Mamba-3B oraz modalności język/audio/genomika. Pełny tekst: <a href="https://arxiv.org/pdf/2312.00752">arxiv.org/pdf/2312.00752</a>.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref3:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref4:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref5:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref6:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref7:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Oficjalna implementacja i checkpointy, GitHub — <a href="https://github.com/state-spaces/mamba">github.com/state-spaces/mamba</a>. Kernel <code>selective_scan_cuda</code>, hiperparametry modułu (<code>d_state</code>, <code>d_conv</code>, <code>expand</code>), pretrenowane modele 130M–2.8B (Mamba/Mamba-2), dane treningowe 300B/600B tokenów, odniesienie do Mamba-2 i structured state space duality.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Karta modelu <code>state-spaces/mamba-2.8b</code>, Hugging Face — <a href="https://huggingface.co/state-spaces/mamba-2.8b">huggingface.co/state-spaces/mamba-2.8b</a>. Potwierdzenie checkpointu ~2.8B (3B) parametrów, zgodnego z modelem „Mamba-3B&quot; z abstraktu.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item></channel></rss>