use core::cell::Ref;
use cubecl_core::{
cmma::{MatrixLayout, MatrixShape, MatrixType},
ir::{
dialect::matrix::{CastOp, FillOp, LoadOp, MultiplyAccumulateOp, StoreOp},
interfaces::TypedExt,
prelude::*,
},
};
use crate::{
metal::{metal_op, ty::metal_ty},
shared::{
CompilationOptions, CppValue, DeclareMatrixOp,
ty::{TypeExtCPP, TypeToCPP},
wmma_api_base::{self, as_scalar_ptr},
},
target::Metal,
};
pub fn compile_cmma_includes_metal() -> String {
"#include <metal_simdgroup_matrix>\n".into()
}
metal_ty!(MatrixType, |ty, ctx| {
let MatrixShape { m, n, k } = ty.shape;
let ty = ty.elem_ty.to_cpp(ctx);
if m != 8 || n != 8 || k != 8 {
panic!("{m}x{n}x{k} fragments not supported. Only 8x8x8 fragments are supported.");
}
format!("simdgroup_{ty}8x8")
});
metal_op!(DeclareMatrixOp, |op, ctx| {
wmma_api_base::compile_matrix_declaration(
ctx,
op.get_result(ctx),
op.value_ty(ctx).get_type(ctx),
)
});
metal_op!(FillOp, |op, ctx| {
let mat = op.matrix(ctx).name(ctx);
let value = op.value(ctx).name(ctx);
let ty = op.value(ctx).get_type(ctx).to_cpp(ctx);
format!("*{mat} = make_filled_simdgroup_matrix<{ty}, 8, 8>({value});\n",)
});
metal_op!(LoadOp, |op, ctx| {
let frag = op.matrix(ctx).name(ctx);
let ptr = as_scalar_ptr(ctx, op.source(ctx));
let stride = op.stride(ctx).name(ctx);
let mat_ty = matrix_ty(ctx, op.matrix(ctx));
let transpose = match mat_ty.layout {
MatrixLayout::RowMajor | MatrixLayout::Undefined => false,
MatrixLayout::ColMajor => true,
};
format!("simdgroup_load(*{frag}, {ptr}, {stride}, 0, {transpose});\n")
});
metal_op!(StoreOp, |op, ctx| {
let mat = op.matrix(ctx).name(ctx);
let destination = as_scalar_ptr(ctx, op.destination(ctx));
let stride = op.stride(ctx).name(ctx);
format!(
"
simdgroup_store(*{mat}, {destination}, {stride});
simdgroup_barrier(mem_flags::mem_threadgroup);"
)
});
metal_op!(MultiplyAccumulateOp, |op, ctx| {
let a = op.mat_a(ctx).name(ctx);
let b = op.mat_b(ctx).name(ctx);
let c = op.mat_c(ctx).name(ctx);
let d = op.mat_d(ctx).name(ctx);
format!("simdgroup_multiply_accumulate(*{d}, *{a}, *{b}, *{c});\n")
});
metal_op!(CastOp, |op, ctx| {
let input = op.input(ctx).name(ctx);
let output = op.output(ctx).name(ctx);
let output_ty = matrix_ty(ctx, op.output(ctx));
let ty = output_ty.elem_ty.to_cpp(ctx);
let threads_per_simdgroup = ctx.aux_ty::<CompilationOptions>().warp_size;
let elements_held_by_each_thread =
elements_held_by_each_thread(&output_ty.shape, threads_per_simdgroup);
format!(
"
simdgroup_barrier(mem_flags::mem_none);
for(int e=0; e<{elements_held_by_each_thread}; e++) {{
{output}->thread_elements()[e] = {ty}({input}->thread_elements()[e]);
}}"
)
});
fn elements_held_by_each_thread(shape: &MatrixShape, threads_per_simdgroup: usize) -> usize {
(shape.m * shape.n) / threads_per_simdgroup
}
fn matrix_ty(ctx: &Context, ty: impl Typed) -> Ref<'_, MatrixType> {
let ty = ty.unwrap_ptr(ctx).deref(ctx);
Ref::map(ty, |ty| {
ty.downcast_ref::<MatrixType>().expect("Should be matrix")
})
}