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#[derive(RudaType, RudaLaunch, Clone, Copy)]
21pub struct SimpleTmaGlobalLayout {
22 #[ruda(comptime)]
23 transposed: bool,
24 shape: BatchedCoords,
25}
26
27#[ruda]
28impl SimpleTmaGlobalLayout {
29 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 let batch = select(self.shape.0 == 1, 0, batch);
45 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 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#[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 #[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 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#[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#[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}