El presupuesto de VRAM: qué cabe en 32 GB
dado un modelo y una tarea, decir en segundos si cabe en tu 5090 y con qué técnica, o si debe ir a cloud. Esto te ahorra horas de experimentos que iban a fallar con OOM (out-of-memory) desde el principio.
3.1La VRAM se reparte en cuatro cubos
Toda la memoria de la GPU durante un workload se divide en:
- Pesos del modelo —
num_params × bytes_por_param. - Estados del optimizador (solo en training) — el más caro y el que la gente olvida.
- Activaciones / KV-cache — depende de batch, longitud de secuencia y arquitectura.
- Overhead — CUDA context, fragmentación, buffers de la librería (~1–2 GB).
La regla es siempre: VRAM_necesaria = pesos + optimizador + activaciones + overhead ≤ 32 GB.
3.2Inferencia: la cuenta fácil
En inferencia no hay optimizador ni gradientes. Solo pesos + KV-cache + overhead.
VRAM_inferencia ≈ num_params × bytes_por_param + KV_cache + ~1.5 GB
bytes_por_param según precisión: FP16/BF16 = 2 · FP8 = 1 · 4-bit (Q4/NF4/AWQ/MXFP4) = 0.5.
Ejemplos (solo pesos, batch pequeño):
| Modelo | FP16 | FP8 | 4-bit |
|---|---|---|---|
| 8B | 16 GB | 8 GB | 4 GB |
| 14B | 28 GB ⚠️ tight | 14 GB | 7 GB |
| 32B denso | 64 GB ❌ | 32 GB ⚠️ | 16 GB ✅ |
| 70B denso | 140 GB ❌ | 70 GB ❌ | 35 GB ❌ |
| 120B MoE (gpt-oss) | — | — | ~30 GB ✅ (MXFP4) |
(Suma el KV-cache de L1·E1 para contextos largos; a 32K tokens puede ser varios GB.)
Conclusión operativa de inferencia:
- FP16 denso: hasta 14B.
- FP8 denso: hasta ~30B.
- 4-bit denso: 32B (Qwen3-32B) cabe; 70B no (35 GB > 32).
- MoE en 4-bit: hasta ~120B (gpt-oss-120B) porque solo cargas pesos esparsos activos en cómputo, pero todos los pesos siguen en VRAM → el truco es que MXFP4 los comprime lo justo para entrar en 31 GB.
3.3Training: por qué cuesta 4–8× más que inferencia
Aquí está la trampa que sorprende a todo el mundo. En full fine-tuning con AdamW, por cada parámetro guardas:
- el peso (2 o 4 bytes),
- el gradiente (mismos bytes que el peso),
- dos estados del optimizador (momentum
my varianzav, normalmente en FP32 = 4 bytes cada uno).
Full FT (mixed precision bf16 + AdamW fp32 states):
por parámetro ≈ 2 (peso bf16) + 2 (grad bf16) + 4 (m) + 4 (v) ≈ 12-16 bytes
→ un modelo de 7B en full FT ≈ 7e9 × 16 ≈ 112 GB ❌ (sin contar activaciones)
Por eso full fine-tuning en la 5090 solo es viable hasta ~1.5–3B.
LoRA/QLoRA cambian las reglas: congelas los pesos del base (no necesitan gradiente ni estados de optimizador) y solo entrenas los adaptadores (millones, no miles de millones, de params). En QLoRA además el base va en 4-bit:
QLoRA:
pesos base en 4-bit (0.5 B/param, congelados, sin optimizador)
+ adaptadores LoRA (pequeños) con sus gradientes y estados
+ activaciones (gradient checkpointing las reduce mucho)
→ un modelo de 14B en QLoRA cabe holgado en 32 GB
→ con Unsloth, MoE 30B-A3B cabe en 17.5 GB
Conclusión operativa de training:
| Técnica | Techo en 32 GB |
|---|---|
| Full fine-tuning (bf16 + AdamW) | 1.5–3B |
| LoRA (base bf16) | 7–13B |
| QLoRA (base 4-bit) | ~14B con calidad; hasta ~40B agresivo |
| QLoRA MoE (Unsloth) | 30B-A3B en 17.5 GB |
| GRPO/RLVR (Unsloth + vLLM colocate) | Qwen3-4B cómodo, 8B LoRA, 30B-A3B QLoRA |
Por qué QLoRA es la palanca del curso: te deja tocar modelos 10× mayores de lo que permitiría full FT, a cambio de entrenar solo adaptadores. En el Nivel 2 verás que la pérdida de calidad frente a full FT es pequeña para la mayoría de tareas — y lo medirás tú mismo.
3.4Las dos técnicas que estiran la VRAM (las usarás siempre)
- Gradient checkpointing: en lugar de guardar todas las activaciones del forward para el backward, guardas solo algunas y recomputas el resto. Cambia memoria por cómputo (~30% más lento, pero permite secuencias/batches mucho mayores). Unsloth tiene una versión optimizada (
use_gradient_checkpointing="unsloth"). - Paged optimizers (QLoRA): los estados del optimizador se paginan a RAM cuando hay picos, evitando OOM. Lo activa bitsandbytes automáticamente con
optim="paged_adamw_8bit".
3.5Laboratorio L3.1 — Calculadora de VRAM
Una herramienta que usarás todo el curso para decidir local vs cloud:
1# lab_l3_vram.py — estima la VRAM de un modelo en inferencia y training
2def bytes_per_param(precision: str) -> float:
3 return {"fp32": 4, "fp16": 2, "bf16": 2, "fp8": 1, "int4": 0.5, "nf4": 0.5}[precision]
4
5def kv_cache_gb(n_layers, n_kv_heads, head_dim, seq_len, batch, kv_dtype="fp16"):
6 b = bytes_per_param(kv_dtype)
7 total = 2 * n_layers * n_kv_heads * head_dim * seq_len * batch * b
8 return total / 1e9
9
10def inference_vram_gb(n_params_b, precision="fp16", kv_gb=0.0, overhead_gb=1.5):
11 weights = n_params_b * 1e9 * bytes_per_param(precision) / 1e9
12 return weights + kv_gb + overhead_gb
13
14def training_vram_gb(n_params_b, mode="qlora", overhead_gb=2.0, activations_gb=4.0):
15 p = n_params_b * 1e9
16 if mode == "full": # peso+grad bf16 + m,v fp32
17 mem = p * (2 + 2 + 4 + 4)
18 elif mode == "lora": # base bf16 congelado, adaptadores ~1% entrenables
19 trainable = p * 0.01
20 mem = p * 2 + trainable * (2 + 4 + 4)
21 elif mode == "qlora": # base 4-bit congelado, adaptadores ~1%
22 trainable = p * 0.01
23 mem = p * 0.5 + trainable * (2 + 4 + 4)
24 return mem / 1e9 + overhead_gb + activations_gb
25
26if __name__ == "__main__":
27 # Qwen3-14B, inferencia FP16, 8K ctx (n_layers=40, kv_heads=8, head_dim=128)
28 kv = kv_cache_gb(40, 8, 128, 8192, 1)
29 print(f"Inf 14B fp16 @8K: {inference_vram_gb(14, 'fp16', kv):.1f} GB")
30 print(f"Inf 14B int4 @8K: {inference_vram_gb(14, 'int4', kv):.1f} GB")
31 for m in ("full", "lora", "qlora"):
32 print(f"Train 7B {m:6}: {training_vram_gb(7, m):.1f} GB")
33 print(f"Train 14B {m:6}: {training_vram_gb(14, m):.1f} GB")Líneas no triviales:
- En
trainingmodefull, el(2+2+4+4)son los cuatro cubos por parámetro (peso bf16, grad bf16, momentum fp32, varianza fp32). Es la fórmula que explica el "×8". - En
lora/qlorasolo lostrainable(≈1%) pagan gradiente y optimizador; el base solo ocupa sus pesos. activations_gbes una estimación; con gradient checkpointing baja mucho y depende de seq_len/batch.
Corre el script y verifica: 14B full FT da ~150 GB (imposible), 14B QLoRA da ~12 GB (cabe de sobra). Esa es la intuición que quieres tener internalizada.
3.6La frontera local↔cloud
Manda a cloud (Hetzner / CloudRift / Spheron spot) sin dudar cuando:
- Inferencia/fine-tune de modelos densos >30B.
- Full fine-tuning de >3B.
- Pretraining serio (>1B params, >100B tokens).
- RL multi-node o eval a gran escala.
Quédate local cuando: itera <24 h, ≤30B, prototipado, eval pequeña, QLoRA/GRPO de hasta ~14–30B-A3B. Nunca alquiles >$1/hr para exploración — la 5090 ya la pagaste.
3.7Ejercicios
E1. Usa la calculadora: ¿cabe un fine-tune QLoRA de Qwen3-32B en 32 GB? ¿Y LoRA (base bf16)? ¿Y full FT de un 3B?
Solución
QLoRA 32B: pesos 4-bit ≈16 GB + adaptadores + overhead+act ≈ ~22-24 GB → cabe tight (Unsloth lo hace con MoE; denso 32B es ajustado pero posible con seq corto). LoRA 32B (base bf16) ≈ 64 GB base → ❌. Full FT 3B ≈ 3e9×16/1e9 + extras ≈ ~54 GB → ❌ en 32 GB; baja a ~1.5B o usa LoRA.E2. Quieres servir gpt-oss-120B (MoE) en tu 5090. ¿Por qué cabe en 4-bit pero un 70B denso en 4-bit no?