<?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>Naukowy — Inferownia</title><link>https://inferownia.pl/naukowy/</link><description>Omówienia świeżych papierów o LLM i lokalnym AI z arXiv — po polsku, własnymi słowami.</description><language>pl-PL</language><copyright>&#169; 2026 Inferownia</copyright><lastBuildDate>Wed, 15 Jul 2026 00:00:00 +0200</lastBuildDate><atom:link href="https://inferownia.pl/naukowy/index.xml" rel="self" type="application/rss+xml"/><item><title>QLoRA: fine-tuning dużego modelu w 4 bitach</title><link>https://inferownia.pl/naukowy/qlora/</link><pubDate>Wed, 15 Jul 2026 00:00:00 +0200</pubDate><author>Tim Dettmers</author><author>Artidoro Pagnoni</author><author>Ari Holtzman</author><author>Luke Zettlemoyer</author><category>fine-tuning</category><guid>https://inferownia.pl/naukowy/qlora/</guid><description>Dostroić model 65B na jednej karcie 48 GB, z jakością pełnego 16-bitowego treningu? QLoRA mrozi wagi w 4 bitach i trenuje same adaptery. Rozkładamy na części, jak to w ogóle ma prawo działać.</description><content:encoded><![CDATA[<p>Wyobraź sobie na chwilę, że bierzesz się wreszcie za to, co wiecznie odkładasz „na kiedyś&quot;:
dostroić model pod siebie — pod swój żargon, swoje dane, swój ton. Wchodzisz na jakiś poradnik o fine-tuningu, czytasz do
połowy z zapałem, a potem pada zdanie, które gasi światło: „potrzebujesz kilku kart A100
po 80 GB&quot;. No i tyle. Zamykasz kartę w przeglądarce, wracasz do promptowania gotowca i
mówisz sobie, że przecież i tak nie miałeś na to czasu. Na forach o fine-tuningu to gatunek posta
sam w sobie — ktoś pyta, jak dotrenować siódemkę na własnym korpusie, i pierwsza odpowiedź
brzmi „wynajmij GPU w chmurze&quot;. Fine-tuning latami był tym pokojem za szybą: widzisz, co
w środku, ale klamka jest po drugiej stronie.</p>
<p>W maju 2023 czterech badaczy z University of Washington tę szybę stłukło. Praca nazywa się
<em>QLoRA: Efficient Finetuning of Quantized LLMs</em>, a jej główna teza brzmi jak przechwałka
z forum: da się dostroić model <strong>65B na jednej karcie 48 GB</strong> i zachować jakość pełnego,
16-bitowego fine-tuningu.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Nie „prawie&quot;, nie „z gwiazdką na jakości&quot;. Tak blisko, że
w benchmarku różnicy praktycznie nie widać. Jak? Rozłóżmy to na części, bo cały trik jest
piękny i składa się z kilku osobnych sprytów, które dopiero razem zaskakują.</p>
<h2 id="skąd-się-bierze-ten-cały-vram">Skąd się bierze ten cały VRAM</h2>
<p>Zanim rozbijemy QLoRA na części, trzeba wiedzieć, na co przy treningu schodzi pamięć — bo to nie
tylko same wagi. Klasyczny fine-tuning musi trzymać na karcie cztery rzeczy naraz: wagi
modelu, gradienty (po jednej liczbie na każdą wagę), stany optymalizatora (Adam trzyma
ich dwa na wagę) i aktywacje. Przy pełnym treningu w 16 bitach to znaczy, że sama arytmetyka
optymalizatora potrafi zająć więcej niż sam model. Dlatego dotrenowanie „całego&quot; modelu 65B
to zabawa nie na jedną kartę, a na małą serwerownię.</p>
<p>Pierwszy pomysł, żeby to obejść, jest starszy od QLoRA i nazywa się <strong>LoRA</strong> (Low-Rank
Adapters). Zamiast ruszać wszystkie miliardy wag, zamrażasz je w całości, a obok doklejasz
malutkie, niskorangowe macierze — adaptery — i trenujesz tylko je. Nagle gradienty i
optymalizator dotyczą ułamka parametrów. To już był przełom. Ale został jeden gruby
kamień w bucie: <strong>zamrożone wagi bazowe i tak muszą siedzieć na karcie</strong>, żeby przez nie
puścić forward i backward. A dla modelu 65B te zamrożone wagi w 16 bitach to wciąż jakieś
130 GB. Adaptery są tanie, ale bilet wstępu — sam model — dalej jest za drogi.</p>
<h2 id="trzy-sprytne-rzeczy-które-robi-qlora">Trzy sprytne rzeczy, które robi QLoRA</h2>
<p>I tu wchodzi sedno pracy. Skoro te wagi bazowe są zamrożone — nikt ich w treningu nie
zmienia — to po co trzymać je w pełnej precyzji? QLoRA mówi: skwantyzujmy je do <strong>4 bitów</strong>,
zamroźmy je w tej postaci, a gradient przepuśćmy wstecz przez ten zamrożony 4-bitowy model
prosto do adapterów LoRA.<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Trenujemy dalej tylko adaptery — ale bilet wstępu właśnie
schudł czterokrotnie. Żeby to zagrało bez utraty jakości, autorzy dorzucili trzy osobne
patenty.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<p><strong>Raz — NF4, czyli 4-bit NormalFloat.</strong> Nie wszystkie 4-bitowe typy danych są sobie równe. Wagi
wytrenowanej sieci nie są rozrzucone byle jak — układają się mniej więcej w rozkład
normalny, taką dzwonową górkę wokół zera. NF4 to typ danych skrojony dokładnie pod ten
kształt, teoretycznie optymalny informacyjnie dla wag o rozkładzie normalnym. To ważne:
NF4 <strong>nie jest</strong> zwykłym zmiennoprzecinkowym FP4 z IEEE. To specjalny format, który
„marnuje&quot; mniej z tych czterech bitów, bo wie z góry, gdzie wagi się tłoczą.<sup id="fnref1:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<p><strong>Dwa — double quantization (podwójna kwantyzacja).</strong> Kwantyzacja blokowa musi gdzieś
zapisać stałe skalujące — po jednej na blok wag. To drobne liczby, ale jak masz ich
miliony, robi się z tego zauważalny narzut. QLoRA kwantyzuje więc… same stałe kwantyzacji.
Kwantyzacja kwantyzacji brzmi jak żart, a oszczędza realnie około 0,4 bita na parametr.<sup id="fnref2:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>
Przy modelu 65B te ułamki bitów zamieniają się w gigabajty.</p>
<p><strong>Trzy — paged optimizers (optymalizatory stronicowane).</strong> Przy gradient checkpointingu
zużycie pamięci potrafi nagle skoczyć — pojawia się pik i karta krztusi się „out of memory&quot;
w najmniej spodziewanym momencie. Paged optimizers używają NVIDIA unified memory, żeby te
skoki obsłużyć: gdy na GPU robi się ciasno, stany optymalizatora są po cichu przerzucane do
RAM-u i z powrotem, jak stronicowanie pamięci w systemie operacyjnym.<sup id="fnref2:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Dzięki temu
pik nie wywala całego treningu.</p>
<div class="callout">
  <span class="callout__label">Dlaczego to ważne dla lokalnego LLM</span>
<p>Najważniejsza rzecz do zapamiętania: <strong>4 bity nie znaczą, że cały trening liczy się w 4
bitach.</strong> W 4 bitach siedzą tylko zamrożone wagi bazowe — i to po to, żeby zająć mało miejsca.
Kiedy przychodzi liczyć forward i backward, te wagi są w locie dekwantyzowane, a same
obliczenia na aktywacjach idą w wyższej precyzji (typowo bf16 albo fp16). QLoRA to sprytny
sposób na <strong>przechowanie</strong> modelu, nie na liczenie w niższej precyzji. Dlatego jakość nie
leci na łeb — arytmetyka zostaje porządna.</p>
</div>
<h2 id="to-nie-jest-to-samo-co-gptq-czy-awq">To nie jest to samo co GPTQ czy AWQ</h2>
<p>Tu łatwo o zamieszanie, więc warto postawić słup graniczny. Kiedy słyszysz „kwantyzacja&quot;,
najczęściej chodzi o coś zupełnie innego niż QLoRA: o GPTQ albo AWQ, czyli metody, które
biorą <strong>gotowy, wytrenowany model i ściskają go pod samą inferencję</strong> (post-training
quantization). Rozgryzaliśmy ten drugi świat osobno <a href="/naukowy/gptq-awq/">tutaj</a> — i to jest
inna bajka.</p>
<p>QLoRA nie służy do odchudzania modelu pod odpalanie. QLoRA to <strong>metoda fine-tuningu</strong> —
kwantyzacja 4-bitowa jest w niej tylko trikiem, który pozwala zmieścić trening na jednej
karcie. Efekt końcowy to wytrenowane adaptery LoRA, które doklejasz do modelu. Zapamiętaj
prostą regułę: GPTQ/AWQ to „jak taniej <strong>uruchomić</strong> cudzy model&quot;, QLoRA to „jak tanio
<strong>dostroić</strong> model pod siebie&quot;. Mylenie tych dwóch to klasyk początkującego na forach.</p>
<h2 id="guanaco-czyli-dowód-że-to-działa">Guanaco, czyli dowód, że to działa</h2>
<p>Teoria teorią, ale autorzy poszli na całość: tą metodą dostroili <strong>ponad 1000 modeli</strong> —
różne architektury (LLaMA, T5), różne skale (7B, 13B, 33B, 65B), osiem zestawów danych
instrukcyjnych. Skale, których zwykłym fine-tuningiem nikt by na tym sprzęcie nie ruszył.<sup id="fnref3:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>Najlepsza rodzina dostała nazwę <strong>Guanaco</strong> (tak, po tej andyjskiej kuzynce lamy — w tym
świecie wszystko jest lamą). Trenowana na danych OpenAssistant, w benchmarku Vicuna
przebiła wszystkie wcześniej publicznie wydane modele i osiągnęła <strong>99,3% poziomu ChatGPT</strong>
— przy zaledwie <strong>24 godzinach</strong> fine-tuningu na jednej karcie.<sup id="fnref4:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>Tu tylko jedna uczciwa gwiazdka, którą stawiają sami autorzy: te 99,3% to wynik z
konkretnego benchmarku Vicuna, ocenianego przez GPT-4 — nie jakaś uniwersalna miara „model
jest w 99% tak dobry jak ChatGPT do wszystkiego&quot;. Praca wprost pokazuje, że ocena GPT-4 to
tania i sensowna alternatywa dla oceny ludzkiej, ale zastrzega, że ówczesne benchmarki
chatbotów nie były w pełni wiarygodne do precyzyjnego rankingowania modeli.<sup id="fnref5:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Liczba
robi wrażenie — traktuj ją jednak jako „zaskakująco blisko&quot;, a nie jako wyrok.</p>
<h2 id="zrób-to-sam-a-przynajmniej-wiedz-gdzie-klikać">Zrób to sam (a przynajmniej wiedz, gdzie klikać)</h2>
<p>Najlepsze jest to, że QLoRA nie została na papierze jako ciekawostka. Kod referencyjny i
modele Guanaco wylądowały na licencji MIT w repo <a href="https://github.com/artidoro/qlora">artidoro/qlora</a>,
razem z jądrami CUDA do treningu 4-bitowego.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> A w praktyce prawie nikt nie pisze tego
od zera — całość jest wpięta w ekosystem Hugging Face. Kwantyzację robi biblioteka
<strong>bitsandbytes</strong>, adaptery dokłada <strong>PEFT</strong>, a spina to wszystko <code>transformers</code>.</p>
<p>Sercem konfiguracji jest <code>BitsAndBytesConfig</code> — i to w nim odnajdziesz jak żywe wszystkie
trzy patenty z papieru:<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">transformers</span> <span class="kn">import</span> <span class="n">BitsAndBytesConfig</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">torch</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">bnb_config</span> <span class="o">=</span> <span class="n">BitsAndBytesConfig</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">load_in_4bit</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>                    <span class="c1"># wagi bazowe w 4 bitach</span>
</span></span><span class="line"><span class="cl">    <span class="n">bnb_4bit_quant_type</span><span class="o">=</span><span class="s2">&#34;nf4&#34;</span><span class="p">,</span>            <span class="c1"># patent nr 1: NF4, nie zwykły FP4</span>
</span></span><span class="line"><span class="cl">    <span class="n">bnb_4bit_use_double_quant</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>       <span class="c1"># patent nr 2: podwójna kwantyzacja</span>
</span></span><span class="line"><span class="cl">    <span class="n">bnb_4bit_compute_dtype</span><span class="o">=</span><span class="n">torch</span><span class="o">.</span><span class="n">bfloat16</span> <span class="c1"># obliczenia w bf16, nie w 4 bitach!</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></div><p>Popatrz na ostatnią linijkę — <code>compute_dtype</code> to bf16 (a czym <code>bf16</code> różni się od <code>f16</code>, rozbieramy <a href="/poradniki/bf16-f16-f32/">osobno</a>), dokładnie ta wyższa precyzja
obliczeń, o której gadaliśmy wyżej. Wagi leżą w 4 bitach, ale liczy się porządnie.</p>
<p>A ile realnie karty to zjada? Zależy od modelu, długości sekwencji, batcha i checkpointingu
— liczby 65B/48 GB z papieru to konkretny eksperyment, nie uniwersalna tabelka. Ale skala
robi wrażenie z drugiej strony też: dzięki nested quantization dokumentacja Hugging Face
pokazuje dostrojenie <strong>Llama-13B na jednej karcie T4 16 GB</strong> — sekwencja 1024 tokeny,
batch 1, gradient accumulation na 4 kroki.<sup id="fnref1:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> T4 to karta, którą dostajesz za darmo
w Google Colab.</p>
<table>
	<thead>
			<tr>
					<th>Metoda</th>
					<th>Co robi z wagami bazowymi</th>
					<th>Ile parametrów trenujesz</th>
					<th>Po co</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Pełny fine-tuning</td>
					<td>16 bitów, wszystkie odmrożone</td>
					<td>wszystkie</td>
					<td>najdroższy, „złoty standard&quot;</td>
			</tr>
			<tr>
					<td>LoRA</td>
					<td>16 bitów, zamrożone</td>
					<td>tylko adaptery</td>
					<td>tanio, ale wagi 16-bit dalej na karcie</td>
			</tr>
			<tr>
					<td><strong>QLoRA</strong></td>
					<td><strong>4 bity (NF4), zamrożone</strong></td>
					<td><strong>tylko adaptery</strong></td>
					<td><strong>tanio i mieści się na jednej karcie</strong></td>
			</tr>
			<tr>
					<td>GPTQ / AWQ</td>
					<td>4 bity, pod inferencję</td>
					<td>nic (to nie trening)</td>
					<td>tanie odpalanie gotowca</td>
			</tr>
	</tbody>
