QLoRA — Fine-tuning با کارت گرافیک معمولی

قسمت ۴ ۱۸ دقیقه

مشکل: مدل‌های بزرگ، GPU‌های کوچک

اپیزود قبلی دیدیم که LoRA خیلی کمک می‌کنه، ولی هنوز یه مشکل داریم. فرض کن می‌خوای مدل LLaMA 3.1 70B رو Fine-tune کنی. حتی با LoRA، فقط لود کردن مدل در FP16 حدود ۱۴۰ گیگابایت حافظه می‌خواد. هیچ GPU مصرفی این حافظه رو نداره.

اینجاست که QLoRA وارد می‌شه — ترکیب هوشمندانه Quantization و LoRA.

Quantization چیه؟

Quantization یعنی کاهش دقت اعداد. به جای اینکه هر وزن رو با ۱۶ بیت (FP16) ذخیره کنی، با ۸ بیت یا حتی ۴ بیت ذخیره‌اش کن. اینطوری حافظه مصرفی به شدت کم می‌شه.

# مقایسه حافظه مصرفی مدل 70B
model_params = 70e9  # 70 میلیارد پارامتر

fp32 = model_params * 4 / 1e9   # هر پارامتر 4 بایت
fp16 = model_params * 2 / 1e9   # هر پارامتر 2 بایت
int8 = model_params * 1 / 1e9   # هر پارامتر 1 بایت
int4 = model_params * 0.5 / 1e9 # هر پارامتر 0.5 بایت

print(f"FP32: {fp32:.0f} GB")   # 280 GB
print(f"FP16: {fp16:.0f} GB")   # 140 GB
print(f"INT8: {int8:.0f} GB")   # 70 GB
print(f"INT4: {int4:.0f} GB")   # 35 GB

یعنی با 4-bit quantization، مدل ۷۰ میلیاردی فقط ۳۵ گیگابایت حافظه می‌خواد! که توی یه A100 80GB یا حتی دو تا RTX 4090 جا می‌شه.

QLoRA: بهترین دو دنیا

QLoRA (Quantized LoRA) از سه نوآوری کلیدی استفاده می‌کنه:

۱. NF4: نوع جدید 4-bit

NF4 (NormalFloat 4-bit) یه فرمت عددی خاصه که برای وزن‌های شبکه عصبی بهینه شده. چون وزن‌های شبکه عصبی معمولاً توزیع نرمال دارن، NF4 بیت‌ها رو طوری تقسیم می‌کنه که دقت بیشتری توی بازه‌های پرتراکم‌تر داشته باشه.

from transformers import BitsAndBytesConfig

# تنظیم 4-bit quantization با NF4
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,                  # 4-bit quantization
    bnb_4bit_quant_type="nf4",          # NormalFloat 4-bit
    bnb_4bit_compute_dtype=torch.bfloat16,  # محاسبات در bfloat16
    bnb_4bit_use_double_quant=True,     # Double Quantization
)

۲. Double Quantization

توی quantization معمولی، هر بلاک از وزن‌ها یه ثابت مقیاس (scaling constant) داره که خودش ۳۲ بیته. Double Quantization میاد و این ثابت‌های مقیاس رو هم quantize می‌کنه! نتیجه: صرفه‌جویی بیشتر در حافظه.

# Double Quantization — مفهومی
# Quantization معمولی:
#   وزن‌ها: 4-bit
#   ثابت مقیاس: 32-bit (هر 64 وزن یه ثابت)
#   overhead: 32/64 = 0.5 bit per param

# Double Quantization:
#   وزن‌ها: 4-bit
#   ثابت مقیاس: quantize شده به 8-bit
#   overhead: 8/64 + 32/1024 ≈ 0.16 bit per param

# صرفه‌جویی: ~0.34 bit per param
# برای مدل 70B: 0.34 × 70e9 / 8 / 1e9 ≈ 3 GB کمتر!

۳. Paged Optimizers

وقتی GPU حافظه‌اش پر می‌شه، QLoRA از مکانیزم paging استفاده می‌کنه و optimizer states رو موقتاً به RAM سیستم منتقل می‌کنه. اینطوری GPU هیچوقت out of memory نمی‌ده.

