fix: update examples to trl 1.x API (SFTConfig + processing_class)
- Replace TrainingArguments with SFTConfig; move dataset_text_field, max_length, packing, dataset_num_proc into SFTConfig (trl 1.x broke these as SFTTrainer params) - Rename tokenizer= to processing_class= in SFTTrainer constructor - Add trl 1.x API migration table to guide - Add runnable verification checklist with exact shell commands Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
442bb9ff5c
commit
70c981b377
2 changed files with 127 additions and 82 deletions
|
|
@ -53,6 +53,42 @@ To fine-tune without spiking past 3.8GB VRAM, every hyperparameter must be caref
|
|||
|
||||
---
|
||||
|
||||
## 4. Complete Reference Script (`train_4gb_vram.py`)
|
||||
## 4. trl 1.x API Note
|
||||
|
||||
See `train_4gb_vram.py` in this directory for a complete executable script demonstrating how to fine-tune `Llama-3.2-1B-Instruct` within a 4GB VRAM budget.
|
||||
In **trl 1.x**, the `SFTTrainer` constructor was simplified. Parameters like `dataset_text_field`, `max_seq_length`, `packing`, and `dataset_num_proc` were **removed from `SFTTrainer`** and moved into `SFTConfig` (which extends `TrainingArguments`). The `tokenizer` parameter was renamed to `processing_class`.
|
||||
|
||||
| Old (trl < 1.0) | New (trl 1.x) |
|
||||
| :--- | :--- |
|
||||
| `SFTTrainer(..., dataset_text_field="text")` | `SFTConfig(dataset_text_field="text")` |
|
||||
| `SFTTrainer(..., max_seq_length=1024)` | `SFTConfig(max_length=1024)` |
|
||||
| `SFTTrainer(..., packing=False)` | `SFTConfig(packing=False)` |
|
||||
| `SFTTrainer(..., tokenizer=tokenizer)` | `SFTTrainer(..., processing_class=tokenizer)` |
|
||||
|
||||
See `train_4gb_vram.py` in this directory for a complete, trl 1.x-compatible executable script.
|
||||
|
||||
---
|
||||
|
||||
## 5. Verification Checklist
|
||||
|
||||
Before submitting this guide to Unsloth's repository:
|
||||
|
||||
```bash
|
||||
# 1. Set env vars
|
||||
export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
|
||||
export CUDA_LAUNCH_BLOCKING=0
|
||||
|
||||
# 2. Run the verification script (first run downloads ~700 MB model)
|
||||
cd examples/
|
||||
python train_4gb_vram.py
|
||||
```
|
||||
|
||||
Expected output (GTX 1650):
|
||||
```
|
||||
GPU = NVIDIA GeForce GTX 1650. Max VRAM = 4.0 GB.
|
||||
✅ Training Complete!
|
||||
Peak reserved memory = X.XXX GB (< 95% of total VRAM).
|
||||
```
|
||||
|
||||
- [ ] Confirm `Peak reserved memory` stays below `3.8 GB`.
|
||||
- [ ] Confirm no OOM crash or KDE/Wayland compositor stutter during training.
|
||||
- [ ] Confirm `lora_model_4gb/` directory is created with adapter weights.
|
||||
|
|
|
|||
|
|
@ -2,124 +2,133 @@
|
|||
"""
|
||||
Optimized QLoRA Fine-Tuning Script for 4GB VRAM Consumer GPUs (GTX 1650 / RTX 3050)
|
||||
Author: Kunwar Satyam Singh (dante@5ingularity)
|
||||
|
||||
Compatible with: unsloth 2025+, trl 1.x (SFTConfig API), transformers 4.45+
|
||||
"""
|
||||
|
||||
import os
|
||||
import torch
|
||||
from datasets import load_dataset
|
||||
from unsloth import FastLanguageModel
|
||||
from trl import SFTTrainer
|
||||
from transformers import TrainingArguments
|
||||
from trl import SFTTrainer, SFTConfig
|
||||
|
||||
# Enforce expandable segments in Python to prevent memory fragmentation on Linux
|
||||
# Prevents PyTorch from allocating fragmented blocks that cause premature OOM on Linux
|
||||
os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True"
|
||||
|
||||
|
||||
def main():
|
||||
print("🦥 Initializing Unsloth 4GB VRAM Optimization Pipeline...")
|
||||
|
||||
# Configuration Constraints for 4GB VRAM
|
||||
max_seq_length = 1024 # Cap at 1024 tokens for memory stability
|
||||
dtype = None # Auto-detection (Float16 for GTX 1650 Turing architecture)
|
||||
load_in_4bit = True # Mandatory NF4 4-bit quantization
|
||||
|
||||
# Load Model & Tokenizer
|
||||
# ── Configuration constants ───────────────────────────────────────────────
|
||||
max_seq_length = 1024 # Cap at 1024 tokens — attention memory scales O(N²)
|
||||
dtype = None # Auto-detect: Float16 on GTX 1650 (Turing), BF16 on Ampere+
|
||||
load_in_4bit = True # Mandatory NF4 4-bit quantization
|
||||
|
||||
# ── 1. Load model & tokenizer ─────────────────────────────────────────────
|
||||
model, tokenizer = FastLanguageModel.from_pretrained(
|
||||
model_name = "unsloth/Llama-3.2-1B-Instruct",
|
||||
model_name = "unsloth/Llama-3.2-1B-Instruct",
|
||||
max_seq_length = max_seq_length,
|
||||
dtype = dtype,
|
||||
load_in_4bit = load_in_4bit,
|
||||
dtype = dtype,
|
||||
load_in_4bit = load_in_4bit,
|
||||
)
|
||||
|
||||
# Attach QLoRA Adapters
|
||||
# ── 2. Attach QLoRA adapters ──────────────────────────────────────────────
|
||||
model = FastLanguageModel.get_peft_model(
|
||||
model,
|
||||
r = 16, # Rank 16 provides strong capacity with low memory footprint
|
||||
target_modules = ["q_proj", "k_proj", "v_proj", "o_proj",
|
||||
"gate_proj", "up_proj", "down_proj"],
|
||||
lora_alpha = 16,
|
||||
lora_dropout = 0, # 0 is optimized in Unsloth
|
||||
bias = "none",
|
||||
use_gradient_checkpointing = "unsloth", # CRITICAL: Unsloth memory-optimized checkpointing
|
||||
random_state = 3407,
|
||||
use_rslora = False,
|
||||
loftq_config = None,
|
||||
r = 16, # Rank 16: strong capacity, low memory footprint
|
||||
target_modules = ["q_proj", "k_proj", "v_proj", "o_proj",
|
||||
"gate_proj", "up_proj", "down_proj"],
|
||||
lora_alpha = 16,
|
||||
lora_dropout = 0, # 0 is Unsloth-optimized
|
||||
bias = "none",
|
||||
use_gradient_checkpointing = "unsloth", # CRITICAL: offloads activations, saves ~60% VRAM
|
||||
random_state = 3407,
|
||||
use_rslora = False,
|
||||
loftq_config = None,
|
||||
)
|
||||
|
||||
# Load Sample Dataset (Alpaca formatting)
|
||||
alpaca_prompt = """Below is an instruction that describes a task. Write a response that appropriately completes the request.
|
||||
|
||||
### Instruction:
|
||||
{}
|
||||
|
||||
### Response:
|
||||
{}"""
|
||||
# ── 3. Load & format dataset ──────────────────────────────────────────────
|
||||
alpaca_prompt = (
|
||||
"Below is an instruction that describes a task. "
|
||||
"Write a response that appropriately completes the request.\n\n"
|
||||
"### Instruction:\n{}\n\n### Response:\n{}"
|
||||
)
|
||||
|
||||
EOS_TOKEN = tokenizer.eos_token
|
||||
def formatting_prompts_func(examples):
|
||||
instructions = examples["instruction"]
|
||||
outputs = examples["output"]
|
||||
texts = []
|
||||
for instruction, output in zip(instructions, outputs):
|
||||
text = alpaca_prompt.format(instruction, output) + EOS_TOKEN
|
||||
texts.append(text)
|
||||
return { "text" : texts, }
|
||||
|
||||
dataset = load_dataset("yahma/alpaca-cleaned", split = "train[:500]")
|
||||
dataset = dataset.map(formatting_prompts_func, batched = True)
|
||||
def format_alpaca(examples):
|
||||
return {
|
||||
"text": [
|
||||
alpaca_prompt.format(inst, out) + EOS_TOKEN
|
||||
for inst, out in zip(examples["instruction"], examples["output"])
|
||||
]
|
||||
}
|
||||
|
||||
# Training Arguments locked for 4GB VRAM budget
|
||||
training_args = TrainingArguments(
|
||||
per_device_train_batch_size = 1, # Mandatory for 4GB VRAM
|
||||
gradient_accumulation_steps = 8, # Effective batch size = 8
|
||||
warmup_steps = 5,
|
||||
max_steps = 60, # Short verification run
|
||||
learning_rate = 2e-4,
|
||||
fp16 = not torch.cuda.is_bf16_supported(),
|
||||
bf16 = torch.cuda.is_bf16_supported(),
|
||||
logging_steps = 10,
|
||||
optim = "paged_adamw_8bit", # Paged 8-bit AdamW offloads spikes to CPU RAM
|
||||
weight_decay = 0.01,
|
||||
lr_scheduler_type = "linear",
|
||||
seed = 3407,
|
||||
output_dir = "outputs_4gb_run",
|
||||
report_to = "none", # Disable logging services for standalone runs
|
||||
dataset = load_dataset("yahma/alpaca-cleaned", split="train[:500]")
|
||||
dataset = dataset.map(format_alpaca, batched=True)
|
||||
|
||||
# ── 4. Training configuration locked for 4GB VRAM ────────────────────────
|
||||
# NOTE: In trl 1.x the SFT-specific params (dataset_text_field, max_length,
|
||||
# packing, dataset_num_proc) moved from SFTTrainer into SFTConfig.
|
||||
sft_config = SFTConfig(
|
||||
output_dir = "outputs_4gb_run",
|
||||
# Memory-critical hyperparameters
|
||||
per_device_train_batch_size = 1, # Mandatory: keeps forward activation memory minimal
|
||||
gradient_accumulation_steps = 8, # Effective batch size = 8, zero extra VRAM cost
|
||||
# Precision: GTX 1650 (Turing) is Float16; Ampere+ supports BF16
|
||||
fp16 = not torch.cuda.is_bf16_supported(),
|
||||
bf16 = torch.cuda.is_bf16_supported(),
|
||||
# Optimizer: 8-bit states + CPU paging absorbs momentary VRAM spikes
|
||||
optim = "paged_adamw_8bit",
|
||||
# Schedule
|
||||
warmup_steps = 5,
|
||||
max_steps = 60, # Short verification run (~15 min on GTX 1650)
|
||||
learning_rate = 2e-4,
|
||||
lr_scheduler_type = "linear",
|
||||
weight_decay = 0.01,
|
||||
logging_steps = 10,
|
||||
seed = 3407,
|
||||
report_to = "none",
|
||||
# SFT-specific (trl 1.x: these live in SFTConfig, not SFTTrainer)
|
||||
dataset_text_field = "text",
|
||||
max_length = max_seq_length,
|
||||
dataset_num_proc = 2,
|
||||
packing = False, # True can spike VRAM on variable-length samples
|
||||
)
|
||||
|
||||
# Initialize Trainer
|
||||
# ── 5. Initialize trainer ─────────────────────────────────────────────────
|
||||
# NOTE: In trl 1.x 'tokenizer' param was renamed to 'processing_class'.
|
||||
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,
|
||||
args = training_args,
|
||||
model = model,
|
||||
processing_class = tokenizer,
|
||||
train_dataset = dataset,
|
||||
args = sft_config,
|
||||
)
|
||||
|
||||
# Execute Training & Track GPU Memory
|
||||
# ── 6. Execute training & track GPU memory ────────────────────────────────
|
||||
print("🚀 Starting GPU Memory Tracking & Training...")
|
||||
gpu_stats = torch.cuda.get_device_properties(0)
|
||||
gpu_stats = torch.cuda.get_device_properties(0)
|
||||
start_gpu_memory = round(torch.cuda.max_memory_reserved() / 1024 / 1024 / 1024, 3)
|
||||
max_memory = round(gpu_stats.total_memory / 1024 / 1024 / 1024, 3)
|
||||
max_memory = round(gpu_stats.total_memory / 1024 / 1024 / 1024, 3)
|
||||
print(f"GPU = {gpu_stats.name}. Max VRAM = {max_memory} GB.")
|
||||
print(f"Initial reserved memory = {start_gpu_memory} GB.")
|
||||
|
||||
trainer_stats = trainer.train()
|
||||
|
||||
# Log Final Memory Statistics
|
||||
used_memory = round(torch.cuda.max_memory_reserved() / 1024 / 1024 / 1024, 3)
|
||||
# ── 7. Report peak memory usage ───────────────────────────────────────────
|
||||
used_memory = round(torch.cuda.max_memory_reserved() / 1024 / 1024 / 1024, 3)
|
||||
used_memory_for_lora = round(used_memory - start_gpu_memory, 3)
|
||||
used_percentage = round(used_memory / max_memory * 100, 2)
|
||||
lora_percentage = round(used_memory_for_lora / max_memory * 100, 2)
|
||||
print(f"✅ Training Complete!")
|
||||
print(f"Peak reserved memory = {used_memory} GB ({used_percentage}% of total VRAM).")
|
||||
print(f"Memory used for fine-tuning = {used_memory_for_lora} GB ({lora_percentage}%).")
|
||||
used_percentage = round(used_memory / max_memory * 100, 2)
|
||||
lora_percentage = round(used_memory_for_lora / max_memory * 100, 2)
|
||||
print(f"\n✅ Training Complete!")
|
||||
print(f"Peak reserved memory = {used_memory} GB ({used_percentage}% of total VRAM).")
|
||||
print(f"Memory used for LoRA = {used_memory_for_lora} GB ({lora_percentage}%).")
|
||||
|
||||
# Save LoRA Adapters
|
||||
# ── 8. Save LoRA adapters ─────────────────────────────────────────────────
|
||||
model.save_pretrained("lora_model_4gb")
|
||||
tokenizer.save_pretrained("lora_model_4gb")
|
||||
print("💾 Adapters saved successfully to `lora_model_4gb/`.")
|
||||
print("💾 Adapters saved to `lora_model_4gb/`.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue