use crate::graph::ComputationGraph;
use crate::JitResult;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NormalDistribution {
pub mean: f64,
pub std_dev: f64,
}
impl NormalDistribution {
pub fn new(mean: f64, std_dev: f64) -> Self {
Self { mean, std_dev }
}
pub fn sample(&self) -> f64 {
use std::f64::consts::PI;
let u1 = self.uniform_sample();
let u2 = self.uniform_sample();
let z0 = (-2.0 * u1.ln()).sqrt() * (2.0 * PI * u2).cos();
self.mean + self.std_dev * z0
}
fn uniform_sample(&self) -> f64 {
use std::time::{SystemTime, UNIX_EPOCH};
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system time should be after UNIX_EPOCH")
.subsec_nanos();
((nanos % 10000) as f64 / 10000.0).max(0.0001)
}
pub fn pdf(&self, x: f64) -> f64 {
let coefficient = 1.0 / (self.std_dev * (2.0 * std::f64::consts::PI).sqrt());
let exponent = -((x - self.mean).powi(2)) / (2.0 * self.std_dev.powi(2));
coefficient * exponent.exp()
}
pub fn cdf(&self, x: f64) -> f64 {
let z = (x - self.mean) / self.std_dev;
0.5 * (1.0 + Self::erf(z / std::f64::consts::SQRT_2))
}
fn erf(x: f64) -> f64 {
let a1 = 0.254829592;
let a2 = -0.284496736;
let a3 = 1.421413741;
let a4 = -1.453152027;
let a5 = 1.061405429;
let p = 0.3275911;
let sign = if x < 0.0 { -1.0 } else { 1.0 };
let x = x.abs();
let t = 1.0 / (1.0 + p * x);
let y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * (-x * x).exp();
sign * y
}
pub fn confidence_interval(&self, confidence: f64) -> (f64, f64) {
let z_score = match confidence {
c if c >= 0.99 => 2.576,
c if c >= 0.95 => 1.96,
c if c >= 0.90 => 1.645,
_ => 1.0,
};
let margin = z_score * self.std_dev;
(self.mean - margin, self.mean + margin)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BetaDistribution {
pub alpha: f64,
pub beta: f64,
}
impl BetaDistribution {
pub fn new(alpha: f64, beta: f64) -> Self {
Self { alpha, beta }
}
pub fn mean(&self) -> f64 {
self.alpha / (self.alpha + self.beta)
}
pub fn mode(&self) -> f64 {
if self.alpha > 1.0 && self.beta > 1.0 {
(self.alpha - 1.0) / (self.alpha + self.beta - 2.0)
} else {
self.mean()
}
}
pub fn variance(&self) -> f64 {
let sum = self.alpha + self.beta;
(self.alpha * self.beta) / (sum.powi(2) * (sum + 1.0))
}
pub fn update(&mut self, success: bool) {
if success {
self.alpha += 1.0;
} else {
self.beta += 1.0;
}
}
pub fn credible_interval(&self, confidence: f64) -> (f64, f64) {
let mean = self.mean();
let std_dev = self.variance().sqrt();
let z_score = if confidence >= 0.95 { 1.96 } else { 1.645 };
let margin = z_score * std_dev;
((mean - margin).max(0.0), (mean + margin).min(1.0))
}
}
#[derive(Debug, Clone)]
pub struct ProbabilisticPerformance {
pub time_dist: NormalDistribution,
pub memory_dist: NormalDistribution,
pub success_prob: BetaDistribution,
pub variance_factors: HashMap<String, f64>,
}
impl ProbabilisticPerformance {
pub fn new(mean_time: f64, mean_memory: f64) -> Self {
Self {
time_dist: NormalDistribution::new(mean_time, mean_time * 0.2), memory_dist: NormalDistribution::new(mean_memory, mean_memory * 0.15), success_prob: BetaDistribution::new(10.0, 1.0), variance_factors: HashMap::new(),
}
}
pub fn sample_time(&self) -> f64 {
self.time_dist.sample().max(0.0)
}
pub fn sample_memory(&self) -> f64 {
self.memory_dist.sample().max(0.0)
}
pub fn time_confidence_interval(&self, confidence: f64) -> (f64, f64) {
self.time_dist.confidence_interval(confidence)
}
pub fn memory_confidence_interval(&self, confidence: f64) -> (f64, f64) {
self.memory_dist.confidence_interval(confidence)
}
pub fn expected_time(&self) -> f64 {
self.time_dist.mean
}
pub fn expected_memory(&self) -> f64 {
self.memory_dist.mean
}
pub fn value_at_risk(&self, percentile: f64) -> f64 {
let z = match percentile {
p if p >= 0.99 => 2.326, p if p >= 0.95 => 1.645, _ => 1.0,
};
self.time_dist.mean + z * self.time_dist.std_dev
}
}
#[derive(Debug, Clone)]
pub struct UncertainDecision {
pub optimization: String,
pub prob_improvement: BetaDistribution,
pub speedup_dist: NormalDistribution,
pub risk: f64,
pub observations: Vec<Observation>,
}
#[derive(Debug, Clone)]
pub struct Observation {
pub beneficial: bool,
pub speedup: f64,
pub context: HashMap<String, f64>,
}
impl UncertainDecision {
pub fn new(optimization: String) -> Self {
Self {
optimization,
prob_improvement: BetaDistribution::new(1.0, 1.0), speedup_dist: NormalDistribution::new(1.2, 0.3), risk: 0.5,
observations: Vec::new(),
}
}
pub fn observe(&mut self, beneficial: bool, speedup: f64, context: HashMap<String, f64>) {
self.prob_improvement.update(beneficial);
if !self.observations.is_empty() {
let n = self.observations.len() as f64;
let old_mean = self.speedup_dist.mean;
let new_mean = (old_mean * n + speedup) / (n + 1.0);
self.speedup_dist.mean = new_mean;
let old_var = self.speedup_dist.std_dev.powi(2);
let new_var = ((old_var * n) + (speedup - new_mean).powi(2)) / (n + 1.0);
self.speedup_dist.std_dev = new_var.sqrt();
} else {
self.speedup_dist.mean = speedup;
}
self.observations.push(Observation {
beneficial,
speedup,
context,
});
self.risk = self.prob_improvement.variance();
}
pub fn should_apply(&self) -> bool {
let prob = self.prob_improvement.mean();
let threshold = 0.6; prob > threshold
}
pub fn expected_utility(&self, risk_aversion: f64) -> f64 {
let expected_reward = self.speedup_dist.mean * self.prob_improvement.mean();
let risk_penalty = risk_aversion * self.risk;
expected_reward - risk_penalty
}
}
pub struct ProbabilisticCompiler {
config: ProbabilisticConfig,
decisions: HashMap<String, UncertainDecision>,
performance_model: Option<ProbabilisticPerformance>,
stats: CompilerStatistics,
}
#[derive(Debug, Clone)]
pub struct ProbabilisticConfig {
pub confidence_level: f64,
pub risk_aversion: f64,
pub num_samples: usize,
pub bayesian_optimization: bool,
pub exploration_rate: f64,
}
impl Default for ProbabilisticConfig {
fn default() -> Self {
Self {
confidence_level: 0.95,
risk_aversion: 0.5,
num_samples: 1000,
bayesian_optimization: true,
exploration_rate: 0.1,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct CompilerStatistics {
pub compilations: usize,
pub avg_prediction_error: f64,
pub calibration_score: f64,
}
impl ProbabilisticCompiler {
pub fn new() -> Self {
Self::with_config(ProbabilisticConfig::default())
}
pub fn with_config(config: ProbabilisticConfig) -> Self {
let mut decisions = HashMap::new();
for opt in [
"constant_folding",
"dead_code_elimination",
"fusion",
"vectorization",
"parallelization",
"tiling",
] {
decisions.insert(opt.to_string(), UncertainDecision::new(opt.to_string()));
}
Self {
config,
decisions,
performance_model: None,
stats: CompilerStatistics::default(),
}
}
pub fn compile(
&mut self,
graph: &ComputationGraph,
) -> JitResult<ProbabilisticCompilationResult> {
let node_count = graph.node_count() as f64;
let base_time = node_count * 10.0; let base_memory = node_count * 1024.0;
let mut perf = ProbabilisticPerformance::new(base_time, base_memory);
let mut applied_opts = Vec::new();
let mut decisions_made = Vec::new();
for (opt_name, decision_model) in &self.decisions {
let should_apply = if self.config.bayesian_optimization {
decision_model.should_apply()
} else {
decision_model.prob_improvement.mean() > 0.5
};
if should_apply {
applied_opts.push(opt_name.clone());
let speedup = decision_model.speedup_dist.sample();
perf.time_dist.mean /= speedup;
}
decisions_made.push(OptimizationDecision {
optimization: opt_name.clone(),
applied: should_apply,
prob_improvement: decision_model.prob_improvement.mean(),
expected_speedup: decision_model.speedup_dist.mean,
credible_interval: decision_model
.prob_improvement
.credible_interval(self.config.confidence_level),
});
}
self.performance_model = Some(perf.clone());
self.stats.compilations += 1;
Ok(ProbabilisticCompilationResult {
performance: perf,
decisions: decisions_made,
applied_optimizations: applied_opts,
confidence_level: self.config.confidence_level,
risk_score: self.compute_overall_risk(),
})
}
pub fn observe_performance(
&mut self,
applied_opts: &[String],
actual_time: f64,
predicted_time: f64,
) -> JitResult<()> {
let error = (actual_time - predicted_time).abs() / predicted_time;
let n = self.stats.compilations as f64;
self.stats.avg_prediction_error = (self.stats.avg_prediction_error * (n - 1.0) + error) / n;
for opt_name in applied_opts {
if let Some(decision) = self.decisions.get_mut(opt_name) {
let speedup = predicted_time / actual_time;
let beneficial = speedup > 1.0;
decision.observe(beneficial, speedup, HashMap::new());
}
}
if let Some(perf_model) = &mut self.performance_model {
let confidence_boost = 0.9; perf_model.time_dist.std_dev *= confidence_boost;
}
log::info!(
"Observed performance: actual={:.2}μs, predicted={:.2}μs, error={:.1}%",
actual_time,
predicted_time,
error * 100.0
);
Ok(())
}
fn compute_overall_risk(&self) -> f64 {
let mut total_risk = 0.0;
let mut count = 0;
for decision in self.decisions.values() {
total_risk += decision.risk;
count += 1;
}
if count > 0 {
total_risk / count as f64
} else {
0.5
}
}
pub fn monte_carlo_simulation(&self, num_samples: usize) -> MonteCarloResult {
if let Some(perf_model) = &self.performance_model {
let mut samples = Vec::with_capacity(num_samples);
for _ in 0..num_samples {
let time = perf_model.sample_time();
samples.push(time);
}
samples.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let mean = samples.iter().sum::<f64>() / num_samples as f64;
let variance =
samples.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / num_samples as f64;
let p50 = samples[num_samples / 2];
let p95 = samples[(num_samples as f64 * 0.95) as usize];
let p99 = samples[(num_samples as f64 * 0.99) as usize];
MonteCarloResult {
mean,
std_dev: variance.sqrt(),
percentiles: vec![(50, p50), (95, p95), (99, p99)],
samples: samples.into_iter().take(100).collect(), }
} else {
MonteCarloResult::default()
}
}
pub fn statistics(&self) -> &CompilerStatistics {
&self.stats
}
}
impl Default for ProbabilisticCompiler {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct ProbabilisticCompilationResult {
pub performance: ProbabilisticPerformance,
pub decisions: Vec<OptimizationDecision>,
pub applied_optimizations: Vec<String>,
pub confidence_level: f64,
pub risk_score: f64,
}
#[derive(Debug, Clone)]
pub struct OptimizationDecision {
pub optimization: String,
pub applied: bool,
pub prob_improvement: f64,
pub expected_speedup: f64,
pub credible_interval: (f64, f64),
}
#[derive(Debug, Clone, Default)]
pub struct MonteCarloResult {
pub mean: f64,
pub std_dev: f64,
pub percentiles: Vec<(usize, f64)>,
pub samples: Vec<f64>,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::GraphBuilder;
use torsh_core::{DType, Shape};
#[test]
fn test_normal_distribution() {
let dist = NormalDistribution::new(100.0, 10.0);
assert_eq!(dist.mean, 100.0);
assert_eq!(dist.std_dev, 10.0);
let sample = dist.sample();
assert!(sample > 0.0);
let (lower, upper) = dist.confidence_interval(0.95);
assert!(lower < dist.mean);
assert!(upper > dist.mean);
}
#[test]
fn test_beta_distribution() {
let mut dist = BetaDistribution::new(10.0, 2.0);
let mean = dist.mean();
assert!(mean > 0.5);
dist.update(true);
assert!(dist.alpha == 11.0);
let (lower, upper) = dist.credible_interval(0.95);
assert!(lower <= upper);
}
#[test]
fn test_uncertain_decision() {
let mut decision = UncertainDecision::new("fusion".to_string());
decision.observe(true, 1.5, HashMap::new());
assert!(decision.prob_improvement.alpha > 1.0);
let utility = decision.expected_utility(0.5);
assert!(utility > 0.0);
}
#[test]
fn test_probabilistic_compilation() {
let mut compiler = ProbabilisticCompiler::new();
let mut builder = GraphBuilder::new();
let x = builder.add_input("x".to_string(), Shape::new(vec![10, 10]), DType::F32);
builder.mark_output(x).unwrap();
let graph = builder.build().unwrap();
let result = compiler.compile(&graph).unwrap();
assert!(!result.decisions.is_empty());
assert!(result.performance.expected_time() > 0.0);
assert!(result.confidence_level == 0.95);
}
#[test]
fn test_performance_observation() {
let mut compiler = ProbabilisticCompiler::new();
let mut builder = GraphBuilder::new();
let x = builder.add_input("x".to_string(), Shape::new(vec![5, 5]), DType::F32);
builder.mark_output(x).unwrap();
let graph = builder.build().unwrap();
let result = compiler.compile(&graph).unwrap();
let predicted = result.performance.expected_time();
let actual = predicted * 1.1;
let obs_result =
compiler.observe_performance(&result.applied_optimizations, actual, predicted);
assert!(obs_result.is_ok());
}
#[test]
fn test_monte_carlo() {
let mut compiler = ProbabilisticCompiler::new();
let mut builder = GraphBuilder::new();
let x = builder.add_input("x".to_string(), Shape::new(vec![8, 8]), DType::F32);
builder.mark_output(x).unwrap();
let graph = builder.build().unwrap();
let _ = compiler.compile(&graph).unwrap();
let mc_result = compiler.monte_carlo_simulation(100);
assert!(mc_result.mean > 0.0);
assert!(mc_result.std_dev >= 0.0);
assert!(!mc_result.percentiles.is_empty());
}
#[test]
fn test_value_at_risk() {
let perf = ProbabilisticPerformance::new(1000.0, 10000.0);
let var_95 = perf.value_at_risk(0.95);
let var_99 = perf.value_at_risk(0.99);
assert!(var_95 > perf.expected_time());
assert!(var_99 > var_95); }
}