</table>
<p>Jeśli martwi cię, czy w ogóle zmieścisz coś na swojej karcie, to osobna rachunkowość —
zbieraliśmy wzory na VRAM <a href="/poradniki/ile-vram-na-model/">tutaj</a>. Ale morał QLoRA jest
prosty: bilet wstępu spadł tak, że fine-tuning przestał być salą VIP.</p>
<p>Przez lata fine-tuning działał jak wędkarstwo z burty jachtu — niby każdy może, tylko
najpierw kup jacht. QLoRA wsadziła ci w rękę wędkę i pokazała, że ryba bierze tak samo z
pomostu za pięć złotych. Zamrożone wagi ściśnięte do czterech bitów, garść adapterów do
dotrenowania, jedna karta pod biurkiem — i model, który jeszcze wczoraj wymagał serwerowni,
uczy się twojego żargonu przez noc. Nie dlatego, że ktoś dołożył ci pamięci. Dlatego, że
ktoś w końcu zapytał, czy naprawdę trzeba trzymać w pełnej precyzji coś, czego i tak nikt
w treningu nie rusza.</p>
<hr>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Tim Dettmers, Artidoro Pagnoni, Ari Holtzman, Luke Zettlemoyer, <em>QLoRA: Efficient Finetuning of Quantized LLMs</em>, arXiv:2305.14314 (23 maja 2023) — abstrakt: 65B na jednej karcie 48 GB przy jakości 16-bit, NF4, double quantization, paged optimizers, rodzina Guanaco 99,3% ChatGPT w benchmarku Vicuna, ponad 1000 wytrenowanych modeli i zastrzeżenia co do wiarygodności benchmarków. <a href="https://arxiv.org/abs/2305.14314">arxiv.org/abs/2305.14314</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>Hugging Face blog, <em>Making LLMs even more accessible with bitsandbytes, 4-bit quantization and QLoRA</em> — opis trzech innowacji (NF4, double quantization, paged optimizers) i oszczędności ~0,4 bita/parametr z podwójnej kwantyzacji. <a href="https://huggingface.co/blog/4bit-transformers-bitsandbytes">huggingface.co/blog/4bit-transformers-bitsandbytes</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>Oficjalne repozytorium kodu QLoRA — implementacja referencyjna, jądra CUDA do treningu 4-bitowego, modele Guanaco (trenowane na OpenAssistant), licencja MIT. <a href="https://github.com/artidoro/qlora">github.com/artidoro/qlora</a>.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Hugging Face Transformers, dokumentacja kwantyzacji bitsandbytes — parametry <code>BitsAndBytesConfig</code> (<code>load_in_4bit</code>, <code>bnb_4bit_quant_type=&quot;nf4&quot;</code>, <code>bnb_4bit_use_double_quant</code>, <code>bnb_4bit_compute_dtype</code>) oraz przykład dostrojenia Llama-13B na T4 16 GB z nested quantization. <a href="https://huggingface.co/docs/transformers/main/en/quantization/bitsandbytes">huggingface.co/docs/transformers/main/en/quantization/bitsandbytes</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><item><title>BitNet b1.58: era 1-bitowych LLM-ów</title><link>https://inferownia.pl/naukowy/bitnet-1bit/</link><pubDate>Mon, 13 Jul 2026 00:00:00 +0200</pubDate><author>Shuming Ma</author><author>Hongyu Wang</author><author>Lingxiao Ma</author><author>Lei Wang</author><author>Wenhui Wang</author><author>Shaohan Huang</author><author>Li Dong</author><author>Ruiping Wang</author><author>Jilong Xue</author><author>Furu Wei</author><category>kwantyzacja</category><guid>https://inferownia.pl/naukowy/bitnet-1bit/</guid><description>Miliardy wag modelu ściśnięte do trzech wartości: minus jeden, zero, plus jeden. Bez mnożeń, prawie bez pamięci — i, od pewnego rozmiaru, prawie bez straty jakości. Rozbieramy pracę, która ogłosiła erę 1-bitowych LLM-ów.</description><content:encoded><![CDATA[<p>Odchudzanie modelu to sport bez mety — zawsze znajdzie się ktoś, kto urwie jeszcze jeden bit.
Najpierw było szesnaście bitów na wagę i pliki, przy których dysk kwiczał. Potem przyszła kwantyzacja
po treningu i zeszliśmy na osiem, na cztery, gdzieś na dnie forum ktoś odpalał dwa. Za każdym
razem wracało to samo pytanie — „a da się jeszcze niżej?&quot; — i za każdym razem ktoś
mądrzejszy odpowiadał, że przy dwóch bitach model zaczyna gadać jak po trzech piwach. No i
w tym całym schodzeniu w dół pada w końcu pytanie, które brzmi jak żart: a gdyby tak jeden
bit? Gdyby waga mogła być tylko włączona albo wyłączona?</p>
<p>Okazuje się, że to nie żart. W lutym 2024 grupa z Microsoft Research położyła na arXiv pracę
o wdzięcznym tytule „The Era of 1-bit LLMs&quot; i pokazała coś, co na pierwszy rzut oka wygląda
na herezję: model, w którym każda waga przyjmuje jedną z trzech wartości — minus jeden, zero
albo plus jeden — a mimo to od pewnego rozmiaru dorównuje pełnoprecyzyjnej LLaMie.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>
Nazwali go BitNet b1.58. I zanim złapiesz się na tym „1.58 bita&quot; jak na literówce — spokojnie,
to nie literówka. To rozłóżmy na części.</p>
<h2 id="skąd-te-158-bita--czyli-dlaczego-nie-okrągła-jedynka">Skąd te 1,58 bita — czyli dlaczego nie okrągła jedynka</h2>
<p>Najpierw sama nazwa, bo ona zdradza cały pomysł. Skoro waga może być tylko jedną z trzech
rzeczy — <code>-1</code>, <code>0</code> albo <code>+1</code> — to ile informacji trzeba, żeby taki stan zapisać? Logarytm
przy podstawie dwa z trzech. A <code>log2(3)</code> to jakieś 1,58.<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Stąd <code>b1.58</code>: nie dlatego,
że ktoś w pamięci trzyma ułamki bitów, tylko dlatego, że tyle wynosi teoretyczny koszt
informacyjny wyboru jednego z trzech wariantów.</p>
<p>I tu pierwsza pułapka, w którą łatwo wdepnąć: <strong>1,58 bita to nie jest dosłowna liczba bitów,
jaką waga zajmuje na dysku.</strong> To granica informacyjna, nie format zapisu. W praktycznych
implementacjach — kiedy ktoś pakuje taki model do GGUF-a — wartość ternarna i tak ląduje
zwykle na dwóch bitach plus trochę struktury dookoła. Więc jak zobaczysz „1.58-bit&quot; na karcie
modelu, czytaj to jako „trzy stany na wagę&quot;, a nie jako obietnicę, że plik jest fizycznie
pięciokrotnie mniejszy niż czterobitowy. To dwa różne piętra: ile <em>musisz</em> zapamiętać, a jak
<em>akurat</em> to zapakowałeś.</p>
<p>Dlaczego akurat ternarne, a nie czyste zero-jeden? Bo to zero pośrodku robi robotę. Waga
równa zeru to po ludzku „to połączenie mnie nie obchodzi&quot; — model dostaje wbudowaną możliwość
wycięcia neuronu z równania. Oryginalny BitNet, wcześniejsza praca tej samej ekipy z 2023
roku, jechał na czystych wagach binarnych, tylko <code>-1</code> i <code>+1</code>.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> Dorzucenie zera to
właśnie ten skok od BitNetu do BitNetu b1.58 — z pozoru drobny, a robi całą różnicę w jakości.</p>
<h2 id="trenowany-od-zera--i-czemu-to-nie-jest-zwykła-kwantyzacja">„Trenowany od zera&quot; — i czemu to nie jest zwykła kwantyzacja</h2>
<p>Teraz sedno, bo tu większość ludzi przy pierwszym czytaniu odruchowo myśli
o złym torze. Kiedy słyszysz „model na trzech wartościach&quot;, w głowie zapala się „aha, czyli
kolejna kwantyzacja&quot; — bierzemy gotową, wytrenowaną w FP16 sieć i po fakcie zaokrąglamy jej
wagi. Tak działa GGUF-owe <code>Q4_K_M</code>, tak działają GPTQ czy AWQ — rozgryzaliśmy te potreningowe
sztuczki <a href="/naukowy/gptq-awq/">tutaj</a>. Model istnieje najpierw w pełnej precyzji, a dopiero
potem go ściskasz i modlisz się, że nie zgłupieje.</p>
<p><strong>BitNet b1.58 działa dokładnie odwrotnie.</strong> Nikt tu niczego nie kwantyzuje po fakcie. Model
jest trenowany od zera z wagami zamkniętymi w tych trzech wartościach — sieć od pierwszego
kroku uczy się żyć w ternarnym świecie i sama układa sobie wagi tak, żeby jej to nie
przeszkadzało.<sup id="fnref2:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> To fundamentalna różnica: taki model <strong>nigdy nie istniał w FP16.</strong> Nie
ma oryginału, z którego zszedłeś w dół i coś po drodze zgubiłeś. Ternarność nie jest stratą
nałożoną na koniec — jest warunkiem brzegowym od startu.</p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>Kwantyzacja po treningu (PTQ)</th>
					<th>BitNet b1.58</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Kiedy niska precyzja</td>
					<td>po treningu, na gotowej sieci</td>
					<td>od pierwszego kroku treningu</td>
			</tr>
			<tr>
					<td>Czy istnieje wersja FP16</td>
					<td>tak, to oryginał</td>
					<td>nie, nigdy nie istniała</td>
			</tr>
			<tr>
					<td>Wagi</td>
					<td>ściśnięte np. do 4 bitów</td>
					<td>ternarne <code>{-1, 0, +1}</code></td>
			</tr>
			<tr>
					<td>Aktywacje</td>
					<td>różnie</td>
					<td>8-bitowe (schemat W1.58A8)</td>
			</tr>
			<tr>
					<td>Typowe ryzyko</td>
					<td>jakość spada przy mocnym cięciu</td>
					<td>luka jakości znika dopiero od pewnego rozmiaru</td>
			</tr>
	</tbody>
