Cuantización agresiva (estado del arte): del 4-bit al ternario

Este post es la continuación directa de Quantization para inferencia LLM, que cubre el régimen “resuelto” (FP8, INT4 con GPTQ/AWQ). Léelo primero: aquí asumo la matemática del scale+zero-point, qué hacen GPTQ y AWQ, y la distinción PTQ/QAT. Lo que añadimos es la frontera sub-4-bit, donde la cuantización post-hoc escalar deja de funcionar y hay que cambiar de herramienta.

TL;DR

Hay una línea divisoria nítida alrededor de los 4 bits. Por encima, cuantizar es un problema resuelto: INT8 es indistinguible de BF16, e INT4 con un método bueno (AWQ, GPTQ) pierde 1-2 puntos de MMLU y poco más. El método sigue siendo el mismo de siempre —tomar cada peso, escalarlo, redondearlo a un entero corto— y funciona. Por debajo de 4 bits, ese método colapsa: a 2 bits la cuantización escalar ingenua puede duplicar la perplexity. La razón es geométrica —cada peso tiene solo 4 valores posibles, el error de redondeo deja de ser despreciable— y la salida no es “redondear mejor”, es cambiar de representación. Los métodos SOTA de 2 bits (AQLM, QuIP#, QTIP) dejan de cuantizar pesos individuales y cuantizan vectores de pesos contra diccionarios (códigos), y “blanquean” la matriz de pesos para repartir su energía y aplastar outliers (incoherence processing). El ternario es otra cosa todavía: BitNet b1.58, con pesos en {-1, 0, +1} (~1.58 bits), no es PTQ —es un modelo entrenado nativamente con esa restricción— y cambia la aritmética de la matmul de multiplicaciones a sumas/restas, tocando a la vez el techo de cómputo y el de memoria. La regla mental: ≥4-bit comprimes la foto; <4-bit tienes que repintarla.

La analogía: el JPEG que ya no se puede comprimir más

En el post de quantization usamos el JPEG con detector de bordes para explicar INT4. Aquí la analogía sigue, pero hay que llevarla hasta su límite.

Un JPEG con factor de calidad 90 es indistinguible del original. A calidad 60 ya se nota un poco, pero sigue siendo “la misma foto”. A calidad 30 aparecen los bloques 8×8, los halos alrededor de los bordes, el banding en los degradados. A calidad 10 la imagen está destruida: reconoces que había una cara, pero los detalles han desaparecido bajo los artefactos. Y aquí está la clave: no existe ningún encoder JPEG que comprima a calidad 10 sin esos artefactos, porque el algoritmo JPEG (DCT por bloques + cuantización de coeficientes) tiene un suelo de información por debajo del cual su propio mecanismo introduce el ruido.

¿Qué haces si necesitas la foto a ese tamaño de archivo y que se siga viendo bien? No comprimes más la original. Repintas la foto sabiendo de antemano que va a vivir comprimida: un ilustrador la redibuja con líneas limpias, paleta reducida, cero degradados sutiles —una imagen diseñada para sobrevivir a la compresión brutal—. El resultado a “10 KB” se ve infinitamente mejor que el JPEG original aplastado a 10 KB, porque no es el mismo proceso: uno destruye información existente, el otro genera información nueva ya adaptada a la restricción.

Esa es exactamente la frontera de este post:

  • PTQ escalar (≥4-bit) = comprimir el JPEG. Hasta cierto ratio, sigue siendo la misma foto.
  • PTQ vectorial SOTA (2-bit: AQLM, QuIP#, QTIP) = un códec de imagen mucho más sofisticado (diccionarios, transformadas que decorrelacionan) que estira el ratio comprimible un poco más antes del colapso.
  • Ternario nativo (BitNet b1.58) = repintar la foto. No comprimes un modelo BF16 existente; entrenas uno nuevo que nace ternario.

El mapa de la frontera, bit a bit

Cuantizar un modelo es decidir cuántos valores distintos puede tomar cada peso. Con b bits por peso hay 2^b valores posibles. La pregunta central es: ¿a partir de qué b el número de valores es tan pequeño que el redondeo destruye el modelo?

BitsValores/pesoEstado del arteMétodo necesarioPérdida típica vs BF16
8256ResueltoRTN, SmoothQuant, FP8~0 (indistinguible)
416ResueltoAWQ, GPTQ1-2 pp MMLU, +0.1-0.3 PPL
38Degradación pequeñaGPTQ/AWQ tuneado, GGUF Q3_K3-5 pp MMLU
24Serio salvo SOTAAQLM, QuIP#, QTIP (no escalar)escalar: colapso; SOTA: 4-8 pp
1.583 (ternario)Solo nativoBitNet b1.58 (QAT/entrenamiento nativo)n/a (no es PTQ)
12 (binario)Investigaciónnativo, claims dudososgrande / sin metodología clara

Las tres transiciones que importan:

8 → 4 bits: nada se rompe. Con 16 niveles por peso y un scale por bloque de 128, el error de redondeo es pequeño relativo a la dinámica de los pesos. GPTQ compensa el error propagándolo a los pesos vecinos; AWQ protege el ~1 % de canales salientes. El modelo casi no lo nota. Esto está en el post anterior.

4 → 2 bits: el codo. Aquí pasan dos cosas a la vez. Primero, con solo 4 niveles, el cuantizador escalar ya no puede representar la distribución de pesos —que es aproximadamente gaussiana con colas largas— sin un error de redondeo enorme en proporción. Segundo, y más sutil: el error de cuantización deja de ser “ruido pequeño que el modelo absorbe” y se vuelve estructurado, sesgando sistemáticamente las activaciones. La PTQ escalar ingenua a 2 bits sobre un Llama 8B típicamente duplica la perplexity o más. Es el codo de la curva.

2 → 1.58 bits: cambio de naturaleza. No se cruza con un método de compresión mejor. Se cruza entrenando el modelo desde el principio con la restricción. Es una discontinuidad: a la izquierda estás haciendo PTQ, a la derecha estás haciendo entrenamiento.

Por qué la PTQ escalar colapsa por debajo de 4 bits

El cuantizador escalar tiene una limitación de fondo: cuantiza cada peso por separado, ignorando que los pesos de una fila/columna están correlacionados y que el error de uno se podría compensar con otro. A 4 bits esto importa poco; a 2 bits es letal. Hay tres ataques posibles, y los métodos SOTA usan los tres.

1. Cuantización vectorial: diccionarios en lugar de escalas

En lugar de mapear cada peso a uno de 4 valores, agrupa los pesos en vectores (p. ej. de 8 pesos) y mapea cada vector al entrada más cercana de un diccionario (codebook) aprendido. Si el diccionario tiene 256 entradas, codificar un vector de 8 pesos cuesta 8 bits (el índice) → 1 bit/peso, pero cada “valor reconstruido” es un punto en un espacio de 8 dimensiones elegido para minimizar el error sobre la distribución real de pesos.

La ventaja es de teoría de la información: un diccionario de vectores puede colocar sus puntos de reconstrucción donde realmente están los pesos (en racimos), mientras que el cuantizador escalar está obligado a poner sus 4 niveles en una rejilla regular, gastando resolución en zonas vacías. Es la diferencia entre un mapa de carreteras con cuadrícula uniforme y uno que pone más detalle donde hay ciudades.

AQLM (Additive Quantization of Language Models, arXiv:2401.06118) lleva esto al extremo con cuantización aditiva: cada vector de pesos se reconstruye como suma de varios códigos de varios diccionarios (multi-codebook). Es más expresivo que un solo diccionario porque el número de combinaciones es el producto de los tamaños, no la suma. AQLM fue uno de los primeros métodos en hacer 2-bit “usable” (no colapsado) en modelos grandes, a costa de un proceso de calibración caro y kernels de inferencia especializados.

2. Incoherence processing: blanquear la matriz

El segundo ataque es contra los outliers. Las matrices de pesos de un transformer tienen unas pocas entradas (y unos pocos canales) con magnitud mucho mayor que el resto. Esos outliers dominan el rango del cuantizador: si tienes que representar un peso de magnitud 8 y el resto son de magnitud 0.5, tu scale se estira para cubrir el 8 y desperdicias casi toda la resolución.

Incoherence processing (la idea central de QuIP y QuIP#) ataca esto multiplicando la matriz de pesos W por matrices ortogonales aleatorias por la izquierda y la derecha: W' = U W V^T. Como U y V son ortogonales, la operación es invertible y la matemática del producto se puede deshacer en inferencia absorbiéndola en las capas vecinas (igual que AWQ absorbe sus escalas). Pero la rotación reparte la energía: una matriz “incoherente” tiene sus valores repartidos de forma casi uniforme, sin outliers concentrados, porque mezclar coordenadas con una rotación aleatoria aplana la distribución (es, en esencia, el teorema central del límite actuando sobre combinaciones lineales). Una matriz sin outliers se cuantiza muchísimo mejor a 2 bits. Es el equivalente a “blanquear” una señal antes de digitalizarla.

QuIP# (arXiv:2402.04396) combina incoherence processing con códigos reticulares E8: en vez de un diccionario arbitrario, usa el retículo E8 (un empaquetamiento de esferas óptimo en 8 dimensiones, el mejor conocido). Cuantizar vectores de 8 pesos contra el retículo E8 da el menor error de reconstrucción posible para una densidad de bits dada, porque E8 es literalmente la forma más eficiente de colocar puntos en 8D. Es teoría de codificación clásica aplicada a pesos de LLM.

3. Codificación con memoria: trellis

QTIP (arXiv:2406.11235) añade el tercer ataque: trellis-coded quantization. En lugar de cuantizar cada vector de forma independiente, modela la secuencia de pesos como un camino a través de un trellis (la misma estructura de los códigos convolucionales de las telecomunicaciones) y elige la secuencia de códigos óptima con el algoritmo de Viterbi. La intuición: introducir memoria entre cuantizaciones sucesivas permite errores correlacionados que se cancelan, en vez de errores independientes que se acumulan. QTIP, sobre incoherence processing, mejora a QuIP# en calidad a 2-3 bits manteniendo kernels de inferencia rápidos.

La idea común a los tres: dejar de cuantizar escalares y empezar a cuantizar vectores con diccionarios, y decorrelacionar la matriz antes de hacerlo. Ninguno es “redondear mejor”; los tres cambian la representación de raíz. Por eso, por debajo de 4 bits, ya no basta con un flag en vLLM: hace falta co-diseño de método de cuantización + kernel de inferencia.

El ternario nativo: BitNet b1.58

Aquí cambiamos de continente. Todo lo anterior es PTQ: parte de un modelo BF16 entrenado y lo comprime. El ternario de BitNet no comprime nada.

BitNet b1.58 (arXiv:2402.17764) entrena un transformer desde cero donde cada peso está restringido a {-1, 0, +1} durante todo el entrenamiento. Tres valores ⇒ log₂(3) ≈ 1.58 bits/peso. La cuantización no es un paso posterior: las capas lineales (BitLinear) cuantizan sus pesos a ternario en el forward pass de cada step de entrenamiento, y los gradientes fluyen a través de un estimador straight-through. El modelo aprende a funcionar con pesos ternarios. Esto es QAT llevado al extremo: no un fine-tune corto de robustez, sino la restricción presente desde el primer token de entrenamiento.

Esa diferencia es la que esquiva el codo de la curva. La PTQ a 2 bits intenta encontrar la mejor aproximación ternaria/quaternaria de un modelo que se entrenó esperando precisión completa —y ese modelo tiene pesos “frágiles” que dependen de matices que 2 bits no capturan—. BitNet, en cambio, nunca tuvo esos matices: sus pesos nacieron ternarios, así que la red distribuyó su capacidad representacional de forma compatible con la restricción. Es repintar la foto en vez de comprimirla.

Lo que cambia no es solo la memoria, es la aritmética

El punto que más se subestima de BitNet: con pesos en {-1, 0, +1}, la multiplicación desaparece de la matmul. Multiplicar una activación x por un peso ternario w es trivial: si w = +1 sumas x, si w = -1 restas x, si w = 0 no haces nada. La operación dominante de un transformer —el producto matriz-vector— pasa de ser un mar de multiplica-acumula (MAC) en coma flotante a ser sumas y restas enteras.

Esto importa porque conecta con el roofline. Como se explica en El roofline invertido de los modelos pequeños, la inferencia LLM tiene dos techos: el de memoria (ancho de banda HBM para cargar pesos) y el de cómputo (FLOPs de las tensor cores). La cuantización normal (INT4, FP8) ataca solo el techo de memoria: el peso ocupa menos, pero para multiplicarlo lo descuantizas a FP16 y haces la misma multiplicación de siempre. El ternario ataca ambos techos a la vez: el peso ocupa 1.58 bits (memoria) y la operación es una suma en lugar de una multiplicación (cómputo). Por eso BitNet necesita kernels propios —bitnet.cpp— que ejecutan la matmul ternaria sin pasar nunca por FP16; un kernel que descuantizara a FP16 para multiplicar tiraría a la basura la mitad de la ventaja.

La contrapartida honesta: BitNet b1.58 es entrenamiento desde cero. No puedes “convertir tu Llama 8B a BitNet”. Si quieres ternario, entrenas (o usas) un modelo nativamente ternario, con todo lo que implica en coste de pre-entrenamiento y en disponibilidad de pesos. Hoy es una línea de investigación con modelos publicados a escalas modestas, no un drop-in para reemplazar tu serving actual.

QAT como puente entre PTQ y nativo

Entre “comprimir post-hoc” (PTQ) y “entrenar nativamente ternario” (BitNet) hay un punto intermedio: QAT (Quantization-Aware Training). Tomas un modelo ya entrenado y haces un fine-tune corto con las operaciones de cuantización dentro del bucle, para que aprenda a ser robusto a bits bajos sin pagar un pre-entrenamiento completo.

Gemma 3 publica variantes QAT oficiales precisamente para esto: modelos que, tras el fine-tune QAT, sostienen INT4 con una pérdida de calidad mucho menor que la PTQ pura sobre el mismo modelo. El coste es de entrenamiento (horas-días de GPU sobre un modelo ya existente), no de inferencia. Para INT4 con QAT recuperas casi toda la calidad; para 2-bit, QAT ayuda pero sigue siendo terreno difícil; para ternario, el QAT deja de ser “fine-tune corto” y se convierte en entrenamiento nativo (BitNet).

La jerarquía de decisión:

  • PTQ = default a ≥4 bits. Minutos-horas, sin tocar pesos de entrenamiento. Cubre el 90 % de producción.
  • QAT = cuando PTQ pierde demasiado y la diferencia importa. Bits bajos (2-3), o modelos sensibles. Pagas fine-tune.
  • Nativo (ternario) = cuando quieres bajar de 2 bits y cambiar la aritmética. Pagas pre-entrenamiento. Solo tiene sentido si controlas el modelo desde su creación.

Las matemáticas que importan: footprint y cuántos caben

El footprint de los pesos es directo: bytes = (bits/param / 8) × N, con N el número de parámetros. Para un modelo de 8B:

Nivelbits/paramFootprint 8BRatio vs BF16
BF161616.0 GB1.0×
INT888.0 GB2.0×
INT444.0 GB4.0×
3-bit33.0 GB5.3×
2-bit22.0 GB8.0×
1.58-bit (ternario)~1.58~1.6 GB~10×

(El ternario real ocupa algo más de 1.58 bits/param porque hay que empaquetar 5 valores ternarios en 8 bits —5 × log₂(3) ≈ 7.92 bits— y porque las normas y embeddings suelen quedarse en más precisión. La cifra de ~1.6 GB para 8B es el orden de magnitud correcto.)

¿Cuántos modelos de 8B caben en una RTX 4090?

Una RTX 4090 (24 GB, Ada Lovelace) tiene 24 GB. Reservamos ~4 GB para KV cache y activaciones, dejando 20 GB para pesos. Cuántos modelos de 8B distintos caben cargados simultáneamente:

NivelFootprint 8BModelos en 20 GBComentario
BF1616.0 GB1uno y queda margen escaso
INT88.0 GB2dos modelos distintos
INT44.0 GB5régimen resuelto; calidad ~lossless con AWQ
3-bit3.0 GB6degradación pequeña ya visible
2-bit2.0 GB10solo viable con AQLM/QuIP#/QTIP
1.58-bit~1.6 GB~12solo modelos nativamente ternarios

La cuenta es seductora —de 1 a 12 modelos en la misma tarjeta— pero hay que leerla con escepticismo. Saltar de INT4 (5 modelos, casi sin pérdida) a 2-bit (10 modelos) duplica la capacidad, pero solo si usas un método SOTA y aceptas 4-8 puntos de MMLU. Y el salto de 2-bit a ternario (10 → 12) es marginal en memoria: el ternario no se justifica por footprint frente a un 2-bit SOTA, se justifica por la aritmética (el techo de cómputo) y porque evita el codo de calidad al ser nativo. Si tu única métrica es “cuántos GB ocupa”, el 2-bit SOTA ya te da casi todo. El ternario es para cuando además quieres el ahorro de cómputo.

La curva conceptual: perplexity vs bits

Perplexity vs bits por peso (conceptual): el codo y la rama nativabits por peso (eje invertido: más comprimido a la derecha)perplexity (peor arriba)1684321.58frontera 4-bitPTQ escalar ingenuacolapsa <3 bitsPTQ SOTA vectorialAQLM / QuIP# / QTIPternario nativoBitNet b1.58 (no PTQ)≈ plano ≥ 4 bits con buen método

Tres lecturas de la curva. Uno: a la derecha de 4 bits, las tres ramas están casi pegadas y casi planas —el régimen resuelto—. Dos: la rama roja (PTQ escalar ingenua) tiene un codo brutal entre 3 y 2 bits; ahí es donde duplica la perplexity. La rama azul (PTQ SOTA vectorial) aplana ese codo —no lo elimina, pero lo hace tolerable hasta 2 bits—. Tres: el punto verde del ternario nativo no está en ninguna de las dos curvas de PTQ, porque no se obtiene comprimiendo: se obtiene entrenando, y por eso puede caer por debajo del codo sin pagar el precio de calidad que paga cualquier PTQ a esa densidad de bits. Es la diferencia entre el JPEG aplastado y la foto repintada.

Escepticismo obligatorio: el 1-bit “sin pérdida” y los benchmarks sin metodología

Tres alertas para leer la literatura de cuantización agresiva:

“1-bit sin pérdida” casi siempre tiene letra pequeña. El binario puro {-1, +1} (1 bit) pierde la capacidad de representar el cero, que en transformers es importante (muchos pesos efectivamente nulos). Por eso el verdadero estado del arte de baja densidad es ternario (1.58 bits), no binario: el cero vale su 0.58 de bit extra. Cuando un paper anuncia “1-bit”, conviene mirar si (a) es realmente 1 bit o 1.58 redondeado hacia abajo en el titular, (b) “sin pérdida” se mide en perplexity de WikiText (fácil) o en benchmarks de razonamiento (donde el colapso aparece), y (c) compara contra un baseline del mismo tamaño efectivo o contra un modelo mucho mayor para inflar la ventaja.

Perplexity plana ≠ calidad preservada. La perplexity en un corpus genérico es la métrica más indulgente con la cuantización agresiva. Un modelo 2-bit puede tener perplexity casi idéntica al BF16 y a la vez caer 10 puntos en GSM8K o en un benchmark de código, porque el razonamiento multi-paso amplifica errores que la perplexity media no ve. Desconfía de cualquier claim sub-4-bit que solo reporte perplexity. Como ya dijimos en el post de quantization, la pérdida hay que medirla en la tarea de destino.

Comparabilidad de hardware. Los números de “X veces más rápido” del ternario solo aplican con los kernels especializados (bitnet.cpp) y en el hardware donde la aritmética suma/resta gana de verdad. En una GPU con tensor cores diseñadas para FP16/FP8, un kernel ternario ingenuo puede ser más lento que INT4 bien optimizado, porque desaprovecha el silicio. La ventaja del ternario es real, pero es una ventaja de co-diseño (modelo + kernel + a veces hardware), no un flag que activas sobre tu stack actual. Cualquier benchmark que no especifique el kernel y el hardware exacto es ruido.

Implicaciones para inferencia on-premise

En la RTX 4090 (24 GB, Ada Lovelace): el régimen práctico hoy sigue siendo INT4 AWQ para modelos de 7-14B —resuelto, casi lossless, soportado nativamente—. El 2-bit SOTA (AQLM/QuIP#/QTIP) es viable y permite cargar modelos más grandes o más modelos a la vez, pero exige los kernels específicos de cada método y una calibración cara, y paga calidad. Tiene sentido cuando el cuello es la VRAM y aceptas el trade-off; no como default. El ternario en 4090 es experimental: sin tensor cores diseñadas para suma/resta ternaria, la ventaja de cómputo se diluye, aunque el ahorro de memoria se mantiene.

En un cluster genérico 4×H100 SXM (320 GB, NVLink, FP8 nativo): aquí el default es FP8 (calidad casi indistinguible, throughput nativo) o INT4 AWQ para modelos que no caben en FP8. El sub-4-bit SOTA es para servir modelos enormes (200B+) cuando ni FP8 ni INT4 caben con el margen de KV cache que quieres, a costa de calidad y de complejidad de kernel. El ternario nativo, hoy, es objeto de investigación más que de producción: su promesa —tocar ambos techos del roofline— es mayor en CPU/edge (donde no hay tensor cores FP8 que aprovechar) que en un cluster H100, que ya tiene hardware FP8 dedicado.

La regla de pulgar, junio 2026: ≥4-bit es ingeniería resuelta; 2-bit SOTA es una palanca real pero con coste de método y de calidad; ternario es una apuesta de arquitectura, no un ajuste de despliegue.

Ver también

Referencias