Unsloth — سریع‌ترین ابزار Fine-tuning

قسمت ۶ ۲۰ دقیقه

چرا Unsloth؟

تا الان ابزارهای مختلفی برای Fine-tuning دیدیم — Hugging Face Transformers، PEFT، TRL. همه‌شون خوبن ولی Unsloth یه چیز خاص داره: سرعت و مصرف حافظه فوق‌العاده بهینه.

  • ۲ برابر سریع‌تر از Hugging Face + PEFT
  • ۶۰ درصد حافظه کمتر مصرف می‌کنه
  • بدون تغییر کیفیت — نتیجه دقیقاً یکیه
  • نصب ساده و API شبیه Hugging Face

Unsloth این بهینه‌سازی رو با بازنویسی kernel‌های CUDA و حذف عملیات غیرضروری انجام می‌ده. یعنی همون محاسبات رو انجام می‌ده ولی خیلی بهینه‌تر.

نصب Unsloth

# نصب در Google Colab یا محیط محلی
# pip install unsloth

# یا نسخه شبانه (nightly) برای آخرین ویژگی‌ها
# pip install "unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git"

# بررسی نصب
import unsloth
print(f"Unsloth version: {unsloth.__version__}")

import torch
print(f"CUDA: {torch.cuda.is_available()}")
print(f"GPU: {torch.cuda.get_device_name(0)}")

قدم ۱: لود مدل

from unsloth import FastLanguageModel
import torch

# تنظیمات
max_seq_length = 2048
dtype = None  # auto-detect (بهترین dtype برای GPU)
load_in_4bit = True  # QLoRA

# لود مدل با Unsloth
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name="unsloth/Llama-3.1-8B-Instruct",  # مدل از Hugging Face
    max_seq_length=max_seq_length,
    dtype=dtype,
    load_in_4bit=load_in_4bit,
)

print("مدل با موفقیت لود شد!")
print(f"حافظه مصرفی: {torch.cuda.memory_allocated() / 1e9:.2f} GB")
نکته: Unsloth مدل‌های بهینه‌شده خودش رو داره (با پیشوند unsloth/). این مدل‌ها سریع‌تر لود می‌شن ولی می‌تونی از هر مدل Hugging Face هم استفاده کنی.

قدم ۲: تنظیم LoRA

# اضافه کردن LoRA adapters
model = FastLanguageModel.get_peft_model(
    model,
    r=16,                      # LoRA rank
    target_modules=[
        "q_proj", "k_proj", "v_proj", "o_proj",
        "gate_proj", "up_proj", "down_proj",
    ],
    lora_alpha=16,
    lora_dropout=0,            # Unsloth توصیه می‌کنه 0
    bias="none",
    use_gradient_checkpointing="unsloth",  # بهینه‌سازی حافظه Unsloth
    random_state=3407,
    use_rslora=False,
    loftq_config=None,
)

# بررسی پارامترهای قابل آموزش
model.print_trainable_parameters()
# خروجی مثلاً: trainable params: 41,943,040 (0.92%)

قدم ۳: آماده‌سازی دیتاست

from datasets import load_dataset

# لود دیتاست — مثال با یه دیتاست آماده
dataset = load_dataset("json", data_files="my_dataset.jsonl", split="train")

# تعریف قالب مکالمه
chat_template = """{{ bos_token }}
{% for message in messages %}
{% if message['role'] == 'system' %}
<|start_header_id|>system<|end_header_id|>
{{ message['content'] }}<|eot_id|>
{% elif message['role'] == 'user' %}
<|start_header_id|>user<|end_header_id|>
{{ message['content'] }}<|eot_id|>
{% elif message['role'] == 'assistant' %}
<|start_header_id|>assistant<|end_header_id|>
{{ message['content'] }}<|eot_id|>
{% endif %}
{% endfor %}"""

# یا ساده‌تر — از chat template خود مدل استفاده کن
def format_prompts(examples):
    texts = []
    for messages in examples["messages"]:
        text = tokenizer.apply_chat_template(
            messages, 
            tokenize=False, 
            add_generation_prompt=False
        )
        texts.append(text)
    return {"text": texts}

dataset = dataset.map(format_prompts, batched=True)