</table>
<p>Skąd model wie, na którą z trzech wartości zaokrąglić daną wagę podczas treningu? Robi to
funkcja o ładnej nazwie <code>absmean</code>. Cała macierz wag jest najpierw skalowana przez swoją
średnią wartość bezwzględną, a potem każda liczba idzie do najbliższej całkowitej ze zbioru
<code>{-1, 0, +1}</code>.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> Proste jak konstrukcja cepa i o to chodzi — im mniej fikuśne, tym
lepiej się liczy.</p>
<h2 id="mnożenie-jakie-mnożenie">Mnożenie? Jakie mnożenie</h2>
<p>Tu wchodzi część, dla której cały ten cyrk się opłaca. Serce każdego LLM-a to mnożenie
macierzy — miliardy mnożeń liczby przez liczbę, i to właśnie one zżerają prąd i czas. A teraz
policz na palcach, co się dzieje, kiedy jeden z czynników może być tylko <code>-1</code>, <code>0</code> albo <code>+1</code>.
Mnożenie przez <code>+1</code>? To po prostu dodaj tę drugą liczbę. Przez <code>-1</code>? Odejmij. Przez <code>0</code>?
Pomiń, nic nie rób.</p>
<p>I nagle znika mnożenie. Zostają same dodawania i odejmowania — a te są dla krzemu dużo
tańsze. Praca podaje twardą liczbę: dla samej operacji mnożenia macierzy to około <strong>71 razy
mniej energii</strong> na chipie 7nm w porównaniu z odpowiednikiem w FP16.<sup id="fnref1:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> Nie o kilka
procent — o rząd wielkości i to z hakiem. To nie jest „lepsza kompresja&quot;, to zmiana tego,
jaką operację w ogóle wykonuje procesor.</p>
<div class="callout">
  <span class="callout__label">Dlaczego to ważne dla lokalnego LLM</span>
