1use cubecl::prelude::*;
10
11use cubecl_std::tensor::layout::CoordsDyn;
12
13use crate::{
14 Axis, Coords, Extent, Space, TileMisfit,
15 algebra::{Integer, IntegerExpand},
16 comptime_only,
17};
18
19#[derive(Clone, PartialEq, Eq, Hash, Debug)]
28pub struct VectorTile {
29 extents: Vec<(Axis, usize)>,
30}
31
32impl VectorTile {
33 pub fn new(
43 stored: &[(Axis, usize)],
44 innermost: Axis,
45 values: usize,
46 ) -> Result<Self, TileMisfit> {
47 if stored.is_empty() || values == 1 {
48 return Ok(Self::run(innermost, values));
49 }
50 let mut extents: Vec<(Axis, usize)> = Vec::new();
51 let mut held = 1;
52 for &(axis, count) in stored.iter().filter(|&&(_, count)| count > 1) {
53 if held == values {
54 break;
55 }
56 if !(values / held).is_multiple_of(count) {
57 return Err(TileMisfit::Overshoots {
58 axis,
59 wanted: values / held,
60 piece: count,
61 });
62 }
63 held *= count;
64 match extents.last().copied() {
65 Some((last, extent)) if last == axis => {
66 *extents.last_mut().expect("just read") = (axis, extent * count);
67 }
68 Some((last, _)) if extents.iter().any(|&(a, _)| a == axis) => {
69 return Err(TileMisfit::Interleaved {
70 wanted: axis,
71 found: last,
72 });
73 }
74 _ => extents.push((axis, count)),
75 }
76 }
77 if held != values {
78 return Err(TileMisfit::RunsOut {
79 wanted: extents.last().map_or(innermost, |&(axis, _)| axis),
80 });
81 }
82 Ok(Self { extents })
83 }
84
85 pub fn run(axis: Axis, values: usize) -> Self {
87 Self {
88 extents: vec![(axis, values)],
89 }
90 }
91
92 pub fn extents(&self) -> &[(Axis, usize)] {
94 &self.extents
95 }
96
97 pub(crate) fn run_length(&self) -> usize {
100 self.extents.first().map_or(1, |&(_, run)| run)
101 }
102
103 pub fn space(&self) -> Space {
105 Space::new(&self.extents)
106 }
107
108 pub fn values(&self) -> usize {
110 self.extents.iter().map(|&(_, extent)| extent).product()
111 }
112
113 pub(crate) fn along_one_axis(&self, reader: &str) -> (Axis, usize) {
120 match self.extents.as_slice() {
121 &[one] => one,
122 extents => panic!(
123 "{reader} places a load along one axis, and this operand loads {extents:?}: \
124 read it with a narrower vector, or through a path that places a tile"
125 ),
126 }
127 }
128
129 pub(crate) fn extent_along(&self, axis: Axis) -> usize {
131 self.extents
132 .iter()
133 .find(|&&(a, _)| a == axis)
134 .map_or(1, |&(_, extent)| extent)
135 }
136
137 pub(crate) fn step_along(&self, axis: Axis) -> usize {
140 let mut step = 1;
141 for &(a, extent) in &self.extents {
142 if a == axis {
143 return step;
144 }
145 step *= extent;
146 }
147 0
148 }
149
150 pub(crate) fn offset_in(&self, line: usize, space: &Space, axis: Axis) -> usize {
154 let mut rest = line;
155 for at in space.axes().collect::<Vec<_>>().into_iter().rev() {
156 let width = self.extent_along(at);
157 let count = space.extent(at).div_ceil(width);
158 if at == axis {
159 return (rest % count) * width;
160 }
161 rest /= count;
162 }
163 0
164 }
165
166 pub(crate) fn offset_along(&self, position: usize, axis: Axis) -> usize {
170 let mut rest = position;
171 for &(a, extent) in &self.extents {
172 if a == axis {
173 return rest % extent;
174 }
175 rest /= extent;
176 }
177 0
178 }
179
180 pub(crate) fn loads_in(&self, axis: Axis, edge: usize, extent: Extent) -> usize {
189 let w = self.extent_along(axis);
190 assert!(
191 edge.is_multiple_of(w) || matches!(extent, Extent::Static(x) if x == edge),
192 "Memory::at: the edge {edge} along {axis:?} is neither a whole number of loads {w} \
193 wide there nor the axis's whole extent ({extent:?}), so a step would start mid-load"
194 );
195 edge.div_ceil(w)
196 }
197
198 pub(crate) fn counts(&self, space: &Space) -> Vec<usize> {
201 space
202 .axes()
203 .map(|axis| space.extent(axis).div_ceil(self.extent_along(axis)))
204 .collect()
205 }
206
207 pub(crate) fn parts(
217 &self,
218 stored: &[(Axis, usize)],
219 labels: &[Axis],
220 rank: usize,
221 ) -> Vec<usize> {
222 let mut parts = vec![1; rank];
223 if stored.is_empty() || self.values() == 1 {
224 if let Some(last) = parts.last_mut() {
225 *last = self.values();
226 }
227 return parts;
228 }
229 let mut taken = 1;
230 for (&(_, count), dim) in stored.iter().zip(Self::stated_dims(stored, labels, rank)) {
231 if count == 1 {
232 continue;
233 }
234 if taken == self.values() {
235 break;
236 }
237 parts[dim] = count;
238 taken *= count;
239 }
240 parts
241 }
242
243 pub(crate) fn stated_extents(
248 stored: &[(Axis, usize)],
249 labels: &[Axis],
250 rank: usize,
251 ) -> Vec<Option<usize>> {
252 let mut extents = vec![None; rank];
253 for (&(_, count), dim) in stored.iter().zip(Self::stated_dims(stored, labels, rank)) {
254 extents[dim] = Some(count);
255 }
256 extents
257 }
258
259 fn stated_dims(stored: &[(Axis, usize)], labels: &[Axis], rank: usize) -> Vec<usize> {
262 let unlabelled = rank - labels.len();
263 let mut from_end: Vec<(Axis, usize)> = Vec::new();
264 stored
265 .iter()
266 .map(|&(axis, _)| {
267 let nth = match from_end.iter_mut().find(|(a, _)| *a == axis) {
268 Some((_, seen)) => {
269 *seen += 1;
270 *seen
271 }
272 None => {
273 from_end.push((axis, 1));
274 1
275 }
276 };
277 let dim = (0..labels.len())
278 .rev()
279 .filter(|&d| labels[d] == axis)
280 .nth(nth - 1)
281 .expect("a stated tile is a dim of the buffer");
282 unlabelled + dim
283 })
284 .collect()
285 }
286}
287
288#[cube]
289impl VectorTile {
290 fn count_of(#[comptime] space: &Space, #[comptime] tile: &VectorTile) -> u32 {
291 comptime!(tile.counts(space).iter().product::<usize>() as u32).runtime()
292 }
293
294 fn start_of(
295 line: u32,
296 #[comptime] space: &Space,
297 #[comptime] tile: &VectorTile,
298 ) -> Coords<u32> {
299 let rank = comptime!(space.rank());
300 let place = Coords::constant(comptime!(tile.counts(space))).unravel(line);
301 let mut coords = Coords::<u32>::new();
302 #[unroll]
303 for p in 0..rank {
304 let extent = comptime!(tile.extent_along(space.axis_at(p)) as u32);
305 coords.push(place.at(p).times(extent));
306 }
307 coords
308 }
309
310 fn index_of(
311 coords: &Coords<u32>,
312 #[comptime] space: &Space,
313 #[comptime] tile: &VectorTile,
314 ) -> CoordsDyn {
315 let rank = comptime!(space.rank());
316 let mut index = CoordsDyn::new();
317 #[unroll]
318 for p in 0..rank {
319 let extent = comptime!(tile.extent_along(space.axis_at(p)) as u32);
320 index.push(coords.at(p).divided_by(extent));
321 }
322 index
323 }
324}
325
326impl VectorTile {
329 pub fn count(&self, space: &Space) -> u32 {
331 VectorTile::count_of(space, self)
332 }
333
334 pub fn start(&self, line: u32, space: &Space) -> Coords<u32> {
337 VectorTile::start_of(line, space, self)
338 }
339
340 pub fn index(&self, coords: &Coords<u32>, space: &Space) -> CoordsDyn {
343 VectorTile::index_of(coords, space, self)
344 }
345
346 pub fn __expand_count_method(&self, scope: &Scope, space: &Space) -> NativeExpand<u32> {
347 VectorTile::__expand_count_of(scope, space, self)
348 }
349
350 pub fn __expand_start_method(
351 &self,
352 scope: &Scope,
353 line: NativeExpand<u32>,
354 space: &Space,
355 ) -> <Coords<u32> as CubeType>::ExpandType {
356 VectorTile::__expand_start_of(scope, line, space, self)
357 }
358
359 pub fn __expand_index_method(
360 &self,
361 scope: &Scope,
362 coords: &<Coords<u32> as CubeType>::ExpandType,
363 space: &Space,
364 ) -> <CoordsDyn as CubeType>::ExpandType {
365 VectorTile::__expand_index_of(scope, coords, space, self)
366 }
367}
368
369comptime_only!(VectorTile);
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374
375 const K: Axis = Axis(0);
376 const N: Axis = Axis(1);
377
378 #[test]
380 fn a_plain_buffer_loads_along_its_innermost_axis() {
381 assert_eq!(VectorTile::new(&[], N, 4).unwrap().extents(), &[(N, 4)]);
382 assert_eq!(VectorTile::new(&[], N, 1).unwrap().extents(), &[(N, 1)]);
383 }
384
385 #[test]
388 fn a_stored_buffer_loads_its_finest_tiles() {
389 let stored = [(K, 8), (K, 2), (N, 2), (K, 32), (N, 8)];
390 assert_eq!(
391 VectorTile::new(&stored, N, 32).unwrap().extents(),
392 &[(K, 16), (N, 2)]
393 );
394 assert_eq!(
395 VectorTile::new(&stored, N, 16).unwrap().extents(),
396 &[(K, 16)]
397 );
398 assert_eq!(VectorTile::new(&stored, N, 8).unwrap().extents(), &[(K, 8)]);
399 assert_eq!(VectorTile::new(&stored, N, 32).unwrap().values(), 32);
400 }
401
402 #[test]
405 fn a_load_that_cuts_a_stored_tile_is_refused() {
406 let stored = [(K, 8), (N, 2)];
407 assert_eq!(
408 VectorTile::new(&stored, N, 4),
409 Err(TileMisfit::Overshoots {
410 axis: K,
411 wanted: 4,
412 piece: 8
413 })
414 );
415 assert!(VectorTile::new(&stored, N, 24).is_err());
416 assert!(VectorTile::new(&stored, N, 32).is_err());
417 }
418
419 #[test]
422 fn a_load_that_returns_to_an_axis_is_refused() {
423 assert_eq!(
424 VectorTile::new(&[(K, 8), (N, 2), (K, 2)], N, 32),
425 Err(TileMisfit::Interleaved {
426 wanted: K,
427 found: N
428 })
429 );
430 }
431
432 #[test]
435 fn a_position_in_a_load_is_an_offset_along_each_axis() {
436 let load = VectorTile::new(&[(K, 8), (K, 2), (N, 2)], N, 32).unwrap();
437 assert_eq!(load.offset_along(20, K), 4);
438 assert_eq!(load.offset_along(20, N), 1);
439 assert_eq!(VectorTile::run(N, 4).offset_along(3, N), 3);
440 assert_eq!(VectorTile::run(N, 4).offset_along(3, K), 0);
441 }
442
443 #[test]
445 fn a_stored_tile_of_one_is_skipped() {
446 let stored = [(N, 4), (N, 1), (K, 2)];
447 assert_eq!(
448 VectorTile::new(&stored, N, 8).unwrap().extents(),
449 &[(N, 4), (K, 2)]
450 );
451 }
452
453 #[test]
456 #[should_panic(expected = "the fragment load places a load along one axis")]
457 fn a_rectangle_is_refused_by_a_one_axis_reader() {
458 let tile = VectorTile::new(&[(K, 8), (N, 2)], N, 16).unwrap();
459 tile.along_one_axis("the fragment load");
460 }
461
462 #[test]
466 fn a_load_covers_the_innermost_dim_or_the_stored_tiles_it_takes() {
467 let run = VectorTile::run(N, 4);
468 assert_eq!(run.parts(&[], &[K, N], 2), vec![1, 4]);
469
470 const M: Axis = Axis(2);
473 let stored = [(K, 8), (K, 2), (M, 2), (K, 4), (M, 4)];
474 let labels = [M, K, M, K, M, K, K];
475 let load = VectorTile::new(&stored, K, 32).unwrap();
476 assert_eq!(load.extents(), &[(K, 16), (M, 2)]);
477 assert_eq!(load.parts(&stored, &labels, 7), vec![1, 1, 1, 1, 2, 2, 8]);
478 assert_eq!(
479 load.parts(&stored, &labels, 8),
480 vec![1, 1, 1, 1, 1, 2, 2, 8]
481 );
482 }
483
484 #[test]
488 fn the_stated_tiles_have_their_stated_extents() {
489 const M: Axis = Axis(2);
490 let stored = [(K, 8), (K, 2), (M, 2), (K, 4), (M, 4)];
491 let labels = [M, K, M, K, M, K, K];
492 let tiled = [Some(4), Some(4), Some(2), Some(2), Some(8)];
493 let expected: Vec<_> = [None, None].into_iter().chain(tiled).collect();
494 assert_eq!(VectorTile::stated_extents(&stored, &labels, 7), expected);
495 let batched: Vec<_> = [None].into_iter().chain(expected).collect();
496 assert_eq!(VectorTile::stated_extents(&stored, &labels, 8), batched);
497 }
498}