#![allow(dead_code)]
mod builder;
mod registry;
mod reporter;
mod runner;
pub use builder::{BenchmarkBuilder, BenchmarkDSL, BenchmarkSpec, BenchmarkStage};
pub use registry::{BenchmarkCategory, BenchmarkMetadata, BenchmarkRegistry};
pub use reporter::{BenchmarkReport, ReportFormat, Reporter};
pub use runner::{BenchmarkRunner, BenchmarkRunnerBuilder, RunConfig, RunMode};
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::time::Duration;
pub trait CustomBenchmark: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn tags(&self) -> Vec<String> {
vec![]
}
fn setup(&mut self) -> Result<()> {
Ok(())
}
fn warmup(&mut self, iterations: usize) -> Result<()> {
for _ in 0..iterations {
self.run_iteration()?;
}
Ok(())
}
fn run_iteration(&mut self) -> Result<BenchmarkIteration>;
fn teardown(&mut self) -> Result<()> {
Ok(())
}
fn validate(&self, #[allow(unused_variables)] iteration: &BenchmarkIteration) -> Result<bool> {
Ok(true)
}
fn config(&self) -> BenchmarkConfig {
BenchmarkConfig::default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenchmarkIteration {
pub duration: Duration,
pub metrics: BenchmarkMetrics,
pub validation_passed: Option<bool>,
pub metadata: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BenchmarkMetrics {
pub throughput: Option<f64>,
pub latency_percentiles: Option<LatencyPercentiles>,
pub memory_bytes: Option<usize>,
pub gpu_utilization: Option<f64>,
pub model_metrics: Option<ModelMetrics>,
pub custom: std::collections::HashMap<String, f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LatencyPercentiles {
pub p50: f64,
pub p90: f64,
pub p95: f64,
pub p99: f64,
pub p999: f64,
pub min: f64,
pub max: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelMetrics {
pub tokens_per_second: Option<f64>,
pub flops_utilization: Option<f64>,
pub batch_size: Option<usize>,
pub sequence_length: Option<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenchmarkConfig {
pub warmup_iterations: usize,
pub iterations: usize,
pub min_duration: Duration,
pub max_duration: Duration,
pub validate_results: bool,
pub num_threads: Option<usize>,
pub device: DeviceConfig,
}
impl Default for BenchmarkConfig {
fn default() -> Self {
Self {
warmup_iterations: 10,
iterations: 100,
min_duration: Duration::from_secs(10),
max_duration: Duration::from_secs(300),
validate_results: true,
num_threads: None,
device: DeviceConfig::default(),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub enum DeviceConfig {
#[default]
Cpu,
Gpu(usize),
AllGpus,
Custom(String),
}
#[macro_export]
macro_rules! create_benchmark {
($name:ident, $description:expr, $run_fn:expr) => {
pub struct $name {
config: BenchmarkConfig,
}
impl $name {
pub fn new() -> Self {
Self {
config: BenchmarkConfig::default(),
}
}
pub fn with_config(config: BenchmarkConfig) -> Self {
Self { config }
}
}
impl CustomBenchmark for $name {
fn name(&self) -> &str {
stringify!($name)
}
fn description(&self) -> &str {
$description
}
fn run_iteration(&mut self) -> Result<BenchmarkIteration> {
$run_fn()
}
fn config(&self) -> BenchmarkConfig {
self.config.clone()
}
}
};
}
pub struct ExampleBenchmark {
model_name: String,
batch_size: usize,
sequence_length: usize,
}
impl ExampleBenchmark {
pub fn new(model_name: String, batch_size: usize, sequence_length: usize) -> Self {
Self {
model_name,
batch_size,
sequence_length,
}
}
}
impl CustomBenchmark for ExampleBenchmark {
fn name(&self) -> &str {
"example_benchmark"
}
fn description(&self) -> &str {
"Example custom benchmark for demonstration"
}
fn tags(&self) -> Vec<String> {
vec!["example".to_string(), "demo".to_string()]
}
fn setup(&mut self) -> Result<()> {
println!("Setting up benchmark for model: {}", self.model_name);
Ok(())
}
fn run_iteration(&mut self) -> Result<BenchmarkIteration> {
use std::time::Instant;
let start = Instant::now();
std::thread::sleep(Duration::from_millis(10));
let duration = start.elapsed();
let metrics = BenchmarkMetrics {
throughput: Some(self.batch_size as f64 / duration.as_secs_f64()),
model_metrics: Some(ModelMetrics {
tokens_per_second: Some(
(self.batch_size * self.sequence_length) as f64 / duration.as_secs_f64(),
),
flops_utilization: Some(0.85),
batch_size: Some(self.batch_size),
sequence_length: Some(self.sequence_length),
}),
..Default::default()
};
Ok(BenchmarkIteration {
duration,
metrics,
validation_passed: Some(true),
metadata: None,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_example_benchmark() {
let mut benchmark = ExampleBenchmark::new("test-model".to_string(), 32, 128);
assert_eq!(benchmark.name(), "example_benchmark");
assert!(benchmark.setup().is_ok());
let iteration = benchmark.run_iteration().expect("operation failed in test");
assert!(iteration.duration.as_millis() >= 10);
assert!(iteration.metrics.throughput.is_some());
}
#[test]
fn test_benchmark_config() {
let config = BenchmarkConfig::default();
assert_eq!(config.warmup_iterations, 10);
assert_eq!(config.iterations, 100);
assert!(config.validate_results);
}
}