<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Awq on lo0 — Blog Técnico</title><link>https://blog.lo0.es/tags/awq/</link><description>Recent content in Awq on lo0 — Blog Técnico</description><generator>Hugo -- gohugo.io</generator><language>es</language><lastBuildDate>Wed, 27 May 2026 11:30:00 +0200</lastBuildDate><atom:link href="https://blog.lo0.es/tags/awq/index.xml" rel="self" type="application/rss+xml"/><item><title>Quantization para inferencia LLM: FP8, INT4 (GPTQ, AWQ) y GGUF — el zoom contable del modelo</title><link>https://blog.lo0.es/posts/quantization-fundamentos-inferencia/</link><pubDate>Wed, 27 May 2026 11:30:00 +0200</pubDate><guid>https://blog.lo0.es/posts/quantization-fundamentos-inferencia/</guid><description>&lt;blockquote>
&lt;p>Este post complementa el de &lt;a href="https://blog.lo0.es/posts/kv-cache-fundamentos/">KV cache: la memoria de trabajo de la inferencia LLM&lt;/a>, donde la cuantización del cache se menciona como una palanca de ahorro; aquí entramos al método entero —pesos del modelo y cache— y a por qué cada formato hace lo que hace.&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>Cuantizar es un cambio de representación: en lugar de guardar cada peso del modelo como un &lt;code>float16&lt;/code> o &lt;code>bfloat16&lt;/code> (2 bytes), se guarda como un entero corto (1 byte INT8, medio byte INT4) con un &lt;strong>factor de escala&lt;/strong> que reconstruye un valor aproximado al original. El precio es &lt;strong>pérdida de precisión numérica&lt;/strong>; la recompensa es &lt;strong>2-4× menos VRAM, 2-3× más throughput y, en Hopper y Blackwell, un coste de cómputo radicalmente menor&lt;/strong> porque las unidades FP8/FP4 ejecutan en menos ciclos que las BF16. Los cuatro formatos dominantes en mayo de 2026 son &lt;strong>FP8&lt;/strong> (E4M3/E5M2, datacenter), &lt;strong>INT4 GPTQ&lt;/strong> (reconstrucción Hessian-aware), &lt;strong>INT4 AWQ&lt;/strong> (activation-aware) y &lt;strong>GGUF&lt;/strong> (familia llama.cpp). Cada uno tiene un sweet spot: FP8 cuando el datacenter es Hopper/Blackwell y la calidad importa; GPTQ y AWQ cuando el serving es Ampere/Ada y los 4 bits son obligatorios; GGUF cuando el target es edge o consumer GPU. Este post explica la matemática mínima, los algoritmos detrás de cada formato, qué pierde cada uno medido en perplexity y MMLU y cómo se aplica en una 4090 frente a un cluster H100.&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;style>.box{stroke:#444;stroke-width:1.4;rx:6}.active{fill:#7ad88f;stroke-width:3}.idle{fill:#f4f4f4}.lbl{font:600 12px sans-serif;fill:#222}.arr{stroke:#666;stroke-width:1.4;fill:none;marker-end:url(#qm)}.cyc{stroke:#888;stroke-width:1.2;fill:none;stroke-dasharray:4 2;marker-end:url(#qm)}&lt;/style>
&lt;defs>&lt;marker id="qm" 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" class="lbl">Estás aquí: DEPLOY · quantization de pesos y de KV cache&lt;/text>
&lt;rect x="30" y="35" width="110" height="35" class="box idle"/>&lt;text x="85" y="58" text-anchor="middle" class="lbl">1 · Data&lt;/text>
&lt;rect x="155" y="35" width="110" height="35" class="box idle"/>&lt;text x="210" y="58" text-anchor="middle" class="lbl">2 · Tune&lt;/text>
&lt;rect x="280" y="35" width="110" height="35" class="box idle"/>&lt;text x="335" y="58" text-anchor="middle" class="lbl">3 · Eval&lt;/text>
&lt;rect x="405" y="35" width="110" height="35" class="box active"/>&lt;text x="460" y="58" text-anchor="middle" class="lbl">4 · Deploy&lt;/text>
&lt;rect x="530" y="35" width="110" height="35" class="box idle"/>&lt;text x="585" y="58" text-anchor="middle" class="lbl">5 · Observe&lt;/text>
&lt;rect x="655" y="35" width="110" height="35" class="box idle"/>&lt;text x="710" y="58" text-anchor="middle" class="lbl">6 · Retrain&lt;/text>
&lt;path class="arr" d="M140,52 L155,52"/>&lt;path class="arr" d="M265,52 L280,52"/>&lt;path class="arr" d="M390,52 L405,52"/>&lt;path class="arr" d="M515,52 L530,52"/>&lt;path class="arr" d="M640,52 L655,52"/>
&lt;path class="cyc" d="M710,72 L710,82 L85,82 L85,72"/>
&lt;/svg>
&lt;/div>
&lt;h2 id="la-analogía-el-jpeg-con-detector-de-bordes">La analogía: el JPEG con detector de bordes&lt;/h2>
&lt;p>Un JPEG comprime una imagen reduciendo la precisión con la que se almacenan los píxeles, pero no la reduce uniformemente. Donde hay un cielo plano —miles de píxeles muy parecidos—, descarta detalle sin que se note. Donde hay un borde nítido —el contorno de una cara—, conserva la fidelidad. El truco es &lt;strong>detectar qué partes son sensibles antes de comprimir&lt;/strong>.&lt;/p>
&lt;p>Quantization de un LLM funciona igual. No tomas todos los pesos del modelo y dices &amp;ldquo;todos en 4 bits&amp;rdquo;. Algunos pesos son muy importantes —pesos de proyecciones que mueven mucho la salida cuando cambian— y otros son menos. Las técnicas modernas (GPTQ, AWQ) son básicamente &lt;strong>detectores de bordes&lt;/strong>: identifican qué pesos pueden cuantizarse agresivamente y cuáles necesitan más bits o tratamiento especial, y aplican la cuantización con esa información.&lt;/p>
&lt;p>La analogía se sostiene en tres detalles:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Calibración con un pequeño dataset&lt;/strong> = la fase en la que el encoder JPEG analiza la imagen antes de elegir bloques.&lt;/li>
&lt;li>&lt;strong>Bloque de 128 pesos con un scale común&lt;/strong> = el bloque 8×8 del JPEG con su DCT.&lt;/li>
&lt;li>&lt;strong>Outliers se preservan con más precisión&lt;/strong> = las altas frecuencias de un borde se preservan más que las planicies.&lt;/li>
&lt;/ul>
&lt;p>A partir de ahí, lo que sigue es la matemática y los detalles operativos.&lt;/p>
&lt;h2 id="la-matemática-mínima-scale-y-zero-point">La matemática mínima: scale y zero-point&lt;/h2>
&lt;p>Cuantizar un vector de pesos &lt;code>w ∈ ℝ^n&lt;/code> (en BF16) a INT4 significa encontrar dos cosas:&lt;/p>
&lt;ul>
&lt;li>Un &lt;strong>scale&lt;/strong> &lt;code>s ∈ ℝ&lt;/code> (en BF16 o FP16).&lt;/li>
&lt;li>Para cada peso, un &lt;strong>código&lt;/strong> entero &lt;code>q ∈ {0, 1, ..., 15}&lt;/code> que cabe en 4 bits.&lt;/li>
&lt;/ul>
&lt;p>Y una fórmula de reconstrucción aproximada:&lt;/p>
&lt;p>$$\hat{w}_i \approx s \cdot (q_i - z)$$&lt;/p>
&lt;p>donde &lt;code>z&lt;/code> es el &lt;strong>zero-point&lt;/strong> (entero que define qué código representa el cero original). El zero-point existe en INT4/INT8 asimétrico para no desperdiciar la mitad del rango con valores negativos cuando la distribución de pesos no es simétrica.&lt;/p>
&lt;p>La elección de &lt;code>s&lt;/code> y &lt;code>z&lt;/code> para un bloque de pesos &lt;code>w_block&lt;/code>:&lt;/p>
&lt;p>$$s = \frac{\max(w_\text{block}) - \min(w_\text{block})}{2^{b} - 1}, \quad z = -\frac{\min(w_\text{block})}{s},$$&lt;/p>
&lt;p>con &lt;code>b&lt;/code> = número de bits (4 en INT4). Codificación:&lt;/p>
&lt;p>$$q_i = \text{clip}!\left(\text{round}!\left(\frac{w_i}{s} + z\right),, 0,, 2^b - 1\right).$$&lt;/p>
&lt;p>Y decodificación en inferencia:&lt;/p>
&lt;p>$$\hat{w}_i = s \cdot (q_i - z).$$&lt;/p>
&lt;h3 id="ejemplo-numérico">Ejemplo numérico&lt;/h3>
&lt;p>Tomemos 8 pesos reales de una capa lineal: &lt;code>w = [0.31, -0.12, 0.78, -0.05, 1.42, -0.91, 0.23, 0.66]&lt;/code>. Queremos cuantizar a INT4 (16 niveles).&lt;/p>
&lt;p>&lt;code>max = 1.42&lt;/code>, &lt;code>min = -0.91&lt;/code>. Rango = 2.33.&lt;/p>
&lt;p>$$s = \frac{2.33}{15} \approx 0.1553, \quad z = -\frac{-0.91}{0.1553} \approx 5.86 \to 6.$$&lt;/p>
&lt;p>Codificación de cada peso:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;code>w_i&lt;/code>&lt;/th>
&lt;th>&lt;code>w_i/s + z&lt;/code>&lt;/th>
&lt;th>&lt;code>round&lt;/code>&lt;/th>
&lt;th>&lt;code>q_i&lt;/code>&lt;/th>
&lt;th>&lt;code>ŵ_i = s·(q-z)&lt;/code>&lt;/th>
&lt;th>error&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>0.31&lt;/td>
&lt;td>8.00&lt;/td>
&lt;td>8&lt;/td>
&lt;td>8&lt;/td>
&lt;td>0.311&lt;/td>
&lt;td>+0.001&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>-0.12&lt;/td>
&lt;td>5.23&lt;/td>
&lt;td>5&lt;/td>
&lt;td>5&lt;/td>
&lt;td>-0.155&lt;/td>
&lt;td>-0.035&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0.78&lt;/td>
&lt;td>11.02&lt;/td>
&lt;td>11&lt;/td>
&lt;td>11&lt;/td>
&lt;td>0.776&lt;/td>
&lt;td>-0.004&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>-0.05&lt;/td>
&lt;td>5.68&lt;/td>
&lt;td>6&lt;/td>
&lt;td>6&lt;/td>
&lt;td>0.000&lt;/td>
&lt;td>+0.050&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1.42&lt;/td>
&lt;td>15.14&lt;/td>
&lt;td>15&lt;/td>
&lt;td>15&lt;/td>
&lt;td>1.398&lt;/td>
&lt;td>-0.022&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>-0.91&lt;/td>
&lt;td>-0.06&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>-0.932&lt;/td>
&lt;td>-0.022&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0.23&lt;/td>
&lt;td>7.48&lt;/td>
&lt;td>7&lt;/td>
&lt;td>7&lt;/td>
&lt;td>0.155&lt;/td>
&lt;td>-0.075&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0.66&lt;/td>
&lt;td>10.25&lt;/td>
&lt;td>10&lt;/td>
&lt;td>10&lt;/td>
&lt;td>0.621&lt;/td>
&lt;td>-0.039&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Error medio cuadrático: &lt;code>MSE ≈ 0.0015&lt;/code>. Para una sola capa con millones de pesos, el efecto agregado es lo que la calibración intenta minimizar.&lt;/p>
&lt;p>Storage: en lugar de 8 valores × 2 bytes = 16 bytes (BF16), tenemos 8 × 4 bits = 4 bytes + 2 bytes (scale BF16) + 0.5 byte (zero-point INT4) ≈ 6.5 bytes. &lt;strong>2.5× menos&lt;/strong>, pero el dato útil sigue siendo recuperable con error pequeño.&lt;/p>
&lt;h2 id="ptq-vs-qat-cuándo-se-cuantiza">PTQ vs QAT: cuándo se cuantiza&lt;/h2>
&lt;p>Dos regímenes operativos distintos.&lt;/p>
&lt;p>&lt;strong>Post-Training Quantization (PTQ)&lt;/strong> se aplica después del entrenamiento, sobre un modelo ya entrenado en BF16/FP16. Lee un dataset pequeño (típicamente 128-512 ejemplos) para calibrar las escalas, ejecuta el algoritmo de cuantización (GPTQ, AWQ, etc.) y produce los pesos cuantizados. &lt;strong>Coste&lt;/strong>: minutos a unas horas. &lt;strong>Pérdida típica&lt;/strong>: 0.05-0.3 PPL en perplexity (~0.5-2 % en MMLU) para INT4 con métodos modernos.&lt;/p>
&lt;p>&lt;strong>Quantization-Aware Training (QAT)&lt;/strong> introduce las operaciones de cuantización &lt;strong>dentro del bucle de entrenamiento&lt;/strong>. El modelo &amp;ldquo;ve&amp;rdquo; durante el entrenamiento que sus pesos se cuantizan y aprende a ser robusto a ello. &lt;strong>Coste&lt;/strong>: re-entrenar el modelo (caro), pero hace falta poco —fine-tune corto sobre el modelo PTQ ya cuantizado—. &lt;strong>Pérdida típica&lt;/strong>: ~0 (la cuantización se vuelve indistinguible del modelo original).&lt;/p>
&lt;p>&lt;strong>Cuándo usar cuál:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>PTQ&lt;/strong> = default. 90 % de los casos en producción. El modelo viene en BF16, lo cuantizas con 1-2 horas en una GPU, lo despliegas.&lt;/li>
&lt;li>&lt;strong>QAT&lt;/strong> = cuando PTQ pierde demasiado y la diferencia importa (caso típico: INT2/INT3, o modelos sensibles como reasoning específicos).&lt;/li>
&lt;/ul>
&lt;h2 id="los-formatos-dominantes-en-2026">Los formatos dominantes en 2026&lt;/h2>
&lt;div class="diagram" style="max-width:760px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 760 360" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Formatos de quantization dominantes">
&lt;style>
.bx{fill:#f8f8f8;stroke:#444;stroke-width:1.4;rx:8}
.b1{fill:#ffe6d6}
.b2{fill:#d6eaff}
.b3{fill:#d9f5d6}
.b4{fill:#fff5b0}
.t{font:700 13px sans-serif;fill:#222}
.s{font:400 11px sans-serif;fill:#555}
.h{font:700 14px sans-serif;fill:#222}
&lt;/style>
&lt;text x="380" y="22" text-anchor="middle" class="h">Mapa de formatos de quantization de pesos (mayo 2026)&lt;/text>
&lt;rect x="20" y="40" width="170" height="300" class="bx b1"/>
&lt;text x="105" y="62" text-anchor="middle" class="t">FP8 (E4M3/E5M2)&lt;/text>
&lt;text x="30" y="84" class="s">Datacenter / Hopper-Blackwell&lt;/text>
&lt;text x="30" y="102" class="s">— H100, H200, B200&lt;/text>
&lt;text x="30" y="120" class="s">— vLLM nativo&lt;/text>
&lt;text x="30" y="138" class="s">— hardware FP8 tensor cores&lt;/text>
&lt;text x="30" y="160" class="t">Pérdida:&lt;/text>
&lt;text x="30" y="176" class="s">— PPL: +0.02-0.05&lt;/text>
&lt;text x="30" y="192" class="s">— MMLU: -0.3-0.8 pp&lt;/text>
&lt;text x="30" y="214" class="t">Sweet spot:&lt;/text>
&lt;text x="30" y="230" class="s">Modelo serving en datacenter&lt;/text>
&lt;text x="30" y="246" class="s">moderno. Calidad casi indistinguible&lt;/text>
&lt;text x="30" y="262" class="s">de BF16, ~2× menos VRAM.&lt;/text>
&lt;text x="30" y="286" class="t">Comando vLLM:&lt;/text>
&lt;text x="30" y="304" class="s" font-family="monospace">--quantization=fp8&lt;/text>
&lt;text x="30" y="320" class="s" font-family="monospace">--kv-cache-dtype=fp8&lt;/text>
&lt;rect x="200" y="40" width="170" height="300" class="bx b2"/>
&lt;text x="285" y="62" text-anchor="middle" class="t">INT4 GPTQ&lt;/text>
&lt;text x="210" y="84" class="s">Reconstrucción Hessian-aware&lt;/text>
&lt;text x="210" y="102" class="s">— Ampere/Ada/Hopper&lt;/text>
&lt;text x="210" y="120" class="s">— vLLM, TensorRT-LLM, ExLlama&lt;/text>
&lt;text x="210" y="138" class="s">— calibración: 128 muestras&lt;/text>
&lt;text x="210" y="160" class="t">Pérdida:&lt;/text>
&lt;text x="210" y="176" class="s">— PPL: +0.15-0.30&lt;/text>
&lt;text x="210" y="192" class="s">— MMLU: -1.5-3 pp&lt;/text>
&lt;text x="210" y="214" class="t">Sweet spot:&lt;/text>
&lt;text x="210" y="230" class="s">Servings GPU sin FP8 (Ampere/Ada),&lt;/text>
&lt;text x="210" y="246" class="s">modelos medianos (8-70B).&lt;/text>
&lt;text x="210" y="262" class="s">~4× menos VRAM.&lt;/text>
&lt;text x="210" y="286" class="t">Comando vLLM:&lt;/text>
&lt;text x="210" y="304" class="s" font-family="monospace">--quantization=gptq&lt;/text>
&lt;text x="210" y="320" class="s" font-family="monospace">(modelo *-GPTQ-Int4)&lt;/text>
&lt;rect x="380" y="40" width="170" height="300" class="bx b3"/>
&lt;text x="465" y="62" text-anchor="middle" class="t">INT4 AWQ&lt;/text>
&lt;text x="390" y="84" class="s">Activation-aware salient weights&lt;/text>
&lt;text x="390" y="102" class="s">— Ampere/Ada/Hopper&lt;/text>
&lt;text x="390" y="120" class="s">— vLLM, TensorRT-LLM&lt;/text>
&lt;text x="390" y="138" class="s">— preserva 1 % outlier channels&lt;/text>
&lt;text x="390" y="160" class="t">Pérdida:&lt;/text>
&lt;text x="390" y="176" class="s">— PPL: +0.10-0.25&lt;/text>
&lt;text x="390" y="192" class="s">— MMLU: -1-2 pp&lt;/text>
&lt;text x="390" y="214" class="t">Sweet spot:&lt;/text>
&lt;text x="390" y="230" class="s">Alternativa preferida a GPTQ&lt;/text>
&lt;text x="390" y="246" class="s">en 2026. Mejor preservación de&lt;/text>
&lt;text x="390" y="262" class="s">calidad con coste similar.&lt;/text>
&lt;text x="390" y="286" class="t">Comando vLLM:&lt;/text>
&lt;text x="390" y="304" class="s" font-family="monospace">--quantization=awq_marlin&lt;/text>
&lt;text x="390" y="320" class="s" font-family="monospace">(modelo *-AWQ-INT4)&lt;/text>
&lt;rect x="560" y="40" width="180" height="300" class="bx b4"/>
&lt;text x="650" y="62" text-anchor="middle" class="t">GGUF (llama.cpp)&lt;/text>
&lt;text x="570" y="84" class="s">Edge / consumer / CPU-friendly&lt;/text>
&lt;text x="570" y="102" class="s">— CPU, Apple Silicon,&lt;/text>
&lt;text x="570" y="120" class="s"> consumer GPU (4090, AMD)&lt;/text>
&lt;text x="570" y="138" class="s">— sub-formatos: Q4_K_M, Q5_K_M…&lt;/text>
&lt;text x="570" y="160" class="t">Pérdida (Q4_K_M):&lt;/text>
&lt;text x="570" y="176" class="s">— PPL: +0.20-0.40&lt;/text>
&lt;text x="570" y="192" class="s">— MMLU: -2-4 pp&lt;/text>
&lt;text x="570" y="214" class="t">Sweet spot:&lt;/text>
&lt;text x="570" y="230" class="s">Cualquier deploy no-CUDA o&lt;/text>
&lt;text x="570" y="246" class="s">con VRAM limitada. Ollama, LMStudio.&lt;/text>
&lt;text x="570" y="262" class="s">~4× menos VRAM/RAM.&lt;/text>
&lt;text x="570" y="286" class="t">Comando:&lt;/text>
&lt;text x="570" y="304" class="s" font-family="monospace">ollama run llama3:8b-q4_K_M&lt;/text>
&lt;text x="570" y="320" class="s" font-family="monospace">llama.cpp --model *.gguf&lt;/text>
&lt;/svg>
&lt;/div>
&lt;h2 id="fp8-el-formato-del-datacenter-hopperblackwell">FP8: el formato del datacenter Hopper/Blackwell&lt;/h2>
&lt;p>FP8 no es &amp;ldquo;INT8 + signo&amp;rdquo;: son &lt;strong>dos formatos en coma flotante de 8 bits&lt;/strong>.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>E4M3&lt;/strong> (4 bits de exponente, 3 de mantisa): rango ±448, precisión razonable en ±1.0. Usado típicamente para &lt;strong>pesos&lt;/strong> y &lt;strong>activaciones de la mayoría de capas&lt;/strong>.&lt;/li>
&lt;li>&lt;strong>E5M2&lt;/strong> (5 de exponente, 2 de mantisa): rango ±57 344, precisión menor. Usado para &lt;strong>gradientes&lt;/strong> durante entrenamiento o para activaciones con outliers grandes en inferencia.&lt;/li>
&lt;/ul>
&lt;p>Por qué FP8 dejó atrás a INT8 en datacenter: las tensor cores de H100/H200/B200 ejecutan operaciones FP8 nativamente con throughput &lt;strong>2× el de BF16&lt;/strong> y &lt;strong>4× el de FP16&lt;/strong>. Y, como FP8 preserva la dinámica logarítmica (igual que FP16), las matrices con valores dispersos en magnitud (típicas de transformers) se cuantizan con menos error que INT8.&lt;/p>
&lt;p>La pérdida medida en producción es &lt;strong>mínima&lt;/strong>: para un Llama 3.1 70B FP8 vs BF16, la perplexity sube ~0.03 y MMLU cae ~0.5 puntos. Es la opción de default en cualquier deploy moderno sobre H100/B200.&lt;/p>
&lt;h3 id="microscaling-nvfp4-y-mxfp4">Microscaling: NVFP4 y MXFP4&lt;/h3>
&lt;p>Blackwell (B100/B200, 2025) introduce &lt;strong>NVFP4&lt;/strong> y &lt;strong>MXFP4&lt;/strong>, formatos de 4 bits con &lt;strong>scaling por bloque pequeño&lt;/strong> (típicamente 16 ó 32 elementos por scale, frente a 128 en INT4 GPTQ). El scale es FP8 en lugar de FP16/BF16, lo que reduce más el storage.&lt;/p>
&lt;p>Resultado: &lt;strong>4 bits con calidad cercana a FP8&lt;/strong>. NVFP4 se está convirtiendo en 2026 en la opción de default para modelos muy grandes (200B+) en clusters Blackwell. Para 4×H100 SXM —Hopper, no Blackwell— sigue siendo FP8 el sweet spot.&lt;/p>
&lt;h2 id="int4-gptq-vs-awq">INT4: GPTQ vs AWQ&lt;/h2>
&lt;p>Los dos algoritmos que dominan la cuantización a 4 bits resuelven el mismo problema con estrategias distintas.&lt;/p>
&lt;h3 id="gptq-frantar-et-al-2022">GPTQ (Frantar et al. 2022)&lt;/h3>
&lt;p>La idea: cuantización &lt;strong>capa por capa&lt;/strong>, minimizando explícitamente el error en la salida de cada capa lineal usando información de la &lt;strong>matriz Hessiana&lt;/strong> (segunda derivada de la pérdida). Para cada capa:&lt;/p>
&lt;ol>
&lt;li>Estima la Hessiana &lt;code>H = X^T X&lt;/code> donde &lt;code>X&lt;/code> son las activaciones de calibración.&lt;/li>
&lt;li>Cuantiza un peso a la vez por orden (típicamente el más sensible primero).&lt;/li>
&lt;li>&lt;strong>Actualiza los pesos restantes&lt;/strong> para compensar el error del peso que se acaba de cuantizar.&lt;/li>
&lt;/ol>
&lt;p>El paso 3 es lo que hace a GPTQ mejor que el round-to-nearest naive: los pesos compensan los errores de sus vecinos. La implementación oficial cuantiza un Llama 3 70B en ~3-4 horas en una H100 con 128 muestras de calibración.&lt;/p>
&lt;h3 id="awq-lin-et-al-2023">AWQ (Lin et al. 2023)&lt;/h3>
&lt;p>La observación de AWQ: dentro de una capa, &lt;strong>no todos los canales (columnas de pesos) son igual de importantes&lt;/strong>. Aproximadamente el &lt;strong>1 % de los canales&lt;/strong> acumulan la mayoría del impacto en las activaciones. AWQ los identifica midiendo la magnitud media de las activaciones que los multiplican, y los &lt;strong>escala antes de cuantizar&lt;/strong> para preservarlos mejor.&lt;/p>
&lt;p>Concretamente: si un canal &lt;code>c&lt;/code> tiene activaciones medias grandes, AWQ multiplica los pesos de ese canal por un factor &lt;code>s_c&lt;/code> antes de cuantizar, y al cuantizar la entrada del siguiente layer la divide por &lt;code>s_c&lt;/code>. La matemática se cancela, pero los pesos importantes acaban con más resolución dentro del rango INT4. Sin re-entrenamiento, sin Hessiana, más rápido que GPTQ (~1-2 h para 70B en H100).&lt;/p>
&lt;h3 id="cuál-elegir">Cuál elegir&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Criterio&lt;/th>
&lt;th>GPTQ&lt;/th>
&lt;th>AWQ&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Velocidad de cuantización&lt;/td>
&lt;td>Más lento&lt;/td>
&lt;td>Más rápido&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Calidad preservada en INT4&lt;/td>
&lt;td>Bueno&lt;/td>
&lt;td>Ligeramente mejor (~0.05-0.1 PPL)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Hardware soportado&lt;/td>
&lt;td>Amplio (Ampere+)&lt;/td>
&lt;td>Amplio (Ampere+)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Ecosistema&lt;/td>
&lt;td>Maduro, ampliamente integrado&lt;/td>
&lt;td>Más reciente, ganando terreno&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Default 2026&lt;/td>
&lt;td>Cuando ya hay artefactos GPTQ&lt;/td>
&lt;td>Default para nuevas cuantizaciones&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>La regla práctica en mayo de 2026: &lt;strong>AWQ por defecto&lt;/strong> para INT4 nuevo. &lt;strong>GPTQ&lt;/strong> cuando ya hay un artefacto GPTQ-Int4 publicado por la comunidad que satisface tus requisitos.&lt;/p>
&lt;h2 id="gguf-el-ecosistema-llamacpp">GGUF: el ecosistema llama.cpp&lt;/h2>
&lt;p>GGUF no es un algoritmo de cuantización, es &lt;strong>un formato de archivo&lt;/strong> —y un ecosistema completo de herramientas— alrededor del runtime llama.cpp.&lt;/p>
&lt;p>Su valor: &lt;strong>compatibilidad universal&lt;/strong>. Un mismo archivo GGUF se ejecuta en:&lt;/p>
&lt;ul>
&lt;li>CPU pura (Intel/AMD x86, ARM).&lt;/li>
&lt;li>Apple Silicon (M1/M2/M3/M4) con aceleración Metal.&lt;/li>
&lt;li>Consumer GPU (RTX, AMD Radeon) con offload de capas a VRAM.&lt;/li>
&lt;li>Edge devices (Jetson, móviles ARM).&lt;/li>
&lt;/ul>
&lt;p>Eso es lo que llama.cpp permite y vLLM/TensorRT-LLM no. La contrapartida: &lt;strong>menos throughput máximo&lt;/strong> en GPU datacenter que vLLM.&lt;/p>
&lt;p>Sub-formatos GGUF más usados en 2026:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Sub-formato&lt;/th>
&lt;th>Bits efectivos&lt;/th>
&lt;th>Calidad relativa&lt;/th>
&lt;th>Uso típico&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>Q8_0&lt;/code>&lt;/td>
&lt;td>8.5&lt;/td>
&lt;td>Casi sin pérdida&lt;/td>
&lt;td>Validación de baseline&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Q6_K&lt;/code>&lt;/td>
&lt;td>6.6&lt;/td>
&lt;td>Pérdida muy pequeña&lt;/td>
&lt;td>Calidad alta + ahorro&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Q5_K_M&lt;/code>&lt;/td>
&lt;td>5.7&lt;/td>
&lt;td>Pérdida pequeña&lt;/td>
&lt;td>Sweet spot calidad/tamaño&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Q4_K_M&lt;/code>&lt;/td>
&lt;td>4.8&lt;/td>
&lt;td>Pérdida moderada&lt;/td>
&lt;td>&lt;strong>Default consumer&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Q4_K_S&lt;/code>&lt;/td>
&lt;td>4.5&lt;/td>
&lt;td>Pérdida moderada-alta&lt;/td>
&lt;td>Cuando no cabe Q4_K_M&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Q3_K_M&lt;/code>&lt;/td>
&lt;td>3.9&lt;/td>
&lt;td>Pérdida notable&lt;/td>
&lt;td>Hardware muy restringido&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Q2_K&lt;/code>&lt;/td>
&lt;td>3.3&lt;/td>
&lt;td>Pérdida grande&lt;/td>
&lt;td>Último recurso&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>El subíndice &lt;code>_K_M&lt;/code> indica el grado de mixto: dentro del archivo, ciertas capas (típicamente &lt;code>attention.wv&lt;/code>, &lt;code>feed_forward.w2&lt;/code>) se guardan con más bits que otras. Es el equivalente al &amp;ldquo;detector de bordes&amp;rdquo; del JPEG aplicado capa-a-capa por heurística pre-establecida.&lt;/p>
&lt;h2 id="kv-cache-quantization">KV cache quantization&lt;/h2>
&lt;p>Cuantizar los pesos del modelo es la mitad del problema. El &lt;strong>KV cache&lt;/strong> —cubierto en detalle en &lt;a href="https://blog.lo0.es/posts/kv-cache-fundamentos/">KV cache: la memoria de trabajo&lt;/a>— consume típicamente &lt;strong>20-50 %&lt;/strong> de la VRAM en producción con concurrencia. Cuantizar el cache también es una palanca:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>--kv-cache-dtype=auto&lt;/code>&lt;/strong> (BF16/FP16, default). 2 bytes por dimensión × num_heads × head_dim × 2 (K y V).&lt;/li>
&lt;li>&lt;strong>&lt;code>--kv-cache-dtype=fp8&lt;/code>&lt;/strong> (E4M3 o E5M2 según hardware). 1 byte. &lt;strong>Divide el cache por 2&lt;/strong> con pérdida típica de &amp;lt; 0.5 % en quality benchmarks.&lt;/li>
&lt;li>&lt;strong>&lt;code>--kv-cache-dtype=int4&lt;/code>&lt;/strong> (con bloques de 128). 0.5 bytes + overhead de scale. &lt;strong>Divide el cache por ~3.5&lt;/strong>. Pérdida medible (1-2 %) pero aceptable en contextos largos.&lt;/li>
&lt;/ul>
&lt;p>La cuantización del KV cache es &lt;strong>ortogonal&lt;/strong> a la cuantización de pesos: puedes tener pesos BF16 y cache FP8, o pesos INT4 y cache FP8, etc. La combinación dominante en 2026 sobre H100: &lt;strong>pesos FP8 + cache FP8&lt;/strong>, que casi indistinguible de BF16 en calidad y duplica capacidad de concurrencia.&lt;/p>
&lt;h2 id="pérdida-de-calidad-medida-llama-31-70b-instruct-referencia">Pérdida de calidad medida (Llama 3.1 70B Instruct, referencia)&lt;/h2>
&lt;p>Tabla representativa para Llama 3.1 70B Instruct con dataset de calibración WikiText-2 (128 muestras). Cifras de fuentes públicas agregadas; pueden variar ±0.05 PPL y ±0.5 MMLU según implementación y seed.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Formato&lt;/th>
&lt;th>VRAM modelo&lt;/th>
&lt;th>Perplexity (WikiText-2)&lt;/th>
&lt;th>MMLU (5-shot)&lt;/th>
&lt;th>Velocidad relativa (H100)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>BF16 (baseline)&lt;/td>
&lt;td>140 GB&lt;/td>
&lt;td>4.85&lt;/td>
&lt;td>82.1&lt;/td>
&lt;td>1.00×&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>FP8 (E4M3)&lt;/td>
&lt;td>70 GB&lt;/td>
&lt;td>4.87 (+0.02)&lt;/td>
&lt;td>81.6 (-0.5)&lt;/td>
&lt;td>1.85×&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>INT8 SmoothQuant&lt;/td>
&lt;td>70 GB&lt;/td>
&lt;td>4.92 (+0.07)&lt;/td>
&lt;td>81.0 (-1.1)&lt;/td>
&lt;td>1.65×&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>INT4 AWQ&lt;/td>
&lt;td>35 GB&lt;/td>
&lt;td>4.99 (+0.14)&lt;/td>
&lt;td>80.4 (-1.7)&lt;/td>
&lt;td>2.50×&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>INT4 GPTQ&lt;/td>
&lt;td>35 GB&lt;/td>
&lt;td>5.05 (+0.20)&lt;/td>
&lt;td>80.0 (-2.1)&lt;/td>
&lt;td>2.40×&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>GGUF Q5_K_M&lt;/td>
&lt;td>49 GB&lt;/td>
&lt;td>4.94 (+0.09)&lt;/td>
&lt;td>81.1 (-1.0)&lt;/td>
&lt;td>n/a (llama.cpp)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>GGUF Q4_K_M&lt;/td>
&lt;td>42 GB&lt;/td>
&lt;td>5.08 (+0.23)&lt;/td>
&lt;td>79.8 (-2.3)&lt;/td>
&lt;td>n/a (llama.cpp)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>GGUF Q3_K_M&lt;/td>
&lt;td>33 GB&lt;/td>
&lt;td>5.45 (+0.60)&lt;/td>
&lt;td>77.5 (-4.6)&lt;/td>
&lt;td>n/a (llama.cpp)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Tres lecciones a retener:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>FP8 es casi gratis en calidad&lt;/strong>. Si tu hardware lo soporta, no hay debate.&lt;/li>
&lt;li>&lt;strong>INT4 AWQ es notablemente mejor que INT4 GPTQ&lt;/strong> en calidad preservada, a velocidad comparable.&lt;/li>
&lt;li>&lt;strong>Q3 ya es zona de pérdida medible&lt;/strong>; Q2 ya no se debería usar excepto para experimentos o demos extremas.&lt;/li>
&lt;/ol>
&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-sin-fp8-nativo">En una RTX 4090 (24 GB, Ada Lovelace, sin FP8 nativo)&lt;/h3>
&lt;p>Llama 3.1 8B Instruct entra holgadamente en BF16 (16 GB), pero queda poco margen para KV cache con concurrencia. El sweet spot habitual:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Llama 3.1 8B AWQ-INT4&lt;/strong>: ~5 GB de pesos, 19 GB libres para KV cache → 4-8 sesiones concurrentes con contexto moderado.&lt;/li>
&lt;li>&lt;strong>Llama 3 70B GGUF Q4_K_M&lt;/strong>: ~42 GB. &lt;strong>No cabe en la 4090 entera&lt;/strong>; requiere offload a CPU con llama.cpp (decode lento pero funcional para single-user).&lt;/li>
&lt;li>&lt;strong>Llama 3 70B AWQ-INT4 con TP=2 (dos 4090)&lt;/strong>: ~17 GB cada GPU → cabe y queda margen.&lt;/li>
&lt;/ul>
&lt;p>La 4090 &lt;strong>no soporta FP8 nativo&lt;/strong> (Ada Lovelace tiene la instrucción pero sin el throughput acelerado de Hopper). En la práctica, FP8 en 4090 funciona pero sin la ganancia de velocidad: la elección razonable es INT4 AWQ.&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í FP8 brilla:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Llama 3.1 70B FP8&lt;/strong> con TP=2: ~35 GB/GPU. Holgado, deja espacio enorme para KV cache → docenas de sesiones concurrentes.&lt;/li>
&lt;li>&lt;strong>Llama 3.1 405B FP8&lt;/strong> con TP=4: ~200 GB/GPU. Cabe justo, con prefill+decode en el mismo pool.&lt;/li>
&lt;li>&lt;strong>Llama 3.1 405B INT4 AWQ&lt;/strong> con TP=2: ~100 GB/GPU. Permite serving del modelo grande sin saturar el cluster; queda margen para cache y para servir simultáneamente otro modelo.&lt;/li>
&lt;/ul>
&lt;p>La regla de pulgar en cluster H100 en 2026: &lt;strong>FP8 si la calidad importa y el modelo cabe; INT4 AWQ si el modelo no cabe en FP8 o si quieres más concurrencia a costa de 1-2 puntos MMLU&lt;/strong>.&lt;/p>
&lt;h2 id="lo-que-no-hemos-cubierto-próximos-artículos">Lo que no hemos cubierto (próximos artículos)&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Speculative decoding&lt;/strong>: la otra palanca grande de aceleración en inferencia. Ortogonal a quantization, multiplica el speedup.&lt;/li>
&lt;li>&lt;strong>MoE quantization&lt;/strong>: los modelos Mixture-of-Experts (Mixtral, DeepSeek V3, Qwen3-235B-A22B) tienen patrones de quantization distintos —los expertos no se cuantizan uniformemente, hay rutado dinámico—.&lt;/li>
&lt;li>&lt;strong>Calibration dataset matters&lt;/strong>: cómo elegir las 128-512 muestras de calibración. El error común de coger un dataset random de internet y cómo evitarlo.&lt;/li>
&lt;li>&lt;strong>Multimodal quantization&lt;/strong>: los modelos vision-language tienen capas heterogéneas (vision encoder en CNN, language en transformer) que necesitan tratamiento separado.&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/kv-cache-fundamentos/">KV cache: la memoria de trabajo que sostiene la inferencia LLM&lt;/a> — el cache también se cuantiza con los mismos formatos (FP8, INT4); este post entra al detalle de cómo y con qué pérdida.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/disaggregated-serving-prefill-decode/">Disaggregated serving: prefill y decode en pods especializados&lt;/a> — los dos pools pueden cuantizarse &lt;strong>asimétricamente&lt;/strong>: prefill con menos compresión (calidad), decode con más compresión (throughput).&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/fine-tuning-continuo-produccion/">Fine-tuning continuo en producción&lt;/a> — QLoRA usa NF4, una variante específica de INT4 para el modelo base durante entrenamiento. Es el primo de los formatos de inferencia descritos aquí.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/alignment-moderno-dpo-kto-orpo-simpo/">Alignment moderno: DPO, KTO, ORPO, SimPO&lt;/a> — el modelo de referencia (&lt;code>π_ref&lt;/code>) en DPO puede cuantizarse a FP8 para liberar VRAM. La calidad de la referencia importa menos que la del modelo entrenado.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/pipeline-llmops-seis-etapas/">El pipeline LLMOps de seis etapas&lt;/a> — el mapa maestro donde Deploy es la etapa 4.&lt;/li>
&lt;/ul>
&lt;h2 id="referencias">Referencias&lt;/h2>
&lt;ul>
&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;/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;/li>
&lt;li>Dettmers, T., Pagnoni, A., Holtzman, A., Zettlemoyer, L. &lt;em>QLoRA: Efficient Finetuning of Quantized LLMs&lt;/em> (NeurIPS 2023). Introduce NF4 y double quantization.&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;/li>
&lt;li>NVIDIA. &lt;em>FP8 Formats for Deep Learning&lt;/em> — white paper E4M3/E5M2: &lt;a href="https://arxiv.org/abs/2209.05433">https://arxiv.org/abs/2209.05433&lt;/a>.&lt;/li>
&lt;li>Rouhani, B. et al. &lt;em>Microscaling Data Formats for Deep Learning&lt;/em> — MXFP4/MXFP8: &lt;a href="https://arxiv.org/abs/2310.10537">https://arxiv.org/abs/2310.10537&lt;/a>.&lt;/li>
&lt;li>llama.cpp GGUF spec: &lt;a href="https://github.com/ggerganov/llama.cpp/blob/master/docs/gguf.md">https://github.com/ggerganov/llama.cpp/blob/master/docs/gguf.md&lt;/a>.&lt;/li>
&lt;li>vLLM quantization docs: &lt;a href="https://docs.vllm.ai/en/latest/quantization/">https://docs.vllm.ai/en/latest/quantization/&lt;/a>.&lt;/li>
&lt;/ul></description></item></channel></rss>