cubecl_core/frontend/wgmma/
base.rs1use super::layout::{Major, WgmmaTileLayout};
2
3use crate::frontend::container::inner_array_ty;
4use crate::frontend::pending::{GroupToken, PendingExpand};
5use crate::{self as cubecl, prelude::*, unexpanded};
6use core::marker::PhantomData;
7use cubecl_ir::{
8 dialect::matrix::{
9 WgmmaDescriptorOp, WgmmaFenceOp, WgmmaFenceOperandOp, WgmmaOp, warpgroup_elems_per_unit,
10 },
11 features::WgmmaElems,
12 types::{MatrixShape, pending::AsyncGroup},
13};
14use cubecl_macros::{comptime_type, cube, intrinsic};
15use pliron::builtin::attributes::TypeAttr;
16
17use alloc::format;
18
19pub use cubecl_ir::dialect::matrix::{WARPGROUP_M, WARPGROUP_UNITS};
20
21const ACCUMULATOR_RUN: u32 = 2;
23const FRAGMENT_RUN_BYTES: usize = 4;
25
26#[derive(CubeType, Clone, Copy)]
29pub struct MatrixDescriptor<E: Scalar> {
30 #[allow(unused)]
31 descriptor: u64,
32 #[allow(unused)]
33 #[cube(comptime)]
34 layout: WgmmaTileLayout,
35 #[cube(comptime)]
36 _elem: PhantomData<E>,
37}
38
39#[cube]
40impl<E: Scalar> MatrixDescriptor<E> {
41 #[allow(unused_variables)]
46 pub fn new<L: CubePrimitive<Scalar = E>>(
47 tile: &[L],
48 #[comptime] layout: WgmmaTileLayout,
49 ) -> Self {
50 intrinsic!(|scope| {
51 if let Err(err) = layout.validate(E::elem_type(scope).size()) {
52 scope.push_error(format!("a warpgroup MMA can't read the tile: {err}"));
53 }
54 let ptr = unsafe { *tile.__expand_as_ptr_method(scope) }.value(scope);
55 let op = WgmmaDescriptorOp::new(
56 scope.ctx_mut(),
57 ptr,
58 layout.leading_byte_offset(),
59 layout.stride_byte_offset(),
60 layout.swizzle,
61 );
62 MatrixDescriptorExpand {
63 descriptor: scope.register_with_result(&op).into(),
64 layout,
65 _elem: PhantomData,
66 }
67 })
68 }
69
70 pub fn at(&self, row: usize, k: usize) -> Self {
76 let elem_size = E::size();
77 let offset = comptime![self.layout].byte_offset(row, k, elem_size);
78 MatrixDescriptor::<E> {
81 descriptor: self.descriptor + (offset as u64 >> 4),
82 layout: comptime![self.layout],
83 _elem: PhantomData,
84 }
85 }
86
87 pub fn layout(&self) -> comptime_type!(WgmmaTileLayout) {
89 intrinsic!(|_| self.layout)
90 }
91}
92
93#[derive(CubeType, Clone)]
95pub struct Accumulator<CD: Numeric> {
96 registers: Array<CD>,
97 #[allow(unused)]
98 #[cube(comptime)]
99 n: usize,
100}
101
102#[cube]
103impl<CD: Numeric> Accumulator<CD> {
104 pub fn new(#[comptime] n: usize) -> Self {
107 let len = comptime![warpgroup_elems_per_unit(n)];
108 let mut registers = Array::new(len);
109 #[unroll]
110 for i in 0..len {
111 registers[i] = CD::from_int(0);
112 }
113 Accumulator::<CD> { registers, n }
114 }
115
116 pub fn n(&self) -> comptime_type!(usize) {
118 intrinsic!(|_| self.n)
119 }
120
121 #[allow(clippy::len_without_is_empty)]
123 pub fn len(&self) -> comptime_type!(usize) {
124 intrinsic!(|_| warpgroup_elems_per_unit(self.n))
125 }
126
127 pub fn get(&self, nth: usize) -> CD {
129 self.registers[nth]
130 }
131
132 pub fn position_of_nth(&self, unit: u32, nth: u32) -> (u32, u32) {
138 position_of_nth(unit, nth, ACCUMULATOR_RUN)
139 }
140
141 pub fn start(self) -> Pending<Accumulator<CD>> {
143 intrinsic!(|scope| {
144 fence_operand(scope, &self.registers);
145 scope.register(&WgmmaFenceOp::new(scope.ctx_mut()));
146 PendingExpand::ready(scope, AsyncGroup::Warpgroup, self)
147 })
148 }
149}
150
151impl<CD: Numeric> Clone for AccumulatorExpand<CD> {
153 fn clone(&self) -> Self {
154 Self {
155 registers: self.registers,
156 n: self.n,
157 }
158 }
159}
160
161impl<CD: Numeric> Pending<Accumulator<CD>> {
162 pub fn downcast<O: Numeric>(&self) -> Pending<Accumulator<O>> {
167 unexpanded!()
168 }
169
170 pub unsafe fn downcast_unchecked<O: Numeric>(&self) -> Pending<Accumulator<O>> {
177 unexpanded!()
178 }
179}
180
181impl<CD: Numeric> PendingExpand<Accumulator<CD>> {
182 pub fn __expand_downcast_method<O: Numeric>(
183 &self,
184 scope: &Scope,
185 ) -> PendingExpand<Accumulator<O>> {
186 let named = O::__expand_as_type(scope);
187 let held = inner_array_ty(scope, self.value.registers.value(scope))
188 .deref(scope.ctx())
189 .inner;
190 assert_eq!(
191 held, named,
192 "Pending<Accumulator>::downcast: the accumulator holds {held:?}, not {named:?}"
193 );
194 self.__expand_downcast_unchecked_method(scope)
195 }
196
197 #[doc(hidden)]
198 pub fn __expand_downcast_unchecked_method<O: Numeric>(
199 &self,
200 _scope: &Scope,
201 ) -> PendingExpand<Accumulator<O>> {
202 PendingExpand {
203 value: AccumulatorExpand {
204 registers: self.value.registers.expand.into(),
205 n: self.value.n,
206 },
207 token: self.token,
208 }
209 }
210}
211
212impl<CD: Numeric> PendingValue for Accumulator<CD> {
213 fn wait_for(_pending: Pending<Self>) -> Self {
214 unexpanded!()
215 }
216
217 fn __expand_wait_for(scope: &Scope, pending: PendingExpand<Self>) -> AccumulatorExpand<CD> {
221 let token = GroupToken::commit(scope, AsyncGroup::Warpgroup);
222 token.__expand_wait_method(scope);
223 fence_operand(scope, &pending.value.registers);
225 pending.value
226 }
227}
228
229#[cube]
230impl<CD: Numeric> Pending<Accumulator<CD>> {
231 #[allow(unused_variables)]
235 pub fn execute<A: Scalar, B: Scalar>(
236 &mut self,
237 a: &MatrixDescriptor<A>,
238 b: &MatrixDescriptor<B>,
239 ) {
240 intrinsic!(|scope| {
241 let a_layout = a.layout;
242 let a = a.descriptor.read_value(scope);
243 issue::<A, B, CD>(scope, &self.value, a, Some(a_layout), b)
244 })
245 }
246
247 #[allow(unused_variables)]
250 pub fn execute_registers<A: Scalar, B: Scalar>(
251 &mut self,
252 a: &Fragment<A>,
253 b: &MatrixDescriptor<B>,
254 ) {
255 intrinsic!(|scope| {
256 fence_operand(scope, &a.registers);
259 scope.register(&WgmmaFenceOp::new(scope.ctx_mut()));
260 let a = a.registers.read_value(scope);
261 issue::<A, B, CD>(scope, &self.value, a, None, b)
262 })
263 }
264
265 pub fn reset(&mut self) {
269 intrinsic!(|scope| {
270 let token = GroupToken::commit(scope, AsyncGroup::Warpgroup);
271 token.__expand_wait_method(scope);
272 fence_operand(scope, &self.value.registers);
273 let len = warpgroup_elems_per_unit(self.value.n);
274 zero_registers::expand::<CD>(scope, &mut self.value.registers, len);
275 fence_operand(scope, &self.value.registers);
276 scope.register(&WgmmaFenceOp::new(scope.ctx_mut()));
277 })
278 }
279
280 pub fn commit(&mut self) -> Pending<()> {
283 intrinsic!(|scope| PendingExpand::commit(scope, AsyncGroup::Warpgroup, ()))
284 }
285}
286
287#[cube]
289fn zero_registers<CD: Numeric>(registers: &mut Array<CD>, #[comptime] len: usize) {
290 #[unroll]
291 for i in 0..len {
292 registers[i] = CD::from_int(0);
293 }
294}
295
296fn fence_operand<E: CubePrimitive>(scope: &Scope, registers: &NativeExpand<Array<E>>) {
298 let registers = registers.__extract_list(scope);
299 scope.register(&WgmmaFenceOperandOp::new(scope.ctx_mut(), registers))
300}
301
302fn issue<A: Scalar, B: Scalar, CD: Numeric>(
305 scope: &Scope,
306 acc: &AccumulatorExpand<CD>,
307 a: cubecl_ir::pliron::value::Value,
308 a_layout: Option<WgmmaTileLayout>,
309 b: &MatrixDescriptorExpand<B>,
310) {
311 let elems = WgmmaElems {
312 a: A::elem_type(scope),
313 b: B::elem_type(scope),
314 cd: CD::elem_type(scope),
315 };
316 let shape = MatrixShape {
317 m: WARPGROUP_M,
318 n: acc.n,
319 k: WgmmaTileLayout::k_step(elems.a.size()),
320 };
321 check_supported(scope, elems, shape);
322 if let Some(a_layout) = a_layout {
323 check_covers(scope, "A", a_layout, shape.m, shape.k);
324 }
325 check_covers(scope, "B", b.layout, shape.n, shape.k);
326 let a_major = a_layout.map_or(Major::K, |layout| layout.major);
327 let b_major = b.layout.major;
328 let b = b.descriptor.read_value(scope);
329 let registers = acc.registers.__extract_list(scope);
330 let a_ty = TypeAttr::new(A::__expand_as_type(scope));
331 let b_ty = TypeAttr::new(B::__expand_as_type(scope));
332 scope.register(&WgmmaOp::new(
333 scope.ctx_mut(),
334 a,
335 b,
336 registers,
337 a_ty,
338 b_ty,
339 shape,
340 a_major,
341 b_major,
342 ));
343}
344
345fn check_covers(scope: &Scope, operand: &str, layout: WgmmaTileLayout, rows: usize, k: usize) {
349 if layout.rows < rows || layout.k < k {
350 scope.push_error(format!(
351 "a warpgroup MMA reads {rows} rows and {k} of K of {operand}, but its tile is {} x {}",
352 layout.rows, layout.k
353 ));
354 }
355}
356
357fn check_supported(scope: &Scope, elems: WgmmaElems, shape: MatrixShape) {
358 let Some(props) = scope.state().device_properties.clone() else {
359 return;
360 };
361 let wgmma = &props.features.matmul.wgmma;
362 if !wgmma.iter().any(|config| config.matches(elems, shape)) {
363 let WgmmaElems { a, b, cd } = elems;
364 let MatrixShape { m, n, k } = shape;
365 scope.push_error(format!(
366 "the device doesn't support a {m}x{n}x{k} warpgroup MMA of {a:?} x {b:?} into \
367 {cd:?}; supported configurations: {wgmma:?}"
368 ));
369 }
370}
371
372#[derive(CubeType)]
375pub struct Fragment<A: Scalar> {
376 registers: Array<A>,
377}
378
379#[cube]
380impl<A: Scalar> Fragment<A> {
381 #[allow(clippy::new_without_default)]
383 pub fn new() -> Self {
384 intrinsic!(|scope| {
385 let len = fragment_elems(A::elem_type(scope).size());
386 FragmentExpand {
387 registers: Array::__expand_new(scope, len),
388 }
389 })
390 }
391
392 #[allow(clippy::len_without_is_empty)]
394 pub fn len(&self) -> comptime_type!(usize) {
395 intrinsic!(|scope| fragment_elems(A::elem_type(scope).size()))
396 }
397
398 pub fn get(&self, nth: usize) -> A {
400 self.registers[nth]
401 }
402
403 pub fn set(&mut self, nth: usize, value: A) {
405 self.registers[nth] = value;
406 }
407
408 pub fn position_of_nth(&self, unit: u32, nth: u32) -> (u32, u32) {
411 intrinsic!(|scope| {
412 let run = (FRAGMENT_RUN_BYTES / A::elem_type(scope).size()) as u32;
413 position_of_nth::expand(scope, unit, nth, run)
414 })
415 }
416}
417
418fn fragment_elems(elem_size: usize) -> usize {
420 warpgroup_elems_per_unit(WgmmaTileLayout::k_step(elem_size))
421}
422
423#[cube]
425fn position_of_nth(unit: u32, nth: u32, #[comptime] run: u32) -> (u32, u32) {
426 let plane = unit / 32;
427 let lane = unit % 32;
428 let chunk = nth / run;
429 let row = plane * 16 + lane / 4 + (chunk % 2) * 8;
430 let col = (chunk / 2) * (run * 4) + (lane % 4) * run + nth % run;
431 (row, col)
432}