Skip to main content

cubecl_tile/tile/accumulator/
smem_accumulation.rs

1//! A shared-memory destination planes add into atomically, and a tile that reads the sum back.
2
3use core::marker::PhantomData;
4
5use cubecl::ir::{ExpandValue, VectorSize};
6use cubecl::prelude::*;
7use cubecl::unexpanded;
8use cubecl_std::tensor::{
9    ErasedTensor, ErasedTensorExpand, ErasedTensorOperationsExpand, ReadsLines, WriteOnly,
10    WritesLines,
11};
12
13use crate::*;
14
15/// A cube's shared-memory accumulator over one box, summing in the element it was opened with,
16/// read back at the element of the tile it was opened over.
17#[derive(CubeType)]
18pub struct SmemAccumulation {
19    /// Writes add into the cell atomically.
20    pub sink: Tile,
21    /// Reads the sum back at the opened tile's element; valid only once every plane has drained
22    /// and the cube has synced.
23    pub source: Tile,
24}
25
26#[cube]
27impl Tile {
28    /// A zeroed shared-memory accumulator over this tile's box, summing in `A`, with the cube
29    /// synchronized on both sides of the zeroing.
30    pub fn smem_accumulation<A: Numeric>(&self) -> SmemAccumulation {
31        let define!(Served) = self.element;
32        let space = comptime!(self.place.space.clone());
33        let elem_bytes = A::size().comptime();
34        let form = comptime!(StageForm::dense(
35            &space,
36            1,
37            StageStorage::Strided,
38            LineBytes(elem_bytes)
39        ));
40        let cells = comptime!(form.cells());
41        let values = Shared::<[Atomic<A>]>::new_slice(cells);
42        // The previous open from this call site may still be read; let it finish first.
43        sync_cube();
44        let mut cell = UNIT_POS as usize;
45        while cell < cells {
46            values[cell].store(A::from_int(0));
47            cell += CUBE_DIM as usize;
48        }
49        sync_cube();
50
51        let units = self.units();
52        let sink = Memory::smem_backed::<A>(
53            comptime!(space.clone()),
54            1usize,
55            comptime!(FillUnits::cube(units)),
56            Backing::new_WriteCall(RawCall::of::<A>(
57                ErasedTensor::<A, WriteOnly>::of_smem_accumulate(&values),
58            )),
59            comptime!(Packing::Plain),
60            comptime!(form.clone()),
61            RuntimeMap::integral(comptime!(form.physical_rank())),
62            ComptimeOption::new_None(),
63            comptime!(Write::Accumulate),
64            comptime!(Overhang::Masked),
65        );
66        let source = Memory::smem_backed::<Served>(
67            space,
68            1usize,
69            comptime!(FillUnits::cube(units)),
70            Backing::new_ReadCall(RawCall::of::<Served>(
71                ErasedTensor::<Served, ReadOnly>::of_smem_load::<A>(&values),
72            )),
73            comptime!(Packing::Plain),
74            comptime!(form.clone()),
75            RuntimeMap::integral(comptime!(form.physical_rank())),
76            ComptimeOption::new_None(),
77            comptime!(Write::Replace),
78            comptime!(Overhang::Never),
79        );
80        SmemAccumulation {
81            sink: sink.nested_like(self),
82            source: source.nested_like(self),
83        }
84    }
85}
86
87/// The erased tensors over a shared buffer of atomics.
88pub(crate) trait SmemAccumulateSink<E: Numeric> {
89    /// The sink that adds into `values`, one scalar per line.
90    fn of_smem_accumulate(_values: &Shared<[Atomic<E>]>) -> ErasedTensor<E, WriteOnly> {
91        unexpanded!()
92    }
93
94    fn __expand_of_smem_accumulate(
95        _scope: &Scope,
96        values: &<Shared<[Atomic<E>]> as CubeType>::ExpandType,
97    ) -> ErasedTensorExpand<E, WriteOnly> {
98        ErasedTensorExpand::new(SmemAccumulate::<E> {
99            values: ExpandTypeClone::clone_unchecked(values),
100            _e: PhantomData,
101        })
102    }
103}
104
105impl<E: Numeric> SmemAccumulateSink<E> for ErasedTensor<E, WriteOnly> {}
106
107/// The source that loads out of a buffer of atomics summing in `A`, served as `T`.
108pub(crate) trait SmemLoadSource<T: Numeric> {
109    /// The source that loads `values`, cast to `T`, one scalar per line.
110    fn of_smem_load<A: Numeric>(_values: &Shared<[Atomic<A>]>) -> ErasedTensor<T, ReadOnly> {
111        unexpanded!()
112    }
113
114    fn __expand_of_smem_load<A: Numeric>(
115        _scope: &Scope,
116        values: &<Shared<[Atomic<A>]> as CubeType>::ExpandType,
117    ) -> ErasedTensorExpand<T, ReadOnly> {
118        ErasedTensorExpand::new(SmemLoad::<A, T> {
119            values: ExpandTypeClone::clone_unchecked(values),
120            _t: PhantomData,
121        })
122    }
123}
124
125impl<T: Numeric> SmemLoadSource<T> for ErasedTensor<T, ReadOnly> {}
126
127/// A backing that adds into a shared buffer of `Atomic<E>`. Writes and never reads.
128struct SmemAccumulate<E: Numeric> {
129    values: <Shared<[Atomic<E>]> as CubeType>::ExpandType,
130    _e: PhantomData<E>,
131}
132
133impl<E: Numeric> ErasedTensorOperationsExpand<E> for SmemAccumulate<E> {
134    fn __expand_vector_size_method(&self, _scope: &Scope) -> VectorSize {
135        1
136    }
137
138    fn __expand_lines_method(&self, scope: &Scope) -> NativeExpand<usize> {
139        self.values.__expand_len_method(scope)
140    }
141
142    fn __expand_write_line_method(
143        &mut self,
144        scope: &Scope,
145        index: NativeExpand<usize>,
146        value: ExpandValue,
147    ) {
148        accumulate_cell::expand::<E>(scope, &self.values, index, value.into());
149    }
150}
151
152impl<E: Numeric> WritesLines<E> for SmemAccumulate<E> {}
153
154/// A backing that loads out of a shared buffer of `Atomic<A>`, casting to `T`.
155struct SmemLoad<A: Numeric, T: Numeric> {
156    values: <Shared<[Atomic<A>]> as CubeType>::ExpandType,
157    _t: PhantomData<T>,
158}
159
160impl<A: Numeric, T: Numeric> ErasedTensorOperationsExpand<T> for SmemLoad<A, T> {
161    fn __expand_vector_size_method(&self, _scope: &Scope) -> VectorSize {
162        1
163    }
164
165    fn __expand_lines_method(&self, scope: &Scope) -> NativeExpand<usize> {
166        self.values.__expand_len_method(scope)
167    }
168
169    fn __expand_read_line_method(&self, scope: &Scope, index: NativeExpand<usize>) -> ExpandValue {
170        load_cell::expand::<A, T>(scope, &self.values, index).expand
171    }
172}
173
174impl<A: Numeric, T: Numeric> ReadsLines<T> for SmemLoad<A, T> {}
175
176/// Add one line, one scalar wide, into its cell.
177#[cube]
178fn accumulate_cell<E: Numeric>(
179    values: &Shared<[Atomic<E>]>,
180    index: usize,
181    value: Vector<E, Const<1>>,
182) {
183    values[index].fetch_add(value.extract(0usize));
184}
185
186/// Load one cell as a line one scalar wide, cast to `T`.
187#[cube]
188fn load_cell<A: Numeric, T: Numeric>(
189    values: &Shared<[Atomic<A>]>,
190    index: usize,
191) -> Vector<T, Const<1>> {
192    Vector::new(T::cast_from(values[index].load()))
193}