Proyecto: Fine-Tune Tu Propio Modelo
Tu Proyecto de Fine-Tuning End-to-End
Esta es la leccion capstone. Aplicaras todo de las 11 lecciones anteriores en un solo proyecto completo: elegir un caso de uso, crear un dataset, hacer fine-tuning de un modelo con QLoRA, evaluarlo contra el modelo base, fusionar el adaptador LoRA, convertir a GGUF, desplegar con Ollama y probar a traves de una API. Cada paso incluye codigo completo y ejecutable.
Paso 1: Elige Tu Caso de Uso
Para este proyecto, construiremos un asistente de documentacion tecnica — un modelo con fine-tuning para generar explicaciones claras y estructuradas de conceptos tecnicos en un formato especifico. Puedes adaptar esto a tu propio caso de uso cambiando el dataset.
Comportamiento objetivo:
- Siempre comenzar con un resumen de una oracion
- Usar viñetas para detalles clave
- Incluir un ejemplo practico cuando sea relevante
- Terminar con una seccion de "Errores Comunes"
- Mantener respuestas concisas (200-400 palabras)
Paso 2: Crea Tu Dataset
Generaremos 300 ejemplos de entrenamiento sinteticos usando GPT-4o, luego validaremos manualmente un subconjunto.
"""
Paso 2: Generar dataset de entrenamiento.
Requiere: pip install openai
"""
import openai
import json
import random
import time
from pathlib import Path
client = openai.OpenAI()
# Temas para generar ejemplos de entrenamiento
topics = [
"Metodos de autenticacion de API REST",
"Estrategias de indexacion de bases de datos",
"Redes de contenedores Docker",
"Estrategias de ramificacion Git",
"Algoritmos de balanceo de carga",
"Estrategias de cache (Redis, Memcached)",
"Colas de mensajes (RabbitMQ, Kafka)",
"Diseno de pipelines CI/CD",
"Programacion de pods en Kubernetes",
"Gestion de certificados SSL/TLS",
"WebSocket vs Server-Sent Events",
"Estrategias de migracion de bases de datos",
"Tecnicas de limitacion de tasa de API",
"Patrones de comunicacion de microservicios",
"Agregacion de logs y monitoreo",
# Agrega 30+ temas mas para diversidad
]
system_prompt = """Eres un escritor de documentacion tecnica. Al explicar un tema:
1. Comienza con un resumen de una oracion
2. Usa viñetas para detalles clave
3. Incluye un ejemplo practico cuando sea relevante
4. Termina con una seccion de "Errores Comunes" listando 2-3 errores
5. Manten tu respuesta entre 200-400 palabras
6. Se preciso y practico, no teorico"""
def generate_example(topic):
"""Generar un solo ejemplo de entrenamiento."""
# Crear una pregunta realista de usuario sobre este tema
question_response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{
"role": "user",
"content": f"Escribe una pregunta tecnica realista y especifica sobre: {topic}. "
f"Devuelve SOLO la pregunta, nada mas."
}],
temperature=0.9,
)
question = question_response.choices[0].message.content.strip()
# Generar la respuesta ideal
answer_response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": question},
],
temperature=0.7,
)
answer = answer_response.choices[0].message.content.strip()
return {
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": question},
{"role": "assistant", "content": answer},
]
}
# Generar ejemplos
Path("data").mkdir(exist_ok=True)
examples = []
for i in range(300):
topic = random.choice(topics)
try:
example = generate_example(topic)
examples.append(example)
if (i + 1) % 10 == 0:
print(f"Generados {i + 1}/300 ejemplos")
except Exception as e:
print(f"Error en {i}: {e}")
continue
time.sleep(0.5) # Limitacion de tasa
# Dividir en train/val
random.shuffle(examples)
train_data = examples[:270]
val_data = examples[270:]
# Guardar
for name, data in [("train", train_data), ("val", val_data)]:
with open(f"data/{name}.jsonl", "w") as f:
for ex in data:
f.write(json.dumps(ex) + "\n")
print(f"Guardados {len(train_data)} train y {len(val_data)} val ejemplos")
Paso 3: Valida Tus Datos
Siempre verifica muestras antes de entrenar:
"""
Paso 3: Validar calidad del dataset.
"""
import json
with open("data/train.jsonl") as f:
examples = [json.loads(line) for line in f]
# Verificar 5 ejemplos aleatorios
import random
for ex in random.sample(examples, 5):
user_msg = ex["messages"][1]["content"]
assistant_msg = ex["messages"][2]["content"]
word_count = len(assistant_msg.split())
print(f"P: {user_msg[:80]}...")
print(f"R: ({word_count} palabras) {assistant_msg[:100]}...")
print(f"Tiene viñetas: {'- ' in assistant_msg or '* ' in assistant_msg}")
print(f"Tiene Errores Comunes: {'Errores Comunes' in assistant_msg or 'error' in assistant_msg.lower()}")
print("---")
Paso 4: Fine-Tune con QLoRA
"""
Paso 4: Fine-tune de Llama 3.1 8B con QLoRA usando Unsloth.
Requiere: pip install unsloth trl datasets
Hardware: 16GB+ VRAM
"""
from unsloth import FastLanguageModel
from trl import SFTTrainer
from transformers import TrainingArguments
from datasets import load_dataset
# 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,
)
# 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,
)
# Cargar dataset
dataset = load_dataset("json", data_files={
"train": "data/train.jsonl",
"validation": "data/val.jsonl",
})
# Entrenar
trainer = SFTTrainer(
model=model,
tokenizer=tokenizer,
args=TrainingArguments(
output_dir="./output/tech-docs-assistant",
num_train_epochs=3,
per_device_train_batch_size=2,
gradient_accumulation_steps=8,
learning_rate=2e-4,
lr_scheduler_type="cosine",
warmup_ratio=0.05,
optim="adamw_8bit",
weight_decay=0.01,
bf16=True,
logging_steps=10,
eval_strategy="steps",
eval_steps=25,
save_strategy="steps",
save_steps=50,
save_total_limit=3,
load_best_model_at_end=True,
metric_for_best_model="eval_loss",
seed=42,
report_to="tensorboard",
),
train_dataset=dataset["train"],
eval_dataset=dataset["validation"],
max_seq_length=2048,
)
trainer.train()
# Guardar adaptador
model.save_pretrained("./output/tech-docs-assistant/final")
tokenizer.save_pretrained("./output/tech-docs-assistant/final")
print("Entrenamiento completo. Adaptador guardado.")
Paso 5: Evaluar Contra el Modelo Base
"""
Paso 5: Comparar modelo fine-tuned vs modelo base.
"""
import json
import openai
client = openai.OpenAI()
# Prompts de prueba (NO de los datos de entrenamiento)
test_prompts = [
"Como funciona el connection pooling en PostgreSQL?",
"Cuales son las compensaciones entre gRPC y REST?",
"Explica despliegues blue-green vs canary releases.",
"Como manejas deadlocks de base de datos en produccion?",
"Cual es la diferencia entre escalado horizontal y vertical?",
]
# Generar salidas del modelo fine-tuned
FastLanguageModel.for_inference(model)
finetuned_outputs = []
for prompt in test_prompts:
messages = [
{"role": "system", "content": "Eres un escritor de documentacion tecnica..."},
{"role": "user", "content": prompt},
]
inputs = tokenizer.apply_chat_template(
messages, return_tensors="pt", add_generation_prompt=True
).to("cuda")
outputs = model.generate(input_ids=inputs, max_new_tokens=512, temperature=0.7, do_sample=True)
response = tokenizer.decode(outputs[0][inputs.shape[1]:], skip_special_tokens=True)
finetuned_outputs.append(response)
# Usar LLM-como-juez para comparar
for i, prompt in enumerate(test_prompts):
judge_response = client.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "user",
"content": f"""Puntua esta respuesta tecnica en una escala de 1-10 para:
- Cumplimiento de formato (sigue la estructura resumen/viñetas/ejemplo/errores)
- Precision tecnica
- Concision (200-400 palabras)
Pregunta: {prompt}
Respuesta: {finetuned_outputs[i]}
Devuelve JSON: {{"format": X, "accuracy": X, "conciseness": X, "overall": X}}"""
}],
response_format={"type": "json_object"},
temperature=0,
)
scores = json.loads(judge_response.choices[0].message.content)
print(f"P: {prompt[:60]}...")
print(f"Puntuaciones: {scores}")
print("---")
Paso 6: Fusionar Adaptador LoRA
"""
Paso 6: Fusionar LoRA en el modelo base.
"""
# Usando la fusion integrada de Unsloth
model.save_pretrained_merged(
"./output/tech-docs-merged",
tokenizer,
save_method="merged_16bit",
)
print("Modelo fusionado guardado en float16.")
Paso 7: Convertir a GGUF
"""
Paso 7: Convertir a GGUF para Ollama.
"""
model.save_pretrained_gguf(
"./output/tech-docs-gguf",
tokenizer,
quantization_method="q4_k_m",
)
print("Modelo GGUF guardado con cuantizacion Q4_K_M.")
Paso 8: Desplegar con Ollama
# Crear el Modelfile
cat > Modelfile << 'MODELEOF'
FROM ./output/tech-docs-gguf/unsloth.Q4_K_M.gguf
TEMPLATE """{{ if .System }}<|start_header_id|>system<|end_header_id|>
{{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|>
{{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|>
{{ .Response }}<|eot_id|>"""
SYSTEM """Eres un escritor de documentacion tecnica. Al explicar un tema:
1. Comienza con un resumen de una oracion
2. Usa viñetas para detalles clave
3. Incluye un ejemplo practico cuando sea relevante
4. Termina con una seccion de Errores Comunes listando 2-3 errores
5. Manten tu respuesta entre 200-400 palabras"""
PARAMETER temperature 0.7
PARAMETER top_p 0.9
PARAMETER stop "<|eot_id|>"
MODELEOF
# Crear y probar el modelo
ollama create tech-docs -f Modelfile
ollama run tech-docs "Explica como funciona el connection pooling en bases de datos"
Paso 9: Probar via API
"""
Paso 9: Probar el modelo desplegado a traves de la API.
"""
import openai
client = openai.OpenAI(
base_url="http://localhost:11434/v1",
api_key="ollama",
)
test_questions = [
"Que es el circuit breaking en microservicios?",
"Como implementas logica de reintentos con backoff exponencial?",
"Explica el teorema CAP con un ejemplo practico.",
]
for question in test_questions:
response = client.chat.completions.create(
model="tech-docs",
messages=[{"role": "user", "content": question}],
temperature=0.7,
max_tokens=512,
)
answer = response.choices[0].message.content
print(f"P: {question}")
print(f"R: {answer}")
print(f"Conteo de palabras: {len(answer.split())}")
print("=" * 60)
Lista de Verificacion: Lo Que Has Construido
Al completar este proyecto, has:
- [ ] Definido un caso de uso claro con requisitos de comportamiento medibles
- [ ] Generado y validado un dataset de entrenamiento (300 ejemplos)
- [ ] Hecho fine-tuning de Llama 3.1 8B con QLoRA (rango 16)
- [ ] Evaluado el modelo fine-tuned contra el modelo base usando LLM-como-juez
- [ ] Fusionado el adaptador LoRA en el modelo base
- [ ] Convertido a formato GGUF (cuantizacion Q4_K_M)
- [ ] Desplegado con Ollama como servicio local
- [ ] Probado a traves de una API compatible con OpenAI
Proximos Pasos
Con esta base, puedes:
- Escalar tu dataset a 1,000-2,000 ejemplos para una calidad aun mejor
- Probar entrenamiento DPO con pares de preferencia para alinear aun mas el comportamiento
- Desplegar en la nube con vLLM para rendimiento de produccion
- Publicar en Hugging Face Hub para compartir con la comunidad
- Entrenar en tu propio dominio — intercambia los temas y el system prompt por tu caso de uso especifico
Todo el pipeline que construiste en este proyecto es reutilizable. Cambia el dataset y puedes hacer fine-tuning de un modelo para cualquier dominio: documentacion medica, soporte al cliente, revision de codigo, analisis financiero o cualquier otra cosa donde importa el comportamiento consistente y de alta calidad del LLM.
Felicitaciones por completar el curso. Ahora tienes las habilidades practicas para llevar cualquier LLM de proposito general a especializado — desde los datos hasta el despliegue.