1use cubecl;
2use cubecl::{prelude::*, std::tensor::layout::Coords2d};
3
4use crate::tile::LOGIT_MASKED;
5use crate::tile::{
6 Plane, RowWise, StridedTile, Tile, TileKind, TileKindExpand,
7 mask::{Mask, MaskExpand},
8 scope::{TileScope, assert_plane_scope},
9};
10
11#[derive(CubeType)]
12pub struct WhiteboxFragment<E: Numeric> {
15 pub array: Array<E>,
16 #[cube(comptime)]
17 pub layout: WhiteboxFragmentLayout,
18}
19
20#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
21pub enum InnerLayout {
22 Contiguous,
33 SplitRows,
44}
45
46#[cube]
47impl<E: Numeric> WhiteboxFragment<E> {
48 pub fn new(#[comptime] layout: WhiteboxFragmentLayout) -> WhiteboxFragment<E> {
49 let array = Array::new(comptime!(layout.unit_size.0 * layout.unit_size.1) as usize);
50
51 WhiteboxFragment::<E> { array, layout }
52 }
53
54 pub fn zero(&mut self) {
55 for i in 0..self.layout.unit_size.0 * self.layout.unit_size.1 {
56 self.array[i as usize] = E::from_int(0);
57 }
58 }
59
60 pub fn load_from_slice(&mut self, smem_slice: &[E]) {
61 for r in 0..self.layout.unit_size.0 {
62 for c in 0..self.layout.unit_size.1 {
63 let (row, col) = whitebox_fragment_absolute_pos(self.layout, (r, c));
64 let index = row * self.layout.total_size.1 + col;
65
66 self.array[(r * self.layout.unit_size.1 + c) as usize] = smem_slice[index as usize];
67 }
68 }
69 }
70
71 pub fn load_from_strided_tile<E2: Numeric, N: Size>(
72 &mut self,
73 strided_tile: &StridedTile<E2, N>,
74 ) {
75 for r in 0..self.layout.unit_size.0 {
77 for c in 0..self.layout.unit_size.1 {
78 let (row, col) = whitebox_fragment_absolute_pos(self.layout, (r, c));
79 self.array[(r * self.layout.unit_size.1 + c) as usize] =
80 E::cast_from(strided_tile.get_vector(row, col))
81 }
82 }
83 }
84
85 pub fn store_to<F: Float>(&self, smem_slice: &mut [F]) {
86 for r in 0..self.layout.unit_size.0 {
87 for c in 0..self.layout.unit_size.1 {
88 let (row, col) = whitebox_fragment_absolute_pos(self.layout, (r, c));
89 let index = row * self.layout.total_size.1 + col;
90
91 smem_slice[index as usize] =
92 F::cast_from(self.array[(r * self.layout.unit_size.1 + c) as usize]);
93 }
94 }
95 }
96
97 pub fn should_mask(&self, local_pos: Coords2d) -> bool {
101 bool::cast_from(self.array[(local_pos.0 * self.layout.unit_size.1 + local_pos.1) as usize])
102 }
103
104 pub fn rowwise_scale(&mut self, scale: &RowWise<E>) {
105 for r in 0..self.layout.unit_size.0 as usize {
106 let row_offset = r as u32 * self.layout.unit_size.1;
107 for c in 0..self.layout.unit_size.1 {
108 let index = row_offset + c;
109 self.array[index as usize] *= scale.vals[r];
110 }
111 }
112 }
113
114 pub fn rowwise_max(&self) -> RowWise<E> {
115 let num_rows = comptime!(self.layout.unit_size.0) as usize;
116 let num_cols = comptime!(self.layout.unit_size.1) as usize;
117 let mut vals = Array::new(num_rows);
118
119 for r in 0..num_rows {
120 let row_offset = r * num_cols;
121 let mut val = E::min_value();
122
123 for c in 0..num_cols {
124 let index = row_offset + c;
125 val = max(val, self.array[index]);
126 }
127
128 vals[r] = val;
129 }
130
131 RowWise::<E> { num_rows, vals }
132 }
133
134 pub fn rowwise_sum(&self) -> RowWise<E> {
135 let num_rows = comptime!(self.layout.unit_size.0) as usize;
136 let num_cols = comptime!(self.layout.unit_size.1) as usize;
137 let mut vals = Array::new(num_rows);
138
139 for r in 0..num_rows {
140 let row_offset = r * num_cols;
141 let mut val = E::from_int(0);
142
143 for c in 0..num_cols {
144 let index = row_offset + c;
145 val += self.array[index];
146 }
147
148 vals[r] = val;
149 }
150
151 RowWise::<E> { num_rows, vals }
152 }
153
154 pub fn num_units_per_row(&self) -> comptime_type!(u32) {
155 comptime!(self.layout.total_size.1 / self.layout.unit_size.1)
156 }
157
158 pub fn scale_and_mask<M: Mask>(&mut self, scale: E, mask: &M) {
159 for r in 0..self.layout.unit_size.0 {
160 let row_offset = r * self.layout.unit_size.1;
161 for c in 0..self.layout.unit_size.1 {
162 let index = row_offset + c;
163 self.array[index as usize] = self.array[index as usize] * scale
164 + E::cast_from(mask.should_mask((r, c))) * E::min_value();
165 }
166 }
167 }
168}
169
170#[cube]
171impl<E: Float> WhiteboxFragment<E> {
172 pub fn exp_diff(&mut self, rowwise: &RowWise<E>) {
173 let num_rows = comptime!(self.layout.unit_size.0) as usize;
174 let num_cols = comptime!(self.layout.unit_size.1) as usize;
175 let threshold = E::new(LOGIT_MASKED);
176
177 for r in 0..num_rows {
178 let row_offset = r * num_cols;
179
180 let val = rowwise.vals[r];
181 let safe_val = clamp_min(val, threshold);
182 let not_masked = E::cast_from(val >= threshold);
183
184 for c in 0..num_cols {
185 let index = row_offset + c;
186
187 self.array[index] = not_masked * (self.array[index] - safe_val).exp();
188 }
189 }
190 }
191}
192
193#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
194pub struct WhiteboxFragmentLayout {
195 pub total_size: Coords2d,
196 pub unit_size: Coords2d,
197 pub num_units_per_row: u32,
198 pub plane_dim: u32,
199}
200
201impl WhiteboxFragmentLayout {
202 pub const fn new(
203 total_size: Coords2d,
204 plane_dim: u32,
205 inner_layout: InnerLayout,
206 ) -> WhiteboxFragmentLayout {
207 let total_elements = total_size.0 * total_size.1;
208 let elements_per_unit = total_elements.div_ceil(plane_dim);
209
210 let (num_rows_per_unit, num_cols_per_unit) = match inner_layout {
211 InnerLayout::Contiguous => (1u32, elements_per_unit),
212 InnerLayout::SplitRows => (2u32, elements_per_unit / 2u32),
213 };
214 let unit_size = (num_rows_per_unit, num_cols_per_unit);
215 let num_units_per_row = total_size.1 / unit_size.1;
216
217 WhiteboxFragmentLayout {
218 total_size,
219 unit_size,
220 num_units_per_row,
221 plane_dim,
222 }
223 }
224
225 pub const fn num_units_per_row(&self) -> u32 {
226 self.total_size.1 / self.unit_size.1
227 }
228}
229
230#[cube]
231pub fn allocate_whitebox_fragment<E: Numeric, Sc: TileScope>(
234 #[comptime] layout: WhiteboxFragmentLayout,
235) -> Tile<E, Sc> {
236 comptime!(assert_plane_scope(Sc::KIND));
237 Tile::from_kind(TileKind::new_WhiteboxFragment(WhiteboxFragment::<E>::new(
238 layout,
239 )))
240}
241
242#[cube]
245pub fn whitebox_fragment_absolute_pos(
246 #[comptime] layout: WhiteboxFragmentLayout,
247 local_pos: Coords2d,
248) -> Coords2d {
249 let abs_row_index = {
250 let row_0 = UNIT_POS_X / layout.num_units_per_row;
251 let row_jump = comptime!(layout.plane_dim / layout.num_units_per_row);
252 local_pos.0 * row_jump + row_0
253 };
254 let abs_col_index = layout.unit_size.1 * (UNIT_POS_X % layout.num_units_per_row) + local_pos.1;
255 (abs_row_index, abs_col_index)
256}
257
258#[cube]
260pub fn whitebox_fragment_zero_slice<E: Numeric>(
261 #[comptime] layout: WhiteboxFragmentLayout,
262 slice: &mut [E],
263) {
264 for r in 0..layout.unit_size.0 {
265 for c in 0..layout.unit_size.1 {
266 let (row, col) = whitebox_fragment_absolute_pos(layout, (r, c));
267 let index = row * layout.total_size.1 + col;
268
269 slice[index as usize] = E::from_int(0);
270 }
271 }
272}
273
274#[cube]
283impl<E: Float> WhiteboxFragment<E> {
284 pub fn row_max(&self, acc: &mut RowWise<E>, base: &RowWise<E>) {
285 acc.copy_from(base);
286 reduce::<E, FragmentRowMax>(acc, self);
287 }
288
289 pub fn row_sum(&self, acc: &mut RowWise<E>) {
290 acc.fill(E::from_int(0));
291 reduce::<E, FragmentRowSum>(acc, self);
292 }
293}
294
295#[cube]
296fn reduce<E: Float, RO: ReduceOp<E>>(vals: &mut RowWise<E>, data: &WhiteboxFragment<E>) {
297 let num_units_per_row = data.num_units_per_row().comptime();
298 let num_shares_within_plane = num_units_per_row.next_power_of_two().ilog2();
299
300 let unit_pos = UNIT_POS_X;
301 let unit_pos_in_row = unit_pos % num_units_per_row;
302
303 RO::reduce_local(data, vals);
304
305 for i in 0..num_shares_within_plane {
306 let offset = num_units_per_row >> (i + 1);
307 let source_unit = unit_pos + offset;
308
309 let value_from_source = rowwise_plane_broadcast(&*vals, source_unit);
310
311 let mask = unit_pos_in_row + offset >= num_units_per_row;
313 RO::reduce_from_peer(vals, &value_from_source, mask);
314 }
315
316 let result = rowwise_plane_broadcast(&*vals, unit_pos - unit_pos_in_row);
318 vals.copy_from(&result);
319}
320
321#[cube]
322fn rowwise_plane_broadcast<E: Float>(rowwise: &RowWise<E>, source_unit: u32) -> RowWise<E> {
323 let mut result = Array::new(rowwise.num_rows);
324
325 for r in 0..rowwise.num_rows {
326 result[r] = plane_shuffle(rowwise.vals[r], source_unit);
327 }
328
329 RowWise::<E> {
330 num_rows: rowwise.num_rows,
331 vals: result,
332 }
333}
334
335#[cube]
336trait ReduceOp<E: Float> {
337 fn reduce_local(data: &WhiteboxFragment<E>, acc: &mut RowWise<E>);
338 fn reduce_from_peer(acc: &mut RowWise<E>, elem: &RowWise<E>, mask: bool);
339}
340
341#[derive(CubeType)]
342struct FragmentRowMax {}
343
344#[derive(CubeType)]
345struct FragmentRowSum {}
346
347#[cube]
348impl<E: Float> ReduceOp<E> for FragmentRowMax {
349 fn reduce_local(data: &WhiteboxFragment<E>, acc: &mut RowWise<E>) {
350 acc.max_inplace(&data.rowwise_max())
351 }
352
353 fn reduce_from_peer(acc: &mut RowWise<E>, elem: &RowWise<E>, mask: bool) {
354 let mut masked = RowWise::new_filled(elem.num_rows, E::cast_from(mask) * E::min_value());
355 masked.add_inplace(elem);
356
357 acc.max_inplace(&masked)
358 }
359}
360
361#[cube]
362impl<E: Float> ReduceOp<E> for FragmentRowSum {
363 fn reduce_local(data: &WhiteboxFragment<E>, acc: &mut RowWise<E>) {
364 acc.add_inplace(&data.rowwise_sum())
365 }
366
367 fn reduce_from_peer(acc: &mut RowWise<E>, elem: &RowWise<E>, mask: bool) {
368 let mut masked = RowWise::new_filled(elem.num_rows, E::cast_from(!mask));
369 masked.mul_inplace(elem);
370
371 acc.add_inplace(&masked)
372 }
373}
374
375#[cube]
380impl<Acc: Float> WhiteboxFragment<Acc> {
381 pub fn softmax<Lhs: Float, M: Mask>(
386 &mut self,
387 mask: &M,
388 softmaxed: &mut Tile<Lhs, Plane>,
389 state: &mut (RowWise<Acc>, RowWise<Acc>),
390 head_dim_factor: Acc,
391 ) -> RowWise<Acc> {
392 let num_rows = comptime!(state.0.num_rows);
393 let mut max_buf = RowWise::<Acc>::new_min_value(num_rows);
394 let mut sum_buf = RowWise::<Acc>::new_zero(num_rows);
395
396 self.scale_and_mask::<M>(head_dim_factor, mask);
397 self.row_max(&mut max_buf, &state.0);
398 self.exp_diff(&max_buf);
399 self.row_sum(&mut sum_buf);
400
401 let exp_m_diff = state.0.exp_diff(&max_buf);
402 let new_l = exp_m_diff.mul(&state.1).add(&sum_buf);
403
404 write_fragment_into::<Acc, Lhs>(&*self, softmaxed);
405
406 RowWise::copy_from(&mut state.0, &max_buf);
407 RowWise::copy_from(&mut state.1, &new_l);
408
409 exp_m_diff
410 }
411}
412
413#[cube]
415fn write_fragment_into<Acc: Float, Lhs: Float>(
416 src: &WhiteboxFragment<Acc>,
417 softmaxed: &mut Tile<Lhs, Plane>,
418) {
419 match &mut softmaxed.kind {
420 TileKind::Bounce(d) => {
421 let stride = comptime!(d.cmma.tile_size.n());
422 src.store_to(&mut d.smem);
423 sync_cube();
424 cubecl::cmma::load(&mut d.cmma.matrix, &d.smem, stride);
425 }
426 TileKind::WhiteboxFragment(d) => {
427 let total = comptime!(src.layout.unit_size.0 * src.layout.unit_size.1);
428 for i in 0..total {
429 d.array[i as usize] = Lhs::cast_from(src.array[i as usize]);
430 }
431 }
432 _ => panic!("write_fragment_into: unsupported softmaxed variant"),
433 }
434}