<?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>Inferownia</title><link>https://inferownia.pl/</link><description>Polski hub wiedzy o uruchamianiu modeli językowych na własnym sprzęcie: kwantyzacja, inferencja, małe modele, fine-tuning i RAG.</description><language>pl-PL</language><copyright>&#169; 2026 Inferownia</copyright><lastBuildDate>Sun, 19 Jul 2026 00:00:00 +0200</lastBuildDate><atom:link href="https://inferownia.pl/index.xml" rel="self" type="application/rss+xml"/><item><title>bf16, f16, f32 — o co chodzi z tymi bitami</title><link>https://inferownia.pl/poradniki/bf16-f16-f32/</link><pubDate>Sun, 19 Jul 2026 00:00:00 +0200</pubDate><category>kwantyzacja</category><guid>https://inferownia.pl/poradniki/bf16-f16-f32/</guid><description>Konwertujesz model do GGUF-a, widzisz --outtype f16 i --outtype bf16, i przez sekundę udajesz przed samym sobą, że wiesz, którego wybrać. Rozbieramy te formaty na bity — znak, wykładnik, mantysa — i tłumaczymy, czemu bf16 wygrał trening ML, choć jest mniej dokładny niż f16.</description><content:encoded><![CDATA[<p>Odpalasz <code>convert_hf_to_gguf.py</code> na świeżym modelu z Hugging Face, lecisz z palca <code>--outtype</code>
i nagle podpowiedź wypluwa ci listę: <code>f32</code>, <code>f16</code>, <code>bf16</code>, <code>q8_0</code>… Zatrzymujesz się na tych
trzech pierwszych. <code>f16</code> znasz, <code>f32</code> to „ten większy&quot;, ale <code>bf16</code> mruga jak nieznajomy na
imprezie, o którym wszyscy mówią, że jest spoko, tylko nikt nie potrafi powiedzieć czemu.
Klikasz <code>f16</code>, bo tak robiłeś zawsze, i ruszasz dalej — a z tyłu głowy zostaje to samo pytanie,
które wraca za każdym razem, gdy w konfiguracji treningu widzisz <code>compute_dtype=torch.bfloat16</code>:
czym one się właściwie różnią, skoro obie „szesnastki&quot; ważą tyle samo?</p>
<p>To jeden z klasycznych sporów o dwie „szesnastki&quot; — ktoś wrzuca skwantyzowany model, ktoś inny zaraz pyta,
czemu baza jest w <code>bf16</code>, a nie <code>f16</code>, i rozkręca się wątek na sto odpowiedzi, z których połowa
myli precyzję z zakresem. Bo to naprawdę nieoczywiste. Dwa formaty, oba po 16 bitów, oba po
2 bajty na liczbę — a jednak jeden wygrał trening wielkich modeli, a drugi został przy inferencji
i grafice. Cała różnica siedzi w tym, jak te 16 bitów pokroić. No to pokrójmy.</p>
<h2 id="liczba-zmiennoprzecinkowa-to-trzy-szufladki">Liczba zmiennoprzecinkowa to trzy szufladki</h2>
<p>Każda liczba w tych formatach — czy to <code>f32</code>, czy <code>f16</code>, czy <code>bf16</code> — składa się z trzech kawałków.
Wyobraź sobie zapis naukowy z podstawówki: −1,5 × 10⁴. Masz tam znak (minus), masz cyfry
znaczące (<code>1,5</code>) i masz potęgę, która mówi, gdzie postawić przecinek (<code>10⁴</code>). Komputer robi
dokładnie to samo, tylko na potęgach dwójki, i pakuje to w trzy pola bitów:</p>
<ul>
<li><strong>bit znaku</strong> (sign bit) — jeden bit, plus albo minus. Tu bez niespodzianek.</li>
<li><strong>wykładnik</strong> (exponent) — potęga dwójki, czyli <em>skala</em> liczby. To on decyduje, jak wielką
albo jak małą liczbę w ogóle da się zapisać. Nazywamy to <strong>zakresem dynamicznym</strong>.</li>
<li><strong>mantysa</strong> (significand, po staremu <em>mantissa</em>) — cyfry znaczące, czyli <em>dokładność</em>. To ona
decyduje, ile pozycji po przecinku odróżnisz od siebie. Nazywamy to <strong>precyzją</strong>.</li>
</ul>
<p>I teraz sedno, które trzeba wbić do głowy jak mantrę: <strong>zakres bierze się z wykładnika, precyzja
z mantysy. To dwie niezależne osie.</strong> Możesz mieć liczbę o gigantycznym zakresie i marnej
dokładności — albo odwrotnie. Nie idą w parze. Cała gra w tych formatach polega na tym, ile
z szesnastu (albo trzydziestu dwóch) dostępnych bitów oddasz jednemu, a ile drugiemu.</p>
<p>Jak to rozdzielono w praktyce? Tak — i to jest tabelka, którą warto zapamiętać:</p>
<table>
	<thead>
			<tr>
					<th>Format</th>
					<th style="text-align: center">Znak</th>
					<th style="text-align: center">Wykładnik</th>
					<th style="text-align: center">Mantysa</th>
					<th style="text-align: center">Zakres (maks.)</th>
					<th style="text-align: center">Precyzja dziesiętna</th>
					<th style="text-align: center">Bajty</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>f32</strong> (single)</td>
					<td style="text-align: center">1</td>
					<td style="text-align: center">8</td>
					<td style="text-align: center">23</td>
					<td style="text-align: center">3,4 × 10³⁸</td>
					<td style="text-align: center">≈ 7 cyfr</td>
					<td style="text-align: center">4</td>
			</tr>
			<tr>
					<td><strong>f16</strong> (half)</td>
					<td style="text-align: center">1</td>
					<td style="text-align: center">5</td>
					<td style="text-align: center">10</td>
					<td style="text-align: center">65 504</td>
					<td style="text-align: center">≈ 3–4 cyfry</td>
					<td style="text-align: center">2</td>
			</tr>
			<tr>
					<td><strong>bf16</strong> (bfloat16)</td>
					<td style="text-align: center">1</td>
					<td style="text-align: center">8</td>
					<td style="text-align: center">7</td>
					<td style="text-align: center">3,4 × 10³⁸</td>
					<td style="text-align: center">≈ 2–3 cyfry</td>
					<td style="text-align: center">2</td>
			</tr>
	</tbody>
</table>
<p><code>f32</code>, czyli <em>single precision</em> z normy IEEE 754, to 1 bit znaku, 8 bitów wykładnika i 23 bity
mantysy.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> To złoty standard „normalnego&quot; liczenia na komputerze — szeroki zakres i siedem
cyfr znaczących. Kosztuje 4 bajty na liczbę i przez lata nikt się nad tym nie zastanawiał.</p>
<h2 id="dwie-szesnastki-dwa-różne-cięcia">Dwie szesnastki, dwa różne cięcia</h2>
<p>I teraz robi się ciekawie, bo <code>f16</code> i <code>bf16</code> to obie „połówki&quot; — po 16 bitów, po 2 bajty. Tyle że
tort pokrojony zupełnie inaczej.</p>
<p><code>f16</code>, czyli <em>half precision</em>, też z IEEE 754, tnie tak: 1 bit znaku, <strong>5 bitów wykładnika</strong>,
<strong>10 bitów mantysy</strong>.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> Zauważasz? Oddano dużo miejsca mantysie, mało wykładnikowi. Efekt:
przyzwoita precyzja jak na 16 bitów, ale <strong>wąziutki zakres</strong> — największa skończona liczba, jaką
<code>f16</code> w ogóle potrafi zapisać, to 65 504. Przekroczysz to i masz overflow, liczba ucieka
w nieskończoność. Zejdziesz za nisko — underflow, liczba znika do zera. Dla grafiki, skąd <code>f16</code>
się wywodzi, to bez znaczenia; kolory i tak siedzą w rozsądnym przedziale.</p>
<p><code>bf16</code> — <em>brain floating point</em>, dziecko Google Brain, i uwaga: <strong>nie jest częścią IEEE 754</strong> —
kroi zupełnie inaczej: 1 bit znaku, <strong>8 bitów wykładnika</strong>, <strong>7 bitów mantysy</strong>.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> Ten
sam wykładnik co <code>f32</code>. Osiem bitów, ten sam bias 127, ta sama arytmetyka skali. A to znaczy, że
<strong><code>bf16</code> ma dokładnie taki sam zakres dynamiczny jak <code>f32</code></strong> — od jakichś 10⁻³⁸ do 3,4 × 10³⁸.
Google w dokumentacji TPU pisze to wprost, bez owijania: zakresy dynamiczne <code>bfloat16</code> i <code>float32</code>
są równoważne, <code>bfloat16</code> po prostu zjada połowę pamięci.<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup></p>
<p>Cena? Mantysa skurczona do siedmiu bitów. Czyli <code>bf16</code> jest <strong>mniej dokładny liczbowo niż <code>f16</code></strong> —
odróżnia mniej cyfr znaczących. I tu wielu ludzi się wykłada, bo intuicja podpowiada: skoro <code>bf16</code>
ma mniej precyzji, to jest gorszą szesnastką. A to nieprawda. To po prostu inny kompromis, zrobiony
pod inny cel.</p>
<h2 id="czemu-bf16-wygrał-trening-choć-jest-mniej-dokładny">Czemu bf16 wygrał trening, choć jest mniej dokładny</h2>
<p>To jest ta część, która zaskakuje każdego, kto pierwszy raz na to trafia. Bierzemy format o <em>gorszej</em>
precyzji i to on stał się domyślnym wyborem do trenowania największych modeli świata. Jak to możliwe?</p>
<p>Bo w treningu sieci neuronowej najgroźniejszym wrogiem nie jest to, że liczba jest odrobinę
niedokładna. Najgroźniejsze jest to, że liczba <strong>wypadnie poza zakres</strong>. Gradienty potrafią być
malutkie jak 10⁻⁷, a aktywacje potrafią chwilowo strzelić wysoko. <code>f16</code> ze swoim sufitem na
65 504 i płytką podłogą po prostu tego nie łapie — wartości albo overflowują do nieskończoności,
albo underflowują do zera, a wyzerowany gradient to gradient, który nic już nie uczy. Dlatego
trening w <code>f16</code> wymagał sztuczek w rodzaju <em>loss scalingu</em> — ręcznego podbijania skali strat, żeby
zmieścić gradienty w tym wąskim oknie.</p>
<p><code>bf16</code> rozwiązuje to od strzału, bo ma zakres <code>f32</code>. Gradient rzędu 10⁻³⁰? Mieści się. Aktywacja,
która na moment wystrzeliła? Też się mieści. Owszem, każda z tych liczb jest zapisana ciut mniej
dokładnie — ale sieci neuronowe są na taki szum zdumiewająco odporne, a na wypadnięcie z zakresu
już nie. Pokazało to twardo osobne badanie tego formatu — praca <em>„A Study of BFLOAT16 for Deep
Learning Training&quot;</em> z 2019 roku (zespół inżynierów Intela i Facebooka): trening w <code>bf16</code> osiąga te same
wyniki co <code>f32</code>, w tej samej liczbie iteracji, bez ruszania hiperparametrów, bez loss scalingu —
czego <code>f16</code> nigdy nie dawał za darmo.<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup></p>
<div class="callout">
  <span class="callout__label">Dlaczego to ważne dla lokalnego LLM</span>
<p>Zapamiętaj to jako jedno zdanie: <strong><code>bf16</code> = zakres <code>f32</code>, precyzja gorsza niż <code>f16</code>; <code>f16</code> = precyzja
lepsza niż <code>bf16</code>, ale zakres wąski jak balkon.</strong> Do treningu i tam, gdzie liczy się stabilność
numeryczna, wybierasz zakres — więc <code>bf16</code> (na sprzęcie, który go wspiera natywnie; szybka akceleracja
<code>bf16</code> w Tensor Core to dopiero Ampere i nowsze). Dodatkowa dokładność <code>f16</code> na nic ci się zda, jeśli
połowa liczb ucieknie do nieskończoności. To dlatego „więcej bitów mantysy&quot; nie znaczy „lepszy format&quot;.</p>
</div>
<p>Jest jeszcze jeden bohater drugiego planu, o którym warto wiedzieć, żeby się nie pogubić: <strong>TF32</strong>
od NVIDIA. To nie jest format przechowywania danych — to wewnętrzny tryb obliczeniowy Tensor Core,
z 8-bitowym wykładnikiem (zakres jak <code>f32</code>) i 10-bitową mantysą (precyzja jak <code>f16</code>).<sup id="fnref1:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> Dane
w pamięci nadal leżą jako <code>f32</code>; karta tylko liczy na nich szybciej, po cichu przycinając mantysę
w trakcie mnożenia. Nie pomyl go z <code>bf16</code> jako typem tensora — to inna bajka, tryb liczenia, a nie
etykieta na danych.</p>
<h2 id="po-co-ci-to-przy-lokalnym-llm">Po co ci to przy lokalnym LLM</h2>
<p>„Fajnie, ale ja nic nie trenuję, tylko odpalam gotowca&quot; — i słusznie, więc przełóżmy to na twoje
podwórko.</p>
<p>Przy inferencji <code>f16</code> albo <code>bf16</code> to typowy <strong>compute dtype</strong>, czyli format, w którym faktycznie
lecą obliczenia — 2 bajty na parametr, dwa razy taniej niż <code>f32</code>, przy jakości wyjścia praktycznie
nie do odróżnienia. Ten sam podział bitów wraca też w <strong>KV-cache</strong>: bufor, w którym model trzyma
przemielony kontekst, standardowo siedzi w <code>f16</code> lub <code>bf16</code>, po 2 bajty na wartość — i przy długim
oknie to właśnie on, a nie wagi, robi się największym żłobem na VRAM (rozgryzaliśmy to bliżej
<a href="/poradniki/kv-cache-vram/">tutaj</a>). Compute dtype pojawi ci się też, jak zajrzysz do
<a href="/naukowy/qlora/">QLoRA</a> — tam wagi leżą w 4 bitach, ale liczenie i tak idzie w <code>bf16</code>.</p>
<p>A <code>f32</code>? Przy samej inferencji lokalnego LLM to zwykle <strong>przepłacanie pamięcią</strong>. Ładujesz dwa razy
cięższy tensor, zżerasz dwa razy więcej VRAM-u, a jakości odpowiedzi i tak nie odróżnisz od <code>f16</code>.
Pełne <code>f32</code> ma sens tam, gdzie liczy się stabilność akumulacji — sumowanie tysięcy iloczynów
cząstkowych, niektóre warstwy normalizacyjne, stany optymalizatora przy treningu. Nie przypadkiem
TPU mnoży w <code>bf16</code>, ale <strong>sumy akumuluje w <code>f32</code></strong>: to klasyka <em>mixed precision</em> — licz tanio, sumuj
dokładnie.<sup id="fnref1:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> Ale to dzieje się pod maską frameworka, nie w twoim <code>--outtype</code>.</p>
<p>No i konkret, od którego zaczęliśmy — konwersja do GGUF. Gdy robisz bazę pod dalszą kwantyzację,
<code>--outtype f16</code> albo <code>bf16</code> to punkt wyjścia: pełna szesnastka jako fundament, z którego dopiero
schodzisz do <code>Q4_K_M</code>, <code>Q5_K_M</code> czy <code>Q8_0</code> (o tych nazwach jest osobno
<a href="/poradniki/nazwy-kwantyzacji-gguf/">tu</a>). Prosta zasada kciuka:</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"># model wytrenowano w bf16 (dziś większość) -&gt; zachowaj zakres, bierz bf16:</span>
</span></span><span class="line"><span class="cl">python convert_hf_to_gguf.py ./Meta-Llama-3-8B --outtype bf16 --outfile llama3-8b-bf16.gguf
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># starszy model natywnie w f16 albo docelowy sprzęt bez akceleracji bf16 -&gt; f16:</span>
</span></span><span class="line"><span class="cl">python convert_hf_to_gguf.py ./stary-model --outtype f16 --outfile model-f16.gguf
</span></span></code></pre></div><p>Skąd wiesz, w czym model żył oryginalnie? Zajrzyj do <code>config.json</code> na karcie modelu — pole
<code>torch_dtype</code>. Jeśli stoi tam <code>bfloat16</code> (a przy nowych modelach zwykle stoi), to konwersja do
<code>f16</code> po drodze przycięłaby zakres i mogłaby narobić drobnych przekłamań w skrajnych wartościach.
Trzymaj bazę w tym, w czym model się urodził. Nazwy <code>torch.float32</code>, <code>torch.float16</code> i <code>torch.bfloat16</code>
to zresztą dokładnie te trzy formaty z tabelki wyżej, tyle że w słowniku PyTorcha.<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup></p>
<p>Bo cała ta szarpanina o bity sprowadza się do jednej sceny: masz 16 kratek do wypełnienia i decydujesz,
ile z nich pójdzie na „jak wielka może być liczba&quot;, a ile na „jak dokładnie ją znam&quot;. <code>f16</code> upchnął
dokładność i został z ciasnym balkonem zamiast horyzontu. <code>bf16</code> odpuścił parę cyfr po przecinku
i dostał w zamian cały zakres <code>f32</code> w połowie miejsca — i właśnie dlatego, kiedy następnym razem
zawahasz się nad <code>--outtype</code>, nie będziesz już zgadywał. Będziesz wiedział, że wybierasz nie „lepszą
szesnastkę&quot;, tylko odpowiedni kształt tego samego pudełka.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Struktura pola <code>binary32</code> (single precision) wg IEEE 754 — 1 bit znaku, 8 bitów wykładnika (bias 127), 23 bity mantysy, zakres do ok. 3,4 × 10³⁸, ok. 7 cyfr znaczących: <a href="https://en.wikipedia.org/wiki/Single-precision_floating-point_format">Single-precision floating-point format (Wikipedia / IEEE 754)</a>.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Struktura pola <code>binary16</code> (half precision) wg IEEE 754-2008 — 1 bit znaku, 5 bitów wykładnika (bias 15), 10 bitów mantysy, maksymalna skończona wartość 65 504: <a href="https://en.wikipedia.org/wiki/Half-precision_floating-point_format">Half-precision floating-point format (Wikipedia / IEEE 754)</a>.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Dokładny układ bitów FP32 / FP16 / BF16 / TF32 (m.in. „FP32… 1 sign bit, an 8-bit exponent, and a 23-bit mantissa&quot;, FP16 max = 65504, TF32 jako tryb obliczeniowy Tensor Core): <a href="https://docs.nvidia.com/deeplearning/tensorrt/latest/inference-library/accuracy-considerations.html">NVIDIA TensorRT — Accuracy Considerations</a>.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>„The dynamic range of bfloat16 and float32 are equivalent. However, bfloat16 uses half of the memory space&quot; oraz mnożenie w bfloat16 z akumulacją w float32: <a href="https://docs.cloud.google.com/tpu/docs/bfloat16">Google Cloud — The bfloat16 numerical format</a>.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Empiryczny dowód, że trening w BF16 osiąga wyniki SOTA jak FP32, w tej samej liczbie iteracji i bez zmiany hiperparametrów, dzięki zachowaniu zakresu dynamicznego FP32: Kalamkar i in., „A Study of BFLOAT16 for Deep Learning Training&quot;, <a href="https://arxiv.org/abs/1905.12322">arXiv:1905.12322</a>.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>Oficjalne nazwy i opisy typów <code>torch.float32</code>, <code>torch.float16</code> (half) oraz <code>torch.bfloat16</code> (Brain floating point): <a href="https://docs.pytorch.org/docs/stable/tensor_attributes.html">PyTorch — Tensor Attributes / torch.dtype</a>.&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>llama.cpp: flagi, które naprawdę musisz znać</title><link>https://inferownia.pl/poradniki/flagi-llama-cpp/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0200</pubDate><category>inferencja</category><guid>https://inferownia.pl/poradniki/flagi-llama-cpp/</guid><description>Wpisujesz llama-cli --help i dostajesz ścianą flag prosto w twarz. Rozbieramy te, które realnie decydują, czy model wejdzie na twój sprzęt i jak szybko będzie gadał.</description><content:encoded><![CDATA[<p>Wpisałeś kiedyś <code>llama-cli --help</code> i przez chwilę pomyślałeś, że terminal się zawiesił?
Zjechało z ekranu jakieś sto flag, jedna pod drugą, każda z krótkim opisem po angielsku,
a człowiek siedzi jak przed instrukcją do mebli z drugiej ręki — niby wszystko po kolei,
a i tak nie wiadomo, od czego zacząć. Łatwo wtedy zamknąć to okno i wrócić do klikania
w Ollamie, bo Ollama „po prostu działa&quot;.</p>
<p>I działa — do momentu, w którym chcesz coś, czego domyślnie nie dostajesz. Więcej kontekstu.
Model wciśnięty w kartę, która teoretycznie jest za mała. Serwer na całą sieć domową, bez
wysyłania czegokolwiek na zewnątrz. Wtedy schodzisz piętro niżej, do <code>llama.cpp</code> — silnika
w czystym C/C++, na którym pół tego lokalnego światka stoi (Ollama, LM Studio, KoboldCpp —
wszystkie mają go gdzieś pod maską). A tam rządzą flagi. I dobra wiadomość jest taka:
z tej setki na co dzień dotykasz może dziesięciu.</p>
<h2 id="zanim-ruszysz-dwie-binarki-nie-jedna">Zanim ruszysz: dwie binarki, nie jedna</h2>
<p>Kiedyś było <code>main</code>. Dziś, po sprzątaniu w repo, masz dwa najważniejsze narzędzia:
<code>llama-cli</code> (rozmowa w terminalu, testy, skrypty) i <code>llama-server</code> (serwer z web UI i API
kompatybilnym z OpenAI). Flagi w dużej mierze się pokrywają, więc to, co niżej, działa
w obu — chyba że zaznaczę inaczej.</p>
<p>Model podajesz jednym parametrem, <code>-m</code> (od <em>model</em>). Reszta to strojenie:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">llama-cli -m qwen2.5-7b-instruct-q4_k_m.gguf -cnv
</span></span></code></pre></div><p><code>-cnv</code> (od <em>conversation</em>) włącza tryb czatu, zamiast jednorazowego dokończenia promptu.
Do tego <code>-sys &quot;jesteś zwięzłym asystentem&quot;</code> ustawia prompt systemowy, a <code>-p &quot;...&quot;</code> podaje
zwykły prompt. Tyle wystarczy, żeby cokolwiek gadało. Tyle że domyślne ustawienia rzadko
są skrojone pod twoją konkretną kartę — więc przechodzimy do sedna.</p>
<h2 id="żeby-model-wszedł-do-karty">Żeby model wszedł do karty</h2>
<p>Tu toczy się cała gra, bo VRAM (pamięć karty) to waluta, której nigdy nie masz dość.
Cztery flagi, które robią 90% roboty:</p>
<ul>
<li><strong><code>-ngl N</code></strong> (<em>gpu-layers</em>) — ile warstw modelu wrzucić na GPU. To najważniejsza flaga
w całym zestawie. Przyjmuje liczbę albo słowo: <code>auto</code> (domyślnie — <code>llama.cpp</code> sam dobiera,
ile się zmieści) oraz <code>all</code> (wrzuć wszystko, co się da). Chcesz maksimum na karcie?
<code>-ngl all</code>. Za mało VRAM-u na cały model? Podaj mniejszą liczbę, np. <code>-ngl 20</code> — reszta
poleci na CPU. Mieszany offload jest wolniejszy, ale często to jedyny sposób, żeby
większy model w ogóle ruszył.</li>
<li><strong><code>-c N</code></strong> (<em>ctx-size</em>) — okno kontekstu w tokenach. Domyślnie <code>llama.cpp</code> bierze rozsądną
wartość, ale to właśnie kontekst potrafi po cichu zjeść ci pamięć. Podwoisz <code>-c</code> z 4096 na
8192 i nagle brakuje pół giga VRAM-u, którego przed chwilą było w sam raz. <code>-c 0</code> mówi
„weź maksimum z modelu&quot; — kuszące, ale sprawdź wcześniej, czy masz na to pamięć.</li>
<li><strong><code>-fa on</code></strong> (<em>flash attention</em>) — zoptymalizowana uwaga: mniej pamięci na KV-cache
(bufor, w którym model trzyma przetworzony kontekst) i zwykle ciut szybciej. Flaga
przyjmuje <code>on</code>/<code>off</code>/<code>auto</code>, a domyślnie stoi na <code>auto</code> (to <code>llama.cpp</code> decyduje). Chcesz
mieć pewność, że działa na nowszej karcie — wal <code>-fa on</code>.</li>
<li><strong><code>-ctk</code> / <code>-ctv</code></strong> (<em>cache-type-k / -v</em>) — kwantyzacja samego KV-cache, np. <code>q8_0</code>
albo <code>q4_0</code>. Przy długim kontekście to on, nie wagi, robi się największym żłobem na pamięć.
Ściśnięcie go do <code>q8_0</code> odzyskuje sporo VRAM-u niemal bez odczuwalnej straty. Najlepiej
razem z <code>-fa on</code>.</li>
</ul>
<div class="callout">
  <span class="callout__label">Dlaczego to ważne dla lokalnego LLM</span>
<p>Kolejność ratowania, gdy „nie wchodzi&quot;: najpierw <code>-fa on</code>, potem <code>-ctk q8_0 -ctv q8_0</code>,
dopiero na końcu tnij <code>-c</code> albo schodź z <code>-ngl</code>. Pierwsze dwa kosztują cię grosze na
jakości, a potrafią odzyskać na tyle VRAM-u, że model klasy 7B–8B wejdzie na kartę
12 GB z długim kontekstem, zamiast dławić się na starcie.</p>
</div>
<h2 id="żeby-było-szybciej">Żeby było szybciej</h2>
<p>Zmieściło się? To teraz o prędkość:</p>
<ul>
<li><strong><code>-t N</code></strong> (<em>threads</em>) — ile wątków CPU. Ustaw na liczbę <strong>fizycznych</strong> rdzeni, nie
logicznych. Więcej nie znaczy szybciej — powyżej liczby rdzeni zaczynasz sobie tylko
przeszkadzać. Liczy się głównie wtedy, gdy część modelu (albo całość) siedzi na CPU.</li>
<li><strong><code>-b</code> / <code>-ub</code></strong> (<em>batch-size / ubatch-size</em>) — jak duże porcje tokenów przetwarza model
naraz przy „czytaniu&quot; promptu. Domyślne wartości są sensowne; ruszaj je, dopiero jak
świadomie walczysz o przepustowość przy długich promptach.</li>
<li><strong><code>--mlock</code></strong> — przypnij model w RAM, żeby system nie wyrzucił go do swapu w najmniej
odpowiednim momencie. <strong><code>--no-mmap</code></strong> ładuje cały plik do pamięci z góry (przydatne na
dziwnych dyskach sieciowych). Na co dzień rzadko potrzebne, ale warto wiedzieć, że są.</li>
<li><strong><code>-ts</code> / <code>-sm</code></strong> (<em>tensor-split / split-mode</em>) — masz dwie karty? <code>-ts 3,1</code> rozłoży
model w proporcji 3:1, a <code>-sm</code> decyduje, jak dokładnie. Temat na osobny wpis, ale niech
ci mignie, że wielo-GPU to nie czarna magia.</li>
</ul>
<h2 id="żeby-generacja-miała-ręce-i-nogi">Żeby generacja miała ręce i nogi</h2>
<p>Te flagi nie dotykają pamięci — sterują tym, <em>jak</em> model dobiera słowa:</p>
<table>
	<thead>
			<tr>
					<th>Flaga</th>
					<th>Co robi</th>
					<th>Bezpieczny start</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>--temp</code></td>
					<td>„temperatura&quot; — im wyżej, tym bardziej kreatywnie (i chaotycznie)</td>
					<td><code>0.7</code></td>
			</tr>
			<tr>
					<td><code>--top-p</code></td>
					<td>odcina mało prawdopodobny ogon słów (nucleus)</td>
					<td><code>0.9</code></td>
			</tr>
			<tr>
					<td><code>--min-p</code></td>
					<td>nowsza, często lepsza alternatywa dla top-p</td>
					<td><code>0.05</code></td>
			</tr>
			<tr>
					<td><code>--repeat-penalty</code></td>
					<td>kara za powtórki, gdy model się zacina w kółko</td>
					<td><code>1.1</code></td>
			</tr>
			<tr>
					<td><code>-n N</code></td>
					<td>ile tokenów wygenerować (<code>-1</code> = bez limitu)</td>
					<td><code>-1</code></td>
			</tr>
	</tbody>
