use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum GpuBackendKind {
Cpu,
Simulated,
OxiBlasGpu,
}
impl Default for GpuBackendKind {
fn default() -> Self {
Self::Simulated
}
}
impl fmt::Display for GpuBackendKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Cpu => write!(f, "CPU"),
Self::Simulated => write!(f, "Simulated-GPU"),
Self::OxiBlasGpu => write!(f, "OxiBLAS-GPU"),
}
}
}
#[derive(Clone, Debug)]
pub struct GpuMatrixConfig {
pub backend: GpuBackendKind,
pub tile_size: usize,
pub use_tensor_cores: bool,
pub memory_limit_mb: u64,
pub gpu_threshold: usize,
}
impl Default for GpuMatrixConfig {
fn default() -> Self {
Self {
backend: GpuBackendKind::default(),
tile_size: 32,
use_tensor_cores: false,
memory_limit_mb: 0,
gpu_threshold: 1_000_000, }
}
}
#[derive(Clone, Debug)]
pub struct GpuMatrixBuffer<T> {
pub(crate) data: Vec<T>,
pub rows: usize,
pub cols: usize,
}
impl<T: Clone + Default> GpuMatrixBuffer<T> {
pub fn zeros(rows: usize, cols: usize) -> Self {
Self {
data: vec![T::default(); rows * cols],
rows,
cols,
}
}
pub fn from_slice(data: &[T], rows: usize, cols: usize) -> GpuResult<Self> {
let expected = rows.checked_mul(cols).ok_or(GpuError::OutOfMemory {
requested_bytes: u64::MAX,
available_bytes: 0,
})?;
if data.len() != expected {
return Err(GpuError::DimensionMismatch {
expected,
got: data.len(),
context: "GpuMatrixBuffer::from_slice: data length mismatch".to_string(),
});
}
Ok(Self {
data: data.to_vec(),
rows,
cols,
})
}
#[inline]
pub fn len(&self) -> usize {
self.data.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
#[inline]
pub fn as_slice(&self) -> &[T] {
&self.data
}
#[inline]
pub fn as_mut_slice(&mut self) -> &mut [T] {
&mut self.data
}
#[inline]
pub fn get(&self, row: usize, col: usize) -> GpuResult<&T> {
if row >= self.rows || col >= self.cols {
return Err(GpuError::DimensionMismatch {
expected: self.rows * self.cols,
got: row * self.cols + col,
context: format!(
"GpuMatrixBuffer::get index ({row},{col}) out of bounds for {}×{}",
self.rows, self.cols
),
});
}
Ok(&self.data[row * self.cols + col])
}
#[inline]
pub fn get_mut(&mut self, row: usize, col: usize) -> GpuResult<&mut T> {
if row >= self.rows || col >= self.cols {
return Err(GpuError::DimensionMismatch {
expected: self.rows * self.cols,
got: row * self.cols + col,
context: format!(
"GpuMatrixBuffer::get_mut index ({row},{col}) out of bounds for {}×{}",
self.rows, self.cols
),
});
}
Ok(&mut self.data[row * self.cols + col])
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum GpuError {
OutOfMemory {
requested_bytes: u64,
available_bytes: u64,
},
UnsupportedOperation {
operation: String,
backend: String,
},
BackendUnavailable {
reason: String,
},
DimensionMismatch {
expected: usize,
got: usize,
context: String,
},
SizeOverflow {
detail: String,
},
}
impl fmt::Display for GpuError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::OutOfMemory {
requested_bytes,
available_bytes,
} => write!(
f,
"GPU out-of-memory: requested {requested_bytes} bytes, \
only {available_bytes} bytes available"
),
Self::UnsupportedOperation { operation, backend } => {
write!(
f,
"GPU unsupported operation '{operation}' on backend '{backend}'"
)
}
Self::BackendUnavailable { reason } => {
write!(f, "GPU backend unavailable: {reason}")
}
Self::DimensionMismatch {
expected,
got,
context,
} => write!(
f,
"GPU dimension mismatch (expected {expected}, got {got}): {context}"
),
Self::SizeOverflow { detail } => {
write!(f, "GPU size overflow: {detail}")
}
}
}
}
impl std::error::Error for GpuError {}
pub type GpuResult<T> = Result<T, GpuError>;
#[derive(Clone, Debug)]
pub struct GpuCapabilities {
pub has_fp16: bool,
pub has_tensor_cores: bool,
pub vram_gb: f64,
pub name: String,
pub compute_units: u32,
pub warp_size: u32,
pub max_buffer_elements: usize,
}
impl Default for GpuCapabilities {
fn default() -> Self {
detect_gpu_capabilities()
}
}
pub fn detect_gpu_capabilities() -> GpuCapabilities {
GpuCapabilities {
has_fp16: true,
has_tensor_cores: false, vram_gb: 8.0,
name: "SciRS2 Simulated GPU (OxiBLAS backend)".to_string(),
compute_units: 64,
warp_size: 32,
max_buffer_elements: 1 << 30, }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gpu_backend_kind_default() {
assert_eq!(GpuBackendKind::default(), GpuBackendKind::Simulated);
}
#[test]
fn test_gpu_matrix_config_default() {
let cfg = GpuMatrixConfig::default();
assert_eq!(cfg.tile_size, 32);
assert!(!cfg.use_tensor_cores);
assert_eq!(cfg.memory_limit_mb, 0);
assert_eq!(cfg.gpu_threshold, 1_000_000);
}
#[test]
fn test_gpu_buffer_zeros() {
let buf = GpuMatrixBuffer::<f64>::zeros(3, 4);
assert_eq!(buf.rows, 3);
assert_eq!(buf.cols, 4);
assert_eq!(buf.len(), 12);
assert!(buf.as_slice().iter().all(|&v| v == 0.0));
}
#[test]
fn test_gpu_buffer_from_slice_ok() {
let data: Vec<f64> = (0..6).map(|i| i as f64).collect();
let buf = GpuMatrixBuffer::from_slice(&data, 2, 3).unwrap();
assert_eq!(buf.rows, 2);
assert_eq!(buf.cols, 3);
assert_eq!(*buf.get(0, 2).unwrap(), 2.0);
assert_eq!(*buf.get(1, 0).unwrap(), 3.0);
}
#[test]
fn test_gpu_buffer_from_slice_mismatch() {
let data = vec![1.0_f64; 5];
let result = GpuMatrixBuffer::from_slice(&data, 2, 3);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
GpuError::DimensionMismatch { .. }
));
}
#[test]
fn test_gpu_buffer_get_out_of_bounds() {
let buf = GpuMatrixBuffer::<f64>::zeros(2, 2);
assert!(buf.get(2, 0).is_err());
assert!(buf.get(0, 2).is_err());
}
#[test]
fn test_gpu_error_display_out_of_memory() {
let err = GpuError::OutOfMemory {
requested_bytes: 1024,
available_bytes: 512,
};
let msg = err.to_string();
assert!(msg.contains("out-of-memory"));
assert!(msg.contains("1024"));
}
#[test]
fn test_gpu_error_display_dimension_mismatch() {
let err = GpuError::DimensionMismatch {
expected: 6,
got: 4,
context: "test".to_string(),
};
let msg = err.to_string();
assert!(msg.contains("dimension mismatch"));
assert!(msg.contains("6"));
assert!(msg.contains("4"));
}
#[test]
fn test_detect_gpu_capabilities() {
let caps = detect_gpu_capabilities();
assert!(caps.vram_gb > 0.0);
assert!(caps.compute_units > 0);
assert!(!caps.name.is_empty());
}
#[test]
fn test_gpu_backend_kind_display() {
assert_eq!(GpuBackendKind::Cpu.to_string(), "CPU");
assert_eq!(GpuBackendKind::Simulated.to_string(), "Simulated-GPU");
assert_eq!(GpuBackendKind::OxiBlasGpu.to_string(), "OxiBLAS-GPU");
}
}