#![deny(missing_docs)]
pub mod actions;
pub mod arena;
pub mod nixl;
#[cfg(target_os = "linux")]
pub mod numa;
pub mod offset;
pub mod pool;
pub mod prelude;
mod device;
#[cfg(target_os = "linux")]
mod disk;
mod external;
mod pinned;
mod system;
mod tensor;
#[cfg(test)]
mod tests;
pub use arena::{ArenaAllocator, ArenaBuffer, ArenaError};
pub use device::DeviceStorage;
#[cfg(target_os = "linux")]
pub use disk::DiskStorage;
pub use external::ExternalDeviceMemory;
#[cfg(target_os = "linux")]
pub use numa::{NumaNode, is_numa_disabled, is_numa_enabled};
pub use offset::OffsetBuffer;
pub use pinned::PinnedStorage;
pub use pool::{CudaMemPool, CudaMemPoolBuilder};
pub use system::SystemStorage;
pub use tensor::{TensorDescriptor, TensorDescriptorExt};
use serde::{Deserialize, Serialize};
use std::any::Any;
use std::fmt;
use std::sync::Arc;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, StorageError>;
pub trait MemoryDescriptor: Send + Sync + fmt::Debug {
fn addr(&self) -> usize;
fn size(&self) -> usize;
fn storage_kind(&self) -> StorageKind;
fn as_any(&self) -> &dyn Any;
fn nixl_descriptor(&self) -> Option<nixl::NixlDescriptor>;
}
#[derive(Debug, Error)]
#[allow(missing_docs)]
pub enum StorageError {
#[error("allocation failed: {0}")]
AllocationFailed(String),
#[error("registration failed: {0}")]
RegistrationFailed(String),
#[error("operation failed: {0}")]
OperationFailed(String),
#[error("unsupported operation: {0}")]
Unsupported(String),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("CUDA error: {0}")]
Cuda(#[from] cudarc::driver::DriverError),
#[error("NIXL error: {0}")]
Nixl(#[from] nixl_sys::NixlError),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum StorageKind {
System,
Pinned,
Device(u32),
Disk(u64),
}
impl StorageKind {
pub fn cuda_device_index(&self) -> Option<u32> {
match self {
StorageKind::Device(idx) => Some(*idx),
_ => None,
}
}
pub fn is_cuda(&self) -> bool {
matches!(self, StorageKind::Device(_))
}
pub fn is_system(&self) -> bool {
matches!(self, StorageKind::System)
}
pub fn is_pinned(&self) -> bool {
matches!(self, StorageKind::Pinned)
}
pub fn is_disk(&self) -> bool {
matches!(self, StorageKind::Disk(_))
}
}
#[derive(Clone)]
pub struct Buffer(Arc<dyn MemoryDescriptor>);
impl Buffer {
pub fn new<S: MemoryDescriptor + 'static>(memory: S) -> Self {
Buffer(Arc::new(memory))
}
}
impl MemoryDescriptor for Buffer {
fn addr(&self) -> usize {
self.0.addr()
}
fn size(&self) -> usize {
self.0.size()
}
fn storage_kind(&self) -> StorageKind {
self.0.storage_kind()
}
fn as_any(&self) -> &dyn Any {
self.0.as_any()
}
fn nixl_descriptor(&self) -> Option<nixl::NixlDescriptor> {
self.0.nixl_descriptor()
}
}
impl std::ops::Deref for Buffer {
type Target = dyn MemoryDescriptor;
fn deref(&self) -> &Self::Target {
self.0.as_ref()
}
}
impl std::fmt::Debug for Buffer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Buffer")
.field("addr", &self.addr())
.field("size", &self.size())
.field("kind", &self.storage_kind())
.finish()
}
}
pub fn create_buffer<S: MemoryDescriptor + 'static>(memory: S) -> Buffer {
Buffer(Arc::new(memory))
}
impl Buffer {
pub fn from_arc(arc: Arc<dyn MemoryDescriptor>) -> Self {
Buffer(arc)
}
}
impl From<Arc<dyn MemoryDescriptor>> for Buffer {
fn from(arc: Arc<dyn MemoryDescriptor>) -> Self {
Buffer::from_arc(arc)
}
}
impl From<Arc<dyn nixl::NixlMemory + Send + Sync>> for Buffer {
fn from(arc: Arc<dyn nixl::NixlMemory + Send + Sync>) -> Self {
Buffer::new(arc)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct MemoryRegion {
pub addr: usize,
pub size: usize,
}
impl MemoryRegion {
pub fn new(addr: usize, size: usize) -> Self {
Self { addr, size }
}
#[inline]
pub fn addr(&self) -> usize {
self.addr
}
#[inline]
pub fn size(&self) -> usize {
self.size
}
#[cfg(feature = "unsafe-slices")]
pub unsafe fn as_slice(&self) -> Result<&[u8]> {
if self.size == 0 {
return Ok(&[]);
}
unsafe {
Ok(std::slice::from_raw_parts(
self.addr as *const u8,
self.size,
))
}
}
#[cfg(feature = "unsafe-slices")]
pub unsafe fn as_slice_mut(&mut self) -> Result<&mut [u8]> {
if self.size == 0 {
return Ok(&mut []);
}
unsafe {
Ok(std::slice::from_raw_parts_mut(
self.addr as *mut u8,
self.size,
))
}
}
}
pub fn env_is_truthy(env: &str) -> bool {
match std::env::var(env) {
Ok(val) => matches!(val.to_lowercase().as_str(), "1" | "true" | "on" | "yes"),
Err(_) => false,
}
}
pub fn parse_bool(val: &str) -> anyhow::Result<bool> {
if matches!(val.to_lowercase().as_str(), "1" | "true" | "on" | "yes") {
Ok(true)
} else if matches!(val.to_lowercase().as_str(), "0" | "false" | "off" | "no") {
Ok(false)
} else {
anyhow::bail!(
"Invalid boolean value: '{val}'. Expected one of: true/false, 1/0, on/off, yes/no",
)
}
}