Inicio / Inteligencia Artificial / AI-First Full Stack: Construye Apps con IA / Fine-tuning Aplicado

Fine-tuning Aplicado

Cuándo hacer fine-tuning, preparar datasets, usar OpenAI fine-tuning API, evaluar resultados y LoRA.

Intermedio
🔒 Solo lectura
📖

Estás en modo lectura

Puedes leer toda la lección, pero para marcar progreso, hacer ejercicios y ganar XP necesitas una cuenta Pro.

Desbloquear por $9/mes

Fine-tuning Aplicado

El fine-tuning permite adaptar un LLM existente a tu dominio o estilo. En esta lección aprenderás cuándo usarlo (vs RAG), cómo preparar datasets, ejecutar fine-tuning via API y evaluar los resultados.


¿Cuándo hacer fine-tuning?

Situación Solución
Datos que cambian frecuentemente RAG
Necesitas citar fuentes específicas RAG
Cambiar el tono/estilo de respuesta Fine-tuning
Dominio muy especializado (médico, legal) Fine-tuning
Formato de output específico y consistente Fine-tuning
Reducir latencia (eliminar retrieval) Fine-tuning
Reducir costos (modelo más pequeño) Fine-tuning
Ambas: conocimiento + estilo Fine-tuning + RAG
Complejidad:  Prompt Engineering → RAG → Fine-tuning → Entrenar desde cero
Costo:        $0                   $$    $$$            $$$$$$
Tiempo:       Minutos              Horas Días           Meses

Preparación del Dataset

Formato JSONL

{"messages": [{"role": "system", "content": "Eres un experto en derecho laboral mexicano."}, {"role": "user", "content": "¿Cuántos días de vacaciones corresponden después de un año?"}, {"role": "assistant", "content": "Según el artículo 76 de la Ley Federal del Trabajo (reformado en 2023), después del primer año de trabajo corresponden **12 días** de vacaciones. Este número incrementa 2 días por año hasta llegar a 20 días, y después aumenta 2 días por cada 5 años adicionales."}]}
{"messages": [{"role": "system", "content": "Eres un experto en derecho laboral mexicano."}, {"role": "user", "content": "¿Qué es el aguinaldo y cuánto es?"}, {"role": "assistant", "content": "El aguinaldo es una prestación obligatoria establecida en el artículo 87 de la Ley Federal del Trabajo. Corresponde a un mínimo de **15 días de salario**, pagado antes del 20 de diciembre. Trabajadores con menos de un año reciben la parte proporcional."}]}

Script para generar dataset

import { writeFileSync } from 'fs';

interface TrainingExample {
  messages: {
    role: 'system' | 'user' | 'assistant';
    content: string;
  }[];
}

function generateTrainingData(
  systemPrompt: string,
  examples: { question: string; answer: string }[]
): TrainingExample[] {
  return examples.map(({ question, answer }) => ({
    messages: [
      { role: 'system', content: systemPrompt },
      { role: 'user', content: question },
      { role: 'assistant', content: answer },
    ],
  }));
}

// Generar dataset
const dataset = generateTrainingData(
  'Eres un asistente de soporte técnico para la app SuperChat.',
  [
    {
      question: '¿Cómo cambio mi contraseña?',
      answer: 'Para cambiar tu contraseña:\n1. Ve a Configuración > Seguridad\n2. Haz clic en "Cambiar contraseña"\n3. Ingresa tu contraseña actual y la nueva\n4. Confirma con "Guardar"\n\nLa contraseña debe tener mínimo 8 caracteres, una mayúscula y un número.',
    },
    // ... más ejemplos (mínimo 10, recomendado 50-100+)
  ]
);

// Guardar como JSONL
const jsonl = dataset.map(d => JSON.stringify(d)).join('\n');
writeFileSync('training_data.jsonl', jsonl);

Validación del dataset

function validateDataset(filePath: string): {
  valid: boolean;
  errors: string[];
  stats: Record<string, number>;
} {
  const lines = readFileSync(filePath, 'utf-8').trim().split('\n');
  const errors: string[] = [];
  let totalTokens = 0;

  for (let i = 0; i < lines.length; i++) {
    try {
      const example = JSON.parse(lines[i]);

      if (!example.messages || !Array.isArray(example.messages)) {
        errors.push(`Line ${i + 1}: missing 'messages' array`);
        continue;
      }

      const roles = example.messages.map((m: any) => m.role);

      if (!roles.includes('user') || !roles.includes('assistant')) {
        errors.push(`Line ${i + 1}: must have 'user' and 'assistant' messages`);
      }

      // Estimar tokens (aprox 4 chars = 1 token)
      const chars = example.messages.reduce(
        (sum: number, m: any) => sum + m.content.length, 0
      );
      totalTokens += Math.ceil(chars / 4);
    } catch {
      errors.push(`Line ${i + 1}: invalid JSON`);
    }
  }

  return {
    valid: errors.length === 0,
    errors,
    stats: {
      totalExamples: lines.length,
      estimatedTokens: totalTokens,
      estimatedCost: totalTokens * 0.000008, // Precio aproximado GPT-4o-mini fine-tuning
    },
  };
}

Fine-tuning con OpenAI API

Paso 1: Subir el archivo

const file = await openai.files.create({
  file: createReadStream('training_data.jsonl'),
  purpose: 'fine-tune',
});