# بررسی یه نمونه
print(dataset[0]["text"][:500])

اگه دیتاستت فرمت instruction داره:

# تبدیل فرمت instruction به messages
def convert_to_messages(examples):
    all_messages = []
    for instruction, output in zip(examples["instruction"], examples["output"]):
        messages = [
            {"role": "system", "content": "تو یه دستیار هوشمند و دقیق هستی."},
            {"role": "user", "content": instruction},
            {"role": "assistant", "content": output},
        ]
        all_messages.append(messages)
    return {"messages": all_messages}

dataset = dataset.map(convert_to_messages, batched=True)
dataset = dataset.map(format_prompts, batched=True)

قدم ۴: تنظیمات آموزش

from trl import SFTTrainer
from transformers import TrainingArguments

trainer = SFTTrainer(
    model=model,
    tokenizer=tokenizer,
    train_dataset=dataset,
    dataset_text_field="text",
    max_seq_length=max_seq_length,
    dataset_num_proc=2,
    packing=False,  # True برای نمونه‌های کوتاه (صرفه‌جویی زمان)
    args=TrainingArguments(
        # تنظیمات اصلی
        output_dir="outputs",
        num_train_epochs=3,
        
        # Batch size
        per_device_train_batch_size=2,
        gradient_accumulation_steps=4,   # effective batch = 8
        
        # Learning rate
        learning_rate=2e-4,
        warmup_steps=5,
        
        # بهینه‌سازی حافظه
        fp16=not torch.cuda.is_bf16_supported(),
        bf16=torch.cuda.is_bf16_supported(),
        optim="adamw_8bit",
        
        # Logging
        logging_steps=10,
        save_strategy="epoch",
        
        # سرعت
        weight_decay=0.01,
        lr_scheduler_type="linear",
        seed=3407,
    ),
)

# بررسی حافظه قبل از شروع
gpu_stats = torch.cuda.get_device_properties(0)
used_memory = round(torch.cuda.memory_allocated() / 1024**3, 2)
total_memory = round(gpu_stats.total_mem / 1024**3, 2)
print(f"GPU: {gpu_stats.name}")
print(f"حافظه مصرفی: {used_memory} GB / {total_memory} GB")

قدم ۵: شروع آموزش

# شروع آموزش!
trainer_stats = trainer.train()

# نمایش آمار آموزش
print(f"زمان آموزش: {trainer_stats.metrics['train_runtime']:.0f} ثانیه")
print(f"نمونه در ثانیه: {trainer_stats.metrics['train_samples_per_second']:.1f}")

# حافظه مصرفی نهایی
used_memory = round(torch.cuda.max_memory_allocated() / 1024**3, 2)
print(f"حداکثر حافظه مصرفی: {used_memory} GB")

قدم ۶: تست مدل

# تست سریع مدل Fine-tune شده
FastLanguageModel.for_inference(model)  # سوئیچ به حالت inference

messages = [
    {"role": "system", "content": "تو یه دستیار هوشمند هستی."},
    {"role": "user", "content": "فرق بین LoRA و QLoRA رو توضیح بده."},
]

inputs = tokenizer.apply_chat_template(
    messages,
    tokenize=True,
    add_generation_prompt=True,
    return_tensors="pt",
).to("cuda")

outputs = model.generate(
    input_ids=inputs,
    max_new_tokens=512,
    temperature=0.7,
    use_cache=True,
)

response = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(response)

قدم ۷: ذخیره مدل

# روش ۱: ذخیره فقط adapter (کم‌حجم)
model.save_pretrained("lora_model")
tokenizer.save_pretrained("lora_model")
# حجم: ~50-100 MB

# روش ۲: ذخیره مدل merge شده (کامل)
model.save_pretrained_merged(
    "merged_model",
    tokenizer,
    save_method="merged_16bit",  # یا "merged_4bit"
)
# حجم: ~16 GB (برای 8B model)

# روش ۳: ذخیره به فرمت GGUF (برای llama.cpp)
model.save_pretrained_gguf(
    "gguf_model",
    tokenizer,
    quantization_method="q4_k_m",  # یا q8_0, f16, ...
)
# حجم: ~4-8 GB

