<?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>VRAM — Inferownia</title><link>https://inferownia.pl/tags/vram/</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/vram/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>Mixture of Experts: czemu aktywne parametry to nie wszystkie</title><link>https://inferownia.pl/naukowy/mixture-of-experts/</link><pubDate>Thu, 09 Jul 2026 00:00:00 +0200</pubDate><author>Albert Q. Jiang</author><author>Alexandre Sablayrolles</author><author>Antoine Roux</author><author>Arthur Mensch</author><author>Blanche Savary</author><author>Chris Bamford</author><author>Devendra Singh Chaplot</author><author>Diego de las Casas</author><author>Emma Bou Hanna</author><author>Florian Bressand</author><author>Gianna Lengyel</author><author>Guillaume Bour</author><author>Guillaume Lample</author><author>Lélio Renard Lavaud</author><author>Lucile Saulnier</author><author>Marie-Anne Lachaux</author><author>Pierre Stock</author><author>Sandeep Subramanian</author><author>Sophia Yang</author><author>Szymon Antoniak</author><author>Teven Le Scao</author><author>Théophile Gervet</author><author>Thibaut Lavril</author><author>Thomas Wang</author><author>Timothée Lacroix</author><author>William El Sayed</author><category>inferencja</category><guid>https://inferownia.pl/naukowy/mixture-of-experts/</guid><description>Mixtral 8x7B na token rusza tylko ~13 z 47 miliardów wag, ale do pamięci musisz wcisnąć wszystkie. Rozbieramy, czemu MoE oszczędza obliczenia, a nie ani grama VRAM-u.</description><content:encoded><![CDATA[<p>Osiem razy siedem to pięćdziesiąt sześć. Tyle że model nazywa się „8x7B&quot;, a waży czterdzieści
siedem miliardów parametrów — nie pięćdziesiąt sześć. I zanim zdążysz się z tym pogodzić, ktoś
dorzuca, że aktywnych jest tylko trzynaście. Trzy liczby, z których żadna nie klei się
z sąsiednią. Ściągasz GGUF-a, plik ma dwadzieścia kilka giga w Q4, a gość w komentarzach pisze,
że „chodzi jak siódemka&quot;. No to jak? Duży jak czterdziestka, szybki jak trzynastka,
a nazwany jak pięćdziesiątka szóstka?</p>
<p>To nie literówka i nie marketing. To Mixtral 8x7B<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> — i cała ta trójca liczb bierze
się z jednego pomysłu architektonicznego, który nazywa się <strong>Mixture of Experts</strong>. Jak go
raz zrozumiesz, przestaniesz się dziwić, czemu model „na 13B&quot; i tak każe ci kupić kartę pod
model „na 47B&quot;. Rozłóżmy to na części.</p>
<h2 id="trzy-liczby-jeden-trik">Trzy liczby, jeden trik</h2>
<p>Klasyczny model językowy jest <strong>gęsty</strong> (<em>dense</em>): na każdy token przepycha wszystkie swoje
wagi. Wrzucasz słowo, przechodzi przez każdą warstwę, w każdej warstwie dotyka każdego
neuronu. Prosto, uczciwie, drogo — bo im więcej parametrów, tym więcej roboty na literkę.</p>
<p>Mixtral robi inaczej. Bierze architekturę zwykłego Mistrala 7B, ale w każdej warstwie
podmienia jeden blok — ten feedforwardowy (FFN), czyli tę część, która „przetwarza&quot; token
po tym, jak uwaga (<em>attention</em>) już popatrzyła na kontekst. Zamiast jednego takiego bloku
wstawia <strong>osiem równoległych</strong>. To są właśnie „eksperci&quot;.<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Osiem kopii tej samej
maszynerii, każda wytrenowana ciut inaczej.</p>
<p>I teraz clou: dla danego tokena <strong>nie odpalają się wszyscy</strong>. Przed blokiem eksperckim stoi
mały <strong>router</strong> (sieć bramkująca, <em>gating network</em>), który patrzy na token i wybiera z ósemki
tylko <strong>dwóch</strong> ekspertów — top-2.<sup id="fnref2:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Ich wyjścia miesza ważoną sumą (wagi bierze
z softmaxu) i jedzie dalej. Sześciu pozostałych w tej warstwie, dla tego tokena, w ogóle
nie rusza. Śpią.</p>
<p>Stąd rozjazd liczb. Parametrów <strong>całkowitych</strong> (<em>total</em>) jest 46,7 miliarda — potocznie
~47B. Ale na przejście jednego tokena <strong>aktywnych</strong> (<em>active</em>) jest tylko 12,9 miliarda,
czyli ~13B.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> Płacisz obliczeniami za trzynastkę, a nosisz na barana czterdziestkę
siódemkę.</p>
<h2 id="skąd-47-a-nie-56">Skąd 47, a nie 56?</h2>
<p>No dobra, ale skoro to „8x7B&quot;, to czemu nie wychodzi 56 miliardów? Bo mnożenie „osiem razy
siedem&quot; jest po prostu błędne. Powielona jest <strong>tylko</strong> ta część FFN. Cała reszta — warstwy
uwagi, embeddingi, normalizacje — siedzi w modelu <strong>raz</strong> i jest współdzielona przez
wszystkich ekspertów i wszystkie tokeny.<sup id="fnref1:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> Nie mnożysz jej przez osiem ani w sumie,
ani w liczbie aktywnej. Dlatego total ląduje na 46,7B (a nie 56B), a active na 12,9B
(a nie na jednej ósmej z 56).</p>
<p>Zajrzyj zresztą do <code>config.json</code> na karcie modelu — tam to stoi czarno na białym:
<code>num_local_experts: 8</code>, <code>num_experts_per_tok: 2</code>, przy <code>hidden_size: 4096</code>
i <code>num_hidden_layers: 32</code>.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> Osiem ekspertów na warstwę, dwóch na token, trzydzieści
dwie warstwy. Mnożenie odpala się w każdej z tych 32 warstw z osobna.</p>
<p>I tu druga rzecz, która ludziom umyka: wybór ekspertów <strong>nie jest stały</strong>. Router decyduje
oddzielnie <strong>dla każdego tokena i dla każdej warstwy</strong>.<sup id="fnref3:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Słowo „kot&quot; w warstwie 3
może trafić do ekspertów 1 i 5, a to samo „kot&quot; w warstwie 4 — do 2 i 7. A następne słowo
w tym samym zdaniu pojedzie zupełnie inną trasą. To nie jest tak, że model „wybiera dwóch
specjalistów na cały prompt&quot; i tyle. To osiem tysięcy mikro-decyzji na akapit, każda robiona
w locie.</p>
<h2 id="to-czemu-nie-zajmuje-tyle-co-trzynastka">To czemu nie zajmuje tyle co trzynastka?</h2>
<p>I tu dochodzimy do pułapki, na którą łapie się pół internetu. Skoro na token aktywne jest
tylko ~13B, to model „zajmuje tyle co trzynastka&quot;, nie? Odpalę na karcie pod 13B i będzie
grało?</p>
<p>Nie będzie. I to jest najważniejsze zdanie w całym tym wpisie.</p>
<p>Router wybiera ekspertów <strong>dopiero w trakcie</strong> przejścia przez daną warstwę, osobno dla
każdego tokena. Znaczy: <strong>z góry nie wiesz, którzy eksperci będą potrzebni</strong>. Następne słowo
może chcieć dowolnej pary z ósemki, w dowolnej z 32 warstw. Więc żeby model w ogóle mógł
ruszyć, <strong>wszystkie 47 miliardów wag musi siedzieć w pamięci naraz</strong> — cała ósemka
ekspertów w każdej warstwie, gotowa do wezwania.<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> Oszczędność z MoE dotyczy
<strong>obliczeń</strong> (compute, latencji), nie <strong>pamięci</strong>. Mistral mówi to wprost: model „przetwarza
wejście i generuje wyjście z taką samą szybkością i kosztem jak model 12,9B&quot;.<sup id="fnref2:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>
Szybkością i kosztem obliczeń — nie apetytem na VRAM.</p>
<div class="callout">
  <span class="callout__label">Dlaczego to ważne dla lokalnego LLM</span>
