El modelo no cabe y ese es todo el problema
Quieres servir un modelo de 13.000 millones de parámetros en una GPU que tienes a
mano y el proceso muere con un CUDA out of memory antes de responder una sola
petición. La reacción típica es alquilar hierro más grande. La correcta, casi
siempre, es preguntarse cuántos bits necesita de verdad cada peso del modelo.
Esa pregunta es la cuantización: guardar los números del modelo con menos precisión para que ocupen menos memoria y se muevan más rápido. Suena a truco sucio y a veces lo es, pero bien hecho es la diferencia entre que un modelo quepa en tu tarjeta o no quepa. Vale la pena entender qué se gana, qué se pierde y dónde está la línea que no conviene cruzar.
Cuánto pesa un peso
Por defecto, los pesos de un LLM se guardan en 16 bits (FP16 o BF16): dos bytes por parámetro. La cuenta de memoria mínima para cargar el modelo es directa: número de parámetros por bytes por parámetro.
Un modelo de 7B en FP16 ocupa unos 14 GB solo en pesos, antes de sumar la caché de atención y los búferes de trabajo. Cuantizarlo a 8 bits lo baja a la mitad; a 4 bits, a la cuarta parte. Ese factor es lo que convierte un modelo “imposible” en uno que arranca.
| Precisión | Bytes/parám. | 7B (solo pesos) | 70B (solo pesos) |
|---|---|---|---|
| FP16 / BF16 | 2 | ~14 GB | ~140 GB |
| INT8 | 1 | ~7 GB | ~70 GB |
| INT4 | 0,5 | ~3,5 GB | ~35 GB |
La lectura práctica de la última fila: un modelo de 70B en FP16 necesita varias GPU de centro de datos; en 4 bits entra en una sola tarjeta de 48 GB con sitio de sobra para la caché. No es una optimización menor, es un cambio de categoría de hardware.
Qué se pierde al recortar bits
La cuantización no es gratis: al redondear cada peso a menos niveles posibles, introduces un error. La pregunta es cuánto duele ese error en la calidad de las respuestas, y la respuesta depende de a cuántos bits bajes.
De 16 a 8 bits, la pérdida es casi imperceptible con métodos modernos: hablamos de diferencias que apenas se notan en las evaluaciones. De 8 a 4 bits, aparece una degradación pequeña pero real, muy asumible si usas un buen algoritmo. Por debajo de 4 bits, el modelo empieza a desmoronarse rápido: alucina más, pierde coherencia en cadenas largas y se rompe en tareas que exigen precisión. Cuatro bits es, hoy, el punto dulce donde el ahorro sigue mereciendo la pena.
Y aquí está la clave que mucha gente ignora: no todos los métodos de cuantización a 4 bits son iguales. Redondear a lo bruto destroza el modelo; redondear con criterio lo conserva.
Redondear con criterio, no a lo bruto
La diferencia entre una cuantización que funciona y una que arruina el modelo está en cómo se decide el redondeo. Los métodos serios no aplican la misma regla a todos los pesos por igual.
Trabajan por bloques: agrupan los pesos, calculan una escala propia para cada grupo y así los valores grandes de una zona no aplastan a los pequeños de otra. Los mejores además miran datos reales para detectar qué pesos importan más y los protegen de la pérdida de precisión. Por eso nombres como GPTQ, AWQ o la cuantización NF4 dan resultados muy por encima de un redondeo ingenuo: no reparten el error a ciegas, lo colocan donde menos molesta.
Para ti, en la práctica, esto se traduce en elegir el formato adecuado según dónde vayas a servir:
| Formato | Dónde brilla | Nota |
|---|---|---|
| bitsandbytes (NF4) | Cargar en GPU desde Transformers, sin paso previo | Cómodo para prototipar; cuantiza al vuelo |
| GPTQ / AWQ | Servir en GPU con throughput alto | Cuantización previa; excelente calidad a 4 bits |
| GGUF (llama.cpp) | CPU, portátiles, Apple Silicon | El estándar para correr modelos en local |
Hacerlo de verdad: cargar un modelo en 4 bits
La vía más rápida para probarlo es cuantizar al vuelo al cargar el modelo. Con Transformers y bitsandbytes son unas pocas líneas, y el modelo pasa de no caber a caber:
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
# NF4: cuantización a 4 bits pensada para pesos de redes neuronales.
# Con doble cuantización recortamos todavía un poco más de memoria.
config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True,
# El cálculo se hace en bf16 aunque los pesos vivan en 4 bits:
# se decodifican al vuelo para cada operación.
bnb_4bit_compute_dtype=torch.bfloat16,
)
modelo = "meta-llama/Llama-3.1-8B-Instruct"
tok = AutoTokenizer.from_pretrained(modelo)
llm = AutoModelForCausalLM.from_pretrained(
modelo,
quantization_config=config,
device_map="auto", # reparte capas entre GPU y CPU si hace falta
)
# Cuánta memoria ocupa de verdad en la GPU tras cuantizar
print(f"{llm.get_memory_footprint() / 1e9:.1f} GB en la tarjeta")
Ese bnb_4bit_compute_dtype esconde un detalle importante: los pesos se guardan en
4 bits, pero cada multiplicación se hace en 16 bits, decodificando el peso justo
antes de usarlo. Ahorras memoria de almacenamiento, no de cálculo. Por eso a veces
la inferencia no va más rápida de lo que esperabas: has resuelto el problema de que
el modelo quepa, que no es lo mismo que el problema de que vuele.
La caché que también hay que vigilar
Cargar el modelo es media película. La otra media es la caché de atención (la KV cache), que guarda el estado de cada token ya procesado y crece con la longitud del contexto. En conversaciones largas o con muchas peticiones a la vez, esa caché puede acabar comiendo más memoria que los propios pesos.
La buena noticia es que también se puede cuantizar, normalmente a 8 bits, sin apenas coste de calidad. Si vas a servir contextos largos, cuantizar la KV cache suele darte más margen real que apurar un bit más en los pesos. Es el ahorro que casi nadie mira y el que más sitio libera cuando el tráfico aprieta.
Lo que me llevo al día a día
La cuantización no es un truco para salir del paso, es una decisión de diseño que tomas antes de elegir hardware. La regla que uso: empieza en 4 bits con un método serio (AWQ o GPTQ para GPU, GGUF para local), mide la calidad en tus tareas reales —no en un benchmark genérico— y solo sube a 8 bits si notas que la degradación te duele. Bajar de 4 bits casi nunca compensa.
Y la trampa que conviene recordar: cuantizar resuelve que el modelo quepa y libera memoria, pero no siempre lo acelera, porque el cálculo sigue haciéndose en alta precisión. Si tu problema es que no cabe, la cuantización es la respuesta. Si tu problema es la latencia, es solo una pieza, y tendrás que mirar también el motor de inferencia, el batching y la caché. Saber cuál de los dos problemas tienes es la mitad del trabajo.