console.log(`File uploaded: ${file.id}`);

Paso 2: Crear el job de fine-tuning

const job = await openai.fineTuning.jobs.create({
  training_file: file.id,
  model: 'gpt-4o-mini-2024-07-18', // Modelo base
  hyperparameters: {
    n_epochs: 3,                    // Número de epochs (auto para automático)
    batch_size: 'auto',
    learning_rate_multiplier: 'auto',
  },
  suffix: 'mi-asistente-soporte',   // Identificador custom
});

console.log(`Job created: ${job.id}`);
console.log(`Status: ${job.status}`);

Paso 3: Monitorear el progreso

async function monitorFineTuning(jobId: string) {
  while (true) {
    const job = await openai.fineTuning.jobs.retrieve(jobId);

    console.log(`Status: ${job.status}`);

    if (job.status === 'succeeded') {
      console.log(`✅ Model ready: ${job.fine_tuned_model}`);
      return job.fine_tuned_model;
    }

    if (job.status === 'failed') {
      console.error(`❌ Fine-tuning failed:`, job.error);
      throw new Error('Fine-tuning failed');
    }

    // Ver eventos
    const events = await openai.fineTuning.jobs.listEvents(jobId, { limit: 5 });
    for (const event of events.data) {
      console.log(`  [${event.created_at}] ${event.message}`);
    }

    await new Promise(r => setTimeout(r, 30_000)); // Esperar 30s
  }
}

Paso 4: Usar el modelo fine-tuned

const response = await openai.chat.completions.create({
  model: 'ft:gpt-4o-mini-2024-07-18:my-org:mi-asistente-soporte:abc123',
  messages: [
    { role: 'system', content: 'Eres un asistente de soporte técnico para SuperChat.' },
    { role: 'user', content: '¿Cómo exporto mis datos?' },
  ],
});

LoRA: Fine-tuning eficiente para modelos open source

# Ejemplo con Hugging Face + PEFT (Python)
from peft import LoraConfig, get_peft_model
from transformers import AutoModelForCausalLM, AutoTokenizer

# Cargar modelo base
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3.1-8B")
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.1-8B")

# Configurar LoRA
lora_config = LoraConfig(
    r=16,                     # Rango de las matrices
    lora_alpha=32,            # Factor de escala
    target_modules=["q_proj", "v_proj"],  # Capas a adaptar
    lora_dropout=0.05,
    task_type="CAUSAL_LM",
)

# Crear modelo con LoRA
peft_model = get_peft_model(model, lora_config)

# Solo ~0.1% de parámetros entrenables!
peft_model.print_trainable_parameters()
# trainable params: 4,194,304 || all params: 8,030,261,248 || trainable%: 0.0522

QLoRA: LoRA + Cuantización

from transformers import BitsAndBytesConfig
import torch

# Cuantización a 4-bit
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
)

# Cargar modelo cuantizado
model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-3.1-8B",
    quantization_config=bnb_config,
    device_map="auto",
)

# Ahora LoRA sobre el modelo cuantizado
# Reduce de 80GB a ~6GB de VRAM!

Evaluación del modelo fine-tuned

interface EvalResult {
  question: string;
  expected: string;
  actual: string;
  score: number;
}

async function evaluateModel(
  modelId: string,
  testSet: { question: string; expectedAnswer: string }[]
): Promise<EvalResult[]> {
  const results: EvalResult[] = [];

  for (const { question, expectedAnswer } of testSet) {
    const response = await openai.chat.completions.create({
      model: modelId,
      messages: [{ role: 'user', content: question }],
    });

    const actual = response.choices[0].message.content!;

    // Evaluar con LLM-as-Judge
    const judge = await openai.chat.completions.create({
      model: 'gpt-4o',
      response_format: { type: 'json_object' },
      messages: [{
        role: 'user',
        content: `Evalúa si la respuesta es correcta y completa.

Pregunta: ${question}
Respuesta esperada: ${expectedAnswer}
Respuesta generada: ${actual}

Retorna JSON: { "score": 1-10, "feedback": "..." }`,
      }],
    });

    const evaluation = JSON.parse(judge.choices[0].message.content!);

    results.push({
      question,
      expected: expectedAnswer,
      actual,
      score: evaluation.score,
    });
  }

  const avgScore = results.reduce((s, r) => s + r.score, 0) / results.length;
  console.log(`Average score: ${avgScore.toFixed(2)}/10`);

  return results;
}

Costos de fine-tuning

Modelo Training (1M tokens) Input (1M) Output (1M)
gpt-4o-mini fine-tuned $3.00 $0.30 $1.20
gpt-4o fine-tuned $25.00 $3.75 $15.00
Llama 3.1 8B (propio) Costo GPU $0 $0

Un dataset de 100 ejemplos ≈ 50K tokens ≈ $0.15 de training con GPT-4o-mini.

🔒

Ejercicio práctico disponible

Prepara un dataset de fine-tuning

Desbloquear ejercicios
// Prepara un dataset de fine-tuning
// Desbloquea Pro para acceder a este ejercicio
// y ganar +50 XP al completarlo

function ejemplo() {
    // Tu código aquí...
}

¿Te gustó esta lección?

Con Pro puedes marcar progreso, hacer ejercicios, tomar quizzes, ganar XP y obtener tu constancia.

Ver planes desde $9/mes