<p>Dla ciebie, człowieka z jedną kartą pod biurkiem, znaczenie jest dwustronne. Z jednej strony
mniej pamięci i mniej prądu na token to marzenie każdego, kto liczył, ile VRAM-u zeżre
<a href="/poradniki/kv-cache-vram/">KV-cache przy długim kontekście</a>. Z drugiej — mnożenie zamienione
na dodawanie najlepiej opłaca się na sprzęcie, który to rozumie. Autorzy wprost sugerują, że
era 1-bitowych modeli woła o nowy, dedykowany krzem projektowany pod ternarną arytmetykę.<sup id="fnref2:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>
Dzisiejsze GPU są zbudowane pod mnożenie zmiennoprzecinkowe — więc pełnię tej obietnicy
zobaczymy dopiero, gdy hardware nadgoni pomysł.</p>
</div>
<h2 id="haczyk-to-działa-dopiero-od-pewnego-rozmiaru">Haczyk: to działa dopiero od pewnego rozmiaru</h2>
<p>Zanim pobiegniesz ogłaszać koniec FP16, jedna trzeźwa uwaga — i to taka, którą sami autorzy
kładą na stół, bez chowania pod dywan. Ta cudowna „jakość jak w pełnej precyzji&quot; <strong>nie pojawia
się na małych modelach.</strong> Przy 700M czy 1,3B parametrów między BitNetem b1.58 a pełną LLaMą
wciąż widać zauważalną lukę — ternarny model gada wyraźnie gorzej.<sup id="fnref3:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></p>
<p>Magia zaczyna się gdzieś od 3 miliardów parametrów w górę. Dopiero od tego progu BitNet b1.58
dogania pełnoprecyzyjną LLaMę zarówno pod względem perplexity (miary tego, jak model „dziwi się&quot;
tekstowi), jak i na zadaniach końcowych.<sup id="fnref4:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> Model 3,9B potrafi już przebić 3B LLaMę — i to
przy mniejszym zużyciu pamięci oraz niższej latencji.<sup id="fnref5:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> Reszta liczb z pracy jest w tym
samym duchu: redukcja pamięci 2,6–3,55×, przyspieszenie generacji 1,23–4,1× (rośnie z
rozmiarem), a dla modelu 70B przepustowość wyższa 8,9× przy jedenastokrotnie większym
batchu.<sup id="fnref6:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></p>
<p>Stąd bierze się najciekawszy wniosek pracy — nowe „prawo skalowania&quot;. Autorzy pokazują, że
13-miliardowy BitNet b1.58 potrafi być bardziej efektywny — pamięciowo, energetycznie i
czasowo — niż zwykły model 3B w FP16.<sup id="fnref7:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> Innymi słowy: przestań patrzeć na samą liczbę
parametrów. Przy ternarnych wagach większy model może być lżejszy w użyciu od mniejszego
pełnoprecyzyjnego. To wywraca do góry nogami intuicję, którą wszyscy nosimy — że więcej
parametrów zawsze znaczy cięższy plik.</p>
<h2 id="zrób-to-sam--microsoft-wypuścił-konkretny-model">Zrób to sam — Microsoft wypuścił konkretny model</h2>
<p>To nie zostało teorią na papierze. Microsoft wydał gotowy, produkcyjny model:
<code>microsoft/bitnet-b1.58-2B-4T</code> — około 2 miliardów parametrów, wytrenowany na 4 bilionach
tokenów, na licencji MIT.<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> Pod maską to transformer z warstwami BitLinear (to w nich
siedzą te ternarne wagi), z RoPE, z aktywacją squared ReLU w FFN, normalizacją subln i bez
biasów.<sup id="fnref1:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> Schemat, który już znasz z tego wpisu: W1.58A8 — wagi 1,58-bitowe, aktywacje
ośmiobitowe.</p>
<p>I teraz najważniejsza pułapka praktyczna, bo tu połowa ludzi się nabiera. <strong>Jeśli odpalisz
ten model przez zwykłą bibliotekę <code>transformers</code> z Hugging Face — nie zobaczysz żadnych
oszczędności.</strong> Karta modelu mówi to wprost: efektywność pamięci i energii ujawnia się
wyłącznie z dedykowaną implementacją.<sup id="fnref2:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> <code>transformers</code> policzy ci ten model jak każdy
inny, gęsto i drogo — bo nie umie skorzystać z tego, że mnożenia są zbędne. To trochę jak
kupić samochód na prąd i tankować go benzyną z kanistra.</p>
<p>Tą dedykowaną implementacją jest <code>bitnet.cpp</code> — framework inferencyjny z repozytorium
<code>microsoft/BitNet</code> na GitHubie.<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> I tu druga pułapka, tym razem dla porządkowych: to
<strong>nie</strong> jest część <code>ggml-org</code>, mimo że stoi na llama.cpp i pachnie tym samym ekosystemem.
To osobny projekt utrzymywany przez Microsoft, opisany we własnej pracy na arXiv.<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup>
Dostarcza dedykowane kernele — <code>I2_S</code> na x86 i ARM, <code>TL1</code> na ARM, <code>TL2</code> na x86 — i to one
robią tę robotę z zamianą mnożeń na dodawania.<sup id="fnref1:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone --recursive https://github.com/microsoft/BitNet.git
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> BitNet
</span></span><span class="line"><span class="cl"><span class="c1"># dokładne kroki (setup_env.py, wybór kernela, pobranie GGUF-a)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># są w README repozytorium — trzymaj się ich, bo się zmieniają</span>
</span></span></code></pre></div><p>Liczby z <code>bitnet.cpp</code> są równie soczyste jak z papieru: na ARM-owym CPU przyspieszenia
1,37–5,07× i redukcja energii 55–70%, na x86 przyspieszenia 2,37–6,17× i redukcja energii
72–82%.<sup id="fnref2:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> Wisienka na torcie: framework pozwala uruchomić model klasy 100B na pojedynczym
CPU z prędkością rzędu 5–7 tokenów na sekundę.<sup id="fnref3:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> Sto miliardów parametrów. Na procesorze.
Bez karty. Przeczytaj to zdanie jeszcze raz i przypomnij sobie, ile kosztuje GPU zdolne
udźwignąć taki model po staremu.</p>
<p>Cała droga lokalnego LLM-owca to jedno długie schodzenie po drabinie precyzji. Szesnaście
bitów, osiem, cztery — i za każdym razem ktoś na forum wróżył, że niżej już się nie da, że
na dole tej drabiny model rozpłynie się w bełkocie. BitNet b1.58 nie tyle zszedł o kolejny
szczebel, co zauważył, że drabina od początku stała nie tam. Zamiast ściskać gotowe liczby
coraz mocniej — nauczył sieć od urodzenia myśleć trzema stanami i wyrzucił mnożenie za burtę.</p>
<p>Czy to już koniec ery zmiennoprzecinkowej? Nie dziś — dopóki krzem pod biurkiem woli mnożyć
niż dodawać, pełnia tej obietnicy leży zapakowana i czeka na sprzęt, który ją rozpakuje. Ale
kierunek jest wytyczony, model do ściągnięcia leży na Hugging Face, a <code>bitnet.cpp</code> już liczy.
Czasem nie zmieniasz odpowiedzi na stare pytanie — zmieniasz pytanie. Nie „ile bitów damy
radę utrzymać&quot;, tylko „po co nam w ogóle mnożenie&quot;.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Ma, Wang, Ma i in., <em>The Era of 1-bit LLMs: All Large Language Models are in 1.58 Bits</em>, arXiv:2402.17764, <a href="https://arxiv.org/abs/2402.17764">arxiv.org/abs/2402.17764</a> — tytuł, autorzy, data zgłoszenia (27 lutego 2024), definicja wag ternarnych <code>{-1, 0, +1}</code>, <code>log2(3) ≈ 1,58 bita</code> i trening od zera.&#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>Wang i in., <em>BitNet: Scaling 1-bit Transformers for Large Language Models</em>, arXiv:2310.11453, <a href="https://arxiv.org/abs/2310.11453">arxiv.org/abs/2310.11453</a> — poprzednik b1.58 z wagami binarnymi <code>{-1, +1}</code>.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><em>The Era of 1-bit LLMs</em> — pełny tekst, <a href="https://arxiv.org/html/2402.17764v1">arxiv.org/html/2402.17764v1</a> — funkcja <code>absmean</code>, próg ~3B dla dopasowania do FP16, luka na 700M/1.3B, model 3.9B kontra 3B LLaMA, liczby dot. pamięci/latencji/przepustowości, ~71× mniej energii na matmul (7nm), nowe prawo skalowania i wniosek o dedykowanym sprzęcie.&#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>&#160;<a href="#fnref2:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref3:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref4:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref5:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref6:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref7:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Karta modelu <code>microsoft/bitnet-b1.58-2B-4T</code>, <a href="https://huggingface.co/microsoft/bitnet-b1.58-2B-4T">huggingface.co/microsoft/bitnet-b1.58-2B-4T</a> — 2B parametrów, 4T tokenów, schemat W1.58A8, warstwy BitLinear, RoPE, squared ReLU, subln, brak biasów, licencja MIT oraz zastrzeżenie o braku zysków wydajności przez <code>transformers</code>.&#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>&#160;<a href="#fnref2:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p><code>microsoft/BitNet</code> — oficjalne repozytorium <code>bitnet.cpp</code>, <a href="https://github.com/microsoft/BitNet">github.com/microsoft/BitNet</a> — kernele <code>I2_S</code>/<code>TL1</code>/<code>TL2</code>, zamiana mnożeń na dodawania/odejmowania, przyspieszenia i redukcja energii na ARM/x86, uruchomienie modelu 100B na pojedynczym CPU (~5–7 tok/s).&#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>&#160;<a href="#fnref2:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref3:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>Wang i in., <em>Bitnet.cpp: Efficient Edge Inference for Ternary LLMs</em>, arXiv:2502.11880, <a href="https://arxiv.org/pdf/2502.11880">arxiv.org/pdf/2502.11880</a> — osobna praca opisująca framework inferencyjny dla modeli ternarnych.&#160;<a href="#fnref: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><item><title>Mamba: co potrafią modele przestrzeni stanów</title><link>https://inferownia.pl/naukowy/mamba-ssm/</link><pubDate>Tue, 07 Jul 2026 00:00:00 +0200</pubDate><author>Albert Gu</author><author>Tri Dao</author><category>inferencja</category><guid>https://inferownia.pl/naukowy/mamba-ssm/</guid><description>Transformer płaci za każdy token kwadratem, a jego KV-cache puchnie z długością rozmowy jak balon. Mamba mówi: da się liniowo i przy stałym stanie. Rozkładamy na części, co potrafią selektywne modele przestrzeni stanów.</description><content:encoded><![CDATA[<p>Jest taki sposób czytania, który brzmi jak kara: przy każdym kolejnym słowie wracasz na
początek i przebiegasz wzrokiem wszystko, co już przeczytałeś. Zdanie dalej — znowu od zera.
Brzmi absurdalnie, a mniej więcej tak właśnie zwykły transformer obchodzi się z długim
tekstem. Im dłuższy kontekst wrzucasz, tym więcej pamięci schodzi, i to nie po
równo, tylko coraz szybciej. Wśród ludzi, co odpalają to w domu, wraca to jak natrętny wątek: „mam 24 GB, czemu przy
32k kontekstu i tak leci OOM?&quot;. Odpowiedź brzmi zawsze tak samo — bo attention, ta sama
uwaga, która zrobiła z transformerów króla, ma pewien brzydki nawyk. Za każdy token płaci
kwadratem.</p>
<p>I przez lata żyliśmy z tym jak z hałaśliwym sąsiadem: da się, tylko trzeba akceptować.
Aż w grudniu 2023 Albert Gu i Tri Dao wrzucili na arXiv pracę o wdzięcznej nazwie
<em>Mamba</em>, w której powiedzieli coś w stylu „a gdyby tak w ogóle wyrzucić attention?&quot;.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>
Nie ograniczyć, nie zoptymalizować — wyrzucić. Cały mechanizm uwagi, a przy okazji i bloki
MLP, i zastąpić je czymś, co skaluje się liniowo, a przy generowaniu trzyma stan o stałym
rozmiarze. Brzmi jak sprzedażowa bajka. A jednak papier ma liczby.</p>
<h2 id="skąd-w-ogóle-ten-kwadrat-w-attention">Skąd w ogóle ten kwadrat w attention</h2>
<p>Zanim zachwycimy się Mambą, warto wiedzieć, od czego ona ucieka. Uwaga w transformerze
działa tak, że każdy token patrzy na każdy inny token w sekwencji. Masz 1000 tokenów?
To milion par do policzenia. Masz 10 000? Sto milionów. Rośnie z kwadratem długości —
stąd to słynne O(n²), które przy długim kontekście robi się głównym żłobem na pamięć i czas.</p>
<p>Do tego dochodzi druga rzecz, boleśnie znajoma każdemu, kto odpala model lokalnie:
KV-cache. Żeby przy generowaniu nie liczyć uwagi od zera dla każdego nowego tokenu,
transformer trzyma w pamięci klucze i wartości wszystkich dotychczasowych tokenów. I ten
bufor rośnie <strong>liniowo z długością rozmowy</strong> — im dłużej gadasz, tym więcej VRAM-u zjada
sam cache, niezależnie od wag modelu. Rozgryzaliśmy ten mechanizm bliżej
<a href="/poradniki/kv-cache-vram/">tutaj</a>, ale sedno jest proste: transformer ma pamięć, która
puchnie. Zawsze puchnie.</p>
<p>Mamba wychodzi z zupełnie innej rodziny — z <strong>modeli przestrzeni stanów</strong> (SSM, <em>state
space models</em>).<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> To pomysł stary jak teoria sterowania: zamiast patrzeć na wszystko
naraz, przesuwasz się przez sekwencję krok po kroku i utrzymujesz jeden <strong>stan</strong> — wektor
o stałym rozmiarze, który streszcza wszystko, co do tej pory widziałeś. Nowy token wpada,
aktualizuje stan, idziesz dalej. Trochę jak RNN, tylko policzony sprytniej.</p>
<h2 id="dlaczego-wcześniejsze-ssm-nie-zabiły-transformera">Dlaczego wcześniejsze SSM nie zabiły transformera</h2>
<p>Bo miały jedną wadę, przez którą były głupsze niż attention. Klasyczny SSM — jak S4,
poprzednik Mamby od tych samych autorów — ma stałe parametry. Te same macierze przetwarzają
każdy token identycznie, bez względu na to, co ten token właściwie mówi. To pozwala policzyć
całość jako jeden wielki splot (konwolucję), błyskawicznie i równolegle — ale odbiera
modelowi zdolność, którą attention ma za darmo: <strong>rozumowanie oparte na treści</strong>.</p>
<p>Wyobraź sobie zdanie, w którym ważne jest jedno słowo gdzieś na początku, a reszta to
wypełniacz. Attention po prostu skupi się na tym jednym słowie. Stary SSM nie umie — traktuje
wypełniacz tak samo poważnie jak sedno, bo nie potrafi wybrać. Nie umie powiedzieć „to
zapamiętaj, a tamto olej&quot;.</p>
<h2 id="na-czym-polega-selekcja-cały-myk-mamby">Na czym polega „selekcja&quot;, cały myk Mamby</h2>
<p>I tu wchodzi jedna zmiana, która robi całą różnicę — <strong>selekcja</strong>. Gu i Dao uzależnili
parametry SSM od wejścia. Krok dyskretyzacji Δ oraz macierze B i C przestają być stałe
i stają się <strong>funkcjami aktualnego tokenu</strong>.<sup id="fnref2:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Innymi słowy: model przy każdym kroku
sam decyduje, ile z nowej informacji wpuścić do stanu, a ile ze starego stanu zapomnieć —
w zależności od tego, co właśnie czyta.</p>
<p>To jest ten moment „aha&quot;. Selektywny SSM potrafi zrobić to, czego stary nie umiał:
przepuścić ważny token dalej wzdłuż sekwencji, a nieważny wygasić. Dostaje content-based
reasoning, którym attention się chwalił — tylko bez płacenia kwadratem.</p>
<div class="callout">
  <span class="callout__label">Dlaczego to ważne dla lokalnego LLM</span>
<p>Dla ciebie, odpalającego model pod biurkiem, sedno jest takie: Mamba przy generowaniu
utrzymuje stan rekurencyjny o <strong>stałym rozmiarze</strong>. Nie ma analogu KV-cache, który rośnie
z długością rozmowy. Piąta godzina konwersacji zjada tyle samo pamięci co pierwsza minuta.
Uwaga tylko — to nie znaczy „brak stanu&quot;. Stan wciąż jest, ukryty wektor sobie siedzi
i pracuje; on po prostu nie puchnie z kontekstem. To różnica między plecakiem o stałej
pojemności a workiem, do którego dorzucasz kamień za każdym tokenem.</p>
</div>
<h2 id="cena-selekcji-znika-droga-na-skróty">Cena selekcji: znika droga na skróty</h2>
<p>Tyle że za wszystko się płaci. Uzależnienie parametrów od wejścia ma paskudny efekt
uboczny: <strong>zabija formę splotową</strong>. Skoro macierze zmieniają się z każdym tokenem, nie
policzysz już całości jednym szybkim splotem, jak w S4. Musisz wrócić do trybu
rekurencyjnego — krok po kroku — a to na GPU brzmi jak przepis na wolny, sekwencyjny
dramat.</p>
<p>Autorzy rozwiązali to algorytmem, który nazwali <strong>selective scan</strong>, i zaprojektowali go
„świadomie sprzętowo&quot; (<em>hardware-aware</em>), czerpiąc wprost z pomysłów, które znasz z
<a href="/naukowy/flashattention/">FlashAttention</a>.<sup id="fnref3:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Cały trik siedzi w tym, gdzie na GPU
lądują dane. Karta graficzna ma pamięć szybką i małą (SRAM) oraz wolną i dużą (HBM) —
a wąskim gardłem prawie zawsze jest przepychanie danych między nimi, nie samo liczenie.</p>
<p>Selective scan robi trzy rzeczy, żeby to obejść:<sup id="fnref4:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<ul>
<li><strong>Nie materializuje</strong> rozdmuchanego stanu w wolnej pamięci HBM — dyskretyzację i rekurencję
liczy w szybkim SRAM.</li>
<li><strong>Zlepia operacje w jeden kernel</strong> (kernel fusion), zamiast latać z wynikami tam i z powrotem.</li>
<li>Przy propagacji wstecznej <strong>przelicza stany od nowa</strong>, zamiast trzymać je w pamięci
(recomputation) — bo na nowoczesnym GPU policzyć jest taniej niż odczytać.</li>
</ul>
<p>Efekt? Model, który w teorii jest rekurencyjny i „powinien&quot; być wolny, w praktyce śmiga.
To ta sama filozofia, co przy flash attention: nie zmieniaj matematyki, zmień to, jak dane
wędrują przez poziomy pamięci karty.</p>
<h2 id="co-z-tego-wychodzi-w-liczbach">Co z tego wychodzi w liczbach</h2>
<p>Papier nie owija w bawełnę. Kilka twardych wyników:<sup id="fnref5:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<table>
	<thead>
			<tr>
					<th>Cecha</th>
					<th>Transformer</th>
					<th>Mamba</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Skalowanie z długością sekwencji</td>
					<td>O(n²)</td>
					<td>O(n) — liniowe</td>
			</tr>
			<tr>
					<td>Stan przy generowaniu</td>
					<td>KV-cache rośnie z kontekstem</td>
					<td>stały rozmiar</td>
			</tr>
			<tr>
					<td>Przepustowość generowania</td>
					<td>punkt odniesienia</td>
					<td><strong>5× wyższa</strong></td>
			</tr>
			<tr>
					<td>Jakość (language modeling)</td>
					<td>model 2× większy dorównuje</td>
					<td>Mamba-3B dorównuje</td>
			</tr>
	</tbody>
