1pub mod container;
3pub mod dtype;
5pub mod layout;
7pub mod matmul;
9pub mod quantization;
11pub mod shape;
13pub mod slice;
15
16pub use dtype::*;
17pub use layout::*;
18pub use matmul::*;
19pub use quantization::*;
20pub use shape::*;
21pub use slice::*;
22
23pub use cubecl_zspace::indexing::{self, *};
24pub use cubecl_zspace::{Strides, metadata::Metadata, strides};
25
26pub fn is_contiguous(shape: &[usize], strides: &[usize]) -> bool {
37 if shape.is_empty() {
38 return true;
39 }
40
41 for ((&expected, &stride), &dim) in contiguous_strides(shape)
46 .iter()
47 .zip(strides)
48 .zip(shape.iter())
49 {
50 if dim != 1 && expected != stride {
51 return false;
52 }
53 }
54
55 true
56}
57
58pub fn is_dense(shape: &[usize], strides: &[usize]) -> bool {
64 if shape.len() != strides.len() {
65 return false;
66 }
67
68 let mut dims: SmallVec<[(usize, usize); 5]> = shape
69 .iter()
70 .zip(strides)
71 .filter(|&(&dim, _)| dim > 1)
72 .map(|(&dim, &stride)| (dim, stride))
73 .collect();
74
75 dims.sort_unstable_by_key(|&(_, stride)| stride);
76
77 let mut expected = 1;
78
79 for (dim, stride) in dims {
80 if stride != expected {
81 return false;
82 }
83
84 expected *= dim;
85 }
86
87 true
88}
89
90pub fn contiguous_strides(shape: &[usize]) -> Strides {
95 let mut strides = strides![0; shape.len()];
96 let mut current = 1;
97
98 for (i, &dim) in shape.iter().enumerate().rev() {
99 strides[i] = current;
100 current *= dim;
101 }
102
103 strides
104}
105
106#[derive(Debug)]
108pub enum ReshapeAction {
109 UpdateStrides {
111 strides: Strides,
113 },
114 Recompute,
116 NoChange,
118}
119
120#[derive(Debug, PartialEq)]
122pub enum ReshapeAnalysis {
123 IsContiguous,
125 HighlyPermuted,
127 Broadcasted,
129 Split,
131 SmallerRank,
133 NoChange,
135}
136
137impl ReshapeAnalysis {
138 pub fn action(&self, shape: &[usize], strides: &[usize], shape_new: &[usize]) -> ReshapeAction {
140 match self {
141 ReshapeAnalysis::IsContiguous => ReshapeAction::UpdateStrides {
142 strides: contiguous_strides(shape_new),
143 },
144 ReshapeAnalysis::NoChange => ReshapeAction::NoChange,
145 ReshapeAnalysis::HighlyPermuted | ReshapeAnalysis::SmallerRank => {
146 ReshapeAction::Recompute
147 }
148 ReshapeAnalysis::Broadcasted => {
149 let shape_rank = shape.len();
150 let shape_new_rank = shape_new.len();
151 let n_new_batch = shape_new_rank - shape_rank;
152 let num_elems = shape.iter().product::<usize>();
153 let strides_new = broadcast_strides(n_new_batch, shape_rank, num_elems, strides);
154
155 ReshapeAction::UpdateStrides {
156 strides: strides_new,
157 }
158 }
159 ReshapeAnalysis::Split => {
160 let strides_new = split_strides(shape, strides, shape_new);
161
162 ReshapeAction::UpdateStrides {
163 strides: strides_new,
164 }
165 }
166 }
167 }
168}
169
170pub fn reshape_action(shape: &Shape, strides: &Strides, shape_new: &Shape) -> ReshapeAction {
172 reshape_analysis(shape, Some(strides), shape_new).action(shape, strides, shape_new)
173}
174
175pub fn broadcast_strides(
177 n_new_batch: usize,
178 rank_prev: usize,
179 num_elems: usize,
180 strides: &[usize],
181) -> Strides {
182 let mut strides_new = strides![num_elems; rank_prev + n_new_batch];
183
184 for (i, s) in strides.iter().enumerate() {
185 strides_new[i + n_new_batch] = *s;
186 }
187
188 strides_new
189}
190
191pub fn split_strides(shape: &[usize], strides: &[usize], shape_new: &[usize]) -> Strides {
193 let mut strides_new = strides![1; shape_new.len()];
194
195 let skip_unit_dims = |mut idx: usize| {
202 while idx > 0 && shape[idx] == 1 {
203 idx -= 1;
204 }
205 idx
206 };
207
208 let mut old_idx = skip_unit_dims(shape.len() - 1);
209 let mut current_stride = strides[old_idx];
210 let mut dim_prod = 1;
211
212 for (i, dim) in shape_new.iter().enumerate().rev() {
213 dim_prod *= *dim;
214 strides_new[i] = current_stride;
215 if *dim == 1 {
216 continue;
217 } else if dim_prod == shape[old_idx] {
218 old_idx = skip_unit_dims(old_idx.saturating_sub(1));
219 current_stride = strides[old_idx];
220 dim_prod = 1;
221 } else {
222 current_stride *= *dim;
223 }
224 }
225
226 strides_new
227}
228
229pub fn reshape_analysis(
231 shape: &Shape,
232 strides: Option<&Strides>,
233 shape_new: &Shape,
234) -> ReshapeAnalysis {
235 let shape_rank = shape.len();
236 let shape_new_rank = shape_new.len();
237
238 let is_contiguous = match strides {
239 Some(strides) => is_contiguous(shape, strides),
240 None => false,
241 };
242
243 if is_contiguous {
244 return ReshapeAnalysis::IsContiguous;
245 }
246
247 if shape_new_rank < shape_rank {
248 return ReshapeAnalysis::SmallerRank;
249 }
250
251 let n_new_batch = shape_new_rank - shape_rank;
252
253 match n_new_batch > 0 {
254 true => {
255 if shape.as_ref() == &shape_new[n_new_batch..shape_new_rank]
256 && shape_new[0..n_new_batch].iter().all(|it| *it == 1)
257 {
258 return ReshapeAnalysis::Broadcasted;
259 } else {
260 let mut dim_prod = 1;
261 let mut old_idx = 0;
262 for dim in shape_new.iter() {
263 dim_prod *= *dim;
264
265 if *dim == 1 {
269 continue;
270 } else if dim_prod == shape[old_idx] {
271 dim_prod = 1;
272 old_idx += 1;
273 } else if dim_prod > shape[old_idx] {
274 return ReshapeAnalysis::HighlyPermuted;
275 }
276 }
277 return ReshapeAnalysis::Split;
278 }
279 }
280
281 false => {
282 if shape == shape_new {
283 return ReshapeAnalysis::NoChange;
284 }
285 }
286 };
287
288 ReshapeAnalysis::HighlyPermuted
289}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294
295 #[test]
296 fn test_reshape_analysis_is_contiguous() {
297 let analysis = reshape_analysis(
298 &[32, 1, 1, 1].into(),
299 Some(&[1, 1, 1, 1].into()),
300 &[1, 1, 32, 1, 1, 1].into(),
301 );
302
303 assert_eq!(analysis, ReshapeAnalysis::IsContiguous)
304 }
305
306 #[test]
307 fn test_reshape_analysis_is_contiguous_2() {
308 let analysis = reshape_analysis(
309 &[32, 1, 1, 8].into(),
310 Some(&[8, 8, 8, 1].into()),
311 &[1, 1, 32, 1, 1, 8].into(),
312 );
313
314 assert_eq!(analysis, ReshapeAnalysis::IsContiguous)
315 }
316
317 #[test]
322 fn test_reshape_analysis_unit_axes_are_contiguous() {
323 for shape_new in [
324 [1, 1, 32, 1, 1, 1].as_slice(),
325 [32, 1, 1, 1, 1].as_slice(),
326 [4, 8, 1, 1, 1].as_slice(),
327 ] {
328 let analysis = reshape_analysis(
329 &[32, 1, 1, 1].into(),
330 Some(&[1, 32, 32, 32].into()),
331 &shape_new.into(),
332 );
333
334 assert_eq!(analysis, ReshapeAnalysis::IsContiguous, "{shape_new:?}")
335 }
336 }
337
338 #[test]
339 fn test_reshape_analysis_broadcasted_batch() {
340 let analysis =
341 reshape_analysis(&[32, 32].into(), Some(&[1, 32].into()), &[1, 32, 32].into());
342
343 assert_eq!(analysis, ReshapeAnalysis::Broadcasted)
344 }
345
346 #[test]
347 fn test_reshape_analysis_unsqueeze_split() {
348 let analysis =
350 reshape_analysis(&[32, 32].into(), Some(&[1, 32].into()), &[32, 32, 1].into());
351
352 assert_eq!(analysis, ReshapeAnalysis::Split)
353 }
354
355 #[test]
356 fn test_reshape_analysis_split() {
357 let analysis =
358 reshape_analysis(&[32, 32].into(), Some(&[1, 32].into()), &[4, 8, 32].into());
359
360 assert_eq!(analysis, ReshapeAnalysis::Split)
361 }
362
363 #[test]
364 fn test_split_strides_trailing_unit_dim_broadcast_view() {
365 let strides = split_strides(&[26, 1], &[1, 0], &[26, 1, 1]);
370 assert_eq!(strides.as_ref(), &[1, 1, 1]);
371 }
372
373 #[test]
374 fn test_split_strides_trailing_unit_dims_arbitrary_strides() {
375 let strides = split_strides(&[32, 1, 1, 1], &[1, 32, 32, 32], &[32, 1, 1, 1, 1]);
378 assert_eq!(strides.as_ref(), &[1, 1, 1, 1, 1]);
379 }
380
381 #[test]
382 fn test_split_strides_split_of_broadcast_dim_keeps_zero() {
383 let strides = split_strides(&[26, 16], &[1, 0], &[26, 4, 4]);
386 assert_eq!(strides.as_ref(), &[1, 0, 0]);
387 }
388
389 #[test]
390 fn test_is_dense_contiguous() {
391 assert!(is_dense(&[2, 2, 2, 2], &[8, 4, 2, 1]));
392 }
393
394 #[test]
395 fn test_is_dense_permuted() {
396 assert!(is_dense(&[2, 2, 2, 2], &[8, 1, 4, 2]));
397 }
398
399 #[test]
400 fn test_is_dense_pitched_row() {
401 assert!(!is_dense(&[2, 2, 2, 2], &[16, 8, 4, 1]));
402 assert!(!is_dense(&[1, 8, 6, 6], &[384, 48, 8, 1]));
403 }
404
405 #[test]
406 fn test_is_dense_unit_dims_carry_no_layout() {
407 assert!(is_dense(&[1, 4, 1], &[0, 1, 7]));
409 }
410
411 #[test]
412 fn test_is_dense_rank_mismatch() {
413 assert!(!is_dense(&[2, 3], &[1]));
414 }
415
416 #[test]
417 fn test_split_strides_plain_unsqueeze() {
418 let strides = split_strides(&[26, 16], &[16, 1], &[26, 16, 1]);
419 assert_eq!(strides.as_ref(), &[16, 1, 1]);
420 }
421}