1use crate::interpreter::execution::ExecutionEnvironment;
8use crate::interpreter::metrics::ExecutionMetrics;
9use crate::{FxGraph, Node, TorshResult};
10use petgraph::graph::NodeIndex;
11use torsh_core::{device::DeviceType, error::TorshError};
12use torsh_tensor::Tensor;
13
14pub struct DebugExecutionEnvironment {
19 env: ExecutionEnvironment,
21 metrics: ExecutionMetrics,
23 debug_mode: bool,
25 execution_log: Vec<String>,
27}
28
29impl DebugExecutionEnvironment {
30 pub fn new(device: DeviceType, debug_mode: bool) -> Self {
39 Self {
40 env: ExecutionEnvironment::new(device),
41 metrics: ExecutionMetrics::new(),
42 debug_mode,
43 execution_log: Vec::new(),
44 }
45 }
46
47 pub fn env(&self) -> &ExecutionEnvironment {
52 &self.env
53 }
54
55 pub fn env_mut(&mut self) -> &mut ExecutionEnvironment {
60 &mut self.env
61 }
62
63 pub fn metrics(&self) -> &ExecutionMetrics {
68 &self.metrics
69 }
70
71 pub fn is_debug_mode(&self) -> bool {
76 self.debug_mode
77 }
78
79 pub fn set_debug_mode(&mut self, enabled: bool) {
84 self.debug_mode = enabled;
85 }
86
87 pub fn log(&mut self, message: String) {
92 if self.debug_mode {
93 self.execution_log.push(message);
94 }
95 }
96
97 pub fn get_log(&self) -> &[String] {
102 &self.execution_log
103 }
104
105 pub fn clear_log(&mut self) {
107 self.execution_log.clear();
108 }
109
110 pub fn log_node_start(&mut self, node_idx: NodeIndex, node: &Node) {
116 let message = match node {
117 Node::Input(name) => format!("Executing input node {:?}: '{}'", node_idx, name),
118 Node::Call(op_name, args) => format!(
119 "Executing call node {:?}: operation '{}' with {} args",
120 node_idx,
121 op_name,
122 args.len()
123 ),
124 Node::Output => format!("Executing output node {:?}", node_idx),
125 Node::Conditional { condition, .. } => format!(
126 "Executing conditional node {:?}: condition '{}'",
127 node_idx, condition
128 ),
129 Node::Loop { condition, .. } => format!(
130 "Executing loop node {:?}: condition '{}'",
131 node_idx, condition
132 ),
133 Node::Merge { inputs, .. } => format!(
134 "Executing merge node {:?}: {} inputs",
135 node_idx,
136 inputs.len()
137 ),
138 Node::GetAttr { target, attr } => format!(
139 "Executing get_attr node {:?}: {}.{}",
140 node_idx, target, attr
141 ),
142 };
143 self.log(message);
144 }
145
146 pub fn log_node_completion(&mut self, node_idx: NodeIndex, duration_ms: f64) {
152 let message = format!("Completed node {:?} in {:.2} ms", node_idx, duration_ms);
153 self.log(message);
154 }
155
156 pub fn log_tensor_info(&mut self, node_idx: NodeIndex, tensor: &Tensor) {
162 if self.debug_mode {
163 let message = format!(
164 "Node {:?} tensor: shape={:?}, dtype={:?}, device={:?}",
165 node_idx,
166 tensor.shape().dims(),
167 tensor.dtype(),
168 tensor.device()
169 );
170 self.log(message);
171 }
172 }
173
174 pub fn log_operation(&mut self, op_name: &str, input_count: usize, duration_ms: f64) {
181 if self.debug_mode {
182 let message = format!(
183 "Operation '{}' with {} inputs completed in {:.2} ms",
184 op_name, input_count, duration_ms
185 );
186 self.log(message);
187 }
188 self.metrics.add_operation_time(op_name, duration_ms);
189 }
190
191 pub fn log_error(&mut self, node_idx: NodeIndex, error: &TorshError) {
197 let message = format!("Error at node {:?}: {}", node_idx, error);
198 self.log(message);
199 }
200
201 pub fn generate_debug_report(&self) -> String {
206 let mut report = String::new();
207
208 report.push_str("=== Debug Execution Report ===\n\n");
209
210 report.push_str(&format!("Device: {:?}\n", self.env.device()));
212 report.push_str(&format!("Debug Mode: {}\n", self.debug_mode));
213 report.push_str(&format!("Stored Values: {}\n", self.env.value_count()));
214 report.push_str(&format!("Log Entries: {}\n\n", self.execution_log.len()));
215
216 if !self.metrics.is_empty() {
218 report.push_str("=== Performance Metrics ===\n");
219 report.push_str(&self.metrics.generate_report());
220 report.push_str("\n\n");
221 }
222
223 if !self.execution_log.is_empty() {
225 report.push_str("=== Execution Log ===\n");
226 for (i, entry) in self.execution_log.iter().enumerate() {
227 report.push_str(&format!("{:4}: {}\n", i + 1, entry));
228 }
229 }
230
231 report
232 }
233
234 pub fn print_debug_info(&self) {
236 if self.debug_mode {
237 println!("{}", self.generate_debug_report());
238 }
239 }
240
241 pub fn save_debug_report(&self) -> String {
246 self.generate_debug_report()
247 }
248
249 pub fn reset(&mut self) {
251 self.env.clear();
252 self.metrics.clear();
253 self.execution_log.clear();
254 }
255
256 pub fn execution_summary(&self) -> String {
261 format!(
262 "Debug Environment: {} values stored, {} operations, {:.2}ms total, {} log entries",
263 self.env.value_count(),
264 self.metrics.operation_count,
265 self.metrics.total_time_ms,
266 self.execution_log.len()
267 )
268 }
269}
270
271pub mod utils {
273 use super::*;
274 use crate::interpreter::operations::is_operation_registered;
275
276 pub fn validate_graph_executability(graph: &FxGraph) -> TorshResult<()> {
287 let mut missing_ops = Vec::new();
288
289 for (_, node) in graph.call_nodes() {
290 if let Node::Call(op_name, _) = node {
291 if !is_operation_registered(op_name) && !is_builtin_operation(op_name) {
292 missing_ops.push(op_name.clone());
293 }
294 }
295 }
296
297 if !missing_ops.is_empty() {
298 return Err(TorshError::InvalidArgument(format!(
299 "Missing operations: {}",
300 missing_ops.join(", ")
301 )));
302 }
303
304 Ok(())
305 }
306
307 pub fn is_builtin_operation(op_name: &str) -> bool {
315 matches!(
316 op_name,
317 "add"
318 | "sub"
319 | "mul"
320 | "div"
321 | "matmul"
322 | "relu"
323 | "sigmoid"
324 | "tanh"
325 | "gelu"
326 | "softmax"
327 | "layer_norm"
328 | "batch_norm"
329 | "conv2d"
330 | "linear"
331 | "linear_relu"
332 | "conv2d_relu"
333 )
334 }
335
336 pub fn estimate_execution_complexity(graph: &FxGraph) -> usize {
347 let mut complexity = 0;
348
349 for (_, node) in graph.call_nodes() {
350 if let Node::Call(op_name, _) = node {
351 complexity += match op_name.as_str() {
352 "add" | "sub" | "mul" | "div" => 1,
353 "matmul" | "linear" => 10,
354 "conv2d" => 20,
355 "relu" | "sigmoid" | "tanh" => 2,
356 "gelu" | "softmax" | "layer_norm" | "batch_norm" => 5,
357 "linear_relu" => 12, "conv2d_relu" => 22, _ => 3, };
361 }
362 }
363
364 complexity
365 }
366
367 pub fn generate_execution_summary(graph: &FxGraph) -> String {
378 let call_nodes = graph.call_nodes();
379 let op_counts = graph.operation_counts();
380 let complexity = estimate_execution_complexity(graph);
381
382 let mut summary = format!(
383 "Graph Execution Summary:\n\
384 Total Operations: {}\n\
385 Estimated Complexity: {}\n\
386 Operation Types: {}\n\
387 Input Nodes: {}\n\
388 Output Nodes: {}\n\n\
389 Operation Distribution:",
390 call_nodes.len(),
391 complexity,
392 op_counts.len(),
393 graph.inputs().len(),
394 graph.outputs().len()
395 );
396
397 let mut sorted_ops: Vec<_> = op_counts.iter().collect();
398 sorted_ops.sort_by(|a, b| b.1.cmp(a.1));
399
400 for (op_name, count) in sorted_ops {
401 let op_complexity = match op_name.as_str() {
402 "add" | "sub" | "mul" | "div" => 1,
403 "matmul" | "linear" => 10,
404 "conv2d" => 20,
405 "relu" | "sigmoid" | "tanh" => 2,
406 "gelu" | "softmax" | "layer_norm" | "batch_norm" => 5,
407 _ => 3,
408 };
409 summary.push_str(&format!(
410 "\n {}: {} instances (complexity: {} each)",
411 op_name, count, op_complexity
412 ));
413 }
414
415 summary.push_str("\n\nRecommendations:");
417 if complexity > 1000 {
418 summary.push_str("\n - High complexity graph: consider optimization");
419 }
420 if op_counts.len() > 50 {
421 summary.push_str("\n - Many operation types: verify all are available");
422 }
423 if call_nodes.len() > 500 {
424 summary.push_str("\n - Large graph: consider batching or partitioning");
425 }
426
427 summary
428 }
429
430 pub fn validate_graph_structure(graph: &FxGraph) -> TorshResult<()> {
441 if graph.graph.node_count() == 0 {
443 return Err(TorshError::InvalidArgument("Graph is empty".to_string()));
444 }
445
446 if graph.inputs().is_empty() {
448 return Err(TorshError::InvalidArgument(
449 "Graph has no input nodes".to_string(),
450 ));
451 }
452
453 if graph.outputs().is_empty() {
454 return Err(TorshError::InvalidArgument(
455 "Graph has no output nodes".to_string(),
456 ));
457 }
458
459 use petgraph::algo::is_cyclic_directed;
461 if is_cyclic_directed(&graph.graph) {
462 return Err(TorshError::InvalidArgument(
463 "Graph contains cycles".to_string(),
464 ));
465 }
466
467 let node_count = graph.graph.node_count();
470 let input_count = graph.inputs().len();
471 let output_count = graph.outputs().len();
472
473 if node_count < input_count + output_count {
474 return Err(TorshError::InvalidArgument(
475 "Invalid node count relationship".to_string(),
476 ));
477 }
478
479 Ok(())
480 }
481
482 pub fn describe_graph(graph: &FxGraph) -> String {
490 let mut description = String::new();
491
492 description.push_str("=== FX Graph Description ===\n\n");
493
494 description.push_str(&format!("Nodes: {}\n", graph.graph.node_count()));
496 description.push_str(&format!("Edges: {}\n", graph.graph.edge_count()));
497 description.push_str(&format!("Inputs: {}\n", graph.inputs().len()));
498 description.push_str(&format!("Outputs: {}\n", graph.outputs().len()));
499
500 description.push_str("\nInput Nodes:\n");
502 for &input_idx in graph.inputs() {
503 if let Some(Node::Input(name)) = graph.get_node(input_idx) {
504 description.push_str(&format!(" {:?}: '{}'\n", input_idx, name));
505 }
506 }
507
508 description.push_str("\nOutput Nodes:\n");
510 for &output_idx in graph.outputs() {
511 if let Some(Node::Output) = graph.get_node(output_idx) {
512 description.push_str(&format!(" {:?}\n", output_idx));
513 }
514 }
515
516 let op_counts = graph.operation_counts();
518 if !op_counts.is_empty() {
519 description.push_str("\nOperations:\n");
520 let mut sorted_ops: Vec<_> = op_counts.iter().collect();
521 sorted_ops.sort_by(|a, b| b.1.cmp(a.1));
522 for (op_name, count) in sorted_ops {
523 description.push_str(&format!(" {}: {} instances\n", op_name, count));
524 }
525 }
526
527 description
528 }
529}