</table>
<p>To „5× higher throughput&quot; to nie chochlik — Mamba generuje tokeny pięć razy szybciej niż
transformer podobnej klasy, właśnie dzięki temu, że nie musi za każdym krokiem przemielać
rosnącego cache&rsquo;u.<sup id="fnref6:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> A jakościowo Mamba-3B dorównuje transformerom <strong>dwukrotnie
większym</strong> i bije te swojej wielkości — zarówno w pretreningu, jak i w ewaluacji downstream.</p>
<p>I jedna rzecz, która dla długiego kontekstu jest najsmaczniejsza: wydajność na realnych
danych <strong>poprawia się aż do sekwencji rzędu milionów tokenów</strong>.<sup id="fnref7:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Nie „nie psuje się&quot; —
poprawia. Tam, gdzie transformer się dusi, Mamba dopiero się rozkręca. Do tego papier
pokazuje wyniki state-of-the-art nie tylko dla języka, ale i dla audio oraz genomiki —
jako ogólny backbone sekwencyjny, nie wąska sztuczka do jednej modalności.</p>
<h2 id="zrób-to-sam-a-przynajmniej-dotknij">Zrób to sam (a przynajmniej dotknij)</h2>
<p>To nie jest zamknięty papier z ładnym wykresem. Kod i checkpointy leżą w oficjalnym repo
<code>state-spaces/mamba</code> — z gotowymi modelami od 130M do 2.8B parametrów, trenowanymi między
innymi na 300 miliardach tokenów (wariant SlimPajama na 600 mld).<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> Model z abstraktu
opisywany jako „Mamba-3B&quot; to na Hugging Face ten sam checkpoint co <code>mamba-2.8b</code> — nazewnictwo
się rozjeżdża, liczba parametrów ta sama.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></p>
<p>Jak chcesz zajrzeć pod maskę bloku Mamba, warto znać trzy pokrętła z implementacji:<sup id="fnref1:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># kluczowe hiperparametry modułu Mamba (Mamba-1)</span>
</span></span><span class="line"><span class="cl"><span class="nv">d_state</span> <span class="o">=</span> <span class="m">16</span>   <span class="c1"># rozmiar stanu SSM — ile &#34;pamięci&#34; trzyma ukryty wektor</span>
</span></span><span class="line"><span class="cl"><span class="nv">d_conv</span>  <span class="o">=</span> <span class="m">4</span>    <span class="c1"># szerokość lokalnej konwolucji przed SSM</span>
</span></span><span class="line"><span class="cl"><span class="nv">expand</span>  <span class="o">=</span> <span class="m">2</span>    <span class="c1"># o ile blok rozszerza wymiar wewnętrzny</span>
</span></span></code></pre></div><p><code>d_state</code> to wielkość tego stałego stanu, o którym cała gadka — domyślnie 16 dla Mamby-1.
<code>d_conv</code> to szerokość krótkiej lokalnej konwolucji (Mamba wciąż lubi lokalny kontekst
tuż-tuż), a <code>expand</code> mówi, o ile blok pompuje wymiar wewnętrzny. Trzy liczby, a definiują
charakter całej architektury.</p>
<p>Jedno zastrzeżenie na koniec, żeby nie wpaść w hype: Mamba <strong>nie</strong> wysyła transformerów na
emeryturę. Papier pokazuje przewagę na modelowaniu języka, audio i genomice — nie ogłasza,
że uwaga jest bezużyteczna wszędzie. Późniejsze prace tych samych autorów (Mamba-2 i tak
zwane <em>structured state space duality</em>) pokazują wręcz, że SSM i attention są matematycznie
spokrewnione bliżej, niż się na pierwszy rzut oka wydaje — to nie dwa wrogie plemiona,
raczej dwie gałęzie tego samego drzewa.</p>
<p>I może właśnie o to chodzi. Przez kilka lat myśleliśmy, że długi kontekst to podatek, który
płaci się kwadratem i pęczniejącym cache&rsquo;em — po prostu prawo natury lokalnego LLM-owania.
Mamba przyszła i pokazała, że to była tylko jedna droga przez las, nie jedyna. Ten sam
worek, do którego dorzucałeś kamień za każdym tokenem, nagle okazał się plecakiem o stałej
wadze — i można iść dalej, aż po horyzont milionów tokenów, bez zaciskania zębów przy
liczniku VRAM-u.</p>
<h2 id="przypisy--źródła">Przypisy · źródła</h2>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Albert Gu, Tri Dao, <em>Mamba: Linear-Time Sequence Modeling with Selective State Spaces</em>, arXiv:2312.00752 (v1: 1 grudnia 2023, v2: 31 maja 2024) — <a href="https://arxiv.org/abs/2312.00752">arxiv.org/abs/2312.00752</a>. Abstrakt i sekcja 3 (hardware-aware selective scan): brak attention/MLP, selekcja parametrów Δ/B/C jako funkcji wejścia, liniowe skalowanie, 5× throughput, poprawa do sekwencji milionowej długości, wyniki Mamba-3B oraz modalności język/audio/genomika. Pełny tekst: <a href="https://arxiv.org/pdf/2312.00752">arxiv.org/pdf/2312.00752</a>.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref3:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref4:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref5:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref6:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref7:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Oficjalna implementacja i checkpointy, GitHub — <a href="https://github.com/state-spaces/mamba">github.com/state-spaces/mamba</a>. Kernel <code>selective_scan_cuda</code>, hiperparametry modułu (<code>d_state</code>, <code>d_conv</code>, <code>expand</code>), pretrenowane modele 130M–2.8B (Mamba/Mamba-2), dane treningowe 300B/600B tokenów, odniesienie do Mamba-2 i structured state space duality.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Karta modelu <code>state-spaces/mamba-2.8b</code>, Hugging Face — <a href="https://huggingface.co/state-spaces/mamba-2.8b">huggingface.co/state-spaces/mamba-2.8b</a>. Potwierdzenie checkpointu ~2.8B (3B) parametrów, zgodnego z modelem „Mamba-3B&quot; z abstraktu.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>YaRN: jak rozciągnąć kontekst, którego model nie widział</title><link>https://inferownia.pl/naukowy/yarn-kontekst/</link><pubDate>Sun, 05 Jul 2026 00:00:00 +0200</pubDate><author>Bowen Peng</author><author>Jeffrey Quesnelle</author><author>Honglu Fan</author><author>Enrico Shippole</author><category>inferencja</category><guid>https://inferownia.pl/naukowy/yarn-kontekst/</guid><description>Model uczony na 4 tysiącach tokenów nagle ogarnia 128 tysięcy. Nie magia, nie retrening od zera — tylko sprytne przekręcenie zegara pozycji w RoPE i kilkaset kroków douczenia.</description><content:encoded><![CDATA[<p>Przewijasz kartę modelu na Hugging Face i w tabelce mruga do ciebie „128k context”.
Sto dwadzieścia osiem tysięcy tokenów — cała książka wleci w jeden prompt. Serce rośnie.
A potem, gdzieś na dole, drobnym druczkiem: model bazowy trenowany na sekwencjach 4096.
No i człowiek głupieje. Jak to? Uczyłeś go patrzeć na cztery tysiące tokenów naraz, a teraz
ma ogarnąć trzydzieści dwa razy więcej? Skąd on niby wie, jak wygląda token numer 100 000,
skoro w życiu takiego nie widział?</p>
<p>To nie jest pytanie z gatunku czepialstwa. Sam pewnie nieraz widziałeś, jak model
z „długim kontekstem” pięknie streszcza pierwsze pięć stron, a od dziesiątej zaczyna
zmyślać, mieszać imiona i gubić wątek — dokładnie w miejscu, w którym teoretycznie
miał błyszczeć. Bo rozciąganie okna kontekstu to nie jest przesunięcie
suwaka. To operacja na sercu tego, jak model w ogóle rozumie „gdzie” jest dany token.
I tu wchodzi YaRN — metoda z pracy grupy z Nous Research, która robi to tanio i, co
ważniejsze, robi to dobrze.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<h2 id="skąd-model-w-ogóle-wie-który-token-jest-który">Skąd model w ogóle wie, który token jest który</h2>
<p>Zacznijmy od tego, że transformer sam z siebie nie ma pojęcia o kolejności. Wrzuć mu
„pies goni kota” i „kota goni pies” — bez informacji o pozycji to dla niego ta sama torba
słów. Trzeba mu jakoś powiedzieć, że ten token jest pierwszy, tamten setny.</p>
<p>Współczesne modele — LLaMA, Mistral, Qwen — robią to przez <strong>RoPE</strong> (<em>rotary position
embeddings</em>, rotacyjne osadzenia pozycyjne) z pracy Su i innych.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> Pomysł jest
elegancki: zamiast doklejać pozycję jako osobny wektor, RoPE <strong>obraca</strong> wektory zapytań
i kluczy o kąt proporcjonalny do pozycji tokena. Każdy wymiar embeddingu kręci się z inną
częstotliwością — jedne szybko, jak wskazówka sekundowa, inne wolno, jak godzinowa. Token
na pozycji 5 jest obrócony trochę, token na pozycji 5000 — dużo. Model uczy się czytać
te kąty i z różnicy obrotów między dwoma tokenami wnioskuje, jak daleko od siebie leżą.</p>
<p>I teraz clou problemu: model widział te wskazówki tykające tylko do pozycji 4096. Dalej
jest terra incognita. Kąty, które w treningu nigdy się nie pojawiły. Puść go na token
50 000, a wskazówka sekundowa zakręci się tyle razy, że model nie ma bladego pojęcia, co
z tym zrobić — to jak pokazać komuś zegar, który obrócił się poza tarczę, w miejsce,
którego nigdy nie oznaczono.</p>
<h2 id="dlaczego-po-prostu-ściśnij-pozycje-psuje-robotę">Dlaczego „po prostu ściśnij pozycje” psuje robotę</h2>
<p>Pierwszy odruch jest oczywisty. Skoro model zna zakres do 4096, a chcemy 64k, to
przeskalujmy pozycje liniowo — token 64 000 udawaj, że jesteś tokenem 4000. Ściśnij całą
oś czasu tak, żeby zmieściła się w znanym zakresie. To jest <strong>Position Interpolation</strong> (PI,
interpolacja pozycji) z pracy Chena i innych — i na papierze brzmi rozsądnie.</p>
<p>Problem w tym, że PI traktuje <strong>wszystkie</strong> częstotliwości RoPE jednakowo — zwalnia i
sekundnik, i wskazówkę godzinową o ten sam czynnik.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> A to katastrofa dla tych
najszybszych wymiarów. To właśnie one odpowiadają za rozróżnianie tokenów leżących tuż
obok siebie — „czy słowo A jest bezpośrednio przed B, czy jest między nimi jeszcze jedno”.
Ściśnij je razem z resztą, a model traci rozdzielczość na najdrobniejszym poziomie.
Efekt? Jakość spada nawet na <strong>krótkim</strong> kontekście, który przecież działał bez zarzutu.
Rozciągnąłeś okno i po drodze rozmyłeś to, co model umiał od początku. Klasyczny handel,
w którym oddajesz więcej, niż dostajesz.</p>
<h2 id="jak-yarn-dzieli-częstotliwości-na-pasma">Jak YaRN dzieli częstotliwości na pasma</h2>
<p>No to jak zrobić to mądrzej? Skoro problem jest w tym, że traktujemy wszystkie
częstotliwości jednakowo — to <strong>przestańmy</strong>. To jest cała intuicja YaRN.</p>
<p>Zanim doszli do wersji finalnej, po drodze była <strong>NTK-aware interpolation</strong> (interpolacja
świadoma NTK) — zamiast liniowo ściskać pozycje, zmienia się podstawę RoPE z <code>b</code> na
<code>b * s^(|D|/(|D|-2))</code>, przez co „nacisk” interpolacji rozkłada się nierówno po
wymiarach.<sup id="fnref1:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> Lepiej, ale wciąż z grubsza jednym pociągnięciem po wszystkim.</p>
<p>YaRN idzie krok dalej z podejściem <strong>NTK-by-parts</strong> (interpolacja pasmowa) — i to jest
serce metody. Dzielisz wymiary częstotliwości na pasma za pomocą funkcji rampy <code>γ(r)</code>:<sup id="fnref2:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></p>
<ul>
<li><strong>wysokie częstotliwości</strong> (te lokalne, szybkie wskazówki — sąsiad przy sąsiedzie)
zostawiasz w spokoju, bez interpolacji. Ekstrapolujesz. Niech tykają jak tykały.</li>
<li><strong>niskie częstotliwości</strong> (te globalne, wolne — gruby zarys „to jest gdzieś na końcu
dokumentu”) interpolujesz normalnie, bo to one muszą pomieścić nowy, większy zakres.</li>
<li>pasmo <strong>pośrodku</strong> rampa miesza płynnie, żeby nie było skoku na styku.</li>
</ul>
<p>Sedno: nie ruszasz tego, co model umie najlepiej (drobna rozdzielczość lokalna), a
rozciągasz tylko to, co i tak opisuje wielką skalę. Zegar dostaje większą tarczę, ale
sekundnik dalej odmierza sekundy tak samo dokładnie.</p>
<table>
	<thead>
			<tr>
					<th>Metoda</th>
					<th>Co robi z częstotliwościami</th>
					<th>Efekt uboczny</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Position Interpolation (PI)</td>
					<td>ściska wszystkie jednakowo, liniowo</td>
					<td>psuje rozdzielczość lokalną, spada jakość i na krótkim kontekście</td>
			</tr>
			<tr>
					<td>NTK-aware</td>
					<td>zmienia podstawę RoPE, nacisk rozkłada nierówno</td>
					<td>lepiej, ale wciąż jeden globalny gest</td>
			</tr>
			<tr>
					<td>NTK-by-parts (YaRN)</td>
					<td>dzieli na pasma rampą <code>γ(r)</code>: wysokie ekstrapoluje, niskie interpoluje</td>
					<td>zachowuje lokalną precyzję, rozciąga tylko skalę globalną</td>
			</tr>
	</tbody>
