Skip to main content

cubecl_tile/ops/
product.rs

1//! `dst.product(&a, &b)`: the elementwise product of two tiles, each broadcasting over the axes it
2//! omits.
3
4use cubecl::prelude::*;
5
6use cubecl_std::tensor::layout::CoordsDyn;
7
8use crate::ops::contraction::resolve_nd_coords;
9use crate::*;
10
11#[cube]
12impl Tile {
13    /// `dst = a ⊗ b`, elementwise over the destination's box.
14    pub fn product(&mut self, a: &Tile, b: &Tile) {
15        let define!(Served) = self.element;
16        let define!(Lhs) = a.read_element();
17        let define!(Rhs) = b.read_element();
18        self.mul_from::<Served, Lhs, Rhs>(a, b)
19    }
20
21    /// The transport: every unit of the cube strides the destination's groups.
22    fn mul_from<D: Numeric, A: Numeric, B: Numeric>(&mut self, a: &Tile, b: &Tile) {
23        let space = comptime!(self.place.space.clone());
24        let width = self.vector_size();
25        let per_read = b.vector_size();
26        let split = comptime!(GroupWalk::of(&space, &b.place.space, width, per_read));
27        let size!(W) = width;
28        let size!(F) = per_read;
29        let a_reader = a.nd_split::<A, W>();
30        let b_reader = b.nd_split::<B, F>();
31
32        let a_step_at = comptime!(a.place.space.position(split.axis));
33        let extents = Coords::constant(comptime!(split.groups.clone()));
34        let total = comptime!(split.groups.iter().product::<usize>());
35
36        let mut dst = self.nd_mut::<D, W>();
37        let workers = CUBE_DIM as usize;
38        let mut i = UNIT_POS as usize;
39        while i < total {
40            let group = group_line(&extents.unravel(i.cast::<u32>()), comptime!(split.clone()));
41            let (a_base, b_base) = bases(
42                &group,
43                comptime!(space.clone()),
44                comptime!(a.place.space.clone()),
45                comptime!(b.place.space.clone()),
46                width,
47                per_read,
48            );
49
50            // The step is a unit of `b`'s read, so its address is the group's.
51            let scales = b_reader
52                .view
53                .read(b_reader.map.anchor(b_base, comptime!(Vec::new())));
54            // `a`'s address moves with the step: its map is evaluated once here, each step an addition.
55            let moving = comptime!(vec![split.axis]);
56            let a_anchor = a_reader
57                .map
58                .anchor(a_base.clone(), comptime!(moving.clone()));
59
60            #[unroll]
61            for f in 0..comptime!(split.per_read) {
62                let value = a_reader.view.read(a_reader.map.advance(
63                    &a_anchor,
64                    stepped(&a_base, comptime!(a_step_at), f),
65                    comptime!(moving.clone()),
66                ));
67                let left = Vector::<D, W>::cast_from(value);
68                let right = Vector::<D, W>::cast_from(scales.extract(f));
69                dst.write(stepped(&group, comptime!(split.at), f), left * right);
70            }
71            i += workers;
72        }
73    }
74}
75
76/// How a product's walk divides: one broadcast-operand read per group, its values stepped inside it.
77#[derive(Clone, Debug)]
78struct GroupWalk {
79    /// The axis a read's values step: the one the broadcast operand's own lines run along.
80    axis: Axis,
81    /// Its position in the destination's box.
82    at: usize,
83    /// Values one read of that operand serves.
84    per_read: usize,
85    /// The destination's box in lines, cut at [`at`](Self::at) into groups of one read.
86    groups: Vec<usize>,
87}
88
89impl GroupWalk {
90    /// How `dst` divides against `b`, read `width` lines wide with `per_read` values a read.
91    fn of(dst: &Space, b: &Space, width: usize, per_read: usize) -> Self {
92        let rank = dst.rank();
93        let axis = b.axis_at(b.rank() - 1);
94        let at = dst.position(axis);
95        assert!(
96            per_read == 1 || at != rank - 1,
97            "Tile::mul: {per_read} of `b` arrive per read along {axis:?}, which is also the axis the \
98             destination's own lines run along, so one line would straddle two of them"
99        );
100        let mut groups = line_extents(dst, width, 0, rank);
101        assert!(
102            groups[at].is_multiple_of(per_read),
103            "Tile::mul: {} positions of {axis:?} do not divide into reads of {per_read}",
104            groups[at]
105        );
106        groups[at] /= per_read;
107        GroupWalk {
108            axis,
109            at,
110            per_read,
111            groups,
112        }
113    }
114}
115
116/// A group's line coordinate in the destination's box.
117#[cube]
118fn group_line(at: &Coords<u32>, #[comptime] split: GroupWalk) -> CoordsDyn {
119    let mut out = CoordsDyn::new();
120    #[unroll]
121    for p in 0..comptime!(split.groups.len()) {
122        let coord = match comptime!(p == split.at) {
123            true => at.at(p).times(comptime!(split.per_read as u32)),
124            false => at.at(p),
125        };
126        out.push(coord);
127    }
128    out
129}
130
131/// Each operand's own coordinate for a group; an axis it does not span drops out.
132#[cube]
133fn bases(
134    group: &CoordsDyn,
135    #[comptime] dst: Space,
136    #[comptime] a: Space,
137    #[comptime] b: Space,
138    #[comptime] width: usize,
139    #[comptime] per_read: usize,
140) -> (CoordsDyn, CoordsDyn) {
141    let mut cells = Coords::<u32>::new();
142    #[unroll]
143    for p in 0..comptime!(dst.rank()) {
144        cells.push(group[p].times(comptime!(match p == dst.rank() - 1 {
145            true => width as u32,
146            false => 1u32,
147        })));
148    }
149    let empty = Coords::<u32>::new();
150    let no_reduce = comptime!(Vec::<Axis>::new());
151    (
152        resolve_nd_coords(&a, &dst, &no_reduce, &cells, &empty, width, true),
153        resolve_nd_coords(&b, &dst, &no_reduce, &cells, &empty, per_read, true),
154    )
155}
156
157/// `base` with the stepped axis advanced by `f`: the one component a step moves.
158#[cube]
159fn stepped(base: &CoordsDyn, #[comptime] at: usize, #[comptime] f: usize) -> CoordsDyn {
160    let mut out = CoordsDyn::new();
161    #[unroll]
162    for p in 0..comptime!(base.len()) {
163        let coord = match comptime!(p == at) {
164            true => base[p] + comptime!(f as u32),
165            false => base[p],
166        };
167        out.push(coord);
168    }
169    out
170}