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 contiguous_strides(shape: &[usize]) -> Strides {
49 let mut strides = strides![0; shape.len()];
50 let mut current = 1;
51
52 for (i, &dim) in shape.iter().enumerate().rev() {
53 strides[i] = current;
54 current *= dim;
55 }
56
57 strides
58}
59
60#[derive(Debug)]
62pub enum ReshapeAction {
63 UpdateStrides {
65 strides: Strides,
67 },
68 Recompute,
70 NoChange,
72}
73
74#[derive(Debug, PartialEq)]
76pub enum ReshapeAnalysis {
77 IsContiguous,
79 HighlyPermuted,
81 Broadcasted,
83 Split,
85 SmallerRank,
87 NoChange,
89}
90
91impl ReshapeAnalysis {
92 pub fn action(&self, shape: &[usize], strides: &[usize], shape_new: &[usize]) -> ReshapeAction {
94 match self {
95 ReshapeAnalysis::IsContiguous => ReshapeAction::UpdateStrides {
96 strides: contiguous_strides(shape_new),
97 },
98 ReshapeAnalysis::NoChange => ReshapeAction::NoChange,
99 ReshapeAnalysis::HighlyPermuted | ReshapeAnalysis::SmallerRank => {
100 ReshapeAction::Recompute
101 }
102 ReshapeAnalysis::Broadcasted => {
103 let shape_rank = shape.len();
104 let shape_new_rank = shape_new.len();
105 let n_new_batch = shape_new_rank - shape_rank;
106 let num_elems = shape.iter().product::<usize>();
107 let strides_new = broadcast_strides(n_new_batch, shape_rank, num_elems, strides);
108
109 ReshapeAction::UpdateStrides {
110 strides: strides_new,
111 }
112 }
113 ReshapeAnalysis::Split => {
114 let strides_new = split_strides(shape, strides, shape_new);
115
116 ReshapeAction::UpdateStrides {
117 strides: strides_new,
118 }
119 }
120 }
121 }
122}
123
124pub fn reshape_action(shape: &Shape, strides: &Strides, shape_new: &Shape) -> ReshapeAction {
126 reshape_analysis(shape, Some(strides), shape_new).action(shape, strides, shape_new)
127}
128
129pub fn broadcast_strides(
131 n_new_batch: usize,
132 rank_prev: usize,
133 num_elems: usize,
134 strides: &[usize],
135) -> Strides {
136 let mut strides_new = strides![num_elems; rank_prev + n_new_batch];
137
138 for (i, s) in strides.iter().enumerate() {
139 strides_new[i + n_new_batch] = *s;
140 }
141
142 strides_new
143}
144
145pub fn split_strides(shape: &[usize], strides: &[usize], shape_new: &[usize]) -> Strides {
147 let mut strides_new = strides![1; shape_new.len()];
148
149 let skip_unit_dims = |mut idx: usize| {
156 while idx > 0 && shape[idx] == 1 {
157 idx -= 1;
158 }
159 idx
160 };
161
162 let mut old_idx = skip_unit_dims(shape.len() - 1);
163 let mut current_stride = strides[old_idx];
164 let mut dim_prod = 1;
165
166 for (i, dim) in shape_new.iter().enumerate().rev() {
167 dim_prod *= *dim;
168 strides_new[i] = current_stride;
169 if *dim == 1 {
170 continue;
171 } else if dim_prod == shape[old_idx] {
172 old_idx = skip_unit_dims(old_idx.saturating_sub(1));
173 current_stride = strides[old_idx];
174 dim_prod = 1;
175 } else {
176 current_stride *= *dim;
177 }
178 }
179
180 strides_new
181}
182
183pub fn reshape_analysis(
185 shape: &Shape,
186 strides: Option<&Strides>,
187 shape_new: &Shape,
188) -> ReshapeAnalysis {
189 let shape_rank = shape.len();
190 let shape_new_rank = shape_new.len();
191
192 let is_contiguous = match strides {
193 Some(strides) => is_contiguous(shape, strides),
194 None => false,
195 };
196
197 if is_contiguous {
198 return ReshapeAnalysis::IsContiguous;
199 }
200
201 if shape_new_rank < shape_rank {
202 return ReshapeAnalysis::SmallerRank;
203 }
204
205 let n_new_batch = shape_new_rank - shape_rank;
206
207 match n_new_batch > 0 {
208 true => {
209 if shape.as_ref() == &shape_new[n_new_batch..shape_new_rank]
210 && shape_new[0..n_new_batch].iter().all(|it| *it == 1)
211 {
212 return ReshapeAnalysis::Broadcasted;
213 } else {
214 let mut dim_prod = 1;
215 let mut old_idx = 0;
216 for dim in shape_new.iter() {
217 dim_prod *= *dim;
218
219 if *dim == 1 {
223 continue;
224 } else if dim_prod == shape[old_idx] {
225 dim_prod = 1;
226 old_idx += 1;
227 } else if dim_prod > shape[old_idx] {
228 return ReshapeAnalysis::HighlyPermuted;
229 }
230 }
231 return ReshapeAnalysis::Split;
232 }
233 }
234
235 false => {
236 if shape == shape_new {
237 return ReshapeAnalysis::NoChange;
238 }
239 }
240 };
241
242 ReshapeAnalysis::HighlyPermuted
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248
249 #[test]
250 fn test_reshape_analysis_is_contiguous() {
251 let analysis = reshape_analysis(
252 &[32, 1, 1, 1].into(),
253 Some(&[1, 1, 1, 1].into()),
254 &[1, 1, 32, 1, 1, 1].into(),
255 );
256
257 assert_eq!(analysis, ReshapeAnalysis::IsContiguous)
258 }
259
260 #[test]
261 fn test_reshape_analysis_is_contiguous_2() {
262 let analysis = reshape_analysis(
263 &[32, 1, 1, 8].into(),
264 Some(&[8, 8, 8, 1].into()),
265 &[1, 1, 32, 1, 1, 8].into(),
266 );
267
268 assert_eq!(analysis, ReshapeAnalysis::IsContiguous)
269 }
270
271 #[test]
272 fn test_reshape_analysis_broadcasted_batch() {
273 let analysis = reshape_analysis(
274 &[32, 1, 1, 1].into(),
275 Some(&[1, 32, 32, 32].into()),
276 &[1, 1, 32, 1, 1, 1].into(),
277 );
278
279 assert_eq!(analysis, ReshapeAnalysis::Broadcasted)
280 }
281
282 #[test]
283 fn test_reshape_analysis_unsqueeze_split() {
284 let analysis = reshape_analysis(
286 &[32, 1, 1, 1].into(),
287 Some(&[1, 32, 32, 32].into()),
288 &[32, 1, 1, 1, 1].into(),
289 );
290
291 assert_eq!(analysis, ReshapeAnalysis::Split)
292 }
293
294 #[test]
295 fn test_reshape_analysis_split() {
296 let analysis = reshape_analysis(
297 &[32, 1, 1, 1].into(),
298 Some(&[1, 32, 32, 32].into()),
299 &[4, 8, 1, 1, 1].into(),
300 );
301
302 assert_eq!(analysis, ReshapeAnalysis::Split)
303 }
304
305 #[test]
306 fn test_split_strides_trailing_unit_dim_broadcast_view() {
307 let strides = split_strides(&[26, 1], &[1, 0], &[26, 1, 1]);
312 assert_eq!(strides.as_ref(), &[1, 1, 1]);
313 }
314
315 #[test]
316 fn test_split_strides_trailing_unit_dims_arbitrary_strides() {
317 let strides = split_strides(&[32, 1, 1, 1], &[1, 32, 32, 32], &[32, 1, 1, 1, 1]);
320 assert_eq!(strides.as_ref(), &[1, 1, 1, 1, 1]);
321 }
322
323 #[test]
324 fn test_split_strides_split_of_broadcast_dim_keeps_zero() {
325 let strides = split_strides(&[26, 16], &[1, 0], &[26, 4, 4]);
328 assert_eq!(strides.as_ref(), &[1, 0, 0]);
329 }
330
331 #[test]
332 fn test_split_strides_plain_unsqueeze() {
333 let strides = split_strides(&[26, 16], &[16, 1], &[26, 16, 1]);
334 assert_eq!(strides.as_ref(), &[16, 1, 1]);
335 }
336}