Saltar al contenido
Lección 7 de 12

Entrenamiento con Unsloth

6 min read

Por Qué Existe Unsloth

El stack de entrenamiento estándar de Hugging Face funciona, pero deja rendimiento sobre la mesa. Unsloth reescribe partes clave del pipeline de entrenamiento con kernels CUDA personalizados y gestión de memoria optimizada, logrando un entrenamiento 2x más rápido y hasta 60% menos uso de memoria — sin ningún cambio en la calidad del modelo. Si estás haciendo fine-tuning en hardware de consumo o quieres minimizar costos de GPU en la nube, Unsloth es la herramienta que debes aprender.

Qué Hace Rápido a Unsloth

La velocidad de Unsloth proviene de varias optimizaciones:

  • Kernels Triton personalizados para la pérdida de entropía cruzada y capas LoRA, eliminando asignaciones de memoria innecesarias
  • Operaciones fusionadas que combinan múltiples pasos (embedding RoPE, normalización de capa, paso forward de LoRA) en llamadas únicas a la GPU
  • Gestión inteligente de memoria que reutiliza buffers en lugar de asignar nuevos
  • Paso backward optimizado que calcula gradientes más eficientemente para parámetros LoRA

Estas optimizaciones se aplican automáticamente cuando cargas un modelo a través de la API de Unsloth. No necesitas cambiar tu código de entrenamiento más allá del paso de carga del modelo.

Instalación

pip install unsloth

Para versiones específicas de CUDA o si encuentras problemas:

# Para CUDA 12.1
pip install unsloth[cu121-ampere-torch250]

# Para CUDA 11.8
pip install unsloth[cu118-torch250]

Unsloth también funciona en notebooks de Google Colab y Kaggle sin configuración especial.

Modelos Soportados

Unsloth soporta las familias de modelos de pesos abiertos más populares:

  • Llama 3 / 3.1 / 3.2 (1B, 3B, 8B, 70B)
  • Mistral / Mixtral (7B, 8x7B)
  • Qwen 2 / 2.5 (0.5B a 72B)
  • Gemma 2 (2B, 9B, 27B)
  • Phi-3 / Phi-4 (3.8B, 14B)

Unsloth proporciona versiones pre-cuantizadas en 4 bits de estos modelos que se cargan más rápido que cuantizar desde cero:

# Modelos pre-cuantizados de Unsloth (recomendado)
"unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bit"
"unsloth/Mistral-7B-Instruct-v0.3-bnb-4bit"
"unsloth/Qwen2.5-7B-Instruct-bnb-4bit"
"unsloth/gemma-2-9b-it-bnb-4bit"

FastLanguageModel: Carga y Configuración

Unsloth reemplaza la carga de modelo estándar de HF con FastLanguageModel:

from unsloth import FastLanguageModel
import torch

# Cargar modelo y tokenizador
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name="unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bit",
    max_seq_length=2048,
    dtype=None,           # Auto-detectar (bfloat16 en Ampere+, float16 en otros)
    load_in_4bit=True,    # QLoRA
)

Esta única llamada maneja todo: carga del modelo, cuantización, configuración del tokenizador y optimización de memoria.

Aplicando LoRA

model = FastLanguageModel.get_peft_model(
    model,
    r=16,
    lora_alpha=32,
    target_modules=[
        "q_proj", "k_proj", "v_proj", "o_proj",
        "gate_proj", "up_proj", "down_proj",
    ],
    lora_dropout=0.0,     # Unsloth recomienda 0 para velocidad
    bias="none",
    use_gradient_checkpointing="unsloth",  # Version optimizada de Unsloth
    random_state=42,
)

Diferencia clave: Unsloth recomienda lora_dropout=0.0 porque sus kernels optimizados son más rápidos sin dropout. Si necesitas regularización, usa una tasa de aprendizaje más baja o early stopping en su lugar.

El flag use_gradient_checkpointing="unsloth" habilita el gradient checkpointing personalizado de Unsloth que es 30% más rápido que la implementación estándar.

Configuración de Entrenamiento

Unsloth usa el SFTTrainer estándar de Hugging Face, así que el código de entrenamiento es casi idéntico:

from trl import SFTTrainer
from transformers import TrainingArguments
from datasets import load_dataset

# Cargar dataset
dataset = load_dataset("json", data_files={
    "train": "data/train.jsonl",
    "validation": "data/val.jsonl",
})

training_args = TrainingArguments(
    output_dir="./output/llama3-unsloth",
    num_train_epochs=3,
    per_device_train_batch_size=2,
    gradient_accumulation_steps=8,
    learning_rate=2e-4,
    lr_scheduler_type="linear",
    warmup_steps=10,
    optim="adamw_8bit",
    weight_decay=0.01,
    bf16=True,
    logging_steps=10,
    eval_strategy="steps",
    eval_steps=50,
    save_strategy="steps",
    save_steps=100,
    save_total_limit=3,
    seed=42,
)

trainer = SFTTrainer(
    model=model,
    tokenizer=tokenizer,
    args=training_args,
    train_dataset=dataset["train"],
    eval_dataset=dataset["validation"],
    max_seq_length=2048,
    packing=False,
    dataset_text_field=None,
)

