use rlx_ir::DType;
use rlx_ir::kernel_schedule::{
Access, Action, Barrier, Feature, Instruction, KernelSchedule, KernelScheduleError, Layout,
Region, Role, Space, Target, verify_kernel_schedule,
};
pub const BLOCK_ROLE: &str = "threadgroup";
pub const METAL_TARGET: Target = Target::METAL_APPLE;
pub const SIMDGROUP_TILE: u32 = 8;
pub const TG_TILE: usize = 32;
pub fn hgemm_simd_4x4_schedule() -> KernelSchedule {
let mut s = KernelSchedule::new("hgemm_simd_4x4");
s.stages = 1;
s.requires = vec![Feature::CoopMatrix {
m: SIMDGROUP_TILE,
n: SIMDGROUP_TILE,
k: SIMDGROUP_TILE,
}];
for name in ["A_tg", "B_tg"] {
s.regions.push(Region {
name: name.into(),
space: Space::Shared,
dims: vec![TG_TILE, TG_TILE],
dtype: DType::F16,
stages: 1,
layout: Layout::row_major(&[TG_TILE, TG_TILE]),
});
}
s.regions.push(Region {
name: "acc".into(),
space: Space::Register,
dims: vec![SIMDGROUP_TILE as usize, SIMDGROUP_TILE as usize],
dtype: DType::F16,
stages: 1,
layout: Layout::row_major(&[SIMDGROUP_TILE as usize, SIMDGROUP_TILE as usize]),
});
s.roles = vec![Role {
name: BLOCK_ROLE.into(),
warps: (0..16).collect(),
}];
for name in ["tiles_filled", "tiles_consumed"] {
s.barriers.push(Barrier {
name: name.into(),
producers: vec![BLOCK_ROLE.into()],
consumers: vec![BLOCK_ROLE.into()],
count: 1,
});
}
s.body.insert(
BLOCK_ROLE.into(),
vec![
Action::Load {
access: Access::plain("A_tg"),
stage: 0,
},
Action::Load {
access: Access::plain("B_tg"),
stage: 0,
},
Action::Arrive {
barrier: "tiles_filled".into(),
stage: 0,
},
Action::Wait {
barrier: "tiles_filled".into(),
stage: 0,
},
Action::Compute {
reads: vec![Access::plain("A_tg"), Access::plain("B_tg")],
writes: vec![Access::plain("acc")],
stage: 0,
via: Some(Instruction::CoopMatrix {
m: SIMDGROUP_TILE,
n: SIMDGROUP_TILE,
k: SIMDGROUP_TILE,
}),
},
Action::Arrive {
barrier: "tiles_consumed".into(),
stage: 0,
},
Action::Wait {
barrier: "tiles_consumed".into(),
stage: 0,
},
Action::Store {
access: Access::plain("acc"),
stage: 0,
},
],
);
s
}
pub fn verify() -> Result<(), Vec<KernelScheduleError>> {
let e = verify_kernel_schedule(&hgemm_simd_4x4_schedule(), METAL_TARGET);
if e.is_empty() { Ok(()) } else { Err(e) }
}
pub fn simdgroup_strides_in(kernel_name: &str) -> Vec<usize> {
let src = crate::kernels::RLX_KERNELS_MSL;
let Some(start) = src.find(&format!("kernel void {kernel_name}(")) else {
return Vec::new();
};
let rest = &src[start + 1..];
let end = rest
.find("\nkernel void ")
.map_or(src.len(), |o| start + 1 + o);
let body = &src[start..end];
let mut out = Vec::new();
for call in ["simdgroup_load(", "simdgroup_store("] {
let mut at = 0usize;
while let Some(i) = body[at..].find(call) {
let open = at + i + call.len();
let (mut depth, mut j) = (1i32, open);
let bytes = body.as_bytes();
while j < body.len() && depth > 0 {
match bytes[j] {
b'(' | b'[' => depth += 1,
b')' | b']' => depth -= 1,
_ => {}
}
j += 1;
}
let args = &body[open..j.saturating_sub(1)];
if let Some(last) = split_top_level(args).last()
&& let Ok(v) = last.trim().parse::<usize>()
{
out.push(v);
}
at = open;
}
}
out
}
fn split_top_level(args: &str) -> Vec<&str> {
let (mut depth, mut start) = (0i32, 0usize);
let mut parts = Vec::new();
for (i, c) in args.char_indices() {
match c {
'(' | '[' => depth += 1,
')' | ']' => depth -= 1,
',' if depth == 0 => {
parts.push(&args[start..i]);
start = i + 1;
}
_ => {}
}
}
parts.push(&args[start..]);
parts
}