1pub mod container;
3pub mod dtype;
5pub mod matmul;
7pub mod quantization;
9pub mod shape;
11pub mod slice;
13
14pub use dtype::*;
15pub use matmul::*;
16pub use quantization::*;
17pub use shape::*;
18pub use slice::*;
19
20pub use cubecl_zspace::indexing::{self, *};
21pub use cubecl_zspace::{Strides, metadata::Metadata, strides};
22
23pub fn is_contiguous(shape: &[usize], strides: &[usize]) -> bool {
31 if shape.is_empty() {
32 return true;
33 }
34
35 for (&expected, &stride) in contiguous_strides(shape).iter().zip(strides) {
36 if expected != stride {
37 return false;
38 }
39 }
40
41 true
42}
43
44pub fn is_dense(shape: &[usize], strides: &[usize]) -> bool {
50 if shape.len() != strides.len() {
51 return false;
52 }
53
54 let mut dims: SmallVec<[(usize, usize); 5]> = shape
55 .iter()
56 .zip(strides)
57 .filter(|&(&dim, _)| dim > 1)
58 .map(|(&dim, &stride)| (dim, stride))
59 .collect();
60
61 dims.sort_unstable_by_key(|&(_, stride)| stride);
62
63 let mut expected = 1;
64
65 for (dim, stride) in dims {
66 if stride != expected {
67 return false;
68 }
69
70 expected *= dim;
71 }
72
73 true
74}
75
76pub fn contiguous_strides(shape: &[usize]) -> Strides {
81 let mut strides = strides![0; shape.len()];
82 let mut current = 1;
83
84 for (i, &dim) in shape.iter().enumerate().rev() {
85 strides[i] = current;
86 current *= dim;
87 }
88
89 strides
90}
91
92#[derive(Debug)]
94pub enum ReshapeAction {
95 UpdateStrides {
97 strides: Strides,
99 },
100 Recompute,
102 NoChange,
104}
105
106#[derive(Debug, PartialEq)]
108pub enum ReshapeAnalysis {
109 IsContiguous,
111 HighlyPermuted,
113 Broadcasted,
115 Split,
117 SmallerRank,
119 NoChange,
121}
122
123impl ReshapeAnalysis {
124 pub fn action(&self, shape: &[usize], strides: &[usize], shape_new: &[usize]) -> ReshapeAction {
126 match self {
127 ReshapeAnalysis::IsContiguous => ReshapeAction::UpdateStrides {
128 strides: contiguous_strides(shape_new),
129 },
130 ReshapeAnalysis::NoChange => ReshapeAction::NoChange,
131 ReshapeAnalysis::HighlyPermuted | ReshapeAnalysis::SmallerRank => {
132 ReshapeAction::Recompute
133 }
134 ReshapeAnalysis::Broadcasted => {
135 let shape_rank = shape.len();
136 let shape_new_rank = shape_new.len();
137 let n_new_batch = shape_new_rank - shape_rank;
138 let num_elems = shape.iter().product::<usize>();
139 let strides_new = broadcast_strides(n_new_batch, shape_rank, num_elems, strides);
140
141 ReshapeAction::UpdateStrides {
142 strides: strides_new,
143 }
144 }
145 ReshapeAnalysis::Split => {
146 let strides_new = split_strides(shape, strides, shape_new);
147
148 ReshapeAction::UpdateStrides {
149 strides: strides_new,
150 }
151 }
152 }
153 }
154}
155
156pub fn reshape_action(shape: &Shape, strides: &Strides, shape_new: &Shape) -> ReshapeAction {
158 reshape_analysis(shape, Some(strides), shape_new).action(shape, strides, shape_new)
159}
160
161pub fn broadcast_strides(
163 n_new_batch: usize,
164 rank_prev: usize,
165 num_elems: usize,
166 strides: &[usize],
167) -> Strides {
168 let mut strides_new = strides![num_elems; rank_prev + n_new_batch];
169
170 for (i, s) in strides.iter().enumerate() {
171 strides_new[i + n_new_batch] = *s;
172 }
173
174 strides_new
175}
176
177pub fn split_strides(shape: &[usize], strides: &[usize], shape_new: &[usize]) -> Strides {
179 let mut strides_new = strides![1; shape_new.len()];
180
181 let skip_unit_dims = |mut idx: usize| {
188 while idx > 0 && shape[idx] == 1 {
189 idx -= 1;
190 }
191 idx
192 };
193
194 let mut old_idx = skip_unit_dims(shape.len() - 1);
195 let mut current_stride = strides[old_idx];
196 let mut dim_prod = 1;
197
198 for (i, dim) in shape_new.iter().enumerate().rev() {
199 dim_prod *= *dim;
200 strides_new[i] = current_stride;
201 if *dim == 1 {
202 continue;
203 } else if dim_prod == shape[old_idx] {
204 old_idx = skip_unit_dims(old_idx.saturating_sub(1));
205 current_stride = strides[old_idx];
206 dim_prod = 1;
207 } else {
208 current_stride *= *dim;
209 }
210 }
211
212 strides_new
213}
214
215pub fn reshape_analysis(
217 shape: &Shape,
218 strides: Option<&Strides>,
219 shape_new: &Shape,
220) -> ReshapeAnalysis {
221 let shape_rank = shape.len();
222 let shape_new_rank = shape_new.len();
223
224 let is_contiguous = match strides {
225 Some(strides) => is_contiguous(shape, strides),
226 None => false,
227 };
228
229 if is_contiguous {
230 return ReshapeAnalysis::IsContiguous;
231 }
232
233 if shape_new_rank < shape_rank {
234 return ReshapeAnalysis::SmallerRank;
235 }
236
237 let n_new_batch = shape_new_rank - shape_rank;
238
239 match n_new_batch > 0 {
240 true => {
241 if shape.as_ref() == &shape_new[n_new_batch..shape_new_rank]
242 && shape_new[0..n_new_batch].iter().all(|it| *it == 1)
243 {
244 return ReshapeAnalysis::Broadcasted;
245 } else {
246 let mut dim_prod = 1;
247 let mut old_idx = 0;
248 for dim in shape_new.iter() {
249 dim_prod *= *dim;
250
251 if *dim == 1 {
255 continue;
256 } else if dim_prod == shape[old_idx] {
257 dim_prod = 1;
258 old_idx += 1;
259 } else if dim_prod > shape[old_idx] {
260 return ReshapeAnalysis::HighlyPermuted;
261 }
262 }
263 return ReshapeAnalysis::Split;
264 }
265 }
266
267 false => {
268 if shape == shape_new {
269 return ReshapeAnalysis::NoChange;
270 }
271 }
272 };
273
274 ReshapeAnalysis::HighlyPermuted
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280
281 #[test]
282 fn test_reshape_analysis_is_contiguous() {
283 let analysis = reshape_analysis(
284 &[32, 1, 1, 1].into(),
285 Some(&[1, 1, 1, 1].into()),
286 &[1, 1, 32, 1, 1, 1].into(),
287 );
288
289 assert_eq!(analysis, ReshapeAnalysis::IsContiguous)
290 }
291
292 #[test]
293 fn test_reshape_analysis_is_contiguous_2() {
294 let analysis = reshape_analysis(
295 &[32, 1, 1, 8].into(),
296 Some(&[8, 8, 8, 1].into()),
297 &[1, 1, 32, 1, 1, 8].into(),
298 );
299
300 assert_eq!(analysis, ReshapeAnalysis::IsContiguous)
301 }
302
303 #[test]
304 fn test_reshape_analysis_broadcasted_batch() {
305 let analysis = reshape_analysis(
306 &[32, 1, 1, 1].into(),
307 Some(&[1, 32, 32, 32].into()),
308 &[1, 1, 32, 1, 1, 1].into(),
309 );
310
311 assert_eq!(analysis, ReshapeAnalysis::Broadcasted)
312 }
313
314 #[test]
315 fn test_reshape_analysis_unsqueeze_split() {
316 let analysis = reshape_analysis(
318 &[32, 1, 1, 1].into(),
319 Some(&[1, 32, 32, 32].into()),
320 &[32, 1, 1, 1, 1].into(),
321 );
322
323 assert_eq!(analysis, ReshapeAnalysis::Split)
324 }
325
326 #[test]
327 fn test_reshape_analysis_split() {
328 let analysis = reshape_analysis(
329 &[32, 1, 1, 1].into(),
330 Some(&[1, 32, 32, 32].into()),
331 &[4, 8, 1, 1, 1].into(),
332 );
333
334 assert_eq!(analysis, ReshapeAnalysis::Split)
335 }
336
337 #[test]
338 fn test_split_strides_trailing_unit_dim_broadcast_view() {
339 let strides = split_strides(&[26, 1], &[1, 0], &[26, 1, 1]);
344 assert_eq!(strides.as_ref(), &[1, 1, 1]);
345 }
346
347 #[test]
348 fn test_split_strides_trailing_unit_dims_arbitrary_strides() {
349 let strides = split_strides(&[32, 1, 1, 1], &[1, 32, 32, 32], &[32, 1, 1, 1, 1]);
352 assert_eq!(strides.as_ref(), &[1, 1, 1, 1, 1]);
353 }
354
355 #[test]
356 fn test_split_strides_split_of_broadcast_dim_keeps_zero() {
357 let strides = split_strides(&[26, 16], &[1, 0], &[26, 4, 4]);
360 assert_eq!(strides.as_ref(), &[1, 0, 0]);
361 }
362
363 #[test]
364 fn test_is_dense_contiguous() {
365 assert!(is_dense(&[2, 2, 2, 2], &[8, 4, 2, 1]));
366 }
367
368 #[test]
369 fn test_is_dense_permuted() {
370 assert!(is_dense(&[2, 2, 2, 2], &[8, 1, 4, 2]));
371 }
372
373 #[test]
374 fn test_is_dense_pitched_row() {
375 assert!(!is_dense(&[2, 2, 2, 2], &[16, 8, 4, 1]));
376 assert!(!is_dense(&[1, 8, 6, 6], &[384, 48, 8, 1]));
377 }
378
379 #[test]
380 fn test_is_dense_unit_dims_carry_no_layout() {
381 assert!(is_dense(&[1, 4, 1], &[0, 1, 7]));
383 }
384
385 #[test]
386 fn test_is_dense_rank_mismatch() {
387 assert!(!is_dense(&[2, 3], &[1]));
388 }
389
390 #[test]
391 fn test_split_strides_plain_unsqueeze() {
392 let strides = split_strides(&[26, 16], &[16, 1], &[26, 16, 1]);
393 assert_eq!(strides.as_ref(), &[16, 1, 1]);
394 }
395}