<p>Zapamiętaj to jedno rozróżnienie, a przestaniesz się przejeżdżać na MoE: <strong>aktywne parametry
liczą się do prędkości, całkowite — do pamięci</strong>. Mixtral 8x7B liczy jak trzynastka, ale
ważyć musisz go jak czterdziestkę siódemkę. Do samej inferencji w Q4 potrzebujesz około
<a href="https://huggingface.co/TheBloke/Mixtral-8x7B-v0.1-GGUF">25–30 GB na wagi</a>, zanim jeszcze dołożysz KV-cache i kontekst. Karta pod 13B tego nie
udźwignie — a naiwne „mało aktywnych = mało VRAM-u&quot; to najczęstsza wpadka przy planowaniu
sprzętu. Ile realnie wchodzi na wagi, liczyliśmy <a href="/poradniki/ile-vram-na-model/">tutaj</a>.</p>
</div>
<p>Zbierzmy to w tabelce, bo dwie kolumny mówią więcej niż akapit:</p>
<table>
	<thead>
			<tr>
					<th>Co porównujemy</th>
					<th>Model gęsty (dense)</th>
					<th>Mixtral 8x7B (MoE)</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Parametry całkowite</td>
					<td>tyle, ile aktywnych</td>
					<td>46,7B (~47B)</td>
			</tr>
			<tr>
					<td>Parametry aktywne na token</td>
					<td>wszystkie</td>
					<td>12,9B (~13B)</td>
			</tr>
			<tr>
					<td>Ekspertów w warstwie FFN</td>
					<td>1</td>
					<td>8</td>
			</tr>
			<tr>
					<td>Ekspertów na token</td>
					<td>1</td>
					<td>2 (top-2)</td>
			</tr>
			<tr>
					<td>Co decyduje o prędkości</td>
					<td>parametry (= wszystkie)</td>
					<td>parametry <strong>aktywne</strong></td>
			</tr>
			<tr>
					<td>Co decyduje o VRAM</td>
					<td>parametry (= wszystkie)</td>
					<td>parametry <strong>całkowite</strong></td>
			</tr>
	</tbody>
