use std::collections::HashMap;
use std::ffi::c_void;
use std::sync::{Arc, Mutex, OnceLock, PoisonError};
use cuda_core::sys;
use super::error::{CudaError, Result, check, driver};
use super::memory::Pool;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct DeviceInfo {
pub name: String,
pub compute_capability: (u32, u32),
pub total_memory: u64,
pub multiprocessors: u32,
pub driver_version: u32,
}
impl DeviceInfo {
pub fn arch(&self) -> String {
format!(
"sm_{}{}",
self.compute_capability.0, self.compute_capability.1
)
}
}
pub struct Device {
pub(crate) device: Arc<cuda_core::Device>,
pub(crate) stream: Arc<cuda_core::Stream>,
info: DeviceInfo,
pub(crate) pool: Pool,
modules: Mutex<HashMap<u64, Arc<cuda_core::Module>>>,
functions: Mutex<HashMap<(u64, String), Arc<cuda_core::Function>>>,
}
unsafe impl Send for Device {}
unsafe impl Sync for Device {}
impl std::fmt::Debug for Device {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Device")
.field("info", &self.info)
.finish_non_exhaustive()
}
}
fn lock<T>(m: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
m.lock().unwrap_or_else(PoisonError::into_inner)
}
fn attribute(device: sys::CUdevice, attr: sys::CUdevice_attribute) -> Result<i32> {
let mut v = 0;
check("cuDeviceGetAttribute", unsafe {
sys::cuDeviceGetAttribute(&mut v, attr, device)
})?;
Ok(v)
}
impl Device {
pub fn shared() -> std::result::Result<&'static Device, &'static CudaError> {
static SHARED: OnceLock<Result<Device>> = OnceLock::new();
SHARED.get_or_init(Device::create).as_ref()
}
fn create() -> Result<Self> {
if !sys::is_cuda_driver_available() {
let why = sys::cuda_driver_load_error().map_or_else(
|| "the driver library could not be loaded".into(),
|e| e.to_string(),
);
return Err(CudaError::NoDevice(why));
}
let count =
cuda_core::Device::device_count().map_err(|e| CudaError::NoDevice(e.to_string()))?;
if count < 1 {
return Err(CudaError::NoDevice("the driver reports no GPU".into()));
}
let device = cuda_core::Device::new(0).map_err(|e| CudaError::NoDevice(e.to_string()))?;
let cu = device.cu_device();
let major = attribute(
cu,
sys::CUdevice_attribute_enum_CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR,
)?;
let minor = attribute(
cu,
sys::CUdevice_attribute_enum_CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR,
)?;
if major < 8 {
return Err(CudaError::NoDevice(format!(
"compute capability {major}.{minor}; nuvec needs 8.0 (Ampere) or newer"
)));
}
let managed = attribute(
cu,
sys::CUdevice_attribute_enum_CU_DEVICE_ATTRIBUTE_CONCURRENT_MANAGED_ACCESS,
)?;
if managed == 0 {
return Err(CudaError::NoDevice(
"the GPU has no concurrent managed memory access (Windows, WSL or Tegra)".into(),
));
}
let mut total = 0usize;
check("cuDeviceTotalMem", unsafe {
sys::cuDeviceTotalMem_v2(&mut total, cu)
})?;
let mut version = 0;
check("cuDriverGetVersion", unsafe {
sys::cuDriverGetVersion(&mut version)
})?;
let info = DeviceInfo {
name: device.name().map_err(driver("cuDeviceGetName"))?,
compute_capability: (major as u32, minor as u32),
total_memory: total as u64,
multiprocessors: attribute(
cu,
sys::CUdevice_attribute_enum_CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT,
)? as u32,
driver_version: version as u32,
};
let stream = device.new_stream().map_err(driver("cuStreamCreate"))?;
Ok(Self {
pool: Pool::new(info.total_memory as usize, device.cu_ctx()),
device,
stream,
info,
modules: Mutex::default(),
functions: Mutex::default(),
})
}
pub fn info(&self) -> &DeviceInfo {
&self.info
}
pub fn synchronize(&self) -> Result<()> {
unsafe { self.stream.synchronize() }.map_err(driver("cuStreamSynchronize"))
}
pub(crate) fn function(&self, ptx: &str, name: &str) -> Result<Arc<cuda_core::Function>> {
use std::hash::{Hash, Hasher};
let mut h = std::collections::hash_map::DefaultHasher::new();
ptx.hash(&mut h);
let key = h.finish();
if let Some(f) = lock(&self.functions).get(&(key, name.to_string())) {
return Ok(f.clone());
}
let cached = lock(&self.modules).get(&key).cloned();
let module = match cached {
Some(m) => m,
None => {
let m =
self.device
.load_module_from_ptx_src(ptx)
.map_err(|e| CudaError::Driver {
call: "cuModuleLoadData",
message: format!("{e} (PTX of {name})"),
})?;
lock(&self.modules).insert(key, m.clone());
m
}
};
let f = Arc::new(
module
.load_function(name)
.map_err(driver("cuModuleGetFunction"))?,
);
lock(&self.functions).insert((key, name.to_string()), f.clone());
Ok(f)
}
pub(crate) fn launch(
&self,
f: &cuda_core::Function,
grid: u32,
block: u32,
shared: u32,
args: &mut [*mut c_void],
) -> Result<()> {
self.device
.bind_to_thread()
.map_err(driver("cuCtxSetCurrent"))?;
let function = unsafe { f.cu_function() };
unsafe {
cuda_core::launch_kernel(
function,
(grid.max(1), 1, 1),
(block, 1, 1),
shared,
self.stream.cu_stream(),
args,
)
}
.map_err(driver("cuLaunchKernel"))
}
}