use cudarc::driver::{CudaDevice, CudaFunction, LaunchAsync, LaunchConfig};
use std::sync::Arc;
use super::compile_cuda_kernel;
use crate::cuda_inference::tensor::GpuTensor;
use crate::cuda_inference::InferenceError;
const ROPE_CUDA: &str = r#"
#include <cuda_fp16.h>
extern "C" __global__ void rope_f16(
__half* __restrict__ x,
const int* __restrict__ positions,
float theta,
int head_dim,
float scaling_factor,
int num_heads
) {
// Thread layout:
// - blockIdx.x = batch index
// - blockIdx.y = head index
// - threadIdx.x = dimension pair (0 to head_dim/2 - 1)
int batch_idx = blockIdx.x;
int head_idx = blockIdx.y;
int dim_pair = threadIdx.x;
int half_dim = head_dim / 2;
// Check bounds
if (dim_pair >= half_dim) return;
// Load position for this batch element
int position = positions[batch_idx];
// Apply scaling factor
float scaled_position = (float)position * scaling_factor;
// Compute frequency: freq = theta^(-2*dim_pair/head_dim)
float exponent = (2.0f * (float)dim_pair) / (float)head_dim;
float freq = powf(theta, -exponent);
// angle = position * freq
float angle = scaled_position * freq;
// Compute sin and cos
float sin_val = sinf(angle);
float cos_val = cosf(angle);
// Calculate offset: [batch, heads, head_dim]
int offset = batch_idx * num_heads * head_dim + head_idx * head_dim + 2 * dim_pair;
// Load x[2i] and x[2i+1]
float x_even = __half2float(x[offset]);
float x_odd = __half2float(x[offset + 1]);
// Apply rotation:
// x_rot[2i] = x[2i] * cos - x[2i+1] * sin
// x_rot[2i+1] = x[2i] * sin + x[2i+1] * cos
float x_rot_even = x_even * cos_val - x_odd * sin_val;
float x_rot_odd = x_even * sin_val + x_odd * cos_val;
// Store back
x[offset] = __float2half(x_rot_even);
x[offset + 1] = __float2half(x_rot_odd);
}
extern "C" __global__ void rope_precompute_freqs(
float* __restrict__ freqs,
float theta,
int head_dim,
int max_seq_len
) {
// Thread processes one dimension pair
int dim_pair = threadIdx.x;
int half_dim = head_dim / 2;
if (dim_pair >= half_dim) return;
// Compute base frequency for this dimension pair
float exponent = (2.0f * (float)dim_pair) / (float)head_dim;
float freq = powf(theta, -exponent);
// Loop over positions
for (int pos = 0; pos < max_seq_len; pos++) {
float angle = (float)pos * freq;
// Compute cos and sin
float cos_val = cosf(angle);
float sin_val = sinf(angle);
// Store at freqs[pos * head_dim + dim_pair * 2]
int offset = pos * head_dim + dim_pair * 2;
freqs[offset] = cos_val;
freqs[offset + 1] = sin_val;
}
}
"#;
pub struct RoPEKernel {
device: Arc<CudaDevice>,
func: Option<CudaFunction>,
precompute_func: Option<CudaFunction>,
}
impl std::fmt::Debug for RoPEKernel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RoPEKernel")
.field("loaded", &self.func.is_some())
.finish()
}
}
impl RoPEKernel {
pub fn new(device: Arc<CudaDevice>) -> Result<Self, InferenceError> {
let mut kernel = Self {
device,
func: None,
precompute_func: None,
};
kernel.load_kernel()?;
Ok(kernel)
}
fn load_kernel(&mut self) -> Result<(), InferenceError> {
let ptx = compile_cuda_kernel(ROPE_CUDA)
.map_err(|e| InferenceError::Kernel(format!("NVRTC compilation failed: {}", e)))?;
self.device
.load_ptx(ptx, "rope_kernels", &["rope_f16", "rope_precompute_freqs"])
.map_err(|e| InferenceError::Kernel(format!("Failed to load PTX: {}", e)))?;
self.func = Some(
self.device
.get_func("rope_kernels", "rope_f16")
.ok_or_else(|| {
InferenceError::Kernel("Failed to get rope_f16 function".to_string())
})?,
);
self.precompute_func = Some(
self.device
.get_func("rope_kernels", "rope_precompute_freqs")
.ok_or_else(|| {
InferenceError::Kernel(
"Failed to get rope_precompute_freqs function".to_string(),
)
})?,
);
Ok(())
}
pub fn forward(
&self,
x: &mut GpuTensor,
positions: &GpuTensor,
theta: f32,
scaling_factor: f32,
) -> Result<(), InferenceError> {
let func = self
.func
.as_ref()
.ok_or_else(|| InferenceError::Kernel("RoPE kernel not loaded".to_string()))?;
let x_shape = x.shape();
if x_shape.len() != 3 {
return Err(InferenceError::Shape {
expected: "[batch, heads, head_dim]".to_string(),
got: format!("{:?}", x_shape),
});
}
let batch = x_shape[0];
let num_heads = x_shape[1];
let head_dim = x_shape[2];
if head_dim % 2 != 0 {
return Err(InferenceError::Shape {
expected: "head_dim must be even".to_string(),
got: format!("head_dim = {}", head_dim),
});
}
let pos_shape = positions.shape();
if pos_shape.len() != 1 || pos_shape[0] != batch {
return Err(InferenceError::Shape {
expected: format!("[{}]", batch),
got: format!("{:?}", pos_shape),
});
}
let cfg = LaunchConfig {
grid_dim: (batch as u32, num_heads as u32, 1),
block_dim: ((head_dim / 2) as u32, 1, 1),
shared_mem_bytes: 0,
};
unsafe {
func.clone().launch(
cfg,
(
x.device_ptr(),
positions.device_ptr(),
theta,
head_dim as i32,
scaling_factor,
num_heads as i32,
),
)
}
.map_err(|e| InferenceError::Kernel(format!("RoPE kernel launch failed: {}", e)))?;
Ok(())
}
pub fn precompute_freqs(
&self,
freqs: &mut GpuTensor,
theta: f32,
head_dim: usize,
max_seq_len: usize,
) -> Result<(), InferenceError> {
let func = self.precompute_func.as_ref().ok_or_else(|| {
InferenceError::Kernel("RoPE precompute kernel not loaded".to_string())
})?;
if head_dim % 2 != 0 {
return Err(InferenceError::Shape {
expected: "head_dim must be even".to_string(),
got: format!("head_dim = {}", head_dim),
});
}
let cfg = LaunchConfig {
grid_dim: (1, 1, 1),
block_dim: ((head_dim / 2) as u32, 1, 1),
shared_mem_bytes: 0,
};
unsafe {
func.clone().launch(
cfg,
(
freqs.device_ptr(),
theta,
head_dim as i32,
max_seq_len as i32,
),
)
}
.map_err(|e| InferenceError::Kernel(format!("RoPE precompute launch failed: {}", e)))?;
Ok(())
}
pub fn forward_with_cache(
&self,
_x: &mut GpuTensor,
_freqs: &GpuTensor,
_position: usize,
) -> Result<(), InferenceError> {
Err(InferenceError::Kernel(
"Cached RoPE forward not yet implemented".to_string(),
))
}
pub fn device(&self) -> &Arc<CudaDevice> {
&self.device
}
}
pub fn ntk_aware_scaling(original_max_len: usize, desired_max_len: usize, base_theta: f32) -> f32 {
if desired_max_len <= original_max_len {
return base_theta;
}
let scale = desired_max_len as f32 / original_max_len as f32;
base_theta * scale
}
pub fn linear_scaling(original_max_len: usize, desired_max_len: usize) -> f32 {
if desired_max_len <= original_max_len {
return 1.0;
}
original_max_len as f32 / desired_max_len as f32
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rope_kernel_compilation() {
let result = compile_cuda_kernel(ROPE_CUDA);
if let Err(e) = &result {
}
}
#[test]
fn test_ntk_scaling_no_extension() {
let theta = ntk_aware_scaling(4096, 4096, 10000.0);
assert!((theta - 10000.0).abs() < 0.01);
}
#[test]
fn test_ntk_scaling_2x() {
let theta = ntk_aware_scaling(4096, 8192, 10000.0);
assert!(theta > 10000.0);
assert!(theta < 30000.0); }
#[test]
fn test_linear_scaling_no_extension() {
let scale = linear_scaling(4096, 4096);
assert!((scale - 1.0).abs() < 0.01);
}
#[test]
fn test_linear_scaling_2x() {
let scale = linear_scaling(4096, 8192);
assert!((scale - 0.5).abs() < 0.01);
}
}