use std::ffi::{CStr, c_char, c_void};
use std::fmt;
use libloading::Library;
use super::pool::Pool;
pub(super) const HOST_TO_DEVICE: i32 = 1;
pub(super) const DEVICE_TO_HOST: i32 = 2;
pub(super) const OP_N: i32 = 0;
pub(super) const OP_T: i32 = 1;
const NO_DEVICE: i32 = 100;
const INSUFFICIENT_DRIVER: i32 = 35;
#[derive(Debug)]
pub(super) enum SetupError {
NoLibrary(&'static str),
NoDevice,
Failed(String),
}
impl fmt::Display for SetupError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoLibrary(name) => write!(formatter, "`{name}` is not available"),
Self::NoDevice => write!(formatter, "no CUDA device"),
Self::Failed(reason) => write!(formatter, "{reason}"),
}
}
}
pub(super) struct Api {
_cudart: Option<Library>,
_cublas: Option<Library>,
pub(super) malloc: unsafe extern "C" fn(*mut *mut c_void, usize) -> i32,
pub(super) free: unsafe extern "C" fn(*mut c_void) -> i32,
pub(super) memcpy: unsafe extern "C" fn(*mut c_void, *const c_void, usize, i32) -> i32,
pub(super) device_synchronize: unsafe extern "C" fn() -> i32,
get_error_string: unsafe extern "C" fn(i32) -> *const c_char,
pub(super) sgemm: unsafe extern "C" fn(
*mut c_void,
i32,
i32,
i32,
i32,
i32,
*const f32,
*const f32,
i32,
*const f32,
i32,
*const f32,
*mut f32,
i32,
) -> i32,
pub(super) dgemm: unsafe extern "C" fn(
*mut c_void,
i32,
i32,
i32,
i32,
i32,
*const f64,
*const f64,
i32,
*const f64,
i32,
*const f64,
*mut f64,
i32,
) -> i32,
}
impl Api {
#[cfg(test)]
pub(super) fn fake(
malloc: unsafe extern "C" fn(*mut *mut c_void, usize) -> i32,
free: unsafe extern "C" fn(*mut c_void) -> i32,
) -> Self {
unsafe extern "C" fn no_memcpy(_: *mut c_void, _: *const c_void, _: usize, _: i32) -> i32 {
0
}
unsafe extern "C" fn no_synchronize() -> i32 {
0
}
unsafe extern "C" fn fake_error(_: i32) -> *const c_char {
c"fake error".as_ptr()
}
unsafe extern "C" fn no_sgemm(
_: *mut c_void,
_: i32,
_: i32,
_: i32,
_: i32,
_: i32,
_: *const f32,
_: *const f32,
_: i32,
_: *const f32,
_: i32,
_: *const f32,
_: *mut f32,
_: i32,
) -> i32 {
0
}
unsafe extern "C" fn no_dgemm(
_: *mut c_void,
_: i32,
_: i32,
_: i32,
_: i32,
_: i32,
_: *const f64,
_: *const f64,
_: i32,
_: *const f64,
_: i32,
_: *const f64,
_: *mut f64,
_: i32,
) -> i32 {
0
}
Self {
_cudart: None,
_cublas: None,
malloc,
free,
memcpy: no_memcpy,
device_synchronize: no_synchronize,
get_error_string: fake_error,
sgemm: no_sgemm,
dgemm: no_dgemm,
}
}
pub(super) fn error_string(&self, status: i32) -> String {
let message = unsafe { CStr::from_ptr((self.get_error_string)(status)) };
message.to_string_lossy().into_owned()
}
}
pub(super) fn cublas_status_name(status: i32) -> String {
let name = match status {
1 => "CUBLAS_STATUS_NOT_INITIALIZED",
3 => "CUBLAS_STATUS_ALLOC_FAILED",
7 => "CUBLAS_STATUS_INVALID_VALUE",
8 => "CUBLAS_STATUS_ARCH_MISMATCH",
11 => "CUBLAS_STATUS_MAPPING_ERROR",
13 => "CUBLAS_STATUS_EXECUTION_FAILED",
14 => "CUBLAS_STATUS_INTERNAL_ERROR",
15 => "CUBLAS_STATUS_NOT_SUPPORTED",
16 => "CUBLAS_STATUS_LICENSE_ERROR",
other => return format!("cublas status {other}"),
};
name.to_string()
}
pub(super) struct Context {
pub(super) api: Api,
pub(super) handle: *mut c_void,
pub(super) pool: Pool,
}
#[allow(unsafe_code)]
unsafe impl Send for Context {}
#[allow(unsafe_code)]
unsafe impl Sync for Context {}
impl Context {
pub(super) fn new() -> Result<Self, SetupError> {
let cudart = open(&["libcudart.so.13", "libcudart.so.12", "libcudart.so"])
.ok_or(SetupError::NoLibrary("libcudart"))?;
let cublas = open(&["libcublas.so.13", "libcublas.so.12", "libcublas.so"])
.ok_or(SetupError::NoLibrary("libcublas"))?;
let get_device_count: unsafe extern "C" fn(*mut i32) -> i32 =
symbol(&cudart, b"cudaGetDeviceCount\0", "libcudart")?;
let set_device: unsafe extern "C" fn(i32) -> i32 =
symbol(&cudart, b"cudaSetDevice\0", "libcudart")?;
let get_error_string: unsafe extern "C" fn(i32) -> *const c_char =
symbol(&cudart, b"cudaGetErrorString\0", "libcudart")?;
let create: unsafe extern "C" fn(*mut *mut c_void) -> i32 =
symbol(&cublas, b"cublasCreate_v2\0", "libcublas")?;
let mut count = 0_i32;
let status = unsafe { get_device_count(&mut count) };
if status == NO_DEVICE || status == INSUFFICIENT_DRIVER || (status == 0 && count == 0) {
return Err(SetupError::NoDevice);
}
if status != 0 {
let message = unsafe { CStr::from_ptr(get_error_string(status)) };
return Err(SetupError::Failed(format!(
"cudaGetDeviceCount failed: {}",
message.to_string_lossy()
)));
}
let status = unsafe { set_device(0) };
if status != 0 {
let message = unsafe { CStr::from_ptr(get_error_string(status)) };
return Err(SetupError::Failed(format!(
"cudaSetDevice failed: {}",
message.to_string_lossy()
)));
}
let api = Api {
malloc: symbol(&cudart, b"cudaMalloc\0", "libcudart")?,
free: symbol(&cudart, b"cudaFree\0", "libcudart")?,
memcpy: symbol(&cudart, b"cudaMemcpy\0", "libcudart")?,
device_synchronize: symbol(&cudart, b"cudaDeviceSynchronize\0", "libcudart")?,
get_error_string,
sgemm: symbol(&cublas, b"cublasSgemm_v2\0", "libcublas")?,
dgemm: symbol(&cublas, b"cublasDgemm_v2\0", "libcublas")?,
_cudart: Some(cudart),
_cublas: Some(cublas),
};
let mut handle = std::ptr::null_mut();
let status = unsafe { create(&mut handle) };
if status != 0 {
return Err(SetupError::Failed(format!(
"cublasCreate failed: {}",
cublas_status_name(status)
)));
}
Ok(Self {
api,
handle,
pool: Pool::new(),
})
}
}
fn open(candidates: &[&str]) -> Option<Library> {
for &name in candidates {
if let Ok(library) = unsafe { Library::new(name) } {
return Some(library);
}
}
None
}
fn symbol<Pointer: Copy>(
library: &Library,
name: &'static [u8],
library_name: &'static str,
) -> Result<Pointer, SetupError> {
unsafe {
library
.get::<Pointer>(name)
.map(|resolved| *resolved)
.map_err(|_| {
SetupError::Failed(format!(
"symbol `{}` missing from {library_name}",
String::from_utf8_lossy(&name[..name.len() - 1])
))
})
}
}