</table>
<p>Jak model gada od rzeczy — zbij <code>--temp</code>. Jak mieli w kółko to samo — podnieś
<code>--repeat-penalty</code> o włos. Bez fanatyzmu, to pokrętła, nie wyrocznia.</p>
<h2 id="serwer-czyli-twój-prywatny-endpoint">Serwer, czyli twój prywatny endpoint</h2>
<p>Najlepszy patent na co dzień: odpal <code>llama-server</code> raz i gadaj z modelem z przeglądarki
albo z kodu, przez API w formacie OpenAI — wszystko lokalnie, offline.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">llama-server -m qwen2.5-7b-instruct-q4_k_m.gguf <span class="se">\
</span></span></span><span class="line"><span class="cl">  -ngl all -c <span class="m">8192</span> -fa on <span class="se">\
</span></span></span><span class="line"><span class="cl">  --host 0.0.0.0 --port <span class="m">8080</span>
</span></span></code></pre></div><p>Wchodzisz na <code>http://localhost:8080</code> i masz web UI od strzału. <code>--host 0.0.0.0</code> wystawia
serwer na całą sieć domową (laptop w kuchni gada z kartą w gabinecie — klasyk domowego setupu).
<code>-np N</code> (<em>parallel</em>) obsłuży kilka rozmów naraz, a <code>-cb</code> (<em>continuous batching</em>) sensownie
je poupycha. <code>--jinja</code> każe użyć szablonu czatu wbudowanego w model — przydaje się, gdy
odpowiedzi wyglądają, jakby model nie wiedział, że rozmawia.</p>
<h2 id="zrób-to-sam">Zrób to sam</h2>
<p>Masz kartę 8 GB i model 7B w Q4? Zacznij od tego i patrz na zużycie VRAM:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">llama-cli -m qwen2.5-7b-instruct-q4_k_m.gguf -ngl all -c <span class="m">4096</span> -fa on -cnv
</span></span></code></pre></div><p>Nie wchodzi? Dorzuć ściśnięty cache i przytnij okno:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">llama-cli -m qwen2.5-7b-instruct-q4_k_m.gguf <span class="se">\
</span></span></span><span class="line"><span class="cl">  -ngl all -c <span class="m">4096</span> -fa on -ctk q8_0 -ctv q8_0 -cnv
</span></span></code></pre></div><p>Modele w formacie GGUF ściągniesz z Hugging Face (szukaj wariantów <code>Q4_K_M</code> — dobry
kompromis rozmiar/jakość). Kod, aktualne flagi i instrukcje budowania są w oficjalnym
repo: <a href="https://github.com/ggml-org/llama.cpp">github.com/ggml-org/llama.cpp</a>. A pełną,
zawsze aktualną listę wywołasz tym, od czego zaczęliśmy — <code>llama-cli --help</code>. Tylko teraz
ta ściana nie będzie już ścianą, a tablicą z pokrętłami, z których wiesz, które przekręcić.</p>
<blockquote>
<p>Wartości domyślne w <code>llama.cpp</code> bywają dobre, ale zmieniają się z wersją. Jak coś
zaczyna działać dziwnie po aktualizacji — najpierw zajrzyj, czy flaga, na której
polegasz, nie dostała nowego domyślnego zachowania.</p>
</blockquote>
<p>Bo cała zabawa w lokalnym AI sprowadza się właśnie do tego jednego uczucia: siedzisz przed
własnym sprzętem, przekręcasz pokrętło o jeden klik i patrzysz, jak model, który „się nie
mieścił&quot;, nagle mruczy pod biurkiem jak trzeba. Nikt ci nie dyktuje, ile masz mu dać
kontekstu ani na czym go odpalić. Ty, karta i garść flag.</p>
]]></content:encoded></item><item><title>Backendy llama.cpp: świat poza CUDA</title><link>https://inferownia.pl/aktualnosci/backendy-llama-cpp/</link><pubDate>Fri, 17 Jul 2026 00:00:00 +0200</pubDate><category>inferencja</category><guid>https://inferownia.pl/aktualnosci/backendy-llama-cpp/</guid><description>Nie masz zielonej karty i już myślisz, że lokalne LLM-y nie są dla ciebie? llama.cpp liczy na CPU, Metalu, Vulkanie i całej reszcie sprzętu — CUDA to tylko jedna z opcji, nie bilet wstępu.</description><content:encoded><![CDATA[<p>Wchodzisz na r/LocalLLaMA, ktoś pyta „jaką kartę wziąć pod modele&quot;, i pod postem od razu
ta sama litania: 3090, 3090, może 4090 jak budżet pozwala, koniecznie NVIDIA, koniecznie
CUDA. Człowiek z Radeonem w obudowie albo z MacBookiem na biurku czyta to i po cichu
zakłada, że wsiadł nie do tego pociągu — że lokalne LLM-y to zabawa wyłącznie dla posiadaczy
zielonych kart, a on ma co najwyżej oglądać z peronu.</p>
<p>I to jest jedno z tych przekonań, które żyje własnym życiem, choć z prawdą ma niewiele
wspólnego. Bo <code>llama.cpp</code> — silnik, na którym stoi pół tego lokalnego światka — od samego
początku był pisany pod jedną obietnicę: odpalać modele „na szerokim zakresie sprzętu&quot;,
lokalnie i w chmurze, w czystym C/C++ bez ciężkich zależności.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> CUDA jest w tym
zestawie, jasne. Ale jest jedną z kilkunastu opcji, nie warunkiem wejścia.</p>
<h2 id="co-to-w-ogóle-jest-backend">Co to w ogóle jest „backend&quot;</h2>
<p>Zajrzyj pod maskę <code>llama.cpp</code>, a znajdziesz <code>ggml</code> — bibliotekę, która robi całą matematykę
modelu. I tu wchodzi słowo klucz: <strong>backend</strong>. To po prostu moduł <code>ggml</code>, który wie, jak
wykonać te wszystkie mnożenia macierzy na konkretnym rodzaju sprzętu. Jeden backend gada
z układem NVIDIA. Inny z GPU Apple. Jeszcze inny z byle czym, co ma sterownik Vulkana.</p>
<p>Domyślnym, zawsze dostępnym backendem jest <strong>CPU</strong>. Zwykły procesor, ten sam, na którym
odpalasz przeglądarkę. Zbudujesz <code>llama.cpp</code> bez ani jednej flagi akceleracji i model
i tak ruszy — wolniej niż na GPU, ale ruszy.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> To jest ta część, o której cała ta litania
zapomina wspomnieć: żeby cokolwiek pogadało lokalnie, karta graficzna nie jest
ci technicznie potrzebna. Jest ci potrzebna, żeby było <em>szybko</em>. A to już zupełnie inna rozmowa.</p>
<h2 id="ilu-tych-backendów-w-ogóle-jest">Ilu tych backendów w ogóle jest?</h2>
<p>Więcej, niż podejrzewasz. Oficjalna tabela w README ciągnie się przez kilkanaście pozycji:
Metal, BLAS, SYCL, CUDA, HIP, Vulkan, MUSA, CANN, OpenCL, ZenDNN, WebGPU, IBM zDNN,
Hexagon i jeszcze parę egzotów.<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Większości z nich w życiu nie dotkniesz — Ascend
NPU czy IBM Z &amp; LinuxONE to sprzęt, którego nie trzymasz pod biurkiem. Ale te, które
realnie decydują o tym, czy twój sprzęt załapie się na zabawę, dają się policzyć na palcach:</p>
<table>
	<thead>
			<tr>
					<th>Backend</th>
					<th>Sprzęt</th>
					<th>Flaga przy budowaniu</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>CPU</strong></td>
					<td>dowolny procesor</td>
					<td>żadna (domyślny)</td>
			</tr>
			<tr>
					<td><strong>CUDA</strong></td>
					<td>GPU NVIDIA</td>
					<td><code>-DGGML_CUDA=ON</code></td>
			</tr>
			<tr>
					<td><strong>Metal</strong></td>
					<td>Apple Silicon (M1–M4)</td>
					<td>domyślnie ON na macOS</td>
			</tr>
			<tr>
					<td><strong>Vulkan</strong></td>
					<td>GPU wielu producentów (AMD, Intel, NVIDIA)</td>
					<td><code>-DGGML_VULKAN=ON</code></td>
			</tr>
			<tr>
					<td><strong>HIP / ROCm</strong></td>
					<td>GPU AMD</td>
					<td><code>-DGGML_HIP=ON -DGPU_TARGETS=…</code></td>
			</tr>
			<tr>
					<td><strong>SYCL</strong></td>
					<td>GPU Intel</td>
					<td>osobna instrukcja</td>
			</tr>
			<tr>
					<td><strong>BLAS</strong></td>
					<td>CPU (przyspieszenie)</td>
					<td><code>-DGGML_BLAS=ON</code></td>
			</tr>
	</tbody>
</table>
<p>Zanim pójdziemy dalej — jedna rzecz, która lubi umykać, a jest w tym wszystkim kluczowa.
Te flagi z <code>-D</code> na przedzie to opcje <strong>budowania</strong>, czyli kompilacji. <code>llama.cpp</code> bierzesz
w postaci kodu źródłowego i raz zamieniasz go w gotowy program — tym właśnie jest
budowanie, a robi to <code>cmake</code>. I to na tym etapie, <em>przed</em> pierwszym uruchomieniem, mówisz,
jakie backendy wkompilować. Nie podajesz <code>-DGGML_CUDA=ON</code> przy odpalaniu modelu — podajesz
to raz, zanim plik wykonywalny w ogóle powstanie. Potem uruchamiasz już gotową binarkę,
a ona z góry wie, na czym ma liczyć.</p>
<p>Przejdźmy tę listę tak, jak się ją czyta w praktyce — od strony pytania „a co ja mam
w obudowie&quot;.</p>
<p><strong>Masz NVIDIA?</strong> No to CUDA, ta zamknięta platforma obliczeniowa NVIDIA, która działa
tylko na ich kartach. Włączasz ją flagą <code>-DGGML_CUDA=ON</code> przy budowaniu.<sup id="fnref1:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> To ta
najbardziej wydeptana ścieżka, o której wszyscy piszą — i faktycznie działa jak trzeba.
Tylko że wydeptana nie znaczy jedyna.</p>
<p><strong>Masz MacBooka na Apple Silicon?</strong> Tu jest najlepszy żart całej układanki: nie musisz
robić absolutnie nic. Backend <strong>Metal</strong> — natywne API GPU Apple — jest na macOS włączony
domyślnie. Budujesz <code>llama.cpp</code> normalnie i twój M2 czy M3 od razu liczy na GPU. Chcesz
go z jakiegoś powodu wyłączyć? Musisz się wręcz postarać: <code>-DGGML_METAL=OFF</code>.<sup id="fnref2:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>
Zunifikowana pamięć w Makach robi tu robotę, o której właściciele osobnych kart mogą
tylko pomarzyć.</p>
<p><strong>Masz Radeona albo GPU Intela?</strong> I tu robi się najciekawiej, bo to dokładnie ci ludzie,
których ta litania odprawia z kwitkiem. A mają aż dwie drogi.</p>
<h2 id="vulkan-czyli-furtka-dla-całej-reszty">Vulkan, czyli furtka dla całej reszty</h2>
<p>Gdybym miał wskazać jeden backend, o którym za mało się mówi, to <strong>Vulkan</strong>. To otwarte,
wieloplatformowe API graficzno-obliczeniowe (ta sama Khronos, co od OpenGL-a), które
obsługuje mnóstwo sterowników GPU — NVIDIA, AMD, Intela. W <code>llama.cpp</code> włączasz je jedną
flagą, <code>-DGGML_VULKAN=ON</code>, i dostajesz akcelerację GPU działającą cross-platform, na
Windowsie, Linuksie i macOS naraz — bez instalowania dedykowanych, ciężkich stosów
producenta.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></p>
<p>I to jest sedno. Bo alternatywą dla AMD jest <strong>HIP/ROCm</strong> — cały software&rsquo;owy stos AMD do
liczenia na GPU (HIP to interfejs w jego ramach, pozwalający pisać kod zbliżony do CUDA;
to nie osobny produkt, tylko część tego samego ekosystemu). Bywa wydajniejszy od Vulkana,
ale kosztuje cię więcej zachodu przy konfiguracji i samym budowaniu. Zobacz, jak to wygląda:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">HIPCXX</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span>hipconfig -l<span class="k">)</span><span class="s2">/clang&#34;</span> <span class="nv">HIP_PATH</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span>hipconfig -R<span class="k">)</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  cmake -S . -B build -DGGML_HIP<span class="o">=</span>ON -DGPU_TARGETS<span class="o">=</span>gfx1030 -DCMAKE_BUILD_TYPE<span class="o">=</span>Release
</span></span><span class="line"><span class="cl">cmake --build build --config Release
</span></span></code></pre></div><p><code>gfx1030</code> to nie ozdobnik — to kod architektury twojego konkretnego układu AMD, który
musisz najpierw poznać i podać ręcznie.<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> U Intela jest podobnie: backend <strong>SYCL</strong>
(otwarty standard programowania heterogenicznego, którym Intel wozi swoje GPU przez oneAPI)
ma na tyle osobną drogę budowania, że w repo doczekał się własnego pliku dokumentacji, obok
głównego <code>build.md</code>.<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> Nikt nie mówi, że to zabawa bez łzawienia. Ale że się da — da się.</p>
<div class="callout">
  <span class="callout__label">Dlaczego to ważne dla lokalnego LLM</span>