پیاده‌سازی عملی QLoRA

import torch
from transformers import (
    AutoModelForCausalLM,
    AutoTokenizer,
    BitsAndBytesConfig,
    TrainingArguments,
)
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from trl import SFTTrainer

# ۱. تنظیم Quantization
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_use_double_quant=True,
)

# ۲. لود مدل با 4-bit quantization
model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-3.1-8B-Instruct",
    quantization_config=bnb_config,
    device_map="auto",
)

tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.1-8B-Instruct")
tokenizer.pad_token = tokenizer.eos_token

# ۳. آماده‌سازی مدل برای آموزش k-bit
model = prepare_model_for_kbit_training(model)

# ۴. تنظیم LoRA
lora_config = LoraConfig(
    r=16,
    lora_alpha=32,
    target_modules=[
        "q_proj", "k_proj", "v_proj", "o_proj",
        "gate_proj", "up_proj", "down_proj",
    ],
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",
)

# ۵. اعمال LoRA
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# خروجی: trainable params: 41,943,040 || all params: 4,579,280,896
#          || trainable%: 0.9160%

مقایسه مصرف حافظه

# مقایسه عملی — مدل LLaMA 3.1 8B
methods = {
    "Full Fine-tuning (FP16)":     "~60 GB VRAM",
    "LoRA (FP16)":                 "~18 GB VRAM",
    "QLoRA (4-bit + LoRA)":        "~6 GB VRAM",
}

for method, memory in methods.items():
    print(f"{method}: {memory}")

# مدل LLaMA 3.1 70B
methods_70b = {
    "Full Fine-tuning (FP16)":     "~500 GB VRAM (غیرممکن)",
    "LoRA (FP16)":                 "~150 GB VRAM (چند GPU)",
    "QLoRA (4-bit + LoRA)":        "~48 GB VRAM (1× A100 80GB!)",
}
نکته کلیدی: با QLoRA می‌تونی مدل ۷۰ میلیاردی رو روی یه GPU A100 80GB آموزش بدی. یا مدل ۸ میلیاردی رو روی یه RTX 3060 12GB!

کتابخانه bitsandbytes

bitsandbytes کتابخانه‌ایه که quantization رو انجام می‌ده. نصبش ساده‌ست:

# نصب
# pip install bitsandbytes

# بررسی نصب
import bitsandbytes as bnb
print(bnb.__version__)

# بررسی CUDA
import torch
print(f"CUDA available: {torch.cuda.is_available()}")
print(f"GPU: {torch.cuda.get_device_name(0)}")
print(f"VRAM: {torch.cuda.get_device_properties(0).total_mem / 1e9:.1f} GB")

نکته مهم درباره bitsandbytes

  • فقط روی GPU‌های NVIDIA کار می‌کنه (نیاز به CUDA)
  • برای Mac با Apple Silicon، از MLX استفاده کن
  • نسخه ۰.۴۲+ نیاز داری برای بهترین عملکرد

آموزش کامل با QLoRA

from datasets import load_dataset

# ۱. لود دیتاست
dataset = load_dataset("json", data_files="my_data.jsonl", split="train")

# ۲. فرمت‌دهی داده
def format_chat(example):
    messages = [
        {"role": "system", "content": "تو یه دستیار هوشمند هستی."},
        {"role": "user", "content": example["question"]},
        {"role": "assistant", "content": example["answer"]},
    ]
    text = tokenizer.apply_chat_template(
        messages, tokenize=False, add_generation_prompt=False
    )
    return {"text": text}

dataset = dataset.map(format_chat)

# ۳. تنظیمات آموزش
training_args = TrainingArguments(
    output_dir="./qlora-output",
    num_train_epochs=3,
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,     # effective batch size = 16
    learning_rate=2e-4,
    warmup_ratio=0.03,
    logging_steps=10,
    save_strategy="epoch",
    fp16=False,
    bf16=True,                         # bfloat16 برای محاسبات
    optim="paged_adamw_8bit",          # Paged optimizer
    gradient_checkpointing=True,       # صرفه‌جویی بیشتر حافظه
    max_grad_norm=0.3,
    lr_scheduler_type="cosine",
)

