Skip to main content

tenflowers_core/
buffer.rs

1#![allow(clippy::result_large_err)]
2
3use crate::{Device, Result, TensorError};
4use std::sync::Arc;
5
6/// Trait for tensor storage backends
7pub trait TensorBuffer: Send + Sync {
8    type Elem: Clone;
9
10    /// Get the device where this buffer is allocated
11    fn device(&self) -> &Device;
12
13    /// Get the number of elements in the buffer
14    fn len(&self) -> usize;
15
16    /// Check if the buffer is empty
17    fn is_empty(&self) -> bool {
18        self.len() == 0
19    }
20
21    /// Get the size in bytes
22    fn size_bytes(&self) -> usize;
23
24    /// Clone the buffer (may involve device memory copy)
25    fn clone_buffer(&self) -> Result<Box<dyn TensorBuffer<Elem = Self::Elem>>>;
26
27    /// Create a view into this buffer (zero-copy when possible)
28    fn view(&self, offset: usize, len: usize) -> Result<Box<dyn TensorBuffer<Elem = Self::Elem>>>;
29
30    /// Convert to a CPU buffer for host operations
31    fn to_cpu(&self) -> Result<Vec<Self::Elem>>;
32
33    /// Get raw pointer (unsafe - for FFI and low-level ops)
34    ///
35    /// # Safety
36    /// The caller must ensure the pointer is valid for the lifetime of the buffer
37    /// and that no mutable references exist when using this pointer.
38    unsafe fn as_ptr(&self) -> *const Self::Elem;
39
40    /// Get mutable raw pointer (unsafe - for FFI and low-level ops)
41    ///
42    /// # Safety
43    /// The caller must ensure the pointer is valid for the lifetime of the buffer
44    /// and that no other references (mutable or immutable) exist when using this pointer.
45    unsafe fn as_mut_ptr(&mut self) -> *mut Self::Elem;
46}
47
48/// Reference-counted tensor buffer for efficient memory sharing
49pub struct SharedBuffer<T> {
50    data: Arc<dyn TensorBuffer<Elem = T>>,
51    offset: usize,
52    len: usize,
53}
54
55impl<T: Clone + Send + Sync + 'static> SharedBuffer<T> {
56    /// Create a new shared buffer from a tensor buffer
57    pub fn new(buffer: Box<dyn TensorBuffer<Elem = T>>) -> Self {
58        let len = buffer.len();
59        Self {
60            data: Arc::from(buffer),
61            offset: 0,
62            len,
63        }
64    }
65
66    /// Create a view into this buffer (zero-copy)
67    pub fn view(&self, offset: usize, len: usize) -> Result<Self> {
68        if offset + len > self.len {
69            return Err(TensorError::invalid_argument(format!(
70                "View out of bounds: offset={offset}, len={len}, buffer_len={}",
71                self.len
72            )));
73        }
74
75        Ok(Self {
76            data: Arc::clone(&self.data),
77            offset: self.offset + offset,
78            len,
79        })
80    }
81
82    /// Get the reference count
83    pub fn ref_count(&self) -> usize {
84        Arc::strong_count(&self.data)
85    }
86}
87
88/// CPU buffer implementation using Vec
89pub struct CpuBuffer<T> {
90    data: Vec<T>,
91    device: Device,
92}
93
94impl<T: Clone + Send + Sync> CpuBuffer<T> {
95    pub fn new(data: Vec<T>) -> Self {
96        Self {
97            data,
98            device: Device::Cpu,
99        }
100    }
101
102    pub fn zeros(len: usize) -> Self
103    where
104        T: Default,
105    {
106        Self {
107            data: vec![T::default(); len],
108            device: Device::Cpu,
109        }
110    }
111}
112
113impl<T: Clone + Send + Sync + 'static> TensorBuffer for CpuBuffer<T> {
114    type Elem = T;
115
116    fn device(&self) -> &Device {
117        &self.device
118    }
119
120    fn len(&self) -> usize {
121        self.data.len()
122    }
123
124    fn size_bytes(&self) -> usize {
125        self.data.len() * std::mem::size_of::<T>()
126    }
127
128    fn clone_buffer(&self) -> Result<Box<dyn TensorBuffer<Elem = Self::Elem>>> {
129        Ok(Box::new(Self {
130            data: self.data.clone(),
131            device: self.device,
132        }))
133    }
134
135    fn view(&self, offset: usize, len: usize) -> Result<Box<dyn TensorBuffer<Elem = Self::Elem>>> {
136        if offset + len > self.data.len() {
137            return Err(TensorError::invalid_argument(format!(
138                "View out of bounds: offset={offset}, len={len}, buffer_len={}",
139                self.data.len()
140            )));
141        }
142
143        Ok(Box::new(Self {
144            data: self.data[offset..offset + len].to_vec(),
145            device: self.device,
146        }))
147    }
148
149    fn to_cpu(&self) -> Result<Vec<Self::Elem>> {
150        Ok(self.data.clone())
151    }
152
153    unsafe fn as_ptr(&self) -> *const Self::Elem {
154        self.data.as_ptr()
155    }
156
157    unsafe fn as_mut_ptr(&mut self) -> *mut Self::Elem {
158        self.data.as_mut_ptr()
159    }
160}
161
162type PoolKey = (Device, usize);
163type PoolValue = Vec<Box<dyn std::any::Any + Send>>;
164type PoolMap = std::collections::HashMap<PoolKey, PoolValue>;
165
166/// Memory pool for efficient buffer allocation and reuse
167pub struct MemoryPool {
168    pools: std::sync::Mutex<PoolMap>,
169    max_pool_size: usize,
170}
171
172impl MemoryPool {
173    pub fn new(max_pool_size: usize) -> Self {
174        Self {
175            pools: std::sync::Mutex::new(std::collections::HashMap::new()),
176            max_pool_size,
177        }
178    }
179
180    /// Allocate a buffer from the pool or create a new one
181    pub fn allocate<
182        T: Clone + Send + Sync + Default + bytemuck::Pod + bytemuck::Zeroable + 'static,
183    >(
184        &self,
185        device: Device,
186        len: usize,
187    ) -> Box<dyn TensorBuffer<Elem = T>> {
188        let key = (device, std::mem::size_of::<T>());
189        let mut pools = match self.pools.lock() {
190            Ok(g) => g,
191            Err(_) => {
192                // poisoned lock — allocate fresh, skip pool
193                return match device {
194                    Device::Cpu => Box::new(CpuBuffer::zeros(len)),
195                    #[cfg(feature = "gpu")]
196                    Device::Gpu(id) => {
197                        use crate::gpu::buffer::GpuBuffer;
198                        match GpuBuffer::<T>::zeros(len, id) {
199                            Ok(buf) => Box::new(buf),
200                            Err(_) => Box::new(CpuBuffer::zeros(len)),
201                        }
202                    }
203                    #[cfg(feature = "rocm")]
204                    Device::Rocm(id) => {
205                        use crate::gpu::buffer::GpuBuffer;
206                        match GpuBuffer::<T>::zeros(len, id) {
207                            Ok(buf) => Box::new(buf),
208                            Err(_) => Box::new(CpuBuffer::zeros(len)),
209                        }
210                    }
211                };
212            }
213        };
214
215        if let Some(pool) = pools.get_mut(&key) {
216            // Try to find a suitable buffer in the pool
217            for i in 0..pool.len() {
218                if let Some(buffer) = pool[i].downcast_ref::<CpuBuffer<T>>() {
219                    if buffer.len() >= len {
220                        let recycled = pool.swap_remove(i);
221                        if let Ok(mut buffer) = recycled.downcast::<CpuBuffer<T>>() {
222                            // Resize if needed
223                            buffer.data.resize(len, T::default());
224                            return buffer;
225                        }
226                    }
227                }
228            }
229        }
230
231        // Allocate new buffer
232        match device {
233            Device::Cpu => Box::new(CpuBuffer::zeros(len)),
234            #[cfg(feature = "gpu")]
235            Device::Gpu(id) => {
236                use crate::gpu::buffer::GpuBuffer;
237                match GpuBuffer::<T>::zeros(len, id) {
238                    Ok(buf) => Box::new(buf),
239                    Err(_) => Box::new(CpuBuffer::zeros(len)), // Fallback to CPU
240                }
241            }
242            #[cfg(feature = "rocm")]
243            Device::Rocm(id) => {
244                use crate::gpu::buffer::GpuBuffer;
245                match GpuBuffer::<T>::zeros(len, id) {
246                    Ok(buf) => Box::new(buf),
247                    Err(_) => Box::new(CpuBuffer::zeros(len)), // Fallback to CPU
248                }
249            }
250        }
251    }
252
253    /// Return a buffer to the pool for reuse
254    pub fn deallocate<T: 'static>(&self, device: Device, buffer: Box<dyn std::any::Any + Send>) {
255        let key = (device, std::mem::size_of::<T>());
256        let mut pools = match self.pools.lock() {
257            Ok(g) => g,
258            Err(_) => return, // poisoned lock — skip pool return
259        };
260
261        let pool = pools.entry(key).or_default();
262        if pool.len() < self.max_pool_size {
263            pool.push(buffer);
264        }
265    }
266}
267
268// Global memory pool instance
269lazy_static::lazy_static! {
270    pub static ref MEMORY_POOL: MemoryPool = MemoryPool::new(100);
271}