<?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>Poradniki — Inferownia</title><link>https://inferownia.pl/poradniki/</link><description>Praktyczne how-to: uruchamianie modeli lokalnie, kwantyzacja krok po kroku, budowa 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/poradniki/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>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>KV-cache: dlaczego długi kontekst zjada VRAM</title><link>https://inferownia.pl/poradniki/kv-cache-vram/</link><pubDate>Tue, 14 Jul 2026 00:00:00 +0200</pubDate><category>inferencja</category><guid>https://inferownia.pl/poradniki/kv-cache-vram/</guid><description>Wagi się załadowały, model wszedł na kartę z zapasem — a po dłuższej rozmowie znów wyskakuje out of memory. Winny nie jest model. Winny jest bufor, który puchnie z każdym tokenem.</description><content:encoded><![CDATA[<p>Znasz ten moment, kiedy model ładnie wszedł na kartę, zostało ci jeszcze ze dwa giga
zapasu, odpalasz dłuższą rozmowę albo wklejasz wielki plik do streszczenia — i po
kilkuset tokenach terminal wita cię tym samym, co zawsze: <code>CUDA out of memory</code>. Cała
na biało. I człowiek stoi zdziwiony, bo przecież model się mieścił. Wagi się nie
rozrosły. Karta nie zmalała. To co, u licha, zeżarło resztę pamięci między jednym
tokenem a drugim?</p>
<p>To wręcz podręcznikowy scenariusz — ktoś odpala 7B na karcie 8 GB, chwali się, że
działa, a dwa dni później wraca z płaczem, że „przy dłuższym kontekście się sypie&quot;.
I zawsze pada to samo pytanie w komentarzach: a ustawiłeś sobie cache? No właśnie.
Bo obok wag, które raz się ładują i grzecznie leżą, siedzi drugi żłob na VRAM —
taki, który rośnie z każdym słowem, jakie model przeczyta albo napisze. Nazywa się
KV-cache i jest głównym podejrzanym w prawie każdej sprawie „mieściło się, a potem
przestało&quot;.</p>
<h2 id="skąd-w-ogóle-bierze-się-ten-cache">Skąd w ogóle bierze się ten cache</h2>
<p>Cofnijmy się o krok. Model generuje tekst token po tokenie — jeden na raz, a każdy
kolejny patrzy wstecz na całą dotychczasową historię. Żeby policzyć uwagę (attention)
dla nowego tokenu, potrzebuje wektorów <strong>K</strong> (key) i <strong>V</strong> (value) dla wszystkiego,
co było wcześniej. I teraz klucz do zrozumienia: te wektory się nie zmieniają. K i V
dla trzeciego tokenu są takie same, kiedy generujesz token dziesiąty, jak wtedy,
gdy generowałeś czwarty.</p>
<p>Skoro się nie zmieniają — po co je liczyć od nowa przy każdym kroku? No i nie liczymy.
KV-cache to dokładnie ten bufor, w którym model odkłada raz wyliczone wektory K i V
dla każdego przetworzonego tokenu, w każdej warstwie, żeby przy generowaniu następnego
nie przemielać całej historii od zera.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> To sprytny handel: dokładasz pamięci,
żeby oszczędzić obliczenia. Bez niego każdy nowy token oznaczałby przeliczanie całego
kontekstu w kółko — decode zamieniłby się w mękę.</p>
<p>Cache jest więc dobry. Problem w tym, że za oszczędność obliczeń płacisz pamięcią.
A ta pamięć nie jest stała.</p>
<h2 id="dlaczego-on-rośnie-a-wagi-nie">Dlaczego on rośnie, a wagi nie</h2>
<p>Tu jest sedno całej sprawy, więc rozłóżmy to powoli. Wagi modelu to wartość stała.
Ładujesz plik GGUF raz, siada na karcie i tyle — czy generujesz jeden token, czy
dziesięć tysięcy, wagi zajmują dokładnie tyle samo. KV-cache zachowuje się odwrotnie:
puchnie z każdym tokenem, jaki wpadnie w okno kontekstu.<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>Ile dokładnie puchnie? Mechanizm da się złapać jednym wzorem, który krąży po całej
scenie:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">rozmiar_KV = 2 × n_layer × n_kv_heads × head_dim × długość_kontekstu × bajty_na_element
</span></span></code></pre></div><p>Rozbierzmy to na palcach. Dwójka na przedzie to osobno tensor K i osobno tensor V —
trzymasz dwa, stąd współczynnik.<sup id="fnref2:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> <code>n_layer</code> to liczba warstw transformera; cache
odkłada się w każdej z osobna. <code>n_kv_heads</code> to liczba głów KV, <code>head_dim</code> to wymiar
jednej głowy. A <code>długość_kontekstu</code> — no i tu jest pies pogrzebany: to jedyny człon,
który rośnie w trakcie. Reszta to stałe architektury danego modelu.</p>
<p>Zauważ, co z tego wynika: rozmiar cache rośnie <strong>liniowo</strong> z długością kontekstu.
Podwoisz kontekst — podwoisz cache. I liniowo z liczbą warstw oraz głów KV. Ani razu
w tym wzorze nie pada rozmiar wag.<sup id="fnref3:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> To dwa niezależne budżety pamięci.</p>
<div class="callout">
  <span class="callout__label">Dlaczego to ważne dla lokalnego LLM</span>
