Skip to content

English · Español

04 — AWQ, SmoothQuant, LLM.int8(): un repaso

Tres trucos que la industria usa pero que Borja no implementa en esta fase: AWQ pondera por importancia de activación, SmoothQuant migra magnitud de activación a peso, LLM.int8() separa los outliers a una ruta FP16. Aquí entendemos el porqué; el código se queda como lectura.


Por qué un repaso

La Fase 26 implementa dos cosas desde cero: INT8 solo de pesos (por-tensor + por-canal) y GPTQ sobre una sola capa Linear. Los papers de abajo son contexto esencial pero sus implementaciones completas triplicarían el wall-clock de la fase sin una ganancia pedagógica proporcional. Léelos; reproduce una figura mentalmente; sigue adelante.

LLM.int8() (Dettmers et al., 2022)

Observación. Las activaciones de capas transformer grandes contienen outliers sistemáticos concentrados en un número pequeño de dimensiones de features (~0.1% de dimensiones, pero alcanzan magnitudes 20× la activación típica). Estos features outlier dominan la salida de atención para algunos tokens específicos.

Problema. Una cuantización INT8 por-tensor ingenua de las activaciones elige s según el máximo — que lo fijan los outliers. El 99.9% de las activaciones típicas se cuantiza a una resolución que resuelve solo ~5 valores distintos de 256.

Solución. Detectar las columnas outlier en calibración. Para esas columnas: hacer el matmul en FP16. Para el resto: hacer el matmul en INT8. Sumar los dos resultados parciales. La división añade sobrecarga de latencia, pero la ruta INT8 del caso típico ya usa todo su rango dinámico.

Trade-off. ~0.5% de gap de PPL respecto a FP32 en INT8; ~2× más rápido en GPUs Ampere (sin kernel equivalente en la CPU de Borja); ~50% de ahorro de memoria.

Por qué no lo implementamos. La detección de outliers + el forward de doble ruta son ~500 líneas de código con casos borde para cada Linear, y la ganancia de velocidad solo se materializa en hardware que Borja no tiene. Léelo; entiende el principio; sigue.

SmoothQuant (Xiao et al., 2022)

Observación. Los outliers en LLM.int8() están en activaciones, no en pesos. Los pesos están bien comportados. Pero el matmul y = W x es simétrico en W y x — podemos mover magnitud de uno a otro sin cambiar la matemática:

\[ W x = (W \text{diag}(s)) \text{diag}(s^{-1}) x = W' x' \]

Si elegimos s_j para encoger el canal outlier de x y crecer la columna correspondiente de W por el mismo factor, x' queda bien comportado (sin outliers) y W' queda ligeramente menos bien comportado (pero los pesos tenían margen).

Solución. Calcular estadísticas por-canal de x con datos de calibración; elegir s_j = max(|x_j|)^\alpha para algún \alpha ∈ [0, 1] (el paper usa 0.5); aplicar el rescalado diagonal offline; cuantizar el resultado con INT8 por-tensor vainilla.

Trade-off. Comparable a LLM.int8() en calidad; más simple en inferencia (ruta única); requiere calibración offline para calcular s. El rescalado se puede plegar en los pesos de la capa anterior así que no hay sobrecarga en runtime.

Por qué no lo implementamos. El pliegue-en-la-capa-anterior requiere conciencia de grafo completo — cada Linear en MiniGPT debe visitarse, las escalas propagarse, las rutas residuales gestionarse. No vale la carga de ingeniería para el contenido pedagógico.

AWQ (Lin et al., 2023)

Observación. No todos los pesos importan por igual. Los pesos "importantes" son los que interactúan con activaciones de gran magnitud. Preservar estos a precisión completa y cuantizar el resto agresivamente da un mejor trade-off calidad/byte que la cuantización uniforme.

Solución. Identificar el ~1% de canales de pesos más importantes (por magnitud de activación en calibración) y aplicar un escalado por-canal que aumente su resolución efectiva bajo cuantización INT4. El escalado es matemáticamente equivalente al rescalado diagonal de SmoothQuant, pero aplicado por la razón opuesta: SmoothQuant aleja la magnitud de sitios sensibles; AWQ mueve resolución de cuantización hacia pesos importantes.

