CUDA Activation Functions Optimization Guide
December 25, 2025 7 min By RightNow AI Team
Introduction
Activation functions are element-wise operations applied after linear layers. While individually simple, they're applied billions of times in large models. The key optimization is fusion - combining activations with adjacent operations to reduce memory bandwidth.
This guide covers common activations, their GPU implementations, and fusion strategies.
Common Performance Issues
- Memory bandwidth limited for simple activations
- Separate kernels for linear + activation waste bandwidth
- Expensive transcendental functions (exp, tanh) in GELU
Optimization Techniques
1. Fused Linear + Activation
Combine matmul and activation in single kernel write.
2. Vectorized Element-wise
Use float4 for 4x memory throughput.
3. Approximate GELU
Use tanh approximation for faster GELU.
Implementation Comparison
Before (Naive Implementation)
Separate kernels require extra memory read/write.
// Separate kernel - extra memory round-trip
__global__ void relu(float* x, float* y, int n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) y[i] = fmaxf(0.0f, x[i]);
}
__global__ void gelu_exact(float* x, float* y, int n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
float val = x[i];
y[i] = 0.5f * val * (1.0f + erff(val * 0.7071067811865476f));
}
}
__global__ void silu(float* x, float* y, int n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
float val = x[i];
y[i] = val / (1.0f + expf(-val)); // x * sigmoid(x)
}
}
}
After (Optimized Implementation)
Fusing activation with GEMM epilogue eliminates memory round-trip.
// Fused in epilogue of GEMM kernel
// During matrix multiply output write, apply activation
__device__ float gelu_approx(float x) {
// Fast approximation: 0.5 * x * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3)))
const float c = 0.7978845608f; // sqrt(2/pi)
const float k = 0.044715f;
float x3 = x * x * x;
return 0.5f * x * (1.0f + tanhf(c * (x + k * x3)));
}
__device__ float silu(float x) {
return x / (1.0f + expf(-x));
}
// In CUTLASS or custom GEMM epilogue:
template<typename Activation>
__global__ void gemm_with_activation(float* A, float* B, float* C, int M, int N, int K) {
// ... GEMM computation ...
// Fused activation in output write
float result = /* gemm result */;
if constexpr (std::is_same_v<Activation, GELU>) {
result = gelu_approx(result);
} else if constexpr (std::is_same_v<Activation, SiLU>) {
result = silu(result);
}
C[output_idx] = result;
}
Performance Results
| Metric | Naive | Optimized | Improvement |
|---|---|---|---|
| Fused vs Separate | 1x | 1.3-1.5x | Reduced memory traffic |
Frequently Asked Questions
Which activation is fastest?
ReLU is fastest (single compare). GELU is slower due to exp/tanh. The approximate GELU is ~2x faster than exact while being nearly identical for training.