//! Fused CUDA kernels for PINN acceleration //! //! This module provides highly optimized fused CUDA kernels that combine multiple //! operations into a single kernel launch, dramatically reducing GPU overhead. //! //! Key optimizations: //! - Fused Fourier features: matmul + scale + sin/cos + concat in ONE kernel //! - Shared memory tiling for weight matrix reuse //! - Coalesced memory access patterns //! - Eliminates 4+ kernel launches and synchronization barriers use rtx_tensor::{Tensor, TensorError, Result, Device}; use std::sync::Arc; // NOTE: CUDA contexts are now managed by the global singleton cache in rtx-tensor // (rtx_tensor::storage::cuda_manager::get_or_create_context) #[cfg(feature = "cuda")] use cudarc::driver::{LaunchConfig, PushKernelArg}; #[cfg(feature = "cuda")] use cudarc::nvrtc::Ptx; /// Fused Fourier features computation /// /// Combines: matmul(x, B) * scale -> sin/cos -> concatenate /// into a single CUDA kernel with shared memory optimization. /// /// Input: x [batch, 1] /// Weights: B [1, ff_dim] /// Output: out [batch, ff_dim * 2] where out = [sin(x @ B * scale), cos(x @ B * scale)] #[cfg(feature = "cuda")] pub fn fused_fourier_features( x: &Tensor, b_weights: &Tensor, scale: f32, output: &mut Tensor, ) -> Result<()> { // Validate inputs let x_shape = x.shape(); let b_shape = b_weights.shape(); let out_shape = output.shape(); let batch_size = x_shape.dims()[0]; let ff_dim = b_shape.dims()[1]; // Validate shapes if x_shape.dims().len() != 2 || x_shape.dims()[1] != 1 { return Err(TensorError::shape(format!( "x must be [batch, 1], got {:?}", x_shape.dims() ))); } if b_shape.dims().len() != 2 || b_shape.dims()[0] != 1 { return Err(TensorError::shape(format!( "B must be [1, ff_dim], got {:?}", b_shape.dims() ))); } if out_shape.dims() != [batch_size, ff_dim * 2] { return Err(TensorError::shape(format!( "output must be [{}, {}], got {:?}", batch_size, ff_dim * 2, out_shape.dims() ))); } // Get device ID let device_id = match x.device() { Device::Cuda(id) => *id, _ => return Err(TensorError::device("fused_fourier_features requires CUDA device")), }; // Launch the fused kernel launch_fused_fourier_kernel(x, b_weights, scale, output, batch_size, ff_dim, device_id) } #[cfg(feature = "cuda")] fn launch_fused_fourier_kernel( x: &Tensor, b_weights: &Tensor, scale: f32, output: &mut Tensor, batch_size: usize, ff_dim: usize, device_id: usize, ) -> Result<()> { // PTX for fused Fourier features kernel // This kernel: // 1. Loads B weights into shared memory (reused across all batch elements) // 2. For each batch element: computes x * B[j] * scale // 3. Applies sin() and cos() // 4. Writes directly to concatenated output buffer static FUSED_FOURIER_PTX: &str = r#" .version 7.0 .target sm_50 .address_size 64 // Fused Fourier features kernel // Input: x[batch, 1], B[1, ff_dim] // Output: out[batch, ff_dim*2] = [sin(x*B*scale), cos(x*B*scale)] // // Grid: (batch_size, 1, 1) // Block: (ff_dim, 1, 1) - one thread per output pair .visible .entry fused_fourier_kernel( .param .u64 x_ptr, // Input tensor [batch, 1] .param .u64 b_ptr, // Weight matrix [1, ff_dim] .param .u64 out_ptr, // Output tensor [batch, ff_dim*2] .param .f32 scale, // Scale factor (2*pi) .param .u32 batch_size, .param .u32 ff_dim ) { // Shared memory for B weights - loaded once, reused for all batch elements .shared .align 4 .f32 B_shared[256]; // Max ff_dim = 256 .reg .u32 %tid, %bid, %ff; .reg .u64 %x_addr, %b_addr, %out_addr; .reg .u64 %offset, %sin_offset, %cos_offset; .reg .f32 %x_val, %b_val, %prod, %scaled, %s, %c, %scale_val; .reg .pred %p_bounds, %p_load; .reg .u32 %ff_dim_reg, %batch_reg, %ff_dim_2; // Get thread and block indices mov.u32 %tid, %tid.x; // Thread ID = which ff_dim element mov.u32 %bid, %ctaid.x; // Block ID = which batch element // Load parameters ld.param.u32 %ff_dim_reg, [ff_dim]; ld.param.u32 %batch_reg, [batch_size]; ld.param.f32 %scale_val, [scale]; // Bounds check setp.ge.u32 %p_bounds, %tid, %ff_dim_reg; setp.ge.u32 %p_load, %bid, %batch_reg; @%p_bounds bra DONE; @%p_load bra DONE; // === Phase 1: Load B weights into shared memory === // Each thread loads one element ld.param.u64 %b_addr, [b_ptr]; cvt.u64.u32 %offset, %tid; shl.b64 %offset, %offset, 2; // * sizeof(float) add.u64 %b_addr, %b_addr, %offset; ld.global.f32 %b_val, [%b_addr]; // Store to shared memory mov.u32 %ff, %tid; st.shared.f32 [B_shared + %ff * 4], %b_val; // Synchronize - ensure all B weights are loaded bar.sync 0; // === Phase 2: Compute x * B * scale for this batch element === // Load x[bid] ld.param.u64 %x_addr, [x_ptr]; cvt.u64.u32 %offset, %bid; shl.b64 %offset, %offset, 2; // * sizeof(float) add.u64 %x_addr, %x_addr, %offset; ld.global.f32 %x_val, [%x_addr]; // Load B[tid] from shared memory ld.shared.f32 %b_val, [B_shared + %ff * 4]; // Compute: x * B * scale mul.f32 %prod, %x_val, %b_val; mul.f32 %scaled, %prod, %scale_val; // === Phase 3: Compute sin and cos === sin.approx.f32 %s, %scaled; cos.approx.f32 %c, %scaled; // === Phase 4: Write to output [batch, ff_dim*2] === // sin goes to out[bid, tid] // cos goes to out[bid, ff_dim + tid] ld.param.u64 %out_addr, [out_ptr]; mul.lo.u32 %ff_dim_2, %ff_dim_reg, 2; // Calculate sin output address: out_ptr + (bid * ff_dim * 2 + tid) * 4 cvt.u64.u32 %sin_offset, %bid; cvt.u64.u32 %offset, %ff_dim_2; mul.lo.u64 %sin_offset, %sin_offset, %offset; cvt.u64.u32 %offset, %tid; add.u64 %sin_offset, %sin_offset, %offset; shl.b64 %sin_offset, %sin_offset, 2; add.u64 %out_addr, %out_addr, %sin_offset; st.global.f32 [%out_addr], %s; // Calculate cos output address: sin_addr + ff_dim * 4 cvt.u64.u32 %cos_offset, %ff_dim_reg; shl.b64 %cos_offset, %cos_offset, 2; add.u64 %out_addr, %out_addr, %cos_offset; st.global.f32 [%out_addr], %c; DONE: ret; } "#; // Use global singleton context cache from rtx-tensor for CUDA graph capture compatibility let ctx = rtx_tensor::storage::cuda_manager::get_or_create_context(device_id)?; // Load PTX module let module = ctx.load_module(Ptx::from_src(FUSED_FOURIER_PTX)) .map_err(|e| TensorError::kernel(format!("Failed to load fused_fourier PTX: {:?}", e)))?; let func = module.load_function("fused_fourier_kernel") .map_err(|e| TensorError::kernel(format!("Failed to load fused_fourier kernel: {:?}", e)))?; // Get exclusive access to output buffer output.make_exclusive()?; // Get CUDA slices let x_slice = x.storage().cuda_slice_clone() .map_err(|e| TensorError::device(format!("Failed to get x CUDA slice: {}", e)))?; let b_slice = b_weights.storage().cuda_slice_clone() .map_err(|e| TensorError::device(format!("Failed to get B CUDA slice: {}", e)))?; let mut out_slice = Arc::get_mut(&mut output.storage_mut()) .ok_or_else(|| TensorError::runtime("Failed to get exclusive output access"))? .cuda_slice_clone() .map_err(|e| TensorError::device(format!("Failed to get output CUDA slice: {}", e)))?; // Launch kernel // Grid: one block per batch element // Block: one thread per ff_dim element let block_size = ff_dim.min(256) as u32; // Max 256 threads per block let grid_size = batch_size as u32; let cfg = LaunchConfig { grid_dim: (grid_size, 1, 1), block_dim: (block_size, 1, 1), shared_mem_bytes: (ff_dim * 4) as u32, // B weights in shared memory }; let stream = ctx.default_stream(); unsafe { stream.launch_builder(&func) .arg(&x_slice) .arg(&b_slice) .arg(&mut out_slice) .arg(&scale) .arg(&(batch_size as u32)) .arg(&(ff_dim as u32)) .launch(cfg) .map_err(|e| TensorError::kernel(format!("fused_fourier kernel launch failed: {:?}", e)))?; } // REMOVED: stream.synchronize() - unnecessary because: // 1. CUDA streams guarantee operation ordering within the same stream // 2. Subsequent operations (linear layers) use the same stream // 3. Sync is only needed when reading data back to CPU (which we defer) // This removal saves ~5-10µs per forward pass. Ok(()) } #[cfg(not(feature = "cuda"))] pub fn fused_fourier_features( _x: &Tensor, _b_weights: &Tensor, _scale: f32, _output: &mut Tensor, ) -> Result<()> { Err(TensorError::device("fused_fourier_features requires CUDA feature")) } #[cfg(test)] mod tests { use super::*; #[test] #[cfg(feature = "cuda")] fn test_fused_fourier_features() { use rtx_tensor::Device; let device = Device::cuda(0); let batch_size = 100; let ff_dim = 64; // Create test tensors let x = Tensor::randn([batch_size, 1], &device).unwrap(); let b = Tensor::randn([1, ff_dim], &device).unwrap(); let mut output = Tensor::zeros([batch_size, ff_dim * 2], &device).unwrap(); // Run fused kernel fused_fourier_features(&x, &b, 2.0 * std::f32::consts::PI, &mut output).unwrap(); // Verify output shape assert_eq!(output.shape().dims(), &[batch_size, ff_dim * 2]); } }