Fine-tuning barato con Unsloth: LoRA en 30 minutos
TL;DR
- Unsloth hace que el fine-tuning con LoRA sea 2-5x más rápido y use menos VRAM que HuggingFace Transformers puro.
- Puedes fine-tunear un modelo de 7B en una RTX 3090 (24GB) en 30 minutos.
- Coste estimado: $0.50-1.00 por run en Google Colab Pro o RunPod.
¿Qué es LoRA?
LoRA (Low-Rank Adaptation) no reentrena todo el modelo. Entrena una capa delgada de parámetros encima del modelo base. El resultado:
- Entrenas 0.1-1% de los parámetros totales
- Necesitas 3-5x menos VRAM que full fine-tuning
- El modelo original se mantiene intacto
- Puedes intercambiar adapters LoRA como plugins
Setup
Opción A: Google Colab (más fácil)
Abre un notebook con GPU T4 (gratis) o A100 (Colab Pro):
!pip install unsloth
Opción B: Local
pip install unsloth torch --index-url https://download.pytorch.org/whl/cu121
Requiere GPU NVIDIA con CUDA. 16GB+ VRAM para modelos de 7B.
Paso 1: Cargar modelo
from unsloth import FastLanguageModel
model, tokenizer = FastLanguageModel.from_pretrained(
model_name="unsloth/llama-4-scout-17b",
max_seq_length=2048,
load_in_4bit=True, # Cuantización 4-bit para ahorrar VRAM
dtype=None, # Auto-detect
)
El flag load_in_4bit=True es clave: carga el modelo en 4-bit, reduciendo VRAM de ~34GB a ~10GB.
Paso 2: Añadir LoRA
model = FastLanguageModel.get_peft_model(
model,
r=16, # Rank del adapter (8, 16, 32, 64)
target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"],
lora_alpha=16,
lora_dropout=0,
bias="none",
use_gradient_checkpointing="unsloth", # Optimización de VRAM
)
rank (r): Controla la capacidad del adapter. r=16 es suficiente para la mayoría de tareas. r=64 para tareas complejas (más VRAM).
Paso 3: Preparar dataset
Necesitas datos en formato de conversación. Ejemplo para un asistente técnico:
train_dataset = [
{
"conversations": [
{"role": "user", "content": "¿Cómo configuro nginx como reverse proxy?"},
{"role": "assistant", "content": "1. Instala nginx..."},
]
},
# ... más ejemplos
]
Mínimo recomendado: 100-500 ejemplos para tasks específicas. 1000+ para cambios de estilo o dominio.
Formato con HuggingFace datasets:
from datasets import load_dataset
dataset = load_dataset("json", data_files="train.jsonl")
def formatting_prompts_func(examples):
convos = examples["conversations"]
texts = [
tokenizer.apply_chat_template(convo, tokenize=False, add_generation_prompt=False)
for convo in convos
]
return {"text": texts}
dataset = dataset.map(formatting_prompts_func, batched=True)
Paso 4: Entrenar
from trl import SFTTrainer
from transformers import TrainingArguments
trainer = SFTTrainer(
model=model,
tokenizer=tokenizer,
train_dataset=dataset,
dataset_text_field="text",
max_seq_length=2048,
args=TrainingArguments(
per_device_train_batch_size=2,
gradient_accumulation_steps=4,
warmup_steps=5,
num_train_epochs=3,
learning_rate=2e-4,
fp16=not torch.cuda.is_bf16_supported(),
bf16=torch.cuda.is_bf16_supported(),
logging_steps=10,
output_dir="outputs",
),
)
trainer.train()
Tiempo estimado con 500 ejemplos, RTX 3090: ~20-30 minutos.
Paso 5: Probar
FastLanguageModel.for_inference(model)
messages = [{"role": "user", "content": "¿Cómo configuro nginx?"}]
inputs = tokenizer.apply_chat_template(
messages, tokenize=True, add_generation_prompt=True, return_tensors="pt"
).to("cuda")
outputs = model.generate(inputs, max_new_tokens=256, temperature=0.7)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
Paso 6: Exportar
Opción A: Guardar LoRA adapter solo
model.save_pretrained("my-lora-adapter")
tokenizer.save_pretrained("my-lora-adapter")
Tamaño: ~50-200MB (frente a 10-30GB del modelo completo).
Opción B: Merge con modelo base
model.save_pretrained_merged("my-merged-model", tokenizer)
Opción C: Exportar a GGUF para llama.cpp
model.save_pretrained_gguf("my-model", tokenizer, quantization_method="q4_k_m")
Esto genera un archivo GGUF que puedes usar con llama.cpp u Ollama directamente.
Errores comunes
Loss no baja: Learning rate demasiado alto o dataset demasiado pequeño. Prueba lr=1e-4 y añade más datos.
Overfitting: El modelo repite frases del training data. Reduce num_train_epochs a 1-2 y añade regularización.
OOM: Reduce per_device_train_batch_size a 1 y aumenta gradient_accumulation_steps. O reduce max_seq_length.
El modelo “olvida”: LoRA intenso puede degradar capacidades generales. Usa r=8-16 (no 64+) y no entrenes demasiadas epochs.
¿Cuándo fine-tunear vs RAG vs prompting?
| Situación | Mejor opción |
|---|---|
| Cambiar estilo/tono | Fine-tuning LoRA |
| Añadir conocimiento nuevo | RAG |
| Formato de salida específico | Prompting + few-shot |
| Dominio muy especializado (médico, legal) | Fine-tuning + RAG |
| Task repetitiva con patrón claro | Fine-tuning LoRA |
Regla: Si puedes resolverlo con prompting, no fine-tunees. Si necesitas que el modelo sea diferente (no que sepa cosas diferentes), fine-tuning.
Coste real
| Setup | Coste por run (500 ejemplos) | Tiempo |
|---|---|---|
| Google Colab T4 (gratis) | $0 | 1-2h |
| Google Colab A100 | ~$1 | 15min |
| RunPod RTX 4090 | ~$0.50 | 20min |
| Local RTX 3090 | ~$0 (electricidad) | 30min |
Fine-tuning ya no es un lujo. Es accesible para cualquier dev con una GPU consumer.
Fuentes: Unsloth docs (unsloth.ai), HuggingFace TRL docs, experiencia propia.