<p>Częsty mit z forów: „KV-cache rośnie kwadratowo z kontekstem&quot;. Nie. <strong>Sam bufor rośnie
liniowo</strong> — podwajasz tokeny, podwajasz cache. Kwadratowy jest koszt <em>obliczeniowy</em>
samej operacji attention w naiwnej implementacji (i pamięć pośrednia, którą ona
zżera po drodze) — i to właśnie ten kwadratowy narzut łagodzi flash attention. Pamięć
samego cache to prosta linia w górę.<sup id="fnref4:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
</div>
<p>Skoro cache rośnie z kontekstem, a wagi stoją w miejscu, to przy dostatecznie długim
oknie następuje przecięcie: KV-cache zaczyna dominować zużycie VRAM, a przy naprawdę
długim kontekście potrafi przerosnąć rozmiar samych wag.<sup id="fnref5:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> I stąd twoje <code>out of memory</code> w połowie rozmowy — nie model spuchł, tylko jego pamięć krótkotrwała napęczniała
do rozmiarów, których karta nie udźwignęła. Jeśli chcesz policzyć ten budżet z wyprzedzeniem,
zamiast zgadywać, sam wzór na VRAM rozkładaliśmy krok po kroku <a href="/poradniki/ile-vram-na-model/">tutaj</a>.</p>
<h2 id="jak-go-ścisnąć--trzy-dźwignie">Jak go ścisnąć — trzy dźwignie</h2>
<p>Dobra wiadomość: skoro wiesz, z czego składa się wzór, wiesz też, gdzie przyłożyć.
Masz trzy dźwignie, każda ciągnie za inny człon.</p>
<h3 id="kwantyzacja-cache--najprostszy-zysk">Kwantyzacja cache — najprostszy zysk</h3>
<p>Spójrz na ostatni człon wzoru: <code>bajty_na_element</code>. Domyślnie cache K i V w llama.cpp
siedzą w <code>f16</code>, czyli po 2 bajty na element<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> — a czym <code>f16</code> różni się od <code>bf16</code>, tłumaczymy <a href="/poradniki/bf16-f16-f32/">tutaj</a>. A nikt nie powiedział, że musi
tak zostać. <code>llama.cpp</code> daje dwie flagi — <code>-ctk</code> (<code>--cache-type-k</code>) i <code>-ctv</code>
(<code>--cache-type-v</code>) — którymi ustawiasz typ danych osobno dla cache K i cache V.
Dozwolone wartości to <code>f32</code>, <code>f16</code>, <code>bf16</code>, <code>q8_0</code>, <code>q4_0</code>, <code>q4_1</code>, <code>iq4_nl</code>, <code>q5_0</code>,
<code>q5_1</code>; domyślnie oba stoją na <code>f16</code>.<sup id="fnref1:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<p>Matematyka jest tu boleśnie prosta. <code>q8_0</code> to 1 bajt na element zamiast dwóch — czyli
z grubsza <strong>dwa razy mniej</strong> pamięci na cache. <code>q4_0</code> to pół bajta — jakieś <strong>cztery
razy mniej</strong> niż <code>f16</code>.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> Nie podam ci, ile to konkretnie megabajtów, bo to zależy
od <code>n_layer</code>, <code>n_kv_heads</code>, <code>head_dim</code> i długości kontekstu twojego modelu — kto rzuca
sztywnym „zaoszczędzisz 3 GB&quot;, ten zgaduje. Ale proporcja trzyma się zawsze: ścinasz
typ, ścinasz cache w tym samym stosunku.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">llama-server -m model-q4_k_m.gguf -c <span class="m">8192</span> -fa on -ctk q8_0 -ctv q8_0
</span></span></code></pre></div><p>I tu ważne rozróżnienie, na którym potyka się pół forum: <strong>to nie jest to samo, co
kwantyzacja wag</strong>. Wagi kwantyzujesz przy pakowaniu GGUF-a (te wszystkie <code>Q4_K_M</code>
i spółka — nazewnictwo rozbieraliśmy <a href="/poradniki/nazwy-kwantyzacji-gguf/">tutaj</a>).
Cache kwantyzujesz flagami runtime, w locie. To dwa niezależne mechanizmy. Ściśnięcie
wag do 4 bitów ani o bajt nie zmniejszy KV-cache, i odwrotnie. Ktoś odpala model
w <code>Q4_K_M</code> i dziwi się, że kontekst dalej zjada pamięć — no bo cache dalej siedzi
w <code>f16</code>, dopóki mu tego ręcznie nie zmienisz.</p>
<h3 id="flash-attention--i-warunek-wstępny">Flash attention — i warunek wstępny</h3>
<p>Druga dźwignia to <code>-fa</code> (<code>--flash-attn</code>). Flaga przyjmuje <code>on</code>, <code>off</code> albo <code>auto</code>,
domyślnie stoi na <code>auto</code>.<sup id="fnref2:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> Co robi flash attention? To nie żadne przybliżenie
uwagi — i tu kolejna pułapka do ominięcia. FlashAttention (Dao i in., NeurIPS 2022)
to algorytm <strong>dokładny</strong> (exact): liczy dokładnie to samo, co naiwna uwaga, wynik
liczbowy jest identyczny.<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> Sztuczka jest gdzie indziej — jest „IO-aware&quot;, czyli
ogranicza liczbę odczytów i zapisów między wolną pamięcią HBM karty a szybką pamięcią
on-chip SRAM. Efekt: szybciej i z mniejszym narzutem pamięci pomocniczej niż naiwna
implementacja uwagi.</p>
<p>Ale flash attention ma tu jeszcze jedną, praktyczną rolę — jest <strong>warunkiem wstępnym</strong>
kwantyzacji cache V. W llama.cpp nie da się skwantyzować cache V bez włączonego flash
attention; implementacja zwyczajnie odmawia startu, rzucając runtime error w rodzaju
<code>quantized V cache was requested, but this requires Flash Attention</code>.<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> Czyli jeśli chcesz <code>-ctv q8_0</code>, musisz najpierw dać <code>-fa on</code>.
Bez tego dostaniesz błąd, nie oszczędność.</p>
<h3 id="gqa--dźwignia-której-nie-przełączysz">GQA — dźwignia, której nie przełączysz</h3>
<p>Trzeci człon wzoru to <code>n_kv_heads</code>. Im mniej głów KV, tym mniejszy cache — wprost,
liniowo. I dokładnie na to celuje <strong>Grouped-Query Attention</strong> (GQA, Ainslie i in.,
EMNLP 2023).<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> Idea: zamiast tylu głów KV, ile jest głów zapytań (pełna uwaga
wielogłowowa, MHA), albo tylko jednej wspólnej (multi-query attention, MQA), bierzesz
liczbę pośrednią. Kilka głów zapytań dzieli jedną głowę KV. Skoro <code>n_kv_heads</code> w naszym
wzorze spada — spada i cache, przy jakości bliskiej pełnemu MHA i szybkości bliskiej
MQA.<sup id="fnref1:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup></p>
<p>Co ciekawe, model GQA nie powstaje od zera. Bierze się istniejący checkpoint MHA
i „doucza&quot; (uptraining) przy jakichś 5% oryginalnego budżetu treningu — tanio, bez
budowania nowej architektury od podstaw.<sup id="fnref2:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup></p>
<p>Jest tylko jeden haczyk, o którym musisz pamiętać: <strong>GQA to nie przełącznik</strong>.
To cecha architektury zapieczona w modelu podczas treningu. Nie ma flagi CLI, która
„włączy GQA&quot; na dowolnym modelu. Możesz co najwyżej <em>wybrać</em> model, który już ma
GQA — a większość nowszych 7B/8B ma — i taki z natury nosi mniejszy cache przy tej
samej długości kontekstu co stary model z pełnym MHA. To kryterium przy pobieraniu,
nie pokrętło w terminalu.</p>
<h2 id="zrób-to-sam-kolejność-ratowania-pamięci">Zrób to sam: kolejność ratowania pamięci</h2>
<p>No dobra, dość teorii — masz <code>out of memory</code> i chcesz uratować kontekst. Kolejność
ma znaczenie, bo pierwsze ruchy kosztują cię grosze na jakości, a ostatnie bolą.</p>
<ol>
<li><strong>Najpierw <code>-fa on</code>.</strong> Ścina narzut pamięci samej operacji uwagi i — co równie
ważne — odblokowuje kwantyzację cache V. To krok zero, nie negocjujemy.<sup id="fnref1:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup></li>
<li><strong>Potem skwantyzuj cache: <code>-ctk q8_0 -ctv q8_0</code>.</strong> <code>q8_0</code> to bezpieczny wybór —
połowa pamięci cache, strata jakości ledwo wyczuwalna. Dopiero gdy VRAM dalej
piszczy, schodź do <code>-ctk q4_0 -ctv q4_0</code> — ćwiartka pamięci, ale za to już płacisz
odczuwalniej jakością.<sup id="fnref1:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></li>
<li><strong>Wybierając model, celuj w te z GQA.</strong> Tego nie zrobisz flagą po fakcie — to
decyzja przy pobieraniu. Model z GQA startuje z mniejszym cache, zanim jeszcze
dotkniesz jakiegokolwiek przełącznika.<sup id="fnref3:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup></li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># krok 1 + 2 razem — tak wygląda ratunek w praktyce</span>
</span></span><span class="line"><span class="cl">llama-server -m model-q4_k_m.gguf -c <span class="m">16384</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -fa on -ctk q8_0 -ctv q8_0
</span></span></code></pre></div><p>Dopiero kiedy to wszystko wyczerpiesz, a i tak nie wchodzi — wtedy tnij samą długość
kontekstu (<code>-c</code>) albo schodź z warstw na GPU (<code>-ngl</code>). Ale to już ostatnia deska
ratunku, bo pierwsze odbiera modelowi pamięć, a drugie prędkość. Sam zestaw flag i to, co która
robi, przeszliśmy po kolei w <a href="/poradniki/flagi-llama-cpp/">osobnym wpisie o flagach llama.cpp</a>.</p>
<p>Pomyśl o tym tak. Wagi modelu to biblioteka — stoi w regale, zajmuje swoje półki
i tyle, nieważne, ile razy do niej zajrzysz. KV-cache to biurko, na którym rozkładasz
wszystko, co akurat czytasz. Im dłużej pracujesz nad jednym tematem, tym więcej kartek
ląduje na blacie — i w pewnym momencie nie ma gdzie postawić kubka, choć regał ani
drgnął. Kwantyzacja cache to składanie tych kartek na pół, flash attention to
sprzątanie w locie, GQA to od razu mniejszy stos notatek na tę samą robotę.</p>
<p>Karta się nie skurczyła. To po prostu twoja rozmowa urosła — a teraz już wiesz,
który człon wzoru za to odpowiada i za które pokrętło pociągnąć, zanim znów zobaczysz
to <code>out of memory</code>.</p>
<h2 id="przypisy--źródła">Przypisy · źródła</h2>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Mechanizm KV-cache: bufor par klucz-wartość liczonych raz na token i warstwę, o rozmiarze <code>2 × n_layer × n_kv_heads × head_dim × długość_kontekstu × bajty_na_element</code> — rośnie liniowo z kontekstem, niezależnie od rozmiaru wag; kwadratowy jest jedynie koszt obliczeniowy naiwnej uwagi. Wyprowadzenie mechanizmu spójne z definicją flash attention w FlashAttention (Dao i in.), <a href="https://arxiv.org/abs/2205.14135">arXiv:2205.14135</a>.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref3:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref4:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref5:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>llama.cpp, definicje flag <code>-ctk</code>/<code>--cache-type-k</code>, <code>-ctv</code>/<code>--cache-type-v</code> (dozwolone typy: <code>f32</code>, <code>f16</code>, <code>bf16</code>, <code>q8_0</code>, <code>q4_0</code>, <code>q4_1</code>, <code>iq4_nl</code>, <code>q5_0</code>, <code>q5_1</code>; domyślnie <code>f16</code>) oraz <code>-fa</code>/<code>--flash-attn</code> (<code>on</code>/<code>off</code>/<code>auto</code>, domyślnie <code>auto</code>), <a href="https://github.com/ggml-org/llama.cpp/blob/master/common/arg.cpp">common/arg.cpp</a>. Te same parametry w <a href="https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md">dokumentacji serwera</a>.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><code>q8_0</code> = 1 bajt na element (ok. 2× mniej niż <code>f16</code>), <code>q4_0</code> = pół bajta (ok. 4× mniej niż <code>f16</code>); dokładne MB zależą od <code>n_layer</code>, <code>n_kv_heads</code>, <code>head_dim</code> i długości kontekstu konkretnego modelu. Typy zdefiniowane w llama.cpp, <a href="https://github.com/ggml-org/llama.cpp/blob/master/common/arg.cpp">common/arg.cpp</a>.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness (Dao, Fu, Ermon, Rudra, Ré, NeurIPS 2022) — dokładny, IO-aware algorytm uwagi redukujący odczyty/zapisy między HBM a SRAM GPU; nie jest aproksymacją uwagi, <a href="https://arxiv.org/abs/2205.14135">arXiv:2205.14135</a>.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Zależność wymuszona w kodzie llama.cpp: przy wyłączonym flash attention skwantyzowany cache V rzuca runtime error <code>quantized V cache was requested, but this requires Flash Attention</code> — sprawdzenie w konstruktorze kontekstu, <a href="https://github.com/ggml-org/llama.cpp/blob/master/src/llama-context.cpp">src/llama-context.cpp</a>.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints (Ainslie i in., EMNLP 2023) — grouped-query attention jako interpolacja między MHA a MQA, redukcja liczby głów KV, uptraining z ok. 5% oryginalnego budżetu treningu, <a href="https://arxiv.org/abs/2305.13245">arXiv:2305.13245</a>.&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref3:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Ile VRAM-u potrzebuje model? Liczymy na palcach</title><link>https://inferownia.pl/poradniki/ile-vram-na-model/</link><pubDate>Sun, 12 Jul 2026 00:00:00 +0200</pubDate><category>inferencja</category><guid>https://inferownia.pl/poradniki/ile-vram-na-model/</guid><description>Nowy model, ładna karta modelu, a na dole jedno zdanie: 16 GB VRAM. Ty masz te swoje 8. Zamiast wróżyć z fusów, policzmy na palcach, ile naprawdę zważy — i jak zejść niżej.</description><content:encoded><![CDATA[<p><code>Wymagania: 16 GB VRAM</code>. Zjeżdżasz na dół karty modelu, widzisz to jedno zdanie, zerkasz na
swoją kartę do gier z jej ośmioma gigabajtami — z czego połowę i tak zżera przeglądarka — i
serce ci lekko siada. A bywa gorzej: czasem nawet tej jednej liczby nie ma, jest za to tabelka
z dwudziestoma wariantami pliku, każdy waży inaczej, i dalej nie wiesz, czy w ogóle podchodzić,
czy zaraz przywita cię „CUDA out of memory&quot;, cała na biało. To codzienność — połowa pytań „will it fit?&quot; to ludzie, którzy kupili
GPU do gier, a teraz próbują wcisnąć na nie model wielkości małego miasta.</p>
<p>Dobra wiadomość: nie musisz zgadywać. VRAM da się policzyć na palcach, z grubsza, w głowie,
zanim cokolwiek ściągniesz. Nie do grama — do grama policzy ci <code>llama.cpp</code> przy starcie —
ale na tyle dokładnie, żeby wiedzieć, czy w ogóle podchodzić. O to cała sztuka:
zamiast wróżyć z rozmiaru pliku, umieć powiedzieć „7B w Q4 to jakieś pięć giga plus
zapas, wejdzie&quot; — i mieć rację.</p>
<h2 id="wzór-na-palcach">Wzór na palcach</h2>
<p>Cały fundament mieści się w jednej linijce:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">rozmiar wag (GB) ≈ liczba parametrów (mld) × bity na wagę / 8
</span></span></code></pre></div><p>I tyle. Bierzesz, ile model ma miliardów parametrów, mnożysz przez to, ile bitów zajmuje
jedna waga w danym formacie, dzielisz przez osiem (bo bajt ma osiem bitów) i masz gramaturę
samych wag w gigabajtach. Model 8B w kwantyzacji Q4, gdzie na wagę schodzi jakieś 4,5 bita?
<code>8 × 4,5 / 8 ≈ 4,5 GB</code>. I to nie jest teoretyczna wróżba — realny plik <code>Q4_K_M</code> dla
Llamy 3.1 8B waży 4,92 GB.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Zgadza się co do joty, a właściwie co do paru setek megabajtów.</p>
<div class="callout">
  <span class="callout__label">Dlaczego to ważne dla lokalnego LLM</span>
