1use std::fmt::Formatter;
2
3use crate::{
4 Dialect,
5 hip::{HipDialect, arch::AMDArchitecture},
6 shared::{
7 Architecture, Component, DialectWmmaCompiler, Elem, Flags, FmtLeft, FragmentIdent,
8 FragmentLayout, FragmentType, Item, ManualMma, MmaShape, SupportedMmaCombinations, Value,
9 WmmaInstruction, frag_as_ptr, frag_ident_str, frag_layout_str, value_to_frag,
10 wmma_api_base,
11 },
12};
13use cubecl_core::ir::{self as gpu, MatrixIdent, MatrixType, features::MmaConfig};
14
15#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
16pub struct WmmaIntrinsicCompiler {}
17
18#[derive(new, Debug, Clone, PartialEq)]
19pub struct WmmaFill<D: Dialect> {
20 frag: FragmentType<D>,
21}
22
23#[derive(new, Debug, Clone, PartialEq)]
24pub struct WmmaLoad<D: Dialect> {
25 frag: FragmentType<D>,
26 layout: Option<FragmentLayout<D>>,
27}
28
29#[derive(new, Debug, Clone, PartialEq)]
30pub struct WmmaStore<D: Dialect> {
31 frag: FragmentType<D>,
32 layout: FragmentLayout<D>,
33}
34
35#[derive(new, Debug, Clone, PartialEq)]
36pub struct WmmaExecute<D: Dialect> {
37 frag_a: FragmentType<D>,
38 frag_b: FragmentType<D>,
39 frag_c: FragmentType<D>,
40 frag_d: FragmentType<D>,
41}
42
43#[derive(new, Debug, Clone, PartialEq)]
44pub struct WmmaCast<D: Dialect> {
45 frag_input: FragmentType<D>,
46 frag_output: FragmentType<D>,
47}
48
49impl<D: Dialect> WmmaFill<D> {
50 pub fn fn_name(&self) -> String {
51 let layout = frag_layout_str(&self.frag.layout);
52 let ident = frag_ident_str(&self.frag.ident);
53 let (m, n, k) = (self.frag.m, self.frag.n, self.frag.k);
54 let elem = self.frag.elem;
55
56 format!("wmma_fill_{elem}_{ident}_{m}x{n}x{k}_{layout}",)
57 }
58
59 pub fn format_extension(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
60 let elem = self.frag.elem;
61 let frag = self.frag;
62 let name = self.fn_name();
63
64 write!(
65 f,
66 "
67// Fill the fragment.
68__device__ void {name}({frag}& frag, {elem} value) {{
69 #pragma unroll
70 for (uint i = 0; i < 8; ++i) {{
71 frag[i] = value;
72 }}
73}}
74 "
75 )
76 }
77}
78
79impl<D: Dialect> WmmaLoad<D> {
80 pub fn fn_name(&self) -> String {
81 let layout_frag = frag_layout_str(&self.frag.layout);
82 let layout = frag_layout_str(&self.layout);
83 let ident = frag_ident_str(&self.frag.ident);
84 let elem = self.frag.elem;
85 let (m, n, k) = (self.frag.m, self.frag.n, self.frag.k);
86
87 format!("wmma_load_{elem}_{ident}_{m}x{n}x{k}_{layout_frag}_{layout}",)
88 }
89
90 pub fn format_extension(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
115 let elem = self.frag.elem;
116 let frag = self.frag;
117 let name = self.fn_name();
118
119 let (index_body, length, step) = match frag.ident {
120 FragmentIdent::A | FragmentIdent::B => {
121 let length = 16;
122 let step = 1;
123 let index = if (frag.ident == FragmentIdent::A
126 && frag.layout.unwrap() == FragmentLayout::ColMajor)
127 || (frag.ident == FragmentIdent::B
128 && frag.layout.unwrap() == FragmentLayout::RowMajor)
129 {
130 "i * stride + wmmaLane".to_string()
131 } else {
132 "i + wmmaLane * stride".to_string()
133 };
134 (index, length, step)
135 }
136 FragmentIdent::Accumulator => {
137 let length = 8;
138 let step = get_output_accumulator_index_step(&elem, &frag);
139 let index = match self.layout {
140 Some(FragmentLayout::ColMajor) => {
141 "(i * uint(2) + threadIdx.x / uint(16)) + wmmaLane * stride".to_string()
142 }
143 Some(FragmentLayout::RowMajor) => {
144 "(i * uint(2) + threadIdx.x / uint(16)) * stride + wmmaLane".to_string()
145 }
146 _ => panic!(
147 "cannot load data to an accumulator without knowing the layout of the data"
148 ),
149 };
150 (index, length, step)
151 }
152 other => panic!("unknown matrix identifier {other}"),
153 };
154
155 write!(
156 f,
157 "
158// Load the fragment.
159__device__ void {name}({frag}& frag, const {elem}* value_ptr, const uint stride) {{
160 {WMMA_LANE_DEF}
161
162 #pragma unroll
163 for (uint i = 0; i < {length}; ++i) {{
164 const uint index = {index_body};
165 frag[i * {step}] = value_ptr[index];
166 }}
167}}
168 "
169 )
170 }
171}
172
173impl<D: Dialect> WmmaStore<D> {
174 pub fn fn_name(&self) -> String {
175 let layout_frag = frag_layout_str(&self.frag.layout);
176 let layout_option = Some(self.layout);
177 let layout = frag_layout_str(&layout_option);
178 let ident = frag_ident_str(&self.frag.ident);
179 let (m, n, k) = (self.frag.m, self.frag.n, self.frag.k);
180 let elem = self.frag.elem;
181
182 format!("wmma_store_{elem}_{ident}_{m}x{n}x{k}_{layout_frag}_{layout}",)
183 }
184
185 pub fn format_extension(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
186 let elem = self.frag.elem;
187 let frag = self.frag;
188 let name = self.fn_name();
189 let frag_idx = match elem {
193 Elem::F16 | Elem::BF16 => "elemIdx * 2",
194 Elem::F32 => "elemIdx",
195 other => {
196 panic!("C fragment format cannot be {other}. Only f16, bf16 and f32 are supported.")
197 }
198 };
199 let output_idx = match self.layout {
201 FragmentLayout::ColMajor => "wmmaLane * stride + rowIdx".to_string(),
202 FragmentLayout::RowMajor => "wmmaLane + rowIdx * stride".to_string(),
203 FragmentLayout::_Dialect(_) => String::new(),
204 };
205
206 write!(
207 f,
208 "
209// Store the fragment.
210__device__ void {name}(const {frag}& frag, {elem}* output_ptr, uint stride) {{
211 {WMMA_LANE_DEF}
212
213 #pragma unroll
214 for (uint elemIdx = 0; elemIdx < uint(8); ++elemIdx) {{
215 const uint rowIdx = elemIdx * uint(2) + threadIdx.x / uint(16);
216 output_ptr[{output_idx}] = frag[{frag_idx}];
217 }}
218}}
219 "
220 )
221 }
222}
223
224impl<D: Dialect> WmmaExecute<D> {
225 pub fn from_manual(shape: MmaShape<D>, ab_elem: Elem<D>, cd_elem: Elem<D>) -> Self {
226 let frag_a = FragmentType {
227 ident: FragmentIdent::A,
228 m: shape.m,
229 n: shape.n,
230 k: shape.k,
231 elem: ab_elem,
232 layout: Some(FragmentLayout::ColMajor),
233 };
234 let frag_b = FragmentType {
235 ident: FragmentIdent::B,
236 layout: Some(FragmentLayout::RowMajor),
237 ..frag_a
238 };
239 let frag_cd = FragmentType {
240 ident: FragmentIdent::Accumulator,
241 elem: cd_elem,
242 ..frag_b
243 };
244 WmmaExecute::new(frag_a, frag_b, frag_cd, frag_cd)
245 }
246
247 pub fn fn_name(&self) -> String {
248 format!(
249 "wmma_execute_16x16x16_{}_{}",
250 self.frag_a.elem, self.frag_c.elem
251 )
252 }
253
254 pub fn format_extension(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
255 let name = self.fn_name();
256 let ab_format = match self.frag_a.elem {
257 Elem::F32 => "f32",
258 Elem::BF16 => "bf16",
259 Elem::F16 => "f16",
260 _ => panic!(),
261 };
262 let (cd_format, opsel) = match self.frag_c.elem {
263 Elem::F32 => ("f32", ""),
264 Elem::BF16 => ("bf16", ", false"),
265 Elem::F16 => ("f16", ", false"),
266 _ => panic!(),
267 };
268 let warp_size = 32;
269 write!(
270 f,
271 "
272// Execute wmma.
273__device__ void {name}(const {}& frag_a, const {}& frag_b, const {}& frag_c, {}& frag_d) {{
274 frag_d = __builtin_amdgcn_wmma_{cd_format}_16x16x16_{ab_format}_w{warp_size}(frag_a, frag_b, frag_c{opsel});
275}}
276 ", self.frag_a, self.frag_b, self.frag_c, self.frag_d
277 )
278 }
279}
280
281impl<D: Dialect> WmmaCast<D> {
282 pub fn fn_name(&self) -> String {
283 let layout = frag_layout_str(&self.frag_input.layout);
284 let ident = frag_ident_str(&self.frag_input.ident);
285 let (m, n, k) = (self.frag_input.m, self.frag_input.n, self.frag_input.k);
286 let elem = self.frag_input.elem;
287 let elem_out = self.frag_output.elem;
288
289 format!("wmma_cast_{elem}_to_{elem_out}_{ident}_{m}x{n}x{k}_{layout}",)
290 }
291
292 pub fn format_extension(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
293 let input = self.frag_input;
294 let output = self.frag_output;
295 let name = self.fn_name();
296 let step = match output.ident {
297 FragmentIdent::Accumulator => {
298 get_output_accumulator_index_step(&self.frag_input.elem, &output)
299 }
300 _ => 1,
301 };
302
303 write!(
304 f,
305 "
306// Cast the fragment.
307__device__ void {name}(const {input}& input, {output}& output) {{
308 #pragma unroll
309 for (uint elemIdx = 0; elemIdx < uint(8); ++elemIdx) {{
310 output[elemIdx * {step}] = input[elemIdx];
311 }}
312}}
313 "
314 )
315 }
316}
317
318impl DialectWmmaCompiler<HipDialect<Self>> for WmmaIntrinsicCompiler {
319 fn compile_wmma_type_definitions(
320 f: &mut std::fmt::Formatter<'_>,
321 flags: &Flags<HipDialect<Self>>,
322 ) -> std::fmt::Result {
323 if flags.elem_bf16 {
324 f.write_str("typedef __bf16 bhalf8_t __attribute__((ext_vector_type(8)));\n")?;
325 f.write_str("typedef __bf16 bhalf16_t __attribute__((ext_vector_type(16)));\n")?;
326 }
327 if flags.elem_f16 {
328 f.write_str("typedef _Float16 half8_t __attribute__((ext_vector_type(8)));\n")?;
329 f.write_str("typedef _Float16 half16_t __attribute__((ext_vector_type(16)));\n")?;
330 }
331 f.write_str("typedef float float8_t __attribute__((ext_vector_type(8)));\n")
332 }
333
334 fn compile_wmma_fragment_declaration(
335 f: &mut std::fmt::Formatter<'_>,
336 val: &crate::shared::Value<HipDialect<Self>>,
337 ty: &crate::shared::Item<HipDialect<Self>>,
338 ) -> std::fmt::Result {
339 wmma_api_base::compile_fragment_declaration(f, val, ty)
340 }
341
342 fn compile_wmma_fragment(
343 f: &mut std::fmt::Formatter<'_>,
344 fragment: &FragmentType<HipDialect<Self>>,
345 ) -> std::fmt::Result {
346 match fragment.ident {
347 FragmentIdent::A | FragmentIdent::B => match fragment.elem {
348 Elem::F16 => write!(f, "half16_t"),
349 Elem::BF16 => write!(f, "bhalf16_t"),
350 other => panic!(
351 "unsupported type {other} for fragment ident {:?}",
352 fragment.ident
353 ),
354 },
355 FragmentIdent::Accumulator => match fragment.elem {
356 Elem::F16 => write!(f, "half16_t"),
357 Elem::BF16 => write!(f, "bhalf16_t"),
358 Elem::F32 => write!(f, "float8_t"),
359 other => panic!(
360 "unsupported type {other} for fragment ident {:?}",
361 fragment.ident
362 ),
363 },
364 FragmentIdent::_Dialect(_) => Ok(()),
365 }
366 }
367
368 fn compile_wmma_instruction(
369 f: &mut std::fmt::Formatter<'_>,
370 instruction: &WmmaInstruction<HipDialect<Self>>,
371 ) -> std::fmt::Result {
372 match instruction {
373 WmmaInstruction::Fill { frag, value } => {
374 let extension = WmmaFill::new(match frag.item().unwrap_ptr() {
375 Item::Fragment(frag) => frag,
376 _ => panic!(),
377 });
378 let name = extension.fn_name();
379 let frag = frag.fmt_ref();
380 writeln!(f, "{name}({frag}, {value});")
381 }
382 WmmaInstruction::Load {
383 frag,
384 ptr,
385 layout,
386 stride,
387 } => {
388 let extension = WmmaLoad::new(value_to_frag(frag), *layout);
389 let name = extension.fn_name();
390 let value_ptr = frag_as_ptr(f, ptr);
391 let frag = frag.fmt_ref();
392 writeln!(f, "{name}({frag}, {value_ptr}, {stride});")
393 }
394 WmmaInstruction::LdMatrix { .. } | WmmaInstruction::StMatrix { .. } => {
395 f.write_str("#error LdMatrix & StMatrix are not supported on HIP\n")
396 }
397 WmmaInstruction::Execute {
398 frag_a,
399 frag_b,
400 frag_c,
401 frag_d,
402 warp_size,
403 } => {
404 if *warp_size != 32 {
405 f.write_str(
406 "#error Only warp size of 32 supported for Wmma::Execute on HIP\n",
407 )?;
408 }
409
410 let extension = WmmaExecute::new(
411 value_to_frag(frag_a),
412 value_to_frag(frag_b),
413 value_to_frag(frag_c),
414 value_to_frag(frag_d),
415 );
416 let name = extension.fn_name();
417 let frag_d = frag_d.fmt_ref();
418 writeln!(f, "{name}({frag_a}, {frag_b}, {frag_c}, {frag_d});")
419 }
420 WmmaInstruction::ExecuteManual {
421 shape,
422 frag_a,
423 frag_b,
424 frag_c,
425 frag_d,
426 } => {
427 Self::compile_manual_mma(f, ManualMma::new(*shape, frag_a, frag_b, frag_c, frag_d))
428 }
429 WmmaInstruction::ExecuteScaled {
430 shape,
431 frag_a,
432 frag_b,
433 frag_c,
434 frag_d,
435 scales_a,
436 scales_b,
437 scales_factor,
438 } => Self::compile_scaled_mma(
439 f,
440 ManualMma::new(*shape, frag_a, frag_b, frag_c, frag_d),
441 *scales_a,
442 *scales_b,
443 *scales_factor,
444 ),
445 WmmaInstruction::Store {
446 frag,
447 layout,
448 destination,
449 stride,
450 } => {
451 let extension = WmmaStore::new(value_to_frag(frag), *layout);
452 let name = extension.fn_name();
453 let output_ptr = frag_as_ptr(f, destination);
454 let frag = frag.fmt_ref();
455 writeln!(f, "{name}({frag}, {output_ptr}, {stride});")
456 }
457 WmmaInstruction::Cast { input, output } => {
458 let extension = WmmaCast::new(value_to_frag(input), value_to_frag(output));
459 let name = extension.fn_name();
460 let input = input.fmt_ref();
461 let output = output.fmt_ref();
462 writeln!(f, "{name}({input}, {output});")
463 }
464 }
465 }
466
467 fn compile_manual_mma(
468 f: &mut std::fmt::Formatter<'_>,
469 mma: ManualMma<HipDialect<Self>>,
470 ) -> std::fmt::Result {
471 compile_manual_mma(f, mma.shape, mma.frag_a, mma.frag_b, mma.frag_c, mma.frag_d)
472 }
473
474 fn compile_scaled_mma(
475 f: &mut std::fmt::Formatter<'_>,
476 _mma: ManualMma<HipDialect<Self>>,
477 _scales_a: Value<HipDialect<Self>>,
478 _scales_b: Value<HipDialect<Self>>,
479 _scales_factor: u32,
480 ) -> std::fmt::Result {
481 f.write_str("#error scaled mma not supported in HIP\n")
482 }
483
484 fn supported_wmma_combinations(arch: &AMDArchitecture) -> SupportedMmaCombinations {
485 let mut result: SupportedMmaCombinations = vec![];
487 if arch.is_wmma_capable() {
488 let types = vec![
490 (
491 gpu::ElemType::Float(gpu::FloatKind::F16), gpu::ElemType::Float(gpu::FloatKind::F16), gpu::ElemType::Float(gpu::FloatKind::F16), ),
495 (
496 gpu::ElemType::Float(gpu::FloatKind::F16),
497 gpu::ElemType::Float(gpu::FloatKind::F16),
498 gpu::ElemType::Float(gpu::FloatKind::F32),
499 ),
500 (
501 gpu::ElemType::Float(gpu::FloatKind::BF16),
502 gpu::ElemType::Float(gpu::FloatKind::BF16),
503 gpu::ElemType::Float(gpu::FloatKind::F32),
504 ),
505 ];
506 let combinations: SupportedMmaCombinations = types
507 .into_iter()
508 .map(|(a, b, c)| MmaConfig {
509 a_type: a.into(),
510 b_type: b.into(),
511 cd_type: c.into(),
512 m: 16,
513 n: 16,
514 k: 16,
515 })
516 .collect();
517 result.extend(combinations);
518 }
519 result
520 }
521
522 fn supported_mma_combinations(arch: &AMDArchitecture) -> SupportedMmaCombinations {
523 supported_mma_combinations(arch)
524 }
525}
526
527fn get_output_accumulator_index_step<D: Dialect>(
528 input_elem: &Elem<D>,
529 output: &FragmentType<D>,
530) -> u32 {
531 assert_eq!(output.ident, FragmentIdent::<D>::Accumulator);
539
540 match input_elem {
541 Elem::F16 | Elem::BF16 | Elem::F32 => {
542 match output.elem {
543 Elem::F16 | Elem::BF16 => 2,
545 Elem::F32 => 1,
547 other => panic!("unsupported format {other} for {output}"),
548 }
549 }
550 other => panic!("unsupported format {other} for {input_elem}"),
551 }
552}
553
554pub(super) fn compile_manual_mma<D: Dialect>(
555 f: &mut std::fmt::Formatter<'_>,
556 shape: MmaShape<D>,
557 frag_a: &Value<D>,
558 frag_b: &Value<D>,
559 frag_c: &Value<D>,
560 frag_d: &Value<D>,
561) -> std::fmt::Result {
562 let extension = WmmaExecute::from_manual(shape, frag_a.elem(), frag_c.elem());
563
564 let cd_elems = shape.num_elems(FragmentIdent::<D>::Accumulator) / 32;
565
566 let frag_cd_step = 4usize.div_ceil(frag_c.elem().size());
567 let frag_d_tmp = Value::tmp_declared(Item::Scalar(Elem::<D>::I32)).fmt_left();
568
569 let frag = |val: &Value<D>, len: usize| {
574 let frag: Vec<_> = if let Item::Vector(_, vec) = *val.item().value_ty() {
575 (0..len)
576 .map(|i| format!("{}.i_{}", val.index(i / vec), i % vec))
577 .collect()
578 } else {
579 (0..len).map(|i| format!("{}", val.index(i))).collect()
580 };
581 frag.join(", ")
582 };
583
584 let frag_a = frag(frag_a, 16);
585 let frag_b = frag(frag_b, 16);
586 let frag_c = {
589 let frag: Vec<_> = if let Item::Vector(_, vec) = frag_c.item() {
590 (0..cd_elems as usize)
591 .flat_map(|i| {
592 (0..frag_cd_step).map(move |_| format!("{frag_c}[{}].i_{}", i / vec, i % vec))
593 })
594 .collect()
595 } else {
596 (0..cd_elems as usize)
597 .flat_map(|i| (0..frag_cd_step).map(move |_| format!("{frag_c}[{}]", i)))
598 .collect()
599 };
600 frag.join(", ")
601 };
602
603 let name = extension.fn_name();
605
606 writeln!(f, "{} {frag_d_tmp} = {{}};", extension.frag_d)?;
608
609 writeln!(
610 f,
611 "{name}({}{{{frag_a}}}, {}{{{frag_b}}}, {}{{{frag_c}}}, {frag_d_tmp});",
612 extension.frag_a, extension.frag_b, extension.frag_c
613 )?;
614
615 for i in 0..cd_elems as usize {
616 if let Item::Vector(_, vec) = frag_d.item() {
617 writeln!(
618 f,
619 "{}.i_{} = {frag_d_tmp}[{i} * {frag_cd_step}];",
620 frag_d.index(i / vec),
621 i % vec
622 )?;
623 } else {
624 writeln!(
625 f,
626 "{} = {frag_d_tmp}[{i} * {frag_cd_step}];",
627 frag_d.index(i)
628 )?;
629 }
630 }
631
632 Ok(())
633}
634
635pub(super) fn supported_mma_combinations(arch: &AMDArchitecture) -> SupportedMmaCombinations {
636 const ENABLED: bool = true;
638
639 if !ENABLED {
640 return Vec::new();
641 }
642
643 let mut result: SupportedMmaCombinations = vec![];
646 if arch.is_wmma_capable() {
647 let types = vec![
649 (
650 gpu::ElemType::Float(gpu::FloatKind::F16),
651 gpu::ElemType::Float(gpu::FloatKind::F32),
652 ),
653 (
654 gpu::ElemType::Float(gpu::FloatKind::BF16),
655 gpu::ElemType::Float(gpu::FloatKind::F32),
656 ),
657 ];
658 let combinations = types.into_iter().map(|(ab_elem, cd_elem)| MmaConfig {
659 a_type: ab_elem.into(),
660 b_type: ab_elem.into(),
661 cd_type: cd_elem.into(),
662 m: 16,
663 n: 16,
664 k: 16,
665 });
666 result.extend(combinations);
667 }
668 result
669}
670
671pub fn contiguous_elements_rdna3(ident: MatrixIdent, matrix: MatrixType) -> usize {
672 let max_vector_size = 16 / matrix.storage.size();
674 match ident {
675 MatrixIdent::A | MatrixIdent::B => 16.min(max_vector_size),
676 MatrixIdent::Accumulator => 1,
677 }
678}
679
680static WMMA_LANE_DEF: &str = "uint wmmaLane = uint(threadIdx.x % 16);";