</table>
<p>Po co ten cały cyrk, skoro pamięć i tak trzeba mieć całą? Bo za cenę pamięci klasy 47B
dostajesz jakość, która w benchmarkach <strong>dorównuje albo bije Llamę 2 70B i GPT-3.5</strong> — przy
koszcie obliczeniowym trzynastki.<sup id="fnref4:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> To jest ten deal: płacisz VRAM-em jak za dużego,
liczysz jak za małego, a mądrość dostajesz bliżej tego dużego. Dla kogoś, kto ma dość RAM-u,
ale mało cierpliwości do tokenów na sekundę — świetny interes.</p>
<h2 id="zrób-to-sam-a-przynajmniej-zaplanuj">Zrób to sam (a przynajmniej zaplanuj)</h2>
<p>MoE to <strong>architektura</strong>, nie kompresja. Łatwo pomylić to z kwantyzacją czy pruningiem, ale
to inna bajka: MoE od początku trenuje się jako rzadko aktywowaną sieć, a kwantyzacja ściska
wagi już po treningu — i spokojnie łączysz jedno z drugim (kwantyzowany GGUF Mixtrala to
codzienność). Jak działają same nazwy kwantyzacji, rozgryzaliśmy
<a href="/poradniki/nazwy-kwantyzacji-gguf/">tu</a> — ta sama logika oszczędzania pamięci działa i na
kwantyzowanym Mixtralu, tyle że na innym piętrze niż samo MoE.</p>
<p>W praktyce <code>llama.cpp</code> daje ci pod to dedykowaną flagę. Skoro attention i embeddingi są
„zawsze aktywne&quot;, a bloki eksperckie odpalają się wybiórczo, to najwięcej ugrasz, trzymając
te zawsze-aktywne komponenty na GPU, a same wagi ekspertów spychając do RAM-u. Od tego jest
<code>--n-cpu-moe N</code> — „trzymaj wagi MoE z pierwszych N warstw na CPU&quot; — albo <code>--cpu-moe</code>, które
przenosi wszystkie.<sup id="fnref1:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> Kroisz VRAM kosztem szybkości — właśnie dlatego, że pamięć musi
pomieścić <strong>całość</strong>, a nie tylko aktywną ścieżkę.</p>
<p>Najprościej — cały model na kartę, jak wejdzie:</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 mixtral-8x7b-instruct-v0.1.Q4_K_M.gguf -ngl all -c <span class="m">8192</span> -fa on -cnv
</span></span></code></pre></div><p>Nie wchodzi (a przy 47B na przeciętnej karcie do gier nie wejdzie)? Zostaw wszystkie warstwy
na GPU, ale wypchnij eksperckie FFN-y z części z nich do RAM-u:</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 mixtral-8x7b-instruct-v0.1.Q4_K_M.gguf <span class="se">\
</span></span></span><span class="line"><span class="cl">  -ngl all --n-cpu-moe <span class="m">12</span> -c <span class="m">8192</span> -fa on -ctk q8_0 -ctv q8_0 -cnv
</span></span></code></pre></div><p>Które flagi co robią i w jakiej kolejności się nimi ratować przy „out of memory&quot;,
rozpisaliśmy w <a href="/poradniki/flagi-llama-cpp/">poradniku o flagach llama.cpp</a>. Sam Mixtral
8x7B (base i Instruct) jest na licencji Apache 2.0<sup id="fnref5:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> — ściągasz, odpalasz, kombinujesz
bez proszenia nikogo o zgodę.</p>
<p>Jedno ostrzeżenie na koniec, żebyś nie wpadł w drugą pułapkę. To „liczy jak 13B&quot;
obowiązuje, gdy generujesz jeden strumień token po tokenie. Gdy walisz <strong>batchem</strong> — wiele
sekwencji naraz — różne tokeny w paczce wybierają różnych ekspertów, więc w danej warstwie
i tak zwykle rozgrzewa się większość albo cała ósemka jednocześnie. Teoretyczne
przyspieszenie topnieje. MoE lubi pojedynczego użytkownika przy biurku bardziej niż
zatłoczony serwer.</p>
<p>Wyobraź sobie warsztat z ośmioma fachowcami przy każdym stanowisku. Do każdej śrubki brygadzista
woła tylko dwóch — akurat tych, co się na niej znają. Reszta stoi i pije kawę. Robota idzie
szybko, jakbyś płacił za dwóch. Ale wypłatę i tak wystawiasz całej ósemce, bo nigdy nie wiesz,
która śrubka przyjdzie następna i kogo do niej trzeba będzie zawołać. I to jest cały Mixtral:
tempo dwójki, lista płac ósemki. Twoja karta nie płaci za tych, co pracują — płaci za tych,
co czekają w gotowości.</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>Jiang i in., <em>Mixtral of Experts</em>, arXiv:2401.04088 — abstrakt i sekcja architektury: 8 ekspertów na warstwę FFN, routing top-2, wynik dorównujący/bijący Llamę 2 70B i GPT-3.5, licencja Apache 2.0. <a href="https://arxiv.org/abs/2401.04088">arxiv.org/abs/2401.04088</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>Mistral AI, oficjalne ogłoszenie <em>Mixtral of Experts</em> — dokładne liczby 46,7B total / 12,9B active, mechanizm routera (8 grup parametrów, wybór 2, addytywne łączenie wyjść), stwierdzenie, że koszt i szybkość odpowiadają modelowi 12,9B. <a href="https://mistral.ai/news/mixtral-of-experts">mistral.ai/news/mixtral-of-experts</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>config.json</code> modelu <code>mistralai/Mixtral-8x7B-v0.1</code> — <code>num_local_experts: 8</code>, <code>num_experts_per_tok: 2</code>, <code>hidden_size: 4096</code>, <code>num_hidden_layers: 32</code>. <a href="https://huggingface.co/mistralai/Mixtral-8x7B-v0.1/raw/main/config.json">huggingface.co/mistralai/Mixtral-8x7B-v0.1/raw/main/config.json</a>.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>llama.cpp, definicje flag <code>-cmoe</code>/<code>--cpu-moe</code> („keep all Mixture of Experts (MoE) weights in the CPU&quot;) oraz <code>-ncmoe</code>/<code>--n-cpu-moe N</code> („keep the Mixture of Experts (MoE) weights of the first N layers in the CPU&quot;) — sam mechanizm istnieje właśnie dlatego, że wag ekspertów nie da się usunąć z pamięci; można je jedynie przenieść z VRAM-u do RAM-u. <a href="https://github.com/ggml-org/llama.cpp/blob/master/common/arg.cpp">common/arg.cpp</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>
</ol>
</div>
]]></content:encoded></item></channel></rss>