use crate::{self as cubecl, prelude::*};
use cubecl_runtime::tma::TensorMapSwizzle;
use cubecl_ir::dialect::matrix::warpgroup_k;
pub use cubecl_ir::dialect::matrix::{WgmmaMajor as Major, WgmmaSwizzle as Swizzle};
const CHUNK_BYTES: usize = 16;
const PATTERN_LINES: usize = 8;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct WgmmaTileLayout {
pub major: Major,
pub swizzle: Swizzle,
pub rows: usize,
pub k: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WgmmaLayoutError {
PartialPanel {
line_bytes: usize,
panel_bytes: usize,
},
PartialPattern { lines: usize },
PartialStep { k: usize, k_step: usize },
MnMajor { elem_size: usize },
}
impl core::fmt::Display for WgmmaLayoutError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::PartialPanel {
line_bytes,
panel_bytes,
} => write!(
f,
"a line of {line_bytes} bytes doesn't fill whole {panel_bytes}-byte panels"
),
Self::PartialPattern { lines } => write!(
f,
"{lines} lines isn't a multiple of the {PATTERN_LINES} the swizzle repeats over"
),
Self::PartialStep { k, k_step } => {
write!(f, "a K of {k} isn't a multiple of the MMA's {k_step}")
}
Self::MnMajor { elem_size } => write!(
f,
"only 16-bit elements may be MN-major, and these are {} bits",
elem_size * 8
),
}
}
}
impl WgmmaTileLayout {
pub fn k_step(elem_size: usize) -> usize {
warpgroup_k(elem_size)
}
pub fn alignment(&self) -> usize {
self.swizzle.alignment()
}
pub fn tensor_map_swizzle(&self) -> TensorMapSwizzle {
self.swizzle.into()
}
pub fn panel_elems(&self, elem_size: usize) -> usize {
self.panel_bytes() / elem_size
}
pub fn validate(&self, elem_size: usize) -> Result<(), WgmmaLayoutError> {
if self.major == Major::MN && elem_size != 2 {
return Err(WgmmaLayoutError::MnMajor { elem_size });
}
let line_bytes = self.line_elems() * elem_size;
let panel_bytes = self.panel_bytes();
if !line_bytes.is_multiple_of(panel_bytes) {
return Err(WgmmaLayoutError::PartialPanel {
line_bytes,
panel_bytes,
});
}
let lines = self.lines();
if !lines.is_multiple_of(PATTERN_LINES) {
return Err(WgmmaLayoutError::PartialPattern { lines });
}
let k_step = Self::k_step(elem_size);
if !self.k.is_multiple_of(k_step) {
return Err(WgmmaLayoutError::PartialStep { k: self.k, k_step });
}
Ok(())
}
pub fn leading_byte_offset(&self) -> usize {
match (self.major, self.swizzle) {
(Major::K, Swizzle::None) => self.panel_stride(),
(Major::K, _) => CHUNK_BYTES,
(Major::MN, Swizzle::None) => self.pattern_stride(),
(Major::MN, _) => self.panel_stride(),
}
}
pub fn stride_byte_offset(&self) -> usize {
match (self.major, self.swizzle) {
(Major::MN, Swizzle::None) => self.panel_stride(),
_ => self.pattern_stride(),
}
}
pub fn byte_offset(&self, row: usize, k: usize, elem_size: usize) -> usize {
let (along, line) = match self.major {
Major::K => (k, row),
Major::MN => (row, k),
};
let along = along * elem_size;
let panel_bytes = self.panel_bytes();
(along / panel_bytes) * self.panel_stride() + line * panel_bytes + along % panel_bytes
}
pub fn offset(&self, row: usize, k: usize, elem_size: usize) -> usize {
let bytes = self.byte_offset(row, k, elem_size);
let pattern = (bytes / (CHUNK_BYTES * PATTERN_LINES)) & self.chunk_mask();
(bytes ^ (pattern * CHUNK_BYTES)) / elem_size
}
pub fn __expand_byte_offset_method(
&self,
scope: &Scope,
row: NativeExpand<usize>,
k: NativeExpand<usize>,
elem_size: usize,
) -> NativeExpand<usize> {
let (along, line) = match self.major {
Major::K => (k, row),
Major::MN => (row, k),
};
byte_offset::expand(scope, along, line, *self, elem_size)
}
pub fn __expand_offset_method(
&self,
scope: &Scope,
row: NativeExpand<usize>,
k: NativeExpand<usize>,
elem_size: usize,
) -> NativeExpand<usize> {
let bytes = self.__expand_byte_offset_method(scope, row, k, elem_size);
swizzle::expand(scope, bytes, self.chunk_mask(), elem_size)
}
fn panel_bytes(&self) -> usize {
self.swizzle.width()
}
fn line_elems(&self) -> usize {
match self.major {
Major::K => self.k,
Major::MN => self.rows,
}
}
fn lines(&self) -> usize {
match self.major {
Major::K => self.rows,
Major::MN => self.k,
}
}
fn panel_stride(&self) -> usize {
self.lines() * self.panel_bytes()
}
fn pattern_stride(&self) -> usize {
PATTERN_LINES * self.panel_bytes()
}
fn chunk_mask(&self) -> usize {
self.panel_bytes() / CHUNK_BYTES - 1
}
}
#[cube]
fn byte_offset(
along: usize,
line: usize,
#[comptime] layout: WgmmaTileLayout,
#[comptime] elem_size: usize,
) -> usize {
let panel_bytes = comptime![layout.panel_bytes()];
let along = along * elem_size;
(along / panel_bytes) * comptime![layout.panel_stride()]
+ line * panel_bytes
+ along % panel_bytes
}
#[cube]
fn swizzle(bytes: usize, #[comptime] chunk_mask: usize, #[comptime] elem_size: usize) -> usize {
let pattern = (bytes / comptime![CHUNK_BYTES * PATTERN_LINES]) & chunk_mask;
(bytes ^ (pattern * CHUNK_BYTES)) / elem_size
}
#[cfg(test)]
mod tests {
use super::*;
fn layout(major: Major, swizzle: Swizzle, rows: usize, k: usize) -> WgmmaTileLayout {
WgmmaTileLayout {
major,
swizzle,
rows,
k,
}
}
#[test]
fn k_major_no_swizzle_matches_the_ptx_example() {
let tile = layout(Major::K, Swizzle::None, 16, 8);
assert_eq!(tile.leading_byte_offset(), 64 * 4);
assert_eq!(tile.stride_byte_offset(), 32 * 4);
for (row, k) in [(0, 0), (1, 0), (7, 3), (8, 0), (0, 4), (15, 7)] {
let expected = (row % 8) * 4 + (row / 8) * 32 + (k % 4) + (k / 4) * 64;
assert_eq!(tile.offset(row, k, 4), expected, "({row}, {k})");
}
}
#[test]
fn k_major_32b_matches_the_ptx_example() {
let tile = layout(Major::K, Swizzle::B32, 16, 8);
assert_eq!(tile.stride_byte_offset(), 64 * 4);
for (row, k) in [(0, 0), (1, 0), (7, 3), (8, 0), (0, 4), (15, 7)] {
let unswizzled = (row % 8) * 8 + (row / 8) * 64 + k;
assert_eq!(tile.byte_offset(row, k, 4), unswizzled * 4, "({row}, {k})");
}
}
fn canonical(tile: &WgmmaTileLayout, row: usize, k: usize, elem_size: usize) -> usize {
let t = CHUNK_BYTES / elem_size;
let c = tile.swizzle.width() / CHUNK_BYTES;
let lbo = tile.leading_byte_offset() / elem_size;
let sbo = tile.stride_byte_offset() / elem_size;
match (tile.major, tile.swizzle) {
(Major::K, Swizzle::None) => (row % 8) * t + (row / 8) * sbo + k % t + (k / t) * lbo,
(Major::K, _) => (row % 8) * c * t + (row / 8) * sbo + k,
(Major::MN, Swizzle::None) => row % t + (row / t) * sbo + (k % 8) * t + (k / 8) * lbo,
(Major::MN, _) => {
row % t
+ ((row / t) % c) * t
+ (row / (c * t)) * lbo
+ (k % 8) * c * t
+ (k / 8) * sbo
}
}
}
#[test]
fn every_layout_is_canonical() {
let swizzles = [Swizzle::None, Swizzle::B32, Swizzle::B64, Swizzle::B128];
for major in [Major::K, Major::MN] {
for swizzle in swizzles {
for (rows, k) in [(64, 64), (128, 64), (256, 128)] {
let tile = layout(major, swizzle, rows, k);
tile.validate(2).unwrap();
for row in 0..rows {
for kk in 0..16 {
assert_eq!(
tile.byte_offset(row, kk, 2),
canonical(&tile, row, kk, 2) * 2,
"{tile:?} at ({row}, {kk})"
);
}
}
}
}
}
}
#[test]
fn mn_major_no_swizzle_uses_core_matrices() {
let tile = layout(Major::MN, Swizzle::None, 16, 16);
assert_eq!(tile.leading_byte_offset(), 128);
assert_eq!(tile.stride_byte_offset(), 16 * 16);
assert_eq!(tile.offset(0, 1, 2), 8);
assert_eq!(tile.offset(8, 0, 2), 128);
}
#[test]
fn swizzle_128b_permutes_chunks_by_line() {
let tile = layout(Major::K, Swizzle::B128, 64, 64);
for line in 0..8 {
for chunk in 0..8 {
let k = chunk * 8;
let expected = line * 64 + (chunk ^ line) * 8;
assert_eq!(
tile.offset(line, k, 2),
expected,
"line {line}, chunk {chunk}"
);
}
}
assert_eq!(tile.offset(9, 0, 2), 9 * 64 + 8);
}
#[test]
fn narrow_swizzles_permute_by_address() {
let b64 = layout(Major::K, Swizzle::B64, 8, 32);
assert_eq!(b64.offset(1, 0, 2), 32);
assert_eq!(b64.offset(2, 0, 2), 64 + 8);
assert_eq!(b64.offset(6, 8, 2), 6 * 32 + (1 ^ 3) * 8);
let b32 = layout(Major::K, Swizzle::B32, 8, 16);
assert_eq!(b32.offset(3, 0, 2), 3 * 16);
assert_eq!(b32.offset(4, 0, 2), 4 * 16 + 8);
assert_eq!(b32.offset(4, 8, 2), 4 * 16);
}
#[test]
fn k_major_panels_follow_each_other() {
let tile = layout(Major::K, Swizzle::B64, 64, 64);
assert_eq!(tile.byte_offset(0, 32, 2), 64 * 64);
assert_eq!(tile.byte_offset(1, 33, 2), 64 * 64 + 64 + 2);
assert_eq!(tile.byte_offset(0, 16, 2), 32);
}
#[test]
fn validate_rejects_layouts_no_mma_reads() {
let partial_panel = layout(Major::K, Swizzle::B128, 64, 32);
assert_eq!(
partial_panel.validate(2),
Err(WgmmaLayoutError::PartialPanel {
line_bytes: 64,
panel_bytes: 128
})
);
let mn_fp8 = layout(Major::MN, Swizzle::B128, 128, 64);
assert_eq!(
mn_fp8.validate(1),
Err(WgmmaLayoutError::MnMajor { elem_size: 1 })
);
let partial_step = layout(Major::MN, Swizzle::B128, 64, 8);
assert_eq!(
partial_step.validate(2),
Err(WgmmaLayoutError::PartialStep { k: 8, k_step: 16 })
);
assert_eq!(layout(Major::K, Swizzle::B128, 64, 64).validate(2), Ok(()));
}
}