use std::error::Error as StdError;
use std::fmt;
use std::sync::{Arc, Mutex};
use crate::error::{Error, Result};
use crate::lock_safe;
#[derive(Debug, Clone)]
pub struct GpuConfig {
pub enabled: bool,
pub memory_limit: usize,
pub device_id: i32,
pub fallback_to_cpu: bool,
pub use_pinned_memory: bool,
pub min_size_threshold: usize,
}
impl Default for GpuConfig {
fn default() -> Self {
GpuConfig {
enabled: true,
memory_limit: 1024 * 1024 * 1024, device_id: 0,
fallback_to_cpu: true,
use_pinned_memory: true,
min_size_threshold: 10_000, }
}
}
pub struct GpuContext {
device_status: Arc<Mutex<GpuDeviceStatus>>,
config: GpuConfig,
}
#[derive(Debug, Clone)]
pub struct GpuDeviceStatus {
pub available: bool,
pub cuda_version: Option<String>,
pub device_name: Option<String>,
pub total_memory: Option<usize>,
pub free_memory: Option<usize>,
pub core_count: Option<usize>,
}
impl Default for GpuDeviceStatus {
fn default() -> Self {
GpuDeviceStatus {
available: false,
cuda_version: None,
device_name: None,
total_memory: None,
free_memory: None,
core_count: None,
}
}
}
impl GpuContext {
pub fn new(config: GpuConfig) -> Self {
let device_status = Arc::new(Mutex::new(Self::detect_device(&config)));
GpuContext {
device_status,
config,
}
}
fn detect_device(_config: &GpuConfig) -> GpuDeviceStatus {
#[cfg(cuda_available)]
{
match cudarc::driver::CudaContext::new(0) {
Ok(context) => {
let device_name = context.name().ok();
let (free_memory, total_memory) = match context.mem_get_info() {
Ok((free, total)) => (Some(free), Some(total)),
Err(_) => (None, None),
};
let cuda_version = context
.compute_capability()
.ok()
.map(|(major, minor)| format!("compute capability {}.{}", major, minor));
return GpuDeviceStatus {
available: true,
cuda_version,
device_name,
total_memory,
free_memory,
core_count: None,
};
}
Err(_) => {
}
}
}
GpuDeviceStatus::default()
}
pub fn is_available(&self) -> bool {
if !self.config.enabled {
return false;
}
lock_safe!(self.device_status, "gpu device status lock")
.map(|status| status.available)
.unwrap_or(false)
}
pub fn get_device_status(&self) -> GpuDeviceStatus {
lock_safe!(self.device_status, "gpu device status lock")
.map(|status| status.clone())
.unwrap_or_default()
}
pub fn update_device_status(&self) {
let new_status = Self::detect_device(&self.config);
if let Ok(mut status) = lock_safe!(self.device_status, "gpu device status lock") {
*status = new_status;
}
}
pub fn should_use_gpu(&self, size: usize) -> bool {
self.is_available() && size >= self.config.min_size_threshold
}
pub fn config(&self) -> &GpuConfig {
&self.config
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum GpuOperationType {
MatrixMultiply,
Add,
Subtract,
Multiply,
Divide,
Aggregate,
Sort,
Filter,
}
#[derive(Debug)]
pub enum GpuError {
DriverNotAvailable,
InsufficientMemory,
IncompatibleDataType,
DeviceError(String),
KernelExecutionError(String),
TransferError(String),
}
impl fmt::Display for GpuError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
GpuError::DriverNotAvailable => write!(f, "CUDA driver not available"),
GpuError::InsufficientMemory => write!(f, "Insufficient GPU memory"),
GpuError::IncompatibleDataType => write!(f, "Incompatible data type for GPU operation"),
GpuError::DeviceError(msg) => write!(f, "GPU device error: {}", msg),
GpuError::KernelExecutionError(msg) => write!(f, "GPU kernel execution error: {}", msg),
GpuError::TransferError(msg) => write!(f, "GPU data transfer error: {}", msg),
}
}
}
impl StdError for GpuError {}
impl From<GpuError> for Error {
fn from(error: GpuError) -> Self {
Error::Computation(error.to_string())
}
}
#[derive(Clone)]
pub struct GpuManager {
context: Arc<GpuContext>,
}
impl GpuManager {
pub fn new() -> Self {
Self::with_config(GpuConfig::default())
}
pub fn with_config(config: GpuConfig) -> Self {
GpuManager {
context: Arc::new(GpuContext::new(config)),
}
}
pub fn context(&self) -> &GpuContext {
&self.context
}
pub fn is_available(&self) -> bool {
self.context.is_available()
}
pub fn device_info(&self) -> GpuDeviceStatus {
self.context.get_device_status()
}
}
lazy_static::lazy_static! {
static ref GPU_MANAGER: Mutex<Option<GpuManager>> = Mutex::new(None);
}
pub fn init_gpu() -> Result<GpuDeviceStatus> {
init_gpu_with_config(GpuConfig::default())
}
pub fn init_gpu_with_config(config: GpuConfig) -> Result<GpuDeviceStatus> {
let manager = GpuManager::with_config(config);
let status = manager.device_info();
let mut global = lock_safe!(GPU_MANAGER, "global gpu manager lock")?;
*global = Some(manager);
Ok(status)
}
pub fn get_gpu_manager() -> Result<GpuManager> {
let global = lock_safe!(GPU_MANAGER, "global gpu manager lock")?;
match &*global {
Some(manager) => Ok(manager.clone()),
None => {
drop(global); init_gpu()?;
get_gpu_manager()
}
}
}
#[cfg(cuda_available)]
pub mod cuda;
pub mod operations;
pub mod benchmark;
pub mod advanced_ops;
pub mod multi_gpu;
pub mod memory_pool;
pub mod cpu_math;