Skip to main content

torsh_tensor/
tensor_views.rs

1// Tensor views and aliasing module for efficient memory management
2
3use crate::{Tensor, TensorStorage};
4use std::collections::HashMap;
5use std::sync::{Arc, RwLock, Weak};
6use torsh_core::sync::RwLockExt;
7use torsh_core::{
8    device::DeviceType,
9    dtype::TensorElement,
10    error::{Result, TorshError},
11    shape::Shape,
12};
13
14/// A view into a tensor that shares memory but may have different shape/strides
15#[derive(Debug, Clone)]
16pub struct TensorView<T: TensorElement> {
17    /// Reference to the underlying tensor storage
18    storage: Arc<RwLock<ViewStorage<T>>>,
19    /// Shape of this view
20    shape: Shape,
21    /// Strides for this view
22    strides: Vec<usize>,
23    /// Offset into the underlying data
24    offset: usize,
25    /// Device type
26    device: DeviceType,
27}
28
29/// Storage for tensor views with reference counting
30#[derive(Debug)]
31struct ViewStorage<T: TensorElement> {
32    /// Weak reference to parent tensor to avoid cycles
33    #[allow(dead_code)]
34    parent: Weak<RwLock<Vec<T>>>,
35    /// Strong reference to keep data alive if needed
36    data_ref: Option<Arc<RwLock<Vec<T>>>>,
37    /// Cache for computed views
38    view_cache: HashMap<ViewKey, Arc<TensorView<T>>>,
39    /// Reference count for active views
40    view_count: usize,
41}
42
43/// Key for view caching
44#[derive(Debug, Hash, PartialEq, Eq, Clone)]
45struct ViewKey {
46    shape: Vec<usize>,
47    strides: Vec<usize>,
48    offset: usize,
49}
50
51impl<T: TensorElement + Copy> Tensor<T> {
52    /// Calculate strides for current tensor shape
53    pub fn calculate_strides(&self) -> Vec<usize> {
54        let shape_binding = self.shape();
55        let dims = shape_binding.dims();
56        let mut strides = vec![1; dims.len()];
57        for i in (0..dims.len().saturating_sub(1)).rev() {
58            strides[i] = strides[i + 1] * dims[i + 1];
59        }
60        strides
61    }
62    /// Create a view of this tensor with a new shape (must have same number of elements)
63    pub fn create_view(&self, new_shape: &[usize]) -> Result<TensorView<T>> {
64        let new_numel = new_shape.iter().product::<usize>();
65        if new_numel != self.numel() {
66            return Err(TorshError::InvalidOperation(format!(
67                "View shape {:?} has {} elements, but tensor has {} elements",
68                new_shape,
69                new_numel,
70                self.numel()
71            )));
72        }
73
74        // Calculate strides for the new shape (row-major)
75        let mut strides = vec![1; new_shape.len()];
76        for i in (0..new_shape.len().saturating_sub(1)).rev() {
77            strides[i] = strides[i + 1] * new_shape[i + 1];
78        }
79
80        self.create_view_with_strides(new_shape, &strides, 0)
81    }
82
83    /// Create a view with custom strides (advanced usage)
84    pub fn view_with_strides(
85        &self,
86        new_shape: &[usize],
87        strides: &[usize],
88    ) -> Result<TensorView<T>> {
89        if new_shape.len() != strides.len() {
90            return Err(TorshError::InvalidOperation(
91                "Shape and strides must have same length".to_string(),
92            ));
93        }
94
95        self.create_view_with_strides(new_shape, strides, 0)
96    }
97
98    /// Create a slice view of the tensor along a specific dimension
99    pub fn slice(&self, dim: usize, start: usize, end: usize) -> Result<TensorView<T>> {
100        let shape_binding = self.shape();
101        let dims = shape_binding.dims();
102        if dim >= dims.len() {
103            return Err(TorshError::InvalidOperation(format!(
104                "Dimension {} out of bounds for tensor with {} dimensions",
105                dim,
106                dims.len()
107            )));
108        }
109
110        if start >= end || end > dims[dim] {
111            return Err(TorshError::InvalidOperation(format!(
112                "Invalid slice range [{}:{}] for dimension of size {}",
113                start, end, dims[dim]
114            )));
115        }
116
117        // Calculate new shape and offset
118        let mut new_shape = dims.to_vec();
119        new_shape[dim] = end - start;
120
121        // Calculate offset for the slice
122        let strides = self.calculate_strides();
123        let offset = start * strides[dim];
124
125        self.create_view_with_strides(&new_shape, &strides, offset)
126    }
127
128    /// Internal method to create views with custom strides and offset
129    fn create_view_with_strides(
130        &self,
131        shape: &[usize],
132        strides: &[usize],
133        offset: usize,
134    ) -> Result<TensorView<T>> {
135        // Get reference to underlying data
136        let data_ref = match &self.storage {
137            TensorStorage::InMemory(data) => data.clone(),
138            TensorStorage::MemoryMapped(_) => {
139                // For memory-mapped storage, convert to in-memory for views
140                let data = self.to_vec()?;
141                Arc::new(RwLock::new(data))
142            }
143            #[cfg(feature = "simd")]
144            TensorStorage::Aligned(data) => {
145                // Convert AlignedVec to Vec for standard view handling
146                let aligned_data = data.read_or_recover();
147                let vec_data = aligned_data.as_slice().to_vec();
148                Arc::new(RwLock::new(vec_data))
149            }
150            #[cfg(feature = "simd")]
151            TensorStorage::SimdOptimized(storage) => {
152                // Lock-free while unmutated; reads through the copy-on-write
153                // buffer once the storage has been written to.
154                let vec_data = storage.to_vec();
155                Arc::new(RwLock::new(vec_data))
156            }
157            #[cfg(feature = "gpu")]
158            TensorStorage::Device { .. } => {
159                // A strided view can never address device memory: the backend's
160                // copy primitives have no offset parameter, so the buffer is
161                // materialised on the host once (through the storage's cache)
162                // and the view reads that.
163                let data = self.to_vec()?;
164                Arc::new(RwLock::new(data))
165            }
166        };
167
168        // Create view storage
169        let view_storage = ViewStorage {
170            parent: Arc::downgrade(&data_ref),
171            data_ref: Some(data_ref),
172            view_cache: HashMap::new(),
173            view_count: 1,
174        };
175
176        Ok(TensorView {
177            storage: Arc::new(RwLock::new(view_storage)),
178            shape: Shape::new(shape.to_vec()),
179            strides: strides.to_vec(),
180            offset,
181            device: self.device,
182        })
183    }
184
185    /// Create an alias (shared reference) to this tensor
186    pub fn alias(&self) -> TensorAlias<T> {
187        TensorAlias {
188            tensor: self.clone(),
189            is_mutable: false,
190        }
191    }
192
193    /// Create a mutable alias to this tensor
194    pub fn alias_mut(&mut self) -> TensorAlias<T> {
195        TensorAlias {
196            tensor: self.clone(),
197            is_mutable: true,
198        }
199    }
200}
201
202impl<T: TensorElement + Copy> TensorView<T> {
203    /// Get the shape of this view
204    pub fn shape(&self) -> &Shape {
205        &self.shape
206    }
207
208    /// Get the strides of this view
209    pub fn strides(&self) -> &[usize] {
210        &self.strides
211    }
212
213    /// Get the offset of this view
214    pub fn offset(&self) -> usize {
215        self.offset
216    }
217
218    /// Convert view to a contiguous tensor
219    pub fn to_tensor(&self) -> Result<Tensor<T>> {
220        let data = self.to_vec()?;
221        Tensor::from_data(data, self.shape.dims().to_vec(), self.device)
222    }
223
224    /// Get data as vector (materializes the view)
225    pub fn to_vec(&self) -> Result<Vec<T>> {
226        let storage = self.storage.read_or_recover();
227        if let Some(data_ref) = &storage.data_ref {
228            let data = data_ref.read_or_recover();
229            let mut result = Vec::with_capacity(self.shape.numel());
230
231            // Extract data according to view's shape, strides, and offset
232            self.extract_view_data(&data, &mut result, &mut vec![0; self.shape.ndim()], 0)?;
233
234            Ok(result)
235        } else {
236            Err(TorshError::InvalidOperation(
237                "View data no longer available".to_string(),
238            ))
239        }
240    }
241
242    /// Recursively extract data for the view
243    fn extract_view_data(
244        &self,
245        data: &[T],
246        result: &mut Vec<T>,
247        indices: &mut [usize],
248        dim: usize,
249    ) -> Result<()> {
250        if dim == self.shape.ndim() {
251            // Calculate flat index from view indices
252            let flat_index = self.offset
253                + indices
254                    .iter()
255                    .zip(self.strides.iter())
256                    .map(|(&idx, &stride)| idx * stride)
257                    .sum::<usize>();
258
259            if flat_index < data.len() {
260                result.push(data[flat_index]);
261            } else {
262                return Err(TorshError::InvalidOperation(
263                    "View index out of bounds".to_string(),
264                ));
265            }
266        } else {
267            for i in 0..self.shape.dims()[dim] {
268                indices[dim] = i;
269                self.extract_view_data(data, result, indices, dim + 1)?;
270            }
271        }
272        Ok(())
273    }
274
275    /// Check if this view is contiguous in memory
276    pub fn is_contiguous(&self) -> bool {
277        // A view is contiguous if its strides match row-major layout
278        let dims = self.shape.dims();
279        let mut expected_strides = vec![1; dims.len()];
280        for i in (0..dims.len().saturating_sub(1)).rev() {
281            expected_strides[i] = expected_strides[i + 1] * dims[i + 1];
282        }
283        self.strides == expected_strides
284    }
285
286    /// Check if this is a view (always true for TensorView)
287    pub fn is_view(&self) -> bool {
288        true
289    }
290
291    /// Get element at specific indices
292    pub fn get(&self, indices: &[usize]) -> Result<T> {
293        if indices.len() != self.shape.ndim() {
294            return Err(TorshError::InvalidOperation(format!(
295                "Expected {} indices, got {}",
296                self.shape.ndim(),
297                indices.len()
298            )));
299        }
300
301        for (i, &idx) in indices.iter().enumerate() {
302            if idx >= self.shape.dims()[i] {
303                return Err(TorshError::InvalidOperation(format!(
304                    "Index {} out of bounds for dimension {} (size {})",
305                    idx,
306                    i,
307                    self.shape.dims()[i]
308                )));
309            }
310        }
311
312        let storage = self.storage.read_or_recover();
313        if let Some(data_ref) = &storage.data_ref {
314            let data = data_ref.read_or_recover();
315
316            // Calculate flat index from view indices
317            let flat_index = self.offset
318                + indices
319                    .iter()
320                    .zip(self.strides.iter())
321                    .map(|(&idx, &stride)| idx * stride)
322                    .sum::<usize>();
323
324            if flat_index < data.len() {
325                Ok(data[flat_index])
326            } else {
327                Err(TorshError::InvalidOperation(
328                    "View index out of bounds".to_string(),
329                ))
330            }
331        } else {
332            Err(TorshError::InvalidOperation(
333                "View data no longer available".to_string(),
334            ))
335        }
336    }
337
338    /// Get memory usage of this view
339    pub fn view_memory_usage(&self) -> ViewMemoryUsage {
340        let storage = self.storage.read_or_recover();
341        ViewMemoryUsage {
342            view_elements: self.shape.numel(),
343            total_elements: storage
344                .data_ref
345                .as_ref()
346                .map(|data| data.read_or_recover().len())
347                .unwrap_or(0),
348            active_views: storage.view_count,
349            is_contiguous: self.is_contiguous(),
350            memory_efficiency: self.calculate_memory_efficiency(),
351        }
352    }
353
354    /// Calculate memory efficiency of this view
355    fn calculate_memory_efficiency(&self) -> f64 {
356        let view_size = self.shape.numel();
357        let storage = self.storage.read_or_recover();
358        let total_size = storage
359            .data_ref
360            .as_ref()
361            .map(|data| data.read_or_recover().len())
362            .unwrap_or(1);
363
364        view_size as f64 / total_size as f64
365    }
366}
367
368/// An alias to a tensor that shares memory
369#[derive(Debug, Clone)]
370pub struct TensorAlias<T: TensorElement> {
371    tensor: Tensor<T>,
372    is_mutable: bool,
373}
374
375impl<T: TensorElement + Copy> TensorAlias<T> {
376    /// Get reference to the underlying tensor
377    pub fn tensor(&self) -> &Tensor<T> {
378        &self.tensor
379    }
380
381    /// Check if this alias allows mutation
382    pub fn is_mutable(&self) -> bool {
383        self.is_mutable
384    }
385
386    /// Convert to owned tensor (creates copy if shared)
387    pub fn to_owned(&self) -> Result<Tensor<T>> {
388        Ok(self.tensor.clone())
389    }
390
391    /// Get the reference count of the underlying data
392    pub fn ref_count(&self) -> usize {
393        match &self.tensor.storage {
394            TensorStorage::InMemory(data) => Arc::strong_count(data),
395            TensorStorage::MemoryMapped(storage) => Arc::strong_count(storage),
396            #[cfg(feature = "simd")]
397            TensorStorage::Aligned(data) => Arc::strong_count(data),
398            #[cfg(feature = "simd")]
399            TensorStorage::SimdOptimized(storage) => Arc::strong_count(storage),
400            #[cfg(feature = "gpu")]
401            TensorStorage::Device { buffer, .. } => Arc::strong_count(buffer),
402        }
403    }
404
405    /// Check if this alias has exclusive access to the data
406    pub fn is_unique(&self) -> bool {
407        self.ref_count() == 1
408    }
409}
410
411/// Memory usage information for tensor views
412#[derive(Debug, Clone)]
413pub struct ViewMemoryUsage {
414    /// Number of elements in this view
415    pub view_elements: usize,
416    /// Total elements in underlying storage
417    pub total_elements: usize,
418    /// Number of active views on this storage
419    pub active_views: usize,
420    /// Whether the view is contiguous in memory
421    pub is_contiguous: bool,
422    /// Memory efficiency (view_size / total_size)
423    pub memory_efficiency: f64,
424}
425
426impl<T: TensorElement + Copy> Drop for ViewStorage<T> {
427    fn drop(&mut self) {
428        // Clean up view cache and decrement reference counts
429        self.view_cache.clear();
430        self.view_count = 0;
431    }
432}
433
434#[cfg(test)]
435mod tests {
436    use crate::creation::*;
437
438    #[test]
439    fn test_tensor_view() {
440        let tensor = ones::<f32>(&[2, 3, 4]).expect("ones creation should succeed");
441        let view = tensor
442            .create_view(&[6, 4])
443            .expect("create_view should succeed");
444        assert_eq!(view.shape().dims(), &[6, 4]);
445        assert_eq!(view.shape().numel(), 24);
446    }
447
448    #[test]
449    fn test_tensor_slice() {
450        let tensor = arange(0.0f32, 12.0, 1.0).expect("arange should succeed");
451        let _reshaped = tensor
452            .create_view(&[3, 4])
453            .expect("create_view should succeed");
454        // This would work in a full implementation
455        // let slice = reshaped.slice(0, 1, 3).unwrap();
456        // assert_eq!(slice.shape().dims(), &[2, 4]);
457    }
458
459    #[test]
460    fn test_tensor_squeeze_unsqueeze() {
461        let tensor = ones::<f32>(&[1, 3, 1, 4]).expect("ones creation should succeed");
462        let squeezed = tensor.squeeze(0).expect("squeeze should succeed");
463        assert_eq!(squeezed.shape().dims(), &[3, 1, 4]);
464
465        let squeezed_all = tensor.squeeze_all().expect("squeeze_all should succeed");
466        assert_eq!(squeezed_all.shape().dims(), &[3, 4]);
467
468        let unsqueezed = tensor.unsqueeze(2).expect("unsqueeze should succeed");
469        assert_eq!(unsqueezed.shape().dims(), &[1, 3, 1, 1, 4]);
470    }
471
472    #[test]
473    fn test_tensor_permute() {
474        let tensor = ones::<f32>(&[2, 3, 4]).expect("ones creation should succeed");
475        let permuted = tensor.permute(&[2, 0, 1]).expect("permute should succeed");
476        assert_eq!(permuted.shape().dims(), &[4, 2, 3]);
477    }
478
479    #[test]
480    fn test_tensor_alias() {
481        let tensor = ones::<f32>(&[10, 10]).expect("ones creation should succeed");
482        let alias = tensor.alias();
483        assert!(!alias.is_mutable());
484        assert!(alias.ref_count() >= 2); // Original + alias
485    }
486
487    #[test]
488    fn test_view_memory_usage() {
489        let tensor = ones::<f32>(&[100, 100]).expect("ones creation should succeed");
490        let view = tensor
491            .create_view(&[1000, 10])
492            .expect("create_view should succeed");
493        let usage = view.view_memory_usage();
494        assert_eq!(usage.view_elements, 10000);
495        assert_eq!(usage.memory_efficiency, 1.0); // Full tensor view
496    }
497}