use std::collections::BTreeMap;
use super::literal::ResultAllocator;
use crate::descriptor::{
KernelBody, KernelDescriptor, KernelOp, KernelOpKind, MatrixMmaElement, MatrixMmaLayout,
MatrixMmaShape,
};
use crate::operand_semantics::remap_body_result_ids;
const MATMUL_TILE_LEN: usize = 4; const A_FRAGMENT_LEN: usize = 4;
const B_FRAGMENT_LEN: usize = 2;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MatmulTileLoopPlan {
pub body_path: Vec<usize>,
pub loop_op_index: usize,
pub child_body_index: u32,
pub fma_start_index: usize,
}
#[must_use]
pub fn infer_matmul_tile_loops(desc: &KernelDescriptor) -> Vec<MatmulTileLoopPlan> {
let mut plans = Vec::new();
infer_body(&desc.body, &mut Vec::new(), &mut plans);
plans
}
#[must_use]
pub fn matmul_promote(desc: &KernelDescriptor) -> KernelDescriptor {
let mut out = desc.clone();
let mut allocator = ResultAllocator::for_body_tree(&desc.body);
let mut id_map = BTreeMap::new();
out.body = promote_body(&out.body, &mut allocator, &mut id_map);
if !id_map.is_empty() {
out.body = remap_body_result_ids(&out.body, &id_map);
}
out
}
fn promote_body(
body: &KernelBody,
allocator: &mut ResultAllocator,
id_map: &mut BTreeMap<u32, u32>,
) -> KernelBody {
let mut new_ops: Vec<KernelOp> = Vec::with_capacity(body.ops.len());
let ops = &body.ops;
let mut i = 0;
while i < ops.len() {
if let Some((promoted, advance, remap)) = try_promote_at(ops, i, allocator) {
new_ops.push(promoted);
id_map.extend(remap);
i += advance;
} else {
new_ops.push(ops[i].clone());
i += 1;
}
}
let new_children: Vec<KernelBody> = body
.child_bodies
.iter()
.map(|c| promote_body(c, allocator, id_map))
.collect();
KernelBody {
ops: new_ops,
child_bodies: new_children,
literals: body.literals.clone(),
}
}
fn infer_body(body: &KernelBody, path: &mut Vec<usize>, plans: &mut Vec<MatmulTileLoopPlan>) {
for (op_index, op) in body.ops.iter().enumerate() {
if matches!(op.kind, KernelOpKind::StructuredForLoop { .. }) {
let Some(child_body_index) = op.operands.get(2).copied() else {
continue;
};
let Some(child) = body.child_bodies.get(child_body_index as usize) else {
continue;
};
for fma_start_index in promotable_fma_starts(&child.ops) {
plans.push(MatmulTileLoopPlan {
body_path: path.clone(),
loop_op_index: op_index,
child_body_index,
fma_start_index,
});
}
}
}
for (child_index, child) in body.child_bodies.iter().enumerate() {
path.push(child_index);
infer_body(child, path, plans);
path.pop();
}
}
fn promotable_fma_starts(ops: &[KernelOp]) -> Vec<usize> {
let mut starts = Vec::new();
for i in 0..ops.len() {
if match_fragment_at(ops, i).is_some() {
starts.push(i);
}
}
starts
}
fn try_promote_at(
ops: &[KernelOp],
i: usize,
allocator: &mut ResultAllocator,
) -> Option<(KernelOp, usize, [(u32, u32); MATMUL_TILE_LEN])> {
let FragmentMatch {
a_ids,
b_unique,
c_ids,
c_out,
} = match_fragment_at(ops, i)?;
let mut operands = Vec::with_capacity(A_FRAGMENT_LEN + B_FRAGMENT_LEN + MATMUL_TILE_LEN);
operands.extend_from_slice(&a_ids);
operands.extend_from_slice(&b_unique);
operands.extend_from_slice(&c_ids);
let result_base = allocator.fresh_block(MATMUL_TILE_LEN as u32);
let promoted = KernelOp {
kind: KernelOpKind::MatrixMma {
shape: MatrixMmaShape::M16N8K16,
a_layout: MatrixMmaLayout::RowMajor,
b_layout: MatrixMmaLayout::ColMajor,
a_type: MatrixMmaElement::F16,
b_type: MatrixMmaElement::F16,
accum_type: MatrixMmaElement::F32,
},
operands,
result: Some(result_base),
};
let remap = [
(c_out[0], result_base),
(c_out[1], result_base + 1),
(c_out[2], result_base + 2),
(c_out[3], result_base + 3),
];
Some((promoted, MATMUL_TILE_LEN, remap))
}
struct FragmentMatch {
a_ids: [u32; A_FRAGMENT_LEN],
b_unique: [u32; B_FRAGMENT_LEN],
c_ids: [u32; MATMUL_TILE_LEN],
c_out: [u32; MATMUL_TILE_LEN],
}
fn match_fragment_at(ops: &[KernelOp], i: usize) -> Option<FragmentMatch> {
let needed = MATMUL_TILE_LEN; if i + needed > ops.len() {
return None;
}
let block = &ops[i..i + needed];
if !block.iter().all(|op| matches!(op.kind, KernelOpKind::Fma)) {
return None;
}
if !block
.iter()
.all(|op| op.operands.len() == 3 && op.result.is_some())
{
return None;
}
let c_ids = [
block[0].operands[2],
block[1].operands[2],
block[2].operands[2],
block[3].operands[2],
];
if !all_distinct(&c_ids) {
return None;
}
let c_out = [
block[0].result?,
block[1].result?,
block[2].result?,
block[3].result?,
];
let a_ids = [
block[0].operands[0],
block[1].operands[0],
block[2].operands[0],
block[3].operands[0],
];
let b_ids = [
block[0].operands[1],
block[1].operands[1],
block[2].operands[1],
block[3].operands[1],
];
if !all_distinct(&a_ids) {
return None;
}
let mut b_unique: Vec<u32> = b_ids.to_vec();
b_unique.sort_unstable();
b_unique.dedup();
if b_unique.len() != B_FRAGMENT_LEN {
return None;
}
Some(FragmentMatch {
a_ids,
b_unique: [b_unique[0], b_unique[1]],
c_ids,
c_out,
})
}
fn all_distinct(ids: &[u32]) -> bool {
let mut sorted = ids.to_vec();
sorted.sort_unstable();
sorted.windows(2).all(|w| w[0] != w[1])
}
#[cfg(test)]
mod tests {
use super::*;
use crate::descriptor::{
BindingLayout, Dispatch, KernelBody, KernelDescriptor, KernelOp, KernelOpKind,
};
fn empty_desc(body: KernelBody) -> KernelDescriptor {
KernelDescriptor {
id: "k".into(),
bindings: BindingLayout { slots: vec![] },
dispatch: Dispatch::new(1, 1, 1),
body,
}
}
fn lit(result: u32) -> KernelOp {
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(result),
}
}
fn fma(a: u32, b: u32, c: u32, result: u32) -> KernelOp {
KernelOp {
kind: KernelOpKind::Fma,
operands: vec![a, b, c],
result: Some(result),
}
}
fn store_global(slot: u32, idx: u32, val: u32) -> KernelOp {
KernelOp {
kind: KernelOpKind::StoreGlobal,
operands: vec![slot, idx, val],
result: None,
}
}
#[test]
fn empty_body_is_unchanged() {
let desc = empty_desc(KernelBody {
ops: vec![],
child_bodies: vec![],
literals: vec![],
});
let out = matmul_promote(&desc);
assert!(out.body.ops.is_empty());
}
#[test]
fn non_matmul_body_is_unchanged() {
let desc = empty_desc(KernelBody {
ops: vec![lit(0), lit(1), lit(2)],
child_bodies: vec![],
literals: vec![],
});
let out = matmul_promote(&desc);
assert_eq!(out.body.ops.len(), 3);
assert!(out
.body
.ops
.iter()
.all(|op| matches!(op.kind, KernelOpKind::Literal)));
}
#[test]
fn four_fma_with_correct_shape_promotes_to_matrix_mma() {
let prelude = vec![
lit(0), lit(1), lit(2), lit(3), lit(4), lit(5), lit(6), lit(7), lit(8), lit(9), ];
let fmas = vec![
fma(0, 4, 6, 10),
fma(1, 5, 7, 11),
fma(2, 4, 8, 12),
fma(3, 5, 9, 13),
];
let mut ops = prelude;
ops.extend(fmas);
let desc = empty_desc(KernelBody {
ops,
child_bodies: vec![],
literals: vec![],
});
let out = matmul_promote(&desc);
assert_eq!(out.body.ops.len(), 11);
let mma = out.body.ops.last().unwrap();
assert!(matches!(mma.kind, KernelOpKind::MatrixMma { .. }));
assert_eq!(mma.operands, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
}
#[test]
fn promotion_keeps_accumulator_consumers_wired() {
let prelude = (0..10).map(lit).collect::<Vec<_>>();
let fmas = vec![
fma(0, 4, 6, 10),
fma(1, 5, 7, 11),
fma(2, 4, 8, 12),
fma(3, 5, 9, 13),
];
let stores = vec![
store_global(0, 0, 10),
store_global(0, 0, 11),
store_global(0, 0, 12),
store_global(0, 0, 13),
];
let mut ops = prelude;
ops.extend(fmas);
ops.extend(stores);
let desc = empty_desc(KernelBody {
ops,
child_bodies: vec![],
literals: vec![],
});
let out = matmul_promote(&desc);
assert_eq!(
out.body
.ops
.iter()
.filter(|op| matches!(op.kind, KernelOpKind::Fma))
.count(),
0,
"all four Fmas collapse"
);
assert_eq!(
out.body
.ops
.iter()
.filter(|op| matches!(op.kind, KernelOpKind::MatrixMma { .. }))
.count(),
1,
"into exactly one MatrixMma"
);
let produced: Vec<u32> = out.body.ops.iter().flat_map(|op| op.result_ids()).collect();
let store_values: Vec<u32> = out
.body
.ops
.iter()
.filter(|op| matches!(op.kind, KernelOpKind::StoreGlobal))
.map(|op| op.operands[2])
.collect();
assert_eq!(store_values.len(), 4, "all four stores survive");
for value in &store_values {
assert!(
produced.contains(value),
"store value {value} dangles: promotion dropped the Fma that \
produced it without re-pointing the consumer to the MatrixMma"
);
}
}
#[test]
fn fma_chain_with_repeated_a_does_not_promote() {
let prelude = (0..10).map(lit).collect::<Vec<_>>();
let fmas = vec![
fma(0, 4, 6, 10),
fma(0, 5, 7, 11), fma(2, 4, 8, 12),
fma(3, 5, 9, 13),
];
let mut ops = prelude;
ops.extend(fmas);
let desc = empty_desc(KernelBody {
ops,
child_bodies: vec![],
literals: vec![],
});
let out = matmul_promote(&desc);
assert_eq!(out.body.ops.len(), 14);
assert!(!out
.body
.ops
.iter()
.any(|op| matches!(op.kind, KernelOpKind::MatrixMma { .. })));
}
#[test]
fn fma_chain_with_three_unique_b_does_not_promote() {
let prelude = (0..11).map(lit).collect::<Vec<_>>();
let fmas = vec![
fma(0, 4, 6, 11),
fma(1, 5, 7, 12),
fma(2, 10, 8, 13), fma(3, 5, 9, 14),
];
let mut ops = prelude;
ops.extend(fmas);
let desc = empty_desc(KernelBody {
ops,
child_bodies: vec![],
literals: vec![],
});
let out = matmul_promote(&desc);
assert!(!out
.body
.ops
.iter()
.any(|op| matches!(op.kind, KernelOpKind::MatrixMma { .. })));
}
#[test]
fn matmul_promote_recurses_into_child_bodies() {
let prelude = (0..10).map(lit).collect::<Vec<_>>();
let fmas = vec![
fma(0, 4, 6, 10),
fma(1, 5, 7, 11),
fma(2, 4, 8, 12),
fma(3, 5, 9, 13),
];
let mut child_ops = prelude;
child_ops.extend(fmas);
let child = KernelBody {
ops: child_ops,
child_bodies: vec![],
literals: vec![],
};
let desc = empty_desc(KernelBody {
ops: vec![lit(0)],
child_bodies: vec![child],
literals: vec![],
});
let out = matmul_promote(&desc);
let promoted_child = &out.body.child_bodies[0];
assert!(promoted_child
.ops
.iter()
.any(|op| matches!(op.kind, KernelOpKind::MatrixMma { .. })));
}
#[test]
fn matmul_tile_loop_inference_finds_fma_fragment_inside_structured_loop() {
let prelude = (0..10).map(lit).collect::<Vec<_>>();
let fmas = vec![
fma(0, 4, 6, 10),
fma(1, 5, 7, 11),
fma(2, 4, 8, 12),
fma(3, 5, 9, 13),
];
let mut child_ops = prelude;
child_ops.extend(fmas);
let child = KernelBody {
ops: child_ops,
child_bodies: vec![],
literals: vec![],
};
let desc = empty_desc(KernelBody {
ops: vec![
lit(20),
lit(21),
KernelOp {
kind: KernelOpKind::StructuredForLoop {
loop_var: "k".into(),
},
operands: vec![20, 21, 0],
result: None,
},
],
child_bodies: vec![child],
literals: vec![],
});
let plans = infer_matmul_tile_loops(&desc);
assert_eq!(
plans,
vec![MatmulTileLoopPlan {
body_path: vec![],
loop_op_index: 2,
child_body_index: 0,
fma_start_index: 10,
}]
);
}
#[test]
fn matmul_promote_is_idempotent() {
let prelude = (0..10).map(lit).collect::<Vec<_>>();
let fmas = vec![
fma(0, 4, 6, 10),
fma(1, 5, 7, 11),
fma(2, 4, 8, 12),
fma(3, 5, 9, 13),
];
let mut ops = prelude;
ops.extend(fmas);
let desc = empty_desc(KernelBody {
ops,
child_bodies: vec![],
literals: vec![],
});
let once = matmul_promote(&desc);
let twice = matmul_promote(&once);
assert_eq!(once.body.ops.len(), twice.body.ops.len());
}
}