Tutorial bitsandbytes: Kuantisasi 4-bit dan 8-bit untuk Large Language Models

# 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 den...

By Ruby Abdullah · · tutorial
bitsandbytesQuantizationLLMQLoRAPyTorch

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:

  • LLM.int8() - Kuantisasi 8-bit dengan dekomposisi mixed-precision. Kolom outlier dipertahankan dalam float16, sisanya dikuantisasi ke int8.
  • NF4 (NormalFloat4) - Kuantisasi 4-bit yang dirancang khusus untuk bobot neural network yang berdistribusi normal. Ini adalah format default untuk QLoRA.
  • 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.padtoken = tokenizer.eostoken

    inputs = tokenizer(

    prompts,

    returntensors="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:

  • Memuat model base dalam presisi 4-bit (NF4) menggunakan bitsandbytes
  • Menambahkan adapter LoRA (trainable) dalam presisi bfloat16
  • Hanya melatih parameter LoRA (biasanya 0.1-1% dari total parameter)
  • Gradient di-backpropagate melalui frozen 4-bit weights
  • 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

    lora
    alpha: 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

    trainingargs = 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.memoryallocated() / 10243:.2f} GB")

    print(f"Reserved: {torch.cuda.memoryreserved() / 10243:.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 Adam8bit atau PagedAdamW32bit untuk 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.

    Artikel Terkait

    Tutorial LitGPT: Framework untuk Melatih dan Fine-Tuning Large Language Models

    Tutorial LitGPT: Framework untuk Melatih dan Fine-Tuning Large Language Models LitGPT adalah framework open-source dari ...

    Tutorial Torchtune: Fine-Tuning LLM dengan PyTorch Native

    Tutorial Torchtune: Fine-Tuning LLM dengan PyTorch Native Pendahuluan Torchtune adalah library resmi dari PyTorch untuk ...

    Tutorial Axolotl: Fine-Tuning LLM Berbasis Konfigurasi YAML

    Fine-Tuning LLM Berbasis Konfigurasi dengan Axolotl Kebanyakan proyek fine-tuning dimulai dengan cara yang sama: seseora...

    Tutorial Unsloth: Fine-Tuning LLM yang Cepat dan Hemat Memori

    Fine-Tuning LLM Secara Efisien dengan Unsloth Dahulu, melakukan fine-tuning model bahasa besar membutuhkan server multi-...