1use std::marker::PhantomData;
7use std::sync::Arc;
8
9use cubecl::frontend::{AsMutExpand, AsRefExpand, CubeDebug, ExpandTypeClone, IntoExpand, IntoMut};
10use cubecl::frontend::{buffer_len::expand_buffer_length_native, slice};
11use cubecl::ir::pliron::value::Value;
12use cubecl::ir::{ElemType, Scope, types::VectorType};
13use cubecl::prelude::*;
14use cubecl::unexpanded;
15use cubecl_std::tensor::{
16 layout::{CoordsDyn, VirtualLayout, VirtualLayoutExpand},
17 view::launch::{VirtualViewLayoutCompilationArg, VirtualViewLayoutLaunch},
18};
19
20use crate::launch::binding::rebound_tensor;
21use crate::tile::packing::base::Packing;
22use crate::*;
23use cubecl::server::BufferBinding;
24
25pub struct TileArg<'a> {
45 pub spec: TileSpec,
46 pub element: ElemType,
48 _values: PhantomData<&'a ()>,
49}
50
51impl TileArg<'_> {
52 pub fn tile(&self, _partitioning: &Partitioning) -> Tile {
54 unexpanded!()
55 }
56
57 pub fn lines<E: Numeric, V: Size>(&self) -> &Tensor<Vector<E, V>> {
61 unexpanded!()
62 }
63}
64
65#[cube]
67fn buffer_tile<E: Numeric>(
68 tensor: &Tensor<E>,
69 partitioning: &Partitioning,
70 coefficients: Coords<u32>,
71 offsets: Coords<i32>,
72 #[comptime] spec: TileSpec,
73) -> Tile {
74 let space = comptime!(partitioning.clone());
75 GlobalOperand::gathered(
76 tensor,
77 comptime!(space.space().clone()),
78 spec,
79 coefficients,
80 offsets,
81 )
82 .tile::<E>(comptime!(space.levels().to_vec()))
83}
84
85#[cube]
87fn table_tile<E: Numeric>(
88 tensor: &Tensor<E>,
89 #[comptime] entries: usize,
90 #[comptime] spec: TileSpec,
91) -> Tile {
92 let space = comptime!(Space::new(&[(spec.axes()[0], entries)]));
93 GlobalOperand::tensor(tensor, comptime!(space.clone()), spec).tile::<E>(comptime!(Vec::new()))
94}
95
96#[cube]
98fn paged_tile<E: Numeric>(
99 tensor: &Tensor<E>,
100 table: &Tensor<u32>,
101 partitioning: &Partitioning,
102 #[comptime] spec: TileSpec,
103) -> Tile {
104 let space = comptime!(partitioning.clone());
105 GlobalOperand::paged(tensor, table, comptime!(space.space().clone()), spec)
106 .tile::<E>(comptime!(space.levels().to_vec()))
107}
108
109#[cube]
111fn tensor_map_tile<E: Numeric>(
112 map: TensorMap<E, Tiled>,
113 layout: VirtualLayout<CoordsDyn, Sequence<i32>>,
114 partitioning: &Partitioning,
115 #[comptime] spec: TileSpec,
116) -> Tile {
117 let space = comptime!(partitioning.clone());
118 let own = comptime!(space.space().subspace(spec.axes()));
119 let data = TmaData::from_tensor_map(
120 map,
121 layout,
122 comptime!(own.rank()),
123 comptime!(spec.units),
124 comptime!(spec.packing),
125 );
126 Tile::new::<E>(
127 TileKind::new_TmaGmem(data),
128 comptime!(Placement::root(own.clone(), space.levels().to_vec())),
129 )
130}
131
132#[derive(Clone)]
134pub struct TileArgExpand<'a> {
135 values: ValuesExpand<'a>,
136 pub spec: TileSpec,
137 pub element: ElemType,
139 scale: Option<Box<TileArgExpand<'static>>>,
141 index: Option<(Box<TileArgExpand<'static>>, Indexing)>,
143 codebook: Option<Box<TileArgExpand<'static>>>,
145 runtime_map: (CoordsExpand<u32>, CoordsExpand<i32>),
147}
148
149#[derive(Clone)]
152enum ValuesExpand<'a> {
153 Tensor {
154 tensor: &'a TensorExpand<Held>,
156 buffer: Value,
157 meta: TensorMetaExpand,
158 width: usize,
159 },
160 TensorMap {
161 map: Value,
162 layout: VirtualLayoutExpand<CoordsDyn, Sequence<i32>>,
163 },
164}
165
166impl<'a> TileArgExpand<'a> {
167 pub fn __expand_tile_method(
168 &self,
169 scope: &Scope,
170 partitioning: &PartitioningExpand,
171 ) -> TileExpand {
172 let values = self.values_tile(scope, partitioning);
173 let values = self.looking_up(scope, values);
174 let values = self.indexing(scope, values, partitioning);
175 self.scaling(scope, values, partitioning)
176 }
177
178 pub fn __expand_lines_method<E: Numeric, V: Size>(
179 &self,
180 scope: &Scope,
181 ) -> &'a TensorExpand<Vector<E, V>> {
182 let ValuesExpand::Tensor {
183 buffer,
184 meta,
185 width: bound,
186 ..
187 } = &self.values
188 else {
189 panic!("TileArg::lines: a tensor map has no lines to add into")
190 };
191 let named = E::elem_type(scope);
192 assert_eq!(
193 named, self.element,
194 "TileArg::lines: the buffer is bound at {}, not {named}",
195 self.element
196 );
197 let width = V::__expand_value(scope);
198 assert_eq!(
199 width, *bound,
200 "TileArg::lines: the buffer is bound {bound} wide, not {width}"
201 );
202 let len = expand_buffer_length_native(scope, *buffer);
203 let lines = slice::from_raw_parts::<Vector<E, V>>(
204 scope,
205 *buffer,
206 0usize.into_expand(scope),
207 len.into(),
208 );
209 scope.create_kernel_ref(TensorExpand::__expand_from_parts(*meta, lines))
210 }
211
212 fn values_tile(&self, scope: &Scope, partitioning: &PartitioningExpand) -> TileExpand {
215 let _held = scope.define_type::<Held>(self.element);
216 match &self.values {
217 ValuesExpand::Tensor { tensor, meta, .. } => {
218 let tensor = scope.create_kernel_ref(TensorExpand::<Held>::__expand_from_parts(
219 *meta,
220 tensor.expand.into(),
221 ));
222 if let (Some(_), Some((table, _))) = (&self.spec.paging, &self.index) {
223 let ValuesExpand::Tensor {
224 tensor: pages,
225 meta,
226 ..
227 } = &table.values
228 else {
229 unreachable!("TileArgLaunch::indexed binds its table as a buffer")
230 };
231 let pages = scope.create_kernel_ref(TensorExpand::<u32>::__expand_from_parts(
232 *meta,
233 pages.expand.into(),
234 ));
235 return paged_tile::expand::<Held>(
236 scope,
237 tensor,
238 pages,
239 partitioning,
240 self.spec.clone(),
241 );
242 }
243 let (coefficients, offsets) = self.runtime_map.clone();
244 buffer_tile::expand::<Held>(
245 scope,
246 tensor,
247 partitioning,
248 coefficients,
249 offsets,
250 self.spec.clone(),
251 )
252 }
253 ValuesExpand::TensorMap { map, layout } => tensor_map_tile::expand::<Held>(
254 scope,
255 (*map).into(),
256 layout.clone(),
257 partitioning,
258 self.spec.clone(),
259 ),
260 }
261 }
262
263 fn looking_up(&self, scope: &Scope, values: TileExpand) -> TileExpand {
265 let Some(table) = &self.codebook else {
266 return values;
267 };
268 let ValuesExpand::Tensor { tensor, meta, .. } = &table.values else {
269 unreachable!("TileArgLaunch::codebook binds a buffer")
270 };
271 let Packing::Packed {
272 field: Field::Index { bits },
273 } = self.spec.packing
274 else {
275 unreachable!("TileArgLaunch::codebook binds values stored as indices")
276 };
277 let _held = scope.define_type::<Held>(table.element);
278 let entries = scope.create_kernel_ref(TensorExpand::<Held>::__expand_from_parts(
279 *meta,
280 tensor.expand.into(),
281 ));
282 let table = table_tile::expand::<Held>(scope, entries, 1 << bits, table.spec.clone());
283 values.__expand_lookup_method(scope, &table)
284 }
285
286 fn indexing(
291 &self,
292 scope: &Scope,
293 values: TileExpand,
294 partitioning: &PartitioningExpand,
295 ) -> TileExpand {
296 let Some((table, indexing)) = &self.index else {
297 return values;
298 };
299 if self.spec.paging.is_some() {
300 return values;
301 }
302 let keys = table.spec.axes().to_vec();
303 let read_at = indexing
304 .read_at(&keys, &partitioning.levels)
305 .unwrap_or_else(|refusal| panic!("{refusal}"));
306 let ValuesExpand::Tensor { tensor, meta, .. } = &table.values else {
307 panic!("{}", Refusal::IndexTableNotU32)
308 };
309 let entries = scope.create_kernel_ref(TensorExpand::<u32>::__expand_from_parts(
310 *meta,
311 tensor.expand.into(),
312 ));
313 let table = IndexTable::__expand_new(scope, entries, keys, indexing.clone());
314 let values = values.__expand_with_index_method(scope, table);
315 match read_at {
316 Some(_) => values,
317 None => values.__expand_indexed_whole_method(scope),
318 }
319 }
320
321 fn scaling(
326 &self,
327 scope: &Scope,
328 values: TileExpand,
329 partitioning: &PartitioningExpand,
330 ) -> TileExpand {
331 let Some(level) = &self.scale else {
332 return values;
333 };
334 let words =
335 level.element == u32::elem_type_native() && level.spec.packing == Packing::Plain;
336 assert!(
337 words || !matches!(self.values, ValuesExpand::TensorMap { .. }),
338 "TileArg: a tensor map lands its values as they lie, so it rides only scales a stage \
339 keeps beside them: words of block scales"
340 );
341 values.__expand_mul_method(scope, &level.__expand_tile_method(scope, partitioning))
342 }
343}
344
345pub struct TileArgLaunch {
349 values: ValuesLaunch,
350 spec: Arc<TileSpec>,
351 element: ElemType,
352 scale: Option<Box<TileArgLaunch>>,
353 index: Option<(Box<TileArgLaunch>, Indexing)>,
354 codebook: Option<Box<TileArgLaunch>>,
355 runtime_map: (Vec<u32>, Vec<i32>),
356}
357
358enum ValuesLaunch {
361 Tensor {
362 tensor: TensorArg,
363 width: usize,
364 },
365 TensorMap {
366 map: Box<TensorMapArg<Tiled>>,
367 layout: VirtualViewLayoutLaunch<CoordsDyn, Sequence<i32>, TensorMapArg<Tiled>>,
368 },
369}
370
371impl TileArgLaunch {
372 pub fn new(tensor: TensorArg, element: ElemType, width: usize, spec: TileSpec) -> Self {
374 Self::of(ValuesLaunch::Tensor { tensor, width }, element, spec)
375 }
376
377 pub(crate) fn of_tensor_map(
380 map: TensorMapArg<Tiled>,
381 layout: VirtualViewLayoutLaunch<CoordsDyn, Sequence<i32>, TensorMapArg<Tiled>>,
382 spec: TileSpec,
383 ) -> Self {
384 let element = map.metadata.elem_ty;
385 let map = Box::new(map);
386 Self::of(ValuesLaunch::TensorMap { map, layout }, element, spec)
387 }
388
389 fn of(values: ValuesLaunch, element: ElemType, spec: TileSpec) -> Self {
390 assert!(
391 spec.packing == Packing::Plain || element == u32::elem_type_native(),
392 "TileArgLaunch: a packed operand is bound at the u32 words its buffer holds, not \
393 {element:?}"
394 );
395 Self {
396 values,
397 spec: Arc::new(spec),
398 element,
399 scale: None,
400 index: None,
401 codebook: None,
402 runtime_map: (Vec::new(), Vec::new()),
403 }
404 }
405
406 pub fn rebound(&self, buffers: &mut impl Iterator<Item = BufferBinding>) -> Option<Self> {
414 let values = match &self.values {
415 ValuesLaunch::Tensor { tensor, width } => ValuesLaunch::Tensor {
416 tensor: rebound_tensor(tensor, buffers.next()?)?,
417 width: *width,
418 },
419 ValuesLaunch::TensorMap { .. } => return None,
420 };
421 let scale = match &self.scale {
422 Some(scale) => Some(Box::new(scale.rebound(buffers)?)),
423 None => None,
424 };
425 let index = match &self.index {
426 Some((table, indexing)) => Some((Box::new(table.rebound(buffers)?), indexing.clone())),
427 None => None,
428 };
429 let codebook = match &self.codebook {
430 Some(table) => Some(Box::new(table.rebound(buffers)?)),
431 None => None,
432 };
433 Some(Self {
434 values,
435 spec: self.spec.clone(),
436 element: self.element,
437 scale,
438 index,
439 codebook,
440 runtime_map: self.runtime_map.clone(),
441 })
442 }
443
444 pub fn codebook(mut self, table: TileArgLaunch) -> Result<Self, Refusal> {
452 let Packing::Packed {
453 field: Field::Index { bits },
454 } = self.spec.packing
455 else {
456 return Err(Refusal::CodebookOfValuesNotIndices);
457 };
458 let ValuesLaunch::Tensor { tensor, width } = &table.values else {
459 return Err(Refusal::CodebookTableNotPlain);
460 };
461 let plain = *width == 1
462 && tensor.shape().len() == 1
463 && table.spec.axes().len() == 1
464 && table.spec.packing == Packing::Plain
465 && table.scale.is_none()
466 && table.index.is_none()
467 && table.codebook.is_none();
468 if !plain {
469 return Err(Refusal::CodebookTableNotPlain);
470 }
471 let entries = tensor.shape()[0];
472 if entries < 1 << bits {
473 return Err(Refusal::CodebookTableShort { entries, bits });
474 }
475 self.codebook = Some(Box::new(table));
476 Ok(self)
477 }
478
479 pub fn runtime_map(mut self, coefficients: Vec<u32>, offsets: Vec<i32>) -> Self {
484 self.runtime_map = (coefficients, offsets);
485 self
486 }
487
488 pub fn scales(mut self, scale: TileArgLaunch) -> Self {
491 self.scale = Some(Box::new(match self.scale {
492 None => scale,
493 Some(inner) => inner.scales(scale),
494 }));
495 self
496 }
497
498 pub fn indexed(self, table: TileArgLaunch, indexing: Indexing) -> Result<Self, Refusal> {
516 self.placed_by(table, indexing, false)
517 }
518
519 pub fn indexed_covering(
529 self,
530 table: TileArgLaunch,
531 indexing: Indexing,
532 ) -> Result<Self, Refusal> {
533 self.placed_by(table, indexing, true)
534 }
535
536 fn placed_by(
538 mut self,
539 table: TileArgLaunch,
540 indexing: Indexing,
541 covering: bool,
542 ) -> Result<Self, Refusal> {
543 let ValuesLaunch::Tensor { tensor, width } = &table.values else {
544 return Err(Refusal::IndexTableNotU32);
545 };
546 let plain_u32 = table.element == u32::elem_type_native()
547 && *width == 1
548 && table.spec.packing == Packing::Plain
549 && table.scale.is_none()
550 && table.index.is_none();
551 if !plain_u32 {
552 return Err(Refusal::IndexTableNotU32);
553 }
554 let keys = table.spec.axes();
555 let rank = tensor.shape().len();
556 if rank != keys.len() {
557 return Err(Refusal::IndexTableRank {
558 keys: keys.len(),
559 rank,
560 });
561 }
562 if let (Indexing::Coordinate(sequence), &[first, position]) = (&indexing, keys)
563 && first == *sequence
564 {
565 self.spec = Arc::new(self.paged_spec(*sequence, position, covering)?);
566 let mut level = self.scale.as_deref();
567 while let Some(scale) = level {
568 let spans = |axis| scale.spec.axes().contains(&axis);
569 if (spans(first) || spans(position)) && scale.spec.paging.is_none() {
570 return Err(Refusal::ScalesOfAPoolUnpaged);
571 }
572 level = scale.scale.as_deref();
573 }
574 self.index = Some((Box::new(table), indexing));
575 return Ok(self);
576 }
577 indexing.validate(&self.spec, self.indexed_width()?, keys)?;
578 let mut level = self.scale.as_deref();
579 while let Some(scale) = level {
580 if scale.spec.axes().contains(&indexing.axis()) {
581 indexing.places(&scale.spec, scale.indexed_width()?)?;
582 }
583 level = scale.scale.as_deref();
584 }
585 self.index = Some((Box::new(table), indexing));
586 Ok(self)
587 }
588
589 fn paged_spec(
593 &self,
594 sequence: Axis,
595 position: Axis,
596 covering: bool,
597 ) -> Result<TileSpec, Refusal> {
598 let ValuesLaunch::Tensor { tensor, .. } = &self.values else {
599 return Err(Refusal::PagingMovedByRuns);
600 };
601 let mut spec = TileSpec::clone(&self.spec);
602 let paging = Paging::of_pool(
603 sequence,
604 position,
605 tensor.shape(),
606 &spec.projection,
607 &spec.stored_tiles,
608 spec.delivery,
609 )?;
610 if !covering {
611 let boundary = spec.boundaries.iter().flatten().next().copied();
612 if spec.boundaries.is_empty() {
613 spec.boundaries = (0..spec.projection.coordinate_rank())
614 .map(|_| None)
615 .collect();
616 }
617 let (sequence_dim, position_dim) = paging.dims(&spec.projection)?;
618 for dim in [sequence_dim, position_dim] {
619 spec.boundaries[dim] = Some(boundary.unwrap_or(Boundary::Zero));
620 }
621 }
622 spec.paging = Some(paging);
623 Ok(spec)
624 }
625
626 fn indexed_width(&self) -> Result<usize, Refusal> {
629 match &self.values {
630 ValuesLaunch::Tensor { width, .. } => Ok(*width),
631 ValuesLaunch::TensorMap { .. } => Err(Refusal::IndexOfAStatedLayout),
632 }
633 }
634}
635
636#[derive(Clone, PartialEq, Eq, Hash, Debug)]
638pub struct TileArgCompilationArg {
639 values: ValuesCompilationArg,
640 spec: Arc<TileSpec>,
641 element: ElemType,
642 scale: Option<Box<TileArgCompilationArg>>,
643 index: Option<(Box<TileArgCompilationArg>, Indexing)>,
644 codebook: Option<Box<TileArgCompilationArg>>,
645 runtime_map: (usize, usize),
647}
648
649#[derive(Clone, PartialEq, Eq, Hash, Debug)]
651enum ValuesCompilationArg {
652 Tensor {
653 tensor: TensorCompilationArg,
654 width: usize,
655 },
656 TensorMap(VirtualViewLayoutCompilationArg<CoordsDyn, Sequence<i32>>),
657}
658
659impl<'a> CubeType for TileArg<'a> {
660 type ExpandType = TileArgExpand<'a>;
661}
662
663impl LaunchArg for TileArg<'static> {
664 type RuntimeArg = TileArgLaunch;
665 type CompilationArg = TileArgCompilationArg;
666
667 fn register(arg: Self::RuntimeArg, launcher: &mut KernelLauncher) -> Self::CompilationArg {
668 let TileArgLaunch {
669 values,
670 spec,
671 element,
672 scale,
673 index,
674 codebook,
675 runtime_map: (coefficients, offsets),
676 } = arg;
677 let values = match values {
678 ValuesLaunch::Tensor { tensor, width } => {
679 let line_size = element.size() * width;
680 let meta = TensorMetaLaunch::new(tensor.size() / width, tensor.shape().len());
681 let buffer = match &tensor {
682 TensorArg::Handle { .. } => BufferCompilationArg { inplace: None },
683 TensorArg::Alias { input_pos, .. } => BufferCompilationArg {
684 inplace: Some(*input_pos),
685 },
686 };
687 launcher.register_tensor(tensor, line_size);
688 let meta = TensorMeta::register(meta, launcher);
689 ValuesCompilationArg::Tensor {
690 tensor: TensorCompilationArg { meta, buffer },
691 width,
692 }
693 }
694 ValuesLaunch::TensorMap { map, layout } => {
695 let layout = layout.register(&map, Type::new(element), launcher);
696 launcher.register_tensor_map(*map, element.size());
697 ValuesCompilationArg::TensorMap(layout)
698 }
699 };
700 let scale = scale.map(|level| Box::new(TileArg::register(*level, launcher)));
701 let index = index
702 .map(|(table, indexing)| (Box::new(TileArg::register(*table, launcher)), indexing));
703 let codebook = codebook.map(|table| Box::new(TileArg::register(*table, launcher)));
704 for coefficient in &coefficients {
705 coefficient.register(launcher);
706 }
707 for offset in &offsets {
708 offset.register(launcher);
709 }
710 TileArgCompilationArg {
711 values,
712 spec,
713 element,
714 scale,
715 index,
716 codebook,
717 runtime_map: (coefficients.len(), offsets.len()),
718 }
719 }
720
721 fn expand(arg: &Self::CompilationArg, builder: &mut KernelBuilder) -> TileArgExpand<'static> {
722 let values = match &arg.values {
723 ValuesCompilationArg::Tensor { tensor, width } => {
724 let buffer = match tensor.buffer.inplace {
725 Some(id) => builder.inplace(id),
726 None => {
727 let scope = &builder.scope;
728 let held = arg.element.to_type(scope.ctx());
729 let line = match *width {
730 1 => held,
731 width => VectorType::get(scope.ctx(), held, width).into(),
732 };
733 builder.tensor(line)
734 }
735 };
736 let meta = TensorMeta::expand(&tensor.meta, builder);
737 let scope = &builder.scope;
738 let _held = scope.define_type::<Held>(arg.element);
739 let len = expand_buffer_length_native(scope, buffer);
740 let values = slice::from_raw_parts::<Held>(
741 scope,
742 buffer,
743 0usize.into_expand(scope),
744 len.into(),
745 );
746 ValuesExpand::Tensor {
747 tensor: scope
748 .create_kernel_ref(TensorExpand::__expand_from_parts(meta, values)),
749 buffer,
750 meta,
751 width: *width,
752 }
753 }
754 ValuesCompilationArg::TensorMap(layout) => ValuesExpand::TensorMap {
757 layout: layout.expand(Type::new(arg.element), builder),
758 map: builder.tensor_map(),
759 },
760 };
761 let scale = arg
762 .scale
763 .as_ref()
764 .map(|level| Box::new(TileArg::expand(level, builder)));
765 let index = arg
766 .index
767 .as_ref()
768 .map(|(table, indexing)| (Box::new(TileArg::expand(table, builder)), indexing.clone()));
769 let codebook = arg
770 .codebook
771 .as_ref()
772 .map(|table| Box::new(TileArg::expand(table, builder)));
773 let (coefficients, offsets) = arg.runtime_map;
774 let mut runtime_map = (
775 Coords::<u32>::__expand_new(&builder.scope),
776 Coords::<i32>::__expand_new(&builder.scope),
777 );
778 for _ in 0..coefficients {
779 let coefficient = u32::expand_scalar(builder);
780 runtime_map
781 .0
782 .__expand_push_method(&builder.scope, coefficient);
783 }
784 for _ in 0..offsets {
785 let offset = i32::expand_scalar(builder);
786 runtime_map.1.__expand_push_method(&builder.scope, offset);
787 }
788 TileArgExpand {
789 values,
790 spec: TileSpec::clone(&arg.spec),
791 element: arg.element,
792 scale,
793 index,
794 codebook,
795 runtime_map,
796 }
797 }
798}
799
800impl IntoExpand for TileArgExpand<'_> {
801 type Expand = Self;
802 fn into_expand(self, _: &Scope) -> Self {
803 self
804 }
805}
806
807impl ExpandTypeClone for TileArgExpand<'_> {
808 fn clone_unchecked(&self) -> Self {
809 self.clone()
810 }
811}
812
813impl IntoMut for TileArgExpand<'_> {
814 fn into_mut(self, _: &Scope) -> Self {
815 self
816 }
817}
818
819impl CubeDebug for TileArgExpand<'_> {}
820
821impl AsRefExpand for TileArgExpand<'_> {
822 fn __expand_ref_method(&self, _: &Scope) -> &Self {
823 self
824 }
825}
826
827impl AsMutExpand for TileArgExpand<'_> {
828 fn __expand_ref_mut_method(&mut self, _: &Scope) -> &mut Self {
829 self
830 }
831}
832
833#[cfg(test)]
834mod tests {
835 use cubecl::ir::FloatKind;
836
837 use super::*;
838
839 fn expanded_f32_buffer(builder: &mut KernelBuilder) -> TileArgExpand<'static> {
841 let mut launcher = KernelLauncher::new(builder_settings());
842 let meta = TensorMeta::register(TensorMetaLaunch::new(4, 1), &mut launcher);
843 let arg = TileArgCompilationArg {
844 values: ValuesCompilationArg::Tensor {
845 tensor: TensorCompilationArg {
846 meta,
847 buffer: BufferCompilationArg { inplace: None },
848 },
849 width: 1,
850 },
851 spec: Arc::new(TileSpec::direct(&[Axis(0)])),
852 element: ElemType::Float(FloatKind::F32),
853 scale: None,
854 index: None,
855 codebook: None,
856 runtime_map: (0, 0),
857 };
858 TileArg::expand(&arg, builder)
859 }
860
861 fn builder_settings() -> KernelSettings {
862 KernelSettings::new(
863 cubecl::ir::settings::Dim3::new_single(),
864 cubecl::ir::settings::ExecutionMode::Checked,
865 cubecl::ir::AddressType::U32,
866 )
867 }
868
869 #[test]
870 #[should_panic(expected = "TileArg::lines: the buffer is bound at f32, not i32")]
871 fn lines_refuse_a_buffer_bound_at_another_element() {
872 let mut builder = KernelBuilder::new(builder_settings());
873 let arg = expanded_f32_buffer(&mut builder);
874 arg.__expand_lines_method::<i32, Const<1>>(&builder.scope);
875 }
876
877 fn expanded_table(builder: &mut KernelBuilder, keys: &[Axis]) -> TileArgExpand<'static> {
879 let mut launcher = KernelLauncher::new(builder_settings());
880 let meta = TensorMeta::register(TensorMetaLaunch::new(4, keys.len()), &mut launcher);
881 let arg = TileArgCompilationArg {
882 values: ValuesCompilationArg::Tensor {
883 tensor: TensorCompilationArg {
884 meta,
885 buffer: BufferCompilationArg { inplace: None },
886 },
887 width: 1,
888 },
889 spec: Arc::new(TileSpec::direct(keys)),
890 element: u32::elem_type_native(),
891 scale: None,
892 index: None,
893 codebook: None,
894 runtime_map: (0, 0),
895 };
896 TileArg::expand(&arg, builder)
897 }
898
899 fn serve_indexed(keys: &[Axis], space: &[(Axis, usize)], levels: Vec<Level>) {
902 let mut builder = KernelBuilder::new(builder_settings());
903 let mut values = expanded_f32_buffer(&mut builder);
904 let table = expanded_table(&mut builder, keys);
905 values.index = Some((Box::new(table), Indexing::Run(Axis(0))));
906 let scope = &builder.scope;
907 let partitioning = PartitioningExpand {
908 space: SpaceExpand {
909 shape: Space::new(space).shape().clone(),
910 sizes: Sequence::<usize>::__expand_new(scope),
911 },
912 levels,
913 };
914 values.__expand_tile_method(scope, &partitioning);
915 }
916
917 #[test]
920 #[should_panic(
921 expected = "the first level cutting one of [Axis(1), Axis(2)] does not cut them all"
922 )]
923 fn keys_cut_at_two_levels_are_refused_as_the_tile_expands() {
924 serve_indexed(
925 &[Axis(1), Axis(2)],
926 &[(Axis(0), 4), (Axis(1), 2), (Axis(2), 2)],
927 vec![Level::every(&[(Axis(1), 1)]), Level::every(&[(Axis(2), 1)])],
928 );
929 }
930
931 #[test]
934 fn a_table_no_level_cuts_is_read_as_the_tile_expands() {
935 serve_indexed(
936 &[Axis(1)],
937 &[(Axis(0), 4), (Axis(1), 1)],
938 vec![Level::every(&[(Axis(0), 2)])],
939 );
940 }
941
942 #[test]
943 fn lines_read_a_buffer_at_the_element_it_is_bound_at() {
944 let mut builder = KernelBuilder::new(builder_settings());
945 let arg = expanded_f32_buffer(&mut builder);
946 arg.__expand_lines_method::<f32, Const<1>>(&builder.scope);
947 }
948}