torsh_data/collate/
stacking.rs1use 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#[cfg(feature = "std")]
14use scirs2_core::parallel_ops::*;
15
16pub 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 pub fn new() -> Self {
36 Self::default()
37 }
38
39 pub fn with_parallel(mut self, enabled: bool) -> Self {
41 self.use_parallel = enabled && cfg!(feature = "std");
42 self
43 }
44
45 pub fn parallel_threshold(mut self, threshold: usize) -> Self {
47 self.parallel_threshold = threshold;
48 self
49 }
50
51 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 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 self.validate_shapes(tensors)?;
71
72 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 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 fn should_use_parallel<T: TensorElement>(&self, tensors: &[Tensor<T>]) -> bool {
98 tensors.len() > 4 && tensors[0].numel() > self.parallel_threshold
99 }
100
101 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 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 let mut new_data = Vec::with_capacity(total_elements);
135 unsafe { new_data.set_len(total_elements) };
137
138 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 #[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 let mut new_data = Vec::with_capacity(total_elements);
161 unsafe { new_data.set_len(total_elements) };
163
164 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 #[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 #[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 self.stack_parallel(tensors, dim)
198 }
199
200 #[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}