Fine-tuning LLM z LoRA i QLoRA w Pythonie – Unsloth krok po kroku (2026)
Fine-tuning LLM z LoRA i QLoRA w Pythonie – trenuj Llama 3.3 8B na jednej karcie 16 GB VRAM z Unsloth. Kompletny kod, dataset i eksport GGUF do Ollama.
Fine-tuning LLM z LoRA i QLoRA w Pythonie to najtańsza droga do dostosowania modelu open-weight (Llama 3.3, Qwen 2.5, Mistral) do własnej domeny bez trenowania wszystkich miliardów parametrów. Zamiast tego dokładasz kilka milionów wag adaptera, a przy QLoRA dodatkowo kwantyzujesz bazowy model do 4 bitów, co pozwala wytrenować 8B na jednej karcie 16 GB VRAM. Szczerze mówiąc, jeszcze dwa lata temu to brzmiało jak science fiction. W 2026 najszybszy stack, po jaki sięgam w praktyce, to Unsloth + Hugging Face TRL + PEFT + bitsandbytes – daje mniej więcej 2x szybszy trening i 60–70% mniej pamięci od waniliowego Hugging Face.
W tym przewodniku pokażę pełną ścieżkę: od wyboru między RAG a fine-tuningiem, przez konfigurację QLoRA i przygotowanie datasetu, aż po eksport do GGUF i uruchomienie w Ollama. Wszystko na przykładzie polskiego asystenta wsparcia klienta, którego trenowałem ostatnio na RTX 4090.
QLoRA łączy 4-bitową kwantyzację NF4 z adapterami LoRA – trenuje model 8B na ~10–14 GB VRAM zamiast 60+ GB przy pełnym fine-tuningu.
Unsloth 2026 to ok. 2x szybszy trening i 70% mniej pamięci niż waniliowy HF Trainer, dzięki custom kernelom Triton i optymalizacjom RoPE.
Fine-tuning ma sens dla stylu, formatu, języka i wąskich domen – dla świeżej wiedzy prawie zawsze wygrywa RAG.
Dataset instrukcyjny (ChatML/ShareGPT) potrzebuje 500–5000 wysokiej jakości przykładów; więcej nie zawsze znaczy lepiej.
Eksport do GGUF pozwala uruchomić fine-tunowany model w Ollama lub llama.cpp na CPU/Mac z 3–5x mniejszym zużyciem pamięci.
Najczęstsze błędy: za wysoki learning rate, catastrophic forgetting, brak walidacji, mieszanie formatów promptów bazowego modelu.
Fine-tuning LLM vs RAG – kiedy stosować które podejście?
Zanim uruchomisz jakikolwiek trening, warto uczciwie odpowiedzieć sobie na pytanie: czy fine-tuning w ogóle rozwiąże twój problem? Sam wielokrotnie widziałem zespoły, które paliły tygodnie na trening, a wystarczył lepszy prompt. W praktyce w 2026 dobrze skonfigurowany hybrid search w RAG (BM25 + embeddingi + reranker) pokrywa jakieś 80% przypadków, w których zespoły odruchowo sięgają po fine-tuning. Fine-tuning nie „uczy” modelu nowych faktów w sensie, w którym RAG podaje mu świeży kontekst. Zmienia natomiast styl, format, ton i reguły decyzyjne.
Poniższa tabela pokazuje, kiedy każde z podejść wygrywa i co warto zrobić najpierw:
Kryterium
Fine-tuning (LoRA/QLoRA)
RAG
Nowe fakty i świeże dane
Słabo – wymaga retreningu
Bardzo dobrze
Ścisły format wyjścia (JSON, XML, DSL)
Bardzo dobrze
Średnio
Styl marki, ton głosu
Bardzo dobrze
Słabo
Wąska domena (medycyna, prawo PL)
Dobrze przy 1000+ przykładach
Dobrze z dobrą retrieval
Latencja produkcyjna
Niższa (mniejszy prompt)
Wyższa (retrieval + kontekst)
Koszt uruchomienia
50–500 USD jednorazowo
Głównie infra wektorowa
Aktualizacje danych
Powtórny trening
Reindexing dokumentów
W praktyce najlepsze systemy 2026 łączą oba podejścia: fine-tunowany LLM 7–8B pełni rolę „małego eksperta domenowego”, który dobrze rozumie strukturę wyjścia i konwencje, a RAG dostarcza świeżej wiedzy z bazy dokumentów. Jeżeli twój use case to klasyfikacja, ekstrakcja pól, przepisywanie w konkretnym stylu albo dopasowanie do wąskiego języka technicznego – fine-tuning zwykle się opłaca. Jeśli chodzi o odpowiedzi z aktualnych dokumentów – zacznij od RAG.
LoRA i QLoRA – jak działa adaptacja niskiego rzędu?
LoRA (Low-Rank Adaptation) to technika, która zamiast aktualizować wszystkie wagi modelu, dodaje do wybranych warstw (najczęściej projekcji q, k, v, o w mechanizmie attention) dwie małe macierze A i B o niskim rzędzie r (typowo 8–64). Efektywna aktualizacja wagi wygląda tak: W' = W + (A @ B) * (alpha / r), gdzie A ma wymiar d × r, B ma r × d, a alpha to skalowanie. Zamiast trenować np. 16 milionów parametrów w jednej warstwie, trenujesz kilkadziesiąt tysięcy, zwykle 0,1–1% oryginalnej liczby parametrów całego modelu. Konkretną implementację referencyjną znajdziesz w dokumentacji Hugging Face PEFT.
QLoRA (opisana w oryginalnym paper QLoRA z 2023 r.) idzie krok dalej: bazowy model jest kwantyzowany do 4 bitów w formacie NF4 (NormalFloat), zamrażany, i dopiero na tak zmniejszonym modelu doczepiamy adaptery LoRA w wyższej precyzji (bf16/fp16). Do tego dochodzą trzy triki:
NF4 – kwantyzacja optymalna informacyjnie dla wag o rozkładzie zbliżonym do normalnego.
Double quantization – kwantyzacja samych stałych kwantyzacji, oszczędzająca dodatkowe ~0,4 bitu na parametr.
Paged optimizers – wymiana pamięci GPU↔RAM przy backpropagation, eliminująca OOM przy długich sekwencjach.
Wynikiem jest dramatyczny spadek zużycia pamięci przy zachowaniu jakoś 99% jakości pełnego fine-tuningu w bf16. Właśnie dlatego w 2026 QLoRA jest domyślnym wyborem dla większości zespołów bez klastra H100. Model 8B z pełnym kontekstem 4k tokenów mieści się spokojnie na jednej karcie konsumenckiej.
Ile VRAM potrzebujesz do fine-tuningu QLoRA w 2026?
To pierwsze pytanie, które zadaje każdy przed uruchomieniem treningu. Poniższe wartości bazują na Unsloth 2026, sekwencjach 2048 tokenów, batch size 2 i gradient accumulation 4 – realistyczna konfiguracja domowa/produkcyjna:
Model bazowy
QLoRA (4-bit)
LoRA (16-bit)
Full fine-tune
Llama 3.2 3B
~5 GB
~12 GB
~40 GB
Llama 3.3 8B
~10 GB
~20 GB
~80 GB
Qwen 2.5 14B
~16 GB
~35 GB
~150 GB
Llama 3.3 70B
~48 GB
~160 GB
~700 GB+
Praktyczny przekład: RTX 4060 Ti 16 GB, RTX 4090 24 GB albo darmowe Colab T4 (16 GB) w zupełności wystarczają dla modeli 7–8B z QLoRA. Dla 14B potrzebujesz już RTX 4090 lub A100 40 GB, a 70B mieści się dopiero na A100 80 GB lub H100. Chcesz zejść jeszcze niżej? Unsloth wspiera dynamic 4-bit, a eksperymentalnie także 3-bit, co zbija pamięć o kolejne 15–20%.
Dłuższy kontekst (8k, 16k, 32k tokenów) rośnie kwadratowo w naiwnej implementacji attention, ale FlashAttention 2 i optymalizacje Unsloth (custom kernele Triton) sprawiają, że wzrost jest praktycznie liniowy. Realistycznie, przy kontekście 8k zamiast 2k dodaj około 30–40% do wartości z tabeli.
Przygotowanie środowiska – Unsloth, PEFT i bitsandbytes
Unsloth to biblioteka open-source, która patchuje warstwy Hugging Face i podmienia kernel attention/MLP na własne implementacje w Triton. Efekt: 1.9–2.2x szybszy trening, 60–70% mniej VRAM i pełna kompatybilność z Hugging Face TRL, PEFT oraz Transformers. Zgodnie z oficjalnym repozytorium Unsloth, w 2026 wspiera Llama 3.1/3.2/3.3, Qwen 2.5, Mistral, Phi-3.5, Gemma 2, DeepSeek, a także multimodalne modele Vision.
Instalacja na Linuxie z CUDA 12.1+ (najprostsza ścieżka):
Na macOS z Apple Silicon Unsloth nie działa (wymaga CUDA), ale możesz trenować przez MLX-LM albo skorzystać z Colab Free/Pro. Windows – używaj WSL2 z Ubuntu 22.04. Weryfikacja instalacji:
import torch
from unsloth import FastLanguageModel
print("CUDA:", torch.cuda.is_available())
print("GPU:", torch.cuda.get_device_name(0) if torch.cuda.is_available() else "brak")
print("VRAM:", round(torch.cuda.get_device_properties(0).total_memory / 1024**3, 1), "GB")
Fine-tuning Llama 3.3 8B z Unsloth – kod krok po kroku
Poniższy skrypt to kompletny, uruchamialny przykład fine-tuningu Llama 3.3 8B Instruct do stylu polskiego asystenta wsparcia klienta. Trening zajmuje ~45 minut na RTX 4090 przy 1000 przykładach i 2 epokach.
from unsloth import FastLanguageModel, is_bfloat16_supported
from unsloth.chat_templates import get_chat_template
from datasets import load_dataset
from trl import SFTTrainer
from transformers import TrainingArguments
import torch
MAX_SEQ_LENGTH = 2048
DTYPE = None # None = auto (bf16 na Ampere+, fp16 na starszych)
LOAD_IN_4BIT = True # QLoRA
# 1) Bazowy model z 4-bit kwantyzacją NF4
model, tokenizer = FastLanguageModel.from_pretrained(
model_name="unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bit",
max_seq_length=MAX_SEQ_LENGTH,
dtype=DTYPE,
load_in_4bit=LOAD_IN_4BIT,
)
# 2) Doklejenie adapterów LoRA
model = FastLanguageModel.get_peft_model(
model,
r=16, # rząd LoRA – 8 (tanio) do 64 (jakość)
target_modules=[
"q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj",
],
lora_alpha=16, # zwykle = r lub 2*r
lora_dropout=0, # 0 = najszybciej, 0.05 = lepsza regularyzacja
bias="none",
use_gradient_checkpointing="unsloth", # oszczędza ~30% VRAM
random_state=42,
)
# 3) Chat template - Llama 3.1/3.3 używa własnego formatu
tokenizer = get_chat_template(tokenizer, chat_template="llama-3.1")
def formatting_prompts_func(examples):
convos = examples["conversations"]
texts = [
tokenizer.apply_chat_template(convo, tokenize=False, add_generation_prompt=False)
for convo in convos
]
return {"text": texts}
# 4) Dataset w formacie ShareGPT (patrz sekcja o datasetach)
dataset = load_dataset("json", data_files="train.jsonl", split="train")
dataset = dataset.map(formatting_prompts_func, batched=True)
# 5) Konfiguracja treningu
trainer = SFTTrainer(
model=model,
tokenizer=tokenizer,
train_dataset=dataset,
dataset_text_field="text",
max_seq_length=MAX_SEQ_LENGTH,
dataset_num_proc=2,
packing=False, # True daje ~2x speed, ale wymaga uwagi z EOS
args=TrainingArguments(
per_device_train_batch_size=2,
gradient_accumulation_steps=4, # efektywny batch = 8
warmup_steps=10,
num_train_epochs=2,
learning_rate=2e-4, # wyższy niż full FT, bo tylko adaptery
fp16=not is_bfloat16_supported(),
bf16=is_bfloat16_supported(),
logging_steps=10,
optim="adamw_8bit", # oszczędza ~4 GB VRAM
weight_decay=0.01,
lr_scheduler_type="linear",
seed=42,
output_dir="outputs",
report_to="none", # albo "wandb"/"tensorboard"
),
)
# 6) Trening
trainer_stats = trainer.train()
# 7) Zapis adapterów (małe pliki ~150 MB)
model.save_pretrained("lora_llama31_8b_pl")
tokenizer.save_pretrained("lora_llama31_8b_pl")
# 8) Szybki test inference
FastLanguageModel.for_inference(model)
messages = [{"role": "user", "content": "Napisz krotkie przeprosiny za opoznienie dostawy."}]
inputs = tokenizer.apply_chat_template(
messages, tokenize=True, add_generation_prompt=True, return_tensors="pt"
).to("cuda")
outputs = model.generate(input_ids=inputs, max_new_tokens=256, temperature=0.7, use_cache=True)
print(tokenizer.batch_decode(outputs, skip_special_tokens=True)[0])
Kilka rzeczy, które łatwo przeoczyć (sam się na nich potknąłem przy pierwszym treningu): r=16 to dobry start dla większości zadań stylistycznych, ale przy złożonym zachowaniu (kod, tłumaczenie techniczne) rozważ r=32 albo 64. lora_alpha ustawiaj na r lub 2*r; wyższe wartości oznaczają silniejszy wpływ adaptera na wyjście modelu. A adamw_8bit z bitsandbytes? Darmowy zysk około 4 GB VRAM, bez zauważalnej straty jakości.
Jak przygotować dataset instrukcyjny (ChatML i ShareGPT)?
Jakość datasetu to jakieś 80% jakości fine-tunowanego modelu. Naprawdę. Reguła kciuka na 2026: 500 świetnych przykładów bije 50 000 średnich. Dla większości zadań stylistycznych 500–2000 przykładów wystarcza, dla złożonych zachowań (agent, tłumaczenie) 3000–5000. Powyżej 10 000 zaczynasz walczyć z overfittingiem, a nie z brakiem danych.
Najpopularniejsze formaty w 2026:
Format ShareGPT (rekomendowany dla Unsloth)
{"conversations": [
{"from": "system", "value": "Jestes pomocnym asystentem obslugi klienta firmy Alfa."},
{"from": "human", "value": "Moja paczka nie dotarla w obiecanym terminie."},
{"from": "gpt", "value": "Bardzo przepraszam za opoznienie. Podaj prosze numer zamowienia, sprawdze status u kuriera i przygotuje rekompensate."}
]}
Różnorodność, nie objętość. Pokryj wszystkie typowe intencje, tony, długości, poziomy złożoności. Model uczy się rozkładu, nie pojedynczych przykładów.
Konsystentny format wyjścia. Jeżeli chcesz zawsze JSON, wszystkie odpowiedzi muszą być JSON. Jeden przykład w prozie zaszczepi niepewność.
Realistyczne wejścia. Skopiuj/generuj wiadomości podobne do tych, jakie zobaczysz w produkcji, nie „idealne” akademickie przypadki.
Trzymaj 5–10% jako walidację. Bez wstrzymanego zbioru nie odróżnisz uczenia się od zapamiętywania.
Deduplikacja i filtrowanie. MinHash lub embedding-based near-duplicate removal – powtórzenia to sygnał, na którym model przetrenuje się w pierwszej kolejności.
Techniki generacji: dystylacja (użyj GPT-4o lub Claude Opus do wygenerowania idealnych odpowiedzi na twoje realne pytania), self-instruct (LLM generuje warianty istniejących par), human curation (najdroższe, ale zwykle daje 2–3x lepszą jakość niż samo syntetyczne).
Eksport do GGUF i inference w Ollama
Adaptery LoRA to nie jest gotowy do wdrożenia model – trzeba je scalić z bazowym modelem, a najlepiej też skonwertować do formatu wygodnego w inference. W 2026 domyślnym wyborem dla lokalnego uruchomienia jest GGUF (używany przez llama.cpp i Ollama), ponieważ pozwala uruchomić model na CPU, MPS (Mac) lub GPU z kwantyzacją Q4_K_M, Q5_K_M, Q8_0 albo pełnym F16.
# Scalenie adaptera z bazowym modelem + eksport GGUF w Q4_K_M
from unsloth import FastLanguageModel
model, tokenizer = FastLanguageModel.from_pretrained(
model_name="lora_llama31_8b_pl",
max_seq_length=2048,
load_in_4bit=True,
)
# Zapis do GGUF - Unsloth wywoluje llama.cpp pod spodem
model.save_pretrained_gguf(
"gguf_out",
tokenizer,
quantization_method=["q4_k_m", "q5_k_m", "q8_0"],
)
Rejestracja modelu w Ollama:
# Modelfile w katalogu gguf_out
FROM ./llama-3.1-8b-instruct-pl.Q4_K_M.gguf
TEMPLATE """{{ if .System }}<|start_header_id|>system<|end_header_id|>
{{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|>
{{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|>
{{ .Response }}<|eot_id|>"""
PARAMETER stop "<|eot_id|>"
PARAMETER temperature 0.7
PARAMETER num_ctx 4096
ollama create alfa-support -f Modelfile
ollama run alfa-support "Napisz przeprosiny za brak faktury VAT"
W wersjach kwantyzowanych model 8B waży ~5 GB (Q4_K_M), ~5,7 GB (Q5_K_M), ~8,5 GB (Q8_0) i mieści się na dowolnym Macu M-series lub karcie 8 GB VRAM. Dla vLLM na produkcji rozważ zamiast tego bezpośrednie ładowanie adaptera LoRA (multi-LoRA serving) – oszczędza pamięć i pozwala serwować wiele wariantów naraz.
Najczęstsze błędy i pułapki fine-tuningu LLM
Poniżej lista rzeczy, na które regularnie natykają się zespoły wchodzące w fine-tuning pierwszy raz – warto ich uniknąć zawczasu:
Zbyt wysoki learning rate. LoRA toleruje 1e-4 do 3e-4; full FT wymaga 5e-6 do 2e-5. Powyżej – catastrophic forgetting i loss NaN.
Mieszanie chat templates. Llama 3.1 używa innych tokenów niż Qwen czy Mistral. Sprawdź tokenizer.chat_template i użyj get_chat_template z Unsloth zamiast ręcznego formatowania.
Brak add_generation_prompt=True przy inference. Bez tego model kontynuuje wiadomość usera zamiast odpowiadać jako assistant.
Overfitting po 3+ epokach. Zwykle 1–3 epoki wystarczą. Monitoruj eval loss, nie train loss – rosnący eval loss przy spadającym train loss to sygnał stopu.
Za mały rząd LoRA. r=4 działa dla drobnych korekt tonu, ale dla zmiany zachowania (nowy język wyjścia, nowa struktura JSON) potrzebujesz r=16 do 64.
Kwantyzacja przy zapisie GGUF. Q4_K_M jest dobrym kompromisem, ale dla zadań kodowych i matematycznych używaj minimum Q5_K_M albo Q8_0 – Q4 psuje wrażliwe rozumowanie.
Brak walidacji regresji. Testuj model również na zadaniach spoza domeny – zwykle chcesz zachować zdolności ogólne, a nie stworzyć wąskiego eksperta, który nie umie się przywitać.
Traktowanie fine-tuningu jak silver bullet. Jeżeli few-shot prompting z dobrym kontekstem daje 90% jakości, fine-tuning zwykle doda 5–7 pkt, a nie 30. Kalkuluj ROI.
Warto też podłączyć trening do observability. Do iteracyjnego rozwoju modelu bardzo mi się sprawdza ewaluacja z DeepEval i Langfuse. Pozwala uruchamiać automatyczne testy jakości po każdym treningu i szybko porównywać wersje adapterów.
Najczęściej zadawane pytania
Czym różni się LoRA od QLoRA?
LoRA doczepia małe macierze adapterów (rank 8–64) do zamrożonego modelu w pełnej precyzji (bf16/fp16). QLoRA dodatkowo kwantyzuje bazowy model do 4 bitów w formacie NF4, obniżając zużycie VRAM o 60–75% przy praktycznie identycznej jakości końcowej. W 2026 QLoRA jest domyślnym wyborem dla modeli 7B+ na kartach konsumenckich.
Ile przykładów w datasecie potrzeba do fine-tuningu?
Dla zadań stylistycznych i formatu wyjścia zwykle 500–2000 wysokiej jakości przykładów. Dla złożonych zachowań, agentów czy tłumaczenia domenowego 3000–5000. Powyżej 10 000 marginalne zyski szybko maleją, a rośnie ryzyko overfittingu. Reguła: 500 świetnych przykładów bije 50 000 średnich.
Czy fine-tuning modelu zastąpi RAG?
Nie – to komplementarne narzędzia. Fine-tuning zmienia styl, format i reguły decyzyjne, ale nie „doładowuje” świeżej wiedzy w niezawodny sposób. Dla aktualnych danych zawsze wygrywa RAG. Najlepsze systemy 2026 łączą oba: fine-tunowany mały model + retrieval z bazy dokumentów.
Ile kosztuje fine-tuning LLM w chmurze?
QLoRA modelu 8B na 1000 przykładach to ok. 1–2 h na RTX 4090 (~2–4 USD w RunPod/Vast.ai) lub 3–5 h na A100 40 GB (~15–25 USD). Modele 70B z QLoRA wymagają A100 80 GB lub H100 i kosztują zwykle 50–300 USD za pełny trening. Colab Free (T4 16 GB) obsłuży modele do 8B za darmo, jeśli zmieścisz się w limicie sesji.
Jak uruchomić fine-tunowany model lokalnie na Macu?
Wyeksportuj adapter scalony z bazowym modelem do GGUF (np. Q4_K_M lub Q5_K_M) używając model.save_pretrained_gguf() z Unsloth, a następnie zarejestruj plik w Ollama przez Modelfile. Model 8B w Q4_K_M waży ~5 GB i uruchamia się z prędkością 20–40 tokenów/s na Macu M2/M3, korzystając z akceleracji Metal.