1use cubecl::prelude::*;
4
5use crate::*;
6
7#[allow(clippy::large_enum_variant)]
9#[expect(
10 dead_code,
11 reason = "built through the expand type's generated constructors"
12)]
13#[derive(CubeType, Clone)]
14#[expand(derive(Clone))]
15pub(crate) enum AxisSlicesKind {
16 Registers(RegisterData),
18 Window(Memory),
21 Fragments(PlanePartition),
25 Fragment(PlaneTile),
27}
28
29#[derive(CubeType)]
36pub struct AxisSlices {
37 kind: AxisSlicesKind,
38 #[cube(comptime)]
39 space: Space,
40 #[cube(comptime)]
41 axis: Axis,
42}
43
44#[cube]
45impl Tile {
46 pub fn along(&self, #[comptime] axis: Axis) -> AxisSlices {
50 let holder = comptime!(self.place.holder());
51 let space = comptime!(self.place.space.clone());
52 comptime!(assert!(
53 space.axis_at(space.rank() - 1) == axis,
54 "Tile::along: a slice runs along the tile's innermost axis, and {axis:?} is not the \
55 innermost of {space:?}"
56 ));
57 let kind = match &self.kind {
58 TileKind::PlanePartition(partition) => partition.slices(),
59 TileKind::PlaneTile(tile) => tile.slices(),
60 TileKind::Memory(window) => {
61 comptime!(assert!(
62 holder == ComputeScope::Plane,
63 "Tile::along: a window of shared memory is read slice by slice by the plane \
64 that holds it; this one is held by {holder:?}"
65 ));
66 AxisSlicesKind::new_Window(window.clone())
67 }
68 TileKind::TmaGmem(_) | TileKind::Procedural(_) | TileKind::Lines(_) => panic!(
69 "Tile::along: a register block, a plane's fragments, or a plane's window holds \
70 slices"
71 ),
72 };
73 AxisSlices { kind, space, axis }
74 }
75}
76
77#[cube]
78impl Tile {
79 pub fn slices_per_unit(&self, #[comptime] axis: Axis) -> comptime_type!(usize) {
82 let define!(Served) = self.element;
83 let every = comptime!(self.place.space.slices_along(axis));
84 match &self.kind {
85 TileKind::PlanePartition(partition) => match partition.at(0usize, 0usize) {
86 PlaneTile::Mma(fragment) => {
87 let rows = fragment.rows_per_unit::<Served>();
88 comptime!(partition.m_tiles * rows)
89 }
90 PlaneTile::Cmma(_) | PlaneTile::Registers(_) => comptime!(every),
91 PlaneTile::Wgmma(_) => panic!(
92 "Tile::slices_per_unit: a warpgroup accumulator's slices are not read in \
93 place; drain it first"
94 ),
95 },
96 TileKind::PlaneTile(tile) => match tile {
97 PlaneTile::Mma(fragment) => fragment.rows_per_unit::<Served>(),
98 PlaneTile::Cmma(_) | PlaneTile::Registers(_) => comptime!(every),
99 PlaneTile::Wgmma(_) => panic!(
100 "Tile::slices_per_unit: a warpgroup accumulator's slices are not read in \
101 place; drain it first"
102 ),
103 },
104 TileKind::Memory(_)
105 | TileKind::TmaGmem(_)
106 | TileKind::Procedural(_)
107 | TileKind::Lines(_) => comptime!(every),
108 }
109 }
110}
111
112#[cube]
113impl AxisSlices {
114 pub(crate) fn scale_add<E: Float>(&mut self, scale: E, bias: &Tile) {
117 let recipe = bias.recipe();
118 match &mut self.kind {
119 AxisSlicesKind::Registers(block) => {
120 block.scale_add_along::<E>(scale, recipe, self.space.clone())
121 }
122 AxisSlicesKind::Window(window) => {
123 window.scale_add_along::<E>(scale, recipe, self.space.clone(), self.axis)
124 }
125 AxisSlicesKind::Fragments(partition) => {
126 partition.scale_add_along::<E>(scale, recipe, self.space.clone())
127 }
128 AxisSlicesKind::Fragment(tile) => {
129 let mut fragment = tile.manual_fragment();
130 fragment.scale_add_along::<E>(scale, recipe, self.space.clone(), (0usize, 0usize))
131 }
132 }
133 }
134
135 pub(crate) fn maxima<E: Float>(&self, seed: &Array<E>) -> Array<E> {
137 match &self.kind {
138 AxisSlicesKind::Registers(block) => block.maxima_along::<E>(seed),
139 AxisSlicesKind::Window(window) => {
140 window.maxima_along::<E>(seed, self.space.clone(), self.axis)
141 }
142 AxisSlicesKind::Fragments(partition) => partition.maxima_along::<E>(seed),
143 AxisSlicesKind::Fragment(tile) => tile.manual_fragment().maxima_along(seed),
144 }
145 }
146
147 pub(crate) fn exp_minus<E: Float>(&mut self, slices: &Array<E>) {
149 match &mut self.kind {
150 AxisSlicesKind::Registers(block) => block.exp_minus_along::<E>(slices),
151 AxisSlicesKind::Window(window) => {
152 window.exp_minus_along::<E>(slices, self.space.clone(), self.axis)
153 }
154 AxisSlicesKind::Fragments(partition) => partition.exp_minus_along::<E>(slices),
155 AxisSlicesKind::Fragment(tile) => {
156 let mut fragment = tile.manual_fragment();
157 fragment.exp_minus_along(slices, 0usize)
158 }
159 }
160 }
161
162 pub(crate) fn sums<E: Float>(&self) -> Array<E> {
164 match &self.kind {
165 AxisSlicesKind::Registers(block) => block.sums_along::<E>(),
166 AxisSlicesKind::Window(window) => window.sums_along::<E>(self.space.clone(), self.axis),
167 AxisSlicesKind::Fragments(partition) => partition.sums_along::<E>(),
168 AxisSlicesKind::Fragment(tile) => tile.manual_fragment().sums_along(),
169 }
170 }
171
172 pub fn mul<E: Float>(&mut self, factors: &Array<E>) {
175 match &mut self.kind {
176 AxisSlicesKind::Registers(block) => block.mul_along::<E>(factors, 0usize),
177 AxisSlicesKind::Window(window) => {
178 window.mul_along::<E>(factors, self.space.clone(), self.axis)
179 }
180 AxisSlicesKind::Fragments(partition) => partition.mul_along::<E>(factors),
181 AxisSlicesKind::Fragment(tile) => tile.mul_along(factors, 0usize),
182 }
183 }
184
185 pub fn probabilities<E: Float>(&mut self, scale: E, bias: &Tile, logsumexp: &Tile) {
193 let recipe = bias.recipe();
194 let broadcast = comptime!(Broadcast::new(&self.space, &logsumexp.place.space));
195 let values = logsumexp.cells::<E>(comptime!(logsumexp.place.space.cells()));
196 match &mut self.kind {
197 AxisSlicesKind::Registers(block) => block.probabilities_along::<E>(
198 scale,
199 recipe,
200 &values,
201 self.space.clone(),
202 broadcast,
203 ),
204 AxisSlicesKind::Window(window) => window.probabilities_along::<E>(
205 scale,
206 recipe,
207 &values,
208 self.space.clone(),
209 broadcast,
210 ),
211 AxisSlicesKind::Fragments(_) | AxisSlicesKind::Fragment(_) => {
212 AxisSlices::refuse_fragments("AxisSlices::probabilities")
213 }
214 }
215 }
216
217 pub fn softmax_gradient<E: Float>(&mut self, probabilities: &AxisSlices, delta: &Tile) {
223 comptime!(assert!(
224 self.space == probabilities.space && self.axis == probabilities.axis,
225 "AxisSlices::softmax_gradient: the probabilities are cut as the gradient is, along \
226 {:?} of {:?}, not along {:?} of {:?}",
227 self.axis,
228 self.space,
229 probabilities.axis,
230 probabilities.space
231 ));
232 let broadcast = comptime!(Broadcast::new(&self.space, &delta.place.space));
233 let values = delta.cells::<E>(comptime!(delta.place.space.cells()));
234 match (&mut self.kind, &probabilities.kind) {
235 (AxisSlicesKind::Registers(block), AxisSlicesKind::Registers(probabilities)) => {
236 block.softmax_gradient_along::<E>(probabilities, &values, broadcast)
237 }
238 (AxisSlicesKind::Window(window), AxisSlicesKind::Window(probabilities)) => window
239 .softmax_gradient_along::<E>(probabilities, &values, self.space.clone(), broadcast),
240 _ => AxisSlices::refuse_fragments("AxisSlices::softmax_gradient"),
241 }
242 }
243
244 pub(crate) fn store_held<E: Float>(&self, values: &Array<E>, window: &mut Tile) {
249 comptime!(assert!(
250 window.place.holder() == ComputeScope::Plane
251 && window.place.space.cells() == self.space.slices_along(self.axis),
252 "AxisSlices::store_held: a plane's window of one cell for each of the {} slices of \
253 {:?}, not {:?} held by {:?}",
254 self.space.slices_along(self.axis),
255 self.space,
256 window.place.space,
257 window.place.holder()
258 ));
259 match &mut window.kind {
260 TileKind::Memory(memory) => self.store_held_in::<E>(values, memory),
261 TileKind::PlanePartition(_)
262 | TileKind::PlaneTile(_)
263 | TileKind::TmaGmem(_)
264 | TileKind::Procedural(_)
265 | TileKind::Lines(_) => panic!(
266 "AxisSlices::store_held: the slices' state lands in a window of shared memory"
267 ),
268 }
269 }
270
271 fn store_held_in<E: Float>(&self, values: &Array<E>, memory: &mut Memory) {
273 match &self.kind {
274 AxisSlicesKind::Fragments(partition) => partition.store_along::<E>(values, memory),
275 AxisSlicesKind::Fragment(tile) => tile
276 .manual_fragment()
277 .store_along::<E>(values, memory, 0usize, 0usize),
278 AxisSlicesKind::Registers(_) | AxisSlicesKind::Window(_) => panic!(
279 "AxisSlices::store_held: a unit's block and a plane's window keep every slice's \
280 slot alike; only a manual-mma grid's units keep their own rows"
281 ),
282 }
283 }
284
285 pub(crate) fn exp_minus_cell<E: Float>(value: E, slice: E) -> E {
288 let live = slice > E::min_value();
289 select(live, (value - slice).exp(), E::from_int(0))
290 }
291}
292
293#[cube]
294impl AxisSlices {
295 fn refuse_fragments(#[comptime] who: &str) -> ! {
298 panic!("{who}: a unit's register blocks or a plane's windows hold the cells, not fragments")
299 }
300}
301
302#[derive(Clone, Debug, PartialEq, Eq, Hash)]
306pub(crate) struct Broadcast {
307 pub(crate) slice: Vec<usize>,
309 pub(crate) position: usize,
312}
313
314impl Broadcast {
315 pub(crate) fn new(space: &Space, broadcast: &Space) -> Self {
322 assert!(
323 broadcast.axes().all(|axis| match space.contains(axis) {
324 true => space.extent(axis) == broadcast.extent(axis),
325 false => broadcast.extent(axis) == 1,
326 }),
327 "Broadcast::new: every axis of {broadcast:?} is one of {space:?}'s at its extent, or \
328 one wide"
329 );
330 let stride = |axis: Axis| {
331 (broadcast.position(axis) + 1..broadcast.rank())
332 .map(|p| broadcast.extent_at(p))
333 .product::<usize>()
334 };
335 let innermost = space.axis_at(space.rank() - 1);
336 let outer: Vec<Axis> = (0..space.rank() - 1).map(|p| space.axis_at(p)).collect();
337 let slices = outer.iter().map(|&a| space.extent(a)).product::<usize>();
338 let slice = (0..slices)
339 .map(|index| {
340 let mut rest = index;
341 let mut offset = 0;
342 for &axis in outer.iter().rev() {
343 let extent = space.extent(axis);
344 if broadcast.contains(axis) {
345 offset += (rest % extent) * stride(axis);
346 }
347 rest /= extent;
348 }
349 offset
350 })
351 .collect();
352 let position = match broadcast.contains(innermost) {
353 true => stride(innermost),
354 false => 0,
355 };
356 Self { slice, position }
357 }
358
359 pub(crate) fn at(&self, slice: usize, position: usize) -> usize {
361 self.slice[slice] + position * self.position
362 }
363}