# روش ۴: آپلود به Hugging Face
model.push_to_hub_merged(
    "your-username/my-fine-tuned-model",
    tokenizer,
    save_method="merged_16bit",
    token="hf_your_token",
)
print("مدل آپلود شد!")

کد کامل از صفر تا صد

"""
Fine-tuning کامل با Unsloth
از لود مدل تا ذخیره نهایی
"""
from unsloth import FastLanguageModel
from trl import SFTTrainer
from transformers import TrainingArguments
from datasets import load_dataset
import torch

# ===== تنظیمات =====
MODEL_NAME = "unsloth/Llama-3.1-8B-Instruct"
DATASET_PATH = "my_dataset.jsonl"
OUTPUT_DIR = "outputs"
MAX_SEQ_LENGTH = 2048
EPOCHS = 3
BATCH_SIZE = 2
GRAD_ACCUM = 4
LEARNING_RATE = 2e-4
LORA_RANK = 16

# ===== لود مدل =====
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name=MODEL_NAME,
    max_seq_length=MAX_SEQ_LENGTH,
    load_in_4bit=True,
)

# ===== LoRA =====
model = FastLanguageModel.get_peft_model(
    model, r=LORA_RANK, lora_alpha=LORA_RANK,
    target_modules=["q_proj","k_proj","v_proj","o_proj",
                     "gate_proj","up_proj","down_proj"],
    lora_dropout=0, bias="none",
    use_gradient_checkpointing="unsloth",
)

# ===== دیتاست =====
dataset = load_dataset("json", data_files=DATASET_PATH, split="train")

def format_fn(examples):
    texts = []
    for msgs in examples["messages"]:
        texts.append(tokenizer.apply_chat_template(
            msgs, tokenize=False, add_generation_prompt=False
        ))
    return {"text": texts}

dataset = dataset.map(format_fn, batched=True)

# ===== آموزش =====
trainer = SFTTrainer(
    model=model, tokenizer=tokenizer,
    train_dataset=dataset,
    dataset_text_field="text",
    max_seq_length=MAX_SEQ_LENGTH,
    args=TrainingArguments(
        output_dir=OUTPUT_DIR,
        num_train_epochs=EPOCHS,
        per_device_train_batch_size=BATCH_SIZE,
        gradient_accumulation_steps=GRAD_ACCUM,
        learning_rate=LEARNING_RATE,
        warmup_steps=5,
        bf16=torch.cuda.is_bf16_supported(),
        fp16=not torch.cuda.is_bf16_supported(),
        optim="adamw_8bit",
        logging_steps=10,
        save_strategy="epoch",
        seed=3407,
    ),
)

stats = trainer.train()
print(f"آموزش تمام شد! زمان: {stats.metrics['train_runtime']:.0f}s")

# ===== ذخیره =====
model.save_pretrained("lora_model")
tokenizer.save_pretrained("lora_model")
print("مدل ذخیره شد!")

نکات و ترفندها

  • Packing: اگه نمونه‌هات کوتاهن (زیر ۵۱۲ توکن)، packing=True بذار تا چند نمونه توی یه sequence جا بشن
  • gradient_checkpointing: حتماً از "unsloth" استفاده کن — ۳۰٪ سریع‌تر از معمولیه
  • lora_dropout=0: Unsloth توصیه می‌کنه dropout رو ۰ بذاری
  • مدل‌های Unsloth: مدل‌های با پیشوند unsloth/ سریع‌تر لود می‌شن
  • Multi-GPU: Unsloth فعلاً فقط روی یه GPU کار می‌کنه

Unsloth بهترین انتخاب برای Fine-tuning روی یه GPU هست. اگه چند GPU داری، از Hugging Face Accelerate یا DeepSpeed استفاده کن.

جمع‌بندی

Unsloth Fine-tuning رو ساده و سریع کرده. با چند خط کد می‌تونی مدل رو لود کنی، LoRA اضافه کنی، آموزش بدی و ذخیره کنی. مهم‌ترین مزیتش سرعت و مصرف حافظه پایینه — یعنی می‌تونی روی GPU‌های ارزون‌تر کار کنی.

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

نظرات

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

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