1use cubecl::prelude::*;
4
5use core::fmt::{self, Display, Formatter};
6
7use cubecl::zspace::{SmallVec, Tiling};
8
9use crate::{Axis, Space, StoragePartitioning, TileMisfit};
10
11use crate::Coords;
12
13#[derive(Debug, Clone, PartialEq, Eq, Hash)]
15pub struct Geometry {
16 shape: Vec<usize>,
17 strides: Vec<usize>,
18 tiling: Tiling,
19}
20
21impl Geometry {
22 pub fn new(dims: &[(usize, usize)]) -> Self {
24 Self {
25 shape: dims.iter().map(|&(extent, _)| extent).collect(),
26 strides: dims.iter().map(|&(_, stride)| stride).collect(),
27 tiling: Tiling::UNTILED,
28 }
29 }
30
31 pub fn row_major(shape: &[usize]) -> Self {
34 let mut strides = vec![1; shape.len()];
35 for dim in (0..shape.len().saturating_sub(1)).rev() {
36 strides[dim] = strides[dim + 1] * shape[dim + 1];
37 }
38 Self {
39 shape: shape.to_vec(),
40 strides,
41 tiling: Tiling::UNTILED,
42 }
43 }
44
45 pub(crate) fn with_tiling(mut self, tiling: Tiling) -> Self {
47 self.tiling = tiling;
48 self
49 }
50
51 pub fn tiling(&self) -> Tiling {
53 self.tiling
54 }
55
56 pub fn labels(&self, axes: &[Axis]) -> Vec<Axis> {
65 if !self.tiling.is_tiled() {
66 return axes.to_vec();
67 }
68 let logical_rank = self
69 .tiling
70 .logical_rank(self.rank())
71 .unwrap_or_else(|e| panic!("Geometry::labels: {e:?}"));
72 assert!(
73 logical_rank >= axes.len(),
74 "Geometry::labels: the buffer stands for {logical_rank} dims but {} axes name them",
75 axes.len()
76 );
77 let fragments = self.tiling.fragments(logical_rank);
78 let (batches, block) = fragments.split_at(logical_rank - axes.len());
79 assert!(
80 batches.iter().all(|&n| n == 1),
81 "Geometry::labels: batch dims are stored one dim each, so {batches:?} pieces over \
82 them cannot be named"
83 );
84 StoragePartitioning::level_major(axes, block)
85 }
86
87 pub fn shape(&self) -> &[usize] {
89 &self.shape
90 }
91
92 pub fn strides(&self) -> &[usize] {
94 &self.strides
95 }
96
97 pub fn rank(&self) -> usize {
99 self.shape.len()
100 }
101
102 pub fn in_stride_order(&self, labels: &[Axis]) -> (Geometry, Vec<Axis>) {
105 let batch_dims = self.rank() - labels.len();
106 let mut trailing: Vec<(usize, (usize, usize))> =
107 self.dims().enumerate().skip(batch_dims).collect();
108 trailing.sort_by_key(|&(_, dim)| stride_order(dim));
109 let dims: Vec<(usize, usize)> = self
110 .dims()
111 .take(batch_dims)
112 .chain(trailing.iter().map(|&(_, dim)| dim))
113 .collect();
114 let ordered = trailing
115 .iter()
116 .map(|&(dim, _)| labels[dim - batch_dims])
117 .collect();
118 (Geometry::new(&dims), ordered)
119 }
120
121 pub fn dims(&self) -> impl Iterator<Item = (usize, usize)> + '_ {
122 self.shape.iter().copied().zip(self.strides.iter().copied())
123 }
124
125 pub fn serves(&self, tile: &[(Axis, usize)], labels: &[Axis]) -> Result<(), LineMisfit> {
128 let values: usize = tile.iter().map(|&(_, count)| count).product();
129 if values == 1 {
130 return Ok(());
131 }
132 if self.rank() == 0 {
133 return Err(LineMisfit::NoDims);
134 }
135 let storage = StoragePartitioning::new(self, labels).ok_or(LineMisfit::NotPartitioned)?;
136 storage
137 .holds(tile, &self.extents(labels))
138 .map_err(LineMisfit::Tile)?;
139 let mut by_stride: SmallVec<[(usize, (usize, usize)); 8]> = self
141 .dims()
142 .enumerate()
143 .filter(|&(_, (extent, stride))| extent > 1 && stride > 0)
144 .collect();
145 by_stride.sort_by_key(|&(dim, (_, stride))| (stride, core::cmp::Reverse(dim)));
146 let mut inside: SmallVec<[usize; 8]> = SmallVec::new();
147 let mut run = 1;
148 for &(dim, (extent, stride)) in &by_stride {
149 if run >= values {
150 break;
151 }
152 if stride != run {
153 return Err(match run {
154 1 => LineMisfit::InnermostStrided(stride),
155 _ => LineMisfit::Gap(stride),
156 });
157 }
158 inside.push(dim);
159 run *= extent;
160 }
161 match self.dims().enumerate().find(|&(dim, (extent, stride))| {
163 extent > 1 && !inside.contains(&dim) && !stride.is_multiple_of(values)
164 }) {
165 Some((_, (_, stride))) => Err(LineMisfit::StrideInsideLine(stride)),
166 None => Ok(()),
167 }
168 }
169
170 pub(crate) fn extents(&self, labels: &[Axis]) -> Vec<(Axis, usize)> {
172 let unlabelled = self.rank().saturating_sub(labels.len());
173 let mut extents: Vec<(Axis, usize)> = Vec::new();
174 for (dim, extent) in self.shape.iter().copied().enumerate().skip(unlabelled) {
175 let axis = labels[dim - unlabelled];
176 match extents.iter_mut().find(|(a, _)| *a == axis) {
177 Some((_, product)) => *product *= extent,
178 None => extents.push((axis, extent)),
179 }
180 }
181 extents
182 }
183
184 pub(crate) fn addressing_dims_by_stride(
187 &self,
188 ) -> SmallVec<[(usize, (usize, usize)); Space::MAX_RANK]> {
189 let mut dims: SmallVec<[(usize, (usize, usize)); Space::MAX_RANK]> = self
190 .dims()
191 .enumerate()
192 .filter(|&(_, (extent, stride))| extent > 1 && stride > 0)
193 .collect();
194 dims.sort_by_key(|&(_, (_, stride))| core::cmp::Reverse(stride));
195 dims
196 }
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq)]
201pub enum LineMisfit {
202 InnermostStrided(usize),
204 Gap(usize),
206 Tile(TileMisfit),
208 StrideInsideLine(usize),
210 NotPartitioned,
212 NoDims,
214}
215
216impl Display for LineMisfit {
217 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
218 match self {
219 Self::InnermostStrided(stride) => write!(
220 f,
221 "its finest dim steps by {stride} rather than 1, so consecutive values are not \
222 one run"
223 ),
224 Self::Gap(stride) => write!(
225 f,
226 "a dim inside the tile steps by {stride}, leaving a gap in its run"
227 ),
228 Self::Tile(why) => write!(f, "its storage does not hold the tile: {why}"),
229 Self::StrideInsideLine(stride) => write!(
230 f,
231 "its stride {stride} is not a whole number of tiles, so a coarser step lands \
232 inside one"
233 ),
234 Self::NotPartitioned => write!(
235 f,
236 "a stated piece sits coarser than the rest of an axis, so its order is no \
237 partitioning"
238 ),
239 Self::NoDims => write!(f, "it has no dims, so only a single value fits"),
240 }
241 }
242}
243
244impl From<&TensorBinding> for Geometry {
245 fn from(binding: &TensorBinding) -> Self {
246 Self {
247 shape: binding.shape.to_vec(),
248 strides: binding.strides.to_vec(),
249 tiling: binding.tiling,
250 }
251 }
252}
253
254#[derive(CubeType, Clone)]
256#[expand(derive(Clone))]
257pub struct RuntimeGeometry {
258 pub(crate) shape: Coords<u32>,
259 pub(crate) strides: Coords<u32>,
260}
261
262#[cube]
263impl RuntimeGeometry {
264 #[allow(clippy::new_without_default)]
267 pub fn new() -> RuntimeGeometry {
268 RuntimeGeometry {
269 shape: Coords::<u32>::new(),
270 strides: Coords::<u32>::new(),
271 }
272 }
273
274 pub fn of_tensor<E: CubePrimitive>(
276 tensor: &Tensor<E>,
277 #[comptime] rank: usize,
278 ) -> RuntimeGeometry {
279 let mut geometry = RuntimeGeometry::new();
280 #[unroll]
281 for i in 0..rank {
282 geometry.push(tensor.shape(i) as u32, tensor.stride(i) as u32);
283 }
284 geometry
285 }
286
287 pub fn push(&mut self, extent: u32, stride: u32) {
289 self.shape.push(extent);
290 self.strides.push(stride);
291 }
292}
293
294pub(crate) fn stride_order((extent, stride): (usize, usize)) -> (core::cmp::Reverse<usize>, bool) {
299 (core::cmp::Reverse(stride), extent != 1)
300}
301
302#[cfg(test)]
303mod tests {
304 use super::*;
305 use crate::StorageLevels;
306
307 const M: Axis = Axis(0);
308 const N: Axis = Axis(1);
309 const K: Axis = Axis(2);
310
311 #[test]
312 fn stride_order_reorders_the_dims_and_their_labels() {
313 let (dims, labels) = Geometry::new(&[(4096, 1), (6144, 4096)]).in_stride_order(&[K, N]);
315 assert_eq!(labels, vec![N, K]);
316 assert_eq!(dims, Geometry::new(&[(6144, 4096), (4096, 1)]));
317
318 let (dims, labels) = Geometry::new(&[(4096, 6144), (6144, 1)]).in_stride_order(&[K, N]);
320 assert_eq!(labels, vec![K, N]);
321 assert_eq!(dims, Geometry::new(&[(4096, 6144), (6144, 1)]));
322
323 let (dims, labels) =
325 Geometry::new(&[(8, 0), (4096, 1), (6144, 4096)]).in_stride_order(&[K, N]);
326 assert_eq!(labels, vec![N, K]);
327 assert_eq!(dims, Geometry::new(&[(8, 0), (6144, 4096), (4096, 1)]));
328 }
329
330 #[test]
332 fn a_line_is_served_where_the_line_rule_served_it() {
333 let geometries: Vec<(Geometry, Vec<Axis>)> = vec![
334 (Geometry::new(&[(64, 128), (128, 1)]), vec![M, N]),
335 (Geometry::new(&[(64, 136), (128, 1)]), vec![M, N]),
336 (Geometry::new(&[(64, 130), (130, 1)]), vec![M, N]),
337 (Geometry::new(&[(64, 1), (128, 64)]), vec![M, N]),
338 (Geometry::new(&[(3, 0), (64, 128), (128, 1)]), vec![M, N]),
339 (Geometry::new(&[(1, 5), (64, 128), (128, 1)]), vec![M, N]),
340 (Geometry::new(&[(2, 4096), (32, 1)]), vec![K, N]),
341 (
342 Geometry::new(&[(4, 2048), (4, 512), (16, 32), (32, 1)]),
343 vec![K, N, K, N],
344 ),
345 (Geometry::new(&[(6, 1)]), vec![N]),
346 (Geometry::new(&[(1, 1), (8, 1)]), vec![M, N]),
348 ];
349 for (geometry, labels) in &geometries {
350 let line = *labels.last().unwrap();
351 for width in [1, 2, 3, 4, 8, 16, 32] {
352 assert_eq!(
353 geometry.serves(&[(line, width)], labels).is_ok(),
354 line_rule_serves(geometry, width),
355 "{geometry:?} at {width}"
356 );
357 }
358 }
359 }
360
361 #[test]
362 fn a_tiled_buffer_serves_the_reads_it_was_stored_for() {
363 let storage = StorageLevels::new(&[(N, 4)])
364 .tile(&[(N, 8), (K, 32)])
365 .grid(&[N, K]);
366 let geometry = storage.physical(&[(K, 64), (N, 64)]).unwrap();
367 let labels = geometry.labels(&[K, N]);
368 assert_eq!(geometry.serves(&[(N, 4)], &labels), Ok(()));
369 assert_eq!(geometry.serves(&[(N, 32), (K, 32)], &labels), Ok(()));
370 assert!(geometry.serves(&[(N, 2)], &labels).is_err());
371 assert!(geometry.serves(&[(N, 8)], &labels).is_err());
372 }
373
374 fn line_rule_serves(geometry: &Geometry, width: usize) -> bool {
378 if width == 1 {
379 return true;
380 }
381 let Some(last) = geometry.rank().checked_sub(1) else {
382 return false;
383 };
384 geometry.strides()[last] == 1
385 && geometry.shape()[last].is_multiple_of(width)
386 && geometry.strides()[..last]
387 .iter()
388 .zip(geometry.shape())
389 .all(|(s, &extent)| extent == 1 || s.is_multiple_of(width))
390 }
391}