English · Español
00 — Por qué escribir kernels¶
🇪🇸 Después de Fase 23 ya sabes lo que es un SM, un warp, una jerarquía HBM/L2/SMEM/registros. Después de Fase 22 ya sabes que la decode-attention es memory-bound. Phase 24 es donde escribes el kernel que materializa la teoría: un kernel propio que sabes leer, profile, y tunear, en CUDA C y en Triton, y comparado con cuBLAS. Es la prueba de fuego de la comprensión.
Esta es la página de motivación. Responde: ¿por qué no basta con "simplemente llamar a torch.nn.functional"? ¿Por qué una fase entera dedicada a escribir kernels a mano? Y — ¿por qué ahora (Fase 24, no Fase 5 ni Fase 40)?
"Simplemente llama a PyTorch" — cuándo basta¶
PyTorch / cuBLAS / cuDNN / Flash-Attention son excelentes para los kernels que cubren. Para el 95% del trabajo, llamar a torch.matmul o F.scaled_dot_product_attention es correcto, rápido y la opción acertada. La Fase 24 no argumenta contra el uso de kernels de framework; argumenta contra ser incapaz de escribir uno cuando hace falta.
Tres lugares donde los kernels de framework dejan de bastar:
- Un operador nuevo que el framework no tiene. Variantes personalizadas de RoPE, QKV+bias+RoPE fusionado, formatos de cuantización personalizados (Fase 26), preprocesamiento de espectrogramas de audio — ninguno viene pre-hecho. El trabajo real en producción escribe kernels personalizados con regularidad.
- Un operador fusionado que el framework no fusiona. El modo eager de PyTorch ejecuta
softmax(x).mul(y)como dos lanzamientos de kernel más una asignación de tensor intermedio. Un kernel fusionado lo hace en un solo lanzamiento sin intermedio. Las rutas críticas en latencia se preocupan por esto. - Un kernel donde la elección del framework está mal. cuBLAS escoge una de varias implementaciones de GEMM por heurística. Para shapes inusuales (p. ej., matrices tall-skinny comunes en el routing de MoE), la heurística elige mal y escribes el tuyo.
Te encontrarás con cada uno de estos hacia la Fase 27 (paged attention no tiene un kernel de framework de fábrica que case con tu layout de memoria exacto), Fase 26 (cuantización personalizada) y Fase 33 (pasadas de compilación). La Fase 24 es la habilidad prerrequisito.
Por qué ahora (Fase 24)¶
Tres razones por las que cae aquí, ni antes ni después:
- Por fin puedes medir. La Fase 23 construyó el roofline de GPU. La Fase 24 coloca los kernels en él. Sin el roofline, "rápido" es una sensación; con él, "rápido" es un porcentaje del pico.
- Tienes un menú real de operadores. La Fase 22 nombró decode attention, prefill GEMM, FFN — operadores concretos. El kernel de la Fase 24 es uno de esos, no un benchmark sintético.
- PyTorch se introduce aquí deliberadamente. Todo el código previo es NumPy. La Fase 24 introduce PyTorch después de entender el sustrato (Fase 23) y después de que la matemática (todas las fases previas) esté sólida. El framework es la herramienta, presentado después de que el problema esté en tus manos. Hacerlo antes habría convertido el framework en una caja mágica.
Si saltaras la Fase 24 por completo, serías un "usuario competente del framework" pero incapaz de depurar o extender. El propósito del currículo es que nunca te encuentres en esa posición.
El principio de un solo kernel¶
Un patrón típico de fracaso en cursos de kernels: cubrir diez kernels superficialmente. El estudiante se va con un pattern-matching vago ("creo que Flash-Attention usa tiling") y sin capacidad de escribir un kernel nuevo.
La alternativa de la Fase 24: un kernel, tres implementaciones, en profundidad. El mismo operador (fused softmax por defecto, GEMM como alternativa) implementado en:
- CUDA C ingenuo — un hilo por salida, sin SMEM, sin florituras. Correcto, lento. Establece "qué hace el kernel".
- CUDA C tuneado — loads coalesced, tile en SMEM, tuning de occupancy. Alcanza ≥30% de cuBLAS /
F.softmax. Establece "qué lo hace rápido". - Triton — kernel tipo Python con autotune. Establece "qué automatiza Triton frente a qué sigue requiriendo que tú especifiques".
Más una cuarta implementación como línea de comparación:
4. cuBLAS / torch.nn.functional — la referencia del framework. La cosa que persigues.
Las cuatro se colocan lado a lado en el roofline de GPU. El estudiante se va con un único kernel que ha perfilado, tuneado y explicado. Ese único kernel es reutilizable como lente para entender cualquier futuro kernel del que lea.
La introducción de PyTorch, deliberadamente tardía¶
Esta fase es también donde torch se importa por primera vez en este codebase. La elección de retrasarlo no es porque PyTorch sea malo — es porque aprender el sustrato primero hace comprensible al framework.
Para la Fase 23, Borja sabe:
- La matemática de cada operador que el modelo necesita (Fases 7–17).
- La jerarquía de memoria de CPU (Fase 1) y GPU (Fase 23).
- La intensidad aritmética del decode (Fase 22).
Así que cuando PyTorch por fin se importe en la Fase 24, cada línea de torch_minigpt.py es un operador conocido con forma familiar, solo con la decoración .cuda() y nn.Module. No hay momento de "no sé qué hace esta capa". El framework es delgado en concepción — rutea operadores a backends, mantiene un grafo de autograd y aporta ergonomía. La Fase 25 abre incluso esos.
Esto contrasta con la ruta de aprendizaje típica (tutorial de PyTorch → "mi modelo entrena, no sé por qué → más o menos funciona → bugs silenciosos a escala"). La ruta de Borja es "cada operador desde cero → los frameworks son obvios → sin magia".
Lo que deberías sentir al final de la Fase 24¶
Tres cosas deberían sentirse viscerales tras la Fase 24, no solo intelectuales:
- Un kernel es un trozo de código, no magia. Cuando
nsight-computete dice "tu kernel alcanza el 23% del pico de ancho de banda HBM", sabes exactamente qué líneas de tu CUDA C son responsables y cómo cambiarlas. - Triton es "CUDA para el caso del 80%". El autotune + sintaxis pythónica de Triton cubren la mayoría de los kernels con ~10× menos código, al ~80–95% del rendimiento de CUDA tuneado a mano. La brecha del 5–20% es cuando el CUDA en crudo todavía gana.
- PyTorch es fontanería. Un
nn.Modulees un registro; unTensores unStorage+ metadatos de vista; una pasada hacia adelante es una secuencia de llamadas de dispatch. La Fase 25 lo hará totalmente explícito, pero la Fase 24 planta la intuición.
Si esas tres sensaciones faltan al cierre de la fase, rehaz el laboratorio 02 (el bucle de tuning) — la comprensión visceral viene del bucle de tuning, no de la teoría.
Lo que deberías ser capaz de hacer al final de la Fase 24¶
- Escribir un kernel CUDA C para un operador row-wise (softmax, layernorm, RMSNorm) que:
- Maneje correctamente un número arbitrario de filas y longitud de fila.
- Use SMEM para la fila.
- Coalescee los loads globales.
- Alcance ≥30% de cuBLAS en el tamaño representativo.
- Reescribir el mismo operador en Triton; explicar qué hace el autotune.
- Perfilar el kernel con
ncu, identificar la razón de stall dominante (memory-bound vs compute-bound vs stall del scheduler) y proponer una mejora. - Cargar el MiniGPT de la Fase 17 en PyTorch (
torch_minigpt.py), correr una pasada hacia adelante en GPU, confirmar byte-idéntica a la referencia NumPy en fp64. - Explicar — sin mirar el código fuente — qué hace
model.cuda()a los tensores y storages subyacentes.
Siguiente: theory/01-cuda-programming-model.md — el modelo de programación CUDA, formalizado.