El detalle ingenioso: AWQ no necesita saber qué pesos son importantes como pesos — lo sabe leyendo las estadísticas de activación. Esto significa que AWQ está guiado por datos de calibración, como GPTQ, pero es muchísimo más barato (sin Cholesky de la Hessiana).

Trade-off. Comparable a GPTQ en INT4; más rápido de calcular (sin Hessiana); requiere elegir \alpha (el exponente de escalado) por capa.

Por qué no lo implementamos. Confundible con SmoothQuant — comparten maquinaria, difieren en motivación. Pedagógicamente es más claro implementar GPTQ (una historia matemática limpia) y repasar AWQ.

Qué enseña el repaso

Tres patrones se repiten:

  1. Mira las activaciones, no solo los pesos. El PTQ ingenuo trata los pesos como el problema; el PTQ moderno trata las distribuciones de activación como el problema.
  2. Usa datos de calibración. Cada esquema moderno gasta 100–500 forward passes sobre entradas representativas para extraer estadísticas. El coste es fijo y pequeño; el aumento de calidad es grande.
  3. Pliega el rescalado por-canal en ops adyacentes en tiempo de despliegue. Sin sobrecarga en runtime de ninguno de estos esquemas. La astucia está en el offline.

Si Borja se queda con una cosa de este fichero: la diferencia entre un PTQ "de investigación" y uno "de producción" suele ser 1–2 días de código de pre-procesado, no un algoritmo fundamentalmente distinto. El GPTQ + INT8 por-canal escrito a mano de la Fase 26 cubre el núcleo algorítmico; los extras de producción son ingeniería encima.

Checklist de lectura

Para el fichero de reflexiones al cierre de fase:

  • Hojear LLM.int8() (secciones 1–3) — criterio de detección de outliers.
  • Hojear SmoothQuant (secciones 1–4) — la identidad del rescalado diagonal.
  • Hojear AWQ (secciones 1–4) — la puntuación de importancia basada en calibración.
  • Hojear GPTQ (secciones 1–4 — ya implementado, pero releer la prueba de optimalidad).
  • Hojear QLoRA (sección 3 — NF4 + doble cuantización).

Opcional: hojear "A Survey of Quantization Methods for Efficient Neural Network Inference" (Gholami et al., 2021) para la historia pre-LLM.

Preguntas de práctica (sin código)

  1. El rescalado diagonal de SmoothQuant W' = W \text{diag}(s), x' = \text{diag}(s^{-1}) x. Demuestra que esto es exactamente equivalente a aplicar s_j a la j-ésima columna de W y dividir el j-ésimo elemento de x por s_j. Después explica por qué esto cambia la dificultad de cuantización aunque la salida del matmul no cambie.
  2. AWQ elige s_j basado en \max |x_j|^{\alpha} en lugar de \max |x_j|^1. ¿Por qué fraccionario? (Pista: piensa en qué harían \alpha = 0 y \alpha = 1 a las magnitudes de los pesos.)
  3. LLM.int8() separa ~0.1% de columnas de features a una ruta FP16. La sobrecarga reportada es ~10% de latencia en Ampere. Estima la sobrecarga de latencia en el i5-8250U de Borja, asumiendo que su ruta INT8-dequant-a-FP32 con AVX2 es aproximadamente 0.5× del throughput de FP32 y que FP16 no tiene soporte hardware (así que FP16 se emula como FP32). Resultado: ¿LLM.int8() sería una ganancia en el hardware de Borja? (Pista: no.)

Resumen en un párrafo

Tres esquemas PTQ de nivel producción — LLM.int8(), SmoothQuant, AWQ — comparten una observación común: los outliers de activación, no los pesos, son el problema. Cada uno los maneja distinto: ruta FP16 separada (LLM.int8()), redistribuir magnitud hacia los pesos (SmoothQuant), redistribuir resolución de cuantización hacia los pesos importantes (AWQ). En la Fase 26 los leemos pero no los implementamos — el núcleo algorítmico que comparten con GPTQ es lo que importa pedagógicamente. El Lab 02 (el barrido de la curva de cuantización) hará aflorar el comportamiento de outliers de activación en MiniGPT empíricamente, aunque no lo arreglemos.

Siguiente: lab/00-int8-ptq.md.