English · Español
03 — Preview de precisión mixta (fp16 / bf16, reglas de acumulador)¶
No vamos a entrenar en mixed precision aquí — la CPU de Borja no se beneficia. Pero la matemática del redondeo decide la mitad de los bugs de las fases 26 y 27. Hoy medimos el drift; en la Fase 26 lo aprovechamos.
Por qué un preview, no una inmersión profunda¶
La Fase 18 va en CPU. La Intel UHD 620 no tiene Tensor Cores, no tiene ops de bf16, no tiene nada de FP8. Entrenar en precisión mixta en este hardware es un ahorro de memoria (2× menos bytes por peso) sin ahorro de velocidad (fp16 en CPU está o emulado por software o, en AVX-512, es más lento que fp32 de todos modos). No merece la ingeniería — la Fase 18 hace fp32 end-to-end.
Pero dos fases posteriores dependen de las matemáticas de precisión mixta:
- Fase 26 (Cuantización) — la cuantización INT8 y 4-bit es el extremo de la aritmética de baja precisión. La precisión mixta (fp16/bf16) es la parada intermedia.
- Fase 27 (FlashAttention) — el softmax online usado por FlashAttention requiere acumuladores en fp32 incluso cuando el matmul es fp16. La regla "el acumulador se queda en mayor precisión" es general.
Así que la Fase 18 cubre las matemáticas de la precisión mixta y mide el drift por capa sobre un MiniGPT entrenado en fp32. El lab fuerza a Borja a ver el precipicio de rango dinámico de fp16 con sus propios ojos. La Fase 26 entonces lo explota.
Los cuatro formatos¶
| Formato | Signo | Exponente | Mantisa | Bits totales | Rango dinámico | Precisión (LSB de mantisa) |
|---|---|---|---|---|---|---|
| fp32 | 1 | 8 | 23 | 32 | \(\sim 10^{-38}\) a \(10^{38}\) | \(\sim 10^{-7}\) |
| fp16 | 1 | 5 | 10 | 16 | \(\sim 10^{-5}\) a \(10^{5}\) | \(\sim 10^{-3}\) |
| bf16 | 1 | 8 | 7 | 16 | \(\sim 10^{-38}\) a \(10^{38}\) | \(\sim 10^{-2}\) |
| fp8 (E4M3) | 1 | 4 | 3 | 8 | \(\sim 10^{-2}\) a \(10^{2}\) | \(\sim 10^{-1}\) |
Dos cosas que notar:
- fp16 vs bf16 cambian exponente por mantisa. fp16 tiene más precisión (un bit extra) pero ~10000× menos rango dinámico. bf16 mantiene el rango de fp32 pero recorta la precisión a la mitad.
- fp32 → fp16 es un redondeo real. Un valor de peso \(1.234567\) en fp32 se convierte en \(1.234\) en fp16 — tres dígitos significativos perdidos. Esto es lo que mide el "drift".
La diferencia de rango dinámico es por qué fp16 necesita loss scaling pero bf16 no. Con fp16, los gradientes menores que \(\sim 10^{-5}\) se vuelven cero (underflow). Con bf16, \(\sim 10^{-38}\) antes del underflow — no hace falta scaling. fp16 fue el estándar de 2017–2020 (NVIDIA V100); bf16 (Ampere+) y fp8 (Hopper+) son el camino moderno.
La cota de drift¶
Castear un valor fp32 \(x\) a fp16 introduce un error de redondeo acotado por:
(La mantisa tiene 10 bits, así que el error relativo es \(\le 2^{-10} \approx 10^{-3}\).) Esta es una cota multiplicativa: los valores grandes pierden más precisión absoluta.
Para matmul: si \(A\) y \(B\) son fp16 y el matmul \(C = AB\) acumula en fp32 (la operación estándar de Tensor-Core), el error en \(C\) está acotado por \(O(N \cdot 2^{-10}) \, \|A\| \|B\|\) donde \(N\) es la dimensión interna. El factor de \(N\) viene de sumar \(N\) productos redondeados. Para un transformer con \(d_\text{model} = 64\), el error por elemento del matmul es \(\sim 64 \cdot 10^{-3} \approx 6\%\) relativo.
Eso es un matmul. Un transformer tiene del orden de \(10\) matmuls por token. Los errores se componen. La Fase 26 deriva formalmente la cota de composición; por ahora: el error por matmul de fp16 es aproximadamente \(10^{-3}\) relativo, el error por bloque de fp16 es aproximadamente \(10^{-2}\) relativo, el error por capa es \(10^{-1}\) si dejas que se componga.
La regla del acumulador¶
La regla más importante de la precisión mixta:
Los acumuladores se quedan en fp32, incluso cuando los operandos son fp16.
Por qué: un producto escalar fp16 de dos vectores de longitud \(N\) ejecuta \(N\) operaciones de multiplicar-sumar. Cada ronda de la suma puede perder precisión. Si la suma corriente se mantiene en fp16, tras ~100 sumas el error de redondeo domina. En acumulación fp32, la suma corriente tiene 23 bits de mantisa; los errores de redondeo se mantienen sub-porcentuales para \(N\) hasta \(10^6\).
Por esto los Tensor Cores funcionan como funcionan: entradas fp16, acumulador fp32, salida fp16. El coste del acumulador fp32 es pequeño (el circuito multiplicador es el grueso de un Tensor Core; el acumulador es contabilidad). El beneficio es enorme: la precisión del matmul se preserva.
Para el lab preview de la Fase 18, no ejecutamos el matmul en fp16. Casteamos los pesos a fp16, casteamos de vuelta a fp32, y medimos el drift de activaciones por capa introducido por el round-trip. Esta es la "sombra de redondeo" de los pesos fp16 sobre el paso forward.
Dónde la precisión mixta de verdad ayuda¶
Tres ganancias concretas, en orden de impacto:
- Memoria. Los pesos en fp16 son la mitad de tamaño. Un modelo de 70B parámetros es 140 GB en fp32, 70 GB en fp16. La KV cache (Fase 22) se reduce de forma similar. Esta es la razón por la que la inferencia en producción es de precisión mixta.
- Ancho de banda (bandwidth). Los pesos se cargan de HBM a SRAM a 2× la velocidad en fp16. Los kernels memory-bound se aceleran directamente (recuerda el roofline de la Fase 1).
- Cómputo. Los Tensor Cores ejecutan matmul fp16 a 2× los FLOPS de fp32 (A100) o 8× (H100 con sparsity). Los kernels puramente de cómputo se aceleran 2-8×.
La CPU de Borja consigue el beneficio #1 (memoria) y parcialmente #2 (bandwidth), pero pierde #3 (fp16 en CPU no es más rápido). En CPU, el coste-beneficio es aproximadamente igual; en GPU, es 4×+. La Fase 26 es donde importa.
El ejemplo de gramática verbal¶
Toma la frase "yo trabajo / I work". Tras tokenización (Fase 11), esto es alguna secuencia de 7 tokens. El paso forward de MiniGPT es:
- Lookup de embedding → tensor de activación
(7, d_model=64)de valores fp32. - Capa 1: LayerNorm → Attention → +residual → LayerNorm → FFN → +residual.
- Capas 2-4: igual.
- LayerNorm final → proyección de salida → logits
(7, V).
Si casteamos los pesos a fp16 únicamente, dejando las activaciones y el cómputo forward en fp32, el drift por capa es lo que medimos. Patrón esperado (el lab 03 produce el plot):
- Activaciones de la capa 1: drift ~\(10^{-3}\) relativo (un matmul de error).
- Activaciones de la capa 4: drift ~\(10^{-2}\) relativo (compuesto).
- Logits de salida: drift ~\(10^{-2}\) relativo; el argmax frecuentemente no cambia.
- Algunas de las 7 posiciones: el argmax cambia. El modelo "predice" un token distinto bajo pesos fp16.
El número de posiciones que cambian de argmax es la cifra titular. Para nuestro modelo diminuto, típicamente son 0–2 de 7 — es decir, las predicciones son mayormente estables, con un puñado de tokens marginales volteándose. Esto importa porque la Fase 21 muestreará de estos logits; los volteos de argmax para un token son visibles en el texto generado.
Lo que no estamos haciendo¶
- Entrenar en fp16. Tanto forward como backward, con loss scaling, es la receta completa de precisión mixta. Fase 26.
- Medición de bf16. bf16 es cualitativamente más fácil (no hace falta scaling, el rango coincide con fp32). Una vez entiendes fp16, bf16 es directo. Fase 26.
- fp8. Feature solo de Hopper, irrelevante para CPU. La Fase 26 lo repasa.
Problemas de drill¶
- fp16 tiene 10 bits de mantisa. ¿Cuál es el mayor entero representable que tiene una representación fp16 exacta? (Pista: \(2^{11}\).)
- Valor fp32 \(x = 0.1\) casteado a fp16. ¿Cuál es el error de redondeo? Compara con \(x = 1000.0\).
- Un matmul \(C = AB\) con \(A, B\) fp16 y \(C\) acumulado en fp16. Dim interna \(N = 1024\). Estima el error relativo en \(C\). ¿Por qué esto es inusable en la práctica?
- La frase verbal "he is going to be working" tiene 8 tokens. Tras el cast de pesos fp16, predecir "working" como token final requiere que la diferencia de logit entre "working" y "work" exceda el suelo de ruido del drift fp16. Si el margen original (fp32) de logit es \(0.5\), ¿preservará fp16 el argmax?
Recap de un párrafo¶
fp16 cambia 16× menos rango dinámico por un bit extra de mantisa vs bf16. El error de redondeo por cast es \(\le 2^{-10}\) relativo; por matmul de dim interna \(N\), el error es \(\sim N \cdot 2^{-10}\) relativo. La regla del acumulador — mantener la suma corriente en fp32 incluso con operandos fp16 — es la convención más importante; es lo que hace funcionar a los Tensor Cores. El beneficio principal de la precisión mixta en GPU es un speedup de 2-8×; en CPU, es solo memoria y no merece la ingeniería. El preview de la Fase 18 mide el drift fp16 por capa; la Fase 26 explota los ahorros en GPU.
Lo que esta sección NO cubre¶
- Loss scaling para backward fp16. Fase 26.
- Redondeo estocástico. Fuera de alcance.
- fp16 en el estado del optimizador (master weights). Fase 26.
- Atención de precisión mixta. Fase 27.
Siguiente: theory/04-checkpoints-and-mlflow.md.