Beranda Blog Store
Natural Language Processing

Fine-tuning Model Bahasa Kecil dengan LoRA untuk Bahasa Indonesia: Persiapan Data sampai Evaluasi

23 Agu 2026 Hartono 5 menit baca 5 Dilihat

Rekan ArtonLabs, artikel ini untuk Anda yang sudah mencoba prompt terbaik dan tetap merasa modelnya kurang konsisten untuk bahasa Indonesia.

Kadang prompt saja tidak cukup. Model kecil yang diminta menulis dengan gaya tertentu, memakai istilah internal, atau mengklasifikasi teks berbahasa Indonesia dengan label khusus akan lebih konsisten kalau dilatih sedikit. Melatih ulang seluruh model tidak masuk akal di perangkat keras biasa, tetapi LoRA memungkinkan melatih hanya sebagian kecil parameter tambahan, seringkali di bawah satu persen. Tulisan ini memandu prosesnya dengan pustaka PEFT dari Hugging Face, dari menyusun data sampai mengukur hasilnya.

Daftar Isi

Apa Itu LoRA

LoRA (Low-Rank Adaptation) membekukan bobot asli model dan menambahkan dua matriks kecil berperingkat rendah di lapisan tertentu, biasanya lapisan perhatian. Hanya matriks kecil itu yang dilatih. Hasilnya adalah "adapter" berukuran beberapa puluh megabyte yang bisa ditempelkan ke model asli saat inferensi. Contoh dari dokumentasi PEFT: pada model 3 miliar parameter, LoRA dengan r=16 hanya melatih sekitar 3,7 juta parameter, kira-kira 0,12 persen. Itu sebabnya ia bisa dilakukan di satu GPU kelas konsumen, atau bahkan di CPU untuk model yang sangat kecil.

Kapan Fine-tuning Layak Dilakukan

Coba dulu prompt yang baik dan beberapa contoh di dalam prompt (few-shot). Kalau itu sudah cukup, berhenti di situ; fine-tuning menambah pekerjaan pemeliharaan. Fine-tuning layak ketika format keluaran harus sangat konsisten (misalnya JSON dengan skema tetap), gaya bahasa harus seragam di ribuan keluaran, atau model harus mengenal kosakata domain yang tidak ada di data pelatihannya. Untuk pengetahuan faktual yang berubah, RAG hampir selalu lebih tepat daripada fine-tuning.

Menyiapkan Data

Format paling sederhana adalah JSONL dengan pasangan instruksi dan jawaban:

{"instruction": "Klasifikasikan sentimen ulasan berikut: positif, negatif, atau netral.", "input": "Pengirimannya cepat, tapi kemasannya penyok.", "output": "netral"}
{"instruction": "Ringkas pengumuman berikut dalam satu kalimat.", "input": "...", "output": "..."}

Beberapa ratus contoh yang bersih dan konsisten lebih berharga daripada ribuan yang berantakan. Perhatikan tiga hal: label harus ditulis persis sama di semua baris (bukan "Positif" di satu baris dan "positif" di baris lain), jawabannya benar-benar diperiksa manusia, dan sisihkan 10 sampai 15 persen data sebagai data uji yang tidak pernah disentuh saat pelatihan. Untuk bahasa Indonesia, pastikan data mencakup ragam yang akan ditemui: bahasa formal, bahasa percakapan, singkatan, dan campuran Inggris.

Lingkungan dan Perangkat Keras

pip install torch transformers peft datasets accelerate

Untuk model 0,5 sampai 3 miliar parameter, GPU dengan 8 sampai 16 GB VRAM cukup. Tanpa GPU, pilih model di bawah satu miliar parameter dan bersiaplah menunggu beberapa jam; ini masih realistis untuk tugas klasifikasi. Layanan GPU sewaan per jam adalah jalan tengah yang masuk akal untuk pelatihan sesekali. Pilih model dasar yang sudah mengenal bahasa Indonesia dengan baik; model keluarga Qwen dan Gemma termasuk yang kami dapati cukup baik.

Melatih dengan PEFT

from transformers import AutoModelForCausalLM, AutoTokenizer, Trainer, TrainingArguments
from peft import LoraConfig, TaskType, get_peft_model
from datasets import load_dataset

model_id = "Qwen/Qwen2.5-3B-Instruct"
tok = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(model_id, device_map="auto")

peft_config = LoraConfig(r=16, lora_alpha=32, lora_dropout=0.05,
                         target_modules=["q_proj", "v_proj"],
                         task_type=TaskType.CAUSAL_LM)