# ۴. شروع آموزش
trainer = SFTTrainer(
    model=model,
    tokenizer=tokenizer,
    train_dataset=dataset,
    args=training_args,
    max_seq_length=2048,
    dataset_text_field="text",
)

trainer.train()

GPU‌های مناسب QLoRA

با QLoRA، Fine-tuning دیگه محدود به GPU‌های گرون نیست:

  • RTX 3060 12GB: مدل‌های ۷-۸ میلیاردی (batch size=1)
  • RTX 3090 / 4090 24GB: مدل‌های ۷-۸ میلیاردی راحت، ۱۳ میلیاردی با تنظیم
  • A100 40GB: مدل‌های تا ۳۰ میلیاردی
  • A100 80GB: مدل‌های ۷۰ میلیاردی
# تنظیم batch size بر اساس حافظه GPU
def suggest_batch_size(gpu_vram_gb, model_size_b, quantization_bits=4):
    """پیشنهاد batch size بر اساس GPU و مدل"""
    # تخمین حافظه مدل quantize شده
    model_memory = model_size_b * quantization_bits / 8 / 1e9
    
    # حافظه باقی‌مانده برای آموزش
    available = gpu_vram_gb - model_memory - 2  # 2GB overhead
    
    if available < 2:
        return 1, 8  # batch_size=1, grad_accum=8
    elif available < 6:
        return 2, 4
    elif available < 12:
        return 4, 2
    else:
        return 8, 1

# مثال
bs, ga = suggest_batch_size(gpu_vram_gb=24, model_size_b=8)
print(f"Batch size: {bs}, Gradient accumulation: {ga}")
# خروجی: Batch size: 4, Gradient accumulation: 2

QLoRA در سرویس‌های ابری

اگه GPU مناسب نداری، می‌تونی از سرویس‌های ابری استفاده کنی:

  • Google Colab (رایگان): T4 16GB — برای مدل‌های ۷-۸B
  • Google Colab Pro: A100 40GB — برای مدل‌های تا ۳۰B
  • RunPod / Lambda Labs: انتخاب GPU دلخواه با قیمت ساعتی
  • Kaggle: P100 16GB یا T4 16GB رایگان

آیا Quantization کیفیت رو کم می‌کنه؟

سوال خوبیه. وقتی دقت عددی رو کم می‌کنی، یه مقدار اطلاعات از دست می‌ره. ولی تحقیقات نشون داده:

مقاله اصلی QLoRA نشون داد که عملکرد QLoRA (4-bit) تقریباً برابر با Full Fine-tuning (16-bit) هست. تفاوت در اکثر benchmark‌ها کمتر از ۱ درصده.

دلیلش اینه که وزن‌های quantize شده فقط برای forward pass استفاده می‌شن. گرادیان‌ها و آپدیت‌ها در دقت بالاتر (bfloat16) محاسبه می‌شن.

نکات مهم

  • از bnb_4bit_compute_dtype=torch.bfloat16 استفاده کن (نه float16)
  • gradient_checkpointing=True رو فعال کن برای صرفه‌جویی بیشتر
  • optim="paged_adamw_8bit" برای paged optimizer
  • اگه GPU حافظه کم داره، batch_size رو کم کن و gradient_accumulation رو زیاد کن
  • مدل quantize شده رو نمی‌تونی مستقیم merge کنی — اول باید dequantize بشه

جمع‌بندی

QLoRA دموکراتیزه کردن Fine-tuning رو واقعیت بخشیده. حالا هرکسی با یه GPU معمولی می‌تونه مدل‌های بزرگ رو Fine-tune کنه. ترکیب 4-bit quantization با LoRA، حافظه مصرفی رو به شکل چشمگیری کاهش می‌ده بدون اینکه کیفیت رو فدا کنه.

ولی بهترین مدل و بهترین ابزار بدون داده خوب فایده‌ای نداره. توی اپیزود بعدی، مهم‌ترین بخش Fine-tuning رو بررسی می‌کنیم: آماده‌سازی دیتاست.

نظرات

هنوز نظری ثبت نشده. اولین نفر باشید!

نظر خود را بنویسید