use cubecl_core as cubecl;
use cubecl_core::prelude::*;
use crate::components::MatmulProblem;
use crate::components::batch::partitioned_matmul::hypercube::global_order::{GlobalOrder, swizzle};
use crate::components::batch::partitioned_matmul::hypercube::sm_allocation::SmAllocation;
use crate::components::batch::{HypercubeConfig, HypercubeSelection};
#[derive(Default, Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub enum CubeCountPlanSelection {
#[default]
FromProblem,
Sm {
cubes_first: bool,
num_sms: u32,
sm_usage: SmAllocation,
},
Flattened,
Spread,
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub enum CubeCountPlan {
FromProblem {
m_cubes: u32,
n_cubes: u32,
batch_cubes: u32,
},
Sm {
cubes_first: bool,
num_sms_used: u32,
cubes_per_sm: u32,
m_cubes: u32,
n_cubes: u32,
batch_cubes: u32,
num_sms: u32,
sm_usage: SmAllocation,
},
Flattened {
m_cubes: u32,
n_cubes: u32,
batch_cubes: u32,
},
Spread {
m_cubes: u32,
n_cubes: u32,
batch_cubes: u32,
x: u32,
y: u32,
z: u32,
},
}
impl CubeCountPlan {
pub fn can_yield_extra_cubes(&self) -> bool {
match self {
CubeCountPlan::FromProblem { .. } | CubeCountPlan::Flattened { .. } => false,
CubeCountPlan::Sm {
num_sms_used,
cubes_per_sm,
m_cubes,
n_cubes,
batch_cubes,
..
} => num_sms_used * cubes_per_sm != m_cubes * n_cubes * batch_cubes,
CubeCountPlan::Spread {
m_cubes,
n_cubes,
batch_cubes,
x,
y,
z,
} => m_cubes * n_cubes * batch_cubes != x * y * z,
}
}
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub enum CubeCountPlanConfig {
FromProblem,
Sm {
cubes_first: bool,
num_sms: u32,
sm_usage: SmAllocation,
can_yield_extra_cubes: bool,
},
Flattened,
Spread {
can_yield_extra_cubes: bool,
},
}
#[derive(CubeType, CubeLaunch)]
pub enum CubeCountInput {
FromProblem,
SmFirst {
m_cubes: u32,
n_cubes: u32,
batch_cubes: u32,
},
CubeFirst {
m_cubes: u32,
n_cubes: u32,
batch_cubes: u32,
},
Flattened {
m_cubes: u32,
n_cubes: u32,
},
Spread {
m_cubes: u32,
n_cubes: u32,
batch_cubes: u32,
},
}
impl CubeCountPlan {
pub fn from_selection(
selection: &HypercubeSelection,
problem: &MatmulProblem,
max_cube_count: CubeCount,
) -> CubeCountPlan {
let (max_x, max_y, max_z) = match max_cube_count {
CubeCount::Static(x, y, z) => (x, y, z),
CubeCount::Dynamic(_) => panic!("Dynamic cube count not supported for cube count plan"),
};
let m_cubes = (problem.m as u32).div_ceil(selection.cube_span.m);
let n_cubes = (problem.n as u32).div_ceil(selection.cube_span.n);
let batch_cubes = (problem.num_batches() as u32).div_ceil(selection.cube_span.batch);
let plan = match selection.cube_count_plan_selection {
CubeCountPlanSelection::FromProblem => {
if m_cubes > max_x || n_cubes > max_y || batch_cubes > max_z {
None
} else {
Some(CubeCountPlan::FromProblem {
m_cubes,
n_cubes,
batch_cubes,
})
}
}
CubeCountPlanSelection::Sm {
cubes_first,
num_sms,
sm_usage,
} => {
let (num_sms_used, cubes_per_sm) =
sm_usage.allocate(num_sms, m_cubes * n_cubes * batch_cubes);
if (cubes_per_sm >= if cubes_first { max_x } else { max_y })
|| (num_sms_used >= if cubes_first { max_y } else { max_x })
{
None
} else {
Some(CubeCountPlan::Sm {
cubes_first,
num_sms_used,
cubes_per_sm,
m_cubes,
n_cubes,
batch_cubes,
num_sms,
sm_usage,
})
}
}
CubeCountPlanSelection::Flattened => {
if m_cubes * n_cubes * batch_cubes >= max_x {
None
} else {
Some(CubeCountPlan::Flattened {
m_cubes,
n_cubes,
batch_cubes,
})
}
}
CubeCountPlanSelection::Spread => None,
};
plan.unwrap_or_else(|| {
spread_cube_count_plan(m_cubes, n_cubes, batch_cubes, max_x, max_y, max_z)
})
}
pub fn from_config(
config: &HypercubeConfig,
problem: &MatmulProblem,
max_cube_count: CubeCount,
) -> CubeCountPlan {
let (max_x, max_y, max_z) = match max_cube_count {
CubeCount::Static(x, y, z) => (x, y, z),
CubeCount::Dynamic(_) => panic!("Dynamic cube count not supported for cube count plan"),
};
let m_cubes = (problem.m as u32).div_ceil(config.cube_span.m);
let n_cubes = (problem.n as u32).div_ceil(config.cube_span.n);
let batch_cubes = (problem.num_batches() as u32).div_ceil(config.cube_span.batch);
match config.cube_count_plan_config {
CubeCountPlanConfig::FromProblem => CubeCountPlan::FromProblem {
m_cubes,
n_cubes,
batch_cubes,
},
CubeCountPlanConfig::Sm {
cubes_first,
num_sms,
sm_usage,
..
} => {
let (num_sms_used, cubes_per_sm) =
sm_usage.allocate(num_sms, m_cubes * n_cubes * batch_cubes);
CubeCountPlan::Sm {
cubes_first,
num_sms_used,
cubes_per_sm,
m_cubes,
n_cubes,
batch_cubes,
num_sms,
sm_usage,
}
}
CubeCountPlanConfig::Flattened => CubeCountPlan::Flattened {
m_cubes,
n_cubes,
batch_cubes,
},
CubeCountPlanConfig::Spread { .. } => {
spread_cube_count_plan(m_cubes, n_cubes, batch_cubes, max_x, max_y, max_z)
}
}
}
}
impl CubeCountPlanConfig {
pub fn can_yield_extra_cubes(&self) -> bool {
match self {
CubeCountPlanConfig::FromProblem | CubeCountPlanConfig::Flattened => false,
CubeCountPlanConfig::Sm {
can_yield_extra_cubes,
..
} => *can_yield_extra_cubes,
CubeCountPlanConfig::Spread {
can_yield_extra_cubes,
} => *can_yield_extra_cubes,
}
}
pub(crate) fn from_cube_count_plan(cube_count_plan: CubeCountPlan) -> CubeCountPlanConfig {
match cube_count_plan {
CubeCountPlan::FromProblem { .. } => CubeCountPlanConfig::FromProblem,
CubeCountPlan::Sm {
cubes_first,
num_sms,
sm_usage,
..
} => CubeCountPlanConfig::Sm {
cubes_first,
num_sms,
sm_usage,
can_yield_extra_cubes: cube_count_plan.can_yield_extra_cubes(),
},
CubeCountPlan::Flattened { .. } => CubeCountPlanConfig::Flattened,
CubeCountPlan::Spread { .. } => CubeCountPlanConfig::Spread {
can_yield_extra_cubes: cube_count_plan.can_yield_extra_cubes(),
},
}
}
}
pub(crate) fn spread_cube_count_plan(
m_cubes: u32,
n_cubes: u32,
batch_cubes: u32,
max_x: u32,
max_y: u32,
max_z: u32,
) -> CubeCountPlan {
let total_cubes = m_cubes * n_cubes * batch_cubes;
let mut best = None;
let mut z = max_z;
while z >= 1 {
let xy_cubes = total_cubes.div_ceil(z);
let mut y = max_y;
while y >= 1 {
let x = xy_cubes.div_ceil(y);
if x <= max_x {
let volume = x * y * z;
let score = (volume, std::cmp::Reverse(z), std::cmp::Reverse(y));
if best.is_none_or(|(_, _, _, _, best_score)| score < best_score) {
best = Some((x, y, z, volume, score));
}
}
if y == 1 {
break;
}
y /= 2;
}
if z == 1 {
break;
}
z /= 2;
}
if let Some((x, y, z, _, _)) = best {
CubeCountPlan::Spread {
m_cubes,
n_cubes,
batch_cubes,
x,
y,
z,
}
} else {
panic!("No valid cube spread plan")
}
}
impl CubeCountPlan {
pub fn resolve(&self) -> CubeCount {
match self {
CubeCountPlan::FromProblem {
m_cubes,
n_cubes,
batch_cubes,
} => CubeCount::Static(*m_cubes, *n_cubes, *batch_cubes),
CubeCountPlan::Sm {
cubes_first,
num_sms_used,
cubes_per_sm,
..
} => match cubes_first {
true => CubeCount::Static(*cubes_per_sm, *num_sms_used, 1),
false => CubeCount::Static(*num_sms_used, *cubes_per_sm, 1),
},
CubeCountPlan::Flattened {
m_cubes,
n_cubes,
batch_cubes,
} => CubeCount::Static(*m_cubes * *n_cubes * *batch_cubes, 1, 1),
CubeCountPlan::Spread { x, y, z, .. } => CubeCount::Static(*x, *y, *z),
}
}
pub fn as_args<'a, R: Runtime>(&self) -> CubeCountInputArgs<'a, R> {
match self {
CubeCountPlan::FromProblem { .. } => CubeCountInputArgs::FromProblem,
CubeCountPlan::Sm {
cubes_first,
m_cubes,
n_cubes,
batch_cubes,
..
} => match cubes_first {
true => CubeCountInputArgs::CubeFirst {
m_cubes: ScalarArg::new(*m_cubes),
n_cubes: ScalarArg::new(*n_cubes),
batch_cubes: ScalarArg::new(*batch_cubes),
},
false => CubeCountInputArgs::SmFirst {
m_cubes: ScalarArg::new(*m_cubes),
n_cubes: ScalarArg::new(*n_cubes),
batch_cubes: ScalarArg::new(*batch_cubes),
},
},
CubeCountPlan::Flattened {
m_cubes, n_cubes, ..
} => CubeCountInputArgs::Flattened {
m_cubes: ScalarArg::new(*m_cubes),
n_cubes: ScalarArg::new(*n_cubes),
},
CubeCountPlan::Spread {
m_cubes,
n_cubes,
batch_cubes,
..
} => CubeCountInputArgs::Spread {
m_cubes: ScalarArg::new(*m_cubes),
n_cubes: ScalarArg::new(*n_cubes),
batch_cubes: ScalarArg::new(*batch_cubes),
},
}
}
}
#[cube]
impl CubeCountInput {
pub fn num_valid_cubes(&self) -> u32 {
match self {
CubeCountInput::FromProblem | CubeCountInput::Flattened { .. } => {
panic!("Shouldn't need to be called because the cube count should always be exact")
}
CubeCountInput::SmFirst {
m_cubes,
n_cubes,
batch_cubes,
} => *m_cubes * *n_cubes * *batch_cubes,
CubeCountInput::CubeFirst {
m_cubes,
n_cubes,
batch_cubes,
} => *m_cubes * *n_cubes * *batch_cubes,
CubeCountInput::Spread {
m_cubes,
n_cubes,
batch_cubes,
} => *m_cubes * *n_cubes * *batch_cubes,
}
}
pub fn cube_pos_to_tensor_pos(&self, #[comptime] global_order: GlobalOrder) -> (u32, u32, u32) {
match self {
CubeCountInput::FromProblem => (CUBE_POS_X, CUBE_POS_Y, CUBE_POS_Z),
CubeCountInput::SmFirst {
m_cubes, n_cubes, ..
} => self.absolute_index_to_m_n_batch(CUBE_POS, *m_cubes, *n_cubes, global_order),
CubeCountInput::CubeFirst {
m_cubes, n_cubes, ..
} => self.absolute_index_to_m_n_batch(
CUBE_POS_Y * CUBE_COUNT_X + CUBE_POS_X,
*m_cubes,
*n_cubes,
global_order,
),
CubeCountInput::Flattened { m_cubes, n_cubes } => {
self.absolute_index_to_m_n_batch(CUBE_POS_X, *m_cubes, *n_cubes, global_order)
}
CubeCountInput::Spread {
m_cubes, n_cubes, ..
} => self.absolute_index_to_m_n_batch(CUBE_POS, *m_cubes, *n_cubes, global_order),
}
}
fn absolute_index_to_m_n_batch(
&self,
absolute_index: u32,
m_cubes: u32,
n_cubes: u32,
#[comptime] global_order: GlobalOrder,
) -> (u32, u32, u32) {
let batch_stride = m_cubes * n_cubes;
let batch_pos = absolute_index / batch_stride;
let matrix_pos = absolute_index % batch_stride;
let (m_pos, n_pos) = match comptime!(global_order) {
GlobalOrder::RowMajor => (matrix_pos / n_cubes, matrix_pos % n_cubes),
GlobalOrder::ColMajor => (matrix_pos % m_cubes, matrix_pos / m_cubes),
GlobalOrder::SwizzleRowMajor(w) => {
let (x, y) = swizzle(matrix_pos, n_cubes, w);
(y, x)
}
GlobalOrder::SwizzleColMajor(w) => swizzle(matrix_pos, m_cubes, w),
};
(m_pos, n_pos, batch_pos)
}
}