English · Español
01 — Formatos numéricos: FP32, FP16, BF16, TF32, FP8¶
Antes de cuantizar a entero, hay que entender qué hace cada formato flotante: cuántos bits para exponente (rango dinámico) y cuántos para mantisa (precisión). BF16 sacrifica precisión por rango; FP16 al revés; FP8 los empuja a ambos al límite.
La anatomía IEEE 754¶
Cualquier valor en coma flotante finito se codifica con tres campos:
Valor decodificado: (-1)^sign × 1.mantissa × 2^(exponent - bias).
Tres palancas:
- Total de bits = 1 + E + M. Determina la huella de memoria por valor.
- Bits de exponente E — determina el rango dinámico. Máximo representable ≈
2^(2^(E-1)). Mínimo normal ≈2^(-(2^(E-1)-2)). - Bits de mantisa M — determina la precisión. Epsilon de máquina ≈
2^(-M).
Los cuatro formatos relevantes para este currículo:
| Formato | Total | E | M | Rango (aprox) | Eps (aprox) | Cuándo usar |
|---|---|---|---|---|---|---|
| FP32 | 32 | 8 | 23 | ±3.4e38 | 1.2e-7 | Precisión de referencia. Entrenamiento por defecto hasta la Fase 23. |
| FP16 | 16 | 5 | 10 | ±6.5e4 | 9.8e-4 | Inferencia + entrenamiento mixto en GPU. Rango ajustado — overflow durante el pre-softmax de atención. |
| BF16 | 16 | 8 | 7 | ±3.4e38 | 7.8e-3 | Entrenamiento (TPUs, A100+). Mismo rango que FP32; intercambia mantisa por seguridad. |
| FP8 (E4M3) | 8 | 4 | 3 | ±448 | 0.125 | Entrenamiento Hopper+; no utilizable en la CPU de Borja. Solo repaso. |
Por qué BF16 ganó en entrenamiento¶
El cambio histórico de FP16 a BF16 en el entrenamiento de modelos grandes es en sí mismo una historia adyacente al roofline, pero la razón dominante es el rango dinámico de los gradientes.
Un gradiente FP16 que hace overflow se convierte en +inf y envenena el optimizador; el remedio FP16 estándar es loss scaling (multiplicar la pérdida por 2^k, hacer el backward en FP16, deshacer la escala antes del paso del optimizador). Funciona pero requiere el hiperparámetro del dynamic-loss-scaler.
El rango de exponente de BF16 coincide con el de FP32. Los gradientes no hacen overflow. La pérdida de precisión (7 bits de mantisa) es aceptable porque las actualizaciones de gradiente son ruidosas de todos modos — los promedios móviles de Adam absorben el redondeo. No hace falta loss scaler.
Para inferencia, el trade-off se invierte. FP16 es aceptable porque no se calculan gradientes; las activaciones están bien comportadas (tras el entrenamiento). BF16 también es aceptable pero raramente mejora de forma significativa la latencia de inferencia.
Dónde duele FP16: el pre-softmax en atención¶
El cálculo de atención softmax(Q Kᵀ / √d) V tiene un punto patológico: Q Kᵀ produce valores cuyas magnitudes escalan con d (la dimensión de la cabeza) antes de la corrección 1/√d. Para d=64, un único producto escalar de dos vectores unitarios cae en [-8, 8]. Para secuencias muy largas con embeddings de gran magnitud, los valores intermedios pueden llegar a 60+. exp(60) ≈ 1e26 — muy por encima del máximo de FP16 (6.5e4). Resultado: inf, luego nan, luego inestabilidad de entrenamiento.
Por esto el softmax online de FlashAttention (Fase 27) mantiene un máximo en curso y lo resta: exp(x_i − max) ∈ (0, 1] es seguro en FP16.
Para el PTQ de la Fase 26 no tocamos la dinámica de atención en FP16 — el modelo ya está entrenado — pero sí observamos que cuantizar la atención a INT8 necesita la misma precaución: los outliers en Q Kᵀ hinchan la escala (scale) INT8 por-tensor. Esto motiva la cuantización por-canal (fichero de teoría 02).
INT8 / INT4 en esta jerarquía¶
INT8 e INT4 no son formatos IEEE 754. Son enteros normales en complemento a dos con una escala (scale) externa s (y opcionalmente un cero (zero-point) z) compartida entre alguna unidad de valores (por-tensor, por-canal, por-grupo):
representable values: {-128, -127, ..., -1, 0, 1, ..., 127} (INT8, symmetric)
decoded value: x̂ = s × q (symmetric, scale stored separately)
Comparado con los 65 536 valores distintos de FP16 en el mismo rango, los 256 valores distintos de INT8 son una rejilla de cuantización 256× más gruesa. La ganga es: la mayoría de las distribuciones de pesos están lejos de ser uniformes. Se agrupan cerca de cero, con una cola larga de outliers. La rejilla solo necesita resolver el grupo central; las colas se recortan a las bandejas extremas (y pagamos una penalización de error allí).
Los 16 valores distintos de INT4 son aún 16× más gruesos. A granularidad por-grupo (una escala (scale) por cada 64 pesos), la resolución efectiva sube fuerte porque la distribución local de cada grupo puede elegir su propia escala.
TF32 — el compromiso de Ampere¶
TF32 (TensorFloat-32) es el formato de entrada de los tensor cores de NVIDIA Ampere: 1 signo + 8 exponente + 10 mantisa = 19 bits útiles (almacenados en palabras de 32 bits por compatibilidad con la pipeline de memoria). Mismo exponente que FP32, misma mantisa que FP16. Los tensor cores acumulan a FP32.
En el i5-8250U de Borja, TF32 no existe. Solo repaso: lo mencionamos porque cada paper de "compute capability 8.0+" habla de él.
Cómo conecta esto con la Fase 26¶
La fase implementa un único cambio de formato numérico: FP32 → INT8 → INT4. No implementamos la conversión FP16↔BF16 (PyTorch la tiene). No implementamos FP8 (sin hardware). La conclusión conceptual de este fichero:
- Los bits por valor son un parámetro libre. El hardware restringe qué valores son rápidos, no cuáles son posibles.
- Rango vs precisión es la palanca de trade-off dentro de un conteo de bits fijo. BF16 conserva rango, FP16 conserva precisión. INT8/INT4 hacen algo distinto — desplazan el "presupuesto de precisión" a un escalar (la escala (scale)).
- Las distribuciones de activación importan más que las de pesos. Los outliers son la razón de que el PTQ ingenuo se degrade; son por lo que cada esquema moderno (SmoothQuant, AWQ, LLM.int8()) trata las activaciones especialmente.
Un pequeño ejercicio empírico (no evaluado)¶
Ejecuta en PyTorch:
Después con x = torch.randn(1000) * 1e30. Observa qué hace BF16 (sigue bien — el rango del exponente lo cubre) frente a FP16 (overflow). Este es el argumento del overflow de gradiente en una línea.
Resumen en un párrafo¶
Los formatos en coma flotante reparten bits entre exponente (rango) y mantisa (precisión). FP32 tiene de sobra en ambos. FP16 aprieta el rango; BF16 aprieta la precisión. La cuantización a entero (INT8, INT4) abandona del todo la rejilla en coma flotante y guarda un escalar aparte para recuperar el rango — a costa de una resolución por-valor mucho más gruesa, mitigada parcialmente eligiendo la escala (scale) a granularidad fina (por-canal, por-grupo). El siguiente fichero de teoría deriva el mapa de cuantización simétrico/asimétrico y acota su error.
Siguiente: theory/02-scales-and-zeros.md.