<p>Dla kogoś bez „zielonej karty&quot; Vulkan bywa najkrótszą drogą do sensownej akceleracji GPU:
jedna flaga <code>-DGGML_VULKAN=ON</code>, sterownik, który i tak masz do grania, i tyle — zamiast
przekopywania się przez cały ROCm czy oneAPI. Nie zawsze wyciśnie z Radeona ostatni token,
ale różnica między „liczę na GPU&quot; a „mielę na samym CPU&quot; jest odczuwalnie większa niż
różnica między dwoma backendami GPU. Najpierw w ogóle wejdź na GPU; to, który z nich
wyciska więcej, dostroisz później.</p>
</div>
<h2 id="a-jeśli-gpu-w-ogóle-nie-ma">A jeśli GPU w ogóle nie ma?</h2>
<p>Zostaje CPU — i wcale nie jesteś skazany na gołą, referencyjną implementację. Masz <strong>BLAS</strong>
(np. OpenBLAS), który przyspiesza obliczenia na procesorze i włącza się parą flag
<code>-DGGML_BLAS=ON -DGGML_BLAS_VENDOR=OpenBLAS</code>.<sup id="fnref3:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> A jak siedzisz na czymś bardziej
konkretnym, <code>llama.cpp</code> ma nawet backendy skrojone pod rodzinę procesora: <strong>ZenDNN</strong> pod
serwerowe CPU AMD EPYC i <strong>Arm KleidiAI</strong> pod układy Arm — obok generycznego CPU.<sup id="fnref4:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>
Mrówcza robota optymalizacyjna, o której mało kto wie, a która potrafi wycisnąć z krzemu,
który już masz, zaskakująco dużo.</p>
<h2 id="zrób-to-sam">Zrób to sam</h2>
<p>Powiedzmy, że budujesz <code>llama.cpp</code> sam (a warto — <a href="/poradniki/flagi-llama-cpp/">rozbieraliśmy jego flagi po
kolei</a>, bo to na nich jedzie pół ekosystemu, od Ollamy po
LM Studio). Najprostszy build, sam CPU, bez żadnych ozdobników:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cmake -B build
</span></span><span class="line"><span class="cl">cmake --build build --config Release
</span></span></code></pre></div><p>Chcesz akcelerację? Dokładasz jedną flagę pod swój sprzęt — <code>-DGGML_CUDA=ON</code>,
<code>-DGGML_VULKAN=ON</code>, <code>-DGGML_HIP=ON</code> — i tyle. Co więcej, backendy da się łączyć w jednym
buildzie (CUDA i Vulkan naraz to normalka), a którym urządzeniem policzysz, wybierasz już
w locie, przy odpalaniu:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">llama-cli --list-devices
</span></span><span class="line"><span class="cl">llama-cli -m model.gguf --device Vulkan0
</span></span><span class="line"><span class="cl">llama-cli -m model.gguf --device none
</span></span></code></pre></div><p><code>--list-devices</code> pokaże ci wszystkie akceleratory, które build widzi, <code>--device</code> wskaże
ten jeden, a <code>--device none</code> każe liczyć wyłącznie na CPU — wygodne, gdy chcesz zobaczyć,
ile tracisz bez GPU.<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> I jedno, co warto rozdzielić w głowie raz na zawsze: wybór
backendu to nie to samo co kwantyzacja modelu. Format GGUF i to, czy bierzesz Q4 czy Q8
(<a href="/poradniki/nazwy-kwantyzacji-gguf/">nazwy rozbieraliśmy osobno</a>), to jedna decyzja —
<em>na czym</em> to policzysz to zupełnie druga. Ten sam plik <code>.gguf</code> pojedzie na CUDA, na
Vulkanie i na gołym procesorze. Nie mieszaj tych dwóch pokręteł, bo to niezależne osie.</p>
<p>Cała aktualna prawda o budowaniu siedzi w jednym pliku:
<a href="https://github.com/ggml-org/llama.cpp/blob/master/docs/build.md">docs/build.md</a> w repo
<code>ggml-org/llama.cpp</code>. Sekcja po sekcji, backend po backendzie.</p>
<p>Bo ta redditowa litania „NVIDIA albo nic&quot; to trochę jak stać przed budynkiem z jednymi
oszklonymi drzwiami frontowymi, na które wszyscy się pchają, i nie wiedzieć, że dookoła
jest jeszcze pięć wejść — węższych, gorzej oznaczonych, część z lekko zacinającą się klamką,
ale prowadzących do tego samego środka. Zielona karta to najwygodniejsze drzwi. Nie jedyne.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><code>ggml-org/llama.cpp</code> — README (deklaracja celu projektu: „a wide range of hardware — locally and in the cloud&quot; i tabela wspieranych backendów, m.in. Metal, BLAS, SYCL, CUDA, HIP, Vulkan, CANN, OpenCL, ZenDNN, IBM zDNN, Hexagon): <a href="https://github.com/ggml-org/llama.cpp#readme">github.com/ggml-org/llama.cpp</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></p>
</li>
<li id="fn:2">
<p><code>ggml-org/llama.cpp</code> — dokumentacja budowania (CPU jako backend domyślny, <code>-DGGML_CUDA=ON</code>, Metal domyślnie ON na macOS z <code>-DGGML_METAL=OFF</code>, <code>-DGGML_BLAS=ON -DGGML_BLAS_VENDOR=OpenBLAS</code>, sekcje ZenDNN i Arm KleidiAI): <a href="https://github.com/ggml-org/llama.cpp/blob/master/docs/build.md">docs/build.md</a>.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#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></p>
</li>
<li id="fn:3">
<p><code>ggml-org/llama.cpp</code> — sekcja Vulkan (flaga <code>-DGGML_VULKAN=ON</code>, wieloplatformowy backend na GPU różnych producentów): <a href="https://github.com/ggml-org/llama.cpp/blob/master/docs/build.md#vulkan">docs/build.md#vulkan</a>.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p><code>ggml-org/llama.cpp</code> — sekcja HIP (zmienne <code>HIPCXX</code>/<code>HIP_PATH</code> z <code>hipconfig</code>, flagi <code>-DGGML_HIP=ON -DGPU_TARGETS=gfx1030</code>): <a href="https://github.com/ggml-org/llama.cpp/blob/master/docs/build.md#hip">docs/build.md#hip</a>.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p><code>ggml-org/llama.cpp</code> — osobna dokumentacja backendu SYCL dla GPU Intel: <a href="https://github.com/ggml-org/llama.cpp/blob/master/docs/backend/SYCL.md">docs/backend/SYCL.md</a>.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p><code>ggml-org/llama.cpp</code> — definicje flag CLI <code>--list-devices</code>, <code>--device</code> (z obsługą wartości <code>none</code> w <code>parse_device_list</code>): <a href="https://github.com/ggml-org/llama.cpp/blob/master/common/arg.cpp">common/arg.cpp</a>.&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Nazwy kwantyzacji GGUF: co znaczy Q4_K_M i cała reszta</title><link>https://inferownia.pl/poradniki/nazwy-kwantyzacji-gguf/</link><pubDate>Thu, 16 Jul 2026 00:00:00 +0200</pubDate><category>kwantyzacja</category><guid>https://inferownia.pl/poradniki/nazwy-kwantyzacji-gguf/</guid><description>Wchodzisz na kartę modelu na Hugging Face, a tam kolumna pełna szyfrów: Q4_K_M, Q5_K_S, IQ2_XXS. Rozbieramy te nazwy litera po literze — żebyś wiedział, który plik pobrać, a nie zgadywał.</description><content:encoded><![CDATA[<p>Który plik w ogóle pobrać — <code>Q4_K_M</code>, <code>Q5_K_S</code>, a może to dziwne <code>IQ2_XXS</code>? Przychodzisz po jeden model, a zastajesz tabelę na dwadzieścia wierszy: <code>Q4_K_M</code>, <code>Q5_K_S</code>, <code>Q6_K</code>, <code>Q8_0</code>, niżej jeszcze <code>IQ2_XXS</code> i <code>IQ3_XS</code>, każdy z inną wagą w gigabajtach. Człowiek stoi nad tym jak nad półką z olejami silnikowymi w markecie — wszystkie coś tam obiecują, wszystkie mają cyferki, a ty nie wiesz, czy <code>5W-40</code> to lepiej, czy gorzej niż <code>10W-30</code>. Nowi ludzie zadają to pytanie mniej więcej co drugi dzień: „którą wersję pobrać?&quot;.</p>
<p>I najczęściej ktoś rzuca: „bierz Q4_K_M i się nie zastanawiaj&quot;. Dobra rada — ale zostawia cię z tym samym uczuciem, co „wlej 5W-40 i jedź&quot;. Działa, tylko dalej nie wiesz, co te znaczki znaczą. A one nie są przypadkowe. Każda litera i każda cyfra w <code>Q4_K_M</code> coś konkretnie mówi: ile bitów, jaka rodzina, ile precyzji odzyskujesz z powrotem. Jak raz to rozłożysz, ta tabela przestaje być loterią, a robi się listą z parametrami. Rozbierzmy ją.</p>
<h2 id="skąd-w-ogóle-biorą-się-te-nazwy">Skąd w ogóle biorą się te nazwy</h2>
<p>Zanim rozbierzemy nazwy, jedno pytanie: po co w ogóle tniemy? Model w oryginale trzyma każdą wagę na 16 bitach (format <code>F16</code>/<code>BF16</code>). Kwantyzacja to zwykłe upakowanie tych samych, już wytrenowanych liczb ciaśniej — nie douczanie, nie trening od nowa, tylko kompresja. To ważne rozróżnienie, bo ludzie mylą to z fine-tuningiem: tu nic się nie uczy, tylko chudnie.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> O tym, jak liczby idą w dół i czemu jakość spada tak zaskakująco mało, rozgryzaliśmy szerzej przy okazji <a href="/aktualnosci/ekosystem-gguf/">wyboru wersji modelu</a>.</p>
<p>GGUF (GPT-Generated Unified Format — binarny format plików modeli spod <code>llama.cpp</code>) każdemu z tych sposobów cięcia daje nazwę. I ta nazwa nie jest marketingowa — to zapis, jak dokładnie wagi są zakodowane w pliku. Format jest zaskakująco regularny: <code>QN_K_X</code>. <code>N</code> to przybliżona liczba bitów na wagę, <code>_K</code> to nazwa rodziny (tak zwane k-quanty), a przyrostek <code>X</code> (<code>S</code>/<code>M</code>/<code>L</code>) mówi, ile tensorów w modelu dostaje podbitą precyzję.<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Jak to raz przeczytasz jak zdanie, a nie jak szyfr, reszta wchodzi sama.</p>
<h2 id="q4_k_m--rozłóżmy-to-na-trzy-kawałki">Q4_K_M — rozłóżmy to na trzy kawałki</h2>
<p>Weźmy najsłynniejszy przykład i przejedźmy po nim znak po znaku.</p>
<p><strong>Q</strong> — to po prostu „quantized&quot;. Standardowa kwantyzacja na liczbach całkowitych. Nic ciekawego, ozdobnik.</p>
<p><strong>4</strong> — przybliżona liczba bitów na wagę. I tu pierwsza pułapka, w którą wpada każdy: to <em>przybliżenie</em>, nie dokładna wartość. Wydaje się, że <code>Q4</code> znaczy „równo 4 bity na liczbę&quot;. Nieprawda. Realnie, na modelu Llama-3.1-8B zmierzono, że <code>Q4_K_M</code> waży <strong>4,89 bita na wagę</strong>, a nie 4.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> Skąd ta nadwyżka? Właśnie z ostatniej litery — za chwilę.</p>
<p><strong>_K</strong> — rodzina „k-quant&quot;. To był przełom z połowy 2023 roku (PR #1684 od ikawrakowa).<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> Zamiast kodować każdy blok wag osobno i głupio, k-quanty pracują na <strong>super-blokach</strong>: większa grupa wag współdzieli metadane, a skala i minimum każdego mniejszego bloku są kodowane osobno, z własną precyzją. W praktyce: <code>Q4_K</code> i <code>Q5_K</code> mają super-bloki z 8 bloków po 32 wagi, a <code>Q2_K</code>, <code>Q3_K</code> i <code>Q6_K</code> — z 16 bloków po 16 wag.<sup id="fnref1:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> Dzięki temu ta sztuczka trzyma jakość dużo lepiej niż stare, proste cięcie.</p>
<p><strong>M</strong> — i tu jest cała finezja, którą prawie wszyscy przegapiają. <code>S</code>/<code>M</code>/<code>L</code> (Small/Medium/Large) <strong>nie</strong> znaczą „mały/średni/duży model&quot;. Znaczą: ile tensorów w środku dostaje podniesioną precyzję.<sup id="fnref2:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Bo model to nie jednorodna zupa liczb — jedne tensory (np. te od atencji <code>attention.wv</code> albo <code>feed_forward.w2</code>) są dla jakości ważniejsze niż inne. Wariant <code>M</code> mówi: „te newralgiczne kawałki potnij łagodniej, resztę ostrzej&quot;.<sup id="fnref2:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> To dlatego <code>Q4_K_M</code> waży te 4,89 bita zamiast 4 — bo część wag siedzi wyżej niż na czterech bitach. To nie jest jedna globalna liczba bitów, to <em>mieszanka</em>.</p>
<p>Czyli <code>Q4_K_M</code> czytasz tak: „około czterobitowa, rodzina k-quant, ze średnim miksem podbitych tensorów&quot;. Trzy słowa, nie szyfr.</p>
<h2 id="ile-realnie-ważą--bo-teoria-kłamie-o-ułamek">Ile realnie ważą — bo teoria kłamie o ułamek</h2>
<p>Skoro cyfra po <code>Q</code> to przybliżenie, to najlepiej patrzeć na liczby zmierzone na prawdziwym modelu. Oto oficjalna tabela <code>llama.cpp</code> dla Llama-3.1-8B (dla porównania: oryginał w <code>F16</code> to 14,96 GiB, a plik źródłowy waży 32,1 GB).<sup id="fnref1:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<table>
	<thead>
			<tr>
					<th>Wariant</th>
					<th>Bity/wagę</th>
					<th>Rozmiar (Llama-3.1-8B)</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>Q4_K_S</code></td>
					<td>4,67</td>
					<td>4,36 GiB</td>
			</tr>
			<tr>
					<td><code>Q4_K_M</code></td>
					<td>4,89</td>
					<td>4,58 GiB</td>
			</tr>
			<tr>
					<td><code>Q5_K_S</code></td>
					<td>5,57</td>
					<td>5,21 GiB</td>
			</tr>
			<tr>
					<td><code>Q5_K_M</code></td>
					<td>5,70</td>
					<td>5,33 GiB</td>
			</tr>
			<tr>
					<td><code>Q6_K</code></td>
					<td>6,56</td>
					<td>6,14 GiB</td>
			</tr>
			<tr>
					<td><code>Q8_0</code></td>
					<td>8,50</td>
					<td>7,95 GiB</td>
			</tr>
			<tr>
					<td><code>F16</code></td>
					<td>16,00</td>
					<td>14,96 GiB</td>
			</tr>
	</tbody>
</table>
<p>Widać z tego dwie rzeczy. Raz — każdy kolejny bit kosztuje mniej więcej pół–jeden gigabajt na modelu tej klasy, więc skok z <code>Q4_K_M</code> na <code>Q6_K</code> to jakieś półtora giga VRAM-u więcej za coraz mniej odczuwalny zysk jakości (a ile pamięci realnie zeżre dany plik z kontekstem, wyliczasz <a href="/poradniki/ile-vram-na-model/">osobnym wzorem</a>). Dwa — <code>Q8_0</code> to niemal bezstratna referencja; różnicy między nim a <code>F16</code> w ślepym teście raczej nie wychwycisz, a plik jest o połowę lżejszy. Dlatego <code>Q8_0</code>, choć technicznie to stary „legacy&quot; format, wciąż żyje i ma się świetnie.<sup id="fnref3:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>A ile to daje na dużym modelu? Oficjalna tabela pokazuje <code>Q4_K_M</code> jako jedyny przykład: Llama 3.1 8B chudnie z 32,1 GB do 4,9 GB, 70B z 280,9 GB do 43,1 GB, a monstrualne 405B z 1625 GB do 249 GB.<sup id="fnref2:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> Cztery bity zamiast szesnastu i nagle model, który potrzebował serwerowni, mieści się na jednej porządnej karcie.</p>
<h2 id="dlaczego-akurat-q4_k_m-jest-tym-domyślnym">Dlaczego akurat Q4_K_M jest tym „domyślnym&quot;</h2>
<p>No dobra, ale skąd się wziął ten konsensus, że <code>Q4_K_M</code> to bezpieczny strzał? Tu muszę cię uczciwie zderzyć z rzeczywistością: <code>llama.cpp</code> <strong>nigdzie formalnie nie ogłasza</strong> żadnego „oficjalnego domyślnego&quot; kwantu.<sup id="fnref3:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> To mit, który sam sobie chodzi po forach. Ale nie wziął się znikąd.</p>
<p>Po pierwsze, <code>Q4_K_M</code> ląduje w dokumentacji jako <strong>przykładowa komenda</strong> — kiedy README pokazuje, jak w ogóle uruchomić <code>llama-quantize</code>, robi to właśnie na nim:<sup id="fnref4: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">./build/bin/llama-quantize model-bf16.gguf model-Q4_K_M.gguf Q4_K_M
</span></span></code></pre></div><p>Po drugie — i ważniejsze — cztery bity to punkt, w którym krzywa „rozmiar kontra jakość&quot; ma najładniejsze kolano. Poniżej zaczynasz tracić jakość szybciej, niż zyskujesz na rozmiarze. Powyżej płacisz gigabajtami za coraz mniejszy zysk. <code>Q4_K_M</code> siedzi dokładnie w tym słodkim punkcie, dlatego społeczność go pokochała. Ale to konsensus praktyczny, nie dekret ze specyfikacji — i warto to trzymać w głowie, zanim ktoś ci powie, że „tak trzeba&quot;.</p>
<h2 id="a-te-dziwne-iq-z-wieloma-iksami">A te dziwne IQ z wieloma iksami?</h2>
<p>Zjeżdżasz w tabeli niżej i trafiasz na drugą rodzinę: <code>IQ2_XXS</code>, <code>IQ3_XS</code>, <code>IQ4_NL</code>. Wyglądają jak k-quanty, które napiły się kawy. To i-quanty (i-quants) — nowsza rodzina, zaprojektowana pod jeden cel: wycisnąć sensowną jakość z <strong>naprawdę</strong> niskich bitów, tam gdzie zwykłe k-quanty się już sypią.<sup id="fnref4:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>Sekret jest w dwóch rzeczach. I-quanty operują na większych super-blokach po 256 wag i — to kluczowe — wyliczają wagi z pomocą tak zwanej importance matrix, w skrócie <strong>imatrix</strong>.<sup id="fnref5:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Co to jest? Wyobraź sobie, że przed kompresją przepuszczasz model przez zestaw tekstów kalibracyjnych i patrzysz, które wagi <em>naprawdę</em> się aktywują, a które ledwo drgają. Imatrix to per-tensor tablica, która to zapamiętuje — dla każdej pozycji notuje, jak mocno ta waga „pracowała&quot;.<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> Potem, przy cięciu, algorytm chroni ważne wagi kosztem tych mało istotnych. Zamiast tłuc wszystko po równo, oszczędza tam, gdzie boli najmniej.</p>
<p>Litery za <code>IQ</code> układają się od najostrzejszego cięcia w górę: <code>XXS</code> (extra-extra-small), <code>XS</code>, <code>S</code>, <code>M</code>. Oto co to daje w liczbach, znów na Llama-3.1-8B:<sup id="fnref5:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<table>
	<thead>
			<tr>
					<th>Wariant</th>
					<th>Bity/wagę</th>
					<th>Rozmiar</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>IQ1_S</code></td>
					<td>2,00</td>
					<td>1,87 GiB</td>
			</tr>
			<tr>
					<td><code>IQ2_XXS</code></td>
					<td>2,38</td>
					<td>2,23 GiB</td>
			</tr>
			<tr>
					<td><code>IQ2_M</code></td>
					<td>2,93</td>
					<td>2,74 GiB</td>
			</tr>
			<tr>
					<td><code>IQ3_XXS</code></td>
					<td>3,25</td>
					<td>3,04 GiB</td>
			</tr>
			<tr>
					<td><code>IQ4_XS</code></td>
					<td>4,46</td>
					<td>4,17 GiB</td>
			</tr>
			<tr>
					<td><code>IQ4_NL</code></td>
					<td>4,68</td>
					<td>4,38 GiB</td>
			</tr>
	</tbody>
</table>
<p>Zauważ jedno: <code>IQ4_XS</code> waży 4,17 GiB, czyli <em>mniej</em> niż <code>Q4_K_S</code> (4,36 GiB) przy porównywalnej klasie. To właśnie ta magia imatrixa — więcej jakości na bit. Cena? I-quanty bywają wolniejsze w inferencji na słabszym sprzęcie, bo używają tablic przeglądowych, i mają sens głównie wtedy, gdy walczysz o każdy gigabajt — na przykład wciskasz model 70B na kartę, która teoretycznie go nie uniesie.</p>
<div class="callout">
  <span class="callout__label">Dlaczego to ważne dla lokalnego LLM</span>
<p>Nie daj się nabrać na jeden zbieg okoliczności: „i&quot; w <strong>i-quant</strong> i „i&quot; w <strong>imatrix</strong> to nie ten sam mechanizm. Imatrix (flaga <code>--imatrix</code>) działa uniwersalnie — możesz nim wesprzeć również k-quanty i stare legacy quanty. A i-quanty teoretycznie da się zrobić i bez niego, tyle że są <em>zaprojektowane</em>, żeby chodzić najlepiej właśnie z imatrixem.<sup id="fnref6:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Dwie różne rzeczy, które przypadkiem zaczynają się na tę samą literę.</p>
</div>
<h2 id="legacy-czyli-dziadkowie-formatu">Legacy, czyli dziadkowie formatu</h2>
<p>Zanim były k-quanty, były proste cięcia: <code>Q4_0</code>, <code>Q4_1</code>, <code>Q5_0</code>, <code>Q5_1</code>, <code>Q8_0</code>. Dzielą wagi na płaskie bloki po 32 i kodują je najprościej jak się da — warianty <code>_0</code> wzorem <code>waga = q × skala</code>, a <code>_1</code> dokładają jeszcze minimum bloku (<code>+ block_minimum</code>).<sup id="fnref7:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Bez super-bloków, bez finezji. Dokumentacja GGUF opisuje je wprost jako „metodę przestarzałą, dziś rzadko używaną&quot; — z jednym wyjątkiem. <code>Q8_0</code> przeżył wszystkich, bo na ośmiu bitach kompresja i tak niczego istotnego nie gubi, więc służy jako wygodna, niemal bezstratna referencja.<sup id="fnref8:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Reszta to eksponaty muzealne — jeśli widzisz świeży model wypuszczony tylko w <code>Q4_0</code>, to znak, że coś jest nie tak z tym uploadem.</p>
<p>Dla porządku: świat GGUF ma jeszcze kilka egzotów, na które możesz się natknąć. <code>Q8_K</code> to nie format do dystrybucji, tylko typ roboczy do liczenia iloczynów skalarnych wewnątrz kwantów 2–6 bit.<sup id="fnref9:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> A z najnowszych — <code>TQ1_0</code>/<code>TQ2_0</code> (kwantyzacja ternarna) i <code>MXFP4</code> (4-bitowy microscaling float) to zupełnie młodsze rodziny niż klasyczne k- i i-quanty.<sup id="fnref10:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Ale to już inna bajka.</p>
<h2 id="zrób-to-sam--i-nie-strzel-sobie-w-stopę">Zrób to sam — i nie strzel sobie w stopę</h2>
<p>Jak sam chcesz coś skwantyzować, narzędzie to <code>llama-quantize</code> z repo <code>ggml-org/llama.cpp</code>. Podstawa jest banalna — podajesz źródło, cel i typ. Ale są trzy grabie, na które warto uważać.</p>
<p>Po pierwsze — <strong>tnij zawsze z oryginału</strong> (<code>F16</code>/<code>BF16</code>/<code>F32</code>), nie z już skwantyzowanego pliku. Requantyzacja (flaga <code>--allow-requantize</code>) potrafi „poważnie obniżyć jakość&quot;, ostrzega o tym sama dokumentacja.<sup id="fnref6:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> To jak przegrywanie MP3 z MP3 — za każdym razem gorzej.</p>
<p>Po drugie — flaga <code>--pure</code> wyłącza całą tę inteligentną mieszankę k-quantów i tnie wszystkie tensory do jednego typu.<sup id="fnref7:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> Brzmi kusząco („czysto!&quot;), ale odbierasz sobie właśnie to, za co <code>_M</code> jest dobre. Zostaw domyślny miks.</p>
<p>Po trzecie — jak kwantyzujesz model multimodalny, <strong>nie tnij komponentów mmproj</strong> (enkoder obrazu/dźwięku i projektory) tak agresywnie jak reszty. Dokumentacja zaleca trzymać je w <code>bf16</code> albo <code>q8</code> (czym <code>bf16</code> różni się od zwykłej „szesnastki&quot; <code>f16</code>, jest <a href="/poradniki/bf16-f16-f32/">tutaj</a>), bo mają bezpośredni wpływ na jakość tego, co trafia do modelu, a oszczędność pamięci przy niższym kwancie i tak jest znikoma.<sup id="fnref8: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"># z importance matrix, dla i-quantów i lepszych niskobitowych k-quantów:</span>
</span></span><span class="line"><span class="cl">./build/bin/llama-quantize --imatrix model.imatrix <span class="se">\
</span></span></span><span class="line"><span class="cl">  model-f16.gguf model-IQ3_M.gguf IQ3_M
</span></span></code></pre></div><p>Cała lista pokręteł — <code>--tensor-type</code> (regex per tensor), <code>--output-tensor-type</code>, <code>--token-embedding-type</code>, <code>--leave-output-tensor</code>, <code>--prune-layers</code> — czeka w dokumentacji narzędzia.<sup id="fnref9:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> Ale szczerze? Na co dzień pobierasz gotowca z Hugging Face i cała ta zabawa sprowadza się do jednego pytania: który wiersz w tabeli kliknąć.</p>
<p>I tu wracamy do tej półki z olejami. Bo kiedy już wiesz, że <code>Q4_K_M</code> to „czterobitowa, k-quant, średni miks&quot;, <code>Q6_K</code> to „szóstka bez wariantów, bo i po co&quot;, a <code>IQ2_XXS</code> to „dwa bity ratowane imatrixem na modelu, który normalnie by się nie zmieścił&quot; — ta tabela na Hugging Face przestaje być ścianą szyfrów. Robi się listą z jasnymi parametrami, gdzie każdy znaczek mówi, ile zapłacisz pamięcią i ile odzyskasz jakości.</p>
<p>Bierz <code>Q4_K_M</code>, jak masz miejsce i nie chcesz myśleć. Schodź do <code>IQ</code>, jak walczysz o ostatni gigabajt. Idź w <code>Q6_K</code> albo <code>Q8_0</code>, jak karta ci pozwala i chcesz mieć pewność. Ale już nie zgadujesz — czytasz. A to jest dokładnie ta różnica między człowiekiem, który wlewa „ten niebieski olej, bo ładny&quot;, a takim, co spojrzał na spód i wie, po co sięga.</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>Hugging Face Hub — dokumentacja formatu GGUF: wzory wag i teoretyczne bits-per-weight każdego typu (Q2_K…Q6_K, IQ…), definicja super-bloków, rozróżnienie legacy quantów od k- i i-quantów, statusy <code>Q8_0</code> i <code>Q8_K</code>, wzmianki o TQ1_0/TQ2_0 i MXFP4. <a href="https://huggingface.co/docs/hub/en/gguf">huggingface.co/docs/hub/gguf</a>.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#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>&#160;<a href="#fnref8:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref9:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref10:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>llama.cpp — dokumentacja narzędzia <code>llama-quantize</code>: tabele zmierzonych bits/weight i rozmiarów dla Llama-3.1-8B, przykładowa komenda <code>Q4_K_M</code>, flagi (<code>--pure</code>, <code>--allow-requantize</code>, <code>--imatrix</code>), zalecenie ws. mmproj oraz tabela rozmiarów 8B/70B/405B. <a href="https://github.com/ggml-org/llama.cpp/blob/master/tools/quantize/README.md">tools/quantize/README.md</a>.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#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>&#160;<a href="#fnref6:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref7:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref8:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref9:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>k-quants (Q2_K…Q6_K), ikawrakow — wprowadzenie rodziny k-quantów, definicja super-bloków oraz miksu tensorów kryjącego się pod przyrostkami S/M/L. <a href="https://github.com/ggml-org/llama.cpp/pull/1684">Pull Request #1684, ggml-org/llama.cpp</a>.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Importance Matrix calculation, ikawrakow — wprowadzenie mechanizmu imatrix, per-tensor tablicy istotności wag używanej do priorytetyzacji precyzji przy kwantyzacji. <a href="https://github.com/ggml-org/llama.cpp/pull/4861">Pull Request #4861, ggml-org/llama.cpp</a>.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><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>KV-cache: dlaczego długi kontekst zjada VRAM</title><link>https://inferownia.pl/poradniki/kv-cache-vram/</link><pubDate>Tue, 14 Jul 2026 00:00:00 +0200</pubDate><category>inferencja</category><guid>https://inferownia.pl/poradniki/kv-cache-vram/</guid><description>Wagi się załadowały, model wszedł na kartę z zapasem — a po dłuższej rozmowie znów wyskakuje out of memory. Winny nie jest model. Winny jest bufor, który puchnie z każdym tokenem.</description><content:encoded><![CDATA[<p>Znasz ten moment, kiedy model ładnie wszedł na kartę, zostało ci jeszcze ze dwa giga
zapasu, odpalasz dłuższą rozmowę albo wklejasz wielki plik do streszczenia — i po
kilkuset tokenach terminal wita cię tym samym, co zawsze: <code>CUDA out of memory</code>. Cała
na biało. I człowiek stoi zdziwiony, bo przecież model się mieścił. Wagi się nie
rozrosły. Karta nie zmalała. To co, u licha, zeżarło resztę pamięci między jednym
tokenem a drugim?</p>
<p>To wręcz podręcznikowy scenariusz — ktoś odpala 7B na karcie 8 GB, chwali się, że
działa, a dwa dni później wraca z płaczem, że „przy dłuższym kontekście się sypie&quot;.
I zawsze pada to samo pytanie w komentarzach: a ustawiłeś sobie cache? No właśnie.
Bo obok wag, które raz się ładują i grzecznie leżą, siedzi drugi żłob na VRAM —
taki, który rośnie z każdym słowem, jakie model przeczyta albo napisze. Nazywa się
KV-cache i jest głównym podejrzanym w prawie każdej sprawie „mieściło się, a potem
przestało&quot;.</p>
<h2 id="skąd-w-ogóle-bierze-się-ten-cache">Skąd w ogóle bierze się ten cache</h2>
<p>Cofnijmy się o krok. Model generuje tekst token po tokenie — jeden na raz, a każdy
kolejny patrzy wstecz na całą dotychczasową historię. Żeby policzyć uwagę (attention)
dla nowego tokenu, potrzebuje wektorów <strong>K</strong> (key) i <strong>V</strong> (value) dla wszystkiego,
co było wcześniej. I teraz klucz do zrozumienia: te wektory się nie zmieniają. K i V
dla trzeciego tokenu są takie same, kiedy generujesz token dziesiąty, jak wtedy,
gdy generowałeś czwarty.</p>
<p>Skoro się nie zmieniają — po co je liczyć od nowa przy każdym kroku? No i nie liczymy.
KV-cache to dokładnie ten bufor, w którym model odkłada raz wyliczone wektory K i V
dla każdego przetworzonego tokenu, w każdej warstwie, żeby przy generowaniu następnego
nie przemielać całej historii od zera.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> To sprytny handel: dokładasz pamięci,
żeby oszczędzić obliczenia. Bez niego każdy nowy token oznaczałby przeliczanie całego
kontekstu w kółko — decode zamieniłby się w mękę.</p>
<p>Cache jest więc dobry. Problem w tym, że za oszczędność obliczeń płacisz pamięcią.
A ta pamięć nie jest stała.</p>
<h2 id="dlaczego-on-rośnie-a-wagi-nie">Dlaczego on rośnie, a wagi nie</h2>
<p>Tu jest sedno całej sprawy, więc rozłóżmy to powoli. Wagi modelu to wartość stała.
Ładujesz plik GGUF raz, siada na karcie i tyle — czy generujesz jeden token, czy
dziesięć tysięcy, wagi zajmują dokładnie tyle samo. KV-cache zachowuje się odwrotnie:
puchnie z każdym tokenem, jaki wpadnie w okno kontekstu.<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>Ile dokładnie puchnie? Mechanizm da się złapać jednym wzorem, który krąży po całej
scenie:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">rozmiar_KV = 2 × n_layer × n_kv_heads × head_dim × długość_kontekstu × bajty_na_element
</span></span></code></pre></div><p>Rozbierzmy to na palcach. Dwójka na przedzie to osobno tensor K i osobno tensor V —
trzymasz dwa, stąd współczynnik.<sup id="fnref2:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> <code>n_layer</code> to liczba warstw transformera; cache
odkłada się w każdej z osobna. <code>n_kv_heads</code> to liczba głów KV, <code>head_dim</code> to wymiar
jednej głowy. A <code>długość_kontekstu</code> — no i tu jest pies pogrzebany: to jedyny człon,
który rośnie w trakcie. Reszta to stałe architektury danego modelu.</p>
<p>Zauważ, co z tego wynika: rozmiar cache rośnie <strong>liniowo</strong> z długością kontekstu.
Podwoisz kontekst — podwoisz cache. I liniowo z liczbą warstw oraz głów KV. Ani razu
w tym wzorze nie pada rozmiar wag.<sup id="fnref3:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> To dwa niezależne budżety pamięci.</p>
<div class="callout">
  <span class="callout__label">Dlaczego to ważne dla lokalnego LLM</span>
<p>Częsty mit z forów: „KV-cache rośnie kwadratowo z kontekstem&quot;. Nie. <strong>Sam bufor rośnie
liniowo</strong> — podwajasz tokeny, podwajasz cache. Kwadratowy jest koszt <em>obliczeniowy</em>
samej operacji attention w naiwnej implementacji (i pamięć pośrednia, którą ona
zżera po drodze) — i to właśnie ten kwadratowy narzut łagodzi flash attention. Pamięć
samego cache to prosta linia w górę.<sup id="fnref4:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
</div>
<p>Skoro cache rośnie z kontekstem, a wagi stoją w miejscu, to przy dostatecznie długim
oknie następuje przecięcie: KV-cache zaczyna dominować zużycie VRAM, a przy naprawdę
długim kontekście potrafi przerosnąć rozmiar samych wag.<sup id="fnref5:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> I stąd twoje <code>out of memory</code> w połowie rozmowy — nie model spuchł, tylko jego pamięć krótkotrwała napęczniała
do rozmiarów, których karta nie udźwignęła. Jeśli chcesz policzyć ten budżet z wyprzedzeniem,
zamiast zgadywać, sam wzór na VRAM rozkładaliśmy krok po kroku <a href="/poradniki/ile-vram-na-model/">tutaj</a>.</p>
<h2 id="jak-go-ścisnąć--trzy-dźwignie">Jak go ścisnąć — trzy dźwignie</h2>
<p>Dobra wiadomość: skoro wiesz, z czego składa się wzór, wiesz też, gdzie przyłożyć.
Masz trzy dźwignie, każda ciągnie za inny człon.</p>
<h3 id="kwantyzacja-cache--najprostszy-zysk">Kwantyzacja cache — najprostszy zysk</h3>
<p>Spójrz na ostatni człon wzoru: <code>bajty_na_element</code>. Domyślnie cache K i V w llama.cpp
siedzą w <code>f16</code>, czyli po 2 bajty na element<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> — a czym <code>f16</code> różni się od <code>bf16</code>, tłumaczymy <a href="/poradniki/bf16-f16-f32/">tutaj</a>. A nikt nie powiedział, że musi
tak zostać. <code>llama.cpp</code> daje dwie flagi — <code>-ctk</code> (<code>--cache-type-k</code>) i <code>-ctv</code>
(<code>--cache-type-v</code>) — którymi ustawiasz typ danych osobno dla cache K i cache V.
Dozwolone wartości to <code>f32</code>, <code>f16</code>, <code>bf16</code>, <code>q8_0</code>, <code>q4_0</code>, <code>q4_1</code>, <code>iq4_nl</code>, <code>q5_0</code>,
<code>q5_1</code>; domyślnie oba stoją na <code>f16</code>.<sup id="fnref1:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<p>Matematyka jest tu boleśnie prosta. <code>q8_0</code> to 1 bajt na element zamiast dwóch — czyli
z grubsza <strong>dwa razy mniej</strong> pamięci na cache. <code>q4_0</code> to pół bajta — jakieś <strong>cztery
razy mniej</strong> niż <code>f16</code>.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> Nie podam ci, ile to konkretnie megabajtów, bo to zależy
od <code>n_layer</code>, <code>n_kv_heads</code>, <code>head_dim</code> i długości kontekstu twojego modelu — kto rzuca
sztywnym „zaoszczędzisz 3 GB&quot;, ten zgaduje. Ale proporcja trzyma się zawsze: ścinasz
typ, ścinasz cache w tym samym stosunku.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">llama-server -m model-q4_k_m.gguf -c <span class="m">8192</span> -fa on -ctk q8_0 -ctv q8_0
</span></span></code></pre></div><p>I tu ważne rozróżnienie, na którym potyka się pół forum: <strong>to nie jest to samo, co
kwantyzacja wag</strong>. Wagi kwantyzujesz przy pakowaniu GGUF-a (te wszystkie <code>Q4_K_M</code>
i spółka — nazewnictwo rozbieraliśmy <a href="/poradniki/nazwy-kwantyzacji-gguf/">tutaj</a>).
Cache kwantyzujesz flagami runtime, w locie. To dwa niezależne mechanizmy. Ściśnięcie
wag do 4 bitów ani o bajt nie zmniejszy KV-cache, i odwrotnie. Ktoś odpala model
w <code>Q4_K_M</code> i dziwi się, że kontekst dalej zjada pamięć — no bo cache dalej siedzi
w <code>f16</code>, dopóki mu tego ręcznie nie zmienisz.</p>
<h3 id="flash-attention--i-warunek-wstępny">Flash attention — i warunek wstępny</h3>
<p>Druga dźwignia to <code>-fa</code> (<code>--flash-attn</code>). Flaga przyjmuje <code>on</code>, <code>off</code> albo <code>auto</code>,
domyślnie stoi na <code>auto</code>.<sup id="fnref2:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> Co robi flash attention? To nie żadne przybliżenie
uwagi — i tu kolejna pułapka do ominięcia. FlashAttention (Dao i in., NeurIPS 2022)
to algorytm <strong>dokładny</strong> (exact): liczy dokładnie to samo, co naiwna uwaga, wynik
liczbowy jest identyczny.<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> Sztuczka jest gdzie indziej — jest „IO-aware&quot;, czyli
ogranicza liczbę odczytów i zapisów między wolną pamięcią HBM karty a szybką pamięcią
on-chip SRAM. Efekt: szybciej i z mniejszym narzutem pamięci pomocniczej niż naiwna
implementacja uwagi.</p>
<p>Ale flash attention ma tu jeszcze jedną, praktyczną rolę — jest <strong>warunkiem wstępnym</strong>
kwantyzacji cache V. W llama.cpp nie da się skwantyzować cache V bez włączonego flash
attention; implementacja zwyczajnie odmawia startu, rzucając runtime error w rodzaju
<code>quantized V cache was requested, but this requires Flash Attention</code>.<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> Czyli jeśli chcesz <code>-ctv q8_0</code>, musisz najpierw dać <code>-fa on</code>.
Bez tego dostaniesz błąd, nie oszczędność.</p>
<h3 id="gqa--dźwignia-której-nie-przełączysz">GQA — dźwignia, której nie przełączysz</h3>
<p>Trzeci człon wzoru to <code>n_kv_heads</code>. Im mniej głów KV, tym mniejszy cache — wprost,
liniowo. I dokładnie na to celuje <strong>Grouped-Query Attention</strong> (GQA, Ainslie i in.,
EMNLP 2023).<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> Idea: zamiast tylu głów KV, ile jest głów zapytań (pełna uwaga
wielogłowowa, MHA), albo tylko jednej wspólnej (multi-query attention, MQA), bierzesz
liczbę pośrednią. Kilka głów zapytań dzieli jedną głowę KV. Skoro <code>n_kv_heads</code> w naszym
wzorze spada — spada i cache, przy jakości bliskiej pełnemu MHA i szybkości bliskiej
MQA.<sup id="fnref1:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup></p>
<p>Co ciekawe, model GQA nie powstaje od zera. Bierze się istniejący checkpoint MHA
i „doucza&quot; (uptraining) przy jakichś 5% oryginalnego budżetu treningu — tanio, bez
budowania nowej architektury od podstaw.<sup id="fnref2:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup></p>
<p>Jest tylko jeden haczyk, o którym musisz pamiętać: <strong>GQA to nie przełącznik</strong>.
To cecha architektury zapieczona w modelu podczas treningu. Nie ma flagi CLI, która
„włączy GQA&quot; na dowolnym modelu. Możesz co najwyżej <em>wybrać</em> model, który już ma
GQA — a większość nowszych 7B/8B ma — i taki z natury nosi mniejszy cache przy tej
samej długości kontekstu co stary model z pełnym MHA. To kryterium przy pobieraniu,
nie pokrętło w terminalu.</p>
<h2 id="zrób-to-sam-kolejność-ratowania-pamięci">Zrób to sam: kolejność ratowania pamięci</h2>
<p>No dobra, dość teorii — masz <code>out of memory</code> i chcesz uratować kontekst. Kolejność
ma znaczenie, bo pierwsze ruchy kosztują cię grosze na jakości, a ostatnie bolą.</p>
<ol>
<li><strong>Najpierw <code>-fa on</code>.</strong> Ścina narzut pamięci samej operacji uwagi i — co równie
ważne — odblokowuje kwantyzację cache V. To krok zero, nie negocjujemy.<sup id="fnref1:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup></li>
<li><strong>Potem skwantyzuj cache: <code>-ctk q8_0 -ctv q8_0</code>.</strong> <code>q8_0</code> to bezpieczny wybór —
połowa pamięci cache, strata jakości ledwo wyczuwalna. Dopiero gdy VRAM dalej
piszczy, schodź do <code>-ctk q4_0 -ctv q4_0</code> — ćwiartka pamięci, ale za to już płacisz
odczuwalniej jakością.<sup id="fnref1:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></li>
<li><strong>Wybierając model, celuj w te z GQA.</strong> Tego nie zrobisz flagą po fakcie — to
decyzja przy pobieraniu. Model z GQA startuje z mniejszym cache, zanim jeszcze
dotkniesz jakiegokolwiek przełącznika.<sup id="fnref3:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup></li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># krok 1 + 2 razem — tak wygląda ratunek w praktyce</span>
</span></span><span class="line"><span class="cl">llama-server -m model-q4_k_m.gguf -c <span class="m">16384</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -fa on -ctk q8_0 -ctv q8_0
</span></span></code></pre></div><p>Dopiero kiedy to wszystko wyczerpiesz, a i tak nie wchodzi — wtedy tnij samą długość
kontekstu (<code>-c</code>) albo schodź z warstw na GPU (<code>-ngl</code>). Ale to już ostatnia deska
ratunku, bo pierwsze odbiera modelowi pamięć, a drugie prędkość. Sam zestaw flag i to, co która
robi, przeszliśmy po kolei w <a href="/poradniki/flagi-llama-cpp/">osobnym wpisie o flagach llama.cpp</a>.</p>
<p>Pomyśl o tym tak. Wagi modelu to biblioteka — stoi w regale, zajmuje swoje półki
i tyle, nieważne, ile razy do niej zajrzysz. KV-cache to biurko, na którym rozkładasz
wszystko, co akurat czytasz. Im dłużej pracujesz nad jednym tematem, tym więcej kartek
ląduje na blacie — i w pewnym momencie nie ma gdzie postawić kubka, choć regał ani
drgnął. Kwantyzacja cache to składanie tych kartek na pół, flash attention to
sprzątanie w locie, GQA to od razu mniejszy stos notatek na tę samą robotę.</p>
<p>Karta się nie skurczyła. To po prostu twoja rozmowa urosła — a teraz już wiesz,
który człon wzoru za to odpowiada i za które pokrętło pociągnąć, zanim znów zobaczysz
to <code>out of memory</code>.</p>
<h2 id="przypisy--źródła">Przypisy · źródła</h2>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Mechanizm KV-cache: bufor par klucz-wartość liczonych raz na token i warstwę, o rozmiarze <code>2 × n_layer × n_kv_heads × head_dim × długość_kontekstu × bajty_na_element</code> — rośnie liniowo z kontekstem, niezależnie od rozmiaru wag; kwadratowy jest jedynie koszt obliczeniowy naiwnej uwagi. Wyprowadzenie mechanizmu spójne z definicją flash attention w FlashAttention (Dao i in.), <a href="https://arxiv.org/abs/2205.14135">arXiv:2205.14135</a>.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref3:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref4:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref5:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>llama.cpp, definicje flag <code>-ctk</code>/<code>--cache-type-k</code>, <code>-ctv</code>/<code>--cache-type-v</code> (dozwolone typy: <code>f32</code>, <code>f16</code>, <code>bf16</code>, <code>q8_0</code>, <code>q4_0</code>, <code>q4_1</code>, <code>iq4_nl</code>, <code>q5_0</code>, <code>q5_1</code>; domyślnie <code>f16</code>) oraz <code>-fa</code>/<code>--flash-attn</code> (<code>on</code>/<code>off</code>/<code>auto</code>, domyślnie <code>auto</code>), <a href="https://github.com/ggml-org/llama.cpp/blob/master/common/arg.cpp">common/arg.cpp</a>. Te same parametry w <a href="https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md">dokumentacji serwera</a>.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><code>q8_0</code> = 1 bajt na element (ok. 2× mniej niż <code>f16</code>), <code>q4_0</code> = pół bajta (ok. 4× mniej niż <code>f16</code>); dokładne MB zależą od <code>n_layer</code>, <code>n_kv_heads</code>, <code>head_dim</code> i długości kontekstu konkretnego modelu. Typy zdefiniowane w llama.cpp, <a href="https://github.com/ggml-org/llama.cpp/blob/master/common/arg.cpp">common/arg.cpp</a>.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness (Dao, Fu, Ermon, Rudra, Ré, NeurIPS 2022) — dokładny, IO-aware algorytm uwagi redukujący odczyty/zapisy między HBM a SRAM GPU; nie jest aproksymacją uwagi, <a href="https://arxiv.org/abs/2205.14135">arXiv:2205.14135</a>.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Zależność wymuszona w kodzie llama.cpp: przy wyłączonym flash attention skwantyzowany cache V rzuca runtime error <code>quantized V cache was requested, but this requires Flash Attention</code> — sprawdzenie w konstruktorze kontekstu, <a href="https://github.com/ggml-org/llama.cpp/blob/master/src/llama-context.cpp">src/llama-context.cpp</a>.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints (Ainslie i in., EMNLP 2023) — grouped-query attention jako interpolacja między MHA a MQA, redukcja liczby głów KV, uptraining z ok. 5% oryginalnego budżetu treningu, <a href="https://arxiv.org/abs/2305.13245">arXiv:2305.13245</a>.&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref3:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>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>Ile VRAM-u potrzebuje model? Liczymy na palcach</title><link>https://inferownia.pl/poradniki/ile-vram-na-model/</link><pubDate>Sun, 12 Jul 2026 00:00:00 +0200</pubDate><category>inferencja</category><guid>https://inferownia.pl/poradniki/ile-vram-na-model/</guid><description>Nowy model, ładna karta modelu, a na dole jedno zdanie: 16 GB VRAM. Ty masz te swoje 8. Zamiast wróżyć z fusów, policzmy na palcach, ile naprawdę zważy — i jak zejść niżej.</description><content:encoded><![CDATA[<p><code>Wymagania: 16 GB VRAM</code>. Zjeżdżasz na dół karty modelu, widzisz to jedno zdanie, zerkasz na
swoją kartę do gier z jej ośmioma gigabajtami — z czego połowę i tak zżera przeglądarka — i
serce ci lekko siada. A bywa gorzej: czasem nawet tej jednej liczby nie ma, jest za to tabelka
z dwudziestoma wariantami pliku, każdy waży inaczej, i dalej nie wiesz, czy w ogóle podchodzić,
czy zaraz przywita cię „CUDA out of memory&quot;, cała na biało. To codzienność — połowa pytań „will it fit?&quot; to ludzie, którzy kupili
GPU do gier, a teraz próbują wcisnąć na nie model wielkości małego miasta.</p>
<p>Dobra wiadomość: nie musisz zgadywać. VRAM da się policzyć na palcach, z grubsza, w głowie,
zanim cokolwiek ściągniesz. Nie do grama — do grama policzy ci <code>llama.cpp</code> przy starcie —
ale na tyle dokładnie, żeby wiedzieć, czy w ogóle podchodzić. O to cała sztuka:
zamiast wróżyć z rozmiaru pliku, umieć powiedzieć „7B w Q4 to jakieś pięć giga plus
zapas, wejdzie&quot; — i mieć rację.</p>
<h2 id="wzór-na-palcach">Wzór na palcach</h2>
<p>Cały fundament mieści się w jednej linijce:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">rozmiar wag (GB) ≈ liczba parametrów (mld) × bity na wagę / 8
</span></span></code></pre></div><p>I tyle. Bierzesz, ile model ma miliardów parametrów, mnożysz przez to, ile bitów zajmuje
jedna waga w danym formacie, dzielisz przez osiem (bo bajt ma osiem bitów) i masz gramaturę
samych wag w gigabajtach. Model 8B w kwantyzacji Q4, gdzie na wagę schodzi jakieś 4,5 bita?
<code>8 × 4,5 / 8 ≈ 4,5 GB</code>. I to nie jest teoretyczna wróżba — realny plik <code>Q4_K_M</code> dla
Llamy 3.1 8B waży 4,92 GB.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Zgadza się co do joty, a właściwie co do paru setek megabajtów.</p>
<div class="callout">
  <span class="callout__label">Dlaczego to ważne dla lokalnego LLM</span>
<p>To „bity na wagę&quot; (po angielsku <em>bits-per-weight</em>, bpw) to sedno całej zabawy z kwantyzacją.
Surowy model trenuje się zwykle w 16 bitach na wagę. Kwantyzacja to nic innego jak zapisanie
tych samych wag ciaśniej — w 8, 5, 4, a nawet 2 bitach — już po treningu, bez ruszania
architektury. Nie myl tego z trenowaniem od zera w niskiej precyzji; to dwie różne bajki.
Jak działają nazwy w stylu <code>Q4_K_M</code> i skąd te dziwne litery, rozgryzaliśmy osobno
<a href="/poradniki/nazwy-kwantyzacji-gguf/">tutaj</a>.</p>
</div>
<h2 id="skąd-biorą-się-ułamkowe-bity">Skąd biorą się ułamkowe bity</h2>
<p>Tu pierwsza pułapka, na którą łapie się każdy początkujący: „Q4 to przecież cztery bity,
nie?&quot;. No właśnie nie do końca. Gdyby świat był prosty, <code>Q4_0</code> (stary, prościutki typ)
brałby równe 4,5 bita na wagę — bo pakuje 32 wagi w blok i dokłada do nich trochę metadanych
o skali.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> Ale nowsze formaty, tak zwane K-quanty (<code>Q2_K</code> do <code>Q6_K</code>, z dopiskami <code>_S</code>,
<code>_M</code>, <code>_L</code>), są sprytniejsze: mieszają precyzję między różnymi warstwami modelu. Te tensory,
od których jakość naprawdę zależy, dostają więcej bitów, reszcie się skąpi. Efekt? Liczba
bitów na wagę wychodzi ułamkowa.</p>
<p>Konkretnie, dla Llamy 3.1 8B według oficjalnej tabeli <code>llama.cpp</code>:<sup id="fnref1:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<table>
	<thead>
			<tr>
					<th>Format</th>
					<th>Bity na wagę</th>
					<th>Rozmiar (tabela llama.cpp)</th>
					<th>Realny plik GGUF</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>Q4_K_M</code></td>
					<td>4,8944</td>
					<td>4,58 GiB</td>
					<td>4,92 GB</td>
			</tr>
			<tr>
					<td><code>Q5_K_M</code></td>
					<td>5,7036</td>
					<td>5,33 GiB</td>
					<td>5,73 GB</td>
			</tr>
			<tr>
					<td><code>Q6_K</code></td>
					<td>~6,5</td>
					<td>—</td>
					<td>6,60 GB</td>
			</tr>
			<tr>
					<td><code>Q8_0</code></td>
					<td>~8,50</td>
					<td>—</td>
					<td>8,54 GB</td>
			</tr>
			<tr>
					<td><code>F16</code></td>
					<td>16,0005</td>
					<td>14,96 GiB</td>
					<td>—</td>
			</tr>
	</tbody>
</table>
<p>Widzisz tę drobną rozbieżność między kolumnami? Jedna to GiB (liczone po 1024), druga GB
(po 1000), plus K-quant miesza precyzję tensorów — stąd <code>Q4_K_M</code> ma prawie 4,9 bita, a nie
równe 4,5.<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Do liczenia na palcach nic to nie zmienia: bierzesz „mniej więcej 4,5–5
bita dla Q4&quot; i tak trafiasz w wynik z dokładnością do zapasu, który i tak musisz zostawić.
Jest jeszcze cała rodzina IQ-quantów (<code>IQ2_XXS</code>, <code>IQ3_S</code> i spółka), które schodzą poniżej
trzech bitów, ważąc jakość specjalną macierzą istotności — ale to temat na osobny wieczór.<sup id="fnref2:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<h2 id="ile-zważy-ten-twój-model">Ile zważy „ten twój&quot; model</h2>
<p>Przełóżmy wzór na cztery rozmiary, które realnie krążą po lokalnym światku. To są same wagi,
bez narzutu — o narzucie za chwilę:</p>
<table>
	<thead>
			<tr>
					<th>Model</th>
					<th>Q4_K_M</th>
					<th>Q5_K_M</th>
					<th>Q8_0</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Llama 3.1 <strong>8B</strong><sup id="fnref2:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></td>
					<td>4,92 GB</td>
					<td>5,73 GB</td>
					<td>8,54 GB</td>
			</tr>
			<tr>
					<td>Mistral Nemo <strong>12B</strong><sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></td>
					<td>7,48 GB</td>
					<td>8,73 GB</td>
					<td>13,02 GB</td>
			</tr>
			<tr>
					<td>Qwen2.5 <strong>14B</strong><sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup></td>
					<td>8,99 GB</td>
					<td>10,51 GB</td>
					<td>15,70 GB</td>
			</tr>
	</tbody>
</table>
<p>Popatrz na to przez chwilę, bo tu widać całą matematykę życia lokalnego LLM-owca. Masz kartę
8 GB? Ósemka w Q4 wchodzi na luzie, w Q5 już z zadyszką, a Q8 nawet nie podchodź. Dwunastka
w Q4 to 7,5 GB samych wag — teoretycznie „się mieści&quot;, tylko że na sam narzut nie zostaje już
nic. A czternastka w Q4 to prawie dziewięć giga — twoje 8 GB kończy się, zanim się zaczęło.
Dlatego „te swoje 8 GB&quot; znika tak szybko: liczysz gramaturę wag, cieszysz się, że wchodzi,
a potem uruchamiasz i okazuje się, że o czymś zapomniałeś.</p>
<h2 id="czego-zapominasz-kv-cache-i-bufory">Czego zapominasz: KV-cache i bufory</h2>
<p>O to zapominasz. Rozmiar pliku GGUF to tylko wagi. Do tego dochodzi KV-cache — pamięć, w której
model trzyma przetworzone klucze i wartości dla każdego tokena, który model już wygenerował lub
wczytał z promptu. I to jest ten cichy złodziej VRAM-u, bo rośnie <strong>liniowo z długością
kontekstu</strong>. Im dłuższa rozmowa, tym większy żłób na pamięć.</p>
<p>Ile dokładnie? Też się liczy na palcach. Na jeden token przypada:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">KV na token = 2 (klucz + wartość) × liczba_warstw × n_head_kv × head_dim × bajty_na_element
</span></span></code></pre></div><p>Dla Llamy 3.1 8B te liczby to: 32 warstwy, <code>n_head_kv</code> = 8, <code>head_dim</code> = 128, a KV-cache
domyślnie leci w <code>f16</code>, czyli 2 bajty na element<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> (co to za <code>f16</code> i czemu nie <code>bf16</code> — <a href="/poradniki/bf16-f16-f32/">tu</a>). Wrzuć to do wzoru:
<code>2 × 32 × 8 × 128 × 2 bajty = 128 KiB</code> na token. Przy kontekście 8192 tokenów robi się z tego
równiutki 1 GiB — tyle dokłada sam cache do tego, co już zjadły wagi. Rozbieramy ten mechanizm
dokładniej <a href="/poradniki/kv-cache-vram/">tutaj</a>, ale zasada do zapamiętania jest prosta: dłuższy
kontekst = więcej VRAM-u, po prostu.</p>
<p>Zwróć uwagę na <code>n_head_kv</code> = 8 — pełna liczba głów uwagi w tym modelu jest większa, ale dzięki
GQA (<em>grouped-query attention</em>, mechanizm współdzielenia głów klucza/wartości) do cache liczy się
tylko ta zredukowana ósemka. Bez GQA, na starej architekturze, ten narzut potrafił być
wielokrotnie większy. Do tego dochodzą jeszcze bufory obliczeniowe samego procesu inferencji.
Dlatego świętą zasadą jest: <strong>zostaw 1–2 GB zapasu ponad rozmiar wag</strong>. Kto liczy VRAM tylko
z gramatury pliku GGUF, ten regularnie ląduje na „out of memory&quot; przy trzecim akapicie rozmowy.</p>
<h2 id="jak-zejść-niżej-gdy-nie-wchodzi">Jak zejść niżej, gdy nie wchodzi</h2>
<p>Powiedzmy, że policzyłeś i wychodzi za dużo. Masz trzy dźwignie, w tej właśnie kolejności.</p>
<p>Raz — <strong>kwantyzacja niżej</strong>. To najprostszy ruch: zamiast Q8 bierzesz Q5, zamiast Q5 bierzesz
Q4. Czternastka w Q8 to 15,7 GB, a w Q4 już tylko 8,99 GB<sup id="fnref1:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> — prawie połowa mniej, a
jakość spada zaskakująco mało, bo K-quanty oszczędzają mądrze. Q4_K_M to nie bez powodu
domyślny wybór połowy internetu.</p>
<p>Dwa — <strong>kwantyzacja KV-cache</strong>. Skoro cache potrafi dołożyć giga przy dłuższym kontekście, to
jego też można ścisnąć. <code>llama-server</code> ma na to flagi <code>-ctk</code> / <code>--cache-type-k</code> i <code>-ctv</code> /
<code>--cache-type-v</code>, osobno dla kluczy i wartości; domyślnie oba stoją na <code>f16</code>, ale przyjmują też
<code>q8_0</code>, <code>q4_0</code> i całą listę innych typów.<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> Ściśnięcie cache z <code>f16</code> do <code>q8_0</code> to znów
zejście z jego rozmiaru mniej więcej o połowę.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">llama-server -m qwen2.5-14b-instruct-q4_k_m.gguf <span class="se">\
</span></span></span><span class="line"><span class="cl">  -ngl all -c <span class="m">8192</span> -fa on <span class="se">\
</span></span></span><span class="line"><span class="cl">  -ctk q8_0 -ctv q8_0
</span></span></code></pre></div><p>Widzisz to <code>-fa on</code>? To nie ozdoba. Flaga <code>-fa</code> / <code>--flash-attn</code> (przyjmuje <code>on</code>/<code>off</code>/<code>auto</code>,
domyślnie <code>auto</code>) włącza Flash Attention — a kwantyzacja KV-cache do typów poniżej <code>f16</code>
praktycznie <strong>wymaga</strong> włączonej Flash Attention.<sup id="fnref1:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> Ustawisz <code>-ctk q4_0</code> bez <code>-fa on</code> i
w najlepszym razie nic nie zyskasz, w najgorszym nie ruszy. Więc: najpierw Flash Attention,
potem duś cache.</p>
<p>Trzy — <strong>przytnij kontekst albo zrzuć część warstw na CPU</strong>. To ostateczność, bo krótszy
kontekst to mniejsza pamięć modelu, a offload na CPU (flaga <code>-ngl</code> z liczbą mniejszą niż
„wszystko&quot;) spowalnia generację. Ale gdy 12B w Q4 nie chce wejść na 8 GB nawet po ściśnięciu
cache — czasem to jedyny sposób, żeby w ogóle ruszyło. Cała ta chirurgia flagami to osobny
rozdział, który rozpisaliśmy <a href="/poradniki/flagi-llama-cpp/">tu</a>.</p>
<h2 id="zrób-to-sam-rachunek-w-trzy-sekundy">Zrób to sam: rachunek w trzy sekundy</h2>
<p>Następnym razem, gdy staniesz przed nowym modelem, przelicz go w głowie, zanim klikniesz
pobieranie:</p>
<ol>
<li><strong>Wagi</strong>: <code>parametry × bity / 8</code>. Ósemka w Q4 → <code>8 × 4,5 / 8 ≈ 4,5 GB</code>. Albo zerknij na
realny plik na Hugging Face i weź gotową liczbę.</li>
<li><strong>Cache</strong>: przy zwykłym kontekście (4–8k) dorzuć z grubsza 0,5–1 GB dla modelu klasy 7–14B
z GQA. Planujesz 32k albo więcej? Licz się z kilkoma gigabajtami i od razu myśl o <code>-fa on</code>
plus <code>-ctk q8_0</code>.</li>
<li><strong>Zapas</strong>: dorzuć 1–2 GB na bufory i oddech.</li>
</ol>
<p>Suma większa niż twój VRAM? Schodzisz z kwantyzacją albo duszisz cache — w tej kolejności.
Suma mniejsza? Ściągaj śmiało, wejdzie.</p>
<p>Bo cała sztuka z VRAM-em nie polega na wróżeniu, tylko na tym jednym prostym mnożeniu na
palcach — parametry razy bity, przez osiem, plus zapas. Kiedy raz je poczujesz, folder <code>models</code>
przestaje być loterią, a zaczyna być tym, czym powinien: półką, na której z góry wiesz, co się
zmieści, a co poleży, aż dokupisz kartę „na później&quot;. A dokupisz. Wszyscy dokupujemy.</p>
<h2 id="przypisy--źródła">Przypisy · źródła</h2>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Realne rozmiary plików GGUF dla Meta-Llama-3.1-8B-Instruct (<code>Q4_K_M</code> = 4,92 GB, <code>Q5_K_M</code> = 5,73 GB, <code>Q6_K</code> = 6,60 GB, <code>Q8_0</code> = 8,54 GB), <a href="https://huggingface.co/bartowski/Meta-Llama-3.1-8B-Instruct-GGUF">bartowski/Meta-Llama-3.1-8B-Instruct-GGUF</a>.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>llama.cpp, tabela typów kwantyzacji z wartościami bits-per-weight i rozmiarami dla Llama 3.1 8B (<code>Q4_K_M</code> = 4,8944 bpw / 4,58 GiB, <code>Q5_K_M</code> = 5,7036 bpw / 5,33 GiB, <code>F16</code> = 16,0005 bpw / 14,96 GiB; <code>Q4_0</code> = 4,5 bpw), <a href="https://github.com/ggml-org/llama.cpp/blob/master/tools/quantize/README.md">tools/quantize/README.md</a>.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Realne rozmiary plików GGUF dla Mistral-Nemo-Instruct-2407 (12B): <code>Q4_K_M</code> = 7,48 GB, <code>Q5_K_M</code> = 8,73 GB, <code>Q8_0</code> = 13,02 GB, <a href="https://huggingface.co/bartowski/Mistral-Nemo-Instruct-2407-GGUF">bartowski/Mistral-Nemo-Instruct-2407-GGUF</a>.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Realne rozmiary plików GGUF dla Qwen2.5-14B-Instruct: <code>Q4_K_M</code> = 8,99 GB, <code>Q5_K_M</code> = 10,51 GB, <code>Q8_0</code> = 15,70 GB, <a href="https://huggingface.co/bartowski/Qwen2.5-14B-Instruct-GGUF">bartowski/Qwen2.5-14B-Instruct-GGUF</a>.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>llama.cpp, parametry KV-cache dla Llama 3 8B (<code>n_layer</code> = 32, <code>n_head_kv</code> = 8, <code>head_dim</code> = 128, <code>n_embd_k_gqa</code>/<code>v_gqa</code> = 1024) i potwierdzenie domyślnego typu cache <code>f16</code>, <a href="https://github.com/ggml-org/llama.cpp/discussions/7949">GitHub Discussion #7949 — KV Cache Dimension</a>.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>llama.cpp, opis flag <code>llama-server</code>: <code>-ctk</code>/<code>--cache-type-k</code>, <code>-ctv</code>/<code>--cache-type-v</code> (domyślnie <code>f16</code>, dozwolone m.in. <code>q8_0</code>, <code>q4_0</code>) oraz <code>-fa</code>/<code>--flash-attn</code> (<code>on</code>/<code>off</code>/<code>auto</code>, domyślnie <code>auto</code>); kwantyzacja KV-cache poniżej <code>f16</code> wymaga Flash Attention, <a href="https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md">tools/server/README.md</a>.&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>FlashAttention: uwaga, która nie zjada pamięci</title><link>https://inferownia.pl/naukowy/flashattention/</link><pubDate>Sat, 11 Jul 2026 00:00:00 +0200</pubDate><author>Tri Dao</author><author>Daniel Y. Fu</author><author>Stefano Ermon</author><author>Atri Rudra</author><author>Christopher Ré</author><category>inferencja</category><guid>https://inferownia.pl/naukowy/flashattention/</guid><description>Wrzucasz jedną flagę, -fa, i model nagle mieści dłuższy kontekst na tej samej karcie. Za tym skrótem stoi praca, która nie przybliża uwagi ani jej nie tnie — tylko przestawia kolejność liczenia tak, żeby wielka macierz N×N nigdy nie musiała wylądować w wolnej pamięci.</description><content:encoded><![CDATA[<p>Znasz to <code>-fa on</code>, które wklepujesz przy odpalaniu <code>llama.cpp</code> prawie z automatu, jak
człowiek zapinający pas w aucie — nie zastanawiasz się, po prostu wiesz, że tak trzeba?
To jeden z tych patentów, który powtarza się jak refren:
„nie wchodzi na kartę? dorzuć flash attention&quot;. Wrzucasz, kontekst się rozciąga, VRAM-u
ubywa wolniej, i jedziesz dalej, nawet nie pytając, co ta flaga właściwie robi pod maską.</p>
<p>A pod maską siedzi jedna z ładniejszych prac ostatnich lat — <em>FlashAttention</em>, Tri Dao
i spółka ze Stanfordu, maj 2022.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Ładna nie dlatego, że wymyśliła nowy rodzaj uwagi.
Wręcz przeciwnie — wynik matematyczny wychodzi <strong>co do liczby taki sam</strong> jak w zwykłej
uwadze. Cały myk polega na czymś, co brzmi jak herezja dla kogoś, kto liczył złożoność
na studiach: żeby przyspieszyć obliczenia, zamiast liczyć mniej, autorzy kazali karcie
<strong>mniej biegać po pamięci</strong>. I to załatwiło sprawę.</p>
<h2 id="skąd-w-ogóle-ten-głód-pamięci">Skąd w ogóle ten głód pamięci</h2>
<p>Najpierw ustalmy, czego uwaga potrzebuje, bo tu leży cały problem. Uwaga (ta z „Attention
Is All You Need&quot;, z 2017 — inna praca, inni ludzie, nie mylić) dla sekwencji o długości <code>N</code>
tokenów porównuje każdy token z każdym. Każdy z każdym to macierz <code>N × N</code>. Masz 4 tysiące
tokenów kontekstu? To 16 milionów liczb w jednej tablicy. Osiem tysięcy? Już 64 miliony.
Pamięć rośnie z kwadratem długości — podwajasz kontekst, płacisz czterokrotnie.<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>I teraz najważniejsze, co ludzie przeoczają: nie chodzi o to, że tych mnożeń jest dużo.
GPU uwielbia mnożyć, od tego jest. Problem w tym, <strong>gdzie</strong> ta wielka macierz ląduje.
Karta ma dwa rodzaje pamięci. Jest HBM — ta duża, te twoje 8, 12 czy 24 giga, wolna jak
poranny ruch na obwodnicy. I jest SRAM — malutka pamięć tuż przy rdzeniach obliczeniowych,
błyskawiczna, ale mieści tyle co nic. Zwykła uwaga liczy całą macierz <code>N × N</code>, zapisuje ją
w HBM, potem odczytuje z powrotem, żeby policzyć softmax, znowu zapisuje, znowu czyta…
i to bieganie tam i z powrotem — nie samo mnożenie — jest tym, co zżera czas i pamięć.</p>
<h2 id="io-aware-czyli-licz-tam-gdzie-blisko">„IO-aware&quot;, czyli licz tam, gdzie blisko</h2>
<p>Tu wchodzi pomysł, od którego cała praca dostała podtytuł: <em>IO-awareness</em>, świadomość
operacji wejścia-wyjścia.<sup id="fnref2:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Brzmi korporacyjnie, a znaczy rzecz prostą jak drut:
przestań optymalizować liczbę mnożeń, zacznij optymalizować liczbę <strong>podróży do wolnej
pamięci</strong>. Bo to one są wąskim gardłem.</p>
<p>Jak to zrobić, skoro macierz <code>N × N</code> z definicji jest ogromna i nie zmieści się w tej
malutkiej szybkiej SRAM? No właśnie — nie zmieści się <strong>w całości</strong>. Ale w kawałkach już
tak. I to jest sedno.</p>
<ul>
<li><strong>Tiling (kafelkowanie).</strong> Zamiast liczyć naraz całą macierz, tniesz <code>Q</code>, <code>K</code> i <code>V</code>
na bloki i przerabiasz je kafelek po kafelku. Każdy kafelek jest na tyle mały, że wjeżdża
do SRAM, tam się go przemiela, wypluwa wynik i bierze następny. Wielka macierz <code>N × N</code>
jako całość <strong>nigdy nie powstaje</strong> w wolnej pamięci — istnieje tylko przelotnie, we
fragmentach, w tej szybkiej pamięci przy rdzeniach.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></li>
<li><strong>Online softmax.</strong> Tu jest haczyk, bo softmax klasycznie potrzebuje całego wiersza
naraz — musi znormalizować po wszystkich elementach. FlashAttention liczy go „w locie&quot;,
z bieżącą normalizacją: przerabia kolejne bloki i na bieżąco koryguje wynik, tak jakby
sumował rachunek pozycja po pozycji, poprawiając napiwek za każdym razem, gdy dojdzie
nowa pozycja. Efekt końcowy identyczny, a pełnego wiersza nigdy nie trzeba trzymać.<sup id="fnref1:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></li>
<li><strong>Rekomputacja.</strong> Przy uczeniu (backward pass) normalnie trzymasz w pamięci wszystkie
pośrednie wyniki, żeby policzyć gradienty. FlashAttention mówi: po co je trzymać, skoro
taniej je <strong>policzyć jeszcze raz</strong> z tego, co już mamy w szybkiej pamięci? Dokłada trochę
mnożenia, żeby oszczędzić na pamięci i na tym przeklętym bieganiu do HBM. I na tym
akurat wychodzi na plus.<sup id="fnref3:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></li>
</ul>
<p>Zbierz to razem i dostajesz uwagę, w której zużycie pamięci rośnie <strong>liniowo</strong> z długością
sekwencji, <code>O(N)</code>, zamiast kwadratowo, <code>O(N²)</code>.<sup id="fnref4:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Ta sama matematyka, ta sama odpowiedź
co do ostatniej cyfry — tylko poukładana tak sprytnie, że wielka tablica pośrednia nigdy
nie musi się zmaterializować tam, gdzie jest wolno.</p>
<div class="callout">
  <span class="callout__label">Dlaczego to ważne dla lokalnego LLM</span>
<p>Dla ciebie, odpalającego model w domu, sedno jest takie: FlashAttention <strong>nie jest</strong>
przybliżeniem. To nie sparse attention, nie low-rank, nie żadna sztuczka, która coś obcina
i modli się, żeby jakość nie spadła. Dostajesz dokładnie ten sam wynik co przy zwykłej
uwadze — po prostu policzony taniej i mniejszym kosztem VRAM-u. To rzadki przypadek obiadu,
za który naprawdę nikt nie płaci. I dlatego w nowszych buildach <code>llama.cpp</code> z backendem CUDA
ta flaga po cichu bywa włączona domyślnie.</p>
</div>
<h2 id="czy-to-na-pewno-działało-liczby-z-pracy">Czy to na pewno działało? Liczby z pracy</h2>
<p>Bo można sobie opowiadać o eleganckim algorytmie do rana, ale papier bez liczb to wróżenie
z fusów. Autorzy zmierzyli i pokazali czarno na białym.<sup id="fnref5:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<table>
	<thead>
			<tr>
					<th>Model / zadanie</th>
					<th>Długość sekwencji</th>
					<th>Efekt</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>BERT-large</td>
					<td>512</td>
					<td>15% szybciej end-to-end</td>
			</tr>
			<tr>
					<td>GPT-2</td>
					<td>1K</td>
					<td>3× szybciej</td>
			</tr>
			<tr>
					<td>Long-Range Arena</td>
					<td>1K–4K</td>
					<td>2,4× szybciej</td>
			</tr>
	</tbody>
</table>
<p>I najciekawsze na koniec — nie samo „szybciej&quot;, ale „w ogóle się dało&quot;. Skoro pamięć
przestała rosnąć kwadratowo, można było pchać modele na kontekst, na którym wcześniejsze
Transformery po prostu się poddawały. Na Path-X (sekwencja 16 tysięcy) FlashAttention
wyciągnął 61,4% dokładności, a na Path-256 (64 tysiące!) — 63,1%.<sup id="fnref6:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> To zadania, na
których wcześniej model radził sobie <strong>nie lepiej niż rzut monetą</strong>. Dłuższy kontekst,
który dziś traktujesz jak coś oczywistego, częściowo wziął się właśnie stąd.</p>
<h2 id="zrób-to-sam--i-nie-pomyl-tego-z-kv-cache">Zrób to sam — i nie pomyl tego z KV-cache</h2>
<p>Nie musisz nic implementować, cały ten mechanizm masz w jednej fladze. W <code>llama.cpp</code>
to <code>-fa</code>, <code>--flash-attn</code>, przyjmuje <code>on</code>/<code>off</code>/<code>auto</code>, domyślnie stoi na <code>auto</code>.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">llama-cli -m qwen2.5-7b-instruct-q4_k_m.gguf -ngl all -c <span class="m">8192</span> -fa on -cnv
</span></span></code></pre></div><p>Jedna rzecz, którą warto tu dorzucić, bo ludzie się na niej wykładają: w <code>llama.cpp</code>
flash attention jest <strong>twardym wymogiem</strong> dla kwantyzacji KV-cache. Chcesz ścisnąć cache
do <code>q8_0</code>, żeby odzyskać VRAM przy długim kontekście? Bez <code>-fa on</code> się nie da — kwantyzacja
V-cache wymaga flash attention wprost, kod ucina to jednym komunikatem błędu.<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>
Więc te dwie sztuczki chodzą w parze:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">llama-cli -m qwen2.5-7b-instruct-q4_k_m.gguf <span class="se">\
</span></span></span><span class="line"><span class="cl">  -ngl all -c <span class="m">8192</span> -fa on -ctk q8_0 -ctv q8_0 -cnv
</span></span></code></pre></div><p>I tu ostrzeżenie, żebyś nie wpadł w częstą pułapkę pojęciową. FlashAttention <strong>to nie
jest</strong> KV-cache — to dwie różne rzeczy, które akurat obie oszczędzają pamięć. KV-cache
dotyczy tego, że model <strong>przechowuje</strong> przeliczony kontekst między kolejnymi tokenami
generacji (rozgryzaliśmy to bliżej <a href="/poradniki/kv-cache-vram/">tutaj</a>). FlashAttention
dotyczy tego, <strong>jak</strong> liczysz uwagę w jednym przejściu — kolejności obliczeń, nie
przechowywania stanu. Oszczędność bierze się wyłącznie z tilingu i rekomputacji, a nie
z jakiejkolwiek utraty precyzji — nikt tu niczego nie kwantyzuje ani nie zaokrągla.</p>
<p>Dwa zastrzeżenia od siebie, żeby cię to potem nie zaskoczyło. Raz — wsparcie nie jest
uniwersalne: na backendzie CUDA jest jak znalazł, ale na Vulkanie czy SYCL dla części kart
flaga bywała kapryśna, więc nie traktuj jej jak działającej wszędzie i zawsze (więcej o tym,
czym różnią się <a href="/aktualnosci/backendy-llama-cpp/">backendy</a>). Dwa — jak zwykle w
<code>llama.cpp</code>, resztę flag, które chodzą obok tej, <a href="/poradniki/flagi-llama-cpp/">ogarnialiśmy osobno</a>.</p>
<p>Historia nie skończyła się w 2022. Rok później przyszedł <em>FlashAttention-2</em> — już samego
Tri Dao — który poprzestawiał podział pracy między wątkami GPU i wycisnął jeszcze z grubsza
dwukrotne przyspieszenie względem jedynki, dobijając do 50–73% teoretycznego maksimum
kart A100.<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> Kod jednego i drugiego (plus wersji trzeciej) siedzi w oficjalnym repo
<code>Dao-AILab/flash-attention</code>, na licencji BSD-3.<sup id="fnref2:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<p>Ale najfajniejsze w tej całej historii jest to, jak przewrotnie brzmi jej morał. Przez lata
uczono nas, że żeby program był szybszy, trzeba go zmusić do liczenia mniej. A tu przyszli
ludzie, którzy kazali karcie <strong>policzyć część rzeczy dwa razy</strong> — i wyszło szybciej. Bo
okazało się, że najdroższa rzecz na GPU to nie mnożenie liczb, tylko chodzenie po nie na
drugi koniec pamięci. Trochę jak z tym kolegą, który zamiast nosić zakupy po jednej siatce
z auta pod blok, woli obładować się wszystkim naraz i przejść ten dystans <strong>raz</strong>. Nie jest
leniwy. Po prostu wie, że najgorszy jest sam spacer tam i z powrotem.</p>
<h2 id="przypisy--źródła">Przypisy · źródła</h2>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Tri Dao, Daniel Y. Fu, Stefano Ermon, Atri Rudra, Christopher Ré, <em>FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness</em>, arXiv:2205.14135 (v1 z 27.05.2022) — złożoność <code>O(N)</code> zamiast <code>O(N²)</code>, tiling, rekomputacja, dokładna (nie przybliżona) uwaga, wyniki 15% / 3× / 2,4× oraz Path-X 61,4% i Path-256 63,1%: <a href="https://arxiv.org/abs/2205.14135">arxiv.org/abs/2205.14135</a>.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref3:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref4:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref5:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref6:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Oficjalna implementacja referencyjna, licencja BSD-3-Clause — tiling, online softmax, brak materializacji macierzy <code>N × N</code>, kod FlashAttention 1/2/3: <a href="https://github.com/Dao-AILab/flash-attention">github.com/Dao-AILab/flash-attention</a>.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>llama.cpp (ggml-org), opis flagi <code>-fa, --flash-attn [on|off|auto]</code> (default: auto): <a href="https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md">tools/server/README.md</a>.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>llama.cpp (ggml-org), twardy wymóg flash attention dla kwantyzacji V-cache — komunikat błędu „V cache quantization requires flash_attn&quot;: <a href="https://github.com/ggml-org/llama.cpp/blob/master/src/llama-context.cpp#L3564">src/llama-context.cpp#L3564</a>.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Tri Dao, <em>FlashAttention-2: Faster Attention with Better Parallelism and Work Partitioning</em>, arXiv:2307.08691 (17.07.2023) — osobna praca, ok. 2× przyspieszenie względem FlashAttention-1 i 50–73% teoretycznego FLOPs/s na A100: <a href="https://arxiv.org/abs/2307.08691">arxiv.org/abs/2307.08691</a>.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>RAG lokalnie, bez chmury: jak to się w ogóle spina</title><link>https://inferownia.pl/poradniki/rag-lokalnie/</link><pubDate>Fri, 10 Jul 2026 00:00:00 +0200</pubDate><category>RAG</category><guid>https://inferownia.pl/poradniki/rag-lokalnie/</guid><description>Chcesz, żeby lokalny model gadał o twoich notatkach, a nie o Wikipedii sprzed treningu. Rozbieramy RAG na części: chunking, embeddingi, baza wektorowa, retrieval — i tłumaczymy, czego on NIE załatwia.</description><content:encoded><![CDATA[<p>Pewien człowiek miał na dysku trzysta PDF-ów — faktury, notatki ze spotkań, instrukcje do
sprzętu, o którym już zdążył zapomnieć, że go w ogóle kupił — więc zrobił rzecz z pozoru
oczywistą: zapytał lokalny model „ej, ile płaciłem za tę kartę w zeszłym roku?&quot;. Chciał tylko
odpowiedzi, lokalnie, bez wysyłania własnego życia do cudzej chmury. Model odpowiedział z pełną
powagą — podał kwotę, sklep i datę, wszystkie trzy wyssane z palca. Bo skąd niby miałby to
wiedzieć? Twoich faktur nie było w danych treningowych. Model zna świat do dnia, w którym
zamknięto mu trening, i ani tokena dalej.</p>
<p>I tu pojawia się pytanie, które w dyskusjach o lokalnych modelach wraca co tydzień jak bumerang: „to jak
zrobić, żeby model gadał o MOICH danych?&quot;. Pierwszy odruch połowy internetu to „dotrenuj
go&quot;. Zły odruch — o tym za chwilę. Odruch drugi, ten dobry, to trzy litery: <strong>RAG</strong>.</p>
<h2 id="co-to-właściwie-jest-ten-rag">Co to właściwie jest ten RAG</h2>
<p>RAG — <em>retrieval-augmented generation</em>, czyli generowanie wspomagane wyszukiwaniem — to
pomysł starszy, niż się wydaje. Opisali go Lewis i spółka w pracy z 2020 roku, jeszcze
zanim „chatbot&quot; wszedł pod strzechy.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Idea jest sprytnie prosta: zamiast upychać całą
wiedzę świata w wagach modelu (to autorzy nazywają <em>pamięcią parametryczną</em> — wszystko, co
model „umie z głowy&quot;), doklejasz mu drugą pamięć — <em>nieparametryczną</em>. U Lewisa był to gęsty
indeks wektorowy całej Wikipedii, przeszukiwany przez osobny model-wyszukiwarkę. Model
najpierw <em>pobiera</em> pasujące fragmenty, a dopiero potem generuje odpowiedź, patrząc na to,
co znalazł.</p>
<p>Przełóż to na twój folder z PDF-ami. Nie douczasz modelu, że kartę kupiłeś za tyle a tyle.
Ty w momencie zadawania pytania podsuwasz mu odpowiedni kawałek faktury i mówisz: „odpowiedz
na podstawie tego&quot;. Model dostaje ściągę tuż przed odpowiedzią. Cała sztuka w tym, żeby
z trzystu PDF-ów wyłuskać akurat ten jeden akapit, który pasuje — i zrobić to w ułamku sekundy.</p>
<h2 id="jak-to-się-spina-krok-po-kroku">Jak to się spina, krok po kroku</h2>
<p>No dobra, „wyłuskać pasujący akapit&quot; brzmi ładnie, ale jak maszyna ma wiedzieć, co pasuje
do „ile płaciłem za kartę&quot;? Rozłóżmy łańcuch na części, bo on ma dokładnie cztery ogniwa
i każde robi jedną robotę.</p>
<p><strong>Raz — chunking.</strong> Dzielisz dokumenty na kawałki (chunki). Nie wrzucasz całego 40-stronicowego
PDF-a jako jednej bryły, tylko tniesz na fragmenty po parę zdań. I tu pierwsza pułapka,
w którą wpada każdy na starcie: cięcie na sztywno co X znaków. Utniesz w połowie zdania,
w połowie tabelki z cenami — i fragment traci sens, a razem z nim traci sens jego reprezentacja.
Podział semantyczny (po akapitach, sekcjach, zdaniach) jest nudniejszy w implementacji, ale
retrieval potem dziękuje.</p>
<p><strong>Dwa — embeddingi.</strong> Każdy chunk przepuszczasz przez <em>model embeddingowy</em> — osobny, mały
model, który zamienia tekst w wektor liczb, czyli <em>osadzenie</em> (embedding). Sęk w tym, że
te wektory układają się w przestrzeni tak, że teksty o podobnym znaczeniu lądują blisko
siebie. „Zapłaciłem 1400 zł za RTX-a&quot; i „koszt karty graficznej wyniósł&quot; wylądują obok,
mimo że nie mają wspólnego słowa. To jest serce całej zabawy.</p>
<p>Dwa modele, które na lokalnej scenie widać najczęściej:</p>
<table>
	<thead>
			<tr>
					<th>Model</th>
					<th>Wymiar wektora</th>
					<th>Kontekst</th>
					<th>Języki</th>
					<th>Licencja</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>nomic-embed-text-v1.5</code></td>
					<td>768 (skalowalny w dół)</td>
					<td>8192 tokenów</td>
					<td>głównie angielski</td>
					<td>Apache 2.0</td>
			</tr>
			<tr>
					<td><code>bge-m3</code></td>
					<td>1024</td>
					<td>8192 tokenów</td>
					<td>100+</td>
					<td>MIT</td>
			</tr>
	</tbody>
</table>
<p><code>nomic-embed-text-v1.5</code> ma jedną fajną sztuczkę — Matryoshka Representation Learning:
natywnie robi wektor 768-wymiarowy, ale możesz go przyciąć w dół, choćby do 64 wymiarów,
tracąc na jakości zaskakująco mało.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> Mniejszy wektor to mniejsza baza i szybsze
wyszukiwanie. <code>bge-m3</code> gra w inną grę — ogarnia ponad sto języków (czyli i polski),
a przy okazji jeden model daje ci trzy tryby wyszukiwania naraz: gęsty, leksykalny i
wielowektorowy w stylu ColBERT.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> Do tego drugiego zaraz wrócimy.</p>
<div class="callout">
  <span class="callout__label">Dlaczego to ważne dla lokalnego LLM</span>
<p>Najczęstszy babol na starcie z nomikiem: model wymaga <strong>prefiksów instrukcji</strong>. Dokument,
który indeksujesz do bazy, poprzedzasz <code>search_document: </code>, a zapytanie użytkownika —
<code>search_query: </code>. To nie kosmetyka. Wrzucisz zapytanie z prefiksem dokumentu (albo w ogóle
bez prefiksu) i trafność retrievalu leci na łeb, bo model liczy embedding „nie w tym trybie&quot;,
w którym powinien.<sup id="fnref1:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> Embedding dokumentu i embedding pytania to u nomika DWIE różne
operacje, nie jedna.</p>
</div>
<p><strong>Trzy — baza wektorowa.</strong> Te wszystkie wektory trzeba gdzieś trzymać i umieć szybko
przeszukać. Tu masz dwa światy. Z jednej strony <strong>FAISS</strong> od Meta — biblioteka do
wyszukiwania podobieństwa w gęstych wektorach, z całym menu typów indeksu: <code>IndexFlatL2</code>
(dokładne przeszukanie wszystkiego), IVF (partycjonowanie na kubełki), HNSW (grafy nawigacyjne)
czy Product Quantization (stratna kompresja wektorów).<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> Szybka i potężna, ale sama
z siebie nie trzyma metadanych ani nie martwi się trwałością — to silnik, nie baza. Z drugiej
strony <strong>Chroma</strong> — pełna baza wektorowa open source, która obok embeddingów przechowuje
metadane, filtruje po nich, wspiera wyszukiwanie gęste, rzadkie i hybrydowe, i którą
odpalisz lokalnie jednym importem.<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> Mylenie tych dwóch to klasyk: FAISS da ci
surową prędkość, Chroma da ci wygodę i „ten dokument jest z folderu faktury, rok 2025&quot;.</p>
<p><strong>Cztery — retrieval i wstrzyknięcie kontekstu.</strong> Użytkownik pyta. Ty liczysz embedding
pytania (z prefiksem <code>search_query: </code>, pamiętasz?), baza zwraca ci <em>top-k</em> najbliższych
chunków — powiedzmy trzy albo pięć najbardziej pasujących — a ty wklejasz je do promptu tuż
przed pytaniem. To jest to całe <em>context injection</em>: „Oto fragmenty z dokumentów: [&hellip;].
Na ich podstawie odpowiedz: ile płaciłem za kartę?&quot;. Model generuje odpowiedź warunkowaną
na tym, co mu podsunąłeś. Koniec łańcucha.</p>
<p>I tu druga pułapka, na którą łapie się nawet ogarnięty ludek: „skoro więcej kontekstu =
lepiej, to wezmę top-k równe 20&quot;. Nie. Nadmiar fragmentów rozmywa uwagę modelu, zapycha
okno kontekstu i topi ten jeden trafny akapit w morzu luźno pasujących. Mniej, ale celniej.
Stąd zresztą pomysł na re-ranking — najpierw baza wyławia dwadzieścia kandydatów, a osobny
model-sędzia przestawia je i oddaje ci trzy najlepsze. Twórcy <code>bge-m3</code> wprost zalecają
łączyć jego tryby hybrydowo i dokładać re-ranker, jak zależy ci na maksymalnej jakości.<sup id="fnref1:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></p>
<h2 id="czego-rag-nie-robi-i-dlaczego-to-ważne">Czego RAG NIE robi (i dlaczego to ważne)</h2>
<p>Wróćmy do tego „złego odruchu&quot; z początku. RAG <strong>nie douczy</strong> modelu. Nie dotyka jego wag,
nie zaszywa wiedzy na stałe. Podaje kontekst w czasie inferencji — i tyle. Wywal dokument
z bazy wektorowej, a model „zapomina&quot; go w tej samej sekundzie, bo nigdy naprawdę go nie
„zapamiętał&quot;. To zupełnie inna bajka niż fine-tuning, gdzie modyfikujesz parametry i żeby
zaktualizować wiedzę, musisz przepuścić model przez kolejny trening — techniki pokroju QLoRA
rozbieraliśmy <a href="/naukowy/qlora/">tutaj</a>. Prosta zasada na później: <strong>wiedza, która się
zmienia albo jest prywatna → RAG. Zachowanie, styl, format odpowiedzi → fine-tuning.</strong></p>
<p>I jeszcze jedno, żebyś nie wyszedł z fałszywym poczuciem bezpieczeństwa: RAG <strong>nie gwarantuje</strong>
braku halucynacji. Podsuniesz modelowi trafny akapit, a on i tak potrafi go zignorować albo
przekręcić. Redukujesz ryzyko, nie kasujesz go do zera. No i pamiętaj, że te embeddery mają
okno 8192 tokenów — wrzucisz dłuższy dokument bez dzielenia, a końcówka po prostu zostanie
obcięta i nigdy nie trafi do bazy.</p>
<h2 id="zrób-to-sam">Zrób to sam</h2>
<p>Najfajniejsze w lokalnym RAG-u jest to, że embeddingi policzysz tym samym <code>llama.cpp</code>,
którym odpalasz zwykły model — <a href="/poradniki/flagi-llama-cpp/">flagi rozbieraliśmy osobno</a>.
Ściągasz GGUF-a z nomikiem i odpalasz serwer z jedną dodatkową flagą:</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 nomic-embed-text-v1.5.Q8_0.gguf <span class="se">\
</span></span></span><span class="line"><span class="cl">  --embeddings <span class="se">\
</span></span></span><span class="line"><span class="cl">  --host 0.0.0.0 --port <span class="m">8080</span>
</span></span></code></pre></div><p><code>--embeddings</code> przełącza <code>llama-server</code> w tryb liczenia wektorów.<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> Potem strzelasz
do niego jak do zwykłego API — i tu drobiazg, o który łatwo się potknąć: endpoint
<code>/embeddings</code> obsługuje wszystkie tryby poolingu, łącznie z <code>--pooling none</code> (wektor
per token), natomiast zgodny z OpenAI <code>/v1/embeddings</code> wymaga poolingu innego niż <code>none</code>.<sup id="fnref1:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl http://localhost:8080/v1/embeddings <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s2">&#34;Content-Type: application/json&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -d <span class="s1">&#39;{&#34;input&#34;: &#34;search_document: RTX 4070, faktura, 2799 zł&#34;}&#39;</span>
</span></span></code></pre></div><p>Widzisz ten <code>search_document: </code> wklejony w treść? To nie ozdoba — to ten prefiks, bez
którego nomic liczy nie to, co trzeba. Dostajesz z powrotem wektor 768 liczb, wsadzasz go
do Chromy albo FAISS-a razem z oryginalnym tekstem i metadanymi, i powtarzasz dla każdego
chunka. Zapytanie liczysz identycznie, tylko z prefiksem <code>search_query: </code>. Cały lokalny RAG
to te cztery ogniwa spięte skryptem na sto linijek — reszta to strojenie.</p>
<p>Kończąc, wyobraź to sobie tak: fine-tuning jest jak wysłanie modelu z powrotem do szkoły,
żeby na nowo wykuł materiał. RAG to podanie mu otwartej książki tuż przed egzaminem —
z zakładką na właściwej stronie. Model dalej jest tym samym modelem, wagi ani drgną.
Zmienia się tylko to, że tym razem nie musi zgadywać, ile płaciłeś za tę kartę. Ma to
czarno na białym, w akapicie, który sam mu podsunąłeś.</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>Lewis et al., „Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks&quot;, NeurIPS 2020 — <a href="https://arxiv.org/abs/2005.11401">arxiv.org/abs/2005.11401</a>. Definicja RAG oraz podział na pamięć parametryczną (model seq2seq) i nieparametryczną (gęsty indeks wektorowy Wikipedii przeszukiwany przez neuronowy retriever).&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Karta modelu <code>nomic-ai/nomic-embed-text-v1.5</code>, Hugging Face — <a href="https://huggingface.co/nomic-ai/nomic-embed-text-v1.5">huggingface.co/nomic-ai/nomic-embed-text-v1.5</a>. Wymiar 768 z Matryoshka Representation Learning (skalowanie w dół, np. do 64), kontekst 8192 tokenów, wymagane prefiksy <code>search_document: </code> / <code>search_query: </code>, licencja Apache 2.0.&#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>BAAI/bge-m3</code>, Hugging Face — <a href="https://huggingface.co/BAAI/bge-m3">huggingface.co/BAAI/bge-m3</a>. Wymiar 1024, kontekst 8192, ponad 100 języków (baza XLM-RoBERTa), trzy tryby retrievalu naraz (dense / sparse / multi-vector w stylu ColBERT), zalecenie hybrydowego łączenia z re-rankerem, licencja MIT.&#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>FAISS (Meta AI Research) — <a href="https://faiss.ai/">faiss.ai</a>. Biblioteka do similarity search i klastrowania gęstych wektorów; typy indeksów <code>IndexFlatL2</code>, IVF, HNSW, Product Quantization, wsparcie GPU.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Dokumentacja Chroma, „Introduction&quot; — <a href="https://docs.trychroma.com/docs/overview/introduction">docs.trychroma.com/docs/overview/introduction</a>. Open-source&rsquo;owa baza wektorowa (Apache 2.0): przechowywanie embeddingów z metadanymi, wyszukiwanie dense/sparse/hybrydowe, filtrowanie po metadanych, tryby lokalny / self-hosted / Chroma Cloud.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>README serwera <code>llama.cpp</code> (ggml-org) — <a href="https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md">github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md</a>. Flaga <code>--embeddings</code> do <code>llama-server</code>; endpoint <code>/embeddings</code> wspiera wszystkie tryby poolingu (w tym <code>--pooling none</code>), a zgodny z OpenAI <code>/v1/embeddings</code> wymaga poolingu innego niż <code>none</code>.&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>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>Speculative decoding: jak mały model przyspiesza duży</title><link>https://inferownia.pl/poradniki/speculative-decoding/</link><pubDate>Wed, 08 Jul 2026 00:00:00 +0200</pubDate><category>inferencja</category><guid>https://inferownia.pl/poradniki/speculative-decoding/</guid><description>Duży model musi robić pełny przebieg dla każdego pojedynczego tokena — stąd ten mielący rytm generacji. Speculative decoding łamie ten rytm: mały model zgaduje kilka tokenów naprzód, duży sprawdza je hurtem. I najlepsze: bez utraty jakości.</description><content:encoded><![CDATA[<p>Token. Chwila. Token. Chwila. Lokalny model wypluwa tekst jak stara drukarka igłowa, literka
po literce, a ty gapisz się w kursor, jakby to on dyktował tempo twojego życia. I prawie każdy jęk o
prędkości sprowadza się do jednego: „mam ładne 4 tokeny na sekundę i cierpliwość
mnicha&quot;. I człowiek zaczyna kombinować — może da się to jakoś oszukać?</p>
<p>Bo pod spodem dzieje się rzecz z pozoru absurdalnie rozrzutna. Żeby dołożyć jeden token,
model o siedmiu czy siedemdziesięciu miliardach parametrów musi przepchnąć całe swoje
wnętrze — pełny przebieg naprzód — po czym dokłada jedną literkę i zaczyna od nowa. Cały
ten kolos rozgrzewa się do jednego słowa, potem znowu, i znowu. Trochę jak odpalać silnik
ciężarówki za każdym razem, gdy chcesz przejechać metr.</p>
<h2 id="skąd-w-ogóle-ten-pomysł">Skąd w ogóle ten pomysł</h2>
<p>W 2022 roku trójka badaczy z Google&rsquo;a — Leviathan, Kalman i Matias — zadała pytanie, które
brzmi jak sabotaż: a co, jeśli większość kolejnych tokenów jest tak oczywista, że nie
trzeba do nich całego dużego modelu? Że „w dniu dzisiejszym&quot; po słowie „w&quot; i „dniu&quot; da się
zgadnąć czymś dużo tańszym? Opisali to w pracy <em>Fast Inference from Transformers via
Speculative Decoding</em> i pokazali coś, co brzmi zbyt pięknie: 2–3 razy szybsza generacja
na modelach klasy T5-XXL, bez douczania czegokolwiek i <strong>bez zmiany rozkładu wyjściowego</strong>
— czyli model gada dokładnie to samo, co gadałby normalnie, tylko szybciej.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Papier
poszedł na ICML 2023 jako prezentacja ustna, więc nie była to notka na marginesie.</p>
<p>Dwa miesiące później niezależnie tę samą ideę opisała ekipa z DeepMind — Chen, Borgeaud,
Irving, Lespiau, Sifre i Jumper — pod nazwą <em>speculative sampling</em>, z lekko innym schematem
matematycznym i przyspieszeniem 2–2,5x na Chinchilli (70B) w środowisku rozproszonym.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>
Dwa zespoły, ten sam pomysł, ta sama gwarancja: przyspieszamy, jakości nie ruszamy. Kiedy
dwie niezależne grupy trafiają w to samo, to zwykle znak, że pod spodem siedzi coś prawdziwego.</p>
<h2 id="jak-to-działa-krok-po-kroku">Jak to działa krok po kroku</h2>
<p>Dobra, ale jak to działa naprawdę? Bierzesz dwa modele. Jeden duży — ten, którego odpowiedzi
chcesz — nazwijmy go <strong>docelowym</strong> (<em>target</em>). I jeden mały, szybki — <strong>szkicownik</strong>
(<em>draft</em>) — najlepiej tej samej rodziny, żeby „myślał&quot; podobnie. I teraz taniec w trzech krokach:</p>
<ol>
<li><strong>Szkicownik zgaduje.</strong> Mały model generuje autoregresywnie kilka kolejnych tokenów —
powiedzmy K sztuk. Robi to szybko, bo jest mały. To są propozycje, brudnopis.</li>
<li><strong>Duży weryfikuje — jednym przebiegiem.</strong> I tu jest cały myk: duży model dostaje te K
proponowanych tokenów naraz i sprawdza je w <strong>jednym</strong> przebiegu naprzód, wsadowo. Nie
K osobnych przejazdów ciężarówką — jeden.</li>
<li><strong>Akceptuj albo popraw.</strong> Tam, gdzie duży model zgadza się ze szkicownikiem, tokeny
wpadają za darmo. Przy pierwszym niezgodnym — odrzucamy resztę, duży dokłada swój token
i cała zabawa rusza od nowa.</li>
</ol>
<p>Jeśli szkicownik trafił w pięć tokenów z rzędu, to policzyłeś je jednym przebiegiem dużego
modelu zamiast pięciu. Jeśli spudłował od razu — koszt jest praktycznie taki, jak przy
zwykłym dekodowaniu. Downside prawie żaden, upside spory. Ta sama logika weryfikacji „licz
wsad, nie sekwencję&quot; siedzi zresztą u podstaw tego, czemu KV-cache w ogóle się opłaca —
rozgryzaliśmy to <a href="/poradniki/kv-cache-vram/">tutaj</a>.</p>
<h2 id="czemu-to-nie-psuje-jakości">Czemu to nie psuje jakości</h2>
<p>W tym miejscu każdy rozsądny człowiek zapala czerwoną lampkę. „Mały model zgaduje za dużego?
To brzmi jak destylacja na skróty, na pewno wychodzi gorsza jakość.&quot; Otóż nie — i to jest
najładniejsza część całej sztuczki.</p>
<p>To <strong>nie</strong> jest kwantyzacja ani destylacja. Wag dużego modelu nikt nie dotyka, jego
dokładność zostaje nietknięta. Sekret siedzi w tym, jak akceptowane są tokeny — używa się
zmodyfikowanego <strong>rejection sampling</strong> (próbkowania odrzucającego). Token zaproponowany
przez szkicownik przyjmujemy z prawdopodobieństwem <code>min(1, p_target(x) / p_draft(x))</code> —
czyli: jeśli duży model uważa dany token za co najmniej tak samo prawdopodobny jak
szkicownik, bierzemy go w ciemno. Jeśli mniej — akceptujemy proporcjonalnie, a przy
odrzuceniu dobieramy nowy token z <strong>rozkładu resztkowego</strong> (residual), tak dobranego, żeby
wszystko się matematycznie spięło.<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>Efekt? Łączny rozkład tokenów na wyjściu jest <strong>identyczny</strong> z tym, co dałoby zwykłe,
niespekulacyjne próbkowanie samego dużego modelu. Nie „prawie taki sam&quot;, nie „w granicach
błędu&quot; — dokładnie taki sam, z dowodem.</p>
<div class="callout">
  <span class="callout__label">Dlaczego to ważne dla lokalnego LLM</span>
<p>To jest ten rzadki obiad, za który nikt nie płaci. Speculative decoding nie jest
kompromisem „szybciej za cenę jakości&quot;, jak kwantyzacja czy przycinanie kontekstu. Rozkład
wyjściowy zostaje matematycznie nienaruszony — dostajesz te same odpowiedzi co z gołego
dużego modelu, tylko prędzej. Jedyne, czym płacisz, to trochę VRAM-u na drugi, mały model
w pamięci i odrobina narzutu na weryfikację.</p>
</div>
<h2 id="skąd-się-bierze-przyspieszenie">Skąd się bierze przyspieszenie</h2>
<p>Skoro nic nie tniemy, to gdzie ten darmowy obiad się chowa? W wąskim gardle, o którym
lokalny LLM-owiec i tak wie w kościach: inferencja jednego tokena naraz jest
<strong>ograniczona pamięcią</strong> (bandwidth-bound), nie liczeniem. Karta większość czasu nie liczy,
tylko czeka, aż wagi modelu przemaszerują z VRAM-u do rdzeni. Przepchnięcie przez ten sam
kanał jednego tokena a pięciu naraz kosztuje prawie tyle samo — bo i tak głównie czekasz na
pamięć, a nie na arytmetykę.</p>
<p>Stąd cały zysk: policzenie K+1 tokenów w jednym wsadowym przebiegu jest znacznie tańsze niż
K+1 osobnych, sekwencyjnych kroków. Im więcej propozycji szkicownika duży model akceptuje,
tym mocniej wygrywasz.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> I tu ważne zastrzeżenie, bo to nie jest magiczne pokrętło
„więcej = szybciej&quot;: przyspieszenie <strong>nie</strong> rośnie liniowo z liczbą draftowanych tokenów.
Wszystko wisi na <strong>współczynniku akceptacji</strong> — jak często szkicownik trafia. Każąc mu
zgadywać dwadzieścia tokenów naprzód przy kiepskiej trafności, generujesz górę brudnopisu,
który duży model i tak wyrzuci do kosza. Bywa, że wtedy jest wolniej niż bez całej zabawy.</p>
<h2 id="zrób-to-sam-w-llamacpp">Zrób to sam w llama.cpp</h2>
<p>Teoria teorią, ale <code>llama.cpp</code> (repo <code>ggml-org/llama.cpp</code>) obsługuje to od dawna — i w
<code>llama-server</code>, i w osobnym narzędziu <code>llama-speculative-simple</code>. Cały wjazd na klasyczny
wariant to jedna flaga: <code>--spec-draft-model</code> (krócej <code>-md</code>), która wskazuje plik GGUF
modelu szkicowego.<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> Bierzesz duży model, dokładasz mały z tej samej rodziny i lecisz:</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 qwen3-14b-q4_k_m.gguf <span class="se">\
</span></span></span><span class="line"><span class="cl">  -md qwen3-0.6b-q4_k_m.gguf <span class="se">\
</span></span></span><span class="line"><span class="cl">  --spec-draft-n-max <span class="m">4</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -ngl all -c <span class="m">8192</span> -fa on
</span></span></code></pre></div><p><code>--spec-draft-n-max N</code> (domyślnie 3) ustawia maksymalną liczbę tokenów szkicowanych na
krok, a <code>--spec-draft-n-min N</code> (domyślnie 0) dolną granicę.<sup id="fnref1:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> Progi akceptacji stroisz
przez <code>--spec-draft-p-min</code> (domyślnie 0.00) i <code>--spec-draft-p-split</code> (domyślnie 0.10). Do
reszty flag <code>llama-server</code> — <code>-ngl</code>, <code>-c</code>, <code>-fa</code> i spółka — wracaliśmy osobno
<a href="/poradniki/flagi-llama-cpp/">tutaj</a>.</p>
<p>Strategię wybiera <code>--spec-type</code>, i tu robi się ciekawie, bo klasyczny draft model to
dopiero początek:</p>
<table>
	<thead>
			<tr>
					<th><code>--spec-type</code></th>
					<th>Co robi</th>
					<th>Potrzebny osobny model draft?</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>draft-simple</code></td>
					<td>zwykły mały model szkicowy</td>
					<td>tak (GGUF przez <code>-md</code>)</td>
			</tr>
			<tr>
					<td><code>draft-eagle3</code></td>
					<td>jedna warstwa czytająca stany ukryte dużego modelu</td>
					<td>tak (lekka głowica EAGLE-3)</td>
			</tr>
			<tr>
					<td><code>draft-dflash</code></td>
					<td>generuje cały blok szkicu jednym przebiegiem (block diffusion)</td>
					<td>tak</td>
			</tr>
			<tr>
					<td><code>ngram-simple</code></td>
					<td>zgaduje z historii wygenerowanego tekstu</td>
					<td><strong>nie</strong></td>
			</tr>
			<tr>
					<td><code>ngram-cache</code></td>
					<td>wariant n-gramowy z podręczną pamięcią</td>
					<td><strong>nie</strong></td>
			</tr>
	</tbody>
</table>
<p>Warianty <strong>n-gramowe</strong> to osobny gatunek: nie potrzebują żadnego drugiego modelu, tylko
zgadują kolejne tokeny na podstawie tego, co już się w tekście pojawiło (świetne, gdy model
dużo cytuje sam siebie albo klepie strukturę). Mechanizm weryfikacji jest ten sam — po
prostu brudnopis powstaje inaczej.<sup id="fnref1:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> A na drugim biegunie <strong>EAGLE-3</strong> (<code>draft-eagle3</code>)
to lekka, jednotransformerowa głowica, która podgląda stany ukryte dużego modelu i przez to
trafia częściej niż samodzielny mały model tej samej wagi. Uruchomienie z dokumentacji
wygląda tak:</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 Qwen3-4B.gguf <span class="se">\
</span></span></span><span class="line"><span class="cl">  -md Qwen3-4B-eagle3.gguf <span class="se">\
</span></span></span><span class="line"><span class="cl">  --spec-type draft-eagle3
</span></span></code></pre></div><p>Jedna pułapka na koniec, żebyś nie stracił wieczora na kopiowanie starych poradników:
dawne flagi <code>--draft</code>, <code>--draft-max</code>, <code>--draft-min</code> <strong>zostały usunięte</strong>. Aktualna składnia
to <code>--spec-draft-n-max</code> i pochodne <code>--spec-*</code>.<sup id="fnref2:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> Jak przepiszesz przykład sprzed roku
i <code>llama-server</code> prycha, że nie zna flagi — to nie ty zwariowałeś, to repo poszło naprzód.</p>
<p>Najładniejsze w tym wszystkim jest to, że speculative decoding nie jest żadnym oszustwem na
jakości — jest raczej jak dobry sekretarz. Ktoś szybki i tani pisze pod dyktando pierwszy
szkic, przewidując, dokąd zmierza zdanie, a szef tylko przelatuje wzrokiem i skreśla to, co
się nie zgadza. Reszta zostaje, jak stała. Szef odpowiada za każde słowo tak samo jak
wtedy, gdy pisał sam — tyle że nie musi już maczać pióra w kałamarzu przy każdej literce.</p>
<p>I to jest ten moment, w którym twoja drukarka igłowa spod biurka nagle łapie zryw — te same
słowa, ten sam rozkład, ta sama karta. Tylko kursor przestaje w końcu dyktować ci tempo.</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>Leviathan, Kalman, Matias, <em>Fast Inference from Transformers via Speculative Decoding</em>, ICML 2023 (oral) — definicja metody, gwarancja identycznego rozkładu wyjściowego przez zmodyfikowany rejection sampling, przyspieszenie 2–3x na T5-XXL. <a href="https://arxiv.org/abs/2211.17192">arxiv.org/abs/2211.17192</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></p>
</li>
<li id="fn:2">
<p>Chen, Borgeaud, Irving, Lespiau, Sifre, Jumper (DeepMind), <em>Accelerating Large Language Model Decoding with Speculative Sampling</em> — niezależne sformułowanie, przyspieszenie 2–2,5x na Chinchilli 70B w środowisku rozproszonym. <a href="https://arxiv.org/abs/2302.01318">arxiv.org/abs/2302.01318</a>.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>llama.cpp, mechanizm weryfikacji wsadowej i lista strategii (<code>draft-eagle3</code>, <code>draft-dflash</code>, warianty <code>ngram-*</code>), przykład uruchomienia z EAGLE-3, <a href="https://github.com/ggml-org/llama.cpp/blob/master/docs/speculative.md">docs/speculative.md</a>.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>llama.cpp, dokładne nazwy i domyślne wartości flag <code>--spec-draft-model</code>/<code>-md</code>, <code>--spec-type</code>, <code>--spec-draft-n-max</code>/<code>n-min</code>, <code>--spec-draft-p-min</code>/<code>p-split</code> oraz informacja o usunięciu dawnych <code>--draft</code>/<code>--draft-max</code>/<code>--draft-min</code>, <a href="https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md">tools/server/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><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>Wymuszanie JSON-a w llama.cpp: gramatyki GBNF</title><link>https://inferownia.pl/poradniki/gbnf-json/</link><pubDate>Mon, 06 Jul 2026 00:00:00 +0200</pubDate><category>inferencja</category><guid>https://inferownia.pl/poradniki/gbnf-json/</guid><description>Prosisz model o czysty JSON, a dostajesz esej, blok w markdownie i przecinek w złym miejscu. Gramatyki GBNF w llama.cpp odbierają mu wybór: model może wypluć tylko te tokeny, które pasują do reguł. Rozbieramy, jak to działa na poziomie samplera.</description><content:encoded><![CDATA[<p>Znasz ten moment. Piszesz w prompcie wielkimi literami „ODPOWIEDZ WYŁĄCZNIE POPRAWNYM
JSON-em, BEZ ŻADNEGO KOMENTARZA&quot;, dorzucasz przykład, błagalnie dodajesz „no proszę&quot;,
a model i tak zaczyna od „Oczywiście! Oto Twój JSON:&quot;, opakowuje go w blok <code>```json</code>,
gdzieś w środku gubi cudzysłów, a na końcu dorzuca jeszcze zdanie, że gdyby coś, to chętnie
pomoże dalej. Twój <code>json.loads()</code> wybucha, a ty siedzisz i parsujesz to regexpem jak jaskiniowiec.
To częsty punkt zapalny — rozjechany nawias, ucięty cudzysłów i podpis w stylu
„przecież prosiłem grzecznie&quot;.</p>
<p>No i tu jest cały myk: prośba w prompcie to prośba. Model może ją spełnić albo nie, bo prompt
to tylko sugestia w tekście. A gramatyka GBNF to coś zupełnie innego — to kaganiec założony na
sampler. Nie mówisz modelowi „bądź tak miły i zwróć JSON&quot;. Ty mu fizycznie <strong>nie pozwalasz</strong>
wygenerować tokenu, który by JSON-a psuł. Różnica jak między „proszę, nie wychodź poza linie&quot;
a kartką do kolorowania, w której poza liniami po prostu nie ma gdzie pociągnąć kredką.</p>
<h2 id="o-co-chodzi-z-gbnf">O co chodzi z GBNF</h2>
<p>GBNF (GGML BNF) to format gramatyki formalnej, którym <code>llama.cpp</code> ogranicza — po angielsku
<em>constrain</em> — to, co model może wygenerować.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Nazwa zdradza rodowód: to wariant
BNF-a, tej samej notacji, którą od dekad opisuje się składnię języków programowania. Piszesz
zestaw reguł „co po czym może stać&quot;, a silnik pilnuje, żeby wyjście trzymało się tych reguł
znak po znaku, token po tokenie.</p>
<p>W praktyce masz dwie drogi do tego samego celu. Albo piszesz gramatykę GBNF ręcznie i podajesz
ją flagą <code>--grammar</code> (wprost jako string) lub <code>--grammar-file</code> (z pliku).<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> Albo, jeśli
myślisz kategoriami JSON Schema, podajesz schemat flagą <code>-j</code> / <code>--json-schema</code> — a <code>llama.cpp</code>
sam przemieli go wewnętrznie na GBNF funkcją <code>json_schema_to_grammar</code>.<sup id="fnref1:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> Do tego jest
bliźniacza para <code>-jf</code> / <code>--json-schema-file</code>, gdy schemat wolisz trzymać w pliku. Efekt
końcowy ten sam: pod spodem i tak rządzi gramatyka.</p>
<div class="callout">
  <span class="callout__label">Dlaczego to ważne dla lokalnego LLM</span>
<p>Najważniejsza rzecz, którą trzeba zrozumieć, i zarazem najczęstszy mit: schemat ani gramatyka
<strong>nie trafiają do promptu</strong>. Model nie widzi w kontekście ani jednej reguły — nie dostaje
few-shota, nie dostaje instrukcji „pamiętaj o cudzysłowach&quot;. Ograniczenie działa wyłącznie na
poziomie samplera, przy każdym kolejnym generowanym tokenie.<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> To dlatego działa nawet
na małym modelu, który normalnie olewa prośby o format: nie prosisz go o współpracę, tylko
zamykasz mu wszystkie złe drzwi.</p>
</div>
<h2 id="jak-to-w-ogóle-działa-pod-maską">Jak to w ogóle działa pod maską?</h2>
<p>Skoro model niczego nie widzi w prompcie, to skąd wie, że ma trzymać się formatu? Nie wie.
I to jest piękne. Cała robota dzieje się piętro niżej, w samplerze — komponencie, który po
każdym przejściu przez sieć wybiera następny token z rozkładu prawdopodobieństw.</p>
<p>Przypomnij sobie, jak model generuje. Sieć wypluwa <strong>logity</strong> — po jednej liczbie na każdy
token ze słownika, im wyższa, tym „chętniej&quot; model by go postawił. Normalnie sampler bierze
tę tablicę kandydatów (<code>llama_token_data_array</code>) i losuje z niej według reguł temperatury,
top-p i reszty pokręteł, które rozgryzaliśmy <a href="/poradniki/flagi-llama-cpp/">tutaj</a>. Gramatyka
wciska się dokładnie w to miejsce, tuż przed losowaniem. Funkcja <code>llama_grammar_apply_impl</code>
przechodzi po całej tablicy kandydatów i każdemu tokenowi, który <strong>nie pasuje</strong> do gramatyki,
ustawia logit na <code>-inf</code>.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> Minus nieskończoność to wyrok: taki token ma po
softmaksie prawdopodobieństwo zero, więc sampler nie ma prawa go wybrać, choćby model bardzo
chciał. To właśnie <strong>maskowanie logitów</strong> — zerowanie szans tokenów niezgodnych z regułami.</p>
<p>A skąd gramatyka wie, co „pasuje&quot; akurat teraz? Trzyma stan. I to nie jeden stan, tylko cały
zbiór jednoczesnych stosów — klasyczny automat ze stosem (<em>pushdown automaton</em>). Każdy stos
to jedna możliwa ścieżka przez gramatykę, zgodna z tym, co model wygenerował do tej pory.
Token przeżywa tylko wtedy, gdy pasuje do terminala na szczycie <strong>któregokolwiek</strong> z aktywnych
stosów — jeśli nie pasuje do żadnego, dostaje <code>-inf</code> i wypada.<sup id="fnref1:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> Kiedy token już
zostanie wybrany, <code>llama_grammar_accept</code> przesuwa stan gramatyki po zaakceptowanych znakach,
stosy się aktualizują, część ścieżek umiera, część zostaje — i cała zabawa leci od nowa przy
następnym tokenie.<sup id="fnref2:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></p>
<p>Wyobraź sobie to jak korytarz z bramkami. Model na każdym kroku pcha się we wszystkie drzwi
naraz, a gramatyka trzyma przy każdych strażnika, który przepuszcza tylko wtedy, gdy za drzwiami
jest legalna kontynuacja. Model nie musi znać planu budynku — i tak nie skręci w ślepy zaułek,
bo tamtędy fizycznie nie da się przejść.</p>
<h2 id="składnia-w-pigułce">Składnia w pigułce</h2>
<p>GBNF wygląda znajomo dla każdego, kto liznął kiedyś opis gramatyki. Reguła to
<code>nonterminal ::= sekwencja</code>. Klocki, z których się to składa:<sup id="fnref2:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<ul>
<li><strong>Terminale</strong> — literały w cudzysłowach (<code>&quot;1&quot;</code>, <code>&quot;true&quot;</code>) albo zakresy znaków w nawiasach
kwadratowych (<code>[0-9]</code>, <code>[a-zA-Z]</code>), z negacją przez <code>^</code> (<code>[^&quot;]</code> = cokolwiek poza cudzysłowem).</li>
<li><strong>Operatory powtórzeń</strong> — <code>*</code> (zero lub więcej), <code>+</code> (jeden lub więcej), <code>?</code> (opcjonalnie),
oraz precyzyjne <code>{m}</code>, <code>{m,}</code>, <code>{m,n}</code> (dokładnie / co najmniej / od–do).</li>
<li><strong>Alternatywy</strong> przez <code>|</code> i <strong>grupowanie</strong> przez <code>( )</code> — dokładnie jak w regexpach.</li>
<li><strong>Unicode</strong> w komplecie, z ucieczkami <code>\xXX</code> (8-bit), <code>\uXXXX</code> (16-bit) i <code>\UXXXXXXXX</code>
(32-bit), więc polskie znaki czy emoji nie są problemem.</li>
</ul>
<p>Jest też smaczek, którego regexp nie ma: GBNF potrafi dopasowywać po konkretnych <strong>tokenach
tokenizera</strong>, nie po znakach. <code>&lt;[1000]&gt;</code> to token o ID 1000, <code>&lt;think&gt;</code> to token o dokładnym
tekście <code>think</code>, a <code>!&lt;...&gt;</code> to negacja.<sup id="fnref3:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Przydaje się, gdy chcesz np. sterować
sekcjami <code>&lt;think&gt;...&lt;/think&gt;</code> u modeli rozumujących — operujesz wtedy na realnych tokenach,
a nie zgadujesz, jak model potnie tekst.</p>
<p>Jedna pułapka na start: nazwy nieterminali piszemy <strong>małymi literami z myślnikami</strong>
(<code>item-name-kv</code>), nie <code>camelCase</code> i nie z podkreślnikami. Tak jest w oficjalnych przykładach
i tego się trzymaj.<sup id="fnref4:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<h2 id="mały-przykład-od-zera">Mały przykład od zera</h2>
<p>Powiedzmy, że chcesz z modelu wyciągnąć obiekt z polem <code>name</code> (tekst) i <code>age</code> (liczba).
Minimalna gramatyka, w duchu przykładu z oficjalnego README:<sup id="fnref5:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">root   ::= &#34;{&#34; ws &#34;\&#34;name\&#34;:&#34; ws string &#34;,&#34; ws &#34;\&#34;age\&#34;:&#34; ws number ws &#34;}&#34;
</span></span><span class="line"><span class="cl">string ::= &#34;\&#34;&#34; char{1,100} &#34;\&#34;&#34;
</span></span><span class="line"><span class="cl">char   ::= [^&#34;\\]
</span></span><span class="line"><span class="cl">number ::= [0-9]+
</span></span><span class="line"><span class="cl">ws     ::= [ \t\n]*
</span></span></code></pre></div><p>Czyta się to prawie jak zdanie: korzeń to klamra, w środku pole <code>name</code> ze stringiem od 1 do
100 znaków, przecinek, pole <code>age</code> z liczbą, klamra zamykająca — a <code>ws</code> to opcjonalne białe
znaki, żeby model mógł sobie ładnie wciąć. Podajesz to modelowi i już na poziomie samplera
nie ma opcji, żeby zaczął od „Oto JSON:&quot;. Pierwszy token, który wolno postawić, to <code>{</code>.
Kropka. Cała reszta korytarza jest zamknięta.</p>
<p>Odpalasz to najprościej tak — gramatyka wprost w linii poleceń:</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">  --grammar <span class="s1">&#39;root ::= &#34;{&#34; &#34;\&#34;ok\&#34;:&#34; (&#34;true&#34; | &#34;false&#34;) &#34;}&#34;&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -p <span class="s2">&#34;Czy 7 jest liczbą pierwszą? Odpowiedz.&#34;</span> -no-cnv
</span></span></code></pre></div><p>Albo, gdy myślisz schematami, oddajesz robotę konwerterowi. <code>--json-schema '{}'</code> przyjmie
dowolny JSON, a węższy schemat zawęzi wyjście:<sup id="fnref2: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">llama-cli -m qwen2.5-7b-instruct-q4_k_m.gguf <span class="se">\
</span></span></span><span class="line"><span class="cl">  --json-schema <span class="s1">&#39;{&#34;type&#34;:&#34;object&#34;,&#34;properties&#34;:{&#34;pierwsza&#34;:{&#34;type&#34;:&#34;boolean&#34;}},&#34;required&#34;:[&#34;pierwsza&#34;]}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -p <span class="s2">&#34;Czy 7 jest liczbą pierwszą?&#34;</span> -no-cnv
</span></span></code></pre></div><h2 id="--grammar-czy---json-schema-to-nie-to-samo"><code>--grammar</code> czy <code>--json-schema</code>? To nie to samo</h2>
<p>Łatwo je pomylić, bo cel jest jeden, ale wejście zupełnie inne. Ściągawka:</p>
<table>
	<thead>
			<tr>
					<th></th>
					<th><code>--grammar</code> / <code>--grammar-file</code></th>
					<th><code>-j</code> / <code>--json-schema</code> (+ <code>-jf</code> z pliku)</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Co podajesz</td>
					<td>gotowy GBNF, napisany ręcznie</td>
					<td>schemat JSON Schema (json-schema.org)</td>
			</tr>
			<tr>
					<td>Kto pisze gramatykę</td>
					<td>ty</td>
					<td><code>json_schema_to_grammar</code>, automatycznie</td>
			</tr>
			<tr>
					<td>Kiedy sięgać</td>
					<td>dowolna struktura, nie tylko JSON</td>
					<td>masz już schemat, myślisz „typami&quot;</td>
			</tr>
			<tr>
					<td>Złożone <code>$ref</code></td>
					<td>ogarnia, bo piszesz wprost</td>
					<td>patrz niżej — jest haczyk</td>
			</tr>
	</tbody>
</table>
<p>Mechanizm wykonawczy w obu wypadkach ten sam: pod spodem i tak powstaje GBNF, który maskuje
logity. To dwa różne wejścia do tej samej maszyny.</p>
<p>I ten haczyk z <code>$ref</code>: jeśli twój schemat JSON Schema wciąga zewnętrzne referencje (<code>$ref</code> do
innych plików), <code>--json-schema</code> może się na tym wyłożyć. Oficjalna dokumentacja radzi wtedy
wygenerować GBNF <strong>offline</strong> skryptem <code>examples/json_schema_to_grammar.py</code>, zapisać do pliku
i podać go przez <code>--grammar-file</code>.<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> Rozdzielasz konwersję od inferencji — najpierw
w spokoju robisz gramatykę, potem karmisz nią model.</p>
<h2 id="gramatyka-pilnuje-składni-nie-sensu">Gramatyka pilnuje składni, nie sensu</h2>
<p>Zanim uwierzysz, że rozwiązałeś wszystkie problemy świata — jedno trzeźwiące zastrzeżenie.
Gramatyka gwarantuje ci poprawną <strong>składnię</strong>, nie <strong>semantykę</strong>. Wymusisz, że <code>age</code> to
liczba — ale nic nie broni modelowi wpisać tam <code>999</code>, jeśli akurat tak mu wyjdzie z rozkładu.
Wymusisz strukturę odpowiedzi RAG-owej — ale nie to, że treść w polach będzie prawdziwa;
o samym łączeniu wyszukiwania z generacją pisaliśmy <a href="/poradniki/rag-lokalnie/">tutaj</a>.
GBNF ogranicza dopuszczalne <strong>sekwencje tokenów</strong>, a nie sens tego, co w nich siedzi. To
kaganiec na formę, nie na głupoty. Walidację wartości i logikę trzymaj po swojej stronie,
tak jak trzymałeś ją zawsze.</p>
<h2 id="zrób-to-sam-przez-serwer">Zrób to sam: przez serwer</h2>
<p>Na co dzień najwygodniej pchać to przez <code>llama-server</code>. Endpoint <code>/completion</code> przyjmuje
w body te same dwa parametry co CLI: <code>grammar</code> (tekst GBNF) oraz <code>json_schema</code> (schemat JSON),
oba domyślnie puste.<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> Odpalasz serwer raz:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">llama-server -m qwen2.5-7b-instruct-q4_k_m.gguf -ngl all -c <span class="m">8192</span> --port <span class="m">8080</span>
</span></span></code></pre></div><p>i strzelasz do niego z gramatyką w środku:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -s http://localhost:8080/completion -d <span class="s1">&#39;{
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#34;prompt&#34;: &#34;Wygeneruj kartę postaci.&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#34;grammar&#34;: &#34;root ::= \&#34;{\&#34; \&#34;\\\&#34;imie\\\&#34;:\&#34; \&#34;\\\&#34;\&#34; [a-zA-Z]+ \&#34;\\\&#34;\&#34; \&#34;}\&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">}&#39;</span>
</span></span></code></pre></div><p>Jak wsadzisz gramatykę z błędem składni, serwer nie udaje, że jest dobrze — odbija żądanie
z HTTP 400 i komunikatem <code>Failed to parse grammar</code>.<sup id="fnref1:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> To dobra wiadomość: literówkę
w regule wyłapiesz od razu, na etapie parsowania, a nie po godzinie zastanawiania się, czemu
wyjście wygląda dziwnie. Gotowe przykłady GBNF — w tym pełną gramatykę dla tablicy obiektów
JSON — znajdziesz w katalogu <code>grammars/</code> w repo <code>ggml-org/llama.cpp</code>.<sup id="fnref6:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>Bo cała ta zabawa sprowadza się do jednej zmiany perspektywy. Przestajesz błagać model
o dobry format i zaczynasz mu ten format wytyczać — nie słowami w prompcie, których i tak
może nie usłuchać, tylko ścianami korytarza, którymi go prowadzisz. Model dalej myśli, co
chce. Po prostu wyjściem z tego myślenia jest już tylko ta jedna furtka, którą sam mu
zostawiłeś otwartą.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Pełna składnia GBNF (reguły <code>nonterminal ::=</code>, terminale, zakresy znaków, operatory powtórzeń, dopasowanie po tokenach <code>&lt;[...]&gt;</code>), przykładowa gramatyka JSON oraz uwaga, że schemat/gramatyka nie trafiają do promptu — <a href="https://github.com/ggml-org/llama.cpp/blob/master/grammars/README.md">grammars/README.md, ggml-org/llama.cpp</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>Definicje i teksty pomocy flag <code>--grammar</code>, <code>--grammar-file</code>, <code>-j</code>/<code>--json-schema</code>, <code>-jf</code>/<code>--json-schema-file</code> oraz konwersja przez <code>json_schema_to_grammar</code> — <a href="https://github.com/ggml-org/llama.cpp/blob/master/common/arg.cpp">common/arg.cpp, ggml-org/llama.cpp</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>Sygnatury <code>llama_grammar_apply_impl</code> (nakłada ograniczenia gramatyki na tablicę kandydatów tokenów / logity) i <code>llama_grammar_accept</code> (przesuwa stan po zaakceptowanym znaku), potwierdzające mechanizm stosów pushdown i maskowanie logitów — <a href="https://github.com/ggml-org/llama.cpp/blob/master/src/llama-grammar.h">src/llama-grammar.h, ggml-org/llama.cpp</a>.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Opis flag <code>--grammar</code>/<code>--grammar-file</code>/<code>--json-schema</code> w narzędziu CLI oraz rekomendacja <code>--grammar</code> + <code>examples/json_schema_to_grammar.py</code> dla schematów z zewnętrznymi <code>$ref</code> — <a href="https://github.com/ggml-org/llama.cpp/blob/master/tools/cli/README.md">tools/cli/README.md, ggml-org/llama.cpp</a>.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Parametry <code>grammar</code> i <code>json_schema</code> w body żądania <code>/completion</code> (domyślnie puste) oraz zachowanie przy błędnej gramatyce (HTTP 400, <code>Failed to parse grammar</code>) — <a href="https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md">tools/server/README.md, ggml-org/llama.cpp</a>.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</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>Ollama, llama.cpp, LM Studio: co siedzi pod maską</title><link>https://inferownia.pl/poradniki/ollama-llama-cpp-lm-studio/</link><pubDate>Sat, 04 Jul 2026 00:00:00 +0200</pubDate><category>inferencja</category><guid>https://inferownia.pl/poradniki/ollama-llama-cpp-lm-studio/</guid><description>Instalujesz Ollamę, model gada od strzału i myślisz: magia. A pod tym ładnym `ollama run` siedzi ten sam silnik, co pod resztą. Rozbieramy, kto tu jest maszyną, a kto tylko obudową.</description><content:encoded><![CDATA[<p>Jedna linijka — <code>ollama run llama3</code> — enter, kilkanaście sekund mielenia i nagle coś
w terminalu zaczyna z tobą gadać. Bez kompilowania, bez flag, bez wgryzania się w sto
stron README: jedna komenda i masz lokalny model pod ręką. Człowiek wtedy myśli,
że Ollama to jakiś osobny, samowystarczalny cud techniki. A potem na jakimś forum
trafiasz na komentarz, gdzie ktoś na dzień dobry rzuca: „przecież to i tak leci na llama.cpp pod
spodem&quot;. I nagle cały ten ekosystem — Ollama, LM Studio, KoboldCpp — zaczyna wyglądać jak
scena, na której jest o wiele więcej podwójnych ról, niż się wydawało.</p>
<p>Bo lokalny LLM to nie jedno pudełko. To silnik i obudowa. I zanim wybierzesz, w czym
odpalać modele, warto wiedzieć, kto tu jest maszyną, a kto tylko ładnie polakierowanym
panelem z pokrętłami. Nie po to, żeby wybierać strony w kolejnej wojnie plemiennej — po to,
żeby wiedzieć, kiedy nakładka ci wystarczy, a kiedy trzeba zejść piętro niżej.</p>
<h2 id="silnik-którego-nie-widać-llamacpp-i-ggml">Silnik, którego nie widać: llama.cpp i ggml</h2>
<p>Zacznijmy od dna, bo tam mieszka bohater tej historii. <code>llama.cpp</code> (repo
<code>ggml-org/llama.cpp</code>) to biblioteka i silnik inferencji LLM napisane w czystym C/C++,
z minimalnymi zależnościami, na licencji MIT.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Nie ma ładnego okienka. Nie ma
listy modeli do kliknięcia. To surowy kod, który bierze wagi modelu i liczy — a liczy na
bibliotece tensorowej niższego poziomu o nazwie <strong>ggml</strong>, i to ona jest właściwym mięśniem,
który mnoży macierze pod całą tą konstrukcją.</p>
<p>To właśnie tu żyją rzeczy, które w codziennym „odpal i gadaj&quot; są niewidzialne. Kwantyzacja
po treningu (<em>post-training quantization</em> — ściskanie gotowych wag, nie trenowanie modelu
od zera w niskiej precyzji, bo to dwie różne bajki) w formacie GGUF, z poziomami od
1.5-bitowego po 8-bitowy.<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Akceleracja na Metal (Apple Silicon), CUDA (NVIDIA),
HIP (AMD), Vulkan, SYCL (Intel), plus optymalizacje CPU (AVX/AVX2/AVX512).<sup id="fnref2:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>
Zestaw narzędzi z linii komend: <code>llama-cli</code> do rozmowy, <code>llama-server</code> z API,
<code>llama-bench</code> do mierzenia wydajności, <code>llama-quantize</code> do ściskania modeli.<sup id="fnref3:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>
Rozbieraliśmy te flagi na czynniki pierwsze <a href="/poradniki/flagi-llama-cpp/">tutaj</a>, a nazwy
kwantyzacji GGUF (<code>Q4_K_M</code> i cała ta rodzina) <a href="/poradniki/nazwy-kwantyzacji-gguf/">tutaj</a>.</p>
<p>Zapamiętaj jedno: kiedy Ollama, LM Studio albo KoboldCpp „uruchamia model&quot;, to w
przytłaczającej większości przypadków pod spodem kręci się właśnie ten silnik. Reszta to
obudowa. Świetna, wygodna, oszczędzająca ci godzin — ale obudowa.</p>
<h2 id="ollama-nakładka-która-po-cichu-wyhodowała-własny-silnik">Ollama: nakładka, która po cichu wyhodowała własny silnik</h2>
<p>Ollama to najpopularniejszy sposób, w jaki ludzie wchodzą w lokalne LLM-y, i przez długi
czas najkrótszy opis brzmiał: „llama.cpp z ładnym zarządzaniem modelami&quot;. Sama dokumentacja
projektu mówi, że Ollama jest oparta na <code>llama.cpp</code> założonym przez Georgiego Gerganova i
historycznie korzystała z niego jako silnika wykonawczego pod spodem; licencja MIT.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<p>Do tego dokłada rzeczy, których gołe <code>llama.cpp</code> ci nie da od strzału. <strong>Modelfile</strong> —
format konfiguracyjny w duchu Dockerfile, który opisuje parametry, prompt systemowy i wagi,
żeby zbudować z tego gotowy „obraz&quot; modelu.<sup id="fnref1:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> Rejestr <code>ollama.com/library</code>, z którego
ściągasz modele jednym <code>ollama pull</code>, jak obrazy z Docker Huba. I własne REST API na porcie
<code>11434</code> (<code>http://localhost:11434/api/</code>), odrębne od tego, co wystawia <code>llama-server</code>.<sup id="fnref2:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>
To jest właśnie ta wygoda, za którą się Ollamę kocha: nie myślisz o plikach GGUF, o ścieżkach,
o flagach. Wpisujesz nazwę i działa.</p>
<p>Ale tu jest zwrot akcji, o którym połowa internetu wciąż nie wie. Od maja 2025, wraz z
ogłoszeniem „Ollama&rsquo;s new engine for multimodal models&quot;, Ollama dla części modeli — między
innymi Meta Llama 4, Google Gemma 3, Qwen 2.5 VL czy Mistral Small 3.1 — przeszła na własny
silnik napisany w Go, który korzysta bezpośrednio z biblioteki ggml, z pominięciem
<code>llama.cpp</code>.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> <code>llama.cpp</code> i ggml zostają dla wstecznej kompatybilności na CPU i
dla pozostałych modeli, ale samo hasło „Ollama to po prostu llama.cpp w ładnym opakowaniu&quot;
zrobiło się nieaktualne. Ciekawostka, ale ważna: nawet gdy nakładka odcina się od
<code>llama.cpp</code>, i tak stoi na tym samym fundamencie — ggml. Mięsień został ten sam, zmieniło się
tylko, kto go obsługuje.</p>
<h2 id="lm-studio-okno-dwa-silniki-i-zamknięte-drzwi">LM Studio: okno, dwa silniki i zamknięte drzwi</h2>
<p>A co, jeśli w ogóle nie chcesz terminala? Wtedy wchodzi LM Studio — aplikacja desktopowa na
Windows, Linux i macOS, z prawdziwym GUI: przeglądasz modele, klikasz, pobierasz, gadasz.
Pod spodem uruchamia modele przez <code>llama.cpp</code> w formacie GGUF, a na Apple Silicon dorzuca
dodatkowo silnik <strong>MLX</strong> — inferencyjny framework Apple — jako alternatywę dla
<code>llama.cpp</code>.<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> Czyli znów: ten sam silnik u podstaw, tylko schowany za oknem, w
które można klikać.</p>
<p>Dla programisty LM Studio wystawia dwie rzeczy naraz: <strong>OpenAI Compatibility API</strong> (żeby
podpiąć się jak do OpenAI, tylko lokalnie) oraz własne <strong>LM Studio REST API</strong> (w wersji
beta), a do tego CLI o nazwie <code>lms</code> do zarządzania modelami, serwerem i pobieraniem.<sup id="fnref1:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>
Wygodne? Bardzo. Jest jednak jeden haczyk, który łatwo przeoczyć: to, że LM Studio stoi na
otwartoźródłowych silnikach (<code>llama.cpp</code>, MLX), <strong>nie</strong> czyni z niego projektu open source.
Sama aplikacja jest zamkniętym oprogramowaniem. Silnik w środku możesz obejrzeć do ostatniej
linijki; obudowę wokół niego — już nie.</p>
<h2 id="koboldcpp-nie-osobny-silnik-tylko-llamacpp-w-jednym-pliku">KoboldCpp: nie osobny silnik, tylko llama.cpp w jednym pliku</h2>
<p>Na koniec przypadek, który najłatwiej źle zaszufladkować. KoboldCpp (<code>LostRuins/koboldcpp</code>)
wygląda jak osobne narzędzie — jeden plik wykonywalny, który odpalasz i masz gotowy interfejs.
Ale to nie konkurencyjny silnik wobec <code>llama.cpp</code>. To fork i nakładka bazująca bezpośrednio
na jego kodzie, spakowana w pojedynczą binarkę i rozszerzona o dodatkowe funkcje. Sam
KoboldCpp jest na licencji AGPL v3.0, przy czym zależność <code>llama.cpp</code> w środku pozostaje na
MIT.<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup></p>
<p>Obsługuje modele GGUF (z kompatybilnością wsteczną dla starszego GGML — o czym za chwilę) i
wystawia równolegle dwa API: własne KoboldAI/KoboldCpp oraz endpoint kompatybilny z OpenAI
pod ścieżką <code>/v1</code>. Do tego dorzuca interfejs KoboldAI Lite z trybami chat, adventure, instruct
i storywriter.<sup id="fnref1:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> Innymi słowy: bierze ten sam silnik, co reszta, i buduje wokół niego
inny zestaw wygód — mocno wychylony w stronę pisania i grania. Nazwanie tego „rywalem
llama.cpp&quot; to jak nazwanie karoserii rywalem silnika.</p>
<div class="callout">
  <span class="callout__label">Dlaczego to ważne dla lokalnego LLM</span>
<p>Przy okazji rozprawmy się z pułapką, na której potyka się pół forum: <strong>GGML to nie GGUF</strong>.
GGML to starszy, dziś przestarzały format plików; GGUF to jego następca — i to właśnie jego
używają dziś <code>llama.cpp</code>, Ollama, LM Studio i KoboldCpp. Jeśli natrafisz w sieci na model
„w GGML&quot;, patrzysz na zabytek. Bierz GGUF. Cały obecny ekosystem stoi na tym drugim —
rozgryzaliśmy go bliżej <a href="/aktualnosci/ekosystem-gguf/">tutaj</a>.</p>
</div>
<h2 id="to-którego-w-końcu-wziąć">To którego w końcu wziąć?</h2>
<p>Teraz konkret, bo o to naprawdę chodzi. Nie o to, który silnik „lepszy&quot; — bo silnik u
podstaw (ggml) jest w dużej mierze wspólny — tylko o to, ile wygody wymieniasz za ile
kontroli.</p>
<table>
	<thead>
			<tr>
					<th>Narzędzie</th>
					<th>Czym jest</th>
					<th>Silnik pod spodem</th>
					<th>API</th>
					<th>Otwarte źródło</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>llama.cpp</strong></td>
					<td>silnik + CLI</td>
					<td>ggml (bezpośrednio)</td>
					<td><code>llama-server</code>, OpenAI-compat</td>
					<td>tak (MIT)</td>
			</tr>
			<tr>
					<td><strong>Ollama</strong></td>
					<td>nakładka + zarządzanie modelami</td>
					<td>llama.cpp/ggml, częściowo własny silnik w Go</td>
					<td>własne REST (port 11434)</td>
					<td>tak (MIT)</td>
			</tr>
			<tr>
					<td><strong>LM Studio</strong></td>
					<td>aplikacja desktopowa (GUI)</td>
					<td>llama.cpp, MLX na Apple Silicon</td>
					<td>OpenAI-compat + LM Studio REST</td>
					<td>nie (zamknięta)</td>
			</tr>
			<tr>
					<td><strong>KoboldCpp</strong></td>
					<td>fork/nakładka w jednym pliku</td>
					<td>llama.cpp</td>
					<td>KoboldAI + OpenAI-compat <code>/v1</code></td>
					<td>tak (AGPL v3.0)</td>
			</tr>
	</tbody>
</table>
<p>Nakładka wystarczy — i to z nawiązką — kiedy chcesz po prostu ściągać modele jednym
poleceniem, przełączać się między nimi bez myślenia o ścieżkach i mieć endpoint, do którego
podepniesz swój kod. To codzienność dziewięciu na dziesięciu lokalnych LLM-owców. Ollama do
szybkiego <code>pull</code> i <code>run</code>, LM Studio jak wolisz klikać w okno, KoboldCpp jak siedzisz w
pisaniu i przygodówkach.</p>
<p>Do gołego <code>llama.cpp</code> schodzisz, gdy zaczyna ci brakować pokręteł. Chcesz wycisnąć konkretny
offload warstw na kartę, ustawić dokładny typ kwantyzacji KV-cache, złapać najświeższą flagę,
która w repo pojawiła się wczoraj, a do nakładki dojdzie za dwa wydania. Albo chcesz
<code>llama-server</code> z równoległym dekodowaniem (<em>parallel decoding</em>), serwujący do tego modele
embeddingowe i rerankingowe pod twojego RAG-a.<sup id="fnref4:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>
Wtedy obudowa zaczyna przeszkadzać, a ty otwierasz maskę i grzebiesz przy silniku sam.</p>
<h2 id="zrób-to-sam-zejdź-o-piętro">Zrób to sam: zejdź o piętro</h2>
<p>Chcesz na własne oczy zobaczyć, że pod spodem siedzi jeden i ten sam mechanizm? Weź dowolny
model GGUF, którego już używasz w Ollamie, i odpal go bezpośrednio w <code>llama.cpp</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://github.com/ggml-org/llama.cpp
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> llama.cpp
</span></span><span class="line"><span class="cl"><span class="c1"># zbuduj wg instrukcji z README pod swój backend (Metal/CUDA/Vulkan/CPU)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">llama-server -m twoj-model-q4_k_m.gguf -c <span class="m">8192</span> -fa on
</span></span></code></pre></div><p>Wchodzisz na <code>http://localhost:8080</code>, dostajesz web UI i OpenAI-kompatybilne API — to samo,
co daje ci nakładka, tylko bez pośrednika i z pełnym dostępem do każdej flagi. Kod, aktualne
narzędzia i instrukcje budowania są w repo:
<a href="https://github.com/ggml-org/llama.cpp">github.com/ggml-org/llama.cpp</a>. Jak zechcesz wrócić
do wygody — Ollama, LM Studio i KoboldCpp czekają tam, gdzie je zostawiłeś. Nic nie tracisz,
bo pliki GGUF działają wszędzie tak samo.</p>
<p>Bo cała ta scena — Ollama, LM Studio, KoboldCpp — to w gruncie rzeczy różne obudowy
przykręcone do wspólnego silnika. Jedni lubią jeździć autem, nie otwierając nigdy maski, i
to jest w porządku — dojedziesz, gdzie chcesz. Inni muszą wiedzieć, co tam warczy, i co
jakiś czas zaglądają, czy wszystko na swoim miejscu. Lokalne AI ma miejsce dla obu. Ważne
tylko, żeby wiedzieć, że pod każdą z tych błyszczących karoserii bije mniej więcej to samo
serce — i że w każdej chwili możesz podnieść klapę i mu się przyjrzeć.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><code>ggml-org/llama.cpp</code> — README (silnik C/C++ na ggml, kwantyzacja GGUF 1.5–8 bit, narzędzia <code>llama-cli</code>/<code>llama-server</code>/<code>llama-bench</code>/<code>llama-quantize</code>, backendy Metal/CUDA/HIP/Vulkan/SYCL/CPU, parallel decoding, embeddingi i reranking, licencja MIT): <a href="https://github.com/ggml-org/llama.cpp">github.com/ggml-org/llama.cpp</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></p>
</li>
<li id="fn:2">
<p><code>ollama/ollama</code> — README (Ollama oparta na <code>llama.cpp</code> założonym przez Georgiego Gerganova, Modelfile, rejestr <code>ollama.com/library</code>, REST API na porcie 11434, licencja MIT): <a href="https://github.com/ollama/ollama">github.com/ollama/ollama</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>„Ollama&rsquo;s new engine for multimodal models&quot; — blog Ollamy (przejście części modeli, m.in. Llama 4, Gemma 3, Qwen 2.5 VL, Mistral Small 3.1, na własny silnik w Go korzystający bezpośrednio z ggml): <a href="https://ollama.com/blog/multimodal-models">ollama.com/blog/multimodal-models</a>.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>LM Studio — dokumentacja (aplikacja desktopowa uruchamiająca modele przez <code>llama.cpp</code>/GGUF, MLX na Apple Silicon, OpenAI Compatibility API i LM Studio REST API, CLI <code>lms</code>): <a href="https://lmstudio.ai/docs">lmstudio.ai/docs</a>.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p><code>LostRuins/koboldcpp</code> — README (fork/nakładka na <code>llama.cpp</code> w jednym pliku wykonywalnym, obsługa GGUF, API KoboldAI oraz OpenAI-compat <code>/v1</code>, UI KoboldAI Lite, licencja AGPL v3.0): <a href="https://github.com/LostRuins/koboldcpp">github.com/LostRuins/koboldcpp</a>.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</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><item><title>Nowy model? Na co patrzeć, zanim go ściągniesz</title><link>https://inferownia.pl/aktualnosci/nowy-model-na-co-patrzec/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +0200</pubDate><category>małe-modele</category><guid>https://inferownia.pl/aktualnosci/nowy-model-na-co-patrzec/</guid><description>Świeży model wpada na Hugging Face, scena huczy, a ty już masz palec nad przyciskiem pobierania. Zanim klikniesz, przejdź tę krótką listę — oszczędzi ci wieczoru walki z czymś, co i tak nie chciało ruszyć.</description><content:encoded><![CDATA[<p>„NEW SOTA MODEL DROPPED&quot;. Wątek puchnie w oczach,
w komentarzach lawina wykrzykników, ktoś już pokazuje screena, jak model rozwiązuje
zagadkę o wilku, kozie i kapuście, a ty siedzisz i czujesz, że folder <code>models</code> zaraz
znowu spuchnie o kolejne kilka giga. Palec sam wędruje nad „Download&quot;. I to jest właśnie
ten moment, w którym warto na sekundę zdjąć rękę z myszki.</p>
<p>Bo entuzjazm sceny jest szczery, ale scena nie odpala tego modelu na twojej karcie. Ty
odpalasz. I między „premiera na Hugging Face&quot; a „gada u ciebie w terminalu tak, jak powinno&quot;
leży kilka pułapek, które regularnie kosztują ludzi wieczór. Dobra wiadomość: da się je
odhaczyć w pięć minut, jeszcze zanim cokolwiek pobierzesz. Zrobimy z tego listę — taką,
którą przejdziesz przy każdym kolejnym „SOTA&quot;, bo modeli będzie jeszcze wysyp.</p>
<h2 id="karta-modelu-czyli-tabliczka-znamionowa">Karta modelu, czyli tabliczka znamionowa</h2>
<p>Zanim cokolwiek zrobisz — otwórz kartę modelu. Na Hugging Face to nie żaden osobny dokument,
tylko plik <code>README.md</code> w repo, a na jego górze siedzi kawałek metadanych w YAML-u.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>
To jest tabliczka znamionowa: licencja, model bazowy, czasem gotowe wyniki ewaluacji.
Cała ta lista, którą zaraz przejdziemy, w dużej mierze czyta się właśnie stamtąd. Nauczysz
się rzucać okiem na te kilka pól i połowa niespodzianek znika, zanim się wydarzy.</p>
<h2 id="ile-ten-model-waży-i-w-jakim-wariancie">Ile ten model waży i w jakim wariancie?</h2>
<p>Pierwsze pytanie brutalnie praktyczne: czy to w ogóle wejdzie? Liczba parametrów (te 7B,
8B, 70B w nazwie) to grube przybliżenie apetytu na pamięć, ale w pełnej precyzji nawet
„mały&quot; 8B to kilkanaście giga. Dlatego naprawdę interesuje cię, czy istnieje wersja
skwantyzowana — a konkretnie, czy ktoś zrobił już pliki <strong>GGUF</strong>, format, którego używają
<code>llama.cpp</code> i Ollama.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<p>Kwantyzacja to nic innego jak zapisanie tych miliardów wag ciaśniej: mniej bitów na liczbę,
mniejszy plik, mniejszy apetyt na VRAM, a jakość spada zaskakująco mało. Dokumentacja
<code>llama.cpp</code> podaje wprost skalę: Llama-3.1-8B w formacie F16 waży jakieś 14,96 GiB, a ta
sama w Q4_K_M — około 4,58 GiB.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> Trzy razy mniej, a <code>Q4_K_M</code> to poziom, który regularnie
pada jako rozsądny kompromis rozmiar-jakość. Co znaczą wszystkie te literki i cyferki po
<code>Q</code>, rozgryzaliśmy osobno <a href="/poradniki/nazwy-kwantyzacji-gguf/">tutaj</a>.</p>
<p>Jedno małe ostrzeżenie, żeby nie pomylić pojęć: kwantyzacja po treningu (to, co dostajesz
w GGUF-ie) to nie to samo, co trenowanie modelu od zera w niskiej precyzji. Dwie różne
rzeczy, różny wpływ na jakość. Ktoś na scenie zawsze je pomiesza — nie bądź tym kimś.</p>
<h2 id="czy-w-ogóle-wolno-ci-go-użyć">Czy w ogóle wolno ci go użyć?</h2>
<p>To pole, które ludzie przewijają najszybciej, a które potrafi najbardziej zaboleć.
W metadanych karty jest <code>license:</code> — identyfikator licencji, po którym Hub nawet pozwala
filtrować modele.<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> I tu jest haczyk: samo to, że repo jest otwarte i pliki leżą do
pobrania, nie znaczy, że wolno ci na tym modelu zbudować cokolwiek komercyjnego.</p>
<p>Część modeli jedzie na licencjach „społecznościowych&quot; albo „research only&quot;, które
uśmiechają się otwarcie, a w treści ograniczają użycie. Gdy w polu widzisz <code>license: other</code>,
to sygnał, żeby kliknąć w <code>license_link</code> i faktycznie przeczytać, na co się piszesz — bo
identyfikator sam z siebie nic ci nie gwarantuje.<sup id="fnref2:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Nudne? Nudne. Ale to jedyny punkt
z tej listy, który może się skończyć rozmową z prawnikiem, a nie z terminalem.</p>
<h2 id="jak-długi-kontekst-realnie-dostaniesz">Jak długi kontekst realnie dostaniesz?</h2>
<p>Model w karcie chwali się 128k kontekstu, więc bierzesz go do przełykania całych PDF-ów —
i tu wpadasz w klasyczne nieporozumienie. Deklarowane maksimum modelu to jedno, a okno,
które faktycznie ustawi ci runtime, to drugie.</p>
<p>Ollama na przykład sama dobiera domyślną długość kontekstu do tego, ile masz VRAM-u: przy
mniej niż 24 GiB da ci 4k, a dopiero przy 48 GiB i więcej sięga nawet po 256k.<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>
Czyli twój wypasiony 128k model może po cichu chodzić na czterech tysiącach tokenów, a ty
się dziwisz, czemu „zapomina&quot; początek dokumentu. Wymusisz to zmienną <code>OLLAMA_CONTEXT_LENGTH</code>
albo parametrem <code>num_ctx</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">OLLAMA_CONTEXT_LENGTH</span><span class="o">=</span><span class="m">64000</span> ollama serve
</span></span></code></pre></div><p>A ustawić to okno możesz na kilka sposobów i jest wśród nich hierarchia: <code>PARAMETER num_ctx</code>
w Modelfile ustala okno na stałe dla danego modelu, a opcje podane w locie w wywołaniu API
nadpisują to, co siedzi w Modelfile.<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> Tylko pamiętaj o dwóch kosztach: większe okno
to znacznie więcej pamięci — dokumentacja Ollamy mówi to wprost — a siłą rzeczy także wolniejsze
liczenie.<sup id="fnref1:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> Do tego rozciąganie okna ponad długość, na jakiej model trenowano, potrafi
pogorszyć jakość odpowiedzi; dokładnie ten problem rozbraja mechanizm YaRN, który rozłożyliśmy
na części <a href="/naukowy/yarn-kontekst/">tu</a>. W <code>llama.cpp</code> tym samym oknem steruje flaga <code>-c</code>.</p>
<h2 id="czy-narzędzie-w-ogóle-to-obsłuży">Czy narzędzie w ogóle to obsłuży?</h2>
<p>Najboleśniejsza pułapka premierowa: model jest na Hugging Face, więc zakładasz, że od razu
odpalisz go w <code>llama.cpp</code> czy Ollamie. A to wcale nie jest automatyczne. <code>llama.cpp</code> wymaga
formatu GGUF, a jak model przychodzi w innym formacie, ktoś musi go najpierw skonwertować
skryptami <code>convert_*.py</code> z repo (Hugging Face udostępnia do tego nawet przestrzeń
„GGUF-my-repo&quot;).<sup id="fnref1:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<p>Gorzej, gdy model przynosi <strong>nową architekturę</strong>. Wtedy wsparcia po prostu nie ma, dopóki
ktoś go nie dopisze — a to nie jest jedna linijka. Trzeba ruszyć skrypty konwersji w Pythonie
(mapowanie tensorów, <code>constants.py</code>, <code>tensor_mapping.py</code>), potem kod C++ (<code>llama-arch</code>,
<code>llama-model-loader</code>, <code>llama-model</code>, obsługa RoPE), a na koniec przetestować to na CPU, CUDA
i Metalu.<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> Dlatego dzień premiery modelu i dzień pierwszego działającego GGUF-a to
często dwie różne daty. Jak śledzisz świeżynkę, sprawdź najpierw, czy w issues repo ktoś już
nie ogarnia integracji — o tym, jak w ogóle działa ten cały ekosystem wokół GGUF-a, pisaliśmy
<a href="/aktualnosci/ekosystem-gguf/">osobno</a>.</p>
<h2 id="instruct-czy-base-sprawdź-zanim-się-zdziwisz">Instruct czy base? Sprawdź, zanim się zdziwisz</h2>
<p>Widzisz w nazwie samo „Llama-3-8B&quot; bez dopisku i myślisz, że pogadasz. Odpalasz, piszesz
pytanie, a model zamiast odpowiedzieć — dopisuje kolejne pytania. Nie zepsuł się. Wziąłeś
wersję <strong>base</strong>, która tylko kontynuuje sekwencję tokenów, zamiast wersji <strong>instruct/chat</strong>,
douczonej do podążania za poleceniami i dialogiem.<sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup></p>
<p>Różnica jest fundamentalna, a diabeł siedzi w formacie. Model czatowy oczekuje wiadomości
opakowanych w konkretne tokeny kontrolne — u jednego to <code>[INST]...[/INST]</code>, u innego
<code>&lt;|user|&gt;</code> i <code>&lt;|assistant|&gt;</code> — i co gorsza, dwa modele dotrenowane z tej samej bazy mogą mieć
zupełnie różny format czatu.<sup id="fnref1:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup> Podasz zły — i instruct-model, który powinien świecić,
nagle gada od rzeczy. W bibliotece <code>transformers</code> załatwia to <code>apply_chat_template</code>, który
sam skleja wiadomości w format właściwy dla danego modelu.<sup id="fnref2:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup> W <code>llama.cpp</code> odpowiada za
to <code>--jinja</code>. Morał krótki: zajrzyj do karty i upewnij się, który to wariant, zanim ogłosisz,
że „model jest słaby&quot;.</p>
<h2 id="benchmarki-tak-ale-z-ręką-na-sercu">Benchmarki? Tak, ale z ręką na sercu</h2>
<p>I na koniec ta tabelka, przez którą w ogóle klikasz „Download&quot; — słupki, w których nowy
model bije wszystko dookoła. Model card może nawet nieść ustrukturyzowane wyniki w polu
<code>model-index</code>, z zadaniem, datasetem, metryką i źródłem.<sup id="fnref3:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Fajnie. Tylko traktuj to jak
deklarację producenta, nie jak wyrok.</p>
<p>Jest udokumentowany problem zwany <strong>benchmark data contamination</strong>: dane testowe benchmarku
wyciekają do zbioru treningowego modelu, przez co wynik na tym teście rośnie bez realnej
poprawy jakości.<sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup> Model po prostu „widział odpowiedzi&quot; na egzaminie. To nie teoria
spiskowa — poświęcono temu prace przeglądowe. Dlatego liczba w tabelce mówi ci co najwyżej,
że warto sprawdzić samemu na swoim zastosowaniu, a nie że sprawa zamknięta. Najlepszy
benchmark i tak jest jeden: twój własny prompt, którego nikt nie mógł wcześniej wkuć.</p>
<h2 id="zrób-to-sam-lista-na-lodówkę">Zrób to sam: lista na lodówkę</h2>
<p>Cała ta gadka sprowadza się do sześciu spojrzeń w kartę modelu, zanim ruszysz pobieranie:</p>
<table>
	<thead>
			<tr>
					<th>Sprawdź</th>
					<th>Gdzie</th>
					<th>Czerwona flaga</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Rozmiar i wariant</td>
					<td>liczba parametrów, czy jest GGUF (np. <code>Q4_K_M</code>)</td>
					<td>tylko pełna precyzja, brak GGUF</td>
			</tr>
			<tr>
					<td>Licencja</td>
					<td>pole <code>license</code> / <code>license_link</code></td>
					<td><code>other</code> bez przeczytanej treści, „research only&quot;</td>
			</tr>
			<tr>
					<td>Kontekst</td>
					<td>deklaracja modelu vs domyślne okno runtime&rsquo;u</td>
					<td>liczysz na 128k, dostajesz 4k</td>
			</tr>
			<tr>
					<td>Wsparcie narzędzia</td>
					<td>GGUF gotowy? architektura znana <code>llama.cpp</code>?</td>
					<td>świeża architektura, brak konwertera</td>
			</tr>
			<tr>
					<td>Instruct vs base</td>
					<td>dopisek w nazwie, karta modelu</td>
					<td>base tam, gdzie chcesz rozmawiać</td>
			</tr>
			<tr>
					<td>Benchmarki</td>
					<td>tabelka wyników, metodologia</td>
					<td>same słupki bez metody, ryzyko skażenia</td>
			</tr>
	</tbody>
</table>
<p>Wydrukuj, przyklej nad biurkiem, albo po prostu zapamiętaj rytm: waga, licencja, kontekst,
wsparcie, wariant, benchmarki. Przy trzecim nowym modelu przejdziesz to odruchowo.</p>
<p>Bo świeży model to trochę jak używane auto z ogłoszenia: zdjęcia lśnią, opis zachwala, scena
piszczy z zachwytu. Ale to ty siadasz za kierownicą i to ty potem stoisz na poboczu, jak się
okaże, że pod maską nie ma silnika pasującego do twojej karty. Pięć minut zaglądania pod
maskę — kartę modelu — kosztuje mniej niż wieczór klikania w „czemu to nie działa&quot;.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Hugging Face Hub docs — Model Cards (metadane: <code>license</code>, <code>license_name</code>/<code>license_link</code>, <code>base_model</code>/<code>base_model_relation</code>, <code>model-index</code>), <a href="https://huggingface.co/docs/hub/en/model-cards">huggingface.co/docs/hub/en/model-cards</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></p>
</li>
<li id="fn:2">
<p>llama.cpp — wymóg formatu GGUF, konwersja skryptami <code>convert_*.py</code>, przestrzeń „GGUF-my-repo&quot;, <a href="https://github.com/ggml-org/llama.cpp/blob/master/README.md">README repo ggml-org/llama.cpp</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></p>
</li>
<li id="fn:3">
<p>llama.cpp — poziomy kwantyzacji GGUF (IQ*, Q2_K–Q8_0, F16) oraz przykład Llama-3.1-8B: F16 ≈ 14,96 GiB, Q4_K_M ≈ 4,58 GiB, <a href="https://github.com/ggml-org/llama.cpp/blob/master/tools/quantize/README.md">tools/quantize/README.md</a>.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Ollama docs — Context length: domyślne dobieranie okna kontekstu do VRAM (poniżej 24 GiB → 4k, 24–48 GiB → 32k, od 48 GiB → 256k), zmienna <code>OLLAMA_CONTEXT_LENGTH</code> oraz uwaga o rosnącym koszcie pamięci, <a href="https://docs.ollama.com/context-length">docs.ollama.com/context-length</a>.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Ollama docs — <code>PARAMETER num_ctx</code> (okno kontekstu modelu) w Modelfile oraz nadpisywanie parametrów Modelfile opcjami podanymi w locie przez API, <a href="https://github.com/ollama/ollama/blob/main/docs/modelfile.mdx#valid-parameters-and-values">docs/modelfile.mdx</a>.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>llama.cpp — kroki dodania wsparcia nowej architektury (skrypty konwersji w Pythonie + kod C++ + testy CPU/CUDA/Metal), <a href="https://github.com/ggml-org/llama.cpp/blob/master/docs/development/HOWTO-add-model.md">docs/development/HOWTO-add-model.md</a>.&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p>Hugging Face transformers docs — Chat templating: różnica base vs instruct/chat, tokeny kontrolne, <code>apply_chat_template</code>, ryzyko złego formatu promptu, <a href="https://huggingface.co/docs/transformers/en/chat_templating">huggingface.co/docs/transformers/en/chat_templating</a>.&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:8">
<p>„Benchmark Data Contamination of Large Language Models: A Survey&quot;, przegląd problemu przecieku danych ewaluacyjnych do zbioru treningowego, <a href="https://arxiv.org/abs/2406.04244">arXiv:2406.04244</a>.&#160;<a href="#fnref:8" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Ekosystem GGUF: skąd się biorą modele, które odpalasz</title><link>https://inferownia.pl/aktualnosci/ekosystem-gguf/</link><pubDate>Wed, 01 Jul 2026 00:00:00 +0200</pubDate><category>kwantyzacja</category><guid>https://inferownia.pl/aktualnosci/ekosystem-gguf/</guid><description>Ściągasz jeden plik z Hugging Face, dwuklik i model gada. Ale kto go w ogóle zrobił, czemu wariantów jest dwadzieścia i co siedzi w środku tego jednego .gguf? Rozbieramy fabrykę.</description><content:encoded><![CDATA[<p>Wchodzisz na Hugging Face po świeży model, otwierasz stronę i widzisz listę plików jak menu w barze mlecznym, w którym wszystko nazywa się prawie tak samo. <code>Q4_K_M</code>, <code>Q5_K_S</code>, <code>Q6_K</code>, <code>Q8_0</code>, <code>IQ4_XS</code>, gdzieś niżej jakiś <code>BF16</code> na 30 gigabajtów. Wszystkie kończą się na <code>.gguf</code>, wszystkie są tego samego modelu, a ty stoisz i myślisz: dobra, ale który? I skąd się to w ogóle wzięło, skoro autor modelu wrzucił coś zupełnie innego?</p>
<p>To jest ten moment, w którym większość ludzi bierze na chybił trafił ten <code>Q4_K_M</code>, bo tak radzą na Reddicie, odpala i idzie dalej. I słusznie — działa. Ale pod tym jednym plikiem stoi cała cicha fabryka: ludzie, narzędzia, konwersje i format, który ktoś kiedyś usiadł i zaprojektował, żebyś ty mógł zrobić dwuklik. Warto wiedzieć, co tam mruczy pod maską — bo dzięki temu przestajesz zgadywać, a zaczynasz wybierać świadomie.</p>
<h2 id="co-to-w-ogóle-jest-ten-gguf">Co to w ogóle jest, ten GGUF</h2>
<p>Zacznijmy od rozczarowania, bo lepiej od razu: <strong>GGUF to nie jest model</strong>. To nie nowa architektura, nie nowy sposób trenowania, nie magiczny AI od ggerganova. To pojemnik. Format pliku — dosłownie „GGML Universal File” — do trzymania modelu na dysku tak, żeby silnik inferencyjny mógł go szybko wczytać i zacząć gadać.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Model (Llama, Qwen, Mistral — cokolwiek) rodzi się gdzie indziej, w treningu na klastrze, którego rachunku za prąd żaden z nas nie chciałby zobaczyć. GGUF to dopiero opakowanie, w które ten wytrenowany model się przepakowuje na koniec, pod kątem tego, że odpalisz go u siebie na jednej karcie.</p>
<p>Format wymyślił Georgi Gerganov, ten sam, który stoi za <code>llama.cpp</code> i biblioteką <code>ggml</code>.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> GGUF jest następcą kilku starszych formatów o równie uroczych nazwach — GGML, GGMF, GGJT — które po drodze się wykruszyły. I tu pierwsza pułapka, w którą łatwo wdepnąć: <strong>GGML i GGUF to nie synonimy</strong>. GGML to ten stary, w dużej mierze wyparty format pliku (i przy okazji wciąż nazwa biblioteki tensorowej pod spodem), a GGUF to jego młodszy, mądrzejszy brat z porządnymi metadanymi.<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<h2 id="co-siedzi-w-tym-jednym-pliku">Co siedzi w tym jednym pliku</h2>
<p>To jest właśnie sedno, dla którego GGUF wygrał. Otwórz sobie w głowie taki plik jak pudełko z przegródkami:</p>
<ul>
<li><strong>Nagłówek</strong> — na samym początku cztery bajty <code>GGUF</code> (magic number, bajt po bajcie <code>0x47 0x47 0x55 0x46</code>), zaraz za nimi numer wersji formatu, liczba tensorów i sterta metadanych.<sup id="fnref2:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></li>
<li><strong>Metadane klucz-wartość</strong> — i tu robi się ciekawie. Zamiast beztypowej listy liczb jak w starych formatach, GGUF trzyma nazwane pola w hierarchii z kropkami: <code>general.architecture</code>, <code>llama.context_length</code>, <code>general.quantization_version</code>. Klucz <code>general.architecture</code> (czym ten model właściwie jest) musi być zawsze, a jak model jest skwantowany, to musi być też <code>general.quantization_version</code>.<sup id="fnref3:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></li>
<li><strong>Tokenizer</strong> — w środku, w tym samym pliku. To jest ten myk, którego naprawdę brakowało wcześniej. GGUF potrafi wnieść cały tokenizer: natywne tablice tokenów, scores i merges, albo pełny <code>tokenizer.json</code> prosto z Hugging Face, albo szablony czatu w Jinja.<sup id="fnref4:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Dlatego jeden <code>.gguf</code> wystarcza — nie musisz dociągać pięciu plików obok.</li>
<li><strong>Same wagi</strong> — na końcu surowe dane tensorów, wyrównane do granicy (domyślnie co 32 bajty, klucz <code>general.alignment</code>), żeby silnik mógł je wczytać przez <code>mmap</code> bez mielenia całego pliku.<sup id="fnref5:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></li>
</ul>
<div class="callout">
  <span class="callout__label">Dlaczego to ważne dla lokalnego LLM</span>
<p>To „wszystko w jednym pliku” jest dokładnie tym, co robi z GGUF format do <strong>lokalnej</strong> inferencji. Ściągasz jeden plik i masz komplet: wagi, architekturę i tokenizer. Nie ma cyrku z dociąganiem <code>config.json</code>, <code>tokenizer.json</code> i trzech innych obok. Silnik typu <code>llama.cpp</code> bierze go przez <code>mmap</code> i startuje w sekundę, bez deserializacji. Mniej ruchomych części to mniej rzeczy, które ci się rozjadą o 23:00, gdy chcesz tylko o coś zapytać model.</p>
</div>
<h2 id="skąd-biorą-się-wszystkie-te-warianty">Skąd biorą się wszystkie te warianty</h2>
<p>No dobra, a czemu tego samego modelu jest dwadzieścia sztuk? Bo GGUF wspiera całą tęczę typów tensorów: pełną precyzję (F32, F16, BF16), serię K (<code>Q2_K</code> do <code>Q6_K</code>), stare legacy (<code>Q4_0</code>, <code>Q8_0</code>), nowszą serię IQ opartą na macierzy istotności (<code>IQ4_XS</code>, <code>IQ2_XXS</code>) i egzotykę w stylu ternarnego <code>TQ1_0</code>.<sup id="fnref6:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Każdy z tych <code>Q</code>-ów to inny schemat ściśnięcia wag. <code>Q4_K</code> upycha wagę w jakichś 4,5 bita, <code>Q6_K</code> w 6,56, <code>Q2_K</code> w 2,63 — i to są dokładnie te przegródki, w których leży cały kompromis rozmiar/jakość/szybkość.<sup id="fnref1:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<p>I tu druga pułapka: <strong>te warianty to nie są różne modele</strong>. <code>Q4_K_M</code>, <code>Q5_K_S</code>, <code>Q8_0</code> tego samego modelu to te same wagi bazowe, tylko skompresowane innym sitem. Grubszy plik = bliżej oryginału i wolniej, chudszy = mniej VRAM-u i szybciej, ale z lekkim ubytkiem jakości. Co ważne, to jest kwantyzacja <strong>post-training</strong> — bierzesz gotowy, wytrenowany model i ściskasz go po fakcie. Nie mylić z trenowaniem od zera w niskiej precyzji. W nazewnictwo tych literek zagłębialiśmy się osobno <a href="/poradniki/nazwy-kwantyzacji-gguf/">tutaj</a>, bo to temat na własny wpis.</p>
<h2 id="kto-to-wszystko-właściwie-robi">Kto to wszystko właściwie robi</h2>
<p>Teraz najlepsze. Ten <code>Q4_K_M</code>, który ściągasz — bardzo często <strong>nie zrobił go autor modelu</strong>. Autor wrzuca zwykle wagi w safetensors i może jedną-dwie wersje GGUF, a resztę tego bufetu produkują ludzie z boku: community-quantizerzy. Konta jak bartowski czy mradermacher (dawniej głośny TheBloke) siedzą i masowo mielą cudze modele na wszystkie możliwe warianty, dzień po dniu, jak taka lokalna piekarnia GGUF-ów.</p>
<p>Jak to robią bez klastra? Dwiema drogami. Pierwsza to skrypt <code>convert_hf_to_gguf.py</code> prosto z repo <code>llama.cpp</code> — łyka wagi z Hugging Face i wypluwa GGUF, obsługuje kupę architektur (w tym multimodalne i MoE), ma opcje <code>--outtype</code> (bierzesz <code>f16</code>, <code>bf16</code>, <code>q8_0</code>… — czym te „szesnastki&quot; różnią się od siebie, jest <a href="/poradniki/bf16-f16-f32/">tu</a>), <code>--vocab-only</code>, <code>--split-max-size</code> do krojenia wielkich plików, a nawet <code>--remote</code>, żeby konwertować prosto z Huba.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> Druga droga jest jeszcze leniwsza: <code>ggml-org/gguf-my-repo</code>, gotowy Space na Hugging Face, gdzie wklejasz repo, klikasz i dostajesz komplet kwantyzacji bez kompilowania czegokolwiek u siebie.<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"><span class="c1"># klasyczna droga: własny build llama.cpp</span>
</span></span><span class="line"><span class="cl">python convert_hf_to_gguf.py ./Qwen2.5-7B-Instruct <span class="se">\
</span></span></span><span class="line"><span class="cl">  --outtype f16 --outfile qwen2.5-7b-f16.gguf
</span></span><span class="line"><span class="cl"><span class="c1"># a potem osobno kwantyzacja narzędziem llama-quantize do Q4_K_M itd.</span>
</span></span></code></pre></div><p>Sam Hugging Face traktuje ten format jak pełnoprawnego obywatela: masz filtr <code>library=gguf</code>, który pokazuje wszystko, co gęgnięte (<a href="https://huggingface.co/models?library=gguf">hf.co/models?library=gguf</a>), a na stronie konkretnego pliku podejrzysz metadane i listę tensorów wprost z przeglądarki, bez ściągania.<sup id="fnref2:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> Praktyczny wniosek: skoro warianty sypie kto chce, <strong>sprawdzaj, czyje repo bierzesz</strong>. Zerknij na autora, na to, czy imatrix był użyty, czy karta modelu ma ręce i nogi. Na co patrzeć przy świeżym modelu rozkładaliśmy <a href="/aktualnosci/nowy-model-na-co-patrzec/">w osobnej notce</a>.</p>
<h2 id="gguf-kontra-safetensors-to-nie-jest-ranking">GGUF kontra safetensors: to nie jest ranking</h2>
<p>Skoro autor wrzuca safetensors, a ty odpalasz GGUF — który jest lepszy? Źle postawione pytanie. To dwa pojemniki do dwóch różnych rzeczy.</p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>GGUF</th>
					<th>safetensors</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Co trzyma</td>
					<td>wagi + architektura + tokenizer, w jednym pliku</td>
					<td><strong>tylko</strong> tensory, goła macierz liczb</td>
			</tr>
			<tr>
					<td>Metadane / tokenizer</td>
					<td>w środku</td>
					<td>osobno (<code>config.json</code>, <code>tokenizer.json</code>)</td>
			</tr>
			<tr>
					<td>Kwantyzacja</td>
					<td>tak, to jego chleb (<code>Q4_K_M</code>, IQ…)</td>
					<td>zwykle pełna precyzja lub bf16/fp16</td>
			</tr>
			<tr>
					<td>Pod co skrojony</td>
					<td>inferencja przez <code>llama.cpp</code>/<code>ggml</code>, CPU/GPU</td>
					<td>trening/inferencja w PyTorch, transformers, diffusers</td>
			</tr>
			<tr>
					<td>Po co powstał</td>
					<td>jeden plik gotowy do odpalenia</td>
					<td>bezpieczna, szybka (zero-copy) alternatywa dla <code>.bin</code>/pickle</td>
			</tr>
	</tbody>
</table>
<p>Safetensors narodził się z zupełnie innej potrzeby: żeby nie ładować cudzych wag przez <code>pickle</code> (który potrafi odpalić dowolny kod przy wczytaniu) i żeby robić to szybko, bez kopiowania w pamięci.<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> To rekomendowany format wag na Hugging Face — ale trzyma <strong>wyłącznie</strong> tensory. Chcesz z niego odpalić model, musisz dorzucić config i tokenizer obok. GGUF ten cały komplet nosi w sobie i dokłada kwantyzację. Nie „lepszy/gorszy” — inne zadanie. Jak chcesz to potem faktycznie uruchomić garścią flag, jest o tym <a href="/poradniki/flagi-llama-cpp/">osobny poradnik</a>.</p>
<h2 id="wróć-do-tego-menu">Wróć do tego menu</h2>
<p>Wróć teraz do tego menu na Hugging Face, do listy dwudziestu plików kończących się na <code>.gguf</code>. Już nie wygląda jak bar mleczny, w którym wszystko nazywa się tak samo. Widzisz teraz, że ktoś wziął model wytrenowany na drugim końcu świata, przepuścił go przez <code>convert_hf_to_gguf.py</code>, upchał wagi, architekturę i tokenizer do jednego pudełka z przegródkami, ścisnął sitem <code>Q4_K_M</code> i wrzucił na Huba, żebyś ty mógł go zassać jednym plikiem i usłyszeć, jak mruczy pod biurkiem. Cała ta cicha fabryka — od klastra treningowego, przez konto bartowskiego, po twój dysk — sprowadza się do jednego czterobajtowego napisu na początku pliku: <code>GGUF</code>. Reszta to już tylko ty i pytanie, który wariant dziś bierzesz.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Specyfikacja formatu: struktura pliku, magic number <code>GGUF</code> (<code>0x47 0x47 0x55 0x46</code>), wersje 1–3, wyrównanie <code>general.alignment</code>, wymagane klucze metadanych, typy tensorów i osadzanie tokenizera, historia jako następca GGML/GGMF/GGJT — <a href="https://github.com/ggml-org/ggml/blob/master/docs/gguf.md">ggml-org/ggml, <code>docs/gguf.md</code></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>Twórca formatu (@ggerganov), integracja z Hub (tag <code>library=gguf</code>, viewer metadanych i tensorów), narzędzie <code>gguf-my-repo</code>, porównanie z safetensors oraz tabela typów kwantyzacji z bitami-na-wagę — <a href="https://huggingface.co/docs/hub/en/gguf">Hugging Face Hub docs, GGUF</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>Mechanizm konwersji modeli HF do GGUF, obsługiwane architektury i opcje <code>--outtype</code>, <code>--vocab-only</code>, <code>--split-max-size</code>, <code>--remote</code> — <a href="https://github.com/ggml-org/llama.cpp/blob/master/convert_hf_to_gguf.py">llama.cpp, <code>convert_hf_to_gguf.py</code></a>.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Narzędzie, którym community-quantizerzy konwertują i kwantyzują dowolne wagi HF do GGUF bez lokalnej kompilacji <code>llama.cpp</code> — <a href="https://huggingface.co/spaces/ggml-org/gguf-my-repo">ggml-org/gguf-my-repo (Hugging Face Space)</a>.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Definicja safetensors jako bezpiecznego (bez deserializacji pickle), zero-copy formatu przechowywania samych tensorów oraz jego rola w ekosystemie transformers/diffusers/candle — <a href="https://huggingface.co/docs/safetensors/index">Hugging Face, dokumentacja safetensors</a>.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item></channel></rss>