1use cubecl::cmma::{MatrixIdent, MatrixLayout};
5use cubecl::prelude::*;
6
7use crate::ops::matmul::leaf::{FragmentRead, window_layouts};
8use crate::*;
9
10#[cube]
11impl Tile {
12 pub fn mm(&mut self, lhs: &Tile, rhs: &Tile) {
16 let in_memory = self.is_memory();
17 if comptime!(in_memory) {
18 let acc = self.clone();
20 let contraction = match &acc.kind {
21 TileKind::Memory(g) => g.stated_contraction(),
22 TileKind::PlaneTile(_)
23 | TileKind::PlanePartition(_)
24 | TileKind::TmaGmem(_)
25 | TileKind::Procedural(_)
26 | TileKind::Lines(_) => {
27 panic!("Tile::mm: only a memory window states its contraction")
28 }
29 };
30 let semiring = contraction.semiring;
31 let init_from = self.request_init_from(comptime!(InitFrom::Identity));
32 let define!(Served) = self.element;
33 match comptime!(init_from) {
34 InitFrom::Identity => {}
35 InitFrom::Cell => self.init_identity::<Served>(comptime!(semiring.add())),
36 }
37 self.mma(lhs, rhs);
38 self.request_init_from(comptime!(InitFrom::Cell));
39 } else {
40 self.reset();
41 self.mma(lhs, rhs);
42 }
43 }
44
45 pub fn dequantized(&self, operand: &Tile, #[comptime] element: ElemType) -> Tile {
54 let (quantized, packing) = (operand.quantized(), operand.packing());
55 let decodes = comptime!(quantized || packing != Packing::Plain);
56 let mma = self.contracts_through_mma();
57 if comptime!(!decodes || mma) {
58 operand.clone()
59 } else {
60 let out = comptime!(self.place.space.clone());
61 let own = comptime!(operand.place.space.clone());
62 let define!(Target) = element;
63 match &self.kind {
64 TileKind::PlanePartition(partition) => {
65 let grid = partition.grid();
66 match comptime!(Descent::of(grid, &self.place, &own, &own)) {
67 Descent::Here => {
68 operand.fragment_for::<Target>(&partition.at(0usize, 0usize), out)
69 }
70 Descent::Cells(instruction) => PlanePartition::operand::<Target>(
71 operand,
72 self,
73 instruction,
74 DeepLoad::Steps,
75 ),
76 Descent::Steps(instruction) => match comptime!(instruction) {
77 Instruction::Cmma => {
78 let side = comptime!(Side::of(&own, &out));
79 operand.landed::<Target>(side, out)
80 }
81 Instruction::Mma { .. }
82 | Instruction::Registers { .. }
83 | Instruction::Wgmma => panic!(
84 "Tile::dequantized: a grid that steps its contraction through \
85 manual-mma fragments holds each region's operand only while it \
86 contracts; walk the regions and dequantize each one into the \
87 accumulator's region"
88 ),
89 },
90 }
91 }
92 TileKind::PlaneTile(fragment) => operand.fragment_for::<Target>(fragment, out),
93 TileKind::Memory(_)
94 | TileKind::TmaGmem(_)
95 | TileKind::Procedural(_)
96 | TileKind::Lines(_) => {
97 panic!(
98 "Tile::dequantized: an operand is dequantized for the fragments it meets"
99 )
100 }
101 }
102 }
103 }
104
105 fn fragment_for<T: Numeric>(&self, acc: &PlaneTile, #[comptime] out: Space) -> Tile {
107 let own = comptime!(self.place.space.clone());
108 let side = comptime!(Side::of(&own, &out));
109 let ident = comptime!(match side {
110 Side::Lhs => MatrixIdent::A,
111 Side::Rhs => MatrixIdent::B,
112 });
113 let fragment = match acc {
114 PlaneTile::Cmma(_) => {
115 let read = comptime!(FragmentRead::new(side, &own, &out));
116 let fragment = self.cmma_fragment::<T>(ident, comptime!(read.clone()));
117 PlaneTile::new_Cmma(CmmaData {
118 fragment: unsafe { fragment.downcast_unchecked::<Held>() },
119 ident,
120 layout: comptime!(read.layout),
121 shape: comptime!((read.m, read.n)),
122 scratch: ComptimeOption::new_None(),
123 })
124 }
125 PlaneTile::Mma(_) => panic!(
126 "Tile::dequantized: a manual-mma sum decodes its factor as its contraction loads it"
127 ),
128 PlaneTile::Registers(_) | PlaneTile::Wgmma(_) => panic!(
129 "Tile::dequantized: a register block and a warpgroup MMA read their operands where \
130 they lie"
131 ),
132 };
133 Tile::new::<T>(
134 TileKind::new_PlaneTile(fragment),
135 comptime!(self.place.clone()),
136 )
137 }
138
139 pub fn mma(&mut self, lhs: &Tile, rhs: &Tile) {
144 let stored = self.reads_codes_as_stored();
145 let mma = self.contracts_through_mma();
146 let (lhs_read, rhs_read) = (lhs.read_element(), rhs.read_element());
147 let (lhs_packing, rhs_packing) = (lhs.packing(), rhs.packing());
148 let (lhs_read, rhs_read) = comptime!(match (
151 mma,
152 lhs_packing == Packing::Plain,
153 rhs_packing == Packing::Plain
154 ) {
155 (true, false, true) => (rhs_read, rhs_read),
156 (true, true, false) => (lhs_read, lhs_read),
157 _ => (lhs_read, rhs_read),
158 });
159 let define!(Served) = self.element;
160 let define!(Lhs) = comptime!(if stored { lhs.element } else { lhs_read });
161 let define!(Rhs) = comptime!(if stored { rhs.element } else { rhs_read });
162 Descent::contract::<Served, Lhs, Rhs>(self, lhs, rhs)
163 }
164
165 fn contracts_through_mma(&self) -> comptime_type!(bool) {
167 match &self.kind {
168 TileKind::PlaneTile(tile) => tile.contracts_through_mma(),
169 TileKind::PlanePartition(partition) => {
170 partition.at(0usize, 0usize).contracts_through_mma()
171 }
172 TileKind::Memory(_)
173 | TileKind::TmaGmem(_)
174 | TileKind::Procedural(_)
175 | TileKind::Lines(_) => comptime!(false),
176 }
177 }
178
179 fn reads_codes_as_stored(&self) -> comptime_type!(bool) {
182 match &self.kind {
183 TileKind::PlaneTile(tile) => tile.reads_codes_as_stored(),
184 TileKind::PlanePartition(partition) => {
185 partition.at(0usize, 0usize).reads_codes_as_stored()
186 }
187 TileKind::Memory(_)
188 | TileKind::TmaGmem(_)
189 | TileKind::Procedural(_)
190 | TileKind::Lines(_) => comptime!(false),
191 }
192 }
193
194 pub fn reset(&mut self) {
196 let acc = self.clone();
198 let semiring = match &acc.kind {
199 TileKind::PlaneTile(t) => t.semiring(),
200 TileKind::PlanePartition(p) => p.at(0usize, 0usize).semiring(),
201 TileKind::Memory(_)
202 | TileKind::TmaGmem(_)
203 | TileKind::Procedural(_)
204 | TileKind::Lines(_) => {
205 panic!(
206 "Tile::reset: only a plane-resident accumulator has an identity to return to"
207 )
208 }
209 };
210 let define!(Served) = self.element;
211 self.init_identity::<Served>(comptime!(semiring.add()));
212 }
213}
214
215#[cube]
216impl Tile {
217 pub fn commit(&self) -> Pending<()> {
221 let define!(Served) = self.element;
222 match &self.kind {
223 TileKind::PlanePartition(p) => p.fragment().commit::<Served>(),
224 TileKind::PlaneTile(t) => t.commit::<Served>(),
225 TileKind::Memory(_)
226 | TileKind::TmaGmem(_)
227 | TileKind::Procedural(_)
228 | TileKind::Lines(_) => panic!(
229 "Tile::commit: only a plane group's warpgroup accumulator contracts asynchronously"
230 ),
231 }
232 }
233}
234
235#[cube]
236impl Tile {
237 pub fn accumulating(
240 &self,
241 #[comptime] block: RegisterBlock,
242 #[comptime] semiring: Semiring,
243 ) -> Tile {
244 match &self.kind {
245 TileKind::Memory(g) => {
246 let mut g = g.clone();
247 g.set_contraction(comptime!(Contraction { block, semiring }));
248 Tile {
249 kind: TileKind::new_Memory(g),
250 factor: self.factor.clone(),
251 place: comptime!(self.place.clone()),
252 element: comptime!(self.element),
253 }
254 }
255 TileKind::PlaneTile(_)
256 | TileKind::PlanePartition(_)
257 | TileKind::TmaGmem(_)
258 | TileKind::Procedural(_)
259 | TileKind::Lines(_) => panic!(
260 "Tile::accumulating: a memory window states how it is contracted into; a \
261 plane-resident accumulator states it when opened (Tile::accumulator)"
262 ),
263 }
264 }
265}
266
267#[cube]
268impl PlaneTile {
269 pub(crate) fn mma<E: Numeric, EL: Numeric, ER: Numeric>(
271 &mut self,
272 lhs: &Tile,
273 rhs: &Tile,
274 #[comptime] out: Space,
275 ) {
276 match self {
277 PlaneTile::Cmma(d) => {
278 let (lhs_window, rhs_window) =
279 comptime!(window_layouts(&lhs.place.space, &rhs.place.space));
280 let lhs_along_m = read_col_major(lhs, lhs_window);
281 let rhs_along_k = read_col_major(rhs, rhs_window);
282 strided_2d(lhs, rhs, comptime!(out.clone()), lhs_along_m, rhs_along_k);
283 d.mma::<E, EL, ER>(lhs, rhs, out)
284 }
285 PlaneTile::Mma(d) => {
286 let (m, n, k) = comptime!((d.m, d.n, d.k));
294 let block_scaled = comptime!(d.operands == MmaOperands::BlockScaled);
295 let (lhs_in_memory, rhs_in_memory) = (lhs.is_memory(), rhs.is_memory());
298 if comptime!(block_scaled && lhs_in_memory) {
299 assert_block_scaled::<EL>(lhs, m, n, k);
300 }
301 if comptime!(block_scaled && rhs_in_memory) {
302 assert_block_scaled::<ER>(rhs, m, n, k);
303 }
304 let steps = lane_run_steps::<EL>(m, n, k);
305 if comptime!(!block_scaled && steps == 1) {
306 lhs.refuse_factor("PlaneTile::Mma");
307 rhs.refuse_factor("PlaneTile::Mma");
308 }
309 flattened_k(lhs, rhs, out);
310 d.mma::<E, EL, ER>(lhs, rhs)
311 }
312 PlaneTile::Wgmma(d) => {
313 lhs.refuse_factor("PlaneTile::Wgmma");
314 rhs.refuse_factor("PlaneTile::Wgmma");
315 d.mma::<E, EL, ER>(lhs, rhs, out)
316 }
317 PlaneTile::Registers(d) => match &lhs.kind {
318 TileKind::PlaneTile(block) => match block {
319 PlaneTile::Registers(block) => d.mma_block::<E, EL, ER>(block, rhs),
320 PlaneTile::Cmma(_) | PlaneTile::Mma(_) | PlaneTile::Wgmma(_) => panic!(
321 "mma: a register block contracts a register block it holds, or memory"
322 ),
323 },
324 TileKind::PlanePartition(block) => match block.fragment() {
325 PlaneTile::Registers(block) => d.mma_block::<E, EL, ER>(&block, rhs),
326 PlaneTile::Cmma(_) | PlaneTile::Mma(_) | PlaneTile::Wgmma(_) => panic!(
327 "mma: a register block contracts a register block it holds, or memory"
328 ),
329 },
330 TileKind::Memory(_)
331 | TileKind::TmaGmem(_)
332 | TileKind::Procedural(_)
333 | TileKind::Lines(_) => {
334 let rhs_along_k = comptime!(
335 lhs.place
336 .space
337 .contains(rhs.place.space.axis_at(rhs.place.space.rank() - 1))
338 );
339 strided_2d(lhs, rhs, comptime!(out.clone()), false, rhs_along_k);
341 d.mma::<E, EL, ER>(lhs, rhs, out)
342 }
343 },
344 }
345 }
346}
347
348#[cube]
350fn strided_2d(
351 lhs: &Tile,
352 rhs: &Tile,
353 #[comptime] out: Space,
354 #[comptime] lhs_along_m: bool,
355 #[comptime] rhs_along_k: bool,
356) {
357 let lhs_gathered = lhs.gathered();
358 let rhs_gathered = rhs.gathered();
359 let flat = comptime!({
360 let kc = Space::merge(&[&lhs.place.space, &rhs.place.space]).contracted_extent(&out);
361 let axes = MatrixAxes::accumulator(&out, &lhs.place.space);
362 let cols = axes.cols(&out);
363 let rhs_matrix = if rhs_along_k {
364 MatrixAxes::new(&rhs.place.space, cols, kc)
365 } else {
366 MatrixAxes::new(&rhs.place.space, kc, cols)
367 };
368 let rows = axes.rows(&out);
369 let lhs_matrix = if lhs_along_m {
370 MatrixAxes::new(&lhs.place.space, kc, rows)
371 } else {
372 MatrixAxes::new(&lhs.place.space, rows, kc)
373 };
374 lhs_matrix.is_ok() && rhs_matrix.is_ok()
375 });
376 comptime!(assert!(
377 !lhs_gathered && !rhs_gathered && flat,
378 "mma: a cmma or plane-register fragment reads one `k` edge off a directly addressed \
379 operand; a gather, or a contraction these axes give no edge for, needs the manual-mma \
380 leaf, or an unpromoted Gmem/Smem accumulator, whose software instruction is the \
381 `memory` arm of `Descent::contract`: {:?} · {:?} into {out:?}",
382 lhs.place.space,
383 rhs.place.space
384 ));
385}
386
387#[cube]
391fn read_col_major(factor: &Tile, #[comptime] window: MatrixLayout) -> comptime_type!(bool) {
392 match &factor.kind {
393 TileKind::PlaneTile(t) => match t {
394 PlaneTile::Cmma(d) => comptime!(d.layout == MatrixLayout::ColMajor),
395 PlaneTile::Mma(_) | PlaneTile::Registers(_) | PlaneTile::Wgmma(_) => comptime!(false),
396 },
397 TileKind::Memory(m) => {
398 comptime!(m.address == AddressSpace::Shared && window == MatrixLayout::ColMajor)
399 }
400 TileKind::PlanePartition(_)
401 | TileKind::TmaGmem(_)
402 | TileKind::Procedural(_)
403 | TileKind::Lines(_) => comptime!(false),
404 }
405}
406
407#[cube]
409fn flattened_k(lhs: &Tile, rhs: &Tile, #[comptime] out: Space) {
410 comptime!(assert!(
411 Space::contraction_agrees(&lhs.place.space, &rhs.place.space, &out),
412 "mma: the operands list their contracted axes in different orders ({:?} against {:?}), \
413 so their `k` edges do not line up",
414 lhs.place.space.difference(&out),
415 rhs.place.space.difference(&out)
416 ));
417}