Skip to main content

cubecl_tile/tile/accumulator/
planes_output.rs

1//! A cube's output as its planes reach it: written by each plane where every plane owns its box of
2//! it, or summed across the planes that share a box before the cube writes it.
3
4use cubecl::prelude::*;
5
6use crate::tile::accumulator::smem_cyclic::SmemCyclicAccumulation;
7use crate::*;
8
9/// The output of one cube, reached by its planes.
10///
11/// Where the partitioning hands every plane a box of the output of its own, a plane's sum drains
12/// into its box. Where it splits an axis the output lacks across the planes, the contracted axis of
13/// a sum they each hold part of, the planes holding the same box drain into a shared-memory
14/// accumulator on the cyclic schedule ([`SmemCyclicAccumulation`]) and [`write`] stores the sum.
15/// The tile says which from the partitioning it was cut by: the caller states nothing.
16///
17/// [`write`]: PlanesOutput::write
18#[derive(CubeType)]
19pub struct PlanesOutput<A: Numeric> {
20    out: Tile,
21    merged: ComptimeOption<SmemCyclicAccumulation<A>>,
22    /// The axes the output lacks that the planes are split along, each with its count of planes.
23    #[cube(comptime)]
24    sharing: Vec<(Axis, usize)>,
25}
26
27#[cube]
28impl Tile {
29    /// This tile, `cube`'s window of an output, as its planes reach it, a sum shared by planes
30    /// added in `A`.
31    pub fn planes_output<A: Numeric>(&self, cube: &Region) -> PlanesOutput<A> {
32        let space = comptime!(self.place.space.clone());
33        let sharing = comptime!(cube.path.planes_along(|axis| !space.contains(axis)));
34        let writers = comptime!(sharing.iter().map(|&(_, count)| count).product::<usize>());
35        let merged = if comptime!(writers == 1) {
36            ComptimeOption::new_None()
37        } else {
38            ComptimeOption::new_Some(self.smem_cyclic_accumulation::<A>(writers))
39        };
40        PlanesOutput::<A> {
41            out: self.clone(),
42            merged,
43            sharing,
44        }
45    }
46}
47
48#[cube]
49impl<A: Numeric> PlanesOutput<A> {
50    /// Drain `partial`, the sum `plane` holds over its box of the output. Where planes share the
51    /// box it is a meeting of the cube: every unit of every plane drains, each with its plane's
52    /// partial.
53    pub fn drain(&self, partial: &Tile, plane: &Region) {
54        #[comptime]
55        match &self.merged {
56            ComptimeOption::None => partial.drained_into(&self.out.at(plane)),
57            ComptimeOption::Some(merged) => {
58                merged.drain_at(partial, plane, PlanesOutput::<A>::writer(self, plane))
59            }
60        }
61    }
62
63    /// Store the sum the planes sharing a box drained into it, once every plane has; nothing where
64    /// each plane drained into its own box.
65    pub fn write(&mut self) {
66        #[comptime]
67        match &self.merged {
68            ComptimeOption::None => {}
69            ComptimeOption::Some(merged) => self.out.copy_from(&merged.source),
70        }
71    }
72
73    /// `plane`'s place among the planes sharing its box.
74    fn writer(&self, plane: &Region) -> usize {
75        let mut writer = 0usize;
76        #[unroll]
77        for i in 0..comptime!(self.sharing.len()) {
78            let (axis, count) = comptime!(self.sharing[i]);
79            writer = writer * count + plane.coord(axis);
80        }
81        writer
82    }
83}