DEV Community

Cover image for Fine-Tuning de Qwen 3.8 27B con Unsloth: guía completa
Eber Cruz Fararoni
Eber Cruz Fararoni

Posted on

Fine-Tuning de Qwen 3.8 27B con Unsloth: guía completa

¿Quieres entrenar un modelo de 27B parámetros sin una granja de GPUs? Con Unsloth y QLoRA puedes hacer fine-tuning de Qwen 3.8 27B con solo 22 GB de VRAM — o incluso gratis en Kaggle.

Esta guía está basada en el anuncio oficial de Daniel Han (Co-founder, Unsloth AI), agosto 2026, y te lleva desde cero hasta exportar tu modelo a GGUF para correrlo en Ollama o llama.cpp.

TL;DR

Característica Detalle
Modelo base Qwen 3.8 27B (dense, vision-language, 262K contexto)
Framework Unsloth (optimizado para velocidad y memoria)
VRAM mínima (QLoRA) 22 GB
VRAM recomendada (FFT) 96 GB+
Aceleración ~1.5x más rápido vs Flash Attention 2
Ahorro VRAM ~50% menos que setups FA2
Formatos de exportación NVFP4, FP8, GGUF, merged (16-bit / 32-bit)

Opciones de ejecución

Opción 1: Kaggle (gratis, recomendado para empezar)

  • 30 horas de GPU gratis por semana
  • 2x Tesla T4 (16 GB VRAM cada una)
  • Notebooks pre-configurados listos para usar
  • Ideal para experimentar sin hardware propio

Opción 2: Local (tu propio hardware)

  • GPU con 24 GB+ VRAM (RTX 4090, A6000, etc.)
  • O Apple Silicon con Unsloth Desktop app
  • Control total del entorno

Opción 3: Unsloth Desktop App

  • Disponible para macOS, Windows, Linux
  • Interfaz gráfica, sin código
  • Descarga desde unsloth.ai

Requisitos previos

Hardware

Modo VRAM requerida GPU ejemplo
QLoRA 4-bit 22 GB RTX 3090 / 4090 (24 GB), A6000
LoRA >36 GB A100 40GB, RTX A6000 Ada
Full Fine-Tuning (FFT) 4x más que QLoRA 2x A100 80GB, H100
RL (GRPO) 22 GB RTX 4090, A6000

Software

# Python 3.10+
python --version

# PyTorch con CUDA (para NVIDIA)
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121

# Unsloth (instalación completa)
pip install "unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git"

# Dependencias adicionales
pip install transformers datasets trl peft accelerate bitsandbytes
Enter fullscreen mode Exit fullscreen mode

Paso 1: Cargar el modelo base (QLoRA 4-bit)

from unsloth import FastModel
import torch

# Configuración
max_seq_length = 2048  # Puedes subir a 4096, 8192, etc.
dtype = None           # Auto-detect (Float16 para Tesla T4, BFloat16 para Ampere+)
load_in_4bit = True    # QLoRA: carga en 4-bit

# Cargar modelo y tokenizer
model, tokenizer = FastModel.from_pretrained(
    model_name="unsloth/Qwen3.8-27B-unsloth-bnb-4bit",
    max_seq_length=max_seq_length,
    dtype=dtype,
    load_in_4bit=load_in_4bit,
    # token="hf_...",  # Tu token de Hugging Face si es necesario
)
Enter fullscreen mode Exit fullscreen mode

Nota: unsloth/Qwen3.8-27B-unsloth-bnb-4bit es la versión 4-bit pre-cuantizada de Unsloth. Ocupa ~15 GB en disco vs ~54 GB del modelo BF16 original.


Paso 2: Configurar LoRA adapters

# Añadir adapters LoRA al modelo
model = FastModel.get_peft_model(
    model,
    r=16,                          # Rank de LoRA (8, 16, 32, 64, 128)
    target_modules=[
        "q_proj", "k_proj", "v_proj", "o_proj",
        "gate_proj", "up_proj", "down_proj",
    ],
    lora_alpha=16,                 # Scaling factor (usualmente = r o 2*r)
    lora_dropout=0,                # 0 es óptimo para QLoRA
    bias="none",
    use_gradient_checkpointing="unsloth",  # Ahorra 30% más VRAM
    random_state=3407,
    use_rslora=False,              # Rank-Stabilized LoRA (opcional)
)
Enter fullscreen mode Exit fullscreen mode

¿Qué es LoRA?

LoRA (Low-Rank Adaptation) entrena solo pequeñas matrices adicionales (adapters) en lugar de todos los pesos del modelo. Esto reduce el uso de VRAM de ~96 GB a ~22 GB.

Parámetro Efecto
r=8 Menos parámetros, más rápido, menos expresivo
r=16 Balance recomendado para la mayoría de casos
r=32 Más capacidad, más VRAM, mejor para tareas complejas
r=64+ Casi full fine-tuning en expresividad

Paso 3: Preparar tu dataset

