use std::sync::Arc;
use cudarc::driver::{CudaDevice, CudaFunction, LaunchAsync, LaunchConfig};
use super::compile_cuda_kernel;
use crate::cuda_inference::tensor::GpuTensor;
use crate::cuda_inference::InferenceError;
const RMSNORM_CUDA: &str = r#"
#include <cuda_fp16.h>
extern "C" __global__ void rmsnorm_f16(
const __half* __restrict__ input,
const __half* __restrict__ weight,
__half* __restrict__ output,
int hidden_size,
float eps
) {
// Each block handles one row (token)
int row = blockIdx.x;
int tid = threadIdx.x;
int block_size = blockDim.x;
// Shared memory for reduction
extern __shared__ float shared[];
// Compute offset for this row
const __half* row_input = input + row * hidden_size;
__half* row_output = output + row * hidden_size;
// Step 1: Compute sum of squares
float sum_sq = 0.0f;
for (int i = tid; i < hidden_size; i += block_size) {
float val = __half2float(row_input[i]);
sum_sq += val * val;
}
// Warp-level reduction
for (int offset = 16; offset > 0; offset /= 2) {
sum_sq += __shfl_down_sync(0xffffffff, sum_sq, offset);
}
// First thread in each warp writes to shared memory
int lane = tid & 31;
int warp_id = tid >> 5;
if (lane == 0) {
shared[warp_id] = sum_sq;
}
__syncthreads();
// First warp reduces across warps
if (tid < 32) {
int num_warps = (block_size + 31) / 32;
sum_sq = (tid < num_warps) ? shared[tid] : 0.0f;
for (int offset = 16; offset > 0; offset /= 2) {
sum_sq += __shfl_down_sync(0xffffffff, sum_sq, offset);
}
if (tid == 0) {
shared[0] = sum_sq;
}
}
__syncthreads();
// Compute normalization factor
float mean_sq = shared[0] / (float)hidden_size;
float rsqrt_val = rsqrtf(mean_sq + eps);
// Step 2: Normalize and scale
for (int i = tid; i < hidden_size; i += block_size) {
float val = __half2float(row_input[i]);
float w = __half2float(weight[i]);
float out = val * rsqrt_val * w;
row_output[i] = __float2half(out);
}
}
"#;
pub struct RMSNormKernel {
device: Arc<CudaDevice>,
func: Option<CudaFunction>,
}
impl std::fmt::Debug for RMSNormKernel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RMSNormKernel")
.field("loaded", &self.func.is_some())
.finish()
}
}
impl RMSNormKernel {
pub fn new(device: Arc<CudaDevice>) -> Result<Self, InferenceError> {
let mut kernel = Self { device, func: None };
kernel.load_kernel()?;
Ok(kernel)
}
fn load_kernel(&mut self) -> Result<(), InferenceError> {
let ptx = compile_cuda_kernel(RMSNORM_CUDA)
.map_err(|e| InferenceError::Kernel(format!("NVRTC compilation failed: {}", e)))?;
self.device
.load_ptx(ptx, "rmsnorm", &["rmsnorm_f16"])
.map_err(|e| InferenceError::Kernel(format!("Failed to load PTX: {}", e)))?;
self.func = Some(
self.device
.get_func("rmsnorm", "rmsnorm_f16")
.ok_or_else(|| {
InferenceError::Kernel("Failed to get rmsnorm_f16 function".to_string())
})?,
);
Ok(())
}
pub fn forward(
&self,
input: &GpuTensor,
weight: &GpuTensor,
output: &mut GpuTensor,
eps: f32,
) -> Result<(), InferenceError> {
let func = self
.func
.as_ref()
.ok_or_else(|| InferenceError::Kernel("RMSNorm kernel not loaded".to_string()))?;
let shape = input.shape();
if shape.len() != 2 {
return Err(InferenceError::Shape {
expected: "2D tensor [tokens, hidden]".to_string(),
got: format!("{:?}", shape),
});
}
let num_tokens = shape[0];
let hidden_size = shape[1];
let block_size = 256usize;
let num_warps = (block_size + 31) / 32;
let shared_mem = num_warps * std::mem::size_of::<f32>();
let cfg = LaunchConfig {
block_dim: (block_size as u32, 1, 1),
grid_dim: (num_tokens as u32, 1, 1),
shared_mem_bytes: shared_mem as u32,
};
unsafe {
func.clone()
.launch(
cfg,
(
input.device_ptr(),
weight.device_ptr(),
output.device_ptr(),
hidden_size as i32,
eps,
),
)
.map_err(|e| InferenceError::Kernel(e.to_string()))?;
}
Ok(())
}
pub fn device(&self) -> &Arc<CudaDevice> {
&self.device
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rmsnorm_kernel_creation() {
let result = compile_cuda_kernel(RMSNORM_CUDA);
if let Err(e) = &result {
}
}
}