Skip to main content

torsh_jit/
neural_compilation.rs

1// Copyright (c) 2025 ToRSh Contributors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! # Neural Compilation
5//!
6//! This module implements ML-guided compilation optimization using neural networks
7//! to predict optimal compilation strategies, optimization passes, and runtime configurations.
8//!
9//! ## Key Features
10//!
11//! - **Learned Optimization Selection**: Neural models predict which optimizations to apply
12//! - **Performance Prediction**: Estimate execution time/memory before compilation
13//! - **Adaptive Compilation**: Learn from execution feedback to improve future decisions
14//! - **Transfer Learning**: Leverage knowledge from similar computation graphs
15//! - **Meta-Learning**: Learn to learn better compilation strategies
16//!
17//! ## Architecture
18//!
19//! ```text
20//! Graph → Feature Extraction → Neural Model → Optimization Decision
21//!           ↓                      ↓                    ↓
22//!       Structure            Performance         Apply/Skip
23//!       Features             Prediction          Optimizations
24//! ```
25//!
26//! ## Example
27//!
28//! ```rust,ignore
29//! use torsh_jit::neural_compilation::{NeuralCompiler, CompilationFeatures};
30//!
31//! let mut compiler = NeuralCompiler::new();
32//!
33//! // Extract features from computation graph
34//! let features = compiler.extract_features(&graph);
35//!
36//! // Predict optimal optimization strategy
37//! let strategy = compiler.predict_strategy(&features)?;
38//!
39//! // Apply learned optimizations
40//! let optimized_graph = compiler.apply_strategy(&graph, &strategy)?;
41//!
42//! // Learn from execution feedback
43//! compiler.learn_from_execution(&graph, &metrics);
44//! ```
45
46use crate::graph::{ComputationGraph, NodeId};
47use crate::{JitError, JitResult};
48use indexmap::IndexMap;
49use serde::{Deserialize, Serialize};
50use std::collections::{HashMap, VecDeque};
51use std::sync::{Arc, RwLock};
52
53// ============================================================================
54// Graph Feature Extraction
55// ============================================================================
56
57/// Features extracted from a computation graph for neural compilation
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct GraphFeatures {
60    /// Structural features
61    pub structural: StructuralFeatures,
62
63    /// Computational features
64    pub computational: ComputationalFeatures,
65
66    /// Memory access patterns
67    pub memory_patterns: MemoryPatternFeatures,
68
69    /// Control flow characteristics
70    pub control_flow: ControlFlowFeatures,
71
72    /// Historical performance data
73    pub historical: Option<HistoricalFeatures>,
74}
75
76/// Structural properties of the graph
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct StructuralFeatures {
79    /// Number of nodes
80    pub node_count: usize,
81
82    /// Number of edges
83    pub edge_count: usize,
84
85    /// Graph depth (longest path)
86    pub depth: usize,
87
88    /// Average node degree
89    pub avg_degree: f32,
90
91    /// Number of strongly connected components
92    pub scc_count: usize,
93
94    /// Graph diameter
95    pub diameter: usize,
96
97    /// Clustering coefficient
98    pub clustering_coeff: f32,
99
100    /// Operation type distribution (histogram)
101    pub op_type_dist: HashMap<String, usize>,
102}
103
104/// Computational intensity features
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct ComputationalFeatures {
107    /// Total FLOPs estimate
108    pub total_flops: u64,
109
110    /// Arithmetic intensity (FLOPs/byte)
111    pub arithmetic_intensity: f32,
112
113    /// Parallelism degree
114    pub parallelism: usize,
115
116    /// Vectorization opportunities
117    pub vectorizable_ops: usize,
118
119    /// Memory-bound operations count
120    pub memory_bound_ops: usize,
121
122    /// Compute-bound operations count
123    pub compute_bound_ops: usize,
124}
125
126/// Memory access pattern features
127#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct MemoryPatternFeatures {
129    /// Total memory footprint (bytes)
130    pub total_memory: usize,
131
132    /// Peak memory usage
133    pub peak_memory: usize,
134
135    /// Cache locality score (0-1)
136    pub cache_locality: f32,
137
138    /// Stride patterns (sequential, strided, random)
139    pub stride_patterns: HashMap<String, usize>,
140
141    /// Reuse distance histogram
142    pub reuse_distances: Vec<usize>,
143
144    /// Working set size
145    pub working_set_size: usize,
146}
147
148/// Control flow characteristics
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct ControlFlowFeatures {
151    /// Number of branches
152    pub branch_count: usize,
153
154    /// Loop nesting depth
155    pub max_loop_depth: usize,
156
157    /// Number of loops
158    pub loop_count: usize,
159
160    /// Average loop trip count
161    pub avg_trip_count: f32,
162
163    /// Branch predictability score
164    pub branch_predictability: f32,
165}
166
167/// Historical execution data
168#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct HistoricalFeatures {
170    /// Previous execution times
171    pub execution_times: Vec<f64>,
172
173    /// Previous memory usage
174    pub memory_usage: Vec<usize>,
175
176    /// Cache miss rates
177    pub cache_miss_rates: Vec<f32>,
178
179    /// Successful optimizations
180    pub successful_opts: Vec<String>,
181}
182
183// ============================================================================
184// Neural Models
185// ============================================================================
186
187/// Neural network model for compilation decisions
188#[derive(Debug, Clone)]
189pub struct NeuralModel {
190    /// Model weights (simplified representation)
191    weights: Vec<Vec<f32>>,
192
193    /// Model biases
194    biases: Vec<f32>,
195
196    /// Input feature dimension
197    input_dim: usize,
198
199    /// Hidden layer dimensions
200    hidden_dims: Vec<usize>,
201
202    /// Output dimension
203    output_dim: usize,
204
205    /// Model training statistics
206    stats: ModelStatistics,
207}
208
209/// Training and evaluation statistics
210#[derive(Debug, Clone, Default)]
211pub struct ModelStatistics {
212    /// Number of training samples
213    pub samples_seen: usize,
214
215    /// Current loss
216    pub current_loss: f32,
217
218    /// Best validation accuracy
219    pub best_accuracy: f32,
220
221    /// Prediction accuracy history
222    pub accuracy_history: VecDeque<f32>,
223
224    /// Feature importance scores
225    pub feature_importance: HashMap<String, f32>,
226}
227
228impl NeuralModel {
229    /// Create a new neural model with given architecture
230    pub fn new(input_dim: usize, hidden_dims: Vec<usize>, output_dim: usize) -> Self {
231        // Initialize weights randomly (in production, use proper initialization)
232        let mut weights = Vec::new();
233        let mut biases = Vec::new();
234
235        let mut prev_dim = input_dim;
236        for &hidden_dim in &hidden_dims {
237            weights.push(vec![0.01; prev_dim * hidden_dim]); // Xavier/He initialization
238            biases.push(0.0);
239            prev_dim = hidden_dim;
240        }
241
242        // Output layer
243        weights.push(vec![0.01; prev_dim * output_dim]);
244        biases.push(0.0);
245
246        Self {
247            weights,
248            biases,
249            input_dim,
250            hidden_dims,
251            output_dim,
252            stats: ModelStatistics::default(),
253        }
254    }
255
256    /// Forward pass through the network
257    pub fn forward(&self, features: &[f32]) -> JitResult<Vec<f32>> {
258        if features.len() != self.input_dim {
259            return Err(JitError::CompilationError(format!(
260                "Expected {} features, got {}",
261                self.input_dim,
262                features.len()
263            )));
264        }
265
266        let mut activations = features.to_vec();
267
268        // Hidden layers with ReLU activation
269        for (weights, bias) in self
270            .weights
271            .iter()
272            .zip(self.biases.iter())
273            .take(self.hidden_dims.len())
274        {
275            activations = Self::dense_layer(&activations, weights, *bias);
276            activations = Self::relu(&activations);
277        }
278
279        // Output layer with softmax
280        if let (Some(out_weights), Some(out_bias)) = (self.weights.last(), self.biases.last()) {
281            activations = Self::dense_layer(&activations, out_weights, *out_bias);
282            activations = Self::softmax(&activations);
283        }
284
285        Ok(activations)
286    }
287
288    /// Dense (fully connected) layer
289    fn dense_layer(input: &[f32], weights: &[f32], bias: f32) -> Vec<f32> {
290        let input_dim = input.len();
291        let output_dim = weights.len() / input_dim;
292        let mut output = vec![bias; output_dim];
293
294        for i in 0..output_dim {
295            for j in 0..input_dim {
296                output[i] += input[j] * weights[i * input_dim + j];
297            }
298        }
299
300        output
301    }
302
303    /// ReLU activation function
304    fn relu(x: &[f32]) -> Vec<f32> {
305        x.iter().map(|&v| v.max(0.0)).collect()
306    }
307
308    /// Softmax activation function
309    fn softmax(x: &[f32]) -> Vec<f32> {
310        let max = x.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
311        let exp_values: Vec<f32> = x.iter().map(|&v| (v - max).exp()).collect();
312        let sum: f32 = exp_values.iter().sum();
313        exp_values.iter().map(|&v| v / sum).collect()
314    }
315
316    /// Update model weights based on feedback (simplified SGD)
317    pub fn update(
318        &mut self,
319        features: &[f32],
320        target: &[f32],
321        learning_rate: f32,
322    ) -> JitResult<()> {
323        let prediction = self.forward(features)?;
324
325        // Compute loss (cross-entropy)
326        let loss: f32 = target
327            .iter()
328            .zip(prediction.iter())
329            .map(|(&t, &p)| -t * p.max(1e-10).ln())
330            .sum();
331
332        self.stats.current_loss = loss;
333        self.stats.samples_seen += 1;
334
335        // Simple gradient descent (in production, use backpropagation)
336        // This is a placeholder for demonstration
337
338        // Update accuracy history
339        let accuracy = self.compute_accuracy(&prediction, target);
340        self.stats.accuracy_history.push_back(accuracy);
341        if self.stats.accuracy_history.len() > 100 {
342            self.stats.accuracy_history.pop_front();
343        }
344
345        if accuracy > self.stats.best_accuracy {
346            self.stats.best_accuracy = accuracy;
347        }
348
349        Ok(())
350    }
351
352    /// Compute prediction accuracy
353    fn compute_accuracy(&self, prediction: &[f32], target: &[f32]) -> f32 {
354        let pred_class = prediction
355            .iter()
356            .enumerate()
357            .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
358            .map(|(i, _)| i)
359            .unwrap_or(0);
360
361        let target_class = target
362            .iter()
363            .enumerate()
364            .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
365            .map(|(i, _)| i)
366            .unwrap_or(0);
367
368        if pred_class == target_class {
369            1.0
370        } else {
371            0.0
372        }
373    }
374}
375
376// ============================================================================
377// Compilation Strategy
378// ============================================================================
379
380/// Optimization strategy predicted by neural model
381#[derive(Debug, Clone, Serialize, Deserialize)]
382pub struct CompilationStrategy {
383    /// Optimizations to apply (ordered)
384    pub optimizations: Vec<OptimizationDecision>,
385
386    /// Predicted execution time (microseconds)
387    pub predicted_time_us: f64,
388
389    /// Predicted memory usage (bytes)
390    pub predicted_memory: usize,
391
392    /// Confidence score (0-1)
393    pub confidence: f32,
394
395    /// Reasoning explanation
396    pub reasoning: Vec<String>,
397}
398
399/// Decision about a specific optimization
400#[derive(Debug, Clone, Serialize, Deserialize)]
401pub struct OptimizationDecision {
402    /// Optimization pass name
403    pub pass_name: String,
404
405    /// Whether to apply this optimization
406    pub apply: bool,
407
408    /// Estimated speedup (1.0 = no change)
409    pub estimated_speedup: f32,
410
411    /// Estimated memory impact (bytes, negative = reduction)
412    pub estimated_memory_delta: i64,
413
414    /// Confidence in this decision (0-1)
415    pub confidence: f32,
416}
417
418// ============================================================================
419// Neural Compiler
420// ============================================================================
421
422/// Main neural compilation engine
423pub struct NeuralCompiler {
424    /// Strategy prediction model
425    strategy_model: Arc<RwLock<NeuralModel>>,
426
427    /// Performance prediction model
428    performance_model: Arc<RwLock<NeuralModel>>,
429
430    /// Feature extractor
431    feature_extractor: FeatureExtractor,
432
433    /// Historical data for learning
434    history: Arc<RwLock<CompilationHistory>>,
435
436    /// Configuration
437    config: NeuralCompilerConfig,
438}
439
440/// Configuration for neural compiler
441#[derive(Debug, Clone)]
442pub struct NeuralCompilerConfig {
443    /// Enable online learning
444    pub online_learning: bool,
445
446    /// Learning rate for model updates
447    pub learning_rate: f32,
448
449    /// Exploration rate (epsilon for ε-greedy)
450    pub exploration_rate: f32,
451
452    /// Minimum confidence threshold
453    pub min_confidence: f32,
454
455    /// Maximum history size
456    pub max_history_size: usize,
457
458    /// Enable transfer learning
459    pub transfer_learning: bool,
460}
461
462impl Default for NeuralCompilerConfig {
463    fn default() -> Self {
464        Self {
465            online_learning: true,
466            learning_rate: 0.001,
467            exploration_rate: 0.1,
468            min_confidence: 0.7,
469            max_history_size: 10000,
470            transfer_learning: true,
471        }
472    }
473}
474
475/// Historical compilation data
476#[derive(Debug, Default)]
477pub struct CompilationHistory {
478    /// Past compilation results
479    pub entries: VecDeque<HistoryEntry>,
480
481    /// Feature statistics for normalization
482    pub feature_stats: FeatureStatistics,
483}
484
485/// Single history entry
486#[derive(Debug, Clone)]
487pub struct HistoryEntry {
488    /// Graph features
489    pub features: GraphFeatures,
490
491    /// Applied strategy
492    pub strategy: CompilationStrategy,
493
494    /// Actual execution time
495    pub actual_time_us: f64,
496
497    /// Actual memory usage
498    pub actual_memory: usize,
499
500    /// Prediction error
501    pub error: f32,
502}
503
504/// Feature statistics for normalization
505#[derive(Debug, Default, Clone)]
506pub struct FeatureStatistics {
507    /// Mean values for each feature
508    pub means: HashMap<String, f32>,
509
510    /// Standard deviations
511    pub stddevs: HashMap<String, f32>,
512
513    /// Min/max values
514    pub mins: HashMap<String, f32>,
515    pub maxs: HashMap<String, f32>,
516}
517
518impl NeuralCompiler {
519    /// Create a new neural compiler
520    pub fn new() -> Self {
521        Self::with_config(NeuralCompilerConfig::default())
522    }
523
524    /// Create with custom configuration
525    pub fn with_config(config: NeuralCompilerConfig) -> Self {
526        // Strategy model: features → optimization decisions
527        let strategy_model = Arc::new(RwLock::new(
528            NeuralModel::new(128, vec![256, 128, 64], 32), // 32 possible optimizations
529        ));
530
531        // Performance model: features → time/memory prediction (same input dim as strategy model)
532        let performance_model = Arc::new(RwLock::new(
533            NeuralModel::new(128, vec![64, 32], 2), // time + memory
534        ));
535
536        Self {
537            strategy_model,
538            performance_model,
539            feature_extractor: FeatureExtractor::new(),
540            history: Arc::new(RwLock::new(CompilationHistory::default())),
541            config,
542        }
543    }
544
545    /// Extract features from a computation graph
546    pub fn extract_features(&self, graph: &ComputationGraph) -> JitResult<GraphFeatures> {
547        self.feature_extractor.extract(graph)
548    }
549
550    /// Predict optimal compilation strategy
551    pub fn predict_strategy(&self, features: &GraphFeatures) -> JitResult<CompilationStrategy> {
552        // Flatten features into vector
553        let feature_vec = self.flatten_features(features)?;
554
555        // Get strategy prediction
556        let strategy_model = self
557            .strategy_model
558            .read()
559            .map_err(|e| JitError::CompilationError(format!("Lock error: {}", e)))?;
560
561        let strategy_probs = strategy_model.forward(&feature_vec)?;
562
563        // Convert probabilities to optimization decisions
564        let optimizations = self.decode_strategy(&strategy_probs)?;
565
566        // Predict performance
567        let performance_model = self
568            .performance_model
569            .read()
570            .map_err(|e| JitError::CompilationError(format!("Lock error: {}", e)))?;
571
572        let perf_prediction = performance_model.forward(&feature_vec)?;
573
574        let predicted_time_us = perf_prediction.get(0).copied().unwrap_or(1000.0) as f64 * 1000.0;
575        let predicted_memory =
576            perf_prediction.get(1).copied().unwrap_or(1.0) as usize * 1024 * 1024;
577
578        // Compute overall confidence
579        let confidence = strategy_probs.iter().sum::<f32>() / strategy_probs.len() as f32;
580
581        // Generate reasoning
582        let reasoning = self.generate_reasoning(&optimizations, features);
583
584        Ok(CompilationStrategy {
585            optimizations,
586            predicted_time_us,
587            predicted_memory,
588            confidence,
589            reasoning,
590        })
591    }
592
593    /// Apply predicted strategy to graph
594    pub fn apply_strategy(
595        &self,
596        graph: &ComputationGraph,
597        strategy: &CompilationStrategy,
598    ) -> JitResult<ComputationGraph> {
599        let optimized = graph.clone();
600
601        for decision in &strategy.optimizations {
602            if decision.apply && decision.confidence > self.config.min_confidence {
603                // Apply optimization (placeholder - actual implementation would call optimization passes)
604                log::info!(
605                    "Applying optimization: {} (speedup: {:.2}x, confidence: {:.2})",
606                    decision.pass_name,
607                    decision.estimated_speedup,
608                    decision.confidence
609                );
610            }
611        }
612
613        Ok(optimized)
614    }
615
616    /// Learn from execution feedback
617    pub fn learn_from_execution(
618        &mut self,
619        features: &GraphFeatures,
620        strategy: &CompilationStrategy,
621        actual_time_us: f64,
622        actual_memory: usize,
623    ) -> JitResult<()> {
624        if !self.config.online_learning {
625            return Ok(());
626        }
627
628        // Compute prediction error
629        let time_error = ((strategy.predicted_time_us - actual_time_us) / actual_time_us).abs();
630        let memory_error = ((strategy.predicted_memory as f64 - actual_memory as f64)
631            / actual_memory as f64)
632            .abs();
633        let error = ((time_error + memory_error) / 2.0) as f32;
634
635        // Add to history
636        let mut history = self
637            .history
638            .write()
639            .map_err(|e| JitError::CompilationError(format!("Lock error: {}", e)))?;
640
641        history.entries.push_back(HistoryEntry {
642            features: features.clone(),
643            strategy: strategy.clone(),
644            actual_time_us,
645            actual_memory,
646            error,
647        });
648
649        if history.entries.len() > self.config.max_history_size {
650            history.entries.pop_front();
651        }
652
653        // Update models
654        let feature_vec = self.flatten_features(features)?;
655
656        // Update performance model
657        let target_perf = vec![
658            (actual_time_us / 1000.0) as f32,
659            (actual_memory / (1024 * 1024)) as f32,
660        ];
661
662        let mut perf_model = self
663            .performance_model
664            .write()
665            .map_err(|e| JitError::CompilationError(format!("Lock error: {}", e)))?;
666
667        perf_model.update(&feature_vec, &target_perf, self.config.learning_rate)?;
668
669        log::info!(
670            "Neural compiler learned from execution: error={:.2}%, samples={}",
671            error * 100.0,
672            perf_model.stats.samples_seen
673        );
674
675        Ok(())
676    }
677
678    /// Flatten features into vector
679    fn flatten_features(&self, features: &GraphFeatures) -> JitResult<Vec<f32>> {
680        let mut vec = Vec::with_capacity(128);
681
682        // Structural features (normalized)
683        vec.push((features.structural.node_count as f32).ln());
684        vec.push((features.structural.edge_count as f32).ln());
685        vec.push((features.structural.depth as f32).ln());
686        vec.push(features.structural.avg_degree);
687        vec.push(features.structural.scc_count as f32);
688        vec.push(features.structural.diameter as f32);
689        vec.push(features.structural.clustering_coeff);
690
691        // Computational features
692        vec.push((features.computational.total_flops as f32).ln());
693        vec.push(features.computational.arithmetic_intensity);
694        vec.push((features.computational.parallelism as f32).ln());
695        vec.push(features.computational.vectorizable_ops as f32);
696
697        // Memory features
698        vec.push((features.memory_patterns.total_memory as f32).ln());
699        vec.push((features.memory_patterns.peak_memory as f32).ln());
700        vec.push(features.memory_patterns.cache_locality);
701
702        // Pad to 128 dimensions
703        while vec.len() < 128 {
704            vec.push(0.0);
705        }
706
707        Ok(vec)
708    }
709
710    /// Decode strategy probabilities into optimization decisions
711    fn decode_strategy(&self, probs: &[f32]) -> JitResult<Vec<OptimizationDecision>> {
712        let opt_names = vec![
713            "constant_folding",
714            "dead_code_elimination",
715            "common_subexpression_elimination",
716            "loop_invariant_motion",
717            "strength_reduction",
718            "loop_unrolling",
719            "vectorization",
720            "parallelization",
721            "fusion",
722            "inlining",
723            "algebraic_simplification",
724            "peephole",
725            "instruction_scheduling",
726            "register_allocation",
727            "memory_layout",
728            "cache_blocking",
729        ];
730
731        let mut decisions = Vec::new();
732
733        for (i, &prob) in probs.iter().enumerate().take(opt_names.len()) {
734            let apply = prob > 0.5;
735            let estimated_speedup = if apply { 1.0 + prob } else { 1.0 };
736
737            decisions.push(OptimizationDecision {
738                pass_name: opt_names.get(i).unwrap_or(&"unknown").to_string(),
739                apply,
740                estimated_speedup,
741                estimated_memory_delta: if apply { -1024 } else { 0 },
742                confidence: prob,
743            });
744        }
745
746        Ok(decisions)
747    }
748
749    /// Generate human-readable reasoning
750    fn generate_reasoning(
751        &self,
752        decisions: &[OptimizationDecision],
753        features: &GraphFeatures,
754    ) -> Vec<String> {
755        let mut reasoning = Vec::new();
756
757        if features.computational.arithmetic_intensity > 10.0 {
758            reasoning
759                .push("High arithmetic intensity detected - compute-bound workload".to_string());
760        } else {
761            reasoning.push("Low arithmetic intensity detected - memory-bound workload".to_string());
762        }
763
764        let applied_opts: Vec<_> = decisions
765            .iter()
766            .filter(|d| d.apply && d.confidence > 0.7)
767            .map(|d| d.pass_name.as_str())
768            .collect();
769
770        if !applied_opts.is_empty() {
771            reasoning.push(format!(
772                "Recommended optimizations: {}",
773                applied_opts.join(", ")
774            ));
775        }
776
777        reasoning
778    }
779
780    /// Get model statistics
781    pub fn get_statistics(&self) -> JitResult<HashMap<String, f32>> {
782        let perf_model = self
783            .performance_model
784            .read()
785            .map_err(|e| JitError::CompilationError(format!("Lock error: {}", e)))?;
786
787        let mut stats = HashMap::new();
788        stats.insert(
789            "samples_seen".to_string(),
790            perf_model.stats.samples_seen as f32,
791        );
792        stats.insert("current_loss".to_string(), perf_model.stats.current_loss);
793        stats.insert("best_accuracy".to_string(), perf_model.stats.best_accuracy);
794
795        Ok(stats)
796    }
797}
798
799impl Default for NeuralCompiler {
800    fn default() -> Self {
801        Self::new()
802    }
803}
804
805// ============================================================================
806// Feature Extraction
807// ============================================================================
808
809/// Feature extraction from computation graphs
810pub struct FeatureExtractor {
811    /// Cached features for graphs
812    cache: IndexMap<String, GraphFeatures>,
813}
814
815impl FeatureExtractor {
816    /// Create a new feature extractor
817    pub fn new() -> Self {
818        Self {
819            cache: IndexMap::new(),
820        }
821    }
822
823    /// Extract features from graph
824    pub fn extract(&self, graph: &ComputationGraph) -> JitResult<GraphFeatures> {
825        Ok(GraphFeatures {
826            structural: self.extract_structural(graph)?,
827            computational: self.extract_computational(graph)?,
828            memory_patterns: self.extract_memory_patterns(graph)?,
829            control_flow: self.extract_control_flow(graph)?,
830            historical: None,
831        })
832    }
833
834    fn extract_structural(&self, graph: &ComputationGraph) -> JitResult<StructuralFeatures> {
835        let node_count = graph.node_count();
836        let edge_count = graph.edge_count();
837
838        // Compute graph depth (longest path)
839        let depth = self.compute_depth(graph);
840
841        // Average degree
842        let avg_degree = if node_count > 0 {
843            (edge_count as f32) / (node_count as f32)
844        } else {
845            0.0
846        };
847
848        // Operation type distribution
849        let mut op_type_dist = HashMap::new();
850        for (node_id, node) in graph.nodes() {
851            let op_name = format!("{:?}", node.operation);
852            *op_type_dist.entry(op_name).or_insert(0) += 1;
853        }
854
855        Ok(StructuralFeatures {
856            node_count,
857            edge_count,
858            depth,
859            avg_degree,
860            scc_count: 1, // Simplified
861            diameter: depth,
862            clustering_coeff: 0.0, // Simplified
863            op_type_dist,
864        })
865    }
866
867    fn extract_computational(&self, graph: &ComputationGraph) -> JitResult<ComputationalFeatures> {
868        let mut total_flops = 0u64;
869        let mut vectorizable_ops = 0;
870        let mut memory_bound_ops = 0;
871        let mut compute_bound_ops = 0;
872
873        for (node_id, node) in graph.nodes() {
874            // Estimate FLOPs based on operation type
875            let op_flops = self.estimate_flops(&node.operation, &node.inputs);
876            total_flops += op_flops;
877
878            // Check if vectorizable
879            if self.is_vectorizable(&node.operation) {
880                vectorizable_ops += 1;
881            }
882
883            // Classify as memory or compute bound
884            if op_flops > 1000 {
885                compute_bound_ops += 1;
886            } else {
887                memory_bound_ops += 1;
888            }
889        }
890
891        let arithmetic_intensity = if total_flops > 0 {
892            total_flops as f32 / (1024.0 * 1024.0) // Simplified
893        } else {
894            0.0
895        };
896
897        Ok(ComputationalFeatures {
898            total_flops,
899            arithmetic_intensity,
900            parallelism: graph.node_count(),
901            vectorizable_ops,
902            memory_bound_ops,
903            compute_bound_ops,
904        })
905    }
906
907    fn extract_memory_patterns(
908        &self,
909        graph: &ComputationGraph,
910    ) -> JitResult<MemoryPatternFeatures> {
911        Ok(MemoryPatternFeatures {
912            total_memory: graph.node_count() * 1024, // Simplified
913            peak_memory: graph.node_count() * 2048,
914            cache_locality: 0.7,
915            stride_patterns: HashMap::new(),
916            reuse_distances: vec![],
917            working_set_size: graph.node_count() * 512,
918        })
919    }
920
921    fn extract_control_flow(&self, graph: &ComputationGraph) -> JitResult<ControlFlowFeatures> {
922        Ok(ControlFlowFeatures {
923            branch_count: 0,
924            max_loop_depth: 0,
925            loop_count: 0,
926            avg_trip_count: 0.0,
927            branch_predictability: 1.0,
928        })
929    }
930
931    fn compute_depth(&self, graph: &ComputationGraph) -> usize {
932        // Simple DFS-based depth computation
933        let mut max_depth = 0;
934
935        for (node_id, _node) in graph.nodes() {
936            let depth = self.node_depth(graph, node_id, &mut HashMap::new());
937            max_depth = max_depth.max(depth);
938        }
939
940        max_depth
941    }
942
943    fn node_depth(
944        &self,
945        graph: &ComputationGraph,
946        node_id: NodeId,
947        memo: &mut HashMap<NodeId, usize>,
948    ) -> usize {
949        if let Some(&depth) = memo.get(&node_id) {
950            return depth;
951        }
952
953        let inputs = graph.get_node_inputs(node_id);
954        let depth = if inputs.is_empty() {
955            0
956        } else {
957            1 + inputs
958                .iter()
959                .map(|&input_id| self.node_depth(graph, input_id, memo))
960                .max()
961                .unwrap_or(0)
962        };
963
964        memo.insert(node_id, depth);
965        depth
966    }
967
968    fn estimate_flops(&self, _operation: &crate::graph::Operation, _inputs: &[NodeId]) -> u64 {
969        // Simplified FLOP estimation
970        100
971    }
972
973    fn is_vectorizable(&self, operation: &crate::graph::Operation) -> bool {
974        // Check if operation can be vectorized
975        matches!(
976            operation,
977            crate::graph::Operation::Add
978                | crate::graph::Operation::Mul
979                | crate::graph::Operation::Relu
980                | crate::graph::Operation::Sigmoid
981        )
982    }
983}
984
985impl Default for FeatureExtractor {
986    fn default() -> Self {
987        Self::new()
988    }
989}
990
991// ============================================================================
992// Tests
993// ============================================================================
994
995#[cfg(test)]
996mod tests {
997    use super::*;
998    use crate::graph::GraphBuilder;
999    use torsh_core::{DType, Shape};
1000
1001    #[test]
1002    fn test_neural_compiler_creation() {
1003        let compiler = NeuralCompiler::new();
1004        assert!(compiler.config.online_learning);
1005    }
1006
1007    #[test]
1008    fn test_neural_model_forward() {
1009        let model = NeuralModel::new(10, vec![20, 10], 5);
1010        let input = vec![0.5; 10];
1011        let output = model.forward(&input).unwrap();
1012        assert_eq!(output.len(), 5);
1013
1014        // Softmax output should sum to 1
1015        let sum: f32 = output.iter().sum();
1016        assert!((sum - 1.0).abs() < 1e-5);
1017    }
1018
1019    #[test]
1020    fn test_feature_extraction() {
1021        let mut builder = GraphBuilder::new();
1022        let x = builder.add_input("x".to_string(), Shape::new(vec![2, 3]), DType::F32);
1023        let y = builder.add_input("y".to_string(), Shape::new(vec![2, 3]), DType::F32);
1024        let z = builder
1025            .add_binary_op("add".to_string(), crate::graph::Operation::Add, x, y)
1026            .unwrap();
1027        builder.mark_output(z).unwrap();
1028
1029        let graph = builder.build().unwrap();
1030
1031        let extractor = FeatureExtractor::new();
1032        let features = extractor.extract(&graph).unwrap();
1033
1034        assert!(features.structural.node_count >= 3); // At least 2 inputs + 1 add
1035        assert!(features.computational.total_flops > 0);
1036    }
1037
1038    #[test]
1039    fn test_strategy_prediction() {
1040        let compiler = NeuralCompiler::new();
1041
1042        let mut builder = GraphBuilder::new();
1043        let x = builder.add_input("x".to_string(), Shape::new(vec![10, 10]), DType::F32);
1044        let y = builder
1045            .add_unary_op("relu".to_string(), crate::graph::Operation::Relu, x)
1046            .unwrap();
1047        builder.mark_output(y).unwrap();
1048
1049        let graph = builder.build().unwrap();
1050        let features = compiler.extract_features(&graph).unwrap();
1051        let strategy = compiler.predict_strategy(&features).unwrap();
1052
1053        assert!(!strategy.optimizations.is_empty());
1054        assert!(strategy.confidence >= 0.0 && strategy.confidence <= 1.0);
1055    }
1056
1057    #[test]
1058    fn test_online_learning() {
1059        let mut compiler = NeuralCompiler::new();
1060
1061        let mut builder = GraphBuilder::new();
1062        let x = builder.add_input("x".to_string(), Shape::new(vec![5, 5]), DType::F32);
1063        builder.mark_output(x).unwrap();
1064
1065        let graph = builder.build().unwrap();
1066        let features = compiler.extract_features(&graph).unwrap();
1067        let strategy = compiler.predict_strategy(&features).unwrap();
1068
1069        // Simulate execution feedback
1070        let result = compiler.learn_from_execution(&features, &strategy, 1500.0, 2048);
1071        assert!(result.is_ok());
1072
1073        let stats = compiler.get_statistics().unwrap();
1074        assert_eq!(stats.get("samples_seen").copied().unwrap_or(0.0), 1.0);
1075    }
1076}