<?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>Flash-Attention — Inferownia</title><link>https://inferownia.pl/tags/flash-attention/</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/flash-attention/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>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></channel></rss>