Saltar al contenido
Lección 8 de 12

Monitoreo y Depuracion del Entrenamiento

7 min read

Por Que Importa el Monitoreo

Una ejecucion de fine-tuning puede tomar desde 30 minutos hasta varias horas. Sin monitoreo adecuado, puedes descubrir problemas solo despues de que la ejecucion termine — desperdiciando computo y tiempo. Un buen monitoreo te permite detectar sobreajuste, identificar problemas de datos y cancelar ejecuciones malas temprano. Esta leccion cubre las metricas que debes rastrear, los patrones que debes reconocer y las herramientas que hacen todo esto visible.

Metricas Esenciales de Entrenamiento

Perdida de Entrenamiento (Training Loss)

La metrica principal. Mide que tan bien el modelo predice el siguiente token en los datos de entrenamiento. Una curva de perdida saludable muestra una caida inicial pronunciada seguida de una disminucion gradual.

Que observar:

  • Disminucion constante: Bien. El modelo esta aprendiendo.
  • Meseta temprana: La tasa de aprendizaje puede ser muy baja, o la tarea es demasiado simple para el rango LoRA.
  • Oscilacion: La tasa de aprendizaje es demasiado alta o el tamano de batch es demasiado pequeno.
  • Pico repentino: Posible corrupcion de datos en ese batch, o inestabilidad numerica.

Perdida de Validacion (Validation Loss)

Mide el rendimiento en datos que el modelo no ha visto durante el entrenamiento. La brecha entre la perdida de entrenamiento y la de validacion es tu indicador de sobreajuste.

Patron saludable: La perdida de entrenamiento y validacion disminuyen juntas, con la de validacion ligeramente mas alta.

Patron de sobreajuste: La perdida de entrenamiento sigue disminuyendo mientras la de validacion comienza a aumentar. Esto significa que el modelo esta memorizando ejemplos de entrenamiento en lugar de aprender patrones generalizables.

# En TrainingArguments, habilitar evaluacion
training_args = TrainingArguments(
    eval_strategy="steps",
    eval_steps=50,              # Evaluar cada 50 pasos
    load_best_model_at_end=True,  # Recargar el mejor checkpoint
    metric_for_best_model="eval_loss",
    greater_is_better=False,
)

Programacion de Tasa de Aprendizaje

Rastrea la tasa de aprendizaje para verificar que tu programador funciona correctamente. Una programacion coseno debe mostrar una curva suave del pico a casi cero. Una programacion lineal debe mostrar un declive recto.

Norma del Gradiente

La magnitud de los gradientes te dice sobre la estabilidad del entrenamiento. Normas de gradiente que se disparan a valores muy altos indican inestabilidad. Normas que colapsan a casi cero indican gradientes que se desvanecen.

# Habilitar registro de norma de gradiente
training_args = TrainingArguments(
    logging_steps=10,
    logging_first_step=True,
    max_grad_norm=1.0,  # Recortar gradientes por encima de esta norma
)

Detectando y Corrigiendo el Sobreajuste

El sobreajuste es el problema mas comun en fine-tuning, especialmente con datasets pequenos.

Senales de Sobreajuste

  1. La perdida de validacion aumenta mientras la de entrenamiento disminuye
  2. Las salidas del modelo se vuelven copias casi exactas de ejemplos de entrenamiento
  3. La calidad del modelo se degrada en entradas fuera de distribucion
  4. La perdida de entrenamiento alcanza valores muy bajos (por debajo de 0.1) inusualmente rapido

Remedios

  • Reducir epocas. Si el sobreajuste comienza en la epoca 2, entrena por 1.5 epocas.
  • Aumentar dropout. Sube lora_dropout de 0.05 a 0.1.
  • Reducir rango. Un rango LoRA mas bajo (r=8 en lugar de r=16) reduce la capacidad del modelo.
  • Agregar mas datos. La mejor solucion si es posible.
  • Usar early stopping. Detener el entrenamiento cuando la perdida de validacion no ha mejorado por N pasos de evaluacion.
from transformers import EarlyStoppingCallback

trainer = SFTTrainer(
    # ... otros argumentos
    callbacks=[EarlyStoppingCallback(early_stopping_patience=5)],
)

Depurando Problemas Comunes

Perdida NaN

La perdida NaN (Not a Number) significa que ha ocurrido un desbordamiento numerico. Esto tipicamente mata la ejecucion de entrenamiento.

Causas comunes y soluciones:

  1. Tasa de aprendizaje demasiado alta. Reducir de 2e-4 a 5e-5.
  2. Problemas de precision mixta. Cambiar de fp16 a bf16 (si tu GPU lo soporta). bf16 tiene un rango dinamico mas grande y es menos propenso al desbordamiento.
  3. Problemas de datos. Verificar ejemplos con texto extremadamente largo o caracteres inusuales que producen valores de perdida muy altos.
  4. Explosion de gradientes. Reducir max_grad_norm de 1.0 a 0.3.
# Depurar perdida NaN paso a paso
training_args = TrainingArguments(
    learning_rate=5e-5,        # Tasa de aprendizaje mas baja
    bf16=True,                 # Usar bfloat16 en lugar de fp16
    max_grad_norm=0.3,         # Recorte agresivo de gradientes
    logging_steps=1,           # Registrar cada paso para encontrar donde ocurre NaN
)

