1use cubecl::prelude::*;
4
5use crate::{Axis, Carrier, ComputeScope, Count, Coverage, Level, Monoid, Space};
6
7#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
9pub enum UnitShare {
10 Repeated,
12 Whole,
14 Plane,
16 Group { unit_bits: usize },
19}
20
21impl UnitShare {
22 pub(crate) fn new(level: &Level, spanned: &Space) -> UnitShare {
24 if level.coverage() != Coverage::Distribute(ComputeScope::Unit) {
25 return UnitShare::Repeated;
26 }
27 if level.unit_cube().is_some() {
29 return UnitShare::Whole;
30 }
31 let (mut weight, mut unit_bits) = (1usize, 0usize);
33 for axis in level.axes().into_iter().rev() {
34 if let Count::Distributed(_) = level.cut(axis).count {
35 match spanned.contains(axis) {
36 true => continue,
37 false => return UnitShare::Plane,
38 }
39 }
40 let plane_units = level
41 .cut(axis)
42 .count
43 .stated()
44 .expect("Level::new: a units level carries stated counts");
45 if plane_units == 1 {
46 continue;
47 }
48 assert!(
49 plane_units.is_power_of_two(),
50 "UnitShare: {axis:?} rides {plane_units} units, which is not a power of two, so its \
51 partials are not a bit range"
52 );
53 if !spanned.contains(axis) {
54 unit_bits |= (plane_units - 1) * weight;
55 }
56 weight *= plane_units;
57 }
58 match unit_bits {
59 0 if weight == 1 => UnitShare::Repeated,
60 0 => UnitShare::Whole,
61 mask if mask == weight - 1 => UnitShare::Plane,
62 unit_bits => UnitShare::Group { unit_bits },
63 }
64 }
65
66 pub(crate) fn under(self, parent: UnitShare) -> UnitShare {
68 match (parent, self) {
69 (UnitShare::Repeated, share) | (share, UnitShare::Repeated) => share,
70 (UnitShare::Whole, share) | (share, UnitShare::Whole) => share,
71 (UnitShare::Group { unit_bits: a }, UnitShare::Group { unit_bits: b }) => {
72 UnitShare::Group { unit_bits: a | b }
73 }
74 _ => {
75 panic!("UnitShare::under: {self:?} under {parent:?}: nothing reduces under a plane")
76 }
77 }
78 }
79
80 pub(crate) fn reduces(self) -> bool {
82 matches!(self, UnitShare::Plane | UnitShare::Group { .. })
83 }
84}
85
86#[cube]
87impl UnitShare {
88 pub(crate) fn reduce_of<T: Carrier + CubePrimitive<Scalar: PlaneNumeric>>(
90 value: T,
91 #[comptime] share: UnitShare,
92 #[comptime] monoid: Monoid,
93 ) -> T {
94 match comptime!(share) {
95 UnitShare::Repeated | UnitShare::Whole => value,
96 UnitShare::Plane => match comptime!(monoid) {
97 Monoid::Sum => plane_sum(value),
98 Monoid::Prod => plane_prod(value),
99 Monoid::Max => plane_max(value),
100 Monoid::Min => plane_min(value),
101 },
102 UnitShare::Group { unit_bits } => {
103 let mut total = value;
104 #[unroll]
105 for bit in 0..comptime!(usize::BITS - unit_bits.leading_zeros()) {
106 if comptime!(unit_bits & (1 << bit) != 0) {
107 total = monoid
108 .combine::<T>(total, plane_shuffle_xor(total, comptime!(1u32 << bit)));
109 }
110 }
111 total
112 }
113 }
114 }
115}
116
117impl UnitShare {
118 pub fn reduce<T: Carrier + CubePrimitive<Scalar: PlaneNumeric>>(
120 self,
121 value: T,
122 monoid: Monoid,
123 ) -> T {
124 UnitShare::reduce_of::<T>(value, self, monoid)
125 }
126
127 pub fn __expand_reduce_method<T: Carrier + CubePrimitive<Scalar: PlaneNumeric>>(
128 self,
129 scope: &Scope,
130 value: T::ExpandType,
131 monoid: Monoid,
132 ) -> T::ExpandType {
133 UnitShare::__expand_reduce_of::<T>(scope, value, self, monoid)
134 }
135}
136
137#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
140pub(crate) enum SplitShare {
141 Whole,
143 PartialAcrossCubes,
145 PartialAcrossPlanes,
147}
148
149impl SplitShare {
150 pub(crate) fn new(level: &Level, space: &Space, spanned: &Space) -> SplitShare {
153 match level.coverage() {
154 Coverage::Walk | Coverage::Distribute(ComputeScope::Unit) => return SplitShare::Whole,
155 Coverage::Distribute(ComputeScope::Plane)
156 | Coverage::Distribute(ComputeScope::PlaneGroup { .. })
157 | Coverage::Distribute(ComputeScope::Cube) => {}
158 }
159 let split = match level.shared_by() {
161 Some(_) => level.axes().iter().any(|axis| !spanned.contains(*axis)),
162 None => level.axes().into_iter().any(|axis: Axis| {
163 !spanned.contains(axis) && level.instances_along(space, axis) != Some(1)
164 }),
165 };
166 match (split, level.coverage()) {
167 (false, _) => SplitShare::Whole,
168 (true, Coverage::Distribute(ComputeScope::Plane | ComputeScope::PlaneGroup { .. })) => {
169 SplitShare::PartialAcrossPlanes
170 }
171 (true, _) => SplitShare::PartialAcrossCubes,
172 }
173 }
174
175 pub(crate) fn under(self, parent: SplitShare) -> SplitShare {
178 match (parent, self) {
179 (SplitShare::PartialAcrossPlanes, _) | (_, SplitShare::PartialAcrossPlanes) => {
180 SplitShare::PartialAcrossPlanes
181 }
182 (SplitShare::PartialAcrossCubes, _) | (_, SplitShare::PartialAcrossCubes) => {
183 SplitShare::PartialAcrossCubes
184 }
185 (SplitShare::Whole, SplitShare::Whole) => SplitShare::Whole,
186 }
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193 use crate::{Levels, Space};
194
195 const M: Axis = Axis(0);
196 const N: Axis = Axis(1);
197 const K: Axis = Axis(2);
198
199 #[test]
201 fn a_cube_cut_contraction_is_partial_to_the_output() {
202 let space = Space::new(&[(M, 4), (N, 4), (K, 8)]);
203 let level = Levels::leaf(&[(K, 4)]).cubes(&[K]).level();
204 assert_eq!(
205 SplitShare::new(&level, &space, &space.subspace(&[M, N])),
206 SplitShare::PartialAcrossCubes
207 );
208 assert_eq!(
209 SplitShare::new(&level, &space, &space.subspace(&[M, K])),
210 SplitShare::Whole
211 );
212 }
213
214 #[test]
216 fn a_plane_cut_contraction_is_partial_to_the_output() {
217 let space = Space::new(&[(M, 4), (N, 4), (K, 8)]);
218 let level = Levels::leaf(&[(K, 4)]).planes(&[(K, 2)]).level();
219 assert_eq!(
220 SplitShare::new(&level, &space, &space.subspace(&[M, N])),
221 SplitShare::PartialAcrossPlanes
222 );
223 }
224
225 #[test]
227 fn a_shared_grid_is_partial_to_the_output() {
228 let space = Space::new(&[(M, 8), (N, 8), (K, 8)]);
229 let level = Levels::leaf(&[(M, 4), (N, 4), (K, 8)])
230 .cubes(&[M, N, K])
231 .shared_by(3)
232 .level();
233 assert_eq!(
234 SplitShare::new(&level, &space, &space.subspace(&[M, N])),
235 SplitShare::PartialAcrossCubes
236 );
237 assert_eq!(SplitShare::new(&level, &space, &space), SplitShare::Whole);
238 }
239
240 #[test]
242 fn a_cube_cut_of_the_whole_axis_is_not_a_split() {
243 let space = Space::new(&[(M, 4), (N, 4), (K, 8)]);
244 let level = Levels::leaf(&[(N, 1), (K, 8)]).cubes(&[N, K]).level();
245 assert_eq!(
246 SplitShare::new(&level, &space, &space.subspace(&[M, N])),
247 SplitShare::Whole
248 );
249 }
250
251 #[test]
253 fn a_cube_cut_output_axis_stays_whole() {
254 let space = Space::new(&[(M, 4), (N, 8), (K, 4)]);
255 let level = Levels::leaf(&[(N, 4)]).cubes(&[N]).level();
256 assert_eq!(
257 SplitShare::new(&level, &space, &space.subspace(&[M, N])),
258 SplitShare::Whole
259 );
260 }
261
262 #[test]
264 fn a_units_level_reduces_what_the_operand_does_not_span() {
265 let space = Space::new(&[(M, 4), (N, 8), (K, 32)]);
266 let out = space.subspace(&[M, N]);
267 let plane = Levels::leaf(&[(K, 1)]).units(&[(K, 32)]).level();
268 assert_eq!(UnitShare::new(&plane, &out), UnitShare::Plane);
269 let whole = Levels::leaf(&[(N, 1)]).units(&[(N, 8)]).level();
270 assert_eq!(UnitShare::new(&whole, &out), UnitShare::Whole);
271 let team = Levels::leaf(&[(N, 1), (K, 1)])
272 .units(&[(N, 8), (K, 4)])
273 .level();
274 assert_eq!(
275 UnitShare::new(&team, &out),
276 UnitShare::Group { unit_bits: 3 }
277 );
278 let walk = Levels::leaf(&[(K, 8)]).walk_every(&[K]).level();
279 assert_eq!(UnitShare::new(&walk, &out), UnitShare::Repeated);
280 }
281
282 #[test]
284 fn shares_compose_level_by_level() {
285 let group = UnitShare::Group { unit_bits: 3 };
286 assert_eq!(group.under(UnitShare::Repeated), group);
287 assert_eq!(UnitShare::Whole.under(group), group);
288 assert_eq!(
289 UnitShare::Group { unit_bits: 12 }.under(group),
290 UnitShare::Group { unit_bits: 15 }
291 );
292 assert_eq!(
293 UnitShare::Repeated.under(UnitShare::Whole),
294 UnitShare::Whole
295 );
296 assert_eq!(
297 SplitShare::Whole.under(SplitShare::PartialAcrossCubes),
298 SplitShare::PartialAcrossCubes
299 );
300 assert_eq!(
302 SplitShare::PartialAcrossPlanes.under(SplitShare::PartialAcrossCubes),
303 SplitShare::PartialAcrossPlanes
304 );
305 assert_eq!(
306 SplitShare::PartialAcrossCubes.under(SplitShare::PartialAcrossPlanes),
307 SplitShare::PartialAcrossPlanes
308 );
309 }
310}