cubek_std/tile/variants/
unit.rs1use cubecl::std::tensor::layout::Coords2d;
2use cubecl::{self, prelude::*};
3
4use crate::tile::{
5 LOGIT_MASKED, Plane, RowWise, StridedTile, Tile, TileKind, TileKindExpand,
6 mask::{Mask, MaskExpand},
7 scope::TileScope,
8};
9
10#[derive(CubeType)]
11pub struct UnitTile<E: Numeric> {
12 pub data: Array<E>,
13 #[cube(comptime)]
14 pub layout: UnitTileLayout,
15}
16
17#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
18pub struct UnitTileLayout {
20 pub num_rows: u32,
21 pub num_cols: u32,
22 pub transposed_load: bool,
23}
24
25impl UnitTileLayout {
26 pub const fn new(num_rows: u32, num_cols: u32, transposed_load: bool) -> UnitTileLayout {
27 UnitTileLayout {
28 num_rows,
29 num_cols,
30 transposed_load,
31 }
32 }
33}
34
35#[cube]
36impl<E: Numeric> UnitTile<E> {
37 pub fn new(#[comptime] layout: UnitTileLayout) -> UnitTile<E> {
38 let data = Array::new(comptime!(layout.num_rows * layout.num_cols) as usize);
39 UnitTile::<E> { data, layout }
40 }
41
42 pub fn zero(&mut self) {
43 for i in 0..self.layout.num_rows * self.layout.num_cols {
44 self.data[i as usize] = E::from_int(0);
45 }
46 }
47
48 pub fn get(&self, row: u32, col: u32) -> E {
49 self.data[(row * self.layout.num_cols + col) as usize]
50 }
51
52 pub fn should_mask(&self, local_pos: Coords2d) -> bool {
56 bool::cast_from(self.data[(local_pos.0 * self.layout.num_cols + local_pos.1) as usize])
57 }
58
59 pub fn accumulate(&mut self, row: u32, col: u32, val: E) {
60 self.data[(row * self.layout.num_cols + col) as usize] += val;
61 }
62
63 pub fn rowwise_scale(&mut self, scale: &RowWise<E>) {
64 for r in 0..self.layout.num_rows as usize {
65 let row_offset = r as u32 * self.layout.num_cols;
66 for c in 0..self.layout.num_cols {
67 let index = row_offset + c;
68 self.data[index as usize] *= scale.vals[r];
69 }
70 }
71 }
72
73 pub fn rowwise_max(&self) -> RowWise<E> {
74 let num_rows = comptime!(self.layout.num_rows) as usize;
75 let num_cols = comptime!(self.layout.num_cols) as usize;
76 let mut vals = Array::new(num_rows);
77
78 for r in 0..num_rows {
79 let row_offset = r * num_cols;
80 let mut val = E::min_value();
81
82 for c in 0..num_cols {
83 let index = row_offset + c;
84 val = max(val, self.data[index]);
85 }
86
87 vals[r] = val;
88 }
89
90 RowWise::<E> { num_rows, vals }
91 }
92
93 pub fn rowwise_sum(&self) -> RowWise<E> {
94 let num_rows = comptime!(self.layout.num_rows) as usize;
95 let num_cols = comptime!(self.layout.num_cols) as usize;
96 let mut vals = Array::new(num_rows);
97
98 for r in 0..num_rows {
99 let row_offset = r * num_cols;
100 let mut val = E::from_int(0);
101
102 for c in 0..num_cols {
103 let index = row_offset + c;
104 val += self.data[index];
105 }
106
107 vals[r] = val;
108 }
109
110 RowWise::<E> { num_rows, vals }
111 }
112
113 pub fn copy_from<E2: Numeric>(&mut self, other: &UnitTile<E2>) {
116 for r in 0..self.layout.num_rows as usize {
119 let row_offset = r as u32 * self.layout.num_cols;
120 for c in 0..self.layout.num_cols {
121 let index = row_offset + c;
122 self.data[index as usize] = E::cast_from(other.data[index as usize]);
123 }
124 }
125 }
126
127 pub fn load_from_strided_tile<E2: Numeric, ES: Size>(&mut self, tile: &StridedTile<E2, ES>) {
128 if comptime!(self.layout.transposed_load) {
129 strided_tile_to_transposed_unit_tile(tile, self)
130 } else {
131 strided_tile_to_unit_tile(tile, self)
132 }
133 }
134
135 pub fn scale_and_mask<M: Mask>(&mut self, scale: E, mask: &M) {
136 for r in 0..self.layout.num_rows {
137 let row_offset = r * self.layout.num_cols;
138 for c in 0..self.layout.num_cols {
139 let index = row_offset + c;
140 self.data[index as usize] = self.data[index as usize] * scale
141 + E::cast_from(mask.should_mask((r, c))) * E::min_value();
142 }
143 }
144 }
145}
146
147#[cube]
148impl<E: Float> UnitTile<E> {
149 pub fn row_max(&self, acc: &mut RowWise<E>, base: &RowWise<E>) {
150 acc.copy_from(base);
151 acc.max_inplace(&self.rowwise_max());
152 }
153
154 pub fn row_sum(&self, acc: &mut RowWise<E>) {
155 acc.fill(E::from_int(0));
156 acc.add_inplace(&self.rowwise_sum());
157 }
158
159 pub fn fill_zero(&mut self) {
160 self.zero();
161 }
162
163 pub fn write_to<Lhs: Float>(&self, dest: &mut UnitTile<Lhs>) {
167 let total = comptime!(self.layout.num_rows * self.layout.num_cols);
168 for i in 0..total {
169 dest.data[i as usize] = Lhs::cast_from(self.data[i as usize]);
170 }
171 }
172
173 pub fn exp_diff(&mut self, rowwise: &RowWise<E>) {
174 let num_rows = comptime!(self.layout.num_rows) as usize;
175 let num_cols = comptime!(self.layout.num_cols) as usize;
176 let threshold = E::new(LOGIT_MASKED);
177
178 for r in 0..num_rows {
179 let row_offset = r * num_cols;
180
181 let val = rowwise.vals[r];
182
183 for c in 0..num_cols {
184 let index = row_offset + c;
185
186 let safe_val = clamp_min(val, threshold);
187 let not_masked = E::cast_from(val >= threshold);
188 self.data[index] = not_masked * (self.data[index] - safe_val).exp();
189 }
190 }
191 }
192}
193
194#[cube]
195pub fn allocate_unit_tile<E: Numeric, Sc: TileScope>(
198 #[comptime] layout: UnitTileLayout,
199) -> Tile<E, Sc> {
200 Tile::from_kind(TileKind::new_Unit(UnitTile::<E>::new(layout)))
201}
202
203#[cube]
204impl<Acc: Float> UnitTile<Acc> {
205 pub fn softmax<Lhs: Float, M: Mask>(
209 &mut self,
210 mask: &M,
211 softmaxed: &mut Tile<Lhs, Plane>,
212 state: &mut (RowWise<Acc>, RowWise<Acc>),
213 head_dim_factor: Acc,
214 ) -> RowWise<Acc> {
215 let num_rows = comptime!(state.0.num_rows);
216 let mut max_buf = RowWise::<Acc>::new_min_value(num_rows);
217 let mut sum_buf = RowWise::<Acc>::new_zero(num_rows);
218
219 self.scale_and_mask::<M>(head_dim_factor, mask);
220 self.row_max(&mut max_buf, &state.0);
221 self.exp_diff(&max_buf);
222 self.row_sum(&mut sum_buf);
223
224 let exp_m_diff = state.0.exp_diff(&max_buf);
225 let new_l = exp_m_diff.mul(&state.1).add(&sum_buf);
226
227 match &mut softmaxed.kind {
228 TileKind::Unit(d) => self.write_to::<Lhs>(d),
229 TileKind::Bounce(_) => panic!("UnitTile::softmax: Bounce destination not supported"),
230 TileKind::WhiteboxFragment(_) => {
231 panic!("UnitTile::softmax: WhiteboxFragment destination not supported")
232 }
233 TileKind::Register(_) => {
234 panic!("UnitTile::softmax: Register destination not supported")
235 }
236 _ => panic!("UnitTile::softmax: unsupported softmaxed variant"),
237 }
238
239 RowWise::copy_from(&mut state.0, &max_buf);
240 RowWise::copy_from(&mut state.1, &new_l);
241
242 exp_m_diff
243 }
244}
245
246#[cube]
247fn strided_tile_to_unit_tile<E: Numeric, N: Size, E2: Numeric>(
248 strided_tile: &StridedTile<E, N>,
249 unit_tile: &mut UnitTile<E2>,
250) {
251 let vector_size = N::value().comptime() as u32;
252 assert!(unit_tile.layout.num_cols.is_multiple_of(vector_size));
253
254 let col_iterations = comptime!(unit_tile.layout.num_cols / vector_size);
255
256 for row in 0..unit_tile.layout.num_rows {
257 for col in 0..col_iterations {
258 let line_read = strided_tile.get_vector(row, col);
259 #[unroll]
260 for i in 0..vector_size {
261 unit_tile.data
262 [(row * unit_tile.layout.num_cols + col * vector_size + i) as usize] =
263 E2::cast_from(line_read.extract(i as usize));
264 }
265 }
266 }
267}
268
269#[cube]
270fn strided_tile_to_transposed_unit_tile<E: Numeric, N: Size, E2: Numeric>(
271 strided_tile: &StridedTile<E, N>,
272 unit_tile: &mut UnitTile<E2>,
273) {
274 let vector_size = N::value().comptime() as u32;
275 assert!(unit_tile.layout.num_cols.is_multiple_of(vector_size));
276
277 let input_num_rows = unit_tile.layout.num_cols.comptime();
278 let input_num_cols = unit_tile.layout.num_rows.comptime();
279 let vector_iterations = input_num_cols / vector_size;
280
281 for input_row in 0..input_num_rows {
282 for input_col_vector in 0..vector_iterations {
283 let vector_read = strided_tile.get_vector(input_row, input_col_vector);
284
285 #[unroll]
286 for i in 0..vector_size {
287 unit_tile.data[((input_col_vector + i) * input_num_rows + input_row) as usize] =
288 E2::cast_from(vector_read.extract(i as usize));
289 }
290 }
291 }
292}