</table>
<h2 id="drugi-bezpiecznik-temperatura-uwagi">Drugi bezpiecznik: temperatura uwagi</h2>
<p>I tu jest smaczek, który łatwo przegapić, bo nie ma nic wspólnego ze skalowaniem
częstotliwości. YaRN dorzuca <strong>attention temperature scaling</strong> — skalowanie temperatury
w softmaxie uwagi.<sup id="fnref3:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> Kiedy rozciągasz kontekst, rozkład uwagi robi się „płaski”,
model rozprasza się na tysiące tokenów zamiast celować. Więc mnożysz logity uwagi przez
stały współczynnik, wyliczony z prostego, empirycznego wzoru:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">sqrt(1/t) = 0.1 * ln(s) + 1
</span></span></code></pre></div><p>gdzie <code>s</code> to współczynnik skalowania kontekstu (dla 4096 → 64k mamy <code>s = 16</code>). Piękne w
tym jest to, że działa jak zwykły mnożnik na logitach — <strong>nie dotyka wag modelu</strong>, nie
wymaga dodatkowego treningu, wchodzi za darmo. Osobne pokrętło, doklejone z boku,
a robi sporo dobrego dla ostrości. Nie pomyl go ze skalowaniem samego RoPE — to dwa różne
mechanizmy, które YaRN łączy w jedno.</p>
<h2 id="tanio-czyli-ile-to-naprawdę-kosztuje">Tanio, czyli ile to naprawdę kosztuje</h2>
<p>Teraz najlepsze — bo tu YaRN naprawdę błyszczy. To <strong>nie</strong> jest trening od zera. To nawet
nie jest porządny fine-tuning na górze danych. To <strong>krótkie douczenie</strong> już gotowego modelu,
żeby oswoił się z nowymi kątami.</p>
<p>Ile krótkie? W pracy LLaMA-2 7B i 13B douczono jakieś <strong>400 kroków</strong> przy <code>s = 16</code>
(4096 → 64k), plus dodatkowe <strong>~200 kroków</strong> przy <code>s = 32</code>, żeby dobić do 128k — razem
około 600 kroków, na danych PG19 w segmentach po 64k, przy batchu 64.<sup id="fnref4:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>
Objętościowo to szacunkowo <strong>~0,1% oryginalnego korpusu</strong> pretreningowego. Jedna dziesiąta
procenta. W liczbach z abstraktu: YaRN potrzebuje około <strong>10x mniej tokenów</strong> douczenia i
<strong>2,5x mniej kroków</strong> treningowych niż wcześniejsze metody.<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>I jeszcze jedna rzecz, która robi wrażenie: modele <strong>ekstrapolują poza</strong> długość, na której
je douczono. Serię Mistral 7B rozciągnięto z 8k do 64k i 128k tą samą metodą — a trening
na segmentach 64k dawał poprawne działanie aż do 128k.<sup id="fnref5:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> Model, który w douczeniu
nie widział nic dłuższego niż 64k, radzi sobie dwa razy dalej. To już nie jest tylko
„nauczyliśmy go nowego zakresu” — to „nauczyliśmy go <em>reguły</em>, którą sam rozciąga dalej”.</p>
<div class="callout">
  <span class="callout__label">Dlaczego to ważne dla lokalnego LLM</span>
