#include "neuroflow/rms_norm.hpp" #include #include #ifdef USE_CUDA #include "cuda_context.hpp" #endif namespace neuroflow { RMSNorm::RMSNorm(size_t dim, float eps) : dim_(dim), eps_(eps), weight_({dim}, QuantType::FP32) { float* wp = weight_.as_fp32(); for (size_t i = 0; i < dim; ++i) wp[i] = 1.0f; } Tensor RMSNorm::forward(const Tensor& x) { cache_.input = x.clone(); size_t batch = x.numel() / dim_; cache_.rms = Tensor({batch}, QuantType::FP32); cache_.normalized = Tensor(x.shape_, QuantType::FP32); Tensor output(x.shape_, QuantType::FP32); #ifdef USE_CUDA if (CudaContext::instance().is_available() && x.is_on_gpu()) { output.to_gpu(); cache_.rms.to_gpu(); cache_.normalized.to_gpu(); launch_rms_norm_forward(output.as_gpu_fp32(), x.as_gpu_fp32(), weight_.as_gpu_fp32(), cache_.rms.as_gpu_fp32(), cache_.normalized.as_gpu_fp32(), batch, dim_, eps_, CudaContext::instance().stream()); output.gpu_dirty_ = true; cache_.rms.gpu_dirty_ = true; cache_.normalized.gpu_dirty_ = true; return output; } #endif const float* xp = x.as_fp32(); const float* wp = weight_.as_fp32(); float* rms_p = cache_.rms.as_fp32(); float* norm_p = cache_.normalized.as_fp32(); float* op = output.as_fp32(); for (size_t b = 0; b < batch; ++b) { float ss = 0.0f; for (size_t d = 0; d < dim_; ++d) { float v = xp[b * dim_ + d]; ss += v * v; } float rms = std::sqrt(ss / static_cast(dim_) + eps_); rms_p[b] = rms; for (size_t d = 0; d < dim_; ++d) { float n = xp[b * dim_ + d] / rms; norm_p[b * dim_ + d] = n; op[b * dim_ + d] = n * wp[d]; } } return output; } RMSNorm::Gradients RMSNorm::backward(const Tensor& output_grad) { Gradients grads; size_t batch = cache_.input.numel() / dim_; grads.weight_grad = Tensor({dim_}, QuantType::FP32); grads.input_grad = Tensor(cache_.input.shape_, QuantType::FP32); #ifdef USE_CUDA if (CudaContext::instance().is_available() && output_grad.is_on_gpu()) { grads.weight_grad.to_gpu(); grads.input_grad.to_gpu(); launch_rms_norm_backward(grads.input_grad.as_gpu_fp32(), grads.weight_grad.as_gpu_fp32(), output_grad.as_gpu_fp32(), cache_.input.as_gpu_fp32(), cache_.rms.as_gpu_fp32(), cache_.normalized.as_gpu_fp32(), weight_.as_gpu_fp32(), batch, dim_, CudaContext::instance().stream()); grads.weight_grad.gpu_dirty_ = true; grads.input_grad.gpu_dirty_ = true; return grads; } #endif const float* og = output_grad.as_fp32(); const float* xp = cache_.input.as_fp32(); const float* rms_p = cache_.rms.as_fp32(); const float* norm_p = cache_.normalized.as_fp32(); const float* wp = weight_.as_fp32(); float* wg = grads.weight_grad.as_fp32(); float* ig = grads.input_grad.as_fp32(); memset(wg, 0, dim_ * sizeof(float)); for (size_t b = 0; b < batch; ++b) { float rms = rms_p[b]; float inv_rms = 1.0f / rms; for (size_t d = 0; d < dim_; ++d) { wg[d] += og[b * dim_ + d] * norm_p[b * dim_ + d]; } float dot_og_n = 0.0f; for (size_t d = 0; d < dim_; ++d) { dot_og_n += og[b * dim_ + d] * wp[d] * xp[b * dim_ + d]; } for (size_t d = 0; d < dim_; ++d) { float d_norm = og[b * dim_ + d] * wp[d]; float d_rms = -dot_og_n / (rms * static_cast(dim_)); float d_ss = d_rms * 0.5f / rms; ig[b * dim_ + d] = d_norm * inv_rms + 2.0f * xp[b * dim_ + d] * d_ss; } } return grads; } #ifdef USE_CUDA __global__ void kernel_rms_norm_forward_impl(float* out, const float* input, const float* weight, float* rms, float* normalized, size_t batch, size_t dim, float eps) { size_t b = blockIdx.x; if (b >= batch) return; float ss = 0.0f; for (size_t d = threadIdx.x; d < dim; d += blockDim.x) { float v = input[b * dim + d]; ss += v * v; } __shared__ float s_ss; if (threadIdx.x == 0) s_ss = 0.0f; __syncthreads(); atomicAdd(&s_ss, ss); __syncthreads(); float rms_val = sqrtf(s_ss / static_cast(dim) + eps); if (threadIdx.x == 0) rms[b] = rms_val; for (size_t d = threadIdx.x; d < dim; d += blockDim.x) { float n = input[b * dim + d] / rms_val; normalized[b * dim + d] = n; out[b * dim + d] = n * weight[d]; } } void launch_rms_norm_forward(float* out, const float* input, const float* weight, float* rms, float* normalized, size_t batch, size_t dim, float eps, cudaStream_t stream) { int block = min(static_cast(dim), 512); kernel_rms_norm_forward_impl<<(batch), block, 0, stream>>>( out, input, weight, rms, normalized, batch, dim, eps); } __global__ void kernel_rms_norm_backward_impl(float* input_grad, float* weight_grad, const float* output_grad, const float* input, const float* rms, const float* normalized, const float* weight, size_t batch, size_t dim) { size_t b = blockIdx.x; if (b >= batch) return; float rms_val = rms[b]; float inv_rms = 1.0f / rms_val; for (size_t d = threadIdx.x; d < dim; d += blockDim.x) { atomicAdd(&weight_grad[d], output_grad[b * dim + d] * normalized[b * dim + d]); } __shared__ float s_dot; if (threadIdx.x == 0) s_dot = 0.0f; __syncthreads(); float local_dot = 0.0f; for (size_t d = threadIdx.x; d < dim; d += blockDim.x) { local_dot += output_grad[b * dim + d] * weight[d] * input[b * dim + d]; } atomicAdd(&s_dot, local_dot); __syncthreads(); for (size_t d = threadIdx.x; d < dim; d += blockDim.x) { float d_norm = output_grad[b * dim + d] * weight[d]; float d_rms = -s_dot / (rms_val * static_cast(dim)); float d_ss = d_rms * 0.5f / rms_val; input_grad[b * dim + d] = d_norm * inv_rms + 2.0f * input[b * dim + d] * d_ss; } } void launch_rms_norm_backward(float* input_grad, float* weight_grad, const float* output_grad, const float* input, const float* rms, const float* normalized, const float* weight, size_t batch, size_t dim, cudaStream_t stream) { int block = min(static_cast(dim), 512); kernel_rms_norm_backward_impl<<(batch), block, 0, stream>>>( input_grad, weight_grad, output_grad, input, rms, normalized, weight, batch, dim); } #endif } // namespace neuroflow