Curso / HF LLM Course / Capítulo 11
● piloto de formato

Hugging Face · LLM Course · Capítulo 11

Fine-tuning de LLMs: lo que el Capítulo 3 adelantó, ahora en serio

Este es el capítulo donde el curso se pone al día con 2026 de verdad: SFT (Supervised Fine-Tuning) reemplazó al fine-tuning de tarea única del Capítulo 3 como el camino estándar para adaptar un LLM — y es exactamente lo que ya usaste en tus propios proyectos.

1. Chat templates, en serio

Ya los mencionamos en los Capítulos 2 y 6 como la pieza que reemplaza a token_type_ids para modelos de chat. Acá se formaliza: un modelo base (recién preentrenado) solo sabe completar texto; un modelo instruct fue afinado para seguir el formato de conversación de su chat template específico — y ese formato varía por familia de modelo (Llama, Mistral, Gemma, cada uno con sus propios tokens de rol y delimitadores).

El template no es cosmético: define dónde empieza y termina cada turno, qué texto es del sistema y cuál del usuario, y en los modelos más nuevos, cómo se representan llamadas a herramientas o inputs multimodales. Usar el template equivocado para un modelo — o concatenar strings a mano en vez de usar el que trae el tokenizer — es una de las formas más comunes de que un modelo instruct responda peor de lo que debería, sin ningún error visible.

2. Fine-tuning supervisado con SFTTrainer

SFT es el sucesor natural del fine-tuning de tarea única del Capítulo 3: en vez de entrenar para una sola tarea (clasificar, traducir), entrenás sobre ejemplos de conversación completa — pares instrucción/respuesta — para que el modelo aprenda a seguir instrucciones en general. Es, literalmente, el paso que convierte un modelo base en algo parecido a ChatGPT o Claude.

Tiene sentido cuando necesitás controlar el formato exacto de salida, o adaptar el modelo a un dominio específico que un modelo instruct genérico no cubre bien — los mismos dos motivos del Capítulo 7, aplicados a instrucciones en vez de a una tarea puntual.

from trl import SFTTrainer, SFTConfig

trainer = SFTTrainer(
    model="HuggingFaceTB/SmolLM2-1.7B",
    train_dataset=dataset,  # conversaciones ya en formato chat template
    args=SFTConfig(max_seq_length=2048, packing=True),
)
trainer.train()

packing=True concatena varios ejemplos cortos en una sola secuencia de longitud máxima — evita desperdiciar cómputo en padding cuando tus ejemplos son mucho más cortos que el límite del modelo. Todo lo del Capítulo 3 sobre leer curvas de aprendizaje aplica igual acá: loss que baja y se estabiliza es sano, un gap creciente entre train y validación es overfitting, sin importar que ahora el objetivo sea "seguir instrucciones" en vez de "clasificar una reseña".

3. LoRA, con código real

El Capítulo 3 introdujo la idea; acá está la implementación real, vía peft integrado directo en SFTTrainer:

from peft import LoraConfig

peft_config = LoraConfig(
    r=16, lora_alpha=32, lora_dropout=0.05,
    target_modules=["q_proj", "v_proj"],
    task_type="CAUSAL_LM",
)
trainer = SFTTrainer(model=model, train_dataset=dataset, peft_config=peft_config)
trainer.train()

El resultado de entrenar así no son pesos nuevos del modelo completo — es un adapter, un puñado de archivos chicos con las matrices A/B entrenadas. Podés servirlo separado (cargando el adapter sobre el modelo base en runtime) o fusionarlo — sumar A·B directamente a los pesos originales — para quedarte con un único checkpoint normal, sin dependencias extra de peft en producción.

🔧 El límite de "fusionar" que ya conocés

Fusionar A·B en los pesos solo tiene sentido cuando esos pesos son números de punto flotante normales. En la página de NVFP4 en difusión viste el caso límite: si el modelo base ya está empaquetado en NVFP4 (4 bits por valor, en bloques de 16), no existe un "peso intermedio" al que sumarle la corrección — por eso nunchaku tiene que fusionar el cómputo de LoRA en el kernel, en cada forward, en vez de fusionarlo en los pesos como acá. Mismo concepto de LoRA, weights float vs. weights cuantizados.

4. Evaluación: benchmarks y sus límites

Un LLM instruction-tuned se evalúa contra baterías de benchmarks estandarizados — útiles para comparar modelos entre sí, con la salvedad de que un buen score no garantiza que sirva para tu caso de uso específico:

BenchmarkMide
MMLUConocimiento general, muchas materias
TruthfulQAResistencia a reproducir conceptos erróneos comunes
BBH / GSM8KRazonamiento lógico y matemático
HumanEvalGeneración de código funcional

Cuando el benchmark genérico no alcanza, dos alternativas: LLM-as-judge (otro LLM evalúa la respuesta contra un criterio) o arenas (comparación humana entre respuestas de distintos modelos) — y para lo más específico, un set de evaluación propio, hecho a medida.

🟢 Ya construiste las dos alternativas que este capítulo sugiere

El claim_verifier.py de tu gate anti-alucinación es exactamente un LLM-as-judge — un modelo evaluando la salida de otro contra un criterio preciso (¿esta cita respalda el reclamo?) — con el agregado de que el veredicto del juez se verifica en código, no se toma como palabra final. Y model_acceptance.py es tu set de evaluación custom: no un benchmark genérico, sino un puñado de casos reales (el incidente Micron, dos casos limpios) que importan específicamente para tu producto. El capítulo lo presenta como la mejor práctica; vos ya la aplicás.

5. Resumen

  1. Un chat template no es cosmético — define el contrato de formato entre vos y el modelo, distinto por familia.
  2. SFT es fine-tuning para seguir instrucciones en general, no para una sola tarea — el paso que convierte un modelo base en algo usable como asistente.
  3. LoRA con peft + SFTTrainer entrena un adapter chico que podés servir separado o fusionar — fusionar solo funciona sobre pesos en punto flotante, no sobre pesos ya cuantizados.
  4. Los benchmarks estandarizados sirven para comparar modelos entre sí; LLM-as-judge y evaluación custom son lo que realmente te dice si el modelo sirve para tu caso — y ya construiste ambos.