<p>To „bity na wagę&quot; (po angielsku <em>bits-per-weight</em>, bpw) to sedno całej zabawy z kwantyzacją.
Surowy model trenuje się zwykle w 16 bitach na wagę. Kwantyzacja to nic innego jak zapisanie
tych samych wag ciaśniej — w 8, 5, 4, a nawet 2 bitach — już po treningu, bez ruszania
architektury. Nie myl tego z trenowaniem od zera w niskiej precyzji; to dwie różne bajki.
Jak działają nazwy w stylu <code>Q4_K_M</code> i skąd te dziwne litery, rozgryzaliśmy osobno
<a href="/poradniki/nazwy-kwantyzacji-gguf/">tutaj</a>.</p>
</div>
<h2 id="skąd-biorą-się-ułamkowe-bity">Skąd biorą się ułamkowe bity</h2>
<p>Tu pierwsza pułapka, na którą łapie się każdy początkujący: „Q4 to przecież cztery bity,
nie?&quot;. No właśnie nie do końca. Gdyby świat był prosty, <code>Q4_0</code> (stary, prościutki typ)
brałby równe 4,5 bita na wagę — bo pakuje 32 wagi w blok i dokłada do nich trochę metadanych
o skali.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> Ale nowsze formaty, tak zwane K-quanty (<code>Q2_K</code> do <code>Q6_K</code>, z dopiskami <code>_S</code>,
<code>_M</code>, <code>_L</code>), są sprytniejsze: mieszają precyzję między różnymi warstwami modelu. Te tensory,
od których jakość naprawdę zależy, dostają więcej bitów, reszcie się skąpi. Efekt? Liczba
bitów na wagę wychodzi ułamkowa.</p>
<p>Konkretnie, dla Llamy 3.1 8B według oficjalnej tabeli <code>llama.cpp</code>:<sup id="fnref1:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<table>
	<thead>
			<tr>
					<th>Format</th>
					<th>Bity na wagę</th>
					<th>Rozmiar (tabela llama.cpp)</th>
					<th>Realny plik GGUF</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>Q4_K_M</code></td>
					<td>4,8944</td>
					<td>4,58 GiB</td>
					<td>4,92 GB</td>
			</tr>
			<tr>
					<td><code>Q5_K_M</code></td>
					<td>5,7036</td>
					<td>5,33 GiB</td>
					<td>5,73 GB</td>
			</tr>
			<tr>
					<td><code>Q6_K</code></td>
					<td>~6,5</td>
					<td>—</td>
					<td>6,60 GB</td>
			</tr>
			<tr>
					<td><code>Q8_0</code></td>
					<td>~8,50</td>
					<td>—</td>
					<td>8,54 GB</td>
			</tr>
			<tr>
					<td><code>F16</code></td>
					<td>16,0005</td>
					<td>14,96 GiB</td>
					<td>—</td>
			</tr>
	</tbody>
