#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BottleneckType {
MemoryBound,
ComputeBound,
Balanced,
}
impl BottleneckType {
pub fn recommendation(&self) -> &'static str {
match self {
BottleneckType::MemoryBound => {
"Improve memory access patterns: coalescing, prefetching, cache blocking"
}
BottleneckType::ComputeBound => {
"Improve compute efficiency: SIMD, kernel fusion, algorithm optimization"
}
BottleneckType::Balanced => {
"Both memory and compute matter equally; profile to find specific bottleneck"
}
}
}
pub fn name(&self) -> &'static str {
match self {
BottleneckType::MemoryBound => "memory-bound",
BottleneckType::ComputeBound => "compute-bound",
BottleneckType::Balanced => "balanced",
}
}
}
#[derive(Debug, Clone)]
pub struct HardwareProfile {
pub name: String,
pub peak_gflops: f64,
pub peak_bandwidth_gbps: f64,
ridge_point: f64,
}
impl HardwareProfile {
pub fn new(name: &str, peak_gflops: f64, peak_bandwidth_gbps: f64) -> Self {
let ridge_point = if peak_bandwidth_gbps > 0.0 {
peak_gflops / peak_bandwidth_gbps
} else {
0.0
};
Self {
name: name.to_string(),
peak_gflops,
peak_bandwidth_gbps,
ridge_point,
}
}
pub fn ridge_point(&self) -> f64 {
self.ridge_point
}
pub fn theoretical_peak_at_oi(&self, operational_intensity: f64) -> f64 {
let memory_bound_peak = self.peak_bandwidth_gbps * operational_intensity;
self.peak_gflops.min(memory_bound_peak)
}
pub fn classify_bottleneck(&self, operational_intensity: f64) -> BottleneckType {
let ratio = operational_intensity / self.ridge_point;
if ratio < 0.9 {
BottleneckType::MemoryBound
} else if ratio > 1.1 {
BottleneckType::ComputeBound
} else {
BottleneckType::Balanced
}
}
}
pub mod profiles {
use super::HardwareProfile;
pub fn a100_sxm() -> HardwareProfile {
HardwareProfile::new("NVIDIA A100 SXM", 19_500.0, 2_039.0)
}
pub fn h100_sxm() -> HardwareProfile {
HardwareProfile::new("NVIDIA H100 SXM", 51_200.0, 3_350.0)
}
pub fn rtx_4090() -> HardwareProfile {
HardwareProfile::new("NVIDIA RTX 4090", 82_580.0, 1_008.0)
}
pub fn rtx_3090() -> HardwareProfile {
HardwareProfile::new("NVIDIA RTX 3090", 35_580.0, 936.0)
}
pub fn mi250x() -> HardwareProfile {
HardwareProfile::new("AMD Instinct MI250X", 47_872.0, 3_277.0)
}
pub fn avx512_per_core() -> HardwareProfile {
HardwareProfile::new("AVX-512 (per core)", 128.0, 50.0)
}
pub fn m2_ultra_gpu() -> HardwareProfile {
HardwareProfile::new("Apple M2 Ultra GPU", 27_200.0, 800.0)
}
pub fn all() -> Vec<HardwareProfile> {
vec![
a100_sxm(),
h100_sxm(),
rtx_4090(),
rtx_3090(),
mi250x(),
avx512_per_core(),
m2_ultra_gpu(),
]
}
}
#[derive(Debug, Clone)]
pub struct WorkloadMetrics {
pub name: String,
pub total_flops: f64,
pub total_bytes: f64,
pub measured_gflops: f64,
pub execution_time_s: f64,
}
impl WorkloadMetrics {
pub fn new(name: &str, total_flops: f64, total_bytes: f64, execution_time_s: f64) -> Self {
let measured_gflops = if execution_time_s > 0.0 {
total_flops / execution_time_s / 1e9
} else {
0.0
};
Self {
name: name.to_string(),
total_flops,
total_bytes,
measured_gflops,
execution_time_s,
}
}
pub fn operational_intensity(&self) -> f64 {
if self.total_bytes > 0.0 {
self.total_flops / self.total_bytes
} else {
0.0
}
}
}
#[derive(Debug, Clone)]
pub struct RooflineAnalysis {
pub hardware: HardwareProfile,
pub workload: WorkloadMetrics,
pub operational_intensity: f64,
pub theoretical_peak: f64,
pub attained_efficiency: f64,
pub bottleneck: BottleneckType,
}
impl RooflineAnalysis {
pub fn analyze(hardware: &HardwareProfile, workload: &WorkloadMetrics) -> Self {
let operational_intensity = workload.operational_intensity();
let theoretical_peak = hardware.theoretical_peak_at_oi(operational_intensity);
let attained_efficiency = if theoretical_peak > 0.0 {
(workload.measured_gflops / theoretical_peak) * 100.0
} else {
0.0
};
let bottleneck = hardware.classify_bottleneck(operational_intensity);
Self {
hardware: hardware.clone(),
workload: workload.clone(),
operational_intensity,
theoretical_peak,
attained_efficiency,
bottleneck,
}
}
pub fn recommendation(&self) -> String {
let base = self.bottleneck.recommendation();
format!(
"{} (OI={:.2} FLOP/Byte, Ridge={:.2}, Efficiency={:.1}%)",
base,
self.operational_intensity,
self.hardware.ridge_point(),
self.attained_efficiency
)
}
}
#[derive(Debug, Clone)]
pub struct RooflinePlotPoint {
pub log_oi: f64,
pub log_perf: f64,
pub oi: f64,
pub perf: f64,
pub label: String,
}
impl RooflinePlotPoint {
pub fn new(label: &str, oi: f64, perf: f64) -> Self {
Self {
log_oi: oi.log2(),
log_perf: perf.log2(),
oi,
perf,
label: label.to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct RooflinePlot {
pub hardware: HardwareProfile,
pub memory_bound_line: Vec<RooflinePlotPoint>,
pub compute_bound_line: Vec<RooflinePlotPoint>,
pub workload_points: Vec<RooflinePlotPoint>,
pub ridge_point: RooflinePlotPoint,
}
impl RooflinePlot {
pub fn generate(hardware: &HardwareProfile, workloads: &[WorkloadMetrics]) -> Self {
let ridge = hardware.ridge_point();
let memory_bound_line: Vec<RooflinePlotPoint> = (0..=20)
.map(|i| {
let oi = 0.1 * (ridge / 0.1).powf(i as f64 / 20.0);
let perf = hardware.peak_bandwidth_gbps * oi;
RooflinePlotPoint::new("memory-bound", oi, perf)
})
.collect();
let compute_bound_line: Vec<RooflinePlotPoint> = (0..=10)
.map(|i| {
let oi = ridge * (100.0 / ridge).powf(i as f64 / 10.0);
RooflinePlotPoint::new("compute-bound", oi, hardware.peak_gflops)
})
.collect();
let workload_points: Vec<RooflinePlotPoint> = workloads
.iter()
.map(|w| RooflinePlotPoint::new(&w.name, w.operational_intensity(), w.measured_gflops))
.collect();
let ridge_point = RooflinePlotPoint::new("ridge", ridge, hardware.peak_gflops);
Self {
hardware: hardware.clone(),
memory_bound_line,
compute_bound_line,
workload_points,
ridge_point,
}
}
}
#[derive(Debug)]
pub struct BatchRooflineAnalysis {
pub hardware: HardwareProfile,
pub analyses: Vec<RooflineAnalysis>,
}
impl BatchRooflineAnalysis {
pub fn analyze(hardware: &HardwareProfile, workloads: &[WorkloadMetrics]) -> Self {
let analyses = workloads
.iter()
.map(|w| RooflineAnalysis::analyze(hardware, w))
.collect();
Self {
hardware: hardware.clone(),
analyses,
}
}
pub fn summary(&self) -> BatchSummary {
let memory_bound = self
.analyses
.iter()
.filter(|a| a.bottleneck == BottleneckType::MemoryBound)
.count();
let compute_bound = self
.analyses
.iter()
.filter(|a| a.bottleneck == BottleneckType::ComputeBound)
.count();
let balanced = self
.analyses
.iter()
.filter(|a| a.bottleneck == BottleneckType::Balanced)
.count();
let avg_efficiency = if self.analyses.is_empty() {
0.0
} else {
self.analyses
.iter()
.map(|a| a.attained_efficiency)
.sum::<f64>()
/ self.analyses.len() as f64
};
BatchSummary {
total: self.analyses.len(),
memory_bound,
compute_bound,
balanced,
avg_efficiency,
}
}
}
#[derive(Debug, Clone)]
pub struct BatchSummary {
pub total: usize,
pub memory_bound: usize,
pub compute_bound: usize,
pub balanced: usize,
pub avg_efficiency: f64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ridge_point_calculation() {
let profile = HardwareProfile::new("Test", 1000.0, 100.0);
assert!((profile.ridge_point() - 10.0).abs() < 0.01);
}
#[test]
fn test_bottleneck_classification_memory_bound() {
let profile = HardwareProfile::new("Test", 1000.0, 100.0);
assert_eq!(
profile.classify_bottleneck(5.0),
BottleneckType::MemoryBound
);
}
#[test]
fn test_bottleneck_classification_compute_bound() {
let profile = HardwareProfile::new("Test", 1000.0, 100.0);
assert_eq!(
profile.classify_bottleneck(20.0),
BottleneckType::ComputeBound
);
}
#[test]
fn test_bottleneck_classification_balanced() {
let profile = HardwareProfile::new("Test", 1000.0, 100.0);
assert_eq!(profile.classify_bottleneck(10.0), BottleneckType::Balanced);
}
#[test]
fn test_operational_intensity() {
let workload = WorkloadMetrics::new("test", 1000.0, 100.0, 1.0);
assert!((workload.operational_intensity() - 10.0).abs() < 0.01);
}
#[test]
fn test_a100_profile() {
let a100 = profiles::a100_sxm();
assert!((a100.peak_gflops - 19500.0).abs() < 1.0);
assert!((a100.peak_bandwidth_gbps - 2039.0).abs() < 1.0);
assert!((a100.ridge_point() - 9.56).abs() < 0.1);
}
#[test]
fn test_h100_profile() {
let h100 = profiles::h100_sxm();
assert!((h100.ridge_point() - 15.28).abs() < 0.1);
}
#[test]
fn test_rtx_4090_profile() {
let rtx4090 = profiles::rtx_4090();
assert!((rtx4090.ridge_point() - 81.9).abs() < 0.5);
}
#[test]
fn test_roofline_analysis() {
let hardware = HardwareProfile::new("Test", 1000.0, 100.0);
let workload = WorkloadMetrics::new("matmul", 1e9, 1e8, 0.01);
let analysis = RooflineAnalysis::analyze(&hardware, &workload);
assert!((analysis.operational_intensity - 10.0).abs() < 0.01);
assert_eq!(analysis.bottleneck, BottleneckType::Balanced);
}
}