model = get_peft_model(model, peft_config)
model.print_trainable_parameters()

def format_baris(b):
    teks = f"Instruksi: {b['instruction']}\nMasukan: {b['input']}\nJawaban: {b['output']}{tok.eos_token}"
    return tok(teks, truncation=True, max_length=512)

data = load_dataset("json", data_files={"train": "latih.jsonl", "test": "uji.jsonl"})
data = data.map(format_baris, remove_columns=data["train"].column_names)

args = TrainingArguments(output_dir="keluaran", per_device_train_batch_size=4,
                         gradient_accumulation_steps=4, num_train_epochs=3,
                         learning_rate=2e-4, logging_steps=10, save_strategy="epoch")
Trainer(model=model, args=args, train_dataset=data["train"],
        eval_dataset=data["test"], tokenizer=tok).train()
model.save_pretrained("adapter-indo")

Dua parameter yang paling sering perlu disetel: r (8 sampai 32; lebih besar berarti lebih mampu tetapi lebih rawan menghafal) dan learning_rate (1e-4 sampai 3e-4 untuk LoRA). Kalau loss pelatihan turun tetapi loss uji naik, model mulai menghafal; kurangi epoch atau tambah data.

Evaluasi yang Jujur

Loss hanya angka pelatihan. Ukur yang sebenarnya Anda pedulikan pada data uji yang tidak pernah dilihat model: akurasi dan F1 untuk klasifikasi, atau penilaian manusia pada sampel acak untuk tugas menulis. Bandingkan dengan model dasar tanpa adapter memakai prompt yang sama. Kalau selisihnya kecil, fine-tuning mungkin tidak sepadan, dan itu kesimpulan yang sah. Simpan contoh kegagalan; biasanya mereka menunjukkan jenis data yang kurang di himpunan latih.

Memakai dan Menggabungkan Adapter

from peft import PeftModel
base = AutoModelForCausalLM.from_pretrained(model_id, device_map="auto")
model = PeftModel.from_pretrained(base, "adapter-indo")
model = model.merge_and_unload()      # gabungkan agar jadi satu model biasa
model.save_pretrained("model-indo-merged")
tok.save_pretrained("model-indo-merged")

Model gabungan ini bisa dikonversi ke GGUF dan dikuantisasi dengan llama.cpp, lalu dijalankan lewat Ollama di server tanpa GPU, seperti dibahas di artikel kuantisasi. Dengan begitu, pelatihan dilakukan sekali di GPU sewaan, dan inferensinya berjalan murah di VPS biasa.

Klasifikasi atau Generasi: Dua Bentuk Data

Untuk tugas klasifikasi (sentimen, kategori pengaduan, deteksi spam), Anda sebenarnya punya dua pilihan. Pertama, tetap memakai model bahasa generatif seperti di atas dan melatihnya mengeluarkan label sebagai teks; fleksibel, tetapi evaluasinya harus mencocokkan teks keluaran dengan label. Kedua, memakai model encoder yang jauh lebih kecil, misalnya IndoBERT dengan kepala klasifikasi, yang bisa dilatih di CPU dalam hitungan jam dan diinferensi dalam milidetik; kami membahasnya di artikel terpisah. Aturan kami: kalau keluarannya hanya label dari daftar tetap, mulai dari encoder kecil; LoRA pada model generatif untuk tugas yang keluarannya teks bebas.

Menghindari Kebocoran Data Uji

Kesalahan paling umum yang membuat hasil terlihat terlalu bagus: data uji yang sebenarnya mirip atau identik dengan data latih. Pisahkan berdasarkan sumber atau waktu, bukan acak per baris, kalau data Anda punya kelompok alami (satu dokumen menghasilkan banyak contoh, ulasan dari pengguna yang sama). Hapus duplikat dan nyaris-duplikat sebelum membagi. Dan jangan pernah menyetel parameter sambil melihat skor data uji; pakai bagian validasi terpisah untuk itu, dan sentuh data uji hanya sekali di akhir.

, tetapi yang menentukan hasilnya tetap data: beberapa ratus contoh yang bersih, data uji yang terpisah, dan evaluasi yang jujur terhadap model dasar. Kalau tiga hal itu ada, sisanya hanya beberapa puluh baris kode di atas.

Selamat bereksperimen. Kalau hasil fine-tuning Anda ternyata tidak lebih baik dari model dasar, itu temuan yang sah juga, dan kami senang mendengarnya.