Tutorial bitsandbytes: Kuantisasi 4-bit dan 8-bit untuk Large Language Models
Menjalankan Large Language Model (LLM) seperti Llama, Mistral, atau Falcon membutuhkan GPU dengan VRAM besar. Model dengan 7 miliar parameter saja bisa memakan 14 GB VRAM dalam presisi float16. Bagaimana jika Anda hanya punya GPU consumer-grade dengan 8 GB VRAM? Di sinilah bitsandbytes berperan.
bitsandbytes adalah library Python yang memungkinkan Anda menjalankan model deep learning dalam presisi 4-bit atau 8-bit, secara drastis mengurangi kebutuhan memori tanpa mengorbankan kualitas output secara signifikan. Library ini menjadi fondasi dari teknik QLoRA (Quantized Low-Rank Adaptation), yang memungkinkan fine-tuning model miliaran parameter di GPU consumer.
Dalam tutorial ini, Anda akan belajar cara menggunakan bitsandbytes untuk:
- Memuat model LLM dalam presisi 8-bit dan 4-bit
- Mengonfigurasi parameter kuantisasi untuk hasil optimal
- Melakukan fine-tuning dengan QLoRA
- Mengoptimasi penggunaan memori GPU
Instalasi
Prasyarat
bitsandbytes membutuhkan GPU NVIDIA dengan dukungan CUDA. Pastikan Anda sudah memiliki:
- Python 3.8 atau lebih baru
- PyTorch 2.0 atau lebih baru
- CUDA Toolkit 11.8 atau 12.x
- GPU NVIDIA (compute capability 7.0+, yaitu Volta atau lebih baru)
Instalasi Library
pip install bitsandbytes
pip install transformers accelerate
pip install peft datasets # untuk QLoRA fine-tuning
Untuk memverifikasi instalasi berhasil:
import bitsandbytes as bnb
import torch
print(f"bitsandbytes version: {bnb.version}")
print(f"PyTorch version: {torch.version}")
print(f"CUDA available: {torch.cuda.isavailable()}")
print(f"GPU: {torch.cuda.getdevicename(0)}")
Jika Anda menggunakan Google Colab, bitsandbytes sudah tersedia secara default. Cukup pastikan runtime menggunakan GPU T4 atau lebih baik.
Konsep Dasar Kuantisasi
Sebelum masuk ke kode, penting untuk memahami apa itu kuantisasi dan mengapa hal ini penting.
Apa itu Kuantisasi?
Kuantisasi adalah proses mengubah representasi numerik dari bobot model dari presisi tinggi (float32/float16) ke presisi rendah (int8/int4). Bayangkan seperti mengompresi gambar dari format RAW ke JPEG. Anda kehilangan sedikit detail, tapi ukuran file jauh lebih kecil.
| Presisi | Bit per Parameter | VRAM untuk Model 7B | Kualitas |
|---------|-------------------|---------------------|----------|
| float32 | 32 bit | ~28 GB | Baseline |
| float16 | 16 bit | ~14 GB | Hampir identik |
| int8 | 8 bit | ~7 GB | Sedikit penurunan |
| int4 (NF4) | 4 bit | ~3.5 GB | Penurunan minimal |
Metode Kuantisasi di bitsandbytes
bitsandbytes menyediakan dua metode utama:
Basic Usage: Memuat Model dalam 8-bit
Mari mulai dengan contoh paling sederhana, memuat model dalam presisi 8-bit.
from transformers import AutoModelForCausalLM, AutoTokenizer
modelname = "mistralai/Mistral-7B-Instruct-v0.3"
tokenizer = AutoTokenizer.frompretrained(modelname)
model = AutoModelForCausalLM.frompretrained(
modelname,
loadin8bit=True,
devicemap="auto"
)
prompt = "Jelaskan apa itu machine learning dalam 3 kalimat:"
inputs = tokenizer(prompt, returntensors="pt").to(model.device)
outputs = model.generate(
inputs,
maxnewtokens=150,
temperature=0.7,
dosample=True
)
response = tokenizer.decode(outputs[0], skipspecialtokens=True)
print(response)
Parameter loadin8bit=True memberitahu Transformers untuk menggunakan bitsandbytes dalam memuatkan bobot model. Parameter devicemap="auto" secara otomatis mendistribusikan layer model ke GPU yang tersedia.
Memeriksa Penggunaan Memori
import torch
def printgpumemory():
if torch.cuda.isavailable():
allocated = torch.cuda.memoryallocated() / 10243
reserved = torch.cuda.memoryreserved() / 10243
print(f"GPU Memory Allocated: {allocated:.2f} GB")
print(f"GPU Memory Reserved: {reserved:.2f} GB")
printgpumemory()
Dengan model Mistral-7B, Anda akan melihat penggunaan VRAM sekitar 7-8 GB dibandingkan 14 GB dalam float16.
Kuantisasi 4-bit dengan BitsAndBytesConfig
Untuk penghematan memori yang lebih agresif, gunakan kuantisasi 4-bit dengan konfigurasi detail menggunakan BitsAndBytesConfig.
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
import torch
quantizationconfig = BitsAndBytesConfig(
loadin4bit=True,
bnb4bitquanttype="nf4",
bnb4bitcomputedtype=torch.bfloat16,
bnb4bitusedoublequant=True
)
modelname = "mistralai/Mistral-7B-Instruct-v0.3"
tokenizer = AutoTokenizer.frompretrained(modelname)
model = AutoModelForCausalLM.frompretrained(
modelname,
quantizationconfig=quantizationconfig,
devicemap="auto"
)
printgpumemory()
Mari bahas setiap parameter:
bnb4bitquanttype
Menentukan tipe kuantisasi 4-bit yang digunakan:
"nf4"(NormalFloat4): Dirancang untuk bobot neural network yang berdistribusi normal. Memberikan kualitas lebih baik dibandingkan format 4-bit standar. Ini adalah pilihan default dan direkomendasikan."fp4"(Float4): Format floating-point 4-bit standar. Sedikit lebih cepat dalam beberapa kasus, tapi kualitasnya lebih rendah dari NF4.
# Perbandingan NF4 vs FP4
confignf4 = BitsAndBytesConfig(
loadin4bit=True,
bnb4bitquanttype="nf4",
bnb4bitcomputedtype=torch.bfloat16
)
configfp4 = BitsAndBytesConfig(
loadin4bit=True,
bnb4bitquanttype="fp4",
bnb4bitcomputedtype=torch.bfloat16
)
bnb4bitcomputedtype
Menentukan tipe data yang digunakan saat komputasi (forward pass). Meskipun bobot disimpan dalam 4-bit, komputasi dilakukan dalam presisi lebih tinggi:
torch.bfloat16: Pilihan terbaik untuk GPU Ampere (A100, RTX 3090) dan lebih baru. Stabil secara numerik.torch.float16: Alternatif untuk GPU yang tidak mendukung bfloat16.torch.float32: Paling akurat tapi paling lambat. Jarang diperlukan.
bnb4bitusedoublequant
Double quantization (nested quantization) mengkuantisasi konstanta kuantisasi itu sendiri. Ini menghemat sekitar 0.4 bit per parameter tambahan, atau sekitar 3 GB untuk model 65B parameter. Overhead komputasi minimal.
# Dengan double quant: ~3.5 GB untuk model 7B
configdouble = BitsAndBytesConfig(
loadin4bit=True,
bnb4bitquanttype="nf4",
bnb4bitcomputedtype=torch.bfloat16,
bnb4bitusedoublequant=True
)
Tanpa double quant: ~3.9 GB untuk model 7B
configsingle = BitsAndBytesConfig(
loadin4bit=True,
bnb4bitquanttype="nf4",
bnb4bitcomputedtype=torch.bfloat16,
bnb4bitusedoublequant=False
)
Inference dengan Model Terkuantisasi
Setelah model dimuat, inference berjalan sama seperti model biasa:
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
import torch
quantizationconfig = BitsAndBytesConfig(
loadin4bit=True,
bnb4bitquanttype="nf4",
bnb4bitcomputedtype=torch.bfloat16,
bnb4bitusedoublequant=True
)
modelname = "mistralai/Mistral-7B-Instruct-v0.3"
tokenizer = AutoTokenizer.frompretrained(modelname)
model = AutoModelForCausalLM.frompretrained(
modelname,
quantizationconfig=quantizationconfig,
devicemap="auto"
)
def generateresponse(prompt, maxtokens=256):
messages = [
{"role": "user", "content": prompt}
]
inputids = tokenizer.applychattemplate(
messages,
returntensors="pt",
addgenerationprompt=True
).to(model.device)
outputs = model.generate(
inputids,
maxnewtokens=maxtokens,
temperature=0.7,
topp=0.9,
dosample=True,
repetitionpenalty=1.1
)
response = tokenizer.decode(
outputs[0][inputids.shape[1]:],
skipspecialtokens=True
)
return response
print(generateresponse("Apa keuntungan menggunakan Python untuk data science?"))
print(generateresponse("Tulis fungsi Python untuk menghitung fibonacci:"))
Batch Inference
Untuk memproses banyak prompt sekaligus:
def batchgenerate(prompts, maxtokens=256):
tokenizer.pad
token = tokenizer.eostoken
inputs = tokenizer(
prompts,
return
tensors="pt",
padding=True,
truncation=True,
maxlength=512
).to(model.device)
outputs = model.generate(
inputs,
maxnewtokens=maxtokens,
temperature=0.7,
dosample=True
)
responses = tokenizer.batchdecode(outputs, skipspecialtokens=True)
return responses
prompts = [
"Jelaskan konsep OOP dalam satu paragraf:",
"Apa itu REST API?",
"Sebutkan 3 database populer:"
]
results = batchgenerate(prompts)
for prompt, result in zip(prompts, results):
print(f"Q: {prompt}")
print(f"A: {result}\n")
Advanced Usage: Fine-Tuning dengan QLoRA
QLoRA (Quantized Low-Rank Adaptation) adalah teknik yang menggabungkan kuantisasi 4-bit dari bitsandbytes dengan LoRA dari library PEFT. Ini memungkinkan Anda melakukan fine-tuning model miliaran parameter di GPU consumer.
Arsitektur QLoRA
QLoRA bekerja dengan cara:
Implementasi QLoRA Lengkap
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
BitsAndBytesConfig,
TrainingArguments
)
from peft import LoraConfig, getpeftmodel, preparemodelforkbittraining
from datasets import loaddataset
from trl import SFTTrainer
import torch
1. Konfigurasi kuantisasi
bnbconfig = BitsAndBytesConfig(
loadin4bit=True,
bnb4bitquanttype="nf4",
bnb4bitcomputedtype=torch.bfloat16,
bnb4bitusedoublequant=True
)
2. Muat model dan tokenizer
modelname = "mistralai/Mistral-7B-Instruct-v0.3"
tokenizer = AutoTokenizer.frompretrained(modelname)
tokenizer.padtoken = tokenizer.eostoken
model = AutoModelForCausalLM.frompretrained(
modelname,
quantizationconfig=bnbconfig,
devicemap="auto",
torchdtype=torch.bfloat16
)
3. Persiapkan model untuk k-bit training
model = preparemodelforkbittraining(model)
4. Konfigurasi LoRA
loraconfig = LoraConfig(
r=16,
loraalpha=32,
targetmodules=[
"qproj", "kproj", "vproj", "oproj",
"gateproj", "upproj", "downproj"
],
loradropout=0.05,
bias="none",
tasktype="CAUSALLM"
)
5. Terapkan LoRA ke model
model = getpeftmodel(model, loraconfig)
model.printtrainableparameters()
6. Muat dataset
dataset = loaddataset("timdettmers/openassistant-guanaco", split="train[:5000]")
7. Format data
def formatinstruction(sample):
return f"""### Human: {sample['text'].split('### Human:')[1].split('### Assistant:')[0].strip()}
Assistant: {sample['text'].split('### Assistant:')[1].strip()}"""
8. Konfigurasi training
trainingargs = TrainingArguments(
outputdir="./qlora-output",
numtrainepochs=1,
perdevicetrainbatchsize=4,
gradientaccumulationsteps=4,
learningrate=2e-4,
weightdecay=0.001,
warmupratio=0.03,
lrschedulertype="cosine",
loggingsteps=10,
savestrategy="steps",
savesteps=100,
fp16=False,
bf16=True,
optim="pagedadamw32bit",
gradientcheckpointing=True,
maxgradnorm=0.3,
reportto="none"
)
9. Inisialisasi trainer
trainer = SFTTrainer(
model=model,
traindataset=dataset,
tokenizer=tokenizer,
args=trainingargs,
maxseqlength=512,
)
10. Mulai training
trainer.train()
11. Simpan adapter
trainer.savemodel("./qlora-adapter")
Parameter LoRA yang Perlu Diperhatikan
r (rank): Dimensi dekomposisi low-rank. Nilai lebih tinggi = lebih ekspresif tapi lebih banyak parameter. Rekomendasi: 8-64.# r=8: ~0.1% trainable params, cepat, untuk task sederhana
r=16: ~0.2% trainable params, keseimbangan baik
r=64: ~0.8% trainable params, untuk task kompleks
loraalpha: Faktor scaling untuk bobot LoRA. Biasanya diset ke 2x nilai r.
targetmodules: Layer mana yang mendapat adapter LoRA. Untuk model Llama/Mistral, menargetkan semua linear layers memberikan hasil terbaik.
Menggunakan Paged Optimizers
bitsandbytes menyediakan paged optimizer yang secara otomatis memindahkan state optimizer ke CPU saat GPU kehabisan memori:
import bitsandbytes as bnb
optimizer = bnb.optim.PagedAdamW32bit(
model.parameters(),
lr=2e-4,
weightdecay=0.001
)
Atau gunakan 8-bit Adam untuk penghematan memori lebih lanjut
optimizer8bit = bnb.optim.Adam8bit(
model.parameters(),
lr=2e-4,
weightdecay=0.001
)
Paged optimizer bekerja dengan memanfaatkan unified memory CUDA untuk melakukan page fault handling secara otomatis, mirip dengan virtual memory di sistem operasi.
Menyimpan dan Memuat Model Terkuantisasi
Menyimpan Adapter QLoRA
# Simpan hanya adapter (berukuran kecil, ~50-200 MB)
model.savepretrained("./my-qlora-adapter")
tokenizer.savepretrained("./my-qlora-adapter")
Memuat Kembali Adapter
from peft import PeftModel
Muat model base dalam 4-bit
basemodel = AutoModelForCausalLM.frompretrained(
"mistralai/Mistral-7B-Instruct-v0.3",
quantizationconfig=bnbconfig,
devicemap="auto"
)
Terapkan adapter
model = PeftModel.frompretrained(basemodel, "./my-qlora-adapter")
Merge Adapter ke Model Base
Jika Anda ingin menggabungkan adapter dengan model base untuk deployment yang lebih sederhana:
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
Muat model base dalam full precision untuk merge
basemodel = AutoModelForCausalLM.frompretrained(
"mistralai/Mistral-7B-Instruct-v0.3",
torchdtype=torch.float16,
devicemap="auto"
)
model = PeftModel.frompretrained(basemodel, "./my-qlora-adapter")
mergedmodel = model.mergeandunload()
mergedmodel.savepretrained("./merged-model")
tokenizer.savepretrained("./merged-model")
Optimizer 8-bit bitsandbytes
Selain kuantisasi model, bitsandbytes juga menyediakan optimizer 8-bit yang mengurangi memori yang digunakan oleh optimizer state:
import bitsandbytes as bnb
Pengganti drop-in untuk torch.optim.Adam
optimizer = bnb.optim.Adam8bit(
model.parameters(),
lr=1e-4,
betas=(0.9, 0.999)
)
Tersedia juga:
bnb.optim.AdamW8bit - AdamW dengan weight decay
bnb.optim.SGD8bit - SGD 8-bit
bnb.optim.Adagrad8bit - Adagrad 8-bit
bnb.optim.LAMB8bit - LAMB 8-bit (untuk batch besar)
Optimizer 8-bit menghemat sekitar 75% memori untuk optimizer state. Untuk model 7B, ini berarti penghematan sekitar 8 GB VRAM.
Linear Layer 8-bit
bitsandbytes juga menyediakan layer Linear8bitLt dan Linear4bit yang bisa digunakan langsung dalam arsitektur custom:
import bitsandbytes as bnb
import torch.nn as nn
class EfficientModel(nn.Module):
def init(self, inputdim, hiddendim, outputdim):
super().init()
self.layer1 = bnb.nn.Linear8bitLt(inputdim, hiddendim, hasfp16weights=False)
self.activation = nn.GELU()
self.layer2 = bnb.nn.Linear8bitLt(hiddendim, outputdim, hasfp16weights=False)
def forward(self, x):
x = self.layer1(x)
x = self.activation(x)
x = self.layer2(x)
return x
Best Practices
1. Pilih Metode Kuantisasi yang Tepat
| Skenario | Rekomendasi |
|----------|-------------|
| Inference saja, GPU memadai | 8-bit (LLM.int8()) |
| Inference saja, GPU terbatas | 4-bit NF4 + double quant |
| Fine-tuning | 4-bit NF4 + QLoRA |
| Produksi, perlu kecepatan | GPTQ atau AWQ |
| Riset, perlu fleksibilitas | bitsandbytes 4-bit |
2. Gunakan bfloat16 sebagai Compute Dtype
# Direkomendasikan untuk GPU Ampere+
config = BitsAndBytesConfig(
loadin4bit=True,
bnb4bitcomputedtype=torch.bfloat16 # bukan float16
)
bfloat16 memiliki range yang sama dengan float32 (sehingga lebih stabil secara numerik) dibandingkan float16 yang bisa overflow pada nilai besar.
3. Aktifkan Gradient Checkpointing untuk Training
model.gradientcheckpointingenable()
Atau melalui TrainingArguments
training
args = TrainingArguments(
gradientcheckpointing=True,
# ...
)
Gradient checkpointing mengurangi penggunaan memori secara signifikan dengan trade-off waktu training 20-30% lebih lambat.
4. Monitor Penggunaan Memori
def printmemorystats():
print(f"Allocated: {torch.cuda.memory
allocated() / 10243:.2f} GB")
print(f"Reserved: {torch.cuda.memoryreserved() / 1024
3:.2f} GB")
print(f"Max Allocated: {torch.cuda.maxmemoryallocated() / 10243:.2f} GB")
Panggil sebelum dan sesudah operasi berat
printmemorystats()
5. Perbandingan Ukuran Model
Berikut perbandingan praktis penggunaan VRAM untuk beberapa model populer:
| Model | float16 | 8-bit | 4-bit NF4 | 4-bit + DQ |
|-------|---------|-------|-----------|------------|
| Llama-2-7B | 14 GB | 7 GB | 3.9 GB | 3.5 GB |
| Mistral-7B | 14 GB | 7 GB | 3.9 GB | 3.5 GB |
| Llama-2-13B | 26 GB | 13 GB | 7.3 GB | 6.8 GB |
| Llama-2-70B | 140 GB | 70 GB | 38 GB | 35 GB |
6. Troubleshooting Umum
Error CUDA Out of Memory:
# Kurangi batch size
trainingargs = TrainingArguments(perdevicetrainbatchsize=1)
Aktifkan gradient accumulation
trainingargs = TrainingArguments(
perdevicetrainbatchsize=1,
gradientaccumulation_steps=16
)
Instalasi gagal di Linux:
# Pastikan CUDA toolkit terinstal
nvcc --version
Install versi spesifik
pip install bitsandbytes==0.43.0
Model menghasilkan output buruk setelah kuantisasi:
Pastikan Anda menggunakan NF4 (bukan FP4) dan compute dtype bfloat16. Jika masih buruk, coba 8-bit sebagai alternatif yang lebih konservatif.
Kesimpulan
bitsandbytes adalah tool yang sangat berguna dalam ekosistem LLM modern. Dengan dukungan kuantisasi 4-bit dan 8-bit, library ini memungkinkan siapa saja menjalankan dan melakukan fine-tuning model besar di hardware yang terjangkau.
Poin-poin penting yang perlu diingat:
- Untuk inference: Gunakan 4-bit NF4 dengan double quantization untuk penghematan memori maksimal
- Untuk fine-tuning: Kombinasikan dengan PEFT/LoRA untuk QLoRA, memungkinkan training di GPU consumer
- Optimizer 8-bit: Gunakan
Adam8bitatauPagedAdamW32bituntuk mengurangi memori optimizer state - Pilih compute dtype yang tepat**: bfloat16 untuk GPU Ampere+, float16 untuk GPU lama
Dengan bitsandbytes, barrier untuk bekerja dengan LLM semakin rendah. Anda bisa menjalankan model 7B parameter di GPU dengan 6 GB VRAM, atau melakukan fine-tuning model 13B di GPU dengan 16 GB VRAM. Ini membuka peluang besar untuk eksperimen, penelitian, dan deployment model AI yang lebih mudah diakses.