use torsh_core::{device::DeviceType, error::Result};
pub trait MemoryPinning<T> {
fn pin_memory(&self, data: T) -> Result<T>;
fn supports_pinning(&self) -> bool;
fn pinning_info(&self) -> String {
if self.supports_pinning() {
"Memory pinning supported".to_string()
} else {
"Memory pinning not supported".to_string()
}
}
}
#[derive(Debug, Clone, Default)]
pub struct CpuMemoryPinner;
impl CpuMemoryPinner {
pub fn new() -> Self {
Self
}
}
impl<T> MemoryPinning<T> for CpuMemoryPinner {
fn pin_memory(&self, data: T) -> Result<T> {
Ok(data)
}
fn supports_pinning(&self) -> bool {
false
}
fn pinning_info(&self) -> String {
"CPU memory pinner (no-op implementation)".to_string()
}
}
#[cfg(feature = "cuda")]
#[derive(Debug, Clone)]
pub struct CudaMemoryPinner {
device_id: usize,
}
#[cfg(feature = "cuda")]
impl CudaMemoryPinner {
pub fn new(device_id: usize) -> Result<Self> {
#[cfg(feature = "cuda")]
{
}
Ok(Self { device_id })
}
pub fn device_id(&self) -> usize {
self.device_id
}
pub fn set_device_id(&mut self, device_id: usize) {
self.device_id = device_id;
}
}
#[cfg(feature = "cuda")]
impl<T> MemoryPinning<torsh_tensor::Tensor<T>> for CudaMemoryPinner
where
T: torsh_core::dtype::TensorElement,
{
fn pin_memory(&self, tensor: torsh_tensor::Tensor<T>) -> Result<torsh_tensor::Tensor<T>> {
Ok(tensor)
}
fn supports_pinning(&self) -> bool {
true
}
fn pinning_info(&self) -> String {
format!("CUDA memory pinner for device {}", self.device_id)
}
}
#[derive(Debug)]
pub struct MemoryPinningManager {
cpu_pinner: CpuMemoryPinner,
#[cfg(feature = "cuda")]
cuda_pinners: std::collections::HashMap<usize, CudaMemoryPinner>,
}
impl MemoryPinningManager {
pub fn new() -> Self {
Self {
cpu_pinner: CpuMemoryPinner::new(),
#[cfg(feature = "cuda")]
cuda_pinners: std::collections::HashMap::new(),
}
}
pub fn pin_memory<T>(
&mut self,
data: torsh_tensor::Tensor<T>,
target_device: Option<DeviceType>,
) -> Result<torsh_tensor::Tensor<T>>
where
T: torsh_core::dtype::TensorElement,
{
match target_device {
Some(DeviceType::Cuda(device_id)) => {
#[cfg(feature = "cuda")]
{
if !self.cuda_pinners.contains_key(&device_id) {
let pinner = CudaMemoryPinner::new(device_id)?;
self.cuda_pinners.insert(device_id, pinner);
}
if let Some(pinner) = self.cuda_pinners.get(&device_id) {
pinner.pin_memory(data)
} else {
Ok(data)
}
}
#[cfg(not(feature = "cuda"))]
{
let _ = device_id; self.cpu_pinner.pin_memory(data)
}
}
_ => {
self.cpu_pinner.pin_memory(data)
}
}
}
pub fn pin_vector_memory<T>(
&mut self,
data: Vec<T>,
target_device: Option<DeviceType>,
) -> Result<Vec<T>> {
match target_device {
Some(DeviceType::Cuda(_device_id)) => {
#[cfg(feature = "cuda")]
{
Ok(data)
}
#[cfg(not(feature = "cuda"))]
{
Ok(data)
}
}
_ => {
Ok(data)
}
}
}
pub fn supports_pinning(&self, target_device: Option<DeviceType>) -> bool {
match target_device {
Some(DeviceType::Cuda(_)) => {
#[cfg(feature = "cuda")]
return true;
#[cfg(not(feature = "cuda"))]
return false;
}
_ => false,
}
}
pub fn available_pinners(&self) -> String {
#[allow(unused_mut)]
let mut info = vec!["CPU (no-op)".to_string()];
#[cfg(feature = "cuda")]
{
if !self.cuda_pinners.is_empty() {
let devices: Vec<String> = self
.cuda_pinners
.keys()
.map(|id| format!("CUDA device {}", id))
.collect();
info.push(format!("CUDA: {}", devices.join(", ")));
} else {
info.push("CUDA (available but no devices initialized)".to_string());
}
}
format!("Available pinners: {}", info.join(", "))
}
#[cfg(feature = "cuda")]
pub fn clear_cuda_pinners(&mut self) {
self.cuda_pinners.clear();
}
#[cfg(feature = "cuda")]
pub fn cuda_pinner_count(&self) -> usize {
self.cuda_pinners.len()
}
#[cfg(feature = "cuda")]
pub fn initialize_cuda_pinner(&mut self, device_id: usize) -> Result<()> {
if !self.cuda_pinners.contains_key(&device_id) {
let pinner = CudaMemoryPinner::new(device_id)?;
self.cuda_pinners.insert(device_id, pinner);
}
Ok(())
}
}
impl Default for MemoryPinningManager {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct PinningConfig {
pub enabled: bool,
pub target_device: Option<DeviceType>,
pub pre_initialize: bool,
}
impl Default for PinningConfig {
fn default() -> Self {
Self {
enabled: true,
target_device: None,
pre_initialize: false,
}
}
}
impl PinningConfig {
pub fn new() -> Self {
Self::default()
}
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
pub fn target_device(mut self, device: DeviceType) -> Self {
self.target_device = Some(device);
self
}
pub fn pre_initialize(mut self, pre_init: bool) -> Self {
self.pre_initialize = pre_init;
self
}
pub fn cuda(device_id: usize) -> Self {
Self {
enabled: true,
target_device: Some(DeviceType::Cuda(device_id)),
pre_initialize: false,
}
}
pub fn cpu() -> Self {
Self {
enabled: false,
target_device: Some(DeviceType::Cpu),
pre_initialize: false,
}
}
}
pub mod utils {
use super::*;
pub fn should_pin_memory(
source_device: DeviceType,
target_device: DeviceType,
data_size: usize,
) -> bool {
match (source_device, target_device) {
(DeviceType::Cpu, DeviceType::Cuda(_)) => {
data_size > 1024 }
(DeviceType::Cuda(_), DeviceType::Cpu) => {
data_size > 1024
}
_ => false, }
}
pub fn estimate_pinning_overhead(data_size: usize) -> usize {
data_size / 100 }
pub fn system_supports_pinning(device: DeviceType) -> bool {
match device {
DeviceType::Cuda(_) => {
#[cfg(feature = "cuda")]
return true;
#[cfg(not(feature = "cuda"))]
return false;
}
_ => false,
}
}
pub fn optimal_pinning_config(
source_device: DeviceType,
target_device: DeviceType,
data_size: usize,
) -> PinningConfig {
if should_pin_memory(source_device, target_device, data_size) {
PinningConfig::new()
.enabled(true)
.target_device(target_device)
.pre_initialize(data_size > 1024 * 1024) } else {
PinningConfig::new().enabled(false)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cpu_memory_pinner() {
let pinner: CpuMemoryPinner = CpuMemoryPinner::new();
let pinner_trait: &dyn MemoryPinning<Vec<i32>> = &pinner;
assert!(!pinner_trait.supports_pinning());
let data = vec![1, 2, 3, 4, 5];
let result = pinner
.pin_memory(data.clone())
.expect("operation should succeed");
assert_eq!(result, data);
}
#[test]
fn test_memory_pinning_manager_creation() {
let manager = MemoryPinningManager::new();
assert!(!manager.supports_pinning(Some(DeviceType::Cpu)));
}
#[test]
fn test_memory_pinning_manager_cpu() {
let mut manager = MemoryPinningManager::new();
let data = vec![1, 2, 3, 4, 5];
let result = manager
.pin_vector_memory(data.clone(), Some(DeviceType::Cpu))
.expect("operation should succeed");
assert_eq!(result, data);
}
#[test]
fn test_pinning_config() {
let config = PinningConfig::new()
.enabled(true)
.target_device(DeviceType::Cpu)
.pre_initialize(true);
assert!(config.enabled);
assert_eq!(config.target_device, Some(DeviceType::Cpu));
assert!(config.pre_initialize);
}
#[test]
fn test_pinning_config_cuda() {
let config = PinningConfig::cuda(0);
assert!(config.enabled);
assert_eq!(config.target_device, Some(DeviceType::Cuda(0)));
assert!(!config.pre_initialize);
}
#[test]
fn test_pinning_config_cpu() {
let config = PinningConfig::cpu();
assert!(!config.enabled);
assert_eq!(config.target_device, Some(DeviceType::Cpu));
assert!(!config.pre_initialize);
}
#[test]
fn test_should_pin_memory() {
assert!(utils::should_pin_memory(
DeviceType::Cpu,
DeviceType::Cuda(0),
2048
));
assert!(!utils::should_pin_memory(
DeviceType::Cpu,
DeviceType::Cuda(0),
512
));
assert!(!utils::should_pin_memory(
DeviceType::Cpu,
DeviceType::Cpu,
2048
));
}
#[test]
fn test_estimate_pinning_overhead() {
let data_size = 1000;
let overhead = utils::estimate_pinning_overhead(data_size);
assert_eq!(overhead, 10); }
#[test]
fn test_system_supports_pinning() {
assert!(!utils::system_supports_pinning(DeviceType::Cpu));
#[cfg(feature = "cuda")]
assert!(utils::system_supports_pinning(DeviceType::Cuda(0)));
#[cfg(not(feature = "cuda"))]
assert!(!utils::system_supports_pinning(DeviceType::Cuda(0)));
}
#[test]
fn test_optimal_pinning_config() {
let config = utils::optimal_pinning_config(DeviceType::Cpu, DeviceType::Cuda(0), 2048);
assert!(config.enabled);
assert_eq!(config.target_device, Some(DeviceType::Cuda(0)));
let config = utils::optimal_pinning_config(DeviceType::Cpu, DeviceType::Cuda(0), 512);
assert!(!config.enabled);
let config =
utils::optimal_pinning_config(DeviceType::Cpu, DeviceType::Cuda(0), 2 * 1024 * 1024);
assert!(config.enabled);
assert!(config.pre_initialize);
}
#[cfg(feature = "cuda")]
#[test]
fn test_cuda_memory_pinner() {
let pinner = CudaMemoryPinner::new(0).expect("Cuda Memory Pinner should succeed");
let pinner_trait: &dyn MemoryPinning<torsh_tensor::Tensor<f32>> = &pinner;
assert!(pinner_trait.supports_pinning());
assert_eq!(pinner.device_id(), 0);
}
#[cfg(feature = "cuda")]
#[test]
fn test_memory_pinning_manager_cuda() {
let mut manager = MemoryPinningManager::new();
assert!(manager.supports_pinning(Some(DeviceType::Cuda(0))));
manager
.initialize_cuda_pinner(0)
.expect("CUDA pinner initialization should succeed");
assert_eq!(manager.cuda_pinner_count(), 1);
manager.clear_cuda_pinners();
assert_eq!(manager.cuda_pinner_count(), 0);
}
}