The high-level libraries you use daily are this kernel underneath
Every line of vector arithmetic in numpy, PyTorch, and MLX eventually dispatches to a CUDA or Metal kernel that looks essentially like the one you just wrote. Understanding that — what gets generated, when, where — is the bridge from kernel-level coding to framework-level coding.
| Layer | What dispatches | To which backend |
|---|---|---|
NumPy a + b (CPU) | OpenBLAS / MKL kernel (SIMD-vectorized) | x86 AVX2/AVX512 or ARM NEON |
PyTorch a + b on CUDA tensor | cuBLAS or hand-rolled CUDA kernel from ATen | NVCC-compiled CUDA |
PyTorch a + b on MPS tensor (Mac) | MPS (Metal Performance Shaders) kernel | Metal compute |
MLX a + b (Apple) | MLX-native Metal kernel | Metal compute |
torch.compile(f) graph | Triton-generated kernel (custom code per shape) | NVCC / Triton |
The thing each library hides is the 9-step workflow. a + b in PyTorch is internally:
- Check if A and B share device and dtype.
- Allocate output tensor on the same device.
- Pick a kernel — element-wise add, broadcast add, fused with prior op via
torch.compile. - Compute launch geometry (threads × blocks) from the tensor shape.
- Launch the kernel.
- Return the output tensor handle (no host copy yet — that happens on
.cpu()or.numpy()).
That's why a snippet like x.cuda() + y.cuda() is fast even though it 'looks like' a Python expression — the heavy lifting is exactly the kernel you wrote in lessons 3–4.