use crate::graph::{ComputationGraph, NodeId};
use crate::JitResult;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompilationParams {
pub pass_weights: HashMap<String, f32>,
pub fusion_temp: f32,
pub unroll_factor: f32,
pub vector_width: f32,
pub layout_preference: f32,
#[serde(skip)]
pub gradients: HashMap<String, f32>,
}
impl CompilationParams {
pub fn new() -> Self {
let mut pass_weights = HashMap::new();
for pass_name in [
"constant_folding",
"dead_code_elimination",
"common_subexpression_elimination",
"loop_invariant_motion",
"strength_reduction",
"fusion",
"vectorization",
"parallelization",
] {
pass_weights.insert(pass_name.to_string(), 0.5); }
Self {
pass_weights,
fusion_temp: 1.0,
unroll_factor: 4.0,
vector_width: 4.0,
layout_preference: 0.5,
gradients: HashMap::new(),
}
}
pub fn update(&mut self, learning_rate: f32) {
for (name, weight) in &mut self.pass_weights {
if let Some(&grad) = self.gradients.get(name) {
*weight -= learning_rate * grad;
*weight = weight.clamp(0.0, 1.0); }
}
if let Some(&grad) = self.gradients.get("fusion_temp") {
self.fusion_temp -= learning_rate * grad;
self.fusion_temp = self.fusion_temp.max(0.1); }
if let Some(&grad) = self.gradients.get("unroll_factor") {
self.unroll_factor -= learning_rate * grad;
self.unroll_factor = self.unroll_factor.clamp(1.0, 32.0);
}
if let Some(&grad) = self.gradients.get("vector_width") {
self.vector_width -= learning_rate * grad;
self.vector_width = self.vector_width.clamp(1.0, 16.0);
}
if let Some(&grad) = self.gradients.get("layout_preference") {
self.layout_preference -= learning_rate * grad;
self.layout_preference = self.layout_preference.clamp(0.0, 1.0);
}
self.gradients.clear();
}
pub fn zero_grad(&mut self) {
self.gradients.clear();
}
pub fn accumulate_grad(&mut self, name: &str, grad: f32) {
*self.gradients.entry(name.to_string()).or_insert(0.0) += grad;
}
}
impl Default for CompilationParams {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct SoftDecision {
pub probability: f32,
pub gradient: f32,
}
impl SoftDecision {
pub fn new(probability: f32) -> Self {
Self {
probability: probability.clamp(0.0, 1.0),
gradient: 0.0,
}
}
pub fn apply<T: Clone>(&self, if_true: T, if_false: T, blend_fn: fn(&T, &T, f32) -> T) -> T {
blend_fn(&if_true, &if_false, self.probability)
}
pub fn backward(&mut self, upstream_grad: f32) {
self.gradient += upstream_grad;
}
pub fn sigmoid(x: f32) -> f32 {
1.0 / (1.0 + (-x).exp())
}
pub fn softmax(logits: &[f32]) -> Vec<f32> {
let max = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
let exp_values: Vec<f32> = logits.iter().map(|&x| (x - max).exp()).collect();
let sum: f32 = exp_values.iter().sum();
exp_values.iter().map(|&x| x / sum).collect()
}
}
#[derive(Debug, Clone)]
pub struct DiffCompilationResult {
pub graph: ComputationGraph,
pub decisions: Vec<CompilationDecision>,
pub estimated_performance: PerformanceMetrics,
pub tape: Arc<Mutex<ComputationTape>>,
}
#[derive(Debug, Clone)]
pub struct CompilationDecision {
pub name: String,
pub decision_type: DecisionType,
pub decision: SoftDecision,
pub impact: f32,
}
#[derive(Debug, Clone, PartialEq)]
pub enum DecisionType {
ApplyOptimization(String),
FuseOperations(NodeId, NodeId),
UnrollLoop(usize),
Vectorize(usize),
MemoryLayout(String),
}
#[derive(Debug, Clone, Default)]
pub struct PerformanceMetrics {
pub exec_time_us: f32,
pub memory_bytes: f32,
pub flops: f32,
pub cache_efficiency: f32,
}
#[derive(Debug, Clone, Default)]
pub struct ComputationTape {
pub operations: Vec<TapeOperation>,
pub gradients: HashMap<String, f32>,
}
#[derive(Debug, Clone)]
pub struct TapeOperation {
pub name: String,
pub inputs: Vec<String>,
pub output: String,
pub forward_val: f32,
pub grad_fn: GradientFunction,
}
#[derive(Debug, Clone)]
pub enum GradientFunction {
Linear(f32),
Product(f32),
Sigmoid,
ReLU,
Custom(fn(f32, f32) -> f32),
}
pub struct DifferentiableCompiler {
config: DiffCompilerConfig,
stats: CompilerStatistics,
}
#[derive(Debug, Clone)]
pub struct DiffCompilerConfig {
pub gradient_checkpointing: bool,
pub straight_through: bool,
pub gumbel_temperature: f32,
pub grad_clip: f32,
}
impl Default for DiffCompilerConfig {
fn default() -> Self {
Self {
gradient_checkpointing: true,
straight_through: true,
gumbel_temperature: 1.0,
grad_clip: 10.0,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct CompilerStatistics {
pub compilations: usize,
pub gradient_updates: usize,
pub avg_loss: f32,
pub best_performance: f32,
}
impl DifferentiableCompiler {
pub fn new() -> Self {
Self::with_config(DiffCompilerConfig::default())
}
pub fn with_config(config: DiffCompilerConfig) -> Self {
Self {
config,
stats: CompilerStatistics::default(),
}
}
pub fn compile_differentiable(
&mut self,
graph: &ComputationGraph,
params: &CompilationParams,
) -> JitResult<DiffCompilationResult> {
let mut tape = ComputationTape::default();
let mut decisions = Vec::new();
let mut compiled_graph = graph.clone();
for (pass_name, &weight) in ¶ms.pass_weights {
let decision = SoftDecision::new(weight);
decisions.push(CompilationDecision {
name: pass_name.clone(),
decision_type: DecisionType::ApplyOptimization(pass_name.clone()),
decision: decision.clone(),
impact: self.estimate_pass_impact(pass_name, graph),
});
if weight > 0.5 {
compiled_graph =
self.apply_soft_optimization(&compiled_graph, pass_name, weight)?;
}
tape.operations.push(TapeOperation {
name: format!("apply_{}", pass_name),
inputs: vec!["graph".to_string()],
output: "graph".to_string(),
forward_val: weight,
grad_fn: GradientFunction::Linear(1.0),
});
}
let estimated_performance = self.estimate_performance(&compiled_graph, params);
self.stats.compilations += 1;
Ok(DiffCompilationResult {
graph: compiled_graph,
decisions,
estimated_performance,
tape: Arc::new(Mutex::new(tape)),
})
}
pub fn backward(
&mut self,
result: &DiffCompilationResult,
loss: f32,
) -> JitResult<CompilationParams> {
let mut params_grad = CompilationParams::new();
params_grad.zero_grad();
for decision in &result.decisions {
match &decision.decision_type {
DecisionType::ApplyOptimization(pass_name) => {
let grad = if decision.impact > 0.0 {
-loss * decision.impact
} else {
loss * decision.impact.abs()
};
params_grad.accumulate_grad(pass_name, grad);
}
_ => {
}
}
}
for (_name, grad) in &mut params_grad.gradients {
*grad = grad.clamp(-self.config.grad_clip, self.config.grad_clip);
}
self.stats.gradient_updates += 1;
self.stats.avg_loss = (self.stats.avg_loss * (self.stats.gradient_updates - 1) as f32
+ loss)
/ self.stats.gradient_updates as f32;
Ok(params_grad)
}
fn apply_soft_optimization(
&self,
graph: &ComputationGraph,
pass_name: &str,
_weight: f32,
) -> JitResult<ComputationGraph> {
log::debug!("Applying soft optimization: {} with weight", pass_name);
Ok(graph.clone())
}
fn estimate_pass_impact(&self, pass_name: &str, _graph: &ComputationGraph) -> f32 {
match pass_name {
"constant_folding" => 0.1,
"dead_code_elimination" => 0.15,
"common_subexpression_elimination" => 0.2,
"fusion" => 0.3,
"vectorization" => 0.4,
"parallelization" => 0.5,
_ => 0.05,
}
}
fn estimate_performance(
&self,
graph: &ComputationGraph,
params: &CompilationParams,
) -> PerformanceMetrics {
let node_count = graph.node_count() as f32;
let base_time = node_count * 10.0;
let mut speedup = 1.0;
for (pass_name, &weight) in ¶ms.pass_weights {
let impact = self.estimate_pass_impact(pass_name, graph);
speedup += weight * impact;
}
let exec_time_us = base_time / speedup;
let memory_bytes = node_count * 1024.0;
PerformanceMetrics {
exec_time_us,
memory_bytes,
flops: node_count * 100.0,
cache_efficiency: 0.7 + params.layout_preference * 0.3,
}
}
pub fn statistics(&self) -> &CompilerStatistics {
&self.stats
}
pub fn reset_stats(&mut self) {
self.stats = CompilerStatistics::default();
}
}
impl Default for DifferentiableCompiler {
fn default() -> Self {
Self::new()
}
}
pub struct GumbelSoftmax {
temperature: f32,
}
impl GumbelSoftmax {
pub fn new(temperature: f32) -> Self {
Self { temperature }
}
fn sample_gumbel(&self) -> f32 {
use std::time::{SystemTime, UNIX_EPOCH};
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system time should be after UNIX_EPOCH")
.subsec_nanos();
let u = ((nanos % 1000) as f32 / 1000.0).max(1e-10);
-(-u).ln().ln()
}
pub fn apply(&self, logits: &[f32]) -> Vec<f32> {
let gumbel_logits: Vec<f32> = logits
.iter()
.map(|&logit| (logit + self.sample_gumbel()) / self.temperature)
.collect();
SoftDecision::softmax(&gumbel_logits)
}
pub fn straight_through(&self, logits: &[f32]) -> (usize, Vec<f32>) {
let probs = SoftDecision::softmax(logits);
let choice = probs
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.map(|(i, _)| i)
.unwrap_or(0);
(choice, probs)
}
}
pub struct CompilationTrainer {
compiler: DifferentiableCompiler,
params: CompilationParams,
learning_rate: f32,
history: Vec<TrainingEpoch>,
}
#[derive(Debug, Clone)]
pub struct TrainingEpoch {
pub epoch: usize,
pub loss: f32,
pub performance: PerformanceMetrics,
pub params: CompilationParams,
}
impl CompilationTrainer {
pub fn new(learning_rate: f32) -> Self {
Self {
compiler: DifferentiableCompiler::new(),
params: CompilationParams::new(),
learning_rate,
history: Vec::new(),
}
}
pub fn train_step(
&mut self,
graph: &ComputationGraph,
target_performance: f32,
) -> JitResult<f32> {
let result = self.compiler.compile_differentiable(graph, &self.params)?;
let loss = (result.estimated_performance.exec_time_us - target_performance).powi(2);
let grads = self.compiler.backward(&result, loss)?;
self.params.gradients = grads.gradients;
self.params.update(self.learning_rate);
Ok(loss)
}
pub fn train(
&mut self,
graphs: &[ComputationGraph],
targets: &[f32],
epochs: usize,
) -> JitResult<Vec<TrainingEpoch>> {
for epoch in 0..epochs {
let mut total_loss = 0.0;
for (graph, &target) in graphs.iter().zip(targets.iter()) {
let loss = self.train_step(graph, target)?;
total_loss += loss;
}
let avg_loss = total_loss / graphs.len() as f32;
let result = self
.compiler
.compile_differentiable(&graphs[0], &self.params)?;
self.history.push(TrainingEpoch {
epoch,
loss: avg_loss,
performance: result.estimated_performance.clone(),
params: self.params.clone(),
});
log::info!("Epoch {}: loss = {:.4}", epoch, avg_loss);
}
Ok(self.history.clone())
}
pub fn best_params(&self) -> &CompilationParams {
self.history
.iter()
.min_by(|a, b| {
a.loss
.partial_cmp(&b.loss)
.unwrap_or(std::cmp::Ordering::Equal)
})
.map(|e| &e.params)
.unwrap_or(&self.params)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::GraphBuilder;
use torsh_core::{DType, Shape};
#[test]
fn test_compilation_params() {
let mut params = CompilationParams::new();
assert!(params.pass_weights.len() > 0);
params.accumulate_grad("fusion", 0.1);
params.update(0.01);
assert!(params.pass_weights.contains_key("fusion"));
}
#[test]
fn test_soft_decision() {
let decision = SoftDecision::new(0.7);
assert!((decision.probability - 0.7).abs() < 1e-6);
let probs = SoftDecision::softmax(&[1.0, 2.0, 3.0]);
let sum: f32 = probs.iter().sum();
assert!((sum - 1.0).abs() < 1e-5);
}
#[test]
fn test_differentiable_compilation() {
let mut compiler = DifferentiableCompiler::new();
let params = CompilationParams::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_differentiable(&graph, ¶ms).unwrap();
assert!(result.decisions.len() > 0);
assert!(result.estimated_performance.exec_time_us > 0.0);
}
#[test]
fn test_backward_pass() {
let mut compiler = DifferentiableCompiler::new();
let params = CompilationParams::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_differentiable(&graph, ¶ms).unwrap();
let loss = 100.0; let grads = compiler.backward(&result, loss).unwrap();
assert!(grads.gradients.len() > 0);
}
#[test]
fn test_compilation_trainer() {
let mut trainer = CompilationTrainer::new(0.01);
let mut builder = GraphBuilder::new();
let x = builder.add_input("x".to_string(), Shape::new(vec![3, 3]), DType::F32);
builder.mark_output(x).unwrap();
let graph = builder.build().unwrap();
let loss = trainer.train_step(&graph, 50.0).unwrap();
assert!(loss >= 0.0);
}
#[test]
fn test_gumbel_softmax() {
let gumbel = GumbelSoftmax::new(1.0);
let logits = vec![1.0, 2.0, 3.0];
let (choice, probs) = gumbel.straight_through(&logits);
assert!(choice < 3);
let sum: f32 = probs.iter().sum();
assert!((sum - 1.0).abs() < 1e-5);
}
}