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