Formato requerido

Tu dataset necesita una columna text con el chat template de Qwen ya aplicado:

{
  "text": "<|im_start|>system\nEres un asistente útil.<|im_end|>\n<|im_start|>user\n¿Cuál es la capital de Francia?<|im_end|>\n<|im_start|>assistant\nLa capital de Francia es París.<|im_end|>"
}
Enter fullscreen mode Exit fullscreen mode

Ejemplo: cargar dataset desde Hugging Face

from datasets import load_dataset

# Dataset de ejemplo (reemplaza con el tuyo)
dataset = load_dataset("yahma/alpaca-cleaned", split="train")

# Función para formatear con el chat template de Qwen
def format_prompt(examples):
    texts = []
    for instruction, input_text, output in zip(
        examples["instruction"],
        examples["input"],
        examples["output"]
    ):
        if input_text:
            prompt = f"{instruction}\n\n{input_text}"
        else:
            prompt = instruction

        # Aplicar chat template de Qwen 3.8
        messages = [
            {"role": "user", "content": prompt},
            {"role": "assistant", "content": output}
        ]
        text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=False)
        texts.append(text)

    return {"text": texts}

# Aplicar formateo
dataset = dataset.map(format_prompt, batched=True)
Enter fullscreen mode Exit fullscreen mode

Ejemplo: dataset propio (CSV/JSON)

import pandas as pd
from datasets import Dataset

# Cargar CSV
df = pd.read_csv("mi_dataset.csv")  # Columnas: instruction, input, output
dataset = Dataset.from_pandas(df)
dataset = dataset.map(format_prompt, batched=True)
Enter fullscreen mode Exit fullscreen mode

Paso 4: Entrenar (SFT - Supervised Fine-Tuning)

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 para secuencias cortas (ahorra VRAM)

    args=TrainingArguments(
        per_device_train_batch_size=2,
        gradient_accumulation_steps=4,     # Efectivo: batch_size * grad_acc = 8
        warmup_steps=5,
        max_steps=60,                      # Total de pasos de entrenamiento
        learning_rate=2e-4,
        logging_steps=1,
        optim="adamw_8bit",                # Optimizador 8-bit (ahorra VRAM)
        weight_decay=0.01,
        lr_scheduler_type="linear",
        seed=3407,
        output_dir="outputs",
        report_to="none",                  # Cambia a "wandb" o "tensorboard" si quieres
    ),
)

# ¡Entrenar!
trainer.train()
Enter fullscreen mode Exit fullscreen mode

Monitoreo de VRAM durante entrenamiento

# Ver uso de GPU
!nvidia-smi

# O en código Python
import torch
print(f"VRAM usada: {torch.cuda.memory_allocated() / 1024**3:.2f} GB")
print(f"VRAM reservada: {torch.cuda.memory_reserved() / 1024**3:.2f} GB")
Enter fullscreen mode Exit fullscreen mode

Paso 5: Fine-tuning con RL (GRPO)

Para entrenamiento por refuerzo con GRPO (Group Relative Policy Optimization):

from trl import GRPOConfig, GRPOTrainer

# Configuración GRPO
training_args = GRPOConfig(
    learning_rate=5e-6,
    per_device_train_batch_size=1,
    gradient_accumulation_steps=8,
    max_prompt_length=512,
    max_completion_length=1024,
    num_generations=4,        # Tamaño del grupo GRPO
    beta=0.04,                # Coeficiente KL
    logging_steps=1,
    max_steps=100,
    output_dir="outputs_grpo",
    report_to="none",
)

# Función de recompensa (ejemplo: longitud de respuesta)
def reward_len(completions, **kwargs):
    return [-abs(20 - len(completion)) for completion in completions]

trainer = GRPOTrainer(
    model=model,
    processing_class=tokenizer,
    reward_funcs=[reward_len],
    args=training_args,
    train_dataset=dataset,
)

trainer.train()
Enter fullscreen mode Exit fullscreen mode

Nota: GRPO requiere que definas una función de recompensa. Puedes usar modelos de recompensa pre-entrenados o funciones custom.


Paso 6: Guardar y exportar

Guardar solo los adapters LoRA

# Guardar adapters (pequeño, ~100-500 MB)
model.save_pretrained("lora_adapters")
tokenizer.save_pretrained("lora_adapters")
Enter fullscreen mode Exit fullscreen mode

Fusionar adapters + modelo base (16-bit)

# Fusionar LoRA con el modelo base para un modelo completo
model.save_pretrained_merged(
    "merged_model_16bit",
    tokenizer,
    save_method="merged_16bit",
)
Enter fullscreen mode Exit fullscreen mode

Exportar a GGUF (para Ollama, llama.cpp)

# Exportar a GGUF con diferentes cuantizaciones
model.save_pretrained_gguf(
    "gguf_model",
    tokenizer,
    quantization_method=[
        "q4_k_m",   # ~18 GB, balance calidad/tamaño
        "q5_k_m",   # ~20 GB, alta calidad
        "q8_0",     # ~30 GB, casi sin pérdida
    ],
)
Enter fullscreen mode Exit fullscreen mode

