use cubecl_macros_internal::TypeHash;
use derive_more::Display;
use derive_new::new;
use pliron::derive::{format, pliron_type, type_interface_impl};
use crate::{
interfaces::{AlignedType, HasElementType, TypedExt},
prelude::*,
};
#[allow(missing_docs)]
#[derive(new, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[pliron_type(
name = "cube.matrix",
format = "$ident `<` $scope `, ` $elem_ty `, ` $shape `, ` $layout `>`",
generate_get = true,
verifier = "succ"
)]
pub struct MatrixType {
pub ident: MatrixIdent,
pub shape: MatrixShape,
pub elem_ty: TypeHandle,
pub layout: MatrixLayout,
pub scope: MatrixScope,
}
impl MatrixType {
pub fn unpacked_elem_size_bits(&self, ctx: &Context) -> usize {
let size_bits = self.elem_ty.size(ctx) * 8;
size_bits / self.elem_ty.packing_factor(ctx)
}
}
#[type_interface_impl]
impl AlignedType for MatrixType {
fn align(&self, ctx: &Context) -> usize {
self.elem_ty.align(ctx)
}
}
#[type_interface_impl]
impl HasElementType for MatrixType {
fn element_type(&self, ctx: &Context) -> Option<TypeHandle> {
type_cast::<dyn HasElementType>(&*self.elem_ty.deref(ctx))?.element_type(ctx)
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, TypeHash, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[format("$m `x` $n `x` $k")]
pub struct MatrixShape {
pub m: usize,
pub n: usize,
pub k: usize,
}
impl MatrixShape {
pub fn num_elems(&self, ident: MatrixIdent) -> usize {
match ident {
MatrixIdent::A => self.m * self.k,
MatrixIdent::B => self.k * self.n,
MatrixIdent::Accumulator => self.m * self.n,
}
}
}
impl From<(usize, usize, usize)> for MatrixShape {
fn from((m, n, k): (usize, usize, usize)) -> Self {
Self { m, n, k }
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, TypeHash, PartialEq, Eq, Hash, PartialOrd, Ord, Display)]
#[format]
#[allow(missing_docs)]
pub enum MatrixIdent {
#[display("IdentA")]
A,
#[display("IdentB")]
B,
#[display("IdentAcc")]
Accumulator,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, TypeHash, PartialEq, Eq, Hash, PartialOrd, Ord, Display)]
#[display(rename_all = "snake_case")]
#[format]
#[allow(missing_docs)]
pub enum MatrixLayout {
ColMajor,
RowMajor,
Undefined,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, TypeHash, PartialEq, Eq, Hash, PartialOrd, Ord, Display)]
#[display(rename_all = "snake_case")]
#[format]
#[allow(missing_docs)]
pub enum MatrixScope {
Plane,
Cube,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[allow(missing_docs)]
pub enum MatrixMoveTransposition {
Optional,
Required,
Unavailable,
}
impl MatrixMoveTransposition {
pub fn admits(self, transpose: bool) -> bool {
match self {
MatrixMoveTransposition::Optional => true,
MatrixMoveTransposition::Required => transpose,
MatrixMoveTransposition::Unavailable => !transpose,
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[format]
pub enum LdMatrixForm {
M8N8B16,
M8N16B4x16P64,
M8N16B6x16P32,
M16N16B8,
M16N16B4x16P64,
M16N16B6x16P32,
}
impl LdMatrixForm {
pub fn registers_per_matrix(self) -> usize {
if self.is_16x16() { 2 } else { 1 }
}
pub fn transposition(self) -> MatrixMoveTransposition {
match self {
LdMatrixForm::M8N8B16 => MatrixMoveTransposition::Optional,
LdMatrixForm::M8N16B4x16P64 | LdMatrixForm::M8N16B6x16P32 => {
MatrixMoveTransposition::Unavailable
}
LdMatrixForm::M16N16B8
| LdMatrixForm::M16N16B4x16P64
| LdMatrixForm::M16N16B6x16P32 => MatrixMoveTransposition::Required,
}
}
pub fn admits_matrix_count(self, num_matrices: usize) -> bool {
if self.is_16x16() {
matches!(num_matrices, 1 | 2)
} else {
matches!(num_matrices, 1 | 2 | 4)
}
}
fn is_16x16(self) -> bool {
matches!(
self,
LdMatrixForm::M16N16B8 | LdMatrixForm::M16N16B4x16P64 | LdMatrixForm::M16N16B6x16P32
)
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[format]
pub enum StMatrixForm {
M8N8B16,
M16N8B8,
}
impl StMatrixForm {
pub fn transposition(self) -> MatrixMoveTransposition {
match self {
StMatrixForm::M8N8B16 => MatrixMoveTransposition::Optional,
StMatrixForm::M16N8B8 => MatrixMoveTransposition::Required,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ldmatrix_forms_admit_the_counts_and_transposition_ptx_allows() {
let sixteen_by_sixteen = [
LdMatrixForm::M16N16B8,
LdMatrixForm::M16N16B4x16P64,
LdMatrixForm::M16N16B6x16P32,
];
for form in sixteen_by_sixteen {
assert_eq!(form.registers_per_matrix(), 2);
assert!(form.admits_matrix_count(1) && form.admits_matrix_count(2));
assert!(!form.admits_matrix_count(4) && !form.admits_matrix_count(3));
assert!(form.transposition().admits(true) && !form.transposition().admits(false));
}
for form in [LdMatrixForm::M8N16B4x16P64, LdMatrixForm::M8N16B6x16P32] {
assert_eq!(form.registers_per_matrix(), 1);
assert!(
[1, 2, 4]
.iter()
.all(|count| form.admits_matrix_count(*count))
);
assert!(!form.admits_matrix_count(3));
assert!(!form.transposition().admits(true) && form.transposition().admits(false));
}
let m8n8 = LdMatrixForm::M8N8B16;
assert_eq!(m8n8.registers_per_matrix(), 1);
assert!(
[1, 2, 4]
.iter()
.all(|count| m8n8.admits_matrix_count(*count))
);
assert!(m8n8.transposition().admits(true) && m8n8.transposition().admits(false));
}
#[test]
fn stmatrix_forms_admit_the_transposition_ptx_allows() {
let m16n8 = StMatrixForm::M16N8B8.transposition();
assert!(m16n8.admits(true) && !m16n8.admits(false));
let m8n8 = StMatrixForm::M8N8B16.transposition();
assert!(m8n8.admits(true) && m8n8.admits(false));
}
}