<p>Dla ciebie, co odpalasz modele lokalnie, to zmienia rachunek na dysku i w głowie. Nie
musisz mieć farmy GPU, żeby dostać model z długim kontekstem — ktoś dorzucił kilkaset
kroków douczenia i wrzucił gotowca na Hugging Face. Ale okno kontekstu to nie darmowy
lunch: każdy dodatkowy tysiąc tokenów to więcej pamięci na KV-cache, a to on, nie wagi,
zjada VRAM przy długich promptach — rozgryzaliśmy to bliżej <a href="/poradniki/kv-cache-vram/">tutaj</a>.
128k w tabelce i 128k, które faktycznie wejdzie na twoją kartę, to dwie różne historie.</p>
</div>
<h2 id="zrób-to-sam-yarn-w-llamacpp">Zrób to sam: YaRN w llama.cpp</h2>
<p>I teraz uwaga na rozróżnienie, na którym łatwo się wyłożyć. Wszystko powyżej — kroki
douczenia, dane PG19 — to warstwa <strong>treningu</strong> opisana w papierze. Ale jest druga warstwa:
<strong>inferencja</strong>. <code>llama.cpp</code> implementuje skalowanie YaRN jako parametry runtime, które
przekręcasz przy uruchomieniu, bez żadnego douczania:<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</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-server -m model-yarn-128k.gguf <span class="se">\
</span></span></span><span class="line"><span class="cl">  -c <span class="m">65536</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --yarn-orig-ctx <span class="m">8192</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --yarn-ext-factor 1.0 <span class="se">\
</span></span></span><span class="line"><span class="cl">  -fa on
</span></span></code></pre></div><ul>
<li><strong><code>--yarn-orig-ctx</code></strong> — oryginalny kontekst treningowy modelu (domyślnie <code>0</code>, czyli
„wczytaj z metadanych GGUF”). To punkt odniesienia, względem którego liczy się <code>s</code>.</li>
<li><strong><code>--yarn-ext-factor</code></strong> — współczynnik mieszania ekstrapolacji (domyślnie <code>-1.00</code>;
<code>0.0</code> to pełna interpolacja).</li>
</ul>
<p>Jeśli model został wypuszczony jako wariant YaRN, metadane zwykle same podpowiedzą sensowne
wartości — wtedy w ogóle nie musisz w to grzebać. A jak flagi w <code>llama.cpp</code> w ogóle działają
i które z tej setki naprawdę musisz znać, rozbieraliśmy <a href="/poradniki/flagi-llama-cpp/">w osobnym wpisie</a>.
Jest jeszcze wariant <strong>Dynamic NTK</strong> — aktualizuje współczynnik skalowania <code>s = max(1, l'/L)</code>
w locie, per krok inferencji, zamiast trzymać stałą wartość ustaloną raz.<sup id="fnref6:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> Sprytne,
bo krótkie prompty nie płacą podatku za rozciągnięcie, którego akurat nie potrzebują.</p>
<p>Cała ta rodzina metod, swoją drogą, ładnie zazębia się z tym, jak w ogóle liczy się uwaga —
a to temat, który drążyliśmy przy <a href="/naukowy/flashattention/">FlashAttention</a>.</p>
<h2 id="większa-tarcza-ten-sam-sekundnik">Większa tarcza, ten sam sekundnik</h2>
<p>Wróćmy na koniec do tego zegara. Naiwna interpolacja bierze tarczę i ściska ją tak, że
sekundnik zlewa się z minutową — niby zmieściłeś więcej godzin, ale nie odczytasz już,
czy jest 12:01, czy 12:02. YaRN robi odwrotnie: zostawia sekundnik w spokoju, a rozciąga
tylko wolne wskazówki, które i tak odmierzają grube kawałki. Dokładasz kilkaset kroków
douczenia — tyle, ile trzeba, żeby model oswoił nowe kąty — i nagle patrzy na sto
dwadzieścia osiem tysięcy tokenów tak, jakby zawsze umiał. Nie dlatego, że zobaczył każdą
nową pozycję. Dlatego, że ktoś mądrze przekręcił mu zegar, nie tłukąc szkła.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Bowen Peng, Jeffrey Quesnelle, Honglu Fan, Enrico Shippole, „YaRN: Efficient Context Window Extension of Large Language Models”, <a href="https://arxiv.org/abs/2309.00071">arXiv:2309.00071</a> (v1 z 31 sierpnia 2023, v2 z 1 listopada 2023). Abstrakt: ok. 10x mniej tokenów i 2,5x mniej kroków treningowych niż wcześniejsze metody.&#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></p>
</li>
<li id="fn:2">
<p>Jianlin Su i in., „RoFormer: Enhanced Transformer with Rotary Position Embedding”, <a href="https://arxiv.org/abs/2104.09864">arXiv:2104.09864</a> — oryginalna praca o RoPE, na której stoi cała ta rodzina metod.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Szczegóły techniczne (krytyka Position Interpolation, definicje NTK-aware / NTK-by-parts / Dynamic NTK, wzór na skalowanie temperatury uwagi, liczby kroków douczenia i długości kontekstu, eksperymenty na Mistral 7B) — pełny tekst pracy w wersji HTML: <a href="https://ar5iv.labs.arxiv.org/html/2309.00071">ar5iv.labs.arxiv.org/html/2309.00071</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>&#160;<a href="#fnref2:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref3:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref4:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref5:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref6:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Parametry runtime <code>--yarn-orig-ctx</code> i <code>--yarn-ext-factor</code> w <code>llama.cpp</code> — dokumentacja serwera: <a href="https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md">ggml-org/llama.cpp, tools/server/README.md</a>.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>GPTQ kontra AWQ: dwie szkoły kwantyzacji po treningu</title><link>https://inferownia.pl/naukowy/gptq-awq/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0200</pubDate><author>Elias Frantar</author><author>Saleh Ashkboos</author><author>Torsten Hoefler</author><author>Dan Alistarh</author><category>kwantyzacja</category><guid>https://inferownia.pl/naukowy/gptq-awq/</guid><description>GPTQ liczy odwrotność Hessianu i po kolei koryguje błąd warstwa po warstwie. AWQ patrzy na aktywacje i skaluje kanały wag. Rozkładamy obie szkoły na części i mówimy, po co komu która.</description><content:encoded><![CDATA[<p>Pod nazwą świeżego modelu ciągnie się zwykle ten sam korowód skrótów, co
zawsze: obok pliku <code>.gguf</code> leży katalog z dopiskiem <code>GPTQ</code>, kawałek niżej ktoś wrzucił
wariant <code>AWQ</code>, a w komentarzach ktoś pyta „którą wersję brać na 3060?&quot; i dostaje trzy
sprzeczne odpowiedzi. To jeden z tych tematów, o które nowy człowiek rozbija się na starcie — pół tuzina akronimów,
każdy obiecuje „ten sam model, tylko mniejszy&quot;, a człowiek stoi jak przed półką z mlekiem,
gdzie każde jest „bez laktozy, light i wzbogacone&quot;, i nie wiadomo, czym się właściwie różnią.</p>
<p>A różnią się. I nie chodzi o to, że jeden jest „lepszy&quot;, tylko że to dwie zupełnie różne
szkoły myślenia o tym samym problemie: jak zapisać miliardy wag ciaśniej i nie zepsuć
modelu. GPTQ i AWQ podchodzą do tego z dwóch przeciwnych stron — jeden liczy matematyczną
korekcję błędu, drugi pyta, które wagi są w ogóle ważne. A na dokładkę cały świat GGUF-a
i k-quantów, który znasz z <code>llama.cpp</code>, to trzecia bajka, jeszcze inna. Rozłóżmy to na części.</p>
<h2 id="najpierw-jedno-rozróżnienie-to-wszystko-jest-po-treningu">Najpierw jedno rozróżnienie: to wszystko jest PO treningu</h2>
<p>Zanim wejdziemy w szczegóły — jedna rzecz, na której wykłada się połowa dyskusji.
GPTQ, AWQ, GGUF: to wszystko <strong>kwantyzacja po treningu</strong> (<em>post-training quantization</em>, PTQ).
Bierzesz gotowy, wytrenowany model w FP16 i przerabiasz jego wagi na coś ciaśniejszego.
Żadna z tych metod <strong>nie trenuje modelu od nowa</strong>. To nie jest <em>quantization-aware training</em>
(QAT), gdzie model uczy się od początku z myślą o niskiej precyzji. Tu nikt niczego nie uczy
— bierzemy skończone dzieło i pakujemy je do mniejszego pudełka.</p>
<p>Dlaczego to ważne? Bo cała sztuka PTQ polega na tym, żeby <strong>nie mieć dostępu do treningu</strong>,
a i tak stracić jak najmniej. Nie przepuszczasz miliardów przykładów przez wsteczną
propagację. Masz gotowe wagi i najwyżej garść tekstu do „kalibracji&quot; — żeby zajrzeć, jak
model się zachowuje, i dobrać kwantyzację mądrzej niż zwykłym zaokrągleniem. I właśnie w tym
„mądrzej&quot; GPTQ i AWQ idą w dwie różne strony.</p>
<h2 id="gptq-kwantyzuj-wagę-popraw-resztę-powtórz">GPTQ: kwantyzuj wagę, popraw resztę, powtórz</h2>
<p>Pierwsza szkoła. GPTQ — z pracy Frantara, Ashkboosa, Hoeflera i Alistarha z 2022 roku<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>
— wywodzi się z klasycznej idei o nazwie <em>Optimal Brain Quantization</em> (OBQ). Pomysł jest
zaskakująco ludzki: <strong>nie kwantyzuj wszystkiego naraz na oślep</strong>. Rób to po jednej wadze,
a po każdym takim ruchu popraw pozostałe, jeszcze nietknięte wagi tak, żeby zrekompensować
błąd, który właśnie wprowadziłeś.</p>
<p>Wyobraź sobie, że rozstawiasz meble w za małym pokoju. Wciskasz szafę w róg — i widzisz, że
przez to stół musi się ciut przesunąć, a krzesło podjechać bliżej ściany. GPTQ robi
dokładnie to: kwantyzuje kolumnę wag, patrzy, ile błędu przez to narobił, i „dosuwa&quot;
pozostałe kolumny, żeby wyjście warstwy zostało jak najbliżej oryginału. Formalnie każdą
warstwę rozwiązuje osobno jako problem <code>argmin ||WX − ŴX||₂²</code> — czyli: znajdź skwantyzowane
wagi <code>Ŵ</code>, które na tych samych wejściach <code>X</code> dają wyjście najbliższe oryginalnym wagom <code>W</code>.<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>Skąd metoda wie, jak dosuwać? Z <strong>informacji drugiego rzędu</strong> — odwrotności Hessianu
warstwy, czyli macierzy mówiącej, jak wrażliwe jest wyjście na zmianę każdej wagi.<sup id="fnref2:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>
To nie jest zwykłe „zaokrąglij do najbliższej wartości&quot; (<em>round-to-nearest</em>). Zaokrąglenie
byłoby jak wciskanie mebli bez patrzenia. GPTQ za każdym krokiem konsultuje się z Hessianem
i wie, którą resztę i o ile przesunąć.</p>
<p>Problem: liczenie odwrotności Hessianu dla każdej wagi z osobna jest morderczo drogie.
I tu wchodzi kluczowy myk GPTQ względem starego OBQ. Autorzy zauważyli, że jeśli kwantyzować
kolumny w <strong>ustalonej, stałej kolejności</strong> — a nie „zawsze tę najmniej wrażliwą&quot; — to
odwrotność Hessianu można policzyć i współdzielić <strong>raz na całą warstwę</strong>, zamiast przeliczać
w kółko.<sup id="fnref3:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> To dało ponad trzy rzędy wielkości przyspieszenia i nagle metoda przestała
się dławić na dużych modelach. Do tego doszła tzw. <em>Cholesky Reformulation</em> — prekomputacja
przez rozkład Choleskiego, żeby przy skali setek miliardów parametrów nie rozjechała się
numeryka.<sup id="fnref4:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>Efekt? OPT-175B skwantyzowany do 3–4 bitów na wagę w jakieś <strong>4,2 godziny na pojedynczym
GPU</strong> — i gotowy do generatywnej inferencji na jednej karcie, co wcześniej było poza
zasięgiem.<sup id="fnref5:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Przy 4 bitach strata perplexity względem FP16 to według pracy raptem
<strong>około 0,03</strong>, a raportowane przyspieszenia inferencji siedzą w okolicach 3,25x–4,5x,
zależnie od sprzętu.<sup id="fnref6:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>Kalibracja? Skromna — 128 losowych kawałków po 2048 tokenów ze zbioru C4, czyli ogólnego
tekstu z sieci. <strong>Bez danych zadaniowych.</strong> Autorzy podkreślają, że wynik pozostaje
„zero-shot&quot;, bo metoda nigdy nie widzi danych spod konkretnego zadania — patrzy tylko na to,
jak wagi reagują na w miarę losowy tekst.<sup id="fnref7:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<h2 id="awq-nie-wszystkie-wagi-są-równe">AWQ: nie wszystkie wagi są równe</h2>
<p>Druga szkoła podchodzi z zupełnie innej strony. AWQ — <em>Activation-aware Weight
Quantization</em>, praca Lina, Tanga, Tanga, Yanga i spółki, wyróżniona nagrodą Best Paper na
MLSys 2024<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> — startuje od obserwacji, która brzmi wręcz banalnie: <strong>nie wszystkie wagi
są tak samo ważne</strong>. Okazuje się, że wystarczy ochronić jakiś <strong>1% najistotniejszych</strong> wag,
żeby błąd kwantyzacji spadł drastycznie.<sup id="fnref1:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<p>Ale — i tu jest cały smaczek — jak poznać, które wagi są „istotne&quot;? Intuicja podpowiada:
te największe co do wartości. AWQ mówi: nie. Istotność wagi wyznacza się z <strong>rozkładu
aktywacji</strong>, a nie z samej wartości wagi.<sup id="fnref2:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> Stąd „activation-aware&quot; w nazwie: patrzysz,
przez które kanały płynie najwięcej „sygnału&quot; na wejściu, i to one decydują, które wagi
trzeba traktować z czułością. Waga może być mała, ale jeśli mnoży aktywację, która regularnie
strzela w górę — jest ważna.</p>
<p>I teraz najczęstsze nieporozumienie w internetach. Ludzie mówią: „AWQ trzyma ten 1% w FP16&quot;.
Nieprawda — a przynajmniej nie w wersji podstawowej. Trzymanie garstki wag w wyższej
precyzji to <em>mixed precision</em>, osobny nurt, który psuje jednolitość i utrudnia szybkie
jądra obliczeniowe. AWQ robi coś sprytniejszego: <strong>matematycznie skaluje w górę</strong> kanały wag
odpowiadające istotnym aktywacjom, jeszcze przed kwantyzacją, i kompensuje to odwrotnym
skalowaniem po drugiej stronie.<sup id="fnref3:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> Efekt jest taki, jakbyś dał tym ważnym wagom więcej
„rozdzielczości&quot; w siatce kwantyzacji — ale precyzja zostaje jednolita, bez wysepek FP16.</p>
<p>Co z tego wynika dla ciebie w praktyce: AWQ <strong>nie robi wstecznej propagacji</strong> przez model
i nie robi rekonstrukcji opartej na regresji.<sup id="fnref4:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> To ją odróżnia od GPTQ, która musi
przejść przez warstwy z rachunkiem Hessianu. Skutkiem ubocznym braku dopasowywania „na siłę&quot;
jest większa odporność na przeuczenie się na konkretnych danych kalibracyjnych — metoda nie
uczy się zbytnio pod ten akurat tekst, który jej pokazałeś.<sup id="fnref5:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<p>AWQ wspiera warianty W4A16 i W3A16 (wagi 4- lub 3-bitowe, aktywacje 16-bitowe), z opcjonalnym
grupowaniem kanałów, np. rozmiar grupy 128.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> A towarzyszący jej framework
inferencyjny TinyChat osiąga ponad 3x przyspieszenie względem FP16 z Hugging Face na kartach
desktopowych i mobilnych; w nowszych benchmarkach na RTX 4090 z LLaMA-3-8B raportowano 2,7x,
a na NVIDIA Jetson Orin 2,9x i okolice 38 tokenów na sekundę.<sup id="fnref1:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></p>
<h2 id="dwie-szkoły-jedna-tabela">Dwie szkoły, jedna tabela</h2>
<p>Zestawmy to na chłodno, bo gdy leżą obok siebie, różnica robi się oczywista:</p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>GPTQ</th>
					<th>AWQ</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Rodowód</td>
					<td>OBQ — korekcja błędu drugim rzędem</td>
					<td>obserwacja o 1% istotnych wag</td>
			</tr>
			<tr>
					<td>Co napędza decyzję</td>
					<td>odwrotność Hessianu warstwy</td>
					<td>rozkład aktywacji</td>
			</tr>
			<tr>
					<td>Mechanizm</td>
					<td>kwantyzacja kolumna po kolumnie + korekcja reszty</td>
					<td>skalowanie kanałów wag przed kwantyzacją</td>
			</tr>
			<tr>
					<td>Wsteczna propagacja / regresja</td>
					<td>rachunek Hessianu przez warstwy</td>
					<td>brak, czysta matematyka skalowania</td>
			</tr>
			<tr>
					<td>Kalibracja</td>
					<td>128 × 2048 tokenów z C4</td>
					<td>statystyki aktywacji z garści tekstu</td>
			</tr>
			<tr>
					<td>Precyzja</td>
					<td>jednolita, sekwencyjna korekcja</td>
					<td>jednolita, bez wysepek FP16</td>
			</tr>
			<tr>
					<td>Typowe warianty</td>
					<td>3–4 bity (badane też 2-bit i ternarne)</td>
					<td>W4A16 / W3A16, group size np. 128</td>
			</tr>
	</tbody>
</table>
<div class="callout">
  <span class="callout__label">Dlaczego to ważne dla lokalnego LLM</span>
<p>Dla ciebie, odpalającego model pod biurkiem, praktyczny wniosek jest prosty: obie metody
celują w to samo — 4-bitowe wagi, aktywacje w 16 bitach, żeby model klasy 7B–13B wszedł na
kartę, na której FP16 nie miało prawa się zmieścić. GPTQ ma dłuższy staż i pełno gotowych
checkpointów; AWQ bywa odporniejsze na to, że dane kalibracyjne odbiegają od tego, do czego
faktycznie użyjesz modelu. Jak masz obie wersje tego samego modelu — bierzesz tę, którą
lepiej wspiera twój silnik inferencyjny, i sprawdzasz jakość na SWOICH promptach, nie na
cudzym benchmarku.</p>
</div>
<h2 id="a-gdzie-w-tym-wszystkim-gguf-i-k-quanty">A gdzie w tym wszystkim GGUF i k-quanty?</h2>
<p>Tu trzeba postawić granicę na czerwono, bo to najczęstsze pomieszanie z poplątaniem.
GGUF i k-quanty z <code>llama.cpp</code> (te wszystkie <code>Q4_K_M</code>, <code>Q5_K_S</code> i spółka, o których nazwach
rozpisaliśmy się osobno <a href="/poradniki/nazwy-kwantyzacji-gguf/">tutaj</a>) to <strong>trzeci, odrębny
nurt</strong>. Owszem, technicznie też PTQ — ale filozofia zupełnie inna.</p>
<p>GGUF to przede wszystkim <strong>format pliku</strong> ekosystemu <code>ggml-org/llama.cpp</code>, a k-quanty to
rodzina schematów kwantyzacji w środku. Zamiast kalibrowanej korekcji błędu opartej na Hessianie
czy aktywacjach, k-quanty grupują wagi w <strong>superbloki po 256 wartości</strong>, z osobnymi (często
też skwantyzowanymi) skalami i minimami dla podbloków oraz skalowaniem superbloku w fp16.<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> (Sama „szesnastka&quot; <code>fp16</code> — i czemu w treningu ML częściej widuje się <code>bf16</code> — to <a href="/poradniki/bf16-f16-f32/">osobny temat</a>.)
Stąd bierze się niecałkowita „efektywna&quot; liczba bitów na wagę — <code>Q4_K_M</code> to w praktyce
jakieś <strong>4,5 bita</strong> na wagę, nie równe 4.<sup id="fnref1:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup></p>
<p>Warianty z dopiskami <code>_S</code>, <code>_M</code>, <code>_L</code> różnią się kompromisem rozmiar/jakość — <code>_M</code>
selektywnie trzyma wrażliwsze warstwy w wyższej precyzji, a <code>Q4_K_M</code> społeczność od dawna
uznaje za praktyczny „sweet spot&quot; (jakieś 75% redukcji rozmiaru przy minimalnej odczuwalnej
stracie).<sup id="fnref2:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> To inżynierski nurt zorientowany na inferencję na CPU i mieszaną CPU–GPU,
nie na kartę wypchaną VRAM-em.</p>
<p>Kluczowe: <strong>nie utożsamiaj tych trzech</strong>. Wszystkie to PTQ, ale:</p>
<ul>
<li>GPTQ = sekwencyjna korekcja błędu przez odwrotność Hessianu,</li>
<li>AWQ = skalowanie kanałów wag wg aktywacji,</li>
<li>GGUF/k-quanty = superbloki z mieszaną precyzją per warstwa i osobnymi skalami.</li>
</ul>
<p>GGUF <em>może</em> korzystać z danych kalibracyjnych (tzw. <em>importance matrix</em>), ale mechanizm jest
inny niż w tamtych dwóch. To trzy różne odpowiedzi na to samo pytanie, nie trzy nazwy tej
samej rzeczy.</p>
<h2 id="zrób-to-sam-jak-to-rozpoznać-w-praniu">Zrób to sam: jak to rozpoznać w praniu</h2>
<p>Nie musisz liczyć żadnego Hessianu ręcznie — ale warto umieć rozpoznać, na co patrzysz.
Katalog z GPTQ albo AWQ na Hugging Face zwykle trzyma wagi w formacie <code>safetensors</code> plus
plik <code>quantize_config.json</code>, gdzie stoi jak byk liczba bitów i rozmiar grupy:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># GPTQ/AWQ zwykle: katalog safetensors + config kwantyzacji</span>
</span></span><span class="line"><span class="cl">ls TheModel-AWQ/
</span></span><span class="line"><span class="cl"><span class="c1"># config.json  model.safetensors  quantize_config.json  ...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># GGUF to jeden plik z całą kwantyzacją zaszytą w nazwie</span>
</span></span><span class="line"><span class="cl">ls TheModel-GGUF/
</span></span><span class="line"><span class="cl"><span class="c1"># the-model-q4_k_m.gguf</span>
</span></span></code></pre></div><p>Reguła kciuka na dziś: jeśli odpalasz na <strong>karcie NVIDII</strong> przez silnik pokroju vLLM albo
TinyChat i chcesz maksimum przepustowości z 4-bitowych wag — patrzysz na GPTQ albo AWQ.
Jeśli grasz na <strong>CPU, Apple Silicon albo mieszance CPU–GPU</strong> i chcesz jeden plik, który
„po prostu działa&quot; w <code>llama.cpp</code> czy Ollamie — bierzesz GGUF, najpewniej <code>Q4_K_M</code>. Ta sama
walka o VRAM, którą liczyliśmy <a href="/poradniki/ile-vram-na-model/">we wzorze na pamięć</a>, tylko
rozegrana trzema różnymi szkołami.</p>
<p>I jeszcze jedno, żeby domknąć układankę: kwantyzacja po treningu to nie to samo, co
4-bitowy <strong>fine-tuning</strong> spod znaku QLoRA — tam ciśniemy model po to, żeby go <strong>doszkolić</strong> na
skromnym VRAM-ie, a rozgryzaliśmy to osobno <a href="/naukowy/qlora/">tutaj</a>. Tu nikt niczego nie
douczy. Tu tylko pakujemy skończony model do mniejszego pudełka.</p>
<p>Bo o to w tym całym zoo skrótów chodzi. Masz jeden wytrenowany mózg zapisany w FP16 i trzy
ekipy, które przyszły go spakować na wyjazd. GPTQ pakuje pedantycznie, walizka po walizce,
za każdym razem dosuwając resztę, żeby nic się nie pogniotło. AWQ najpierw pyta, co jest
naprawdę cenne, owija ten 1% w folię bąbelkową i resztę wciska bez ceregieli. A ekipa GGUF-a
nawet nie próbuje być elegancka — tnie na superbloki, pakuje w jeden plik i wrzuca do
bagażnika starego kombi, byle ruszyło z miejsca na tym, co masz pod ręką.</p>
<p>Trzy szkoły, jeden model, ten sam cel: żeby coś, co „się nie mieściło&quot;, w końcu odpaliło
u ciebie na biurku. Reszta to spór o to, jak elegancko go do tej walizki włożyć.</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>Frantar, Ashkboos, Hoefler, Alistarh, <em>GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers</em> — <a href="https://arxiv.org/abs/2210.17323">abstrakt, arXiv:2210.17323</a>; szczegóły metody (layer-wise problem <code>argmin ||WX − ŴX||₂²</code>, OBQ, stała kolejność kolumn i współdzielony Hessian, Cholesky Reformulation, kalibracja 128 × 2048 tokenów z C4, ~4,2 h dla OPT-175B, ~0,03 perplexity dla 4-bit, przyspieszenia 3,25x–4,5x) w <a href="https://ar5iv.labs.arxiv.org/html/2210.17323">pełnym tekście HTML na ar5iv</a>.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref3:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref4:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref5:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref6:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref7:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Lin, J. Tang, H. Tang, Yang, W.-M. Chen, W.-C. Wang, Xiao, Dang, Gan, Han, <em>AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration</em> (MLSys 2024, Best Paper) — <a href="https://arxiv.org/abs/2306.00978">abstrakt, arXiv:2306.00978</a>: ochrona ~1% istotnych wag, wybór wg rozkładu aktywacji, skalowanie kanałów zamiast mieszanej precyzji, brak backpropagation i rekonstrukcji regresyjnej.&#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>&#160;<a href="#fnref3:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref4:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref5:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Oficjalne repozytorium AWQ, <a href="https://github.com/mit-han-lab/llm-awq">mit-han-lab/llm-awq</a> — implementacja W4A16 / W3A16, group size, framework TinyChat oraz benchmarki przyspieszenia (RTX 4090 z LLaMA-3-8B: 2,7x; Jetson Orin: 2,9x / ~38 tok/s).&#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><code>ggml-org/llama.cpp</code>, <a href="https://github.com/ggml-org/llama.cpp/wiki/Tensor-Encoding-Schemes">Tensor Encoding Schemes (Wiki)</a> — opis GGUF i rodziny k-quantów: superbloki po 256 wag, skwantyzowane skale/minima per podblok, ~4,5 bpw dla <code>Q4_K_M</code>, warianty <code>_S</code>/<code>_M</code>/<code>_L</code>; dokumentacja narzędzia w <a href="https://github.com/ggml-org/llama.cpp/blob/master/tools/quantize/README.md">tools/quantize/README.md</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>&#160;<a href="#fnref2:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item></channel></rss>