use crate::InputStage;
use cubek_test_utils::CatalogEntry;
use crate::{InterpolateBlueprint, InterpolateStrategy};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BenchTier {
#[default]
Light,
Extensive,
Full,
}
impl BenchTier {
pub fn from_env() -> Self {
match std::env::var("CUBEK_BENCH_TIER")
.unwrap_or_default()
.to_lowercase()
.as_str()
{
"extensive" => Self::Extensive,
"full" => Self::Full,
_ => Self::Light,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BenchTarget {
Gpu,
Cpu,
}
impl BenchTarget {
fn residences(self) -> &'static [InputStage] {
match self {
BenchTarget::Gpu => &[InputStage::Smem, InputStage::InPlace],
BenchTarget::Cpu => &[InputStage::InPlace],
}
}
fn sweep(self, tier: BenchTier) -> Sweep {
match (self, tier) {
(_, BenchTier::Full) => Sweep::new(32, 64, 16, 512),
(BenchTarget::Gpu, BenchTier::Light) => Sweep::new(8, 8, 1, 8),
(BenchTarget::Gpu, BenchTier::Extensive) => Sweep::new(16, 64, 1, 256),
(BenchTarget::Cpu, BenchTier::Light) => Sweep::new(8, 16, 2, 128),
(BenchTarget::Cpu, BenchTier::Extensive) => Sweep::new(16, 64, 8, 512),
}
}
fn geometries(self, tier: BenchTier) -> Vec<(usize, usize, usize)> {
self.sweep(tier).geometries()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Sweep {
planes: usize,
rows: usize,
cols: usize,
units: usize,
}
impl Sweep {
const fn new(planes: usize, rows: usize, cols: usize, units: usize) -> Self {
Self {
planes,
rows,
cols,
units,
}
}
fn geometries(&self) -> Vec<(usize, usize, usize)> {
let mut out = Vec::new();
for planes in powers_of_two(self.planes) {
for rows in powers_of_two(self.rows) {
for cols in powers_of_two(self.cols) {
if planes * rows * cols <= self.units {
out.push((planes, rows, cols));
}
}
}
}
out
}
}
fn powers_of_two(max: usize) -> impl Iterator<Item = usize> {
core::iter::successors(Some(1usize), move |n| (n * 2 <= max).then_some(n * 2))
}
fn kernel_entry(
residence: InputStage,
(planes, rows, cols): (usize, usize, usize),
) -> CatalogEntry<InterpolateStrategy> {
let (tag, label) = match residence {
InputStage::Smem => ("smem", "staged"),
_ => ("in_place", "in-place"),
};
CatalogEntry::new(
format!("{tag}_p{planes}_r{rows}_c{cols}"),
format!("{label} (p={planes}, r={rows}, c={cols})"),
InterpolateStrategy::Forced(InterpolateBlueprint::new(residence, planes, rows, cols)),
)
}
fn inferred_entries(target: BenchTarget) -> Vec<CatalogEntry<InterpolateStrategy>> {
let mut entries = vec![CatalogEntry::new(
"maximize_throughput".to_string(),
"selected (maximize throughput)".to_string(),
InterpolateStrategy::MaximizeThroughput,
)];
if target == BenchTarget::Gpu {
entries.push(CatalogEntry::new(
"minimize_latency".to_string(),
"selected (minimize latency)".to_string(),
InterpolateStrategy::MinimizeLatency,
));
}
entries
}
pub fn strategies_at(
tier: BenchTier,
target: BenchTarget,
) -> Vec<CatalogEntry<InterpolateStrategy>> {
let mut entries = inferred_entries(target);
for &residence in target.residences() {
for geometry in target.geometries(tier) {
entries.push(kernel_entry(residence, geometry));
}
}
entries
}
pub fn strategies(target: BenchTarget) -> Vec<CatalogEntry<InterpolateStrategy>> {
strategies_at(BenchTier::from_env(), target)
}
pub fn every_strategy() -> Vec<CatalogEntry<InterpolateStrategy>> {
strategies_at(BenchTier::Full, BenchTarget::Gpu)
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
const TIERS: [BenchTier; 3] = [BenchTier::Light, BenchTier::Extensive, BenchTier::Full];
const TARGETS: [BenchTarget; 2] = [BenchTarget::Gpu, BenchTarget::Cpu];
fn contains(outer: Sweep, inner: Sweep) -> bool {
inner.planes <= outer.planes
&& inner.rows <= outer.rows
&& inner.cols <= outer.cols
&& inner.units <= outer.units
}
fn geometries(target: BenchTarget, tier: BenchTier) -> HashSet<(usize, usize, usize)> {
target.geometries(tier).iter().copied().collect()
}
#[test]
fn the_tiers_nest() {
for target in TARGETS {
let light = geometries(target, BenchTier::Light);
let extensive = geometries(target, BenchTier::Extensive);
let full = geometries(target, BenchTier::Full);
assert!(light.is_subset(&extensive), "{target:?}: light ⊄ extensive");
assert!(extensive.is_subset(&full), "{target:?}: extensive ⊄ full");
}
}
#[test]
fn the_full_tier_is_every_geometry() {
let gpu = BenchTarget::Gpu.sweep(BenchTier::Full);
assert_eq!(gpu, BenchTarget::Cpu.sweep(BenchTier::Full));
assert_eq!(
geometries(BenchTarget::Gpu, BenchTier::Full),
geometries(BenchTarget::Cpu, BenchTier::Full)
);
}
#[test]
fn the_tier_bounds_nest() {
for target in TARGETS {
let light = target.sweep(BenchTier::Light);
let extensive = target.sweep(BenchTier::Extensive);
let full = target.sweep(BenchTier::Full);
assert!(contains(extensive, light), "{target:?}: light ⊄ extensive");
assert!(contains(full, extensive), "{target:?}: extensive ⊄ full");
}
}
#[test]
fn every_geometry_lies_inside_its_box() {
for tier in TIERS {
for target in TARGETS {
let sweep = target.sweep(tier);
for (planes, rows, cols) in target.geometries(tier) {
for extent in [planes, rows, cols] {
assert!(extent.is_power_of_two(), "{extent} is not a power of two");
}
assert!(planes <= sweep.planes && rows <= sweep.rows && cols <= sweep.cols);
assert!(planes * rows * cols <= sweep.units, "{tier:?}/{target:?}");
}
}
}
}
#[test]
fn every_entry_is_named_once() {
for tier in TIERS {
for target in TARGETS {
let entries = strategies_at(tier, target);
let ids: HashSet<_> = entries.iter().map(|entry| entry.id.clone()).collect();
assert_eq!(ids.len(), entries.len(), "{tier:?}/{target:?}");
}
}
}
#[test]
fn every_tier_is_a_subset_of_the_nameable_catalogue() {
let nameable: HashSet<_> = every_strategy().into_iter().map(|e| e.id).collect();
for tier in TIERS {
for target in TARGETS {
for entry in strategies_at(tier, target) {
assert!(nameable.contains(&entry.id), "{} is unnameable", entry.id);
}
}
}
}
#[test]
fn the_cpu_geometries_stage_nothing() {
for tier in TIERS {
for entry in strategies_at(tier, BenchTarget::Cpu) {
if let InterpolateStrategy::Forced(blueprint) = entry.value {
assert_ne!(blueprint.input_residence, InputStage::Smem, "{}", entry.id);
}
}
}
}
#[test]
fn every_tier_measures_the_inferred_intents() {
for tier in TIERS {
for target in TARGETS {
let entries = strategies_at(tier, target);
for intent in inferred_entries(target) {
assert!(
entries.iter().any(|entry| entry.value == intent.value),
"{tier:?}/{target:?} is missing {}",
intent.id
);
}
}
}
}
}