use alloc::{borrow::Cow, ffi::CString, sync::Arc};
use core::{
ffi::{CStr, c_char, c_int, c_void},
hash::{Hash, Hasher},
marker::PhantomData,
mem,
ptr::{self, NonNull},
str::{self, FromStr}
};
use crate::{AsPointer, error::Result, ortsys, session::Session};
#[derive(Debug)]
pub struct Allocator {
ptr: NonNull<ort_sys::OrtAllocator>,
info: MemoryInfo<'static>,
pub(crate) handle: Arc<AllocatorHandle>
}
unsafe impl Send for Allocator {}
#[derive(Debug)]
pub(crate) struct AllocatorHandle {
pub(crate) _drop_ptr: Option<NonNull<ort_sys::OrtAllocator>>
}
unsafe impl Send for AllocatorHandle {}
unsafe impl Sync for AllocatorHandle {}
impl Drop for AllocatorHandle {
fn drop(&mut self) {
if let Some(ptr) = self._drop_ptr {
ortsys![unsafe ReleaseAllocator(ptr.as_ptr())];
crate::logging::drop!(Allocator, ptr);
}
}
}
impl Allocator {
pub(crate) unsafe fn from_raw(ptr: NonNull<ort_sys::OrtAllocator>, is_default: bool) -> Allocator {
let mut memory_info_ptr = ptr::null();
ortsys![unsafe AllocatorGetInfo(ptr.as_ptr(), &mut memory_info_ptr).expect("Failed to get memory info"); nonNull(memory_info_ptr)];
Allocator {
ptr,
info: MemoryInfo::from_raw(memory_info_ptr, false),
handle: Arc::new(AllocatorHandle {
_drop_ptr: (!is_default).then_some(ptr)
})
}
}
pub fn alloc<T>(&self, len: usize) -> Result<AllocatedBlock<'_>> {
let mut ptr = ptr::null_mut();
ortsys![unsafe AllocatorAlloc(self.ptr.as_ptr(), (len * mem::size_of::<T>()) as _, &mut ptr)?; nonNull(ptr)];
crate::logging::create!(AllocatedBlock, ptr);
Ok(AllocatedBlock { ptr, allocator: self })
}
pub unsafe fn free<T>(&self, ptr: *mut T) {
ortsys![unsafe AllocatorFree(self.ptr.as_ptr(), ptr.cast()).expect("Failed to free")];
}
pub fn memory_info<'a>(&'a self) -> &'a MemoryInfo<'a> {
&self.info
}
pub fn new(session: &Session, memory_info: MemoryInfo<'_>) -> Result<Self> {
let mut ptr: *mut ort_sys::OrtAllocator = ptr::null_mut();
ortsys![unsafe CreateAllocator(session.ptr(), memory_info.ptr.as_ptr(), &mut ptr)?; nonNull(ptr)];
crate::logging::create!(Allocator, ptr);
Ok(Self {
ptr,
info: memory_info.to_owned(),
handle: Arc::new(AllocatorHandle { _drop_ptr: Some(ptr) })
})
}
}
impl Default for Allocator {
fn default() -> Self {
let mut allocator_ptr: *mut ort_sys::OrtAllocator = ptr::null_mut();
ortsys![
unsafe GetAllocatorWithDefaultOptions(&mut allocator_ptr)
.expect("Failed to get default allocator");
nonNull(allocator_ptr)
];
let mut memory_info_ptr = ptr::null();
ortsys![unsafe AllocatorGetInfo(allocator_ptr.as_ptr(), &mut memory_info_ptr).expect("Failed to get memory info"); nonNull(memory_info_ptr)];
Self {
ptr: allocator_ptr,
info: MemoryInfo::from_raw(memory_info_ptr, false),
handle: Arc::new(AllocatorHandle { _drop_ptr: None })
}
}
}
impl AsPointer for Allocator {
type Sys = ort_sys::OrtAllocator;
fn ptr(&self) -> *const Self::Sys {
self.ptr.as_ptr()
}
}
pub struct AllocatedBlock<'a> {
ptr: NonNull<c_void>,
allocator: &'a Allocator
}
impl<'a> AllocatedBlock<'a> {
pub fn as_ptr(&self) -> *const c_void {
self.ptr.as_ptr()
}
pub fn as_mut_ptr(&mut self) -> *mut c_void {
self.ptr.as_ptr()
}
pub fn allocator(&self) -> &'a Allocator {
self.allocator
}
#[must_use = "the returned pointer must be freed with the allocator that created it"]
pub fn into_raw(self) -> *mut c_void {
let ptr = self.ptr;
mem::forget(self);
ptr.as_ptr()
}
}
impl Drop for AllocatedBlock<'_> {
fn drop(&mut self) {
unsafe { self.allocator.free(self.ptr.as_ptr()) };
crate::logging::drop!(AllocatedBlock, self.ptr);
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AllocationDevice<'s>(Cow<'s, CStr>);
impl<'s> AllocationDevice<'s> {
pub const CPU: AllocationDevice<'static> = AllocationDevice(Cow::Borrowed(c"Cpu"));
pub const CUDA: AllocationDevice<'static> = AllocationDevice(Cow::Borrowed(c"Cuda"));
pub const CUDA_PINNED: AllocationDevice<'static> = AllocationDevice(Cow::Borrowed(c"CudaPinned"));
pub const CANN: AllocationDevice<'static> = AllocationDevice(Cow::Borrowed(c"Cann"));
pub const CANN_PINNED: AllocationDevice<'static> = AllocationDevice(Cow::Borrowed(c"CannPinned"));
pub const DIRECTML: AllocationDevice<'static> = AllocationDevice(Cow::Borrowed(c"DML"));
pub const HIP: AllocationDevice<'static> = AllocationDevice(Cow::Borrowed(c"Hip"));
pub const HIP_PINNED: AllocationDevice<'static> = AllocationDevice(Cow::Borrowed(c"HipPinned"));
pub const OPENVINO_CPU: AllocationDevice<'static> = AllocationDevice(Cow::Borrowed(c"OpenVINO_CPU"));
pub const OPENVINO_GPU: AllocationDevice<'static> = AllocationDevice(Cow::Borrowed(c"OpenVINO_GPU"));
pub const QNN_HTP_SHARED: AllocationDevice<'static> = AllocationDevice(Cow::Borrowed(c"QnnHtpShared"));
pub const WEBGPU_BUFFER: AllocationDevice<'static> = AllocationDevice(Cow::Borrowed(c"WebGPU_Buffer"));
pub fn new(name: &str) -> Self {
AllocationDevice(Cow::Owned(CString::from_str(name).expect("invalid device name")))
}
pub fn as_str(&self) -> &str {
unsafe { str::from_utf8_unchecked(self.0.as_ref().to_bytes()) }
}
}
impl PartialEq<str> for AllocationDevice<'_> {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum AllocatorType {
Device,
Arena,
ReadOnly
}
impl From<AllocatorType> for ort_sys::OrtAllocatorType {
fn from(val: AllocatorType) -> Self {
match val {
AllocatorType::Device => ort_sys::OrtAllocatorType::OrtDeviceAllocator,
AllocatorType::Arena => ort_sys::OrtAllocatorType::OrtArenaAllocator,
AllocatorType::ReadOnly => ort_sys::OrtAllocatorType::OrtReadOnlyAllocator
}
}
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum MemoryType {
CPUInput,
CPUOutput,
#[default]
Default
}
impl MemoryType {
pub const CPU: MemoryType = MemoryType::CPUOutput;
}
impl From<MemoryType> for ort_sys::OrtMemType {
fn from(val: MemoryType) -> Self {
match val {
MemoryType::CPUInput => ort_sys::OrtMemType::OrtMemTypeCPUInput,
MemoryType::CPUOutput => ort_sys::OrtMemType::OrtMemTypeCPUOutput,
MemoryType::Default => ort_sys::OrtMemType::OrtMemTypeDefault
}
}
}
impl From<ort_sys::OrtMemType> for MemoryType {
fn from(value: ort_sys::OrtMemType) -> Self {
match value {
ort_sys::OrtMemType::OrtMemTypeCPUInput => MemoryType::CPUInput,
ort_sys::OrtMemType::OrtMemTypeCPUOutput => MemoryType::CPUOutput,
ort_sys::OrtMemType::OrtMemTypeDefault => MemoryType::Default
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[allow(clippy::upper_case_acronyms)]
pub enum DeviceType {
CPU,
GPU,
FPGA,
NPU
}
impl From<DeviceType> for ort_sys::OrtMemoryInfoDeviceType {
fn from(value: DeviceType) -> Self {
match value {
DeviceType::CPU => ort_sys::OrtMemoryInfoDeviceType::OrtMemoryInfoDeviceType_CPU,
DeviceType::GPU => ort_sys::OrtMemoryInfoDeviceType::OrtMemoryInfoDeviceType_GPU,
DeviceType::FPGA => ort_sys::OrtMemoryInfoDeviceType::OrtMemoryInfoDeviceType_FPGA,
DeviceType::NPU => ort_sys::OrtMemoryInfoDeviceType::OrtMemoryInfoDeviceType_NPU
}
}
}
impl From<ort_sys::OrtMemoryInfoDeviceType> for DeviceType {
fn from(value: ort_sys::OrtMemoryInfoDeviceType) -> Self {
match value {
ort_sys::OrtMemoryInfoDeviceType::OrtMemoryInfoDeviceType_CPU => DeviceType::CPU,
ort_sys::OrtMemoryInfoDeviceType::OrtMemoryInfoDeviceType_GPU => DeviceType::GPU,
ort_sys::OrtMemoryInfoDeviceType::OrtMemoryInfoDeviceType_FPGA => DeviceType::FPGA,
ort_sys::OrtMemoryInfoDeviceType::OrtMemoryInfoDeviceType_NPU => DeviceType::NPU
}
}
}
#[derive(Debug)]
pub struct MemoryInfo<'a> {
ptr: NonNull<ort_sys::OrtMemoryInfo>,
should_release: bool,
_p: PhantomData<&'a ()>
}
unsafe impl Send for MemoryInfo<'_> {}
unsafe impl Sync for MemoryInfo<'_> {}
impl MemoryInfo<'static> {
pub fn new(allocation_device: AllocationDevice, device_id: c_int, allocator_type: AllocatorType, memory_type: MemoryType) -> Result<Self> {
let mut ptr: *mut ort_sys::OrtMemoryInfo = ptr::null_mut();
ortsys![
unsafe CreateMemoryInfo(allocation_device.0.as_ptr().cast(), allocator_type.into(), device_id, memory_type.into(), &mut ptr)?;
nonNull(ptr)
];
crate::logging::create!(MemoryInfo, ptr);
Ok(Self {
ptr,
should_release: true,
_p: PhantomData
})
}
}
impl<'a> MemoryInfo<'a> {
pub(crate) unsafe fn from_tensor_value(value_ptr: NonNull<ort_sys::OrtValue>) -> Self {
let mut memory_info_ptr: *const ort_sys::OrtMemoryInfo = ptr::null_mut();
ortsys![unsafe GetTensorMemoryInfo(value_ptr.as_ptr(), &mut memory_info_ptr).expect("infallible"); nonNull(memory_info_ptr)];
Self::from_raw(memory_info_ptr, false)
}
pub(crate) fn from_raw(ptr: NonNull<ort_sys::OrtMemoryInfo>, should_release: bool) -> Self {
MemoryInfo { ptr, should_release, _p: PhantomData }
}
pub fn memory_type(&self) -> MemoryType {
let mut raw_type: ort_sys::OrtMemType = ort_sys::OrtMemType::OrtMemTypeDefault;
ortsys![unsafe MemoryInfoGetMemType(self.ptr.as_ptr(), &mut raw_type).expect("infallible")];
MemoryType::from(raw_type)
}
pub fn allocator_type(&self) -> AllocatorType {
let mut raw_type: ort_sys::OrtAllocatorType = ort_sys::OrtAllocatorType::OrtInvalidAllocator;
ortsys![unsafe MemoryInfoGetType(self.ptr.as_ptr(), &mut raw_type).expect("infallible")];
match raw_type {
ort_sys::OrtAllocatorType::OrtArenaAllocator => AllocatorType::Arena,
ort_sys::OrtAllocatorType::OrtDeviceAllocator => AllocatorType::Device,
ort_sys::OrtAllocatorType::OrtReadOnlyAllocator => AllocatorType::ReadOnly,
ort_sys::OrtAllocatorType::OrtInvalidAllocator => unreachable!("memory info has invalid allocator type")
}
}
pub fn allocation_device(&self) -> AllocationDevice<'_> {
let mut name_ptr: *const c_char = ptr::null_mut();
ortsys![unsafe MemoryInfoGetName(self.ptr.as_ptr(), &mut name_ptr).expect("infallible"); nonNull(name_ptr)];
let name = unsafe { CStr::from_ptr(name_ptr.as_ptr()) };
assert!(name.to_str().is_ok(), "invalid device name");
AllocationDevice(Cow::Borrowed(name))
}
pub fn device_id(&self) -> i32 {
let mut raw: ort_sys::c_int = 0;
ortsys![unsafe MemoryInfoGetId(self.ptr.as_ptr(), &mut raw).expect("infallible")];
raw as _
}
pub fn device_type(&self) -> DeviceType {
let mut raw: ort_sys::OrtMemoryInfoDeviceType = ort_sys::OrtMemoryInfoDeviceType::OrtMemoryInfoDeviceType_CPU;
ortsys![unsafe MemoryInfoGetDeviceType(self.ptr.as_ptr(), &mut raw)];
raw.into()
}
pub fn is_cpu_accessible(&self) -> bool {
#[cfg(feature = "api-23")]
return self.device_type() == DeviceType::CPU
|| ortsys![unsafe MemoryInfoGetDeviceMemType(self.ptr.as_ptr())] == ort_sys::OrtDeviceMemoryType::OrtDeviceMemoryType_HOST_ACCESSIBLE;
#[cfg(not(feature = "api-23"))]
return self.device_type() == DeviceType::CPU;
}
pub fn to_owned(&self) -> MemoryInfo<'static> {
MemoryInfo::new(self.allocation_device(), self.device_id(), self.allocator_type(), self.memory_type()).expect("failed to clone memory info")
}
}
impl Default for MemoryInfo<'_> {
fn default() -> Self {
MemoryInfo::new(AllocationDevice::CPU, 0, AllocatorType::Device, MemoryType::Default).expect("failed to create default memory info")
}
}
impl Clone for MemoryInfo<'_> {
fn clone(&self) -> Self {
self.to_owned()
}
}
impl PartialEq<MemoryInfo<'_>> for MemoryInfo<'_> {
fn eq(&self, other: &MemoryInfo) -> bool {
let mut out = 0;
ortsys![unsafe CompareMemoryInfo(self.ptr.as_ptr(), other.ptr.as_ptr(), &mut out).expect("infallible")]; out == 0
}
}
impl Hash for MemoryInfo<'_> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.device_type().hash(state);
self.memory_type().hash(state);
self.allocator_type().hash(state);
self.allocation_device().hash(state);
self.device_id().hash(state);
}
}
impl AsPointer for MemoryInfo<'_> {
type Sys = ort_sys::OrtMemoryInfo;
fn ptr(&self) -> *const Self::Sys {
self.ptr.as_ptr()
}
}
impl Drop for MemoryInfo<'_> {
fn drop(&mut self) {
if self.should_release {
ortsys![unsafe ReleaseMemoryInfo(self.ptr.as_ptr())];
crate::logging::drop!(MemoryInfo, self.ptr);
}
}
}
#[cfg(test)]
mod tests {
use super::{AllocationDevice, AllocatorType, MemoryInfo, MemoryType};
#[test]
fn test_memory_info_eq() -> crate::Result<()> {
let a = MemoryInfo::new(AllocationDevice::CUDA, 1, AllocatorType::Device, MemoryType::Default)?;
let b = MemoryInfo::new(AllocationDevice::CUDA, 1, AllocatorType::Device, MemoryType::Default)?;
assert_eq!(a, b);
assert_eq!(a.to_owned(), a);
let c = MemoryInfo::new(AllocationDevice::CPU, 0, AllocatorType::Device, MemoryType::Default)?;
assert_ne!(a, c);
Ok(())
}
#[test]
#[cfg(feature = "cuda")]
fn test_cpu_accessible() -> crate::Result<()> {
let mem = MemoryInfo::new(AllocationDevice::CUDA_PINNED, 0, AllocatorType::Device, MemoryType::Default)?;
assert!(mem.is_cpu_accessible());
let mem = MemoryInfo::new(AllocationDevice::CUDA, 0, AllocatorType::Device, MemoryType::Default)?;
assert!(!mem.is_cpu_accessible());
let mem = MemoryInfo::new(AllocationDevice::CPU, 0, AllocatorType::Device, MemoryType::Default)?;
assert!(mem.is_cpu_accessible());
Ok(())
}
}