Kamu sudah mencoba segala trik prompt, tapi chatbot support-mu tetap membalas laporan bug dengan format ngawur — kadang JSON, kadang paragraf panjang, kadang bagian langkah reproduksi hilang begitu saja. RAG memberi model bahan bacaan, tapi ia tidak mengubah cara model menyusun jawaban. Kalau format dan gaya harus patuh sepenuhnya, fine-tuning adalah jalannya. LoRA dan QLoRA membuat pekerjaan ini terjangkau: kamu cukup satu GPU 16 GB atau Colab gratis.
Kenapa LoRA, Bukan Full Fine-Tuning
Full fine-tuning memperbarui seluruh miliaran parameter model. Butuh ratusan GB VRAM, waktu berhari-hari, dan menghasilkan file sebesar model aslinya. LoRA (Low-Rank Adaptation) mengambil jalan pintas: bobot asli dibekukan, lalu matriks kecil ber-rank rendah disuntikkan ke lapisan attention. Hasilnya, kamu cuma melatih di bawah 1% parameter dengan kualitas yang nyaris setara full fine-tuning di banyak kasus.
QLoRA mendorong ini lebih jauh dengan mengkuantisasi model dasar ke 4-bit sebelum melatih adapter LoRA. Model 7B yang biasanya butuh sekitar 14 GB VRAM menyusut ke sekitar 4 GB. Itu sebabnya tutorial ini bisa dijalankan di Colab free tier.
Kamu butuh GPU. Kalau tidak punya, pakai Google Colab (runtime T4 gratis) atau Kaggle Notebook. CPU bisa jalan, tapi 10–20x lebih lambat bahkan untuk model sekecil ini.
Siapkan Lingkungan
Buat virtual environment lalu pasang dependensinya. Versi kunci: transformers ≥ 4.40, peft ≥ 0.10, trl ≥ 0.9, dan bitsandbytes yang cocok dengan CUDA-mu.
python -m venv .venv
source .venv/bin/activate
pip install torch transformers peft trl datasets bitsandbytes accelerate
Muat Model 4-Bit dengan bitsandbytes
Kita pakai Qwen/Qwen2.5-0.5B-Instruct, model 0,5 miliar parameter berlisensi Apache 2.0 — cukup kecil untuk latihan pertama, cukup bagus untuk melihat efek fine-tuning. Kalau istilah tokenizer dan pipeline masih asing, baca dulu tutorial Hugging Face Transformers. Kode berikut memuat model dalam 4-bit lewat BitsAndBytesConfig.
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig
model_name = "Qwen/Qwen2.5-0.5B-Instruct"
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.float16,
bnb_4bit_use_double_quant=True,
)
model = AutoModelForCausalLM.from_pretrained(
model_name,
quantization_config=bnb_config,
device_map="auto",
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token
Siapkan Dataset
Untuk demonstrasi kita pakai dataset kecil buatan sendiri: model dilatih menulis subject email dan meringkas insiden dalam format poin. Di proyek nyata, ganti dengan ratusan atau ribuan contoh dari domainmu. Kualitas data jauh lebih menentukan hasil daripada ukuran model.
from datasets import Dataset
contoh_data = [
{
"instruction": "Tulis subject email follow-up untuk klien yang belum membayar invoice.",
"response": "Subject: Pengingat Pembayaran Invoice #INV-1024\n\nHalo Pak/Bu,\n\nKami ingin mengingatkan bahwa invoice #INV-1024 jatuh tempo pada 15 Oktober 2026...",
},
{
"instruction": "Ringkas laporan insiden berikut dalam tiga poin.",
"response": "1. Server prod lonjakan CPU 90% selama 20 menit\n2. Penyebab: query tanpa index di tabel orders\n3. Perbaikan: tambah index dan restart worker",
},
{
"instruction": "Jelaskan perbedaan full fine-tuning dan LoRA dalam dua kalimat.",
"response": "Full fine-tuning memperbarui semua parameter model sehingga butuh VRAM besar. LoRA membekukan bobot asli dan hanya melatih matriks adapter kecil, jadi jauh lebih hemat.",
},
]
def format_prompt(contoh):
return {
"text": f"### Instruksi:\n{contoh['instruction']}\n\n### Jawaban:\n{contoh['response']}"
}
dataset = Dataset.from_list(contoh_data).map(format_prompt)
Konfigurasi LoRA
PEFT menangani semua detail LoRA. Target modul q_proj, k_proj, v_proj, o_proj adalah lapisan attention yang paling umum disuntik. Parameter r mengontrol rank matriks adapter: makin besar makin ekspresif, tapi makin berat. Kalau tensor dan training loop masih terasa asing, tutorial PyTorch bisa menutup celahnya.
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
model = prepare_model_for_kbit_training(model)
lora_config = LoraConfig(
r=16,
lora_alpha=32,
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM",
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
Mulai dari r=16 dan lora_alpha=32. Kalau model overfit — jawabannya mulai menghafal data latih — turunkan r atau naikkan lora_dropout. Jangan ubah banyak hal sekaligus, nanti kamu tidak tahu mana yang berpengaruh.
Jalankan Training
TrainingArguments mengatur batch, learning rate, dan epoch. Nilai learning_rate=2e-4 adalah titik awal yang umum untuk LoRA. gradient_accumulation_steps=4 meniru batch 8 meski GPU cuma muat batch 2, jadi kamu tetap bisa melatih dengan batch efektif yang lebih stabil.
from transformers import TrainingArguments
from trl import SFTTrainer
training_args = TrainingArguments(
output_dir="./hasil-finetune",
per_device_train_batch_size=2,
gradient_accumulation_steps=4,
num_train_epochs=3,
learning_rate=2e-4,
fp16=True,
logging_steps=10,
save_strategy="epoch",
report_to="none",
)
trainer = SFTTrainer(
model=model,
args=training_args,
train_dataset=dataset,
dataset_text_field="text",
max_seq_length=512,
)
trainer.train()
Uji dan Simpan Model
Setelah training selesai, simpan adapter LoRA-nya dulu — file ini cuma beberapa MB. Untuk inferensi cepat, gabungkan adapter ke model dasar dengan merge_and_unload(), lalu simpan sebagai satu model utuh.
model.save_pretrained("./model-lora")
tokenizer.save_pretrained("./model-lora")
from peft import PeftModel
base = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16,
device_map="auto",
)
model_gabung = PeftModel.from_pretrained(base, "./model-lora")
model_gabung = model_gabung.merge_and_unload()
model_gabung.save_pretrained("./model-final")
tokenizer.save_pretrained("./model-final")
from transformers import pipeline
pipe = pipeline("text-generation", model="./model-final", tokenizer="./model-final")
prompt = "### Instruksi:\nTulis subject email follow-up untuk klien yang belum membayar invoice.\n\n### Jawaban:\n"
hasil = pipe(prompt, max_new_tokens=100, do_sample=True, temperature=0.7)
print(hasil[0]["generated_text"])
Langkah Berikutnya
Naikkan datanya: kumpulkan 500–1000 contoh dari domainmu, jalankan lagi dengan Qwen/Qwen2.5-1.5B-Instruct atau Llama-3.2-1B-Instruct, lalu bandingkan hasilnya lewat set evaluasi kecil berisi 50–100 kasus. Simpan tiap adapter versi terbaik dengan nama yang jelas, karena kamu akan sering kembali mengulang eksperimen ini.