Capítulo 19 de 22 · avanzado
Fine-tuning y personalización
Qué cubre esta sesión
El fine-tuning le enseña a un modelo base un comportamiento con ejemplos — una voz de la casa, un formato de salida estricto, una clasificación de nicho que el modelo base sigue fallando — entrenándolo con cientos o miles de pares de input/output tuyos. Es potente, y es la última herramienta a la que deberías recurrir, no la primera. El prompting, los ejemplos few-shot y RAG resuelven la gran mayoría de los problemas más rápido, más barato y sin un loop de entrenamiento. El fine-tuning se gana su lugar cuando ya agotaste esos y aún así necesitas una consistencia que el prompt no puede comprar — o cuando quieres el comportamiento sin pagar un prompt largo en cada llamada.
Así que esta hora trata tanto de no hacer fine-tuning como de hacerlo. Cuando lo haces, el trabajo es abrumadoramente los datos: cientos de ejemplos consistentes en el formato correcto. El job de entrenamiento en sí son unas pocas líneas y luego una espera. Aquí construimos y validamos el dataset; el job en sí está protegido, porque cuesta dinero y toma tiempo.
OpenAI
Los datos de fine-tuning son JSONL — una conversación de chat por línea, cada una con los mensajes de system y user y la completion del assistant que quieres que el modelo imite. La propiedad más importante de todas es la consistencia: el mismo system prompt y la misma forma de salida en cada ejemplo, porque lo que sea inconsistente en tus datos es en lo que el modelo aprende a ser inconsistente.
# Fine-tune data is JSONL — one chat conversation per line, each with the system
# and user messages and the assistant completion you want the model to learn.
# Consistency is everything: the same system prompt, the same output shape, on
# every example. Sloppy data teaches sloppy behavior.
EXAMPLES = [
("hello there", "Ahoy there, matey!"),
("where is the treasure?", "Arr, where be the treasure buried?"),
("I am hungry", "Me belly be growlin' for grub!"),
("good morning", "Mornin', ye scurvy dog!"),
]
SYSTEM = "You are a pirate translator. Rewrite the user's line in pirate speak."
def to_jsonl(examples: list[tuple[str, str]], system: str) -> str:
lines = []
for user, assistant in examples:
lines.append(json.dumps({"messages": [
{"role": "system", "content": system},
{"role": "user", "content": user},
{"role": "assistant", "content": assistant},
]}))
return "\n".join(lines)
Lanzarlo son unas pocas líneas — sube el archivo, crea el job, haz polling hasta que termine — y produce un id de modelo que llamas exactamente como cualquier otro modelo. Aquí está detrás de un flag para que no gastes dinero por accidente.
# Launching is async and billable, so it's behind a flag. Upload the JSONL,
# create the job, then poll until it finishes — the job produces a model id you
# call exactly like any other model. (openai fine-tuning targets specific base
# models, e.g. gpt-4o-mini-2024-07-18.)
def launch(path: str) -> None:
from openai import OpenAI
client = OpenAI()
uploaded = client.files.create(file=open(path, "rb"), purpose="fine-tune")
job = client.fine_tuning.jobs.create(training_file=uploaded.id, model="gpt-4o-mini-2024-07-18")
print(f"started job {job.id} — poll client.fine_tuning.jobs.retrieve('{job.id}') until status='succeeded'")
Gemini
El tuning supervisado de Gemini toma una forma de datos más simple — pares de input/output en lugar de JSONL de chat completo — pero la disciplina es idéntica, y también lo es el job asíncrono y facturable.
# Gemini tuning takes input/output pairs directly. Same rule as everywhere:
# consistency across examples is what the model actually learns.
EXAMPLES = [
("hello there", "Ahoy there, matey!"),
("where is the treasure?", "Arr, where be the treasure buried?"),
("I am hungry", "Me belly be growlin' for grub!"),
("good morning", "Mornin', ye scurvy dog!"),
]
def build_dataset(examples):
from google.genai import types
return types.TuningDataset(
examples=[types.TuningExample(text_input=u, output=a) for u, a in examples],
)
Este es el marco de decisión que hay que llevarse de este capítulo. Recurre primero al prompting (rápido, gratis de cambiar). Agrega ejemplos few-shot cuando necesites un estilo o formato específico (sigue siendo solo un prompt). Agrega RAG cuando el problema es conocimiento que el modelo no tiene (el fine-tuning enseña comportamiento, no hechos — esto confunde a la gente constantemente). Haz fine-tuning solo cuando necesites un comportamiento consistente a una escala o latencia donde meter ejemplos en cada prompt es demasiado lento o demasiado caro. Y recuerda que el fine-tuning es un compromiso: salen modelos base nuevos todo el tiempo, y un fine-tune te ancla al que entrenaste hasta que lo rehagas.
Ponlo a trabajar
Tres apps de docker-compose bajo code/showcase/<slug>/, ordenadas alrededor del
flujo real: preparar los datos, probar la alternativa más barata, generar más
datos. La misma rutina: bash bootstrap-secrets.sh, docker compose up --build,
http://localhost:3000.
Showcase 1 — Constructor de dataset
Pega ejemplos, obtén JSONL de entrenamiento válido — formato de chat de OpenAI o pares de Gemini, lado a lado. Sin llamada al modelo, porque esto es pura plomería de datos, y la plomería de datos es donde los proyectos de fine-tuning de verdad viven o mueren. Ve ambos formatos y la diferencia de forma es obvia.
Showcase 2 — Estilo few-shot
La alternativa que deberías probar primero. Un puñado de pares de ejemplo en el prompt logra una voz personalizada sin entrenamiento, sin costo y con iteración instantánea. Córrelo y pregúntate honestamente si de verdad seguirías necesitando un fine-tune.
Showcase 3 — Datos sintéticos
El arreglo al problema del huevo y la gallina: necesitas cientos de ejemplos y tienes cinco. Describe la tarea y un modelo redacta más, con el formato correcto. Los revisarías y expandirías — los datos sintéticos son una línea de salida, no una de meta — pero le gana a un archivo en blanco.
Juntos son el arco honesto de un proyecto de fine-tuning: sobre todo trabajo de datos, con una fuerte tentación a resolver el problema sin entrenar del todo.
Córrelo
El README de esta carpeta tiene la versión de Python, la línea de instalación,
las dos variables de entorno y los comandos exactos. Las keys vienen del .env
sin trackear en la raíz del curso. Los scripts básicos construyen un dataset y
solo lanzan un job si pones RUN_FINETUNE=1 — cuesta dinero, así que está
apagado por defecto.
Lo que te llevas
El fine-tuning es real y ocasionalmente la respuesta correcta, pero es el último recurso, no el primero — el prompting, few-shot y RAG manejan la mayoría de lo que la gente recurre al fine-tuning para arreglar, y RAG (no fine-tuning) es la herramienta cuando la brecha es conocimiento, porque el tuning enseña comportamiento, no hechos. Cuando sí haces fine-tuning, el job es una nota al pie y los datos son el trabajo: cientos de ejemplos consistentes y correctamente formateados, que es por lo que el constructor de dataset y el generador sintético importan más que la llamada de entrenamiento. Sopesa también el compromiso — un fine-tune te ancla a un modelo base en un mundo donde siguen llegando mejores. La próxima semana juntamos todo el curso y dejamos que el modelo maneje: un agente de un solo loop con herramientas.