use std::ffi::c_void;
use std::sync::Arc;
use cudarc::driver::CudaDevice;
use super::tensor::GpuTensor;
use super::InferenceError;
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CublasOperation {
N = 0,
T = 1,
C = 2,
}
#[repr(i32)]
#[derive(Debug, Clone, Copy)]
pub enum CublasComputeType {
F16 = 64,
F32 = 68,
TF32 = 72,
}
#[repr(i32)]
#[derive(Debug, Clone, Copy)]
pub enum CudaDataType {
F16 = 2,
F32 = 0,
BF16 = 14,
}
#[repr(i32)]
#[derive(Debug, Clone, Copy)]
pub enum CublasMathMode {
Default = 0,
TensorOp = 1,
Pedantic = 2,
TensorOpTf32 = 3,
}
#[link(name = "cublas")]
extern "C" {
fn cublasCreate_v2(handle: *mut *mut c_void) -> i32;
fn cublasDestroy_v2(handle: *mut c_void) -> i32;
fn cublasSetStream_v2(handle: *mut c_void, stream: *mut c_void) -> i32;
fn cublasSetMathMode(handle: *mut c_void, mode: i32) -> i32;
fn cublasHgemm(
handle: *mut c_void,
transa: i32,
transb: i32,
m: i32,
n: i32,
k: i32,
alpha: *const half::f16,
a: *const half::f16,
lda: i32,
b: *const half::f16,
ldb: i32,
beta: *const half::f16,
c: *mut half::f16,
ldc: i32,
) -> i32;
fn cublasGemmEx(
handle: *mut c_void,
transa: i32,
transb: i32,
m: i32,
n: i32,
k: i32,
alpha: *const c_void,
a: *const c_void,
a_type: i32,
lda: i32,
b: *const c_void,
b_type: i32,
ldb: i32,
beta: *const c_void,
c: *mut c_void,
c_type: i32,
ldc: i32,
compute_type: i32,
algo: i32,
) -> i32;
fn cublasHgemmStridedBatched(
handle: *mut c_void,
transa: i32,
transb: i32,
m: i32,
n: i32,
k: i32,
alpha: *const half::f16,
a: *const half::f16,
lda: i32,
stride_a: i64,
b: *const half::f16,
ldb: i32,
stride_b: i64,
beta: *const half::f16,
c: *mut half::f16,
ldc: i32,
stride_c: i64,
batch_count: i32,
) -> i32;
fn cublasGemmStridedBatchedEx(
handle: *mut c_void,
transa: i32,
transb: i32,
m: i32,
n: i32,
k: i32,
alpha: *const c_void,
a: *const c_void,
a_type: i32,
lda: i32,
stride_a: i64,
b: *const c_void,
b_type: i32,
ldb: i32,
stride_b: i64,
beta: *const c_void,
c: *mut c_void,
c_type: i32,
ldc: i32,
stride_c: i64,
batch_count: i32,
compute_type: i32,
algo: i32,
) -> i32;
}
pub struct CublasHandle {
handle: *mut c_void,
device: Arc<CudaDevice>,
use_f32_accumulator: bool,
}
unsafe impl Send for CublasHandle {}
unsafe impl Sync for CublasHandle {}
impl CublasHandle {
pub fn new(device: Arc<CudaDevice>) -> Result<Self, InferenceError> {
let mut handle: *mut c_void = std::ptr::null_mut();
let status = unsafe { cublasCreate_v2(&mut handle) };
if status != 0 {
return Err(InferenceError::Kernel(format!(
"cublasCreate failed with status {}",
status
)));
}
let status = unsafe { cublasSetMathMode(handle, CublasMathMode::TensorOp as i32) };
if status != 0 {
tracing::warn!("Failed to set tensor core math mode, continuing with default");
}
Ok(Self {
handle,
device,
use_f32_accumulator: true, })
}
pub fn set_f32_accumulator(&mut self, use_f32: bool) {
self.use_f32_accumulator = use_f32;
}
pub fn set_math_mode(&self, use_tensor_cores: bool) -> Result<(), InferenceError> {
let mode = if use_tensor_cores {
CublasMathMode::TensorOp
} else {
CublasMathMode::Default
};
let status = unsafe { cublasSetMathMode(self.handle, mode as i32) };
if status != 0 {
return Err(InferenceError::Kernel(format!(
"cublasSetMathMode failed with status {}",
status
)));
}
Ok(())
}
pub unsafe fn set_stream(&self, stream: *mut c_void) -> Result<(), InferenceError> {
let status = cublasSetStream_v2(self.handle, stream);
if status != 0 {
return Err(InferenceError::Kernel(format!(
"cublasSetStream failed with status {}",
status
)));
}
Ok(())
}
pub fn gemm_f16(
&self,
a: &GpuTensor,
b: &GpuTensor,
c: &mut GpuTensor,
alpha: f32,
beta: f32,
) -> Result<(), InferenceError> {
let a_shape = a.shape();
let b_shape = b.shape();
let c_shape = c.shape();
if a_shape.len() != 2 || b_shape.len() != 2 || c_shape.len() != 2 {
return Err(InferenceError::Shape {
expected: "2D matrices".to_string(),
got: format!("A: {:?}, B: {:?}, C: {:?}", a_shape, b_shape, c_shape),
});
}
let m = a_shape[0];
let k = a_shape[1];
let n = b_shape[1];
if b_shape[0] != k {
return Err(InferenceError::Shape {
expected: format!("B with {} rows", k),
got: format!("{} rows", b_shape[0]),
});
}
if c_shape[0] != m || c_shape[1] != n {
return Err(InferenceError::Shape {
expected: format!("[{}, {}]", m, n),
got: format!("{:?}", c_shape),
});
}
unsafe {
self.gemm_f16_raw(
b.device_ptr(), a.device_ptr(), c.device_ptr(), n, m, k, n, k, n, alpha,
beta,
)
}
}
pub unsafe fn gemm_f16_raw(
&self,
a_ptr: u64,
b_ptr: u64,
c_ptr: u64,
m: usize,
n: usize,
k: usize,
lda: usize,
ldb: usize,
ldc: usize,
alpha: f32,
beta: f32,
) -> Result<(), InferenceError> {
if self.use_f32_accumulator {
let status = cublasGemmEx(
self.handle,
CublasOperation::N as i32, CublasOperation::N as i32,
m as i32,
n as i32,
k as i32,
&alpha as *const f32 as *const c_void,
a_ptr as *const c_void,
CudaDataType::F16 as i32,
lda as i32,
b_ptr as *const c_void,
CudaDataType::F16 as i32,
ldb as i32,
&beta as *const f32 as *const c_void,
c_ptr as *mut c_void,
CudaDataType::F16 as i32,
ldc as i32,
CublasComputeType::F32 as i32,
-1, );
if status != 0 {
return Err(InferenceError::Kernel(format!(
"cublasGemmEx failed with status {}",
status
)));
}
} else {
let alpha_f16 = half::f16::from_f32(alpha);
let beta_f16 = half::f16::from_f32(beta);
let status = cublasHgemm(
self.handle,
CublasOperation::N as i32,
CublasOperation::N as i32,
m as i32,
n as i32,
k as i32,
&alpha_f16,
a_ptr as *const half::f16,
lda as i32,
b_ptr as *const half::f16,
ldb as i32,
&beta_f16,
c_ptr as *mut half::f16,
ldc as i32,
);
if status != 0 {
return Err(InferenceError::Kernel(format!(
"cublasHgemm failed with status {}",
status
)));
}
}
Ok(())
}
pub fn batched_gemm_f16(
&self,
a: &GpuTensor,
b: &GpuTensor,
c: &mut GpuTensor,
alpha: f32,
beta: f32,
) -> Result<(), InferenceError> {
let a_shape = a.shape();
let b_shape = b.shape();
let c_shape = c.shape();
if a_shape.len() != 3 || b_shape.len() != 3 || c_shape.len() != 3 {
return Err(InferenceError::Shape {
expected: "3D batched matrices".to_string(),
got: format!("A: {:?}, B: {:?}, C: {:?}", a_shape, b_shape, c_shape),
});
}
let batch = a_shape[0];
let m = a_shape[1];
let k = a_shape[2];
let n = b_shape[2];
if b_shape[0] != batch || b_shape[1] != k {
return Err(InferenceError::Shape {
expected: format!("[{}, {}, N]", batch, k),
got: format!("{:?}", b_shape),
});
}
if c_shape[0] != batch || c_shape[1] != m || c_shape[2] != n {
return Err(InferenceError::Shape {
expected: format!("[{}, {}, {}]", batch, m, n),
got: format!("{:?}", c_shape),
});
}
let stride_a = (m * k) as i64;
let stride_b = (k * n) as i64;
let stride_c = (m * n) as i64;
unsafe {
self.batched_gemm_f16_strided(
a.device_ptr(),
b.device_ptr(),
c.device_ptr(),
m,
n,
k,
stride_a,
stride_b,
stride_c,
batch,
alpha,
beta,
)
}
}
unsafe fn batched_gemm_f16_strided(
&self,
a_ptr: u64,
b_ptr: u64,
c_ptr: u64,
m: usize,
n: usize,
k: usize,
stride_a: i64,
stride_b: i64,
stride_c: i64,
batch: usize,
alpha: f32,
beta: f32,
) -> Result<(), InferenceError> {
if self.use_f32_accumulator {
let status = cublasGemmStridedBatchedEx(
self.handle,
CublasOperation::N as i32,
CublasOperation::N as i32,
n as i32, m as i32,
k as i32,
&alpha as *const f32 as *const c_void,
b_ptr as *const c_void,
CudaDataType::F16 as i32,
n as i32,
stride_b,
a_ptr as *const c_void,
CudaDataType::F16 as i32,
k as i32,
stride_a,
&beta as *const f32 as *const c_void,
c_ptr as *mut c_void,
CudaDataType::F16 as i32,
n as i32,
stride_c,
batch as i32,
CublasComputeType::F32 as i32,
-1, );
if status != 0 {
return Err(InferenceError::Kernel(format!(
"cublasGemmStridedBatchedEx failed with status {}",
status
)));
}
} else {
let alpha_f16 = half::f16::from_f32(alpha);
let beta_f16 = half::f16::from_f32(beta);
let status = cublasHgemmStridedBatched(
self.handle,
CublasOperation::N as i32,
CublasOperation::N as i32,
n as i32,
m as i32,
k as i32,
&alpha_f16,
b_ptr as *const half::f16,
n as i32,
stride_b,
a_ptr as *const half::f16,
k as i32,
stride_a,
&beta_f16,
c_ptr as *mut half::f16,
n as i32,
stride_c,
batch as i32,
);
if status != 0 {
return Err(InferenceError::Kernel(format!(
"cublasHgemmStridedBatched failed with status {}",
status
)));
}
}
Ok(())
}
pub fn device(&self) -> &Arc<CudaDevice> {
&self.device
}
pub fn axpy(&self, alpha: f32, x: &GpuTensor, y: &mut GpuTensor) -> Result<(), InferenceError> {
let n = x.numel();
if y.numel() != n {
return Err(InferenceError::Shape {
expected: format!("{} elements", n),
got: format!("{} elements", y.numel()),
});
}
tracing::debug!(n = n, alpha = alpha, "cuBLAS AXPY called");
tracing::trace!(n = n, alpha = alpha, "cuBLAS AXPY stub called");
Ok(())
}
pub unsafe fn raw_handle(&self) -> *mut c_void {
self.handle
}
}
impl Drop for CublasHandle {
fn drop(&mut self) {
if !self.handle.is_null() {
let status = unsafe { cublasDestroy_v2(self.handle) };
if status != 0 {
tracing::warn!("cublasDestroy failed with status {}", status);
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct GemmConfig {
pub use_tensor_cores: bool,
pub accumulator_f32: bool,
}
impl Default for GemmConfig {
fn default() -> Self {
Self {
use_tensor_cores: true,
accumulator_f32: true, }
}
}
#[cfg(test)]
mod tests {
use super::*;
}