English · Español
03 — El modelo roofline¶
La ecuación que une todo:
perf_alcanzable = min(FLOPS_pico, BW_pico × intensidad_aritmética). La gráfica resultante (líneas log-log que forman un "tejado") es el mapa donde colocas cualquier kernel para diagnosticar si está limitado por cómputo o por memoria.
Este archivo es la página más importante de la Fase 1. Léelo una vez, léelo otra, y no avances hasta que puedas re-derivar la fórmula y esbozar la gráfica sin mirar.
El planteamiento¶
Un kernel realiza algo de trabajo. Queremos saber cuán rápido puede correr en un ordenador dado. Dos techos lo limitan:
- Techo de cómputo. El ordenador puede hacer como mucho
πFLOPS (operaciones en coma flotante por segundo pico). No importa cuán perfectamente cacheado, el kernel no puede ir más rápido que eso. - Techo de memoria. El ordenador puede mover como mucho
βbytes por segundo desde la memoria principal a los registros. Si el kernel lee más bytes que los FLOPs que hace, las FPUs se quedan ociosas esperando.
Estos techos se combinan a través de la intensidad aritmética I — la razón de FLOPs a bytes que hace el kernel.
Derivando la fórmula del roofline¶
Sea:
- F = FLOPs totales que realiza el kernel.
- B = bytes totales que el kernel mueve entre DRAM y cache (lo formalizaremos en un momento).
- π = FLOPS pico del ordenador.
- β = ancho de banda pico del ordenador, bytes/s.
Queremos rendimiento = FLOPs por segundo. Dos rutas desde el kernel al reloj de pared:
Tiempo de cómputo = F / π. El kernel necesita al menos F / π segundos solo para hacer la matemática.
Tiempo de memoria = B / β. El kernel necesita al menos B / β segundos solo para mover los bytes.
El kernel puede solapar estos — mientras espera memoria, las FPUs pueden hacer otra matemática (la ejecución fuera de orden lo hace real). En el mejor caso, el kernel corre en el máximo de los dos tiempos:
El rendimiento es F / T:
perf_max = F / max(F/π, B/β)
= min(π, F × β / B)
= min(π, I × β) donde I = F / B (intensidad aritmética)
Esta es la ecuación del roofline:
perf_alcanzable = min(π, I × β)
Dos regímenes:
- Si I < π / β → memory-bound: perf = I × β. Duplicar la intensidad duplica el rendimiento.
- Si I > π / β → compute-bound: perf = π. Ya saturando las FPUs; más intensidad no compra nada.
El cruce ocurre en I_crit = π / β — el punto de equilibrio del ordenador. Para un i5-8250U:
π ≈ 200 GFLOPS (AVX2 con limitación térmica)
β ≈ 20 GB/s
I_crit = π / β = 200 GF / 20 GB = 10 FLOPs/byte
Cualquier kernel por debajo de 10 FLOPs/byte en este ordenador está memory-bound. El matmul ingenuo hace 2 FLOPs/byte. Está limitado por ancho de banda en 5×.
Dibujando el roofline¶
La gráfica es log-log: intensidad aritmética en el eje x (FLOPs/byte), rendimiento en el eje y (GFLOPS).
GFLOPS (log)
^
π ┤ ╭───────────────────── ← techo de cómputo
│ ╱
│ ╱
│ ╱
│ ╱
│ ╱
│ ╱ ← techo de memoria: pendiente = β
│ ╱
│ ╱
│ ╱
└───────┴──────────────────────────────→
I_crit intensidad aritmética (log)
Los dos segmentos forman un "tejado" — de ahí roofline. La esquina está en I_crit. Cualquier kernel se grafica como un único punto (intensidad, rendimiento); cuanto más cerca esté el punto del tejado, mejor ajustado está el kernel.
Dónde vive el matmul ingenuo¶
Para C = A × B con todas las matrices N×N fp32:
- FLOPs:
2 × N³(una multiplicación + una acumulación por iteración interna; N³ iteraciones internas). - Bytes (peor caso, sin reutilización): lee cada elemento de A
Nveces (N filas de A × N columnas de B), lee cada elemento de BNveces, escribe C una vez. Lecturas totales =2 × N × N² = 2 N³fp32 =8 N³bytes. Escrituras despreciables. - Intensidad:
2 N³ FLOPs / 8 N³ bytes = 0.25 FLOPs/byte.
Un cuarto de un FLOP por byte. El roofline del i5-8250U pone eso en 0.25 × 20 GB/s = 5 GFLOPS. 2.5% del pico.
Pero si haces bloque (block) el matmul para que cada bloque quepa en L1, lees cada elemento de A solo N/B veces (B = tamaño de bloque), no N. La intensidad escala con el tamaño de bloque. A un bloque 64×64 (16 KiB, cómodamente en los 32 KiB de L1), intensidad ≈ 2N³ / (3 × 64²) ≈ N / 6 FLOPs/byte. Para N = 1024, intensidad ≈ 170 — muy por encima de I_crit = 10. Compute-bound. Ingenuo 5 GF → ajustado 200 GF. Speedup de 40×.
Por eso np.matmul es 50× más rápido que tres bucles anidados de Python. La matemática es idéntica. El patrón de acceso a memoria no.
Tres advertencias honestas sobre el roofline¶
El roofline es un modelo de primer orden. Tres cosas que no te dice:
- Qué nivel de cache representa β. La gráfica de arriba usa el ancho de banda de DRAM. Puedes dibujar tejados separados para BW-L1, BW-L2, BW-L3, BW-DRAM, cada uno un techo más alto. Un kernel cuyo working set cabe en L1 corre contra el tejado L1, no contra el tejado DRAM. Las gráficas del mundo real apilan los rooflines.
- Latencia vs ancho de banda. El modelo asume que puedes saturar
β— es decir, suficientes peticiones de memoria paralelas en vuelo. Una carga de pointer-chasing veβ_efectivo ≈ 1 byte / 70 ns ≈ 14 MB/sen lugar de 20 GB/s. Tejado diferente. - Precisión mixta. Si la mitad de tu kernel es fp32 y la mitad es fp16,
πes una media ponderada y el tejado se dobla. Para la mayoría del currículo, la precisión simple domina. Revisitaremos en la Fase 26 (cuantización).
Por qué este es el modelo mental para hardware de IA¶
Cada propuesta de acelerador es implícitamente un argumento de roofline:
- "H100 tiene 2× el ancho de banda de A100" → la pendiente del techo de memoria es 2× más empinada. Los kernels memory-bound (la mayoría de la inferencia) obtienen ~2×. Los kernels compute-bound (entrenamiento con batch grande) obtienen ~1.5× del aumento de FLOPS.
- "Flash attention es 3× más rápida" → no cambia los FLOPs; reduce
Bmanteniendo el working set en SRAM. Mismo tejado, mayor intensidad → punto más alto. - "La inferencia cuantizada es más rápida" → reduce los bytes por peso, elevando la intensidad para el mismo kernel. Mismo cómputo, tráfico de memoria diferente.
- "Este kernel es GEMM-bound" → GEMM es compute-bound; el resto del modelo es memory-bound. La gráfica del roofline muestra GEMM cerca de la esquina y todo lo demás abrazando la pendiente.
Una vez que puedas leer estas one-liners como afirmaciones del roofline, has graduado la Fase 1.
Cómo computar esto realmente para tu ordenador¶
Hay tres caminos tratables:
- Cálculo a mano desde
lscpu+ datasheet. Usa la fórmulaπ = reloj × cores × ancho_SIMD × factor_FMA. Usa el conteo de canales de memoria + velocidad DDR4 × 8 bytes paraβ. Esto es lo que hace el lab 03 como camino por defecto. - Microbenchmarks. Ejecuta un bucle ajustado puramente aritmético (satura FPUs) y otro puramente memcpy (satura DRAM). Tómales tiempo. Computa
πyβa partir de los tiempos. Los labs 01 y 03 juntos hacen esto. - Una herramienta.
peakperf,Empirical Roofline Tool(ERT),LIKWID-perfctr. Vale la pena saber que existen. No se asumen instaladas en el lab 03.
El Lab 03 te hace hacer (1) y (2) y comparar. (3) es opcional.
Problemas de drill (resuélvelos antes del lab)¶
Soluciones en solutions/03-roofline-ref.md — escritas en apertura de fase, no visibles durante el pre-write. Intenta resolverlos razonando, no ejecutando.
- Un kernel hace
dot(a, b)para dos vectores de longitudN. FLOPs =2N - 1. Bytes movidos (sin reutilización) =8N(dos vectores fp32). ¿Intensidad? ¿Bandwidth- o compute-bound en el i5-8250U? softmax(x)para longitudNrequiere: leerx(N fp32), computar max (N FLOPs), restar max (N FLOPs), exp (N FLOPs), sumar (N FLOPs), dividir (N FLOPs), escribir resultado (N fp32). FLOPs totales = 5N (aproximadamente). Bytes = 8N (una lectura + una escritura). ¿Intensidad? ¿Por qué softmax es memory-bound en cada máquina jamás construida?- Intensidad de
matmul(N×N, N×N)ingenuo =0.25 FLOPs/byte. Muestra que un matmul tileado B×B con B elegido de forma que 3·B² fp32 ≤ L1_size tiene intensidad ≈B/6. ¿Cuál es el B más grande en el i5-8250U de Borja (L1 de 32 KiB)? ¿Qué intensidad da eso? ¿Es compute-bound?
Si puedes responder los tres de memoria + aritmética al dorso del sobre, entiendes el roofline. Si alguno tambalea, releer la sección relevante de arriba.
Recapitulación en un párrafo¶
La ecuación del roofline perf = min(π, I × β) traza un "tejado" con un techo de cómputo plano y un techo de memoria inclinado, unidos en la intensidad de equilibrio de la máquina I_crit = π / β. Cada kernel es un punto en esta gráfica. Los kernels con intensidad por debajo de I_crit son bandwidth-bound; por encima, compute-bound. El matmul ingenuo vive cerca de I = 0.25, profundamente bandwidth-bound; el matmul por bloques eleva la intensidad a ~N/6, compute-bound. Esta única imagen explica por qué la optimización de jerarquía de memoria es el tipo dominante de optimización en IA, y te permite predecir — antes de ejecutar nada — en qué lado del tejado aterrizará tu código.
Teoría terminada. Pasa a lab/00-machine-profile.md.