<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Cuantizacion on lo0 — Blog Técnico</title><link>https://blog.lo0.es/tags/cuantizacion/</link><description>Recent content in Cuantizacion on lo0 — Blog Técnico</description><generator>Hugo -- gohugo.io</generator><language>es</language><lastBuildDate>Tue, 09 Jun 2026 02:10:00 +0000</lastBuildDate><atom:link href="https://blog.lo0.es/tags/cuantizacion/index.xml" rel="self" type="application/rss+xml"/><item><title>Cuantización agresiva (estado del arte): del 4-bit al ternario</title><link>https://blog.lo0.es/posts/cuantizacion-agresiva-sub-4-bit-ternario/</link><pubDate>Tue, 09 Jun 2026 02:10:00 +0000</pubDate><guid>https://blog.lo0.es/posts/cuantizacion-agresiva-sub-4-bit-ternario/</guid><description>&lt;blockquote>
&lt;p>Este post es la continuación directa de &lt;a href="https://blog.lo0.es/posts/quantization-fundamentos-inferencia/">Quantization para inferencia LLM&lt;/a>, que cubre el régimen &amp;ldquo;resuelto&amp;rdquo; (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 &lt;strong>frontera sub-4-bit&lt;/strong>, donde la cuantización post-hoc escalar deja de funcionar y hay que cambiar de herramienta.&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>Hay una línea divisoria nítida alrededor de los 4 bits. Por encima, cuantizar es un problema &lt;strong>resuelto&lt;/strong>: 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 &lt;strong>colapsa&lt;/strong>: 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 &amp;ldquo;redondear mejor&amp;rdquo;, es &lt;strong>cambiar de representación&lt;/strong>. Los métodos SOTA de 2 bits (AQLM, QuIP#, QTIP) dejan de cuantizar pesos individuales y cuantizan &lt;strong>vectores&lt;/strong> de pesos contra diccionarios (códigos), y &amp;ldquo;blanquean&amp;rdquo; 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), &lt;strong>no es PTQ&lt;/strong> —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; &amp;lt;4-bit tienes que repintarla.&lt;/p>
&lt;h2 id="la-analogía-el-jpeg-que-ya-no-se-puede-comprimir-más">La analogía: el JPEG que ya no se puede comprimir más&lt;/h2>
&lt;p>En &lt;a href="https://blog.lo0.es/posts/quantization-fundamentos-inferencia/">el post de quantization&lt;/a> usamos el JPEG con detector de bordes para explicar INT4. Aquí la analogía sigue, pero hay que llevarla hasta su límite.&lt;/p>
&lt;p>Un JPEG con factor de calidad 90 es indistinguible del original. A calidad 60 ya se nota un poco, pero sigue siendo &amp;ldquo;la misma foto&amp;rdquo;. 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 &lt;strong>había&lt;/strong> una cara, pero los detalles han desaparecido bajo los artefactos. Y aquí está la clave: &lt;strong>no existe ningún encoder JPEG que comprima a calidad 10 sin esos artefactos&lt;/strong>, 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.&lt;/p>
&lt;p>¿Qué haces si necesitas la foto a ese tamaño de archivo y que se siga viendo bien? No comprimes más la original. &lt;strong>Repintas la foto sabiendo de antemano que va a vivir comprimida&lt;/strong>: 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 &amp;ldquo;10 KB&amp;rdquo; 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.&lt;/p>
&lt;p>Esa es exactamente la frontera de este post:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>PTQ escalar (≥4-bit)&lt;/strong> = comprimir el JPEG. Hasta cierto ratio, sigue siendo la misma foto.&lt;/li>
&lt;li>&lt;strong>PTQ vectorial SOTA (2-bit: AQLM, QuIP#, QTIP)&lt;/strong> = 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.&lt;/li>
&lt;li>&lt;strong>Ternario nativo (BitNet b1.58)&lt;/strong> = repintar la foto. No comprimes un modelo BF16 existente; entrenas uno nuevo que nace ternario.&lt;/li>
&lt;/ul>
&lt;h2 id="el-mapa-de-la-frontera-bit-a-bit">El mapa de la frontera, bit a bit&lt;/h2>
&lt;p>Cuantizar un modelo es decidir cuántos valores distintos puede tomar cada peso. Con &lt;code>b&lt;/code> bits por peso hay &lt;code>2^b&lt;/code> valores posibles. La pregunta central es: ¿a partir de qué &lt;code>b&lt;/code> el número de valores es tan pequeño que el redondeo destruye el modelo?&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Bits&lt;/th>
&lt;th>Valores/peso&lt;/th>
&lt;th>Estado del arte&lt;/th>
&lt;th>Método necesario&lt;/th>
&lt;th>Pérdida típica vs BF16&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>8&lt;/td>
&lt;td>256&lt;/td>
&lt;td>&lt;strong>Resuelto&lt;/strong>&lt;/td>
&lt;td>RTN, SmoothQuant, FP8&lt;/td>
&lt;td>~0 (indistinguible)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>4&lt;/td>
&lt;td>16&lt;/td>
&lt;td>&lt;strong>Resuelto&lt;/strong>&lt;/td>
&lt;td>AWQ, GPTQ&lt;/td>
&lt;td>1-2 pp MMLU, +0.1-0.3 PPL&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3&lt;/td>
&lt;td>8&lt;/td>
&lt;td>Degradación pequeña&lt;/td>
&lt;td>GPTQ/AWQ tuneado, GGUF Q3_K&lt;/td>
&lt;td>3-5 pp MMLU&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>2&lt;/td>
&lt;td>4&lt;/td>
&lt;td>Serio salvo SOTA&lt;/td>
&lt;td>&lt;strong>AQLM, QuIP#, QTIP&lt;/strong> (no escalar)&lt;/td>
&lt;td>escalar: colapso; SOTA: 4-8 pp&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1.58&lt;/td>
&lt;td>3 (ternario)&lt;/td>
&lt;td>Solo nativo&lt;/td>
&lt;td>&lt;strong>BitNet b1.58&lt;/strong> (QAT/entrenamiento nativo)&lt;/td>
&lt;td>n/a (no es PTQ)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>2 (binario)&lt;/td>
&lt;td>Investigación&lt;/td>
&lt;td>nativo, claims dudosos&lt;/td>
&lt;td>grande / sin metodología clara&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Las tres transiciones que importan:&lt;/p>
&lt;p>&lt;strong>8 → 4 bits: nada se rompe.&lt;/strong> 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.&lt;/p>
&lt;p>&lt;strong>4 → 2 bits: el codo.&lt;/strong> 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 &amp;ldquo;ruido pequeño que el modelo absorbe&amp;rdquo; y se vuelve &lt;strong>estructurado&lt;/strong>, sesgando sistemáticamente las activaciones. La PTQ escalar ingenua a 2 bits sobre un Llama 8B típicamente &lt;strong>duplica la perplexity o más&lt;/strong>. Es el codo de la curva.&lt;/p>
&lt;p>&lt;strong>2 → 1.58 bits: cambio de naturaleza.&lt;/strong> 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.&lt;/p>
&lt;h2 id="por-qué-la-ptq-escalar-colapsa-por-debajo-de-4-bits">Por qué la PTQ escalar colapsa por debajo de 4 bits&lt;/h2>
&lt;p>El cuantizador escalar tiene una limitación de fondo: cuantiza &lt;strong>cada peso por separado&lt;/strong>, 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.&lt;/p>
&lt;h3 id="1-cuantización-vectorial-diccionarios-en-lugar-de-escalas">1. Cuantización vectorial: diccionarios en lugar de escalas&lt;/h3>
&lt;p>En lugar de mapear cada peso a uno de 4 valores, agrupa los pesos en &lt;strong>vectores&lt;/strong> (p. ej. de 8 pesos) y mapea cada vector al entrada más cercana de un &lt;strong>diccionario&lt;/strong> (codebook) aprendido. Si el diccionario tiene 256 entradas, codificar un vector de 8 pesos cuesta 8 bits (el índice) → 1 bit/peso, pero cada &amp;ldquo;valor reconstruido&amp;rdquo; es un punto en un espacio de 8 dimensiones elegido para minimizar el error sobre la distribución real de pesos.&lt;/p>
&lt;p>La ventaja es de teoría de la información: un diccionario de vectores puede colocar sus puntos de reconstrucción donde &lt;strong>realmente&lt;/strong> 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.&lt;/p>
&lt;p>&lt;strong>AQLM&lt;/strong> (Additive Quantization of Language Models, arXiv:2401.06118) lleva esto al extremo con &lt;strong>cuantización aditiva&lt;/strong>: cada vector de pesos se reconstruye como &lt;strong>suma de varios códigos&lt;/strong> 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 &amp;ldquo;usable&amp;rdquo; (no colapsado) en modelos grandes, a costa de un proceso de calibración caro y kernels de inferencia especializados.&lt;/p>
&lt;h3 id="2-incoherence-processing-blanquear-la-matriz">2. Incoherence processing: blanquear la matriz&lt;/h3>
&lt;p>El segundo ataque es contra los &lt;strong>outliers&lt;/strong>. 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.&lt;/p>
&lt;p>&lt;strong>Incoherence processing&lt;/strong> (la idea central de QuIP y QuIP#) ataca esto multiplicando la matriz de pesos &lt;code>W&lt;/code> por matrices ortogonales aleatorias por la izquierda y la derecha: &lt;code>W' = U W V^T&lt;/code>. Como &lt;code>U&lt;/code> y &lt;code>V&lt;/code> 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 &lt;strong>reparte la energía&lt;/strong>: una matriz &amp;ldquo;incoherente&amp;rdquo; 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 &amp;ldquo;blanquear&amp;rdquo; una señal antes de digitalizarla.&lt;/p>
&lt;p>&lt;strong>QuIP#&lt;/strong> (arXiv:2402.04396) combina incoherence processing con &lt;strong>códigos reticulares E8&lt;/strong>: 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.&lt;/p>
&lt;h3 id="3-codificación-con-memoria-trellis">3. Codificación con memoria: trellis&lt;/h3>
&lt;p>&lt;strong>QTIP&lt;/strong> (arXiv:2406.11235) añade el tercer ataque: &lt;strong>trellis-coded quantization&lt;/strong>. 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 &lt;strong>memoria&lt;/strong> 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.&lt;/p>
&lt;p>La idea común a los tres: &lt;strong>dejar de cuantizar escalares y empezar a cuantizar vectores con diccionarios, y decorrelacionar la matriz antes de hacerlo&lt;/strong>. Ninguno es &amp;ldquo;redondear mejor&amp;rdquo;; 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.&lt;/p>
&lt;h2 id="el-ternario-nativo-bitnet-b158">El ternario nativo: BitNet b1.58&lt;/h2>
&lt;p>Aquí cambiamos de continente. Todo lo anterior es &lt;strong>PTQ&lt;/strong>: parte de un modelo BF16 entrenado y lo comprime. El ternario de BitNet no comprime nada.&lt;/p>
&lt;p>&lt;strong>BitNet b1.58&lt;/strong> (arXiv:2402.17764) entrena un transformer desde cero donde &lt;strong>cada peso está restringido a {-1, 0, +1}&lt;/strong> durante todo el entrenamiento. Tres valores ⇒ log₂(3) ≈ &lt;strong>1.58 bits/peso&lt;/strong>. La cuantización no es un paso posterior: las capas lineales (&lt;code>BitLinear&lt;/code>) 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 &lt;strong>aprende a funcionar con pesos ternarios&lt;/strong>. Esto es QAT llevado al extremo: no un fine-tune corto de robustez, sino la restricción presente desde el primer token de entrenamiento.&lt;/p>
&lt;p>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 &amp;ldquo;frágiles&amp;rdquo; 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.&lt;/p>
&lt;h3 id="lo-que-cambia-no-es-solo-la-memoria-es-la-aritmética">Lo que cambia no es solo la memoria, es la aritmética&lt;/h3>
&lt;p>El punto que más se subestima de BitNet: con pesos en {-1, 0, +1}, &lt;strong>la multiplicación desaparece de la matmul&lt;/strong>. Multiplicar una activación &lt;code>x&lt;/code> por un peso ternario &lt;code>w&lt;/code> es trivial: si &lt;code>w = +1&lt;/code> sumas &lt;code>x&lt;/code>, si &lt;code>w = -1&lt;/code> restas &lt;code>x&lt;/code>, si &lt;code>w = 0&lt;/code> 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 &lt;strong>sumas y restas enteras&lt;/strong>.&lt;/p>
&lt;p>Esto importa porque conecta con el roofline. Como se explica en &lt;a href="https://blog.lo0.es/posts/roofline-invertido-modelos-pequenos/">El roofline invertido de los modelos pequeños&lt;/a>, la inferencia LLM tiene dos techos: el de &lt;strong>memoria&lt;/strong> (ancho de banda HBM para cargar pesos) y el de &lt;strong>cómputo&lt;/strong> (FLOPs de las tensor cores). La cuantización normal (INT4, FP8) ataca &lt;strong>solo el techo de memoria&lt;/strong>: el peso ocupa menos, pero para multiplicarlo lo descuantizas a FP16 y haces la misma multiplicación de siempre. El ternario ataca &lt;strong>ambos techos a la vez&lt;/strong>: el peso ocupa 1.58 bits (memoria) &lt;strong>y&lt;/strong> la operación es una suma en lugar de una multiplicación (cómputo). Por eso BitNet necesita kernels propios —&lt;strong>bitnet.cpp&lt;/strong>— 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.&lt;/p>
&lt;p>La contrapartida honesta: BitNet b1.58 es entrenamiento desde cero. No puedes &amp;ldquo;convertir tu Llama 8B a BitNet&amp;rdquo;. 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.&lt;/p>
&lt;h2 id="qat-como-puente-entre-ptq-y-nativo">QAT como puente entre PTQ y nativo&lt;/h2>
&lt;p>Entre &amp;ldquo;comprimir post-hoc&amp;rdquo; (PTQ) y &amp;ldquo;entrenar nativamente ternario&amp;rdquo; (BitNet) hay un punto intermedio: &lt;strong>QAT&lt;/strong> (Quantization-Aware Training). Tomas un modelo ya entrenado y haces un fine-tune corto &lt;strong>con las operaciones de cuantización dentro del bucle&lt;/strong>, para que aprenda a ser robusto a bits bajos sin pagar un pre-entrenamiento completo.&lt;/p>
&lt;p>&lt;strong>Gemma 3&lt;/strong> publica variantes &lt;strong>QAT&lt;/strong> 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 &amp;ldquo;fine-tune corto&amp;rdquo; y se convierte en entrenamiento nativo (BitNet).&lt;/p>
&lt;p>La jerarquía de decisión:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>PTQ&lt;/strong> = default a ≥4 bits. Minutos-horas, sin tocar pesos de entrenamiento. Cubre el 90 % de producción.&lt;/li>
&lt;li>&lt;strong>QAT&lt;/strong> = cuando PTQ pierde demasiado y la diferencia importa. Bits bajos (2-3), o modelos sensibles. Pagas fine-tune.&lt;/li>
&lt;li>&lt;strong>Nativo (ternario)&lt;/strong> = cuando quieres bajar de 2 bits &lt;strong>y&lt;/strong> cambiar la aritmética. Pagas pre-entrenamiento. Solo tiene sentido si controlas el modelo desde su creación.&lt;/li>
&lt;/ul>
&lt;h2 id="las-matemáticas-que-importan-footprint-y-cuántos-caben">Las matemáticas que importan: footprint y cuántos caben&lt;/h2>
&lt;p>El footprint de los pesos es directo: &lt;code>bytes = (bits/param / 8) × N&lt;/code>, con &lt;code>N&lt;/code> el número de parámetros. Para un modelo de 8B:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Nivel&lt;/th>
&lt;th>bits/param&lt;/th>
&lt;th>Footprint 8B&lt;/th>
&lt;th>Ratio vs BF16&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>BF16&lt;/td>
&lt;td>16&lt;/td>
&lt;td>16.0 GB&lt;/td>
&lt;td>1.0×&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>INT8&lt;/td>
&lt;td>8&lt;/td>
&lt;td>8.0 GB&lt;/td>
&lt;td>2.0×&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>INT4&lt;/td>
&lt;td>4&lt;/td>
&lt;td>4.0 GB&lt;/td>
&lt;td>4.0×&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3-bit&lt;/td>
&lt;td>3&lt;/td>
&lt;td>3.0 GB&lt;/td>
&lt;td>5.3×&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>2-bit&lt;/td>
&lt;td>2&lt;/td>
&lt;td>2.0 GB&lt;/td>
&lt;td>8.0×&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1.58-bit (ternario)&lt;/td>
&lt;td>~1.58&lt;/td>
&lt;td>~1.6 GB&lt;/td>
&lt;td>~10×&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>(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.)&lt;/p>
&lt;h3 id="cuántos-modelos-de-8b-caben-en-una-rtx-4090">¿Cuántos modelos de 8B caben en una RTX 4090?&lt;/h3>
&lt;p>Una &lt;strong>RTX 4090 (24 GB, Ada Lovelace)&lt;/strong> tiene 24 GB. Reservamos ~4 GB para KV cache y activaciones, dejando &lt;strong>20 GB&lt;/strong> para pesos. Cuántos modelos de 8B distintos caben cargados simultáneamente:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Nivel&lt;/th>
&lt;th>Footprint 8B&lt;/th>
&lt;th>Modelos en 20 GB&lt;/th>
&lt;th>Comentario&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>BF16&lt;/td>
&lt;td>16.0 GB&lt;/td>
&lt;td>&lt;strong>1&lt;/strong>&lt;/td>
&lt;td>uno y queda margen escaso&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>INT8&lt;/td>
&lt;td>8.0 GB&lt;/td>
&lt;td>&lt;strong>2&lt;/strong>&lt;/td>
&lt;td>dos modelos distintos&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>INT4&lt;/td>
&lt;td>4.0 GB&lt;/td>
&lt;td>&lt;strong>5&lt;/strong>&lt;/td>
&lt;td>régimen resuelto; calidad ~lossless con AWQ&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3-bit&lt;/td>
&lt;td>3.0 GB&lt;/td>
&lt;td>&lt;strong>6&lt;/strong>&lt;/td>
&lt;td>degradación pequeña ya visible&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>2-bit&lt;/td>
&lt;td>2.0 GB&lt;/td>
&lt;td>&lt;strong>10&lt;/strong>&lt;/td>
&lt;td>solo viable con AQLM/QuIP#/QTIP&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1.58-bit&lt;/td>
&lt;td>~1.6 GB&lt;/td>
&lt;td>&lt;strong>~12&lt;/strong>&lt;/td>
&lt;td>solo modelos nativamente ternarios&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>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 &lt;strong>no se justifica por footprint&lt;/strong> 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 &amp;ldquo;cuántos GB ocupa&amp;rdquo;, el 2-bit SOTA ya te da casi todo. El ternario es para cuando además quieres el ahorro de cómputo.&lt;/p>
&lt;h3 id="la-curva-conceptual-perplexity-vs-bits">La curva conceptual: perplexity vs bits&lt;/h3>
&lt;div class="diagram" style="max-width:780px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 780 380" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Curva conceptual de perplexity frente a bits por peso">
&lt;text x="390" y="26" text-anchor="middle" fill="currentColor" font-size="15" font-weight="700">Perplexity vs bits por peso (conceptual): el codo y la rama nativa&lt;/text>
&lt;line x1="90" y1="320" x2="730" y2="320" stroke="currentColor" stroke-width="1.5"/>
&lt;line x1="90" y1="60" x2="90" y2="320" stroke="currentColor" stroke-width="1.5"/>
&lt;text x="410" y="362" text-anchor="middle" fill="currentColor" font-size="13">bits por peso (eje invertido: más comprimido a la derecha)&lt;/text>
&lt;text x="30" y="190" text-anchor="middle" fill="currentColor" font-size="13" transform="rotate(-90 30 190)">perplexity (peor arriba)&lt;/text>
&lt;text x="120" y="338" text-anchor="middle" fill="currentColor" font-size="12">16&lt;/text>
&lt;text x="250" y="338" text-anchor="middle" fill="currentColor" font-size="12">8&lt;/text>
&lt;text x="380" y="338" text-anchor="middle" fill="currentColor" font-size="12">4&lt;/text>
&lt;text x="470" y="338" text-anchor="middle" fill="currentColor" font-size="12">3&lt;/text>
&lt;text x="560" y="338" text-anchor="middle" fill="currentColor" font-size="12">2&lt;/text>
&lt;text x="650" y="338" text-anchor="middle" fill="currentColor" font-size="12">1.58&lt;/text>
&lt;line x1="380" y1="60" x2="380" y2="320" stroke="currentColor" stroke-width="0.8" stroke-dasharray="4 3"/>
&lt;text x="384" y="74" fill="currentColor" font-size="11">frontera 4-bit&lt;/text>
&lt;polyline points="120,300 250,298 380,292 470,278 560,170 620,95" fill="none" stroke="#c0392b" stroke-width="2.6"/>
&lt;circle cx="120" cy="300" r="4" fill="#c0392b"/>
&lt;circle cx="250" cy="298" r="4" fill="#c0392b"/>
&lt;circle cx="380" cy="292" r="4" fill="#c0392b"/>
&lt;circle cx="470" cy="278" r="4" fill="#c0392b"/>
&lt;circle cx="560" cy="170" r="4" fill="#c0392b"/>
&lt;text x="600" y="92" fill="#c0392b" font-size="12" font-weight="700">PTQ escalar ingenua&lt;/text>
&lt;text x="600" y="108" fill="#c0392b" font-size="11">colapsa &amp;lt;3 bits&lt;/text>
&lt;polyline points="380,292 470,284 560,250 650,232" fill="none" stroke="#2471a3" stroke-width="2.6" stroke-dasharray="6 3"/>
&lt;circle cx="560" cy="250" r="4" fill="#2471a3"/>
&lt;circle cx="650" cy="232" r="4" fill="#2471a3"/>
&lt;text x="560" y="282" fill="#2471a3" font-size="12" font-weight="700">PTQ SOTA vectorial&lt;/text>
&lt;text x="560" y="298" fill="#2471a3" font-size="11">AQLM / QuIP# / QTIP&lt;/text>
&lt;circle cx="650" cy="225" r="6" fill="#27ae60"/>
&lt;text x="600" y="208" fill="#27ae60" font-size="12" font-weight="700">ternario nativo&lt;/text>
&lt;text x="600" y="222" fill="#27ae60" font-size="11">BitNet b1.58 (no PTQ)&lt;/text>
&lt;text x="120" y="290" fill="currentColor" font-size="11">≈ plano ≥ 4 bits con buen método&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>Tres lecturas de la curva. &lt;strong>Uno&lt;/strong>: a la derecha de 4 bits, las tres ramas están casi pegadas y casi planas —el régimen resuelto—. &lt;strong>Dos&lt;/strong>: 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—. &lt;strong>Tres&lt;/strong>: el punto verde del ternario nativo &lt;strong>no está en ninguna de las dos curvas de PTQ&lt;/strong>, 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.&lt;/p>
&lt;h2 id="escepticismo-obligatorio-el-1-bit-sin-pérdida-y-los-benchmarks-sin-metodología">Escepticismo obligatorio: el 1-bit &amp;ldquo;sin pérdida&amp;rdquo; y los benchmarks sin metodología&lt;/h2>
&lt;p>Tres alertas para leer la literatura de cuantización agresiva:&lt;/p>
&lt;p>&lt;strong>&amp;ldquo;1-bit sin pérdida&amp;rdquo; casi siempre tiene letra pequeña.&lt;/strong> 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 &lt;strong>ternario&lt;/strong> (1.58 bits), no binario: el cero vale su 0.58 de bit extra. Cuando un paper anuncia &amp;ldquo;1-bit&amp;rdquo;, conviene mirar si (a) es realmente 1 bit o 1.58 redondeado hacia abajo en el titular, (b) &amp;ldquo;sin pérdida&amp;rdquo; 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.&lt;/p>
&lt;p>&lt;strong>Perplexity plana ≠ calidad preservada.&lt;/strong> 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 &lt;a href="https://blog.lo0.es/posts/quantization-fundamentos-inferencia/">el post de quantization&lt;/a>, la pérdida hay que medirla en la tarea de destino.&lt;/p>
&lt;p>&lt;strong>Comparabilidad de hardware.&lt;/strong> Los números de &amp;ldquo;X veces más rápido&amp;rdquo; del ternario solo aplican &lt;strong>con los kernels especializados&lt;/strong> (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 &lt;strong>más lento&lt;/strong> que INT4 bien optimizado, porque desaprovecha el silicio. La ventaja del ternario es real, pero es una ventaja de &lt;strong>co-diseño&lt;/strong> (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.&lt;/p>
&lt;h2 id="implicaciones-para-inferencia-on-premise">Implicaciones para inferencia on-premise&lt;/h2>
&lt;p>En la &lt;strong>RTX 4090 (24 GB, Ada Lovelace)&lt;/strong>: 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.&lt;/p>
&lt;p>En un &lt;strong>cluster genérico 4×H100 SXM (320 GB, NVLink, FP8 nativo)&lt;/strong>: 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.&lt;/p>
&lt;p>La regla de pulgar, junio 2026: &lt;strong>≥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&lt;/strong>.&lt;/p>
&lt;h2 id="ver-también">Ver también&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/posts/quantization-fundamentos-inferencia/">Quantization para inferencia LLM: FP8, INT4, GGUF&lt;/a> — la base imprescindible: la matemática del scale+zero-point, GPTQ/AWQ y PTQ vs QAT que aquí se dan por sabidas; este post es su continuación hacia la frontera sub-4-bit.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/fp8-end-to-end-pesos-kv-calidad/">FP8 end-to-end: pesos, KV y calidad&lt;/a> — el otro extremo del espectro, el régimen resuelto del datacenter donde la cuantización ya casi no cuesta calidad.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/knowledge-distillation-fundamentos/">Knowledge distillation&lt;/a> — la palanca complementaria: destilar reduce parámetros, cuantizar reduce bits por parámetro; a 2-bit suelen combinarse para llegar al footprint objetivo.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/poda-pruning-llm-fundamentos/">Poda de modelos LLM&lt;/a> — sparsidad y cuantización agresiva son ortogonales y se acumulan: 50 % sparso + 2-bit es otra ruta al mismo footprint que el ternario.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/kv-cache-fundamentos/">KV cache: la memoria de trabajo&lt;/a> — los ~4 GB que reservamos para KV en la cuenta de la 4090 salen de aquí; cuantizar el cache es la otra mitad del presupuesto de memoria.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/roofline-invertido-modelos-pequenos/">El roofline invertido de los modelos pequeños&lt;/a> — por qué el ternario es especial: ataca a la vez el techo de memoria y el de cómputo, mientras INT4/FP8 solo tocan el de memoria.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/test-time-quantization-en-caliente/">Test-time quantization en caliente&lt;/a> — cuantizar dinámicamente en inferencia frente a la cuantización estática y calibrada que describen AQLM/QuIP#/QTIP.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/arquitecturas-nativas-device-moe-grano-fino/">Arquitecturas nativas device + MoE de grano fino&lt;/a> — el Q4 en device como punto de partida del que el sub-4-bit y el ternario son la siguiente frontera para edge.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/qlora-multi-lora-agresivo-slm/">QLoRA y multi-LoRA agresivo en SLM&lt;/a> — fine-tune sobre una base ya cuantizada; el límite de cuánto puedes comprimir la base antes de que el adapter no pueda recuperar la calidad.&lt;/li>
&lt;/ul>
&lt;h2 id="referencias">Referencias&lt;/h2>
&lt;ul>
&lt;li>Ma, S. et al. &lt;em>The Era of 1-bit LLMs: All Large Language Models are in 1.58 Bits&lt;/em> (BitNet b1.58). &lt;a href="https://arxiv.org/abs/2402.17764">https://arxiv.org/abs/2402.17764&lt;/a>&lt;/li>
&lt;li>Egiazarian, V., Panferov, A., Kuznedelev, D. et al. &lt;em>Extreme Compression of Large Language Models via Additive Quantization&lt;/em> (AQLM). &lt;a href="https://arxiv.org/abs/2401.06118">https://arxiv.org/abs/2401.06118&lt;/a>&lt;/li>
&lt;li>Tseng, A., Chee, J., Sun, Q., Kuleshov, V., De Sa, C. &lt;em>QuIP#: Even Better LLM Quantization with Hadamard Incoherence and Lattice Codebooks&lt;/em>. &lt;a href="https://arxiv.org/abs/2402.04396">https://arxiv.org/abs/2402.04396&lt;/a>&lt;/li>
&lt;li>Tseng, A., Sun, Q., Hou, D., De Sa, C. &lt;em>QTIP: Quantization with Trellises and Incoherence Processing&lt;/em>. &lt;a href="https://arxiv.org/abs/2406.11235">https://arxiv.org/abs/2406.11235&lt;/a>&lt;/li>
&lt;li>Frantar, E., Ashkboos, S., Hoefler, T., Alistarh, D. &lt;em>GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers&lt;/em>. &lt;a href="https://arxiv.org/abs/2210.17323">https://arxiv.org/abs/2210.17323&lt;/a>&lt;/li>
&lt;li>Lin, J., Tang, J., Tang, H., Yang, S., Dang, X., Han, S. &lt;em>AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration&lt;/em>. &lt;a href="https://arxiv.org/abs/2306.00978">https://arxiv.org/abs/2306.00978&lt;/a>&lt;/li>
&lt;li>Google DeepMind. &lt;em>Gemma 3 QAT (Quantization-Aware Training) models&lt;/em> — blog oficial: &lt;a href="https://developers.googleblog.com/en/gemma-3-quantized-aware-trained-state-of-the-art-ai-to-consumer-gpus/">https://developers.googleblog.com/en/gemma-3-quantized-aware-trained-state-of-the-art-ai-to-consumer-gpus/&lt;/a>&lt;/li>
&lt;li>Microsoft. &lt;em>bitnet.cpp&lt;/em> — kernels de inferencia ternaria 1-bit: &lt;a href="https://github.com/microsoft/BitNet">https://github.com/microsoft/BitNet&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Test-time quantization: cuantizar en caliente sin dataset de calibración</title><link>https://blog.lo0.es/posts/test-time-quantization-en-caliente/</link><pubDate>Tue, 09 Jun 2026 02:00:00 +0000</pubDate><guid>https://blog.lo0.es/posts/test-time-quantization-en-caliente/</guid><description>&lt;blockquote>
&lt;p>Este post es la continuación natural de &lt;a href="https://blog.lo0.es/posts/quantization-fundamentos-inferencia/">Quantization para inferencia LLM&lt;/a>, que conviene leer primero: allí están GPTQ, AWQ, el scale + zero-point y por qué los outliers de activación son el problema central. Aquí no discutimos &lt;em>cuántos bits&lt;/em> usar, sino &lt;strong>cuándo y con qué información se calculan las escalas&lt;/strong>: offline contra un corpus (PTQ) o en caliente contra el tráfico real (TTQ).&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>La cuantización activation-aware (AWQ, SmoothQuant) decide qué canales proteger midiendo la magnitud de las activaciones sobre un &lt;strong>dataset de calibración&lt;/strong> en un &lt;strong>pase offline&lt;/strong>, antes de desplegar. El supuesto implícito es que ese corpus representa el tráfico futuro. Pero los outliers de activación —los canales de magnitud 10-100× la mediana que dominan el error de cuantización— &lt;strong>dependen del input&lt;/strong>: cambian con el dominio, el idioma y la distribución del cliente. Cuando el tráfico real se aleja de la calibración, las escalas fijas dejan de ser óptimas y la calidad cae. &lt;strong>Test-time quantization (TTQ)&lt;/strong> elimina el corpus y el pase offline: deriva las escalas activation-aware &lt;strong>en tiempo de inferencia&lt;/strong>, a partir de las activaciones que realmente se observan, por token o por batch. La contrapartida es honesta y no menor: introduce &lt;strong>overhead en runtime&lt;/strong> —calcular estadísticas, detectar outliers, recomputar escalas en cada step— que compite directamente con el ahorro de cuantizar. En modelos pequeños ese overhead pesa proporcionalmente más, porque el forward es corto y los costes fijos por step dominan (el marco está en &lt;a href="https://blog.lo0.es/posts/roofline-invertido-modelos-pequenos/">roofline invertido para SLM&lt;/a>). TTQ es &lt;strong>ortogonal&lt;/strong> al formato: no es un competidor de INT4 o FP8, es una forma distinta de derivar &lt;em>s&lt;/em>. Compensa cuando no hay pipeline de calibración, cuando la distribución del tráfico es cambiante o desconocida, y en multitenant donde no existe un corpus representativo.&lt;/p>
&lt;h2 id="estás-aquí-deploy">Estás aquí: DEPLOY&lt;/h2>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 90" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="estás aquí: Deploy">
&lt;defs>&lt;marker id="ttqm" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#666"/>&lt;/marker>&lt;/defs>
&lt;text x="390" y="20" text-anchor="middle" fill="currentColor" font-size="12" font-weight="600">Estás aquí: DEPLOY · derivar escalas de cuantización en caliente&lt;/text>
&lt;rect x="30" y="35" width="110" height="35" rx="6" fill="#f4f4f4" stroke="#444" stroke-width="1.4"/>&lt;text x="85" y="58" text-anchor="middle" fill="currentColor" font-size="12" font-weight="600">1 · Data&lt;/text>
&lt;rect x="155" y="35" width="110" height="35" rx="6" fill="#f4f4f4" stroke="#444" stroke-width="1.4"/>&lt;text x="210" y="58" text-anchor="middle" fill="currentColor" font-size="12" font-weight="600">2 · Tune&lt;/text>
&lt;rect x="280" y="35" width="110" height="35" rx="6" fill="#f4f4f4" stroke="#444" stroke-width="1.4"/>&lt;text x="335" y="58" text-anchor="middle" fill="currentColor" font-size="12" font-weight="600">3 · Eval&lt;/text>
&lt;rect x="405" y="35" width="110" height="35" rx="6" fill="#7ad88f" stroke="#444" stroke-width="3"/>&lt;text x="460" y="58" text-anchor="middle" fill="#111" font-size="12" font-weight="600">4 · Deploy&lt;/text>
&lt;rect x="530" y="35" width="110" height="35" rx="6" fill="#f4f4f4" stroke="#444" stroke-width="1.4"/>&lt;text x="585" y="58" text-anchor="middle" fill="currentColor" font-size="12" font-weight="600">5 · Observe&lt;/text>
&lt;rect x="655" y="35" width="110" height="35" rx="6" fill="#f4f4f4" stroke="#444" stroke-width="1.4"/>&lt;text x="710" y="58" text-anchor="middle" fill="currentColor" font-size="12" font-weight="600">6 · Retrain&lt;/text>
&lt;path d="M140,52 L155,52" stroke="#666" stroke-width="1.4" fill="none" marker-end="url(#ttqm)"/>
&lt;path d="M265,52 L280,52" stroke="#666" stroke-width="1.4" fill="none" marker-end="url(#ttqm)"/>
&lt;path d="M390,52 L405,52" stroke="#666" stroke-width="1.4" fill="none" marker-end="url(#ttqm)"/>
&lt;path d="M515,52 L530,52" stroke="#666" stroke-width="1.4" fill="none" marker-end="url(#ttqm)"/>
&lt;path d="M640,52 L655,52" stroke="#666" stroke-width="1.4" fill="none" marker-end="url(#ttqm)"/>
&lt;path d="M710,72 L710,82 L85,82 L85,72" stroke="#888" stroke-width="1.2" fill="none" stroke-dasharray="4 2" marker-end="url(#ttqm)"/>
&lt;/svg>
&lt;/div>
&lt;h2 id="la-analogía-el-sastre-que-toma-medidas-frente-a-las-tallas-pre-confeccionadas">La analogía: el sastre que toma medidas frente a las tallas pre-confeccionadas&lt;/h2>
&lt;p>Una tienda de ropa tiene dos formas de vestir a un cliente.&lt;/p>
&lt;p>La primera es &lt;strong>vender tallas pre-confeccionadas&lt;/strong>. La fábrica midió en su día a un &amp;ldquo;cliente medio&amp;rdquo; —un maniquí promedio construido sobre una muestra de población— y cortó las prendas según esas medidas. Cuando entra un cliente, le das la talla que más se le acerca. Es rapidísimo: la prenda ya está cosida, solo se entrega. El problema aparece cuando el cliente no se parece al maniquí promedio: si tiene los hombros mucho más anchos que la media —su outlier particular—, la talla estándar le tira o le sobra tela, porque se cortó protegiendo &lt;em>otras&lt;/em> zonas. Esto es la &lt;strong>PTQ offline calibrada&lt;/strong>: AWQ midió la importancia de cada canal sobre un corpus y fijó las escalas de una vez; rápido en inferencia, pero ciego al cliente concreto.&lt;/p>
&lt;p>La segunda es &lt;strong>el sastre que toma medidas en el momento&lt;/strong>. Cuando entra el cliente, el sastre saca el metro, mide &lt;em>a ese cliente&lt;/em>, detecta dónde está su volumen particular y ajusta el corte a su anatomía real. El resultado encaja mejor, sobre todo en los clientes que se salen del molde. Pero cada cliente cuesta tiempo: medir, marcar, decidir. Esto es &lt;strong>TTQ&lt;/strong>: las escalas se derivan en caliente de las activaciones que ese input genera realmente.&lt;/p>
&lt;p>La analogía se sostiene en tres detalles:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>El maniquí promedio = el dataset de calibración.&lt;/strong> Si la población que entra a la tienda se parece al maniquí, las tallas funcionan; si no, fallan en los extremos.&lt;/li>
&lt;li>&lt;strong>Tomar medidas en cada cliente = calcular estadísticas de activación por token/batch.&lt;/strong> Mejor ajuste, pero un coste fijo que se paga en &lt;em>cada&lt;/em> prenda.&lt;/li>
&lt;li>&lt;strong>Los hombros anchos = los canales outlier de activación.&lt;/strong> Son precisamente las zonas donde el ajuste importa y donde la talla genérica más se equivoca.&lt;/li>
&lt;/ul>
&lt;p>El sastre gana cuando los clientes son variados o desconocidos. Pierde cuando tienes una población homogénea y un maniquí que la representa bien: ahí pagar la medición en cada cliente es tirar el tiempo.&lt;/p>
&lt;h2 id="el-problema-que-ttq-resuelve-la-calibración-fija-envejece-con-el-tráfico">El problema que TTQ resuelve: la calibración fija envejece con el tráfico&lt;/h2>
&lt;p>Recordemos del &lt;a href="https://blog.lo0.es/posts/quantization-fundamentos-inferencia/">post de quantization&lt;/a> qué hacen exactamente AWQ y SmoothQuant. No cuantizan todos los canales por igual: identifican el ~1 % de canales cuyas activaciones tienen magnitud grande —los &lt;em>salient channels&lt;/em>— y los protegen escalándolos antes de cuantizar. Para medir esa importancia necesitan ver activaciones, y las ven sobre un &lt;strong>dataset de calibración&lt;/strong> (128-512 muestras, típicamente WikiText o un slice del dominio) en un &lt;strong>pase offline&lt;/strong> previo al despliegue.&lt;/p>
&lt;p>El supuesto es fuerte: que la distribución de activaciones del corpus de calibración &lt;strong>representa la del tráfico de producción&lt;/strong>. Dos razones por las que ese supuesto se rompe:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Los outliers de activación dependen del input.&lt;/strong> No son una propiedad fija del modelo como los pesos. El canal que es outlier procesando código C++ puede no serlo procesando árabe conversacional o JSON de logs. La magnitud y la posición de los picos cambian con el dominio, el idioma y el formato de entrada.&lt;/li>
&lt;li>&lt;strong>El tráfico real rara vez es el corpus.&lt;/strong> Calibras con WikiText en inglés y el cliente te manda tickets de soporte en español con tablas pegadas. La calibración protegió los canales que &lt;em>WikiText&lt;/em> activaba, no los que activa el tráfico real. Las escalas son subóptimas justo donde el cliente vive.&lt;/li>
&lt;/ol>
&lt;p>El resultado es &lt;strong>degradación dependiente de la distribución&lt;/strong>: el modelo cuantizado mantiene la calidad mientras el input se parece a la calibración y la pierde a medida que se aleja. El caso más incómodo es el &lt;strong>multitenant&lt;/strong>: si sirves a clientes con dominios distintos desde el mismo modelo cuantizado, no existe un único corpus representativo; cualquier calibración fija favorece a unos tenants y penaliza a otros.&lt;/p>
&lt;h2 id="el-mecanismo-de-ttq-medir-las-activaciones-reales-y-escalar-en-caliente">El mecanismo de TTQ: medir las activaciones reales y escalar en caliente&lt;/h2>
&lt;p>TTQ (arXiv:2603.19296, marzo 2026) propone derivar la cuantización &lt;strong>activation-aware en tiempo de inferencia&lt;/strong>, sin pase offline ni dataset de calibración. La idea, en su forma desnuda y conceptual:&lt;/p>
&lt;p>&lt;strong>Paso 1 — Observar.&lt;/strong> Cuando llega el tensor de activaciones &lt;code>X&lt;/code> a una capa lineal (por token o por batch), se calculan estadísticas baratas sobre los canales: una medida de tendencia central (mediana o media de magnitud) y una de dispersión por canal. Esto es el equivalente a que AWQ mirase su corpus, pero hecho sobre las activaciones que &lt;em>de verdad&lt;/em> están entrando ahora.&lt;/p>
&lt;p>&lt;strong>Paso 2 — Detectar outliers en caliente.&lt;/strong> Con esas estadísticas se identifican los canales cuya magnitud se dispara respecto a la mediana del tensor —el criterio típico es un umbral del estilo &amp;ldquo;magnitud &amp;gt; k × mediana&amp;rdquo;. Son los canales que, si se cuantizan con la misma escala que el resto, disparan el error.&lt;/p>
&lt;p>&lt;strong>Paso 3 — Derivar escalas y segregar.&lt;/strong> Para los canales normales se calcula una escala que aprovecha el rango; para los outliers se aplica un tratamiento distinto —una escala propia, o mantenerlos en precisión más alta— al estilo &lt;em>mixed-precision en caliente&lt;/em>. Es la misma filosofía que LLM.int8() (segregar outliers a FP16) o AWQ (escalar salient channels), pero con el umbral y las escalas &lt;strong>recalculados sobre el input actual&lt;/strong>, no congelados desde la calibración.&lt;/p>
&lt;p>&lt;strong>Paso 4 — Cuantizar y multiplicar.&lt;/strong> Con las escalas frescas se cuantiza y se ejecuta el GEMM. Las activaciones que entran al siguiente layer compensan el reescalado, igual que en AWQ, para que la matemática se cancele.&lt;/p>
&lt;p>La diferencia clave con AWQ no está en &lt;em>qué&lt;/em> se hace (proteger outliers de activación) sino en &lt;em>cuándo&lt;/em> y &lt;em>contra qué&lt;/em>: AWQ lo decide una vez, offline, contra un corpus; TTQ lo decide en cada step, en caliente, contra el tráfico real. Es la traslación a inferencia de la idea de &amp;ldquo;test-time&amp;rdquo;: adaptar el cómputo a la muestra concreta que tienes delante en lugar de a un promedio precomputado.&lt;/p>
&lt;div class="diagram" style="max-width:780px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 780 300" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="PTQ offline calibrada frente a TTQ en caliente">
&lt;defs>&lt;marker id="ttq2" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#666"/>&lt;/marker>&lt;/defs>
&lt;text x="195" y="22" text-anchor="middle" fill="currentColor" font-size="13" font-weight="700">PTQ offline-calibrada (AWQ / GPTQ)&lt;/text>
&lt;text x="585" y="22" text-anchor="middle" fill="currentColor" font-size="13" font-weight="700">TTQ en-caliente&lt;/text>
&lt;line x1="390" y1="35" x2="390" y2="285" stroke="#bbb" stroke-width="1" stroke-dasharray="4 3"/>
&lt;p>&lt;rect x="30" y="45" width="150" height="38" rx="6" fill="#ffe6d6" stroke="#a05a2c" stroke-width="1.4"/>&lt;text x="105" y="68" text-anchor="middle" fill="#111" font-size="11" font-weight="600">dataset calibración&lt;/text>
&lt;rect x="210" y="45" width="150" height="38" rx="6" fill="#ffe6d6" stroke="#a05a2c" stroke-width="1.4"/>&lt;text x="285" y="64" text-anchor="middle" fill="#111" font-size="11" font-weight="600">pase OFFLINE&lt;/text>&lt;text x="285" y="78" text-anchor="middle" fill="#444" font-size="10">fija escalas s, outliers&lt;/text>
&lt;path d="M180,64 L210,64" stroke="#666" stroke-width="1.4" fill="none" marker-end="url(#ttq2)"/>
&lt;rect x="120" y="105" width="150" height="34" rx="6" fill="#f0f0f0" stroke="#444" stroke-width="1.4"/>&lt;text x="195" y="126" text-anchor="middle" fill="#111" font-size="11" font-weight="600">escalas CONGELADAS&lt;/text>
&lt;path d="M285,83 L285,98 L195,98 L195,105" stroke="#666" stroke-width="1.4" fill="none" marker-end="url(#ttq2)"/>
&lt;rect x="30" y="165" width="150" height="34" rx="6" fill="#d6eaff" stroke="#1f5fa8" stroke-width="1.4"/>&lt;text x="105" y="186" text-anchor="middle" fill="#111" font-size="11" font-weight="600">input parecido → OK&lt;/text>
&lt;rect x="210" y="165" width="150" height="34" rx="6" fill="#f6caca" stroke="#a52a2a" stroke-width="1.4"/>&lt;text x="285" y="182" text-anchor="middle" fill="#111" font-size="11" font-weight="600">input lejano →&lt;/text>&lt;text x="285" y="195" text-anchor="middle" fill="#a52a2a" font-size="10" font-weight="600">degradación&lt;/text>
&lt;path d="M150,139 L105,165" stroke="#666" stroke-width="1.4" fill="none" marker-end="url(#ttq2)"/>
&lt;path d="M240,139 L285,165" stroke="#666" stroke-width="1.4" fill="none" marker-end="url(#ttq2)"/>
&lt;text x="195" y="225" text-anchor="middle" fill="#444" font-size="10">overhead inferencia ≈ 0 · calidad depende de la calibración&lt;/text>&lt;/p>
&lt;p>&lt;rect x="510" y="45" width="150" height="38" rx="6" fill="#d9f5d6" stroke="#2a7a40" stroke-width="1.4"/>&lt;text x="585" y="62" text-anchor="middle" fill="#111" font-size="11" font-weight="600">activaciones REALES&lt;/text>&lt;text x="585" y="77" text-anchor="middle" fill="#444" font-size="10">del tráfico actual&lt;/text>
&lt;rect x="510" y="100" width="150" height="34" rx="6" fill="#d9f5d6" stroke="#2a7a40" stroke-width="1.4"/>&lt;text x="585" y="115" text-anchor="middle" fill="#111" font-size="11" font-weight="600">medir + detectar&lt;/text>&lt;text x="585" y="128" text-anchor="middle" fill="#444" font-size="10">outliers EN CALIENTE&lt;/text>
&lt;path d="M585,83 L585,100" stroke="#666" stroke-width="1.4" fill="none" marker-end="url(#ttq2)"/>
&lt;rect x="510" y="151" width="150" height="34" rx="6" fill="#d9f5d6" stroke="#2a7a40" stroke-width="1.4"/>&lt;text x="585" y="166" text-anchor="middle" fill="#111" font-size="11" font-weight="600">escalas FRESCAS&lt;/text>&lt;text x="585" y="179" text-anchor="middle" fill="#444" font-size="10">por token / batch&lt;/text>
&lt;path d="M585,134 L585,151" stroke="#666" stroke-width="1.4" fill="none" marker-end="url(#ttq2)"/>
&lt;rect x="510" y="202" width="150" height="34" rx="6" fill="#fff5b0" stroke="#9a8400" stroke-width="1.4"/>&lt;text x="585" y="217" text-anchor="middle" fill="#111" font-size="11" font-weight="600">cuantizar + GEMM&lt;/text>&lt;text x="585" y="230" text-anchor="middle" fill="#9a6b00" font-size="10" font-weight="600">+ overhead por step&lt;/text>
&lt;path d="M585,185 L585,202" stroke="#666" stroke-width="1.4" fill="none" marker-end="url(#ttq2)"/>
&lt;text x="585" y="258" text-anchor="middle" fill="#444" font-size="10">sin corpus · calidad robusta a la distribución · overhead ≠ 0&lt;/text>
&lt;/svg>&lt;/p>
&lt;/div>
&lt;h2 id="las-matemáticas-que-importan">Las matemáticas que importan&lt;/h2>
&lt;h3 id="el-error-de-cuantizar-un-outlier-con-la-escala-equivocada">El error de cuantizar un outlier con la escala equivocada&lt;/h3>
&lt;p>Recordemos la cuantización uniforme afín del &lt;a href="https://blog.lo0.es/posts/quantization-fundamentos-inferencia/">post base&lt;/a>: un código entero &lt;code>q = round(x/s) - z&lt;/code> con escala &lt;code>s&lt;/code> y zero-point &lt;code>z&lt;/code>, y reconstrucción &lt;code>x̂ = s·(q + z)&lt;/code>. Para un cuantizador de &lt;code>b&lt;/code> bits con rango simétrico, la escala que cubre un tensor de magnitud máxima &lt;code>M&lt;/code> es aproximadamente &lt;code>s = M / (2^{b-1} - 1)&lt;/code>. El error de redondeo de cada elemento está acotado por media escala: &lt;code>|x - x̂| ≤ s/2&lt;/code>.&lt;/p>
&lt;p>Aquí está el problema del outlier. La escala &lt;code>s&lt;/code> se elige para cubrir el valor &lt;strong>más grande&lt;/strong> del grupo. Si un canal tiene magnitud 30× la mediana y compartes una sola escala con el resto del tensor, esa magnitud manda: &lt;code>M&lt;/code> es el outlier, así que &lt;code>s&lt;/code> se infla 30× respecto a lo que necesitaría la mayoría. El error absoluto de redondeo de los valores normales sube proporcionalmente.&lt;/p>
&lt;p>Cuenta concreta. Tomemos un grupo donde la mediana de magnitudes es 1.0 y un canal outlier vale 30.0, cuantizado a INT4 (&lt;code>b = 4&lt;/code>, niveles ±7):&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Con escala compartida&lt;/strong>, &lt;code>s = 30 / 7 ≈ 4.29&lt;/code>. El error de redondeo de un valor típico (magnitud ~1) es de hasta &lt;code>s/2 ≈ 2.14&lt;/code>. Es decir, &lt;strong>el error sobre los valores normales es del orden de su propio valor&lt;/strong>: el outlier ha destruido la resolución de todo lo demás. Error relativo de un valor de magnitud 1: hasta ~214 %.&lt;/li>
&lt;li>&lt;strong>Segregando el outlier&lt;/strong> (lo sacas a FP16 o le das su propia escala) y cuantizando el resto con &lt;code>M = 1&lt;/code>, &lt;code>s = 1/7 ≈ 0.143&lt;/code>. El error de un valor típico baja a &lt;code>s/2 ≈ 0.071&lt;/code>, ~7 % relativo. &lt;strong>Treinta veces menos error&lt;/strong> sobre la mayoría de los pesos del grupo.&lt;/li>
&lt;/ul>
&lt;p>Esa es toda la razón de ser de la cuantización activation-aware: &lt;strong>detectar y tratar aparte el ~1 % de canales que, de no segregarse, secuestran la escala&lt;/strong>. AWQ lo hace contra el corpus; TTQ lo hace contra el input real. Y si el canal que es outlier &lt;em>en producción&lt;/em> no era outlier &lt;em>en la calibración&lt;/em>, AWQ no lo protegió: cuantizó el tráfico real con la escala inflada del caso de arriba. Ahí TTQ gana precisión.&lt;/p>
&lt;h3 id="el-overhead-el-coste-de-medir-en-cada-step">El overhead: el coste de medir en cada step&lt;/h3>
&lt;p>El precio es simétrico. Calcular las estadísticas por token —magnitudes por canal, mediana o percentil, umbral de outlier, escalas— son reducciones sobre el tensor de activación que &lt;strong>no existían&lt;/strong> en el forward con escalas congeladas. Llamemos:&lt;/p>
&lt;ul>
&lt;li>&lt;code>T&lt;/code> = tiempo del forward por token con escalas fijas (PTQ estática), en µs.&lt;/li>
&lt;li>&lt;code>Δ&lt;/code> = coste extra por token de derivar las estadísticas y escalas en caliente, en µs.&lt;/li>
&lt;/ul>
&lt;p>El overhead relativo es simplemente:&lt;/p>
&lt;p>$$\text{overhead} = \frac{\Delta}{T}$$&lt;/p>
&lt;p>La clave es que &lt;code>Δ&lt;/code> es relativamente &lt;strong>fijo por step&lt;/strong> (depende del número de canales y capas, no de cuánto trabajo &amp;ldquo;útil&amp;rdquo; haga el modelo), mientras que &lt;code>T&lt;/code> escala con el tamaño del modelo. Por eso el cociente se comporta de forma muy distinta según el modelo:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Modelo grande&lt;/strong> (p. ej. 70B): &lt;code>T&lt;/code> es grande —cada forward mueve decenas de GB de pesos desde HBM—. Si &lt;code>Δ ≈ 8 µs&lt;/code> y &lt;code>T ≈ 800 µs&lt;/code>, el overhead es &lt;code>8/800 = 1 %&lt;/code>. Despreciable frente al ahorro de cuantizar.&lt;/li>
&lt;li>&lt;strong>SLM&lt;/strong> (p. ej. 1B): &lt;code>T&lt;/code> es pequeño —el forward por token es corto—. Con el mismo &lt;code>Δ ≈ 8 µs&lt;/code> y &lt;code>T ≈ 60 µs&lt;/code>, el overhead es &lt;code>8/60 ≈ 13 %&lt;/code>. Ya no es despreciable: se come buena parte de lo que ganaste cuantizando.&lt;/li>
&lt;/ul>
&lt;p>Esto conecta directamente con el &lt;a href="https://blog.lo0.es/posts/roofline-invertido-modelos-pequenos/">roofline invertido para modelos pequeños&lt;/a>: en SLM los &lt;strong>costes fijos por step&lt;/strong> (lanzamiento de kernels, sincronizaciones, overheads que no escalan con el modelo) pesan proporcionalmente más, porque hay menos trabajo útil entre los que repartirlos. El &lt;code>Δ&lt;/code> de TTQ es exactamente uno de esos costes fijos. Per-batch en lugar de per-token amortiza &lt;code>Δ&lt;/code> entre todos los tokens del batch y baja el overhead relativo, a costa de escalas menos finas; es el primer parámetro a tocar.&lt;/p>
&lt;p>La conclusión incómoda: TTQ regala robustez a la distribución pero &lt;strong>gasta parte del presupuesto de aceleración en medir&lt;/strong>, y en el régimen donde la aceleración más escasea —los SLM, los que más se despliegan en el edge— es donde ese gasto más duele. No es gratis; es un cambio de moneda.&lt;/p>
&lt;blockquote>
&lt;p>Nota de escepticismo metodológico: arXiv:2603.19296 es de &lt;strong>marzo de 2026&lt;/strong>, muy reciente, y a la fecha de este post no hay reproducciones independientes amplias. Las cifras de speedup y de calidad que circulen conviene tomarlas con la misma cautela que cualquier número sin metodología publicada: ¿qué hardware, qué tamaño de batch, qué &lt;code>Δ&lt;/code> real medido, contra qué baseline (PTQ bien calibrada o mal calibrada), en qué dominio? El argumento &lt;em>conceptual&lt;/em> —robustez a la distribución a cambio de overhead por step— es sólido; los multiplicadores concretos, pendientes de validación.&lt;/p>
&lt;/blockquote>
&lt;h2 id="qué-no-es-ttq-deslindando-del-resto-del-zoo">Qué NO es TTQ: deslindando del resto del zoo&lt;/h2>
&lt;p>TTQ se confunde fácilmente con técnicas vecinas. La distinción que importa es que &lt;strong>TTQ es el &lt;em>cómo&lt;/em> derivas las escalas, no el formato ni el momento del entrenamiento&lt;/strong>.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Técnica&lt;/th>
&lt;th>Cuándo se fijan las escalas&lt;/th>
&lt;th>Necesita corpus calibración&lt;/th>
&lt;th>Toca entrenamiento&lt;/th>
&lt;th>Es un formato&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>PTQ estática&lt;/strong> (GPTQ, AWQ)&lt;/td>
&lt;td>Offline, antes de desplegar&lt;/td>
&lt;td>Sí&lt;/td>
&lt;td>No&lt;/td>
&lt;td>No (usa INT4/INT8)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>QAT&lt;/strong>&lt;/td>
&lt;td>Durante el entrenamiento&lt;/td>
&lt;td>No (datos de train)&lt;/td>
&lt;td>Sí (re-entrena)&lt;/td>
&lt;td>No&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>FP8 end-to-end&lt;/strong>&lt;/td>
&lt;td>En runtime, pero escalas simples por tensor&lt;/td>
&lt;td>Mínimo / ninguno&lt;/td>
&lt;td>No&lt;/td>
&lt;td>&lt;strong>Sí&lt;/strong> (E4M3/E5M2)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>TTQ&lt;/strong>&lt;/td>
&lt;td>En runtime, activation-aware por token/batch&lt;/td>
&lt;td>&lt;strong>No&lt;/strong>&lt;/td>
&lt;td>No&lt;/td>
&lt;td>No (ortogonal al formato)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Las cuatro distinciones, una a una:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Frente a PTQ estática (GPTQ/AWQ).&lt;/strong> Misma meta (proteger outliers), mismo formato posible (INT4), pero PTQ congela las decisiones offline contra un corpus y TTQ las recalcula en caliente. TTQ es, en cierto sentido, &amp;ldquo;AWQ sin la fase de calibración, pagada en runtime&amp;rdquo;.&lt;/li>
&lt;li>&lt;strong>Frente a QAT.&lt;/strong> QAT mete la cuantización dentro del bucle de entrenamiento para que el modelo aprenda a ser robusto a ella; cuesta re-entrenar. TTQ no toca el entrenamiento: opera sobre un modelo ya entrenado, en inferencia. Son ataques en momentos opuestos del pipeline.&lt;/li>
&lt;li>&lt;strong>Frente a FP8 end-to-end.&lt;/strong> FP8 es un &lt;strong>formato&lt;/strong> con su propio rango logarítmico; su &amp;ldquo;dynamic scaling&amp;rdquo; calcula un escalar simple por tensor en runtime, pero no hace detección activation-aware de outliers por canal. TTQ podría, conceptualmente, derivar escalas en caliente &lt;em>para&lt;/em> un cuantizador FP8 o INT4: es ortogonal al formato.&lt;/li>
&lt;li>&lt;strong>TTQ es ortogonal al formato.&lt;/strong> Decide &lt;em>cómo&lt;/em> obtener &lt;code>s&lt;/code>, no en cuántos bits guardas &lt;code>q&lt;/code>. Puedes imaginar &amp;ldquo;TTQ sobre INT4&amp;rdquo; o &amp;ldquo;TTQ sobre FP8&amp;rdquo;. Lo que define a TTQ es la fuente de la escala —activaciones reales en caliente— no el ancho del código.&lt;/li>
&lt;/ul>
&lt;h2 id="cuándo-compensa-y-cuándo-no">Cuándo compensa (y cuándo no)&lt;/h2>
&lt;p>TTQ no es un reemplazo universal de AWQ. Es una herramienta para un perfil concreto de despliegue. &lt;strong>Compensa cuando:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>No tienes pipeline de calibración.&lt;/strong> Quieres desplegar un modelo cuantizado &lt;em>ya&lt;/em>, sin montar el dataset de calibración, ejecutar el pase offline ni validar que el corpus representa el tráfico. TTQ recorta esa fase entera: cargas el modelo y sirves.&lt;/li>
&lt;li>&lt;strong>La distribución del tráfico es cambiante o desconocida.&lt;/strong> Un asistente que un día recibe código y otro día contratos legales en otro idioma. Ninguna calibración fija cubre bien ambos; la adaptación en caliente sigue la distribución sin re-calibrar.&lt;/li>
&lt;li>&lt;strong>Multitenant sin corpus representativo.&lt;/strong> Sirves el mismo modelo a clientes con dominios dispares. No existe un corpus único que represente a todos; cualquier calibración fija crea ganadores y perdedores entre tenants. TTQ ajusta a cada input, sea del tenant que sea.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>No compensa cuando:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Tienes un dominio estable y un buen corpus de calibración.&lt;/strong> Si tu tráfico es homogéneo y representativo, AWQ offline te da la misma calidad con &lt;strong>cero overhead en runtime&lt;/strong>. Pagar &lt;code>Δ&lt;/code> en cada token para reaprender lo que un corpus ya capturó es desperdicio.&lt;/li>
&lt;li>&lt;strong>Sirves SLM con SLA de latencia ajustado.&lt;/strong> Es justo el caso donde &lt;code>Δ/T&lt;/code> es alto. Si el modelo es pequeño y el TPOT importa, el overhead de medir puede borrar la ganancia de cuantizar. Mide tu &lt;code>Δ&lt;/code> real antes de asumir que sale a cuenta.&lt;/li>
&lt;li>&lt;strong>El batch es grande y compute-bound.&lt;/strong> Con concurrencia alta el forward ya no está memory-bound y el coste de las reducciones extra compite peor; conviene al menos amortizar &lt;code>Δ&lt;/code> per-batch.&lt;/li>
&lt;/ul>
&lt;h2 id="implicaciones-en-hardware-on-premise">Implicaciones en hardware on-premise&lt;/h2>
&lt;h3 id="en-una-rtx-4090-24-gb-ada-lovelace">En una RTX 4090 (24 GB, Ada Lovelace)&lt;/h3>
&lt;p>El caso natural de la 4090 es el SLM —Qwen 3 1.5B, Llama 3 8B AWQ-INT4— sirviendo a baja concurrencia. Es precisamente el régimen donde TTQ es más arriesgado: &lt;code>T&lt;/code> por token es pequeño y la 4090 no tiene FP8 nativo acelerado (lo discutimos en el &lt;a href="https://blog.lo0.es/posts/quantization-fundamentos-inferencia/">post de quantization&lt;/a>), así que las reducciones extra de TTQ corren en CUDA cores compitiendo por el mismo tiempo. Aquí la pregunta no es &amp;ldquo;¿mejora la calidad?&amp;rdquo; sino &amp;ldquo;¿el overhead me deja un TPOT aceptable?&amp;rdquo;. Si el tráfico es homogéneo, AWQ offline gana por simplicidad y latencia. TTQ solo justifica su &lt;code>Δ&lt;/code> si la distribución de inputs es genuinamente impredecible y la degradación de la calibración fija es medible.&lt;/p>
&lt;h3 id="en-un-cluster-genérico-4h100-sxm-320-gb-nvlink-fp8-nativo">En un cluster genérico 4×H100 SXM (320 GB, NVLink, FP8 nativo)&lt;/h3>
&lt;p>Aquí el cálculo se invierte parcialmente. Con modelos grandes &lt;code>T&lt;/code> es alto y el &lt;code>Δ/T&lt;/code> baja a la zona de pocos puntos porcentuales, así que el overhead de TTQ es más digerible. El caso de uso fuerte es el &lt;strong>multitenant&lt;/strong>: un cluster que sirve un modelo grande a clientes con dominios heterogéneos, donde no hay un corpus de calibración que contente a todos. Ahí la robustez a la distribución de TTQ tiene valor real y el overhead se diluye en un forward grande. Aun así, sobre H100 con FP8 nativo, el baseline a batir es exigente: FP8 estático casi no pierde calidad (ver tabla del post de quantization) y no cuesta nada en runtime. TTQ tiene que demostrar que su ganancia de robustez en los tenants outlier supera lo que regala en overhead. Con un paper de marzo de 2026 y sin reproducciones, esa demostración está pendiente.&lt;/p>
&lt;h2 id="lo-que-no-hemos-cubierto">Lo que no hemos cubierto&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>El coste de memoria de las estadísticas en caliente&lt;/strong>: buffers por canal, su impacto en el footprint y en la presión de cache.&lt;/li>
&lt;li>&lt;strong>Interacción con continuous batching&lt;/strong>: cómo se derivan escalas cuando un batch mezcla requests de dominios distintos en el mismo step.&lt;/li>
&lt;li>&lt;strong>TTQ + speculative decoding&lt;/strong>: si el draft y el target derivan escalas en caliente por separado, y cómo afecta eso a la tasa de aceptación.&lt;/li>
&lt;li>&lt;strong>Estabilidad numérica&lt;/strong>: qué pasa cuando un batch tiene un outlier extremo puntual que infla la escala de todos los tokens de ese step.&lt;/li>
&lt;/ul>
&lt;h2 id="ver-también">Ver también&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/posts/quantization-fundamentos-inferencia/">Quantization para inferencia LLM&lt;/a> — la base imprescindible: scale + zero-point, GPTQ, AWQ y por qué los outliers de activación son el problema; TTQ es AWQ con las escalas derivadas en caliente en vez de offline.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/roofline-invertido-modelos-pequenos/">Roofline invertido para modelos pequeños&lt;/a> — por qué los costes fijos por step pesan más en SLM; explica directamente por qué el overhead &lt;code>Δ&lt;/code> de TTQ duele más en modelos pequeños.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/cuantizacion-agresiva-sub-4-bit-ternario/">Cuantización agresiva sub-4-bit y ternario&lt;/a> — la frontera estática por debajo de 4 bits; complementa a TTQ, que ataca el &lt;em>cómo&lt;/em> de la escala en vez del &lt;em>cuántos bits&lt;/em>.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/qlora-multi-lora-agresivo-slm/">QLoRA y multi-LoRA agresivo en SLM&lt;/a> — adapters sobre un base cuantizado; el base podría derivar escalas en caliente mientras los adapters van en BF16.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/fp8-end-to-end-pesos-kv-calidad/">FP8 end-to-end: pesos, KV y calidad&lt;/a> — el formato del datacenter Hopper/Blackwell; TTQ es ortogonal y podría derivar escalas para un cuantizador FP8.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/kv-cache-fundamentos/">KV cache: la memoria de trabajo de la inferencia LLM&lt;/a> — el KV cache también se cuantiza; sus escalas son otro candidato a derivarse en caliente por la misma lógica.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/knowledge-distillation-fundamentos/">Knowledge distillation&lt;/a> — la otra vía para servir modelos pequeños robustos; destilar reduce el modelo, TTQ ajusta su cuantización al tráfico.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/decode-optimizaciones-vllm/">Optimizando el decode en vLLM&lt;/a> — donde se materializan en parámetros las palancas de cuantización en runtime para exprimir una 4090.&lt;/li>
&lt;/ul>
&lt;h2 id="referencias">Referencias&lt;/h2>
&lt;ul>
&lt;li>&lt;em>TTQ: Activation-Aware Test-Time Quantization to Accelerate LLM Inference On The Fly&lt;/em> (marzo 2026). &lt;a href="https://arxiv.org/abs/2603.19296">https://arxiv.org/abs/2603.19296&lt;/a>&lt;/li>
&lt;li>Lin, J., Tang, J., Tang, H., Yang, S., Dang, X., Han, S. &lt;em>AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration&lt;/em> (MLSys 2024). &lt;a href="https://arxiv.org/abs/2306.00978">https://arxiv.org/abs/2306.00978&lt;/a>&lt;/li>
&lt;li>Frantar, E., Ashkboos, S., Hoefler, T., Alistarh, D. &lt;em>GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers&lt;/em> (ICLR 2023). &lt;a href="https://arxiv.org/abs/2210.17323">https://arxiv.org/abs/2210.17323&lt;/a>&lt;/li>
&lt;li>Xiao, G., Lin, J., Seznec, M., Wu, H., Demouth, J., Han, S. &lt;em>SmoothQuant: Accurate and Efficient Post-Training Quantization for Large Language Models&lt;/em> (ICML 2023). &lt;a href="https://arxiv.org/abs/2211.10438">https://arxiv.org/abs/2211.10438&lt;/a>&lt;/li>
&lt;li>Dettmers, T., Lewis, M., Belkada, Y., Zettlemoyer, L. &lt;em>LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale&lt;/em> (NeurIPS 2022). &lt;a href="https://arxiv.org/abs/2208.07339">https://arxiv.org/abs/2208.07339&lt;/a>&lt;/li>
&lt;/ul></description></item></channel></rss>