A Triton implementation of the Root Mean Square Normalization (RMSNorm) forward and backward passes. This project demonstrates low-level GPU programming, memory-coalescing, tiling strategies, and kernel fusion to eliminate High Bandwidth Memory (HBM) round-trips compared to naive PyTorch eager execution.
The forward pass achieves relative performance parity with torch.compile (TorchInductor), peaking at ~240 GB/s of effective memory bandwidth on a NVIDIA T4 GPU running through Google Colab (est. 320 GB/s bandwidth). The backward pass achieves a 2.7x speedup over PyTorch Eager, but does not beat torch.compile due to redundancy in my backwards pass implementation.
- PyTorch Eager: ~62 GB/s (bottlenecked by 3-5 distinct sequential kernel launches and intermediate HBM reads/writes).
- Torch Compile / Custom Triton: ~240 GB/s (bound by physical hardware memory bandwidth limits; 3.8x faster than Eager).
- PyTorch Eager: ~20 GB/s.
- Custom Triton: ~54 GB/s (2.7x speedup over Eager).
- Torch Compile: ~65–75 GB/s.
RMSNorm is fundamentally a memory-bound operation. The mathematical formulation is:
In a naive PyTorch eager execution model, this operation launches separate kernels for squaring elements, calculating the row-wise mean, adding epsilon, computing the reciprocal square root, multiplying by the input, and scaling by the weight vector
- Fused Execution: Combines the entire mathematical sequence into a single kernel launch per row, forcing all intermediate states to live inside fast On-Chip SRAM registers instead of HBM.
- Persistent Reductions: Exploits thread-block level caching for the variance calculation, ensuring data is loaded exactly once per row.
- FP32 Accumulation: Loads and stores tensors in
float16orbfloat16while executing algebraic accumulations infloat32to preserve numerical stability and combat rounding drift. - Autotuning: Integrated
triton.autotuneto dynamically sweep optimal configurations forBLOCK_SIZEbased on the input matrix dimensions.
While the forward pass achieves parity with PyTorch's automated compiler, the manual backward pass exhibits a ~30% performance delta compared to torch.compile.
My custom Triton kernel handles the weight gradient by executing a direct tl.atomic_add on global memory pointers from within each parallel block. At high thread counts, this triggers severe global atomic lock contention in HBM, stalling execution blocks while they wait to write to the shared
TorchInductor (torch.compile) circumvents this bottleneck by automatically split-compiling the backward pass. It caches block-level partial gradients into temporary structural workspace buffers, then dispatches a highly vectorized, separate reduction kernel to finalize
├── data/
│ └── rmsnorm-backward-bandwidth.csv
│ └── rmsnorm-backward-bandwidth.png
│ └── rmsnorm-forward-bandwidth.csv
│ └── rmsnorm-forward-bandwidth.png
├── benchmark.py
├── kernels.py # Core Triton forward & backward kernel implementations
├── rmsnorm.py
├── test_rmsnorm.py
├── README.md
└── requirements.txt
- NVIDIA GPU (Ampere or newer recommended)
- CUDA Toolkit installedInstallation
pip install -r requirements.txtValidates the custom Triton kernel outputs against the PyTorch reference implementation across float32, float16, and bfloat16 with rigid numeric tolerances:
pytest test_rmsnorm.pySweeps hidden dimensions from
python benchmark.py