GPU•8 min czytania•Aktualizacja: 2026-10-20

Fine-tuning modelu Llama 3 / Qwen z użyciem Unsloth w RunPod: Własny model pod JSON za mniej niż $2

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.

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.
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:
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:
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 bitsandbytes

5. 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):
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`
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.