Skip to main content

rublas/kernel_ir/components/global/memory/
layout.rs

1use ruda_kernel::dsl as kernel_dsl;
2use ruda_kernel::dsl::prelude::*;
3use ruda_kernel::library::FastDivmod;
4use ruda_kernel::library::tensor::layout::Coords1d;
5use ruda_kernel::library::tensor::layout::Coords2d;
6use ruda_kernel::library::tensor::layout::Layout;
7use ruda_kernel::library::tensor::layout::LayoutExpand;
8use ruda_kernel::library::tensor::layout::VirtualLayout;
9use ruda_kernel::library::tensor::layout::VirtualLayoutLaunch;
10use ruda_kernel::dsl::zspace::Shape;
11use ruda_core::quant::scheme::{QuantLevel, QuantScheme};
12use ruda_kernel::tiling::MatrixLayout;
13
14use crate::kernel_ir::{
15    definition::MatmulProblem,
16    {components::global::memory::GlobalMemoryConfig, launch::BatchedCoords},
17};
18
19/// Global layout that uses the last two dimensions and ignores all others.
20#[derive(RudaType, RudaLaunch, Clone, Copy)]
21pub struct SimpleTmaGlobalLayout {
22    #[ruda(comptime)]
23    transposed: bool,
24    shape: BatchedCoords,
25}
26
27#[ruda]
28impl SimpleTmaGlobalLayout {
29    /// Creates a new 2D layout with the batch set to `nth_batch`.
30    pub fn new(shape: BatchedCoords, #[comptime] layout: MatrixLayout) -> Self {
31        let transposed = comptime![matches!(layout, MatrixLayout::ColMajor)];
32        SimpleTmaGlobalLayout { shape, transposed }
33    }
34}
35
36#[ruda]
37impl Layout for SimpleTmaGlobalLayout {
38    type Coordinates = BatchedCoords;
39    type SourceCoordinates = BatchedCoords;
40
41    fn to_source_pos(&self, coords: Self::Coordinates) -> BatchedCoords {
42        let (batch, row, col) = coords;
43        // Don't care if it's actually broadcast, setting batch to 0 is fine either way
44        let batch = select(self.shape.0 == 1, 0, batch);
45        // Tensor maps are required to have a stride of 1 on the last dim, so their shape is
46        // transposed for col-major matrices. Need to compensate by swapping the coordinates.
47        if self.transposed.comptime() {
48            (batch, col, row)
49        } else {
50            (batch, row, col)
51        }
52    }
53
54    fn to_source_pos_checked(&self, coords: Self::Coordinates) -> (BatchedCoords, bool) {
55        (self.to_source_pos(coords), self.is_in_bounds(coords))
56    }
57
58    fn shape(&self) -> Self::Coordinates {
59        self.shape
60    }
61
62    fn is_in_bounds(&self, _pos: Self::Coordinates) -> bool {
63        // No need to bounds check TMA loads
64        true.runtime()
65    }
66}
67
68#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Default)]
69pub struct GlobalLayoutConfig {
70    pub matrix_layout: MatrixLayout,
71    pub check_row_bounds: bool,
72    pub check_col_bounds: bool,
73}
74
75impl From<GlobalMemoryConfig> for GlobalLayoutConfig {
76    fn from(gmem_config: GlobalMemoryConfig) -> Self {
77        gmem_config.as_global_layout_config()
78    }
79}
80
81/// Global layout that uses the last two dimensions and ignores all others.
82#[derive(RudaType, RudaLaunch, Clone)]
83pub struct GlobalLayout {
84    batch_layout: VirtualLayout<Coords1d, Coords1d>,
85    rows: u32,
86    cols: u32,
87
88    stride_row: usize,
89    stride_col: usize,
90
91    #[ruda(comptime)]
92    vector_size: VectorSize,
93    #[ruda(comptime)]
94    packing: u32,
95    #[ruda(comptime)]
96    config: GlobalLayoutConfig,
97}
98
99#[ruda]
100impl GlobalLayout {
101    /// Create a new batched global layout. `batch_shape` should be based on the output shape.
102    #[allow(clippy::too_many_arguments)]
103    pub fn new(
104        batch_layout: VirtualLayout<Coords1d, Coords1d>,
105        shape_row: u32,
106        shape_col: u32,
107        stride_row: usize,
108        stride_col: usize,
109        #[comptime] vector_size: VectorSize,
110        #[comptime] packing: u32,
111        #[comptime] config: GlobalLayoutConfig,
112    ) -> Self {
113        GlobalLayout {
114            batch_layout,
115            rows: shape_row,
116            cols: shape_col,
117            stride_row,
118            stride_col,
119            vector_size,
120            packing,
121            config,
122        }
123    }
124}
125
126#[ruda]
127impl Layout for GlobalLayout {
128    type Coordinates = BatchedCoords;
129    type SourceCoordinates = Coords1d;
130
131    fn to_source_pos(&self, coords: Self::Coordinates) -> usize {
132        let (batch, row, col) = coords;
133        let batch_offs = self.batch_layout.to_source_pos(batch);
134
135        let (row, col) = match self.config.matrix_layout.comptime() {
136            MatrixLayout::RowMajor => (row, col / self.packing),
137            MatrixLayout::ColMajor => (row / self.packing, col),
138        };
139
140        let idx = batch_offs + row as usize * self.stride_row + col as usize * self.stride_col;
141
142        idx / self.vector_size
143    }
144
145    fn to_source_pos_checked(&self, coords: Self::Coordinates) -> (usize, bool) {
146        (self.to_source_pos(coords), self.is_in_bounds(coords))
147    }
148
149    fn shape(&self) -> Self::Coordinates {
150        (u32::MAX.runtime() as usize, self.rows, self.cols)
151    }
152
153    fn is_in_bounds(&self, pos: Self::Coordinates) -> bool {
154        let config = self.config.comptime();
155        let (_, row, col) = pos;
156
157        match (config.check_row_bounds, config.check_col_bounds) {
158            (true, true) => row < self.rows && col < self.cols,
159            (true, false) => row < self.rows,
160            (false, true) => col < self.cols,
161            (false, false) => true,
162        }
163    }
164}
165
166impl<R: Runtime> GlobalLayoutLaunch<R> {
167    pub fn from_handle(
168        handle: &TensorBinding<R>,
169        vector_size: VectorSize,
170        config: GlobalLayoutConfig,
171    ) -> Self {
172        let rank = handle.shape.len();
173        let rows = handle.shape[rank - 2];
174        let cols = handle.shape[rank - 1];
175        let stride_row = handle.strides[rank - 2];
176        let stride_col = handle.strides[rank - 1];
177
178        GlobalLayoutLaunch::new(
179            VirtualLayoutLaunch::new::<NoopLayout>(NoopLayoutLaunch::new()),
180            rows as u32,
181            cols as u32,
182            stride_row,
183            stride_col,
184            vector_size,
185            1,
186            config,
187        )
188    }
189
190    pub fn from_handle_batched(
191        handle: &TensorBinding<R>,
192        problem: &MatmulProblem,
193        vector_size: VectorSize,
194        config: GlobalLayoutConfig,
195    ) -> Self {
196        let rank = handle.shape.len();
197        let rows = handle.shape[rank - 2];
198        let cols = handle.shape[rank - 1];
199        let stride_row = handle.strides[rank - 2];
200        let stride_col = handle.strides[rank - 1];
201
202        let batch_layout = BatchLayoutLaunch::from_handle(handle, problem);
203
204        GlobalLayoutLaunch::new(
205            VirtualLayoutLaunch::new::<BatchLayout>(batch_layout),
206            rows as u32,
207            cols as u32,
208            stride_row,
209            stride_col,
210            vector_size,
211            1,
212            config,
213        )
214    }
215
216    #[allow(clippy::too_many_arguments)]
217    pub fn from_quantized_handle(
218        values: &TensorBinding<R>,
219        scales: &TensorBinding<R>,
220        shape: &Shape,
221        problem: &MatmulProblem,
222        scheme: QuantScheme,
223        vector_size: VectorSize,
224        config: GlobalLayoutConfig,
225    ) -> (GlobalLayoutLaunch<R>, GlobalScaleLayoutArgs<R>) {
226        let rank = values.shape.len();
227        let (rows, cols) = (shape[rank - 2], shape[rank - 1]);
228        let values_layout = {
229            let (stride_row, stride_col) = (values.strides[rank - 2], values.strides[rank - 1]);
230
231            let batch_layout = BatchLayoutLaunch::from_handle(values, problem);
232
233            GlobalLayoutLaunch::new(
234                VirtualLayoutLaunch::new::<BatchLayout>(batch_layout),
235                rows as u32,
236                cols as u32,
237                stride_row,
238                stride_col,
239                vector_size / scheme.num_quants(),
240                scheme.num_quants() as u32,
241                config,
242            )
243        };
244
245        let scales_layout = {
246            let shape = (rows as u32, cols as u32);
247
248            match scheme.level {
249                QuantLevel::Tensor => GlobalScaleLayoutArgs::PerTensor { shape },
250                QuantLevel::Block(block_size) => {
251                    let [block_row, block_col] = block_size.as_dim();
252                    // Scales are never vectorized because we require that `block_size >= vector_size * num_quants`.
253                    let scales_layout =
254                        GlobalLayoutLaunch::from_handle_batched(scales, problem, 1, config);
255                    GlobalScaleLayoutArgs::BlockScaled(BlockScaledLayoutLaunch::new(
256                        shape,
257                        scales_layout,
258                        (block_row as u32, block_col as u32),
259                    ))
260                }
261            }
262        };
263
264        (values_layout, scales_layout)
265    }
266}
267
268#[derive(RudaType, RudaLaunch)]
269pub struct BatchLayout {
270    batch_shape: Sequence<FastDivmod<u32>>,
271    batch_strides: Sequence<usize>,
272}
273
274#[ruda]
275impl BatchLayout {
276    pub fn new(batch_strides: Sequence<usize>, batch_shape: Sequence<FastDivmod<u32>>) -> Self {
277        BatchLayout {
278            batch_shape,
279            batch_strides,
280        }
281    }
282}
283
284#[ruda]
285impl Layout for BatchLayout {
286    type Coordinates = Coords1d;
287    type SourceCoordinates = Coords1d;
288
289    fn to_source_pos(&self, pos: Self::Coordinates) -> Self::SourceCoordinates {
290        let mut batch = pos as u32;
291        let mut batch_offs = 0;
292        let batch_shape = self.batch_shape.rev();
293        let batch_strides = self.batch_strides.rev();
294
295        #[unroll]
296        for i in 0..batch_shape.len() {
297            let (rem, local_pos) = batch_shape[i].div_mod(batch);
298            batch = rem;
299            batch_offs += local_pos as usize * batch_strides[i];
300        }
301
302        batch_offs
303    }
304
305    #[allow(clippy::legacy_numeric_constants)]
306    fn shape(&self) -> Self::Coordinates {
307        usize::max_value()
308    }
309
310    fn is_in_bounds(&self, _pos: Self::Coordinates) -> bool {
311        true.runtime()
312    }
313
314    fn to_source_pos_checked(&self, pos: Self::Coordinates) -> (Self::SourceCoordinates, bool) {
315        (self.to_source_pos(pos), self.is_in_bounds(pos))
316    }
317}
318
319/// Layout that passed through the coordinates with no checks or modification.
320#[derive(RudaType, RudaLaunch)]
321pub struct NoopLayout {}
322
323#[ruda]
324impl NoopLayout {
325    #[allow(clippy::new_without_default)]
326    pub fn new() -> Self {
327        NoopLayout {}
328    }
329}
330
331#[ruda]
332impl Layout for NoopLayout {
333    type Coordinates = Coords1d;
334    type SourceCoordinates = Coords1d;
335
336    fn to_source_pos(&self, pos: Self::Coordinates) -> Self::SourceCoordinates {
337        pos
338    }
339
340    #[allow(clippy::legacy_numeric_constants)]
341    fn shape(&self) -> Self::Coordinates {
342        usize::max_value()
343    }
344
345    fn is_in_bounds(&self, _pos: Self::Coordinates) -> bool {
346        true.runtime()
347    }
348
349    fn to_source_pos_checked(&self, pos: Self::Coordinates) -> (Self::SourceCoordinates, bool) {
350        (self.to_source_pos(pos), self.is_in_bounds(pos))
351    }
352}
353
354impl<R: Runtime> BatchLayoutLaunch<R> {
355    pub fn from_handle(handle: &TensorBinding<R>, problem: &MatmulProblem) -> Self {
356        let rank = handle.shape.len();
357        let batch_shape = problem
358            .out_batches
359            .iter()
360            .map(|shape| *shape as u32)
361            .collect();
362        let batch_strides = handle.strides[..rank - 2]
363            .iter()
364            .zip(&handle.shape[..rank - 2])
365            .map(|(stride, shape)| if *shape == 1 { 0 } else { *stride })
366            .collect();
367        BatchLayoutLaunch::new(batch_shape, batch_strides)
368    }
369}
370
371#[derive(RudaType, RudaLaunch)]
372pub enum GlobalScaleLayout {
373    PerTensor { shape: Coords2d },
374    BlockScaled(BlockScaledLayout),
375}
376
377/// Workaround for enums not supporting `comptime`, should fix that in the future
378#[derive(RudaType, RudaLaunch)]
379pub struct BlockScaledLayout {
380    shape: Coords2d,
381    scales_layout: GlobalLayout,
382    #[ruda(comptime)]
383    block_size: Coords2d,
384}
385
386#[ruda]
387impl BlockScaledLayout {
388    pub fn new(
389        shape: Coords2d,
390        scales_layout: GlobalLayout,
391        #[comptime] block_size: Coords2d,
392    ) -> Self {
393        BlockScaledLayout {
394            shape,
395            scales_layout,
396            block_size,
397        }
398    }
399}
400
401#[ruda]
402impl Layout for GlobalScaleLayout {
403    type Coordinates = BatchedCoords;
404    type SourceCoordinates = Coords1d;
405
406    fn to_source_pos(&self, coords: Self::Coordinates) -> usize {
407        match self {
408            GlobalScaleLayout::PerTensor { .. } => 0usize.runtime(),
409            GlobalScaleLayout::BlockScaled(layout) => {
410                let BlockScaledLayout {
411                    scales_layout,
412                    block_size,
413                    ..
414                } = layout;
415
416                let (batch, row, col) = coords;
417                let (block_row, block_col) = block_size;
418                let (row, col) = (row / block_row, col / block_col);
419                scales_layout.to_source_pos((batch, row, col))
420            }
421        }
422    }
423
424    fn to_source_pos_checked(&self, coords: Self::Coordinates) -> (usize, bool) {
425        (self.to_source_pos(coords), self.is_in_bounds(coords))
426    }
427
428    fn shape(&self) -> Self::Coordinates {
429        match self {
430            GlobalScaleLayout::PerTensor { shape } => {
431                (u32::MAX.runtime() as usize, shape.0, shape.1)
432            }
433            GlobalScaleLayout::BlockScaled(layout) => {
434                let (row, col) = layout.shape;
435                (u32::MAX.runtime() as usize, row, col)
436            }
437        }
438    }
439
440    fn is_in_bounds(&self, pos: Self::Coordinates) -> bool {
441        match self {
442            GlobalScaleLayout::PerTensor { .. } => true.runtime(),
443            GlobalScaleLayout::BlockScaled(layout) => {
444                let (_, row, col) = pos;
445                let config = &layout.scales_layout.config.comptime();
446                let (rows, cols) = layout.shape;
447
448                match (config.check_row_bounds, config.check_col_bounds) {
449                    (true, true) => row < rows && col < cols,
450                    (true, false) => row < rows,
451                    (false, true) => col < cols,
452                    (false, false) => true,
453                }
454            }
455        }
456    }
457}
458
459#[derive(RudaType, RudaLaunch)]
460pub struct Transpose<Inner: Layout + LaunchArg> {
461    inner: Inner,
462}
463
464#[ruda]
465impl<Inner: Layout + LaunchArg> Transpose<Inner> {
466    pub fn new(inner: Inner) -> Self {
467        Transpose::<Inner> { inner }
468    }
469}
470
471#[ruda]
472impl<Inner: Layout<Coordinates = BatchedCoords> + LaunchArg> Layout for Transpose<Inner> {
473    type Coordinates = BatchedCoords;
474    type SourceCoordinates = Inner::SourceCoordinates;
475
476    fn to_source_pos(&self, pos: Self::Coordinates) -> Self::SourceCoordinates {
477        let (batch, row, col) = pos;
478        self.inner.to_source_pos((batch, col, row))
479    }
480
481    fn is_in_bounds(&self, pos: Self::Coordinates) -> bool {
482        let (batch, row, col) = pos;
483        self.inner.is_in_bounds((batch, col, row))
484    }
485
486    fn shape(&self) -> Self::Coordinates {
487        let (batches, rows, cols) = self.inner.shape();
488        (batches, cols, rows)
489    }
490
491    fn to_source_pos_checked(&self, pos: Self::Coordinates) -> (Self::SourceCoordinates, bool) {
492        (self.to_source_pos(pos), self.is_in_bounds(pos))
493    }
494}