mod attention;
mod batched;
pub use batched::{dispatch_oxicuda_matmul, dispatch_oxicuda_matmul_resident, BatchedMatmulPlan};
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use oxicuda_blas::level3::gemm_api::gemm;
use oxicuda_blas::{BlasHandle, Layout, MatrixDesc, MatrixDescMut, Transpose};
use oxicuda_dnn::norm::layer_norm;
use oxicuda_dnn::types::{TensorDesc, TensorDescMut};
use oxicuda_dnn::DnnHandle;
use oxicuda_memory::DeviceBuffer;
use crate::errors::TrustformersError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct OxiCudaBufferId(u64);
impl OxiCudaBufferId {
pub fn new() -> Self {
static COUNTER: AtomicU64 = AtomicU64::new(0);
OxiCudaBufferId(COUNTER.fetch_add(1, Ordering::SeqCst))
}
#[inline]
pub fn raw(&self) -> u64 {
self.0
}
}
impl Default for OxiCudaBufferId {
fn default() -> Self {
Self::new()
}
}
type ReleaseFn = Box<dyn Fn(usize, OxiCudaBufferId) + Send + Sync + 'static>;
struct BufferHandleInner {
buffer_id: OxiCudaBufferId,
device_id: usize,
release: ReleaseFn,
}
impl Drop for BufferHandleInner {
fn drop(&mut self) {
(self.release)(self.device_id, self.buffer_id);
}
}
pub struct OxiCudaBufferHandle {
inner: Arc<BufferHandleInner>,
}
impl OxiCudaBufferHandle {
pub fn new(buffer_id: OxiCudaBufferId, device_id: usize) -> Self {
Self::with_release(buffer_id, device_id, Box::new(release_persistent_buffer))
}
pub(crate) fn with_release(
buffer_id: OxiCudaBufferId,
device_id: usize,
release: ReleaseFn,
) -> Self {
Self {
inner: Arc::new(BufferHandleInner {
buffer_id,
device_id,
release,
}),
}
}
#[inline]
pub fn id(&self) -> OxiCudaBufferId {
self.inner.buffer_id
}
#[inline]
pub fn device_id(&self) -> usize {
self.inner.device_id
}
#[inline]
pub fn ref_count(&self) -> usize {
Arc::strong_count(&self.inner)
}
}
impl Clone for OxiCudaBufferHandle {
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
}
}
}
impl std::fmt::Debug for OxiCudaBufferHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OxiCudaBufferHandle")
.field("buffer_id", &self.inner.buffer_id)
.field("device_id", &self.inner.device_id)
.field("ref_count", &Arc::strong_count(&self.inner))
.finish()
}
}
fn release_persistent_buffer(device_id: usize, buffer_id: OxiCudaBufferId) {
let backend = match OXICUDA_BACKENDS.lock() {
Ok(cache) => cache.get(&device_id).cloned(),
Err(_) => None,
};
if let Some(backend) = backend {
let _ = backend.remove_persistent_buffer(&buffer_id);
}
}
pub fn default_cuda_device_id() -> usize {
static DEFAULT: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
*DEFAULT.get_or_init(|| {
std::env::var("TRUSTFORMERS_CUDA_DEVICE")
.ok()
.and_then(|value| value.trim().parse::<usize>().ok())
.unwrap_or(0)
})
}
pub struct OxicudaCudaBackend {
ctx: Arc<oxicuda_driver::Context>,
handle: BlasHandle,
buffer_cache: Mutex<HashMap<OxiCudaBufferId, DeviceBuffer<f32>>>,
}
impl OxicudaCudaBackend {
pub fn new(device_id: usize) -> crate::errors::Result<Self> {
oxicuda_driver::init().map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to initialize CUDA driver: {}", e),
"OxicudaCudaBackend::new",
)
})?;
let device_count = oxicuda_driver::Device::count().map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to enumerate CUDA devices: {}", e),
"OxicudaCudaBackend::new",
)
})?;
let device_count = usize::try_from(device_count).unwrap_or(0);
if device_id >= device_count {
return Err(TrustformersError::hardware_error(
&format!(
"CUDA device index {} out of range: {} device(s) available",
device_id, device_count
),
"OxicudaCudaBackend::new",
));
}
let device = oxicuda_driver::Device::get(device_id as i32).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to get CUDA device: {}", e),
"OxicudaCudaBackend::new",
)
})?;
let ctx = Arc::new(oxicuda_driver::Context::new(&device).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to create CUDA context: {}", e),
"OxicudaCudaBackend::new",
)
})?);
let handle = BlasHandle::new(&ctx).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to create cuBLAS handle: {}", e),
"OxicudaCudaBackend::new",
)
})?;
Ok(Self {
ctx,
handle,
buffer_cache: Mutex::new(HashMap::new()),
})
}
pub fn context(&self) -> &Arc<oxicuda_driver::Context> {
&self.ctx
}
pub fn create_persistent_buffer(&self, data: &[f32]) -> crate::errors::Result<OxiCudaBufferId> {
let buffer = DeviceBuffer::<f32>::from_host(data).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to copy data to device: {}", e),
"create_persistent_buffer",
)
})?;
let buffer_id = OxiCudaBufferId::new();
let mut cache = self.buffer_cache.lock().map_err(|_| {
TrustformersError::hardware_error(
"Failed to lock buffer cache",
"create_persistent_buffer",
)
})?;
cache.insert(buffer_id, buffer);
Ok(buffer_id)
}
pub fn create_persistent_buffer_zeroed(
&self,
len: usize,
) -> crate::errors::Result<OxiCudaBufferId> {
let buffer = DeviceBuffer::<f32>::zeroed(len).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to allocate device buffer of {} f32s: {}", len, e),
"create_persistent_buffer_zeroed",
)
})?;
let buffer_id = OxiCudaBufferId::new();
let mut cache = self.buffer_cache.lock().map_err(|_| {
TrustformersError::hardware_error(
"Failed to lock buffer cache",
"create_persistent_buffer_zeroed",
)
})?;
cache.insert(buffer_id, buffer);
Ok(buffer_id)
}
pub fn get_persistent_buffer(&self, id: &OxiCudaBufferId) -> crate::errors::Result<usize> {
let cache = self.buffer_cache.lock().map_err(|_| {
TrustformersError::hardware_error(
"Failed to lock buffer cache",
"get_persistent_buffer",
)
})?;
cache.get(id).map(|buf| buf.len()).ok_or_else(|| {
TrustformersError::hardware_error(
&format!("Buffer {:?} not found in cache", id),
"get_persistent_buffer",
)
})
}
pub fn remove_persistent_buffer(&self, id: &OxiCudaBufferId) -> crate::errors::Result<()> {
let mut cache = self.buffer_cache.lock().map_err(|_| {
TrustformersError::hardware_error(
"Failed to lock buffer cache",
"remove_persistent_buffer",
)
})?;
cache.remove(id);
Ok(())
}
pub fn clear_buffer_cache(&self) -> crate::errors::Result<()> {
let mut cache = self.buffer_cache.lock().map_err(|_| {
TrustformersError::hardware_error("Failed to lock buffer cache", "clear_buffer_cache")
})?;
cache.clear();
Ok(())
}
pub fn buffer_cache_size(&self) -> crate::errors::Result<usize> {
let cache = self.buffer_cache.lock().map_err(|_| {
TrustformersError::hardware_error("Failed to lock buffer cache", "buffer_cache_size")
})?;
Ok(cache.len())
}
fn synchronize_stream(&self, op: &'static str) -> crate::errors::Result<()> {
self.handle.stream().synchronize().map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to synchronize CUDA stream: {}", e),
op,
)
})
}
pub fn download_buffer(&self, buffer_id: &OxiCudaBufferId) -> crate::errors::Result<Vec<f32>> {
let cache = self.buffer_cache.lock().map_err(|_| {
TrustformersError::hardware_error("Failed to lock buffer cache", "download_buffer")
})?;
let buffer = cache.get(buffer_id).ok_or_else(|| {
TrustformersError::hardware_error(
&format!("Buffer {:?} not found in cache", buffer_id),
"download_buffer",
)
})?;
let mut result = vec![0.0f32; buffer.len()];
self.synchronize_stream("download_buffer")?;
buffer.copy_to_host(&mut result).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to copy data from device: {}", e),
"download_buffer",
)
})?;
Ok(result)
}
pub fn buffer_to_cpu(
&self,
buffer_id: &OxiCudaBufferId,
_size: usize,
) -> crate::errors::Result<Vec<f32>> {
self.download_buffer(buffer_id)
}
pub fn matmul_gpu_to_gpu(
&self,
input_buffer_id: &OxiCudaBufferId,
weight_buffer_id: &OxiCudaBufferId,
m: usize,
k: usize,
n: usize,
) -> crate::errors::Result<OxiCudaBufferId> {
let mut cache = self.buffer_cache.lock().map_err(|_| {
TrustformersError::hardware_error("Failed to lock buffer cache", "matmul_gpu_to_gpu")
})?;
let a_buf = cache.get(input_buffer_id).ok_or_else(|| {
TrustformersError::hardware_error(
&format!("Input buffer {:?} not found in cache", input_buffer_id),
"matmul_gpu_to_gpu",
)
})?;
let b_buf = cache.get(weight_buffer_id).ok_or_else(|| {
TrustformersError::hardware_error(
&format!("Weight buffer {:?} not found in cache", weight_buffer_id),
"matmul_gpu_to_gpu",
)
})?;
if a_buf.len() != m * k {
return Err(TrustformersError::shape_error(format!(
"Input buffer length {} doesn't match m {} * k {}",
a_buf.len(),
m,
k
)));
}
if b_buf.len() != k * n {
return Err(TrustformersError::shape_error(format!(
"Weight buffer length {} doesn't match k {} * n {}",
b_buf.len(),
k,
n
)));
}
let mut c_buf = DeviceBuffer::<f32>::alloc(m * n).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to allocate result buffer on device: {}", e),
"matmul_gpu_to_gpu",
)
})?;
let a_desc =
MatrixDesc::from_buffer(a_buf, m as u32, k as u32, Layout::RowMajor).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to describe input matrix: {}", e),
"matmul_gpu_to_gpu",
)
})?;
let b_desc =
MatrixDesc::from_buffer(b_buf, k as u32, n as u32, Layout::RowMajor).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to describe weight matrix: {}", e),
"matmul_gpu_to_gpu",
)
})?;
let mut c_desc =
MatrixDescMut::from_buffer(&mut c_buf, m as u32, n as u32, Layout::RowMajor).map_err(
|e| {
TrustformersError::hardware_error(
&format!("Failed to describe result matrix: {}", e),
"matmul_gpu_to_gpu",
)
},
)?;
gemm::<f32>(
&self.handle,
Transpose::NoTrans,
Transpose::NoTrans,
1.0f32,
&a_desc,
&b_desc,
0.0f32,
&mut c_desc,
)
.map_err(|e| {
TrustformersError::hardware_error(
&format!("GEMM execution failed: {}", e),
"matmul_gpu_to_gpu",
)
})?;
let output_id = OxiCudaBufferId::new();
cache.insert(output_id, c_buf);
Ok(output_id)
}
pub fn gelu_gpu_to_gpu(
&self,
input_buffer_id: &OxiCudaBufferId,
size: usize,
) -> crate::errors::Result<OxiCudaBufferId> {
let mut cache = self.buffer_cache.lock().map_err(|_| {
TrustformersError::hardware_error("Failed to lock buffer cache", "gelu_gpu_to_gpu")
})?;
let in_buf = cache.get(input_buffer_id).ok_or_else(|| {
TrustformersError::hardware_error(
&format!("Input buffer {:?} not found in cache", input_buffer_id),
"gelu_gpu_to_gpu",
)
})?;
if in_buf.len() != size {
return Err(TrustformersError::shape_error(format!(
"Input buffer length {} doesn't match size {}",
in_buf.len(),
size
)));
}
let mut out_buf = DeviceBuffer::<f32>::alloc(size).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to allocate output buffer on device: {}", e),
"gelu_gpu_to_gpu",
)
})?;
oxicuda_blas::elementwise::gelu(&self.handle, size as u32, in_buf, &mut out_buf).map_err(
|e| {
TrustformersError::hardware_error(
&format!("GELU execution failed: {}", e),
"gelu_gpu_to_gpu",
)
},
)?;
let output_id = OxiCudaBufferId::new();
cache.insert(output_id, out_buf);
Ok(output_id)
}
pub fn add_bias_gpu_to_gpu(
&self,
input_buffer_id: &OxiCudaBufferId,
bias_buffer_id: &OxiCudaBufferId,
m: usize,
n: usize,
) -> crate::errors::Result<OxiCudaBufferId> {
let total_size = m * n;
let mut cache = self.buffer_cache.lock().map_err(|_| {
TrustformersError::hardware_error("Failed to lock buffer cache", "add_bias_gpu_to_gpu")
})?;
let in_buf = cache.get(input_buffer_id).ok_or_else(|| {
TrustformersError::hardware_error(
&format!("Input buffer {:?} not found in cache", input_buffer_id),
"add_bias_gpu_to_gpu",
)
})?;
let bias_buf = cache.get(bias_buffer_id).ok_or_else(|| {
TrustformersError::hardware_error(
&format!("Bias buffer {:?} not found in cache", bias_buffer_id),
"add_bias_gpu_to_gpu",
)
})?;
if in_buf.len() != total_size {
return Err(TrustformersError::shape_error(format!(
"Input buffer length {} doesn't match m {} * n {}",
in_buf.len(),
m,
n
)));
}
if bias_buf.len() != n {
return Err(TrustformersError::shape_error(format!(
"Bias buffer length {} doesn't match n {}",
bias_buf.len(),
n
)));
}
let mut out_buf = DeviceBuffer::<f32>::alloc(total_size).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to allocate output buffer on device: {}", e),
"add_bias_gpu_to_gpu",
)
})?;
oxicuda_blas::elementwise::bias_add(
&self.handle,
m as u32,
n as u32,
in_buf,
bias_buf,
&mut out_buf,
)
.map_err(|e| {
TrustformersError::hardware_error(
&format!("Bias-add execution failed: {}", e),
"add_bias_gpu_to_gpu",
)
})?;
let output_id = OxiCudaBufferId::new();
cache.insert(output_id, out_buf);
Ok(output_id)
}
pub fn layernorm_gpu_to_gpu(
&self,
input_buffer_id: &OxiCudaBufferId,
weight_buffer_id: &OxiCudaBufferId,
bias_buffer_id: &OxiCudaBufferId,
seq_len: usize,
hidden_size: usize,
eps: f32,
) -> crate::errors::Result<OxiCudaBufferId> {
let total_size = seq_len * hidden_size;
let mut cache = self.buffer_cache.lock().map_err(|_| {
TrustformersError::hardware_error("Failed to lock buffer cache", "layernorm_gpu_to_gpu")
})?;
let in_buf = cache.get(input_buffer_id).ok_or_else(|| {
TrustformersError::hardware_error(
&format!("Input buffer {:?} not found in cache", input_buffer_id),
"layernorm_gpu_to_gpu",
)
})?;
let weight_buf = cache.get(weight_buffer_id).ok_or_else(|| {
TrustformersError::hardware_error(
&format!("Weight buffer {:?} not found in cache", weight_buffer_id),
"layernorm_gpu_to_gpu",
)
})?;
let bias_buf = cache.get(bias_buffer_id).ok_or_else(|| {
TrustformersError::hardware_error(
&format!("Bias buffer {:?} not found in cache", bias_buffer_id),
"layernorm_gpu_to_gpu",
)
})?;
if in_buf.len() != total_size {
return Err(TrustformersError::shape_error(format!(
"Input buffer length {} doesn't match seq_len {} * hidden_size {}",
in_buf.len(),
seq_len,
hidden_size
)));
}
if weight_buf.len() != hidden_size || bias_buf.len() != hidden_size {
return Err(TrustformersError::shape_error(format!(
"Weight/bias buffer lengths ({}, {}) must match hidden_size {}",
weight_buf.len(),
bias_buf.len(),
hidden_size
)));
}
let dnn = DnnHandle::new(self.context()).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to create cuDNN handle: {}", e),
"layernorm_gpu_to_gpu",
)
})?;
let mut out_buf = DeviceBuffer::<f32>::alloc(total_size).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to allocate output buffer on device: {}", e),
"layernorm_gpu_to_gpu",
)
})?;
let in_desc = TensorDesc::<f32>::matrix(in_buf, seq_len as u32, hidden_size as u32)
.map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to describe input tensor: {}", e),
"layernorm_gpu_to_gpu",
)
})?;
{
let mut out_desc =
TensorDescMut::<f32>::matrix(&mut out_buf, seq_len as u32, hidden_size as u32)
.map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to describe output tensor: {}", e),
"layernorm_gpu_to_gpu",
)
})?;
layer_norm(&dnn, &in_desc, weight_buf, bias_buf, &mut out_desc, eps).map_err(|e| {
TrustformersError::hardware_error(
&format!("LayerNorm execution failed: {}", e),
"layernorm_gpu_to_gpu",
)
})?;
}
let output_id = OxiCudaBufferId::new();
cache.insert(output_id, out_buf);
Ok(output_id)
}
pub fn matmul_with_cached_weight(
&self,
a: &[f32],
weight_buffer_id: &OxiCudaBufferId,
m: usize,
k: usize,
n: usize,
) -> crate::errors::Result<Vec<f32>> {
if a.len() != m * k {
return Err(TrustformersError::shape_error(format!(
"Activation length {} doesn't match m {} * k {}",
a.len(),
m,
k
)));
}
let a_buf = DeviceBuffer::<f32>::from_host(a).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to upload activations to device: {}", e),
"matmul_with_cached_weight",
)
})?;
let cache = self.buffer_cache.lock().map_err(|_| {
TrustformersError::hardware_error(
"Failed to lock buffer cache",
"matmul_with_cached_weight",
)
})?;
let b_buf = cache.get(weight_buffer_id).ok_or_else(|| {
TrustformersError::hardware_error(
&format!("Weight buffer {:?} not found in cache", weight_buffer_id),
"matmul_with_cached_weight",
)
})?;
if b_buf.len() != k * n {
return Err(TrustformersError::shape_error(format!(
"Weight buffer length {} doesn't match k {} * n {}",
b_buf.len(),
k,
n
)));
}
let mut c_buf = DeviceBuffer::<f32>::alloc(m * n).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to allocate result buffer on device: {}", e),
"matmul_with_cached_weight",
)
})?;
let a_desc = MatrixDesc::from_buffer(&a_buf, m as u32, k as u32, Layout::RowMajor)
.map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to describe activation matrix: {}", e),
"matmul_with_cached_weight",
)
})?;
let b_desc =
MatrixDesc::from_buffer(b_buf, k as u32, n as u32, Layout::RowMajor).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to describe weight matrix: {}", e),
"matmul_with_cached_weight",
)
})?;
let mut c_desc =
MatrixDescMut::from_buffer(&mut c_buf, m as u32, n as u32, Layout::RowMajor).map_err(
|e| {
TrustformersError::hardware_error(
&format!("Failed to describe result matrix: {}", e),
"matmul_with_cached_weight",
)
},
)?;
gemm::<f32>(
&self.handle,
Transpose::NoTrans,
Transpose::NoTrans,
1.0f32,
&a_desc,
&b_desc,
0.0f32,
&mut c_desc,
)
.map_err(|e| {
TrustformersError::hardware_error(
&format!("GEMM execution failed: {}", e),
"matmul_with_cached_weight",
)
})?;
let mut result = vec![0.0f32; m * n];
self.synchronize_stream("matmul_with_cached_weight")?;
c_buf.copy_to_host(&mut result).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to copy result back to host: {}", e),
"matmul_with_cached_weight",
)
})?;
Ok(result)
}
pub fn device_info(&self) -> String {
format!(
"CUDA Device (ordinal: {})",
self.context().device().ordinal()
)
}
pub fn matmul_f32(
&self,
a: &[f32],
b: &[f32],
m: usize,
k: usize,
n: usize,
) -> crate::errors::Result<Vec<f32>> {
if a.len() != m * k {
return Err(TrustformersError::shape_error(format!(
"Matrix A length {} doesn't match m {} * k {}",
a.len(),
m,
k
)));
}
if b.len() != k * n {
return Err(TrustformersError::shape_error(format!(
"Matrix B length {} doesn't match k {} * n {}",
b.len(),
k,
n
)));
}
let a_buf = DeviceBuffer::<f32>::from_host(a).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to upload matrix A to device: {}", e),
"matmul_f32",
)
})?;
let b_buf = DeviceBuffer::<f32>::from_host(b).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to upload matrix B to device: {}", e),
"matmul_f32",
)
})?;
let mut c_buf = DeviceBuffer::<f32>::alloc(m * n).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to allocate result buffer on device: {}", e),
"matmul_f32",
)
})?;
let a_desc = MatrixDesc::from_buffer(&a_buf, m as u32, k as u32, Layout::RowMajor)
.map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to describe matrix A: {}", e),
"matmul_f32",
)
})?;
let b_desc = MatrixDesc::from_buffer(&b_buf, k as u32, n as u32, Layout::RowMajor)
.map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to describe matrix B: {}", e),
"matmul_f32",
)
})?;
let mut c_desc =
MatrixDescMut::from_buffer(&mut c_buf, m as u32, n as u32, Layout::RowMajor).map_err(
|e| {
TrustformersError::hardware_error(
&format!("Failed to describe result matrix: {}", e),
"matmul_f32",
)
},
)?;
gemm::<f32>(
&self.handle,
Transpose::NoTrans,
Transpose::NoTrans,
1.0f32,
&a_desc,
&b_desc,
0.0f32,
&mut c_desc,
)
.map_err(|e| {
TrustformersError::hardware_error(
&format!("GEMM execution failed: {}", e),
"matmul_f32",
)
})?;
let mut result = vec![0.0f32; m * n];
self.synchronize_stream("matmul_f32")?;
c_buf.copy_to_host(&mut result).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to copy result back to host: {}", e),
"matmul_f32",
)
})?;
Ok(result)
}
pub fn gelu_f32(&self, input: &[f32]) -> crate::errors::Result<Vec<f32>> {
let size = input.len();
if size == 0 {
return Ok(Vec::new());
}
let in_buf = DeviceBuffer::<f32>::from_host(input).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to upload input to device: {}", e),
"gelu_f32",
)
})?;
let mut out_buf = DeviceBuffer::<f32>::alloc(size).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to allocate output buffer on device: {}", e),
"gelu_f32",
)
})?;
oxicuda_blas::elementwise::gelu(&self.handle, size as u32, &in_buf, &mut out_buf).map_err(
|e| {
TrustformersError::hardware_error(
&format!("GELU execution failed: {}", e),
"gelu_f32",
)
},
)?;
let mut result = vec![0.0f32; size];
self.synchronize_stream("gelu_f32")?;
out_buf.copy_to_host(&mut result).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to copy result back to host: {}", e),
"gelu_f32",
)
})?;
Ok(result)
}
pub fn layernorm_f32(
&self,
input: &[f32],
weight: &[f32],
bias: &[f32],
seq_len: usize,
hidden_size: usize,
eps: f32,
) -> crate::errors::Result<Vec<f32>> {
let total_size = seq_len * hidden_size;
if input.len() != total_size {
return Err(TrustformersError::shape_error(format!(
"Input size {} doesn't match seq_len {} * hidden_size {}",
input.len(),
seq_len,
hidden_size
)));
}
if weight.len() != hidden_size || bias.len() != hidden_size {
return Err(TrustformersError::shape_error(
"Weight/bias size must match hidden_size".to_string(),
));
}
if total_size == 0 {
return Ok(Vec::new());
}
let dnn = DnnHandle::new(self.context()).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to create cuDNN handle: {}", e),
"layernorm_f32",
)
})?;
let in_buf = DeviceBuffer::<f32>::from_host(input).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to upload input to device: {}", e),
"layernorm_f32",
)
})?;
let weight_buf = DeviceBuffer::<f32>::from_host(weight).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to upload weight to device: {}", e),
"layernorm_f32",
)
})?;
let bias_buf = DeviceBuffer::<f32>::from_host(bias).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to upload bias to device: {}", e),
"layernorm_f32",
)
})?;
let mut out_buf = DeviceBuffer::<f32>::alloc(total_size).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to allocate output buffer on device: {}", e),
"layernorm_f32",
)
})?;
let in_desc = TensorDesc::<f32>::matrix(&in_buf, seq_len as u32, hidden_size as u32)
.map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to describe input tensor: {}", e),
"layernorm_f32",
)
})?;
{
let mut out_desc =
TensorDescMut::<f32>::matrix(&mut out_buf, seq_len as u32, hidden_size as u32)
.map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to describe output tensor: {}", e),
"layernorm_f32",
)
})?;
layer_norm(&dnn, &in_desc, &weight_buf, &bias_buf, &mut out_desc, eps).map_err(
|e| {
TrustformersError::hardware_error(
&format!("LayerNorm execution failed: {}", e),
"layernorm_f32",
)
},
)?;
}
let mut result = vec![0.0f32; total_size];
self.synchronize_stream("layernorm_f32")?;
out_buf.copy_to_host(&mut result).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to copy result back to host: {}", e),
"layernorm_f32",
)
})?;
Ok(result)
}
pub fn softmax_causal_f32(
&self,
input: &[f32],
seq_len: usize,
) -> crate::errors::Result<Vec<f32>> {
let total_size = seq_len * seq_len;
if input.len() != total_size {
return Err(TrustformersError::shape_error(format!(
"Input size {} doesn't match seq_len^2 {}",
input.len(),
total_size
)));
}
if total_size == 0 {
return Ok(Vec::new());
}
let in_buf = DeviceBuffer::<f32>::from_host(input).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to upload input to device: {}", e),
"softmax_causal_f32",
)
})?;
let mut out_buf = DeviceBuffer::<f32>::alloc(total_size).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to allocate output buffer on device: {}", e),
"softmax_causal_f32",
)
})?;
oxicuda_blas::reduction::causal_softmax::<f32>(
&self.handle,
seq_len as u32,
seq_len as u32,
seq_len as u32,
&in_buf,
&mut out_buf,
)
.map_err(|e| {
TrustformersError::hardware_error(
&format!("Causal softmax execution failed: {}", e),
"softmax_causal_f32",
)
})?;
let mut result = vec![0.0f32; total_size];
self.synchronize_stream("softmax_causal_f32")?;
out_buf.copy_to_host(&mut result).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to copy result back to host: {}", e),
"softmax_causal_f32",
)
})?;
Ok(result)
}
#[allow(clippy::too_many_arguments)]
pub fn rope_f32(
&self,
input: &[f32],
seq_len: usize,
num_heads: usize,
head_dim: usize,
rotary_ndims: usize,
base: f32,
) -> crate::errors::Result<Vec<f32>> {
let total_size = seq_len * num_heads * head_dim;
if input.len() != total_size {
return Err(TrustformersError::shape_error(format!(
"Input size {} doesn't match seq_len {} * num_heads {} * head_dim {}",
input.len(),
seq_len,
num_heads,
head_dim
)));
}
if total_size == 0 {
return Ok(Vec::new());
}
let dnn = DnnHandle::new(self.context()).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to create cuDNN handle: {}", e),
"rope_f32",
)
})?;
let in_buf = DeviceBuffer::<f32>::from_host(input).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to upload input to device: {}", e),
"rope_f32",
)
})?;
let mut out_buf = DeviceBuffer::<f32>::alloc(total_size).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to allocate output buffer on device: {}", e),
"rope_f32",
)
})?;
oxicuda_dnn::attn::rope_neox_half_split_f32(
&dnn,
&in_buf,
&mut out_buf,
seq_len as u32,
num_heads as u32,
head_dim as u32,
rotary_ndims as u32,
base,
)
.map_err(|e| {
TrustformersError::hardware_error(&format!("RoPE execution failed: {}", e), "rope_f32")
})?;
let mut result = vec![0.0f32; total_size];
self.synchronize_stream("rope_f32")?;
out_buf.copy_to_host(&mut result).map_err(|e| {
TrustformersError::hardware_error(
&format!("Failed to copy result back to host: {}", e),
"rope_f32",
)
})?;
Ok(result)
}
}
pub fn oxicuda_cuda_available() -> bool {
static AVAILABLE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*AVAILABLE.get_or_init(|| {
oxicuda_driver::init().is_ok()
&& matches!(oxicuda_driver::Device::count(), Ok(count) if count > 0)
})
}
static OXICUDA_BACKENDS: once_cell::sync::Lazy<Mutex<HashMap<usize, Arc<OxicudaCudaBackend>>>> =
once_cell::sync::Lazy::new(|| Mutex::new(HashMap::new()));
pub fn oxicuda_backend(device_id: usize) -> crate::errors::Result<Arc<OxicudaCudaBackend>> {
let mut cache = OXICUDA_BACKENDS.lock().map_err(|_| {
TrustformersError::hardware_error("Failed to lock oxicuda backend cache", "oxicuda_backend")
})?;
if let std::collections::hash_map::Entry::Vacant(e) = cache.entry(device_id) {
let backend = OxicudaCudaBackend::new(device_id)?;
e.insert(Arc::new(backend));
}
cache.get(&device_id).cloned().ok_or_else(|| {
TrustformersError::hardware_error("oxicuda backend not found", "oxicuda_backend")
})
}
#[cfg(all(
test,
feature = "cuda",
any(target_os = "linux", target_os = "windows")
))]
mod tests;
#[cfg(test)]
mod resident_handle_tests;