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:
Kunwar Satyam Singh 2026-06-29 15:18:01 +05:30
commit 70c981b377
2 changed files with 127 additions and 82 deletions

View file

@ -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.

View file

@ -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()