1use std::marker::PhantomData;
4
5use cubecl::prelude::*;
6use cubecl::server::TensorMapMeta;
7pub use cubecl::zspace::metadata::Metadata;
8use cubecl::zspace::{Shape, Strides, shape, strides};
9
10use crate::MatrixLayout;
11
12pub fn stride_align_bits(strides: &[usize], layout: &MatrixLayout, dtype: &ElemType) -> u32 {
14 let exclude_dim = match layout {
15 MatrixLayout::RowMajor => strides.len() - 1,
16 MatrixLayout::ColMajor => strides.len() - 2,
17 };
18 strides
19 .iter()
20 .enumerate()
21 .filter(|(i, _)| *i != exclude_dim)
22 .map(|(_, it)| (*it * dtype.size_bits()) / 8)
23 .map(|it| it.trailing_zeros())
24 .min()
25 .unwrap_or(31)
26}
27
28pub fn remap_storage_for_tma(ty: ElemType) -> ElemType {
30 if ty == f32::elem_type_native() {
31 tf32::elem_type_native()
32 } else {
33 ty
34 }
35}
36
37pub fn transpose_inner_for_tma(
46 shape: &mut Shape,
47 strides: &mut Strides,
48 layout: MatrixLayout,
49) -> bool {
50 if matches!(layout, MatrixLayout::ColMajor) {
51 let s_rank = shape.num_dims();
52 let t_rank = strides.rank();
53 shape.swap(s_rank - 1, s_rank - 2);
54 strides.swap(t_rank - 1, t_rank - 2);
55 true
56 } else {
57 false
58 }
59}
60
61pub fn tma_operand(
67 binding: TensorBinding,
68 batches: usize,
69 layout: MatrixLayout,
70 box_shape: (usize, usize),
71 storage_ty: ElemType,
72 swizzle: TensorMapSwizzle,
73) -> (TensorMapArg<Tiled>, bool) {
74 let rank = binding.shape.len();
75 let mut shape = shape![batches, binding.shape[rank - 2], binding.shape[rank - 1]];
76 let mut strides: Strides = if rank > 2 {
77 binding.strides[rank - 3..].into()
78 } else {
79 strides![binding.strides[0], binding.strides[1]]
80 };
81 let transposed = transpose_inner_for_tma(&mut shape, &mut strides, layout);
82 if strides.len() == 2 {
84 let stride = strides[0];
85 strides.insert(0, stride);
86 }
87
88 let (box_rows, box_cols) = box_shape;
89 let tile_size = match transposed {
90 true => shape![1, box_cols, box_rows],
91 false => shape![1, box_rows, box_cols],
92 };
93 let meta = tma_meta_tiled(
94 Metadata::new(shape, strides),
95 tile_size,
96 remap_storage_for_tma(storage_ty),
97 swizzle,
98 );
99 let arg = TensorMapArg {
100 tensor: binding.into_tensor_arg(),
101 metadata: meta,
102 _kind: PhantomData,
103 };
104 (arg, transposed)
105}
106
107pub fn tma_operand_tiled(
121 binding: TensorBinding,
122 tile: (usize, usize),
123 storage_ty: ElemType,
124 swizzle: TensorMapSwizzle,
125) -> TensorMapArg<Tiled> {
126 let rank = binding.shape.len();
127 let stored = (binding.shape[rank - 2], binding.shape[rank - 1]);
128 assert_eq!(
129 stored, tile,
130 "tma_operand_tiled: the binding is stored in {stored:?} tiles, not the {tile:?} asked for"
131 );
132 let inner = (binding.strides[rank - 2], binding.strides[rank - 1]);
133 assert_eq!(
134 inner,
135 (tile.1, 1),
136 "tma_operand_tiled: a box is one contiguous run only for a tile stored a row at a time, \
137 which steps ({}, 1); this one steps {inner:?}",
138 tile.1
139 );
140 let mut dims = vec![1usize; rank];
142 dims[rank - 2] = tile.0;
143 dims[rank - 1] = tile.1;
144 let box_shape: Shape = dims.into();
145 let meta = tma_meta_tiled(
146 Metadata::new(binding.shape.clone(), binding.strides.clone()),
147 box_shape,
148 remap_storage_for_tma(storage_ty),
149 swizzle,
150 );
151 TensorMapArg {
152 tensor: binding.into_tensor_arg(),
153 metadata: meta,
154 _kind: PhantomData,
155 }
156}
157
158pub fn tma_meta_tiled(
161 metadata: Metadata,
162 tile_size: Shape,
163 elem_ty: ElemType,
164 swizzle: TensorMapSwizzle,
165) -> TensorMapMeta {
166 let rank = metadata.rank();
167 TensorMapMeta {
168 format: TensorMapFormat::Tiled(TiledArgs { tile_size }),
169 metadata,
170 elem_stride: Strides::new(&vec![1; rank]),
171 interleave: TensorMapInterleave::None,
172 swizzle,
173 prefetch: TensorMapPrefetch::None,
174 oob_fill: OobFill::Zero,
175 elem_ty,
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use cubecl::zspace::Tiling;
182
183 use super::*;
184
185 fn tiled() -> TensorBinding {
187 let client = cubecl::test_device().client();
188 let shape = shape![8, 8, 32, 64];
189 let strides = strides![64 * 32 * 8, 64 * 32, 64, 1];
190 TensorBinding {
191 handle: client.empty(256 * 512 * 4).binding(),
192 strides,
193 shape,
194 tiling: Tiling::new(&[2, 2]).unwrap(),
195 }
196 }
197
198 #[test]
199 fn a_stored_descriptor_keeps_the_rank_and_boxes_one_storage_tile() {
200 let arg = tma_operand_tiled(
201 tiled(),
202 (32, 64),
203 f32::elem_type_native(),
204 TensorMapSwizzle::None,
205 );
206 assert_eq!(&arg.metadata.metadata.shape()[..], &[8, 8, 32, 64]);
208 assert_eq!(
209 &arg.metadata.metadata.strides()[..],
210 &[64 * 32 * 8, 64 * 32, 64, 1]
211 );
212 let TensorMapFormat::Tiled(TiledArgs { tile_size }) = &arg.metadata.format else {
215 panic!("expected a tiled tensor map");
216 };
217 assert_eq!(&tile_size[..], &[1, 1, 32, 64]);
218 }
219
220 #[test]
221 #[should_panic(expected = "stored in (32, 64) tiles, not the (64, 64) asked for")]
222 fn a_stored_descriptor_refuses_a_tile_the_buffer_does_not_hold() {
223 tma_operand_tiled(
224 tiled(),
225 (64, 64),
226 f32::elem_type_native(),
227 TensorMapSwizzle::None,
228 );
229 }
230}