#![cfg(all(feature = "candle", feature = "cuda", feature = "fused-act"))]
use std::sync::Arc;
use candle_core::{DType, Device, Tensor};
use cudarc::driver::{CudaContext, CudaModule, CudaSlice, CudaStream, LaunchConfig, PushKernelArg};
use cudarc::nvrtc::compile_ptx;
use once_cell::sync::OnceCell;
use crate::error::{Result, RuvLLMError};
const ACT_KERNEL_SRC: &str = r#"
// act_fused.cu — fused ACT step for OpenMythos / RDT recurrent loops.
//
// Each CUDA thread handles one token position. The state arrays live entirely
// in registers or L1 cache for the common case (n <= 512), so global-memory
// traffic is reduced to one read and one write per state element per iteration.
// BF16 → F32 without cuda_bf16.h: BF16 occupies the upper 16 bits of F32.
__device__ __forceinline__ float bf16_to_f32(unsigned short x) {
return __int_as_float((unsigned int)x << 16);
}
// F32 variant: p tensor already in F32.
extern "C" __global__ void act_fused_step_f32(
const float* p, // [n] halt prob (F32, read-only)
float* __restrict__ cum, // [n] cumulative prob (in-out)
float* __restrict__ not_halted, // [n] 1.0=running, 0.0=done (in-out)
float* __restrict__ depth, // [n] halt iteration index (in-out)
float* __restrict__ w_out, // [n] weight for h_out accum (out)
int n,
float threshold,
float step_plus_one
) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i >= n) return;
float pi = p[i];
float ci = cum[i];
float ni = not_halted[i];
float di = depth[i];
float p_eff = pi * ni;
float new_cum = ci + p_eff;
float wh = (new_cum >= threshold && ni > 0.5f) ? 1.0f : 0.0f;
float still = ni - wh;
w_out[i] = wh * (1.0f - ci) + still * p_eff;
cum[i] = ci + still * p_eff;
not_halted[i] = still;
depth[i] = di + wh * (step_plus_one - di);
}
// BF16 variant: p tensor in BF16, passed as raw u16 bits.
extern "C" __global__ void act_fused_step_bf16(
const unsigned short* p, // [n] halt prob (BF16 as u16, read-only)
float* __restrict__ cum,
float* __restrict__ not_halted,
float* __restrict__ depth,
float* __restrict__ w_out,
int n,
float threshold,
float step_plus_one
) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i >= n) return;
float pi = bf16_to_f32(p[i]);
float ci = cum[i];
float ni = not_halted[i];
float di = depth[i];
float p_eff = pi * ni;
float new_cum = ci + p_eff;
float wh = (new_cum >= threshold && ni > 0.5f) ? 1.0f : 0.0f;
float still = ni - wh;
w_out[i] = wh * (1.0f - ci) + still * p_eff;
cum[i] = ci + still * p_eff;
not_halted[i] = still;
depth[i] = di + wh * (step_plus_one - di);
}
"#;
use cudarc::nvrtc::Ptx;
static COMPILED_PTX: OnceCell<Ptx> = OnceCell::new();
fn get_or_compile_ptx() -> Result<Ptx> {
COMPILED_PTX
.get_or_try_init(|| {
compile_ptx(ACT_KERNEL_SRC)
.map_err(|e| RuvLLMError::Model(format!("nvrtc compile act_fused: {e}")))
})
.cloned()
}
fn load_module(ctx: &Arc<CudaContext>) -> Result<Arc<CudaModule>> {
let ptx = get_or_compile_ptx()?;
ctx.load_module(ptx)
.map_err(|e| RuvLLMError::Model(format!("cudarc load_module: {e}")))
}
pub struct FusedActKernel {
stream: Arc<CudaStream>,
module: Arc<CudaModule>,
cum: CudaSlice<f32>,
not_halted: CudaSlice<f32>,
depth: CudaSlice<f32>,
w_out: CudaSlice<f32>,
pub n: usize,
}
impl FusedActKernel {
pub fn new(n: usize) -> Result<Self> {
let ctx = CudaContext::new(0)
.map_err(|e| RuvLLMError::Model(format!("cudarc CudaContext::new: {e}")))?;
let stream = ctx.default_stream();
let module = load_module(&ctx)?;
let cum = stream
.clone_htod(vec![0.0f32; n].as_slice())
.map_err(|e| RuvLLMError::Model(format!("htod cum: {e}")))?;
let not_halted = stream
.clone_htod(vec![1.0f32; n].as_slice())
.map_err(|e| RuvLLMError::Model(format!("htod not_halted: {e}")))?;
let depth = stream
.clone_htod(vec![0.0f32; n].as_slice())
.map_err(|e| RuvLLMError::Model(format!("htod depth: {e}")))?;
let w_out = stream
.clone_htod(vec![0.0f32; n].as_slice())
.map_err(|e| RuvLLMError::Model(format!("htod w_out: {e}")))?;
Ok(Self {
stream,
module,
cum,
not_halted,
depth,
w_out,
n,
})
}
pub fn step(
&mut self,
p_tensor: &Tensor,
b: usize,
seq: usize,
threshold: f32,
t: usize,
) -> Result<Tensor> {
let p_flat = p_tensor
.flatten_all()
.map_err(|e| RuvLLMError::Model(format!("flatten p: {e}")))?;
let n_i32 = self.n as i32;
let step_f32 = (t + 1) as f32;
let cfg = LaunchConfig::for_num_elems(self.n as u32);
match p_flat.dtype() {
DType::F32 => {
let p_host: Vec<f32> = p_flat
.to_vec1()
.map_err(|e| RuvLLMError::Model(format!("to_vec1 f32: {e}")))?;
let p_dev = self
.stream
.clone_htod(p_host.as_slice())
.map_err(|e| RuvLLMError::Model(format!("htod p_f32: {e}")))?;
let f = self
.module
.load_function("act_fused_step_f32")
.map_err(|e| RuvLLMError::Model(format!("load_function f32: {e}")))?;
unsafe {
self.stream
.launch_builder(&f)
.arg(&p_dev)
.arg(&mut self.cum)
.arg(&mut self.not_halted)
.arg(&mut self.depth)
.arg(&mut self.w_out)
.arg(&n_i32)
.arg(&threshold)
.arg(&step_f32)
.launch(cfg)
.map_err(|e| RuvLLMError::Model(format!("launch f32: {e}")))?;
}
}
DType::BF16 => {
let p_host: Vec<half::bf16> = p_flat
.to_vec1()
.map_err(|e| RuvLLMError::Model(format!("to_vec1 bf16: {e}")))?;
let p_u16: Vec<u16> = p_host.iter().map(|x| x.to_bits()).collect();
let p_dev = self
.stream
.clone_htod(p_u16.as_slice())
.map_err(|e| RuvLLMError::Model(format!("htod p_bf16: {e}")))?;
let f = self
.module
.load_function("act_fused_step_bf16")
.map_err(|e| RuvLLMError::Model(format!("load_function bf16: {e}")))?;
unsafe {
self.stream
.launch_builder(&f)
.arg(&p_dev)
.arg(&mut self.cum)
.arg(&mut self.not_halted)
.arg(&mut self.depth)
.arg(&mut self.w_out)
.arg(&n_i32)
.arg(&threshold)
.arg(&step_f32)
.launch(cfg)
.map_err(|e| RuvLLMError::Model(format!("launch bf16: {e}")))?;
}
}
other => {
return Err(RuvLLMError::Model(format!(
"fused-act: p dtype must be F32 or BF16, got {other:?}"
)));
}
}
let w_host = self
.stream
.clone_dtoh(&self.w_out)
.map_err(|e| RuvLLMError::Model(format!("dtoh w_out: {e}")))?;
let w_cpu = Tensor::from_slice(&w_host, (b, seq, 1), &Device::Cpu)
.map_err(|e| RuvLLMError::Model(format!("from_slice w: {e}")))?;
w_cpu
.to_device(p_tensor.device())
.map_err(|e| RuvLLMError::Model(format!("to_device w: {e}")))
}
pub fn all_halted(&self) -> Result<bool> {
let v = self
.stream
.clone_dtoh(&self.not_halted)
.map_err(|e| RuvLLMError::Model(format!("dtoh not_halted: {e}")))?;
Ok(v.iter().sum::<f32>() < 0.5)
}
pub fn depths(&self) -> Result<Vec<usize>> {
let v = self
.stream
.clone_dtoh(&self.depth)
.map_err(|e| RuvLLMError::Model(format!("dtoh depth: {e}")))?;
Ok(v.iter().map(|&d| d as usize).collect())
}
}
pub unsafe fn with_tensor_f32_ptr<R, F: FnOnce(u64) -> R>(
tensor: &Tensor,
f: F,
) -> Result<R> {
use candle_core::Storage;
use cudarc::driver::DevicePtr;
let cuda_dev = tensor
.device()
.as_cuda_device()
.map_err(|e| RuvLLMError::Model(format!("not CUDA: {e}")))?;
let stream = cuda_dev.cuda_stream();
let (storage, layout) = tensor.storage_and_layout();
let Storage::Cuda(ref cs) = *storage else {
return Err(RuvLLMError::Model("tensor not on CUDA device".into()));
};
let slice = cs
.as_cuda_slice::<f32>()
.map_err(|e| RuvLLMError::Model(format!("dtype: {e}")))?;
let offset_bytes = (layout.start_offset() * 4) as u64;
let (base_ptr, _guard) = slice.device_ptr(&stream);
let result = f(base_ptr + offset_bytes);
Ok(result)
}
pub unsafe fn with_tensor_bf16_ptr<R, F: FnOnce(u64) -> R>(
tensor: &Tensor,
f: F,
) -> Result<R> {
use candle_core::Storage;
use cudarc::driver::DevicePtr;
use half::bf16;
let cuda_dev = tensor
.device()
.as_cuda_device()
.map_err(|e| RuvLLMError::Model(format!("not CUDA: {e}")))?;
let stream = cuda_dev.cuda_stream();
let (storage, layout) = tensor.storage_and_layout();
let Storage::Cuda(ref cs) = *storage else {
return Err(RuvLLMError::Model("tensor not on CUDA device".into()));
};
let slice = cs
.as_cuda_slice::<bf16>()
.map_err(|e| RuvLLMError::Model(format!("dtype: {e}")))?;
let offset_bytes = (layout.start_offset() * 2) as u64;
let (base_ptr, _guard) = slice.device_ptr(&stream);
let result = f(base_ptr + offset_bytes);
Ok(result)
}
pub struct FusedActZeroCopy {
kernel: FusedActKernel,
w_candle: Tensor,
}
impl FusedActZeroCopy {
pub fn new(n: usize, device: &candle_core::Device) -> Result<Self> {
let kernel = FusedActKernel::new(n)?;
let w_candle = candle_core::Tensor::zeros((n,), DType::F32, device)
.map_err(|e| RuvLLMError::Model(format!("w_candle alloc: {e}")))?;
Ok(Self { kernel, w_candle })
}
pub fn step_zero_copy(
&mut self,
p_tensor: &Tensor,
threshold: f32,
t: usize,
) -> Result<&Tensor> {
let n_i32 = self.kernel.n as i32;
let step_f32 = (t + 1) as f32;
let cfg = LaunchConfig::for_num_elems(self.kernel.n as u32);
match p_tensor.dtype() {
DType::F32 => {
let f = self
.kernel
.module
.load_function("act_fused_step_f32")
.map_err(|e| RuvLLMError::Model(format!("load_function: {e}")))?;
let kernel = &mut self.kernel;
let w_candle = &self.w_candle;
unsafe {
with_tensor_f32_ptr(p_tensor, |p_ptr| {
with_tensor_f32_ptr(w_candle, |w_ptr| {
kernel
.stream
.launch_builder(&f)
.arg(&p_ptr)
.arg(&mut kernel.cum)
.arg(&mut kernel.not_halted)
.arg(&mut kernel.depth)
.arg(&w_ptr)
.arg(&n_i32)
.arg(&threshold)
.arg(&step_f32)
.launch(cfg)
})
})
}
.map_err(|e| RuvLLMError::Model(format!("zero-copy launch: {e}")))?
.map_err(|e| RuvLLMError::Model(format!("inner launch: {e}")))?
.map_err(|e| RuvLLMError::Model(format!("kernel: {e}")))?;
}
other => {
return Err(RuvLLMError::Model(format!(
"zero-copy ACT: dtype {other:?} not yet supported (add BF16 with_tensor_bf16_ptr)"
)));
}
}
Ok(&self.w_candle)
}
pub fn all_halted(&self) -> Result<bool> {
self.kernel.all_halted()
}
pub fn depths(&self) -> Result<Vec<usize>> {
self.kernel.depths()
}
}