# Entrenar
trainer.train()

Ejemplo Completo: Fine-Tuning de Llama 3 con Datos Personalizados

Aquí hay un script completo y autocontenido:

"""
Script completo de fine-tuning con Unsloth para Llama 3.1 8B.
Requiere: pip install unsloth trl datasets
Hardware: Funciona con 16GB de VRAM (T4, RTX 4060, etc.)
"""
from unsloth import FastLanguageModel
from trl import SFTTrainer
from transformers import TrainingArguments
from datasets import load_dataset
import torch

# === 1. Cargar Modelo ===
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name="unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bit",
    max_seq_length=2048,
    dtype=None,
    load_in_4bit=True,
)

# === 2. Aplicar LoRA ===
model = FastLanguageModel.get_peft_model(
    model,
    r=16,
    lora_alpha=32,
    target_modules=[
        "q_proj", "k_proj", "v_proj", "o_proj",
        "gate_proj", "up_proj", "down_proj",
    ],
    lora_dropout=0.0,
    bias="none",
    use_gradient_checkpointing="unsloth",
    random_state=42,
)

# === 3. Cargar Dataset ===
dataset = load_dataset("json", data_files={
    "train": "data/train.jsonl",
    "validation": "data/val.jsonl",
})

# === 4. Entrenamiento ===
trainer = SFTTrainer(
    model=model,
    tokenizer=tokenizer,
    args=TrainingArguments(
        output_dir="./output/llama3-custom",
        num_train_epochs=3,
        per_device_train_batch_size=2,
        gradient_accumulation_steps=8,
        learning_rate=2e-4,
        lr_scheduler_type="linear",
        warmup_steps=10,
        optim="adamw_8bit",
        weight_decay=0.01,
        bf16=True,
        logging_steps=10,
        eval_strategy="steps",
        eval_steps=50,
        save_strategy="steps",
        save_steps=100,
        save_total_limit=3,
        seed=42,
    ),
    train_dataset=dataset["train"],
    eval_dataset=dataset["validation"],
    max_seq_length=2048,
)

trainer.train()

# === 5. Guardar Adaptador LoRA ===
model.save_pretrained("./output/llama3-custom/final")
tokenizer.save_pretrained("./output/llama3-custom/final")

# === 6. Probar el Modelo ===
FastLanguageModel.for_inference(model)  # Habilitar modo de inferencia rapida

messages = [
    {"role": "user", "content": "Explica que es el fine-tuning."}
]

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

outputs = model.generate(
    input_ids=inputs,
    max_new_tokens=256,
    temperature=0.7,
    do_sample=True,
)

print(tokenizer.decode(outputs[0][inputs.shape[1]:], skip_special_tokens=True))

Comparación: Unsloth vs Hugging Face Vanilla

Benchmarks en una RTX 3090 (24GB), Llama 3.1 8B, QLoRA r=16, batch size 2:

| Métrica | Hugging Face | Unsloth | Mejora | |---------|-------------|---------|--------| | Velocidad de entrenamiento | 1.2 it/s | 2.5 it/s | 2.1x más rápido | | VRAM pico | 14.2 GB | 8.1 GB | 43% menos | | Tiempo total (1000 pasos) | 14 min | 6.7 min | 2.1x más rápido | | Pérdida final | 0.847 | 0.845 | Equivalente |

La calidad (medida por la pérdida final y evaluación humana) es idéntica — Unsloth no corta esquinas en las matemáticas, simplemente computa las mismas matemáticas de manera más eficiente.

Cuándo Usar Unsloth

Usa Unsloth cuando:

  • Estás entrenando en GPUs de consumo (16-24GB de VRAM)
  • Quieres minimizar costos de GPU en la nube
  • Estás haciendo iteración rápida y necesitas rápido retorno
  • Estás entrenando en la capa gratuita de Colab/Kaggle

Usa Hugging Face vanilla cuando:

  • Necesitas entrenamiento multi-GPU (el soporte multi-GPU de Unsloth es más limitado)
  • Necesitas bucles de entrenamiento personalizados que modifiquen el paso de entrenamiento
  • Estás usando una arquitectura de modelo que Unsloth no soporta aún
  • Necesitas integración con DeepSpeed o FSDP

Guardado y Exportación

Unsloth hace la exportación fácil:

# Guardar como adaptador LoRA (mas pequeno, para fusion posterior)
model.save_pretrained("output/lora-adapter")

# Guardar modelo fusionado en float16
model.save_pretrained_merged("output/merged-f16", tokenizer, save_method="merged_16bit")

# Guardar como GGUF para Ollama/llama.cpp
model.save_pretrained_gguf("output/gguf", tokenizer, quantization_method="q4_k_m")

# Subir a Hugging Face Hub
model.push_to_hub_merged("tu-usuario/tu-modelo", tokenizer)
model.push_to_hub_gguf("tu-usuario/tu-modelo-gguf", tokenizer, quantization_method="q4_k_m")

Esta es una de las mayores conveniencias de Unsloth — convertir a GGUF directamente desde el script de entrenamiento, sin un paso de conversión separado.

En la próxima lección, cubrimos cómo monitorear tus ejecuciones de entrenamiento y depurar los problemas más comunes que surgen durante el fine-tuning.