Explosion de Gradientes

Sintomas: La perdida se dispara repentinamente, las normas de gradiente se vuelven muy grandes (>100), el entrenamiento puede recuperarse o colapsar.

Soluciones:

  • Bajar max_grad_norm (prueba 0.3 o incluso 0.1)
  • Reducir tasa de aprendizaje
  • Aumentar pasos de calentamiento para dejar que el modelo se ajuste gradualmente

Errores de Formateo de Datos

Los bugs mas insidiosos. El modelo entrena sin errores, pero produce basura porque la plantilla de conversacion era incorrecta.

Como verificar:

# Siempre inspecciona ejemplos formateados antes de entrenar
sample = dataset["train"][0]
formatted = tokenizer.apply_chat_template(
    sample["messages"],
    tokenize=False,
)
print("=== Ejemplo formateado ===")
print(formatted)
print("=== IDs de tokens ===")
tokens = tokenizer.encode(formatted)
print(tokens[:50])  # Primeros 50 tokens
print(f"Total de tokens: {len(tokens)}")

Verifica que:

  • Los tokens especiales (BOS, EOS, marcadores de rol) estan presentes y son correctos
  • La respuesta del modelo no esta siendo enmascarada durante el entrenamiento
  • Los tokens de relleno no estan mezclados en el contenido

El Modelo Produce Salida Repetitiva

El modelo genera la misma frase una y otra vez, o entra en un bucle.

Causas:

  • Sobreajuste en datos de entrenamiento repetitivos
  • La perdida de entrenamiento llego demasiado baja (sobre-optimizacion)
  • Parametros de generacion incorrectos (temperature=0 sin penalizacion)

Soluciones:

  • Verificar datos de entrenamiento por duplicados
  • Reducir epocas de entrenamiento
  • Usar repetition_penalty=1.1 durante la generacion

Configurando TensorBoard

TensorBoard es la solucion de monitoreo mas simple — viene integrada con Hugging Face:

training_args = TrainingArguments(
    report_to="tensorboard",
    logging_dir="./logs",
    logging_steps=10,
)

Lanzar TensorBoard:

tensorboard --logdir ./logs

Esto abre un panel en http://localhost:6006 donde puedes ver curvas de perdida, tasa de aprendizaje, normas de gradiente y mas en tiempo real.

Configurando Weights and Biases

W&B proporciona mas funcionalidades: comparacion de experimentos, barridos de hiperparametros, colaboracion en equipo y rastreo de artefactos de modelo.

pip install wandb
wandb login  # Ingresar tu clave API
import wandb

wandb.init(
    project="llm-fine-tuning",
    name="llama3-8b-legal-v1",
    config={
        "model": "Llama-3.1-8B",
        "rank": 16,
        "alpha": 32,
        "learning_rate": 2e-4,
        "dataset_size": len(dataset["train"]),
    }
)

training_args = TrainingArguments(
    report_to="wandb",
    logging_steps=10,
)

W&B automaticamente registra todas las metricas de entrenamiento, metricas del sistema (memoria GPU, utilizacion) y te permite agregar registro personalizado:

# Registrar predicciones de muestra durante entrenamiento
class PredictionCallback(TrainerCallback):
    def on_evaluate(self, args, state, control, **kwargs):
        # Generar predicciones en algunos ejemplos
        model.training = False  # Cambiar a modo evaluacion
        test_prompts = ["Explica LoRA", "Que es QLoRA"]
        for prompt in test_prompts:
            output = generate(model, tokenizer, prompt)
            wandb.log({f"prediction/{prompt}": output, "step": state.global_step})

Estrategia de Checkpointing

Guarda checkpoints sabiamente — son tu poliza de seguro:

training_args = TrainingArguments(
    save_strategy="steps",
    save_steps=100,           # Guardar cada 100 pasos
    save_total_limit=3,       # Mantener solo los 3 mas recientes
    load_best_model_at_end=True,
    metric_for_best_model="eval_loss",
)

Consejos:

  • Para ejecuciones cortas (menos de 500 pasos), guarda cada 50 pasos
  • Para ejecuciones largas (mas de 2000 pasos), guarda cada 200 pasos
  • Siempre manten al menos el mejor checkpoint basado en la perdida de evaluacion
  • save_total_limit previene llenar el espacio en disco con checkpoints grandes

Lista de Verificacion Practica de Depuracion

Cuando una ejecucion de entrenamiento produce malos resultados, trabaja a traves de esta lista:

  1. Inspecciona datos en bruto. Lee 10 ejemplos de entrenamiento aleatorios. Son correctos?
  2. Verifica formato. Imprime un ejemplo formateado con todos los tokens especiales visibles. Coinciden con la plantilla esperada del modelo?
  3. Verifica tokenizacion. Se estan truncando los ejemplos? Cual es la distribucion de longitud de tokens?
  4. Revisa curvas de perdida. Esta disminuyendo la perdida de entrenamiento? Esta divergiendo la de evaluacion?
  5. Prueba manualmente. Genera salidas del modelo en diferentes checkpoints. Mejora la calidad?
  6. Compara con el modelo base. Es el modelo fine-tuned realmente mejor que el modelo base en tu tarea?

En la proxima leccion, abordamos el tema frecuentemente pasado por alto de la evaluacion — como medir sistematicamente si tu modelo fine-tuned realmente mejoro.