Skip to main content

cubek_std/tile/variants/instruction/
register.rs

1use cubecl::prelude::*;
2
3use crate::{
4    MatrixLayout, StageIdent, TileSize,
5    tile::{
6        Plane, RowWise, SharedTile, StridedTile, Tile, TileKind, TileKindExpand, TileScope,
7        mask::Mask,
8        variants::unit::{UnitTile, UnitTileLayout},
9    },
10};
11
12/// Execution mode for the register-resident matmul. Lives at tile level
13/// because the load + execute paths branch on it directly.
14#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
15pub enum ProductType {
16    /// Computes the Tile Matmul as m*n inner products of length k.
17    ///
18    /// Needs Lhs to be row major and Rhs to be col major
19    /// If not the case, tile will be transposed during load
20    Inner,
21    /// Computes the Stage Matmul as the sum of k outer products of size m*n.
22    ///
23    /// Needs Lhs to be col major and Rhs to be row major
24    /// If not the case, tile will be transposed during load
25    Outer,
26}
27
28impl ProductType {
29    pub fn from_layouts(
30        lhs_layout: MatrixLayout,
31        rhs_layout: MatrixLayout,
32        tile_size: TileSize,
33    ) -> Self {
34        let lhs_preferred = match lhs_layout {
35            MatrixLayout::RowMajor => ProductType::Inner,
36            MatrixLayout::ColMajor => ProductType::Outer,
37        };
38        let rhs_preferred = match rhs_layout {
39            MatrixLayout::RowMajor => ProductType::Outer,
40            MatrixLayout::ColMajor => ProductType::Inner,
41        };
42
43        if lhs_preferred == rhs_preferred {
44            lhs_preferred
45        } else if tile_size.m() == 1 {
46            rhs_preferred
47        } else if tile_size.n() == 1 {
48            lhs_preferred
49        } else {
50            // No better solution
51            ProductType::Outer
52        }
53    }
54}
55
56/// Register-resident matmul tile. Built on top of [`UnitTile`] (per-unit full
57/// register copy of the data). Holds only the minimal comptime data the tile
58/// body uses (`tile_size` + `product_type`); rowwise / elementwise ops are
59/// delegated to the inner [`UnitTile`]. The matmul-level configuration lives
60/// in cubek-matmul as `RegisterMatmul`.
61#[derive(CubeType)]
62pub struct RegisterTile<N: Numeric> {
63    pub tile: UnitTile<N>,
64    #[cube(comptime)]
65    pub matrix_layout: MatrixLayout,
66    #[cube(comptime)]
67    pub tile_size: TileSize,
68    #[cube(comptime)]
69    pub product_type: ProductType,
70}
71
72#[cube]
73impl<E: Float> RegisterTile<E> {
74    pub fn row_max(&self, acc: &mut RowWise<E>, base: &RowWise<E>) {
75        self.tile.row_max(acc, base);
76    }
77
78    pub fn row_sum(&self, acc: &mut RowWise<E>) {
79        self.tile.row_sum(acc);
80    }
81
82    pub fn exp_diff(&mut self, rowwise: &RowWise<E>) {
83        self.tile.exp_diff(rowwise);
84    }
85
86    pub fn rowwise_scale(&mut self, scale: &RowWise<E>) {
87        self.tile.rowwise_scale(scale);
88    }
89
90    pub fn scale_and_mask<M: Mask>(&mut self, scale: E, mask: &M) {
91        self.tile.scale_and_mask::<M>(scale, mask);
92    }
93
94    pub fn fill_zero(&mut self) {
95        self.tile.fill_zero();
96    }
97
98    /// Cast-copies this register tile into `dest`. Used by per-variant softmax
99    /// helpers when writing the post-softmax score into a same-storage
100    /// destination.
101    pub fn write_to<Lhs: Float>(&self, dest: &mut RegisterTile<Lhs>) {
102        self.tile.write_to::<Lhs>(&mut dest.tile);
103    }
104}
105
106#[cube]
107impl<A: Numeric> RegisterTile<A> {
108    /// Executes `lhs ยท rhs`, accumulating into `self` via the configured
109    /// inner/outer software product.
110    pub fn mma<L: Numeric, R: Numeric>(&mut self, lhs: &RegisterTile<L>, rhs: &RegisterTile<R>) {
111        register_execute(
112            &lhs.tile.data,
113            &rhs.tile.data,
114            &mut self.tile.data,
115            self.tile_size,
116            self.product_type,
117        );
118    }
119}
120
121#[cube]
122impl<N: Numeric> RegisterTile<N> {
123    /// Copies into the register tile from `source`. Supported sources:
124    /// `Shared` (product-type-aware load) and `None` (zero-init).
125    pub fn copy_from<SE: Numeric, SS: Size, Sc: TileScope>(
126        &mut self,
127        source: &Tile<SE, Sc>,
128        #[comptime] ident: StageIdent,
129    ) {
130        match &source.kind {
131            TileKind::SharedTile(shared) => {
132                register_load_from_shared::<SE, SS, N>(
133                    shared,
134                    &mut self.tile.data,
135                    self.matrix_layout,
136                    self.tile_size,
137                    self.product_type,
138                    ident,
139                );
140            }
141            TileKind::None => {
142                register_load_zeros::<N>(&mut self.tile.data, self.tile_size, ident);
143            }
144            TileKind::Cmma(_)
145            | TileKind::Mma(_)
146            | TileKind::Register(_)
147            | TileKind::PlaneVec(_)
148            | TileKind::Interleaved(_)
149            | TileKind::Unit(_)
150            | TileKind::WhiteboxFragment(_)
151            | TileKind::RowWise(_)
152            | TileKind::Bounce(_)
153            | TileKind::Stage(_)
154            | TileKind::Partition(_)
155            | TileKind::Pipelined(_) => {
156                panic!("RegisterTile::copy_from: unsupported source variant")
157            }
158        }
159    }
160
161    pub fn init_zero(&mut self, #[comptime] ident: StageIdent) {
162        register_load_zeros::<N>(&mut self.tile.data, self.tile_size, ident);
163    }
164}
165
166#[cube]
167impl<Acc: Float> RegisterTile<Acc> {
168    /// Online softmax for the Register variant (legacy direct-register
169    /// attention path). Destination must be another `RegisterTile`.
170    pub fn softmax<Lhs: Float, M: Mask>(
171        &mut self,
172        mask: &M,
173        softmaxed: &mut Tile<Lhs, Plane>,
174        state: &mut (RowWise<Acc>, RowWise<Acc>),
175        head_dim_factor: Acc,
176    ) -> RowWise<Acc> {
177        let num_rows = comptime!(state.0.num_rows);
178        let mut max_buf = RowWise::<Acc>::new_min_value(num_rows);
179        let mut sum_buf = RowWise::<Acc>::new_zero(num_rows);
180
181        self.scale_and_mask::<M>(head_dim_factor, mask);
182        self.row_max(&mut max_buf, &state.0);
183        self.exp_diff(&max_buf);
184        self.row_sum(&mut sum_buf);
185
186        let exp_m_diff = state.0.exp_diff(&max_buf);
187        let new_l = exp_m_diff.mul(&state.1).add(&sum_buf);
188
189        match &mut softmaxed.kind {
190            TileKind::Register(d) => self.write_to::<Lhs>(d),
191            TileKind::Bounce(_) => {
192                panic!("RegisterTile::softmax: Bounce destination not supported")
193            }
194            TileKind::WhiteboxFragment(_) => {
195                panic!("RegisterTile::softmax: WhiteboxFragment destination not supported")
196            }
197            TileKind::Unit(_) => panic!("RegisterTile::softmax: Unit destination not supported"),
198            _ => panic!("RegisterTile::softmax: unsupported softmaxed variant"),
199        }
200
201        RowWise::copy_from(&mut state.0, &max_buf);
202        RowWise::copy_from(&mut state.1, &new_l);
203
204        exp_m_diff
205    }
206}
207
208#[cube]
209pub fn register_allocate_lhs<L: Numeric, Sc: TileScope>(
210    #[comptime] layout: MatrixLayout,
211    #[comptime] tile_size: TileSize,
212    #[comptime] product_type: ProductType,
213) -> Tile<L, Sc> {
214    let m = comptime!(tile_size.m());
215    let k = comptime!(tile_size.k());
216    let inner_layout = comptime!(UnitTileLayout::new(m, k, false));
217    Tile::from_kind(TileKind::new_Register(RegisterTile::<L> {
218        tile: UnitTile::<L>::new(inner_layout),
219        matrix_layout: layout,
220        tile_size,
221        product_type,
222    }))
223}
224
225#[cube]
226pub fn register_allocate_rhs<R: Numeric, Sc: TileScope>(
227    #[comptime] layout: MatrixLayout,
228    #[comptime] tile_size: TileSize,
229    #[comptime] product_type: ProductType,
230) -> Tile<R, Sc> {
231    let n = comptime!(tile_size.n());
232    let k = comptime!(tile_size.k());
233    let inner_layout = comptime!(UnitTileLayout::new(n, k, false));
234    Tile::from_kind(TileKind::new_Register(RegisterTile::<R> {
235        tile: UnitTile::<R>::new(inner_layout),
236        matrix_layout: layout,
237        tile_size,
238        product_type,
239    }))
240}
241
242#[cube]
243pub fn register_allocate_acc<A: Numeric, Sc: TileScope>(
244    #[comptime] layout: MatrixLayout,
245    #[comptime] tile_size: TileSize,
246    #[comptime] product_type: ProductType,
247) -> Tile<A, Sc> {
248    let m = comptime!(tile_size.m());
249    let n = comptime!(tile_size.n());
250    let inner_layout = comptime!(UnitTileLayout::new(m, n, false));
251    Tile::from_kind(TileKind::new_Register(RegisterTile::<A> {
252        tile: UnitTile::<A>::new(inner_layout),
253        matrix_layout: layout,
254        tile_size,
255        product_type,
256    }))
257}
258
259// ===========================================================================
260// Compute: matmul / load / write / zero-init
261// ===========================================================================
262
263pub(crate) const UNROLL: bool = false;
264
265#[cube]
266pub fn register_execute<L: Numeric, R: Numeric, A: Numeric>(
267    lhs: &Array<L>,
268    rhs: &Array<R>,
269    acc: &mut Array<A>,
270    #[comptime] tile_size: TileSize,
271    #[comptime] product_type: ProductType,
272) {
273    let m = tile_size.m();
274    let n = tile_size.n();
275    let k = tile_size.k();
276    match product_type {
277        ProductType::Inner => {
278            inner_product::<L, R, A>(lhs, rhs, acc, m, n, k);
279        }
280        ProductType::Outer => {
281            outer_product::<L, R, A>(lhs, rhs, acc, m, n, k);
282        }
283    }
284}
285
286#[cube]
287fn inner_product<L: Numeric, R: Numeric, A: Numeric>(
288    lhs: &Array<L>,
289    rhs: &Array<R>,
290    acc: &mut Array<A>,
291    #[comptime] m: u32,
292    #[comptime] n: u32,
293    #[comptime] k: u32,
294) {
295    #[unroll(UNROLL)]
296    for m_ in 0..m as usize {
297        #[unroll(UNROLL)]
298        for n_ in 0..n as usize {
299            #[unroll(UNROLL)]
300            for k_ in 0..k as usize {
301                let lhs_elem = A::cast_from(lhs[m_ * k as usize + k_]);
302                let rhs_elem = A::cast_from(rhs[n_ * k as usize + k_]);
303                acc[m_ * n as usize + n_] += lhs_elem * rhs_elem;
304            }
305        }
306    }
307}
308
309#[cube]
310fn outer_product<L: Numeric, R: Numeric, A: Numeric>(
311    lhs: &Array<L>,
312    rhs: &Array<R>,
313    acc: &mut Array<A>,
314    #[comptime] m: u32,
315    #[comptime] n: u32,
316    #[comptime] k: u32,
317) {
318    #[unroll(UNROLL)]
319    for k_ in 0..k as usize {
320        #[unroll(UNROLL)]
321        for m_ in 0..m as usize {
322            let lhs_elem = A::cast_from(lhs[k_ * m as usize + m_]);
323            #[unroll(UNROLL)]
324            for n_ in 0..n as usize {
325                let rhs_elem = A::cast_from(rhs[k_ * n as usize + n_]);
326                acc[m_ * n as usize + n_] += lhs_elem * rhs_elem;
327            }
328        }
329    }
330}
331
332#[cube]
333#[allow(clippy::too_many_arguments)]
334pub fn register_load_from_shared<E: Numeric, ES: Size, N: Numeric>(
335    shared: &SharedTile<E>,
336    arr: &mut Array<N>,
337    #[comptime] matrix_layout: MatrixLayout,
338    #[comptime] tile_size: TileSize,
339    #[comptime] product_type: ProductType,
340    #[comptime] ident: StageIdent,
341) {
342    let shared = shared.view::<ES>();
343    let shared = &shared;
344    let m = tile_size.m();
345    let n = tile_size.n();
346    let k = tile_size.k();
347
348    match ident {
349        StageIdent::Lhs => match product_type {
350            ProductType::Inner => match matrix_layout {
351                MatrixLayout::RowMajor => {
352                    load_plain::<E, ES, N>(shared, arr, m, k);
353                }
354                MatrixLayout::ColMajor => {
355                    load_transposed::<E, ES, N>(shared, arr, k, m);
356                }
357            },
358            ProductType::Outer => match matrix_layout {
359                MatrixLayout::RowMajor => {
360                    load_transposed::<E, ES, N>(shared, arr, m, k);
361                }
362                MatrixLayout::ColMajor => {
363                    load_plain::<E, ES, N>(shared, arr, k, m);
364                }
365            },
366        },
367        StageIdent::Rhs => match product_type {
368            ProductType::Inner => match matrix_layout {
369                MatrixLayout::RowMajor => {
370                    load_transposed::<E, ES, N>(shared, arr, k, n);
371                }
372                MatrixLayout::ColMajor => {
373                    load_plain::<E, ES, N>(shared, arr, n, k);
374                }
375            },
376            ProductType::Outer => match matrix_layout {
377                MatrixLayout::RowMajor => {
378                    load_plain::<E, ES, N>(shared, arr, k, n);
379                }
380                MatrixLayout::ColMajor => {
381                    load_transposed::<E, ES, N>(shared, arr, n, k);
382                }
383            },
384        },
385        StageIdent::Acc => match matrix_layout {
386            MatrixLayout::RowMajor => {
387                load_plain::<E, ES, N>(shared, arr, m, n);
388            }
389            MatrixLayout::ColMajor => {
390                load_transposed::<E, ES, N>(shared, arr, n, m);
391            }
392        },
393        _ => panic!("Invalid ident for Register load"),
394    }
395}
396
397#[cube]
398fn load_plain<E: Numeric, ES: Size, N: Numeric>(
399    tile: &StridedTile<E, ES>,
400    arr: &mut Array<N>,
401    #[comptime] num_segments: u32,
402    #[comptime] segment_size: u32,
403) {
404    let line_size = ES::value() as u32;
405    let num_lines_per_segment = segment_size / line_size;
406
407    #[unroll(UNROLL)]
408    for segment in 0..num_segments {
409        #[unroll(UNROLL)]
410        for line_within_segment in 0..num_lines_per_segment {
411            let line = tile.get_vector(segment, line_within_segment);
412            #[unroll]
413            for pos_within_line in 0..line_size {
414                arr[(segment * segment_size + line_within_segment * line_size + pos_within_line)
415                    as usize] = N::cast_from(line.extract(pos_within_line as usize));
416            }
417        }
418    }
419}
420
421#[cube]
422fn load_transposed<E: Numeric, ES: Size, N: Numeric>(
423    tile: &StridedTile<E, ES>,
424    arr: &mut Array<N>,
425    #[comptime] num_segments: u32,
426    #[comptime] segment_size: u32,
427) {
428    let line_size = ES::value() as u32;
429    let num_lines_per_segment = segment_size / line_size;
430
431    #[unroll(UNROLL)]
432    for segment in 0..num_segments {
433        #[unroll(UNROLL)]
434        for line_within_segment in 0..num_lines_per_segment {
435            let line = tile.get_vector(segment, line_within_segment);
436            #[unroll]
437            for pos_within_line in 0..line_size {
438                arr[((line_within_segment * line_size + pos_within_line) * num_segments + segment)
439                    as usize] = N::cast_from(line.extract(pos_within_line as usize));
440            }
441        }
442    }
443}
444
445#[cube]
446pub fn register_load_zeros<N: Numeric>(
447    arr: &mut Array<N>,
448    #[comptime] tile_size: TileSize,
449    #[comptime] ident: StageIdent,
450) {
451    let size = match ident {
452        StageIdent::Lhs => tile_size.m() * tile_size.k(),
453        StageIdent::Rhs => tile_size.n() * tile_size.k(),
454        StageIdent::Acc | StageIdent::Out => tile_size.m() * tile_size.n(),
455    };
456    for i in 0..size {
457        arr[i as usize] = N::from_int(0);
458    }
459}
460
461#[cube]
462pub fn register_write_to_shared<E: Numeric, ES: Size, A: Numeric>(
463    shared: &mut SharedTile<E>,
464    arr: &Array<A>,
465    #[comptime] tile_size: TileSize,
466) {
467    let mut shared = shared.view::<ES>();
468    let shared = &mut shared;
469    let out_vector_size = shared.container.vector_size().comptime() as u32;
470    let size_mn = tile_size.m() * tile_size.n();
471
472    #[unroll(false)]
473    for i in 0..size_mn / out_vector_size {
474        let offs = shared.stage_offset(i);
475        let mut vector = Vector::<A, ES>::empty();
476        #[unroll]
477        for j in 0..out_vector_size {
478            vector.insert(j as usize, arr[(i * out_vector_size + j) as usize]);
479        }
480        shared.container[offs as usize] = Vector::cast_from(vector);
481    }
482}