1use 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#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct GraphFeatures {
60 pub structural: StructuralFeatures,
62
63 pub computational: ComputationalFeatures,
65
66 pub memory_patterns: MemoryPatternFeatures,
68
69 pub control_flow: ControlFlowFeatures,
71
72 pub historical: Option<HistoricalFeatures>,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct StructuralFeatures {
79 pub node_count: usize,
81
82 pub edge_count: usize,
84
85 pub depth: usize,
87
88 pub avg_degree: f32,
90
91 pub scc_count: usize,
93
94 pub diameter: usize,
96
97 pub clustering_coeff: f32,
99
100 pub op_type_dist: HashMap<String, usize>,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct ComputationalFeatures {
107 pub total_flops: u64,
109
110 pub arithmetic_intensity: f32,
112
113 pub parallelism: usize,
115
116 pub vectorizable_ops: usize,
118
119 pub memory_bound_ops: usize,
121
122 pub compute_bound_ops: usize,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct MemoryPatternFeatures {
129 pub total_memory: usize,
131
132 pub peak_memory: usize,
134
135 pub cache_locality: f32,
137
138 pub stride_patterns: HashMap<String, usize>,
140
141 pub reuse_distances: Vec<usize>,
143
144 pub working_set_size: usize,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct ControlFlowFeatures {
151 pub branch_count: usize,
153
154 pub max_loop_depth: usize,
156
157 pub loop_count: usize,
159
160 pub avg_trip_count: f32,
162
163 pub branch_predictability: f32,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct HistoricalFeatures {
170 pub execution_times: Vec<f64>,
172
173 pub memory_usage: Vec<usize>,
175
176 pub cache_miss_rates: Vec<f32>,
178
179 pub successful_opts: Vec<String>,
181}
182
183#[derive(Debug, Clone)]
189pub struct NeuralModel {
190 weights: Vec<Vec<f32>>,
192
193 biases: Vec<f32>,
195
196 input_dim: usize,
198
199 hidden_dims: Vec<usize>,
201
202 output_dim: usize,
204
205 stats: ModelStatistics,
207}
208
209#[derive(Debug, Clone, Default)]
211pub struct ModelStatistics {
212 pub samples_seen: usize,
214
215 pub current_loss: f32,
217
218 pub best_accuracy: f32,
220
221 pub accuracy_history: VecDeque<f32>,
223
224 pub feature_importance: HashMap<String, f32>,
226}
227
228impl NeuralModel {
229 pub fn new(input_dim: usize, hidden_dims: Vec<usize>, output_dim: usize) -> Self {
231 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]); biases.push(0.0);
239 prev_dim = hidden_dim;
240 }
241
242 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 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 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 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 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 fn relu(x: &[f32]) -> Vec<f32> {
305 x.iter().map(|&v| v.max(0.0)).collect()
306 }
307
308 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 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 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 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 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#[derive(Debug, Clone, Serialize, Deserialize)]
382pub struct CompilationStrategy {
383 pub optimizations: Vec<OptimizationDecision>,
385
386 pub predicted_time_us: f64,
388
389 pub predicted_memory: usize,
391
392 pub confidence: f32,
394
395 pub reasoning: Vec<String>,
397}
398
399#[derive(Debug, Clone, Serialize, Deserialize)]
401pub struct OptimizationDecision {
402 pub pass_name: String,
404
405 pub apply: bool,
407
408 pub estimated_speedup: f32,
410
411 pub estimated_memory_delta: i64,
413
414 pub confidence: f32,
416}
417
418pub struct NeuralCompiler {
424 strategy_model: Arc<RwLock<NeuralModel>>,
426
427 performance_model: Arc<RwLock<NeuralModel>>,
429
430 feature_extractor: FeatureExtractor,
432
433 history: Arc<RwLock<CompilationHistory>>,
435
436 config: NeuralCompilerConfig,
438}
439
440#[derive(Debug, Clone)]
442pub struct NeuralCompilerConfig {
443 pub online_learning: bool,
445
446 pub learning_rate: f32,
448
449 pub exploration_rate: f32,
451
452 pub min_confidence: f32,
454
455 pub max_history_size: usize,
457
458 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#[derive(Debug, Default)]
477pub struct CompilationHistory {
478 pub entries: VecDeque<HistoryEntry>,
480
481 pub feature_stats: FeatureStatistics,
483}
484
485#[derive(Debug, Clone)]
487pub struct HistoryEntry {
488 pub features: GraphFeatures,
490
491 pub strategy: CompilationStrategy,
493
494 pub actual_time_us: f64,
496
497 pub actual_memory: usize,
499
500 pub error: f32,
502}
503
504#[derive(Debug, Default, Clone)]
506pub struct FeatureStatistics {
507 pub means: HashMap<String, f32>,
509
510 pub stddevs: HashMap<String, f32>,
512
513 pub mins: HashMap<String, f32>,
515 pub maxs: HashMap<String, f32>,
516}
517
518impl NeuralCompiler {
519 pub fn new() -> Self {
521 Self::with_config(NeuralCompilerConfig::default())
522 }
523
524 pub fn with_config(config: NeuralCompilerConfig) -> Self {
526 let strategy_model = Arc::new(RwLock::new(
528 NeuralModel::new(128, vec![256, 128, 64], 32), ));
530
531 let performance_model = Arc::new(RwLock::new(
533 NeuralModel::new(128, vec![64, 32], 2), ));
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 pub fn extract_features(&self, graph: &ComputationGraph) -> JitResult<GraphFeatures> {
547 self.feature_extractor.extract(graph)
548 }
549
550 pub fn predict_strategy(&self, features: &GraphFeatures) -> JitResult<CompilationStrategy> {
552 let feature_vec = self.flatten_features(features)?;
554
555 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 let optimizations = self.decode_strategy(&strategy_probs)?;
565
566 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 let confidence = strategy_probs.iter().sum::<f32>() / strategy_probs.len() as f32;
580
581 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 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 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 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 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 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 let feature_vec = self.flatten_features(features)?;
655
656 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 fn flatten_features(&self, features: &GraphFeatures) -> JitResult<Vec<f32>> {
680 let mut vec = Vec::with_capacity(128);
681
682 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 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 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 while vec.len() < 128 {
704 vec.push(0.0);
705 }
706
707 Ok(vec)
708 }
709
710 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 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 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
805pub struct FeatureExtractor {
811 cache: IndexMap<String, GraphFeatures>,
813}
814
815impl FeatureExtractor {
816 pub fn new() -> Self {
818 Self {
819 cache: IndexMap::new(),
820 }
821 }
822
823 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 let depth = self.compute_depth(graph);
840
841 let avg_degree = if node_count > 0 {
843 (edge_count as f32) / (node_count as f32)
844 } else {
845 0.0
846 };
847
848 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, diameter: depth,
862 clustering_coeff: 0.0, 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 let op_flops = self.estimate_flops(&node.operation, &node.inputs);
876 total_flops += op_flops;
877
878 if self.is_vectorizable(&node.operation) {
880 vectorizable_ops += 1;
881 }
882
883 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) } 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, 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 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 100
971 }
972
973 fn is_vectorizable(&self, operation: &crate::graph::Operation) -> bool {
974 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#[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 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); 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 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}