1. Fine-tuning vs RAG: Kiedy warto trenować własny model?
Jednym z najczęstszych błędów przy wdrożeniach jest próba użycia fine-tuningu do nauczenia modelu nowych faktów (np. aktualnego cennika firmy). Do przekazywania zmiennej wiedzy służy **RAG (bazy wektorowe)**. Do czego więc służy **Fine-Tuning**?
Fine-tuning zmienia **zachowanie, styl, format wyjściowy oraz rozumienie specyficznej domeny** przez model. W automatyzacjach n8n i systemach backendowych sprawdza się idealnie, gdy:
* Chcesz, aby mały, szybki model (7B–8B parametrów) **w 100% przypadków zwracał czysty, ustrukturyzowany JSON** bez zbędnego tekstu typu *„Oto Twój plik JSON:”*.
* Potrzebujesz skrócić gigantyczny prompt systemowy (np. z 4000 tokenów instrukcji i przykładów Few-Shot do zaledwie 50 tokenów), co drastycznie obniża opóźnienia (latencję) i zużycie mocy obliczeniowej.
* Uczysz model specyficznego żargonu branżowego, klasyfikacji zgłoszeń klientów lub ekstrakcji danych z nieustrukturyzowanych maili.
Fine-tuning zmienia **zachowanie, styl, format wyjściowy oraz rozumienie specyficznej domeny** przez model. W automatyzacjach n8n i systemach backendowych sprawdza się idealnie, gdy:
* Chcesz, aby mały, szybki model (7B–8B parametrów) **w 100% przypadków zwracał czysty, ustrukturyzowany JSON** bez zbędnego tekstu typu *„Oto Twój plik JSON:”*.
* Potrzebujesz skrócić gigantyczny prompt systemowy (np. z 4000 tokenów instrukcji i przykładów Few-Shot do zaledwie 50 tokenów), co drastycznie obniża opóźnienia (latencję) i zużycie mocy obliczeniowej.
* Uczysz model specyficznego żargonu branżowego, klasyfikacji zgłoszeń klientów lub ekstrakcji danych z nieustrukturyzowanych maili.
2. Dlaczego Unsloth? Rewolucja w szybkości i zużyciu VRAM
Tradycyjny trening modelu 8B wymaga drogich kart pokroju NVIDIA A100 (80 GB VRAM). Biblioteka **Unsloth** zmienia reguły gry dzięki przepisaniu kluczowych operacji matematycznych (kerneli w języku OpenAI Triton) oraz wykorzystaniu techniki **QLoRA** (Quantized Low-Rank Adaptation).
Zamiast modyfikować wszystkie 8 miliardów parametrów modelu, QLoRA zamraża oryginalny model skompresowany do 4 bitów i dokleja do niego niewielką warstwę adapterów (zaledwie 1–2% oryginalnych wag). Dzięki Unsloth:
* Trening przebiega **2x szybciej** niż w standardowym HuggingFace TRL.
* Zużycie pamięci VRAM spada o **ponad 60%** – model Llama 3.1 8B lub Qwen 2.5 7B wytrenujesz bez problemu na konsumenckiej karcie z 16–24 GB VRAM.
* Cały proces dla 500–1000 przykładów trwa na karcie RTX 4090 około **15–25 minut**, co przy stawkach w RunPod oznacza koszt poniżej **$0.50 – $1.50** za pełny cykl treningowy.
Zamiast modyfikować wszystkie 8 miliardów parametrów modelu, QLoRA zamraża oryginalny model skompresowany do 4 bitów i dokleja do niego niewielką warstwę adapterów (zaledwie 1–2% oryginalnych wag). Dzięki Unsloth:
* Trening przebiega **2x szybciej** niż w standardowym HuggingFace TRL.
* Zużycie pamięci VRAM spada o **ponad 60%** – model Llama 3.1 8B lub Qwen 2.5 7B wytrenujesz bez problemu na konsumenckiej karcie z 16–24 GB VRAM.
* Cały proces dla 500–1000 przykładów trwa na karcie RTX 4090 około **15–25 minut**, co przy stawkach w RunPod oznacza koszt poniżej **$0.50 – $1.50** za pełny cykl treningowy.
Wskazówka: Do zadań czysto narzędziowych, ekstrakcji danych do JSON i pisania kodu świetne rezultaty daje model Qwen 2.5 (7B lub 14B) oraz Llama 3.1 (8B). Oba są w pełni wspierane przez Unsloth.
3. Przygotowanie zbioru danych (Dataset) pod ustrukturyzowany JSON
Sukces fine-tuningu zależy w 80% od jakości danych treningowych. Do nauczenia modelu konkretnego schematu odpowiedzi potrzebujesz zaledwie **od 200 do 1000 wysokiej jakości przykładów** (możesz wygenerować je syntetycznie przy użyciu dużego modelu, np. Claude 3.7 Sonnet przez OpenRouter, a następnie zweryfikować).
Najwygodniejszym formatem jest plik `dataset.jsonl` (JSON Lines) w standardzie **ShareGPT / ChatML**, gdzie każda linia to osobna konwersacja zawierająca rolę systemową, wiadomość użytkownika i idealną odpowiedź modelu:
Najwygodniejszym formatem jest plik `dataset.jsonl` (JSON Lines) w standardzie **ShareGPT / ChatML**, gdzie każda linia to osobna konwersacja zawierająca rolę systemową, wiadomość użytkownika i idealną odpowiedź modelu:
dataset.jsonl
{"conversations": [{"from": "system", "value": "Wyciągnij dane zamówienia z wiadomości klienta i zwróć wyłącznie poprawny obiekt JSON."}, {"from": "human", "value": "Cześć, chciałbym zamówić 3 sztuki routera MikroTik hAP ax3 na firmę TechPol (NIP: 525-000-11-22), wysyłka ekspresowa kurierem."}, {"from": "gpt", "value": "{\"product\": \"MikroTik hAP ax3\", \"quantity\": 3, \"company\": \"TechPol\", \"nip\": \"5250001122\", \"express_shipping\": true}"}]}4. Konfiguracja poda z kartą RTX 4090 w RunPod
Zaloguj się do panelu **RunPod**, doładuj konto kwotą minimum $10 i przejdź do sekcji **Pods → + Deploy**.
1. **Wybór GPU:** Wybierz kartę **RTX 4090 (24 GB VRAM)** (koszt ok. $0.44 – $0.69/h) lub tańszą **RTX 4000 Ada (20 GB VRAM)**.
2. **Wybór szablonu (Template):** Wybierz oficjalny szablon **RunPod PyTorch 2.4.0** (lub nowszy z CUDA 12.1+).
3. **Konfiguracja dysku:** Kliknij *Edit Template* i ustaw **Container Disk** na `50 GB` oraz **Volume Disk** (montowany w `/workspace`) na `50 GB`. W folderze `/workspace` zapiszemy pobrane wagi i nasz finalny model, aby nie stracić ich przy ewentualnym restarcie kontenera.
4. Po uruchomieniu poda kliknij **Connect → Connect to Jupyter Lab**, otwórz okno **Terminal** i zainstaluj bibliotekę Unsloth wewnątrz środowiska:
1. **Wybór GPU:** Wybierz kartę **RTX 4090 (24 GB VRAM)** (koszt ok. $0.44 – $0.69/h) lub tańszą **RTX 4000 Ada (20 GB VRAM)**.
2. **Wybór szablonu (Template):** Wybierz oficjalny szablon **RunPod PyTorch 2.4.0** (lub nowszy z CUDA 12.1+).
3. **Konfiguracja dysku:** Kliknij *Edit Template* i ustaw **Container Disk** na `50 GB` oraz **Volume Disk** (montowany w `/workspace`) na `50 GB`. W folderze `/workspace` zapiszemy pobrane wagi i nasz finalny model, aby nie stracić ich przy ewentualnym restarcie kontenera.
4. Po uruchomieniu poda kliknij **Connect → Connect to Jupyter Lab**, otwórz okno **Terminal** i zainstaluj bibliotekę Unsloth wewnątrz środowiska:
terminal-runpod
# Aktualizacja pip i instalacja Unsloth z wymaganymi zależnościami
pip install --upgrade pip
pip install "unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git"
pip install --no-deps xformers "trl<0.9.0" peft accelerate bitsandbytes5. Trening modelu Qwen 2.5 / Llama 3.1 w Pythonie krok po kroku
Prześlij swój plik `dataset.jsonl` do katalogu `/workspace` (przeciągając go bezpośrednio do paska plików w Jupyter Lab). Następnie utwórz nowy notatnik Jupyter lub plik `train.py`.
Poniższy skrypt ładuje 4-bitowy model bazowy, nakłada na niego adaptery LoRA i przeprowadza proces dostrajania (Supervised Fine-Tuning):
Poniższy skrypt ładuje 4-bitowy model bazowy, nakłada na niego adaptery LoRA i przeprowadza proces dostrajania (Supervised Fine-Tuning):
Wskazówka: Obserwuj parametr Training Loss w konsoli. Powinien stopniowo spadać (np. z poziomu 1.5 do około 0.3–0.5). Jeśli Loss spadnie do 0.01, oznacza to przeuczenie modelu (overfitting) – zmniejsz wtedy liczbę epok (num_train_epochs) do 1 lub 2.
train.py
from unsloth import FastLanguageModel
from unsloth.chat_templates import get_chat_template
from datasets import load_dataset
from trl import SFTTrainer
from transformers import TrainingArguments
import torch
# 1. Konfiguracja i pobranie modelu bazowego (Llama 3.1 8B lub Qwen 2.5 7B)
max_seq_length = 2048
model, tokenizer = FastLanguageModel.from_pretrained(
model_name = "unsloth/Qwen2.5-7B-Instruct-bnb-4bit",
max_seq_length = max_seq_length,
load_in_4bit = True,
)
# 2. Konfiguracja adapterów LoRA
model = FastLanguageModel.get_peft_model(
model,
r = 16, # Ranga LoRA (16 lub 32 to optymalny wybór dla JSON/ekstrakcji)
target_modules = ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
lora_alpha = 16,
lora_dropout = 0,
bias = "none",
use_gradient_checkpointing = "unsloth",
random_state = 3407,
)
# 3. Formatowanie zbioru danych do szablonu ChatML
tokenizer = get_chat_template(
tokenizer,
chat_template = "chatml",
mapping = {"role": "from", "content": "value", "user": "human", "assistant": "gpt"}
)
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 }
dataset = load_dataset("json", data_files = "/workspace/dataset.jsonl", split = "train")
dataset = dataset.map(formatting_prompts_func, batched = True)
# 4. Konfiguracja i start 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,
args = TrainingArguments(
per_device_train_batch_size = 2,
gradient_accumulation_steps = 4,
warmup_steps = 5,
num_train_epochs = 3, # 3 epoki zazwyczaj wystarczają dla zbioru 300-1000 przykładów
learning_rate = 2e-4,
fp16 = not torch.cuda.is_bf16_supported(),
bf16 = torch.cuda.is_bf16_supported(),
logging_steps = 1,
optim = "adamw_8bit",
weight_decay = 0.01,
lr_scheduler_type = "linear",
seed = 3407,
output_dir = "/workspace/outputs",
),
)
trainer.train()6. Eksport wag do GGUF i uruchomienie we własnej Ollamie
Gdy trening dobiegnie końca, w pamięci karty znajdują się wytrenowane adaptery LoRA. Unsloth potrafi automatycznie połączyć je z modelem bazowym i przekonwertować bezpośrednio do pojedynczego pliku **GGUF**, gotowego do uruchomienia na serwerze VPS lub lokalnym komputerze w **Ollama**.
Dodaj na końcu skryptu Pythonowego poniższe linijki, aby zapisać skwantyzowany model `Q4_K_M` (najlepszy balans jakości do rozmiaru – ok. 4.7 GB):
Następnie na swoim serwerze VPS z Ollamą utwórz plik `Modelfile` wskazujący na pobrany plik `.gguf` i zarejestruj model komendą:
`ollama create qwen-json-extractor -f Modelfile`
Dodaj na końcu skryptu Pythonowego poniższe linijki, aby zapisać skwantyzowany model `Q4_K_M` (najlepszy balans jakości do rozmiaru – ok. 4.7 GB):
Następnie na swoim serwerze VPS z Ollamą utwórz plik `Modelfile` wskazujący na pobrany plik `.gguf` i zarejestruj model komendą:
`ollama create qwen-json-extractor -f Modelfile`
export_gguf.py
# Zapisanie gotowego pliku GGUF bezpośrednio na dysku /workspace
model.save_pretrained_gguf(
"/workspace/moj_model_json",
tokenizer,
quantization_method = "q4_k_m"
)Często zadawane pytania (FAQ)
Jak przenieść wygenerowany plik GGUF z RunPoda na mój własny serwer VPS z Ollamą?
Najprościej wysłać model bezpośrednio z terminala RunPod na prywatne repozytorium HuggingFace za pomocą metody model.push_to_hub_gguf("twoj-nick/nazwa-modelu", tokenizer, quantization_method="q4_k_m", token="hf_..."). Następnie na swoim VPS pobierzesz go jednym poleceniem lub przez narzędzie rclone / scp. Pamiętaj, aby po zgraniu plików usunąć (Terminate) poda w RunPod!
Ile przykładów treningowych potrzebuję, żeby model przestał się mylić w strukturze JSON?
Do wyuczenia sztywnego formatu odpowiedzi (np. konkretny schemat JSON z 5–10 polami) zazwyczaj wystarczy od 150 do 300 zróżnicowanych przykładów. Ważniejsze od samej liczby jest to, aby przykłady obejmowały sytuacje brzegowe (np. brakujące dane w wiadomości klienta i przypisanie im wartości null).
Co wybrać jako bazę pod język polski: Llama 3.1 8B czy Qwen 2.5 7B?
Oba modele radzą sobie z językiem polskim dobrze po dostrojeniu, jednak w zadaniach polegających na ścisłym przestrzeganiu formatu wyjściowego (JSON, XML, klasyfikacja danych) oraz kodowaniu model Qwen 2.5 często wykazuje wyższą stabilność i mniejszą tendencję do halucynowania kluczy.