Skip to main content

torsh_data/collate/
stacking.rs

1//! Tensor stacking utilities
2
3use torsh_core::{
4    dtype::TensorElement,
5    error::{Result, TorshError},
6};
7use torsh_tensor::Tensor;
8
9#[cfg(not(feature = "std"))]
10use alloc::vec::Vec;
11
12// ✅ SciRS2 POLICY: Use scirs2_core::parallel_ops instead of rayon::prelude
13#[cfg(feature = "std")]
14use scirs2_core::parallel_ops::*;
15
16/// Consolidated tensor stacking utility that reduces code duplication
17pub struct TensorStacker {
18    use_parallel: bool,
19    parallel_threshold: usize,
20    memory_mapped: bool,
21}
22
23impl Default for TensorStacker {
24    fn default() -> Self {
25        Self {
26            use_parallel: cfg!(feature = "std"),
27            parallel_threshold: 1000,
28            memory_mapped: false,
29        }
30    }
31}
32
33impl TensorStacker {
34    /// Create a new tensor stacker with default settings
35    pub fn new() -> Self {
36        Self::default()
37    }
38
39    /// Enable/disable parallel processing
40    pub fn with_parallel(mut self, enabled: bool) -> Self {
41        self.use_parallel = enabled && cfg!(feature = "std");
42        self
43    }
44
45    /// Set threshold for parallel processing
46    pub fn parallel_threshold(mut self, threshold: usize) -> Self {
47        self.parallel_threshold = threshold;
48        self
49    }
50
51    /// Enable memory-mapped stacking for very large batches
52    pub fn with_memory_mapping(mut self, enabled: bool) -> Self {
53        self.memory_mapped = enabled && cfg!(all(feature = "std", feature = "mmap-support"));
54        self
55    }
56
57    /// Stack tensors along the specified dimension
58    pub fn stack<T: TensorElement + Copy>(
59        &self,
60        tensors: &[Tensor<T>],
61        dim: usize,
62    ) -> Result<Tensor<T>> {
63        if tensors.is_empty() {
64            return Err(TorshError::InvalidArgument(
65                "Cannot stack empty tensor list".to_string(),
66            ));
67        }
68
69        // Validate shapes
70        self.validate_shapes(tensors)?;
71
72        // Choose stacking strategy based on configuration and batch size
73        if self.memory_mapped && tensors.len() > 100 {
74            self.stack_with_mmap(tensors, dim)
75        } else if self.use_parallel && self.should_use_parallel(tensors) {
76            self.stack_parallel(tensors, dim)
77        } else {
78            self.stack_sequential(tensors, dim)
79        }
80    }
81
82    /// Check if all tensors have the same shape
83    fn validate_shapes<T: TensorElement>(&self, tensors: &[Tensor<T>]) -> Result<()> {
84        let first_shape = tensors[0].shape();
85        for tensor in tensors[1..].iter() {
86            if tensor.shape() != first_shape {
87                return Err(TorshError::ShapeMismatch {
88                    expected: first_shape.dims().to_vec(),
89                    got: tensor.shape().dims().to_vec(),
90                });
91            }
92        }
93        Ok(())
94    }
95
96    /// Determine if parallel processing should be used
97    fn should_use_parallel<T: TensorElement>(&self, tensors: &[Tensor<T>]) -> bool {
98        tensors.len() > 4 && tensors[0].numel() > self.parallel_threshold
99    }
100
101    /// Create new shape with additional dimension
102    fn create_new_shape(
103        &self,
104        original_dims: &[usize],
105        batch_size: usize,
106        dim: usize,
107    ) -> Vec<usize> {
108        let mut new_dims = Vec::with_capacity(original_dims.len() + 1);
109
110        if dim == 0 {
111            new_dims.push(batch_size);
112            new_dims.extend_from_slice(original_dims);
113        } else {
114            new_dims.extend_from_slice(&original_dims[..dim.min(original_dims.len())]);
115            new_dims.push(batch_size);
116            if dim < original_dims.len() {
117                new_dims.extend_from_slice(&original_dims[dim..]);
118            }
119        }
120
121        new_dims
122    }
123
124    /// Sequential tensor stacking
125    fn stack_sequential<T: TensorElement + Copy>(
126        &self,
127        tensors: &[Tensor<T>],
128        dim: usize,
129    ) -> Result<Tensor<T>> {
130        let new_dims = self.create_new_shape(tensors[0].shape().dims(), tensors.len(), dim);
131        let tensor_size = tensors[0].numel();
132        let total_elements = new_dims.iter().product::<usize>();
133        // Optimize: pre-allocate without unnecessary zero-initialization
134        let mut new_data = Vec::with_capacity(total_elements);
135        // SAFETY: We immediately fill all elements in the loop below
136        unsafe { new_data.set_len(total_elements) };
137
138        // Copy data sequentially
139        for (i, tensor) in tensors.iter().enumerate() {
140            let data = tensor.to_vec()?;
141            let start_idx = i * tensor_size;
142            let end_idx = start_idx + tensor_size;
143            new_data[start_idx..end_idx].copy_from_slice(&data);
144        }
145
146        torsh_tensor::Tensor::from_data(new_data, new_dims, tensors[0].device())
147    }
148
149    /// Parallel tensor stacking
150    #[cfg(feature = "std")]
151    fn stack_parallel<T: TensorElement + Copy>(
152        &self,
153        tensors: &[Tensor<T>],
154        dim: usize,
155    ) -> Result<Tensor<T>> {
156        let new_dims = self.create_new_shape(tensors[0].shape().dims(), tensors.len(), dim);
157        let tensor_size = tensors[0].numel();
158        let total_elements = new_dims.iter().product::<usize>();
159        // Optimize: pre-allocate without unnecessary zero-initialization
160        let mut new_data = Vec::with_capacity(total_elements);
161        // SAFETY: We immediately fill all elements after parallel collection
162        unsafe { new_data.set_len(total_elements) };
163
164        // Parallel data collection
165        let parallel_data: std::result::Result<Vec<Vec<T>>, TorshError> =
166            tensors.par_iter().map(|tensor| tensor.to_vec()).collect();
167        let parallel_data = parallel_data?;
168
169        for (i, data) in parallel_data.into_iter().enumerate() {
170            let start_idx = i * tensor_size;
171            let end_idx = start_idx + tensor_size;
172            new_data[start_idx..end_idx].copy_from_slice(&data);
173        }
174
175        torsh_tensor::Tensor::from_data(new_data, new_dims, tensors[0].device())
176    }
177
178    /// Fallback for no_std
179    #[cfg(not(feature = "std"))]
180    fn stack_parallel<T: TensorElement + Copy>(
181        &self,
182        tensors: &[Tensor<T>],
183        dim: usize,
184    ) -> Result<Tensor<T>> {
185        self.stack_sequential(tensors, dim)
186    }
187
188    /// Memory-mapped stacking for very large batches
189    #[cfg(all(feature = "std", feature = "mmap-support"))]
190    fn stack_with_mmap<T: TensorElement + Copy>(
191        &self,
192        tensors: &[Tensor<T>],
193        dim: usize,
194    ) -> Result<Tensor<T>> {
195        // Placeholder implementation - in practice would use memory mapping
196        // For now, fallback to parallel stacking
197        self.stack_parallel(tensors, dim)
198    }
199
200    /// Fallback when mmap is not available
201    #[cfg(not(all(feature = "std", feature = "mmap-support")))]
202    fn stack_with_mmap<T: TensorElement + Copy>(
203        &self,
204        tensors: &[Tensor<T>],
205        dim: usize,
206    ) -> Result<Tensor<T>> {
207        if cfg!(feature = "std") {
208            self.stack_parallel(tensors, dim)
209        } else {
210            self.stack_sequential(tensors, dim)
211        }
212    }
213}