En la Lección 5 viste que LLaMA-7B necesita ~60 GB solo para los pesos. ¿Por qué? ¿Por qué fine-tuning necesita todavía más? Esta lección lo calcula con precisión y presenta las tres técnicas para reducir ese coste — motivando la LoRA de la próxima lección.
Cuando entrenas un modelo, la GPU necesita guardar en memoria cuatro cosas distintas. Mucha gente solo piensa en los pesos del modelo, pero eso es solo una cuarta parte del total — y no siempre la parte más grande.
El estado del optimizador Adam es el componente más costoso: guarda dos valores por cada peso del modelo.
Vamos a calcular exactamente cuántos gigabytes necesita fine-tuning completo de LLaMA-7B en condiciones estándar (FP32, optimizador Adam). No dejamos ningún paso sin mostrar.
Primero: un parámetro es un número (un peso) en el modelo. LLaMA-7B tiene 7.000.000.000 parámetros (siete mil millones). En el formato FP32 (float de 32 bits — el formato estándar en computación), cada número ocupa 32 bits = 4 bytes.
LLaMA-7B tiene exactamente 6.738.415.616 parámetros
(≈ 6.7 mil millones — "7B" es el redondeo de marketing)
Usaremos 7.000.000.000 para simplificar el cálculo.
───────────────────────────────────────────────────────────
COMPONENTE 1: PESOS DEL MODELO
7.000.000.000 params × 4 bytes/param
= 28.000.000.000 bytes
= 28.000.000.000 / (1024 × 1024 × 1024) GB
= 28.000.000.000 / 1.073.741.824
≈ 26.1 GB
───────────────────────────────────────────────────────────
COMPONENTE 2: GRADIENTES
(exactamente el mismo tamaño que los pesos)
7.000.000.000 params × 4 bytes/param ≈ 26.1 GB
¿Por qué el mismo tamaño?
Cada peso tiene exactamente un gradiente.
El gradiente es un número del mismo tipo (float32 = 4 bytes).
───────────────────────────────────────────────────────────
COMPONENTE 3: ESTADO DEL OPTIMIZADOR ADAM
Adam guarda DOS valores por cada peso:
· m_t (momento de primer orden — media exponencial de gradientes)
· v_t (momento de segundo orden — media de gradientes al cuadrado)
Costo: 7.000.000.000 × 2 × 4 bytes = 52.2 GB
¡El estado de Adam pesa el DOBLE del modelo!
───────────────────────────────────────────────────────────
COMPONENTE 4: ACTIVACIONES INTERMEDIAS (batch_size=4, seq_len=512)
Las activaciones son los resultados intermedios de cada capa
que hay que guardar para el backward pass.
Para LLaMA-7B con batch_size=4, seq_len=512:
≈ 4 × 512 × 4096 × 32_capas × 4_bytes ≈ ~8.6 GB
(esto es una estimación — varía según la implementación)
───────────────────────────────────────────────────────────
TOTAL APROXIMADO:
Pesos: 26.1 GB
Gradientes: 26.1 GB
Adam state: 52.2 GB
Activaciones: 8.6 GB
─────────────────────
TOTAL: ≈113.0 GB
La GPU A100 más grande de NVIDIA tiene 80 GB.
LLaMA-7B en fine-tuning completo NO CABE en una sola A100.
Necesitarías 2 A100 de 80 GB (costo: ~$20.000 cada una en 2024).
El gradient accumulation (acumulación de gradientes) es una técnica para simular un batch grande cuando la GPU no tiene suficiente memoria para procesarlo de una vez.
La idea es simple: en vez de procesar 32 ejemplos a la vez y actualizar los pesos, procesas 4 ejemplos a la vez durante 8 pasos, acumulas (sumas) los gradientes de cada paso, y solo al final de los 8 pasos actualizas los pesos. El resultado matemático es idéntico.
SIN gradient accumulation (batch real = 32): Paso 1: procesa 32 ejemplos → calcula gradientes → actualiza pesos Paso 2: procesa 32 ejemplos → calcula gradientes → actualiza pesos ... Memoria necesaria: suficiente para 32 ejemplos simultáneos CON gradient accumulation (steps=8, batch por paso=4): Paso 1: procesa 4 ejemplos → calcula gradientes → NO actualiza (acumula) Paso 2: procesa 4 ejemplos → calcula gradientes → NO actualiza (acumula) ... Paso 8: procesa 4 ejemplos → calcula gradientes → SÍ actualiza (borra acumulado) Memoria necesaria: suficiente para 4 ejemplos simultáneos Resultado matemático: equivalente a batch_size=32 (las sumas de gradientes de 4×8 = suma de gradientes de 32) ¿Cuándo usar gradient_accumulation_steps=8? · Cuando quieres batch efectivo de 32 pero solo caben 4 en GPU · Coste: es 8 veces más lento (8 pasos en vez de 1) · Beneficio: reduce memoria de activaciones por factor de 8
# Configurar gradient accumulation en TrainingArguments
args = TrainingArguments(
output_dir="./modelo",
per_device_train_batch_size=4, # solo 4 en GPU a la vez (menos memoria)
gradient_accumulation_steps=8, # batch efectivo = 4 × 8 = 32
# ... resto de argumentos
)
# El Trainer lo maneja automáticamente
# Al hacer trainer.train(), internamente:
# for step, batch in enumerate(dataloader):
# loss = model(batch)
# loss = loss / 8 ← normalizar
# loss.backward() ← acumular, no borrar gradientes
# if (step + 1) % 8 == 0:
# optimizer.step() ← actualizar solo cada 8 pasos
# optimizer.zero_grad()
print("gradient_accumulation_steps=8 configurado")
print("Batch efectivo = 4 × 8 = 32 (equivalente a batch_size=32)")
Hasta ahora hemos asumido que todos los números se guardan en FP32 (32 bits = 4 bytes). Pero los pesos de un modelo no necesitan tanta precisión para funcionar bien. En vez de usar 32 bits por número, podemos usar 16 bits — reduciendo la memoria a la mitad.
FP32 (float de 32 bits):
· 4 bytes por número
· Rango: ±3.4 × 10^38
· Precisión: ~7 dígitos decimales
· Uso: cálculo estándar, gradientes (¡siempre en FP32!)
FP16 (float de 16 bits):
· 2 bytes por número — MITAD de memoria
· Rango: ±65.504 (mucho menor que FP32)
· Precisión: ~3-4 dígitos decimales
· Problema: overflow fácil (números > 65.504 se convierten en Inf o NaN)
· Uso: activaciones y pesos durante forward/backward
BF16 (bfloat16, Brain Float):
· 2 bytes por número — también MITAD de memoria
· Rango: ±3.4 × 10^38 (igual rango que FP32 ← esto es crucial)
· Precisión: ~2-3 dígitos decimales (menor que FP16)
· No tiene el problema de overflow
· Uso: el preferido actualmente para LLMs (si la GPU lo soporta)
· Soportado en: A100, H100, RTX 3090+, Apple M1+
"Mixed" = los pesos se guardan en FP16/BF16 pero los gradientes
y las actualizaciones se calculan en FP32 para estabilidad
# Activar mixed precision en TrainingArguments
args = TrainingArguments(
output_dir="./modelo",
fp16=True, # usar FP16 (para GPUs NVIDIA más viejas: V100, T4)
# bf16=True, # usar BF16 (para NVIDIA A100, H100, RTX 3090+)
# ... resto de argumentos
)
# Verificar soporte de BF16 en la GPU actual
import torch
soporta_bf16 = torch.cuda.is_bf16_supported() if torch.cuda.is_available() else False
print(f"GPU disponible: {torch.cuda.is_available()}")
print(f"BF16 soportado: {soporta_bf16}")
print(f"Recomendado: {'bf16=True' if soporta_bf16 else 'fp16=True'}")
SIN mixed precision (todo FP32): Pesos: 26.1 GB Gradientes: 26.1 GB Adam state: 52.2 GB ← Adam SIEMPRE en FP32 SUBTOTAL: 104.4 GB CON mixed precision BF16: Pesos (BF16): 13.1 GB ← MITAD Gradientes (BF16): 13.1 GB ← MITAD Adam state (FP32): 52.2 GB ← igual (Adam necesita FP32 para estabilidad) SUBTOTAL: 78.4 GB Ahorro: 26 GB (25% de reducción) ¿Por qué Adam sigue en FP32? Los valores de momentum y variance de Adam son muy pequeños (del orden de 1e-8 a 1e-4). En FP16/BF16 estos valores se redondearían a cero y el optimizador dejaría de funcionar. FP32 es obligatorio aquí.
Las activaciones intermedias (componente 4) son el problema más variable. Aquí está el porqué de que existan: para hacer el backward pass y calcular los gradientes, PyTorch necesita saber cuáles fueron los valores en cada capa durante el forward pass. Normalmente guarda todo esto en memoria durante el forward — de ahí el enorme coste.
El gradient checkpointing (también llamado activation checkpointing) propone un trueque: en vez de guardar todas las activaciones, guarda solo algunas (los "checkpoints") y recalcula las demás durante el backward pass cuando se necesitan.
# Activar gradient checkpointing en el modelo
model.gradient_checkpointing_enable()
# En TrainingArguments también hay un flag
args = TrainingArguments(
output_dir="./modelo",
gradient_checkpointing=True,
# ... resto de argumentos
)
# Verificar que está activo
print("Gradient checkpointing habilitado")
print("Coste: entrenamiento ~20-30% más lento")
print("Beneficio: reduce activaciones hasta 10× menos memoria")
Configuración base (FP32, sin optimizaciones): Pesos: 26.1 GB Gradientes: 26.1 GB Adam state: 52.2 GB Activaciones: 8.6 GB (batch=4, seq=512) ───────────────────────────── TOTAL: ≈ 113.0 GB (necesitas 2× A100 de 80 GB) Después de todas las optimizaciones: +BF16 pesos/gradientes → pesos: 13.1, gradientes: 13.1 +Adam en FP32 → Adam: 52.2 (sin cambio) +Gradient checkpointing → activaciones: ~0.9 GB (10× reducción) ───────────────────────────── TOTAL optimizado: ≈ 79.3 GB (barely cabría en 1× A100 de 80 GB) La conclusión honesta: Incluso con TODAS las optimizaciones disponibles, fine-tuning completo de LLaMA-7B requiere una A100 de 80 GB ($15.000+). Eso es exactamente el problema que resuelven LoRA y QLoRA en la siguiente lección.
Aquí viene la pregunta clave: ¿es realmente necesario modificar los 7 billones de parámetros de LLaMA para que clasifique reseñas de restaurantes?
La respuesta, que investigadores de Microsoft demostraron en 2021, es no. Se comprobó experimentalmente que cuando adaptas un modelo grande a una tarea específica, el cambio real en los pesos es de muy bajo rango — matemáticamente, la diferencia entre el modelo original y el adaptado tiene un rango intrínseco muy pequeño.
PEFT (Parameter-Efficient Fine-Tuning) es el nombre genérico para todas las técnicas que adaptan el modelo cambiando solo una fracción pequeña de los parámetros. Las más importantes son:
| Técnica | % parámetros modificados | Memoria necesaria | Calidad vs full FT |
|---|---|---|---|
| Fine-tuning completo | 100% | 113 GB (LLaMA-7B) | Referencia 100% |
| LoRA (r=8) | ~0.5-1% | ~18 GB (LLaMA-7B) | ~95-99% |
| QLoRA (r=8, 4-bit) | ~0.5-1% | ~6-8 GB (LLaMA-7B) | ~90-95% |
| Prompt tuning | <0.1% | Mínima | ~80-90% |
LoRA sobre LLaMA-7B necesita ~18 GB — que cabe cómodamente en una RTX 3090 de consumidor ($800 al momento de escribir esto). QLoRA reduce eso a 6-8 GB — que cabe en una RTX 3080 o incluso en Google Colab gratuito.
Elige el tamaño del modelo, el tipo de datos y las optimizaciones activadas. La calculadora muestra exactamente cuánta VRAM necesitas.
En la próxima lección: LoRA — cómo funciona matemáticamente la descomposición de bajo rango, qué matrices se adaptan, el parámetro de escala α, y el código real con la librería PEFT.