Skip to main content

cubecl_std/tensor/layout/
tiled_view.rs

1use cubecl::prelude::*;
2use cubecl_core::{self as cubecl};
3
4use crate::tensor::{
5    View,
6    launch::{MemoryArg, ViewArg, ViewLayoutLaunchArg},
7    layout::{Coords1d, CoordsDyn, Layout, LayoutExpand},
8};
9
10/// Tiling as a composable layout: splits each tiled axis into `levels + 1`
11/// factors — one grid plus `levels` nested tile sizes — via a mixed-radix
12/// decomposition, expanding logical rank R to physical rank `R + levels * n`. The
13/// factors are read from the physical shape, laid out `[pre, grid…, level1…, …,
14/// levelL…, post]` (coarsest grid first, finest tile last). `levels == 1` is a
15/// single split `c -> (c / T, c % T)`.
16#[derive(CubeType, Clone)]
17pub struct TiledLayout {
18    physical_shape: CoordsDyn,
19    #[cube(comptime)]
20    start_axis: usize,
21    #[cube(comptime)]
22    num_tiled: usize,
23    #[cube(comptime)]
24    levels: usize,
25}
26
27#[cube]
28impl TiledLayout {
29    pub fn new(
30        physical_shape: CoordsDyn,
31        #[comptime] start_axis: usize,
32        #[comptime] num_tiled: usize,
33        #[comptime] levels: usize,
34    ) -> TiledLayout {
35        TiledLayout {
36            physical_shape,
37            start_axis,
38            num_tiled,
39            levels,
40        }
41    }
42}
43
44#[cube]
45impl Layout for TiledLayout {
46    type Coordinates = CoordsDyn;
47    type SourceCoordinates = CoordsDyn;
48
49    /// Logical coords to physical tile coords, laid out
50    /// `[pre, grid…, level1…, …, levelL…, post]`. Each tiled axis is decomposed
51    /// mixed-radix: the finest level (last block) is the least significant digit.
52    fn to_source_pos(&self, pos: Self::Coordinates) -> Self::SourceCoordinates {
53        #[comptime]
54        let n = self.num_tiled;
55        #[comptime]
56        let levels = self.levels;
57        #[comptime]
58        let physical_rank = self.physical_shape.len();
59
60        let mut physical = CoordsDyn::new();
61        #[unroll]
62        for i in 0..self.start_axis {
63            physical.push(pos[i]);
64        }
65        #[unroll]
66        for k in 0..comptime!(levels + 1) {
67            #[unroll]
68            for i in 0..n {
69                // Strip the finer blocks, then take this block's digit. The grid
70                // (k == 0) keeps the full quotient — it has no enclosing tile.
71                let mut divisor = 1u32;
72                #[unroll]
73                for finer in 0..comptime!(levels + 1) {
74                    if comptime!(finer > k) {
75                        divisor *= self.physical_shape[comptime!(self.start_axis + finer * n + i)];
76                    }
77                }
78                let digit = pos[comptime!(self.start_axis + i)] / divisor;
79                if comptime!(k == 0) {
80                    physical.push(digit);
81                } else {
82                    physical
83                        .push(digit % self.physical_shape[comptime!(self.start_axis + k * n + i)]);
84                }
85            }
86        }
87        #[unroll]
88        for i in 0..comptime!(physical_rank - (self.start_axis + (levels + 1) * n)) {
89            physical.push(pos[comptime!(self.start_axis + n + i)]);
90        }
91        physical
92    }
93
94    fn to_source_pos_checked(&self, pos: Self::Coordinates) -> (Self::SourceCoordinates, bool) {
95        let in_bounds = self.is_in_bounds(pos.clone());
96        (self.to_source_pos(pos), in_bounds)
97    }
98
99    /// Logical shape: each tiled axis collapses its `levels + 1` factors back to
100    /// their product.
101    fn shape(&self) -> Self::Coordinates {
102        #[comptime]
103        let n = self.num_tiled;
104        #[comptime]
105        let levels = self.levels;
106        #[comptime]
107        let physical_rank = self.physical_shape.len();
108
109        let mut semantic = CoordsDyn::new();
110        #[unroll]
111        for i in 0..self.start_axis {
112            semantic.push(self.physical_shape[i]);
113        }
114        #[unroll]
115        for i in 0..n {
116            let mut extent = 1u32;
117            #[unroll]
118            for k in 0..comptime!(levels + 1) {
119                extent *= self.physical_shape[comptime!(self.start_axis + k * n + i)];
120            }
121            semantic.push(extent);
122        }
123        #[unroll]
124        for i in 0..comptime!(physical_rank - (self.start_axis + (levels + 1) * n)) {
125            semantic.push(self.physical_shape[comptime!(self.start_axis + (levels + 1) * n + i)]);
126        }
127        semantic
128    }
129
130    fn is_in_bounds(&self, pos: Self::Coordinates) -> bool {
131        let bounds = self.shape();
132        let mut is_valid = true;
133        #[unroll]
134        for i in 0..bounds.len() {
135            is_valid = is_valid && pos[i] < bounds[i];
136        }
137        is_valid
138    }
139}
140
141/// Tiling above a strided buffer: [`TiledLayout`]'s split, then per-axis strides
142/// to a linear offset. The buffer is a plain strided tensor; tiling is explicit
143/// (`start_axis` / `num_tiled`), with no `Tiler` metadata on it.
144#[derive(CubeType, Clone)]
145pub struct TiledViewLayout {
146    physical_shape: CoordsDyn,
147    physical_strides: CoordsDyn,
148    #[cube(comptime)]
149    start_axis: usize,
150    #[cube(comptime)]
151    num_tiled: usize,
152    #[cube(comptime)]
153    levels: usize,
154}
155
156#[cube]
157impl Layout for TiledViewLayout {
158    type Coordinates = CoordsDyn;
159    type SourceCoordinates = Coords1d;
160
161    /// Logical coords to buffer offset: split to physical coords, then strides.
162    fn to_source_pos(&self, pos: Self::Coordinates) -> Self::SourceCoordinates {
163        let split = TiledLayout::new(
164            self.physical_shape.clone(),
165            self.start_axis,
166            self.num_tiled,
167            self.levels,
168        );
169        let physical = split.to_source_pos(pos);
170
171        let mut offset = 0u32;
172        #[unroll]
173        for i in 0..self.physical_strides.len() {
174            offset += physical[i] * self.physical_strides[i];
175        }
176        offset as usize
177    }
178
179    fn to_source_pos_checked(&self, pos: Self::Coordinates) -> (Self::SourceCoordinates, bool) {
180        let in_bounds = self.is_in_bounds(pos.clone());
181        (self.to_source_pos(pos), in_bounds)
182    }
183
184    fn shape(&self) -> Self::Coordinates {
185        let split = TiledLayout::new(
186            self.physical_shape.clone(),
187            self.start_axis,
188            self.num_tiled,
189            self.levels,
190        );
191        split.shape()
192    }
193
194    fn is_in_bounds(&self, pos: Self::Coordinates) -> bool {
195        let bounds = self.shape();
196        let mut is_valid = true;
197        #[unroll]
198        for i in 0..bounds.len() {
199            is_valid = is_valid && pos[i] < bounds[i];
200        }
201        is_valid
202    }
203}
204
205/// Tiling spec for [`tiled_view`]: where the tiled axes start, how many there are,
206/// and how many nested tile levels each carries (`levels == 1` is a single split).
207/// The per-level sizes are read from the buffer's `[grid…, level1…, …]` dims.
208#[derive(Clone, Debug)]
209pub struct TileSpec {
210    pub start_axis: u8,
211    pub num_tiled: usize,
212    pub levels: usize,
213}
214
215#[derive(Debug, Hash, PartialEq, Eq, Clone)]
216pub struct TiledViewLayoutCompilationArg {
217    physical_shape: <CoordsDyn as LaunchArg>::CompilationArg,
218    physical_strides: <CoordsDyn as LaunchArg>::CompilationArg,
219    start_axis: u8,
220    num_tiled: usize,
221    levels: usize,
222}
223
224impl ViewLayoutLaunchArg for TiledViewLayout {
225    type RuntimeArg<R: Runtime> = TileSpec;
226    type CompilationArg = TiledViewLayoutCompilationArg;
227
228    fn register<R: Runtime, B: MemoryArg>(
229        spec: Self::RuntimeArg<R>,
230        buffer: &B,
231        _ty: Type,
232        launcher: &mut KernelLauncher<R>,
233    ) -> Self::CompilationArg {
234        let shape = buffer.shape();
235        let strides = buffer.strides();
236
237        let shape_arg: <CoordsDyn as LaunchArg>::RuntimeArg<R> =
238            shape.iter().map(|&s| s as u32).collect();
239        let strides_arg: <CoordsDyn as LaunchArg>::RuntimeArg<R> =
240            strides.iter().map(|&s| s as u32).collect();
241
242        TiledViewLayoutCompilationArg {
243            physical_shape: <CoordsDyn as LaunchArg>::register(shape_arg, launcher),
244            physical_strides: <CoordsDyn as LaunchArg>::register(strides_arg, launcher),
245            start_axis: spec.start_axis,
246            num_tiled: spec.num_tiled,
247            levels: spec.levels,
248        }
249    }
250
251    fn expand(
252        arg: &Self::CompilationArg,
253        _ty: Type,
254        builder: &mut KernelBuilder,
255    ) -> <Self as CubeType>::ExpandType {
256        TiledViewLayoutExpand {
257            physical_shape: <CoordsDyn as LaunchArg>::expand(&arg.physical_shape, builder),
258            physical_strides: <CoordsDyn as LaunchArg>::expand(&arg.physical_strides, builder),
259            start_axis: arg.start_axis as usize,
260            num_tiled: arg.num_tiled,
261            levels: arg.levels,
262        }
263    }
264}
265
266/// View type alias for a tiled buffer seen through its logical coordinates.
267pub type TiledView<'a, E> = View<'a, E, CoordsDyn>;
268pub type TiledViewLaunch<R> = ViewArg<CoordsDyn, R>;