</table>
<p>Widzisz tę drobną rozbieżność między kolumnami? Jedna to GiB (liczone po 1024), druga GB
(po 1000), plus K-quant miesza precyzję tensorów — stąd <code>Q4_K_M</code> ma prawie 4,9 bita, a nie
równe 4,5.<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Do liczenia na palcach nic to nie zmienia: bierzesz „mniej więcej 4,5–5
bita dla Q4&quot; i tak trafiasz w wynik z dokładnością do zapasu, który i tak musisz zostawić.
Jest jeszcze cała rodzina IQ-quantów (<code>IQ2_XXS</code>, <code>IQ3_S</code> i spółka), które schodzą poniżej
trzech bitów, ważąc jakość specjalną macierzą istotności — ale to temat na osobny wieczór.<sup id="fnref2:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<h2 id="ile-zważy-ten-twój-model">Ile zważy „ten twój&quot; model</h2>
<p>Przełóżmy wzór na cztery rozmiary, które realnie krążą po lokalnym światku. To są same wagi,
bez narzutu — o narzucie za chwilę:</p>
<table>
	<thead>
			<tr>
					<th>Model</th>
					<th>Q4_K_M</th>
					<th>Q5_K_M</th>
					<th>Q8_0</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Llama 3.1 <strong>8B</strong><sup id="fnref2:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></td>
					<td>4,92 GB</td>
					<td>5,73 GB</td>
					<td>8,54 GB</td>
			</tr>
			<tr>
					<td>Mistral Nemo <strong>12B</strong><sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></td>
					<td>7,48 GB</td>
					<td>8,73 GB</td>
					<td>13,02 GB</td>
			</tr>
			<tr>
					<td>Qwen2.5 <strong>14B</strong><sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup></td>
					<td>8,99 GB</td>
					<td>10,51 GB</td>
					<td>15,70 GB</td>
			</tr>
	</tbody>