Exportar a FP8 / NVFP4

# FP8 (para servidores con soporte Hopper/Ada)
model.save_pretrained_merged(
    "merged_model_fp8",
    tokenizer,
    save_method="merged_8bit",
)
Enter fullscreen mode Exit fullscreen mode

Paso 7: Inferencia con tu modelo fine-tuneado

# Cargar modelo fine-tuneado (adapters)
from unsloth import FastModel

model, tokenizer = FastModel.from_pretrained(
    model_name="unsloth/Qwen3.8-27B-unsloth-bnb-4bit",
    max_seq_length=2048,
    load_in_4bit=True,
)

# Cargar adapters entrenados
model = FastModel.get_peft_model(model, r=16)
model.load_adapter("lora_adapters")

# Generar respuesta
messages = [
    {"role": "user", "content": "¿Cuál es la capital de Francia?"}
]
inputs = tokenizer.apply_chat_template(messages, tokenize=True, return_tensors="pt").to("cuda")

outputs = model.generate(
    input_ids=inputs,
    max_new_tokens=256,
    temperature=0.7,
    top_p=0.95,
)

response = tokenizer.batch_decode(outputs, skip_special_tokens=True)[0]
print(response)
Enter fullscreen mode Exit fullscreen mode

Comparativa: modos de entrenamiento

Modo VRAM Velocidad Calidad Cuándo usarlo
QLoRA 4-bit 22 GB Rápido Muy buena La mayoría de casos. Balance ideal.
LoRA 16-bit >36 GB Media Buena Cuando 4-bit no es suficiente
FFT (Full) ~96 GB Lento Máxima Cuando necesitas cambiar todo el modelo
RL (GRPO) 22 GB Variable Depende de recompensa Para alinear comportamiento por refuerzo

Opción especial: Apple Silicon (macOS)

# Instalar Unsloth para Mac
pip install unsloth

# O usar Unsloth Desktop App
# Descarga desde: https://unsloth.ai
Enter fullscreen mode Exit fullscreen mode

En Mac con Apple Silicon (M1/M2/M3/M4), Unsloth usa MLX como backend para aceleración nativa. El consumo de memoria es similar pero usa RAM unificada en lugar de VRAM dedicada.


Troubleshooting

"CUDA out of memory"

# Soluciones:
# 1. Reduce max_seq_length
max_seq_length = 1024  # En lugar de 2048

# 2. Reduce batch_size
per_device_train_batch_size = 1
gradient_accumulation_steps = 8  # Mantiene batch efectivo de 8

# 3. Activa gradient checkpointing
use_gradient_checkpointing = "unsloth"

# 4. Usa QLoRA en lugar de LoRA
load_in_4bit = True
Enter fullscreen mode Exit fullscreen mode

"Model not found"

# Autenticación con Hugging Face
huggingface-cli login
# O en Python:
# from huggingface_hub import login
# login(token="tu_token_aqui")
Enter fullscreen mode Exit fullscreen mode

Lentitud en Kaggle

# En Kaggle, asegúrate de tener GPU activada:
# Settings → Accelerator → GPU T4 x2

# Verificar GPU disponible
import torch
print(torch.cuda.device_count())      # Debe mostrar 2
print(torch.cuda.get_device_name(0))  # Tesla T4
Enter fullscreen mode Exit fullscreen mode

Recursos


Checklist final

  • [ ] Acceso a GPU con 22+ GB VRAM (o Kaggle/Colab Pro)
  • [ ] Python 3.10+ y PyTorch con CUDA instalados
  • [ ] Unsloth instalado: pip install "unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git"
  • [ ] Dataset con columna text y chat template de Qwen aplicado
  • [ ] Modo elegido: QLoRA (recomendado) / LoRA / FFT / RL
  • [ ] Hiperparámetros configurados (r, learning_rate, batch_size, steps)
  • [ ] Plan de exportación (GGUF para Ollama, merged para vLLM, etc.)
  • [ ] VRAM verificada antes de entrenar

¿Tú ya hiciste fine-tuning de un modelo de este tamaño? ¿Qué dataset usaste y qué resultados obtuviste? Cuéntame en los comentarios. 👇

Guía basada en el anuncio oficial de Daniel Han, Co-founder de Unsloth AI (agosto 2026).


Eber Cruz Fararoni is a software architect specialized in distributed systems, event-driven architectures, and applied artificial intelligence. He builds Fararoni Flow, an open-source AI agent orchestrator, on Java 25, NATS, and hexagonal architecture. He writes at ebercruz.com about the intersection of software engineering and artificial intelligence.

If you found this article useful, share it with someone navigating the complex enterprise AI landscape in 2026. And if you want to try Fararoni Flow or exchange ideas about agent orchestration: contact me.

Top comments (0)