</table>
<p>Popatrz na to przez chwilę, bo tu widać całą matematykę życia lokalnego LLM-owca. Masz kartę
8 GB? Ósemka w Q4 wchodzi na luzie, w Q5 już z zadyszką, a Q8 nawet nie podchodź. Dwunastka
w Q4 to 7,5 GB samych wag — teoretycznie „się mieści&quot;, tylko że na sam narzut nie zostaje już
nic. A czternastka w Q4 to prawie dziewięć giga — twoje 8 GB kończy się, zanim się zaczęło.
Dlatego „te swoje 8 GB&quot; znika tak szybko: liczysz gramaturę wag, cieszysz się, że wchodzi,
a potem uruchamiasz i okazuje się, że o czymś zapomniałeś.</p>
<h2 id="czego-zapominasz-kv-cache-i-bufory">Czego zapominasz: KV-cache i bufory</h2>
<p>O to zapominasz. Rozmiar pliku GGUF to tylko wagi. Do tego dochodzi KV-cache — pamięć, w której
model trzyma przetworzone klucze i wartości dla każdego tokena, który model już wygenerował lub
wczytał z promptu. I to jest ten cichy złodziej VRAM-u, bo rośnie <strong>liniowo z długością
kontekstu</strong>. Im dłuższa rozmowa, tym większy żłób na pamięć.</p>
<p>Ile dokładnie? Też się liczy na palcach. Na jeden token przypada:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">KV na token = 2 (klucz + wartość) × liczba_warstw × n_head_kv × head_dim × bajty_na_element
</span></span></code></pre></div><p>Dla Llamy 3.1 8B te liczby to: 32 warstwy, <code>n_head_kv</code> = 8, <code>head_dim</code> = 128, a KV-cache
domyślnie leci w <code>f16</code>, czyli 2 bajty na element<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> (co to za <code>f16</code> i czemu nie <code>bf16</code> — <a href="/poradniki/bf16-f16-f32/">tu</a>). Wrzuć to do wzoru:
<code>2 × 32 × 8 × 128 × 2 bajty = 128 KiB</code> na token. Przy kontekście 8192 tokenów robi się z tego
równiutki 1 GiB — tyle dokłada sam cache do tego, co już zjadły wagi. Rozbieramy ten mechanizm
dokładniej <a href="/poradniki/kv-cache-vram/">tutaj</a>, ale zasada do zapamiętania jest prosta: dłuższy
kontekst = więcej VRAM-u, po prostu.</p>
<p>Zwróć uwagę na <code>n_head_kv</code> = 8 — pełna liczba głów uwagi w tym modelu jest większa, ale dzięki
GQA (<em>grouped-query attention</em>, mechanizm współdzielenia głów klucza/wartości) do cache liczy się
tylko ta zredukowana ósemka. Bez GQA, na starej architekturze, ten narzut potrafił być
wielokrotnie większy. Do tego dochodzą jeszcze bufory obliczeniowe samego procesu inferencji.
Dlatego świętą zasadą jest: <strong>zostaw 1–2 GB zapasu ponad rozmiar wag</strong>. Kto liczy VRAM tylko
z gramatury pliku GGUF, ten regularnie ląduje na „out of memory&quot; przy trzecim akapicie rozmowy.</p>
<h2 id="jak-zejść-niżej-gdy-nie-wchodzi">Jak zejść niżej, gdy nie wchodzi</h2>
<p>Powiedzmy, że policzyłeś i wychodzi za dużo. Masz trzy dźwignie, w tej właśnie kolejności.</p>
<p>Raz — <strong>kwantyzacja niżej</strong>. To najprostszy ruch: zamiast Q8 bierzesz Q5, zamiast Q5 bierzesz
Q4. Czternastka w Q8 to 15,7 GB, a w Q4 już tylko 8,99 GB<sup id="fnref1:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> — prawie połowa mniej, a
jakość spada zaskakująco mało, bo K-quanty oszczędzają mądrze. Q4_K_M to nie bez powodu
domyślny wybór połowy internetu.</p>
<p>Dwa — <strong>kwantyzacja KV-cache</strong>. Skoro cache potrafi dołożyć giga przy dłuższym kontekście, to
jego też można ścisnąć. <code>llama-server</code> ma na to flagi <code>-ctk</code> / <code>--cache-type-k</code> i <code>-ctv</code> /
<code>--cache-type-v</code>, osobno dla kluczy i wartości; domyślnie oba stoją na <code>f16</code>, ale przyjmują też
<code>q8_0</code>, <code>q4_0</code> i całą listę innych typów.<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> Ściśnięcie cache z <code>f16</code> do <code>q8_0</code> to znów
zejście z jego rozmiaru mniej więcej o połowę.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">llama-server -m qwen2.5-14b-instruct-q4_k_m.gguf <span class="se">\
</span></span></span><span class="line"><span class="cl">  -ngl all -c <span class="m">8192</span> -fa on <span class="se">\
</span></span></span><span class="line"><span class="cl">  -ctk q8_0 -ctv q8_0
</span></span></code></pre></div><p>Widzisz to <code>-fa on</code>? To nie ozdoba. Flaga <code>-fa</code> / <code>--flash-attn</code> (przyjmuje <code>on</code>/<code>off</code>/<code>auto</code>,
domyślnie <code>auto</code>) włącza Flash Attention — a kwantyzacja KV-cache do typów poniżej <code>f16</code>
praktycznie <strong>wymaga</strong> włączonej Flash Attention.<sup id="fnref1:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> Ustawisz <code>-ctk q4_0</code> bez <code>-fa on</code> i
w najlepszym razie nic nie zyskasz, w najgorszym nie ruszy. Więc: najpierw Flash Attention,
potem duś cache.</p>
<p>Trzy — <strong>przytnij kontekst albo zrzuć część warstw na CPU</strong>. To ostateczność, bo krótszy
kontekst to mniejsza pamięć modelu, a offload na CPU (flaga <code>-ngl</code> z liczbą mniejszą niż
„wszystko&quot;) spowalnia generację. Ale gdy 12B w Q4 nie chce wejść na 8 GB nawet po ściśnięciu
cache — czasem to jedyny sposób, żeby w ogóle ruszyło. Cała ta chirurgia flagami to osobny
rozdział, który rozpisaliśmy <a href="/poradniki/flagi-llama-cpp/">tu</a>.</p>
<h2 id="zrób-to-sam-rachunek-w-trzy-sekundy">Zrób to sam: rachunek w trzy sekundy</h2>
<p>Następnym razem, gdy staniesz przed nowym modelem, przelicz go w głowie, zanim klikniesz
pobieranie:</p>
<ol>
<li><strong>Wagi</strong>: <code>parametry × bity / 8</code>. Ósemka w Q4 → <code>8 × 4,5 / 8 ≈ 4,5 GB</code>. Albo zerknij na
realny plik na Hugging Face i weź gotową liczbę.</li>
<li><strong>Cache</strong>: przy zwykłym kontekście (4–8k) dorzuć z grubsza 0,5–1 GB dla modelu klasy 7–14B
z GQA. Planujesz 32k albo więcej? Licz się z kilkoma gigabajtami i od razu myśl o <code>-fa on</code>
plus <code>-ctk q8_0</code>.</li>
<li><strong>Zapas</strong>: dorzuć 1–2 GB na bufory i oddech.</li>
</ol>
<p>Suma większa niż twój VRAM? Schodzisz z kwantyzacją albo duszisz cache — w tej kolejności.
Suma mniejsza? Ściągaj śmiało, wejdzie.</p>
<p>Bo cała sztuka z VRAM-em nie polega na wróżeniu, tylko na tym jednym prostym mnożeniu na
palcach — parametry razy bity, przez osiem, plus zapas. Kiedy raz je poczujesz, folder <code>models</code>
przestaje być loterią, a zaczyna być tym, czym powinien: półką, na której z góry wiesz, co się
zmieści, a co poleży, aż dokupisz kartę „na później&quot;. A dokupisz. Wszyscy dokupujemy.</p>
<h2 id="przypisy--źródła">Przypisy · źródła</h2>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Realne rozmiary plików GGUF dla Meta-Llama-3.1-8B-Instruct (<code>Q4_K_M</code> = 4,92 GB, <code>Q5_K_M</code> = 5,73 GB, <code>Q6_K</code> = 6,60 GB, <code>Q8_0</code> = 8,54 GB), <a href="https://huggingface.co/bartowski/Meta-Llama-3.1-8B-Instruct-GGUF">bartowski/Meta-Llama-3.1-8B-Instruct-GGUF</a>.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>llama.cpp, tabela typów kwantyzacji z wartościami bits-per-weight i rozmiarami dla Llama 3.1 8B (<code>Q4_K_M</code> = 4,8944 bpw / 4,58 GiB, <code>Q5_K_M</code> = 5,7036 bpw / 5,33 GiB, <code>F16</code> = 16,0005 bpw / 14,96 GiB; <code>Q4_0</code> = 4,5 bpw), <a href="https://github.com/ggml-org/llama.cpp/blob/master/tools/quantize/README.md">tools/quantize/README.md</a>.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Realne rozmiary plików GGUF dla Mistral-Nemo-Instruct-2407 (12B): <code>Q4_K_M</code> = 7,48 GB, <code>Q5_K_M</code> = 8,73 GB, <code>Q8_0</code> = 13,02 GB, <a href="https://huggingface.co/bartowski/Mistral-Nemo-Instruct-2407-GGUF">bartowski/Mistral-Nemo-Instruct-2407-GGUF</a>.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Realne rozmiary plików GGUF dla Qwen2.5-14B-Instruct: <code>Q4_K_M</code> = 8,99 GB, <code>Q5_K_M</code> = 10,51 GB, <code>Q8_0</code> = 15,70 GB, <a href="https://huggingface.co/bartowski/Qwen2.5-14B-Instruct-GGUF">bartowski/Qwen2.5-14B-Instruct-GGUF</a>.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>llama.cpp, parametry KV-cache dla Llama 3 8B (<code>n_layer</code> = 32, <code>n_head_kv</code> = 8, <code>head_dim</code> = 128, <code>n_embd_k_gqa</code>/<code>v_gqa</code> = 1024) i potwierdzenie domyślnego typu cache <code>f16</code>, <a href="https://github.com/ggml-org/llama.cpp/discussions/7949">GitHub Discussion #7949 — KV Cache Dimension</a>.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>llama.cpp, opis flag <code>llama-server</code>: <code>-ctk</code>/<code>--cache-type-k</code>, <code>-ctv</code>/<code>--cache-type-v</code> (domyślnie <code>f16</code>, dozwolone m.in. <code>q8_0</code>, <code>q4_0</code>) oraz <code>-fa</code>/<code>--flash-attn</code> (<code>on</code>/<code>off</code>/<code>auto</code>, domyślnie <code>auto</code>); kwantyzacja KV-cache poniżej <code>f16</code> wymaga Flash Attention, <a href="https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md">tools/server/README.md</a>.&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>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>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>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>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></channel></rss>